@browserbridge/bbx 1.0.0 → 1.1.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.
Files changed (72) hide show
  1. package/README.md +6 -4
  2. package/package.json +53 -53
  3. package/packages/agent-client/src/cli-helpers.js +43 -5
  4. package/packages/agent-client/src/cli.js +176 -171
  5. package/packages/agent-client/src/client.js +66 -21
  6. package/packages/agent-client/src/command-registry.js +104 -69
  7. package/packages/agent-client/src/detect.js +162 -54
  8. package/packages/agent-client/src/install.js +34 -28
  9. package/packages/agent-client/src/mcp-config.js +40 -40
  10. package/packages/agent-client/src/runtime.js +41 -20
  11. package/packages/agent-client/src/setup-status.js +23 -30
  12. package/packages/mcp-server/src/bin.js +57 -5
  13. package/packages/mcp-server/src/handlers.js +573 -256
  14. package/packages/mcp-server/src/server.js +568 -257
  15. package/packages/native-host/bin/bridge-daemon.js +39 -6
  16. package/packages/native-host/bin/install-manifest.js +26 -4
  17. package/packages/native-host/bin/postinstall.js +4 -2
  18. package/packages/native-host/src/config.js +142 -13
  19. package/packages/native-host/src/daemon-process.js +396 -0
  20. package/packages/native-host/src/daemon.js +350 -150
  21. package/packages/native-host/src/framing.js +131 -11
  22. package/packages/native-host/src/install-manifest.js +194 -29
  23. package/packages/native-host/src/native-host.js +154 -102
  24. package/packages/protocol/src/budget.js +3 -7
  25. package/packages/protocol/src/capabilities.js +6 -3
  26. package/packages/protocol/src/defaults.js +1 -0
  27. package/packages/protocol/src/errors.js +15 -11
  28. package/packages/protocol/src/payload-cost.js +19 -6
  29. package/packages/protocol/src/protocol.js +242 -73
  30. package/packages/protocol/src/registry.js +311 -45
  31. package/packages/protocol/src/summary.js +260 -109
  32. package/packages/protocol/src/types.js +29 -4
  33. package/skills/browser-bridge/SKILL.md +3 -2
  34. package/skills/browser-bridge/agents/openai.yaml +3 -3
  35. package/skills/browser-bridge/references/interaction.md +34 -11
  36. package/skills/browser-bridge/references/patch-workflow.md +3 -0
  37. package/skills/browser-bridge/references/protocol.md +127 -71
  38. package/skills/browser-bridge/references/tailwind.md +12 -11
  39. package/skills/browser-bridge/references/token-efficiency.md +23 -22
  40. package/skills/browser-bridge/references/ui-workflows.md +8 -0
  41. package/CHANGELOG.md +0 -55
  42. package/assets/banner.jpg +0 -0
  43. package/assets/logo.png +0 -0
  44. package/assets/logo.svg +0 -65
  45. package/docs/api-reference.md +0 -157
  46. package/docs/cli-guide.md +0 -128
  47. package/docs/index.md +0 -25
  48. package/docs/manual-setup.md +0 -140
  49. package/docs/mcp-vs-cli.md +0 -258
  50. package/docs/publishing.md +0 -114
  51. package/docs/quickstart.md +0 -104
  52. package/docs/troubleshooting.md +0 -59
  53. package/docs/usage-scenarios.md +0 -136
  54. package/manifest.json +0 -52
  55. package/packages/extension/assets/icon-128.png +0 -0
  56. package/packages/extension/assets/icon-16.png +0 -0
  57. package/packages/extension/assets/icon-32.png +0 -0
  58. package/packages/extension/assets/icon-48.png +0 -0
  59. package/packages/extension/src/background-helpers.js +0 -459
  60. package/packages/extension/src/background-routing.js +0 -91
  61. package/packages/extension/src/background.js +0 -3227
  62. package/packages/extension/src/content-script-helpers.js +0 -281
  63. package/packages/extension/src/content-script.js +0 -1977
  64. package/packages/extension/src/debugger-coordinator.js +0 -188
  65. package/packages/extension/src/sidepanel-helpers.js +0 -102
  66. package/packages/extension/ui/offscreen.html +0 -6
  67. package/packages/extension/ui/offscreen.js +0 -61
  68. package/packages/extension/ui/popup.html +0 -35
  69. package/packages/extension/ui/popup.js +0 -279
  70. package/packages/extension/ui/sidepanel.html +0 -102
  71. package/packages/extension/ui/sidepanel.js +0 -1854
  72. package/packages/extension/ui/ui.css +0 -1159
