OpenProgram · 技术规格

Web Use 技术规格

本页只保留实现需要的接口、Page 身份、失败和验收规格。结构图与 Web Use / Computer Use 边界见 设计概览。

WEB_USE 公共 API 已实现 · 仅浏览器运行时

公开命名与兼容边界

层正式名称兼容规则
Agent / MCP toolweb_use模型工具列表只公开新名称,不同时公开两个等价工具。
HTTP bridge/api/web-use旧 /api/computer-use 只作为隐藏兼容路由,复用同一 handler。
Sessionweb_session_id 与 WebUseSessionRegistry旧 HTTP 路由可以把 computer_session_id 转换为新字段;Runtime 不维护两份 session。
Computer Use保留给未来桌面控制本次不新增桌面 tool、配置、adapter 或截图进程。

三个执行 backend

Playwright MCPChrome DevTools MCP兼容 Open Claude in Chrome
设置值playwright_mcp 默认chrome_devtools_mcpopen_claude_chrome
Runtime adapter固定版本的官方 @playwright/mcp,作为私有 child process 通过 stdio 接入。固定版本的官方 chrome-devtools-mcp,通过 MCP adapter 绑定已授权 CDP target。OpenProgram 实现兼容工具契约,并通过第一方 Electron/CDP adapter 绑定已授权 WebContents target;不运行原始 Chrome extension/native host。
观察Accessibility snapshot、DOM/ARIA、稳定 ref。DOM snapshot、可访问性信息和受限 console/network diagnostics。页面结构、ref;语义定位不足时返回一张 viewport screenshot。
动作一个 canonical Action 映射为一个 allowlisted upstream action。同一个 Action 映射为一次 bound CDP action。同一个 Action 映射为一次 bound Electron/CDP action。
接入条件禁用 tabs/eval/storage/files 等越界工具;先通过 exact Page 隔离测试。禁止自行枚举或切换 Page;所有 target 由 Runtime adapter 指定。只复用工具契约与交互语义;Page identity、权限、transport 和 verification 均由 Runtime 决定。

设置只影响新的 WebSession。session 创建后 backend 冻结;失败、超时或 stale frame 都不能自动切换 backend,避免重复点击和重复提交。

官方 MCP backend 的每个 WebSession 独占一个私有 MCP 进程,用 npx --prefer-offline 启动。两个包都固定到精确版本,缓存中的副本可以直接使用,不再向 registry 校验;因此网络很慢或断开时,启动不会超过 MCP 启动超时。启动超时后,在它自己的 event loop 上取消挂起的 supervisor,在 loop 关闭前结束 npx 子进程。私有进程之后停止时,同一 WebSession 的下一次 observe 启动新进程并重新绑定同一个 exact target,这次重启是只读的。act 绝不通过需要重启的进程写入,因为 upstream ref 没有随进程保留:它返回 computer_use_backend_unavailable、action_dispatched=false、observe_required=true,并使当前 frame 失效。backend 始终不变。

BrowserContext、Window、顶部标签与 Page

层级表示什么控制目标变化规则
BrowserContext同一浏览会话的 cookies、storage 与网络隔离边界。否Context 销毁时,其下全部 Page capability 失效。
Window一个 OpenProgram 窗口;list_pages 聚合全部已登记窗口并保留各自 window_id。否某个窗口关闭或 control connection 重建时,只撤销该窗口的 inventory 与 token。
tab_entry顶部标签栏中的一个项目。single 含一个 Page;split 含两个有序 pane。否分组、拆分、换位或比例变化更新 inventory_revision,不改变底层 Page identity。
PagePlaywright Page / Electron WebContents / CDP target 的统一身份。是导航保留 identity;close 或 target replacement 撤销旧 token/session。
panePage 在当前 tab entry 中的布局位置,例如 order 0/1 与当前 bounds。否它是 Page 的布局元数据,不创建第二种 observe/act API。

目标结构同时返回 tab_entries 和 pages:前者回答窗口如何排列,后者列出 Agent 可以控制的网页。一个分屏 tab entry 有两个 Page,因此示例窗口是 3 个顶部标签项、4 个 Page。single entry ID 直接取 tab:<tab_id>,split entry ID 直接取 group:<CenterTabGroup.id>;pane ID 由 group 与当次 order 派生。这里不增加数据库表、第二套 registry 或后台进程。下面只展示 identity、布局和 token;title、origin、opener 与 capabilities 延用现有 Page 字段。

