@evan_zhong/browser-attach 0.0.0-stage → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CONTRIBUTING.md +27 -0
  2. package/LICENSE +21 -0
  3. package/README.md +169 -3
  4. package/bin/browser-attach.mjs +43 -0
  5. package/docs/ARCHITECTURE.md +129 -0
  6. package/docs/NOSIS-INTEGRATION.md +53 -0
  7. package/docs/TEST-REPORT.md +39 -0
  8. package/docs/USAGE.md +370 -0
  9. package/extension/actionability.js +66 -0
  10. package/extension/background.js +98 -0
  11. package/extension/batch.js +32 -0
  12. package/extension/click-visuals.js +82 -0
  13. package/extension/click.js +308 -0
  14. package/extension/controller.js +812 -0
  15. package/extension/diagnostics.js +205 -0
  16. package/extension/drag.js +163 -0
  17. package/extension/evaluate.js +131 -0
  18. package/extension/icons/BrowserAttach.png +0 -0
  19. package/extension/input.js +318 -0
  20. package/extension/limits.js +6 -0
  21. package/extension/locator.js +115 -0
  22. package/extension/manifest.json +34 -0
  23. package/extension/navigation.js +65 -0
  24. package/extension/options.html +6 -0
  25. package/extension/options.js +15 -0
  26. package/extension/outcomes.js +15 -0
  27. package/extension/popup.html +5 -0
  28. package/extension/popup.js +10 -0
  29. package/extension/scroll.js +29 -0
  30. package/extension/sessions.js +100 -0
  31. package/extension/snapshot.js +75 -0
  32. package/extension/text.js +37 -0
  33. package/extension/ui.css +12 -0
  34. package/extension/visuals.js +93 -0
  35. package/extension/wait.js +81 -0
  36. package/mcp-config.json +8 -0
  37. package/package.json +54 -6
  38. package/scripts/check-package.mjs +20 -0
  39. package/src/client.mjs +91 -0
  40. package/src/config.mjs +36 -0
  41. package/src/hub.mjs +197 -0
  42. package/src/mcp.mjs +167 -0
  43. package/src/setup.mjs +59 -0
  44. package/src/upload.mjs +15 -0