@@ -19,7 +19,7 @@ import {
19
19
  DEFAULT_WAIT_TIMEOUT_MS,
20
20
  } from './defaults.js';
21
21
  import { BridgeError, ERROR_CODES, getErrorRecovery } from './errors.js';
22
- import { BRIDGE_METHODS, createBridgeMethodGroups } from './registry.js';
22
+ import { BRIDGE_METHODS, METHOD_SET, createBridgeMethodGroups } from './registry.js';
23
23
 
24
24
  /** @typedef {import('./types.js').AccessibilityTreeParams} AccessibilityTreeParams */
25
25
  /** @typedef {import('./types.js').BridgeFailureResponse} BridgeFailureResponse */
@@ -28,6 +28,7 @@ import { BRIDGE_METHODS, createBridgeMethodGroups } from './registry.js';
28
28
  /** @typedef {import('./types.js').BridgeRequest} BridgeRequest */
29
29
  /** @typedef {import('./types.js').BridgeSuccessResponse} BridgeSuccessResponse */
30
30
  /** @typedef {import('./types.js').CheckedActionParams} CheckedActionParams */
31
+ /** @typedef {import('./types.js').CdpDispatchKeyEventParams} CdpDispatchKeyEventParams */
31
32
  /** @typedef {import('./types.js').ConsoleParams} ConsoleParams */
32
33
  /** @typedef {import('./types.js').DomQueryParams} DomQueryParams */
33
34
  /** @typedef {import('./types.js').DragParams} DragParams */
@@ -41,6 +42,7 @@ import { BRIDGE_METHODS, createBridgeMethodGroups } from './registry.js';
41
42
  /** @typedef {import('./types.js').NetworkParams} NetworkParams */
42
43
  /** @typedef {import('./types.js').NormalizedAccessibilityTreeParams} NormalizedAccessibilityTreeParams */
43
44
  /** @typedef {import('./types.js').NormalizedCheckedAction} NormalizedCheckedAction */
45
+ /** @typedef {import('./types.js').NormalizedCdpDispatchKeyEventParams} NormalizedCdpDispatchKeyEventParams */
44
46
  /** @typedef {import('./types.js').NormalizedConsoleParams} NormalizedConsoleParams */
45
47
  /** @typedef {import('./types.js').NormalizedDomQuery} NormalizedDomQuery */
46
48
  /** @typedef {import('./types.js').NormalizedDragParams} NormalizedDragParams */
@@ -114,8 +116,8 @@ export function createRequest({ id, method, tabId = null, params = {}, meta = {}
114
116
  params,
115
117
  meta: {
116
118
  protocol_version: PROTOCOL_VERSION,
117
- ...meta
118
- }
119
+ ...meta,
120
+ },
119
121
  });
120
122
  }
121
123
 
@@ -133,8 +135,8 @@ export function createSuccess(id, result, meta = {}) {
133
135
  error: null,
134
136
  meta: {
135
137
  protocol_version: PROTOCOL_VERSION,
136
- ...meta
137
- }
138
+ ...meta,
139
+ },
138
140
  };
139
141
  }
140
142
 
@@ -156,12 +158,12 @@ export function createFailure(id, code, message, details = null, meta = {}) {
156
158
  code,
157
159
  message,
158
160
  details,
159
- ...(recovery && { recovery })
161
+ ...(recovery && { recovery }),
160
162
  },
161
163
  meta: {
162
164
  protocol_version: PROTOCOL_VERSION,
163
- ...meta
164
- }
165
+ ...meta,
166
+ },
165
167
  };
166
168
  }
167
169
 
@@ -180,45 +182,142 @@ export function validateBridgeRequest(request) {
180
182
  throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'Request id must be a non-empty string.');
181
183
  }
182
184
 
183
- if (typeof candidate.method !== 'string' || !METHODS.includes(/** @type {BridgeMethod} */ (candidate.method))) {
184
- throw new BridgeError(ERROR_CODES.INVALID_REQUEST, `Unsupported method: ${String(candidate.method)}`);
185
+ if (
186
+ typeof candidate.method !== 'string' ||
187
+ !METHOD_SET.has(/** @type {BridgeMethod} */ (candidate.method))
188
+ ) {
189
+ throw new BridgeError(
190
+ ERROR_CODES.INVALID_REQUEST,
191
+ `Unsupported method: ${String(candidate.method)}`
192
+ );
193
+ }
194
+
195
+ if (
196
+ candidate.meta != null &&
197
+ (typeof candidate.meta !== 'object' || Array.isArray(candidate.meta))
198
+ ) {
199
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'Request meta must be an object.');
185
200
  }
