dsh-builtin-browser 0.4.7 → 0.4.9

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/CHANGELOG.md +74 -0
  2. package/README.en.md +62 -491
  3. package/README.md +44 -470
  4. package/client.js +10 -10
  5. package/cordis.patch.yml +4 -4
  6. package/desktop-bridge/plugin-browser-bridge.js +1 -1
  7. package/docs/README.md +8 -4
  8. package/docs/architecture.md +82 -11
  9. package/docs/development.md +38 -0
  10. package/docs/limitations.md +49 -0
  11. package/docs/tool-reference.md +42 -42
  12. package/docs/user-guide.md +93 -37
  13. package/docs/why-browser.md +3 -3
  14. package/lib/browser/types.d.ts +2 -2
  15. package/lib/browser-electron/entry.d.ts +2 -2
  16. package/lib/browser-electron/entry.js +1 -1
  17. package/lib/browser-electron/history-store.d.ts +1 -1
  18. package/lib/browser-electron/history-store.js +1 -1
  19. package/lib/browser-electron/host-main.js +3 -3
  20. package/lib/browser-electron/provider.d.ts +9 -9
  21. package/lib/browser-electron/provider.js +14 -14
  22. package/lib/browser-electron/remote-host.js +1 -1
  23. package/lib/browser-electron/settings-store.d.ts +4 -4
  24. package/lib/browser-electron/system-browser.d.ts +60 -0
  25. package/lib/browser-electron/system-browser.js +257 -23
  26. package/lib/index.d.ts +1 -1
  27. package/lib/index.js +1 -1
  28. package/lib/tool-browser/element-fields.d.ts +4 -4
  29. package/lib/tool-browser/element-fields.js +6 -6
  30. package/lib/tool-browser/index.d.ts +6 -6
  31. package/lib/tool-browser/index.js +161 -139
  32. package/package.json +2 -2
  33. package/scripts/verify-host-compat.mjs +6 -6
  34. package/src/browser/types.ts +2 -2
  35. package/src/browser-electron/entry.ts +2 -2
  36. package/src/browser-electron/history-store.ts +1 -1
  37. package/src/browser-electron/host-main.ts +3 -3
  38. package/src/browser-electron/provider.ts +15 -15
  39. package/src/browser-electron/remote-host.ts +1 -1
  40. package/src/browser-electron/settings-store.ts +4 -4
  41. package/src/browser-electron/system-browser.ts +244 -16
  42. package/src/index.ts +1 -1
  43. package/src/tool-browser/element-fields.ts +6 -6
  44. package/src/tool-browser/index.ts +164 -140
@@ -24,9 +24,9 @@ dsh plugin --profile web add <本仓库路径>
24
24
  | `dsh-builtin-browser` | 包名本身 | **惰性根行**:不注册任何东西(空 `apply()`),只用来声明包名 —— 宿主的客户端插件扫描靠精确包名读到 `dsh.client`,少了这行设置栏不会出现 |
25
25
  | `browser` | `dsh-builtin-browser/browser` | `ctx.browser` 能力 seam(始终挂载) |
26
26
  | `browser-electron` | `dsh-builtin-browser/browser-electron` | Electron CDP provider |
27
- | `tool-browser` | `dsh-builtin-browser/tool-browser` | `browser_*` 模型侧工具 |
27
+ | `tool-browser` | `dsh-builtin-browser/tool-browser` | `dsh_browser_*` 模型侧工具 |
28
28
 
29
- > 没有桌面外壳时插件**自托管**:自己拉起一个标题为 `dsh-browser` 的 Electron 窗口,`browser_*` 工具照常可用。
29
+ > 没有桌面外壳时插件**自托管**:自己拉起一个标题为 `dsh-browser` 的 Electron 窗口,`dsh_browser_*` 工具照常可用。
30
30
 
31
31
  ## 配置
32
32
 
@@ -36,20 +36,20 @@ dsh plugin --profile web add <本仓库路径>
36
36
  | `browser-electron` | `httpOnly` | 布尔 | `true` | 仅允许 HTTP(S) 导航;`file:`/`data:` 等拒绝 |
37
37
  | `browser-electron` | `snapshotMaxElements` | 数字 | `60` | 快照最多收录的交互元素数 |
38
38
  | `browser-electron` | `contentMaxChars` | 数字 | 未设置(按格式分档) | 内容抓取的字符上限。**不设**时按格式取分档默认(html 50 000、json 50 000、txt 20 000、markdown 20 000);**设了**就是操作者的值,覆盖分档默认。单次调用的 `maxChars` 优先级最高 |
39
- | `browser-electron` | `downloadDir` | 字符串 | 系统下载目录(自动识别 `Downloads`/`下载`/`下載`,或 `XDG_DOWNLOAD_DIR`) | 限定 `browser_download` 与 `browser_screenshot` 保存路径必须位于该目录内且不覆盖已有文件;默认收敛到系统下载目录,可改沙箱目录 |
39
+ | `browser-electron` | `downloadDir` | 字符串 | 系统下载目录(自动识别 `Downloads`/`下载`/`下載`,或 `XDG_DOWNLOAD_DIR`) | 限定 `dsh_browser_download` 与 `dsh_browser_screenshot` 保存路径必须位于该目录内且不覆盖已有文件;默认收敛到系统下载目录,可改沙箱目录 |
40
40
  | `tool-browser` | `timeoutMs` | 数字 | `60000` | 工具协作超时(ms) |
41
41
  | `tool-browser` | `tabTools` | 布尔 | `true` | 是否注册标签管理工具 |
42
- | `tool-browser` | `allowedActions` | 字符串数组 | 不限制 | **部署级**动作白名单,对该部署里的每个任务生效(单个任务仍可用 `browser_restrict` 再收窄);`READ_ONLY_TOOLS` 与 `browser_restrict` 无论列表怎么写都不拦。`cordis.patch.yml` 默认给的是空 `config: {}`(等于不限制) |
42
+ | `tool-browser` | `allowedActions` | 字符串数组 | 不限制 | **部署级**动作白名单,对该部署里的每个任务生效(单个任务仍可用 `dsh_browser_restrict` 再收窄);`READ_ONLY_TOOLS` 与 `dsh_browser_restrict` 无论列表怎么写都不拦。`cordis.patch.yml` 默认给的是空 `config: {}`(等于不限制) |
43
43
 
44
44
  ## 快速上手(给 agent 的提示词示例)
45
45
 
