"""L6 Capability API:对外能力接口(SendResult + WeChatCapabilities)。 P1 阶段:WeChatCapabilities.send_text 委托给旧 xdotool_driver.send_text, 把返回的 local_send_id 包成 SendResult(success=True, verified=True)。 P3 阶段:替换为 FlowOrchestrator.send_text,统一走状态机 + DB 校验 + 幂等。 """ from __future__ import annotations import logging from dataclasses import dataclass, field from typing import Optional logger = logging.getLogger("woc-bridge") @dataclass class SendResult: """发送文本结果。 所有字段带默认值,避免构造时必填字段顺序问题。 diagnostics 用 field(default=None) + Optional 避免可变默认值陷阱。 Attributes: success: 是否成功(含校验通过) local_id: 本地生成的消息 ID(如 local__) verified: 是否经 DB 校验确认(P1 阶段默认 True,P3 接入真实校验) skipped: 是否因幂等命中而跳过实际发送 error: 错误描述(成功时为空) error_code: 错误码(如 WINDOW_NOT_FOUND / SEND_TIMEOUT) retryable: 是否可安全重试 diagnostics: 诊断信息字典(trace_id / image_confidence / duration_ms 等) """ success: bool = False local_id: str = "" verified: bool = False skipped: bool = False error: str = "" error_code: str = "" retryable: bool = False diagnostics: Optional[dict] = field(default=None) class WeChatCapabilities: """L6 对外能力 API:业务层(路由 / outbox)通过此类调用 UI 自动化。 P1 阶段:xdotool_driver 非空、orchestrator 为 None,send_text 委托给 旧 xdotool_driver.send_text,包成 SendResult。 P3 阶段:orchestrator 非空、xdotool_driver 为 None,send_text 委托给 FlowOrchestrator.send_text(TODO: P3 时统一走 orchestrator)。 """ def __init__(self, xdotool_driver=None, orchestrator=None) -> None: """初始化能力 API。 Args: xdotool_driver: 旧 XdotoolDriver 实例(P1 阶段非空) orchestrator: P3 阶段的 FlowOrchestrator 实例(P1 阶段为 None) """ self.xdotool_driver = xdotool_driver self.orchestrator = orchestrator async def send_text( self, to_wxid: str, content: str, display_name: Optional[str] = None, client_request_id: str = "", ) -> SendResult: """发送文本消息。 P1 阶段:若 orchestrator 非空则委托 orchestrator.send_text; 否则调 xdotool_driver.send_text,把返回的 local_send_id 包成 SendResult(success=True, local_id=..., verified=True)。 TODO: P3 时统一走 orchestrator,移除 xdotool_driver 分支。 Args: to_wxid: 目标 wxid content: 文本内容 display_name: 用于搜索定位会话的显示名(备注/昵称/微信号) client_request_id: 幂等键维度(P1 阶段忽略,P3 接入 IdemCache) Returns: SendResult:发送结果 """ # TODO: P3 时统一走 orchestrator if self.orchestrator is not None: return await self.orchestrator.send_text( to_wxid=to_wxid, content=content, display_name=display_name, client_request_id=client_request_id, ) # P1 阶段:委托给旧 xdotool_driver if self.xdotool_driver is None: return SendResult( success=False, error="no backend available (xdotool_driver and orchestrator both None)", error_code="BRIDGE_NOT_INITIALIZED", retryable=False, ) try: local_send_id = await self.xdotool_driver.send_text( to_wxid=to_wxid, content=content, display_name=display_name, ) return SendResult( success=True, local_id=str(local_send_id), verified=True, ) except Exception as exc: logger.error( "[capability] send_text failed to=%s: %s", to_wxid, exc ) return SendResult( success=False, error=str(exc), error_code="SEND_FAILED", retryable=True, )