186
201
 
187
- const meta = candidate.meta && typeof candidate.meta === 'object'
188
- ? /** @type {Record<string, unknown>} */ (candidate.meta)
189
- : {};
202
+ const meta =
203
+ candidate.meta && typeof candidate.meta === 'object'
204
+ ? /** @type {Record<string, unknown>} */ (candidate.meta)
205
+ : {};
190
206
  if (candidate.session_id != null) {
191
- throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'session_id is no longer supported. Use tab_id or window-scoped default routing.');
207
+ throw new BridgeError(
208
+ ERROR_CODES.INVALID_REQUEST,
209
+ 'session_id is no longer supported. Use tab_id or window-scoped default routing.'
210
+ );
192
211
  }
193
212
  const parsedTabId = Number(candidate.tab_id);
194
213
 
214
+ const method = /** @type {BridgeMethod} */ (candidate.method);
215
+ const params =
216
+ candidate.params && typeof candidate.params === 'object'
217
+ ? /** @type {Record<string, unknown>} */ (candidate.params)
218
+ : {};
219
+
195
220
  return {
196
221
  id: candidate.id,
197
- method: /** @type {BridgeMethod} */ (candidate.method),
222
+ method,
198
223
  tab_id: Number.isFinite(parsedTabId) && parsedTabId > 0 ? parsedTabId : null,
199
- params: candidate.params && typeof candidate.params === 'object'
200
- ? /** @type {Record<string, unknown>} */ (candidate.params)
201
- : {},
224
+ params: normalizeRequestParams(method, params),
202
225
  meta: {
203
226
  ...meta,
204
- protocol_version: typeof meta.protocol_version === 'string'
205
- ? meta.protocol_version
206
- : PROTOCOL_VERSION,
227
+ protocol_version:
228
+ typeof meta.protocol_version === 'string' ? meta.protocol_version : PROTOCOL_VERSION,
207
229
  token_budget: typeof meta.token_budget === 'number' ? meta.token_budget : null,
208
- source: meta.source === 'cli' || meta.source === 'mcp' ? meta.source : undefined
209
- }
230
+ source: meta.source === 'cli' || meta.source === 'mcp' ? meta.source : undefined,
231
+ },
210
232
  };
211
233
  }
212
234
 
235
+ /**
236
+ * Normalize request params early so malformed payloads fail at the shared
237
+ * protocol boundary before daemon or extension dispatch.
238
+ *
239
+ * @param {BridgeMethod} method
240
+ * @param {Record<string, unknown>} params
241
+ * @returns {Record<string, unknown>}
242
+ */
243
+ function normalizeRequestParams(method, params) {
244
+ switch (method) {
245
+ case 'dom.query':
246
+ return normalizeDomQuery(params);
247
+ case 'page.evaluate':
248
+ return normalizeEvaluateParams(params);
249
+ case 'page.get_console':
250
+ return normalizeConsoleParams(params);
251
+ case 'page.wait_for_load_state':
252
+ return normalizeWaitForLoadStateParams(params);
253
+ case 'page.get_storage':
254
+ return normalizeStorageParams(params);
255
+ case 'page.get_text':
256
+ return normalizePageTextParams(params);
257
+ case 'page.get_network':
258
+ return normalizeNetworkParams(params);
259
+ case 'navigation.navigate':
260
+ case 'navigation.reload':
261
+ case 'navigation.go_back':
262
+ case 'navigation.go_forward':
263
+ return normalizeNavigationAction(params);
264
+ case 'dom.wait_for':
265
+ return normalizeWaitForParams(params);
266
+ case 'dom.find_by_text':
267
+ return normalizeFindByTextParams(params);
268
+ case 'dom.find_by_role':
269
+ return normalizeFindByRoleParams(params);
270
+ case 'dom.get_html':
271
+ return normalizeGetHtmlParams(params);
272
+ case 'dom.get_accessibility_tree':
273
+ return normalizeAccessibilityTreeParams(params);
274
+ case 'styles.get_computed':
275
+ case 'styles.get_matched_rules':
276
+ return normalizeStyleQuery(params);
277
+ case 'viewport.scroll':
278
+ return normalizeViewportAction(params);
279
+ case 'viewport.resize':
280
+ return normalizeViewportResizeParams(params);
281
+ case 'input.click':
282
+ case 'input.focus':
283
+ case 'input.type':
284
+ case 'input.press_key':
285
+ return normalizeInputAction(params);
286
+ case 'cdp.dispatch_key_event':
287
+ return normalizeCdpDispatchKeyEventParams(params);
288
+ case 'input.set_checked':
289
+ return normalizeCheckedAction(params);
290
+ case 'input.select_option':
291
+ return normalizeSelectAction(params);
292
+ case 'input.hover':
293
+ return normalizeHoverParams(params);
294
+ case 'input.drag':
295
+ return normalizeDragParams(params);
296
+ case 'patch.apply_styles':
297
+ case 'patch.apply_dom':
298
+ case 'patch.list':
299
+ case 'patch.rollback':
300
+ case 'patch.commit_session_baseline':
301
+ return normalizePatchOperation(params);
302
+ case 'tabs.create':
303
+ return normalizeTabCreateParams(params);
304
+ case 'tabs.close':
305
+ return normalizeTabCloseParams(params);
306
+ default:
307
+ return params;
308
+ }
309
+ }
310
+
213
311
  /**
214
312
  * @param {DomQueryParams} [params={}]
215
313
  * @returns {NormalizedDomQuery}
216
314
  */
