@browserbridge/bbx 1.7.7 → 1.8.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 (34) hide show
  1. package/PRIVACY.md +111 -0
  2. package/README.md +13 -5
  3. package/RESPONSIBLE_USE.md +16 -0
  4. package/SECURITY.md +52 -0
  5. package/package.json +12 -6
  6. package/packages/agent-client/src/cli-args.js +24 -0
  7. package/packages/agent-client/src/cli.js +46 -52
  8. package/packages/agent-client/src/client.js +140 -5
  9. package/packages/agent-client/src/command-registry.js +56 -53
  10. package/packages/agent-client/src/content-extract.js +198 -0
  11. package/packages/agent-client/src/remotes.js +1 -1
  12. package/packages/agent-client/src/runtime.js +768 -35
  13. package/packages/agent-client/src/types.ts +131 -6
  14. package/packages/mcp-server/src/guidance.js +1 -1
  15. package/packages/mcp-server/src/handlers-capture.js +41 -5
  16. package/packages/mcp-server/src/handlers-dom.js +8 -2
  17. package/packages/mcp-server/src/handlers-navigation.js +2 -1
  18. package/packages/mcp-server/src/handlers-page.js +41 -4
  19. package/packages/mcp-server/src/handlers-utils.js +67 -7
  20. package/packages/mcp-server/src/handlers.js +4 -4
  21. package/packages/mcp-server/src/server.js +102 -6
  22. package/packages/native-host/src/daemon.js +158 -6
  23. package/packages/native-host/src/native-host.js +3 -1
  24. package/packages/protocol/src/defaults.js +3 -0
  25. package/packages/protocol/src/errors.js +54 -1
  26. package/packages/protocol/src/protocol.js +297 -1
  27. package/packages/protocol/src/registry.js +66 -35
  28. package/packages/protocol/src/summary.js +87 -5
  29. package/packages/protocol/src/types.ts +234 -0
  30. package/skills/browser-bridge/SKILL.md +34 -13
  31. package/skills/browser-bridge/references/interaction.md +55 -11
  32. package/skills/browser-bridge/references/patch-workflow.md +2 -0
  33. package/skills/browser-bridge/references/protocol.md +157 -82
  34. package/skills/browser-bridge/references/token-efficiency.md +19 -13
@@ -41,6 +41,10 @@ const METHOD_POLICIES = Object.freeze({
41
41
  debuggerBacked: true,
42
42
  }),
43
43
  navigationControl: Object.freeze({ capability: CAPABILITIES.NAVIGATION_CONTROL }),
44
+ navigationControlDebugger: Object.freeze({
45
+ capability: CAPABILITIES.NAVIGATION_CONTROL,
46
+ debuggerBacked: true,
47
+ }),
44
48
  domRead: Object.freeze({ capability: CAPABILITIES.DOM_READ }),