46
46
  ```
47
- 1. browser_open 打开 https://example.com
48
- 2. 慢站点先 browser_wait(url=…) 等页面就绪,再 browser_snapshot 查看可交互元素
49
- 3. 需要填表时用 browser_fill(按 name/label/placeholder 匹配,一次填多个字段)
50
- 4. 需要截图确认时用 browser_screenshot(可 savePath 存文件,大页面用 maxWidth 缩小)
51
- 5. 需要滚动/回退/按键时用 browser_scroll / browser_back / browser_forward / browser_key
52
- 6. 遇到验证码(browser_challenge 或快照标注 CHALLENGE)时,停下请用户处理
47
+ 1. dsh_browser_open 打开 https://example.com
48
+ 2. 慢站点先 dsh_browser_wait(url=…) 等页面就绪,再 dsh_browser_snapshot 查看可交互元素
49
+ 3. 需要填表时用 dsh_browser_fill(按 name/label/placeholder 匹配,一次填多个字段)
50
+ 4. 需要截图确认时用 dsh_browser_screenshot(可 savePath 存文件,大页面用 maxWidth 缩小)
51
+ 5. 需要滚动/回退/按键时用 dsh_browser_scroll / dsh_browser_back / dsh_browser_forward / dsh_browser_key
52
+ 6. 遇到验证码(dsh_browser_challenge 或快照标注 CHALLENGE)时,停下请用户处理
53
53
  7. 每次操作后告知用户你在页面上做了什么
54
54
  ```
55
55
 
@@ -64,58 +64,58 @@ dsh plugin --profile web add <本仓库路径>
64
64
 
65
65
  **定位:先用语义,再考虑坐标**
66
66
 
67
- - **`browser_a11y` 是理解页面的首选**,它给出每个交互节点的**角色**(button/textbox/…)与**可访问名称**,比读 DOM 更接近"这页面上有什么";`browser_snapshot` 给出可点击元素的**引用与坐标**。
67
+ - **`dsh_browser_a11y` 是理解页面的首选**,它给出每个交互节点的**角色**(button/textbox/…)与**可访问名称**,比读 DOM 更接近"这页面上有什么";`dsh_browser_snapshot` 给出可点击元素的**引用与坐标**。
68
68
  - **两者都带 `selector`**:无 id 且无 name 时是空串。**有 `selector` 就优先用它**,它比坐标稳定 —— 页面滚动、布局变化都不会让它失效,而坐标会。
69
69
  - **优先用 DOM 语义而非坐标**:表单提交优先 `form.requestSubmit()`;点击优先 `element.click()`;坐标点击是最后手段。
70
- - **`browser_click` 的 `target`(css/text)本质上就是语义定位**,能用它就别自己算坐标 —— 它会把元素滚进视野并在中心点击,坐标抖动、像素比、iframe 偏移都由它处理。
71
- - **选中正确的元素**:页面常有隐藏副本(如移动端按钮),用 `browser_execute` 过滤可见元素(`getBoundingClientRect()` 宽高 > 0、`getComputedStyle` 非 `display:none`),再取坐标。
70
+ - **`dsh_browser_click` 的 `target`(css/text)本质上就是语义定位**,能用它就别自己算坐标 —— 它会把元素滚进视野并在中心点击,坐标抖动、像素比、iframe 偏移都由它处理。
71
+ - **选中正确的元素**:页面常有隐藏副本(如移动端按钮),用 `dsh_browser_execute` 过滤可见元素(`getBoundingClientRect()` 宽高 > 0、`getComputedStyle` 非 `display:none`),再取坐标。
72
72
  - **取坐标后立即点击**:中间不要插入其他操作(填表、滚动会移动元素,旧坐标立即失效)。
73
73
  - **点击前验证命中**:`document.elementFromPoint(x, y)` 确认该坐标确实是目标元素,再执行真实点击。
74
74
  - **DPR 注意**:CDP 输入使用 CSS 像素;高 DPI 屏上若点击落空,用 `elementFromPoint` 校准,不要盲试坐标。
75
75
 
76
76
  **截图与内容:先问"我要的是判断还是像素"**
77
77
 
78
- - **能用文本就用文本。** `browser_snapshot` / `browser_a11y` / `browser_content` 给出同一页面的文本表示;**没有图像输入能力的模型看截图什么也得不到**,只会花钱。截图留给布局、图表、设计核对这类**必须看像素**的事。
79
- - **`browser_screenshot` 带 `maxWidth`/`maxHeight` 时,结果里会返回 `width`/`height`**(实际像素尺寸)。需要控制成本时用它确认降采样生效了,而不是自己解码 data URL。
80
- - **`browser_content` 的 `maxChars` 是按格式分档的**(html/json 默认 5 万,其余 2 万),单次传 `maxChars` 优先于设置里的 `contentMaxChars`。
78
+ - **能用文本就用文本。** `dsh_browser_snapshot` / `dsh_browser_a11y` / `dsh_browser_content` 给出同一页面的文本表示;**没有图像输入能力的模型看截图什么也得不到**,只会花钱。截图留给布局、图表、设计核对这类**必须看像素**的事。
79
+ - **`dsh_browser_screenshot` 带 `maxWidth`/`maxHeight` 时,结果里会返回 `width`/`height`**(实际像素尺寸)。需要控制成本时用它确认降采样生效了,而不是自己解码 data URL。
80
+ - **`dsh_browser_content` 的 `maxChars` 是按格式分档的**(html/json 默认 5 万,其余 2 万),单次传 `maxChars` 优先于设置里的 `contentMaxChars`。
81
81
 
82
82
  **动作开关:三个门是操作者(人)的,不是你的**
83
83
 
84
- 设置页里的**执行页面脚本**、**下载文件到磁盘**、**写入登录态** 由人控制,**任何 `browser_*` 工具都改不了它们**。关掉时对应调用会明确报错,一眼能认出:
84
+ 设置页里的**执行页面脚本**、**下载文件到磁盘**、**写入登录态** 由人控制,**任何 `dsh_browser_*` 工具都改不了它们**。关掉时对应调用会明确报错,一眼能认出:
85
85
 
86
86
  | 谁被关 | 报的错误码 | 哪些工具会遇到 |
87
87
  | --- | --- | --- |
88
- | 执行页面脚本 | `BROWSER_EXECUTE_DISABLED` | `browser_execute`、以及需要页内脚本的工具 |
89
- | 下载文件到磁盘 | `BROWSER_DOWNLOAD_DISABLED` | `browser_download` |
90
- | 写入登录态 | `BROWSER_AUTH_WRITE_DISABLED` | `browser_auth` 的 `restore` |
91
- | 读取 cookie | `BROWSER_AUTH_DISABLED` | `browser_auth` 的 `flush` |
88
+ | 执行页面脚本 | `BROWSER_EXECUTE_DISABLED` | `dsh_browser_execute`、以及需要页内脚本的工具 |
89
+ | 下载文件到磁盘 | `BROWSER_DOWNLOAD_DISABLED` | `dsh_browser_download` |
90
+ | 写入登录态 | `BROWSER_AUTH_WRITE_DISABLED` | `dsh_browser_auth` 的 `restore` |
91
+ | 读取 cookie | `BROWSER_AUTH_DISABLED` | `dsh_browser_auth` 的 `flush` |
92
92
 