{
  "browser_context_id": "ctx_1",
  "window_id": "win_1",
  "inventory_revision": 42,
  "active_tab_entry_id": "group:g3",
  "focused_page": "p4",
  "tab_entries": [
    { "id": "tab:w:google", "mode": "single", "pages": ["p1"] },
    { "id": "tab:w:github", "mode": "single", "pages": ["p2"] },
    {
      "id": "group:g3",
      "mode": "split",
      "pages": ["p3", "p4"],
      "split": {
        "axis": "horizontal",
        "ratio": 0.5,
        "panes": [
          { "pane_id": "pane:g3:0", "order": 0, "page": "p3" },
          { "pane_id": "pane:g3:1", "order": 1, "page": "p4" }
        ]
      }
    }
  ],
  "pages": [
    { "page": "p1", "tab_id": "w:google", "tab_entry_id": "tab:w:google", "visible": false, "focused": false, "placement": { "mode": "single" }, "page_context_token": "pct_…" },
    { "page": "p2", "tab_id": "w:github", "tab_entry_id": "tab:w:github", "visible": false, "focused": false, "placement": { "mode": "single" }, "page_context_token": "pct_…" },
    { "page": "p3", "tab_id": "w:bilibili", "tab_entry_id": "group:g3", "visible": true, "focused": false, "placement": { "mode": "split", "pane_id": "pane:g3:0", "order": 0 }, "page_context_token": "pct_…" },
    { "page": "p4", "tab_id": "w:youtube", "tab_entry_id": "group:g3", "visible": true, "focused": true, "placement": { "mode": "split", "pane_id": "pane:g3:1", "order": 1 }, "page_context_token": "pct_…" }
  ]
}

axis 与 order 是规范字段;left/right/top/bottom 由当次 bounds 派生,不能作为 Page identity。当前 UI 主要使用水平双 pane,但接口不把“右侧网页”写成固定目标。移动 Page、调整比例或切换 active tab entry 后,下一次 list_pages 返回新布局并递增 inventory_revision;受影响的 visible Page 继续通过 geometry revision 阻止旧布局坐标生效。一个窗口最多只有一个 focused_page,但 active split 中可以有两个 visible=true 的 Page。

开源、开放接口与不可直接复用部分

组件公开状态能否直接用在 OpenProgram 中的处理
Microsoft Playwright MCP开源 · Apache-2.0可以使用官方 @playwright/mcp,固定版本;外层保留 Page token、权限与 verification。
Playwright / playwright-core开源 · Apache-2.0可以作为 Playwright MCP 的执行层;不再实现第二套 DOM locator。
MCP TypeScript SDKSDK code:新贡献 Apache-2.0、未重许可 legacy code 为 MIT可以作为代码依赖复用现有 OpenProgram MCP server/client;MCP 只承担工具协议,不决定点击方式。
MCP 协议 / 规格spec contributions:Apache-2.0;未重许可 legacy 内容为 MIT可以按规范实现采用标准 tools/list、tools/call、content 与 cancellation 语义,不复制特定 client 的浏览器工具。
MCP 非 specification 文档CC-BY-4.0可以引用或复用并署名只作为实现参考,不作为运行依赖。
Chrome DevTools Protocol开放协议与类型 · BSD-3-Clause可以直接调用 Page.captureScreenshot、Input.dispatchMouseEvent 等命令,不需要 Claude 代码。
Electron webContents.debuggerElectron 开源 · MIT;API 已内置可以内置浏览器的首选 CDP transport;绑定 exact WebContents/target,不要求窗口置前。
Chrome debugger API 与 Native MessagingChrome/Chromium 的公开内置接口,不是独立库可以调用只在以后控制独立 Chrome 扩展时需要;OpenProgram 内置浏览器不需要 Native Messaging host。
Open Claude in Chrome第三方 source-available 实现 · PolyForm Noncommercial 1.0.0;不是 Anthropic 官方项目当前非商业范围可评估其契约与实现内置浏览器只采用兼容工具契约与交互语义;运行 transport 是 OpenProgram Electron/CDP adapter,不依赖原始 extension/native host。
Claude Code 公开仓库仓库内容 all rights reserved;该结论只适用于此仓库不可以按开源代码复制不依赖其内部代码,只参考官方公开产品行为。
Anthropic 官方 Claude Chrome 扩展的 action handler、视觉光标、权限 UI 与 native host发布产品可安装;未公开可复用源码或明确的开源许可证不可以直接复制只参考官方公开行为;OpenProgram 保持自己的 web_use contract 和 backend adapter。
Claude 模型的视觉定位能力托管模型能力,不开源只能通过服务使用不依赖 Claude 专用实现;把单张截图交给当前所选模型的原生 vision。

