浏览器/桌面 Computer Use Agent 的核心分岔点,不是模型、不是框架,而是「模型靠什么看到页面」——感知方式。感知方式决定了三件事:精度(点得准不准)、token 成本(省不省钱)、稳定性(页面改版后还能不能用)。

本文对比三大流派,并解剖两个真实实现(OpenAI Codex CUA、OpenMinis)的源码级细节。分析日期:2026-09-20。

一、三大感知流派总览

流派感知输入代表实现精度token 成本弱点
纯视觉 (Pure Vision / Screenshot-first)截图 + 多模态模型Midscene.js、browser-use(Set-of-Marks 标号)、UI-TARS高(对所见即所得)最高(每步一张图)贵、慢、坐标易漂移
DOM 树 (DOM Tree)简化 DOM / 可读文本 / 自研骨干树OpenMinis get_backbone、简单 text 提取中低动态页面脆弱、canvas/iframe 抓瞎
无障碍树 (Accessibility Tree, a11y)浏览器语义树(role+name+index)Playwright MCP、Stagehand、OpenAI Codex CUA高(语义稳定)低依赖站点 a11y 质量、需要把树算出来

一句话总结:纯视觉看"长得像什么",DOM 树看"源码是什么",a11y 树看"读屏器读出来是什么"。后者是 agent 的甜点区——既便宜又有语义。

二、a11y 树到底是什么(和 DOM 树不是一回事)

无障碍树 (Accessibility Tree) 是浏览器为辅助技术(读屏器)单独计算出来的一棵语义树。它不是 DOM,虽然从 DOM 派生。

核心区别:

  • DOM 树:原始 HTML 结构,包含 <div>、<span>、<style>、布局元素、脚本注入的垃圾节点。对 agent 来说噪音巨大。
  • a11y 树:浏览器自己把 DOM"翻译"成读屏器需要的语义结构——折叠表现层节点、合并冗余、给每个有意义元素算 role(角色)和 name(名字)。一棵几十层的 DOM 常常压成一棵 3~4 层的 a11y 树。

agent 用它天然合适,因为 读屏器面对的问题 = agent 面对的问题:都要在不可见的页面里找到"可交互的东西",并且需要稳定、可指向的标识。a11y 树给每个元素编号(elementIndex / element_index),模型点 element_index=12 就行。

为什么会有 wasm 的事

页面里的 JS(content script / 油猴 / 注入脚本)拿不到浏览器算好的 a11y 树——那棵树只在浏览器内核里,不对页面 JS 暴露(读屏器走的是原生 accessibility API)。

所以要在页面内拿 a11y 树,只有两条路:

  1. 重算:把 Chromium 计算 accessibility 树的逻辑(ARIA 解析、role/name 计算、树折叠)自己实现一份。
  2. 编译:把那个逻辑编译成 wasm,注入页面执行——这正是 OpenAI 干的事。

Codex 的 browser-service.mjs 里带一个 browser-accessibility.wasm.br(Brotli 压缩的 wasm 模块),注入页面后用 wasm 在页面上下文里算 a11y 树。选 wasm 的原因:纯 JS 重写整套 ARIA 计算太重、太慢,wasm 是二进制体积和性能的最优解。

参考:Playwright 的 ariaSnapshot 也是同思路——自己实现 a11y 快照生成,而不是依赖浏览器原生。@oai/browser-desktop/scripts/ 里同时有 browser-accessibility.wasm.br 和 zxing_reader.wasm(二维码识别),说明 wasm 是它做页面内重活的通用手段。

流派的澄清

a11y 流派不是新流派,它是"结构化文本流派"里最精细的进化版:DOM 树提取 → 可读文本提取 → 语义树提取。三者在 token 价格上同档(都是文本),但语义树的精度天花板最高,因为浏览器替你做了去噪和语义标注。

三、实现解剖 1:OpenAI Codex CUA

位置(私有 npm 包,装上才有,npm 上是 404):

AppData\Local\OpenAI\Codex\runtimes\cua_node\<hash>\bin\node_modules\@oai\

包:@oai/cua(17M)、@oai/sky(57M)、@oai/browser-desktop(7.6M)、@oai/cua-repl(103K)。

架构:不是工具调用,是"模型写 JS"

Codex 的 computer use 不用 JSON 参数的工具调用,而是给模型一个 node_repl(Node.js REPL,状态跨调用持久),模型直接写 JavaScript 调 Sky/CUA 的 API,nodeRepl.write() 输出。这是它和 OpenMinis 最大的架构差异。

var sky = (globalThis.sky ??= (await import("@oai/sky")).sky);
var state = await sky.get_window_state({ window, include_screenshot: true, include_text: true });
nodeRepl.write(String(state.accessibility?.tree || state.accessibility?.document_text || ""));

两层

层包管什么关键接口
整机级 (Computer Use)@oai/skyWindows/macOS/Linux 原生 applist_apps / get_window_state / click(element_index 或坐标) / type_text / scroll / set_value / drag / perform_secondary_action / activate_window
浏览器级 (Browser Use)@oai/cua 的 tinysky_alt浏览器 tabgetAXState() / getScreenshot() / getAXStateAndScreenshot() / click(element_index) / selectText / setValue / drag / scroll / performSecondaryAction

Tab = BrowserTab & Target——浏览器 tab 直接增强出共享交互面。浏览器通过 @oai/browser 的 GlobalAgentApi(agent.browsers.get() / browser.tabs)枚举。