217
315
  export function normalizeDomQuery(params = {}) {
218
316
  return {
219
- selector: typeof params.selector === 'string' && params.selector.trim() ? params.selector : 'body',
317
+ selector:
318
+ typeof params.selector === 'string' && params.selector.trim() ? params.selector : 'body',
220
319
  withinRef: typeof params.withinRef === 'string' ? params.withinRef : null,
221
- budget: applyBudget(params)
320
+ budget: applyBudget(params),
222
321
  };
223
322
  }
224
323
 
@@ -234,12 +333,15 @@ export function normalizeStyleQuery(params = {}) {
234
333
  );
235
334
  const elementRef = String(params.elementRef || target.elementRef || '');
236
335
  if (!elementRef && !target.selector) {
237
- throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'elementRef or target is required for style queries.');
336
+ throw new BridgeError(
337
+ ERROR_CODES.INVALID_REQUEST,
338
+ 'elementRef or target is required for style queries.'
339
+ );
238
340
  }
239
341
  return {
240
342
  elementRef,
241
343
  target,
242
- properties: Array.isArray(params.properties) ? params.properties.filter(Boolean) : []
344
+ properties: Array.isArray(params.properties) ? params.properties.filter(Boolean) : [],
243
345
  };
244
346
  }
245
347
 
