AIRI Desktop Lane 状态解析:macOS 桌面操作、Chrome 语义 DOM 与 Overlay 可视化链路实战

发布时间:2026/9/12 15:44:23
AIRI Desktop Lane 状态解析:macOS 桌面操作、Chrome 语义 DOM 与 Overlay 可视化链路实战 AIRI Desktop Lane 状态解析macOS 桌面操作、Chrome 语义 DOM 与 Overlay 可视化链路实战【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi导读本文基于 airi 仓库中的状态备忘 docs/desktop-lane-status.md系统梳理当前桌面Desktop Lane技术线的完整现状macOS 本机窗口自动化、Chrome 语义 DOM 路由、透明 Overlay 可视化层以及围绕它们的冒烟测试与 RPC 就绪契约。文章以原文档为骨架逐一对照仓库源码给出可验证的实现证据帮助你理解「保存-执行-恢复光标」「fail-closed 路由」「心跳拆除」「就绪握手」等关键机制的真实代码形态并明确当前分支上哪些能力已经落地、哪些仍是待验证的运行期风险、哪些属于不该阻塞主线的次要事项。说明原文档是针对某个 recut 分支的状态备忘文中涉及的文件路径在仓库中已映射为根目录相对路径命令以仓库当前package.json中实际存在的 script 为准。一、桌面技术线的既定方向What is already true原文档开篇先划定了本技术线的稳定边界这四条约束决定了下游所有模块的设计取舍且都能在源码中找到对应实现方向约束含义源码佐证macOS only本机桌面执行器仅面向 darwin 平台macos-local.ts 中ensureMacOS()在非darwin平台直接抛错Chrome-first浏览器 DOM 采集优先以 Chrome/Chromium 为目标cdp-bridge.ts 的头部注释明确了 CDP 接入方式visual semantic tree OS input三条信息/控制通道并行视觉截图、语义树AX/DOM、OS 级输入注入分别对应takeScreenshot、getAccessibilityTree、SwiftQuartzCGEvent注入见下文overlay 是可视化层不是第二套系统光标透明覆盖窗口只负责绘制 ghost pointer、候选框、置信度徽标不拦截任何真实鼠标事件window-contract.ts 中的applyDesktopOverlayInputIsolation()这四条约束在后续每一节都会反复出现尤其是「overlay 不拦截输入」这一条是判断许多问题是否为当前阻塞项的前提。二、已落地的四大基线Baselines in code原文档列出四条「代码中已经存在」的基线下面逐一对应仓库源码展开。2.1 保存并恢复真实光标位置macos-local原文档指出macos-local.ts保存真实光标位置并在操作后用CGWarpMouseCursorPosition(...)恢复。在 macos-local.ts 中buildMacOSMoveAndClickScript()生成的 Swift 脚本使用CGEvent(mouseEventSource: nil, mouseType: .mouseMoved, mouseCursorPosition: location, mouseButton: .left)沿pointerTrace逐点移动光标并支持delayMs步进最终在trace.last位置按下/抬起对应鼠标键鼠标键位映射0左键、1右键、2中键buttonNames常量macos-local.ts单击与多击通过clickCount控制循环次数并用mouseEventClickState写入 click state事件投递目标统一为.cghidEventTap系统级 HID 事件 tap这是 Quartz 注入的核心路径。同时执行器对外暴露的click方法会返回本次使用的pointerTrace供上层追踪指针意图见第五节 v3 冒烟测试。这是「保存 → 执行 → 恢复」模式中「执行」一环的落地实现「保存与恢复」由调用链中更上层的封装完成以便点击结束后光标回到用户原本的位置。2.2 点击穿透的 Overlay 窗口makeWindowPassThrough原文档提到window.ts中的makeWindowPassThrough()使用 ignore-mouse-events 非可聚焦non-focusable行为。在 recut 分支上这一职责被收敛到 desktop-overlay 专属的 window-contract.tsexport function applyDesktopOverlayInputIsolation(window): void { window.setIgnoreMouseEvents(true, { forward: true }) window.setAlwaysOnTop(true, screen-saver) window.setContentProtection(true) window.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true }) }配合createDesktopOverlayWindowOptions()中的窗口构造选项选项值作用transparent/frametrue/false无边框透明层只叠加可视化内容alwaysOnToptrue悬浮在被控应用之上focusablefalse永不抢焦点skipTaskbartrue不进入 Dock/任务栏showfalse先隐藏待ready-to-show后用showInactive()展示避免激活窗口创建流程见 desktop-overlay/index.ts用screen.getPrimaryDisplay().bounds覆盖整个主显示器注意使用bounds而非size以兼容多显示器排列时非零原点加载渲染页前先完成 RPC 注册见第四节。2.3 CDP 心跳与三次失败拆除cdp-bridge原文档描述的「5 秒心跳、连续 3 次失败后拆除」在 cdp-bridge.ts 的startHeartbeat()中实现心跳间隔默认5000msheartbeatIntervalMscdp-bridge.ts失败阈值默认3次heartbeatFailureLimitcdp-bridge.ts每个周期先检查 WebSocket 是否 OPEN再检查上一轮 pong 是否已回来若awaitingHeartbeatPong仍为 true 则累计失败计数达到阈值调用teardownAfterHeartbeatFailure()拒绝所有 pending 请求、terminate()socket、复位状态socket 收到pong时重置失败计数。桥接器同时实现了完整的请求-响应骨架send()以自增id匹配响应、单请求超时requestTimeoutMs、close()时拒绝全部 pending。语义树通过Accessibility.getFullAXTree拉取扁平节点后重建父子树collectInteractiveElements()则通过Runtime.evaluate注入选择器脚本收集可见可交互元素默认上限 200 个。2.4 desktop v3 冒烟测试smoke-chrome-grounding原文档指出该冒烟测试证明「ensure → observe → click → state」链路。对应文件 smoke-chrome-grounding.ts 的runDesktopV3Smoke()分为五个阶段Phase 1: desktop_ensure_chrome (打开/加入 agent Chrome 窗口加载 smoke 页面) Phase 2: desktop_observe (includeChrome: true采集 grounding 快照) Phase 3: desktop_get_state (点击前状态选中目标候选) Phase 4: desktop_click_target (对选中候选执行 left 单击) Phase 5: desktop_get_state (点击后状态校验 pointerIntent 与 clickedCandidate)关键校验逻辑可直接复用于其他测试/工具链requireChromeDomSmokeCandidate()断言被选中的目标必须是source chrome_dom的候选否则报「verify extension is connected」——这用于确认 Chrome 扩展桥确实在线requirePostClickOverlayState()点击后lastPointerIntent.candidateId与lastClickedCandidateId都必须等于选中候选 id路由断言chrome_dom候选的点击其backendResult.executionRoute必须以browser_dom开头smoke-chrome-grounding.ts。运行方式来自文件头注释pnpm -F proj-airi/computer-use-mcp smoke:desktop-v3可用的环境变量环境变量默认值含义COMPUTER_USE_DESKTOP_V3_SMOKE_URL内联data:页面目标 URLCOMPUTER_USE_DESKTOP_V3_SMOKE_CANDIDATE_ID无显式指定候选 idCOMPUTER_USE_DESKTOP_V3_SMOKE_SETTLE_MS750ensure_chrome 后的稳定等待COMPUTER_USE_SMOKE_EXECUTORmacos-local执行器覆盖COMPUTER_USE_SMOKE_APPROVAL_MODEactions审批模式覆盖该冒烟还覆盖了审批链路desktop_list_pending_actionsdesktop_approve_pending_action当工具返回approval_required时自动审批首个 pending action。三、不再是假设的两件事扩展桥与 iframe 偏移原文档特别强调「Chrome 扩展桥与 iframe 偏移工作不再是假设」recut 分支中已存在真实实现扩展侧 WebSocket 客户端桥BrowserDomExtensionBridge实现在 extension-bridge.ts配套测试在 extension-bridge.test.ts扩展侧的注入脚本位于 chrome-extension/background.js。它是对 CDP 桥的补充CDP 桥要求 Chrome 以--remote-debugging-port启动且不需要扩展而扩展桥需要安装扩展但能提供更强的 DOM 采集能力。iframe DOM 候选的帧偏移传播当候选元素位于 iframe 内时需要把 iframe 相对页面的偏移叠加到元素坐标上这一逻辑已并入候选生成流程source: chrome_dom候选使点击坐标在跨 frame 场景下依然正确。两个入口的关系可以从 desktop-grounding.ts 与 chrome-semantic-adapter.ts 中看到chrome_semantic与chrome_dom两类候选统一汇入 grounding 快照供desktop_observe/desktop_get_state消费。四、browser-dom 路由契约已经 fail-closed这是原文档中最重要的「已达成」项之一包含两条规则非左键点击与多击走 OS 输入desktop_click_target只有在满足 Chrome DOM 路由条件左键、单击等时才走browser_dom执行路由其余情况回落到 macOS 本地输入注入macos-local扩展桥拒绝ok: false响应在 extension-bridge.ts 的响应分发中data.ok false直接pending.reject(new Error(message))而不是把失败响应当作成功结果吞掉——这是「fail-closed默认失败显式成功」原则的直接代码体现。冒烟测试中的路由断言chrome_dom候选必须走browser_dom路由正是为了验证该契约在真实链路中生效。五、当前真实阻塞项What is actually still blocking原文档按严重程度列出三个阻塞项这里结合代码状态逐一分析。5.1 阻塞项 1v3 冒烟只是「基线覆盖」不是「产品支持」现状smoke:desktop-v3即 smoke-chrome-grounding.ts只证明五阶段工具链可用但不覆盖Chrome 语义 DOM 路由的完整正确性它只验证了选中候选来自chrome_dom且点击路由前缀为browser_dom真实 overlay 窗口的渲染ghost pointer、候选框、徽标用户输入隔离overlay 窗口是否真的不抢鼠标/焦点。也就是说该冒烟保证的是「管道是通的」而非「每个环节都达到产品级行为」。5.2 阻塞项 2Overlay 生命周期 / RPC 就绪仍需在 recut 分支做一次活窗口验证这是原文档认定的「overlay 路径上剩余的最大运行期风险」。涉及的四个文件在仓库中的对应位置与职责原文档路径仓库路径职责desktop-overlay/rpc/contracts.tscontracts.ts复用共享就绪契约DesktopOverlayReadinessdesktop-overlay/rpc/index.electron.tsrpc/index.electron.ts注册 eventa invoke 处理器维护就绪状态机renderer/pages/desktop-overlay-polling.tsdesktop-overlay-polling.ts框架无关的轮询控制器与状态提取renderer/pages/desktop-overlay-polling.test.tsdesktop-overlay-polling.test.ts轮询控制器单元测试就绪契约定义在 shared/eventa/index.tsexport interface DesktopOverlayReadiness { state: booting | ready | degraded error?: string } export const getDesktopOverlayReadinessContract defineInvokeEventaDesktopOverlayReadiness(eventa:invoke:electron:windows:desktop-overlay:get-readiness)主进程侧状态机rpc/index.electron.ts初始readiness { state: booting }依次完成setupBaseWindowElectronInvokes与createMcpServersService后置为ready任一环节抛错则置为degraded并记录error但不向外抛——窗口照常打开渲染端通过轮询感知降级。这是「渲染端等待就绪 处理降级」设计的关键。渲染端轮询控制器desktop-overlay-polling.ts的核心行为start()先做一次bootstrapPoll()通过 eventa 调用getReadiness握手主进程只有返回ready才进入真正的 MCP 轮询booting/degraded则以回退间隔默认 500ms重试握手正常轮询computer_use::desktop_get_state默认间隔250ms出错回退到500msintervalMs/fallbackIntervalMs单次调用超时默认5000mscallTimeoutMs因为 eventa 不暴露 abort 语义超时调用会在后台挂起控制器跟踪这些 hung call达到MAX_BACKGROUND_HUNG_CALLS 2后停止发起新轮询仅当超过10s恢复探测窗口才丢弃一个旧槽位再次探测——既避免 IPC 无限堆积又保证 overlay 不会永久 stale状态提取以extractOverlayState()为唯一真源产出OverlayState含bootstrapState、staleFlags、candidates、pointerIntent。心跳标记心跳启用时?heartbeat1渲染端输出形如[overlay-poll] snapshotId... candidates... pointerIntent...的标记行主进程在 desktop-overlay/index.ts 监听console-message并透传供活窗口冒烟断言轮询确实在跑。当前缺口上述逻辑均已接线但 recut 分支上还差一次「真实 Electron overlay 窗口的活运行」用以确认心跳标记与 MCP 轮询在真实窗口中的行为与旧分支一致。5.3 阻塞项 3本地 overlay 活窗口冒烟已接线但尚未在 recut 分支实跑仓库中存在完整的活窗口冒烟脚本 desktop-overlay-live-window-smoke.ts约 566 行其单元测试在 desktop-overlay-live-window-smoke.test.ts。在 apps/stage-tamagotchi/package.json 中已注册 scriptsmoke:desktop-overlay-live-window: NODE_OPTIONS--experimental-websocket tsx scripts/desktop-overlay-live-window-smoke.ts注意该命令需要--experimental-websocket标志说明脚本在 Node 侧使用了内置 WebSocket用于连接 CDP/扩展桥。从脚本源码可看到其编排方式动态分配空闲调试端口findAvailablePort()、生成独立userDataDir与mcp.json、启动真实 Electron 进程与 computer-use-mcp server、加载一个内联 smoke HTML含#airi-desktop-overlay-smoke-button并通过desktopOverlayPollHeartbeatMarker断言 overlay 渲染端的心跳输出。冒烟产物与日志统一落在.temp/desktop-overlay-live-window-smoke-runId/下。候选选择帮助函数已有单元测试共享逻辑位于 desktop-overlay-live-window-smoke.test.ts。也就是说「测试逻辑本身被测试过」缺的只是用真实 overlay 窗口跑一遍完整冒烟。六、当前不构成阻塞的事项What is not a current blocker原文档明确这些是真实想法或清理工作但不应阻塞主线Eager overlay init 的整洁性涉及 apps/stage-tamagotchi/src/main/index.ts即 overlay 窗口是否在应用启动早期就创建属于初始化时机的代码整洁问题嵌套 browser-dom 路由逻辑的重构为可读性重排路由分支不影响行为将macos-local.ts改为 instant-warp-only 回退即去掉运动轨迹、仅瞬间跳转的降级方案与原文档「减少原生运动轨迹、让 UI 主导可见指针动画」的未来方向相关见第七节重写 overlay 视觉、ghost pointer 打磨、额外渲染端调试 UI纯打磨类工作。这些事项的共同特征不影响正确性可以放后面做。七、如何正确理解 m13v 的评审意见原文档用一个专门小节说明外部评审m13v意见的分类其方法论值得保留与当前代码已一致的部分评审正确、代码已对齐save → act → restore 光标模式即保存真实光标位置 → 执行操作 → 恢复位置正是 macos-local.ts 的 Swift/Quartz 注入所支撑的模式overlay 不应拦截用户输入对应 window-contract.ts 的setIgnoreMouseEvents(true, { forward: true })focusable: false崩溃 CDP 会话的心跳拆除对应 cdp-bridge.ts 的 5s 心跳 / 3 次失败 teardown。仍有价值的未来优化项不构成当前阻塞减少原生运动轨迹让 UI 拥有更多可见指针动画的主导权即 ghost pointer 的动画由 overlay 渲染层完成OS 层只负责最终落点更深的会话生命周期纪律围绕 session 生命周期做更强的运行期约束。原文档的结论很明确m13v 给出了好的运行期建议但不意味着每条建议都是当前阻塞项。区分「评审意见」与「阻塞项」的标准是是否影响当前正确性验证。八、接下来做什么现在 vs 稍后现在What should happen now扩展 unknown-action 契约无需处理已经是 fail-closed无需追加动作保持 browser-dom 路由对非左键点击与桥错误 fail-closed这部分保持「被测试覆盖」状态即可不需要升级为「产品支持」在 recut 分支重跑本地 overlay 活窗口冒烟在认定其为「当前证明」之前必须先用真实 Electron overlay 窗口实跑pnpm -F proj-airi/stage-tamagotchi smoke:desktop-overlay-live-window稍后What should happen later仅在上述干净之后再考虑三个可选跟进原文档给出的 commit message 级描述fix(stage-tamagotchi): validate desktop overlay lifecycle and RPC readiness in live window context—— 即把阻塞项 2/3 的活窗口验证固化refactor(computer-use-mcp): evaluate instant-warp-only macOS fallback against ghost-pointer UX—— 评估纯瞬间跳转的 macOS 回退与 ghost pointer 体验的权衡加强高度相似兄弟 iframe 下的锚点匹配当多个 iframe 结构高度相似时chrome_dom候选的锚点匹配需要更鲁棒。九、结论Bottom line综合原文档与仓库代码可以给出如下判断方向没有阻塞macOS-only、Chrome-first、visual semantic tree OS input、overlay 仅可视化这四条方向约束已经稳定且每条都有真实代码支撑当前真正阻塞的是少量正确性问题 一个尚未闭合的 overlay 生命周期验证步骤其中「在 recut 分支上用真实窗口重跑 overlay 活窗口冒烟」是唯一一个必须在本分支上完成的操作不要重开架构、不要混入打磨、不要往同一个 PR 里堆无关改动——这是原文档对开发纪律的明确要求也是桌面线能否尽快收敛的关键。对后续维护者而言本仓库的可验证抓手非常清晰smoke:desktop-v3smoke-chrome-grounding.ts验证工具链与路由契约smoke:desktop-overlay-live-windowdesktop-overlay-live-window-smoke.ts验证真实窗口下的生命周期与轮询行为二者结合即可闭环桌面线的核心风险面。【免费下载链接】airi Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考