dsh-builtin-browser 0.1.14 → 0.1.16

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/README.en.md CHANGED
@@ -18,7 +18,7 @@
18
18
  | --- | --- |
19
19
  | Why a shared real browser, and how it differs from headless approaches | [Why a shared real browser](docs/why-browser.md) |
20
20
  | Installation, configuration, day-to-day use | [User guide](docs/user-guide.md) |
21
- | All 20 tools: parameters, output, examples | [Tool reference](docs/tool-reference.md) |
21
+ | All 33 tools: parameters, output, examples | [Tool reference](docs/tool-reference.md) |
22
22
  | How the seam / provider / tools layers and self-hosting work | [Architecture](docs/architecture.md) |
23
23
  | Documentation index and README split | [Docs index](docs/README.md) |
24
24
 
@@ -28,7 +28,7 @@
28
28
 
29
29
  - **A real view, not a relay**: the browser is a native `WebContentsView`; the human sees every step the agent takes and can grab control at any time;
30
30
  - **Install-and-use**: with a desktop shell the shell's embedded view is used; on plain `dsh web` the plugin **self-hosts** — it spawns its own Electron window with zero extra configuration;
31
- - **One plugin, one toolset**: after install the agent automatically gets 20 `browser_*` tools (open, inspect, interact, fill forms, screenshot, download, auth management…).
31
+ - **One plugin, one toolset**: after install the agent automatically gets 33 `browser_*` tools (open, a11y tree, wait, semantic/coordinate interaction, scroll, back/forward, batch and single-control form filling, keys, structured scraping, screenshot, download, auth management…).
32
32
 
33
33
  In one sentence: **installing the plugin gives you a real browser that is shared with the user and drivable by the agent.**
34
34
 
