跳转到内容

15. 内置浏览器与远程能力

Craft 的内置浏览器不是打开外链按钮,而是一套 session-owned Chromium automation surface:模型可以导航、读 accessibility snapshot、交互、截图、看 console/network/download,并把窗口交还用户。远程模式又把这套能力从服务端反向代理到某个在线 Electron 客户端。

15.1 为什么既有 Source 又有 Browser

Section titled “15.1 为什么既有 Source 又有 Browser”

Source 适合稳定、重复、结构化集成;Browser 适合:

  • 一次性 UI 工作;
  • 没有 API/MCP 的产品;
  • 需要用户现有登录 cookie;
  • Canvas/复杂前端;
  • 临时绕过 source setup/auth 故障。

内置指南也提醒:重复/团队化/自动化任务仍优先 source。Browser 的 UI selector/流程更脆弱。

Agent 只看到一个 browser_tool,schema:

{ command: string | string[] }

而不是几十个独立工具。browser-tool-runtime.ts 解析命令:

open / windows / focus
navigate / back / forward
snapshot / find
click / click-at / drag
fill / type / select / key / scroll
set-clipboard / get-clipboard / paste
wait / console / network / downloads
screenshot / screenshot-region / window-resize
evaluate / upload
release / hide / close

源码:browser-tool-runtime.tsbrowser-tools.md

优点:

  • tool schema 很小,省 token;
  • help/parse/batch 可复用 CLI 语法;
  • 新 verb 不改变 provider tool schema;
  • 单次可批处理多步。

代价:

  • 再造 tokenizer/quote/escape;
  • 细粒度 permission 较难;
  • 编译期类型弱;
  • 模型参数错误到运行期才发现。

支持单双引号、反斜线 escape、分号 batch;引号内分号不是分隔符。Batch 左到右执行,但遇到可能导航的命令(navigate/click/back/forward)自动停止,原因是下一页的 @eN ref 会失效。

这是一个很好的“默认正确性”设计:模型想一口气填两格再 click 可以;click 后继续用旧 ref 则被阻止。

flowchart LR
O["open"] --> N["navigate"]
N --> S["snapshot"]
S --> F["find / inspect @refs"]
F --> I["click/fill/select"]
I --> V["snapshot/screenshot verify"]
V -->|"next state"| F
V --> R["release/hide/close"]

snapshot 返回 accessibility tree 和临时 refs;find 在 role/name/value/description 中搜。DOM变化后 ref 可能 stale,错误会提示重新 snapshot。

Google Sheets 等内容在 canvas 上,accessibility/DOM refs不完整。Browser 提供:

  • annotated screenshot;
  • click-at
  • drag
  • keyboard navigation;
  • clipboard TSV paste/copy;
  • export URL。

这是一种多模态 fallback:优先语义 DOM,缺失时才用像素坐标,避免所有操作都变成脆弱视觉点击。

Electron main 的 browser-pane-manager.ts 约 3,600 行,拥有真实 WebContentsView 与:

  • instance registry;
  • session binding/owner;
  • page + toolbar + overlay view;
  • navigation/interaction/accessibility script;
  • screenshot/console/network/download;
  • cookie/session partition;
  • window geometry/theme;
  • Electron permission handler;
  • lifecycle/reuse。

Renderer 只能通过受控 main/preload bridge操作,不直接拿 WebContents。

Browser instance 记录:

  • ownerType: session | manual
  • ownerSessionId
  • boundSessionId
  • workspaceId
  • agent control overlay state。

一次 session turn结束时可 unbind 但保留 window 给用户/下轮;同 workspace 的本地 session可按策略复用 manual window。跨 workspace 不复用,防 cookie/页面状态串线。

stateDiagram-v2
[*] --> SessionOwned: open by agent
SessionOwned --> Manual: release/unbind
Manual --> SessionOwned: same workspace/session rebind
SessionOwned --> Hidden: hide
Hidden --> SessionOwned: focus/open
SessionOwned --> [*]: close/destroy
Manual --> [*]: close

release 只撤 agent overlay、保留窗口给用户;hide 保留状态但不显示;close 销毁。三者不能混成一个 stop。

tool start 时 SessionManager/BPM 设置 setAgentControl(sessionId,{intent,displayName}),让用户知道窗口当前被 Agent 操作;turn结束、显式 release或 abort 时清。

这是安全/可理解性功能:自动点击发生在真实登录会话里,用户必须知道谁在控制。Overlay 状态也要在 error cleanup 中释放,避免“Agent 已停但窗口一直锁住”。

内置指南说明 browser tools 在 Safe/Explore 默认允许。这是一个重要语义:Safe 模式的“只读”对网页并不严格成立——click/fill 可能提交表单、发消息或购买。

实际风险缓解依赖:

  • 工具 guide要求确认/验证;
  • browser command/runtime input validation;
  • Electron page permission denylist;
  • agent overlay和用户可见窗口;
  • 高风险页面自身确认;
  • remote evaluate/upload额外禁止。

若产品要求强“只读浏览”,应把 navigate/snapshot/screenshot 与 click/fill/select 分成权限类别,而不能把整个 browser_tool 视为 read-only。

BrowserPaneManager 对 Electron session 设置 permission check/request handler,只允许明确集合,默认拒绝摄像头/麦克风/定位等;window.open只允许 HTTP/HTTPS,拒绝未知 protocol,并控制新窗口归属。

页面是外部不可信代码:

  • executeJavaScript 返回值必须转 plain serializable snapshot;
  • 不能把 Electron native object直接跨 WS/IPC;
  • download filename/path要 sanitize并限制 session downloads;
  • upload path要检查敏感位置;
  • console/network日志要限量/脱敏。
Agent browser_tool
→ executeBrowserToolCommand(parser)
→ BrowserPaneFns callback
→ SessionManager-wired IBrowserPaneManager
→ Electron BrowserPaneManager
→ WebContentsView / CDP-like JS
→ structured result/image
→ tool_result

browser-tools.ts 只知道函数接口,不依赖 Electron。Headless 时可换 RemoteBrowserPaneManager 或报告无能力。

Headless server运行 Agent,但没有 GUI/cookie;用户 Electron 有 GUI,但不拥有远程 session文件。系统选择“借用客户端浏览器能力”:

sequenceDiagram
participant A as Remote Agent
participant SM as Remote SessionManager
participant WS as WsRpcServer
participant P as Electron preload/main
participant BPM as BrowserPaneManager
A->>SM: browser_tool navigate
SM->>SM: choose/pin capable client
SM->>WS: invokeClient(client:browser:invoke)
WS->>P: BrowserCapabilityRequest
P->>BPM: dispatch owned method
BPM-->>P: plain result / image bytes
P-->>WS: invoke result
WS-->>SM: result
SM-->>A: tool_result

Electron握手声明 client:browser:invoke。SessionManager:

  1. 找同 workspace 的 capable clients;
  2. 为 session 选 host;
  3. pin session→client,保证一系列命令命中同一 window/cookies;
  4. client断线时清 pin;
  5. 下一次调用可重新选择或返回无能力错误。

若每命令随机客户端,snapshot 在 A、click @e1 在 B 会立刻失效。Pin 是浏览器会话一致性的必要条件。

BrowserCapabilityRequest

{
v: 1,
method: BrowserCapabilityMethod,
args: unknown[],
sessionId,
workspaceId
}

Method 白名单一一对应 IBrowserPaneManager,不是让服务端传任意 JS function name。

截图的 Node Buffer 不能直接跨 WS structured clone,于是 dispatcher转 Uint8Array wire,远端 manager再转 Buffer,见 ScreenshotResultWire

RemoteBrowserPaneManager 实现同一 IBrowserPaneManager,每 (workspaceId,sessionId) 一个实例。

invoke() 检查:

  • host client存在,否则 BROWSER_NO_CAPABLE_CLIENT
  • client仍声明 capability;
  • 组 versioned request;
  • await server→client RPC。

接口历史上有同步方法,但 WS 往返必然异步。Remote 实现对 sync方法 fire-and-forget并返回 remote-pending:<session> sentinel,同时为真正需要结果的路径增加 Async variant。这是抽象边界的一处技术债:更理想的 IBrowserPaneManager 应从一开始全 async。

远程 sessionId 可能与客户端本地 session同名,且远端 server不能访问本地其他窗口。Dispatcher 把 owner变为:

remote:<workspaceId>:<sessionId>

每个带 instanceId 的方法先 requireOwnedInstance(instanceId, ownerKey)listInstances 只返回此 owner的窗口。远程 session不能接管 manual/local window,通常获得 fresh window。

源码定位:browser-pane-manager.ts

远程 agent不能上传客户端本地路径。Remote manager直接抛 BROWSER_REMOTE_UPLOAD_NOT_SUPPORTED,提示用户把文件附到 session。否则服务端传 /tmp/x,客户端可能误读另一文件,或允许任意本机路径窃取。

远程 JS evaluation 默认可由客户端配置禁用;dispatcher返回 BROWSER_REMOTE_EVALUATE_BLOCKED。任意 evaluate比 click/fill能力强得多,可读取页面敏感 DOM/token。

远程不复用未绑定 manual/local window,降低跨 session cookie/页面泄漏。

Runtime 不只返回“ok”:

  • URL/title before/after;
  • scroll position;
  • active element;
  • selector/value assertion;
  • window ownership/visibility;
  • network/console摘要;
  • screenshot image;
  • download savePath;
  • security challenge detection。

Rich feedback让模型做闭环验证,而不是把“click方法返回成功”误判为业务成功。

故障 提示/恢复
无 desktop host 打开同 workspace 的桌面 app
host断线 清 pin,重新选择/重试
stale ref 重新 snapshot
页面导航未完成 wait network-idle/selector
Canvas 无 ref screenshot + click-at/keyboard
remote upload 让用户附文件到 session
remote evaluate禁用 用 snapshot/标准命令或客户端显式授权
ownership不匹配 拒绝,不能跨 session接管
screenshot clone失败 plain wire bytes
turn异常退出 cleanup overlay/unbind/browser lease

内置浏览器把 GUI自动化做成 session-owned resource,而远程桥进一步证明 capability-based architecture 的价值:Agent runtime与能力宿主可以不在一台机器,只要协议明确所有权、限制和结果序列化。

下一章从 renderer 视角看产品表面:Electron、WebUI 和 Viewer 分别承担什么,UI state 为什么以纯事件投影为主。