@@ -0,0 +1,27 @@
1
+ # Contributing to BrowserAttach
2
+
3
+ 欢迎提交问题和 Pull Request。讨论较大功能前,请先说明使用场景与现有工具无法满足的需求。
4
+
5
+ ## 开发与验证
6
+
7
+ 使用 Node.js 22+,从仓库根目录执行:
8
+
9
+ ```sh
10
+ npm ci
11
+ npm test
12
+ npm run test:package
13
+ npm pack --dry-run
14
+ ```
15
+
16
+ 浏览器交互改动还应按 [README](README.md#本地开发) 运行真实浏览器 fixture 测试。测试必须使用可丢弃的页面和配置,不应修改真实账号数据。
17
+
18
+ 修改版本时同步 `package.json`、`package-lock.json`、`src/config.mjs`、`extension/manifest.json`、MCP 配置及安装示例。扩展和 Hub 必须保持版本一致。
19
+
20
+ ## 提交内容
21
+
22
+ - 说明实际问题、修改后的行为和相关验证。
23
+ - 保留明确接管、独占控制、释放和结果未知时不重放的语义。
24
+ - 避免开发者本机绝对路径;资源相对模块定位,用户数据按平台目录或显式配置定位。
25
+ - 不提交生成的配对配置、令牌、Hub 日志、浏览器配置或截图中的个人信息。
26
+
27
+ 报告问题时附最小复现、预期/实际结果及系统、Node、浏览器和扩展版本。提交贡献即表示同意按项目的 [MIT License](LICENSE) 提供贡献内容。
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 EvannZhongg
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,169 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/EvannZhongg/BrowserAttach/main/extension/icons/BrowserAttach.png" width="112" alt="BrowserAttach logo">
3
+ </p>
4
+
5
+ # BrowserAttach
6
+
7
+ 让 MCP 客户端操作你已经打开、已经登录的 Chrome / Edge 标签页。BrowserAttach 通过本机配对扩展连接浏览器,保留现有登录状态,不需要启动远程调试端口或单独的自动化浏览器。
8
+
9
+ [npm](https://www.npmjs.com/package/@evan_zhong/browser-attach) · [使用指南](docs/USAGE.md) · [架构与设计](docs/ARCHITECTURE.md) · [验证报告](docs/TEST-REPORT.md) · [问题反馈](https://github.com/EvannZhongg/BrowserAttach/issues) · [贡献指南](CONTRIBUTING.md)
10
+
11
+ ## 能做什么
12
+
13
+ - 发现多个浏览器配置,精确选择并独占接管标签页,完成后释放。
14
+ - 读取可访问性树、字段值和支持的编辑器模型,通过当前快照引用或严格 CSS/XPath 定位点击、替换填写和逐字输入。
15
+ - 原生指针悬停、分步拖拽、条件等待;无需打开系统弹层即可选择 HTML 下拉选项。
16
+ - 导航与历史前进/后退、打开或关闭标签页,处理 JavaScript 对话框,上传本机文件并截取整页或单个元素。
17
+ - 在主页面执行 JavaScript 表达式,读取接管后的控制台、页面异常和网络生命周期日志;需要校验 XHR/fetch 时显式开启有界、脱敏的请求/响应详情。
18
+ - 使用标准 stdio MCP,共提供 25 个工具。Hub 仅监听本机 loopback,通过随机密钥配对扩展。
19
+
20
+ ## 环境要求
21
+
22
+ - Node.js **22 或更新版本**,以及 npm。
23
+ - Chrome / Edge **125 或更新版本**,且允许加载未打包扩展。推荐使用最新稳定版。
24
+ - 支持 stdio MCP 的客户端,能从 `PATH` 找到 Node/npm。
25
+
26
+ ## 快速开始
27
+
28
+ ### 1. 安装与配对
29
+
30
+ ```sh
31
+ npm install --global @evan_zhong/browser-attach
32
+ browser-attach setup
33
+ ```
34
+
35
+ `setup` 会输出稳定的扩展目录并启动本地 Hub。扩展目录通常是:
36
+
37
+ - Windows:`%USERPROFILE%\.browser-attach\extension`
38
+ - macOS / Linux:`~/.local/share/browser-attach/extension`
39
+
40
+ 打开 `chrome://extensions` 或 `edge://extensions`,启用开发者模式,选择“加载已解压的扩展程序”,加载该目录。每个需要接入的浏览器配置分别加载一次;扩展选项可设置容易辨认的配置名称。
41
+
42
+ 也可以不全局安装:
43
+
44
+ ```sh
45
+ npx --yes --package=@evan_zhong/browser-attach@latest browser-attach setup
46
+ ```
47
+
48
+ ### 2. 配置 MCP 客户端
49
+
50
+ 将以下配置合并到客户端的 MCP 配置中,或参考 [mcp-config.json](mcp-config.json):
51
+
52
+ ```json
53
+ {
54
+ "mcpServers": {
55
+ "browser_attach": {
56
+ "command": "npx",
57
+ "args": ["--yes", "--package=@evan_zhong/browser-attach@latest", "browser-attach", "mcp"]
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ Windows 客户端若要求完整命令扩展名,使用 `npx.cmd`。已全局安装时,也可以配置 `"command": "browser-attach", "args": ["mcp"]`。重启 MCP 客户端后发现工具。
64
+
65
+ `@latest` 会在启动时解析 npm 最新版本。MCP 包与已加载扩展必须为同一版本;升级后重新运行 `browser-attach setup` 并在扩展页重新加载扩展,否则 Hub 会拒绝旧扩展连接。
66
+
67
+ Nosis 用户可运行 `browser-attach setup --nosis` 自动备份并更新配置,详见 [Nosis 集成](docs/NOSIS-INTEGRATION.md)。
68
+
69
+ ### 3. 开始操作
70
+
71
+ 1. `browser_list_browsers` → `browser_list_tabs`,确认浏览器、标签页标题和 URL。
72
+ 2. `browser_claim_tab`,用 `list_tabs` 的 `tab_id` 接管(可选 `expected_url` / `expected_title` 精确校验),取得本连接的 `tab_handle`。
73
+ 3. `browser_snapshot` 取得元素 `ref`,再执行点击、填写或其他动作。
74
+ 4. 动作后重新观察,结束时调用 `browser_release_tab`。
75
+
76
+ 标题易变时,claim 可省略 `expected_title`;`expected_url` 也可选。点击/填写可改用每次重新解析的 `selector`。原生元素点击自动滚动;整页坐标点击使用 `coordinate_space: "document"`。求值结果使用 `cursor` 续读缓存,不会重新运行脚本。设计取舍见 [架构说明](docs/ARCHITECTURE.md#设计取舍)。
77
+
78
+ 接管和普通输入不会主动激活标签或窗口;需要前台渲染时显式调用 `browser_activate_tab`。操作结果未知时先重新观察,避免重复提交。完整参数、分页、上传与输入语义见 [使用指南](docs/USAGE.md)。
79
+
80
+ ## 工具
81
+
82
+ | 工具 | 用途 |
83
+ | --- | --- |
84
+ | `browser_list_browsers` / `browser_list_tabs` | 发现浏览器配置、标签身份及控制状态 |
85
+ | `browser_claim_tab` / `browser_release_tab` | 独占接管与释放 |
86
+ | `browser_open_tab` / `browser_activate_tab` / `browser_close_tab` | 显式管理标签页 |
87
+ | `browser_snapshot` / `browser_read_text` | AX 快照、元素引用和正文 |
88
+ | `browser_wait` | 只读等待元素、文本、URL 或网络空闲 |
89
+ | `browser_click` / `browser_hover` / `browser_drag` / `browser_select_option` / `browser_type` / `browser_fill` / `browser_press_key` / `browser_scroll` | 原生/DOM 输入、指针悬停与拖拽、下拉选择、逐字输入与滚动 |
90
+ | `browser_batch` | 单队列顺序执行 1–20 步 click/fill/type/press_key/hover/scroll/select_option/wait,失败即停并返回部分结果 |
91
+ | `browser_navigate` | HTTP(S) 导航与加载等待 |
92
+ | `browser_handle_dialog` | alert、confirm、prompt |
93
+ | `browser_upload_file` | 原生文件 input 或拦截的文件选择器 |
94
+ | `browser_screenshot` | 视口或整页 PNG,附坐标映射 |
95
+ | `browser_evaluate` | 主页面 JS 求值、Promise 等待、有界结果 |
96
+ | `browser_logs` | 用 `kind=console/network` 选择控制台、异常或请求日志,支持过滤和游标续读 |
97
+
98
+ ## 本机数据与升级
99
+
100
+ | 平台 | 默认数据目录 |
101
+ | --- | --- |
102
+ | Windows | `%USERPROFILE%/.browser-attach` |
103
+ | macOS / Linux | `${XDG_DATA_HOME:-~/.local/share}/browser-attach` |
104
+
105
+ 可通过绝对路径 `BROWSER_ATTACH_HOME` 显式覆盖。Windows 默认避开可能被打包宿主虚拟化的 AppData;不会搜索或自动合并其他宿主的私有缓存。目录包含配对配置、Hub 日志及稳定的扩展副本;不依赖 clone 位置、客户端工作目录或 npm 缓存。已有自定义目录请继续使用相同环境变量或 `--config`,不要在新默认目录意外创建另一套配对。
106
+
107
+ `setup --nosis` 会备份配置并固定当前 Node、CLI 和实际配对文件的绝对路径,同时设置 MCP 的 `BROWSER_ATTACH_HOME`;支持与自定义 `--config` 一起使用。MCP 启动 Hub 时继续传入同一绝对配对路径及目录,避免不同启动环境重新推导出另一份配置。
108
+
109
+ ```sh
110
+ browser-attach status
111
+ browser-attach stop
112
+ npm install --global @evan_zhong/browser-attach
113
+ browser-attach setup
114
+ ```
115
+
116
+ 升级后在浏览器扩展管理页重新加载扩展,再重启 MCP 连接。Hub 与扩展版本必须一致。首次使用本项目的新名称时,应停止之前运行的 Hub 并重新配对;默认端口为 `47831`,可用 `setup --port` 修改。
117
+
118
+ ## 架构与使用边界
119
+
120
+ ```text
121
+ MCP 客户端 → Node.js stdio MCP → 127.0.0.1 Hub
122
+ ↕ 本机配对
123
+ Chrome / Edge MV3 扩展
124
+ ↓ chrome.debugger / CDP
125
+ 现有 HTTP(S) 标签页
126
+ ```
127
+
128
+ 每个 MCP 连接拥有独立控制权,句柄不能跨连接使用。断连或停止调试会释放控制。客户端端点要求 Bearer 密钥并拒绝网页 Origin;扩展验证固定 Origin、密钥和版本。`browser_evaluate` 可以使用页面现有登录态读写数据,执行后快照分页失效,原节点 refs 可继续解析;超时或报错前可能已产生作用,不应自动重放。未暴露通用 CDP 工具。
129
+
130
+ 日志从接管开始采集,跨导航保留,释放时清除;每类最多保留 500 条、1,048,576 个 JSON 字符,超限淘汰会报告 `data_loss`。网络日志默认只记录生命周期元数据;显式 capture 才读取之后的 XHR/fetch 有界、脱敏 headers/正文,不改变缓存或拦截流量。完整参数及边界见[使用指南](docs/USAGE.md#诊断日志)。
131
+
132
+ 浏览器扩展具有调试权限,客户端操作可使用现有网站登录态;只应将其接入你信任的 MCP 客户端。网页内容不应被视为用户授权。配对密钥和生成的 `config.generated.js` 不应提交或发布,打包检查会拒绝包含该文件的包。
133
+
134
+ 目前仅支持 Chrome / Edge 的 HTTP(S) 页面。下载管理、网络拦截和 Firefox / WebKit 尚不支持。跨进程 iframe 使用递归 session 路由,覆盖快照、输入、上传和日志;透视变换的 iframe 点击会明确拒绝。原生指针输入要求活动且未暂停的标签及未最小化窗口,焦点和文档可见性只作诊断;不就绪时快速返回未派发及 activate_tab 提示;不会自动抢焦点。后台截图可能因浏览器暂停渲染而超时;原生菜单和标签拖动也可能抑制输入。
135
+
136
+ ## 本地开发
137
+
138
+ ```sh
139
+ git clone https://github.com/EvannZhongg/BrowserAttach.git
140
+ cd BrowserAttach
141
+ npm ci
142
+ npm test
143
+ npm run test:package
144
+ ```
145
+
146
+ 直接运行源码并配对仓库中的扩展:
147
+
148
+ ```sh
149
+ node bin/browser-attach.mjs setup --extension-dir ./extension
150
+ node bin/browser-attach.mjs mcp
151
+ ```
152
+
153
+ 开发模式有意使用仓库扩展路径。一般安装请使用不带 `--extension-dir` 的 setup,确保移动仓库后仍可运行。Windows 也可运行 `./scripts/install.ps1`,或添加 `-ConfigureNosis`。
154
+
155
+ 真实浏览器测试需要一个允许命令行加载扩展的 Chromium / Edge 可执行文件,路径由开发者传入:
156
+
157
+ ```sh
158
+ npm run test:live -- --executable /path/to/chromium --family chromium
159
+ npm run test:interaction -- --executable /path/to/chromium --family chromium
160
+ npm run test:interaction -- --tab-indicators --executable /path/to/chromium --family chromium
161
+ ```
162
+
163
+ 这些测试使用临时配置和本地 fixture。测试覆盖与限制见 [验证报告](docs/TEST-REPORT.md),实现与设计取舍见 [架构与设计](docs/ARCHITECTURE.md)。
164
+
165
+ ## 参与贡献
166
+
167
+ 欢迎通过 [Issues](https://github.com/EvannZhongg/BrowserAttach/issues) 提交可复现问题,或提交 Pull Request。请附上 Node、系统、浏览器与扩展版本,移除配对密钥和网页隐私数据。具体流程见 [CONTRIBUTING.md](CONTRIBUTING.md)。
168
+
169
+ 本项目采用 [MIT License](LICENSE)。
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ import { parseArgs } from 'node:util';
3
+ import { DEFAULT_CONFIG, loadConfig } from '../src/config.mjs';
4
+ import { ensureHub, hubIsRunning, AttachClient } from '../src/client.mjs';
5
+
6
+ try {
7
+ const { values, positionals } = parseArgs({ allowPositionals: true, options: {
8
+ config: { type: 'string', default: DEFAULT_CONFIG },
9
+ 'extension-dir': { type: 'string' }, port: { type: 'string', default: '47831' },
10
+ nosis: { type: 'boolean', default: false }, help: { type: 'boolean', short: 'h' },
11
+ } });
12
+ const action = positionals[0] || 'mcp', configPath = values.config;
13
+ if (values.help) {
14
+ console.log(`browser-attach [mcp|setup|hub|start|status|stop]\n setup [--extension-dir PATH] [--port 47831] [--nosis]\n --config PATH (default: ${DEFAULT_CONFIG})`);
15
+ } else if (action === 'mcp') {
16
+ const { runMcp } = await import('../src/mcp.mjs'); await runMcp(configPath);
17
+ } else if (action === 'hub') {
18
+ const { startHub } = await import('../src/hub.mjs');
19
+ const hub = await startHub(await loadConfig(configPath));
20
+ const stop = () => hub.close(); process.once('SIGINT', stop); process.once('SIGTERM', stop);
21
+ } else if (action === 'setup') {
22
+ const { setup, configureNosis } = await import('../src/setup.mjs');
23
+ const result = await setup({ configPath, extensionDir: values['extension-dir'], port: Number(values.port) });
24
+ console.log(`Extension folder: ${result.extensionDir}\nExtension ID: ${result.config.extension_id}\nPairing: ${result.configPath}`);
25
+ if (values.nosis) console.log(JSON.stringify(await configureNosis(undefined, { configPath: result.configPath })));
26
+ await ensureHub(result.configPath); console.log('Local hub started. Load/reload the extension in Chrome/Edge.');
27
+ } else if (action === 'start') { await ensureHub(configPath); console.log('Local hub started.'); }
28
+ else if (action === 'status') {
29
+ const config = await loadConfig(configPath);
30
+ if (!await hubIsRunning(config)) console.log('Local hub is stopped.');
31
+ else {
32
+ const client = new AttachClient(config);
33
+ try { console.log(JSON.stringify(await client.call('list_browsers'), null, 2)); } finally { await client.close(); }
34
+ }
35
+ } else if (action === 'stop') {
36
+ const config = await loadConfig(configPath);
37
+ const response = await fetch(`http://127.0.0.1:${config.port}/shutdown`, {
38
+ method: 'POST', headers: { Authorization: `Bearer ${config.token}` }, signal: AbortSignal.timeout(5000),
39
+ });
40
+ if (!response.ok) throw new Error(`Hub refused shutdown (${response.status})`);
41
+ console.log('Hub stopped; extension control released.');
42
+ } else throw new Error(`Unknown command: ${action}`);
43
+ } catch (error) { console.error(error.message); process.exitCode = 1; }
@@ -0,0 +1,129 @@
1
+ # 架构与设计边界
2
+
3
+ 当前实现为 **0.10.1**。本文说明 BrowserAttach 的组件、控制模型与设计取舍;工具参数见 [使用指南](USAGE.md)。
4
+
5
+ ## 组件与数据流
6
+
7
+ ```text
8
+ MCP 客户端
9
+ → 每连接独立的 Node stdio MCP(src/mcp.mjs)
10
+ → 127.0.0.1 Hub(src/hub.mjs,Bearer 密钥 + 随机 owner)
11
+ ↔ 配对的 Chrome / Edge MV3 扩展(WebSocket + 固定 Origin)
12
+ → chrome.debugger / CDP + chrome.tabs / chrome.tabGroups
13
+ → 用户已登录的现有标签页
14
+ ```
15
+
16
+ | 模块 | 职责 |
17
+ |---|---|
18
+ | `bin/browser-attach.mjs` | `setup` / `mcp` / `hub` / `status` / `stop` 入口 |
19
+ | `src/config.mjs` | 数据目录、配对文件校验、版本常量 |
20
+ | `src/setup.mjs` | 生成配对密钥、复制稳定扩展目录、写 Nosis 配置 |
21
+ | `src/hub.mjs` | 浏览器与会话注册、独占 claim、每标签串行队列、超时 |
22
+ | `src/client.mjs` | MCP 进程到 Hub 的 WebSocket 客户端,按需拉起 Hub |
23
+ | `src/mcp.mjs` | 25 个工具的 schema、结果与图片封装 |
24
+ | `extension/controller.js` | 操作实现;传输层从不暴露原始 CDP |
25
+ | `extension/sessions.js` | 扁平 debugger session 与 frame/OOPIF 归属 |
26
+ | `extension/{click,input,actionability,scroll}.js` | 原生/DOM 输入、逐字按键、可操作性、容器滚动与有界查找 |
27
+ | `extension/{drag,wait}.js` | 复用鼠标准备和严格定位的拖拽、只读条件等待;网络空闲复用被动请求表 |
28
+ | `extension/batch.js` | 有限输入序列的参数预检;执行复用 controller 的单步流程与标签队列 |
29
+ | `extension/outcomes.js` | 临时 DOM 变动观察器与有界只读探测,不推断业务成功 |
30
+ | `extension/{snapshot,navigation,evaluate,diagnostics}.js` | 观察、导航、有界求值结果缓存与分页、日志缓冲 |
31
+ | `extension/{locator,text}.js` | 严格 CSS/XPath 定位、每次解析的 frame URL/title/iframe 路径、DOM/字段/支持的编辑器模型读取 |
32
+ | `extension/visuals.js`、`click-visuals.js` | 无标题红色原生标签组、临时点击反馈 |
33
+
34
+ Hub 按需后台启动(`ensureHub`),多客户端共享;只监听 loopback。
35
+
36
+ ## MCP 描述分层
37
+
38
+ - 初始化响应的 `instructions` 集中说明跨工具规则:接管/释放、不自动激活、ref 生命周期、selector 解析、未知结果不重放、原生/DOM 输入、超时、验证范围和页面内容不可信。客户端应将这些 instructions 纳入模型上下文,不能只提供 tools/list。
39
+ - 单个工具 description 只保留用途和特有行为,例如 evaluate 的缓存续读、navigate 的观察预算、fill 的 setter/input/change、press_key 的 keydown 回执。
40
+ - 参数 schema 表达类型、枚举、范围、默认值与参数专属限制;例如 DOM click 仅支持元素目标和无修饰键的左键单击。
41
+ - [使用指南](USAGE.md) 保留完整原理、边界与示例。包内文档不会自动成为模型上下文,不能替代初始化 instructions 中的必要规则。
42
+
43
+ ## 控制模型
44
+
45
+ 1. **发现**:`list_tabs` 的结果被 Hub 记为本次连接的观察快照。
46
+ 2. **校验身份**:`claim_tab` 必须使用本连接列出的 tab_id;可选 expected_url / expected_title 在 Hub 与扩展侧逐字符校验。省略标题校验可容忍标题漂移;省略全部校验表示明确按 ID 接管,返回当前身份。
47
+ 3. **独占**:Hub 为每个 `(browser_id, tab_id)` 维护唯一 owner;同一标签第二次 claim 会返回已有的 `tab_handle`(同 owner)或报错(其他 owner)。
48
+ 4. **句柄**:`tab_handle` 是 Hub 生成的随机 UUID,只在当前 MCP 连接内有效;跨连接不共享。
49
+ 5. **串行**:Hub 与扩展都对每个标签维护 Promise 队列,动作不会并发穿插。
50
+ 6. **超时**:Hub 单次浏览器操作 60 秒,客户端等待 65 秒。Hub 超时会关闭该客户端连接并释放它的 claim,错误文本明确提示"结果可能未知"。
51
+ 7. **清理**:`release_tab` 先移除可视标记,再 `debugger.detach`,最后才忘记 claim——detach 失败不会静默把标签交给别人。断连、标签关闭、浏览器重启、用户停止调试都会清理状态。
52
+
53
+ claim 与普通输入都不激活标签或窗口,也不移动系统鼠标;需要前台渲染时显式调用 `activate_tab`。
54
+
55
+ ## 观察模型
56
+
57
+ - **AX 渲染**:从 `Accessibility.getFullAXTree` 读取,移除 `InlineTextBox`,合并纯文本列表项与段落名称,丢弃 `generic` / `none` 且无名称无状态的节点,只保留少量状态属性和浏览器提供的 value、invalid。每行 `[token:n] role "name"`,`token` 是本次观察的随机前缀;这些字段不证明应用已保存。
58
+ - **ref 解析**:ref 保存 `backendNodeId`、所属 frame/session 和观察时的 AX role/name;动作时重新 `DOM.resolveNode` 并用局部 AX 查询核对语义身份。输入与求值不清空 refs,只使快照分页失效;原节点仍连接时可复用,节点替换、role/name 改变、文档或 frame 变化时安全失败,不按 role/name 猜测替代节点。
59
+ - **双预算**:`max_nodes` 限制节点数,`max_chars` 限制每行序列化后的累计字符数;两者独立,任一耗尽都会返回 `next_cursor`。
60
+ - **续读**:续读沿用同一份 `state`(`entries`、`refs`、`nextOffset`),所以 refs 在分页过程中保持有效。单个节点放不进预算时直接报错要求提高预算,避免静默截断尾部。
61
+ - **frame 失效**:主文档导航或文档更新清空整份观察;子 frame 导航、移除或 session detach 只使该子树失效,并把受影响 frame 写入 `omitted_frames`。
62
+ - **OOPIF**:`Target.setAutoAttach` 以 `flatten: true` 递归附着 iframe target,每个 session 独立 enable Runtime / Log / Network / Page / DOM / Accessibility,frame 与 session 的映射由 `sessions.js` 维护。
63
+
64
+ ## 输入模型
65
+
66
+ 原生输入是默认路径,流程为:
67
+
68
+ ```text
69
+ 解析 ref → 可操作性等待(可见/启用/非 inert/几何稳定)
70
+ → 滚入视口 → 重新确认 → 命中检查与遮挡检查
71
+ → 在隔离世界安装校验 → 一次性派发 move/down/up
72
+ → 等待可信事件回执
73
+ ```
74
+
75
+ - **type 与 fill**:fill 替换文字,type 保留选区,逐字符复用原生按键与 keydown 回执;字符之间可延迟,不把 insertText 伪装成键盘序列。浏览器处理 keypress/input 及 preventDefault,非 ASCII 不模拟系统 IME。
76
+ - **只重试准备**:等待阶段可以反复轮询,但一旦派发就不再重试。超时分为"未派发"和"已派发、结果未知"两种错误文本。
77
+ - **原生指针就绪检查**:准备前和派发前各做一次至多 500 ms 的有界检查;要求活动、未暂停的 tab 与未最小化的窗口。不满足时以 TAB_NOT_READY 明确报告当前指针输入未派发,并提示 activate_tab → snapshot → click。click 不主动激活;仅显式 activate_tab 在 focus_window=true 时请求窗口聚焦和 Page.bringToFront。
78
+ - **焦点信号边界**:OS 窗口失焦、遮挡或 document.visibilityState=hidden 不一定阻止 CDP 输入;document.hasFocus() 在 iframe 导航后也可能为 false。这些值作为 page_state 诊断,不独立拒绝点击。实际送达仍由隔离世界的可信事件回执校验;没有回执时不自动重放。
79
+ - **送达预算**:原生 click/hover/press_key 的派发及可信回执另有 1250 ms 上限,清理最多 250 ms;系统菜单捕获输入时快速释放队列,仍报告结果未知并提示 Esc,不自动重放。
80
+ - **hover 与 drag**:hover 复用点击准备与隔离校验,仅派发一次 mouseMoved,清理不移走指针。drag 显式区分 pointer 与 HTML5;后者从真实 dragstart 截获 DragData,再用 CDP 派发 dragover/drop,校验可信 dragstart/drop/dragend。目的节点在按住后滚入视口;所有步骤共用标签队列和总预算,失败时只取消/释放本次手势。
81
+ - **条件等待**:wait 复用严格 selector、可操作性和命中检查,支持出现、可见、可操作、文本/URL 子串及网络空闲;只读轮询,错误附带条件和最后状态。
82
+ - **原生下拉**:select_option 直接用 selectedIndex setter 和合成 input/change 设置一个 HTML 选项,核验保留结果并返回 events_trusted=false。native click 的 select 命中(含 UA shadow 内部节点和关联 label)在派发前拒绝。
83
+ - **命中提示**:普通命中保留实际 hit node,沿父元素、Shadow host、slot 查找最近的 HTML/ARIA 可点击候选,返回 clickable_target;hint=false 表示未识别候选,不代表无法响应事件。CSSPseudoElement 先解析为事件所属的 originating element,提示标记 pseudo_element=true,并使用该元素安装事件校验。语义候选不会被用于替换点击目标。
84
+ - **送达不等于业务完成**:`target_verified` / `delivery_verified` / `value_verified` 分别证明可信事件命中、keydown 送达、字段文本写入;点击、按键、fill 和 batch 返回 `application_result: "not_checked"`。fill 不证明 React 状态、选项提交、blur 校验或保存完成。
85
+ - **有限多步输入**:batch 在现有标签队列内执行 1–20 步 click/fill/type/press_key/hover/scroll/select_option/wait,共享至多 30 秒预算,每步可另设更短超时;不增加后台任务、循环或脚本引擎;batch 本身只接受每步重新解析的 selector。所有参数先验证,selector 每步重解析,尚未出现的目标只在准备阶段等待;歧义立即报错。首个失败、主文档变化或接管结束后停止,返回此前结果与失败索引;不回滚、不重放、不自动激活或切换输入模式。start_at 显式跳过已完成前缀,返回 failed_step、next_unattempted_step、retry_failed_step_safe;没有后台作业或自动选择续跑点。
86
+ - **OOPIF 坐标**:点击从顶层派发。逐层用 `DOM.getBoxModel` 把坐标换算到祖先文档,并用 `DOM.getNodeForLocation` 确认该位置确实落在 frame owner 上,防止祖先页面的遮挡被跳过。
87
+ - **DOM 模式**:`mode: "dom"` 是显式的、受控的降级路径,用于原生菜单或标签拖动暂时捕获输入时操作普通 HTML 控件。它调用 `HTMLElement.click()` 或原生 value setter,事件非可信、无用户激活,且绝不会在原生失败后自动启用。
88
+ - **对话框策略**:ask 仍为默认;单操作/单步骤的显式 accept/dismiss 在事件回调中解除 JS 模态阻塞,保留消息与处理结果,最多自动处理 10 个。beforeunload dismiss 快速取消本次导航等待,实际主文档变化仍会停止 batch。
89
+ - **未知结果探测**:在可能写入的操作准备期安装目标 frame 的临时 MutationObserver,仅在不确定失败后做一次有界只读探测,回传对话框、导航、DOM 变动。观察不到变化不等于没有派发,不将证据升级成业务成功,不自动重放。
90
+ - **元素截图/求值 ref**:复用既有截图 clip/元数据与 ref 注册/失效模型,分别增加元素 border box 截图和显式单 DOM Element 结果;不暴露 Runtime objectId。JSON 求值自动调用函数一次并等待 Promise,缓存续读不执行脚本。
91
+ - **历史导航**:在已有 navigate 中选择 URL 或 back/forward,按 history entry 与主 frame 生命周期等待;同文档/BFCache 恢复不等新的加载事件,不用 evaluate 重放导航。
92
+ - **不做通用 CDP 透传**:`browser_evaluate` 是页面表达式 API,不是 CDP 通道;没有暴露任意 domain/method 的工具。
93
+
94
+ ## 诊断模型
95
+
96
+ `browser_logs` 通过必填 `kind=console/network` 读取控制台与网络日志。两类日志共享只读权限、生命周期、分页和预算接口;事件结构与过滤条件由 `kind` 区分,各自维护独立缓冲。工具层只有一套 schema 和调用路由,不引入聚合状态机。
97
+
98
+ - 每次 claim 开启 Runtime / Log / Network 观察,默认 `Network.enable` 把正文缓冲区设为 0;显式 capture 才启用每 session 2 MiB 总量和单资源上限,OOPIF 新 session 继承本次 claim 配置。
99
+ - 两类 append-only 缓冲各自维护 `seq`:读取使用 `after_seq` + `limit` + `max_chars`;续读位置必须与 tab claim 和 kind 一起保存。网络突发流量不会挤掉控制台错误。console 的 level 与 network 的 url_contains 不得跨类使用。两种 timestamp 基准不同,不混排或伪造统一时间轴。同一 requestId 的重定向用 `hop` 区分,完成事件后追加而不是覆盖请求行。
100
+ - 上限为每类 500 条 / 1,048,576 字符,活跃请求表 500 项;淘汰记录 `dropped_through_seq`,读取返回 `data_loss`。
101
+ - 导航保留缓冲,release / detach / 关闭 / 断连清理;不落盘、不修改缓存策略与 service worker。默认不读取 headers/正文;按需开启只捕获之后的 XHR/fetch,凭据头和命名敏感字段脱敏,解压后体积、并发读取、浏览器缓存和扩展日志均有界,未捕获/超限明示 unavailable。
102
+
103
+ ## 安全与隐私
104
+
105
+ - Hub 只监听 `127.0.0.1`;HTTP 端点拒绝任何带 `Origin` 的请求,客户端必须携带 Bearer 密钥。
106
+ - 扩展连接固定校验 `chrome-extension://<extension_id>` Origin、随机 token 与版本号,三者任一不符即断开。
107
+ - 配对文件 `config.json` 与生成的 `extension/config.generated.js` 含密钥:目录在 Windows 上用当前用户 SID 收紧 ACL,在 POSIX 上为 `0700`/`0600`;发布包检查会拒绝包含 `config.generated.js`。
108
+ - 扩展具备 `debugger` 与 `tabGroups` 权限,操作使用用户现有登录态;只应接入信任的 MCP 客户端。网页内容、控制台与网络日志均视为非可信数据。
109
+ - 文件上传只传路径,Hub 与模型都不接触文件内容;选择文件可能立即把内容发给网站,因此只传任务明确指定的文件。
110
+
111
+ ## 设计取舍
112
+
113
+ | 能力 | 决策 | 原因 |
114
+ |---|---|---|
115
+ | 定位方式 | AX ref + CSS/XPath selector,可显式 nth | ref 保持观察身份,selector 每次重解析;省略 nth 仍严格要求唯一;索引是调用者明确选择,不猜测同名目标 |
116
+ | 动作重试 | 只重试派发前的准备 | 网页操作可能提交真实数据,重复派发不可接受 |
117
+ | DOM 模式 | 需要显式 `mode: "dom"` | 非可信事件、无用户激活,不能当作原生输入的透明替代 |
118
+ | hover / drag | 原生指针工具,复用 click 准备 | 纯悬停和可信拖拽无法由 click 或页面 evaluate 等价表达 |
119
+ | HTML select / checkbox | select_option;checkbox 仍用 click + 观察 | select 的系统弹层会捕获输入,直接赋值必须明确合成事件;checkbox 无需新增工具 |
120
+ | 网络拦截 | 只读事件,不拦截、不改写 | 拦截会改变网站行为并扩大故障面;诊断需求用被动日志满足 |
121
+ | trace / video | 不提供 | 会大量保存网站内容,存储与隐私成本高于收益 |
122
+ | 独立 worker / WebSocket | 不订阅 | 需要额外的 target 管理;当前以页面与 iframe 为主 |
123
+ | 浏览器引擎 | 仅 Chrome / Edge 125+ | 依赖 `chrome.debugger`、`tabGroups` 与扁平 session,其他引擎没有等价能力 |
124
+ | 用户配置 | 复用现有已登录 profile | 这是产品定位:不创建隔离 context、不导出 cookie、不启动独立自动化浏览器 |
125
+ | 分发 | npm 包 + 稳定扩展副本 | 避免 npx 缓存目录变化导致扩展失效;不保留 Python 运行时或旧配对兼容层 |
126
+
127
+ ## 版本与兼容
128
+
129
+ `package.json`、`src/config.mjs`、`extension/manifest.json` 的版本必须一致;Hub 会拒绝版本不同的扩展,`hubIsRunning` 也会拒绝版本不同的 Hub。MCP 示例配置使用 `@latest`,升级流程:停止旧 Hub → `browser-attach setup` → 在扩展管理页重新加载扩展 → 重连 MCP。
@@ -0,0 +1,53 @@
1
+ # Nosis 集成
2
+
3
+ BrowserAttach 是标准 stdio MCP,也可以通过 `setup --nosis` 直接写入 Nosis 的用户配置。
4
+
5
+ ## 写入内容
6
+
7
+ ```sh
8
+ browser-attach setup --nosis
9
+ ```
10
+
11
+ 该命令在完成普通配对后,备份并更新 `~/.nosis/agent_config.json`(备份名为 `agent_config.json.browser-attach-<时间戳>.bak`),写入:
12
+
13
+ | 字段 | 值 |
14
+ |---|---|
15
+ | 服务器名 | `browser_attach` |
16
+ | `command` | 执行 setup 的 Node 可执行文件绝对路径 |
17
+ | `args` | 当前包 `bin/browser-attach.mjs` 绝对路径、`mcp`、`--config <绝对配对路径>` |
18
+ | `env.BROWSER_ATTACH_HOME` | 配对文件所在目录 |
19
+ | `startup_timeout_seconds` / `call_timeout_seconds` | `20` / `60` |
20
+ | `tools.approval` | `always` |
21
+
22
+ 同时会设置 `mcp.enabled = true`。命令与配对路径都固定为绝对路径,因此本机安装位置变化后需要重新执行 `setup --nosis`;请从长期保留的位置安装,而不是临时解压目录。
23
+
24
+ 重启或重建 Nosis Runtime 后重新发现工具。
25
+
26
+ ## 自定义运行目录
27
+
28
+ Windows 默认目录是 `%USERPROFILE%/.browser-attach`,避开打包应用与普通宿主可能重定向到不同实际文件的 AppData;macOS / Linux 使用 XDG 用户数据目录。`BROWSER_ATTACH_HOME` 必须是绝对目录。
29
+
30
+ 已有可共享目录可直接沿用:
31
+
32
+ ```sh
33
+ browser-attach setup --config /absolute/shared-directory/config.json --nosis
34
+ ```
35
+
36
+ setup 会把解析后的配对路径写进配置,MCP 与 Hub 后续都显式传递同一路径,不会在不同启动环境中重新推导出第二套密钥。setup 不会搜索或自动迁移其他应用的私有缓存;已有配对请显式保留原目录。
37
+
38
+ ## 验证
39
+
40
+ ```sh
41
+ python scripts/verify-nosis.py --nosis-root /path/to/Nosis --family chrome
42
+ python scripts/verify-nosis.py --nosis-root /path/to/Nosis --family edge
43
+ ```
44
+
45
+ 脚本读取本机 Nosis 配置,通过 Nosis ToolCatalog 发现并调用全部 25 个工具,其中 `browser_logs` 分别覆盖 console/network,验证结果封装、截图到模型消息的图片适配以及 `isError` 后连接仍可用。它用 `browser_open_tab` 在选定配置中创建本地 fixture;同一 family 有多个 profile 时用 `--browser-id <精确 browser_id>` 指定,结束后释放控制。Python 仅用于该测试,不是运行依赖。
46
+
47
+ ## 故障排查
48
+
49
+ - 工具结果走 MCP 文本内容,截图走标准图片内容;能否进入模型取决于宿主的图片适配、大小预算与模型能力。
50
+ - 失效句柄不能跨连接复用,错误后应重新 list + claim。
51
+ - 文件上传只在浏览器本机读取路径,内容不经过 Hub 或模型。
52
+ - 宿主把大结果保存为 Artifact 时,嵌套 JSON 长行可能被宿主的读取工具截断;这是宿主的分页行为,不能靠调大 Attach 的预算解决,也不等同于快照游标分页。
53
+ - `Connection closed` 时依次检查:宿主的 stderr、配对文件是否可见、`hub.log`、以及 Hub 与扩展的版本是否一致(版本不一致会直接拒绝连接)。
@@ -0,0 +1,39 @@
1
+ # 验证报告
2
+
3
+ 当前版本 **0.10.0**(2026-10-22)。验证使用本地 fixture 和临时 profile,链路为扩展 → 本机 Hub → stdio MCP。
4
+
5
+ ## 当前版本
6
+
7
+ - `npm test`:141 项通过。覆盖独占控制、快照/ref 生命周期、严格 selector、原生/DOM 输入、逐字输入、hover、pointer/HTML5 拖拽、条件等待、HTML select、batch、历史导航、OOPIF、日志、上传、截图、对话框和未知结果不重放。
8
+ - `npm run test:package`:仓库外含空格路径安装运行通过,25 个 MCP 工具、44 个发布文件;包含 `drag.js` / `wait.js` / `scroll.js` / `outcomes.js`,不包含配对密钥。版本为 0.10.0,示例配置使用 `@latest`。
9
+ - Edge 155 headless `--primitives`:10 个场景通过,包含 hover 停留后点击子项、一次快照的 ref 连续输入并跨只读 evaluate 保持有效、替换节点拒绝、label/value/index 下拉选择、三种等待失败、pointer/HTML5 拖拽和目的元素滚入视口。
10
+ - Edge 155 完整 stdio MCP 集成:192 个调用通过,包含嵌套 OOPIF、原生/DOM 输入、遮挡、上传、日志、按键、滚动与导航;扩展报告 `extension_version=0.10.0`。
11
+ - Chromium 156、Edge 155 headless `--extended`:各 12 个场景通过,包含 ref 生命周期、选择框、拖拽、等待、历史导航和 OOPIF。
12
+ - `select_option` 明确返回 `events_trusted=false`;native select 点击在派发前拒绝。拖拽和输入只证明送达,不证明页面业务成功。
13
+
14
+ ## 复跑
15
+
16
+ ```sh
17
+ npm test
18
+ npm run test:package
19
+ node scripts/live-smoke.mjs --executable /path/to/browser --family edge
20
+ node scripts/interaction-smoke.mjs --primitives --headless --executable /path/to/browser --family edge
21
+ node scripts/interaction-smoke.mjs --extended --headless --executable /path/to/browser --family edge
22
+ ```
23
+
24
+ 真实浏览器测试需要允许命令行加载扩展的 Chromium / Edge 可执行文件,并只操作临时 profile 与本地 fixture。原始结果保存在被忽略的 `test-results/` 目录(`primitives-*.json`、`npm-live-*.json`、`extended-*.json`)。
25
+
26
+ ## 已知限制
27
+
28
+ - 未覆盖独立 worker / service worker、WebSocket 消息、`window.showOpenFilePicker()`、目录上传、下载管理和网络拦截。
29
+ - 后台截图可能因浏览器暂停渲染而在 5 秒后超时;原生菜单和标签拖动可能抑制输入。
30
+ - readiness 是派发前的保守条件,不保证 compositor 或业务处理;输入真正派发后丢失可信回执仍报告 Outcome is unknown,不把“没观察到效果”当作零效果证明。
31
+ - 拖拽的自动滚动是目的元素驱动的滚入视口,不提供持续边缘滚动任务。
32
+ - `select_option` 不制造可信事件;`network_idle` 只覆盖 claim 后观察到的页面/iframe 请求。
33
+ - Monaco / CodeMirror 5 分支通过接口级测试,未针对第三方生产编辑器或特定 React/Vue 版本做站点验收。
34
+ - 计时数据仅代表本机 fixture,不构成对任意网站的耗时承诺。
35
+
36
+ ## 历史验证
37
+
38
+ - 0.8.0(2026-10-21):`npm test` 118 项通过;20 个工具、40 个发布文件;Chromium 156 / Edge 155 各 191 次真实调用通过。
39
+ - 0.7.0(2026-10-21):`npm test` 110 项通过;19 个工具、40 个发布文件;Chromium 156 / Edge 155 各 191 次真实调用通过。