@@ -131,13 +131,26 @@ See the full list in [Tool reference](#tool-reference).
131
131
  | Tool | Purpose | Guard |
132
132
  | --- | --- | --- |
133
133
  | `browser_open` | Open a URL (optionally in a new tab); returns a page snapshot | ✅ |
134
- | `browser_snapshot` | Numbered inventory of interactive elements (inputs/buttons/links) | – |
134
+ | `browser_wait` | Wait for page load (optional expected URL / CSS selector), returns readiness | – |
135
+ | `browser_snapshot` | Numbered inventory of interactive elements (inputs/buttons/links; pierces same-origin iframes and Shadow DOM) | – |
136
+ | `browser_a11y` | Accessibility tree: semantic role/name/value/states + coordinates per interactive node (pierces same-origin iframes and Shadow DOM) | – |
135
137
  | `browser_execute` | Run JS in the page; args arrive as `arguments[0..n]` | ✅ |
136
138
  | `browser_content` | Fetch the page as html / markdown / txt / json (selector, maxChars, timeoutMs) | – |
137
- | `browser_click` | Click at viewport coordinates (for vision-located targets) | ✅ |
138
- | `browser_type` | Type text into the focused element (CDP `Input.insertText`) | ✅ |
139
+ | `browser_click` | Click a semantic target (`target`: css/text/xpath, scrolls into view and clicks center) or viewport coordinates (vision-located) | ✅ |
140
+ | `browser_type` | Type text (optionally focusing a `target` element first; CDP `Input.insertText`) | ✅ |
141
+ | `browser_key` | Press a named key (Enter/Tab/arrows/Home/End…) | ✅ |
142
+ | `browser_scroll` | Scroll the page (pixel deltas / selector / top-bottom) | ✅ |
143
+ | `browser_back` | One step back in page history (no-op at the start) | ✅ |
144
+ | `browser_forward` | One step forward in page history (no-op at the end) | ✅ |
145
+ | `browser_refresh` | Reload the current page (like a browser refresh button) | ✅ |
139
146
  | `browser_fill` | Batch form fill (selector/name/label matching, controlled inputs, selects, checkbox/radio, optional submit) | ✅ |
140
- | `browser_screenshot` | PNG capture, optional `fullPage` and `savePath` | – |
147
+ | `browser_set_value` | Set one control's value (`target`-located; native setter + input/change, React-controlled friendly) | ✅ |
148
+ | `browser_check` | Check/uncheck a checkbox or radio (`target`-located) | ✅ |
149
+ | `browser_select` | Select an option of a `<select>` by value/text/index (`target`-located) | ✅ |
150
+ | `browser_clear` | Clear an input/textarea/contenteditable, or uncheck (`target`-located) | ✅ |
151
+ | `browser_get_value` | Read an element's current value for verification (`target`-located) | – |
152
+ | `browser_scrape` | Structured extraction: container selector + field map (`selector@attr`), static CSS only, CSP-safe | – |
153
+ | `browser_screenshot` | Capture, optional `fullPage`, `savePath`, JPEG (`format`/`quality`) and scaling (`maxWidth`/`maxHeight`) | – |
141
154
  | `browser_list_tabs` | List the session's tabs | – |
142
155
  | `browser_switch_tab` | Switch to a tab by id (also switches the visible view when self-hosted) | ✅ |
143
156
  | `browser_close_tab` | Close a tab by id; closing the active tab activates the next | – |
@@ -146,13 +159,24 @@ See the full list in [Tool reference](#tool-reference).
146
159
  | `browser_reset_session` | Close and rebuild this task's browser session | ✅ |
147
160
  | `browser_history` | Operation log (newest last), with per-step success/error and result summary | – |
148
161
  | `browser_replay` | Replay one step by sequence number (navigate/execute/click/type) | ✅ |
149
- | `browser_download` | Download a URL with session cookies to a local file (256 MB cap) | ✅ |
162
+ | `browser_download` | Download an HTTP(S) URL with session cookies to a local file (absolute `savePath`, 256 MB cap) | ✅ |
150
163
  | `browser_auth` | Export/restore cookies (login persistence, self-hosted) | ✅ |
151
164
  | `browser_challenge` | Detect a human-verification challenge (CAPTCHA / Cloudflare / reCAPTCHA / hCaptcha / Turnstile) | – |
152
- | `browser_restrict` | Restrict allowed browser actions (allow-list; empty list lifts it) | – |
165
+ | `browser_restrict` | Restrict allowed browser actions (allow-list; empty list lifts it). **Soft guardrail** — the model can lift it itself; not a security boundary | – |
153
166
 
154
167
  > "Guard" column: ✅ actions are governed by the `browser_restrict` allow-list; read-only tools (snapshot/content/screenshot/list_tabs/session/challenge/history) are never blocked.
155
168
 
169
+ ### Waiting for the page
170
+
171
+ - **After `browser_open`, before `browser_snapshot`, call `browser_wait` on slow sites**: pass `url` (what you opened) and an optional `selector`, and wait for `ready: true` — otherwise you snapshot the old document or a white screen.
172
+ - **Content you cannot see may live in an iframe / Shadow DOM**: snapshots and the a11y tree pierce same-origin iframes and shadow roots and mark them `(iframe)`; coordinates are always top-document, so `browser_click` works directly. DOM selectors are frame-scoped — reach them via `iframe.contentDocument` in `browser_execute`.
173
+
174
+ ### Semantic targets and the a11y tree
175
+
176
+ - **`browser_a11y` is the best way to understand a page**: every interactive node carries its semantic role (button/textbox/checkbox…), accessible name, current value, states (enabled/checked/expanded…) and coordinates — click/type them directly.
177
+ - **`browser_click`/`browser_type` accept a `target`**: `{by: css|text|xpath, value, index?}` — `text` matches an element's own visible text (exact first, then contains, deepest preferred); clicks scroll the element to the viewport center first; typing focuses it first.
178
+ - **Use the single-control tools for one field** (`browser_set_value`/`browser_check`/`browser_select`/`browser_clear`/`browser_get_value`), `browser_fill` for batches, and `browser_scrape` for structured list extraction.
179
+
156
180
  ### Operating discipline (click/fill)
157
181
 
158
182
  - **Prefer DOM semantics over coordinates**: submit forms with `form.requestSubmit()`; click with `element.click()`; coordinate clicks are the last resort.
@@ -171,6 +195,7 @@ The plugin mounts through `cordis.patch.yml` (three rows); per-row config:
171
195
  | `browser-electron` | `httpOnly` | boolean | `true` | Allow HTTP(S) navigation only; other protocols (e.g. `file:`/`data:`) rejected (`BROWSER_NAVIGATION_BLOCKED`) |
172
196
  | `browser-electron` | `snapshotMaxElements` | number | `60` | Max snapshot elements before truncation |
173
197
  | `browser-electron` | `contentMaxChars` | number | `100000` | Default content character cap |
198
+ | `browser-electron` | `downloadDir` | string | `~/Downloads` | Confine `browser_download` save paths to this directory (stops a prompt-injected agent writing arbitrary paths); defaults to the OS Downloads folder, override for a sandbox dir |
174
199
  | `tool-browser` | `timeoutMs` | number | `60000` | Cooperative tool timeout (ms) |
175
200
  | `tool-browser` | `tabTools` | boolean | `true` | Register tab-management tools (`browser_list_tabs` etc.) |
176
201
 
@@ -186,9 +211,11 @@ agent (browser_* tools)
186
211
 
187
212
  - **Seam** (`browser` row): provides the `ctx.browser` service — provider registration, session lifecycle, error codes — decoupled from any implementation.
188
213
  - **Provider** (`browser-electron` row): operates views through the `ElectronBrowserViewHost` seam (create/destroy/show, `sendCommand`), implemented with real Electron objects by the shell.
189
- - **Tools** (`tool-browser` row): the 20 model-facing `browser_*` tools, maintaining one browser session per calling task (DSH session).
214
+ - **Tools** (`tool-browser` row): the 33 model-facing `browser_*` tools, maintaining one browser session per calling task (DSH session).
215
+
216
+ **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; screenshots prefer Electron's native `capturePage` (CDP capture can hang with multiple views in the window); the plugin automatically picks the **newest** Electron in the environment (33.x has a compositor defect; ≥ 40 recommended; 44+ downloads its binary on first use, needs network).
190
217
 
191
- **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 (window title `dsh-browser`). The child auto-restarts after a crash; screenshots prefer Electron's native `capturePage` (CDP capture can hang with multiple views in the window); the plugin automatically picks the **newest** Electron in the environment (33.x has a compositor defect; ≥ 40 recommended).
218
+ **The self-hosted browser IS a real browser**: every task (DSH session) gets its **own browser window** with a full toolbar — address bar, back/forward/reload buttons, and a tab strip (new/switch/close tabs). A human can use it exactly like Chrome: type a URL in the address bar (https:// is added automatically), click tabs, open new ones. Keyboard focus follows your clicks — **click the address bar to type, click the page to interact** (Windows focus routing; fixes the case where clicks did not move focus and the address bar could not receive typed URLs). Human and agent actions feed the **same session model** (same tabs, history, and navigation); the window title always shows the task label plus the page title/URL, and views follow the window size on resize. A window closes automatically with its session when the task ends.
192
219
 
193
220
  **Electron lookup order**: ① `require('electron')` (peer dependency) → ② `ELECTRON_PATH` (explicit override) → ③ the newest among DSH install anchors and pnpm virtual stores. A clear error tells you when none is found.
194
221
 
@@ -205,35 +232,39 @@ The browser's **visible view**, the **browser column layout**, and the **column-
205
232
 
206
233
  | Component | Version |
207
234
  | --- | --- |
208
- | DeepSeek Harness (dsh) | `0.1.0-rc.5` |
209
- | Electron | `43.4.0` (≥ 40 recommended; 33.x has a compositor defect) |
235
+ | DeepSeek Harness (dsh) | `0.1.1-rc.2` (peer range `^0.1.1-rc.2`) |
236
+ | Electron | `44.0.0` (≥ 40 recommended; 33.x has a compositor defect) |
210
237
  | Node.js | `22.20.0` |
211
- | dsh-builtin-browser | `0.1.11` |
238
+ | dsh-builtin-browser | `0.1.16` |
212
239
  | OS | Windows 10 (10.0.26200) |
213
240
 
214
241
  > The plugin declares `electron >= 30`; it has **only been verified on Windows** (macOS/Linux untested, not yet promised).
215
242
 
216
243
  ## Known limitations
217
244
 
218
- - Screenshots are PNG-only (CDP JPEG hangs on Electron 43); JPEG awaits a non-CDP conversion path.
245
+ - JPEG screenshots are available only on the self-hosted native path (`capturePage` `toJPEG`); the desktop shell's CDP fallback stays PNG (CDP JPEG hangs on Electron 43).
219
246
  - Self-hosted captures prefer Electron's native `capturePage` (CDP `captureScreenshot` can hang with multiple views in the window); the target tab is raised before capturing.
220
247
  - `fullPage` capture is flaky under software compositing on some hosts.
221
248
  - CAPTCHA cannot be solved automatically: snapshots flag detected challenges; ask the human to complete it in the shared window instead of retrying.
222
249
  - Private mode (`privateMode`) is not implemented: it needs Electron session partitioning, which is host-layer territory; this plugin does not promise it.
223
- - `browser_download` fetches in the page context (keeps logins) and is subject to same-origin/CORS constraints; single files are capped at 256 MB.
250
+ - `browser_download` fetches in the page context (keeps logins) and is subject to same-origin/CORS constraints; HTTP(S) targets only; `savePath` must be absolute (default confined to `~/Downloads`, override with `downloadDir`); single files are capped at 256 MB (streamed with a Content-Length early reject) and are written by the browser child itself (temp file + atomic rename).
251
+ - The self-hosted browser's cookies are stored in plaintext on disk (Electron default); deployments that need encrypted-at-rest should integrate a system keychain / DPAPI at the host layer.
252
+ - `browser_restrict` is a **soft guardrail** against accidental actions, not a security boundary: the model can lift it itself.
253
+ - Popups (`window.open` / `target=_blank`) are re-routed into the current tab instead of opening untracked native windows, so the tab/session model stays intact.
224
254
  - The `browser_auth` cookie round-trip does not preserve `hostOnly`/`sameSite` (host-only cookies come back as domain cookies); it is available on the self-hosted browser only.
225
255
  - After a self-hosted child crash the browser host restarts automatically, but sessions opened before the crash are gone — call `browser_reset_session` to rebuild.
256
+ - Electron 44+ downloads its binary (~100 MB) on the first window open and needs network; it is never needed again afterwards. If detection runs without network, pre-install a binary and point `ELECTRON_PATH` at it.
226
257
  - This plugin contains no browser-column UI — that is host-shell territory; do not treat "browser column" as a plugin feature.
227
258
 
228
259
  ## Development
229
260
 
230
261
  ```sh
231
262
  # Type-check + build (lib/)
232
- pnpm run build
233
-
234
- # Functional tests: start a local page server + Electron probe (see repo test scripts)
263
+ npm run build
235
264
  ```
236
265
 
266
+ > Run tests: `npm test` (= `tsc -p tsconfig.json` + `node --test "tests/*.test.mjs"`; fake-host tests, no Electron needed).
267
+
237
268
  Code layout:
238
269
 
239
270
  | Directory | Responsibility |
@@ -243,6 +274,21 @@ Code layout:
243
274
  | `src/tool-browser/` | Model-facing `browser_*` tools |
244
275
  | `src/types/` | Electron ambient types (shim; no hard electron type dependency) |
245
276
 
277
+ ## Update history
278
+
279
+ > Round-by-round development and fixes (full detail in [CHANGELOG.md](CHANGELOG.md)). Published as of **0.1.16** (tag `v0.1.16`).
280
+
281
+ | Round | Date | Content |
282
+ | --- | --- | --- |
283
+ | 1 | 2026-08-18 | **Security & robustness**: random-token RPC auth + single connection; download admission (HTTP(S) only, absolute path, `downloadDir`-confined) with streamed caps (Content-Length early reject, 256 MB max); CDP timeout interrupts and click/type timeout key-release recovery; per-task sessions/allow-lists with agent-lifecycle auto-close; history redaction (typed text, replay/execute args not leaked); popup re-routing into the tab |
284
+ | 2 | 2026-08 | **Feature completion + tests + CI**: window title shows the task; flicker-free showView; snapshots/a11y pierce same-origin iframes & Shadow DOM; new `browser_wait`/`scroll`/`back`/`forward`/`key` tools; real `available()` probe; child-side downloads (temp file + atomic rename); constrained Electron lookup; JPEG/scaled screenshots; snapshot perf; test suite + CI |
285
+ | 3 | 2026-08 | **browser-bridge parity + review fixes**: `browser_a11y` a11y tree; 6 form-control tools (`browser_set_value`/`check`/`select`/`clear`/`get_value`/`refresh`); semantic `target` (css/text/xpath); `browser_scrape` structured extraction; independent BrowserWindow + real toolbar (address bar, back/forward/reload, tab strip) routed back into the session model; tool count **20 → 33**; CI switched to npm (no lockfile → pnpm cache broken), README corrections |
286
+ | 4 | 2026-08 | **DSH 0.1.1-rc.2 alignment + review fixes**: peer floor `^0.1.1-rc.2`; fixed `browser_type` dropping text with a target, `browser_key` Space missing CDP `text`, keyUp failure sticking a key, `browser_wait` same-origin URL mis-match, `.part` rename residue, `snapshotMaxElements`/`contentMaxChars` config wiring, missing type exports; 3 regression tests |
287
+ | 5 | 2026-08 | **Electron 44 compatibility**: `available()` is now side-effect free (no more triggering Electron 44 lazy download); `flushAuth` cookie-domain build fix |
288
+ | 6 | 2026-08 | **Windows handshake & tab lookup**: the Electron GUI process never receives piped stdin → RPC token now flows over **stdin + env var**; `browser_switch_tab`/`browser_close_tab` locate tabs across sessions (`locateTab`), `browser_close_tab` no longer fakes success, unknown ids error with the session's actual tab list |
289
+ | 7 | 2026-08 | **Toolbar interaction (Windows focus routing)**: keyboard input only reaches the focused view and the page view grabbed it, so the address bar could not receive input → added `wireFocusRouting` (clicking a view focuses it) + window refocus restores the last-clicked view; verified with real OS input probes |
290
+ | **0.1.16** | 2026-08-26 | **Release**: all seven rounds ship as **0.1.16** (build clean, 21/21 tests pass, `v0.1.16`) |
291
+
246
292
  ## Acknowledgements
247
293
 
248
294
  Special thanks to the [DeepSeek Harness repository](https://github.com/deepseek-ai/deepseek-harness) and the DeepSeek AI team: the seam, the tool runtime, and the plugin system this plugin builds on all come from that project.
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
  | --- | --- |
19
19
  | 了解插件为什么存在、与无头方案的区别 | [为什么做共享真实浏览器](docs/why-browser.md) |
20
20
  | 安装、配置与日常使用 | [用户指南](docs/user-guide.md) |
21
- | 全部 20 个工具的参数、输出与示例 | [工具参考](docs/tool-reference.md) |
21
+ | 全部 33 个工具的参数、输出与示例 | [工具参考](docs/tool-reference.md) |
22
22
  | 了解 seam / provider / 工具三层与自托管实现 | [架构说明](docs/architecture.md) |
23
23
  | 查看全部文档与 README 分工 | [文档索引](docs/README.md) |
24
24
 
@@ -28,7 +28,7 @@
28
28
 
29
29
  - **真实视图,而非转播**:浏览器是原生 `WebContentsView`,用户直接看到 agent 在做什么,随时可以上手接管;
30
30
  - **装好即用**:有桌面外壳时嵌入外壳视图;纯 `dsh web` 也能**自托管**——插件自己拉起一个 Electron 窗口,不需要任何额外配置;
31
- - **一插件即一套工具**:安装后 agent 自动获得 20 个 `browser_*` 工具(打开、查看、操作、填表、截图、下载、登录态管理……)。
31
+ - **一插件即一套工具**:安装后 agent 自动获得 33 个 `browser_*` 工具(打开、查看、无障碍树、等待、语义/坐标操作、滚动、回退、批量/单控件填表、按键、结构化提取、截图、下载、登录态管理……)。
32
32
 
33
33
  一句话:**安装插件 = 获得一个与用户共享、可被 agent 驱动的真实浏览器。**
34
34
 
@@ -131,13 +131,26 @@ dsh plugin --profile web add <本仓库路径>
131
131
  | 工具 | 用途 | 守卫 |
132
132
  | --- | --- | --- |
133
133
  | `browser_open` | 打开 URL(可选新标签),返回页面快照 | ✅ |
134
- | `browser_snapshot` | 交互元素(输入框/按钮/链接)带编号清单 | – |
134
+ | `browser_wait` | 等待页面加载完成(可选期望 URL / CSS 选择器),返回是否就绪 | – |
135
+ | `browser_snapshot` | 交互元素(输入框/按钮/链接)带编号清单(穿透同源 iframe 与 Shadow DOM) | – |
136
+ | `browser_a11y` | 无障碍树:每个交互节点的语义角色/名称/值/状态 + 坐标(穿透同源 iframe 与 Shadow DOM) | – |
135
137
  | `browser_execute` | 在页面执行 JS;参数以 `arguments[0..n]` 传入 | ✅ |
136
138
  | `browser_content` | 以 html / markdown / txt / json 抓取页面(selector、maxChars、timeoutMs) | – |
137
- | `browser_click` | 按视口坐标点击(配合截图做视觉定位) | ✅ |
138
- | `browser_type` | 向聚焦元素输入文本(CDP `Input.insertText`) | ✅ |
139
+ | `browser_click` | 点击:语义目标(`target`: css/text/xpath,滚动到元素并点中心)或视口坐标(配合截图视觉定位) | ✅ |
140
+ | `browser_type` | 输入文本(可先按 `target` 聚焦元素;CDP `Input.insertText`) | ✅ |
141
+ | `browser_key` | 按命名按键(Enter/Tab/方向键/Home/End 等) | ✅ |
142
+ | `browser_scroll` | 滚动页面(像素增量 / 选择器定位 / 顶部底部) | ✅ |
143
+ | `browser_back` | 页面历史后退一步(无前项时为空操作) | ✅ |
144
+ | `browser_forward` | 页面历史前进一步(无后项时为空操作) | ✅ |
145
+ | `browser_refresh` | 刷新当前页(等价浏览器的刷新按钮) | ✅ |
139
146
  | `browser_fill` | 批量填充表单(选择器/名称/标签匹配,受控输入、下拉、单选/复选,可选提交) | ✅ |
140
- | `browser_screenshot` | PNG 截图,可选 `fullPage` 与 `savePath` | – |
147
+ | `browser_set_value` | 单个控件设值(按 `target` 定位;原生 setter + input/change,React 受控输入可用) | ✅ |
148
+ | `browser_check` | 勾选/取消勾选 checkbox 或 radio(按 `target` 定位) | ✅ |
149
+ | `browser_select` | 选中 `<select>` 的某个选项(按值/文本/索引,按 `target` 定位) | ✅ |
150
+ | `browser_clear` | 清空输入/文本域/contenteditable,或取消勾选(按 `target` 定位) | ✅ |
151
+ | `browser_get_value` | 读取元素当前值(操作后验证用;按 `target` 定位) | – |
152
+ | `browser_scrape` | 结构化提取:容器选择器 + 字段映射(`选择器[@属性]`),静态 CSS 查询、CSP 安全 | – |
153
+ | `browser_screenshot` | 截图,可选 `fullPage`、`savePath`、JPEG(`format`/`quality`)与缩放(`maxWidth`/`maxHeight`) | – |
141
154
  | `browser_list_tabs` | 当前会话的标签列表 | – |
142
155
  | `browser_switch_tab` | 按 id 切换标签(自托管下同步切换可见视图) | ✅ |
143
156
  | `browser_close_tab` | 按 id 关闭标签;关闭活动标签后激活下一个 | – |
@@ -146,13 +159,24 @@ dsh plugin --profile web add <本仓库路径>
146
159
  | `browser_reset_session` | 关闭并重建本任务的浏览器会话 | ✅ |
147
160
  | `browser_history` | 操作日志(最新在后),含成功/失败与结果摘要 | – |
148
161
  | `browser_replay` | 按序号回放某一步(navigate/execute/click/type) | ✅ |
149
- | `browser_download` | 带会话 cookie 下载 URL 到本地文件(上限 256MB) | ✅ |
162
+ | `browser_download` | 带会话 cookie 下载 HTTP(S) URL 到本地文件(`savePath` 必须绝对路径,上限 256MB) | ✅ |
150
163
  | `browser_auth` | 导出/恢复 cookie(登录态持久化,自托管可用) | ✅ |
151
164
  | `browser_challenge` | 检测人机验证(CAPTCHA / Cloudflare / reCAPTCHA / hCaptcha / Turnstile) | – |
152
- | `browser_restrict` | 限制允许的浏览器动作(白名单;空列表解除) | – |
165
+ | `browser_restrict` | 限制允许的浏览器动作(白名单;空列表解除)。**软护栏**,模型可自行解除,非安全边界 | – |
153
166
 
154
167
  > 「守卫」列:打 ✅ 的动作受 `browser_restrict` 白名单约束;只读工具(snapshot/content/screenshot/list_tabs/session/challenge/history)永不拦截。
155
168
 
169
+ ### 等待页面就绪
170
+
171
+ - **`browser_open` 之后、`browser_snapshot` 之前,慢站点请先 `browser_wait`**:传 `url`(你打开的地址)与可选的 `selector`,等它返回 `ready: true` 再拍照——否则拍到的是旧页面或白屏。
172
+ - 页面里看不到的内容先想 iframe / Shadow DOM:快照与无障碍树会穿透同源 iframe 与 shadow root 并标注 `(iframe)`,坐标始终是顶层文档坐标,可直接用 `browser_click`;DOM 选择器则是 frame 作用域的,需用 `browser_execute` 经 `iframe.contentDocument` 访问。
173
+
174
+ ### 语义定位(`target`)与无障碍树
175
+
176
+ - **`browser_a11y` 是理解页面的首选**:它返回每个交互节点的语义角色(button/textbox/checkbox…)、可访问名称、当前值、状态(enabled/checked/expanded…)与坐标,比编号快照更能说明“这是什么、能做什么”;拿到坐标后可直接 `browser_click`/`browser_type`。
177
+ - **`browser_click`/`browser_type` 支持 `target` 定位**:`{by: css|text|xpath, value, index?}`——`text` 按元素自身可见文本匹配(精确优先、退化包含、最深元素优先);点击会把元素滚动到视口中央再点;输入会先聚焦该元素。
178
+ - **单控件操作用 `browser_set_value`/`browser_check`/`browser_select`/`browser_clear`/`browser_get_value`**,批量用 `browser_fill`,列表页结构化抓取用 `browser_scrape`。
179
+
156
180
  ### 操作纪律(点击/填表)
157
181
 
158
182
  - **优先用 DOM 语义而非坐标**:表单提交优先 `form.requestSubmit()`;点击优先 `element.click()`;坐标点击是最后手段。
@@ -171,6 +195,7 @@ dsh plugin --profile web add <本仓库路径>
171
195
  | `browser-electron` | `httpOnly` | 布尔 | `true` | 仅允许 HTTP(S) 导航;其余协议(如 `file:`/`data:`)拒绝(`BROWSER_NAVIGATION_BLOCKED`) |
172
196
  | `browser-electron` | `snapshotMaxElements` | 数字 | `60` | 快照最多收录的交互元素数,超出截断 |
173
197
  | `browser-electron` | `contentMaxChars` | 数字 | `100000` | 内容抓取默认字符上限 |
198
+ | `browser-electron` | `downloadDir` | 字符串 | `~/Downloads` | 限定 `browser_download` 保存路径必须位于该目录内(防 agent 写任意路径);默认收敛到系统下载目录,可改为沙箱目录 |
174
199
  | `tool-browser` | `timeoutMs` | 数字 | `60000` | 工具协作超时(ms) |
175
200
  | `tool-browser` | `tabTools` | 布尔 | `true` | 是否注册标签管理工具(`browser_list_tabs` 等) |
176
201
 
@@ -186,9 +211,11 @@ agent (browser_* 工具)
186
211
 
187
212
  - **seam 层**(`browser` 行)提供 `ctx.browser` 服务:provider 注册、会话生命周期、错误码,与具体实现解耦;
188
213
  - **provider 层**(`browser-electron` 行)通过 `ElectronBrowserViewHost` 接缝操作视图(创建/销毁/显示/`sendCommand`),由真实外壳用 Electron 对象实现;
189
- - **工具层**(`tool-browser` 行)提供模型侧的 20 个 `browser_*` 工具,按调用方任务(DSH 会话)维护独立的浏览器会话。
214
+ - **工具层**(`tool-browser` 行)提供模型侧的 33 个 `browser_*` 工具,按调用方任务(DSH 会话)维护独立的浏览器会话。
215
+
216
+ **自托管模式**:没有桌面外壳时,插件自己拉起一个 Electron 子进程(`host-main.js`),通过本机 TCP JSON-RPC 驱动。RPC 带随机 token 认证,token 经 **stdin + 环境变量双通道**传递——Windows 上 Electron 是 GUI 子系统进程、收不到 piped stdin,环境变量兜底保证握手稳定。子进程崩溃会自动重启;截图优先走 Electron 原生 `capturePage`(CDP 截图在多视图下会挂起),并自动选择环境中**最新版本**的 Electron(33.x 有合成器缺陷,建议 ≥ 40;44+ 首次使用自动下载二进制,需联网)。
190
217
 
191
- **自托管模式**:没有桌面外壳时,插件自己拉起一个 Electron 子进程(`host-main.js`),通过本机 TCP JSON-RPC 驱动,窗口标题 `dsh-browser`。子进程崩溃会自动重启;截图优先走 Electron 原生 `capturePage`(CDP 截图在多视图下会挂起),并自动选择环境中**最新版本**的 Electron(33.x 有合成器缺陷,建议 ≥ 40)。
218
+ **自托管浏览器就是一台真正的浏览器**:每个任务(DSH 会话)拥有**独立的浏览器窗口**,窗口自带完整工具栏——地址栏、后退/前进/刷新按钮、标签条(新建/切换/关闭标签)。人可以直接像用 Chrome 一样使用它:在地址栏输入网址(自动补 `https://`)、点标签切换页面、开新标签;键盘焦点跟随点击——**点地址栏即可输入、点页面即可操作**(Windows 焦点路由,修复了点击不转移焦点导致地址栏无法输入的问题)。agent 与人的操作都汇入**同一个会话模型**(同一套标签、历史与导航),窗口标题实时显示当前任务标识与页面标题/URL,窗口缩放时视图自动跟随。任务结束后窗口随会话自动关闭。
192
219
 
193
220
  **Electron 定位顺序**:① `require('electron')`(peer 依赖)→ ② `ELECTRON_PATH`(显式覆盖)→ ③ DSH 安装锚点与 pnpm 虚拟仓库中**版本最新**者。找不到时工具会报清晰的错误提示。
194
221
 
@@ -205,35 +232,39 @@ agent (browser_* 工具)
205
232
 
206
233
  | 组件 | 版本 |
207
234
  | --- | --- |
208
- | DeepSeek Harness(dsh) | `0.1.0-rc.5` |
209
- | Electron | `43.4.0`(推荐 ≥ 40;33.x 存在合成器缺陷) |
235
+ | DeepSeek Harness(dsh) | `0.1.1-rc.2`(peer 声明 `^0.1.1-rc.2`) |
236
+ | Electron | `44.0.0`(推荐 ≥ 40;33.x 存在合成器缺陷) |
210
237
  | Node.js | `22.20.0` |
211
- | dsh-builtin-browser | `0.1.11` |
238
+ | dsh-builtin-browser | `0.1.16` |
212
239
  | 操作系统 | Windows 10 (10.0.26200) |
213
240
 
214
241
  > 插件声明 `electron >= 30`;**当前仅在 Windows 环境实测**(macOS/Linux 未验证,暂不承诺)。
215
242
 
216
243
  ## 已知限制
217
244
 
218
- - 截图仅 PNG(CDP JPEG 在 Electron 43 上挂起);JPEG 等待非 CDP 转换路径。
245
+ - JPEG 截图仅自托管原生路径可用(`capturePage` 的 `toJPEG`);桌面壳的 CDP 回退路径仍为 PNG(CDP JPEG 在 Electron 43 上挂起)。
219
246
  - 自托管截图优先走 Electron 原生 `capturePage`(CDP `captureScreenshot` 在多视图下会挂起);截图前自动把目标标签置顶。
220
247
  - 部分主机在软件合成下 `fullPage` 截图不稳定。
221
248
  - 人机验证(CAPTCHA)无法自动解决:快照会标注检测到的挑战,此时应请用户在共享窗口中人工完成,而不是反复重试。
222
249
  - 无痕模式(`privateMode`)未实现:它需要 Electron 的 session 分区能力,属于宿主层,本插件不承诺。
223
- - `browser_download` 在页面上下文内 `fetch`(带登录态),受同源/CORS 约束;单文件上限 256MB。
250
+ - `browser_download` 在页面上下文内 `fetch`(带登录态),受同源/CORS 约束;仅允许 HTTP(S) 目标;`savePath` 必须为绝对路径(默认限定在 `~/Downloads`,可用 `downloadDir` 覆盖);单文件上限 256MB(流式限流,按 Content-Length 提前拒绝),文件由浏览器子进程直接落盘(临时文件 + 原子改名)。
251
+ - 自托管浏览器的 cookie 在磁盘上以明文存储(Electron 默认行为);需要加密落盘的部署应在宿主层接入系统钥匙串 / DPAPI。
252
+ - `browser_restrict` 是防误操作的**软护栏**,不是安全边界:模型可以自行解除白名单。
253
+ - 页面弹窗(`window.open` / `target=_blank`)会被重定向到当前标签页内打开,不创建独立窗口,以免破坏标签/会话模型。
224
254
  - `browser_auth` 的 cookie 往返不保留 `hostOnly`/`sameSite` 字段(host-only cookie 恢复后变成 domain cookie);仅自托管浏览器可用。
225
255
  - 自托管浏览器子进程崩溃后会自动重启,但崩溃前已打开的会话视图已失效,调用 `browser_reset_session` 重建即可。
256
+ - Electron 44+ 首次打开浏览器窗口时会自动下载二进制(约 100MB,需网络);之后不再需要。若探测时网络不可用,可预装 `ELECTRON_PATH` 指定的二进制。
226
257
  - 本插件不含浏览器列 UI——那是宿主外壳的配套,别把"浏览器列"当成插件能力。
227
258
 
228
259
  ## 开发
229
260
 
230
261
  ```sh
231
262
  # 类型检查 + 构建(lib/)
232
- pnpm run build
233
-
234
- # 功能测试:启动本地页面服务器 + Electron probe(见仓库测试脚本)
263
+ npm run build
235
264
  ```
236
265
 
266
+ > 运行测试: `npm test`(= `tsc -p tsconfig.json` + `node --test "tests/*.test.mjs"`,假 host 测试,无需 Electron)。
267
+
237
268
  代码结构:
238
269
 
239
270
  | 目录 | 职责 |
@@ -243,6 +274,21 @@ pnpm run build
243
274
  | `src/tool-browser/` | 模型侧 `browser_*` 工具 |
244
275
  | `src/types/` | electron 环境类型(shim,避免强制依赖 electron 类型) |
245
276
 
277
+ ## 更新记录
278
+
279
+ > 按轮次记录的开发与修复历程(完整明细见 [CHANGELOG.md](CHANGELOG.md))。**0.1.16** 起随版本发布(tag `v0.1.16`)。
280
+
281
+ | 轮次 | 日期 | 内容 |
282
+ | --- | --- | --- |
283
+ | 第一轮 | 2026-08-18 | **安全与健壮性修复**:RPC 随机 token 认证 + 单连接强制;下载准入(仅 HTTP(S)、绝对路径、`downloadDir` 限定)与流式限流(Content-Length 提前拒绝,256MB 上限);CDP 超时打断与 click/type 超时松键恢复;会话/白名单改为每任务作用域并随 agent 生命周期自动关闭;操作历史脱敏(输入文本、replay/execute 参数不泄露);弹窗重定向回标签页 |
284
+ | 第二轮 | 2026-08 | **功能补全 + 测试 + CI**:窗口标题显示任务标识、showView 无闪烁;快照/无障碍树穿透同源 iframe 与 Shadow DOM;新增 `browser_wait`/`scroll`/`back`/`forward`/`key` 工具;真实 `available()` 探测;下载改由子进程直接落盘(临时文件 + 原子改名);Electron 定位收敛;JPEG/缩放截图;快照性能优化;新增测试套件与 CI |
285
+ | 第三轮 | 2026-08 | **对标 browser-bridge 的功能 + 审查修复**:`browser_a11y` 无障碍树;表单控件 6 件套(`browser_set_value`/`check`/`select`/`clear`/`get_value`/`refresh`);语义定位 `target`(css/text/xpath);`browser_scrape` 结构化提取;独立 BrowserWindow + 真实工具栏(地址栏/后退/前进/刷新/标签条),工具栏操作路由回会话模型;工具总数 **20 → 33**;CI 改 npm(无 lockfile 不兼容 pnpm cache)、README 修正等审查项 |
286
+ | 第四轮 | 2026-08 | **DSH 0.1.1-rc.2 对齐 + 复查修复**:peer 下限对齐 `^0.1.1-rc.2`;修复 `browser_type` 带 target 丢文本、`browser_key` 空格缺 CDP `text`、keyUp 失败卡键、`browser_wait` URL 同源误匹配、download `.part` rename 残留、`snapshotMaxElements`/`contentMaxChars` 配置接线、导出类型补齐;新增 3 个回归测试 |
287
+ | 第五轮 | 2026-08 | **Electron 44 兼容**:`available()` 改为无副作用探测(不再触发 Electron 44 懒下载);`flushAuth` cookie-domain 构建错误修复 |
288
+ | 第六轮 | 2026-08 | **Windows 握手与标签定位**:Electron GUI 进程收不到 piped stdin → RPC token 改 **stdin + 环境变量双通道**;`browser_switch_tab`/`browser_close_tab` 跨会话按 id 定位(`locateTab`),`browser_close_tab` 不再静默假成功,未知 id 报错附带现有标签列表 |
289
+ | 第七轮 | 2026-08 | **工具栏交互(Windows 焦点路由)**:键盘输入只进有焦点的 view,页面 view 抢占焦点导致地址栏无法输入 → 新增 `wireFocusRouting`(点击即聚焦该 view)+ 窗口 refocus 恢复上次点击的 view;真机 OS 输入探针验证 |
290
+ | **0.1.16** | 2026-08-26 | **发布**:以上七轮全部随 **0.1.16** 发布(构建零错误、21 项测试全绿,`v0.1.16`) |
291
+
246
292
  ## 特别感谢
247
293
 
248
294
  特别感谢 [DeepSeek Harness 原始仓库](https://github.com/deepseek-ai/deepseek-harness) 与 DeepSeek AI 团队:本插件的 seam、工具运行时与插件体系都构建在这个项目之上。
package/docs/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
  | --- | --- |
7
7
  | 了解插件为什么存在、与无头方案的区别 | [为什么做共享真实浏览器](why-browser.md) |
8
8
  | 安装、配置、日常使用与常见问题 | [用户指南](user-guide.md) |
9
- | 全部 20 个工具的参数、输出与示例 | [工具参考](tool-reference.md) |
9
+ | 全部 25 个工具的参数、输出与示例 | [工具参考](tool-reference.md) |
10
10
  | 了解 seam / provider / 工具三层与自托管实现 | [架构说明](architecture.md) |
11
11
  | 回到项目首页 | [README](../README.md) |
12
12
 
@@ -37,11 +37,13 @@ agent (browser_* 工具)
37
37
 
38
38
  ### 工具层(`src/tool-browser/`)
39
39
 
40
- 20 个 `browser_*` 工具,按**调用方任务**(`exec.agent.id`)维护独立浏览器会话:
40
+ 25 个 `browser_*` 工具,按**调用方任务**(`exec.agent.id`)维护独立浏览器会话:
41
41
 
42
42
  - 会话缓存 `sessionsByTask`:同一任务复用同一会话,并发首开去重;
43
43
  - `browser_reset_session` 关闭本任务会话并遗忘映射(即使 close 抛错也清除,下次调用重建);
44
- - `browser_restrict` 维护模块级白名单,守卫所有非只读工具;
44
+ - 会话生命周期绑定 agent 作用域 ctx:agent(DSH 会话)销毁时自动关闭对应浏览器会话,任务结束后不再泄漏窗口/视图;
45
+ - 会话/开启动态/白名单都是**每插件实例(每 context)作用域**的,多 context 互不共享、互不污染;
46
+ - `browser_restrict` 维护本实例白名单,守卫所有非只读工具;
45
47
  - 输出 schema 与返回值严格一致(DSH 运行时会校验,`additionalProperties: false` 下多一个字段都会报错)。
46
48
 
47
49
  ## 自托管实现(纯 `dsh web`)
@@ -57,9 +59,10 @@ RemoteElectronViewHost ──TCP JSON-RPC──▶ host-main.js
57
59
  ```
58
60
 
59
61
  - **协议**:本机 loopback TCP,每行一个 JSON(`{ id, op, ... }` ↔ `{ id, ok, result|err }`);
62
+ - **认证**:每次 spawn 生成随机 token(经 `--rpc-token` 传入),子进程首条消息必须回传该 token(`hello`);服务端只接受**一个**连接,其余连接直接断开——本机其他进程无法伪冒子进程或注入回复;
60
63
  - **Electron 定位**:① peer 依赖 → ② `ELECTRON_PATH` → ③ 锚点与 pnpm store 中**版本最新**者(33.x 有合成器缺陷,建议 ≥ 40);
61
- - **稳健性**:子进程/套接字都有 `error` 监听(否则未捕获事件会炸掉整个 DSH 进程);子进程退出自动重启;物化失败可重试;下载有 256MB 上限与 60s 超时;cookie 导出/恢复有 30s 超时;
62
- - **视图可见性**:多标签/多会话时,`showView` 隐藏其他视图并把目标视图置顶(remove+re-add),确保用户看到的是活动标签;
64
+ - **稳健性**:子进程/套接字都有 `error` 监听(否则未捕获事件会炸掉整个 DSH 进程);子进程退出自动重启;物化失败可重试;下载仅限 HTTP(S)、`savePath` 必须绝对路径(默认限定 `~/Downloads`,可配置 `downloadDir` 覆盖)、流式限流 + Content-Length 提前拒绝、256MB 上限与 60s 超时,文件由**子进程直接落盘**(临时文件 + rename,不再经 RPC 传 base64);cookie 导出/恢复有 30s 超时;
65
+ - **视图可见性**:多标签/多会话时,`showView` 隐藏其他视图并把目标视图置顶(remove+re-add),确保用户看到的是活动标签;目标是当前可见视图时**跳过** remove/re-add(否则每次操作都闪烁);窗口标题实时显示当前可见会话的任务标识 + 页面标题/URL(`showView` 携带会话 label,子进程读 `getTitle`/`getURL`);窗口 resize 时视图 bounds 自动跟随;
63
66
  - **孤儿防护**:父进程断开时子进程自动退出,不留僵尸窗口;
64
67
  - **cookie 落盘**:子进程使用独立 userData 目录(`<DSH_HOME>/dsh-builtin-browser-host`),登录态跨重启保留(另有 `browser_auth` 手动导出/恢复)。
65
68
 
@@ -1,13 +1,15 @@
1
1
  # 工具参考
2
2
 
3
- 全部 20 个 `browser_*` 工具。守卫列:✅ 表示该动作受 `browser_restrict` 白名单约束;只读工具永不拦截。
3
+ 全部 33 个 `browser_*` 工具。守卫列:✅ 表示该动作受 `browser_restrict` 白名单约束;只读工具永不拦截。
4
4
 
5
5
  ## 页面与导航
6
6
 
7
7
  | 工具 | 参数 | 输出 | 守卫 | 说明 |
8
8
  | --- | --- | --- | --- | --- |
9
9
  | `browser_open` | `url`(必填), `newTab?` | 快照(url/title/elements/truncated/challenge) | ✅ | 打开 URL,返回带编号元素的快照;`newTab: true` 在新标签打开 |
10
- | `browser_snapshot` | – | 快照 | – | 交互元素(输入框/按钮/链接)编号清单,供定位与点击 |
10
+ | `browser_wait` | `timeoutMs?`, `url?`, `selector?` | `{ ready, reason }` | – | 等待页面加载完成(可选期望 URL / CSS 选择器);未就绪不抛错,返回原因 |
11
+ | `browser_snapshot` | – | 快照 | – | 交互元素(输入框/按钮/链接)编号清单,供定位与点击;穿透同源 iframe 与 Shadow DOM,iframe 内元素标注 `frame` |
12
+ | `browser_a11y` | `includeHidden?`, `maxNodes?`(10-5000,默认 500) | `{ url, title?, count, nodes[], truncated }` | – | 无障碍树:每个交互节点的 `role`/`name`/`value`/`states`/`depth`/`tag`/坐标;优先 Chrome `computedRole`/`computedName`,穿透同源 iframe 与 Shadow DOM;坐标可直接喂 `browser_click`/`browser_type` |
11
13
  | `browser_content` | `format`(html/markdown/txt/json,必填), `selector?`, `maxChars?`, `timeoutMs?` | `{ content, truncated }` | – | 抓取页面内容;`selector` 限定区域 |
12
14
  | `browser_challenge` | – | `{ blocked, kind?, reason?, hint? }` | – | 检测人机验证(CAPTCHA/Cloudflare/reCAPTCHA/hCaptcha/Turnstile);阻塞时请用户处理 |
13
15
 
@@ -16,9 +18,20 @@
16
18
  | 工具 | 参数 | 输出 | 守卫 | 说明 |
17
19
  | --- | --- | --- | --- | --- |
18
20
  | `browser_execute` | `script`(必填), `args?` | `{ ok, value? / exception? }` | ✅ | 在页面执行 JS;脚本以 `return` 开头或作为表达式;`args` 以 `arguments[0..n]` 传入 |
19
- | `browser_click` | `x`, `y`(必填) | `{ clicked }` | ✅ | 视口坐标点击(配合截图做视觉定位) |
20
- | `browser_type` | `text`(必填) | `{ typed }` | ✅ | 向聚焦元素输入文本(CDP `Input.insertText`) |
21
+ | `browser_click` | `target?`(css/text/xpath), `x?`, `y?`(target 与坐标二选一) | `{ clicked }` | ✅ | 语义目标点击:按 `target` 定位、滚动到视口中央再点中心;或视口坐标点击(配合截图做视觉定位) |
22
+ | `browser_type` | `text`(必填), `target?` | `{ typed }` | ✅ | 输入文本;传 `target` 先聚焦该元素(CDP `Input.insertText`) |
23
+ | `browser_key` | `key`(必填,枚举) | `{ pressed }` | ✅ | 按命名按键:Enter/Tab/Escape/Backspace/Delete/方向键/Home/End/PageUp/PageDown/Space |
24
+ | `browser_scroll` | `deltaX?`, `deltaY?`, `selector?`, `toTop?`, `toBottom?` | `{ scrolled }` | ✅ | 滚动页面:像素增量 / 选择器定位 / 顶部底部 |
25
+ | `browser_back` | – | `{ back }` | ✅ | 页面历史后退一步(无前项时为空操作) |
26
+ | `browser_forward` | – | `{ forward }` | ✅ | 页面历史前进一步(无后项时为空操作) |
27
+ | `browser_refresh` | – | `{ refreshed }` | ✅ | 刷新当前页(等价浏览器刷新按钮) |
21
28
  | `browser_fill` | `fields`(必填,数组), `submit?` | `{ fields[], submitted }` | ✅ | 批量填表;字段按 `selector`/`name`/`label`/`placeholder` 匹配,值支持字符串/数字/布尔;单个字段失败不影响其余;`submit: true` 提交表单 |
29
+ | `browser_set_value` | `target`(必填), `value`(必填) | `{ method, value }` | ✅ | 单个控件设值(按 css/text/xpath 定位);原生 setter + input/change,React 受控输入可用;select 按值/文本 |
30
+ | `browser_check` | `target`(必填), `checked?` | `{ checked }` | ✅ | 勾选/取消勾选 checkbox 或 radio(按 target 定位) |
31
+ | `browser_select` | `target`(必填), `optionValue?`/`optionText?`/`optionIndex?`(三选一) | `{ value, text }` | ✅ | 选中 `<select>` 的某个选项(按 target 定位) |
32
+ | `browser_clear` | `target`(必填) | `{ cleared }` | ✅ | 清空输入/文本域/contenteditable,或取消勾选 checkbox/radio |
33
+ | `browser_get_value` | `target`(必填) | `{ value?, checked?, selectedText? }` | – | 读取元素当前值,用于操作后验证 |
34
+ | `browser_scrape` | `item`(必填), `fields`(必填,映射), `timeoutMs?` | `{ count, items[] }` | – | 结构化提取:容器选择器 + 字段映射(如 `{"title": "h3", "url": "a@href"}`);静态 CSS 查询、不执行任意代码、CSP 安全;等待 item 出现(默认 5s) |
22
35
 
23
36
  ## 标签与会话
24
37
 
@@ -36,27 +49,27 @@
36
49
  | 工具 | 参数 | 输出 | 守卫 | 说明 |
37
50
  | --- | --- | --- | --- | --- |
38
51
  | `browser_history` | – | `{ entries[] }` | – | 操作日志(最新在后),含 seq/action/ok/params/result/error |
39
- | `browser_replay` | `seq`(必填) | `{ replayed }` | ✅ | 按序号回放某一步(navigate/execute/click/type) |
40
- | `browser_download` | `url`(必填), `savePath`(必填) | `{ path }` | ✅ | 带会话 cookie 下载到本地(上限 256MB,受 CORS 约束) |
52
+ | `browser_replay` | `seq`(必填) | `{ replayed }` | ✅ | 按序号回放某一步(navigate/execute/click/type/scroll/key) |
53
+ | `browser_download` | `url`(必填), `savePath`(必填) | `{ path }` | ✅ | 带会话 cookie 下载到本地(仅 http(s);`savePath` 必须为绝对路径,默认限定在 `~/Downloads`,可用 `downloadDir` 覆盖;上限 256MB,受 CORS 约束;由子进程直接落盘) |
41
54
 
42
55
  ## 登录态与安全
43
56
 
44
57
  | 工具 | 参数 | 输出 | 守卫 | 说明 |
45
58
  | --- | --- | --- | --- | --- |
46
59
  | `browser_auth` | `action`(flush/restore,必填), `cookies?` | `{ cookies[]? / restored? }` | ✅ | 导出/恢复 cookie(自托管可用);flush 返回 cookie 列表,restore 带列表写回 |
47
- | `browser_restrict` | `allowed?` | `{ restrictedTo[] }` | – | 设置动作白名单;空列表解除;未知工具名报错 |
60
+ | `browser_restrict` | `allowed?` | `{ restrictedTo[] }` | – | 设置动作白名单;空列表解除;未知工具名报错。**软护栏**——模型可自行解除,非安全边界 |
48
61
 
49
62
  ## 截图
50
63
 
51
64
  | 工具 | 参数 | 输出 | 守卫 | 说明 |
52
65
  | --- | --- | --- | --- | --- |
53
- | `browser_screenshot` | `fullPage?`, `savePath?` | `{ dataUrl, path? }` | – | PNG 截图;`savePath` 落盘供视觉模型读取 |
66
+ | `browser_screenshot` | `fullPage?`, `savePath?`, `format?`(png/jpeg), `quality?`, `maxWidth?`, `maxHeight?` | `{ dataUrl, path? }` | – | 截图;PNG 默认,JPEG 仅自托管原生路径;`maxWidth`/`maxHeight` 等比缩放降低视觉模型开销;`savePath` 落盘供视觉模型读取 |
54
67
 
55
68
  ## 常用组合
56
69
 
57
70
  **调研一个网站**
58
71
  ```
59
- browser_open https://site → browser_content format=markdown → browser_snapshot → 逐页浏览
72
+ browser_open https://site → browser_wait(url=...) → browser_content format=markdown → browser_snapshot → 逐页浏览
60
73
  ```
61
74
 
62
75
  **登录并下载文件**
@@ -33,6 +33,7 @@ dsh plugin --profile web add <本仓库路径>
33
33
  | `browser-electron` | `httpOnly` | 布尔 | `true` | 仅允许 HTTP(S) 导航;`file:`/`data:` 等拒绝 |
34
34
  | `browser-electron` | `snapshotMaxElements` | 数字 | `60` | 快照最多收录的交互元素数 |
35
35
  | `browser-electron` | `contentMaxChars` | 数字 | `100000` | 内容抓取默认字符上限 |
36
+ | `browser-electron` | `downloadDir` | 字符串 | `~/Downloads` | 限定 `browser_download` 保存路径必须位于该目录内;默认收敛到系统下载目录,可改沙箱目录 |
36
37
  | `tool-browser` | `timeoutMs` | 数字 | `60000` | 工具协作超时(ms) |
37
38
  | `tool-browser` | `tabTools` | 布尔 | `true` | 是否注册标签管理工具 |
38
39
 
@@ -40,11 +41,12 @@ dsh plugin --profile web add <本仓库路径>
40
41
 
41
42
  ```
42
43
  1. browser_open 打开 https://example.com
43
- 2. browser_snapshot 查看页面有哪些可交互元素(带编号)
44
+ 2. 慢站点先 browser_wait(url=…) 等页面就绪,再 browser_snapshot 查看可交互元素
44
45
  3. 需要填表时用 browser_fill(按 name/label/placeholder 匹配,一次填多个字段)
45
- 4. 需要截图确认时用 browser_screenshot(可 savePath 存文件)
46
- 5. 遇到验证码(browser_challenge 或快照标注 CHALLENGE)时,停下请用户处理
47
- 6. 每次操作后告知用户你在页面上做了什么
46
+ 4. 需要截图确认时用 browser_screenshot(可 savePath 存文件,大页面用 maxWidth 缩小)
47
+ 5. 需要滚动/回退/按键时用 browser_scroll / browser_back / browser_forward / browser_key
48
+ 6. 遇到验证码(browser_challenge 或快照标注 CHALLENGE)时,停下请用户处理
49
+ 7. 每次操作后告知用户你在页面上做了什么
48
50
  ```
49
51
 
50
52
  ## 操作纪律
@@ -79,10 +81,10 @@ dsh plugin --profile web add <本仓库路径>
79
81
  窗口标题为 `dsh-browser`(自托管)。若子进程崩溃会自动重启;重启后旧会话失效,调用 `browser_reset_session` 重建。
80
82
 
81
83
  **Q:下载报 CORS 错误?**
82
- `browser_download` 在页面上下文内 `fetch`,受同源/CORS 约束;跨域文件请先在同源页面内操作,或直接请求用户提供。
84
+ `browser_download` 在页面上下文内 `fetch`,受同源/CORS 约束;跨域文件请先在同源页面内操作,或直接请求用户提供。仅支持 HTTP(S) URL;`savePath` 必须为绝对路径(配置 `downloadDir` 后限定在该目录内)。
83
85
 
84
86
  **Q:如何禁止 agent 乱点?**
85
- `browser_restrict` 设置白名单(如只允许 `browser_snapshot`/`browser_content`);传空列表解除。
87
+ `browser_restrict` 设置白名单(如只允许 `browser_snapshot`/`browser_content`);传空列表解除。注意它是防误操作的**软护栏**,模型可自行解除,不是安全边界。
86
88
 
87
89
  ## 故障排查
88
90
 
@@ -8,9 +8,9 @@
8
8
  */
9
9
  import { Context, Service } from '@deepseek-ai/cordis';
10
10
  import z from '@deepseek-ai/schemastery';
11
- import type { BrowserClickRequest, BrowserContentRequest, BrowserContentResult, BrowserDownloadRequest, BrowserExecuteRequest, BrowserExecuteResult, BrowserFillRequest, BrowserFillResult, BrowserHistoryEntry, BrowserNavigateRequest, BrowserOpenRequest, BrowserProvider, BrowserScreenshotRequest, BrowserScreenshotResult, BrowserSessionId, BrowserSnapshotResult, BrowserTab, BrowserTypeRequest, BrowserChallenge, ExportedCookie } from './types.ts';
12
- export { BrowserError, } from './types.ts';
13
- export type { BrowserChallenge, BrowserClickRequest, BrowserContentFormat, BrowserContentRequest, BrowserContentResult, BrowserDownloadRequest, BrowserExecuteRequest, BrowserExecuteResult, BrowserFillField, BrowserFillRequest, BrowserFillResult, BrowserHistoryEntry, BrowserNavigateRequest, BrowserOpenRequest, BrowserProvider, BrowserScreenshotRequest, BrowserScreenshotResult, BrowserSessionId, BrowserSnapshotElement, BrowserSnapshotResult, BrowserTab, BrowserTypeRequest, ExportedCookie, } from './types.ts';
11
+ import type { BrowserA11yRequest, BrowserA11yResult, BrowserCheckRequest, BrowserClearRequest, BrowserClickRequest, BrowserContentRequest, BrowserContentResult, BrowserDownloadRequest, BrowserElementTarget, BrowserExecuteRequest, BrowserExecuteResult, BrowserFillRequest, BrowserFillResult, BrowserGetValueRequest, BrowserGetValueResult, BrowserHistoryEntry, BrowserNavigateRequest, BrowserOpenRequest, BrowserProvider, BrowserScrapeRequest, BrowserScrapeResult, BrowserScreenshotRequest, BrowserScreenshotResult, BrowserSelectRequest, BrowserSelectResult, BrowserSessionId, BrowserSetValueRequest, BrowserSetValueResult, BrowserSnapshotResult, BrowserTab, BrowserTypeRequest, BrowserWaitRequest, BrowserWaitResult, BrowserScrollRequest, BrowserKeyRequest, BrowserChallenge, ExportedCookie } from './types.js';
12
+ export { BrowserError, } from './types.js';
13
+ export type { BrowserA11yNode, BrowserA11yRequest, BrowserA11yResult, BrowserChallenge, BrowserCheckRequest, BrowserClearRequest, BrowserClickRequest, BrowserContentFormat, BrowserElementTarget, BrowserGetValueRequest, BrowserGetValueResult, BrowserScrapeField, BrowserScrapeRequest, BrowserScrapeResult, BrowserSelectRequest, BrowserSelectResult, BrowserSetValueRequest, BrowserSetValueResult, BrowserWaitRequest, BrowserWaitResult, BrowserScrollRequest, BrowserKeyRequest, BrowserContentRequest, BrowserContentResult, BrowserDownloadRequest, BrowserExecuteRequest, BrowserExecuteResult, BrowserFillField, BrowserFillRequest, BrowserFillResult, BrowserHistoryEntry, BrowserNavigateRequest, BrowserOpenRequest, BrowserProvider, BrowserScreenshotRequest, BrowserScreenshotResult, BrowserSessionId, BrowserSnapshotElement, BrowserSnapshotResult, BrowserTab, BrowserTypeRequest, ExportedCookie, } from './types.js';
14
14
  declare module '@deepseek-ai/cordis' {
15
15
  interface Context {
16
16
  browser: BrowserRuntime;
@@ -53,7 +53,7 @@ export declare class BrowserRuntime extends Service {
53
53
  /** Resolve the selected provider or throw the matching {@link BrowserError}. */
54
54
  private resolveProvider;
55
55
  /** Open a new browser session through the selected provider. */
56
- open(): Promise<BrowserSessionId>;
56
+ open(label?: string): Promise<BrowserSessionId>;
57
57
  /** Open a URL through the selected provider, optionally in a new tab. */
58
58
  openUrl(session: BrowserSessionId, request: BrowserOpenRequest, signal?: AbortSignal): Promise<void>;
59
59
  /** List the session's tabs through the selected provider. */
@@ -68,16 +68,50 @@ export declare class BrowserRuntime extends Service {
68
68
  navigate(session: BrowserSessionId, request: BrowserNavigateRequest, signal?: AbortSignal): Promise<void>;
69
69
  /** Execute JS in the session's page context through the selected provider. */
70
70
  execute(session: BrowserSessionId, request: BrowserExecuteRequest, signal?: AbortSignal): Promise<BrowserExecuteResult>;
71
+ /** Wait until the session's page is ready (and optional URL/selector match). */
72
+ waitFor(session: BrowserSessionId, request: BrowserWaitRequest, signal?: AbortSignal): Promise<BrowserWaitResult>;
71
73
  /** Produce an AI-friendly snapshot of the session's page. */
72
74
  snapshot(session: BrowserSessionId, signal?: AbortSignal): Promise<BrowserSnapshotResult>;
75
+ /** Read the session's page accessibility tree through the selected provider. */
76
+ a11y(session: BrowserSessionId, request: BrowserA11yRequest, signal?: AbortSignal): Promise<BrowserA11yResult>;
77
+ /** Reload the session's active tab through the selected provider. */
78
+ reload(session: BrowserSessionId, signal?: AbortSignal): Promise<void>;
73
79
  /** Fetch page content in a requested format. */
74
80
  content(session: BrowserSessionId, request: BrowserContentRequest, signal?: AbortSignal): Promise<BrowserContentResult>;
75
- /** Click at viewport coordinates through the selected provider. */
76
- click(session: BrowserSessionId, request: BrowserClickRequest, signal?: AbortSignal): Promise<void>;
77
- /** Type into the focused element through the selected provider. */
78
- type(session: BrowserSessionId, request: BrowserTypeRequest, signal?: AbortSignal): Promise<void>;
81
+ /** Click at viewport coordinates (or a located element) through the selected provider. */
82
+ click(session: BrowserSessionId, request: BrowserClickRequest | {
83
+ readonly target: BrowserElementTarget;
84
+ }, signal?: AbortSignal): Promise<void>;
85
+ /** Type into the focused element (or a located one) through the selected provider. */
86
+ type(session: BrowserSessionId, request: BrowserTypeRequest | {
87
+ readonly target: BrowserElementTarget;
88
+ }, signal?: AbortSignal): Promise<void>;
89
+ /** Scroll the session's page through the selected provider. */
90
+ scroll(session: BrowserSessionId, request: BrowserScrollRequest, signal?: AbortSignal): Promise<void>;
91
+ /** Go back in the session's page history through the selected provider. */
92
+ back(session: BrowserSessionId, signal?: AbortSignal): Promise<void>;
93
+ /** Go forward in the session's page history through the selected provider. */
94
+ forward(session: BrowserSessionId, signal?: AbortSignal): Promise<void>;
95
+ /** Press one named key through the selected provider. */
96
+ key(session: BrowserSessionId, request: BrowserKeyRequest, signal?: AbortSignal): Promise<void>;
79
97
  /** Fill a form's fields in one batch through the selected provider. */
80
98
  fillForm(session: BrowserSessionId, request: BrowserFillRequest, signal?: AbortSignal): Promise<BrowserFillResult>;
99
+ /** Set one element's value through the selected provider. */
100
+ setValue(session: BrowserSessionId, request: BrowserSetValueRequest, signal?: AbortSignal): Promise<BrowserSetValueResult>;
101
+ /** Check or uncheck one checkbox/radio through the selected provider. */
102
+ check(session: BrowserSessionId, request: BrowserCheckRequest, signal?: AbortSignal): Promise<{
103
+ readonly checked: boolean;
104
+ }>;
105
+ /** Select one option of a `<select>` through the selected provider. */
106
+ selectOption(session: BrowserSessionId, request: BrowserSelectRequest, signal?: AbortSignal): Promise<BrowserSelectResult>;
107
+ /** Clear one element through the selected provider. */
108
+ clearField(session: BrowserSessionId, request: BrowserClearRequest, signal?: AbortSignal): Promise<{
109
+ readonly cleared: boolean;
110
+ }>;
111
+ /** Read one element's value through the selected provider. */
112
+ getValue(session: BrowserSessionId, request: BrowserGetValueRequest, signal?: AbortSignal): Promise<BrowserGetValueResult>;
113
+ /** Extract structured data through the selected provider. */
114
+ scrape(session: BrowserSessionId, request: BrowserScrapeRequest, signal?: AbortSignal): Promise<BrowserScrapeResult>;
81
115
  /** Capture the current page through the selected provider. */
82
116
  screenshot(session: BrowserSessionId, request?: BrowserScreenshotRequest, signal?: AbortSignal): Promise<BrowserScreenshotResult>;
83
117
  /** Check for a human-verification challenge on the active tab. */