dsh-builtin-browser 0.2.0 → 0.3.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 (34) hide show
  1. package/README.en.md +50 -5
  2. package/README.md +62 -10
  3. package/client.js +44 -8
  4. package/desktop-bridge/install.mjs +98 -0
  5. package/desktop-bridge/plugin-browser-bridge.js +385 -0
  6. package/lib/browser-electron/bridge-connection.d.ts +58 -0
  7. package/lib/browser-electron/bridge-connection.js +133 -0
  8. package/lib/browser-electron/desktop-bridge-host.d.ts +124 -0
  9. package/lib/browser-electron/desktop-bridge-host.js +297 -0
  10. package/lib/browser-electron/entry.js +81 -13
  11. package/lib/browser-electron/history-store.d.ts +5 -1
  12. package/lib/browser-electron/history-store.js +12 -1
  13. package/lib/browser-electron/provider.d.ts +42 -1
  14. package/lib/browser-electron/provider.js +96 -24
  15. package/lib/browser-electron/remote-host.d.ts +9 -1
  16. package/lib/browser-electron/remote-host.js +17 -3
  17. package/lib/browser-electron/settings-store.d.ts +5 -0
  18. package/lib/browser-electron/settings-store.js +24 -1
  19. package/lib/browser-electron/system-browser.d.ts +132 -0
  20. package/lib/browser-electron/system-browser.js +380 -0
  21. package/lib/browser-electron/virtual-cursor.d.ts +27 -2
  22. package/lib/browser-electron/virtual-cursor.js +77 -7
  23. package/lib/tool-browser/index.js +51 -15
  24. package/package.json +2 -1
  25. package/src/browser-electron/bridge-connection.ts +145 -0
  26. package/src/browser-electron/desktop-bridge-host.ts +305 -0
  27. package/src/browser-electron/entry.ts +84 -13
  28. package/src/browser-electron/history-store.ts +11 -2
  29. package/src/browser-electron/provider.ts +124 -25
  30. package/src/browser-electron/remote-host.ts +17 -2
  31. package/src/browser-electron/settings-store.ts +30 -1
  32. package/src/browser-electron/system-browser.ts +371 -0
  33. package/src/browser-electron/virtual-cursor.ts +76 -6
  34. package/src/tool-browser/index.ts +51 -15
package/README.en.md CHANGED
@@ -232,7 +232,7 @@ agent (browser_* tools)
232
232
 
233
233
  - **Seam** (`browser` row): provides the `ctx.browser` service — provider registration, session lifecycle, error codes — decoupled from any implementation.
234
234
  - **Provider** (`browser-electron` row): operates views through the `ElectronBrowserViewHost` seam (create/destroy/show, `sendCommand`), implemented with real Electron objects by the shell.
235
- - **Tools** (`tool-browser` row): the 33 model-facing `browser_*` tools, maintaining one browser session per calling task (DSH session).
235
+ - **Tools** (`tool-browser` row): the 34 model-facing `browser_*` tools, maintaining one browser session per calling task (DSH session).
236
236
 
237
237
  **Self-hosted mode**: without a desktop shell, the plugin spawns its own Electron child process (`host-main.js`) and drives it over loopback TCP JSON-RPC. The RPC is authenticated with a random per-spawn token delivered over **both stdin and an environment variable** — on Windows the Electron GUI process never receives piped stdin, so the env fallback keeps the handshake reliable. The child auto-restarts after a crash; the plugin prefers its own bundled electron package — packaged app executables (e.g. DSH Desktop.exe) are never reused as the spawnable binary, which would launch the app itself and exit immediately; screenshots prefer Electron's native `capturePage` (CDP capture can hang with multiple views in the window); the Electron lookup order follows below (33.x has a compositor defect; ≥ 40 recommended; the electron 44+ package no longer downloads its binary at install time — if it is missing on first use, the tool errors and tells you to run `npx install-electron` first, needs network).
238
238
 
@@ -242,7 +242,52 @@ agent (browser_* tools)
242
242
 
243
243
  ## Division of labor with the desktop shell
244
244
 
