Skip to content

✨ feat(sidecar,desktop): 添加任务级内置浏览器工具与剪枝快照 - #266

Open
CavinHuang wants to merge 21 commits into
mainfrom
codex/browser-tools-snapshot-260
Open

✨ feat(sidecar,desktop): 添加任务级内置浏览器工具与剪枝快照#266
CavinHuang wants to merge 21 commits into
mainfrom
codex/browser-tools-snapshot-260

Conversation

@CavinHuang

@CavinHuang CavinHuang commented Aug 20, 2026

Copy link
Copy Markdown
Owner

结果

完成任务级内置浏览器控制的纵向闭环:模型直接调用 25 个 mcp__browser__* 宿主工具,不再依赖 Browser Skill、Node REPL 或临时编写 BrowserClient 代码。

核心能力

  • 任务级 BrowserSession 与 Agent Tab 锁定;仅显式 switch_tab 切换,任务结束自动关闭未被用户接管的 Agent Tab
  • tab/navigation:list/open/switch/navigate/back/forward/reload
  • AX Tree 剪枝快照:稳定单调 @eN、scope_ref/cursor 宿主缓存分层读取、旧快照拒绝、跨源 iframe、cursor/onclick/tabindex/contenteditable DOM 补充
  • ref 动作:click/double_click/hover/fill/type/press/select/check/scroll;Desktop 以 snapshotId + backendDOMNodeId 精确解析并做可见性、可用性和遮挡检查
  • 观察:普通截图与 annotated=true 同源引用标注截图;标注在隔离画布完成,不修改页面 DOM
  • 托管 I/O:file chooser 上传、下载等待与任务文件引用
  • 安全凭据:仅列元数据,密码从 Vault 直达 Desktop 填充,不进入参数、结果、trace 或 audit
  • dialog 显式读取/处理;有界隔离世界 run_script
  • 动作后自动返回新交互快照;动作已成功但观察失败时不误报动作失败

审计收口增量一(2026-08-21:错误模型与托管 I/O)

  • 传输超时改报 executed_unknown:请求已送达 desktop、变更动作可能已执行,不再与"根本没执行"塌缩为 browser_internal_error(保持 retryable=false,重试前先观察)
  • 错误结果补 active_tab_id:失败时模型不再需要重新 list_tabs 恢复现场
  • 下载三态收口:desktop 保留 cancelled/interrupted 终态并随 download:path 返回 state;等待超时不再误报 download_failed,改返回 state:"in_progress" + download_id;download 工具新增 download_id 查询模式(只读状态,不重复点击触发)
  • 上传校验:chooser 的 is_multiple 返回值不再被忽略——单文件 input 传多文件在设置前明确拒绝(invalid_browser_request);超量文件显式报错而非静默截断