这里没有“CDP 只能由 Claude 使用”的限制。Anthropic 官方工具编排、扩展实现和产品权限层没有公开可复用源码;截图与坐标点击所用的 CDP 命令是公开接口。第三方 clean-room 项目可以作为实现参考,但不代表 Anthropic 官方实现已经开源。

Agent 只需要五个 command

01list_pages

读取当前 turn 已授权 Page inventory,不调用 upstream tab 枚举。

02observe

选择 Page 并创建 WebSession;返回有界文本、ARIA 和 refs。

03act

携带最新 frame,只执行一个 click、type、press、scroll、select 或 navigate。

04verify

只执行 Runtime assertion;模型声称“完成”不能产生成功。

05close

释放 WebSession、Page binding 和调用方持有的 capability。

web_use({ command: "list_pages" })
web_use({ command: "observe", page_context_token: "pct_…", arguments: { detail: "interactive" } })
web_use({ command: "act", web_session_id: "cs_…", arguments: { action: "click", expected_frame_id: "f_…", ref: "e1" } })
web_use({ command: "verify", web_session_id: "cs_…", arguments: { action: "verify", expected_frame_id: "f_…", assertion: "text_contains", value: "Saved" } })
web_use({ command: "close", web_session_id: "cs_…" })

list_pages 的目标返回全部已登记 OpenProgram window 的 group-aware snapshot:windows[] 分别保存每个窗口的 window_id、inventory_revision、active/focused identity 与 tab_entries;扁平 pages 让 Agent 直接选择 Page,每项携带所属 window_id。原顶层窗口字段继续表示发起任务的 primary window,以兼容现有调用方。每个 Page 包含稳定的 tab_id、所属 tab_entry_id、placement、title、origin、focused、visible、可选 opener_tab_id 与一次性 page_context_token。Agent 不用 window、tab entry 或 pane 执行动作,只在首次 observe 中原样回传目标 Page token。token 绑定调用 owner、window_id、exact Page target 和 server-owned page/access revision;缺少、重放、跨 owner 使用、连接重建或 target replacement 必须在 backend 调用前拒绝。同一 owner 对同一 Page 和同一 backend 重复发起不带 web_session_id 的 observe 时,Runtime 复用已有 WebSession 并返回同一 ID;不同 owner 或不同 backend 仍按排他 lease 拒绝。聊天 owner 固定为当前 session 与 assistant turn;turn 无论正常完成、重试、取消或异常结束,dispatcher teardown 都释放该 owner 的全部 WebSession 和 Page lease,不依赖模型显式调用 close。首次 observe 之后,每次 observe、act 和 verify 都重新验证 exact binding。visible Page 继续验证 geometry revision;background Page 通过不改变 tab、store 或窗口焦点的 resolve path 校验 target。web_use 的 Page inventory、WebSession registry 与 renderer WebSocket registry 必须在 worker 主进程内执行:聊天中的 runtime card 可以保留 agentic function UI,但不得把该工具迁移到隔离子进程。GUI Agent Harness 的长循环如在子进程运行,只能经父进程 bridge 调用同一 worker-owned WebSession,并原样传递 revision;任一不一致即返回 page_context_stale,且不得执行页面动作。

list_pages 不依赖发起窗口。发起窗口或本轮 binding 断开时,worker 列出其余所有已登记窗口;workflow 子进程仍限定在自己的发起窗口内,只会再次列出该窗口。没有任何 Desktop 窗口登记时(例如 worker 或 App 重启后 App 正在重新打开),发现流程最多等待 20 秒,等窗口重新登记;仍然没有就返回 desktop_unavailable 与 retry_command=list_pages,而不是抛出异常。在线的发起窗口若 inventory 请求失败,仍立即失败,绝不提升其他窗口。

