@angelky7/dsh-browser-control 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +5 -4
- package/README.md +5 -4
- package/lib/index.js +72 -0
- package/lib/types/index.js +53 -0
- package/llms.txt +1 -1
- package/package.json +2 -2
package/README.en.md
CHANGED
|
@@ -64,8 +64,8 @@ In one line: **for "use MY browser" tasks (logged-in Bilibili, Zhihu, admin pane
|
|
|
64
64
|
|
|
65
65
|
| File | Purpose |
|
|
66
66
|
|---|---|
|
|
67
|
-
| [DSH-Browser-Control-v1.
|
|
68
|
-
| [dsh-browser-control-plugin-v1.
|
|
67
|
+
| [DSH-Browser-Control-v1.5.0.zip](https://github.com/angelky7/dsh-browser-control/releases/download/v1.5.0/DSH-Browser-Control-v1.5.0.zip) | Chrome extension (unzip and load; this release adds the archive command — reinstall required) |
|
|
68
|
+
| [dsh-browser-control-plugin-v1.5.0.zip](https://github.com/angelky7/dsh-browser-control/releases/download/v1.5.0/dsh-browser-control-plugin-v1.5.0.zip) | dsh plugin (offline fallback; online installs use Option A/B) |
|
|
69
69
|
|
|
70
70
|
## Install the Chrome extension (30 seconds)
|
|
71
71
|
|
|
@@ -104,7 +104,7 @@ npm install @angelky7/dsh-browser-control
|
|
|
104
104
|
|
|
105
105
|
```bash
|
|
106
106
|
# Straight from GitHub
|
|
107
|
-
dsh plugin --profile web add "github:angelky7/dsh-browser-control#v1.
|
|
107
|
+
dsh plugin --profile web add "github:angelky7/dsh-browser-control#v1.5.0"
|
|
108
108
|
|
|
109
109
|
# Local checkout for debugging (note: the explicit file: prefix is required)
|
|
110
110
|
dsh plugin --profile web add "file:D:\path\to\dsh-browser-control"
|
|
@@ -263,11 +263,12 @@ curl -X POST http://127.0.0.1:9777/api/command \
|
|
|
263
263
|
| `browser_network_log` | Captured HTTP requests, listed with method/status/URL (v1.0.7+) |
|
|
264
264
|
| `browser_network_clear` | Clear the network log buffer on its own (v1.0.7+) |
|
|
265
265
|
| `browser_pdf` | Export the current page as PDF (v1.0.7+) |
|
|
266
|
+
| `browser_archive` | Save the whole page as a single-file MHTML (text/images/styles inline; a greppable offline evidence snapshot — embedded scripts do not run, v1.5.0+) |
|
|
266
267
|
| `browser_emulate` | Switch to a device viewport (mobile / desktop / custom, v1.0.7+) |
|
|
267
268
|
| `browser_cleanup` | Clean up temp files |
|
|
268
269
|
| `browser_info` | Which browser and extension build is attached (run it first when debugging, v1.0.9+) |
|
|
269
270
|
|
|
270
|
-
|
|
271
|
+
23 tools in total. `browser_wait`, `browser_find`, `browser_dialog` and `browser_info` existed inside the extension up to v1.0.8 but were never registered as tools, so the agent could not reach them; `browser_network_clear` existed in code and was simply missing from this table. `verify/verify_surface.mjs` now reconciles the extension command table, the commands the plugin calls, this table and the built bundle, so any drift fails `npm run verify`.
|
|
271
272
|
|
|
272
273
|
## Architecture
|
|
273
274
|
|
package/README.md
CHANGED
|
@@ -64,8 +64,8 @@ Chrome 扩展(CDP 驱动)
|
|
|
64
64
|
|
|
65
65
|
| 文件 | 说明 |
|
|
66
66
|
|---|---|
|
|
67
|
-
| [DSH-Browser-Control-v1.
|
|
68
|
-
| [dsh-browser-control-plugin-v1.
|
|
67
|
+
| [DSH-Browser-Control-v1.5.0.zip](https://github.com/angelky7/dsh-browser-control/releases/download/v1.5.0/DSH-Browser-Control-v1.5.0.zip) | Chrome 扩展(解压后加载;这一版扩展新增 archive 存档命令,**必须重装**) |
|
|
68
|
+
| [dsh-browser-control-plugin-v1.5.0.zip](https://github.com/angelky7/dsh-browser-control/releases/download/v1.5.0/dsh-browser-control-plugin-v1.5.0.zip) | dsh 插件(离线兜底,在线装直接用方式 A/B) |
|
|
69
69
|
|
|
70
70
|
## 安装 Chrome 扩展(30 秒)
|
|
71
71
|
|
|
@@ -104,7 +104,7 @@ npm install @angelky7/dsh-browser-control
|
|
|
104
104
|
|
|
105
105
|
```bash
|
|
106
106
|
# 直接从 GitHub 安装
|
|
107
|
-
dsh plugin --profile web add "github:angelky7/dsh-browser-control#v1.
|
|
107
|
+
dsh plugin --profile web add "github:angelky7/dsh-browser-control#v1.5.0"
|
|
108
108
|
|
|
109
109
|
# 本地目录调试(注意:必须显式 file: 前缀)
|
|
110
110
|
dsh plugin --profile web add "file:D:\path\to\dsh-browser-control"
|
|
@@ -257,11 +257,12 @@ curl -X POST http://127.0.0.1:9777/api/command \
|
|
|
257
257
|
| `browser_network_log` | 抓取 HTTP 请求/响应并逐条给出方法/状态/URL(v1.0.7+) |
|
|
258
258
|
| `browser_network_clear` | 单独清空网络日志缓冲(v1.0.7+) |
|
|
259
259
|
| `browser_pdf` | 当前页导出 PDF(v1.0.7+) |
|
|
260
|
+
| `browser_archive` | 整页存成单文件 MHTML(正文/图片/样式全内联,可当文本 grep 的离线证据;是快照,内嵌脚本不会跑,v1.5.0+) |
|
|
260
261
|
| `browser_emulate` | 切设备视口(移动 / 桌面 / 自定义,v1.0.7+) |
|
|
261
262
|
| `browser_cleanup` | 清理临时文件 |
|
|
262
263
|
| `browser_info` | 报告实际连上的浏览器与扩展版本(排障先跑它,v1.0.9+) |
|
|
263
264
|
|
|
264
|
-
共
|
|
265
|
+
共 23 个工具。`browser_wait` / `browser_find` / `browser_dialog` / `browser_info` 在 v1.0.8 及以前是「扩展里已实现、但没注册成工具」,agent 调不到;`browser_network_clear` 则是代码里有、这张表漏了。这些现在由 `verify/verify_surface.mjs` 做三方对账(扩展命令表 ↔ 插件调用 ↔ 文档表 ↔ 构建产物),漂移会直接让 `npm run verify` 失败。
|
|
265
266
|
|
|
266
267
|
## 架构
|
|
267
268
|
|
package/lib/index.js
CHANGED
|
@@ -1238,6 +1238,30 @@ async function savePdf(controller, payload, requestedPath) {
|
|
|
1238
1238
|
...payload.tabUrl === void 0 ? {} : { url: payload.tabUrl }
|
|
1239
1239
|
};
|
|
1240
1240
|
}
|
|
1241
|
+
/** Write one MHTML archive payload to `path` (absolute or under `controller.shotsDir`).
|
|
1242
|
+
* Mirrors savePdf's contract; MHTML is text, so it is written verbatim as UTF-8. */
|
|
1243
|
+
async function saveMhtml(controller, payload, requestedPath) {
|
|
1244
|
+
const dir = controller.shotsDir;
|
|
1245
|
+
if (dir === void 0) throw new Error("browser-bridge is not configured yet");
|
|
1246
|
+
let file;
|
|
1247
|
+
if (requestedPath && path.isAbsolute(requestedPath)) {
|
|
1248
|
+
file = requestedPath;
|
|
1249
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
1250
|
+
} else {
|
|
1251
|
+
await mkdir(dir, { recursive: true });
|
|
1252
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
1253
|
+
const random = Math.random().toString(36).slice(2, 6);
|
|
1254
|
+
const name = requestedPath ? path.basename(requestedPath) : `${stamp}-${random}.mhtml`;
|
|
1255
|
+
file = path.join(dir, name);
|
|
1256
|
+
}
|
|
1257
|
+
const buffer = Buffer.from(payload.mhtml, "utf8");
|
|
1258
|
+
await writeFile(file, buffer);
|
|
1259
|
+
return {
|
|
1260
|
+
file,
|
|
1261
|
+
bytes: buffer.length,
|
|
1262
|
+
tabId: payload.tabId
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1241
1265
|
/** Register every `browser_*` tool; each is a thin adapter over one extension command. */
|
|
1242
1266
|
function applyBrowserTools(ctx, controller) {
|
|
1243
1267
|
ctx.systemPrompt.section({
|
|
@@ -2590,6 +2614,54 @@ function applyBrowserTools(ctx, controller) {
|
|
|
2590
2614
|
return await savePdf(controller, await controller.execute("pdf", params, exec.signal), typeof args.path === "string" ? args.path : void 0);
|
|
2591
2615
|
}
|
|
2592
2616
|
}));
|
|
2617
|
+
ctx.tools.register(defineTool({
|
|
2618
|
+
name: "browser_archive",
|
|
2619
|
+
description: "Save the whole page as a single-file MHTML archive (text, images and styles inline; no network needed to reopen it). Returns the absolute path. The archive is a snapshot of when it was taken — scripts inside do not run and later page changes are not reflected; read it back with file tools or grep it like text.",
|
|
2620
|
+
parameters: {
|
|
2621
|
+
tabId: {
|
|
2622
|
+
type: "number",
|
|
2623
|
+
description: "Target tab; defaults to the active tab."
|
|
2624
|
+
},
|
|
2625
|
+
path: {
|
|
2626
|
+
type: "string",
|
|
2627
|
+
description: "Absolute path. Omit to save under the configured shotsDir with a timestamped .mhtml name."
|
|
2628
|
+
}
|
|
2629
|
+
},
|
|
2630
|
+
output: {
|
|
2631
|
+
schema: {
|
|
2632
|
+
type: "object",
|
|
2633
|
+
additionalProperties: false,
|
|
2634
|
+
properties: {
|
|
2635
|
+
file: {
|
|
2636
|
+
type: "string",
|
|
2637
|
+
required: true
|
|
2638
|
+
},
|
|
2639
|
+
bytes: {
|
|
2640
|
+
type: "number",
|
|
2641
|
+
required: true
|
|
2642
|
+
},
|
|
2643
|
+
tabId: {
|
|
2644
|
+
type: "number",
|
|
2645
|
+
required: true
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
},
|
|
2649
|
+
render: (_args, value) => [{
|
|
2650
|
+
type: "text",
|
|
2651
|
+
text: `MHTML archive written to ${value.file} (${(value.bytes / 1024).toFixed(1)} KB)`
|
|
2652
|
+
}]
|
|
2653
|
+
},
|
|
2654
|
+
presentCall: (args) => ({
|
|
2655
|
+
card: "generic",
|
|
2656
|
+
title: `Save page archive${args.path ? " → " + args.path : ""}`,
|
|
2657
|
+
kind: "other"
|
|
2658
|
+
}),
|
|
2659
|
+
async execute(args, exec) {
|
|
2660
|
+
const params = {};
|
|
2661
|
+
if (args.tabId !== void 0) params.tabId = args.tabId;
|
|
2662
|
+
return await saveMhtml(controller, await controller.execute("archive", params, exec.signal), typeof args.path === "string" ? args.path : void 0);
|
|
2663
|
+
}
|
|
2664
|
+
}));
|
|
2593
2665
|
ctx.tools.register(defineTool({
|
|
2594
2666
|
name: "browser_emulate",
|
|
2595
2667
|
description: "Switch the tab into a device viewport (mobile / tablet / desktop) for responsive-UI testing. `device:\"reset\"` restores the user's actual viewport. Custom `width`/`height`/`deviceScaleFactor`/`isMobile`/`hasTouch` override any preset field.",
|
package/lib/types/index.js
CHANGED
|
@@ -351,6 +351,28 @@ async function savePdf(controller, payload, requestedPath) {
|
|
|
351
351
|
...(payload.tabUrl === undefined ? {} : { url: payload.tabUrl }),
|
|
352
352
|
};
|
|
353
353
|
}
|
|
354
|
+
/** Write one MHTML archive payload to `path` (absolute or under `controller.shotsDir`).
|
|
355
|
+
* Mirrors savePdf's contract; MHTML is text, so it is written verbatim as UTF-8. */
|
|
356
|
+
async function saveMhtml(controller, payload, requestedPath) {
|
|
357
|
+
const dir = controller.shotsDir;
|
|
358
|
+
if (dir === undefined)
|
|
359
|
+
throw new Error('browser-bridge is not configured yet');
|
|
360
|
+
let file;
|
|
361
|
+
if (requestedPath && path.isAbsolute(requestedPath)) {
|
|
362
|
+
file = requestedPath;
|
|
363
|
+
await mkdir(path.dirname(file), { recursive: true });
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
await mkdir(dir, { recursive: true });
|
|
367
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
368
|
+
const random = Math.random().toString(36).slice(2, 6);
|
|
369
|
+
const name = requestedPath ? path.basename(requestedPath) : `${stamp}-${random}.mhtml`;
|
|
370
|
+
file = path.join(dir, name);
|
|
371
|
+
}
|
|
372
|
+
const buffer = Buffer.from(payload.mhtml, 'utf8');
|
|
373
|
+
await writeFile(file, buffer);
|
|
374
|
+
return { file, bytes: buffer.length, tabId: payload.tabId };
|
|
375
|
+
}
|
|
354
376
|
/** Register every `browser_*` tool; each is a thin adapter over one extension command. */
|
|
355
377
|
function applyBrowserTools(ctx, controller) {
|
|
356
378
|
ctx.systemPrompt.section({
|
|
@@ -1251,6 +1273,37 @@ function applyBrowserTools(ctx, controller) {
|
|
|
1251
1273
|
return await savePdf(controller, payload, typeof args.path === 'string' ? args.path : undefined);
|
|
1252
1274
|
},
|
|
1253
1275
|
}));
|
|
1276
|
+
ctx.tools.register(defineTool({
|
|
1277
|
+
name: 'browser_archive',
|
|
1278
|
+
description: 'Save the whole page as a single-file MHTML archive (text, images and styles inline; no network needed to reopen it). Returns the absolute path. The archive is a snapshot of when it was taken — scripts inside do not run and later page changes are not reflected; read it back with file tools or grep it like text.',
|
|
1279
|
+
parameters: {
|
|
1280
|
+
tabId: { type: 'number', description: 'Target tab; defaults to the active tab.' },
|
|
1281
|
+
path: { type: 'string', description: 'Absolute path. Omit to save under the configured shotsDir with a timestamped .mhtml name.' },
|
|
1282
|
+
},
|
|
1283
|
+
output: {
|
|
1284
|
+
schema: {
|
|
1285
|
+
type: 'object',
|
|
1286
|
+
additionalProperties: false,
|
|
1287
|
+
properties: {
|
|
1288
|
+
file: { type: 'string', required: true },
|
|
1289
|
+
bytes: { type: 'number', required: true },
|
|
1290
|
+
tabId: { type: 'number', required: true },
|
|
1291
|
+
},
|
|
1292
|
+
},
|
|
1293
|
+
render: (_args, value) => [{
|
|
1294
|
+
type: 'text',
|
|
1295
|
+
text: `MHTML archive written to ${value.file} (${(value.bytes / 1024).toFixed(1)} KB)`,
|
|
1296
|
+
}],
|
|
1297
|
+
},
|
|
1298
|
+
presentCall: args => ({ card: 'generic', title: `Save page archive${args.path ? ' → ' + args.path : ''}`, kind: 'other' }),
|
|
1299
|
+
async execute(args, exec) {
|
|
1300
|
+
const params = {};
|
|
1301
|
+
if (args.tabId !== undefined)
|
|
1302
|
+
params.tabId = args.tabId;
|
|
1303
|
+
const payload = await controller.execute('archive', params, exec.signal);
|
|
1304
|
+
return await saveMhtml(controller, payload, typeof args.path === 'string' ? args.path : undefined);
|
|
1305
|
+
},
|
|
1306
|
+
}));
|
|
1254
1307
|
ctx.tools.register(defineTool({
|
|
1255
1308
|
name: 'browser_emulate',
|
|
1256
1309
|
description: 'Switch the tab into a device viewport (mobile / tablet / desktop) for responsive-UI testing. `device:"reset"` restores the user\'s actual viewport. Custom `width`/`height`/`deviceScaleFactor`/`isMobile`/`hasTouch` override any preset field.',
|
package/llms.txt
CHANGED
|
@@ -35,7 +35,7 @@ Chrome 浏览器扩展 + DeepSeek Harness 插件,让 AI Agent 通过 CDP 驱
|
|
|
35
35
|
|
|
36
36
|
## 关键文件
|
|
37
37
|
|
|
38
|
-
- `src/index.ts` -
|
|
38
|
+
- `src/index.ts` - 23 个 `browser_*` 工具注册 + settings 接线的版本兼容分支
|
|
39
39
|
- `src/policy.ts` - 三张互不相同的表:只读白名单、脚本命令、插件本地破坏性命令(`browser_cleanup` 不经过 `execute()`,必须自己站闸门)
|
|
40
40
|
- `src/server.ts` - WebSocket 服务 + HTTP 面(鉴权、来源校验、状态页)
|
|
41
41
|
- `src/ws.ts` - RFC 6455 帧编解码
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@angelky7/dsh-browser-control",
|
|
3
3
|
"description": "Local WebSocket bridge between dsh and the DSH Browser Control browser extension, with model-facing browser_* tools and a Settings-managed on/off switch",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.5.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"build": "tsc -p tsconfig.json && tsdown",
|
|
38
38
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
39
39
|
"check": "npm run typecheck && npm run build && publint",
|
|
40
|
-
"verify": "node verify/verify_config_patch.mjs && node verify/verify_peers.mjs && node verify/verify_surface.mjs && node verify/verify_tab_groups.mjs && node verify/verify_tabs_close_many.mjs && node verify/verify_cdp_retry.mjs && node verify/verify_snapshot_ax.mjs && node verify/verify_hard_lock.mjs && node verify/verify_policy.mjs && node verify/verify_cookies.mjs && node verify/verify_extension_e2e.mjs && node verify/verify_plugin.mjs legacy && node verify/verify_plugin.mjs conflict && node verify/verify_plugin.mjs new && node verify/verify_http.mjs && node verify/verify_errors.mjs && node verify/verify_hold.mjs && node verify/verify_protocol.mjs && node verify/verify_bundle_http.mjs && node verify/verify_nav_report.mjs && node verify/verify_nav_landing.mjs && node verify/verify_render_contract.mjs && node verify/verify_press_events.mjs && node verify/verify_nav_timeout.mjs && node verify/verify_nav_waituntil.mjs"
|
|
40
|
+
"verify": "node verify/verify_config_patch.mjs && node verify/verify_peers.mjs && node verify/verify_surface.mjs && node verify/verify_tab_groups.mjs && node verify/verify_tabs_close_many.mjs && node verify/verify_cdp_retry.mjs && node verify/verify_snapshot_ax.mjs && node verify/verify_hard_lock.mjs && node verify/verify_policy.mjs && node verify/verify_cookies.mjs && node verify/verify_extension_e2e.mjs && node verify/verify_plugin.mjs legacy && node verify/verify_plugin.mjs conflict && node verify/verify_plugin.mjs new && node verify/verify_http.mjs && node verify/verify_errors.mjs && node verify/verify_hold.mjs && node verify/verify_protocol.mjs && node verify/verify_bundle_http.mjs && node verify/verify_nav_report.mjs && node verify/verify_nav_landing.mjs && node verify/verify_render_contract.mjs && node verify/verify_press_events.mjs && node verify/verify_nav_timeout.mjs && node verify/verify_nav_waituntil.mjs && node verify/verify_archive.mjs"
|
|
41
41
|
},
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public",
|