Pi Agent 浏览器控制能力

给 Pi 加了一套浏览器工具,让它能像人一样操作网页

2026-06-04

一句话

开发了一个 Chrome 扩展作为浏览器控制的执行端,通过 HTTP Bridge 连接到 Pi Agent,以 9 个 AgentTool 的形式注册。 Pi Agent 通过这些工具可以导航网页、阅读页面结构、截图理解画面、点击按钮、填写表单。 已用本地 Qwen3.5-122B-int4 完成了多站点账号注册等端到端任务。

新增了什么

Pi Agent (已有) │ │ 调用 AgentTool ▼ ┌─────────────────────────────────────────┐ │ browser-tools.ts (新增 · 9 个工具) │ │ 导出 createBrowserTools(bridge) │ │ 返回 AgentTool[] 直接注册到 Pi │ └────────────────┬────────────────────────┘ │ bridge.sendCommand() ▼ ┌─────────────────────────────────────────┐ │ bridge-server.ts (新增 · HTTP 中间层) │ │ Express · 127.0.0.1:17170 │ │ GET /api/next · POST /api/result │ └────────────────┬────────────────────────┘ │ 扩展轮询 (800ms) ▼ ┌─────────────────────────────────────────┐ │ Chrome Extension (新增 · MV3) │ │ ├ background.js 命令调度 / CDP │ │ ├ accessibility-tree.js 页面结构化 │ │ └ visual-indicator.js 光标/发光/Stop │ └─────────────────────────────────────────┘

集成方式:const tools = createBrowserTools(bridge) → 传入 AgentHarness 的 options.tools。和 Pi 现有的工具(calculate、get_current_time 等)同级。

新增的 9 个浏览器工具

工具名 底层实现 做什么 / 返回什么
screenshot CDP Page.captureScreenshot 截取当前页面,返回 ImageContent(base64 PNG)。多模态模型可以直接"看"。
read_page 注入 JS 脚本 生成 Accessibility Tree —— 每个元素一行,带 [ref_id]。支持 filter="interactive" 只返回可操作元素。默认限制 30K 字符。
click CDP Input.dispatchMouseEvent 可信点击(isTrusted=true)。传 ref_id 或 x,y。元素不在视口内时自动 scrollIntoView。
type 注入 nativeValueSetter 输入文本,兼容 React/Vue(不走 CDP insertText)。支持 clearFirst 清空已有内容。
key CDP Input.dispatchKeyEvent 按键:Enter、Tab、Escape、Backspace、方向键等。
navigate chrome.tabs.update 导航到 URL,等待页面加载完成(监听 status=complete)再返回。
new_tab chrome.tabs.create 新建标签页,自动归入 "🤖 Pi Agent" Tab Group。
scroll 注入 JS 脚本 按像素滚动(y=300 向下)或按 ref_id scrollIntoView。
wait Node.js setTimeout 等待指定毫秒。不经过 Bridge,在 Node 端直接执行。

页面理解:两种模态互补

Accessibility Tree(read_page)

注入到所有页面的 content script,遍历 DOM 生成结构化文本。每个元素分配 ref_id,Agent 通过 ref_id 精确操作。

// 输出示例 navigation [ref_1] link "首页" [ref_2] href="/" main [ref_3] textbox [ref_4] placeholder="搜索..." button "搜索" [ref_5] combobox [ref_6] option "选项A" (selected)

适合:表单操作、精确点击、结构化数据提取。

Screenshot(截图)

CDP 截图 → base64 PNG → 作为 ImageContent 返回给多模态模型。模型直接"看"页面。

什么时候有用?

  • • 页面太大,Accessibility Tree 截断了
  • • 弹窗、CAPTCHA 等 a11y tree 无法表达的内容
  • • 需要理解布局和视觉上下文

实测:ProtonMail 的付费升级弹窗就是靠截图识别并关闭的。

用户侧可视化

用户打开浏览器时能看到 Agent 在做什么,不会觉得"浏览器自己在动但不知道为什么"。

虚拟光标

跟随点击目标平滑移动

发光边框

脉冲动画,表示"Agent 在操控"

■ Stop

Stop 按钮

底部居中,随时可终止

🤖 Pi Agent

Tab Group

Agent 控制的标签页自动归组

踩过的坑

1

正式版 Chrome 禁用了 --load-extension

日志里一行 --load-extension is not allowed in Google Chrome, ignoring。自动化测试必须用 Chrome for Testing(通过 npx @puppeteer/browsers install chrome@stable 安装)。