page_context_token 在每个命令中都指定它的 Page。act 或 verify 只带 token、没有存活的 web_session_id 时,复用该 token 已打开的存活 WebSession。token 尚未使用时,Runtime 先用一次只读 observe 打开 WebSession;随后 navigate、screenshot、wait 与不带 ref 的 verify 在这个 frame 上继续执行。ref 或坐标动作返回这次 observation 并带 action_performed=false,因为它们的目标必须来自调用方读过的 observation。token 绝不回退到该 owner 在其他 Page 上最近的 WebSession;与 token 同时出现的未知 web_session_id 让位于 token。

已结束的句柄可以恢复,但不会重放。registry 在内存中有界地只保留已结束 WebSession(turn 结束、binding 丢失、启动失败)以及过期或被已结束 WebSession 消费的 token 的精确 window_id/tab_id。同一个 chat 的各个 turn owner 跨 turn 共用一个作用域;其他 owner 只匹配自己。之后同一作用域内的 page_context_not_found、page_context_consumed 或 web_session_not_found 会带 recovery_tab_id、recovery_window_id 与 recovery_command=observe。Agent tool 随即列出 Page,用新 token observe 这个精确 tab,并返回带 recovered_page=true 与 previous_action_replayed=false 的 observation。它绝不重放上一个命令,也绝不打开它不知道的 URL。调用方主动 close 的 WebSession 不会被记住。没有记住的 tab 时(例如 worker 重启清空了 registry),失败结果带当前 pages 与新 token,以及一条指令:用任务 Page 的 token 执行 observe。这类失败都带一条 message,写明下一次调用。

网页 popup 创建独立 web tab 和独立 WebContents/Page,并保存 opener_tab_id。popup 不替换 opener,也不隐式转移现有 WebSession;下一次 list_pages 发现 popup,Agent 使用它的 token 显式建立新 session。GUI Agent Harness 在每次写动作后刷新有界 Page inventory,并只通过 token 切换 Page。跨窗口聚合只扩展发现范围,不改变 exact Page binding、Page lease 或 session 生命周期。

每个可见 web tab 还持有 renderer-owned 单调 geometry_revision。现有 bounds lifecycle 在 mount、resize、ancestor scroll、pane 重排、窗口 resize、pane 显隐与 transfer 时测量 viewport-relative bounds;只有规范化后的 x/y/width/height 或应用内可见性变化才递增 revision,focus 变化不递增。发送 turn 时,surface ref 带当前 revision;preview 必须在调用 native preview 前验证并回显同一 revision。WebSession 保存该值,后续 observe、act 和 verify 经父子进程 bridge 原样传到 exact renderer;renderer 在调用 Electron activate 前再次比较。缺失的旧客户端 revision 保持兼容,但一旦 capability 含有非零 revision,任何不一致都返回 page_context_stale,不得调用 native preview、activate 或 backend。Page identity 与 lease 不因单纯 geometry 变化而重建;调用方重新 list/observe 后继续使用同一 Page。这里的可见性是 OpenProgram 内部 pane 布局状态,不是 macOS 窗口层级、遮挡或最小化状态。

成本与功能边界

默认

DOM / ARIA

URL、title、页面文本、accessibility snapshot 和 element refs。普通网页交互不发送截图。

回退

一张 viewport screenshot

仅用于视觉验收、canvas 或语义定位失败。图片只进入紧邻的下一次模型请求;提示文本只记录 frame 与 viewport 元数据,请求结束后立即清除调用方持有的图片副本并撤销坐标权限。

关闭

昂贵视觉管线

不启用 OCR、YOLO、object detector、crop/zoom、component memory、vision memory、workflow replay 或自动学习。

实施顺序