245
- The browser's **visible view**, the **browser column layout**, and the **column-to-view alignment** belong to the host shell (e.g. dsh's `apps/desktop`), not this plugin. This plugin consumes the shell-provided `electronViewHost` and owns the seam, provider, and tools. Without a shell the plugin **self-hosts** and everything still works.
245
+ The plugin picks a carrier automatically, and the setting can override it (four in total):
246
+
247
+ **① Desktop: drive the official sidebar's page (one page for both parties)**
248
+
249
+ DSH Desktop is two layers: an Electron shell plus an `--expose-internals` **Node-mode host** (the plugin runs there, with **no Electron API**). 0.2 removed `electronViewHost`, and the host/shell event set carries nothing view-related either — so the plugin borrows a **small bridge**: a loopback + token service inside the shell's main process hands the plugin CDP access to the sidebar browser's guest, i.e. the very page you see on screen.
250
+
251
+ The result: **the page the agent works on is the page the human looks at**. The plugin no longer spawns its own Electron and no second window appears.
252
+
253
+ Install that bridge (it modifies an **installed** desktop app, so it is replayable):
254
+
255
+ ```bash
256
+ node desktop-bridge/install.mjs # idempotent; backs up main.js.before-bridge on first run
257
+ node desktop-bridge/install.mjs --revert # roll back
258
+ ```
259
+
260
+ > **Re-run `install.mjs` after every desktop upgrade** — the upgrade replaces `resources/app/` and takes the bridge with it. With no bridge the plugin falls back to self-hosting: nothing breaks, you just get a separate window again.
261
+
262
+ > **⚠️ What the sandbox boundary change means**
263
+ >
264
+ > The official sidebar browser is built on the premise that its pages are **not readable from outside** — it uses a separate partition and the shell refuses cross-site content access. Letting the agent drive that guest **deliberately breaks that premise**:
265
+ >
266
+ > - the agent can read the content of **any page you open in the sidebar** (that is precisely what "one page for both parties" means);
267
+ > - the agent can read the **cookies and login state** in that partition, and `browser_auth` can export them (controlled by a setting);
268
+ > - your actions in the sidebar and the agent's actions act on **the same page** and can affect each other (the agent will not overwrite what you are typing, but navigation changes what you both see).
269
+ >
270
+ > That is the inherent cost of one shared page. We think it is worth it — it turns "the agent is doing something in a window you cannot see" into "you can watch it work and take over" — but you are entitled to know it exists, so there are switches: with **credential access** off the agent stops reading cookies and login state, and with the **vision strategy** set to non-visual any coordinate click that depends on a screenshot is refused. If you would rather not accept the boundary change at all, removing the plugin from the desktop profile returns you to the old separate-window shape.
271
+
272
+ **② Use the browser you already have (Chrome / Edge)**
273
+
274
+ The settings panel can point the plugin at an **installed Chrome or Edge** (`browser.channel`: `bundled` / `auto` / `chrome` / `edge`). The approach is the same one Codex Browser Use takes: launch it with `--remote-debugging-port=0`, read the port it writes into `DevToolsActivePort`, and drive it entirely over CDP (through Node 22's built-in `WebSocket` — **no new dependency**).
275
+
276
+ **Your data is not touched**: the plugin launches it with a **separate profile** (`$DSH_HOME/dsh-builtin-browser-host/<chrome|edge>-profile`). Your everyday windows, bookmarks and logins are never opened, locked or modified, and closing the plugin never closes your browser.
277
+
278
+ **What happens to login state**:
279
+
280
+ - `cookies.persist` **on** (default) → that fixed profile is kept, so **you stay signed in across DSH restarts**, and `browser_auth` can still export/restore its cookies.
281
+ - `cookies.persist` **off** → a **throwaway profile** each time, deleted when the browser is released; no login trace is left behind.
282
+ - The trade-off, stated plainly: a separate profile **does not see** the sites you are signed into in your everyday browser. Sign in once in the window the plugin opens and the session stays in its own profile.
283
+
284
+ **③ A shell that provides `electronViewHost`** (older desktop shells): that view is used directly.
285
+
286
+ **④ No shell at all (plain `dsh web`)**: self-hosted — the plugin spawns the Electron it ships.
287
+
288
+ > The visible view and column layout always belong to the host shell; the plugin owns the seam, the provider and the tools. Across all carriers the **toolset, browsing history, settings panel, synthetic cursor and teardown rules are identical** — only the carrier of the page differs.
289
+ >
290
+ > **Precedence**: an explicitly chosen installed browser > the desktop sidebar > self-hosting. If the chosen browser is **missing or fails to start**, a warning is logged and the bundled browser is kept — it is **never silently swapped** for something else.
246
291
 
247
292
  ## Requirements
248
293
 
@@ -256,10 +301,10 @@ The browser's **visible view**, the **browser column layout**, and the **column-
256
301
 
257
302
  | Component | Version |
258
303
  | --- | --- |
259
- | DeepSeek Harness (dsh) | `0.1.1-rc.2` (peer range `^0.1.1-rc.2`) |
304
+ | DeepSeek Harness (dsh) | `0.2.0-rc.2` (peer range `>=0.1.1-rc.2 <0.3.0`) |
260
305
  | Electron | `44.0.0` (≥ 40 recommended; 33.x has a compositor defect) |
261
306
  | Node.js | `22.20.0` |
262
- | dsh-builtin-browser | `0.1.21` |
307
+ | dsh-builtin-browser | `0.3.0` |
263
308
  | OS | Windows 10 (10.0.26200) |
264
309
 
265
310
  > The plugin declares `electron >= 30`; it has **only been verified on Windows** (macOS/Linux untested, not yet promised).
@@ -358,7 +403,7 @@ Code layout:
358
403
  | Round 20 addendum | 2026-10-01 | **Root cause of the invisible settings panel, plus crash diagnostics**: (1) On a real host the panel did not appear — the cause is that all three `cordis.patch.yml` rows were registered under **subpath** names (`dsh-builtin-browser/browser`), while the host's client-module scan resolves a row's specifier to a **package root** (`exactPackageSpecifier` returns `undefined` as soon as it sees a `/`), leaving the package with no row to read its `dsh.client` from. Fixed by adding an inert root row under the bare package name and giving the root entry `export const name` plus an empty `apply()`. Measured: boot entries 67 → **68**, the panel appears below "规则设定", and switches persist immediately. (2) Three host-log defects fixed: ISO timestamps, both paths plus their existence recorded before spawning, and a 2 MiB rotation that leaves a dated marker instead of wiping the file (which is how weeks of history vanished); the `exit` line gained `pid=` and `entryExists=` so "present at spawn, absent at exit" names an installation replaced underneath a running host. (3) Test pollution of the real log fixed: three spawn-based tests restored `DSH_HOME` in a `finally` while `dispose()` kills children asynchronously, so their synthetic exit lines landed in the operator's real log — now isolated at module scope. **70/70 tests** |
359
404
  | **0.2.0** | 2026-10-01 | **Release**: round 20 (persistent browsing history / the "Browser" settings section / synthetic cursor / teardown semantics) and its addendum ship as **0.2.0** — tool count **33 → 34** (new `browser_visited`), plus a new "Browser" section in Settings. Build clean, **70/70 tests pass**, tag `v0.2.0` |
360
405
 
361
- > The npm badge at the top is the authority on the registry's latest version: `0.1.23` is committed and tagged; if the badge still reads `0.1.22`, that version is not published yet.
406
+ > The npm badge at the top is the authority on the registry's latest version. (was: `0.1.23` is committed and tagged; if the badge still reads `0.1.22`, that version is not published yet.
362
407
 
363
408
  ## Acknowledgements
364
409
 
package/README.md CHANGED
@@ -236,13 +236,58 @@ agent (browser_* 工具)
236
236
 
237
237
  **自托管模式**:没有桌面外壳时,插件自己拉起一个 Electron 子进程(`host-main.js`),通过本机 TCP JSON-RPC 驱动。RPC 带随机 token 认证,token 经 **stdin + 环境变量双通道**传递——Windows 上 Electron 是 GUI 子系统进程、收不到 piped stdin,环境变量兜底保证握手稳定。子进程崩溃会自动重启;优先使用随插件安装的 electron 包(**打包应用如 DSH Desktop.exe 不会被误当作可复用二进制**,避免 spawn 秒退);截图优先走 Electron 原生 `capturePage`(CDP 截图在多视图下会挂起);Electron 的定位顺序见下(33.x 有合成器缺陷,建议 ≥ 40;44+ 的 electron 包不再随安装自动下载二进制,首次使用若缺失会按报错提示先 `npx install-electron`,需联网)。
238
238
 
239
- **自托管浏览器就是一台真正的浏览器**:每个任务(DSH 会话)拥有**独立的浏览器窗口**,窗口自带完整工具栏——地址栏、后退/前进/刷新按钮、标签条(新建/切换/关闭标签)。人可以直接像用 Chrome 一样使用它:在地址栏输入网址(自动补 `https://`)、点标签切换页面、开新标签;键盘焦点跟随点击——**点地址栏即可输入、点页面即可操作**(Windows 焦点路由,修复了点击不转移焦点导致地址栏无法输入的问题)。agent 与人的操作都汇入**同一个会话模型**(同一套标签、历史与导航),窗口标题实时显示当前任务标识与页面标题/URL,窗口缩放时视图自动跟随。任务结束后窗口随会话自动关闭。
239
+ **自托管浏览器就是一台真正的浏览器**(Web 端、或没有桌面外壳时的形态):每个任务(DSH 会话)拥有**独立的浏览器窗口**,窗口自带完整工具栏——地址栏、后退/前进/刷新按钮、标签条(新建/切换/关闭标签)。人可以直接像用 Chrome 一样使用它:在地址栏输入网址(自动补 `https://`)、点标签切换页面、开新标签;键盘焦点跟随点击——**点地址栏即可输入、点页面即可操作**(Windows 焦点路由,修复了点击不转移焦点导致地址栏无法输入的问题)。agent 与人的操作都汇入**同一个会话模型**(同一套标签、历史与导航),窗口标题实时显示当前任务标识与页面标题/URL,窗口缩放时视图自动跟随。任务结束后窗口随会话自动关闭。
240
240
 
241
241
  **Electron 定位顺序**:① `ELECTRON_PATH`(显式覆盖,用户显式意图最优先)→ ② 随插件安装的 electron 包(纯文件系统探测,不触发 44+ 懒下载;覆盖 node_modules 与 pnpm store 两种布局)→ ③ DSH 安装锚点与 pnpm 虚拟仓库中**版本最新**者 → ④ 当前进程为**裸** Electron 时复用宿主二进制(开发模式)→ ⑤ 进程祖先树中的**裸** Electron 宿主(Windows 走 PowerShell CIM,仅最后手段)。**打包应用(如 `DSH Desktop.exe`)一律不复用**——它们不能按脚本参数拉起,误用会导致 spawn 秒退(issue #6);找不到时工具会报清晰的错误提示(含 `npx install-electron` 指引)。
242
242
 
243
243
  ## 与桌面外壳的分工
244
244
 
245
- 浏览器**可见视图**、**浏览器列布局**、**列与视图的对齐**都属于宿主外壳(如 dsh 的 `apps/desktop`),不在本插件内。本插件只消费外壳提供的 `electronViewHost`,负责 seam、provider 与工具。没有配套外壳时插件**自托管**,功能照常可用。
245
+ 插件会自动选载体,也可以在设置里指定(共四种,见下):
246
+
247
+ **① 桌面端:驱动官方侧栏的页面(人机同页)**
248
+
249
+ DSH Desktop 是两层结构:Electron 外壳 + 一个 `--expose-internals` 的 **Node 模式宿主**(插件就跑在宿主里,**没有 Electron API**)。0.2 移除了 `electronViewHost`,宿主与外壳之间也没有任何承载视图的通道,所以插件**借道一条小桥**:外壳主进程里跑一个 loopback + token 的 bridge,把侧栏浏览器的 guest(就是你在界面上看到的那个页面)的 CDP 交给插件。
250
+
251
+ 结果是:**agent 操作的页面就是人看的页面**,插件不再 spawn 自己的 Electron,也不再出现第二个窗口。
252
+
253
+ 安装这条桥(改的是**已安装的桌面端**,所以做成可重放):
254
+
255
+ ```bash
256
+ node desktop-bridge/install.mjs # 幂等;首次会备份 main.js.before-bridge
257
+ node desktop-bridge/install.mjs --revert # 回滚
258
+ ```
259
+
260
+ > **桌面端升级后要重跑一次 `install.mjs`** —— 升级会替换 `resources/app/`,bridge 随之消失。bridge 不在时插件自动退回自托管,功能不会中断,只是会多出一个独立窗口。
261
+
262
+ > **⚠️ 沙箱边界的变化(请明示知悉)**
263
+ >
264
+ > 官方侧栏浏览器的前提是:侧栏里的页面**不被外部读取** —— 它有独立的 partition,且宿主拒绝跨站内容访问。让 Agent 驱动该 guest,等于**有意打破这个前提**:
265
+ >
266
+ > - Agent 能读到你在侧栏访问的**任何页面的内容**(这正是"人机同页"的意义);
267
+ > - Agent 能读到该 partition 中的 **Cookie / 登录态**,`browser_auth` 可将其导出(由设置项控制);
268
+ > - 反过来,你在侧栏里的操作与 Agent 的操作**作用于同一个页面**,可能互相影响(Agent 不会主动覆盖你的输入,但导航会改变双方看到的内容)。
269
+ >
270
+ > 这是"人机同页"的必然代价。我们认为值得(它把"Agent 在一个你看不见的窗口里操作"变成"你能看着它操作并随时接手"),但你有权知道它存在 —— 因此也提供了开关:**「凭据访问」关闭后 Agent 不再读取 Cookie/登录态**;**「视觉策略」设为纯非视觉后,任何依赖截图定位的坐标点击都会被拒绝**。不想接受这个边界变化时,把 desktop profile 里的插件移除即可回到"独立窗口"的旧形态。
271
+
272
+ **② 用你自己的浏览器(Chrome / Edge)**
273
+
274
+ 设置里可以把载体改成**本机已安装的 Chrome 或 Edge**(`browser.channel`:`bundled` / `auto` / `chrome` / `edge`)。做法与 Codex Browser Use 一致:以 `--remote-debugging-port=0` 启动,读浏览器自己写下的 `DevToolsActivePort` 得到端口,再全程走 CDP(用 Node 22 内置的 `WebSocket`,**不新增依赖**)。
275
+
276
+ **你自己的数据不会被碰**:插件用的是**独立 profile**(`$DSH_HOME/dsh-builtin-browser-host/<chrome|edge>-profile`),不会打开、占用或修改你日常的窗口、书签与登录状态;插件退出也不会关掉你的浏览器。
277
+
278
+ **登录态怎么办**:
279
+
280
+ - `cookies.persist` **开**(默认)→ 上面那个固定 profile 会保留,**重启 DSH 后仍是登录状态**;`browser_auth` 也照常可导出/恢复该 profile 的 Cookie。
281
+ - `cookies.persist` **关** → 每次用**临时 profile**,释放浏览器时整个目录被删除,不留登录痕迹。
282
+ - 代价要说清:独立 profile **看不到**你日常浏览器里已登录的站点 —— 在插件打开的窗口里登录一次即可,之后登录态就存在它自己的 profile 里。
283
+
284
+ **③ 有 `electronViewHost` 的宿主(旧版桌面外壳)**:直接使用外壳提供的视图。
285
+
286
+ **④ 没有外壳(纯 `dsh web`)**:自托管 —— spawn 插件自带的 Electron 窗口,功能照常。
287
+
288
+ > 可见视图与列布局始终属于宿主外壳;插件只负责 seam、provider 与工具。各形态下**工具集、浏览历史、设置栏、可视化鼠标、收尾语义完全一致**,差别只在页面由谁承载。
289
+ >
290
+ > **优先级**:设置里显式选择的本机浏览器 > 桌面端侧栏 > 自托管。选定的浏览器**没装或起不来**时会记一条警告并继续用内置浏览器 —— **不会静默换成别的**。
246
291
 
247
292
  ## 环境要求
248
293
 
@@ -256,10 +301,11 @@ agent (browser_* 工具)
256
301
 
257
302
  | 组件 | 版本 |
258
303
  | --- | --- |
259
- | DeepSeek Harness(dsh) | `0.1.1-rc.2`(peer 声明 `^0.1.1-rc.2`) |
304
+ | DeepSeek Harness(dsh) | `0.2.0-rc.2`(peer 声明 `>=0.1.1-rc.2 <0.3.0`) |
260
305
  | Electron | `44.0.0`(推荐 ≥ 40;33.x 存在合成器缺陷) |
261
306
  | Node.js | `22.20.0` |
262
- | dsh-builtin-browser | `0.1.21` |
307
+ | 本机 Chrome / Edge(可选载体) | `154.0.8037.58` / `154.0.4258.37` |
308
+ | dsh-builtin-browser | `0.3.0` |
263
309
  | 操作系统 | Windows 10 (10.0.26200) |
264
310
 
265
311
  > 插件声明 `electron >= 30`;**当前仅在 Windows 环境实测**(macOS/Linux 未验证,暂不承诺)。
@@ -270,18 +316,24 @@ agent (browser_* 工具)
270
316
 
271
317
  **桌面端(DSH Desktop)**
272
318
  - 插件是桌面端 profile 里的依赖(profile 目录通常是 `$DSH_HOME/profiles/desktop`)。更新它 = 把该 profile 里的依赖更新到新版本,然后**重启 DSH Desktop**,客户端设置栏与工具才会换成新代码。
273
- - 浏览器内核由桌面端自带的 Electron 提供,插件不会再下载一份 Electron。
274
- - 桌面端自身的升级不会自动带来插件升级,需要按上面的方式单独更新。
319
+ - **桌面端还多一步,而 Web 端没有**:让插件驱动侧栏的那条 bridge 装在**桌面端自己的安装目录**里(`resources/app/`),插件更新**不会**带上它。桌面端升级会替换该目录、bridge 随之消失,所以请重跑一次:
320
+ ```bash
321
+ node desktop-bridge/install.mjs # 幂等;已装则只刷新模块
322
+ node desktop-bridge/install.mjs --revert # 回滚
323
+ ```
324
+ bridge 不在时插件自动退回自托管(多出一个独立窗口),功能不中断。
325
+ - 浏览器内核默认由桌面端自带的 Electron 提供,插件不会再下载一份 Electron;也可以在设置里改用它自己装的 Chrome / Edge。
275
326
 
276
327
  **Web 端(`dsh web`)**
277
328
  - 插件是 web profile 里的依赖(`$DSH_HOME/profiles/web`),更新后**重启 `dsh web`** 生效。
278
- - Web 端没有桌面外壳,共享浏览器由插件自托管拉起:首次安装可能需要 Electron 二进制;若包管理器的构建白名单拦下了它(pnpm v10+ 会拦 `electron` 的 postinstall),执行一次 `npx install-electron` 补上即可。
329
+ - Web 端**没有侧栏、也没有 bridge**:共享浏览器由插件自托管拉起。首次安装可能需要 Electron 二进制;若包管理器的构建白名单拦下了它(pnpm v10+ 会拦 `electron` 的 postinstall),执行一次 `npx install-electron` 补上即可。
330
+ - 同样可以在设置里改用本机 Chrome / Edge —— 这是两端**行为一致**的选项。
279
331
  - 更新方式与你首次安装它时一致(按 npm 包名 `dsh-builtin-browser`、按 GitHub 仓库 `wqty123/dsh-browser`,或本地目录)。
280
332
 
281
333
  **两端一致的体验**
282
- - 工具集(34 个 `browser_*`)、设置页里的「浏览器」栏、浏览历史与 cookie 的持久化行为完全相同;差别只在浏览器窗口由谁承载(桌面外壳 / 插件自托管)。
283
- - 升级不会丢数据:浏览历史与设置都在 `$DSH_HOME/dsh-builtin-browser-host/`(`history.jsonl`、`settings.json`),登录状态在同一 profile 目录里。
284
- - 升级后如果历史记录不符合预期,先去「设置 → 浏览器」确认这些开关:历史默认**开**、侧栏自动展开默认**开**、会话结束时自动关闭浏览器默认**关**。
334
+ - 工具集(34 个 `browser_*`)、设置页里的「浏览器」栏、浏览历史与 cookie 的持久化行为完全相同;差别只在页面由谁承载(桌面端=官方侧栏,Web 端=插件自托管窗口,或你在设置里指定的本机浏览器)。
335
+ - 升级不会丢数据:浏览历史与设置都在 `$DSH_HOME/dsh-builtin-browser-host/`(`history.jsonl`、`settings.json`),登录状态在同一 profile 目录里 —— 包括使用本机浏览器时的 `<chrome|edge>-profile`。
336
+ - 升级后如果历史记录不符合预期,先去「设置 → 浏览器」确认这些开关:历史默认**开**、侧栏自动展开默认**开**、会话结束时自动关闭浏览器默认**关**、载体默认**内置**。
285
337
 
286
338
  ## 已知限制
287
339
 
package/client.js CHANGED
@@ -50,12 +50,19 @@ window.__ModuleLoader__.load({ id: "dsh-builtin-browser", factory: (require) =>
50
50
  "ui.virtualCursor.hint": "让 Agent 正在操作的位置可见(出现即代表它在接管该标签页)。",
51
51
  "vision.title": "视觉策略",
52
52
  "vision.strategy": "默认策略",
53
- "vision.auto": "Auto(按模型能力与页面情况自动切换)",
54
- "vision.nonVisual": "纯非视觉(DOM / 无障碍树)",
55
- "vision.hint": "模型不具备图像理解能力时必须使用非视觉方式。",
53
+ "vision.auto": "Auto(坐标与语义都允许;适合能读图的模型)",
54
+ "vision.nonVisual": "纯非视觉(拒绝坐标点击,只用 DOM / 无障碍树定位)",
55
+ "vision.hint": "纯非视觉下,坐标点击会被明确拒绝并提示改用语义定位,避免靠猜坐标点错。两种策略下 DOM、无障碍树、文本提取与结构化抓取的能力完全一致 —— 不需要图像输入也能完整操作页面。",
56
56
  "credentials.title": "凭据",
57
57
  "credentials.allowRead": "允许 Agent 读取 cookies / 导出登录状态",
58
- "credentials.hint": "关闭后 browser_auth 会被拒绝。",
58
+ "browser.title": "浏览器载体",
59
+ "browser.channel": "使用哪个浏览器",
60
+ "browser.bundled": "内置 Electron(随插件提供,无需安装)",
61
+ "browser.chrome": "本机 Chrome",
62
+ "browser.edge": "本机 Edge",
63
+ "browser.auto": "自动(优先 Chrome,其次 Edge)",
64
+ "browser.hint": "选本机浏览器时会以**独立 profile** 启动它:不会打开、占用或修改你日常的窗口、书签与登录状态,关闭也不会关掉你自己的浏览器;代价是它看不到你日常浏览器里已登录的站点,需要时请在那个窗口里登录一次(登录态会留在插件自己的 profile 里)。桌面端默认由官方侧栏承载页面,此选项优先于侧栏。",
65
+ "credentials.hint": "关闭后 browser_auth 会被拒绝。桌面端请注意:Agent 驱动的是侧栏里那一个页面(人机同页),因此它与该页面共用同一个 partition —— 这也正是它能读到登录态的原因,属于有意接受的沙箱边界变化;关闭本项即拒绝读取。",
59
66
  }
60
67
 
61
68
  const en = {
@@ -82,12 +89,20 @@ window.__ModuleLoader__.load({ id: "dsh-builtin-browser", factory: (require) =>
82
89
  "ui.virtualCursor.hint": "Makes the agent's operating position visible (its presence means the agent has taken over that tab).",
83
90
  "vision.title": "Vision strategy",
84
91
  "vision.strategy": "Default strategy",
85
- "vision.auto": "Auto (switch by model capability and page)",
86
- "vision.nonVisual": "Non-visual only (DOM / a11y tree)",
87
- "vision.hint": "A model without image input always uses non-visual operation.",
92
+ "vision.auto": "Auto (coordinates and semantics both allowed; for models that read images)",
93
+ "vision.nonVisual": "Non-visual only (coordinate clicks refused; locate via DOM / a11y tree)",
94
+ "vision.hint": "Under non-visual, a coordinate click is refused outright and the error says to use a semantic target — so nothing is clicked on a guessed position. DOM lookup, the accessibility tree, text extraction and structured scraping behave identically in both strategies: the whole page stays operable without image input.",
88
95
  "credentials.title": "Credentials",
89
96
  "credentials.allowRead": "Allow the agent to read cookies / export login state",
90
- "credentials.hint": "Off makes browser_auth refuse.",
97
+ "browser.title": "Browser",
98
+ "browser.channel": "Which browser to use",
99
+ "browser.bundled": "Bundled Electron (ships with the plugin, nothing to install)",
100
+ "browser.chrome": "Installed Chrome",
101
+ "browser.edge": "Installed Edge",
102
+ "browser.auto": "Automatic (Chrome first, then Edge)",
103
+ "browser.hint": "Choosing an installed browser launches it with a **separate profile**: your everyday windows, bookmarks and logins are never opened, locked or modified, and closing the plugin never closes your browser. The trade-off is that it does not see sites you are already signed into there — sign in once in that window and the session stays in the plugin's own profile. On the desktop the shell's sidebar normally carries the page; this setting outranks it.",
104
+ "credentials.allowRead": "Allow the agent to read cookies / export login state",
105
+ "credentials.hint": "Off makes browser_auth refuse. On the desktop, note that the agent drives the sidebar's own page (one page for both parties), so it shares that page's partition — which is exactly why it can reach the login state. That is a deliberate sandbox-boundary change; this switch is how you refuse it.",
91
106
  }
92
107
 
93
108
  /** Read or patch the host-owned settings document. */
@@ -254,6 +269,27 @@ window.__ModuleLoader__.load({ id: "dsh-builtin-browser", factory: (require) =>
254
269
  ]),
255
270
  ]),
256
271
 
272
+ section("browser.title", [
273
+ h("div", { key: "row", style: styles.row }, [
274
+ h("label", { key: "l", style: styles.label }, [
275
+ h("span", { key: "t" }, t("browser.channel")),
276
+ h("select", {
277
+ key: "s",
278
+ style: styles.select,
279
+ value: settings.browser.channel,
280
+ disabled: busy,
281
+ onChange: event => patch({ browser: { channel: event.target.value } }),
282
+ }, [
283
+ h("option", { key: "bundled", value: "bundled" }, t("browser.bundled")),
284
+ h("option", { key: "auto", value: "auto" }, t("browser.auto")),
285
+ h("option", { key: "chrome", value: "chrome" }, t("browser.chrome")),
286
+ h("option", { key: "edge", value: "edge" }, t("browser.edge")),
287
+ ]),
288
+ h("span", { key: "h", style: styles.hint }, t("browser.hint")),
289
+ ]),
290
+ ]),
291
+ ]),
292
+
257
293
  section("credentials.title", [
258
294
  h(Toggle, {
259
295
  key: "allowRead",
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Install the plugin browser bridge into an installed DSH Desktop.
3
+ *
4
+ * WHY A SCRIPT
5
+ * The bridge has to live inside the desktop app (it is the only process with
6
+ * Electron access), and the desktop app is somebody else's installed artifact:
7
+ * a desktop update replaces everything this script touches. So the change is
8
+ * expressed as something replayable rather than as a one-off edit — re-run this
9
+ * after every desktop upgrade.
10
+ *
11
+ * WHAT IT DOES (idempotent)
12
+ * 1. copies `plugin-browser-bridge.js` next to the app's main bundle;
13
+ * 2. appends a guarded import to `lib/main.js` that starts it after `whenReady`,
14
+ * keeping a pristine `main.js.before-bridge` backup the first time;
15
+ * 3. does nothing if the import is already present.
16
+ *
17
+ * USAGE
18
+ * node apps/desktop/bridge/install.mjs [path-to-DeepSeek-Harness-install]
19
+ * Default path: %LOCALAPPDATA%\Programs\DeepSeek Harness
20
+ * Pass --revert to restore the pre-bridge main.js instead.
21
+ */
22
+ import { copyFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs'
23
+ import { join } from 'node:path'
24
+
25
+ const DEFAULT_INSTALL = join(process.env.LOCALAPPDATA ?? '', 'Programs', 'DeepSeek Harness')
26
+ const args = process.argv.slice(2)
27
+ const revert = args.includes('--revert')
28
+ const installRoot = args.find(arg => !arg.startsWith('--')) ?? DEFAULT_INSTALL
29
+
30
+ const appDir = join(installRoot, 'resources', 'app')
31
+ const mainPath = join(appDir, 'lib', 'main.js')
32
+ const bridgeTarget = join(appDir, 'lib', 'plugin-browser-bridge.js')
33
+ const bridgeSource = join(import.meta.dirname, 'plugin-browser-bridge.js')
34
+ const backupPath = `${mainPath}.before-bridge`
35
+
36
+ /** The exact block appended to main.js. Keep it recognisable: idempotence and
37
+ * reverting both key off this marker. */
38
+ const MARKER = '// dsh-builtin-browser: hand the plugin a way to drive the sidebar'
39
+ const SNIPPET = `
40
+ ${MARKER} pages.
41
+ // The plugin runs inside the Node-mode host, where there is no Electron API, so the
42
+ // shell is the only process that can own a view. Exposing the existing webview
43
+ // guests (and their CDP) is what lets the agent work on the page the human is
44
+ // looking at, instead of spawning a second, parallel browser window.
45
+ // Installed by apps/desktop/bridge/install.mjs — re-run it after a desktop upgrade.
46
+ // A bridge failure is never fatal to the shell.
47
+ app.whenReady().then(async () => {
48
+ try {
49
+ const bridge = await import("./plugin-browser-bridge.js");
50
+ bridge.start();
51
+ } catch (error) {
52
+ console.error("[dsh-browser-bridge] start failed:", error);
53
+ }
54
+ });
55
+ `
56
+
57
+ if (!existsSync(mainPath)) {
58
+ console.error(`not a DSH Desktop install (no ${mainPath})`)
59
+ process.exit(1)
60
+ }
61
+
62
+ if (revert) {
63
+ if (!existsSync(backupPath)) {
64
+ console.error(`nothing to revert: ${backupPath} does not exist`)
65
+ process.exit(1)
66
+ }
67
+ copyFileSync(backupPath, mainPath)
68
+ console.log(`reverted ${mainPath} from ${backupPath}`)
69
+ process.exit(0)
70
+ }
71
+
72
+ const current = readFileSync(mainPath, 'utf8')
73
+ if (current.includes(MARKER)) {
74
+ console.log('bridge already installed (main.js carries the marker); refreshing the module copy')
75
+ copyFileSync(bridgeSource, bridgeTarget)
76
+ process.exit(0)
77
+ }
78
+
79
+ if (!existsSync(backupPath)) {
80
+ copyFileSync(mainPath, backupPath)
81
+ console.log(`backed up main.js -> ${backupPath}`)
82
+ }
83
+
84
+ // Append before the trailing `export {};` so the import stays inside the module.
85
+ const exportTail = 'export {};'
86
+ const insertAt = current.lastIndexOf(exportTail)
87
+ if (insertAt === -1) {
88
+ console.error('unexpected main.js shape: no trailing `export {};` to insert before')
89
+ process.exit(1)
90
+ }
91
+ const patched = `${current.slice(0, insertAt)}${SNIPPET}${current.slice(insertAt)}`
92
+ writeFileSync(mainPath, patched)
93
+
94
+ copyFileSync(bridgeSource, bridgeTarget)
95
+ console.log(`installed bridge into ${appDir}`)
96
+ console.log(` module: ${bridgeTarget}`)
97
+ console.log(` patch: ${mainPath} (+${SNIPPET.split('\n').length} lines)`)
98
+ console.log('restart DSH Desktop to activate it.')