@@ -250,7 +352,7 @@ export function normalizeStyleQuery(params = {}) {
250
352
  function normalizeTarget(target) {
251
353
  return {
252
354
  elementRef: typeof target?.elementRef === 'string' ? target.elementRef : undefined,
253
- selector: typeof target?.selector === 'string' ? target.selector : undefined
355
+ selector: typeof target?.selector === 'string' ? target.selector : undefined,
254
356
  };
255
357
  }
256
358
 
@@ -259,9 +361,7 @@ function normalizeTarget(target) {
259
361
  * @returns {NormalizedInputAction}
260
362
  */
261
363
  export function normalizeInputAction(params = {}) {
262
- const button = params.button === 'middle' || params.button === 'right'
263
- ? params.button
264
- : 'left';
364
+ const button = params.button === 'middle' || params.button === 'right' ? params.button : 'left';
265
365
 
266
366
  return {
267
367
  target: normalizeTarget(
@@ -277,7 +377,55 @@ export function normalizeInputAction(params = {}) {
277
377
  key: typeof params.key === 'string' ? params.key : '',
278
378
  modifiers: Array.isArray(params.modifiers)
279
379
  ? params.modifiers.filter((modifier) => typeof modifier === 'string' && modifier.trim())
280
- : []
380
+ : [],
381
+ };
382
+ }
383
+
384
+ /**
385
+ * @param {CdpDispatchKeyEventParams} [params={}]
386
+ * @returns {NormalizedCdpDispatchKeyEventParams}
387
+ */
388
+ export function normalizeCdpDispatchKeyEventParams(params = {}) {
389
+ if (typeof params.key !== 'string' || !params.key.trim()) {
390
+ throw new BridgeError(ERROR_CODES.INVALID_REQUEST, 'key must be a non-empty string.');
391
+ }
392
+
393
+ if (
394
+ params.modifiers != null &&
395
+ typeof params.modifiers !== 'number' &&
396
+ !Array.isArray(params.modifiers)
397
+ ) {
398
+ throw new BridgeError(
399
+ ERROR_CODES.INVALID_REQUEST,
400
+ 'modifiers must be an array of Alt, Control, Meta, Shift or a bitmask 0-15.'
401
+ );
402
+ }
403
+
404
+ if (
405
+ typeof params.modifiers === 'number' &&
406
+ (!Number.isInteger(params.modifiers) || params.modifiers < 0 || params.modifiers > 15)
407
+ ) {
408
+ throw new BridgeError(
409
+ ERROR_CODES.INVALID_REQUEST,
410
+ 'modifiers must be an array of Alt, Control, Meta, Shift or a bitmask 0-15.'
411
+ );
412
+ }
413
+
414
+ if (Array.isArray(params.modifiers)) {
415
+ for (const modifier of params.modifiers) {
416
+ if (typeof modifier !== 'string' || !['Alt', 'Control', 'Meta', 'Shift'].includes(modifier)) {
417
+ throw new BridgeError(
418
+ ERROR_CODES.INVALID_REQUEST,
419
+ 'modifiers must contain only Alt, Control, Meta, or Shift.'
420
+ );
421
+ }
422
+ }
423
+ }
424
+
425
+ return {
426
+ key: params.key,
427
+ code: typeof params.code === 'string' ? params.code.trim() : '',
428
+ modifiers: params.modifiers ?? [],
281
429
  };
282
430
  }
283
431
 
@@ -292,7 +440,7 @@ export function normalizeCheckedAction(params = {}) {
292
440
  ? /** @type {{ elementRef?: string, selector?: string }} */ (params.target)
293
441
  : undefined
294
442
  ),
295
- checked: params.checked !== false
443
+ checked: params.checked !== false,
296
444
  };
297
445
  }
298
446
 
@@ -315,9 +463,9 @@ export function normalizeSelectAction(params = {}) {
315
463
  : [],
316
464
  indexes: Array.isArray(params.indexes)
317
465
  ? params.indexes
318
- .map((index) => Number(index))
319
- .filter((index) => Number.isInteger(index) && index >= 0)
320
- : []
466
+ .map((index) => Number(index))
467
+ .filter((index) => Number.isInteger(index) && index >= 0)
468
+ : [],
321
469
  };
322
470
  }
323
471
 
@@ -335,7 +483,7 @@ export function normalizeViewportAction(params = {}) {
335
483
  top: Number.isFinite(Number(params.top)) ? Number(params.top) : 0,
336
484
  left: Number.isFinite(Number(params.left)) ? Number(params.left) : 0,
337
485
  behavior: params.behavior === 'smooth' ? 'smooth' : 'auto',
338
- relative: Boolean(params.relative)
486
+ relative: Boolean(params.relative),
339
487
  };
340
488
  }
341
489
 
@@ -362,7 +510,7 @@ export function normalizeNavigationAction(params = {}) {
362
510
  return {
363
511
  url,
364
512
  waitForLoad: params.waitForLoad !== false,
365
- timeoutMs: clampInt(params.timeoutMs, 500, 120_000, 15_000)
513
+ timeoutMs: clampInt(params.timeoutMs, 500, 120_000, 15_000),
366
514
  };
367
515
  }
368
516
 