阶段 0
验证官方 MCP 隔离能力
连接测试 Electron CDP,验证 snapshot/ref 和 exact Page 限制。
出口:A/B 两个 web Page 与 chat Page 的越权测试通过;否则选 upstream contract adapter。
阶段 1
扩展现有 OpenProgram MCP server
新增一个 browser-control tool 和一次性 Page token;保留现有非浏览器 tools。
出口:真实 initialize/list/call/cancel 通过,伪造与重放在 backend 前拒绝。
阶段 2
完成 Page Registry 与 group-aware inventory
把现有 CenterTabStripEntry、CenterTabGroup 和 Page inventory 组合成 tab_entries + pages;加入 inventory_revision、access、exact resolve 与失效处理。
出口:3 个 tab entry / 4 个 Page 示例、左右互换、上下分屏、后台 Page、双窗口与 target replacement 通过。
阶段 3
接入 Playwright MCP backend
只开放 snapshot/ref 和允许的动作,复用共同 policy/verification。
出口:DOM、canvas、非前台、多 Page、popup 和失败路径通过。
阶段 4
接入 Chrome DevTools MCP backend
实现 exact target adapter,禁止 upstream 自行选择 Page。
出口:与 Playwright backend 共用的 parity suite 通过。
阶段 5
接入 Open Claude in Chrome-compatible backend
在 Electron/CDP adapter 上实现兼容工具契约、结构化读取、截图和 action cursor。
出口:同一 Page、权限、失败与 verification parity suite 通过;运行时没有 Chrome extension/native host。
阶段 6
设置与安装版验收
增加一个 backend selector,并在默认 18100、/Applications/OpenProgram.app 中运行相同任务集。
出口:当前 session backend 冻结;没有双执行或自动 fallback。

发布前必须证明

  1. 工具面:浏览器控制只有一个 web_use。Desktop Page inventory 已登记时即可使用;turn-start preview 只是当前 Page 的上下文增强,单次 preview 失败不得移除工具或改为公开会启动独立浏览器的旧工具。Agent 先调用 list_pages,再用返回的 page_context_token 选择 exact Page。
  2. Page 隔离:list_pages 可以列出其他窗口的 Page;选择 A 后,对 B、chat Page 和其他窗口未选 Page 的读写调用次数都是 0。
  3. 分组结构:Google、GitHub、Bilibili/YouTube 分屏返回 3 个有序 tab_entries 和 4 个唯一 Page;第三项为 split,pane order 与实际 UI 一致。
  4. 布局与身份:交换 split pane、调整 ratio 或拆分后,Page identity 保持且 inventory_revision 更新;受影响 visible Page 的旧 geometry 在写入前 stale,不得新建另一套 split action。
  5. 可见与焦点:active split 的两个 Page 都是 visible,只有一个 focused;前两个 single Page 是 background。切换 active entry 后下一 snapshot 反映新状态。
  6. 弹窗清单:A 打开的 popup C 单独出现并带 opener_tab_id=A;每个 token 只消费一次。
  7. 后台 Page:选择 B 后读取和动作只到达 B;active tab、visible ids、BrowserWindow focus 与系统鼠标均不变化。
  8. popup 切换:A 的 session 不因 C 创建而改绑;GUI Agent 只有选择 C 的 token 后才建立 C session,关闭 C 后其 token/session 在下一动作前失效。
  9. backend 冻结:设置变更不影响当前 WebSession,只影响下一 session。
  10. 副作用唯一:backend failure 不触发另一 backend;一个 Action 最多一次真实写入。
  11. 跨 backend 一致:同一任务得到相同 Page/frame 生命周期、权限、reason code 和 verification。
  12. 页面感知:页面换到 chat 左侧或右侧后,下一 turn relation 与 preview 正确更新。
  13. 跨窗口发现:两个已登记 OpenProgram window 返回两个有序 window snapshot;同名 tab/page 不合并,每个 token 仍解析到原 connection、window_id 与 target。
  14. 窗口状态独立:DOM/ref 与 viewport screenshot 直接作用于相同 Page/WebContents;macOS 窗口层级、遮挡、位置与最小化状态不进入 Web Use 的选择、截图或动作条件,且没有窗口 focus、bringToFront()、整屏截图或 OS input 调用。
  15. Agent 光标:三个 backend 的 click command 都只在 exact Page 内临时启用捕获监听器;真实 pointer event 到达时,在该 viewport 坐标显示无文本、aria-hidden、pointer-events:none 的光标与点击波纹,并在 900ms 内移除。用户点击、失败且未产生 pointer event 的动作、其他 Page 和系统鼠标不受影响;反馈渲染失败不得改变 click 结果。
  16. 视觉限制:普通 DOM 流截图数为 0;canvas 流每次 observation 最多 1 张。
  17. 图片保留:截图只出现在紧邻的一次 provider request;同一请求的文字提示不含原始 bytes/base64,返回或异常后调用方 content 与 ToolReturn 不再持有图片,最终 artifacts 仍为空。
  18. 失败诚实性:空 assertion、stale frame、timeout、cancel、target lost 都不能返回 succeeded。
  19. Chat 稳定:GUI tool update 不重建历史 message/avatar/markdown DOM。
  20. 安装版双窗口事务:只连接默认 18100 的 /Applications/OpenProgram.app,通过正式 preload transfer API 创建真实隐藏目标窗口;目标 renderer 完成 inspect/accept/stage 后由源端拒绝临时 payload,事务必须回滚并关闭目标窗口。验收前后的窗口集合、源窗口 tab 状态和持久存储必须一致。

