@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 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.2.0.zip](https://github.com/angelky7/dsh-browser-control/releases/download/v1.2.0/DSH-Browser-Control-v1.2.0.zip) | Chrome extension (unzip and load; this release adds the three cookie commands — reinstall required) |
68
- | [dsh-browser-control-plugin-v1.2.0.zip](https://github.com/angelky7/dsh-browser-control/releases/download/v1.2.0/dsh-browser-control-plugin-v1.2.0.zip) | dsh plugin (offline fallback; online installs use Option A/B) |
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.2.0"
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.2.0.zip](https://github.com/angelky7/dsh-browser-control/releases/download/v1.2.0/DSH-Browser-Control-v1.2.0.zip) | Chrome 扩展(解压后加载;这一版扩展新增了 cookie 三条命令,**必须重装**) |
68
- | [dsh-browser-control-plugin-v1.2.0.zip](https://github.com/angelky7/dsh-browser-control/releases/download/v1.2.0/dsh-browser-control-plugin-v1.2.0.zip) | dsh 插件(离线兜底,在线装直接用方式 A/B) |
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.2.0"
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 params = { url: args.url };
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
- const raw = await controller.execute("nav", params, exec.signal);
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 params = { url: args.url };
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 raw = await controller.execute("eval", args.tabId === void 0 ? { expression: args.expression } : {
2085
- expression: args.expression,
2086
- tabId: args.tabId
2087
- }, exec.signal);
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);
@@ -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 params = { url: args.url };
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
- const raw = await controller.execute('nav', params, exec.signal);
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 params = { url: args.url };
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
- return await controller.execute('tabs.open', params, exec.signal);
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 raw = await controller.execute('eval', args.tabId === undefined ? { expression: args.expression } : { expression: args.expression, tabId: args.tabId }, exec.signal);
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.2.0",
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",