93
93
  **看到这些码不要重试、不要绕路** —— **它是人的决定,不是可以克服的故障**,直接告诉人"需要他在设置里打开"。
94
94
 
95
95
  另外几个容易认错来源的码:`BROWSER_DOWNLOAD_BLOCKED` / `BROWSER_SCREENSHOT_BLOCKED` 是**保存路径被沙箱拒绝**(不是开关问题,换路径而不是找开关);`BROWSER_DOWNLOAD_UNSUPPORTED` 是**该载体没有这个能力**(比如非自托管浏览器)。
96
96
 
97
- `browser_restrict` **不一样** —— 那是**你自己**设的临时白名单,你有权设也有权解除;它不该被用来绕过上面三个门。
97
+ `dsh_browser_restrict` **不一样** —— 那是**你自己**设的临时白名单,你有权设也有权解除;它不该被用来绕过上面三个门。
98
98
 
99
99
  **失败怎么读**
100
100
 
101
101
  - **`CDP error: ...` 是浏览器答了但拒绝**(比如元素已经不在)—— 多半是页面变了,重新快照再试,不要盲目重复同一动作。
102
102
  - **超时 / 连接类错误**才是浏览器可能有问题;这类错误会触发一次自动探测,通常下一条命令就能恢复。
103
- - **`BROWSER_SESSION_UNKNOWN`** 说明会话没了(浏览器被替换过);`browser_reset_session` 重建本任务的会话。
104
- - **同一个动作连续失败两次就换策略**:换 `selector`、换语义定位、或者用 `browser_snapshot` 重新看页面 —— **不要第三次用同样的坐标**。
103
+ - **`BROWSER_SESSION_UNKNOWN`** 说明会话没了(浏览器被替换过);`dsh_browser_reset_session` 重建本任务的会话。
104
+ - **同一个动作连续失败两次就换策略**:换 `selector`、换语义定位、或者用 `dsh_browser_snapshot` 重新看页面 —— **不要第三次用同样的坐标**。
105
105
 
106
- **`browser_history` 与动作集**
106
+ **`dsh_browser_history` 与动作集**
107
107
 
108
- - `browser_history` **默认只给 20 条**,要更多自己传 `limit`。
109
- - **可重放的动作有六种**:`navigate` / `click` / `type` / `scroll` / `key` / `execute`。不是每种历史记录都能 `browser_replay`,遇到不可重放的会明确报错。
108
+ - `dsh_browser_history` **默认只给 20 条**,要更多自己传 `limit`。
109
+ - **可重放的动作有六种**:`navigate` / `click` / `type` / `scroll` / `key` / `execute`。不是每种历史记录都能 `dsh_browser_replay`,遇到不可重放的会明确报错。
110
110
 
111
111
  ## 多任务并行
112
112
 
113
113
  每个 DSH 会话(任务)拥有独立的浏览器会话(独立标签页与历史),并发任务互不干扰:
114
114
 
115
- - `browser_session` 查看本任务的会话与标签;
116
- - `browser_reset_session` 关闭并重建本任务的会话(崩溃或卡死后用它恢复)。
115
+ - `dsh_browser_session` 查看本任务的会话与标签;
116
+ - `dsh_browser_reset_session` 关闭并重建本任务的会话(崩溃或卡死后用它恢复)。
117
117
 
118
- 登录态(cookie)为所有任务共享;可用 `browser_auth` 导出/恢复,重启后不丢。注意 **`flush` 只导出当前页所属站点的 cookie**(按本会话所在页面的 URL 收敛),页面 URL 未知时**直接拒绝**而不是扩大范围去读整台机器的 cookie 罐;`restore` 会向任意域写 cookie,所以 `browser_auth` **同样受 `browser_restrict` 白名单约束**,不属于只读豁免。
118
+ 登录态(cookie)为所有任务共享;可用 `dsh_browser_auth` 导出/恢复,重启后不丢。注意 **`flush` 只导出当前页所属站点的 cookie**(按本会话所在页面的 URL 收敛),页面 URL 未知时**直接拒绝**而不是扩大范围去读整台机器的 cookie 罐;`restore` 会向任意域写 cookie,所以 `dsh_browser_auth` **同样受 `dsh_browser_restrict` 白名单约束**,不属于只读豁免。
119
119
 
120
120
  ## FAQ
121
121
 