Backend parity suite 的规范矩阵

同一组用例必须通过 WebUseSessionRegistry.execute() 依次运行 playwright_mcp、chrome_devtools_mcp 与 open_claude_chrome 的真实 adapter class。官方 MCP 的进程和网络边界可由记录调用的 test client 代替,但不得用三个同构 fake adapter 代替生产映射。矩阵比较 OpenProgram 的规范化结果,不比较上游特有的 ARIA 文本、ref 名称或诊断文字。

场景三个 backend 的共同结果额外不变量
observe返回非空 web_session_id、冻结后的 backend、当前 frame_id 与结构化页面观察只绑定授权的 exact Page;不调用 tab select、bringToFront() 或 OS input
allowlisted actionok=true、observe_required=true,旧 frame 立即失效navigate/click/type/press/scroll/hover/select 各产生一次且仅一次 backend write;不自动 fallback
无效 / 过期 / 不支持公开 Agent tool 与 MCP tool 共用同一 command-conditioned schema;未使用的可选字符串可以省略,旧会话或模型生成的空字符串按“未指定”兼容处理。act 缺少 action 或 expected_frame_id 时返回 invalid_arguments 与完整 missing_arguments;verify 强制要求 frame、assertion 与非空 value,close 强制要求非空 session;真实旧 frame 返回 stale_observation;未知 action 返回 unsupported_action。写动作成功后清除记住的 frame,因此下一次不带 frame 的 ref 动作对 expected_frame_id 返回 invalid_arguments,而不是填入已被替换的 frame。stale_observation 与缺少 frame 都带 observe_required=true、recovery_command=observe 和 message均在 backend write 前拒绝,调用次数为 0;参数缺失不得清除当前有效 observation,调用方补齐参数后可继续使用同一 frame。list_pages 携带空的旧版可选字段不得在进入 Runtime 前失败
后端动作错误返回 backend_action_failed 与 observe_required=true当前 frame 失效;其他两个 backend 调用次数为 0
verify同一 assertion 得到相同 passed、evidence 与 frame 语义不通过的 assertion 不能形成 succeeded 终态
screenshot一个当前 viewport PNG;tool-result 仅保留 frame_id、viewport、image_attached图片只进入紧邻一次 provider request,并按视觉 artifact 生命周期释放
不依赖 frame 的动作没有当前 frame(写动作或 desktop 超时之后)时,screenshot 和 wait 先只读 observe,再在新 frame 上执行;它们的 frame 在执行前被页面改变时,observe 后重试一次。navigate 只在调用方没有指定 frame 时这样做;显式传入的旧 frame 仍返回 stale_observation仍然恰好一次 backend write。自动 frame 上的 ref 在显式 observe 之前以 stale_observation 拒绝;该 frame 截图得到的坐标仍然有效。每个 frame 仍只允许一张截图
closeok=true、closed=true;后续命令返回 web_session_not_foundcontroller、MCP client、Page lease 与 context 全部释放

当前实现证据

