@angelky7/dsh-browser-control 1.2.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 +80 -9
- package/lib/types/index.js +74 -5
- 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
|
@@ -939,6 +939,27 @@ function policyRefusal(command, flags, params) {
|
|
|
939
939
|
* fix, so the model can tell the user what to do instead of hanging.
|
|
940
940
|
* @module @deepseek-ai/dsh-browser-bridge
|
|
941
941
|
*/
|
|
942
|
+
/** Navigation wait ceiling (same as browser_wait); the bridge gets this plus BRIDGE_MARGIN_MS. */
|
|
943
|
+
const NAV_MAX_MS = 12e4;
|
|
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
|
+
/**
|
|
957
|
+
* Extra time handed to the bridge on top of the navigation budget. Without it a
|
|
958
|
+
* caller-requested budget longer than the bridge default (60s) would be beaten by
|
|
959
|
+
* the bridge, and the model would get `command_timeout` (effect unknown) instead
|
|
960
|
+
* of a navigation timeout (nothing was touched) - two very different next steps.
|
|
961
|
+
*/
|
|
962
|
+
const BRIDGE_MARGIN_MS = 2e3;
|
|
942
963
|
/** Cordis plugin name used by loader diagnostics. */
|
|
943
964
|
const name = "browser-bridge";
|
|
944
965
|
/** The tool registry this plugin contributes `browser_*` tools to. */
|
|
@@ -1236,6 +1257,14 @@ function applyBrowserTools(ctx, controller) {
|
|
|
1236
1257
|
tabId: {
|
|
1237
1258
|
type: "number",
|
|
1238
1259
|
description: "Target tab; defaults to the active tab."
|
|
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
|
+
},
|
|
1265
|
+
timeoutMs: {
|
|
1266
|
+
type: "number",
|
|
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.`
|
|
1239
1268
|
}
|
|
1240
1269
|
},
|
|
1241
1270
|
output: {
|
|
@@ -1251,7 +1280,10 @@ function applyBrowserTools(ctx, controller) {
|
|
|
1251
1280
|
title: { type: "string" },
|
|
1252
1281
|
pendingUrl: { type: "string" },
|
|
1253
1282
|
loadPending: { type: "boolean" },
|
|
1254
|
-
siteUnreachable: { type: "boolean" }
|
|
1283
|
+
siteUnreachable: { type: "boolean" },
|
|
1284
|
+
waitUntil: { type: "string" },
|
|
1285
|
+
waitedMs: { type: "number" },
|
|
1286
|
+
readyState: { type: "string" }
|
|
1255
1287
|
}
|
|
1256
1288
|
},
|
|
1257
1289
|
render: (_args, value) => {
|
|
@@ -1285,15 +1317,26 @@ function applyBrowserTools(ctx, controller) {
|
|
|
1285
1317
|
kind: "other"
|
|
1286
1318
|
}),
|
|
1287
1319
|
async execute(args, exec) {
|
|
1288
|
-
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
|
+
};
|
|
1289
1326
|
if (args.tabId !== void 0) params.tabId = args.tabId;
|
|
1290
|
-
|
|
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);
|
|
1291
1331
|
const out = { tabId: raw.tabId };
|
|
1292
1332
|
if (typeof raw.url === "string") out.url = raw.url;
|
|
1293
1333
|
if (typeof raw.title === "string") out.title = raw.title;
|
|
1294
1334
|
if (typeof raw.pendingUrl === "string") out.pendingUrl = raw.pendingUrl;
|
|
1295
1335
|
if (raw.loadPending === true) out.loadPending = true;
|
|
1296
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;
|
|
1297
1340
|
return out;
|
|
1298
1341
|
}
|
|
1299
1342
|
}));
|
|
@@ -1996,6 +2039,14 @@ function applyBrowserTools(ctx, controller) {
|
|
|
1996
2039
|
type: "boolean",
|
|
1997
2040
|
description: "For open: put the new tab in the DSH tab group. Defaults to true; pass false to keep it loose."
|
|
1998
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
|
+
},
|
|
1999
2050
|
windowId: {
|
|
2000
2051
|
type: "number",
|
|
2001
2052
|
description: "For ungroup: only dissolve that window’s DSH group; omit to clear every window."
|
|
@@ -2021,10 +2072,19 @@ function applyBrowserTools(ctx, controller) {
|
|
|
2021
2072
|
case "list": return await controller.execute("tabs.list", {}, exec.signal);
|
|
2022
2073
|
case "open": {
|
|
2023
2074
|
if (typeof args.url !== "string" || args.url.length === 0) throw new Error("open requires url");
|
|
2024
|
-
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
|
+
};
|
|
2025
2081
|
if (args.active !== void 0) params.active = args.active;
|
|
2026
2082
|
if (args.group !== void 0) params.group = args.group;
|
|
2027
|
-
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);
|
|
2028
2088
|
}
|
|
2029
2089
|
case "close": {
|
|
2030
2090
|
const ids = closeTabIds(args);
|
|
@@ -2041,6 +2101,8 @@ function applyBrowserTools(ctx, controller) {
|
|
|
2041
2101
|
}
|
|
2042
2102
|
}
|
|
2043
2103
|
}));
|
|
2104
|
+
/** Page-side ceiling of the eval race in the extension; the bridge budget adds slack on top. */
|
|
2105
|
+
const EVAL_MAX_MS = 12e4;
|
|
2044
2106
|
ctx.tools.register(defineTool({
|
|
2045
2107
|
name: "browser_evaluate",
|
|
2046
2108
|
description: "Run JavaScript in the page and get the JSON result back as a string. Prefer read-only inspection.",
|
|
@@ -2053,6 +2115,10 @@ function applyBrowserTools(ctx, controller) {
|
|
|
2053
2115
|
tabId: {
|
|
2054
2116
|
type: "number",
|
|
2055
2117
|
description: "Target tab; defaults to the active tab."
|
|
2118
|
+
},
|
|
2119
|
+
timeoutMs: {
|
|
2120
|
+
type: "number",
|
|
2121
|
+
description: `Give up after this many milliseconds; defaults to 60000, capped at ${EVAL_MAX_MS}.`
|
|
2056
2122
|
}
|
|
2057
2123
|
},
|
|
2058
2124
|
output: {
|
|
@@ -2081,10 +2147,15 @@ function applyBrowserTools(ctx, controller) {
|
|
|
2081
2147
|
kind: "other"
|
|
2082
2148
|
}),
|
|
2083
2149
|
async execute(args, exec) {
|
|
2084
|
-
const
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2150
|
+
const params = { expression: args.expression };
|
|
2151
|
+
if (args.tabId !== void 0) params.tabId = args.tabId;
|
|
2152
|
+
let bridgeCeiling;
|
|
2153
|
+
if (typeof args.timeoutMs === "number" && Number.isFinite(args.timeoutMs)) {
|
|
2154
|
+
const evalBudget = Math.min(EVAL_MAX_MS, Math.max(100, Math.trunc(args.timeoutMs)));
|
|
2155
|
+
params.timeoutMs = evalBudget;
|
|
2156
|
+
bridgeCeiling = evalBudget + 2e3;
|
|
2157
|
+
}
|
|
2158
|
+
const raw = await controller.execute("eval", params, exec.signal, bridgeCeiling);
|
|
2088
2159
|
let json;
|
|
2089
2160
|
try {
|
|
2090
2161
|
json = JSON.stringify(raw.value) ?? String(raw.value);
|
package/lib/types/index.js
CHANGED
|
@@ -24,6 +24,22 @@ import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
|
24
24
|
import { BridgeServer, cleanupArtifacts } from "./server.js";
|
|
25
25
|
import { BridgeError, classify } from "./errors.js";
|
|
26
26
|
import { policyRefusal } from "./policy.js";
|
|
27
|
+
/** Navigation wait ceiling (same as browser_wait); the bridge gets this plus BRIDGE_MARGIN_MS. */
|
|
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 };
|
|
36
|
+
/**
|
|
37
|
+
* Extra time handed to the bridge on top of the navigation budget. Without it a
|
|
38
|
+
* caller-requested budget longer than the bridge default (60s) would be beaten by
|
|
39
|
+
* the bridge, and the model would get `command_timeout` (effect unknown) instead
|
|
40
|
+
* of a navigation timeout (nothing was touched) - two very different next steps.
|
|
41
|
+
*/
|
|
42
|
+
const BRIDGE_MARGIN_MS = 2_000;
|
|
27
43
|
// Re-exported so the policy tables stay testable from the published bundle
|
|
28
44
|
// (verify/verify_policy.mjs reconciles them against the extension's COMMANDS).
|
|
29
45
|
export { LOCAL_DESTRUCTIVE_COMMANDS, PAGE_MUTATING_COMMANDS, READ_ONLY_ALLOWED, SCRIPT_COMMANDS, policyRefusal } from "./policy.js";
|
|
@@ -358,6 +374,8 @@ function applyBrowserTools(ctx, controller) {
|
|
|
358
374
|
parameters: {
|
|
359
375
|
url: { type: 'string', required: true, description: 'Absolute URL to open in the tab.' },
|
|
360
376
|
tabId: { type: 'number', description: 'Target tab; defaults to the active tab.' },
|
|
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.` },
|
|
361
379
|
},
|
|
362
380
|
output: {
|
|
363
381
|
schema: {
|
|
@@ -370,6 +388,9 @@ function applyBrowserTools(ctx, controller) {
|
|
|
370
388
|
pendingUrl: { type: 'string' },
|
|
371
389
|
loadPending: { type: 'boolean' },
|
|
372
390
|
siteUnreachable: { type: 'boolean' },
|
|
391
|
+
waitUntil: { type: 'string' },
|
|
392
|
+
waitedMs: { type: 'number' },
|
|
393
|
+
readyState: { type: 'string' },
|
|
373
394
|
},
|
|
374
395
|
},
|
|
375
396
|
render: (_args, value) => {
|
|
@@ -394,10 +415,23 @@ function applyBrowserTools(ctx, controller) {
|
|
|
394
415
|
isConcurrencySafe: () => false,
|
|
395
416
|
presentCall: args => ({ card: 'generic', title: `Open ${args.url}`, kind: 'other' }),
|
|
396
417
|
async execute(args, exec) {
|
|
397
|
-
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 };
|
|
398
423
|
if (args.tabId !== undefined)
|
|
399
424
|
params.tabId = args.tabId;
|
|
400
|
-
|
|
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);
|
|
401
435
|
// The declared schema is closed, so the fields describing a navigation that did
|
|
402
436
|
// not arrive have to be carried over on purpose. The old shape check
|
|
403
437
|
// (`siteUnreachable === true`) also missed the object the extension really
|
|
@@ -415,6 +449,12 @@ function applyBrowserTools(ctx, controller) {
|
|
|
415
449
|
|| (typeof raw.siteUnreachable === 'object' && raw.siteUnreachable !== null);
|
|
416
450
|
if (unreachable)
|
|
417
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;
|
|
418
458
|
return out;
|
|
419
459
|
},
|
|
420
460
|
}));
|
|
@@ -822,6 +862,8 @@ function applyBrowserTools(ctx, controller) {
|
|
|
822
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.' },
|
|
823
863
|
active: { type: 'boolean', description: 'Whether a newly opened tab becomes active; defaults to true.' },
|
|
824
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).' },
|
|
825
867
|
windowId: { type: 'number', description: 'For ungroup: only dissolve that window’s DSH group; omit to clear every window.' },
|
|
826
868
|
},
|
|
827
869
|
output: {
|
|
@@ -836,12 +878,24 @@ function applyBrowserTools(ctx, controller) {
|
|
|
836
878
|
case 'open': {
|
|
837
879
|
if (typeof args.url !== 'string' || args.url.length === 0)
|
|
838
880
|
throw new Error('open requires url');
|
|
839
|
-
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 };
|
|
840
886
|
if (args.active !== undefined)
|
|
841
887
|
params.active = args.active;
|
|
842
888
|
if (args.group !== undefined)
|
|
843
889
|
params.group = args.group;
|
|
844
|
-
|
|
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);
|
|
845
899
|
}
|
|
846
900
|
case 'close': {
|
|
847
901
|
const ids = closeTabIds(args);
|
|
@@ -866,12 +920,15 @@ function applyBrowserTools(ctx, controller) {
|
|
|
866
920
|
}
|
|
867
921
|
},
|
|
868
922
|
}));
|
|
923
|
+
/** Page-side ceiling of the eval race in the extension; the bridge budget adds slack on top. */
|
|
924
|
+
const EVAL_MAX_MS = 120_000;
|
|
869
925
|
ctx.tools.register(defineTool({
|
|
870
926
|
name: 'browser_evaluate',
|
|
871
927
|
description: 'Run JavaScript in the page and get the JSON result back as a string. Prefer read-only inspection.',
|
|
872
928
|
parameters: {
|
|
873
929
|
expression: { type: 'string', required: true, description: 'JavaScript expression or statement sequence; awaited like a promise body.' },
|
|
874
930
|
tabId: { type: 'number', description: 'Target tab; defaults to the active tab.' },
|
|
931
|
+
timeoutMs: { type: 'number', description: `Give up after this many milliseconds; defaults to 60000, capped at ${EVAL_MAX_MS}.` },
|
|
875
932
|
},
|
|
876
933
|
output: {
|
|
877
934
|
schema: {
|
|
@@ -886,7 +943,19 @@ function applyBrowserTools(ctx, controller) {
|
|
|
886
943
|
},
|
|
887
944
|
presentCall: () => ({ card: 'generic', title: 'Evaluate in page', kind: 'other' }),
|
|
888
945
|
async execute(args, exec) {
|
|
889
|
-
const
|
|
946
|
+
const params = { expression: args.expression };
|
|
947
|
+
if (args.tabId !== undefined)
|
|
948
|
+
params.tabId = args.tabId;
|
|
949
|
+
// The extension races the evaluation at this budget; give the bridge 2s of
|
|
950
|
+
// slack so the page-side timeout reports first. Omitted → send nothing and
|
|
951
|
+
// let the bridge default (60s) govern, exactly as before the knob existed.
|
|
952
|
+
let bridgeCeiling;
|
|
953
|
+
if (typeof args.timeoutMs === 'number' && Number.isFinite(args.timeoutMs)) {
|
|
954
|
+
const evalBudget = Math.min(EVAL_MAX_MS, Math.max(100, Math.trunc(args.timeoutMs)));
|
|
955
|
+
params.timeoutMs = evalBudget;
|
|
956
|
+
bridgeCeiling = evalBudget + 2_000;
|
|
957
|
+
}
|
|
958
|
+
const raw = await controller.execute('eval', params, exec.signal, bridgeCeiling);
|
|
890
959
|
let json;
|
|
891
960
|
try {
|
|
892
961
|
json = JSON.stringify(raw.value) ?? String(raw.value);
|
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"
|
|
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",
|