为什么我为浏览器构建了文件系统
Source: Dev.to
当今三大主流方法(以及它们为何不匹配)
| Approach | Drawbacks |
|---|---|
| Screenshots + vision models | • 在每次操作时都燃烧视觉 token • 为每次交互增加完整的往返时间 • 当坐标变化时会出现静默失败(例如,Cookie 横幅移动了按钮) |
| CSS selectors / XPath | • 结构脆弱(#main > div:nth-child(3) … 在添加包装元素时会失效) • 依赖开发者添加测试 ID( [data-testid="submit"]) • 代理必须在原始 HTML 上推理——噪声数千个 token |
| Coordinate‑based clicks | • 受分辨率、视口、缩放和响应式布局的影响 • 任何不可控变量都会成为失败模式 |
Common problem: 所有这三种方法都迫使代理使用一种并非为程序化导航设计的表示形式。
可访问性 (AX) 树 – 开箱即用的解决方案
浏览器已经解决了 “在不观看页面的情况下进行导航。” 屏幕阅读器使用的可访问性树具有:
- 确定性
- 语义化
- 紧凑
每个按钮都知道自己是按钮,每个链接都携带其 href,每个输入都有标签和类型。没有不可见的包装 <div>,没有 CSS 噪声,也没有依赖布局的坐标。
AX 树是代理所需的低熵结构化信号。问题在于 如何暴露它。
将 AX 树映射到文件系统
AX 树具有自然的层次结构:
- 容器(导航、主要内容、侧边栏、表单) → 目录
- 交互元素(按钮、链接、输入框) → 文件
这可以干净利落地映射到文件系统——而且每个大型语言模型已经会操作文件系统了。
| 命令 | 用途 |
|---|---|
ls | 列出页面上的内容 |
cd | 进入某个章节 |
cat | 检查元素 |
grep | 搜索 |
find | 按类型发现 |
click | 交互 |
text | 批量提取 |
这些命令出现在每个模型的训练数据中 → 零样本可用性。
示例会话
dom@shell:$ cd %here%
✓ Entered tab 386872589
Title: Wikipedia
URL: https://www.wikipedia.org/
dom@shell:$ ls
[d] main/
[d] contentinfo/
dom@shell:$ cd main
dom@shell:$ tree 2
main/
├── [d] top_languages/
│ ├── [x] english_7141000_articles_link
│ ├── [x] deutsch_3099000_artikel_link
│ ├── [x] français_2740000_articles_link
│ └── …
├── [d] search/
│ └── [x] search_input
└── [x] read_wikipedia_in_your_language_btn
页面现在是一个目录树。
submit search_input "Artificial intelligence"
导航后,树会自动刷新,你看到的是文章的文件系统。
没有截图。没有坐标。没有选择器。
清理原始 AX 树
原始 AX 树很嘈杂:数百个包装节点(role=generic、role=none、未命名的 <div>)仅用于 CSS 布局,而非语义。若不进行过滤,你会看到 generic_1、generic_2 … 这类没有实际意义的节点。
DOMShell 的 VFS 映射器(vfs_mapper.ts) 递归地扁平化非语义节点,将它们的子节点提升:
- 如果
role=generic节点只有一个子节点,则用该子节点替代它。 - 可见元素会根据其可访问名称和角色生成名称(
submit_btn、contact_us_link、email_input)。 - 对重复的名称使用
_2、_3等进行消歧。
设计决策: 最小化节点膨胀可最大化代理的信噪比。每个被扁平化的包装节点都是模型不必浪费推理的代币。
架构 – 三个清晰分离的组件
-
Chrome 扩展(内核)
- 后台 Service Worker 负责运行外壳:命令解析、通过 CDP 进行 AX 树遍历、文件系统映射、DOM 变化检测。
- 侧边面板是一个轻量终端(React + Xterm.js)——仅负责 I/O,不包含逻辑。
- 通过
chrome.debugger(Chrome DevTools Protocol 1.3)读取 AX 树,包括使用Page.getFrameTree进行跨 iframe 的发现。
-
MCP 服务器(桥梁)
- 在
localhost:3001上运行的独立 Node.js HTTP 服务器。 - 任意兼容 MCP 的客户端(Claude Desktop、Claude Code、Cursor、Windsurf、Gemini CLI)均可连接。
- 将 MCP 工具调用翻译为 Shell 命令,并通过 WebSocket(
localhost:9876)发送给扩展,随后将结果流回。 - 支持多个客户端同时连接。
- 在
-
安全层级
- 默认 只读——代理可以浏览但不能执行操作。
- 写入类命令(
click、type、scroll、js)需加--allow-write。 - 敏感命令(例如获取 cookie 的
whoami)需加--allow-sensitive。 - 域名白名单限制代理可操作的站点。
- 每条命令都会带时间戳进行审计日志记录。
- 认证令牌用于控制 WebSocket 桥接的访问。
这种分离是有意为之:你可以在不启动 MCP 服务器的情况下交互式使用 DOMShell,或者让代理在不具备点击“删除账户”等操作权限的情况下浏览你的标签页。
性能结果
我使用 Claude Opus 4.6 在 DOMShell 和 Anthropic 内置的浏览器自动化(Chrome 中的 Claude)上进行 8 次试验,覆盖 4 项任务。
指标: 工具调用次数 – 与延迟和 API 成本成正比。
| 系统 | 每任务平均调用次数 |
|---|---|
| DOMShell | 4.3 |
| Claude in Chrome | 8.6 |
结果: 调用次数降低 50 %。
最大的收益来自 内容提取:DOMShell 只用了 2 次调用(导航 + 提取),而 Chrome 中的 Claude 需要 5‑6 次。
TL;DR
- Accessibility Tree 是页面的确定性、语义化、低熵表示。
- 将其映射到虚拟文件系统为代理提供了熟悉的零样本接口(
ls、cd、click…)。 - 将非语义节点扁平化以最大化信噪比。
- 三层架构(Chrome 扩展、MCP 桥接、安全层)保持系统的灵活性和安全性。
- 实际上,DOMShell 将典型浏览任务所需的工具调用次数减半。
概述
The new approach lets the agent 定位到正确的章节 (cd main/article) and 批量提取 (text) in a single call, instead of navigating through read_page results iteratively.
为什么 DOMShell 出色
- 原始 JavaScript 执行 –
javascript_exec可以将多个 DOM 操作批量合并为一次调用。 - 复合管道 –
for + script + each管道通过遍历命令输出并在多个 URL 上重放已保存的脚本,将多页面工作流压缩为 1‑2 次调用。
影响
- 50 % reduction 在工具调用中的减少 → 在 成本 和 延迟 上直接节省。
- 对于生产代理(其中每次工具调用都是一次 API 往返),将调用次数减半是一次 有意义的运营改进。
开源与路线图
- DOMShell 是开源的(MIT)且免费。
- Roadmap: headless mode – 一个自包含的 Chromium 进程,代理可以直接启动,用于 CI 流水线和服务器端自动化,无需可视化浏览器。
复合效率提升
for + script + each 管道是实现真正收益的所在:
- 保存 一段命令序列为脚本。
- 在单次调用中 将其 重放 于 N 个 URL。
结果:O(2N) 次工具调用变为 O(2)。
对于在多个页面上进行研究、提取或监控的任何代理,这是一种 质的飞跃 效率提升。
快速参考
# Example: change directory and bulk‑extract text
cd main/article
bulk-extract text
浏览器就是你的文件系统。
ls它。
GitHub
项目: DOMShell – Pireno