能力当前状态直接证据
内置 web tab exact CDP attach已验证apps/desktop/main.js、_actions/open_action.py
DOM-first task wrapper 与 point fallback已验证 baselineopenprogram/programs/gui_browser_agent.py 与 tests/component/programs/browser/test_browser_agent.py
视觉 artifact retention已实现调用方 ephemeral policy截图只发送给紧邻的一次模型请求;planner 文字只含元数据。正常返回或异常后清除 content image block 与 ToolReturn.images,坐标能力同步撤销;不写本地 artifact,最终 artifacts 为空。provider 侧保留不在本地 policy 的控制范围内。
通用 MCP server/client 与 image content已有基础openprogram/mcp/server/、openprogram/mcp/
动态 Page geometry、registry、leasebaseline 已实现当前 turn surface 已按实际 pane 顺序生成 left、right 或 center;服务端为 connection、Page 和 access 分配单调 revision,renderer 为每个实际可见 web tab 分配 monotonic geometry revision,并以 Page revision 生成 canonical key。同一连接内 recapture 保持 Page identity;pane bounds 或应用内可见性变化使旧 WebSession stale,但不重建 Page identity。WebSession 对 canonical key 持有排他 lease;session 内操作串行执行,首次及后续 observe、act 和 verify 在 backend 前重新验证 exact binding。preview/activate Promise 完成后再次检查 ready、当前 bounds 和非零 geometry revision;pane 从当前布局移除或 transfer 后返回 page_context_stale,不向 backend 暴露过期 preview/target。后台 Page 使用 resolve,不要求成为当前 tab;macOS 遮挡、窗口层级、位置与最小化不属于该 revision。owner disconnect 立即撤销该连接的全部 binding 和 pending exact-socket request。137 项相关 Python 测试、完整 Web check、TypeScript 与生产 build 通过;安装版隐藏目标窗口的 stage、rollback、destroy 与无残留验收通过
同窗口多 Page 与 popup handoffgroup-aware browser-only baseline 已实现Renderer 为当前 window 的每个 web tab 生成 exact target、实时 URL/title、visible/focused/placement 与可选 opener;后台 Page 通过 native WebContents/CDP 解析,不置前。Agent 通过一次性 Page token 选择目标,WebSession 不因新 popup 自动改绑;GUI Agent 只在显式 switch_page 后切换。
标签页入口 / 分栏结构已实现并通过并发与生命周期测试browserPageInventory() 从同一次 store snapshot 返回 tab_entries + pages、active_tab_entry_id、focused_page 与单调 inventory_revision;pane 顺序使用实际渲染的 visibleIds。每个 Page capability 独立持有 binding context,关闭一个 Page session 不会撤销同批次其他 Page。
跨窗口 Page inventory已实现,安装版 E2E 通过每个 Desktop renderer 先登记 window_id;list_pages 返回 windows[] 与扁平 Page 列表,binding 保留 connection/window/tab/target/revision。connection revision CAS 拒绝断线后的延迟结果;origin 断开时其他在线窗口继续可发现,origin 仍在线但 inventory 失败时不提升 secondary。安装版在两个窗口中选择 detached window Page 完成 observe → screenshot → close,前台应用保持不变。
内部后台 Page 控制独立安装版自动化 E2E测试自行创建临时 Page 并切回原 tab,使目标 WebContents 保持 visible=false;随后通过 exact Page CDP session 完成 DOM observe、单张 viewport screenshot、ref click、observe、DOM verify 和 close,每个 Agent 动作后原 tab 均保持可见。Web Use 不读取系统屏幕,也不查询或改变窗口是否前台、最小化、位于何处或被遮挡;不调用 browser.close()、窗口 focus、bringToFront() 或 OS input。
Agent click cursor已实现,安装版后台 Page E2E 通过exact Page 在 Agent click command 期间启用一次捕获监听器,真实 pointer event 到达时显示无文本、不可交互的 cursor 与 click ring,900ms 内移除。DOM ref、viewport coordinate、三个 backend、用户点击隔离与反馈失败不影响 action 的 component 验收通过;安装版目标 visible=false 且原 tab 不变时,页面 click handler 记录 cursor=true。未移动系统鼠标、切换 Page、依赖 screenshot 或把用户点击标成 Agent 动作。
Playwright MCP 后端已实现 baseline,安装版 E2E 通过固定 @playwright/mcp@0.0.79;通过官方 createConnection(contextGetter) 只提供 exact Page,不调用 tab select 或 bringToFront();只映射 allowlisted action。安装版在同一 exact target 上完成 navigate → observe → click → observe → verify → close。
Chrome DevTools MCP 后端已实现 baseline,安装版 E2E 通过固定 chrome-devtools-mcp@1.7.0;marker 绑定 exact pageId;启用 pageId routing。安装版在同一 exact target 上完成完整命令序列与 DOM assertion。
兼容 Open Claude in Chrome 的后端已实现 baseline,安装版 E2E 通过复用现有 exact Page DOM/ref、单截图和坐标 action;不运行 Chrome extension/native host。安装版在同一 exact target 上完成完整命令序列与 DOM assertion。
多后端选择器已实现 command selectorobserve 选择 backend 并冻结到 WebSession;后续变更返回 backend_mismatch。
后端一致性测试集已实现并独立复核生产 adapter 经公共 Registry 运行统一矩阵;覆盖 7 个写动作、stale/unsupported 的零写入、后端错误与真实 Playwright timeout 的统一 reason code、frame invalidation、PNG screenshot、verification、close 后 lease 重获及无自动 fallback。

