dsh-browser-plus 0.0.0-stage → 0.5.0
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.
- package/CHANGELOG.md +166 -0
- package/LICENSE +22 -0
- package/NOTICE.md +7 -0
- package/README.en.md +100 -0
- package/README.md +99 -2
- package/assets/dsh-browser-plus-256.png +0 -0
- package/assets/dsh-browser-plus-512.png +0 -0
- package/assets/dsh-browser-plus-small.svg +9 -0
- package/assets/dsh-browser-plus.ico +0 -0
- package/assets/dsh-browser-plus.svg +11 -0
- package/assets/readme-workspace.png +0 -0
- package/cordis.patch.yml +17 -0
- package/docs/MIGRATION.md +48 -0
- package/docs/README.md +22 -0
- package/docs/SOAK-CHECKLIST.md +98 -0
- package/docs/architecture.md +88 -0
- package/docs/tool-reference.md +124 -0
- package/docs/user-guide.md +121 -0
- package/docs/why-browser.md +45 -0
- package/lib/browser/runtime.d.ts +225 -0
- package/lib/browser/runtime.js +302 -0
- package/lib/browser/types.d.ts +668 -0
- package/lib/browser/types.js +18 -0
- package/lib/browser-electron/auth-cookies.d.ts +54 -0
- package/lib/browser-electron/auth-cookies.js +83 -0
- package/lib/browser-electron/chrome-state.d.ts +187 -0
- package/lib/browser-electron/chrome-state.js +12 -0
- package/lib/browser-electron/entry.d.ts +66 -0
- package/lib/browser-electron/entry.js +62 -0
- package/lib/browser-electron/fingerprint.d.ts +29 -0
- package/lib/browser-electron/fingerprint.js +42 -0
- package/lib/browser-electron/host-main.d.ts +18 -0
- package/lib/browser-electron/host-main.js +2494 -0
- package/lib/browser-electron/icon.d.ts +11 -0
- package/lib/browser-electron/icon.js +23 -0
- package/lib/browser-electron/page-chrome.d.ts +21 -0
- package/lib/browser-electron/page-chrome.js +2034 -0
- package/lib/browser-electron/provider.d.ts +709 -0
- package/lib/browser-electron/provider.js +2575 -0
- package/lib/browser-electron/remote-host.d.ts +143 -0
- package/lib/browser-electron/remote-host.js +952 -0
- package/lib/browser-electron/task-summary.d.ts +2 -0
- package/lib/browser-electron/task-summary.js +12 -0
- package/lib/browser-electron/task-thumbnail.d.ts +11 -0
- package/lib/browser-electron/task-thumbnail.js +9 -0
- package/lib/browser-electron/write-guard.d.ts +41 -0
- package/lib/browser-electron/write-guard.js +123 -0
- package/lib/index.d.ts +16 -0
- package/lib/index.js +14 -0
- package/lib/tool-browser/index.d.ts +31 -0
- package/lib/tool-browser/index.js +1931 -0
- package/package.json +95 -4
- package/screenshots.json +3 -0
- package/scripts/build-icons.mjs +80 -0
- package/scripts/capture-window.ps1 +79 -0
- package/scripts/crop-image.ps1 +20 -0
- package/scripts/smoke-browser-tools.mjs +1968 -0
- package/scripts/smoke-chrome-world.mjs +63 -0
- package/scripts/smoke-electron-host.mjs +50 -0
- package/src/browser/runtime.ts +470 -0
- package/src/browser/types.ts +649 -0
- package/src/browser-electron/auth-cookies.ts +125 -0
- package/src/browser-electron/chrome-state.ts +174 -0
- package/src/browser-electron/entry.ts +115 -0
- package/src/browser-electron/fingerprint.ts +45 -0
- package/src/browser-electron/host-main.ts +2330 -0
- package/src/browser-electron/icon.ts +26 -0
- package/src/browser-electron/page-chrome.ts +2046 -0
- package/src/browser-electron/provider.ts +3088 -0
- package/src/browser-electron/remote-host.ts +1004 -0
- package/src/browser-electron/task-summary.ts +10 -0
- package/src/browser-electron/task-thumbnail.ts +17 -0
- package/src/browser-electron/write-guard.ts +134 -0
- package/src/index.ts +52 -0
- package/src/tool-browser/index.ts +1974 -0
- package/src/types/electron-shim.d.ts +143 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# 重启后浸泡验证清单(SOAK-CHECKLIST)
|
|
2
|
+
|
|
3
|
+
> 前置:重启 DSH Web(使 provider/remote-host/tool-browser 新代码生效),然后在 DSH 会话中依次执行。
|
|
4
|
+
> 每个工具调用后记录结果;任何**白屏**立即停止并回滚 host-main.js 至上一提交。
|
|
5
|
+
|
|
6
|
+
## 0. 不重启也能跑的真实集成冒烟(先跑这个)
|
|
7
|
+
```bash
|
|
8
|
+
npm run smoke:browser-tools
|
|
9
|
+
```
|
|
10
|
+
用**真实 Electron 宿主 + 真实 Chromium** 驱动**真实 provider**,覆盖 **31 项**:
|
|
11
|
+
|
|
12
|
+
- **provider 层(12 项)**:navigate / content / snapshot / screenshot / **click 三种寻址(坐标、选择器、文字)** /
|
|
13
|
+
目标缺失的错误码 / waitForElement / **scrape 并发** / **cookie 导出→文件→清除→导入往返** / listTabs。
|
|
14
|
+
- **工具层(6 项)**:真实 `apply(ctx)` 注册的工具跑在真实 provider 上 —— `browser_open` / `browser_content` /
|
|
15
|
+
`browser_click`(文字寻址) / `browser_snapshot` / `browser_scrape`(start+status) / `browser_auth`(file)。
|
|
16
|
+
每次调用还会**逐字段比对声明的输出 schema**——DSH 会在运行时校验输出,而直接调 `execute()` 绕过了它。
|
|
17
|
+
- **输入真的到达页面(10 项)**:左键 → 页面应收到 `mousedown,mouseup,click`;右键 → 页面自己的
|
|
18
|
+
`contextmenu` handler 应收到且 `button:2`;修饰键 → `shiftKey` 与 `ctrlKey` 均应为真;
|
|
19
|
+
拖拽 → 手势应跨多次移动、按在源、释放在目标;键盘 → 页面应收到 `keydown`(Enter);
|
|
20
|
+
输入法 → 聚焦的输入框内容应变成所输入文本;**`click_ref`**(文档里的主要交互方式) → 快照取 ref
|
|
21
|
+
再点,页面应收到 `mousedown,click`;双击 → 页面应收到 `dblclick`;滚动 → `scrollY` 应真的变化
|
|
22
|
+
(页面本身不够长时先注入高元素);`fill` 选 select → 其 `value` 应变成所设值。
|
|
23
|
+
**这一组全部断言「页面真的收到了」,而不是「调用返回了」** —— 后者会漏掉整类静默失效。
|
|
24
|
+
**这三项曾长期为假绿**(只验证「目标解析对了」,空串也算通过)。它们现在能通过,靠的是
|
|
25
|
+
`Emulation.setFocusEmulationEnabled` —— **Chromium 会在渲染进程自认未聚焦时丢弃合成的鼠标按压**,
|
|
26
|
+
而移动不受此门控,所以 `hover` 一直正常、掩盖了点击全废。`data:` URL 的渲染器在进程内不做这个门控,
|
|
27
|
+
因此**本地用 `data:` 页面做输入验证会得出错误的「一切正常」** —— 必须打真实站点。
|
|
28
|
+
- **并行与规模(3 项)**:双任务(A 跑抓取时 B 的 snapshot 应在毫秒级返回)/ 两个任务各持独立会话 /
|
|
29
|
+
**100 个 URL @ 并发 8**(应为 100 行、100 个不同 `seq`、0 失败,结束后标签页数回到 1)。
|
|
30
|
+
|
|
31
|
+
- 它自带 profile(`DSH_BROWSER_PLUS_USER_DATA` 指向临时目录),**不与正在运行的 DSH 抢 profile 锁**,所以可以在 DSH 运行时跑;会短暂弹出一个窗口。
|
|
32
|
+
- 退出码 0 = 全绿。任何 FAIL 都会打印期望与实际。
|
|
33
|
+
- 这是唯一覆盖「provider → RPC → host-main → CDP → Chromium」整条链路的检查:单测用的是假宿主,够不到这一层。
|
|
34
|
+
- **历史**:它第一次跑就抓到一个真 bug —— 文字匹配的候选标签表漏了 `p`,导致「正文被拆成逐字符 span」的页面(example.com 现在就是这样)匹配不到容器。
|
|
35
|
+
|
|
36
|
+
## 1. 对话框自动处理
|
|
37
|
+
- [ ] `browser_open https://example.com`(host child 全新启动,无白屏)
|
|
38
|
+
- [ ] `browser_execute` 脚本 `setTimeout(() => { window.confirm('soak'); }, 0); 'scheduled'` → 页面不卡
|
|
39
|
+
- [ ] 二次 `browser_execute Date.now()` 返回数字(confirm 已自动 accept)
|
|
40
|
+
- [ ] `browser_history` 出现 `#n dialog ok {"type":"confirm",...}`
|
|
41
|
+
|
|
42
|
+
## 2. 输入工具(GUI 受控)
|
|
43
|
+
- [ ] `browser_execute` 聚焦输入后 `browser_press_key key="Enter"` → 快照见行为变化;history 有 pressKey
|
|
44
|
+
- [ ] `browser_press_key key="a" modifiers=["ctrl"]`(键盘事件低位键 'a')
|
|
45
|
+
- [ ] `browser_double_click` 选中文本段;history 有 doubleClick
|
|
46
|
+
- [ ] `browser_hover` 导航项 → `browser_screenshot` 目视 hover 态;history 有 hover
|
|
47
|
+
- [ ] `browser_execute` 注入 `<input type=file>` → 在工作目录/临时目录建样本文件 → `browser_upload_file filePath=<该文件绝对路径>` → `browser_execute` 读 `input.files[0]?.name` 与文件同名
|
|
48
|
+
- [ ] 传根外路径(如 `C:\Windows\win.ini`)调用 `browser_upload_file` → 必须报 `BROWSER_READ_PATH_DENIED`,且页面收不到该文件
|
|
49
|
+
|
|
50
|
+
## 3. 等待与定位
|
|
51
|
+
- [ ] `browser_wait_for selector="a[href]"` 立即命中(iana.org)
|
|
52
|
+
- [ ] 动态元素:注入延时节点后 `browser_wait_for selector="#late"` 命中
|
|
53
|
+
- [ ] `browser_snapshot` 每行含 `loc=`,结果含 `snapshotId`
|
|
54
|
+
- [ ] `browser_click_ref(snapshotId, ref)` 点击快照中的链接或按钮;导航后用旧 snapshotId 再调用应明确提示重新快照
|
|
55
|
+
- [ ] `browser_scroll` 无参数向下滚动;`browser_scroll_into_view(snapshotId, ref)` 将目标滚入视口
|
|
56
|
+
- [ ] `browser_back` / `browser_forward` / `browser_reload` / `browser_stop` 分别与页面工具栏行为一致
|
|
57
|
+
|
|
58
|
+
## 4. 共享窗口、任务管理器与 space
|
|
59
|
+
- [ ] 本会话 `browser_open https://www.iana.org/` → 一个可见 `dsh-browser-plus` 窗口和对应任务视图
|
|
60
|
+
- [ ] **另一个 DSH 会话** `browser_open https://www.w3.org/` → 仍只有**一个共享窗口**,页面任务管理器显示两个隔离任务
|
|
61
|
+
- [ ] `browser_space label="奖励任务"` → 当前浏览器任务在任务管理器中显示该标签,活动时标题为 `dsh-browser-plus — 奖励任务`;history 有 setSpace
|
|
62
|
+
- [ ] `browser_space`(无参)→ 列出全部浏览器任务(key + label);不产生新窗口
|
|
63
|
+
- [ ] 在任务管理器切换两个任务 → 各自 URL/标签正确;隐藏任务的浏览器操作更新自身状态但不抢当前可见页面
|
|
64
|
+
- [ ] Agent 执行长等待时任务卡显示“执行中”;调用 `browser_handoff state=waiting-user` 后显示“等待用户”
|
|
65
|
+
- [ ] 在当前任务卡点击“接管” → 显示“用户接管”,新的 Agent 页面操作被拒绝;点击“交还 Agent”后恢复操作
|
|
66
|
+
- [ ] `browser_tasks` 的状态、控制方、标签页数和最近动作与任务卡一致
|
|
67
|
+
- [ ] 关闭共享窗口后再次 `browser_open` → 窗口重建且不残留
|
|
68
|
+
|
|
69
|
+
## 5. 增量更新与性能
|
|
70
|
+
- [ ] 打开任务和轨迹面板后连续执行 100 次轻量页面操作 → 当前任务轨迹持续追加,其他任务卡不闪烁或重建
|
|
71
|
+
- [ ] 创建至少 3 个任务、每个 2 个标签 → 后台页面不持续刷新缩略图;打开任务面板并切换当前任务后才刷新当前缩略图
|
|
72
|
+
- [ ] 保持任务面板关闭执行操作 → 无可见缩略图捕获;重新打开后当前任务缩略图按需更新
|
|
73
|
+
|
|
74
|
+
## 6. 稳定性
|
|
75
|
+
- [ ] 连续导航 5 站(example.com → bing.com → w3.org → iana.org → example.com)→ 无白屏,每窗口有且仅有一个视图
|
|
76
|
+
- [ ] 回收 Electron child(Get-CimInstance ... Stop-Process)→ 下一次工具调用自动重启、无残留窗口
|
|
77
|
+
- [ ] `browser_auth action="flush"` → cookies 数量正常(换名安装前迁移用)
|
|
78
|
+
|
|
79
|
+
## 7. 已知 deferred minors(合并后择机)
|
|
80
|
+
见 `.superpowers/sdd/2026-08-21-dsh-browser-plus-ego-features/progress.md` 的 "minor (deferred)" 行(全部为非阻塞风格/文档项)。
|
|
81
|
+
## 8. chrome 隔离世界(可选,默认关)
|
|
82
|
+
|
|
83
|
+
仅在把 `browser-electron.chromeWorld` 设为 `isolated` 后执行。这一步会改变工具栏的注入世界,必须逐项人工确认后才可切换默认值:
|
|
84
|
+
|
|
85
|
+
**可自动验证的部分**(不需要 DSH 重启,自己起一个隔离 profile 的宿主):
|
|
86
|
+
```bash
|
|
87
|
+
npm run smoke:chrome-world
|
|
88
|
+
```
|
|
89
|
+
它做 **A/B 对照**并断言:两种模式下工具栏都挂载 ✓;**默认(main)模式会把 `__dshTasks`/`__dshTrail` 泄露给页面** ✗;`isolated` 模式下两者对页面**均为 `undefined`** ✓ —— 后者正是下面第 4 条的核心断言。
|
|
90
|
+
**注意**:脚本**不**断言 `__dshBrowserTaskAction` —— 它是**异步出现**的(2.5s 与 3s 两次测量结果不同),固定等待测不准,故只记录不断言。
|
|
91
|
+
|
|
92
|
+
- [ ] `browser_open https://example.com` → 工具栏正常显示,顶部中央悬停可展开
|
|
93
|
+
- [ ] 点击「接管」→ 状态变为等待用户;点击「交还 Agent」→ 恢复(隔离世界内 binding 仍能触发 set-control-owner)
|
|
94
|
+
- [ ] 打开任务面板与轨迹面板 → 任务卡、缩略图、操作轨迹正常渲染与追加
|
|
95
|
+
- [ ] 在页面控制台执行 `[typeof window.__dshTasks, typeof window.__dshTrail, typeof window.__dshBrowserTaskAction]` → 三项**全部为 undefined**
|
|
96
|
+
- [ ] 切换任务后,旧视图的 chrome 停表(无残留定时器);切回后工具栏与面板状态正确
|
|
97
|
+
- [ ] 连续导航 5 站 → 无白屏、工具栏每次都重新出现(每次导航会新建一个隔离世界)
|
|
98
|
+
- [ ] 回收 Electron child → 下一次调用自愈后工具栏仍正常
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# 架构说明
|
|
2
|
+
|
|
3
|
+
## 三层结构
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
agent (browser_* 工具)
|
|
7
|
+
→ ctx.browser (seam, dsh-browser-plus/browser)
|
|
8
|
+
→ dsh-browser-plus/browser-electron (provider)
|
|
9
|
+
→ ElectronBrowserViewHost (由宿主外壳提供)
|
|
10
|
+
→ WebContentsView + webContents.debugger (CDP)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### seam 层(`src/browser/`)
|
|
14
|
+
|
|
15
|
+
`BrowserRuntime` 以 Cordis Service 形式注册为 `ctx.browser`:
|
|
16
|
+
|
|
17
|
+
- **provider 注册**:`registerBrowserProvider(provider)` 登记一个实现 `BrowserProvider` 接口的 provider,重名抛 `BROWSER_DUPLICATE_PROVIDER`;disposer 挂在注册方自身的 fiber 上,插件重载时正确清理。
|
|
18
|
+
- **provider 选择**(执行期解析,不依赖顺序):
|
|
19
|
+
- 配置了 id 且注册且可用 → 该 provider
|
|
20
|
+
- 配置了 id 但未注册 → `BROWSER_PROVIDER_CONFIGURED_MISSING`
|
|
21
|
+
- 配置了 id 但不可用 → `BROWSER_PROVIDER_CONFIGURED_UNAVAILABLE`
|
|
22
|
+
- 未配置、恰一个可用 → 自动选择
|
|
23
|
+
- 未配置、多个可用 → `BROWSER_PROVIDER_AMBIGUOUS`
|
|
24
|
+
- 未配置、无可用 → `BROWSER_PROVIDER_UNAVAILABLE`
|
|
25
|
+
- 所有请求/结果类型(`BrowserProvider` 接口、`BrowserError` 错误码)定义在 `src/browser/types.ts`。
|
|
26
|
+
|
|
27
|
+
### provider 层(`src/browser-electron/`)
|
|
28
|
+
|
|
29
|
+
`ElectronBrowserProvider` 通过 `ElectronBrowserViewHost` 接缝操作视图,与 Electron 解耦:
|
|
30
|
+
|
|
31
|
+
- 会话 = 有序标签列表 + 历史;每次 `open()` 新建会话(工具层按任务缓存复用);
|
|
32
|
+
- 每个标签对应一个视图(handle);`showActive` 让宿主把活动标签的视图置顶;
|
|
33
|
+
- 页面驱动全部走 CDP:`Page.navigate` / history navigation / `Page.reload` / `Page.stopLoading` / `Runtime.evaluate` / `Input.dispatchMouseEvent` / `Input.insertText` / `Page.captureScreenshot`(兜底);
|
|
34
|
+
- 每个 tab 保留最近 10 个短生命周期快照引用;`browser_click_ref` 与 `browser_scroll_into_view` 用内部 CSS 路径、元素指纹、URL 和文档代次验证目标,变化后明确要求重新快照;
|
|
35
|
+
- **人类工具栏是页面注入 chrome**:通过 `Page.addScriptToEvaluateOnNewDocument` 在顶层文档挂载 closed Shadow DOM,不创建第二个 `WebContentsView`;
|
|
36
|
+
- **可见性不重挂**: `showView` 只切换 `setVisible`,导航、加载、标题和 resize 路径不得执行 `removeChildView` / `addChildView`;
|
|
37
|
+
- **截图优先走宿主原生 `capturePage`**(新增 `capture` 通道):CDP `captureScreenshot` 在窗口存在多个(隐藏)视图时会挂起,原生捕获对可见视图快速可靠,失败时自动回退 CDP(临时摘除其他视图保证单视图状态);
|
|
38
|
+
- **写入路径受白名单约束**:`browser_screenshot` 与 `browser_download` 落盘前经 `resolveWritePath()`(解析最深已存在祖先的真实路径,防 `..` 与符号链接逃逸),只允许 `browser-electron.writeRoots`(默认工作目录 + 系统临时目录)之内的路径;`browser_download` 复用 `admitUrl()`,与导航同一套 URL 准入;
|
|
39
|
+
- **读取路径同样受白名单约束**:`browser_upload_file` 在触碰 DOM 之前经 `resolveReadPath()` 校验(文件必须存在、按真实路径比对,链接逃逸会被拒),只允许 `browser-electron.readRoots`(默认同 `writeRoots`)之内的文件;
|
|
40
|
+
- **chrome 所在的世界可切换**:默认注入页面主世界;`browser-electron.chromeWorld: isolated` 时 chrome 改注入自己的隔离世界(`Page.createIsolatedWorld`),任务状态与 binding token 都不再落在页面可读的上下文里(binding 用 `executionContextName` 限定在该世界)。默认仍为 `main`,切换前需按 SOAK 第 8 节在真实窗口验证;
|
|
41
|
+
- **注入 chrome 的信任边界**:页面可见的轨迹经 `redactTraceParams()` 白名单脱敏(`type`→字符数、`execute`→丢弃脚本、URL→origin、路径→basename),被访问页面无法从轨迹里读走此前输入的文本或脚本;`Runtime.addBinding('__dshBrowserTaskAction')` 的每个 payload 必须携带 `createView` 生成的每视图随机 token,否则忽略,页面脚本无法伪造任务切换或控制权变更;
|
|
42
|
+
- 所有 CDP 调用都有超时兜底(`withTimeout`),避免卡死工具调用;
|
|
43
|
+
- 历史记录单调递增的 seq,截断(500 条)后不回绕;失败导航只记一条。
|
|
44
|
+
|
|
45
|
+
### 工具层(`src/tool-browser/`)
|
|
46
|
+
|
|
47
|
+
36 个 `browser_*` 工具,按**调用方任务**(`exec.agent.id`)维护独立浏览器会话:
|
|
48
|
+
|
|
49
|
+
- 会话缓存 `sessionsByTask`:同一任务复用同一会话,并发首开去重;
|
|
50
|
+
- 变更型调用经过每任务 FIFO 操作通道;相同 in-flight snapshot/content/无落盘截图会合并,避免重复 CDP 与渲染工作;
|
|
51
|
+
- `browser_tasks` 与 `browser_handoff` 暴露运行、等待用户、用户接管、失败和空闲状态;用户接管后新的变更型 Agent 调用会等待交还;
|
|
52
|
+
- `browser_reset_session` 关闭本任务会话并遗忘映射(即使 close 抛错也清除,下次调用重建);
|
|
53
|
+
- `browser_restrict` 维护**按调用任务隔离**的白名单(插件级 `allowedActions` 作为默认值,任务可为自己覆盖或解除),守卫所有非只读工具;一个任务的规则不会限制其它任务;
|
|
54
|
+
- 输出 schema 与返回值严格一致(DSH 运行时会校验,`additionalProperties: false` 下多一个字段都会报错)。
|
|
55
|
+
|
|
56
|
+
## 自托管实现(纯 `dsh web`)
|
|
57
|
+
|
|
58
|
+
没有桌面外壳时,`RemoteElectronViewHost` 接管:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
父进程(DSH) 子进程(Electron main)
|
|
62
|
+
RemoteElectronViewHost ──TCP JSON-RPC──▶ host-main.js
|
|
63
|
+
resolveElectronPath() BrowserWindow('dsh-browser-plus')
|
|
64
|
+
ElectronChildClient WebContentsView × N
|
|
65
|
+
DeferredRemoteView(物化缓存) webContents.debugger(CDP)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- **协议**:本机 loopback TCP,每行一个 JSON(`{ id, op, ... }` ↔ `{ id, ok, result|err }`);
|
|
69
|
+
- **Electron 定位**:优先 package-local 的精确 `42.9.3` optional dependency;其次只接受经 package metadata 验证为 `42.9.3` 的 `ELECTRON_PATH`、DSH 锚点或 pnpm store 候选;找不到即失败,绝不回退到 43.x。
|
|
70
|
+
- **稳健性**:子进程/套接字都有 `error` 监听(否则未捕获事件会炸掉整个 DSH 进程);子进程退出自动重启;物化失败可重试;下载有 64MB 上限与 60s 超时;cookie 导出/恢复有 30s 超时;
|
|
71
|
+
- **视图可见性**:所有任务键(DSH 会话)共用一个 `BrowserWindow`,每个任务有隔离视图;页面任务管理器选择可见任务,`showView` 对后台任务只更新其活动视图,不改变用户当前选择。切换仅用 `setVisible`,绝不 remove/re-add(capture 的 CDP 兜底仍只临时 detach/restore 同窗口兄弟视图);
|
|
72
|
+
- **任务状态传递**:页面首次挂载、导航重装 chrome 或任务切换时接收完整 bootstrap;常规状态、任务卡、面板和轨迹变化使用带 epoch/revision 的增量 patch。摘要中的 URL 只保留 origin,避免泄露完整路径与查询参数;
|
|
73
|
+
- **任务缩略图**:缩略图使用原生 `capturePage` 生成 JPEG data URL,最长边限制为 288px、质量 58、上限 180 KiB。仅在任务面板打开时为可见任务按需捕获,单飞、最短 2 秒间隔、32 项缓存;后台任务保留最后成功图像。
|
|
74
|
+
- **孤儿防护**:父进程断开时子进程自动退出,不留僵尸窗口;
|
|
75
|
+
- **cookie 落盘**:子进程使用独立 userData 目录(`<DSH_HOME>/dsh-browser-plus-host`),登录态跨重启保留(另有 `browser_auth` 手动导出/恢复/按域清理)。
|
|
76
|
+
|
|
77
|
+
## 关键设计决策
|
|
78
|
+
|
|
79
|
+
| 决策 | 原因 |
|
|
80
|
+
| --- | --- |
|
|
81
|
+
| 任务级会话隔离,共享 cookie | 并行任务不抢页面;登录一次到处可用 |
|
|
82
|
+
| 原生 capturePage 优先,CDP 兜底 | CDP 截图多视图挂起;原生捕获窗口未激活时失败——两通道互补 |
|
|
83
|
+
| 页面注入 chrome | 保留单视图合成树,避免第二个 WebContentsView 引发的人眼白屏 |
|
|
84
|
+
| 一个共享窗口 + 页面任务管理器 | 任务视图、标签与历史隔离;`browser_space` 命名浏览器任务,后台更新不抢可见页面 |
|
|
85
|
+
| 固定 Electron 42.9.3 | 43.4.1 合成器故障会导致截图/白屏;找不到 pin 时明确失败 |
|
|
86
|
+
| 独立 userData | 多实例争用默认目录导致 GPU 缓存/会话锁冲突 |
|
|
87
|
+
| withTimeout 全覆盖 | 卡死的 CDP 调用必须能被工具超时兜底 |
|
|
88
|
+
| 输出 schema 严格匹配 | DSH 运行时会校验返回值,多字段即报错 |
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# 工具参考
|
|
2
|
+
|
|
3
|
+
全部 41 个 `browser_*` 工具。守卫列:✅ 表示该动作受 `browser_restrict` 白名单约束(白名单**按调用任务隔离**,一个任务的规则不影响其它任务);只读工具永不拦截。
|
|
4
|
+
|
|
5
|
+
## 页面与导航
|
|
6
|
+
|
|
7
|
+
| 工具 | 参数 | 输出 | 守卫 | 说明 |
|
|
8
|
+
| --- | --- | --- | --- | --- |
|
|
9
|
+
| `browser_open` | `url`(必填), `newTab?` | 快照(snapshotId/url/title/elements/truncated/challenge) | ✅ | 打开 URL,返回带编号元素和短生命周期 `snapshotId`;`newTab: true` 在新标签打开 |
|
|
10
|
+
| `browser_snapshot` | `query?`, `limit?` | 快照 | – | 交互元素(输入框/按钮/链接)编号清单和 `snapshotId`,供精确定位。`query` 按 kind+label 做大小写不敏感过滤,**过滤发生在计上限之前**(所以能搜到第 60 个之后的元素);`limit` 1-1000,默认 60 |
|
|
11
|
+
| `browser_back` | – | `{ navigated }` | ✅ | 返回上一条历史;没有上一页时返回 `false` |
|
|
12
|
+
| `browser_forward` | – | `{ navigated }` | ✅ | 前进到下一条历史;没有下一页时返回 `false` |
|
|
13
|
+
| `browser_reload` | – | `{ reloaded }` | ✅ | 刷新当前页 |
|
|
14
|
+
| `browser_stop` | – | `{ stopped }` | ✅ | 停止当前页加载 |
|
|
15
|
+
| `browser_scroll` | `deltaX?`, `deltaY?` | `{ x,y,maxX,maxY }` | ✅ | 按 CSS 像素滚动;无参数时向下一个视口 |
|
|
16
|
+
| `browser_wait_for` | `selector`(必填), `timeoutMs?`, `visible?` | `{ found, selector, tag, text? }` | ✅ | 等待 CSS 选择器匹配的元素出现且可见(250ms 轮询,默认 15s 超时);SPA 动态内容交互前使用 |
|
|
17
|
+
| `browser_content` | `format`(html/markdown/txt/json,必填), `selector?`, `maxChars?`, `timeoutMs?` | `{ content, truncated }` | – | 抓取页面内容;`selector` 限定区域 |
|
|
18
|
+
| `browser_challenge` | – | `{ blocked, kind?, reason?, hint? }` | – | 检测人机验证(CAPTCHA/Cloudflare/reCAPTCHA/hCaptcha/Turnstile);阻塞时请用户处理 |
|
|
19
|
+
|
|
20
|
+
## 页面操作
|
|
21
|
+
|
|
22
|
+
| 工具 | 参数 | 输出 | 守卫 | 说明 |
|
|
23
|
+
| --- | --- | --- | --- | --- |
|
|
24
|
+
| `browser_click_ref` | `snapshotId`, `ref`(必填) | `{ clicked }` | ✅ | 以快照引用进行真实 CDP 点击;页面变化后返回过期引用错误并要求重新快照 |
|
|
25
|
+
| `browser_scroll_into_view` | `snapshotId`, `ref`, `block?` | `{ scrolled,x,y,maxX,maxY }` | ✅ | 将快照引用元素滚入可见区域 |
|
|
26
|
+
| `browser_execute` | `script`(必填), `args?` | `{ ok, value? / exception? }` | ✅ | 仅在引用、表单和原生浏览工具无法表达时执行页面 JS。`script` 可以是**表达式**,也可以是**语句体**(自动判别,语句体用 `return` 返回值,如 `const rows = [...document.querySelectorAll('a')]; return rows.length`);两种都不是时报可读的解析错误 |
|
|
27
|
+
| `browser_click` | `x?`, `y?`, `selector?`, `text?` | `{ clicked, x?, y?, target? }` | ✅ | 点击元素,**三种寻址任选其一**:① `x`+`y` 视口坐标(配合截图做视觉定位;**坐标不会自动滚动** —— 落在视口外会**明确报错**而不是静默丢弃,并报出那个点上是什么元素;覆盖图标/图片按钮/canvas);② `selector` CSS 选择器;③ `text` 可见文字(或 aria-label/value,不区分大小写)。后两者在**页内解析**并把元素滚入视野,所以「点登录按钮」**不必先 snapshot 拿 ref**(省一轮);返回 `target` 告诉你实际点到了什么。多个匹配时**最内层的可见元素胜出**(文字最短者优先,同长取更深者) |
|
|
28
|
+
| `browser_double_click` | `x?`, `y?`, `selector?`, `text?` | `{ clicked, x?, y?, target? }` | ✅ | 同上寻址方式;用于选中文本、展开忽略单击的 UI |
|
|
29
|
+
| `browser_hover` | `x?`, `y?`, `selector?`, `text?` | `{ hovered, x?, y?, target? }` | ✅ | 同上寻址方式;悬停不点击(触发 hover 态、tooltip、下拉菜单) |
|
|
30
|
+
| `browser_drag` | `from`(必填), `to`(必填), `steps?` | `{ dragged, from?, to? }` | ✅ | 拖拽:`from` 按下 → 中间移动 → 在 `to` 释放。两端都用与 `browser_click` 相同的寻址(`x`+`y` / `selector` / `text`)并先滚入视野。`steps` 默认 12、上限 60 —— 中间移动是滑块/可排序库监听的东西,**瞬移会被忽略**。用于滑块、可排序列表、canvas 编辑器。**只驱动指针式拖拽**:依赖 HTML5 拖放(`dragstart`/`drop`)的页面不会响应,那种页面请用其自带控件 |
|
|
31
|
+
| `browser_type` | `text`(必填) | `{ typed }` | ✅ | 向聚焦元素输入文本(CDP `Input.insertText`) |
|
|
32
|
+
| `browser_press_key` | `key`(必填), `modifiers?` | `{ pressed }` | ✅ | 向聚焦元素物理按键(keyDown+keyUp;Enter/Tab/F1-F12/方向键及 Ctrl+A 等修饰组合) |
|
|
33
|
+
| `browser_fill` | `fields`(必填,数组), `submit?` | `{ fields[], submitted }` | ✅ | 批量填表;字段按 `selector`/`name`/`label`/`placeholder` 匹配,值支持字符串/数字/布尔;单个字段失败不影响其余;`submit: true` 提交表单 |
|
|
34
|
+
| `browser_upload_file` | `filePath`(必填), `selector?` | `{ path }` | ✅ | 给文件输入附加本地文件(CDP `DOM.setFileInputFiles`,页面视为真实选择);缺省页面第一个 `input[type="file"]`;`filePath` 必须存在且落在 `browser-electron.readRoots` 之内,越界抛 `BROWSER_READ_PATH_DENIED` |
|
|
35
|
+
|
|
36
|
+
## 标签与会话
|
|
37
|
+
|
|
38
|
+
| 工具 | 参数 | 输出 | 守卫 | 说明 |
|
|
39
|
+
| --- | --- | --- | --- | --- |
|
|
40
|
+
| `browser_list_tabs` | – | `{ session, tabs[] }` | – | 当前会话的标签列表 |
|
|
41
|
+
| `browser_switch_tab` | `tabId`(必填) | `{ switched }` | ✅ | 按 id 切换标签;自托管下同步切换可见视图 |
|
|
42
|
+
| `browser_close_tab` | `tabId`(必填) | `{ closed }` | – | 关闭标签;关闭活动标签后激活下一个 |
|
|
43
|
+
| `browser_reset` | – | `{ reset }` | ✅ | 关闭本任务所有标签,回到一个空白标签 |
|
|
44
|
+
| `browser_session` | – | `{ session, tabs[] }` | – | 查看本任务的浏览器会话与标签 |
|
|
45
|
+
| `browser_space` | `label?` | `{ label? / spaces[] }` | – | 命名本浏览器任务或列出浏览器任务;页面任务管理器控制哪个隔离任务视图显示在共享窗口中 |
|
|
46
|
+
| `browser_tasks` | – | `{ tasks[] }` | – | 查看每个任务的状态、控制方、标签页数、最近动作和错误摘要 |
|
|
47
|
+
| `browser_handoff` | `state`(`waiting-user` / `agent`) | 当前任务状态 | – | 让 Agent 等待用户操作,或在用户交还后恢复 Agent 控制 |
|
|
48
|
+
| `browser_reset_session` | – | `{ reset }` | ✅ | 关闭并重建本任务的浏览器会话(崩溃/卡死后恢复) |
|
|
49
|
+
|
|
50
|
+
## 历史与下载
|
|
51
|
+
|
|
52
|
+
| 工具 | 参数 | 输出 | 守卫 | 说明 |
|
|
53
|
+
| --- | --- | --- | --- | --- |
|
|
54
|
+
| `browser_history` | – | `{ entries[] }` | – | 操作日志(最新在后),含 seq/action/ok/params/result/error |
|
|
55
|
+
| `browser_replay` | `seq`(必填) | `{ replayed }` | ✅ | 按序号回放某一步(navigate/execute/click/type) |
|
|
56
|
+
| `browser_download` | `url`(必填), `savePath`(必填) | `{ path }` | ✅ | 带会话 cookie 下载到本地(上限 64MB,受 CORS 约束);`savePath` 受 `writeRoots` 限制,URL 与导航共用 HTTP(S) 准入(拒绝内嵌凭据) |
|
|
57
|
+
|
|
58
|
+
## 登录态与安全
|
|
59
|
+
|
|
60
|
+
| 工具 | 参数 | 输出 | 守卫 | 说明 |
|
|
61
|
+
| --- | --- | --- | --- | --- |
|
|
62
|
+
| `browser_auth` | `action`(flush/restore/clear,必填), `cookies?`, `file?`, `domain?`, `name?`, `all?` | `{ cookies[]? / restored? / failed? / removed? , names[]? }` | ✅ | 导出/恢复/清理 cookie(自托管可用);flush 返回列表,restore 写回,clear 按 domain(含子域)与/或 name 精确删除,未限定范围时必须显式 `all: true` |
|
|
63
|
+
| `browser_restrict` | `allowed?` | `{ restrictedTo[] }` | – | 设置**本任务**的动作白名单;空列表解除本任务的限制;未知工具名报错 |
|
|
64
|
+
|
|
65
|
+
## 批量抓取
|
|
66
|
+
|
|
67
|
+
| 工具 | 参数 | 输出 | 守卫 | 说明 |
|
|
68
|
+
| --- | --- | --- | --- | --- |
|
|
69
|
+
| `browser_scrape` | `action?`(start/status/stop/list), `urls?`, `script?`, `outPath?`, `waitFor?`, `timeoutMs?`, `concurrency?`, `id?` | `{ id?, state?, total?, done?, failed?, path?, error?, jobs[]? }` | ✅ | 后台批量访问 URL,把**每页一行 JSON** 追加到文件,结果**不经模型往返**——一千条与一条的 token 成本相同。`action=start` 立即返回,用 `action=status` 轮询。每行是 `{ seq, url, ok, data }` 或 `{ seq, url, ok, error }`(`seq` = 该 URL 在输入里的下标;并发时行按**完成顺序**落盘,按 `seq` 排序即可还原),**产生即落盘**,所以 `stop` 或中断都保留已抓到的行;单页失败不终止整批(计入 `failed`)。`outPath` 受 `writeRoots` 限制并在开始时截断。批次使用**自己的标签页**(不激活,所以不会抢走你正在看的页面,也不与同任务的工具调用争用),结束后销毁。`concurrency` 默认 1、上限 8,每个 worker 占一个标签页;后台批次**跳过 250ms 的绘制等待**(它只读 DOM 不读像素),实测单页开销约 6ms。
|
|
70
|
+
|
|
71
|
+
## 截图
|
|
72
|
+
|
|
73
|
+
| 工具 | 参数 | 输出 | 守卫 | 说明 |
|
|
74
|
+
| --- | --- | --- | --- | --- |
|
|
75
|
+
| `browser_screenshot` | `fullPage?`, `savePath?` | `{ dataUrl, path? }` | – | PNG 截图;`savePath` 落盘供视觉模型读取,且必须落在 `browser-electron.writeRoots` 之内 |
|
|
76
|
+
|
|
77
|
+
## 对话框与诊断
|
|
78
|
+
|
|
79
|
+
| 工具 | 参数 | 输出 | 守卫 | 说明 |
|
|
80
|
+
| --- | --- | --- | --- | --- |
|
|
81
|
+
| `browser_dialog` | `action`(`inspect` / `accept` / `dismiss`,必填), `promptText?` | `{ dialog?, policy }` | – | 查看/引导 JS 对话框(`alert`/`confirm`/`prompt`)。**对话框会冻住渲染器**,所以宿主默认立刻接受并记下内容 —— `inspect` 报告上一次(排空后再报,所以紧跟着触发它的那次调用也能看到)与当前策略;要驱动「确认删除」这类页面,先用 `dismiss`(或 `accept`,配 `promptText` 填 `prompt()`)设好**下一个**怎么答,再触发它 |
|
|
82
|
+
| `browser_console` | `level?`, `limit?`, `clear?` | `{ messages[] }` | – | 读控制台消息与未捕获异常(有界环形缓冲,各 200 条,最新在后)。**读不清空**,`clear: true` 才清;`level` 过滤 `log`/`info`/`warning`/`error`/`debug` |
|
|
83
|
+
| `browser_network` | `urlContains?`, `failedOnly?`, `limit?`, `clear?` | `{ requests[] }` | – | 读网络请求:`method`/`url`/`status`/`mime`/`kind`/`ms`/`failed`。同样**读不清空**;`failedOnly` 只看没跑完的 |
|
|
84
|
+
|
|
85
|
+
## 设备模拟
|
|
86
|
+
|
|
87
|
+
| 工具 | 参数 | 输出 | 守卫 | 说明 |
|
|
88
|
+
| --- | --- | --- | --- | --- |
|
|
89
|
+
| `browser_emulate` | `width?`, `height?`, `deviceScaleFactor?`, `mobile?`, `userAgent?`, `colorScheme?`, `clear?` | `{ applied[] }` | ✅ | 在活动标签上模拟设备:视口尺寸(可带移动端行为与 DPR)、自定义 UA、`prefers-color-scheme`。这是**渲染层覆盖**,窗口本身不变大;`clear: true` 一次撤销三样 |
|
|
90
|
+
|
|
91
|
+
**页面没反应时怎么查**(这三件套比截图有用)
|
|
92
|
+
```
|
|
93
|
+
browser_console → 看有没有报错 / 未捕获异常
|
|
94
|
+
browser_network urlContains="/api" → 请求到底发出去没有、状态码是多少
|
|
95
|
+
browser_execute → 页面状态探针(比如 elementFromPoint(x,y) 到底命中谁)
|
|
96
|
+
```
|
|
97
|
+
## 常用组合
|
|
98
|
+
|
|
99
|
+
**调研一个网站**
|
|
100
|
+
```
|
|
101
|
+
browser_open https://site → browser_content format=markdown → browser_snapshot → browser_click_ref(snapshotId, ref) → 逐页浏览
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**登录并下载文件**
|
|
105
|
+
```
|
|
106
|
+
browser_open https://site/login → browser_fill(用户名/密码) submit=true →
|
|
107
|
+
等待跳转 → browser_download(url, savePath)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**表单填写(React/Vue 页面)**
|
|
111
|
+
```
|
|
112
|
+
browser_snapshot → browser_fill(fields=[{name:'email',value:'a@b.c'},{label:'密码',value:'***'}], submit=true)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**误操作恢复**
|
|
116
|
+
```
|
|
117
|
+
browser_reset_session → browser_open(重新开始)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**遇到验证码**
|
|
121
|
+
```
|
|
122
|
+
browser_challenge → browser_handoff state=waiting-user → 用户在共享窗口完成验证并交还 Agent →
|
|
123
|
+
browser_snapshot 复查
|
|
124
|
+
```
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# 用户指南
|
|
2
|
+
|
|
3
|
+
## 环境要求
|
|
4
|
+
|
|
5
|
+
- DeepSeek Harness(dsh)且安装了 `web` profile
|
|
6
|
+
- **Electron 运行时**(可选 package dependency):插件固定 `42.9.3` 并优先使用自身安装的 binary;纯 `dsh web` 下找不到该版本会明确失败,避免 43.x compositor 故障。
|
|
7
|
+
|
|
8
|
+
## 安装
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
# 从 npm 安装(已发布)
|
|
12
|
+
dsh plugin --profile web add github:ParticleLight/dsh-browser-plus
|
|
13
|
+
|
|
14
|
+
# 或从源码目录(独立仓库,一插件一仓库)
|
|
15
|
+
dsh plugin --profile web add <本仓库路径>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
安装会链接插件、把 `dsh-browser-plus` 加入 profile 的 bundle 层,并挂载三行:
|
|
19
|
+
|
|
20
|
+
| 行 | 子路径 | 角色 |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| `browser` | `dsh-browser-plus/browser` | `ctx.browser` 能力 seam(始终挂载) |
|
|
23
|
+
| `browser-electron` | `dsh-browser-plus/browser-electron` | Electron CDP provider |
|
|
24
|
+
| `tool-browser` | `dsh-browser-plus/tool-browser` | `browser_*` 模型侧工具 |
|
|
25
|
+
|
|
26
|
+
> 没有桌面外壳时插件**自托管**:自己拉起一个标题为 `dsh-browser-plus` 的 Electron 窗口,`browser_*` 工具照常可用。
|
|
27
|
+
|
|
28
|
+
## 配置
|
|
29
|
+
|
|
30
|
+
| 行 | 配置项 | 类型 | 默认 | 说明 |
|
|
31
|
+
| --- | --- | --- | --- | --- |
|
|
32
|
+
| `browser-electron` | `viewHost` | 对象 | 必填 | 宿主提供的 `ElectronBrowserViewHost`(通常 `!!js ctx.get('electronViewHost')`) |
|
|
33
|
+
| `browser-electron` | `httpOnly` | 布尔 | `true` | 仅允许 HTTP(S) 导航;`file:`/`data:` 等拒绝 |
|
|
34
|
+
| `browser-electron` | `writeRoots` | 字符串数组 | `[工作目录, 系统临时目录]` | `browser_screenshot`/`browser_download` 允许写入的绝对目录;越界拒绝 |
|
|
35
|
+
| `browser-electron` | `readRoots` | 字符串数组 | 同 `writeRoots` | `browser_upload_file` 与 `browser_auth action=restore file=…` 允许读取的绝对目录;越界拒绝 |
|
|
36
|
+
| `browser-electron` | `chromeWorld` | `main` / `isolated` | `main` | 注入 chrome 所在的 JS 世界。`isolated` 让页面读不到任务状态与 binding token,但每个文档多一个 CDP context——**需先在真实窗口验证工具栏**(见 SOAK 第 8 节) |
|
|
37
|
+
| `browser-electron` | `snapshotMaxElements` | 数字 | `60` | 快照最多收录的交互元素数 |
|
|
38
|
+
| `browser-electron` | `contentMaxChars` | 数字 | `100000` | 内容抓取默认字符上限 |
|
|
39
|
+
| `tool-browser` | `timeoutMs` | 数字 | `60000` | 工具协作超时(ms) |
|
|
40
|
+
| `tool-browser` | `allowedActions` | 字符串数组 | 无 | 插件级初始动作白名单;每个任务可用 `browser_restrict` 为自己覆盖或解除 |
|
|
41
|
+
| `tool-browser` | `tabTools` | 布尔 | `true` | 是否注册标签管理工具 |
|
|
42
|
+
|
|
43
|
+
## 快速上手(给 agent 的提示词示例)
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
1. browser_open 打开 https://example.com
|
|
47
|
+
2. browser_snapshot 查看页面有哪些可交互元素、编号和 snapshotId
|
|
48
|
+
3. 优先 browser_click_ref(snapshotId, ref) 或 browser_scroll_into_view(snapshotId, ref),页面变化后重新快照
|
|
49
|
+
4. 需要填表时用 browser_fill(按 name/label/placeholder 匹配,一次填多个字段)
|
|
50
|
+
5. 后退、前进、刷新、停止和滚动使用 browser_back/browser_forward/browser_reload/browser_stop/browser_scroll
|
|
51
|
+
6. 遇到验证码(browser_challenge 或快照标注 CHALLENGE)时,调用 browser_handoff state=waiting-user,停下等待用户交还任务
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 操作纪律
|
|
55
|
+
|
|
56
|
+
- **优先用快照引用**:先取得 `snapshotId`,再用 `browser_click_ref` 或 `browser_scroll_into_view`;引用过期时重新快照,而不是猜测同名控件。
|
|
57
|
+
- **常用浏览操作不用写脚本**:后退、前进、刷新、停止和滚动优先使用对应 `browser_*` 工具。
|
|
58
|
+
- **表单优先批量填写**:React/Vue 页面用 `browser_fill`;坐标点击只保留给 canvas、图标等没有语义节点的控件。
|
|
59
|
+
- **browser_execute 是最后手段**:只在新工具无法表达的页面特有操作中使用。
|
|
60
|
+
- **DPR 注意**:CDP 输入使用 CSS 像素;高 DPI 屏上若点击落空,用 `elementFromPoint` 校准,不要盲试坐标。
|
|
61
|
+
|
|
62
|
+
## 多任务并行
|
|
63
|
+
|
|
64
|
+
每个 DSH 会话(任务)拥有独立的浏览器会话(独立标签页与历史),并发任务互不干扰:
|
|
65
|
+
|
|
66
|
+
- `browser_session` 查看本任务的会话与标签;
|
|
67
|
+
- `browser_reset_session` 关闭并重建本任务的会话(崩溃或卡死后用它恢复)。
|
|
68
|
+
|
|
69
|
+
当前版本使用**一个共享可见浏览器窗口**,每个任务仍有隔离的任务视图、标签与历史。页面任务管理器切换可见任务;后台任务操作只更新自己的视图,不会抢走当前页面。`browser_space label="..."` 为本浏览器任务命名,`browser_space`(无参)列出全部浏览器任务。
|
|
70
|
+
|
|
71
|
+
工具栏默认收在页面上方。鼠标移到页面顶部中间时会出现小圆形下箭头,点击后工具栏从上方滑出;工具栏最右侧的上箭头会收回工具栏,并同时关闭书签、任务与轨迹浮层。任务按钮打开左侧工作区面板,操作轨迹按钮在桌面端打开右侧工作区面板。顶部工具栏最右侧常驻“接管 / 交还 Agent”控件,不必先打开任务面板;任务卡继续显示执行中、等待用户、用户接管、失败和空闲状态。接管期间新的 Agent 页面操作会停止,快照和内容读取仍可用于确认状态。
|
|
72
|
+
|
|
73
|
+
用户直接点击页面、编辑表单或使用非滚动键盘操作时,会自动切换为用户控制;滚轮、触摸拖动、滚动条操作,以及页面非编辑区的上下翻页键不会触发接管。Agent 自己的 CDP 鼠标和键盘输入带有短暂抑制标记,不会误交还控制权。
|
|
74
|
+
|
|
75
|
+
任务与轨迹状态采用版本化增量更新:普通操作只更新受影响的任务卡和一条轨迹。缩略图仅在工作区打开时按需刷新当前可见任务,后台任务保留最后图像。
|
|
76
|
+
|
|
77
|
+
页面原生 `alert/confirm/prompt` **默认**会被立刻接受(页面永不卡死),内容记录在 `browser_history`(`dialog` 条目)中。要驱动「确认删除」这类页面,先用 `browser_dialog` 设好**下一个**对话框怎么答(`accept`/`dismiss`,`prompt()` 可配 `promptText`)再触发它;`inspect` 报告上一次。
|
|
78
|
+
|
|
79
|
+
按键、双击、悬停、文件上传、等待元素、快照引用和原生导航:见 `browser_press_key` / `browser_double_click` / `browser_hover` / `browser_upload_file` / `browser_wait_for` / `browser_click_ref` / `browser_back` 等工具(完整参考见 [工具参考](tool-reference.md))。
|
|
80
|
+
|
|
81
|
+
登录态(cookie)为所有任务共享;可用 `browser_auth` 导出/恢复,重启后不丢。
|
|
82
|
+
|
|
83
|
+
某些站点(带 WAF 挑战的站点)会用**轮换名称**续期挑战 Cookie,旧代不会自动消失;两代共存时站点可能直接返回 400/412。遇到这种情况用 `browser_auth action="clear" domain="example.com"`(可再加 `name` 只删一个)清掉旧代,不必清空整个 profile;清除只影响该域及其子域,其他站点登录态保留。
|
|
84
|
+
|
|
85
|
+
## FAQ
|
|
86
|
+
|
|
87
|
+
**Q:纯 `dsh web` 能用吗?**
|
|
88
|
+
能。插件自托管:自己拉起 Electron 窗口,无需桌面外壳。
|
|
89
|
+
|
|
90
|
+
**Q:找不到 Electron?**
|
|
91
|
+
插件只接受 Electron `42.9.3`:优先自身 optional dependency,其次校验 `ELECTRON_PATH`、DSH 锚点与 pnpm store 候选。找不到时重新安装插件依赖,或把 `ELECTRON_PATH` 指向一个经 package metadata 验证为 `42.9.3` 的 binary。
|
|
92
|
+
|
|
93
|
+
**Q:截图失败或挂起?**
|
|
94
|
+
确认运行时是 Electron `42.9.3`,不要用 43.x。自托管截图优先走原生 `capturePage`,共享窗口内存在多个视图且目标未激活时自动兜底到 CDP。
|
|
95
|
+
|
|
96
|
+
**Q:浏览器窗口不见了?**
|
|
97
|
+
窗口标题为 `dsh-browser-plus`(显示当前任务标签时为 `dsh-browser-plus — <名>`);所有任务共享这一可见窗口,通过页面任务管理器切换各自隔离视图。若子进程崩溃会自动重启;重启后旧会话失效,调用 `browser_reset_session` 重建。
|
|
98
|
+
|
|
99
|
+
**Q:下载报 CORS 错误?**
|
|
100
|
+
`browser_download` 在页面上下文内 `fetch`,受同源/CORS 约束;跨域文件请先在同源页面内操作,或直接请求用户提供。
|
|
101
|
+
|
|
102
|
+
**Q:如何禁止 agent 乱点?**
|
|
103
|
+
`browser_restrict` 设置白名单(如只允许 `browser_snapshot`/`browser_content`);传空列表解除。**规则按任务隔离**:一个任务设的白名单不会影响其它并行任务;`tool-browser.allowedActions` 配置作为所有任务的默认值。
|
|
104
|
+
|
|
105
|
+
**Q:能直接导入 Edge/Chrome 的登录状态吗?**
|
|
106
|
+
**不能自动导入**,这是浏览器的安全机制而非本插件的限制:Chrome/Edge 127+ 用 **App-Bound Encryption** 加密 cookie 值(实测本机 Chrome 的 cookie 全部是 `v20` 前缀),密钥绑定浏览器自身可执行文件身份,**复制 profile 也解不开**——实测把 `Local State` + `Default/Network/Cookies` 复制到临时目录再启动 Chrome,`Storage.getCookies` 返回 0 条。两条可行路径:
|
|
107
|
+
|
|
108
|
+
1. **在本插件自己的浏览器里登录一次(推荐)**:profile 是持久的(`<DSH_HOME>/dsh-browser-plus-host`),点页面工具栏的「接管」手动登录,之后 agent 的任务就一直带着这份登录态。
|
|
109
|
+
2. **导入用户导出的 cookie 文件**:用扩展或 DevTools 导出成 JSON,然后 `browser_auth { action: "restore", file: "<路径>" }`。文件须在 `browser-electron.readRoots` 内(默认:工作目录与系统临时目录);接受裸数组或 `{"cookies": [...]}` 两种形状,**两种字段风格都认**:① 本插件导出的 `url` 风格;② **浏览器扩展(Cookie-Editor / EditThisCookie)与 Edge 自带导出的 `domain` + `path` 风格 —— 没有 `url` 字段,插件会自行推导**(这正是「从浏览器导出再导入」的实际用法)。`sameSite` 同时接受 Chromium 拼写(`no_restriction`)与 Playwright 拼写(`None`/`Lax`/`Strict`)。格式不合法的条目会被跳过并在 `failed` 里计数。
|
|
110
|
+
|
|
111
|
+
## 故障排查
|
|
112
|
+
|
|
113
|
+
| 现象 | 可能原因 | 处理 |
|
|
114
|
+
| --- | --- | --- |
|
|
115
|
+
| `BROWSER_SESSION_UNKNOWN` | 子进程重启后旧会话失效 | `browser_reset_session` |
|
|
116
|
+
| 工具超时 | 页面卡死/未渲染完成 | 稍后重试;`browser_reset` 重置标签 |
|
|
117
|
+
| 导航被拒 | 非 HTTP(S) 协议,或 URL 内嵌凭据 | 检查 URL;`httpOnly` 配置 |
|
|
118
|
+
| `BROWSER_WRITE_PATH_DENIED` | 落盘路径不在允许根内 | 改存工作目录/临时目录,或在 `browser-electron.writeRoots` 追加该目录 |
|
|
119
|
+
| `BROWSER_READ_PATH_DENIED` | 上传的文件不存在,或不在允许根内 | 改传工作目录/临时目录内的文件,或配置 `browser-electron.readRoots` |
|
|
120
|
+
| 下载被拒 | 非 HTTP(S),或 URL 内嵌凭据 | 下载与导航共用 `admitUrl` 准入;凭据请走页面登录态 |
|
|
121
|
+
| 快照为空 | 页面尚未加载 | 等待后重试 `browser_snapshot` |
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# 为什么做共享真实浏览器
|
|
2
|
+
|
|
3
|
+
## 问题
|
|
4
|
+
|
|
5
|
+
LLM agent 需要"上网"。传统做法是给 agent 一个**无头浏览器**(Playwright/Puppeteer 等)或**网页抓取工具**:
|
|
6
|
+
|
|
7
|
+
- 无头浏览器:agent 在看不见的页面里操作,用户无法确认它在做什么,也无法中途接管;遇到登录、验证码、人机检测时寸步难行。
|
|
8
|
+
- 网页抓取:只能读不能交互,且大量现代网站依赖 JS 渲染,抓到的内容失真。
|
|
9
|
+
|
|
10
|
+
这两条路都绕开了"用户"——浏览器成了 agent 的私有工具,而不是用户和 agent 共用的工具。
|
|
11
|
+
|
|
12
|
+
## 方案:共享真实浏览器
|
|
13
|
+
|
|
14
|
+
`dsh-browser-plus` 的思路很朴素:**让 agent 驱动用户眼前那个真实的浏览器窗口**。
|
|
15
|
+
|
|
16
|
+
- 浏览器是原生 `WebContentsView`,用户直接看到 agent 的每一步操作(打开页面、填写表单、点击、滚动);
|
|
17
|
+
- 用户**随时可以上手接管**:输入、点击、登录、过验证码——做完之后 agent 继续;
|
|
18
|
+
- agent 通过 CDP 在同一个页面里执行 JS,拿到的是与用户所见完全一致的真实 DOM。
|
|
19
|
+
|
|
20
|
+
一句话:浏览器不是 agent 的"黑盒工具",而是**用户与 agent 之间的共享工作台**。
|
|
21
|
+
|
|
22
|
+
## 与无头方案的能力对比
|
|
23
|
+
|
|
24
|
+
| 能力 | 无头浏览器 | 网页抓取 | 本插件(共享真实浏览器) |
|
|
25
|
+
| --- | --- | --- | --- |
|
|
26
|
+
| 真实页面渲染 | ✅ | 部分 | ✅ |
|
|
27
|
+
| 复杂交互(填表/点击/拖拽) | ✅ | ❌ | ✅ |
|
|
28
|
+
| 用户可见、可接管 | ❌ | ❌ | ✅ |
|
|
29
|
+
| 保留登录态 | 需手动管理 | ❌ | ✅(`browser_auth` + cookie 落盘) |
|
|
30
|
+
| 人工处理验证码 | ❌ 卡死 | ❌ | ✅ 请用户点一下即可 |
|
|
31
|
+
| 多任务并行 | 需多实例 | – | ✅ 任务级会话隔离 |
|
|
32
|
+
|
|
33
|
+
## 设计取舍
|
|
34
|
+
|
|
35
|
+
- **登录态共享**:所有任务共享同一份 cookie(符合"用户登录一次,agent 到处可用");需要隔离时用 `browser_auth` 手动导出/恢复。
|
|
36
|
+
- **任务级会话隔离**:每个 DSH 会话(任务)拥有独立的浏览器会话(标签页与历史),并发任务互不抢页面;但窗口只有一个,当前活动的任务视图可见。
|
|
37
|
+
- **装好即用**:不依赖桌面外壳。有外壳时嵌入外壳视图;纯 `dsh web` 时插件自托管——自己拉起 Electron 窗口,通过本机 TCP JSON-RPC 驱动。
|
|
38
|
+
- **边界清晰**:可见视图、浏览器列布局属于宿主外壳;插件只负责 seam、provider 与工具,不含任何 UI。
|
|
39
|
+
- **人机验证不硬刚**:检测到 Cloudflare/reCAPTCHA/hCaptcha/Turnstile 时停下,请用户在共享窗口人工完成,而不是盲目重试。
|
|
40
|
+
|
|
41
|
+
## 什么场景不适合
|
|
42
|
+
|
|
43
|
+
- 需要**无头批量抓取**(成千上万页面):请用专门的抓取工具/服务,不必开窗口。
|
|
44
|
+
- 需要**每个任务完全独立的登录态**(互不可见):本插件默认共享 cookie;如需强隔离,可用多个 DSH 实例或 `browser_auth` 手动管理。
|
|
45
|
+
- 需要**浏览器列 UI / 布局管理**:那是宿主外壳的配套,本插件不包含。
|