@browserbridge/bbx 1.0.0 → 1.0.1

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 (52) hide show
  1. package/README.md +3 -1
  2. package/docs/api-reference.md +33 -33
  3. package/docs/mcp-vs-cli.md +104 -104
  4. package/docs/publishing.md +1 -3
  5. package/docs/quickstart.md +6 -6
  6. package/docs/unpacked-extension.md +72 -0
  7. package/manifest.json +3 -17
  8. package/package.json +44 -42
  9. package/packages/agent-client/src/cli-helpers.js +10 -5
  10. package/packages/agent-client/src/cli.js +65 -135
  11. package/packages/agent-client/src/client.js +37 -17
  12. package/packages/agent-client/src/command-registry.js +101 -69
  13. package/packages/agent-client/src/detect.js +3 -6
  14. package/packages/agent-client/src/install.js +10 -27
  15. package/packages/agent-client/src/mcp-config.js +11 -30
  16. package/packages/agent-client/src/runtime.js +41 -20
  17. package/packages/agent-client/src/setup-status.js +13 -28
  18. package/packages/extension/src/background-helpers.js +51 -36
  19. package/packages/extension/src/background-routing.js +11 -13
  20. package/packages/extension/src/background.js +562 -299
  21. package/packages/extension/src/content-script-helpers.js +17 -16
  22. package/packages/extension/src/content-script.js +175 -109
  23. package/packages/extension/src/sidepanel-helpers.js +3 -1
  24. package/packages/extension/ui/popup.js +39 -20
  25. package/packages/extension/ui/sidepanel.js +108 -191
  26. package/packages/extension/ui/ui.css +2 -1
  27. package/packages/mcp-server/src/handlers.js +546 -250
  28. package/packages/mcp-server/src/server.js +558 -257
  29. package/packages/native-host/bin/bridge-daemon.js +6 -2
  30. package/packages/native-host/bin/install-manifest.js +2 -2
  31. package/packages/native-host/bin/postinstall.js +4 -2
  32. package/packages/native-host/src/config.js +11 -7
  33. package/packages/native-host/src/daemon.js +143 -92
  34. package/packages/native-host/src/install-manifest.js +73 -22
  35. package/packages/native-host/src/native-host.js +55 -40
  36. package/packages/protocol/src/budget.js +3 -7
  37. package/packages/protocol/src/capabilities.js +3 -3
  38. package/packages/protocol/src/errors.js +11 -11
  39. package/packages/protocol/src/protocol.js +104 -71
  40. package/packages/protocol/src/registry.js +300 -45
  41. package/packages/protocol/src/summary.js +249 -106
  42. package/packages/protocol/src/types.js +1 -1
  43. package/skills/browser-bridge/SKILL.md +1 -1
  44. package/skills/browser-bridge/agents/openai.yaml +3 -3
  45. package/skills/browser-bridge/references/interaction.md +33 -11
  46. package/skills/browser-bridge/references/patch-workflow.md +3 -0
  47. package/skills/browser-bridge/references/protocol.md +125 -70
  48. package/skills/browser-bridge/references/tailwind.md +12 -11
  49. package/skills/browser-bridge/references/token-efficiency.md +23 -22
  50. package/skills/browser-bridge/references/ui-workflows.md +8 -0
  51. package/packages/extension/ui/offscreen.html +0 -6
  52. package/packages/extension/ui/offscreen.js +0 -61
@@ -17,12 +17,9 @@ import {
17
17
  getDoctorReport,
18
18
  requestBridge,
19
19
  resolveRef,
20
- withBridgeClient
20
+ withBridgeClient,
21
21
  } from '../../agent-client/src/runtime.js';
22
- import {
23
- annotateBridgeSummary,
24
- summarizeBridgeResponse,
25
- } from '../../agent-client/src/subagent.js';
22
+ import { annotateBridgeSummary, summarizeBridgeResponse } from '../../agent-client/src/subagent.js';
26
23
 
27
24
  /** @typedef {import('../../protocol/src/types.js').BridgeMethod} BridgeMethod */
28
25
  /** @typedef {import('../../protocol/src/types.js').BridgeResponse} BridgeResponse */