表中的“已实现”表示当前 multi-backend、geometry、transfer、视觉 artifact 生命周期与 parity suite。专用 permission card 仍属于 UI 后续项,不是当前功能实现的发布条件。当前实现与验证证据维护在 实施记录。

需要实现者展开时再读

Page identity 与生命周期
Canonical Page 绑定 window_id + registry_page_id + tab_id + cdp_target_id + revisions。tab_entry_id、pane order、split ratio 和 inventory_revision 不是 Page identity;它们描述同一次 inventory snapshot。首次 observe 建立 WebSession binding,并保存 capability 中的 page/access/geometry revision。后续命令先在父 worker 比较 page/access revision,再由 exact renderer 在 native activate 前比较 geometry revision;任一比较失败都不调用 Electron 或 backend,并清理当前 session。focus 变化不改变 target;pane 位置、bounds 或可见性变化保留 Page identity,但会使依赖旧 geometry 的 session stale;transfer、close、target replacement、access disable 或 disconnect 仍会在下一次副作用前撤销 binding。
Popup 与并发
当前 browser-only 增量采用 Electron 已有的同步 popup request:main 拒绝原生 BrowserWindow,renderer 创建独立 web tab/WebContents,并记录 opener。它不把 popup 自动归因成当前 action 的成功结果,也不隐式改绑 session;GUI Agent 在写动作后的 Page inventory 中发现并显式选择。相同 exact Page 的 WebSession 写 lease仍排他,不同 Page 可并行。
安装版双窗口验收
验收脚本只能连接已安装 App 的 CDP endpoint,禁止从源码启动 Electron、启动第二个 worker或增加生产测试接口。脚本先记录现有 shell targets 和源窗口本地状态,再用正式 tabTransfer.prepare/detach 提交一个唯一、未出现在源 store 中的临时 NTP payload。脚本在源端失败 sourceRemoved 调用点设置临时 CDP breakpoint,并直接连接真实目标 renderer,确认临时 tab DOM、目标 windowId 与对应 token journal 已存在;随后恢复源端,让 main process 执行 rollback、关闭仍未显示的目标窗口并清除 transaction。脚本在 finally 中使用带超时的正式 cancel,并仅在 shell target 集合回到基线、源状态字节未变化、目标三个 storage key 均为空且 transaction 已消失时返回成功。
视觉 artifact 生命周期
screenshot 的 PNG bytes 只在内存中转换成一个 provider image block,并且只附加到紧邻的下一次 planner request。传给 _step_prompt 的 previous result 先转换成 frame、viewport 与 image_attached 元数据,不允许 bytes 或 base64 进入文字通道。无论该请求正常返回还是抛出异常,finally 都删除调用方 content 中的 image block、清空原 ToolReturn.images 并撤销截图坐标能力;最终结果继续返回空 artifacts。
统一失败语义
invalid_arguments、invalid_capability、ambiguous_page、page_context_stale、stale_observation、page_access_disabled、target_lost、needs_user、timeout、cancelled 和 verification_failed 都由 Runtime 生成。backend 文本不能覆盖 reason code 或 completion。调用方可以恢复的失败(stale_observation、web_session_not_found、page_context_not_found、page_context_consumed、computer_use_backend_unavailable、desktop_unavailable)带 Runtime 生成的 message,只写明一次下一步调用,并且按它调用就能成功。

官方参考