#!/usr/bin/env python3 """Run the WeChat OCR/reply chain against the current VNC window geometry.""" from __future__ import annotations import importlib.machinery import importlib.util import json import hashlib import logging import os import queue import socket import sys import threading import time from dataclasses import dataclass, field from datetime import datetime from pathlib import Path from types import ModuleType from typing import Any, Callable import jwebot_edge HOME = Path.home() UPSTREAM = Path( os.environ.get( "JWEBOT_OCR_UPSTREAM", str(HOME / ".local" / "lib" / "wechat-health-watch" / "wechat_local_ocr.py"), ) ).expanduser() STATE_ROOT = HOME / ".local" / "state" / "wechat-adaptive-ocr" GUI_BRIDGE = HOME / ".local" / "lib" / "wechat-health-watch" / "wechat_adaptive_gui_bridge.py" XDG_RUNTIME_DIR = Path(os.environ.get("XDG_RUNTIME_DIR", f"/run/user/{os.getuid()}")) GUI_SENDER_SOCKET = Path( os.environ.get( "JWEBOT_GUI_SENDER_SOCKET", str(XDG_RUNTIME_DIR / "jwebot-gui-sender.sock"), ) ).expanduser() GUI_SENDER_TIMEOUT_SECONDS = float( os.environ.get("JWEBOT_GUI_SENDER_TIMEOUT_SECONDS", "55") ) GUI_SENDER_MAX_PAYLOAD_BYTES = 65536 BASE_WIDTH = 1440 BASE_HEIGHT = 873 BASE_MESSAGE_BOX = (300, 80, 1439, 732) BASE_TARGET_CHAT_BOX = (0, 33, 300, 147) BASE_TITLE_BOX = (300, 33, 900, 80) BASE_FOLLOW_X = 1432 BASE_FOLLOW_Y = 500 MIN_READY_WINDOW_WIDTH = 700 MIN_READY_WINDOW_HEIGHT = 500 READY_POLL_SECONDS = 10.0 READY_LOG_INTERVAL_SECONDS = 60.0 CAPTURE_HEARTBEAT_SECONDS = 45 FAST_ACK_CONFIRM_TIMEOUT_SECONDS = 1.0 GUI_SEND_PRIORITY_STOP = -1 GUI_SEND_PRIORITY_IMMEDIATE = 0 GUI_SEND_PRIORITY_FINAL = 1 TELEMETRY = jwebot_edge.RuntimeTelemetry( component="adaptive-recorder", enabled=os.environ.get("JWEBOT_TELEMETRY_ENABLED", "1").strip() != "0", ) def configure_logging() -> None: logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s", force=False, ) logging.getLogger().setLevel(logging.INFO) logging.getLogger("jwebot.telemetry").setLevel(logging.INFO) def elapsed_since_iso_ms( value: object, *, now: datetime | None = None, ) -> float | None: try: observed = datetime.fromisoformat(str(value)) except (TypeError, ValueError): return None current = now or datetime.now().astimezone() if observed.tzinfo is None: observed = observed.replace(tzinfo=current.tzinfo) return max(0.0, (current - observed).total_seconds() * 1000) def scale_box( box: tuple[int, int, int, int], *, width: int, height: int, ) -> tuple[int, int, int, int]: left, top, right, bottom = box scaled = ( round(left * width / BASE_WIDTH), round(top * height / BASE_HEIGHT), round(right * width / BASE_WIDTH), round(bottom * height / BASE_HEIGHT), ) return ( max(0, min(width - 1, scaled[0])), max(0, min(height - 1, scaled[1])), max(1, min(width, scaled[2])), max(1, min(height, scaled[3])), ) def load_upstream() -> ModuleType: loader = importlib.machinery.SourceFileLoader( "wechat_local_ocr_adaptive", str(UPSTREAM), ) spec = importlib.util.spec_from_loader(loader.name, loader) if spec is None: raise RuntimeError("cannot load the installed WeChat OCR module") module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module loader.exec_module(module) return module def load_gui_bridge() -> ModuleType: loader = importlib.machinery.SourceFileLoader( "wechat_adaptive_gui_bridge_runtime", str(GUI_BRIDGE), ) spec = importlib.util.spec_from_loader(loader.name, loader) if spec is None: raise RuntimeError("cannot load the installed WeChat GUI bridge") module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module loader.exec_module(module) return module def configure_state(module: ModuleType) -> None: module.STATE_ROOT = STATE_ROOT module.HISTORY_DIR = STATE_ROOT / "history" module.QUEUE_DIR = STATE_ROOT / "queue" module.FAILED_DIR = STATE_ROOT / "failed" module.RECORDER_STATE = STATE_ROOT / "recorder-state.json" module.RESPONDER_STATE = STATE_ROOT / "responder-state.json" # Keep the shared GUI lock so this process never races another GUI sender. module.GUI_LOCK_FILE = Path("/var/lib/wechat-local-ocr/gui-interaction.lock") def configure_layout( module: ModuleType, *, identifier: str | None = None, ) -> tuple[int, int]: identifier = identifier or module.window_id() width, height = module.window_geometry(identifier) if width < MIN_READY_WINDOW_WIDTH or height < MIN_READY_WINDOW_HEIGHT: raise RuntimeError("WeChat client is not in its ready main-window state") module.EXPECTED_WIDTH = width module.EXPECTED_HEIGHT = height module.MESSAGE_BOX = scale_box( BASE_MESSAGE_BOX, width=width, height=height, ) module.TARGET_CHAT_BOX = scale_box( BASE_TARGET_CHAT_BOX, width=width, height=height, ) module.TITLE_BOX = scale_box( BASE_TITLE_BOX, width=width, height=height, ) module.FOLLOW_LATEST_MOUSE_X = round(BASE_FOLLOW_X * width / BASE_WIDTH) module.FOLLOW_LATEST_MOUSE_Y = round(BASE_FOLLOW_Y * height / BASE_HEIGHT) return width, height def wait_for_ready( module: ModuleType, *, poll_seconds: float = READY_POLL_SECONDS, sleep: Callable[[float], None] = time.sleep, monotonic: Callable[[], float] = time.monotonic, ) -> tuple[int, int]: """Keep the service alive while WeChat is absent or awaiting login.""" if poll_seconds <= 0: raise ValueError("ready poll interval must be positive") last_log_at: float | None = None while True: try: layout = configure_layout(module) except Exception as error: now = monotonic() if ( last_log_at is None or now - last_log_at >= READY_LOG_INTERVAL_SECONDS ): logging.warning( "waiting for ready WeChat main window error_type=%s", type(error).__name__, ) last_log_at = now sleep(poll_seconds) continue logging.info( "ready WeChat main window width=%d height=%d", layout[0], layout[1], ) return layout def user_openclaw_command( module: ModuleType, *, agent: str, session: str, timeout_seconds: int, ) -> list[str]: cloud_host = os.environ.get("WECHAT_HEALTH_CLOUD_HOST", "").strip() if not cloud_host: raise RuntimeError("missing cloud host configuration") return [ "/usr/bin/ssh", "-F", "/dev/null", "-i", str(module.SSH_KEY), "-o", "BatchMode=yes", "-o", "ConnectTimeout=8", "-o", "ConnectionAttempts=3", "-o", "StrictHostKeyChecking=yes", "-o", "ServerAliveInterval=5", "-o", "ServerAliveCountMax=2", "-o", "IPQoS=none", f"root@{cloud_host}", "docker", "exec", "-i", "openclaw-gateway", "node", "/app/dist/index.js", "agent", "--agent", agent, "--session-key", session, "--message-file", "/dev/stdin", "--json", "--timeout", str(timeout_seconds), ] def uses_cloud_prefix(event: dict[str, Any]) -> bool: return jwebot_edge.event_question(event).lstrip().startswith(("#", "#")) @dataclass class GuiSendJob: reply: str expected_group: str event_id: str = "" source: str = "ocr" before_send: Callable[[], None] | None = None after_send: Callable[[], None] | None = None enqueued_at: float = field(default_factory=time.monotonic) done: threading.Event = field(default_factory=threading.Event) payload: dict[str, Any] | None = None error: BaseException | None = None def gui_send_priority(source: str) -> int: if source.startswith("ack-") or source == "ocr": return GUI_SEND_PRIORITY_IMMEDIATE return GUI_SEND_PRIORITY_FINAL class GuiSender: def __init__( self, upstream: ModuleType, bridge: ModuleType, *, socket_path: Path = GUI_SENDER_SOCKET, ) -> None: self.upstream = upstream self.bridge = bridge self.socket_path = socket_path self.jobs: queue.PriorityQueue[ tuple[int, int, GuiSendJob | None] ] = queue.PriorityQueue() self.sequence_lock = threading.Lock() self.next_sequence = 0 self.stop_event = threading.Event() self.worker = threading.Thread( target=self._worker_loop, name="wechat-gui-sender-worker", daemon=True, ) self.server = threading.Thread( target=self._server_loop, name="wechat-gui-sender-socket", daemon=True, ) def start(self) -> None: self.worker.start() self.server.start() def stop(self) -> None: self.stop_event.set() with self.sequence_lock: sequence = self.next_sequence self.next_sequence += 1 self.jobs.put((GUI_SEND_PRIORITY_STOP, sequence, None)) try: with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as connection: connection.settimeout(0.2) connection.connect(str(self.socket_path)) except OSError: pass self.worker.join(timeout=1) self.server.join(timeout=1) self.socket_path.unlink(missing_ok=True) def send_reply( self, reply: str, expected_group: str, *, before_send: Callable[[], None] | None = None, after_send: Callable[[], None] | None = None, event_id: object = "", source: str = "ocr", timeout_seconds: float = GUI_SENDER_TIMEOUT_SECONDS, ) -> dict[str, Any]: if not reply or len(reply) > 900: raise RuntimeError("invalid GUI reply") if not expected_group: raise RuntimeError("missing expected group fingerprint") job = GuiSendJob( reply=reply, expected_group=expected_group, event_id=str(event_id or ""), source=source, before_send=before_send, after_send=after_send, ) priority = gui_send_priority(job.source) with self.sequence_lock: sequence = self.next_sequence self.next_sequence += 1 depth = self.jobs.qsize() self.jobs.put((priority, sequence, job)) self.upstream.telemetry_event( "gui.sender.enqueue", event_id=job.event_id, group_id=job.expected_group, queue="gui", depth=depth, source=job.source, ) if not job.done.wait(timeout_seconds): raise RuntimeError("GUI sender timed out") if job.error is not None: raise RuntimeError("GUI sender failed") from job.error return job.payload or {"ok": True, "state": "sent", "timings": []} def _worker_loop(self) -> None: while True: priority, sequence, job = self.jobs.get() if job is None: self.jobs.task_done() break started = time.monotonic() try: job.payload = self._deliver(job) except BaseException as error: job.error = error self.upstream.telemetry_event( "gui.sender.total", status="error", elapsed_ms=(time.monotonic() - started) * 1000, event_id=job.event_id, group_id=job.expected_group, queue="gui", source=job.source, error_type=type(error).__name__, ) else: self.upstream.telemetry_event( "gui.sender.total", elapsed_ms=(time.monotonic() - started) * 1000, event_id=job.event_id, group_id=job.expected_group, queue="gui", source=job.source, delivered=True, ) finally: job.done.set() self.jobs.task_done() def _deliver(self, job: GuiSendJob) -> dict[str, Any]: wait_ms = (time.monotonic() - job.enqueued_at) * 1000 self.upstream.telemetry_event( "gui.sender.step", elapsed_ms=wait_ms, event_id=job.event_id, group_id=job.expected_group, queue="gui", phase="queue_wait", source=job.source, ) total_started = time.monotonic() with self.upstream.gui_interaction_lock(blocking=True) as acquired: self.upstream.telemetry_event( "gui.sender.step", elapsed_ms=(time.monotonic() - total_started) * 1000, event_id=job.event_id, group_id=job.expected_group, queue="gui", phase="lock_acquire", source=job.source, ) if not acquired: raise RuntimeError("cannot acquire GUI interaction lock") self._assert_group(job, phase="pre_draft") def before_bridge_send() -> None: self._assert_group(job, phase="post_draft") if job.before_send is not None: job.before_send() try: payload = self.bridge.deliver_text( job.reply, before_send=before_bridge_send, ) except Exception as error: timings = getattr(error, "timings", None) if isinstance(timings, list): self.upstream.emit_bridge_timings( {"timings": timings}, event_id=job.event_id, group_id=job.expected_group, source=job.source, ) raise self.upstream.emit_bridge_timings( payload, event_id=job.event_id, group_id=job.expected_group, source=job.source, ) if job.after_send is not None: step_started = time.monotonic() job.after_send() self.upstream.telemetry_event( "gui.sender.step", elapsed_ms=(time.monotonic() - step_started) * 1000, event_id=job.event_id, group_id=job.expected_group, queue="gui", phase="after_send_callback", source=job.source, ) return payload def _assert_group(self, job: GuiSendJob, *, phase: str) -> None: step_started = time.monotonic() capture_title = getattr( self.upstream, "capture_title_fingerprint", None, ) if callable(capture_title): current_group = capture_title() capture_method = "title-region" else: current = self.upstream.capture_image() current_group = self.upstream.title_fingerprint(current) capture_method = "full-window" self.upstream.telemetry_event( "gui.sender.step", elapsed_ms=(time.monotonic() - step_started) * 1000, event_id=job.event_id, group_id=job.expected_group, queue="gui", phase=f"{phase}_screenshot", source=job.source, method=capture_method, ) step_started = time.monotonic() matched = current_group == job.expected_group self.upstream.telemetry_event( "gui.sender.step", elapsed_ms=(time.monotonic() - step_started) * 1000, event_id=job.event_id, group_id=job.expected_group, queue="gui", phase=f"{phase}_group_check", outcome="matched" if matched else "changed", source=job.source, ) if not matched: raise RuntimeError("selected chat changed; send aborted") def _server_loop(self) -> None: self.socket_path.parent.mkdir(parents=True, exist_ok=True, mode=0o700) self.socket_path.unlink(missing_ok=True) with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as listener: listener.bind(str(self.socket_path)) os.chmod(self.socket_path, 0o600) listener.listen(8) listener.settimeout(1.0) while not self.stop_event.is_set(): try: connection, _ = listener.accept() except TimeoutError: continue threading.Thread( target=self._handle_connection, args=(connection,), name="wechat-gui-sender-client", daemon=True, ).start() self.socket_path.unlink(missing_ok=True) def _handle_connection(self, connection: socket.socket) -> None: with connection: try: request = self._read_request(connection) reply = str(request.get("reply", "")) expected_group = str(request.get("group_fingerprint", "")) event_id = str(request.get("event_id", "")) source = str(request.get("source", "delivery-helper"))[:40] payload = self.send_reply( reply, expected_group, event_id=event_id, source=source, ) response = {"ok": True, **payload} except Exception as error: response = {"ok": False, "error": type(error).__name__} try: connection.sendall( (json.dumps(response, ensure_ascii=False) + "\n").encode() ) except OSError: pass def _read_request(self, connection: socket.socket) -> dict[str, Any]: connection.settimeout(2) chunks: list[bytes] = [] size = 0 while True: chunk = connection.recv(4096) if not chunk: break chunks.append(chunk) size += len(chunk) if b"\n" in chunk: break if size > GUI_SENDER_MAX_PAYLOAD_BYTES: raise RuntimeError("GUI sender payload is too large") raw = b"".join(chunks).split(b"\n", 1)[0] value = json.loads(raw.decode("utf-8")) if not isinstance(value, dict): raise RuntimeError("GUI sender request must be an object") if value.get("op") != "send": raise RuntimeError("unsupported GUI sender operation") return value def submit_gui_sender_request( payload: dict[str, Any], *, socket_path: Path = GUI_SENDER_SOCKET, timeout_seconds: float = GUI_SENDER_TIMEOUT_SECONDS, ) -> dict[str, Any]: started = time.monotonic() try: with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as connection: connection.settimeout(timeout_seconds) connection.connect(str(socket_path)) connection.sendall((json.dumps(payload, ensure_ascii=False) + "\n").encode()) chunks: list[bytes] = [] size = 0 while True: chunk = connection.recv(4096) if not chunk: break chunks.append(chunk) size += len(chunk) if b"\n" in chunk: break if size > GUI_SENDER_MAX_PAYLOAD_BYTES: raise RuntimeError("GUI sender response is too large") except OSError as error: raise RuntimeError("GUI sender socket unavailable") from error try: response = json.loads(b"".join(chunks).split(b"\n", 1)[0]) except (IndexError, json.JSONDecodeError, UnicodeDecodeError) as error: raise RuntimeError("GUI sender response is invalid") from error if not isinstance(response, dict): raise RuntimeError("GUI sender response is invalid") TELEMETRY.elapsed( "gui.sender.client", started, status="ok" if response.get("ok") is True else "error", event_id=payload.get("event_id", ""), group_id=payload.get("group_fingerprint", ""), transport="unix", source=str(payload.get("source", "")), outcome=str(response.get("state", response.get("error", ""))), ) if response.get("ok") is not True: raise RuntimeError("GUI sender returned an error") return response def edge_router_reply( event: dict[str, Any], *, legacy_router: Any, fast_ack_sender: Callable[[dict[str, Any], dict[str, Any]], bool] | None = None, reply_sender: Callable[[dict[str, Any], dict[str, Any]], bool] | None = None, ) -> str: """Use the local edge if healthy, otherwise preserve the existing cloud route.""" started = time.monotonic() event_id = str(event.get("event_id", "")) group_id = str(event.get("group_fingerprint", "")) if os.environ.get("JWEBOT_EDGE_ENABLED", "1").strip() == "0": logging.info("edge router disabled event_hash=%s", hashlib.sha256( str(event.get("event_id", "")).encode("utf-8", "replace") ).hexdigest()[:12]) TELEMETRY.elapsed( "recorder.edge.request", started, status="disabled", event_id=event_id, group_id=group_id, fallback=uses_cloud_prefix(event), ) if uses_cloud_prefix(event): return legacy_router(event) return "JWeBot Error: Local edge unavailable" try: response = jwebot_edge.request_edge(event) except jwebot_edge.EdgeError as error: logging.warning( "edge router unavailable event_hash=%s error_type=%s", hashlib.sha256( str(event.get("event_id", "")).encode("utf-8", "replace") ).hexdigest()[:12], type(error).__name__, ) TELEMETRY.elapsed( "recorder.edge.request", started, status="error", event_id=event_id, group_id=group_id, error_type=type(error).__name__, fallback=uses_cloud_prefix(event), ) if uses_cloud_prefix(event): return legacy_router(event) return "JWeBot Error: Local edge unavailable" if response.get("status") == "reply": texts = response.get("texts") if ( isinstance(texts, list) and len(texts) > 1 and all(isinstance(item, str) and 0 < len(item) <= 900 for item in texts) ): if reply_sender is None: logging.warning( "multi reply sender is unavailable event_hash=%s", hashlib.sha256( str(event.get("event_id", "")).encode("utf-8", "replace") ).hexdigest()[:12], ) return "JWeBot Error: Multi reply unavailable" if not reply_sender(event, response): return "JWeBot Error: Multi reply delivery failed" TELEMETRY.elapsed( "recorder.edge.request", started, event_id=event_id, group_id=group_id, route=str(response.get("route", "")), execution_path=str(response.get("execution_path", "")), chars=sum(len(item) for item in texts), messages=len(texts), outcome="multi_reply", ) return "NO_REPLY" reply = str(response.get("text", "")) if reply and len(reply) <= 900: if response.get("fast_ack") is True: if fast_ack_sender is None: logging.warning( "fast ack sender is unavailable event_hash=%s", hashlib.sha256( str(event.get("event_id", "")).encode("utf-8", "replace") ).hexdigest()[:12], ) return "JWeBot Error: Fast acknowledgement unavailable" fast_ack_sender(event, response) TELEMETRY.elapsed( "recorder.edge.request", started, event_id=event_id, group_id=group_id, route=str(response.get("route", "")), execution_path=str( response.get("execution_path", "") ), ack_kind=str(response.get("route", "")), outcome="fast_ack", ) return "NO_REPLY" logging.info( "edge router reply event_hash=%s route=%s chars=%d", hashlib.sha256( str(event.get("event_id", "")).encode("utf-8", "replace") ).hexdigest()[:12], str(response.get("route", "")), len(reply), ) TELEMETRY.elapsed( "recorder.edge.request", started, event_id=event_id, group_id=group_id, route=str(response.get("route", "")), execution_path=str(response.get("execution_path", "")), chars=len(reply), outcome="reply", ) return reply if response.get("status") == "ignored": TELEMETRY.elapsed( "recorder.edge.request", started, event_id=event_id, group_id=group_id, route=str(response.get("route", "")), execution_path=str(response.get("execution_path", "")), outcome="ignored", duplicate=bool(response.get("duplicate")), ) return "NO_REPLY" logging.warning( "edge router fallback event_hash=%s status=%s", hashlib.sha256( str(event.get("event_id", "")).encode("utf-8", "replace") ).hexdigest()[:12], str(response.get("status", "")), ) TELEMETRY.elapsed( "recorder.edge.request", started, status="fallback", event_id=event_id, group_id=group_id, outcome=str(response.get("status", "")), fallback=True, ) if uses_cloud_prefix(event): return legacy_router(event) return "JWeBot Error: Local edge unavailable" def capture_when_ready( module: Any, capture: Callable[..., Any], identifier: str | None = None, ) -> Any: if identifier is None: wait_for_ready(module) return capture() configure_layout(module, identifier=identifier) return capture(identifier) def run() -> None: configure_logging() module = load_upstream() configure_state(module) bridge = load_gui_bridge() original_capture = module.capture_image def adaptive_capture(identifier: str | None = None) -> Any: return capture_when_ready(module, original_capture, identifier) module.capture_image = adaptive_capture module.GUI_BRIDGE = str(GUI_BRIDGE) module.openclaw_command = lambda **kwargs: user_openclaw_command(module, **kwargs) legacy_router = module.ask_openclaw gui_sender = GuiSender(module, bridge) gui_sender.start() module.send_reply = gui_sender.send_reply def send_fast_ack( event: dict[str, Any], response: dict[str, Any], ) -> bool: started = time.monotonic() event_id = str(response.get("event_id", "")) expected_group = str(event.get("group_fingerprint", "")) acknowledgement = str(response.get("text", "")) ack_kind = str(response.get("route", "")) execution_path = str(response.get("execution_path", "")) if not event_id or not acknowledgement or len(acknowledgement) > 900: logging.error("edge fast ack response was invalid") return False try: module.send_reply( acknowledgement, expected_group, event_id=event_id, source=f"ack-{ack_kind or 'unknown'}", ) except Exception as error: logging.warning( "fast ack GUI delivery failed event_hash=%s error_type=%s", hashlib.sha256(event_id.encode("utf-8", "replace")).hexdigest()[:12], type(error).__name__, ) TELEMETRY.elapsed( "recorder.ack.gui", started, status="error", event_id=event_id, group_id=expected_group, ack_kind=ack_kind, execution_path=execution_path, error_type=type(error).__name__, ) TELEMETRY.elapsed( "recorder.fast_ack.gui", started, status="error", event_id=event_id, group_id=expected_group, ack_kind=ack_kind, execution_path=execution_path, error_type=type(error).__name__, ) end_to_end_ms = elapsed_since_iso_ms(event.get("observed_at")) if end_to_end_ms is not None: TELEMETRY.emit( "recorder.ack.end_to_end", status="error", elapsed_ms=end_to_end_ms, event_id=event_id, group_id=expected_group, ack_kind=ack_kind, execution_path=execution_path, error_type=type(error).__name__, ) TELEMETRY.emit( "recorder.fast_ack.end_to_end", status="error", elapsed_ms=end_to_end_ms, event_id=event_id, group_id=expected_group, ack_kind=ack_kind, execution_path=execution_path, error_type=type(error).__name__, ) try: jwebot_edge.request_edge( { "op": "fast_ack_failed", "event_id": event_id, "error": type(error).__name__, }, timeout_seconds=FAST_ACK_CONFIRM_TIMEOUT_SECONDS, ) except jwebot_edge.EdgeError: logging.exception( "fast ack failure could not be recorded event_hash=%s", hashlib.sha256( event_id.encode("utf-8", "replace") ).hexdigest()[:12], ) return False TELEMETRY.elapsed( "recorder.ack.gui", started, event_id=event_id, group_id=expected_group, ack_kind=ack_kind, execution_path=execution_path, delivered=True, ) TELEMETRY.elapsed( "recorder.fast_ack.gui", started, event_id=event_id, group_id=expected_group, ack_kind=ack_kind, execution_path=execution_path, delivered=True, ) end_to_end_ms = elapsed_since_iso_ms(event.get("observed_at")) if end_to_end_ms is not None: TELEMETRY.emit( "recorder.ack.end_to_end", elapsed_ms=end_to_end_ms, event_id=event_id, group_id=expected_group, ack_kind=ack_kind, execution_path=execution_path, delivered=True, ) TELEMETRY.emit( "recorder.fast_ack.end_to_end", elapsed_ms=end_to_end_ms, event_id=event_id, group_id=expected_group, ack_kind=ack_kind, execution_path=execution_path, delivered=True, ) confirm_started = time.monotonic() try: confirmation = jwebot_edge.request_edge( {"op": "fast_ack_sent", "event_id": event_id}, timeout_seconds=FAST_ACK_CONFIRM_TIMEOUT_SECONDS, ) except jwebot_edge.EdgeError: logging.exception( "fast ack confirmation unavailable event_hash=%s", hashlib.sha256(event_id.encode("utf-8", "replace")).hexdigest()[:12], ) TELEMETRY.elapsed( "recorder.ack.confirm", confirm_started, status="error", event_id=event_id, group_id=expected_group, ack_kind=ack_kind, execution_path=execution_path, error_type="EdgeError", ) TELEMETRY.elapsed( "recorder.fast_ack.confirm", confirm_started, status="error", event_id=event_id, group_id=expected_group, ack_kind=ack_kind, execution_path=execution_path, error_type="EdgeError", ) return False if confirmation.get("status") != "ok": logging.error( "fast ack confirmation rejected event_hash=%s status=%s", hashlib.sha256(event_id.encode("utf-8", "replace")).hexdigest()[:12], str(confirmation.get("status", "")), ) TELEMETRY.elapsed( "recorder.ack.confirm", confirm_started, status="error", event_id=event_id, group_id=expected_group, ack_kind=ack_kind, execution_path=execution_path, outcome=str(confirmation.get("status", "")), ) TELEMETRY.elapsed( "recorder.fast_ack.confirm", confirm_started, status="error", event_id=event_id, group_id=expected_group, ack_kind=ack_kind, execution_path=execution_path, outcome=str(confirmation.get("status", "")), ) return False TELEMETRY.elapsed( "recorder.ack.confirm", confirm_started, event_id=event_id, group_id=expected_group, ack_kind=ack_kind, execution_path=execution_path, delivered=True, ) TELEMETRY.elapsed( "recorder.fast_ack.confirm", confirm_started, event_id=event_id, group_id=expected_group, ack_kind=ack_kind, execution_path=execution_path, delivered=True, ) return True def send_multi_reply( event: dict[str, Any], response: dict[str, Any], ) -> bool: started = time.monotonic() event_id = str(response.get("event_id") or event.get("event_id", "")) expected_group = str(event.get("group_fingerprint", "")) texts = response.get("texts") if ( not event_id or not expected_group or not isinstance(texts, list) or not texts or any(not isinstance(item, str) or not item or len(item) > 900 for item in texts) ): logging.error("edge multi reply response was invalid") return False try: for index, text in enumerate(texts, start=1): module.send_reply( text, expected_group, event_id=event_id, source=f"local-multi-{index}", ) except Exception as error: logging.warning( "multi reply GUI delivery failed event_hash=%s error_type=%s", hashlib.sha256(event_id.encode("utf-8", "replace")).hexdigest()[:12], type(error).__name__, ) TELEMETRY.elapsed( "recorder.multi_reply.gui", started, status="error", event_id=event_id, group_id=expected_group, messages=len(texts), error_type=type(error).__name__, ) return False TELEMETRY.elapsed( "recorder.multi_reply.gui", started, event_id=event_id, group_id=expected_group, messages=len(texts), delivered=True, ) return True module.route_event = lambda event: edge_router_reply( event, legacy_router=legacy_router, fast_ack_sender=send_fast_ack, reply_sender=send_multi_reply, ) wait_for_ready(module) recorder = module.Recorder(queue_enabled=True) responder = module.Responder() original_capture_cycle = recorder.capture_cycle def capture_cycle_with_heartbeat(*, stable_frames: int) -> None: original_capture_cycle(stable_frames=stable_frames) recorder.state["last_capture_at"] = module.now_iso() recorder.save_state() recorder.capture_cycle = capture_cycle_with_heartbeat # Establish a no-reply baseline before XDamage starts tracking live deltas. recorder.capture_cycle(stable_frames=1) def heartbeat() -> None: while True: time.sleep(CAPTURE_HEARTBEAT_SECONDS) recorder.state["last_capture_at"] = module.now_iso() recorder.save_state() threading.Thread( target=heartbeat, name="wechat-adaptive-heartbeat", daemon=True, ).start() thread = threading.Thread( target=recorder.run_damage_monitor, name="wechat-adaptive-recorder", daemon=True, ) thread.start() responder.run_forever() if __name__ == "__main__": run()