@@ -373,17 +521,19 @@ export function normalizeNavigationAction(params = {}) {
373
521
  export function normalizePatchOperation(params = {}) {
374
522
  return {
375
523
  patchId: typeof params.patchId === 'string' ? params.patchId : null,
376
- target: params.target && typeof params.target === 'object'
377
- ? /** @type {Record<string, unknown>} */ (params.target)
378
- : {},
524
+ target:
525
+ params.target && typeof params.target === 'object'
526
+ ? /** @type {Record<string, unknown>} */ (params.target)
527
+ : {},
379
528
  operation: typeof params.operation === 'string' ? params.operation : null,
380
529
  name: typeof params.name === 'string' ? params.name : null,
381
- declarations: params.declarations && typeof params.declarations === 'object'
382
- ? /** @type {Record<string, string>} */ (params.declarations)
383
- : {},
530
+ declarations:
531
+ params.declarations && typeof params.declarations === 'object'
532
+ ? /** @type {Record<string, string>} */ (params.declarations)
533
+ : {},
384
534
  value: params.value ?? null,
385
535
  important: Boolean(params.important),
386
- verify: Boolean(params.verify)
536
+ verify: Boolean(params.verify),
387
537
  };
388
538
  }
389
539
 
@@ -403,7 +553,7 @@ export function normalizeEvaluateParams(params = {}) {
403
553
  expression,
404
554
  awaitPromise: Boolean(params.awaitPromise),
405
555
  timeoutMs: clampInt(params.timeoutMs, 100, 30_000, 5_000),
406
- returnByValue: params.returnByValue !== false
556
+ returnByValue: params.returnByValue !== false,
407
557
  };
408
558
  }
409
559
 
@@ -416,7 +566,7 @@ export function normalizeConsoleParams(params = {}) {
416
566
  return {
417
567
  level: validLevels.includes(String(params.level ?? '')) ? String(params.level) : 'all',
418
568
  clear: Boolean(params.clear),
419
- limit: clampInt(params.limit, 1, 200, 50)
569
+ limit: clampInt(params.limit, 1, 200, 50),
420
570
  };
421
571
  }
422
572
 
@@ -432,7 +582,7 @@ export function normalizeWaitForParams(params = {}) {
432
582
  state: validStates.includes(String(params.state ?? ''))
433
583
  ? /** @type {'attached' | 'detached' | 'visible' | 'hidden'} */ (String(params.state))
434
584
  : 'attached',
435
- timeoutMs: clampInt(params.timeoutMs, 100, 30_000, 5_000)
585
+ timeoutMs: clampInt(params.timeoutMs, 100, 30_000, 5_000),
436
586
  };
437
587
  }
438
588
 
@@ -445,7 +595,7 @@ export function normalizeFindByTextParams(params = {}) {
445
595
  text: typeof params.text === 'string' ? params.text : '',
446
596
  exact: Boolean(params.exact),
447
597
  selector: typeof params.selector === 'string' && params.selector.trim() ? params.selector : '*',
448
- maxResults: clampInt(params.maxResults, 1, 50, 10)
598
+ maxResults: clampInt(params.maxResults, 1, 50, 10),
449
599
  };
450
600
  }
451
601
 
@@ -458,7 +608,7 @@ export function normalizeFindByRoleParams(params = {}) {
458
608
  role: typeof params.role === 'string' ? params.role : '',
459
609
  name: typeof params.name === 'string' ? params.name : '',
460
610
  selector: typeof params.selector === 'string' && params.selector.trim() ? params.selector : '*',
461
- maxResults: clampInt(params.maxResults, 1, 50, 10)
611
+ maxResults: clampInt(params.maxResults, 1, 50, 10),
462
612
  };
463
613
  }
464
614
 
@@ -476,7 +626,7 @@ export function normalizeGetHtmlParams(params = {}) {
476
626
  elementRef: String(params.elementRef || target.elementRef || ''),
477
627
  target,
478
628
  outer: Boolean(params.outer),
479
- maxLength: clampInt(params.maxLength, 32, 50_000, 2000)
629
+ maxLength: clampInt(params.maxLength, 32, 50_000, 2000),
480
630
  };
481
631
  }
482
632
 
@@ -491,7 +641,7 @@ export function normalizeHoverParams(params = {}) {
491
641
  ? /** @type {{ elementRef?: string, selector?: string }} */ (params.target)
492
642
  : undefined
493
643
  ),
494
- duration: clampInt(params.duration, 0, 5_000, 0)
644
+ duration: clampInt(params.duration, 0, 5_000, 0),
495
645
  };
496
646
  }
497
647
 
@@ -512,7 +662,7 @@ export function normalizeDragParams(params = {}) {
512
662
  : undefined
513
663
  ),
514
664
  offsetX: Number.isFinite(Number(params.offsetX)) ? Number(params.offsetX) : 0,
515
- offsetY: Number.isFinite(Number(params.offsetY)) ? Number(params.offsetY) : 0
665
+ offsetY: Number.isFinite(Number(params.offsetY)) ? Number(params.offsetY) : 0,
516
666
  };
517
667
  }
518
668
 
@@ -523,7 +673,7 @@ export function normalizeDragParams(params = {}) {
523
673
  export function normalizeStorageParams(params = {}) {
524
674
  return {
525
675
  type: params.type === 'session' ? 'session' : 'local',
526
- keys: Array.isArray(params.keys) ? params.keys.filter((k) => typeof k === 'string') : null
676
+ keys: Array.isArray(params.keys) ? params.keys.filter((k) => typeof k === 'string') : null,
527
677
  };
528
678
  }
529
679
 
@@ -534,7 +684,7 @@ export function normalizeStorageParams(params = {}) {
534
684
  export function normalizeWaitForLoadStateParams(params = {}) {
535
685
  return {
536
686
  waitForLoad: params.waitForLoad !== false,
537
- timeoutMs: clampInt(params.timeoutMs, 500, 120_000, 15_000)
687
+ timeoutMs: clampInt(params.timeoutMs, 500, 120_000, 15_000),
538
688
  };
539
689
  }
540
690
 
@@ -543,7 +693,8 @@ export function normalizeWaitForLoadStateParams(params = {}) {
543
693
  * @returns {NormalizedTabCreateParams}
544
694
  */
545
695
  export function normalizeTabCreateParams(params = {}) {
546
- const url = typeof params.url === 'string' && params.url.trim() ? params.url.trim() : 'about:blank';
696
+ const url =
697
+ typeof params.url === 'string' && params.url.trim() ? params.url.trim() : 'about:blank';
547
698
  if (url !== 'about:blank') {
548
699
  try {
549
700
  const parsed = new URL(url);
@@ -560,7 +711,7 @@ export function normalizeTabCreateParams(params = {}) {
560
711
  }
561
712
  return {
562
713
  url,
563
- active: params.active !== false
714
+ active: params.active !== false,
564
715
  };
565
716
  }
566
717
 
@@ -583,7 +734,7 @@ export function normalizeTabCloseParams(params = {}) {
583
734
  export function normalizeAccessibilityTreeParams(params = {}) {
584
735
  return {
585
736
  maxDepth: clampInt(params.maxDepth, 1, 20, DEFAULT_A11Y_MAX_DEPTH),
586
- maxNodes: clampInt(params.maxNodes, 10, 5000, DEFAULT_A11Y_MAX_NODES)
737
+ maxNodes: clampInt(params.maxNodes, 10, 5000, DEFAULT_A11Y_MAX_NODES),
587
738
  };
588
739
  }
589
740
 
@@ -595,9 +746,10 @@ export function normalizeNetworkParams(params = {}) {
595
746
  return {
596
747
  clear: Boolean(params.clear),
597
748
  limit: clampInt(params.limit, 1, 500, DEFAULT_NETWORK_LIMIT),
598
- urlPattern: typeof params.urlPattern === 'string' && params.urlPattern.trim()
599
- ? params.urlPattern.trim()
600
- : null
749
+ urlPattern:
750
+ typeof params.urlPattern === 'string' && params.urlPattern.trim()
751
+ ? params.urlPattern.trim()
752
+ : null,
601
753
  };
602
754
  }
603
755
 
@@ -607,7 +759,7 @@ export function normalizeNetworkParams(params = {}) {
607
759
  */
608
760
  export function normalizePageTextParams(params = {}) {
609
761
  return {
610
- textBudget: clampInt(params.textBudget, 100, 100_000, DEFAULT_PAGE_TEXT_BUDGET)
762
+ textBudget: clampInt(params.textBudget, 100, 100_000, DEFAULT_PAGE_TEXT_BUDGET),
611
763
  };
612
764
  }
613
765
 
@@ -620,7 +772,7 @@ export function normalizeViewportResizeParams(params = {}) {
620
772
  width: clampInt(params.width, 320, 7680, DEFAULT_VIEWPORT_WIDTH),
621
773
  height: clampInt(params.height, 200, 4320, DEFAULT_VIEWPORT_HEIGHT),
622
774
  deviceScaleFactor: clampInt(params.deviceScaleFactor, 0, 4, 0),
623
- reset: Boolean(params.reset)
775
+ reset: Boolean(params.reset),
624
776
  };
625
777
  }
626
778
 
@@ -641,24 +793,37 @@ export function createRuntimeContext() {
641
793
  return {
642
794
  v: PROTOCOL_VERSION,
643
795
  budgets: {
644
- quick: { n: BUDGET_PRESETS.quick.maxNodes, d: BUDGET_PRESETS.quick.maxDepth, t: BUDGET_PRESETS.quick.textBudget },
645
- normal: { n: BUDGET_PRESETS.normal.maxNodes, d: BUDGET_PRESETS.normal.maxDepth, t: BUDGET_PRESETS.normal.textBudget },
646
- deep: { n: BUDGET_PRESETS.deep.maxNodes, d: BUDGET_PRESETS.deep.maxDepth, t: BUDGET_PRESETS.deep.textBudget }
796
+ quick: {
797
+ n: BUDGET_PRESETS.quick.maxNodes,
798
+ d: BUDGET_PRESETS.quick.maxDepth,
799
+ t: BUDGET_PRESETS.quick.textBudget,
800
+ },
801
+ normal: {
802
+ n: BUDGET_PRESETS.normal.maxNodes,
803
+ d: BUDGET_PRESETS.normal.maxDepth,
804
+ t: BUDGET_PRESETS.normal.textBudget,
805
+ },
806
+ deep: {
807
+ n: BUDGET_PRESETS.deep.maxNodes,
808
+ d: BUDGET_PRESETS.deep.maxDepth,
809
+ t: BUDGET_PRESETS.deep.textBudget,
810
+ },
647
811
  },
648
812
  methods: methodGroups,
649
813
  errors: {
650
- ACCESS_DENIED: 'Browser Bridge is off for this window or the page is restricted; if access is off, the first denied call surfaces an Enable cue in the extension UI',
814
+ ACCESS_DENIED:
815
+ 'Browser Bridge is off for this window or the page is restricted; if access is off, the first denied call surfaces an Enable cue in the extension UI',
651
816
  TAB_MISMATCH: 'Tab closed or not found',
652
817
  ELEMENT_STALE: 'Element removed from DOM - re-query',
653
818
  INVALID_REQUEST: 'Malformed method or params',
654
819
  TIMEOUT: 'Operation exceeded time limit',
655
820
  RATE_LIMITED: 'Too many requests - back off',
656
821
  INTERNAL_ERROR: 'Unexpected extension error',
657
- EXTENSION_DISCONNECTED: 'Extension not connected to daemon - check Chrome'
822
+ EXTENSION_DISCONNECTED: 'Extension not connected to daemon - check Chrome',
658
823
  },
659
824
  tips: [
660
825
  'dom.query quick budget first; widen only if truncated',
661
- 'Reuse elementRef; don\'t re-query',
826
+ "Reuse elementRef; don't re-query",
662
827
  'Set attributeAllowlist for focused DOM reads',
663
828
  'patch.apply_styles before patch.apply_dom',
664
829
  'Verify with get_box_model not screenshots',
@@ -676,7 +841,7 @@ export function createRuntimeContext() {
676
841
  'performance.get_metrics for Core Web Vitals and load timing',
677
842
  'viewport.resize to test responsive layouts',
678
843
  'Prefer screenshot.capture_element, or a tight screenshot.capture_region when element capture cannot express the needed area',
679
- 'page.get_storage reads localStorage/sessionStorage without evaluate'
844
+ 'page.get_storage reads localStorage/sessionStorage without evaluate',
680
845
  ],
681
846
  flow: [
682
847
  'health.ping',
@@ -686,7 +851,7 @@ export function createRuntimeContext() {
686
851
  'patch.apply_styles',
687
852
  'layout.get_box_model',
688
853
  'page.get_console',
689
- 'patch.rollback'
854
+ 'patch.rollback',
690
855
  ],
691
856
  limits: {
692
857
  maxNodes: { min: 1, max: 250, default: DEFAULT_MAX_NODES },
@@ -699,7 +864,11 @@ export function createRuntimeContext() {
699
864
  a11yMaxNodes: { min: 10, max: 5000, default: DEFAULT_A11Y_MAX_NODES },
700
865
  networkLimit: { min: 1, max: 500, default: DEFAULT_NETWORK_LIMIT },
701
866
  consoleLimit: { min: 1, max: 200, default: DEFAULT_CONSOLE_LIMIT },
702
- pageTextBudget: { min: 100, max: 100000, default: DEFAULT_PAGE_TEXT_BUDGET }
703
- }
867
+ pageTextBudget: {
868
+ min: 100,
869
+ max: 100000,
870
+ default: DEFAULT_PAGE_TEXT_BUDGET,
871
+ },
872
+ },
704
873
  };
705
874
  }