提示词怎么拼

  • 整机级:@oai/sky/docs/skills/oai_sky_lib/windows/SKILL.md(231 行)就是提示词本体,完整 TS API 参考整个嵌在文件里,模型作为 skill 强制先读。
  • 浏览器级:tinysky_alt/documentation.js 通过 nodeRepl.requestMeta 按需分发 docs/tinysky-alt-*.md(confirmations / core-cua-repl / core-node-repl / other-browser-apis),不是一次性全塞。

感知与省 token

browser-service.mjs(1.3MB,注入页面的 agent 脚本,自带 processShim)关键词统计:screenshot×230、elementIndex×42、getAXState×12、ariaSnapshot×5、disableDiffing×12。

  • 主感知 = a11y 树(wasm 在页面内算),截图是辅助/视觉确认。
  • state diffing(disableDiffing 默认关掉才算关):状态只给变化部分,不是整棵树重发。
  • 截图自动展示、禁止重复 emit:SKILL.md 明确"不要 decode/save/print 截图 payload 再次检查"。
  • 批量输入再抓一次状态:"treat get_window_state as an expensive point-in-time snapshot. Batch related inputs, then capture a new state."
  • Playwright locators 只是 fallback("Other Browser APIs"):文档明说它比 a11y API 啰嗦,除非能省掉多次 getAXState() 才值得用。

详见已归档笔记:Windows 桌面自动化 Computer Use (Codex Computer Use via node_repl)

四、实现解剖 2:OpenMinis(端侧 agent)

位置:OpenMinis/OpenMinis,Swift 实现,浏览器 agent 在 src/ios/Agent/BrowserUse/,7641 行 10 个文件。基于 WKWebView + 注入页面 JS。

文件行数职责
BrowserUseManager.swift2897runtime 循环、action 分发、DOM 沉降检查
BrowserTabPool.swift1517多标签池
BrowserUseJavaScript.swift858注入页面的 JS(DOM 提取 + 操作)
BrowserUseActions.swift21822 种 action 枚举

范式:自研 DOM 骨干树 + 结构化工具调用

  • 工具调用形态:一个 browser_use 工具、22 种 action、约 30 个参数(枚举 BrowserAction)。
  • 主感知 = get_backbone:自研"矩形合并骨干树"——收集可见叶子 → 按矩形重叠向上合并 → 注入含≥2 叶子的祖先做分组 → 生成短选择器(#id 或 #ancestor > tag:nth(n))→ 文本截 120 字符。不是 a11y 树,是它自己的 DOM 流派。
  • 省 token 大招 = DOM 变更摘要(BrowserUseManager.swift):click/type/navigate 后等 DOM 沉降(MutationObserver),返回"页面变了什么"的文本摘要,而不是重截图。快照只给 UI 预览,不进模型。
  • 其他:get_readable(正文提取)、scroll_and_collect(无限滚动去重,30k 上限)、wait_for_dom_stable(突变率梯度稳定判定)。
  • 视觉兼容:模型能看图→截图给像素;纯文本模型→Vision Group 描述成文字(activeModelHasNativeVision)。

原创性

自研。THIRD_PARTY_LICENSES.md 无 browser-use/Stagehand/Midscene 归因,fork 的只有 ish-arm64 / proot(沙盒基础设施)。

五、省 token 手段对比

手段CodexOpenMinis
主感知a11y 树(wasm 页面内算)自研矩形合并骨干树
状态去重state diffing(只给变化部分)DOM 变更摘要(MutationObserver)
截图策略自动展示,禁止重复 emit快照只给 UI,不进模型
操作目标element_index(稳定语义)短选择器(#id / nth)
失败兜底Playwright locatorsCSS selector / 坐标

结论:两者同属"结构化文本流派",都极度在意省 token。Codex 靠浏览器语义基建(a11y),OpenMinis 靠自研 DOM 压缩。Codex 的 diffing 更系统(整棵树级),OpenMinis 的变更摘要在"动作后"场景更细。

六、选型框架(给油猴/插件项目的决策依据)

场景推荐流派理由
长任务、重复性操作a11y 树element_index 稳定,不易被页面刷新破坏
快速一次性任务DOM 树(骨干提取)便宜、零依赖,OpenMinis get_backbone 可直接抄
复杂动态 UI / canvas / iframe纯视觉只有图能解决,认账 token 成本
油猴脚本(页面内注入)DOM 树为主页面 JS 拿不到浏览器原生 a11y 树,要么重算(重)要么自己压缩 DOM(轻)。油猴场景下 OpenMinis 路线性价比最高;a11y 树需要引入 wasm 或 JS 版 ARIA 计算,成本高

关键权衡:油猴里三种都能做,但——纯视觉最贵、a11y 最稳但最重(要自带树计算)、DOM 骨干树最轻但最脆。做产品先选"够用且便宜"的 DOM 骨干树,遇到痛点再局部上 a11y 或视觉。

参考

  • 本机 Codex CUA 源码:C:\Users\18086\AppData\Local\OpenAI\Codex\runtimes\cua_node\4004642ff3fabdc7\bin\node_modules\@oai\
  • OpenMinis 浏览器 agent:C:\Users\18086\.workspace\git program\OpenMinis\src\ios\Agent\BrowserUse\
  • 公开逆向项目:JimLiu/decode-codex(macOS,解 ChatGPT.app 的 asar)
  • 关联:Windows 桌面自动化 Computer Use (Codex Computer Use via node_repl)

本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/AI apps