@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.
- package/README.md +3 -1
- package/docs/api-reference.md +33 -33
- package/docs/mcp-vs-cli.md +104 -104
- package/docs/publishing.md +1 -3
- package/docs/quickstart.md +6 -6
- package/docs/unpacked-extension.md +72 -0
- package/manifest.json +3 -17
- package/package.json +44 -42
- package/packages/agent-client/src/cli-helpers.js +10 -5
- package/packages/agent-client/src/cli.js +65 -135
- package/packages/agent-client/src/client.js +37 -17
- package/packages/agent-client/src/command-registry.js +101 -69
- package/packages/agent-client/src/detect.js +3 -6
- package/packages/agent-client/src/install.js +10 -27
- package/packages/agent-client/src/mcp-config.js +11 -30
- package/packages/agent-client/src/runtime.js +41 -20
- package/packages/agent-client/src/setup-status.js +13 -28
- package/packages/extension/src/background-helpers.js +51 -36
- package/packages/extension/src/background-routing.js +11 -13
- package/packages/extension/src/background.js +562 -299
- package/packages/extension/src/content-script-helpers.js +17 -16
- package/packages/extension/src/content-script.js +175 -109
- package/packages/extension/src/sidepanel-helpers.js +3 -1
- package/packages/extension/ui/popup.js +39 -20
- package/packages/extension/ui/sidepanel.js +108 -191
- package/packages/extension/ui/ui.css +2 -1
- package/packages/mcp-server/src/handlers.js +546 -250
- package/packages/mcp-server/src/server.js +558 -257
- package/packages/native-host/bin/bridge-daemon.js +6 -2
- package/packages/native-host/bin/install-manifest.js +2 -2
- package/packages/native-host/bin/postinstall.js +4 -2
- package/packages/native-host/src/config.js +11 -7
- package/packages/native-host/src/daemon.js +143 -92
- package/packages/native-host/src/install-manifest.js +73 -22
- package/packages/native-host/src/native-host.js +55 -40
- package/packages/protocol/src/budget.js +3 -7
- package/packages/protocol/src/capabilities.js +3 -3
- package/packages/protocol/src/errors.js +11 -11
- package/packages/protocol/src/protocol.js +104 -71
- package/packages/protocol/src/registry.js +300 -45
- package/packages/protocol/src/summary.js +249 -106
- package/packages/protocol/src/types.js +1 -1
- package/skills/browser-bridge/SKILL.md +1 -1
- package/skills/browser-bridge/agents/openai.yaml +3 -3
- package/skills/browser-bridge/references/interaction.md +33 -11
- package/skills/browser-bridge/references/patch-workflow.md +3 -0
- package/skills/browser-bridge/references/protocol.md +125 -70
- package/skills/browser-bridge/references/tailwind.md +12 -11
- package/skills/browser-bridge/references/token-efficiency.md +23 -22
- package/skills/browser-bridge/references/ui-workflows.md +8 -0
- package/packages/extension/ui/offscreen.html +0 -6
- 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(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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(
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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 =
|
|
309
|
-
|
|
310
|
-
|
|
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:
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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:
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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:
|
|
395
|
-
evaluate:
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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:
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
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(
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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(
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
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(
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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 =
|
|
518
|
-
const response = await requestBridge(
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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(
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
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(
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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(
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
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:
|
|
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:
|
|
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(
|
|
750
|
+
return summarizeToolError(
|
|
751
|
+
'sourceElementRef/sourceSelector and destinationElementRef/destinationSelector are required for drag.'
|
|
752
|
+
);
|
|
573
753
|
}
|
|
574
|
-
const response = await requestBridge(
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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(
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
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:
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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:
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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.', {
|
|
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
|
|
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(
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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({
|
|
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(
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
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
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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 =
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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(
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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
|
-
{
|
|
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(
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
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
|
}
|