45
49
  domReadDebugger: Object.freeze({
46
50
  capability: CAPABILITIES.DOM_READ,
@@ -102,10 +106,16 @@ const BRIDGE_METHOD_DESCRIPTIONS = Object.freeze({
102
106
  'page.get_state': 'Get URL, title, origin, and ready-state for the active page.',
103
107
  'page.evaluate': 'Evaluate JavaScript in the page context.',
104
108
  'page.get_console': 'Read buffered console output from the page.',
105
- 'page.wait_for_load_state': 'Wait for the page to finish loading.',
109
+ 'page.handle_dialog':
110
+ 'Inspect or explicitly act on the current JavaScript dialog. expectedDialogId is only a stale-decision check immediately before dispatch; Chrome cannot bind the CDP command atomically to that identifier.',
111
+ 'page.wait_for_load_state':
112
+ 'Wait for truthful tab-complete state and/or an event-aware URL condition.',
106
113
  'page.get_storage': 'Read local or session storage values.',
107
114
  'page.get_text': 'Read bounded visible text from the page.',
108
- 'page.get_network': 'Read buffered fetch and XHR network activity.',
115
+ 'page.extract_content':
116
+ 'Extract bounded semantic page content as text or Markdown without returning source HTML.',
117
+ 'page.get_network':
118
+ 'Read buffered fetch/XHR activity or explicitly manage bounded all-resource CDP capture.',
109
119
  'network.intercept.add': 'Add a request interception rule (CDP Fetch domain).',
110
120
  'network.intercept.remove': 'Remove a request interception rule by ID.',
111
121
  'network.intercept.list': 'List active interception rules.',
@@ -122,22 +132,26 @@ const BRIDGE_METHOD_DESCRIPTIONS = Object.freeze({
122
132
  'dom.find_by_text': 'Find elements by visible text.',
123
133
  'dom.find_by_role': 'Find elements by ARIA role and optional name.',
124
134
  'dom.get_html': 'Read inner or outer HTML for one element.',
125
- 'dom.get_accessibility_tree': 'Read a pruned accessibility tree for the tab.',
135
+ 'dom.get_accessibility_tree':
136
+ 'Read a depth-limited accessibility tree with optional compact or interactive filtering.',
126
137
  'layout.get_box_model': 'Read the box model for one element.',
127
138
  'layout.hit_test': 'Resolve the topmost element at a viewport point.',
128
- 'styles.get_computed': 'Read computed style properties for one element.',
129
- 'styles.get_matched_rules': 'Read matched CSS rule context for one element.',
139
+ 'styles.get_computed':
140
+ 'Read requested computed styles; omission returns display, position, width, height, and color.',
141
+ 'styles.get_matched_rules':
142
+ 'Read element classes and inline style context (not stylesheet cascade data).',
130
143
  'viewport.scroll': 'Scroll the viewport or a scrollable element.',
131
144
  'viewport.resize': 'Resize or reset the tab viewport.',
132
- 'input.click': 'Click an element.',
133
- 'input.focus': 'Focus an element.',
134
- 'input.type': 'Type text into an element.',
135
- 'input.fill': 'Set value of an input/textarea element (React/Vue/Angular-safe).',
145
+ 'input.click': 'Actionability-check and click an element through DOM or optional CDP input.',
146
+ 'input.focus': 'Actionability-check and focus an element through DOM input.',
147
+ 'input.type': 'Actionability-check and type through DOM or optional CDP text input.',
148
+ 'input.fill':
149
+ 'Fill an editable target using a DOM strategy or optional CDP text input; verify afterward.',
136
150
  'input.press_key': 'Send a key press to the page or an element.',
137
151
  'input.set_checked': 'Set checkbox or radio checked state.',
138
152
  'input.select_option': 'Select options in a select element.',
139
- 'input.hover': 'Hover over an element.',
140
- 'input.drag': 'Drag from one element to another.',
153
+ 'input.hover': 'Actionability-check and hover through DOM or optional CDP pointer input.',
154
+ 'input.drag': 'Actionability-check and drag through DOM or optional CDP pointer input.',
141
155
  'input.scroll_into_view': 'Scroll an element into the visible viewport.',
142
156
  'screenshot.capture_region': 'Capture a screenshot of a viewport region.',
143
157
  'screenshot.capture_element': 'Capture a screenshot of one element.',
@@ -146,7 +160,8 @@ const BRIDGE_METHOD_DESCRIPTIONS = Object.freeze({
146
160
  'patch.apply_dom': 'Apply a reversible DOM patch.',
147
161
  'patch.list': 'List active reversible patches.',
148
162
  'patch.rollback': 'Rollback one reversible patch.',
149
- 'patch.commit_session_baseline': 'Commit the current patch session baseline.',
163
+ 'patch.commit_session_baseline':
164
+ 'Keep current document mutations and discard their rollback history.',
150
165
  'cdp.get_document': 'Read the CDP DOM document tree.',
151
166
  'cdp.get_dom_snapshot': 'Read a CDP DOM snapshot.',
152
167
  'cdp.get_box_model': 'Read a CDP box model for a node.',
@@ -195,7 +210,7 @@ function createRegistryEntry(
195
210
  /** @type {Readonly<Record<import('./types.js').BridgeMethod, BridgeMethodRegistryEntry>>} */
196
211
  export const BRIDGE_METHOD_REGISTRY = Object.freeze({
197
212
  // system - trivial
198
- 'access.request': createRegistryEntry('access.request', 'system', false, [], 'trivial'),
213
+ 'access.request': createRegistryEntry('access.request', 'system', false, ['intent'], 'trivial'),
199
214
  'skill.get_runtime_context': createRegistryEntry(
200
215
  'skill.get_runtime_context',
201
216
  'system',
@@ -265,11 +280,19 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
265
280
  'low',
266
281
  METHOD_POLICIES.pageRead
267
282
  ),
283
+ 'page.handle_dialog': createRegistryEntry(
284
+ 'page.handle_dialog',
285
+ 'page',
286
+ true,
287
+ ['action', 'promptText', 'expectedDialogId'],
288
+ 'low',
289
+ METHOD_POLICIES.navigationControlDebugger
290
+ ),
268
291
  'page.wait_for_load_state': createRegistryEntry(
269
292
  'page.wait_for_load_state',
270
293
  'wait',
271
294
  true,
272
- ['timeoutMs'],
295
+ ['waitForLoad', 'timeoutMs', 'url', 'urlMatch'],
273
296
  'low',
274
297
  METHOD_POLICIES.pageRead
275
298
  ),
@@ -289,11 +312,19 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
289
312
  'low',
290
313
  METHOD_POLICIES.pageRead
291
314
  ),
315
+ 'page.extract_content': createRegistryEntry(
316
+ 'page.extract_content',
317
+ 'page',
318
+ true,
319
+ ['format', 'selector', 'includeMetadata', 'consistency', 'textBudget', 'settleTimeoutMs'],
320
+ 'moderate',
321
+ METHOD_POLICIES.pageRead
322
+ ),
292
323
  'page.get_network': createRegistryEntry(
293
324
  'page.get_network',
294
325
  'page',
295
326
  true,
296
- ['clear', 'limit', 'urlPattern'],
327
+ ['clear', 'limit', 'urlPattern', 'source', 'capture'],
297
328
  'low',
298
329
  METHOD_POLICIES.networkRead
299
330
  ),
@@ -384,7 +415,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
384
415
  'dom.describe',
385
416
  'inspect',
386
417
  true,
387
- ['elementRef'],
418
+ ['elementRef', 'target'],
388
419
  'low',
389
420
  METHOD_POLICIES.domRead
390
421
  ),
@@ -392,7 +423,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
392
423
  'dom.get_text',
393
424
  'inspect',
394
425
  true,
395
- ['elementRef', 'textBudget'],
426
+ ['elementRef', 'target', 'textBudget'],
396
427
  'low',
397
428
  METHOD_POLICIES.domRead
398
429
  ),
@@ -400,7 +431,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
400
431
  'dom.get_attributes',
401
432
  'inspect',
402
433
  true,
403
- ['elementRef', 'attributes'],
434
+ ['elementRef', 'target', 'attributes'],
404
435
  'low',
405
436
  METHOD_POLICIES.domRead
406
437
  ),
@@ -432,7 +463,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
432
463
  'dom.get_html',
433
464
  'inspect',
434
465
  true,
435
- ['elementRef', 'outer', 'maxLength'],
466
+ ['elementRef', 'target', 'outer', 'maxLength'],
436
467
  'low',
437
468
  METHOD_POLICIES.domRead
438
469
  ),
@@ -440,7 +471,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
440
471
  'dom.get_accessibility_tree',
441
472
  'inspect',
442
473
  true,
443
- ['maxNodes', 'maxDepth'],
474
+ ['selector', 'maxNodes', 'maxDepth', 'compact', 'interactiveOnly'],
444
475
  'moderate',
445
476
  METHOD_POLICIES.domReadDebugger
446
477
  ),
@@ -449,7 +480,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
449
480
  'layout.get_box_model',
450
481
  'inspect',
451
482
  true,
452
- ['elementRef'],
483
+ ['elementRef', 'target'],
453
484
  'low',
454
485
  METHOD_POLICIES.layoutRead
455
486
  ),
@@ -466,7 +497,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
466
497
  'styles.get_computed',
467
498
  'inspect',
468
499
  true,
469
- ['elementRef', 'properties'],
500
+ ['elementRef', 'target', 'properties'],
470
501
  'low',
471
502
  METHOD_POLICIES.stylesRead
472
503
  ),
@@ -474,7 +505,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
474
505
  'styles.get_matched_rules',
475
506
  'inspect',
476
507
  true,
477
- ['elementRef'],
508
+ ['elementRef', 'target'],
478
509
  'moderate',
479
510
  METHOD_POLICIES.stylesRead
480
511
  ),
@@ -500,7 +531,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
500
531
  'input.click',
501
532
  'interact',
502
533
  true,
503
- ['target', 'button', 'clickCount', 'modifiers'],
534
+ ['target', 'button', 'clickCount', 'modifiers', 'executionMode', 'recoverStale'],
504
535
  'low',
505
536
  METHOD_POLICIES.automationInput
506
537
  ),
@@ -508,7 +539,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
508
539
  'input.focus',
509
540
  'interact',
510
541
  true,
511
- ['target'],
542
+ ['target', 'executionMode', 'recoverStale'],
512
543
  'low',
513
544
  METHOD_POLICIES.automationInput
514
545
  ),
@@ -516,7 +547,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
516
547
  'input.type',
517
548
  'interact',
518
549
  true,
519
- ['target', 'text', 'clear', 'submit', 'modifiers'],
550
+ ['target', 'text', 'clear', 'submit', 'modifiers', 'executionMode', 'recoverStale'],
520
551
  'low',
521
552
  METHOD_POLICIES.automationInput
522
553
  ),
@@ -524,7 +555,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
524
555
  'input.fill',
525
556
  'interact',
526
557
  true,
527
- ['target', 'value', 'mode'],
558
+ ['target', 'value', 'mode', 'executionMode', 'recoverStale'],
528
559
  'low',
529
560
  METHOD_POLICIES.automationInput
530
561
  ),
@@ -532,7 +563,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
532
563
  'input.press_key',
533
564
  'interact',
534
565
  true,
535
- ['target', 'key', 'modifiers'],
566
+ ['target', 'key', 'modifiers', 'executionMode', 'recoverStale'],
536
567
  'low',
537
568
  METHOD_POLICIES.automationInput
538
569
  ),
@@ -540,7 +571,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
540
571
  'input.set_checked',
541
572
  'interact',
542
573
  true,
543
- ['target', 'checked'],
574
+ ['target', 'checked', 'executionMode', 'recoverStale'],
544
575
  'low',
545
576
  METHOD_POLICIES.automationInput
546
577
  ),
@@ -548,7 +579,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
548
579
  'input.select_option',
549
580
  'interact',
550
581
  true,
551
- ['target', 'values', 'labels', 'indexes'],
582
+ ['target', 'values', 'labels', 'indexes', 'executionMode', 'recoverStale'],
552
583
  'low',
553
584
  METHOD_POLICIES.automationInput
554
585
  ),
@@ -556,7 +587,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
556
587
  'input.hover',
557
588
  'interact',
558
589
  true,
559
- ['target', 'duration', 'modifiers'],
590
+ ['target', 'duration', 'modifiers', 'executionMode', 'recoverStale'],
560
591
  'low',
561
592
  METHOD_POLICIES.automationInput
562
593
  ),
@@ -564,7 +595,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
564
595
  'input.drag',
565
596
  'interact',
566
597
  true,
567
- ['source', 'destination', 'offsetX', 'offsetY'],
598
+ ['source', 'destination', 'offsetX', 'offsetY', 'executionMode', 'recoverStale'],
568
599
  'moderate',
569
600
  METHOD_POLICIES.automationInput
570
601
  ),
@@ -581,7 +612,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
581
612
  'screenshot.capture_region',
582
613
  'capture',
583
614
  true,
