@angelky7/dsh-browser-control 1.3.0 → 1.4.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 +3 -3
- package/README.md +3 -3
- package/lib/index.js +53 -8
- package/lib/types/index.js +49 -9
- 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.4.0.zip](https://github.com/angelky7/dsh-browser-control/releases/download/v1.4.0/DSH-Browser-Control-v1.4.0.zip) | Chrome extension (unzip and load; navigation waiting is now event-driven — reinstall required) |
|
|
68
|
+
| [dsh-browser-control-plugin-v1.4.0.zip](https://github.com/angelky7/dsh-browser-control/releases/download/v1.4.0/dsh-browser-control-plugin-v1.4.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.4.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"
|
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.4.0.zip](https://github.com/angelky7/dsh-browser-control/releases/download/v1.4.0/DSH-Browser-Control-v1.4.0.zip) | Chrome 扩展(解压后加载;这一版扩展的导航等待换成事件驱动,**必须重装**) |
|
|
68
|
+
| [dsh-browser-control-plugin-v1.4.0.zip](https://github.com/angelky7/dsh-browser-control/releases/download/v1.4.0/dsh-browser-control-plugin-v1.4.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.4.0"
|
|
108
108
|
|
|
109
109
|
# 本地目录调试(注意:必须显式 file: 前缀)
|
|
110
110
|
dsh plugin --profile web add "file:D:\path\to\dsh-browser-control"
|
package/lib/index.js
CHANGED
|
@@ -942,6 +942,18 @@ function policyRefusal(command, flags, params) {
|
|
|
942
942
|
/** Navigation wait ceiling (same as browser_wait); the bridge gets this plus BRIDGE_MARGIN_MS. */
|
|
943
943
|
const NAV_MAX_MS = 12e4;
|
|
944
944
|
/**
|
|
945
|
+
* Same tier table as the extension's NAV_TIERS (extension/background.js). Both
|
|
946
|
+
* literals must stay in lockstep — verify_nav_waituntil.mjs extracts the
|
|
947
|
+
* extension's by regex and asserts the two agree (cross-side literals are wire
|
|
948
|
+
* protocol: a value only one side knows is a lie on the other).
|
|
949
|
+
*/
|
|
950
|
+
const NAV_TIERS = {
|
|
951
|
+
none: 0,
|
|
952
|
+
commit: 4e3,
|
|
953
|
+
domcontentloaded: 4e3,
|
|
954
|
+
load: 15e3
|
|
955
|
+
};
|
|
956
|
+
/**
|
|
945
957
|
* Extra time handed to the bridge on top of the navigation budget. Without it a
|
|
946
958
|
* caller-requested budget longer than the bridge default (60s) would be beaten by
|
|
947
959
|
* the bridge, and the model would get `command_timeout` (effect unknown) instead
|
|
@@ -1246,9 +1258,13 @@ function applyBrowserTools(ctx, controller) {
|
|
|
1246
1258
|
type: "number",
|
|
1247
1259
|
description: "Target tab; defaults to the active tab."
|
|
1248
1260
|
},
|
|
1261
|
+
waitUntil: {
|
|
1262
|
+
type: "string",
|
|
1263
|
+
description: "When the navigation counts as arrived: \"load\" (default) waits for the page load event (15s budget), \"domcontentloaded\" returns once the DOM is parsed (async components may still be mounting — follow with browser_wait if an element is missing), \"commit\" once the new document's address lands, \"none\" returns immediately with no arrival check at all."
|
|
1264
|
+
},
|
|
1249
1265
|
timeoutMs: {
|
|
1250
1266
|
type: "number",
|
|
1251
|
-
description: `How long to wait for the page load to settle: defaults to 15000, capped at ${NAV_MAX_MS}.
|
|
1267
|
+
description: `How long to wait for the page load to settle: defaults to the waitUntil tier (load 15000, commit/domcontentloaded 4000), capped at ${NAV_MAX_MS}. An explicit value always wins over the tier.`
|
|
1252
1268
|
}
|
|
1253
1269
|
},
|
|
1254
1270
|
output: {
|
|
@@ -1264,7 +1280,10 @@ function applyBrowserTools(ctx, controller) {
|
|
|
1264
1280
|
title: { type: "string" },
|
|
1265
1281
|
pendingUrl: { type: "string" },
|
|
1266
1282
|
loadPending: { type: "boolean" },
|
|
1267
|
-
siteUnreachable: { type: "boolean" }
|
|
1283
|
+
siteUnreachable: { type: "boolean" },
|
|
1284
|
+
waitUntil: { type: "string" },
|
|
1285
|
+
waitedMs: { type: "number" },
|
|
1286
|
+
readyState: { type: "string" }
|
|
1268
1287
|
}
|
|
1269
1288
|
},
|
|
1270
1289
|
render: (_args, value) => {
|
|
@@ -1298,17 +1317,26 @@ function applyBrowserTools(ctx, controller) {
|
|
|
1298
1317
|
kind: "other"
|
|
1299
1318
|
}),
|
|
1300
1319
|
async execute(args, exec) {
|
|
1301
|
-
const
|
|
1320
|
+
const waitUntil = args.waitUntil === void 0 ? "load" : String(args.waitUntil);
|
|
1321
|
+
if (!(waitUntil in NAV_TIERS)) throw new Error(`browser_navigate: waitUntil must be one of none, commit, domcontentloaded, load — got "${waitUntil}"`);
|
|
1322
|
+
const params = {
|
|
1323
|
+
url: args.url,
|
|
1324
|
+
waitUntil
|
|
1325
|
+
};
|
|
1302
1326
|
if (args.tabId !== void 0) params.tabId = args.tabId;
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1327
|
+
let budgetMs = NAV_TIERS[waitUntil];
|
|
1328
|
+
if (typeof args.timeoutMs === "number" && Number.isFinite(args.timeoutMs)) budgetMs = Math.min(NAV_MAX_MS, Math.max(100, Math.trunc(args.timeoutMs)));
|
|
1329
|
+
if (waitUntil !== "none") params.timeoutMs = budgetMs;
|
|
1330
|
+
const raw = await controller.execute("nav", params, exec.signal, waitUntil === "none" ? void 0 : budgetMs + BRIDGE_MARGIN_MS);
|
|
1306
1331
|
const out = { tabId: raw.tabId };
|
|
1307
1332
|
if (typeof raw.url === "string") out.url = raw.url;
|
|
1308
1333
|
if (typeof raw.title === "string") out.title = raw.title;
|
|
1309
1334
|
if (typeof raw.pendingUrl === "string") out.pendingUrl = raw.pendingUrl;
|
|
1310
1335
|
if (raw.loadPending === true) out.loadPending = true;
|
|
1311
1336
|
if (raw.siteUnreachable === true || typeof raw.siteUnreachable === "object" && raw.siteUnreachable !== null) out.siteUnreachable = true;
|
|
1337
|
+
if (typeof raw.waitUntil === "string") out.waitUntil = raw.waitUntil;
|
|
1338
|
+
if (typeof raw.waitedMs === "number") out.waitedMs = raw.waitedMs;
|
|
1339
|
+
if (typeof raw.readyState === "string" && raw.readyState.length > 0) out.readyState = raw.readyState;
|
|
1312
1340
|
return out;
|
|
1313
1341
|
}
|
|
1314
1342
|
}));
|
|
@@ -2011,6 +2039,14 @@ function applyBrowserTools(ctx, controller) {
|
|
|
2011
2039
|
type: "boolean",
|
|
2012
2040
|
description: "For open: put the new tab in the DSH tab group. Defaults to true; pass false to keep it loose."
|
|
2013
2041
|
},
|
|
2042
|
+
waitUntil: {
|
|
2043
|
+
type: "string",
|
|
2044
|
+
description: "For open: when the new tab counts as arrived — same tiers as browser_navigate (load default / domcontentloaded / commit / none)."
|
|
2045
|
+
},
|
|
2046
|
+
timeoutMs: {
|
|
2047
|
+
type: "number",
|
|
2048
|
+
description: "For open: wait budget, overriding the waitUntil tier (load 15000, commit/domcontentloaded 4000)."
|
|
2049
|
+
},
|
|
2014
2050
|
windowId: {
|
|
2015
2051
|
type: "number",
|
|
2016
2052
|
description: "For ungroup: only dissolve that window’s DSH group; omit to clear every window."
|
|
@@ -2036,10 +2072,19 @@ function applyBrowserTools(ctx, controller) {
|
|
|
2036
2072
|
case "list": return await controller.execute("tabs.list", {}, exec.signal);
|
|
2037
2073
|
case "open": {
|
|
2038
2074
|
if (typeof args.url !== "string" || args.url.length === 0) throw new Error("open requires url");
|
|
2039
|
-
const
|
|
2075
|
+
const waitUntil = args.waitUntil === void 0 ? "load" : String(args.waitUntil);
|
|
2076
|
+
if (!(waitUntil in NAV_TIERS)) throw new Error(`browser_tabs open: waitUntil must be one of none, commit, domcontentloaded, load — got "${waitUntil}"`);
|
|
2077
|
+
const params = {
|
|
2078
|
+
url: args.url,
|
|
2079
|
+
waitUntil
|
|
2080
|
+
};
|
|
2040
2081
|
if (args.active !== void 0) params.active = args.active;
|
|
2041
2082
|
if (args.group !== void 0) params.group = args.group;
|
|
2042
|
-
return await controller.execute("tabs.open", params, exec.signal);
|
|
2083
|
+
if (waitUntil === "none") return await controller.execute("tabs.open", params, exec.signal);
|
|
2084
|
+
let budgetMs = NAV_TIERS[waitUntil];
|
|
2085
|
+
if (typeof args.timeoutMs === "number" && Number.isFinite(args.timeoutMs)) budgetMs = Math.min(NAV_MAX_MS, Math.max(100, Math.trunc(args.timeoutMs)));
|
|
2086
|
+
params.timeoutMs = budgetMs;
|
|
2087
|
+
return await controller.execute("tabs.open", params, exec.signal, budgetMs + BRIDGE_MARGIN_MS);
|
|
2043
2088
|
}
|
|
2044
2089
|
case "close": {
|
|
2045
2090
|
const ids = closeTabIds(args);
|
package/lib/types/index.js
CHANGED
|
@@ -26,6 +26,13 @@ import { BridgeError, classify } from "./errors.js";
|
|
|
26
26
|
import { policyRefusal } from "./policy.js";
|
|
27
27
|
/** Navigation wait ceiling (same as browser_wait); the bridge gets this plus BRIDGE_MARGIN_MS. */
|
|
28
28
|
const NAV_MAX_MS = 120_000;
|
|
29
|
+
/**
|
|
30
|
+
* Same tier table as the extension's NAV_TIERS (extension/background.js). Both
|
|
31
|
+
* literals must stay in lockstep — verify_nav_waituntil.mjs extracts the
|
|
32
|
+
* extension's by regex and asserts the two agree (cross-side literals are wire
|
|
33
|
+
* protocol: a value only one side knows is a lie on the other).
|
|
34
|
+
*/
|
|
35
|
+
const NAV_TIERS = { none: 0, commit: 4_000, domcontentloaded: 4_000, load: 15_000 };
|
|
29
36
|
/**
|
|
30
37
|
* Extra time handed to the bridge on top of the navigation budget. Without it a
|
|
31
38
|
* caller-requested budget longer than the bridge default (60s) would be beaten by
|
|
@@ -367,7 +374,8 @@ function applyBrowserTools(ctx, controller) {
|
|
|
367
374
|
parameters: {
|
|
368
375
|
url: { type: 'string', required: true, description: 'Absolute URL to open in the tab.' },
|
|
369
376
|
tabId: { type: 'number', description: 'Target tab; defaults to the active tab.' },
|
|
370
|
-
|
|
377
|
+
waitUntil: { type: 'string', description: 'When the navigation counts as arrived: "load" (default) waits for the page load event (15s budget), "domcontentloaded" returns once the DOM is parsed (async components may still be mounting — follow with browser_wait if an element is missing), "commit" once the new document\'s address lands, "none" returns immediately with no arrival check at all.' },
|
|
378
|
+
timeoutMs: { type: 'number', description: `How long to wait for the page load to settle: defaults to the waitUntil tier (load 15000, commit/domcontentloaded 4000), capped at ${NAV_MAX_MS}. An explicit value always wins over the tier.` },
|
|
371
379
|
},
|
|
372
380
|
output: {
|
|
373
381
|
schema: {
|
|
@@ -380,6 +388,9 @@ function applyBrowserTools(ctx, controller) {
|
|
|
380
388
|
pendingUrl: { type: 'string' },
|
|
381
389
|
loadPending: { type: 'boolean' },
|
|
382
390
|
siteUnreachable: { type: 'boolean' },
|
|
391
|
+
waitUntil: { type: 'string' },
|
|
392
|
+
waitedMs: { type: 'number' },
|
|
393
|
+
readyState: { type: 'string' },
|
|
383
394
|
},
|
|
384
395
|
},
|
|
385
396
|
render: (_args, value) => {
|
|
@@ -404,14 +415,23 @@ function applyBrowserTools(ctx, controller) {
|
|
|
404
415
|
isConcurrencySafe: () => false,
|
|
405
416
|
presentCall: args => ({ card: 'generic', title: `Open ${args.url}`, kind: 'other' }),
|
|
406
417
|
async execute(args, exec) {
|
|
407
|
-
const
|
|
418
|
+
const waitUntil = args.waitUntil === undefined ? 'load' : String(args.waitUntil);
|
|
419
|
+
if (!(waitUntil in NAV_TIERS)) {
|
|
420
|
+
throw new Error(`browser_navigate: waitUntil must be one of none, commit, domcontentloaded, load — got "${waitUntil}"`);
|
|
421
|
+
}
|
|
422
|
+
const params = { url: args.url, waitUntil };
|
|
408
423
|
if (args.tabId !== undefined)
|
|
409
424
|
params.tabId = args.tabId;
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
425
|
+
// Explicit timeoutMs always wins over the tier; 'none' waits 0 and sends
|
|
426
|
+
// no budget at all. The bridge gets budget+2s so the extension's own
|
|
427
|
+
// "navigation did not arrive" always reports before the bridge's ceiling.
|
|
428
|
+
let budgetMs = NAV_TIERS[waitUntil];
|
|
429
|
+
if (typeof args.timeoutMs === 'number' && Number.isFinite(args.timeoutMs)) {
|
|
430
|
+
budgetMs = Math.min(NAV_MAX_MS, Math.max(100, Math.trunc(args.timeoutMs)));
|
|
431
|
+
}
|
|
432
|
+
if (waitUntil !== 'none')
|
|
433
|
+
params.timeoutMs = budgetMs;
|
|
434
|
+
const raw = await controller.execute('nav', params, exec.signal, waitUntil === 'none' ? undefined : budgetMs + BRIDGE_MARGIN_MS);
|
|
415
435
|
// The declared schema is closed, so the fields describing a navigation that did
|
|
416
436
|
// not arrive have to be carried over on purpose. The old shape check
|
|
417
437
|
// (`siteUnreachable === true`) also missed the object the extension really
|
|
@@ -429,6 +449,12 @@ function applyBrowserTools(ctx, controller) {
|
|
|
429
449
|
|| (typeof raw.siteUnreachable === 'object' && raw.siteUnreachable !== null);
|
|
430
450
|
if (unreachable)
|
|
431
451
|
out.siteUnreachable = true;
|
|
452
|
+
if (typeof raw.waitUntil === 'string')
|
|
453
|
+
out.waitUntil = raw.waitUntil;
|
|
454
|
+
if (typeof raw.waitedMs === 'number')
|
|
455
|
+
out.waitedMs = raw.waitedMs;
|
|
456
|
+
if (typeof raw.readyState === 'string' && raw.readyState.length > 0)
|
|
457
|
+
out.readyState = raw.readyState;
|
|
432
458
|
return out;
|
|
433
459
|
},
|
|
434
460
|
}));
|
|
@@ -836,6 +862,8 @@ function applyBrowserTools(ctx, controller) {
|
|
|
836
862
|
tabIds: { type: 'array', items: { type: 'number' }, description: 'For close: every tab to close in one call. The result reports each id (closed / alreadyGone / failed) instead of stopping at the first failure.' },
|
|
837
863
|
active: { type: 'boolean', description: 'Whether a newly opened tab becomes active; defaults to true.' },
|
|
838
864
|
group: { type: 'boolean', description: 'For open: put the new tab in the DSH tab group. Defaults to true; pass false to keep it loose.' },
|
|
865
|
+
waitUntil: { type: 'string', description: 'For open: when the new tab counts as arrived — same tiers as browser_navigate (load default / domcontentloaded / commit / none).' },
|
|
866
|
+
timeoutMs: { type: 'number', description: 'For open: wait budget, overriding the waitUntil tier (load 15000, commit/domcontentloaded 4000).' },
|
|
839
867
|
windowId: { type: 'number', description: 'For ungroup: only dissolve that window’s DSH group; omit to clear every window.' },
|
|
840
868
|
},
|
|
841
869
|
output: {
|
|
@@ -850,12 +878,24 @@ function applyBrowserTools(ctx, controller) {
|
|
|
850
878
|
case 'open': {
|
|
851
879
|
if (typeof args.url !== 'string' || args.url.length === 0)
|
|
852
880
|
throw new Error('open requires url');
|
|
853
|
-
const
|
|
881
|
+
const waitUntil = args.waitUntil === undefined ? 'load' : String(args.waitUntil);
|
|
882
|
+
if (!(waitUntil in NAV_TIERS)) {
|
|
883
|
+
throw new Error(`browser_tabs open: waitUntil must be one of none, commit, domcontentloaded, load — got "${waitUntil}"`);
|
|
884
|
+
}
|
|
885
|
+
const params = { url: args.url, waitUntil };
|
|
854
886
|
if (args.active !== undefined)
|
|
855
887
|
params.active = args.active;
|
|
856
888
|
if (args.group !== undefined)
|
|
857
889
|
params.group = args.group;
|
|
858
|
-
|
|
890
|
+
if (waitUntil === 'none') {
|
|
891
|
+
return await controller.execute('tabs.open', params, exec.signal);
|
|
892
|
+
}
|
|
893
|
+
let budgetMs = NAV_TIERS[waitUntil];
|
|
894
|
+
if (typeof args.timeoutMs === 'number' && Number.isFinite(args.timeoutMs)) {
|
|
895
|
+
budgetMs = Math.min(NAV_MAX_MS, Math.max(100, Math.trunc(args.timeoutMs)));
|
|
896
|
+
}
|
|
897
|
+
params.timeoutMs = budgetMs;
|
|
898
|
+
return await controller.execute('tabs.open', params, exec.signal, budgetMs + BRIDGE_MARGIN_MS);
|
|
859
899
|
}
|
|
860
900
|
case 'close': {
|
|
861
901
|
const ids = closeTabIds(args);
|
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.4.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"
|
|
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"
|
|
41
41
|
},
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public",
|