@browserbridge/bbx 1.7.2 → 1.7.4
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/package.json +10 -9
- package/packages/agent-client/src/cli.js +8 -2
- package/packages/agent-client/src/mcp-config.js +55 -55
- package/packages/agent-client/src/runtime.js +20 -1
- package/packages/mcp-server/src/guidance.js +4 -4
- package/packages/mcp-server/src/handlers-capture.js +221 -30
- package/packages/mcp-server/src/handlers-navigation.js +10 -0
- package/packages/mcp-server/src/handlers-page.js +154 -67
- package/packages/mcp-server/src/handlers-utils.js +27 -68
- package/packages/mcp-server/src/handlers.js +2 -0
- package/packages/mcp-server/src/server.js +38 -13
- package/packages/native-host/src/daemon-process.js +16 -8
- package/packages/native-host/src/daemon.js +201 -79
- package/packages/protocol/src/capabilities.js +1 -1
- package/packages/protocol/src/defaults.js +17 -0
- package/packages/protocol/src/protocol.js +60 -5
- package/packages/protocol/src/registry.js +15 -15
- package/packages/protocol/src/summary.js +61 -30
- package/packages/protocol/src/types.ts +11 -0
- package/skills/browser-bridge/SKILL.md +15 -11
- package/skills/browser-bridge/references/interaction.md +11 -8
- package/skills/browser-bridge/references/protocol.md +9 -5
- package/skills/browser-bridge/references/token-efficiency.md +5 -4
- package/skills/browser-bridge/references/ui-workflows.md +11 -9
|
@@ -112,7 +112,7 @@ function createRegistryEntry(method, group, tab, params, complexity = 'low') {
|
|
|
112
112
|
|
|
113
113
|
/** @type {Readonly<Record<import('./types.js').BridgeMethod, BridgeMethodRegistryEntry>>} */
|
|
114
114
|
export const BRIDGE_METHOD_REGISTRY = Object.freeze({
|
|
115
|
-
// system
|
|
115
|
+
// system - trivial
|
|
116
116
|
'access.request': createRegistryEntry('access.request', 'system', false, [], 'trivial'),
|
|
117
117
|
'skill.get_runtime_context': createRegistryEntry(
|
|
118
118
|
'skill.get_runtime_context',
|
|
@@ -126,18 +126,18 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
|
|
|
126
126
|
'setup.install',
|
|
127
127
|
'system',
|
|
128
128
|
false,
|
|
129
|
-
['kind', 'target'],
|
|
129
|
+
['action', 'kind', 'target'],
|
|
130
130
|
'trivial'
|
|
131
131
|
),
|
|
132
132
|
'log.tail': createRegistryEntry('log.tail', 'system', false, ['limit'], 'trivial'),
|
|
133
133
|
'health.ping': createRegistryEntry('health.ping', 'system', false, [], 'trivial'),
|
|
134
134
|
'daemon.metrics': createRegistryEntry('daemon.metrics', 'system', false, [], 'trivial'),
|
|
135
|
-
// tabs
|
|
135
|
+
// tabs - trivial
|
|
136
136
|
'tabs.list': createRegistryEntry('tabs.list', 'tabs', false, [], 'trivial'),
|
|
137
137
|
'tabs.create': createRegistryEntry('tabs.create', 'tabs', false, ['url', 'active'], 'trivial'),
|
|
138
138
|
'tabs.close': createRegistryEntry('tabs.close', 'tabs', false, ['tabId'], 'trivial'),
|
|
139
139
|
'tabs.activate': createRegistryEntry('tabs.activate', 'tabs', false, ['tabId'], 'trivial'),
|
|
140
|
-
// page
|
|
140
|
+
// page - low (basic reads), moderate (evaluate, debugger-backed)
|
|
141
141
|
'page.get_state': createRegistryEntry('page.get_state', 'page', true, [], 'low'),
|
|
142
142
|
'page.evaluate': {
|
|
143
143
|
...createRegistryEntry(
|
|
@@ -177,7 +177,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
|
|
|
177
177
|
['clear', 'limit', 'urlPattern'],
|
|
178
178
|
'low'
|
|
179
179
|
),
|
|
180
|
-
// network intercept
|
|
180
|
+
// network intercept - moderate (holds debugger session)
|
|
181
181
|
'network.intercept.add': createRegistryEntry(
|
|
182
182
|
'network.intercept.add',
|
|
183
183
|
'page',
|
|
@@ -206,7 +206,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
|
|
|
206
206
|
[],
|
|
207
207
|
'low'
|
|
208
208
|
),
|
|
209
|
-
// navigation
|
|
209
|
+
// navigation - low
|
|
210
210
|
'navigation.navigate': createRegistryEntry(
|
|
211
211
|
'navigation.navigate',
|
|
212
212
|
'navigate',
|
|
@@ -235,7 +235,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
|
|
|
235
235
|
['waitForLoad', 'timeoutMs'],
|
|
236
236
|
'low'
|
|
237
237
|
),
|
|
238
|
-
// dom
|
|
238
|
+
// dom - low (reads), moderate (accessibility tree)
|
|
239
239
|
'dom.query': {
|
|
240
240
|
...createRegistryEntry(
|
|
241
241
|
'dom.query',
|
|
@@ -303,7 +303,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
|
|
|
303
303
|
['maxNodes', 'maxDepth'],
|
|
304
304
|
'moderate'
|
|
305
305
|
),
|
|
306
|
-
// layout
|
|
306
|
+
// layout - low
|
|
307
307
|
'layout.get_box_model': createRegistryEntry(
|
|
308
308
|
'layout.get_box_model',
|
|
309
309
|
'inspect',
|
|
@@ -312,7 +312,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
|
|
|
312
312
|
'low'
|
|
313
313
|
),
|
|
314
314
|
'layout.hit_test': createRegistryEntry('layout.hit_test', 'inspect', true, ['x', 'y'], 'low'),
|
|
315
|
-
// styles
|
|
315
|
+
// styles - low (computed), moderate (matched rules)
|
|
316
316
|
'styles.get_computed': createRegistryEntry(
|
|
317
317
|
'styles.get_computed',
|
|
318
318
|
'inspect',
|
|
@@ -327,7 +327,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
|
|
|
327
327
|
['elementRef'],
|
|
328
328
|
'moderate'
|
|
329
329
|
),
|
|
330
|
-
// viewport
|
|
330
|
+
// viewport - low (scroll), moderate (resize, debugger-backed)
|
|
331
331
|
'viewport.scroll': createRegistryEntry(
|
|
332
332
|
'viewport.scroll',
|
|
333
333
|
'navigate',
|
|
@@ -342,7 +342,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
|
|
|
342
342
|
['width', 'height', 'deviceScaleFactor', 'reset'],
|
|
343
343
|
'moderate'
|
|
344
344
|
),
|
|
345
|
-
// input
|
|
345
|
+
// input - low (simple), moderate (drag)
|
|
346
346
|
'input.click': createRegistryEntry(
|
|
347
347
|
'input.click',
|
|
348
348
|
'interact',
|
|
@@ -407,7 +407,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
|
|
|
407
407
|
['target'],
|
|
408
408
|
'low'
|
|
409
409
|
),
|
|
410
|
-
// capture
|
|
410
|
+
// capture - high (screenshots, CDP)
|
|
411
411
|
'screenshot.capture_region': createRegistryEntry(
|
|
412
412
|
'screenshot.capture_region',
|
|
413
413
|
'capture',
|
|
@@ -429,7 +429,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
|
|
|
429
429
|
[],
|
|
430
430
|
'high'
|
|
431
431
|
),
|
|
432
|
-
// patch
|
|
432
|
+
// patch - moderate (side effects)
|
|
433
433
|
'patch.apply_styles': createRegistryEntry(
|
|
434
434
|
'patch.apply_styles',
|
|
435
435
|
'patch',
|
|
@@ -453,7 +453,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
|
|
|
453
453
|
[],
|
|
454
454
|
'low'
|
|
455
455
|
),
|
|
456
|
-
// cdp
|
|
456
|
+
// cdp - high (raw protocol, large payloads)
|
|
457
457
|
'cdp.get_document': createRegistryEntry('cdp.get_document', 'cdp', true, [], 'high'),
|
|
458
458
|
'cdp.get_dom_snapshot': createRegistryEntry('cdp.get_dom_snapshot', 'cdp', true, [], 'high'),
|
|
459
459
|
'cdp.get_box_model': createRegistryEntry('cdp.get_box_model', 'cdp', true, ['nodeId'], 'high'),
|
|
@@ -471,7 +471,7 @@ export const BRIDGE_METHOD_REGISTRY = Object.freeze({
|
|
|
471
471
|
['key', 'code', 'modifiers'],
|
|
472
472
|
'moderate'
|
|
473
473
|
),
|
|
474
|
-
// performance
|
|
474
|
+
// performance - moderate (debugger-backed)
|
|
475
475
|
'performance.get_metrics': createRegistryEntry(
|
|
476
476
|
'performance.get_metrics',
|
|
477
477
|
'performance',
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
estimateSerializedPayloadCost,
|
|
5
|
+
getErrorRecovery,
|
|
5
6
|
getCostClass,
|
|
6
7
|
getProtocolVersion,
|
|
7
8
|
serializeJsonPayload,
|
|
@@ -26,7 +27,8 @@ import {
|
|
|
26
27
|
* @typedef {{
|
|
27
28
|
* ok: boolean,
|
|
28
29
|
* summary: string,
|
|
29
|
-
* evidence: unknown
|
|
30
|
+
* evidence: unknown,
|
|
31
|
+
* recovery?: import('./types.js').BridgeRecovery | null
|
|
30
32
|
* }} BridgeSummary
|
|
31
33
|
*/
|
|
32
34
|
|
|
@@ -49,7 +51,7 @@ import {
|
|
|
49
51
|
* approxTokens: number,
|
|
50
52
|
* meta?: Record<string, unknown>,
|
|
51
53
|
* error: unknown,
|
|
52
|
-
* response
|
|
54
|
+
* response?: unknown
|
|
53
55
|
* }} BatchItemSummary
|
|
54
56
|
*/
|
|
55
57
|
|
|
@@ -164,14 +166,15 @@ function getMetaCostClass(meta, field) {
|
|
|
164
166
|
export function summarizeBridgeResponse(response, method) {
|
|
165
167
|
const protocolWarning = getProtocolWarning(response.meta);
|
|
166
168
|
if (!response.ok) {
|
|
167
|
-
const
|
|
169
|
+
const recovery = response.error.recovery ?? getErrorRecovery(response.error.code);
|
|
168
170
|
return {
|
|
169
171
|
ok: false,
|
|
170
172
|
summary: appendProtocolWarning(
|
|
171
|
-
`${response.error.code}: ${response.error.message}${hint ? ` ${hint}` : ''}`,
|
|
173
|
+
`${response.error.code}: ${response.error.message}${recovery?.hint ? ` ${recovery.hint}` : ''}`,
|
|
172
174
|
protocolWarning
|
|
173
175
|
),
|
|
174
176
|
evidence: response.error.details ?? null,
|
|
177
|
+
recovery,
|
|
175
178
|
};
|
|
176
179
|
}
|
|
177
180
|
|
|
@@ -348,16 +351,38 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
348
351
|
}
|
|
349
352
|
return entry;
|
|
350
353
|
});
|
|
351
|
-
|
|
352
|
-
if (
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
354
|
+
const isFind = method === 'dom.find_by_text' || method === 'dom.find_by_role';
|
|
355
|
+
if (isFind) {
|
|
356
|
+
const count = typeof result.count === 'number' ? result.count : nodes.length;
|
|
357
|
+
const scanned = typeof result.scanned === 'number' ? result.scanned : null;
|
|
358
|
+
const truncated = result.truncated === true;
|
|
359
|
+
let summary;
|
|
360
|
+
if (count === 0) {
|
|
361
|
+
summary = truncated
|
|
362
|
+
? `No matching elements returned; search stopped${scanned === null ? '' : ` after scanning ${scanned}`}.`
|
|
363
|
+
: 'No matching elements found.';
|
|
364
|
+
} else if (truncated) {
|
|
365
|
+
summary = `Found ${count} matching element(s); additional matches were omitted.`;
|
|
366
|
+
} else {
|
|
367
|
+
summary = `Found ${count} matching element(s)${scanned === null ? '' : ` after scanning ${scanned}`}.`;
|
|
368
|
+
}
|
|
369
|
+
return {
|
|
370
|
+
ok: true,
|
|
371
|
+
summary: appendProtocolWarning(summary, protocolWarning),
|
|
372
|
+
evidence: {
|
|
373
|
+
found: count > 0,
|
|
374
|
+
count,
|
|
375
|
+
scanned,
|
|
376
|
+
truncated,
|
|
377
|
+
truncationReason: result.truncationReason ?? null,
|
|
378
|
+
nodes: compact,
|
|
379
|
+
},
|
|
380
|
+
};
|
|
356
381
|
}
|
|
357
382
|
return {
|
|
358
383
|
ok: true,
|
|
359
384
|
summary: appendProtocolWarning(
|
|
360
|
-
|
|
385
|
+
`DOM query returned ${nodes.length} element(s)${nodes.length > 15 ? '; showing first 15' : ''}.`,
|
|
361
386
|
protocolWarning
|
|
362
387
|
),
|
|
363
388
|
evidence: compact,
|
|
@@ -666,9 +691,10 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
666
691
|
|
|
667
692
|
/**
|
|
668
693
|
* @param {{ method: SummaryMethod, tabId: number | null, response: BridgeResponse, durationMs: number }} input
|
|
694
|
+
* @param {{ compact?: boolean }} [options]
|
|
669
695
|
* @returns {BatchItemSummary}
|
|
670
696
|
*/
|
|
671
|
-
export function summarizeBatchResponseItem({ method, tabId, response, durationMs }) {
|
|
697
|
+
export function summarizeBatchResponseItem({ method, tabId, response, durationMs }, options = {}) {
|
|
672
698
|
const summary = annotateBridgeSummary(summarizeBridgeResponse(response, method), response);
|
|
673
699
|
return {
|
|
674
700
|
method,
|
|
@@ -676,17 +702,18 @@ export function summarizeBatchResponseItem({ method, tabId, response, durationMs
|
|
|
676
702
|
...summary,
|
|
677
703
|
durationMs,
|
|
678
704
|
approxTokens: summary.transportTokens,
|
|
679
|
-
meta: response.meta,
|
|
705
|
+
meta: options.compact ? compactBatchMeta(response.meta) : response.meta,
|
|
680
706
|
error: response.ok ? null : response.error,
|
|
681
|
-
response: response.ok ? response.result : null,
|
|
707
|
+
...(options.compact ? {} : { response: response.ok ? response.result : null }),
|
|
682
708
|
};
|
|
683
709
|
}
|
|
684
710
|
|
|
685
711
|
/**
|
|
686
712
|
* @param {{ method: SummaryMethod, tabId: number | null, error: unknown, durationMs: number }} input
|
|
713
|
+
* @param {{ compact?: boolean }} [options]
|
|
687
714
|
* @returns {BatchItemSummary}
|
|
688
715
|
*/
|
|
689
|
-
export function summarizeBatchErrorItem({ method, tabId, error, durationMs }) {
|
|
716
|
+
export function summarizeBatchErrorItem({ method, tabId, error, durationMs }, options = {}) {
|
|
690
717
|
const message = error instanceof Error ? error.message : String(error);
|
|
691
718
|
const response = /** @type {BridgeResponse} */ ({
|
|
692
719
|
id: 'batch_error',
|
|
@@ -706,12 +733,30 @@ export function summarizeBatchErrorItem({ method, tabId, error, durationMs }) {
|
|
|
706
733
|
...summary,
|
|
707
734
|
durationMs,
|
|
708
735
|
approxTokens: 0,
|
|
709
|
-
meta: response.meta,
|
|
736
|
+
meta: options.compact ? compactBatchMeta(response.meta) : response.meta,
|
|
710
737
|
error: response.error,
|
|
711
|
-
response: null,
|
|
738
|
+
...(options.compact ? {} : { response: null }),
|
|
712
739
|
};
|
|
713
740
|
}
|
|
714
741
|
|
|
742
|
+
/**
|
|
743
|
+
* @param {Record<string, unknown>} meta
|
|
744
|
+
* @returns {Record<string, unknown>}
|
|
745
|
+
*/
|
|
746
|
+
function compactBatchMeta(meta) {
|
|
747
|
+
/** @type {Record<string, unknown>} */
|
|
748
|
+
const compact = {};
|
|
749
|
+
for (const key of [
|
|
750
|
+
'protocol_version',
|
|
751
|
+
'protocol_warning',
|
|
752
|
+
'budget_truncated',
|
|
753
|
+
'continuation_hint',
|
|
754
|
+
]) {
|
|
755
|
+
if (meta[key] !== undefined && meta[key] !== null) compact[key] = meta[key];
|
|
756
|
+
}
|
|
757
|
+
return compact;
|
|
758
|
+
}
|
|
759
|
+
|
|
715
760
|
/**
|
|
716
761
|
* @param {unknown} value
|
|
717
762
|
* @returns {Record<string, unknown>}
|
|
@@ -758,20 +803,6 @@ function truncateUrl(url) {
|
|
|
758
803
|
}
|
|
759
804
|
}
|
|
760
805
|
|
|
761
|
-
/**
|
|
762
|
-
* @param {string} code
|
|
763
|
-
* @returns {string}
|
|
764
|
-
*/
|
|
765
|
-
function summarizeErrorHint(code) {
|
|
766
|
-
if (code === 'ELEMENT_STALE') {
|
|
767
|
-
return 'Re-query the current page after navigation or DOM updates.';
|
|
768
|
-
}
|
|
769
|
-
if (code === 'RESULT_TRUNCATED') {
|
|
770
|
-
return 'Narrow the query or raise the relevant budget if more detail is required.';
|
|
771
|
-
}
|
|
772
|
-
return '';
|
|
773
|
-
}
|
|
774
|
-
|
|
775
806
|
/**
|
|
776
807
|
* Check if an array looks like a list of patch entries.
|
|
777
808
|
* Returns true for empty arrays when method context suggests patch.list.
|
|
@@ -140,6 +140,7 @@ export interface BridgeRecovery {
|
|
|
140
140
|
hint: string;
|
|
141
141
|
retry?: boolean;
|
|
142
142
|
retryAfterMs?: number;
|
|
143
|
+
alternativeMethod?: string;
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
export interface BridgeFailure {
|
|
@@ -192,6 +193,7 @@ export interface TruncateResult {
|
|
|
192
193
|
export interface DomQueryParams extends BudgetOptions {
|
|
193
194
|
selector?: string;
|
|
194
195
|
withinRef?: string | null;
|
|
196
|
+
budget?: BudgetOptions;
|
|
195
197
|
}
|
|
196
198
|
|
|
197
199
|
export interface NormalizedDomQuery extends BridgeParams {
|
|
@@ -200,6 +202,15 @@ export interface NormalizedDomQuery extends BridgeParams {
|
|
|
200
202
|
budget: Budget;
|
|
201
203
|
}
|
|
202
204
|
|
|
205
|
+
export interface DomFindResult {
|
|
206
|
+
found: boolean;
|
|
207
|
+
nodes: Record<string, unknown>[];
|
|
208
|
+
count: number;
|
|
209
|
+
scanned: number;
|
|
210
|
+
truncated: boolean;
|
|
211
|
+
truncationReason: 'maxResults' | 'scanLimit' | null;
|
|
212
|
+
}
|
|
213
|
+
|
|
203
214
|
export interface StyleQueryParams {
|
|
204
215
|
elementRef?: string;
|
|
205
216
|
target?: InputTarget;
|
|
@@ -5,7 +5,7 @@ description: 'Token-efficient Chrome tab inspection, interaction, and patching v
|
|
|
5
5
|
|
|
6
6
|
# Browser Bridge
|
|
7
7
|
|
|
8
|
-
Token-efficient Chrome tab inspection, interaction, and CSS/DOM patching through a local native-messaging bridge. Reads structured DOM, styles, layout, console, storage, network, and applies reversible patches
|
|
8
|
+
Token-efficient Chrome tab inspection, interaction, and CSS/DOM patching through a local native-messaging bridge. Reads structured DOM, styles, layout, console, storage, network, and applies reversible patches - all from the real tab the user already has open.
|
|
9
9
|
|
|
10
10
|
This CLI skill is for agents that can run shell commands and where direct `bbx` control fits better than MCP tools: manual debugging, terminal reproduction, install/doctor flows, raw protocol access, or environments without MCP.
|
|
11
11
|
|
|
@@ -13,7 +13,7 @@ Permission prompts are controlled by the host agent, not by Browser Bridge. In p
|
|
|
13
13
|
|
|
14
14
|
Skill name: `browser-bridge` (also known as `bbx`). In GitHub Copilot, invoke as `/browser-bridge`. `bbx` is the CLI command used throughout this skill.
|
|
15
15
|
When the runtime supports subagents, delegate bridge inspection to a smaller, lower-cost worker and return only concise findings to the parent.
|
|
16
|
-
For open-ended investigation, start with structured reads (`page.get_state`, `dom.query`, `page.get_text`, `styles.get_computed`, `bbx batch`) and escalate to screenshots or debugger-backed methods only when structured evidence is insufficient.
|
|
16
|
+
For open-ended investigation, start with structured reads (`page.get_state`, `dom.query`, `page.get_text`, `styles.get_computed`, and `bbx batch` for CLI or `browser_batch` for MCP) and escalate to screenshots or debugger-backed methods only when structured evidence is insufficient. `browser_call` accepts one protocol method at a time; `batch` is not a valid `browser_call` method.
|
|
17
17
|
|
|
18
18
|
## CLI
|
|
19
19
|
|
|
@@ -154,12 +154,12 @@ Error responses now include a machine-readable `error.recovery` field with `retr
|
|
|
154
154
|
6. **Rollback** - revert every patch before finishing unless user wants mutations kept.
|
|
155
155
|
7. **Confirm scope** - `status` first; stop if no extension connection.
|
|
156
156
|
8. **Screenshots last** - only when structured evidence is ambiguous; prefer `screenshot.capture_element`, then a tight `screenshot.capture_region`; use `screenshot.capture_full_page` only when document-level context matters.
|
|
157
|
-
9. **Batch reads** - combine independent reads in
|
|
157
|
+
9. **Batch reads** - combine independent reads with `bbx batch` in CLI mode or `browser_batch` in MCP mode; do not pass `batch` as a `browser_call` method.
|
|
158
158
|
10. **Avoid debugger first** - prefer DOM/content-script methods (`dom.*`, `styles.*`, `layout.get_box_model`, `page.get_console`, `page.get_text`, `page.get_storage`, `page.get_network`) before any debugger-backed method. Escalate to CDP only when those cannot answer the question.
|
|
159
159
|
11. **Evaluate only when needed** - `page.evaluate` is powerful but debugger-backed; use it only when DOM, storage, console, network, or text reads cannot expose the needed state.
|
|
160
160
|
12. **Debugger-backed methods are last resort** - treat `page.evaluate`, `dom.get_accessibility_tree`, `viewport.resize`, `performance.get_metrics`, `screenshot.capture_*`, and all `cdp.*` methods as escalation steps because they attach `chrome.debugger`.
|
|
161
|
-
13. **Wait after change** - after editing source
|
|
162
|
-
14. **
|
|
161
|
+
13. **Wait after change** - after editing source, wait for expected new text, a selector matching the changed attribute, or a detach/attach remount; waiting for `attached` on a selector that already exists does not prove HMR ran. Use `page.wait_for_load_state` for navigation, not HMR.
|
|
162
|
+
14. **Prime event buffers** - before reproducing a console or network issue, call `page.get_console` and `page.get_network` once with `clear: true`; then reproduce the event and read each buffer without clearing it.
|
|
163
163
|
15. **Semantic finding** - use `dom.find_by_text` / `dom.find_by_role` when you know the label but not the selector.
|
|
164
164
|
16. **Text extraction** - use `page.get_text` for full page text instead of `dom.query` on body.
|
|
165
165
|
17. **Network monitoring** - use `page.get_network` to inspect API calls; auto-installs interceptor.
|
|
@@ -218,8 +218,11 @@ bbx rollback patch_2 # clean up
|
|
|
218
218
|
### Verify an API call
|
|
219
219
|
|
|
220
220
|
```bash
|
|
221
|
-
bbx
|
|
222
|
-
bbx
|
|
221
|
+
bbx call page.get_console '{"clear":true}' # install + clear console capture
|
|
222
|
+
bbx call page.get_network '{"clear":true}' # install + clear network capture
|
|
223
|
+
# reproduce the API action here
|
|
224
|
+
bbx console error # read newly captured runtime errors
|
|
225
|
+
bbx network 20 # read newly captured requests
|
|
223
226
|
bbx eval 'document.querySelector("#app").__vue__.$store.state.user' # read framework state
|
|
224
227
|
bbx page-text 2000 # extract page content
|
|
225
228
|
```
|
|
@@ -232,7 +235,7 @@ bbx page-text 2000 # extract page content
|
|
|
232
235
|
| Inspect | `dom.query`, `dom.describe`, `dom.get_html`, `styles.get_computed`, `layout.get_box_model` |
|
|
233
236
|
| Find | `dom.find_by_text`, `dom.find_by_role`, `dom.wait_for`, `dom.get_accessibility_tree` |
|
|
234
237
|
| Page State | `page.get_console`, `page.get_storage`, `page.get_text`, `page.wait_for_load_state`, `page.evaluate` (debugger-backed) |
|
|
235
|
-
| Network | `page.get_network`, `network.intercept.add/remove/list/clear` (debugger-backed; rules are in-memory and drop if the debugger detaches
|
|
238
|
+
| Network | `page.get_network`, `network.intercept.add/remove/list/clear` (debugger-backed; rules are in-memory and drop if the debugger detaches - verify with `list`) |
|
|
236
239
|
| Interact | `input.click`, `input.type`, `input.fill`, `input.focus`, `input.press_key`, `cdp.dispatch_key_event`, `input.hover`, `input.drag`, `input.scroll_into_view` |
|
|
237
240
|
| Tabs | `tabs.list` (preferred), `tabs.create` (avoid unless necessary), `tabs.close`, `tabs.activate` |
|
|
238
241
|
| Patch | `patch.apply_styles`, `patch.apply_dom`, `patch.rollback` |
|
|
@@ -249,7 +252,7 @@ When the user has a localhost dev server with watch/HMR:
|
|
|
249
252
|
3. **Identify the problem** - use `styles.get_computed`, `dom.get_html`, or `page.get_console` for errors.
|
|
250
253
|
4. **Prototype with patches** - `patch.apply_styles` / `patch.apply_dom` to verify the fix visually.
|
|
251
254
|
5. **Edit source files** - modify the actual code in the agent's workspace.
|
|
252
|
-
6. **Wait for HMR** - `dom.wait_for`
|
|
255
|
+
6. **Wait for HMR** - use `dom.wait_for` for expected new text, a selector matching the changed attribute, or a detach/attach remount. Do not wait for `attached` on an unchanged selector that already exists.
|
|
253
256
|
7. **Verify the change** - re-inspect the same area; compare with patch expectations.
|
|
254
257
|
8. **Check for regressions** - `page.get_console` for new errors; scroll and inspect adjacent areas.
|
|
255
258
|
9. **Rollback patches** - `patch.rollback` all temporary patches.
|
|
@@ -257,7 +260,8 @@ When the user has a localhost dev server with watch/HMR:
|
|
|
257
260
|
## Investigate-a-Bug Workflow
|
|
258
261
|
|
|
259
262
|
```
|
|
260
|
-
page.get_state → page.get_console (
|
|
263
|
+
page.get_state → page.get_console(clear: true) + page.get_network(clear: true)
|
|
264
|
+
→ reproduce the bug → page.get_console + page.get_network
|
|
261
265
|
→ dom.find_by_text('<error text>') or dom.query('<selector>')
|
|
262
266
|
→ styles.get_computed (check layout/visibility)
|
|
263
267
|
→ page.evaluate('document.querySelector(...).dataset') (read data attrs)
|
|
@@ -287,7 +291,7 @@ dom.find_by_role('button', 'Login') → input.click
|
|
|
287
291
|
|
|
288
292
|
`bbx a11y-tree` and `dom.get_accessibility_tree` are sensitive to `maxDepth` and `maxNodes`. Shallow runs can undercount interactive nodes on real pages, so widen those limits before treating a low interactive count as a bug.
|
|
289
293
|
|
|
290
|
-
> **MCP mode:** If Browser Bridge is connected via MCP (tools named `browser_dom`, `browser_capture`, etc.), use the MCP tools directly
|
|
294
|
+
> **MCP mode:** If Browser Bridge is connected via MCP (tools named `browser_dom`, `browser_capture`, etc.), use the MCP tools directly - do not shell out to `bbx`. The MCP tools map 1:1 to CLI capabilities. In prompts, `BB MCP` and `Browser Bridge MCP` both work. Do not treat `bbx-mcp` as a skill alias.
|
|
291
295
|
>
|
|
292
296
|
> For open-ended MCP inspection tasks, prefer `browser_investigate` first. It is read-only, designed for cheaper delegated investigation, and falls back to a deterministic sequence when the client cannot delegate. Escalate to `browser_capture` only when the structured investigation is not enough.
|
|
293
297
|
|
|
@@ -144,19 +144,22 @@ bbx call input.scroll_into_view '{"target":{"selector":"[data-testid=\"submit-bu
|
|
|
144
144
|
## Network Monitoring
|
|
145
145
|
|
|
146
146
|
```bash
|
|
147
|
-
bbx
|
|
148
|
-
bbx
|
|
149
|
-
|
|
147
|
+
bbx call page.get_console '{"clear":true}' # install capture and clear old console entries
|
|
148
|
+
bbx call page.get_network '{"clear":true}' # install capture and clear old network entries
|
|
149
|
+
# reproduce the interaction here
|
|
150
|
+
bbx network 50 # newly captured requests
|
|
151
|
+
bbx console error # newly captured errors
|
|
150
152
|
```
|
|
151
153
|
|
|
152
154
|
Each entry: `method`, `url`, `status`, `duration`, `initiator`.
|
|
153
155
|
|
|
154
156
|
Typical workflow - debug API calls:
|
|
155
157
|
|
|
156
|
-
1. `page.
|
|
157
|
-
2.
|
|
158
|
-
3.
|
|
159
|
-
4.
|
|
158
|
+
1. Prime and clear `page.get_console` and `page.get_network`
|
|
159
|
+
2. Reproduce the interaction
|
|
160
|
+
3. Read and filter `page.get_network` by URL pattern or status code
|
|
161
|
+
4. Cross-reference with `page.get_console` for errors
|
|
162
|
+
5. Use `page.evaluate` only if lighter evidence cannot expose the needed response state
|
|
160
163
|
|
|
161
164
|
## Network Interception
|
|
162
165
|
|
|
@@ -192,7 +195,7 @@ bbx call input.select_option '{"target":{"elementRef":"el_456"},"values":["us"]}
|
|
|
192
195
|
|
|
193
196
|
Select by value, label, or index. Multiple values for multi-select.
|
|
194
197
|
|
|
195
|
-
**Text fields
|
|
198
|
+
**Text fields - `fill` vs `type`:**
|
|
196
199
|
|
|
197
200
|
```bash
|
|
198
201
|
bbx fill el_123 hello@example.com # set value instantly (preferred for forms)
|
|
@@ -27,7 +27,7 @@ The table below includes the legacy capability bucket for each method so agents
|
|
|
27
27
|
- `-` means the method is global/system-scoped and was never gated by a former capability bucket.
|
|
28
28
|
- Capability names are descriptive coverage labels only. Browser Bridge access is window-scoped now; there are no capability-scoped sessions.
|
|
29
29
|
|
|
30
|
-
## All Methods (
|
|
30
|
+
## All Methods (65)
|
|
31
31
|
|
|
32
32
|
| # | Method | Tab? | CDP? | Group | Capability | Notes |
|
|
33
33
|
| --- | ---------------------------------- | ---- | ---- | ----------- | -------------------- | ----------------------------------------------------------------------- |
|
|
@@ -44,7 +44,7 @@ The table below includes the legacy capability bucket for each method so agents
|
|
|
44
44
|
| 11 | `page.get_state` | Yes | - | page | `page.read` | URL, readiness, focus, scroll, viewport |
|
|
45
45
|
| 12 | `page.evaluate` | Yes | CDP | page | `page.evaluate` | JS expression in page context; last resort |
|
|
46
46
|
| 13 | `page.get_console` | Yes | - | page | `page.read` | Buffered console messages; filter by `level`, `limit` |
|
|
47
|
-
| 14 | `page.wait_for_load_state` | Yes | - | wait | `page.read` | Block until tab `complete`; `timeoutMs`
|
|
47
|
+
| 14 | `page.wait_for_load_state` | Yes | - | wait | `page.read` | Block until tab `complete`; `timeoutMs` range 0.5-120 s |
|
|
48
48
|
| 15 | `page.get_storage` | Yes | - | page | `page.read` | `localStorage`/`sessionStorage`; optional `keys` |
|
|
49
49
|
| 16 | `page.get_text` | Yes | - | page | `page.read` | Full page text; `textBudget` limits size |
|
|
50
50
|
| 17 | `page.get_network` | Yes | - | page | `network.read` | Intercepted fetch/XHR; `limit` entries |
|
|
@@ -121,7 +121,7 @@ bbx access-request
|
|
|
121
121
|
bbx call access.request
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
If a tab-bound call returns `ACCESS_DENIED`, it also surfaces the Enable prompt automatically
|
|
124
|
+
If a tab-bound call returns `ACCESS_DENIED`, it also surfaces the Enable prompt automatically - so explicit `access.request` is optional but useful for proactive setup.
|
|
125
125
|
|
|
126
126
|
If access is already pending for a window, do not call `access.request` again. Ask the user to click `Enable` for the requested window and wait for confirmation before continuing.
|
|
127
127
|
|
|
@@ -139,7 +139,9 @@ bbx call page.evaluate '{"expression":"await fetch(\"/api/health\").then(r=>r.js
|
|
|
139
139
|
|
|
140
140
|
### page.get_console
|
|
141
141
|
|
|
142
|
-
Read buffered console output. The console interceptor is auto-installed on first call. Captures `log`, `warn`, `error`, `info`, `debug` plus uncaught exceptions and unhandled rejections.
|
|
142
|
+
Read buffered console output. The console interceptor is auto-installed on first call. Captures `log`, `warn`, `error`, `info`, `debug` plus uncaught exceptions and unhandled rejections. `level` is a minimum severity, so `warn` includes errors, exceptions, and rejections; `exception` and `rejection` select only that exact stream.
|
|
143
|
+
|
|
144
|
+
To capture a reproduction reliably, call it once with `clear: true` before triggering the event. This installs the interceptor and removes older entries. Reproduce the issue, then read again without `clear`.
|
|
143
145
|
|
|
144
146
|
```bash
|
|
145
147
|
bbx console # all levels
|
|
@@ -180,7 +182,7 @@ If `_registryPruned` is true, refresh previously cached refs before reusing them
|
|
|
180
182
|
|
|
181
183
|
### dom.wait_for
|
|
182
184
|
|
|
183
|
-
Wait for a DOM condition using MutationObserver + 250 ms polling fallback. Returns `{found, elementRef, duration}`.
|
|
185
|
+
Wait for a DOM condition using MutationObserver + 250 ms polling fallback. A text-only wait searches all elements. `hidden` succeeds when no matching visible element remains, including when it is absent. Returns `{found, elementRef, duration}`.
|
|
184
186
|
|
|
185
187
|
- `state`: `attached` (default), `detached`, `visible`, `hidden`
|
|
186
188
|
- `text`: optional text content filter
|
|
@@ -285,6 +287,8 @@ bbx call page.get_text '{"textBudget":2000}'
|
|
|
285
287
|
|
|
286
288
|
### page.get_network
|
|
287
289
|
|
|
290
|
+
The fetch/XHR interceptor is installed on the first call. Before reproducing a network issue, call `page.get_network` with `clear: true`, trigger the action, then read the buffer again without `clear` so the relevant request is not missed or erased.
|
|
291
|
+
|
|
288
292
|
Read intercepted fetch/XHR requests. The interceptor is auto-installed on first call (via MAIN world script). Returns `{entries, count}` sorted newest-first.
|
|
289
293
|
|
|
290
294
|
```bash
|
|
@@ -125,7 +125,7 @@ bbx network # recent fetch/XHR entries
|
|
|
125
125
|
bbx network 50 # last 50 entries
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
The interceptor auto-installs on first call. Each entry shows `method`, `url`, `status`, `duration`.
|
|
128
|
+
The interceptor auto-installs on first call. Before reproducing an event, call `page.get_network` once with `clear: true` to install capture and remove old entries. Trigger the action, then read again without `clear`. Each entry shows `method`, `url`, `status`, `duration`.
|
|
129
129
|
|
|
130
130
|
## Accessibility Tree for Semantic Discovery
|
|
131
131
|
|
|
@@ -151,12 +151,13 @@ bbx find-role navigation # semantic, works regardless of classes
|
|
|
151
151
|
|
|
152
152
|
## HMR-Aware Waiting
|
|
153
153
|
|
|
154
|
-
After modifying source code, the dev server hot-reloads.
|
|
154
|
+
After modifying source code, the dev server hot-reloads. Wait for evidence of the change, not `attached` on a selector that already exists:
|
|
155
155
|
|
|
156
156
|
```bash
|
|
157
|
-
bbx
|
|
157
|
+
bbx call dom.wait_for '{"selector":"[data-component=\"Header\"][data-version=\"new\"]","state":"attached","timeoutMs":5000}'
|
|
158
|
+
bbx call dom.wait_for '{"selector":"[data-component=\"Header\"]","text":"Expected new heading","timeoutMs":5000}'
|
|
159
|
+
# For a known remount, wait for detached and then attached states.
|
|
158
160
|
bbx console error # check for HMR errors
|
|
159
|
-
bbx eval 'module.hot?.status?.()' # check HMR status (webpack)
|
|
160
161
|
```
|
|
161
162
|
|
|
162
163
|
## Parent-Agent Response Policy
|
|
@@ -9,7 +9,7 @@ Use when a dev server is already running and you want to prove a fix rendered.
|
|
|
9
9
|
3. `styles.get_computed` or `layout.get_box_model`
|
|
10
10
|
4. `patch.apply_styles` to prove the visual fix
|
|
11
11
|
5. Edit source files
|
|
12
|
-
6. `dom.wait_for` on
|
|
12
|
+
6. `dom.wait_for` for expected new text, a selector matching a changed attribute, or a detach/attach remount; do not wait for `attached` on an unchanged selector
|
|
13
13
|
7. `page.get_console`
|
|
14
14
|
8. `patch.rollback`
|
|
15
15
|
|
|
@@ -23,12 +23,14 @@ Acceptance:
|
|
|
23
23
|
|
|
24
24
|
Use when a submission flow fails silently or the UI state is inconsistent.
|
|
25
25
|
|
|
26
|
-
1. `
|
|
27
|
-
2. `
|
|
28
|
-
3. `
|
|
29
|
-
4. `
|
|
30
|
-
5. `
|
|
31
|
-
6. `
|
|
26
|
+
1. `page.get_console` with `clear: true` to install and clear console capture
|
|
27
|
+
2. `page.get_network` with `clear: true` to install and clear network capture
|
|
28
|
+
3. `dom.find_by_role` for the form control
|
|
29
|
+
4. `input.type` / `input.click` to reproduce the issue
|
|
30
|
+
5. `page.get_network` without `clear`
|
|
31
|
+
6. `page.get_console` without `clear`
|
|
32
|
+
7. `dom.wait_for` for success or error state
|
|
33
|
+
8. `dom.query` on the resulting panel or message
|
|
32
34
|
|
|
33
35
|
Acceptance:
|
|
34
36
|
|
|
@@ -60,7 +62,7 @@ Use when a component only breaks at a specific breakpoint.
|
|
|
60
62
|
|
|
61
63
|
1. `page.get_state`
|
|
62
64
|
2. `viewport.resize`
|
|
63
|
-
3. `dom.wait_for`
|
|
65
|
+
3. `dom.wait_for` for the expected responsive text, attribute selector, visibility change, or remount
|
|
64
66
|
4. `layout.get_box_model`
|
|
65
67
|
5. `styles.get_computed`
|
|
66
68
|
6. `page.get_console`
|
|
@@ -87,7 +89,7 @@ Drag:
|
|
|
87
89
|
|
|
88
90
|
1. `dom.query` for source and destination
|
|
89
91
|
2. `input.drag`
|
|
90
|
-
3. `dom.wait_for`
|
|
92
|
+
3. `dom.wait_for` for expected changed text, attribute selector, or a detach/attach remount
|
|
91
93
|
4. `dom.query` to verify order or placement
|
|
92
94
|
|
|
93
95
|
Acceptance:
|