审计收口增量二(2026-08-21:CAPTCHA/MFA 协议级网关)

  • 新增 user_action_required 稳定错误码(shared 枚举 + broker 白名单)
  • policy 对 CAPTCHA 意图与新增 MFA/硬件密钥意图(otp/passkey/security key/验证口令等)deny 时携带该码,broker 透传——模型收到即停下等用户完成该步骤,不再依赖提示词约束(【内置浏览器】【安全与 I/O】托管下载、文件上传和敏感字段填充 #264 验收)
  • 支付等普通 deny 保持 action_denied;MFA 正则含 verification code,邮箱验证码流程也被保守拦下等用户(fail-closed)

已知不修(记录边界):upload 的 accept 属性校验(页面自身会拒绝不匹配类型,快照可见);DOM.setFileInputFiles 后回读 input.files(CDP 失败已带码传播);Shadow DOM 显式穿透(AX composed tree 隐式覆盖);cursor 过期细分(与 stale_target 同导向重拍快照)。

稳定性边界

  • 标注截图仅标主 Frame 当前可见元素;跨 Frame ref 仍可直接执行语义动作
  • 不提供无页面状态绑定的坐标点击/拖拽包装;普通交互优先稳定 ref
  • 子代理暂不共享主任务 BrowserSession,避免并发争用同一 Tab

验证

  • Browser Tools + Broker + policy 相关测试:20+15 pass(含新增:in_progress 下载轮询、单文件 chooser 拒绝、captcha/MFA 网关码)
  • context-assembler/computer-use 测试 40 pass;Desktop、Sidecar、Shared typecheck 通过
  • Electron 真浏览器回归已通过:精确主 Frame ref、旧快照拒绝、跨源 iframe ref、DOM 补充 ref、同源标注截图
  • 完整 Electron 套件继续执行时停在既有 WebMCP 归一化用例;对应修复 PR 🐛 fix(desktop): 修复 WebMCP 桥接工具调用 #270 已独立通过 CI

集成关系

Closes #255
Related: #251 #254 #260 #261 #262 #263 #264 #265 #268 #270 #272

把任务级 BrowserSession、Agent 标签页锁定和 AX Tree 剪枝快照下沉到宿主工具层,模型无需再通过 Node REPL 编写浏览器控制代码。保留现有 BrowserClient DOMSnapshot 路径,便于后续动作工具渐进迁移。

Constraint: 保留旧 BrowserClient DOMSnapshot 兼容路径

Constraint: 子代理不共享主任务的浏览器控制标签页

Tested: 32 个相关单元测试通过

Tested: @lume/sidecar 与 @lume/desktop 类型检查通过
为内置 Browser Tools 增加 run_script。脚本仅能在当前任务锁定的 Agent 标签页中运行,每次创建全新隔离世界,并通过现有浏览器动作确认策略执行。参数、时间和 JSON 结果均设置宿主边界,页面异常以结构化工具错误返回。

Constraint: 脚本不能控制用户标签页或其他任务标签页

Constraint: 任意脚本执行必须经过浏览器动作确认

Constraint: 返回值必须可 JSON 序列化且不超过 200000 字符

Rejected: 复用 Node REPL 注入 BrowserClient | 继续依赖模型拼装控制代码

Tested: 41 个相关单元测试通过

Tested: @lume/desktop 与 @lume/sidecar 类型检查通过
@CavinHuang CavinHuang changed the title ✨ feat(sidecar,desktop): 添加内置浏览器剪枝快照工具 ✨ feat(sidecar,desktop): 添加任务级内置浏览器工具与剪枝快照 Aug 20, 2026
浏览器工具结果包含 operation id 与 tab id 等易变字段,不能直接用于判断页面是否产生新进展。为 open、switch_tab、run_script 和错误结果提供稳定状态,供 Agent Loop 的重复动作熔断使用。

Constraint: 不改变模型可见的浏览器工具结果

Tested: bun test apps/sidecar/src/services/agent-runtime/tools/browser/create-browser-tools.test.ts

Tested: bun run --filter @lume/sidecar typecheck
@CavinHuang

Copy link
Copy Markdown
Owner Author

补充与 #254 / PR #272 的运行时熔断集成:Browser 工具现在通过 _meta.repeatGuard.state\ 提供稳定状态,重复 open 即使产生不同 operation_id/tab_id,也能按 URL、标题和 generation 判断是否没有新进展。

普通 Agent 浏览器任务直接使用 mcp__browser__* 工具,并在整条用户请求的内部迭代间复用,不再暴露或要求激活 browser:browser。用户显式引用已有浏览器页面时仍保留旧 claim 流程。

Constraint: 浏览器附件仍依赖旧 Skill 的用户 Tab 授权流程

Rejected: 仅修改 Skill 文案 | 仍会暴露 Node REPL API 猜测路径

Tested: ContextAssembler 20 tests

Tested: runtime-core Browser route test

Tested: bun run --filter @lume/sidecar typecheck
将剪枝快照中的 ref 绑定到任务锁定标签页,并提供点击、填写、按键、选择、勾选和滚动工具。每次动作完成后自动返回新快照,观察失败时保留动作成功结果,避免模型重复执行已提交动作。

Constraint: 不新增依赖,复用现有 Browser Broker locator 动作
Constraint: 标签页只能由 Agent 显式切换
Tested: 40 个相关单元测试与 sidecar typecheck
将 user_takeover_required 明确为等待用户交还控制的终止状态,禁止模型继续调用 Browser 或绕到 Computer Use。

Constraint: 只调整内置 Browser 工具指导,不改变其他能力路由
Tested: ContextAssembler 20 tests
在任务锁定标签页上补齐 navigate/back/forward/reload、双击、悬停、追加输入和 JavaScript dialog 处理。所有变更继续复用统一的动作后观察,避免模型退回脚本或 Node REPL。

Constraint: 不新增依赖,不开放坐标型无状态点击
Constraint: dialog 必须先读取 id 再显式处理
Tested: 41 个相关单元测试与 sidecar typecheck
将浏览器工具会话绑定到顶层运行结束阶段,正常完成、取消和失败都会请求 finalize;用户已接管的标签页仍由桌面端 handoff 语义保留。

Constraint: 浏览器清理失败不能覆盖 Agent 的原始运行结果

Tested: runner 与 browser tools 相关单元测试(35 项)

Tested: sidecar TypeScript 类型检查
将截图、文件选择器上传和点击触发下载收口为任务级内置工具。上传与下载在工具内部协调事件等待与点击,避免模型自行编排造成竞态。

Constraint: 上传仅接受当前任务授权路径或同一浏览器会话的下载引用

Constraint: 截图像素仅作为不持久化图像块返回,不写入文本结果

Tested: browser tools、tool registry 与 context assembler 单元测试(43 项)

Tested: sidecar TypeScript 类型检查
将上传数量和下载文件引用写入不可变 repeatGuard 状态,避免上下文压缩后丢失已完成文件操作的结果。

Tested: browser tools 单元测试(10 项)
TaTaLiao and others added 7 commits August 21, 2026 09:27
内置浏览器动作不再仅靠 role/name/nth 二次查找,而是携带 snapshotId 与 ref,由桌面运行时解析同一会话 RefMap 中的 backendDOMNodeId。动作前同时校验 Tab、导航代次、可见性、可用性和命中遮挡。

Constraint: 旧 snapshotId 的 ref 必须立即返回 stale_target,不能进入 locator 自动等待

Tested: desktop 与 sidecar TypeScript 类型检查

Tested: semantic snapshot 与 browser tools 单元测试(12 项)

Not-tested: 完整 browser-runtime E2E 在新语义 ref 断言全部通过后,停在 PR #270 修复的既有 WebMCP 归一化问题
新增当前来源凭据元数据查询与 fill_secret 内置工具。模型只传 secretId 和快照 ref;密码值由桌面保险库按来源校验后直接写入页面,不进入工具参数、结果、trace 或审计参数。

Constraint: 填充密码需要一次性 credential 确认

Constraint: secretId 只能读取当前标签页精确 origin 的密码

Tested: Browser API、policy、broker、credential vault 与内置 Browser 工具相关测试(67 项)

Tested: desktop、sidecar、shared TypeScript 类型检查
遍历 Page frame tree 并按 frameId 获取各 Frame 的 AX Tree,节点 ID 在合并前按 Frame 命名空间隔离,RefMap 同时绑定 frameId 与 backendDOMNodeId。跨域 Frame 的 @en 可直接走精确节点动作。

Constraint: 单个不可访问 Frame 失败时保留其他 Frame 快照

Tested: semantic snapshot、broker 与 browser tools 单元测试(31 项)

Tested: desktop、sidecar、shared TypeScript 类型检查

Not-tested: 完整 E2E 已通过跨域 Frame 快照与精确点击断言,随后停在 PR #270 已修复的既有 WebMCP 归一化问题
在每个 Frame 的隔离世界批量发现 cursor:pointer、onclick、tabindex 与 contenteditable 元素,通过 CDP objectId 解析 backendDOMNodeId,并合并回已有 AX 节点或生成 clickable/focusable 节点。

Constraint: 跳过原生交互标签、交互 ARIA role、隐藏节点和继承 pointer 的子节点

Constraint: 每个 Frame 最多补充 500 个节点且远程对象始终释放

Tested: semantic snapshot 单元测试(4 项)与 desktop TypeScript 类型检查

Not-tested: 完整 E2E 已通过 cursor-pointer 快照与精确点击断言,随后停在 PR #270 已修复的既有 WebMCP 归一化问题
标注截图复用最新语义快照的稳定引用,并在隔离画布中绘制,不修改页面 DOM。仅标注主 Frame 可见元素,跨 Frame 引用继续保留语义动作能力。`n`nConstraint: 不引入图像处理依赖`nConstraint: 标注截图不能与 full_page 同时使用`nTested: Browser Tools 与 Broker 单元测试(30 项)`nTested: desktop/sidecar typecheck`nTested: Electron 回归两次通过标注截图与同源引用断言`nNot-tested: 完整 Electron 套件仍被 PR #270 修复的既有 WebMCP 归一化失败阻断
scope_ref 复用宿主缓存的同一份语义快照,不重新抓取页面,不失效当前 refs,也不增加 CDP 观察压力。cursor 继续对切片后的宿主树分页。`n`nConstraint: scope_ref 必须绑定当前 snapshot_id`nRejected: 每次 scope 重新抓取 AX Tree | 连续观察会增加 CDP 抖动并意外失效 refs`nTested: 快照、Browser Tools 与 Broker 相关测试(35 项)`nTested: desktop/sidecar typecheck`nTested: Electron 回归通过 scope 子树与标注截图断言,继续停在 PR #270 对应的既有 WebMCP 用例
沿调用链审计发现四条塌缩/缺口:① 传输超时(请求已送达 desktop,
变更动作可能已执行)落为 browser_internal_error,与"根本没执行"不
可区分——现携带 executed_unknown 码;② 错误结果缺 active_tab_id,
模型恢复现场需重新 list_tabs;③ 下载超时一律误报 download_failed
(点击已成功、下载可能仍在进行),cancelled/interrupted 被合并成
failed——desktop 保留三态终态并随 download:path 返回,超时改报
in_progress+download_id,download 工具新增 download_id 查询模式
(不重复点击触发);④ chooser 返回的 is_multiple 被忽略,单文件
input 传多文件会被页面静默丢弃——设置前明确拒绝。

Constraint: executed_unknown 保持 retryable=false,重试前应先观察确认副作用
Rejected: download_failed 再细分 cancelled/interrupted 错误码 | 需动 shared 枚举+broker 白名单,message 带 state 已够模型决策
Tested: create-browser-tools.test.ts 15 pass(新增 in_progress 轮询/单文件拒绝两用例)
Tested: @lume/sidecar、@lume/desktop typecheck 通过
Not-tested: desktop downloadPath 状态透传的真 Electron 回归(改动为直通赋值)

Co-Authored-By: Claude <noreply@anthropic.com>
TaTaLiao and others added 4 commits August 21, 2026 11:14
policy 对 CAPTCHA/MFA/硬件密钥的 deny 此前塌缩为 action_denied,
与普通拒绝不可区分,模型只能靠提示词教育约束不盲目重试(#264
验收要求协议级稳定返回)。现 policy 决策携带 errorCode,broker
deny 时透传:captcha/mfa 类返回 user_action_required,模型收到
即停下等用户完成;支付等普通 deny 保持 action_denied。新增 MFA
意图判定(otp/passkey/security key/验证口令等),提示词同步该码
语义。

Constraint: MFA 正则含 verification code,邮箱验证码注册流也会被保守拦下等用户确认(fail-closed 方向)
Tested: browser-action-policy.test.ts + browser-broker.test.ts 20 pass(新增 captcha/MFA 网关用例)
Tested: context-assembler/computer-use 测试 40 pass;sidecar+shared typecheck 通过

Co-Authored-By: Claude <noreply@anthropic.com>
真实运行时验证发现工具层与 broker 出口契约错位:broker.dispatch
对所有方法过 adaptBrowserResult(extension 协议形状),list_tabs
返回 {tabs:[{id,title,url}]}(有损压缩,丢 tabId/backend/
profileKind/ownerThreadId)、create_tab 返回 {id}、dialog 返回
{dialog};工具层按 runtime 原生形状消费——ownedAgentTabs 收到
对象抛 browser_internal_error,open 拿不到 tabId,全部浏览器工具
瘫瘓。单测未拦截因 mock 模拟了原生形状,与真实 broker 从未对齐。

- descriptorResult 改无损超集:保留原 descriptor 全部字段并注入
  id 别名;extension 消费者只读 id/title/url,超集兼容
- 工具层解包三处:list_tabs/{tabs}、open/{id}、dialog/{dialog}
- open 的 repeatGuard url 改从工具入参取(归一化后 tab 无 url)
- 单测 mock 全部改为 broker 真实形状,堵住测试绿生产挂的错位
- 另给 25 个浏览器工具 runtimeMetadata 标 requiredDuringSkillScope,
  使其进入 core 注入池——此前全部落入 ToolSearch deferred 池,
  未提升的工具被模型直接调用时报 Unknown tool(与 link 工具同
  模式)

Tested: create-browser-tools/browser-broker 32 pass;@lume/sidecar typecheck 通过
Tested: Electron dev 真实链路:list_tabs/open/snapshot 全通(fill/click 的
actionability_failed 属 #268 分支定位修复域,由集成分支验证)

Co-Authored-By: Claude <noreply@anthropic.com>
非重试浏览器动作失败后,模型可通过插入新快照和无效输入操作绕过连续重复检测,直至耗尽 turn 预算。现在同一页面 generation 上相同动作与 ref 连续失败两次后锁定后续变更动作,并让等价快照使用稳定 repeat guard 状态。

Constraint: 只阻断变更动作;页面导航、切换或 generation 变化会解除锁定

Rejected: 仅降低 maxTurns | 仍会浪费完整预算且不能表达失败原因

Tested: bun test src/services/agent-runtime/tools/browser/create-browser-tools.test.ts(16 pass)

Not-tested: Electron e2e(按现场调试流程暂不执行)
文本 input 的中心点在 includeUserAgentShadowDOM:true 时命中其 UA shadow
内部节点,contains 跨 shadow 边界恒 false,导致 fill/click 对一切输入框
永久 action_denied(3s auto-wait 后报 actionability_failed)。containment
判定同时补上 shadow host 链上溯,兼容 open shadow 命中。

Constraint: 不加裸坐标点击;命中测试仍以 backendNodeId 精确解析为前提
Rejected: 仅修 containment 不关穿透——UA shadow 节点的 resolveNode 语义不稳定
Tested: browser-runtime.e2e.mjs 90 断言全绿(新增 input semanticRef fill 用例先复现后验证)
Not-tested: 真实站点页面端到端(dev 环境待人工验证)

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

【BUG】【Browser Skill】turn 语义混乱导致重复激活并诱发未公开 API 猜测

2 participants