@@ -131,7 +131,7 @@ DSH Desktop 上②命中即可用(0.1.18+ 插件自带 electron 包;44+ 二进
131
131
  确保 Electron ≥ 40(33.x 有合成器缺陷)。自托管截图优先走原生 `capturePage`,多视图/窗口未激活时自动兜底到 CDP。
132
132
 
133
133
  **Q:浏览器窗口不见了?**
134
- 窗口标题为 `dsh-browser`(自托管)。若子进程崩溃(或宿主 DSH 重启)会自动重启;崩溃前已打开的会话在**下一次调用时自动重建**——仅页面状态丢失,无需手动 `browser_reset_session`。`browser_reset_session` 仍可用于主动重置。
134
+ 窗口标题为 `dsh-browser`(自托管)。若子进程崩溃(或宿主 DSH 重启)会自动重启;崩溃前已打开的会话在**下一次调用时自动重建**——仅页面状态丢失,无需手动 `dsh_browser_reset_session`。`dsh_browser_reset_session` 仍可用于主动重置。
135
135
 
136
136
  **Q:设置里的 `contentMaxChars` 改了没反应?**
137
137
  先确认改的是哪一层。优先级从高到低是:**单次调用的 `maxChars`** > **配置里的 `contentMaxChars`** > **按格式的分档默认**(html/json 50 000,txt/markdown 20 000)。`contentMaxChars` 的作用就是给这个部署换一个默认值:不设时才用分档,设了就盖过分档。若改了没反应,通常是那一次调用自己传了 `maxChars`。
@@ -140,18 +140,74 @@ DSH Desktop 上②命中即可用(0.1.18+ 插件自带 electron 包;44+ 二进
140
140
  能。该载体观察进程退出并清掉**已失效的会话映射**,下一次调用会**重新拉起**浏览器并继续工作;重新拉起前会先 kill 旧进程并**最多等 3 秒**它真正退出,且**只认本次启动写下的 `DevToolsActivePort`**(被杀掉的浏览器没机会清理那个文件,照读会连到别的 Chromium 或 `node --inspect` 上)。反过来,页面只是**慢**不会让插件杀掉你的窗口:命令超时只把连接标记为存疑,下一次调用先用 `Browser.getVersion` 探一次再决定是否丢弃,只有 socket 已关闭才重启。
141
141
 
142
142
  **Q:下载报 CORS 错误?**
143
- `browser_download` 在页面上下文内 `fetch`,受同源/CORS 约束;跨域文件请先在同源页面内操作,或直接请求用户提供。仅支持 HTTP(S) URL;`savePath` 必须为绝对路径且位于 `downloadDir` 内(默认系统下载目录,自动识别 `Downloads`/`下载`/`下載`),不覆盖已有文件;`browser_screenshot` 的 `savePath` 受同一套限制。
143
+ `dsh_browser_download` 在页面上下文内 `fetch`,受同源/CORS 约束;跨域文件请先在同源页面内操作,或直接请求用户提供。仅支持 HTTP(S) URL;`savePath` 必须为绝对路径且位于 `downloadDir` 内(默认系统下载目录,自动识别 `Downloads`/`下载`/`下載`),不覆盖已有文件;`dsh_browser_screenshot` 的 `savePath` 受同一套限制。
144
144
 
145
145
  **Q:如何禁止 agent 乱点?**
146
- `browser_restrict` 设置白名单(如只允许 `browser_snapshot`/`browser_content`);传空列表解除。注意它是防误操作的**软护栏**,模型可自行解除,不是安全边界。
146
+ `dsh_browser_restrict` 设置白名单(如只允许 `dsh_browser_snapshot`/`dsh_browser_content`);传空列表解除。注意它是防误操作的**软护栏**,模型可自行解除,不是安全边界。
147
147
 
148
- 要一个**模型自己解不掉**的限制,用设置页「Agent 能做什么」的三个开关(执行页面脚本 / 下载文件到磁盘 / 写入登录状态):它们写在设置文档里,而没有任何工具能写那个文档,所以模型只能被拒 —— 这与 `browser_restrict` 的区别不是"谁更强",而是"谁能改"。部署级(对所有人生效)还可以用 `tool-browser` 的 `allowedActions` 配置,见 `cordis.patch.yml` 的注释。
148
+ 要一个**模型自己解不掉**的限制,用设置页「Agent 能做什么」的三个开关(执行页面脚本 / 下载文件到磁盘 / 写入登录状态):它们写在设置文档里,而没有任何工具能写那个文档,所以模型只能被拒 —— 这与 `dsh_browser_restrict` 的区别不是"谁更强",而是"谁能改"。部署级(对所有人生效)还可以用 `tool-browser` 的 `allowedActions` 配置,见 `cordis.patch.yml` 的注释。
149
149
 
150
150
  ## 故障排查
151
151
 
152
152
  | 现象 | 可能原因 | 处理 |
153
153
  | --- | --- | --- |
154
- | `BROWSER_SESSION_UNKNOWN` | 会话已被关闭/重建(如 `browser_reset_session` 后仍引用旧会话) | `browser_session` 查看现状;或 `browser_reset_session` 重建 |
155
- | 工具超时 | 页面卡死/未渲染完成 | 稍后重试;`browser_reset` 重置标签 |
154
+ | `BROWSER_SESSION_UNKNOWN` | 会话已被关闭/重建(如 `dsh_browser_reset_session` 后仍引用旧会话) | `dsh_browser_session` 查看现状;或 `dsh_browser_reset_session` 重建 |
155
+ | 工具超时 | 页面卡死/未渲染完成 | 稍后重试;`dsh_browser_reset` 重置标签 |
156
156
  | 导航被拒 | 非 HTTP(S) 协议 | 检查 URL;`httpOnly` 配置 |
157
- | 快照为空 | 页面尚未加载 | 等待后重试 `browser_snapshot` |
157
+ | 快照为空 | 页面尚未加载 | 等待后重试 `dsh_browser_snapshot` |
158
+
159
+ ## 验证过的版本
160
+
161
+ | 组件 | 版本 |
162
+ | --- | --- |
163
+ | DeepSeek Harness(dsh) | `0.2.1-alpha.1`(peer 声明 `>=0.1.1-rc.2 <0.3.0`;更早的 `0.2.0-rc.2`、`0.1.x` 亦有实测记录) |
164
+ | Electron | `44.0.0`(推荐 ≥ 40;33.x 存在合成器缺陷) |
165
+ | Node.js | `22.20.0` |
166
+ | 本机 Chrome / Edge(可选载体) | `154.0.8037.58` / `154.0.4258.37` |
167
+ | 操作系统 | Windows 10 (10.0.26200) |
168
+
169
+ > 插件声明 `electron >= 30`。**核心链路在 Windows 上完整实测**;系统浏览器的查找已适配 Linux 与 macOS(先查 `PATH`,再查各平台的惯例安装位置,均可用 `DSH_BROWSER_CHROME_PATH` / `DSH_BROWSER_EDGE_PATH` 覆盖),但这两个平台上的**端到端链路尚未实测**,暂不承诺。
170
+
171
+ > **宿主兼容性可以随时复验**:`node scripts/verify-host-compat.mjs` 会用某个 profile 里**真实安装**的 `@deepseek-ai/*` 包组装一个宿主,加载本插件的编译产物并真调两个工具。详见[开发](development.md)。
172
+
173
+ ## 更新方式(两端不同)
174
+
175
+ 插件在两种宿主里各有一份安装,更新路径也不同 —— **更新其中一端不会连带更新另一端**。
176
+
177
+ **桌面端(DSH Desktop)**
178
+
179
+ - 插件是桌面端 profile 里的依赖(profile 目录通常是 `$DSH_HOME/profiles/desktop`)。更新它 = 把该 profile 里的依赖更新到新版本,然后**重启 DSH Desktop**,客户端设置栏与工具才会换成新代码。
180
+ - **桌面端还多一步,而 Web 端没有**:让插件驱动侧栏的那条 bridge 装在**桌面端自己的安装目录**里(`resources/app/`),插件更新**不会**带上它。桌面端升级会替换该目录、bridge 随之消失,所以请重跑一次:
181
+ ```bash
182
+ node desktop-bridge/install.mjs # 幂等;已装则只刷新模块
183
+ node desktop-bridge/install.mjs --revert # 回滚
184
+ ```
185
+ bridge 不在时插件自动退回自托管(多出一个独立窗口),功能不中断。
186
+ - 浏览器内核默认由桌面端自带的 Electron 提供,插件不会再下载一份 Electron;也可以在设置里改用它自己装的 Chrome / Edge。
187
+ - **升级桌面端本身不会带上插件**,按上面的方法单独更新它。
188
+
189
+ **Web 端(`dsh web`)**
190
+
191
+ - 插件是 web profile 里的依赖(`$DSH_HOME/profiles/web`),更新后**重启 `dsh web`** 生效。
192
+ - Web 端**没有侧栏、也没有 bridge**:共享浏览器由插件自托管拉起。首次安装可能需要 Electron 二进制;若包管理器的构建白名单拦下了它(pnpm v10+ 会拦 `electron` 的 postinstall),执行一次 `npx install-electron` 补上即可。
193
+ - 同样可以在设置里改用本机 Chrome / Edge —— 这是两端**行为一致**的选项。
194
+ - 更新方式与首次安装它时一致(按 npm 包名 `dsh-builtin-browser`、按 GitHub 仓库 `wqty123/dsh-browser`,或本地目录)。
195
+
196
+ **两端一致的体验**
197
+
198
+ - 工具集、设置页里的「浏览器」栏、浏览历史与 cookie 的持久化行为完全相同;差别只在页面由谁承载(桌面端=官方侧栏,Web 端=插件自托管窗口,或你在设置里指定的本机浏览器)。
199
+ - 升级不会丢数据:浏览历史与设置都在 `$DSH_HOME/dsh-builtin-browser-host/`(`history.jsonl`、`settings.json`),登录状态在同一 profile 目录里 —— 包括使用本机浏览器时的 `<chrome|edge>-profile`。
200
+ - 升级后如果历史记录不符合预期,先去「设置 → 浏览器」确认这些开关:历史默认**开**、侧栏自动展开默认**开**、会话结束时自动关闭浏览器默认**关**、载体默认**内置**。
201
+
202
+ ## 从 0.4.7 升级到 0.4.8
203
+
204
+ **这是一个改名版本:34 个工具从 `browser_*` 移入 `dsh_browser_*`,功能一字未改。**
205
+
206
+ | | |
207
+ | --- | --- |
208
+ | **只装这一个浏览器插件** | **不必更新。** 旧版继续正常工作。 |
209
+ | **还装了别的浏览器插件**(尤其腾讯 BrowserSkill) | **建议更新**,否则对方那套工具注册不上。 |
210
+
211
+ 改名原因见 [README 的工具名空间一节](../README.md#工具名空间)。更新后 `browser_open` → `dsh_browser_open`(其余同理),并**重启 DSH** 使其生效。
212
+
213
+ > **0.4.8 起,一个名字被占不再毁掉整套工具。** 此前 34 个工具是顺序直接注册的,一个重名抛错会中断余下的注册,表现成"插件装了但没反应"。现在每个工具独立注册,碰撞只产生一条点名报错的日志(`could not register dsh_browser_xxx`),其余工具照常可用。
@@ -26,13 +26,13 @@ LLM agent 需要"上网"。传统做法是给 agent 一个**无头浏览器**(Pl
26
26
  | 真实页面渲染 | ✅ | 部分 | ✅ |
27
27
  | 复杂交互(填表/点击/拖拽) | ✅ | ❌ | ✅ |
28
28
  | 用户可见、可接管 | ❌ | ❌ | ✅ |
29
- | 保留登录态 | 需手动管理 | ❌ | ✅(`browser_auth` + cookie 落盘) |
29
+ | 保留登录态 | 需手动管理 | ❌ | ✅(`dsh_browser_auth` + cookie 落盘) |
30
30
  | 人工处理验证码 | ❌ 卡死 | ❌ | ✅ 请用户点一下即可 |
31
31
  | 多任务并行 | 需多实例 | – | ✅ 任务级会话隔离 |
32
32
 
33
33
  ## 设计取舍
34
34
 
35
- - **登录态共享**:所有任务共享同一份 cookie(符合"用户登录一次,agent 到处可用");需要搬运时用 `browser_auth` 手动导出/恢复 —— 它的 `flush` **只导出当前页所属站点的 cookie**(按本会话所在页面的 URL 收敛,页面 URL 未知时拒绝而不是放宽),所以"导出"是逐站点的一步,不是一次性打包整个 cookie 罐。
35
+ - **登录态共享**:所有任务共享同一份 cookie(符合"用户登录一次,agent 到处可用");需要搬运时用 `dsh_browser_auth` 手动导出/恢复 —— 它的 `flush` **只导出当前页所属站点的 cookie**(按本会话所在页面的 URL 收敛,页面 URL 未知时拒绝而不是放宽),所以"导出"是逐站点的一步,不是一次性打包整个 cookie 罐。
36
36
  - **任务级会话隔离**:每个 DSH 会话(任务)拥有独立的浏览器会话(标签页与历史),并发任务互不抢页面;自托管载体下**每个会话连窗口都各自一份**(窗口按会话 id 分组,窗口标题带任务标识),侧栏与本机浏览器载体则是同一个宿主里切换当前可见视图。
37
37
  - **装好即用**:不依赖桌面外壳。有外壳时嵌入外壳视图;纯 `dsh web` 时插件自托管——自己拉起 Electron 窗口,通过本机 TCP JSON-RPC 驱动。
38
38
  - **边界清晰**:可见视图、浏览器列布局属于宿主外壳;插件只负责 seam、provider 与工具,不含任何 UI。
@@ -41,5 +41,5 @@ LLM agent 需要"上网"。传统做法是给 agent 一个**无头浏览器**(Pl
41
41
  ## 什么场景不适合
42
42
 
43
43
  - 需要**无头批量抓取**(成千上万页面):请用专门的抓取工具/服务,不必开窗口。
44
- - 需要**每个任务完全独立的登录态**(互不可见):本插件默认共享 cookie;如需强隔离,可用多个 DSH 实例或 `browser_auth` 手动管理。
44
+ - 需要**每个任务完全独立的登录态**(互不可见):本插件默认共享 cookie;如需强隔离,可用多个 DSH 实例或 `dsh_browser_auth` 手动管理。
45
45
  - 需要**浏览器列 UI / 布局管理**:那是宿主外壳的配套,本插件不包含。
@@ -58,7 +58,7 @@ export interface BrowserScrollRequest {
58
58
  }
59
59
  /** Press one named key (Enter, Tab, arrows, …). */
60
60
  export interface BrowserKeyRequest {
61
- /** Key name from the supported set (see the browser_key tool). */
61
+ /** Key name from the supported set (see the dsh_browser_key tool). */
62
62
  readonly key: string;
63
63
  }
64
64
  /**
@@ -333,7 +333,7 @@ export interface BrowserSnapshotElement {
333
333
  /**
334
334
  * True when the element lives inside an (same-origin) iframe: coordinates
335
335
  * are still top-document, but DOM selectors are frame-scoped — reach the
336
- * element via `iframe.contentDocument` in browser_execute.
336
+ * element via `iframe.contentDocument` in dsh_browser_execute.
337
337
  */
338
338
  readonly frame?: boolean;
339
339
  }
@@ -5,7 +5,7 @@
5
5
  * `ctx.electronViewHost`, that host is used (embedded, human-machine shared
6
6
  * view). Otherwise the plugin self-hosts: it spawns its own Electron child
7
7
  * (`host-main.js`) and drives it over a local TCP JSON-RPC socket, so
8
- * installing the plugin is enough for `browser_*` tools to work on any
8
+ * installing the plugin is enough for `dsh_browser_*` tools to work on any
9
9
  * surface.
10
10
  * @module dsh-browser/browser-electron
11
11
  */
@@ -27,7 +27,7 @@ export interface Config {
27
27
  /** Allow navigation only to HTTP(S) URLs. Default true. */
28
28
  readonly httpOnly?: boolean;
29
29
  /**
30
- * Directory `browser_download` save paths must resolve inside (prevents a
30
+ * Directory `dsh_browser_download` save paths must resolve inside (prevents a
31
31
  * prompt-injected agent from writing arbitrary machine paths). Default:
32
32
  * the user's Downloads folder; override to confine downloads elsewhere.
33
33
  */
@@ -5,7 +5,7 @@
5
5
  * `ctx.electronViewHost`, that host is used (embedded, human-machine shared
6
6
  * view). Otherwise the plugin self-hosts: it spawns its own Electron child
7
7
  * (`host-main.js`) and drives it over a local TCP JSON-RPC socket, so
8
- * installing the plugin is enough for `browser_*` tools to work on any
8
+ * installing the plugin is enough for `dsh_browser_*` tools to work on any
9
9
  * surface.
10
10
  * @module dsh-browser/browser-electron
11
11
  */
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Persistent browsing history: which pages the shared browser actually visited,
3
- * kept independently from any session's operation log (`browser_history`) and
3
+ * kept independently from any session's operation log (`dsh_browser_history`) and
4
4
  * from the browser process's lifetime.
5
5
  *
6
6
  * It lives beside the browser profile (`$DSH_HOME/dsh-builtin-browser-host/
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Persistent browsing history: which pages the shared browser actually visited,
3
- * kept independently from any session's operation log (`browser_history`) and
3
+ * kept independently from any session's operation log (`dsh_browser_history`) and
4
4
  * from the browser process's lifetime.
5
5
  *
6
6
  * It lives beside the browser profile (`$DSH_HOME/dsh-builtin-browser-host/
@@ -38,7 +38,7 @@ import { dirname, join } from 'node:path';
38
38
  // several Electron instances sharing Roaming\Electron fight over the GPU
39
39
  // cache/session locks, which can leave the window without a display surface
40
40
  // (capturePage then fails). A dedicated userData also persists cookies across
41
- // host restarts (on top of browser_auth). Must run before app is ready.
41
+ // host restarts (on top of dsh_browser_auth). Must run before app is ready.
42
42
  try {
43
43
  const base = process.env.DSH_HOME ?? app.getPath('appData');
44
44
  app.setPath('userData', join(base, 'dsh-builtin-browser-host'));
@@ -785,7 +785,7 @@ async function handle(op, msg) {
785
785
  if (win === undefined || entry === undefined)
786
786
  throw new Error(`focus: unknown view ${viewId}`);
787
787
  // Keys go to the view that holds web focus. A view that was never
788
- // clicked holds none, so the FIRST browser_key of a session was dropped
788
+ // clicked holds none, so the FIRST dsh_browser_key of a session was dropped
789
789
  // by the renderer while CDP still answered the dispatch with success —
790
790
  // and `document.hasFocus()` inside the page even read true.
791
791
  const contents = entry.webContentsView.webContents;
@@ -920,7 +920,7 @@ async function handle(op, msg) {
920
920
  throw new Error('capture produced no image (view not painted)');
921
921
  }
922
922
  // The measured size travels with the image. Without it the native path — which is the
923
- // default self-hosted one — could never answer `browser_screenshot`'s width/height,
923
+ // default self-hosted one — could never answer `dsh_browser_screenshot`'s width/height,
924
924
  // because the seam's only channel was {base64, mime} and the provider had nothing to
925
925
  // forward. The caller's `maxWidth`/`maxHeight` are applied here, so this is the only
926
926
  // place that knows what came back.
@@ -285,7 +285,7 @@ export interface ElectronBrowserProviderConfig {
285
285
  */
286
286
  readonly contentMaxChars?: number;
287
287
  /**
288
- * Directory `browser_download` save paths must resolve inside (prevents a
288
+ * Directory `dsh_browser_download` save paths must resolve inside (prevents a
289
289
  * prompt-injected agent from writing arbitrary machine paths). Default:
290
290
  * `~/Downloads`. NOTE: this default is a convenience, not a security
291
291
  * boundary — on Windows it may resolve to OneDrive/redirected paths;
@@ -339,7 +339,7 @@ export declare function layoutSize(metrics: Record<string, unknown>): {
339
339
  viewportHeight: number;
340
340
  } | undefined;
341
341
  /**
342
- * Map CDP cookies onto the shape `browser_auth` exports.
342
+ * Map CDP cookies onto the shape `dsh_browser_auth` exports.
343
343
  *
344
344
  * CDP reports a cookie as domain + path rather than a URL, and in seconds rather than
345
345
  * milliseconds, so both are converted. Entries missing a name, value or domain are
@@ -452,9 +452,9 @@ export declare class ElectronBrowserProvider implements BrowserProvider {
452
452
  /**
453
453
  * Page.navigate resolves at navigation COMMIT, not at load: the new document
454
454
  * is already current (so location.href and document.title are the new page's)
455
- * while its DOM is still being parsed. browser_open snapshots immediately
455
+ * while its DOM is still being parsed. dsh_browser_open snapshots immediately
456
456
  * after navigating, which is why it could report the right title with zero
457
- * interactive elements — and why a separate browser_snapshot right after
457
+ * interactive elements — and why a separate dsh_browser_snapshot right after
458
458
  * always found them.
459
459
  *
460
460
  * Wait — bounded, best-effort — for the new document to settle: readyState
@@ -493,7 +493,7 @@ export declare class ElectronBrowserProvider implements BrowserProvider {
493
493
  *
494
494
  * A renderer only delivers key events to the view that holds web focus. A
495
495
  * view that was created but never clicked holds none — and `Input` events
496
- * injected over CDP do not grant it — so the FIRST `browser_key` of a fresh
496
+ * injected over CDP do not grant it — so the FIRST `dsh_browser_key` of a fresh
497
497
  * session vanished inside the renderer while the command still answered
498
498
  * success. Hosts that cannot focus a view (a test double, a different shell
499
499
  * adapter) simply have no `focus`, and nothing changes for them.
@@ -598,7 +598,7 @@ export declare class ElectronBrowserProvider implements BrowserProvider {
598
598
  * Refuse an action the settings have switched off.
599
599
  *
600
600
  * These switches belong to the OPERATOR. The settings document is written through the
601
- * plugin's own panel and no tool can reach it, so — unlike `browser_restrict`, which the
601
+ * plugin's own panel and no tool can reach it, so — unlike `dsh_browser_restrict`, which the
602
602
  * model owns and can lift at will — a refusal here stands for as long as the setting does.
603
603
  * That is the point of them: a deployment can take page-script execution, downloads, or
604
604
  * login-state writes off the table without relying on the model's cooperation.
@@ -612,7 +612,7 @@ export declare class ElectronBrowserProvider implements BrowserProvider {
612
612
  private assertActionAllowed;
613
613
  /**
614
614
  * Admit a caller-supplied save path for a file the browser writes to disk.
615
- * ONE gate for both `browser_download` and `browser_screenshot`: the path
615
+ * ONE gate for both `dsh_browser_download` and `dsh_browser_screenshot`: the path
616
616
  * must be absolute, must resolve inside `downloadDir`, and must not already
617
617
  * exist. Without it a prompt-injected agent could write — or silently
618
618
  * replace — any file the DSH process can reach, which also escapes the file
@@ -676,7 +676,7 @@ export declare class ElectronBrowserProvider implements BrowserProvider {
676
676
  */
677
677
  private recordVisit;
678
678
  /**
679
- * List persisted visits, newest first — what `browser_visited` reads back.
679
+ * List persisted visits, newest first — what `dsh_browser_visited` reads back.
680
680
  * @param options - result cap and an optional hostname filter.
681
681
  * @returns the matching visits, or an empty list when history is disabled.
682
682
  */
@@ -721,7 +721,7 @@ export declare class ElectronBrowserProvider implements BrowserProvider {
721
721
  *
722
722
  * A view whose renderer never commits cannot be read again: every evaluate into it runs out its
723
723
  * whole budget. Replacing it is what turns that from permanent into one bad call — the tab id
724
- * callers hold stays valid, and the next `browser_open` with the real URL re-populates it.
724
+ * callers hold stays valid, and the next `dsh_browser_open` with the real URL re-populates it.
725
725
  *
726
726
  * The old view is destroyed AFTER the new one is in place, so a failure here cannot leave the
727
727
  * session holding a handle that is already gone.
@@ -99,7 +99,7 @@ export function layoutSize(metrics) {
99
99
  return { width: content.width, height: content.height, viewportHeight: viewport?.height ?? content.height };
100
100
  }
101
101
  /**
102
- * Map CDP cookies onto the shape `browser_auth` exports.
102
+ * Map CDP cookies onto the shape `dsh_browser_auth` exports.
103
103
  *
104
104
  * CDP reports a cookie as domain + path rather than a URL, and in seconds rather than
105
105
  * milliseconds, so both are converted. Entries missing a name, value or domain are
@@ -556,9 +556,9 @@ export class ElectronBrowserProvider {
556
556
  /**
557
557
  * Page.navigate resolves at navigation COMMIT, not at load: the new document
558
558
  * is already current (so location.href and document.title are the new page's)
559
- * while its DOM is still being parsed. browser_open snapshots immediately
559
+ * while its DOM is still being parsed. dsh_browser_open snapshots immediately
560
560
  * after navigating, which is why it could report the right title with zero
561
- * interactive elements — and why a separate browser_snapshot right after
561
+ * interactive elements — and why a separate dsh_browser_snapshot right after
562
562
  * always found them.
563
563
  *
564
564
  * Wait — bounded, best-effort — for the new document to settle: readyState
@@ -666,7 +666,7 @@ export class ElectronBrowserProvider {
666
666
  *
667
667
  * A renderer only delivers key events to the view that holds web focus. A
668
668
  * view that was created but never clicked holds none — and `Input` events
669
- * injected over CDP do not grant it — so the FIRST `browser_key` of a fresh
669
+ * injected over CDP do not grant it — so the FIRST `dsh_browser_key` of a fresh
670
670
  * session vanished inside the renderer while the command still answered
671
671
  * success. Hosts that cannot focus a view (a test double, a different shell
672
672
  * adapter) simply have no `focus`, and nothing changes for them.
@@ -676,7 +676,7 @@ export class ElectronBrowserProvider {
676
676
  return;
677
677
  try {
678
678
  // Bounded, unlike the rest of this call: the child RPC has no timeout of its own,
679
- // and this runs before typing, so an unresponsive child would hang browser_key for
679
+ // and this runs before typing, so an unresponsive child would hang dsh_browser_key for
680
680
  // good. A timeout is caught below exactly like any other focus failure, so the
681
681
  // best-effort contract is unchanged.
682
682
  await withTimeout(handle.focus(), 5_000, undefined, 'browser: focusing the view timed out');
@@ -906,11 +906,11 @@ export class ElectronBrowserProvider {
906
906
  // A timeout leaves the wedged view in the session, so every later read would pay the same
907
907
  // 30 seconds again — and `terminatePage` only interrupts the running script, it does not
908
908
  // make the view readable. Replacing it is what turns a permanent loss into one bad call:
909
- // the tab keeps the id the caller holds, and the next browser_open with the real URL
909
+ // the tab keeps the id the caller holds, and the next dsh_browser_open with the real URL
910
910
  // repopulates it.
911
911
  if (error instanceof Error && error.name === 'TimeoutError') {
912
912
  this.replaceTabWithBlank(s, tab);
913
- throw new BrowserError('browser: snapshot timed out; the stuck tab was reset to blank. Call browser_open with the real target URL before reading it again.', 'BROWSER_SNAPSHOT_TIMEOUT', { cause: error });
913
+ throw new BrowserError('browser: snapshot timed out; the stuck tab was reset to blank. Call dsh_browser_open with the real target URL before reading it again.', 'BROWSER_SNAPSHOT_TIMEOUT', { cause: error });
914
914
  }
915
915
  throw error;
916
916
  }
@@ -1105,7 +1105,7 @@ export class ElectronBrowserProvider {
1105
1105
  // charge every later read the same 30 seconds. Replaced in place, so the tab id survives.
1106
1106
  if (error instanceof Error && error.name === 'TimeoutError') {
1107
1107
  this.replaceTabWithBlank(s, tab);
1108
- throw new BrowserError('browser: a11y timed out; the stuck tab was reset to blank. Call browser_open with the real target URL before reading it again.', 'BROWSER_A11Y_TIMEOUT', { cause: error });
1108
+ throw new BrowserError('browser: a11y timed out; the stuck tab was reset to blank. Call dsh_browser_open with the real target URL before reading it again.', 'BROWSER_A11Y_TIMEOUT', { cause: error });
1109
1109
  }
1110
1110
  throw error;
1111
1111
  }
@@ -1517,7 +1517,7 @@ export class ElectronBrowserProvider {
1517
1517
  placeholder: f.placeholder ?? null,
1518
1518
  // A field with NO kind is a field with no kind FILTER: absent, not 'text'. Defaulting
1519
1519
  // it to 'text' here made `if (spec.kind)` above always true, so every unspecified
1520
- // field was narrowed to an <input> — browser_fill on a <textarea> reported
1520
+ // field was narrowed to an <input> — dsh_browser_fill on a <textarea> reported
1521
1521
  // `no "text" control matched`, and a contenteditable could not match any kind at all.
1522
1522
  // The value stays a string or null (never undefined): the in-page check tests truthiness,
1523
1523
  // and JSON drops an undefined member entirely, which is the same thing here.
@@ -1652,7 +1652,7 @@ export class ElectronBrowserProvider {
1652
1652
  el.dispatchEvent(new Event('change', { bubbles: true }))
1653
1653
  out.push({ ok: true, method: 'select', target: describe(spec) })
1654
1654
  } else if (type === 'file') {
1655
- out.push({ ok: false, error: 'file inputs cannot be set from script; use browser_download or ask the human', target: describe(spec) })
1655
+ out.push({ ok: false, error: 'file inputs cannot be set from script; use dsh_browser_download or ask the human', target: describe(spec) })
1656
1656
  } else if (type === 'checkbox') {
1657
1657
  const want = spec.value === true || spec.value === 'true' || spec.value === 'on'
1658
1658
  if (el.checked !== want) el.click()
@@ -2080,7 +2080,7 @@ export class ElectronBrowserProvider {
2080
2080
  * Refuse an action the settings have switched off.
2081
2081
  *
2082
2082
  * These switches belong to the OPERATOR. The settings document is written through the
2083
- * plugin's own panel and no tool can reach it, so — unlike `browser_restrict`, which the
2083
+ * plugin's own panel and no tool can reach it, so — unlike `dsh_browser_restrict`, which the
2084
2084
  * model owns and can lift at will — a refusal here stands for as long as the setting does.
2085
2085
  * That is the point of them: a deployment can take page-script execution, downloads, or
2086
2086
  * login-state writes off the table without relying on the model's cooperation.
@@ -2107,7 +2107,7 @@ export class ElectronBrowserProvider {
2107
2107
  }
2108
2108
  /**
2109
2109
  * Admit a caller-supplied save path for a file the browser writes to disk.
2110
- * ONE gate for both `browser_download` and `browser_screenshot`: the path
2110
+ * ONE gate for both `dsh_browser_download` and `dsh_browser_screenshot`: the path
2111
2111
  * must be absolute, must resolve inside `downloadDir`, and must not already
2112
2112
  * exist. Without it a prompt-injected agent could write — or silently
2113
2113
  * replace — any file the DSH process can reach, which also escapes the file
@@ -2489,7 +2489,7 @@ export class ElectronBrowserProvider {
2489
2489
  .catch(() => { });
2490
2490
  }
2491
2491
  /**
2492
- * List persisted visits, newest first — what `browser_visited` reads back.
2492
+ * List persisted visits, newest first — what `dsh_browser_visited` reads back.
2493
2493
  * @param options - result cap and an optional hostname filter.
2494
2494
  * @returns the matching visits, or an empty list when history is disabled.
2495
2495
  */
@@ -2637,7 +2637,7 @@ export class ElectronBrowserProvider {
2637
2637
  *
2638
2638
  * A view whose renderer never commits cannot be read again: every evaluate into it runs out its
2639
2639
  * whole budget. Replacing it is what turns that from permanent into one bad call — the tab id
2640
- * callers hold stays valid, and the next `browser_open` with the real URL re-populates it.
2640
+ * callers hold stays valid, and the next `dsh_browser_open` with the real URL re-populates it.
2641
2641
  *
2642
2642
  * The old view is destroyed AFTER the new one is in place, so a failure here cannot leave the
2643
2643
  * session holding a handle that is already gone.
@@ -1275,7 +1275,7 @@ class DeferredRemoteView {
1275
1275
  * with "browser host is not running". Instead: drop the cache, rebuild the
1276
1276
  * view against a freshly spawned child, and retry the operation exactly
1277
1277
  * once — so sessions that survive a host death self-heal on their FIRST
1278
- * call after it, without a manual browser_reset_session. Page-level errors
1278
+ * call after it, without a manual dsh_browser_reset_session. Page-level errors
1279
1279
  * are never retried (the view itself is fine).
1280
1280
  */
1281
1281
  async withRecovery(op) {
@@ -49,15 +49,15 @@ export interface BrowserSettings {
49
49
  *
50
50
  * These are OPERATOR switches: the settings panel is not reachable from the model, so a
51
51
  * value here refuses whatever the model asks for, and no tool can lift it. That is what
52
- * separates them from `browser_restrict`, which is the model's own soft guardrail. All
52
+ * separates them from `dsh_browser_restrict`, which is the model's own soft guardrail. All
53
53
  * three default to on, which is the documented out-of-the-box behaviour.
54
54
  */
55
55
  readonly actions: {
56
- /** Run arbitrary page scripts in the active tab (`browser_execute`). */
56
+ /** Run arbitrary page scripts in the active tab (`dsh_browser_execute`). */
57
57
  readonly allowExecute: boolean;
58
- /** Download a URL to disk (`browser_download`). */
58
+ /** Download a URL to disk (`dsh_browser_download`). */
59
59
  readonly allowDownload: boolean;
60
- /** Write cookies — importing login state (`browser_auth` restore). */
60
+ /** Write cookies — importing login state (`dsh_browser_auth` restore). */
61
61
  readonly allowCredentialWrite: boolean;
62
62
  };
63
63
  }