为什么我为浏览器构建了文件系统

发布: (2026年2月27日 GMT+8 06:04)
9 分钟阅读
原文: Dev.to

Source: Dev.to

当今三大主流方法(以及它们为何不匹配)

ApproachDrawbacks
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=genericrole=none、未命名的 <div>)仅用于 CSS 布局,而非语义。若不进行过滤,你会看到 generic_1generic_2 … 这类没有实际意义的节点。

DOMShell 的 VFS 映射器(vfs_mapper.ts 递归地扁平化非语义节点,将它们的子节点提升:

  • 如果 role=generic 节点只有一个子节点,则用该子节点替代它。
  • 可见元素会根据其可访问名称和角色生成名称(submit_btncontact_us_linkemail_input)。
  • 对重复的名称使用 _2_3 等进行消歧。

设计决策: 最小化节点膨胀可最大化代理的信噪比。每个被扁平化的包装节点都是模型不必浪费推理的代币。

架构 – 三个清晰分离的组件

  1. Chrome 扩展(内核)

    • 后台 Service Worker 负责运行外壳:命令解析、通过 CDP 进行 AX 树遍历、文件系统映射、DOM 变化检测。
    • 侧边面板是一个轻量终端(React + Xterm.js)——仅负责 I/O,不包含逻辑。
    • 通过 chrome.debugger(Chrome DevTools Protocol 1.3)读取 AX 树,包括使用 Page.getFrameTree 进行跨 iframe 的发现。
  2. MCP 服务器(桥梁)

    • localhost:3001 上运行的独立 Node.js HTTP 服务器。
    • 任意兼容 MCP 的客户端(Claude Desktop、Claude Code、Cursor、Windsurf、Gemini CLI)均可连接。
    • 将 MCP 工具调用翻译为 Shell 命令,并通过 WebSocket(localhost:9876)发送给扩展,随后将结果流回。
    • 支持多个客户端同时连接。
  3. 安全层级

    • 默认 只读——代理可以浏览但不能执行操作。
    • 写入类命令(clicktypescrolljs)需加 --allow-write
    • 敏感命令(例如获取 cookie 的 whoami)需加 --allow-sensitive
    • 域名白名单限制代理可操作的站点。
    • 每条命令都会带时间戳进行审计日志记录。
    • 认证令牌用于控制 WebSocket 桥接的访问。

这种分离是有意为之:你可以在不启动 MCP 服务器的情况下交互式使用 DOMShell,或者让代理在不具备点击“删除账户”等操作权限的情况下浏览你的标签页。

性能结果

我使用 Claude Opus 4.6DOMShell 和 Anthropic 内置的浏览器自动化(Chrome 中的 Claude)上进行 8 次试验,覆盖 4 项任务

指标: 工具调用次数 – 与延迟和 API 成本成正比。

系统每任务平均调用次数
DOMShell4.3
Claude in Chrome8.6

结果: 调用次数降低 50 %。

最大的收益来自 内容提取:DOMShell 只用了 2 次调用(导航 + 提取),而 Chrome 中的 Claude 需要 5‑6 次

TL;DR

  • Accessibility Tree 是页面的确定性、语义化、低熵表示。
  • 将其映射到虚拟文件系统为代理提供了熟悉的零样本接口(lscdclick …)。
  • 将非语义节点扁平化以最大化信噪比。
  • 三层架构(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 管道是实现真正收益的所在:

  1. 保存 一段命令序列为脚本。
  2. 在单次调用中 将其 重放N 个 URL。

结果:O(2N) 次工具调用变为 O(2)
对于在多个页面上进行研究、提取或监控的任何代理,这是一种 质的飞跃 效率提升。

快速参考

# Example: change directory and bulk‑extract text
cd main/article
bulk-extract text

浏览器就是你的文件系统。
ls 它。

GitHub

项目: DOMShell – Pireno

0 浏览
Back to Blog

相关文章

阅读更多 »