584
- ['x', 'y', 'width', 'height'],
615
+ ['x', 'y', 'width', 'height', 'format', 'quality'],
585
616
  'high',
586
617
  METHOD_POLICIES.screenshotPartialDebugger
587
618
  ),
@@ -589,7 +620,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
589
620
  'screenshot.capture_element',
590
621
  'capture',
591
622
  true,
592
- ['elementRef'],
623
+ ['elementRef', 'format', 'quality'],
593
624
  'high',
594
625
  METHOD_POLICIES.screenshotPartialDebugger
595
626
  ),
@@ -597,7 +628,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
597
628
  'screenshot.capture_full_page',
598
629
  'capture',
599
630
  true,
600
- [],
631
+ ['format', 'quality'],
601
632
  'high',
602
633
  METHOD_POLICIES.screenshotPartialDebugger
603
634
  ),
@@ -68,16 +68,31 @@ const ACTION_SUMMARIES = {
68
68
  },
69
69
  closed: { text: (r) => `Tab ${r.tabId} closed.`, evidence: (r) => r },
70
70
  clicked: {
71
- text: (r) => `Clicked ${r.elementRef ?? 'element'}.`,
72
- evidence: (r) => ({ elementRef: r.elementRef }),
71
+ text: (r) => {
72
+ const mode = toRecord(r.execution).actualMode;
73
+ return `Clicked ${r.elementRef ?? 'element'}${typeof mode === 'string' ? ` via ${mode}` : ''}.`;
74
+ },
75
+ evidence: (r) => ({
76
+ elementRef: r.elementRef,
77
+ resolution: r.resolution,
78
+ execution: r.execution,
79
+ }),
73
80
  },
74
81
  focused: {
75
82
  text: (r) => `Focused ${r.elementRef ?? 'element'}.`,
76
- evidence: (r) => ({ elementRef: r.elementRef }),
83
+ evidence: (r) => ({
84
+ elementRef: r.elementRef,
85
+ resolution: r.resolution,
86
+ execution: r.execution,
87
+ }),
77
88
  },
78
89
  typed: {
79
90
  text: (r) => `Typed into ${r.elementRef ?? 'element'}.`,
80
- evidence: (r) => ({ elementRef: r.elementRef }),
91
+ evidence: (r) => ({
92
+ elementRef: r.elementRef,
93
+ resolution: r.resolution,
94
+ execution: r.execution,
95
+ }),
81
96
  },
