@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.
@@ -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 — trivial
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 — trivial
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 — low (basic reads), moderate (evaluate, debugger-backed)
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 — moderate (holds debugger session)
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 — low
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 — low (reads), moderate (accessibility tree)
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 — low
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 — low (computed), moderate (matched rules)
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 — low (scroll), moderate (resize, debugger-backed)
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 — low (simple), moderate (drag)
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 — high (screenshots, CDP)
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 — moderate (side effects)
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 — high (raw protocol, large payloads)
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 — moderate (debugger-backed)
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: unknown
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 hint = summarizeErrorHint(response.error.code);
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
- let label;
352
- if (method === 'dom.find_by_text' || method === 'dom.find_by_role') {
353
- label = 'Found';
354
- } else {
355
- label = 'DOM query returned';
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
- `${label} ${nodes.length} element(s)${nodes.length > 15 ? '; showing first 15' : ''}.`,
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 — all from the real tab the user already has open.
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 one `batch` call (executes concurrently via Promise.all).
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 files or triggering navigation, use `dom.wait_for` or `page.wait_for_load_state` before inspecting.
162
- 14. **Console after interaction** - call `page.get_console` after mutations to catch runtime errors early.
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 console error # check for runtime errors
222
- bbx network 20 # inspect recent requests
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 — verify with `list`) |
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` with the selector that should change, or `page.wait_for_load_state`.
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 (check for errors)
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 — 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.
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 network # recent requests
148
- bbx network 50 # last 50
149
- bbx call page.get_network '{"limit":20,"clear":true}'
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.get_network` to see recent requests
157
- 2. Filter by URL pattern or status code
158
- 3. Cross-reference with `page.get_console` for errors
159
- 4. Use `page.evaluate` to replay or inspect response data
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 — `fill` vs `type`:**
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 (59)
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` capped 30 s |
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 — so explicit `access.request` is optional but useful for proactive setup.
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`. Use `clear: true` to reset the buffer.
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. Always wait before inspecting:
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 wait '[data-component="Header"]' 5000 # wait for component re-mount
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 the component selector
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. `dom.find_by_role` for the form control
27
- 2. `input.type` / `input.click`
28
- 3. `page.get_network`
29
- 4. `page.get_console`
30
- 5. `dom.wait_for` for success or error state
31
- 6. `dom.query` on the resulting panel or message
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: