浏览器操作链路 (Browser Use Pipeline) 指的是在纯前端无沙箱或 iframe 隔离环境下,由大语言模型(Large Language Model, LLM)智能体(Agent)感知目标网页文档对象模型(Document Object Model, DOM)结构,并通过跨域通信协议执行交互动作(如点击、键入、滚动、提取)的端到端执行回路。

在 inpage-agent 项目中,系统采用了 DOM 骨干树 (DOM Backbone Tree) 感知路线与基于 postMessage 的双向通信架构,使 Agent 能够直接操作自身在虚拟文件系统(Virtual File System, VFS)中生成并在工作区 iframe 渲染的 HTML 页面。


1. 核心架构与协议契约

[Agent Runtime (runtime.ts)]
       │
       ▼ tools: AGENT_TOOLS
[Tools Dispatch (tools.ts)] ── (容错层兼容直接调用 BROWSER_ACTIONS)
       │
       ▼
[browserUseTool.ts] ── 解析校验 action 与参数
       │
       ▼
[iframeHost.ts] ── 定位 #ws-html-iframe,管理 id 与 15s 超时队列
       │  postMessage({ type: '__browser_use_action__', id, action, params })
       ▼
[Iframe 页面] ── srcdoc 自动注入 browserAgent.ts
       ├─ get_backbone: 7 步裁剪生成 DOM 骨干树(省 Token 感知)
       ├─ click / type: 模拟原生事件与受控组件 Value Setter 穿透
       ├─ scroll / execute_js / get_readable / wait_for_dom_stable
       └─ window.parent.postMessage({ type: '__browser_use_result__', id, ok, text, screenshot })

1.1 动作协议 (Action Schema)

系统定义了 12 项标准化动作:

  • get_backbone: 提取 DOM 骨干树摘要,返回带有层次缩进、选择器(sel)、尺寸边界(rect)和短文本的精简树结构,是操作前的关键感知步骤。
  • click: 触发完整鼠标事件序列(mouseover → mousedown → mouseup → click),支持 CSS 选择器或坐标兜底(elementFromPoint)。
  • type: 写入输入框文本,通过 HTMLInputElement.prototype 的原生 Setter 穿透 React / Vue 受控组件机制,逐字符派发输入事件。
  • scroll / scroll_and_collect: 页面滚动与无限滚动去重收集。
  • execute_js: 页面上下文内执行原生 JS 代码,支持 await 与 return。
  • wait_for_dom_stable: 基于 MutationObserver 探测页面 DOM 变更梯度,待稳定后回传变更摘要。

2. 踩坑记录与排障要点 (Pitfalls & Troubleshooting)

2.1 坑点 1:系统提示词遗漏导致 Agent 无法主动感知工具能力

  • 现象 (Symptom): 用户提出「点那个按钮」或「往输入框输入 xxx」时,Agent 未能使用 browser_use,而是尝试编造回复或使用 run_js 执行宿主浏览器脚本。
  • 根因 (Root Cause): 前端 runtime.ts 的 buildSystemPrompt() 硬编码了 read_file、write_file、run_js 等工具列表,却未包含 browser_use 的说明。LLM 接收到的系统提示词引导与 Schema 存在语义偏差。
  • 修复措施 (Resolution): 在 buildSystemPrompt() 中明确加入 browser_use 工具描述及其 12 种动作的建议使用流程(如推荐操作前先调用 get_backbone)。

2.2 坑点 2:模型幻觉将动作名称作为顶层工具调用

  • 现象 (Symptom): 当用户指令为「先 get_backbone 看下页面结构」时,部分模型可能直接输出 <tool_call name="get_backbone"> 或 {"name": "click"},导致工具分发器报 Error: 未知工具 "get_backbone"。
  • 根因 (Root Cause): normalizeToolName 仅校验了 ['read_file', 'write_file', 'edit_file', 'list_files', 'run_js', 'browser_use'] 白名单,未考虑模型将 action 误作为 tool name 的边界情况。
  • 修复措施 (Resolution): 在 tools.ts 中引入协议定义的 BROWSER_ACTIONS 列表,当检测到工具名称属于 12 个动作之一时,自动将调用重定向为 browser_use 并将动作名注入参数结构:

    if ((BROWSER_ACTIONS as readonly string[]).includes(cleanName)) {
      const merged = { action: cleanName, ...args };
      return executeBrowserUseTool(JSON.stringify(merged));
    }

2.3 坑点 3:HTML 预览视图模式与 Iframe 存在性

  • 现象 (Symptom): Agent 提示 未打开 HTML 预览:请先在预览抽屉打开一个 HTML 文件。
  • 根因 (Root Cause): 工作区支持「只读预览(View)」与「代码编辑(Edit)」模式。若文件当前处于 CodeMirror 编辑器状态,DOM 中不存在 #ws-html-iframe 容器。
  • 认知与使用规范: 用户需确保中间主工作台打开该 .html 文件且处于「预览」模式,沙箱 Iframe 才会加载并注入监听脚本。

3. 端到端 Playwright 自动化验证规范 (E2E Verification)

在 app/tests/verify-user-path.mjs 中固化了全链路自动化验收流程:

  1. 生成 HTML: 对话框输入「写一个带按钮和输入框的 test.html」,Agent 调用 write_file 写入虚拟文件系统并自动/手动在工作区打开。
  2. 预览抽屉渲染: 校验 #ws-html-iframe 成功挂载,srcdoc 中包含沙箱虚拟存储与 Browser Agent 注入脚本。
  3. 结构感知 (get_backbone): 发送指令「先 get_backbone 看下页面结构」,断言 Agent 返回 DOM 骨干树并成功识别按钮与输入框选择器。
  4. 键盘键入 (type): 发送指令「往输入框输入 Playwright测试内容」,通过 Playwright 的 frameLocator 校验 iframe 内部 input 元素的真实 value === "Playwright测试内容"。
  5. 鼠标点击 (click): 发送指令「点那个按钮」,通过 Playwright 校验 iframe 内部按钮文字切实发生变化(如变为「已成功点击」)。

4. 多文件工作区标签页体系 (Workspace Multi-Tab System)

为了让用户与智能体在多文件、多网页任务中保持清晰的工作上下文,系统在主工作台顶部设计并实现了多标签页容器(Multi-Tab Container):

  • 状态模型: openTabs: WorkspaceTab[],区分本地 VFS 文件(isUrl: false)与外部网络页面(isUrl: true)。
  • 生命周期管理:

    • openFile(file, mode): 文件已存在时激活并切页;新文件则 push 进 openTabs 并自动高亮。
    • openUrl(url, mode): 提取 URL 主机与路径简写作为 Tab 标题,注册网络页面 Tab。
    • switchToTab(tabId): 支持脏检查拦截(Unsaved Changes Guard),未保存时弹窗确认保存或丢弃后再平滑切换。
    • closeTab(tabId): 关闭目标标签页,自动激活相邻最近的 Tab;当全部标签页关闭时,重置工作台并平滑淡入主仪表盘(Dashboard)。

5. Agent 远程自动接管体系 (open_in_workspace)

5.1 痛点与解决思路

传统网页 Agent 往往依赖用户在界面中手动寻找文件、点击打开抽屉后才能执行交互,体验割裂。
通过在 Agent Runtime 中引入 open_in_workspace 工具与前端 workspaceBridge,实现智能体的主动唤起与自动化接管:

  1. 统一命令桥接 (Workspace Bridge): workspaceBridge.ts 提供无依赖解耦通道,允许 Agent 工具层安全调度 UI 视图。
  2. 本地文件自动挂载: Agent 写入 *.html 后,直接调用 open_in_workspace({ path: "test.html" }),自动在 Tab 栏创建标签,将视图切换为 view 模式并立即加载沙箱 iframe,注入 Browser Agent 监听器。
  3. 外部 URL 代理接管: Agent 调用 open_in_workspace({ url: "https://example.com" }) 时,系统自动通过 Go 轻量网关 /v1/proxy?url=... 代理抓取并重写 <base href="..."> 标签,将 BROWSER_AGENT_SCRIPT 注入页面。绕过浏览器的 X-Frame-Options: DENY 与跨域 postMessage 限制,使智能体能够在同一工作台中无缝对真实 Web 页面进行感知与自动化操作。

5.2 避坑要点:Go 反向代理与本地回环网络陷阱

  • 现象: 本地环境配置系统代理(如 7890 端口)时,Go Gateway 代理抓取 localhost 或 127.0.0.1 页面会报错 502 / Connection Refused。
  • 根因: Go 语言 http.DefaultTransport 在读取到环境变量 HTTP_PROXY 时,会将所有 HTTP 请求全部转发给代理服务,包括回环地址。
  • 修复措施: 在网关代理处理器中对目标 URL 识别,若属于 127.0.0.1、localhost 或私有子网,显式指定不走系统代理的专用 &http.Transport{Proxy: nil}。

6. 三栏可拖拽分界与容器查询自适应排版 (Three-Column Resizable Layout & Container Query Typography)

为了使用户在桌面端更自如地分配「文件管理」、「工作台编辑/预览」与「Agent 对话」的视野,系统构建了带有硬性约束的三栏边界拖拽体系:

  • HTML5 PointerCapture 拖拽手柄:

    • 在左栏与工作台、工作台与右栏间分别嵌入 4px 宽的分界手柄(#resizer-left、#resizer-right),并将其鼠标交互热区通过伪元素扩大至 12px,极大提升光标抓取手感;
    • 拖拽激活时为 body 增加 resizing-active,将内部所有 iframe 的 pointer-events 临时设为 none,避免光标滑入沙箱导致全局拖拽事件丢失。
  • 最小/最大范围约束与中央工作台保护:

    • 左栏(文件空间): 最小 180px,最大 420px,默认 260px;
    • 右栏(Agent 对话): 最小 280px,最大 580px,默认 420px;
    • 中央工作台: 设定 360px 安全保底宽度,左右拖拽均以 window.innerWidth - activeOther - 360 为硬性上界,确保代码编辑器与预览区永不被压垮。
    • 双击重置与记忆: 支持双击拖拽手柄一键重置默认宽度,并利用 localStorage 实现刷新保持。
  • CSS 容器查询 (@container) DOM 排版适配:

    • 传统媒体查询基于整个屏幕视口,无法针对单个侧栏被拖窄时的内部组件提供精准响应。
    • 系统为左栏、中央舞台、右栏声明独立的 container-type: inline-size:

      • 左栏 <= 220px: 自动隐藏文件列表的体积标签(.file-size),紧凑化条目内边距,确保文件名具有完整的可视截断空间;
      • 右栏 <= 340px: 自动微调对话气泡字号、时间戳和底部输入框内边距,避免密集卡片挤压;
      • 工作台 <= 520px: 自动将顶部保存与工具按钮收敛为纯图标模式,缩减标签页最大宽度,防止多 Tab 溢出换行。

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