给 Pi 加了一套浏览器工具,让它能像人一样操作网页
2026-06-04
开发了一个 Chrome 扩展作为浏览器控制的执行端,通过 HTTP Bridge 连接到 Pi Agent,以 9 个 AgentTool 的形式注册。 Pi Agent 通过这些工具可以导航网页、阅读页面结构、截图理解画面、点击按钮、填写表单。 已用本地 Qwen3.5-122B-int4 完成了多站点账号注册等端到端任务。
集成方式:const tools = createBrowserTools(bridge) → 传入 AgentHarness 的 options.tools。和 Pi 现有的工具(calculate、get_current_time 等)同级。
| 工具名 | 底层实现 | 做什么 / 返回什么 |
|---|---|---|
| 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 端直接执行。 |
注入到所有页面的 content script,遍历 DOM 生成结构化文本。每个元素分配 ref_id,Agent 通过 ref_id 精确操作。
适合:表单操作、精确点击、结构化数据提取。
CDP 截图 → base64 PNG → 作为 ImageContent 返回给多模态模型。模型直接"看"页面。
什么时候有用?
实测:ProtonMail 的付费升级弹窗就是靠截图识别并关闭的。
用户打开浏览器时能看到 Agent 在做什么,不会觉得"浏览器自己在动但不知道为什么"。
跟随点击目标平滑移动
脉冲动画,表示"Agent 在操控"
底部居中,随时可终止
Agent 控制的标签页自动归组
--load-extension日志里一行 --load-extension is not allowed in Google Chrome, ignoring。自动化测试必须用 Chrome for Testing(通过 npx @puppeteer/browsers install chrome@stable 安装)。
CDP 的 Input.insertText 修改了 DOM 但不触发 React 的 synthetic event,表单提交时字段值为空。改用注入脚本 nativeInputValueSetter.call(el, value) + dispatch input/change 事件。这是 OpenAI Codex Chrome Bridge 的同款方案。
点击链接或提交表单触发页面跳转时,chrome.debugger 会被自动 detach,后续所有 CDP 命令全部卡死。解决:navigate 前主动 detach,所有 CDP 调用加重试逻辑(检测到 detach 时自动重新 attach)。
Wikipedia 文章页面的 a11y tree 有 396K 字符(~127K tokens),直接超出 131K context。解决:默认限制 30K 字符并截断(而非报错),旧截图自动替换为占位文本,溢出时删除最旧消息对。
getBoundingClientRect() 对视口外的元素返回负坐标,CDP 点击打到了画面外。解决:getElementCoords 检测坐标是否在视口内,不在则先 scrollIntoView。
全部使用本地 Qwen3.5-122B-A10B-int4 模型(local-gpu:30001),无任何外部 API 调用。
导航 → 搜索 "artificial intelligence" → 阅读第一段
导航 → 点击 "past" 链接 → 读取昨天热门文章
填写文本框 → 选择 radio → 勾选 checkbox → 提交 → 读取 JSON 响应
GuerrillaMail 取邮箱 → HN 注册(自适应处理用户名长度限制)→ 查看 profile
选免费计划 → 填用户名(✓ 验证通过)→ 填密码 → 提交 → 跳过付费弹窗 → 遇拼图 CAPTCHA → 报告阻碍
基于对 OpenAI Codex 和 Anthropic Claude 两款 Chrome 扩展的逆向分析,提取了各自最优的技术方案:
| 技术点 | 来源 | 选择理由 |
|---|---|---|
| Accessibility Tree 页面理解 | Claude 扩展 | 比 Codex 的 CSS selector 匹配更结构化,ref_id 对 LLM 更友好 |
| CDP 可信点击 | 两家都用 | mouseMoved → mousePressed → mouseReleased,isTrusted=true |
| 文本输入(nativeValueSetter) | Codex Bridge | CDP insertText 在 React 框架中无效,必须用注入脚本 |
| CDP 截图 | Claude 扩展 | Page.captureScreenshot 比 captureVisibleTab 更灵活 |
| HTTP 轮询通信 | Codex Bridge | 比 WebSocket 更稳定(SW 被杀后自动恢复),实现更简单 |
| SW 保活 | Codex 扩展 | chrome.alarms 每 24s 触发一次,防止 MV3 的 30s 空闲超时 |
当前测试用单一弱模型同时做规划和执行。更合理的架构是分层:
拆解任务、监控进度、异常重规划。不碰 ref_id 和 CDP 细节。
接收单步指令 + 当前页面,操控浏览器,返回结果摘要。Context 始终很短。
弱模型擅长"看到搜索框→点→输入→提交",但不擅长跨页面规划。分层让各自发挥所长。