82
97
  pressed: {
83
98
  text: (r) => `Key pressed${r.key ? ` (${r.key})` : ''}.`,
@@ -179,6 +194,29 @@ export function summarizeBridgeResponse(response, method) {
179
194
  }
180
195
 
181
196
  const result = toRecord(response.result);
197
+ if (method === 'page.handle_dialog') {
198
+ const action = typeof result.action === 'string' ? result.action : 'inspect';
199
+ return {
200
+ ok: true,
201
+ summary: appendProtocolWarning(
202
+ result.commandDispatched === true
203
+ ? `JavaScript dialog ${action} command dispatched to the current dialog; Chrome did not atomically bind it to the observation identifier.`
204
+ : `JavaScript ${result.type ?? 'unknown'} dialog is open.`,
205
+ protocolWarning
206
+ ),
207
+ evidence: result,
208
+ };
209
+ }
210
+ if (method === 'page.wait_for_load_state' && typeof result.finalUrl === 'string') {
211
+ return {
212
+ ok: true,
213
+ summary: appendProtocolWarning(
214
+ `URL matched via ${result.observedNavigationKind ?? 'unknown'} after ${result.elapsedMs ?? 0}ms.`,
215
+ protocolWarning
216
+ ),
217
+ evidence: result,
218
+ };
219
+ }
182
220
  if (typeof result.daemon === 'string') {
183
221
  const access =
184
222
  result.access && typeof result.access === 'object'
@@ -232,6 +270,23 @@ export function summarizeBridgeResponse(response, method) {
232
270
  evidence: result,
233
271
  };
234
272
  }
273
+ if (typeof result.content === 'string' && typeof result.source === 'string') {
274
+ return {
275
+ ok: true,
276
+ summary: appendProtocolWarning(
277
+ `Extracted ${result.source} ${result.format ?? 'text'}: ${result.length ?? result.content.length} chars${result.truncated ? ' (truncated)' : ''}.`,
278
+ protocolWarning
279
+ ),
280
+ evidence: {
281
+ content: result.content.slice(0, 500),
282
+ format: result.format,
283
+ source: result.source,
284
+ length: result.length,
285
+ truncated: result.truncated,
286
+ settlement: result.settlement,
287
+ },
288
+ };
289
+ }
235
290
  if (
236
291
  typeof result.url === 'string' &&
237
292
  typeof result.title === 'string' &&
@@ -466,7 +521,9 @@ export function summarizeBridgeResponse(response, method) {
466
521
  if (
467
522
  Array.isArray(result.entries) &&
468
523
  (result.entries.length > 0
469
- ? result.entries[0]?.type === 'fetch' || result.entries[0]?.type === 'xhr'
524
+ ? result.entries[0]?.type === 'fetch' ||
525
+ result.entries[0]?.type === 'xhr' ||
526
+ typeof result.entries[0]?.resourceType === 'string'
470
527
  : method === 'page.get_network')
471
528
  ) {
472
529
  const entries = /** @type {Array<Record<string, unknown>>} */ (result.entries);
@@ -481,6 +538,10 @@ export function summarizeBridgeResponse(response, method) {
481
538
  url: truncateUrl(/** @type {string} */ (e.url)),
482
539
  status: e.status,
483
540
  duration: e.duration,
541
+ ...(typeof e.resourceType === 'string' ? { resourceType: e.resourceType } : {}),
542
+ ...(typeof e.failureReason === 'string' && e.failureReason
543
+ ? { failureReason: e.failureReason }
544
+ : {}),
484
545
  })),
485
546
  };
486
547
  }
@@ -524,6 +585,27 @@ export function summarizeBridgeResponse(response, method) {
524
585
  };
525
586
  }
526
587
  }
588
+ if (method?.startsWith('input.') && result.execution && typeof result.execution === 'object') {
589
+ const execution = toRecord(result.execution);
590
+ return {
591
+ ok: true,
592
+ summary: appendProtocolWarning(
593
+ `${method} completed via ${execution.actualMode ?? 'dom'}${result.elementRef ? ` on ${result.elementRef}` : ''}.`,
594
+ protocolWarning
595
+ ),
596
+ evidence: {
597
+ elementRef: result.elementRef ?? null,
598
+ sourceRef: result.sourceRef ?? null,
599
+ destinationRef: result.destinationRef ?? null,
600
+ typed: result.typed,
601
+ checked: result.checked,
602
+ selectedValues: result.selectedValues,
603
+ mode: result.mode,
604
+ resolution: result.resolution,
605
+ execution: result.execution,
606
+ },
607
+ };
608
+ }
527
609
  if (typeof result.tabId === 'number' && typeof result.url === 'string') {
528
610
  const actionMethod = typeof result.method === 'string' ? result.method : method;
529
611
  if (actionMethod === 'navigation.navigate') {