@@ -47,7 +44,7 @@ function createToolResult(summary, structuredContent = {}, isError = false) {
47
44
  const toolResult = {
48
45
  content: [{ type: /** @type {'text'} */ ('text'), text: summary }],
49
46
  structuredContent,
50
- ...(isError ? { isError: true } : {})
47
+ ...(isError ? { isError: true } : {}),
51
48
  };
52
49
  const delivered = estimateJsonPayloadCost(toolResult);
53
50
  return {
@@ -77,10 +74,14 @@ function summarizeToolResponse(response, method) {
77
74
  */
78
75
  function summarizeToolError(error) {
79
76
  const message = error instanceof Error ? error.message : String(error);
80
- return createToolResult(`ERROR: ${message}`, {
81
- ok: false,
82
- evidence: null
83
- }, true);
77
+ return createToolResult(
78
+ `ERROR: ${message}`,
79
+ {
80
+ ok: false,
81
+ evidence: null,
82
+ },
83
+ true
84
+ );
84
85
  }
85
86
 
86
87
  /**
@@ -235,12 +236,12 @@ function applyHtmlBudgetPreset(args) {
235
236
  */
236
237
  async function requestBridgeWithRetry(client, method, params, options) {
237
238
  const response = await requestBridge(client, method, params, options);
238
- const recovery = !response.ok && response.error
239
- ? getErrorRecovery(response.error.code)
240
- : null;
239
+ const recovery = !response.ok && response.error ? getErrorRecovery(response.error.code) : null;
241
240
  if (!response.ok && recovery?.retry) {
242
241
  const delay = recovery.retryAfterMs ?? 1000;
243
- process.stderr.write(`[bbx-mcp] Retrying ${method} after ${delay}ms (${response.error.code})\n`);
242
+ process.stderr.write(
243
+ `[bbx-mcp] Retrying ${method} after ${delay}ms (${response.error.code})\n`
244
+ );
244
245
  await new Promise((r) => setTimeout(r, delay));
245
246
  return requestBridge(client, method, params, options);
246
247
  }
@@ -285,10 +286,10 @@ async function dispatchToolAction(actions, args, toolName) {
285
286
  const requestedTabId = typeof args.tabId === 'number' ? args.tabId : null;
286
287
  const ref = entry.ref
287
288
  ? await resolveToolRef(
288
- client,
289
- /** @type {{ elementRef?: string, selector?: string }} */ (args),
290
- requestedTabId,
291
- )
289
+ client,
290
+ /** @type {{ elementRef?: string, selector?: string }} */ (args),
291
+ requestedTabId
292
+ )
292
293
  : undefined;
293
294
  const response = await requestBridgeWithRetry(client, entry.method, entry.params(args, ref), {
294
295
  tabId: requestedTabId,
@@ -305,12 +306,13 @@ async function dispatchToolAction(actions, args, toolName) {
305
306
  export async function handleStatusTool() {
306
307
  try {
307
308
  const report = await getDoctorReport();
308
- const summary = report.issues.length === 0
309
- ? 'Browser Bridge is ready.'
310
- : `Browser Bridge has ${report.issues.length} setup issue(s).`;
309
+ const summary =
310
+ report.issues.length === 0
311
+ ? 'Browser Bridge is ready.'
312
+ : `Browser Bridge has ${report.issues.length} readiness issue(s).`;
311
313
  return createToolResult(summary, {
312
314
  ok: report.issues.length === 0,
313
- evidence: report
315
+ evidence: report,
314
316
  });
315
317
  } catch (error) {
316
318
  return summarizeToolError(error);
@@ -328,7 +330,7 @@ export async function handleTabsTool(args) {
328
330
  if (args.action === 'create') {
329
331
  return callBridgeTool('tabs.create', {
330
332
  url: args.url,
331
- active: args.active
333
+ active: args.active,
332
334
  });
333
335
  }
334
336
  if (args.action === 'close') {
@@ -343,15 +345,78 @@ export async function handleTabsTool(args) {
343
345
  /**
344
346
  /** @type {Record<string, ToolAction>} */
345
347
  export const DOM_ACTIONS = {
346
- query: { ref: false, method: 'dom.query', params: a => ({ selector: a.selector || 'body', withinRef: a.withinRef, maxNodes: a.maxNodes, maxDepth: a.maxDepth, textBudget: a.textBudget, includeBbox: a.includeBbox, attributeAllowlist: a.attributeAllowlist }) },
347
- describe: { ref: true, method: 'dom.describe', params: (_, r) => ({ elementRef: r }) },
348
- text: { ref: true, method: 'dom.get_text', params: (a, r) => ({ elementRef: r, textBudget: a.textBudget }) },
349
- attributes: { ref: true, method: 'dom.get_attributes', params: (a, r) => ({ elementRef: r, attributes: a.attributes || [] }) },
350
- wait: { ref: false, method: 'dom.wait_for', params: a => ({ selector: a.selector, text: a.text, state: a.state, timeoutMs: a.timeoutMs }) },
351
- find_text: { ref: false, method: 'dom.find_by_text', params: a => ({ text: a.text, exact: a.exact, selector: a.selector, maxResults: a.maxResults }) },
352
- find_role: { ref: false, method: 'dom.find_by_role', params: a => ({ role: a.role, name: a.name, selector: a.selector, maxResults: a.maxResults }) },
353
- html: { ref: true, method: 'dom.get_html', params: (a, r) => ({ elementRef: r, outer: a.outer, maxLength: a.maxLength }) },
354
- accessibility_tree: { ref: false, method: 'dom.get_accessibility_tree', params: a => ({ maxNodes: a.maxNodes, maxDepth: a.maxDepth }) },
348
+ query: {
349
+ ref: false,
350
+ method: 'dom.query',
351
+ params: (a) => ({
352
+ selector: a.selector || 'body',
353
+ withinRef: a.withinRef,
354
+ maxNodes: a.maxNodes,
355
+ maxDepth: a.maxDepth,
356
+ textBudget: a.textBudget,
357
+ includeBbox: a.includeBbox,
358
+ attributeAllowlist: a.attributeAllowlist,
359
+ }),
360
+ },
361
+ describe: {
362
+ ref: true,
363
+ method: 'dom.describe',
364
+ params: (_, r) => ({ elementRef: r }),
365
+ },
366
+ text: {
367
+ ref: true,
368
+ method: 'dom.get_text',
369
+ params: (a, r) => ({ elementRef: r, textBudget: a.textBudget }),
370
+ },
371
+ attributes: {
372
+ ref: true,
373
+ method: 'dom.get_attributes',
374
+ params: (a, r) => ({ elementRef: r, attributes: a.attributes || [] }),
375
+ },
376
+ wait: {
377
+ ref: false,
378
+ method: 'dom.wait_for',
379
+ params: (a) => ({
380
+ selector: a.selector,
381
+ text: a.text,
382
+ state: a.state,
383
+ timeoutMs: a.timeoutMs,
384
+ }),
385
+ },
386
+ find_text: {
387
+ ref: false,
388
+ method: 'dom.find_by_text',
389
+ params: (a) => ({
390
+ text: a.text,
391
+ exact: a.exact,
392
+ selector: a.selector,
393
+ maxResults: a.maxResults,
394
+ }),
395
+ },
396
+ find_role: {
397
+ ref: false,
398
+ method: 'dom.find_by_role',
399
+ params: (a) => ({
400
+ role: a.role,
401
+ name: a.name,
402
+ selector: a.selector,
403
+ maxResults: a.maxResults,
404
+ }),
405
+ },
406
+ html: {
407
+ ref: true,
408
+ method: 'dom.get_html',
409
+ params: (a, r) => ({
410
+ elementRef: r,
411
+ outer: a.outer,
412
+ maxLength: a.maxLength,
413
+ }),
414
+ },
415
+ accessibility_tree: {
416
+ ref: false,
417
+ method: 'dom.get_accessibility_tree',
418
+ params: (a) => ({ maxNodes: a.maxNodes, maxDepth: a.maxDepth }),
419
+ },
355
420
  };
356
421
 
357
422
  /**
@@ -375,10 +440,26 @@ export async function handleDomTool(args) {
375
440
 
376
441
  /** @type {Record<string, ToolAction>} */
377
442
  export const STYLES_LAYOUT_ACTIONS = {
378
- computed: { ref: true, method: 'styles.get_computed', params: (a, r) => ({ elementRef: r, properties: a.properties }) },
379
- matched_rules: { ref: true, method: 'styles.get_matched_rules', params: (_, r) => ({ elementRef: r }) },
380
- box_model: { ref: true, method: 'layout.get_box_model', params: (_, r) => ({ elementRef: r }) },
381
- hit_test: { ref: false, method: 'layout.hit_test', params: a => ({ x: a.x, y: a.y }) },
443
+ computed: {
444
+ ref: true,
445
+ method: 'styles.get_computed',
446
+ params: (a, r) => ({ elementRef: r, properties: a.properties }),
447
+ },
448
+ matched_rules: {
449
+ ref: true,
450
+ method: 'styles.get_matched_rules',
451
+ params: (_, r) => ({ elementRef: r }),
452
+ },
453
+ box_model: {
454
+ ref: true,
455
+ method: 'layout.get_box_model',
456
+ params: (_, r) => ({ elementRef: r }),
457
+ },
458
+ hit_test: {
459
+ ref: false,
460
+ method: 'layout.hit_test',
461
+ params: (a) => ({ x: a.x, y: a.y }),
462
+ },
382
463
  };
383
464
 
384
465
  /**
@@ -391,14 +472,41 @@ export async function handleStylesLayoutTool(args) {
391
472
 
392
473
  /** @type {Record<string, { method: BridgeMethod, params: (a: Record<string, unknown>) => Record<string, unknown> }>} */
393
474
  export const PAGE_ACTIONS = {
394
- state: { method: 'page.get_state', params: () => ({}) },
395
- evaluate: { method: 'page.evaluate', params: a => ({ expression: a.expression, awaitPromise: a.awaitPromise, timeoutMs: a.timeoutMs, returnByValue: a.returnByValue }) },
396
- console: { method: 'page.get_console', params: a => ({ level: a.level, clear: a.clear, limit: a.limit }) },
397
- wait_for_load: { method: 'page.wait_for_load_state', params: a => ({ timeoutMs: a.timeoutMs }) },
398
- storage: { method: 'page.get_storage', params: a => ({ type: a.type, keys: a.keys }) },
399
- text: { method: 'page.get_text', params: a => ({ textBudget: a.textBudget }) },
400
- network: { method: 'page.get_network', params: a => ({ clear: a.clear, limit: a.limit, urlPattern: a.urlPattern }) },
401
- performance: { method: 'performance.get_metrics', params: () => ({}) },
475
+ state: { method: 'page.get_state', params: () => ({}) },
476
+ evaluate: {
477
+ method: 'page.evaluate',
478
+ params: (a) => ({
479
+ expression: a.expression,
480
+ awaitPromise: a.awaitPromise,
481
+ timeoutMs: a.timeoutMs,
482
+ returnByValue: a.returnByValue,
483
+ }),
484
+ },
485
+ console: {
486
+ method: 'page.get_console',
487
+ params: (a) => ({ level: a.level, clear: a.clear, limit: a.limit }),
488
+ },
489
+ wait_for_load: {
490
+ method: 'page.wait_for_load_state',
491
+ params: (a) => ({ timeoutMs: a.timeoutMs }),
492
+ },
493
+ storage: {
494
+ method: 'page.get_storage',
495
+ params: (a) => ({ type: a.type, keys: a.keys }),
496
+ },
497
+ text: {
498
+ method: 'page.get_text',
499
+ params: (a) => ({ textBudget: a.textBudget }),
500
+ },
501
+ network: {
502
+ method: 'page.get_network',
503
+ params: (a) => ({
504
+ clear: a.clear,
505
+ limit: a.limit,
506
+ urlPattern: a.urlPattern,
507
+ }),
508
+ },
509
+ performance: { method: 'performance.get_metrics', params: () => ({}) },
402
510
  };
403
511
 
404
512
  /**
@@ -432,12 +540,39 @@ export async function handlePageTool(args) {
432
540
 
433
541
  /** @type {Record<string, { method: BridgeMethod, params: (a: Record<string, unknown>) => Record<string, unknown> }>} */
434
542
  export const NAVIGATION_ACTIONS = {
435
- navigate: { method: 'navigation.navigate', params: a => ({ url: a.url, waitForLoad: a.waitForLoad, timeoutMs: a.timeoutMs }) },
436
- reload: { method: 'navigation.reload', params: a => ({ waitForLoad: a.waitForLoad, timeoutMs: a.timeoutMs }) },
437
- go_back: { method: 'navigation.go_back', params: a => ({ waitForLoad: a.waitForLoad, timeoutMs: a.timeoutMs }) },
438
- go_forward: { method: 'navigation.go_forward', params: a => ({ waitForLoad: a.waitForLoad, timeoutMs: a.timeoutMs }) },
439
- scroll: { method: 'viewport.scroll', params: a => ({ top: a.top, left: a.left, behavior: a.behavior, relative: a.relative }) },
440
- resize: { method: 'viewport.resize', params: a => ({ width: a.width, height: a.height, reset: a.reset }) },
543
+ navigate: {
544
+ method: 'navigation.navigate',
545
+ params: (a) => ({
546
+ url: a.url,
547
+ waitForLoad: a.waitForLoad,
548
+ timeoutMs: a.timeoutMs,
549
+ }),
550
+ },
551
+ reload: {
552
+ method: 'navigation.reload',
553
+ params: (a) => ({ waitForLoad: a.waitForLoad, timeoutMs: a.timeoutMs }),
554
+ },
555
+ go_back: {
556
+ method: 'navigation.go_back',
557
+ params: (a) => ({ waitForLoad: a.waitForLoad, timeoutMs: a.timeoutMs }),
558
+ },
559
+ go_forward: {
560
+ method: 'navigation.go_forward',
561
+ params: (a) => ({ waitForLoad: a.waitForLoad, timeoutMs: a.timeoutMs }),
562
+ },
563
+ scroll: {
564
+ method: 'viewport.scroll',
565
+ params: (a) => ({
566
+ top: a.top,
567
+ left: a.left,
568
+ behavior: a.behavior,
569
+ relative: a.relative,
570
+ }),
571
+ },
572
+ resize: {
573
+ method: 'viewport.resize',
574
+ params: (a) => ({ width: a.width, height: a.height, reset: a.reset }),
575
+ },
441
576
  };
442
577
 
443
578
  /**
@@ -463,7 +598,7 @@ export const INPUT_ACTION_METHODS = {
463
598
  select_option: 'input.select_option',
464
599
  hover: 'input.hover',
465
600
  drag: 'input.drag',
466
- scroll_into_view: 'input.scroll_into_view'
601
+ scroll_into_view: 'input.scroll_into_view',
467
602
  };
468
603
 
469
604
  /**
@@ -479,118 +614,173 @@ export async function handleInputTool(args) {
479
614
 
480
615
  switch (args.action) {
481
616
  case 'click': {
482
- const response = await requestBridge(client, 'input.click', {
483
- target: await elementTarget(),
484
- button: args.button,
485
- clickCount: args.clickCount
486
- }, {
487
- tabId: requestedTabId,
488
- source: REQUEST_SOURCE,
489
- tokenBudget: getToolTokenBudget(args),
490
- });
617
+ const response = await requestBridge(
618
+ client,
619
+ 'input.click',
620
+ {
621
+ target: await elementTarget(),
622
+ button: args.button,
623
+ clickCount: args.clickCount,
624
+ },
625
+ {
626
+ tabId: requestedTabId,
627
+ source: REQUEST_SOURCE,
628
+ tokenBudget: getToolTokenBudget(args),
629
+ }
630
+ );
491
631
  return summarizeToolResponse(response, 'input.click');
492
632
  }
493
633
  case 'focus': {
494
- const response = await requestBridge(client, 'input.focus', {
495
- target: await elementTarget()
496
- }, {
497
- tabId: requestedTabId,
498
- source: REQUEST_SOURCE,
499
- tokenBudget: getToolTokenBudget(args),
500
- });
634
+ const response = await requestBridge(
635
+ client,
636
+ 'input.focus',
637
+ {
638
+ target: await elementTarget(),
639
+ },
640
+ {
641
+ tabId: requestedTabId,
642
+ source: REQUEST_SOURCE,
643
+ tokenBudget: getToolTokenBudget(args),
644
+ }
645
+ );
501
646
  return summarizeToolResponse(response, 'input.focus');
502
647
  }
503
648
  case 'type': {
504
- const response = await requestBridge(client, 'input.type', {
505
- target: await elementTarget(),
506
- text: args.text,
507
- clear: args.clear,
508
- submit: args.submit
509
- }, {
510
- tabId: requestedTabId,
511
- source: REQUEST_SOURCE,
512
- tokenBudget: getToolTokenBudget(args),
513
- });
649
+ const response = await requestBridge(
650
+ client,
651
+ 'input.type',
652
+ {
653
+ target: await elementTarget(),
654
+ text: args.text,
655
+ clear: args.clear,
656
+ submit: args.submit,
657
+ },
658
+ {
659
+ tabId: requestedTabId,
660
+ source: REQUEST_SOURCE,
661
+ tokenBudget: getToolTokenBudget(args),
662
+ }
663
+ );
514
664
  return summarizeToolResponse(response, 'input.type');
515
665
  }
516
666
  case 'press_key': {
517
- const target = (args.elementRef || args.selector) ? await elementTarget() : undefined;
518
- const response = await requestBridge(client, 'input.press_key', {
519
- target,
520
- key: args.key,
521
- modifiers: args.modifiers
522
- }, {
523
- tabId: requestedTabId,
524
- source: REQUEST_SOURCE,
525
- tokenBudget: getToolTokenBudget(args),
526
- });
667
+ const target = args.elementRef || args.selector ? await elementTarget() : undefined;
668
+ const response = await requestBridge(
669
+ client,
670
+ 'input.press_key',
671
+ {
672
+ target,
673
+ key: args.key,
674
+ modifiers: args.modifiers,
675
+ },
676
+ {
677
+ tabId: requestedTabId,
678
+ source: REQUEST_SOURCE,
679
+ tokenBudget: getToolTokenBudget(args),
680
+ }
681
+ );
527
682
  return summarizeToolResponse(response, 'input.press_key');
528
683
  }
529
684
  case 'set_checked': {
530
- const response = await requestBridge(client, 'input.set_checked', {
531
- target: await elementTarget(),
532
- checked: args.checked
533
- }, {
534
- tabId: requestedTabId,
535
- source: REQUEST_SOURCE,
536
- tokenBudget: getToolTokenBudget(args),
537
- });
685
+ const response = await requestBridge(
686
+ client,
687
+ 'input.set_checked',
688
+ {
689
+ target: await elementTarget(),
690
+ checked: args.checked,
691
+ },
692
+ {
693
+ tabId: requestedTabId,
694
+ source: REQUEST_SOURCE,
695
+ tokenBudget: getToolTokenBudget(args),
696
+ }
697
+ );
538
698
  return summarizeToolResponse(response, 'input.set_checked');
539
699
  }
540
700
  case 'select_option': {
541
- const response = await requestBridge(client, 'input.select_option', {
542
- target: await elementTarget(),
543
- values: args.values,
544
- labels: args.labels,
545
- indexes: args.indexes
546
- }, {
547
- tabId: requestedTabId,
548
- source: REQUEST_SOURCE,
549
- tokenBudget: getToolTokenBudget(args),
550
- });
701
+ const response = await requestBridge(
702
+ client,
703
+ 'input.select_option',
704
+ {
705
+ target: await elementTarget(),
706
+ values: args.values,
707
+ labels: args.labels,
708
+ indexes: args.indexes,
709
+ },
710
+ {
711
+ tabId: requestedTabId,
712
+ source: REQUEST_SOURCE,
713
+ tokenBudget: getToolTokenBudget(args),
714
+ }
715
+ );
551
716
  return summarizeToolResponse(response, 'input.select_option');
552
717
  }
553
718
  case 'hover': {
554
- const response = await requestBridge(client, 'input.hover', {
555
- target: await elementTarget(),
556
- duration: args.duration
557
- }, {
558
- tabId: requestedTabId,
559
- source: REQUEST_SOURCE,
560
- tokenBudget: getToolTokenBudget(args),
561
- });
719
+ const response = await requestBridge(
720
+ client,
721
+ 'input.hover',
722
+ {
723
+ target: await elementTarget(),
724
+ duration: args.duration,
725
+ },
726
+ {
727
+ tabId: requestedTabId,
728
+ source: REQUEST_SOURCE,
729
+ tokenBudget: getToolTokenBudget(args),
730
+ }
731
+ );
562
732
  return summarizeToolResponse(response, 'input.hover');
563
733
  }
564
734
  case 'drag': {
565
735
  const source = {
566
- elementRef: args.sourceElementRef || (args.sourceSelector ? await resolveRef(client, args.sourceSelector, requestedTabId, REQUEST_SOURCE) : '')
736
+ elementRef:
737
+ args.sourceElementRef ||
738
+ (args.sourceSelector
739
+ ? await resolveRef(client, args.sourceSelector, requestedTabId, REQUEST_SOURCE)
740
+ : ''),
567
741
  };
568
742
  const destination = {
569
- elementRef: args.destinationElementRef || (args.destinationSelector ? await resolveRef(client, args.destinationSelector, requestedTabId, REQUEST_SOURCE) : '')
743
+ elementRef:
744
+ args.destinationElementRef ||
745
+ (args.destinationSelector
746
+ ? await resolveRef(client, args.destinationSelector, requestedTabId, REQUEST_SOURCE)
747
+ : ''),
570
748
  };
571
749
  if (!source.elementRef || !destination.elementRef) {
572
- return summarizeToolError('sourceElementRef/sourceSelector and destinationElementRef/destinationSelector are required for drag.');
750
+ return summarizeToolError(
751
+ 'sourceElementRef/sourceSelector and destinationElementRef/destinationSelector are required for drag.'
752
+ );
573
753
  }
574
- const response = await requestBridge(client, 'input.drag', {
575
- source,
576
- destination,
577
- offsetX: args.offsetX,
578
- offsetY: args.offsetY
579
- }, {
580
- tabId: requestedTabId,
581
- source: REQUEST_SOURCE,
582
- tokenBudget: getToolTokenBudget(args),
583
- });
754
+ const response = await requestBridge(
755
+ client,
756
+ 'input.drag',
757
+ {
758
+ source,
759
+ destination,
760
+ offsetX: args.offsetX,
761
+ offsetY: args.offsetY,
762
+ },
763
+ {
764
+ tabId: requestedTabId,
765
+ source: REQUEST_SOURCE,
766
+ tokenBudget: getToolTokenBudget(args),
767
+ }
768
+ );
584
769
  return summarizeToolResponse(response, 'input.drag');
585
770
  }
586
771
  case 'scroll_into_view': {
587
- const response = await requestBridge(client, 'input.scroll_into_view', {
588
- target: await elementTarget()
589
- }, {
590
- tabId: requestedTabId,
591
- source: REQUEST_SOURCE,
592
- tokenBudget: getToolTokenBudget(args),
593
- });
772
+ const response = await requestBridge(
773
+ client,
774
+ 'input.scroll_into_view',
775
+ {
776
+ target: await elementTarget(),
777
+ },
778
+ {
779
+ tabId: requestedTabId,
780
+ source: REQUEST_SOURCE,
781
+ tokenBudget: getToolTokenBudget(args),
782
+ }
783
+ );
594
784
  return summarizeToolResponse(response, 'input.scroll_into_view');
595
785
  }
596
786
  default:
@@ -601,23 +791,50 @@ export async function handleInputTool(args) {
601
791
 
602
792
  /** @type {Record<string, ToolAction>} */
603
793
  export const PATCH_ACTIONS = {
604
- apply_styles: { ref: true, method: 'patch.apply_styles', params: (a, r) => ({ target: { elementRef: r }, declarations: a.declarations, important: a.important, verify: a.verify }) },
605
- apply_dom: { ref: true, method: 'patch.apply_dom', params: (a, r) => {
606
- const operation = typeof a.operation === 'string' ? a.operation : '';
607
- /** @type {Record<string, string>} */
608
- const opMap = {
609
- setAttribute: 'set_attribute',
610
- removeAttribute: 'remove_attribute',
611
- addClass: 'toggle_class',
612
- removeClass: 'toggle_class',
613
- setTextContent: 'set_text',
614
- setProperty: 'set_attribute',
615
- };
616
- return { target: { elementRef: r }, operation: opMap[operation] || operation, value: a.value, name: a.name, verify: a.verify };
617
- }},
618
- list: { ref: false, method: 'patch.list', params: () => ({}) },
619
- rollback: { ref: false, method: 'patch.rollback', params: a => ({ patchId: a.patchId }) },
620
- commit_baseline: { ref: false, method: 'patch.commit_session_baseline', params: () => ({}) },
794
+ apply_styles: {
795
+ ref: true,
796
+ method: 'patch.apply_styles',
797
+ params: (a, r) => ({
798
+ target: { elementRef: r },
799
+ declarations: a.declarations,
800
+ important: a.important,
801
+ verify: a.verify,
802
+ }),
803
+ },
804
+ apply_dom: {
805
+ ref: true,
806
+ method: 'patch.apply_dom',
807
+ params: (a, r) => {
808
+ const operation = typeof a.operation === 'string' ? a.operation : '';
809
+ /** @type {Record<string, string>} */
810
+ const opMap = {
811
+ setAttribute: 'set_attribute',
812
+ removeAttribute: 'remove_attribute',
813
+ addClass: 'toggle_class',
814
+ removeClass: 'toggle_class',
815
+ setTextContent: 'set_text',
816
+ setProperty: 'set_attribute',
817
+ };
818
+ return {
819
+ target: { elementRef: r },
820
+ operation: opMap[operation] || operation,
821
+ value: a.value,
822
+ name: a.name,
823
+ verify: a.verify,
824
+ };
825
+ },
826
+ },
827
+ list: { ref: false, method: 'patch.list', params: () => ({}) },
828
+ rollback: {
829
+ ref: false,
830
+ method: 'patch.rollback',
831
+ params: (a) => ({ patchId: a.patchId }),
832
+ },
833
+ commit_baseline: {
834
+ ref: false,
835
+ method: 'patch.commit_session_baseline',
836
+ params: () => ({}),
837
+ },
621
838
  };
622
839
 
623
840
  /**
@@ -630,13 +847,37 @@ export async function handlePatchTool(args) {
630
847
 
631
848
  /** @type {Record<string, ToolAction>} */
632
849
  export const CAPTURE_ACTIONS = {
633
- element: { ref: true, method: 'screenshot.capture_element', params: (_, r) => ({ elementRef: r }) },
634
- region: { ref: false, method: 'screenshot.capture_region', params: a => /** @type {Record<string, unknown>} */ (a.rect || {}) },
635
- full_page: { ref: false, method: 'screenshot.capture_full_page', params: () => ({}) },
636
- cdp_document: { ref: false, method: 'cdp.get_document', params: () => ({}) },
637
- cdp_dom_snapshot: { ref: false, method: 'cdp.get_dom_snapshot', params: () => ({}) },
638
- cdp_box_model: { ref: true, method: 'cdp.get_box_model', params: (_, r) => ({ elementRef: r }) },
639
- cdp_computed_styles: { ref: true, method: 'cdp.get_computed_styles_for_node', params: (_, r) => ({ elementRef: r }) },
850
+ element: {
851
+ ref: true,
852
+ method: 'screenshot.capture_element',
853
+ params: (_, r) => ({ elementRef: r }),
854
+ },
855
+ region: {
856
+ ref: false,
857
+ method: 'screenshot.capture_region',
858
+ params: (a) => /** @type {Record<string, unknown>} */ (a.rect || {}),
859
+ },
860
+ full_page: {
861
+ ref: false,
862
+ method: 'screenshot.capture_full_page',
863
+ params: () => ({}),
864
+ },
865
+ cdp_document: { ref: false, method: 'cdp.get_document', params: () => ({}) },
866
+ cdp_dom_snapshot: {
867
+ ref: false,
868
+ method: 'cdp.get_dom_snapshot',
869
+ params: () => ({}),
870
+ },
871
+ cdp_box_model: {
872
+ ref: true,
873
+ method: 'cdp.get_box_model',
874
+ params: (_, r) => ({ elementRef: r }),
875
+ },
876
+ cdp_computed_styles: {
877
+ ref: true,
878
+ method: 'cdp.get_computed_styles_for_node',
879
+ params: (_, r) => ({ elementRef: r }),
880
+ },
640
881
  };
641
882
 
642
883
  /**
@@ -657,7 +898,10 @@ export async function handleSkillTool() {
657
898
  try {
658
899
  const { createRuntimeContext } = await import('../../protocol/src/index.js');
659
900
  const ctx = createRuntimeContext();
660
- return createToolResult('Runtime context retrieved.', { ok: true, runtimeContext: ctx });
901
+ return createToolResult('Runtime context retrieved.', {
902
+ ok: true,
903
+ runtimeContext: ctx,
904
+ });
661
905
  } catch (error) {
662
906
  return summarizeToolError(error);
663
907
  }
@@ -675,13 +919,11 @@ export async function handleSetupTool(args) {
675
919
  const status = await collectSetupStatus({
676
920
  global: args.global !== false,
677
921
  cwd: process.cwd(),
678
- projectPath
922
+ projectPath,
679
923
  });
680
924
  const configuredMcp = status.mcpClients.filter((e) => e.configured).length;
681
925
  const installedSkills = status.skillTargets.filter((e) => e.installed).length;
682
- const summary = configuredMcp === 0 && installedSkills === 0
683
- ? 'No MCP or skill setup found. Run `bbx install-mcp` and `bbx install-skill`.'
684
- : `Setup: ${configuredMcp}/${status.mcpClients.length} MCP clients configured, ${installedSkills}/${status.skillTargets.length} skills installed.`;
926
+ const summary = `Optional agent integration status: ${configuredMcp}/${status.mcpClients.length} MCP clients configured, ${installedSkills}/${status.skillTargets.length} skills installed.`;
685
927
  return createToolResult(summary, { ok: true, status });
686
928
  }
687
929
 
@@ -697,11 +939,15 @@ export async function handleLogTool(args) {
697
939
  normal: DEFAULT_CONSOLE_LIMIT,
698
940
  deep: 100,
699
941
  });
700
- return callBridgeTool('log.tail', {
701
- limit: normalizedArgs.limit ?? DEFAULT_CONSOLE_LIMIT,
702
- }, {
703
- tokenBudget: getToolTokenBudget(normalizedArgs),
704
- });
942
+ return callBridgeTool(
943
+ 'log.tail',
944
+ {
945
+ limit: normalizedArgs.limit ?? DEFAULT_CONSOLE_LIMIT,
946
+ },
947
+ {
948
+ tokenBudget: getToolTokenBudget(normalizedArgs),
949
+ }
950
+ );
705
951
  }
706
952
 
707
953
  /**
@@ -711,7 +957,10 @@ export async function handleLogTool(args) {
711
957
  */
712
958
  export async function handleHealthTool() {
713
959
  return withToolClient(async (client) => {
714
- const response = await client.request({ method: 'health.ping', meta: { source: REQUEST_SOURCE } });
960
+ const response = await client.request({
961
+ method: 'health.ping',
962
+ meta: { source: REQUEST_SOURCE },
963
+ });
715
964
  return summarizeToolResponse(response, 'health.ping');
716
965
  });
717
966
  }
@@ -727,75 +976,87 @@ export async function handleBatchTool(args) {
727
976
 
728
977
  const calls = args.calls;
729
978
  return withToolClient(async (client) => {
730
- const results = await Promise.all(calls.map(async (call) => {
731
- if (!call || typeof call !== 'object' || typeof call.method !== 'string') {
732
- return {
733
- method: '',
734
- tabId: null,
735
- ok: false,
736
- summary: 'INVALID_REQUEST: Each batch call needs a method.',
737
- evidence: null,
738
- error: { code: 'INVALID_REQUEST', message: 'Each batch call needs a method.' },
739
- response: null,
740
- };
741
- }
979
+ const results = await Promise.all(
980
+ calls.map(async (call) => {
981
+ if (!call || typeof call !== 'object' || typeof call.method !== 'string') {
982
+ return {
983
+ method: '',
984
+ tabId: null,
985
+ ok: false,
986
+ summary: 'INVALID_REQUEST: Each batch call needs a method.',
987
+ evidence: null,
988
+ error: {
989
+ code: 'INVALID_REQUEST',
990
+ message: 'Each batch call needs a method.',
991
+ },
992
+ response: null,
993
+ };
994
+ }
742
995
 
743
- if (!METHODS.includes(/** @type {BridgeMethod} */ (call.method))) {
744
- return {
745
- method: call.method,
746
- tabId: null,
747
- ok: false,
748
- summary: `INVALID_REQUEST: Unknown bridge method "${call.method}".`,
749
- evidence: null,
750
- error: {
751
- code: 'INVALID_REQUEST',
752
- message: `Unknown bridge method "${call.method}".`,
753
- },
754
- response: null,
755
- };
756
- }
996
+ if (!METHODS.includes(/** @type {BridgeMethod} */ (call.method))) {
997
+ return {
998
+ method: call.method,
999
+ tabId: null,
1000
+ ok: false,
1001
+ summary: `INVALID_REQUEST: Unknown bridge method "${call.method}".`,
1002
+ evidence: null,
1003
+ error: {
1004
+ code: 'INVALID_REQUEST',
1005
+ message: `Unknown bridge method "${call.method}".`,
1006
+ },
1007
+ response: null,
1008
+ };
1009
+ }
757
1010
 
758
- const method = /** @type {BridgeMethod} */ (call.method);
759
- const tabId = bridgeMethodNeedsTab(method)
760
- ? (typeof call.tabId === 'number' ? call.tabId : null)
761
- : null;
762
- const tokenBudget = getToolTokenBudget(call);
1011
+ const method = /** @type {BridgeMethod} */ (call.method);
1012
+ const tabId = bridgeMethodNeedsTab(method)
1013
+ ? typeof call.tabId === 'number'
1014
+ ? call.tabId
1015
+ : null
1016
+ : null;
1017
+ const tokenBudget = getToolTokenBudget(call);
763
1018
 
764
- const startTime = Date.now();
765
- try {
766
- const response = await client.request({
767
- method,
768
- params: call.params || {},
769
- tabId,
770
- meta: {
771
- source: REQUEST_SOURCE,
772
- ...(tokenBudget != null ? { token_budget: tokenBudget } : {}),
773
- },
774
- });
775
- return summarizeBatchResponseItem({
776
- method,
777
- tabId,
778
- response,
779
- durationMs: Date.now() - startTime,
780
- });
781
- } catch (error) {
782
- return summarizeBatchErrorItem({
783
- method,
784
- tabId,
785
- error,
786
- durationMs: Date.now() - startTime,
787
- });
788
- }
789
- }));
1019
+ const startTime = Date.now();
1020
+ try {
1021
+ const response = await client.request({
1022
+ method,
1023
+ params: call.params || {},
1024
+ tabId,
1025
+ meta: {
1026
+ source: REQUEST_SOURCE,
1027
+ ...(tokenBudget != null ? { token_budget: tokenBudget } : {}),
1028
+ },
1029
+ });
1030
+ return summarizeBatchResponseItem({
1031
+ method,
1032
+ tabId,
1033
+ response,
1034
+ durationMs: Date.now() - startTime,
1035
+ });
1036
+ } catch (error) {
1037
+ return summarizeBatchErrorItem({
1038
+ method,
1039
+ tabId,
1040
+ error,
1041
+ durationMs: Date.now() - startTime,
1042
+ });
1043
+ }
1044
+ })
1045
+ );
790
1046
 
791
1047
  const failureCount = results.filter((result) => !result.ok).length;
792
- const summary = failureCount === 0
793
- ? `Batch executed ${results.length} call(s).`
794
- : `Batch executed ${results.length} call(s) with ${failureCount} error(s).`;
795
- return createToolResult(summary, {
796
- ok: failureCount === 0,
797
- results,
798
- }, failureCount > 0);
1048
+ const summary =
1049
+ failureCount === 0
1050
+ ? `Batch executed ${results.length} call(s).`
1051
+ : `Batch executed ${results.length} call(s) with ${failureCount} error(s).`;
1052
+ return createToolResult(
1053
+ summary,
1054
+ {
1055
+ ok: failureCount === 0,
1056
+ results,
1057
+ },
1058
+ failureCount > 0
1059
+ );
799
1060
  });
800
1061
  }
801
1062
 
@@ -815,21 +1076,25 @@ export async function handleRawCallTool(args) {
815
1076
  args.params || {},
816
1077
  {
817
1078
  tabId: typeof args.tabId === 'number' ? args.tabId : null,
818
- source: REQUEST_SOURCE
1079
+ source: REQUEST_SOURCE,
819
1080
  }
820
1081
  );
821
1082
 
822
1083
  if (!response.ok) {
823
- return createToolResult(response.error.message, {
824
- ok: false,
825
- error: response.error,
826
- response
827
- }, true);
1084
+ return createToolResult(
1085
+ response.error.message,
1086
+ {
1087
+ ok: false,
1088
+ error: response.error,
1089
+ response,
1090
+ },
1091
+ true
1092
+ );
828
1093
  }
829
1094
 
830
1095
  return createToolResult(`Called ${args.method}.`, {
831
1096
  ok: true,
832
- response: response.result
1097
+ response: response.result,
833
1098
  });
834
1099
  });
835
1100
  }
@@ -858,14 +1123,30 @@ const INVESTIGATE_SCOPES = {
858
1123
  label: 'quick',
859
1124
  steps: [
860
1125
  { method: 'page.get_state', params: () => ({}) },
861
- { method: 'dom.query', params: (a) => ({ selector: a.selector || 'body', maxNodes: 10, maxDepth: 2, textBudget: 300 }) },
1126
+ {
1127
+ method: 'dom.query',
1128
+ params: (a) => ({
1129
+ selector: a.selector || 'body',
1130
+ maxNodes: 10,
1131
+ maxDepth: 2,
1132
+ textBudget: 300,
1133
+ }),
1134
+ },
862
1135
  ],
863
1136
  },
864
1137
  normal: {
865
1138
  label: 'normal',
866
1139
  steps: [
867
1140
  { method: 'page.get_state', params: () => ({}) },
868
- { method: 'dom.query', params: (a) => ({ selector: a.selector || 'body', maxNodes: 25, maxDepth: 4, textBudget: 600 }) },
1141
+ {
1142
+ method: 'dom.query',
1143
+ params: (a) => ({
1144
+ selector: a.selector || 'body',
1145
+ maxNodes: 25,
1146
+ maxDepth: 4,
1147
+ textBudget: 600,
1148
+ }),
1149
+ },
869
1150
  { method: 'page.get_text', params: () => ({ textBudget: 4000 }) },
870
1151
  ],
871
1152
  },
@@ -873,9 +1154,20 @@ const INVESTIGATE_SCOPES = {
873
1154
  label: 'deep',
874
1155
  steps: [
875
1156
  { method: 'page.get_state', params: () => ({}) },
876
- { method: 'dom.query', params: (a) => ({ selector: a.selector || 'body', maxNodes: 50, maxDepth: 6, textBudget: 1000 }) },
1157
+ {
1158
+ method: 'dom.query',
1159
+ params: (a) => ({
1160
+ selector: a.selector || 'body',
1161
+ maxNodes: 50,
1162
+ maxDepth: 6,
1163
+ textBudget: 1000,
1164
+ }),
1165
+ },
877
1166
  { method: 'page.get_text', params: () => ({ textBudget: 8000 }) },
878
- { method: 'page.get_console', params: () => ({ level: 'warn', limit: 20 }) },
1167
+ {
1168
+ method: 'page.get_console',
1169
+ params: () => ({ level: 'warn', limit: 20 }),
1170
+ },
879
1171
  { method: 'page.get_network', params: () => ({ limit: 20 }) },
880
1172
  ],
881
1173
  },
@@ -947,12 +1239,16 @@ export async function handleInvestigateTool(args) {
947
1239
  ? `Investigation complete (${scope.label}, ${stepResults.length} steps, ${totalDuration}ms). Objective: ${objective}`
948
1240
  : `Investigation partial (${scope.label}, ${stepResults.length} steps, ${failedSteps.length} failed, ${totalDuration}ms). Objective: ${objective}`;
949
1241
 
950
- return createToolResult(summaryText, {
951
- ok: allOk,
952
- objective,
953
- scope: scopeName,
954
- heuristicFallback: true,
955
- steps: stepResults,
956
- }, !allOk);
1242
+ return createToolResult(
1243
+ summaryText,
1244
+ {
1245
+ ok: allOk,
1246
+ objective,
1247
+ scope: scopeName,
1248
+ heuristicFallback: true,
1249
+ steps: stepResults,
1250
+ },
1251
+ !allOk
1252
+ );
957
1253
  });
958
1254
  }