2

CDP insertText 在 React 输入框中无效

CDP 的 Input.insertText 修改了 DOM 但不触发 React 的 synthetic event,表单提交时字段值为空。改用注入脚本 nativeInputValueSetter.call(el, value) + dispatch input/change 事件。这是 OpenAI Codex Chrome Bridge 的同款方案。

3

页面导航导致 CDP Debugger 断开

点击链接或提交表单触发页面跳转时,chrome.debugger 会被自动 detach,后续所有 CDP 命令全部卡死。解决:navigate 前主动 detach,所有 CDP 调用加重试逻辑(检测到 detach 时自动重新 attach)。

4

大页面 Accessibility Tree 撑爆 Context

Wikipedia 文章页面的 a11y tree 有 396K 字符(~127K tokens),直接超出 131K context。解决:默认限制 30K 字符并截断(而非报错),旧截图自动替换为占位文本,溢出时删除最旧消息对。

5

元素在视口外时坐标为负

getBoundingClientRect() 对视口外的元素返回负坐标,CDP 点击打到了画面外。解决:getElementCoords 检测坐标是否在视口内,不在则先 scrollIntoView

测试结果

全部使用本地 Qwen3.5-122B-A10B-int4 模型(local-gpu:30001),无任何外部 API 调用。

✅ Wikipedia 搜索与阅读

导航 → 搜索 "artificial intelligence" → 阅读第一段

9 步 · ~40s
✅ Hacker News 多页导航

导航 → 点击 "past" 链接 → 读取昨天热门文章

5 步 · ~20s
✅ httpbin 表单填写与提交

填写文本框 → 选择 radio → 勾选 checkbox → 提交 → 读取 JSON 响应

9 步 · ~30s
✅ 多站点任务:临时邮箱 + HN 账号注册

GuerrillaMail 取邮箱 → HN 注册(自适应处理用户名长度限制)→ 查看 profile

34 步 · ~100s
⚠️ ProtonMail 注册(到 CAPTCHA 停止)

选免费计划 → 填用户名(✓ 验证通过)→ 填密码 → 提交 → 跳过付费弹窗 → 遇拼图 CAPTCHA → 报告阻碍

34 步 · ~90s

技术来源

基于对 OpenAI Codex 和 Anthropic Claude 两款 Chrome 扩展的逆向分析,提取了各自最优的技术方案:

技术点 来源 选择理由
Accessibility Tree 页面理解Claude 扩展比 Codex 的 CSS selector 匹配更结构化,ref_id 对 LLM 更友好
CDP 可信点击两家都用mouseMoved → mousePressed → mouseReleased,isTrusted=true
文本输入(nativeValueSetter)Codex BridgeCDP insertText 在 React 框架中无效,必须用注入脚本
CDP 截图Claude 扩展Page.captureScreenshot 比 captureVisibleTab 更灵活
HTTP 轮询通信Codex Bridge比 WebSocket 更稳定(SW 被杀后自动恢复),实现更简单
SW 保活Codex 扩展chrome.alarms 每 24s 触发一次,防止 MV3 的 30s 空闲超时

下一步:双模型分层

当前测试用单一弱模型同时做规划和执行。更合理的架构是分层:

Pi Agent 规划层

拆解任务、监控进度、异常重规划。不碰 ref_id 和 CDP 细节。

浏览器执行层(122B-int4)

接收单步指令 + 当前页面,操控浏览器,返回结果摘要。Context 始终很短。

弱模型擅长"看到搜索框→点→输入→提交",但不擅长跨页面规划。分层让各自发挥所长。

项目文件

~/daily/pi-browser-agent/ ├── extension/ # Chrome 扩展(新增) │ ├── manifest.json # MV3 manifest │ ├── background.js # 命令调度 / CDP / Tab Group │ ├── accessibility-tree.js # 页面 → 结构化文本 + ref_id │ └── visual-indicator.js # 光标 / 发光边框 / Stop 按钮 ├── src/ # Node.js TypeScript(新增) │ ├── browser-tools.ts # 9 个 AgentTool 定义 │ ├── bridge-server.ts # HTTP 中间层 │ ├── chrome-launcher.ts # Chrome for Testing 启动器 │ ├── types.ts # 兼容 Pi AgentTool 接口 │ ├── standalone-agent.ts # 独立测试用 agent loop │ ├── index.ts # 公开 API 导出 │ └── main.ts # 入口 ├── package.json └── tsconfig.json