@browserbridge/bbx 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +6 -4
  2. package/package.json +53 -53
  3. package/packages/agent-client/src/cli-helpers.js +43 -5
  4. package/packages/agent-client/src/cli.js +176 -171
  5. package/packages/agent-client/src/client.js +66 -21
  6. package/packages/agent-client/src/command-registry.js +104 -69
  7. package/packages/agent-client/src/detect.js +162 -54
  8. package/packages/agent-client/src/install.js +34 -28
  9. package/packages/agent-client/src/mcp-config.js +40 -40
  10. package/packages/agent-client/src/runtime.js +41 -20
  11. package/packages/agent-client/src/setup-status.js +23 -30
  12. package/packages/mcp-server/src/bin.js +57 -5
  13. package/packages/mcp-server/src/handlers.js +573 -256
  14. package/packages/mcp-server/src/server.js +568 -257
  15. package/packages/native-host/bin/bridge-daemon.js +39 -6
  16. package/packages/native-host/bin/install-manifest.js +26 -4
  17. package/packages/native-host/bin/postinstall.js +4 -2
  18. package/packages/native-host/src/config.js +142 -13
  19. package/packages/native-host/src/daemon-process.js +396 -0
  20. package/packages/native-host/src/daemon.js +350 -150
  21. package/packages/native-host/src/framing.js +131 -11
  22. package/packages/native-host/src/install-manifest.js +194 -29
  23. package/packages/native-host/src/native-host.js +154 -102
  24. package/packages/protocol/src/budget.js +3 -7
  25. package/packages/protocol/src/capabilities.js +6 -3
  26. package/packages/protocol/src/defaults.js +1 -0
  27. package/packages/protocol/src/errors.js +15 -11
  28. package/packages/protocol/src/payload-cost.js +19 -6
  29. package/packages/protocol/src/protocol.js +242 -73
  30. package/packages/protocol/src/registry.js +311 -45
  31. package/packages/protocol/src/summary.js +260 -109
  32. package/packages/protocol/src/types.js +29 -4
  33. package/skills/browser-bridge/SKILL.md +3 -2
  34. package/skills/browser-bridge/agents/openai.yaml +3 -3
  35. package/skills/browser-bridge/references/interaction.md +34 -11
  36. package/skills/browser-bridge/references/patch-workflow.md +3 -0
  37. package/skills/browser-bridge/references/protocol.md +127 -71
  38. package/skills/browser-bridge/references/tailwind.md +12 -11
  39. package/skills/browser-bridge/references/token-efficiency.md +23 -22
  40. package/skills/browser-bridge/references/ui-workflows.md +8 -0
  41. package/CHANGELOG.md +0 -55
  42. package/assets/banner.jpg +0 -0
  43. package/assets/logo.png +0 -0
  44. package/assets/logo.svg +0 -65
  45. package/docs/api-reference.md +0 -157
  46. package/docs/cli-guide.md +0 -128
  47. package/docs/index.md +0 -25
  48. package/docs/manual-setup.md +0 -140
  49. package/docs/mcp-vs-cli.md +0 -258
  50. package/docs/publishing.md +0 -114
  51. package/docs/quickstart.md +0 -104
  52. package/docs/troubleshooting.md +0 -59
  53. package/docs/usage-scenarios.md +0 -136
  54. package/manifest.json +0 -52
  55. package/packages/extension/assets/icon-128.png +0 -0
  56. package/packages/extension/assets/icon-16.png +0 -0
  57. package/packages/extension/assets/icon-32.png +0 -0
  58. package/packages/extension/assets/icon-48.png +0 -0
  59. package/packages/extension/src/background-helpers.js +0 -459
  60. package/packages/extension/src/background-routing.js +0 -91
  61. package/packages/extension/src/background.js +0 -3227
  62. package/packages/extension/src/content-script-helpers.js +0 -281
  63. package/packages/extension/src/content-script.js +0 -1977
  64. package/packages/extension/src/debugger-coordinator.js +0 -188
  65. package/packages/extension/src/sidepanel-helpers.js +0 -102
  66. package/packages/extension/ui/offscreen.html +0 -6
  67. package/packages/extension/ui/offscreen.js +0 -61
  68. package/packages/extension/ui/popup.html +0 -35
  69. package/packages/extension/ui/popup.js +0 -279
  70. package/packages/extension/ui/sidepanel.html +0 -102
  71. package/packages/extension/ui/sidepanel.js +0 -1854
  72. package/packages/extension/ui/ui.css +0 -1159
@@ -1,9 +1,6 @@
1
1
  // @ts-check
2
2
 
3
- import {
4
- estimateJsonPayloadCost,
5
- getCostClass,
6
- } from './index.js';
3
+ import { estimateSerializedPayloadCost, getCostClass, serializeJsonPayload } from './index.js';
7
4
 
8
5
  /** @typedef {import('./types.js').BridgeResponse} BridgeResponse */
9
6
  /** @typedef {import('./types.js').BridgeMethod} SummaryMethod */
@@ -53,16 +50,44 @@ import {
53
50
 
54
51
  /** @type {Record<string, ActionSummary>} */
55
52
  const ACTION_SUMMARIES = {
56
- hovered: { text: r => `Hover ${r.hovered ? 'active' : 'failed'} on ${r.elementRef}.`, evidence: r => r },
57
- dragged: { text: r => `Drag ${r.dragged ? 'completed' : 'failed'}: ${r.sourceRef} ${r.destinationRef}.`, evidence: r => r },
58
- closed: { text: r => `Tab ${r.tabId} closed.`, evidence: r => r },
59
- clicked: { text: r => `Clicked ${r.elementRef ?? 'element'}.`, evidence: r => ({ elementRef: r.elementRef }) },
60
- focused: { text: r => `Focused ${r.elementRef ?? 'element'}.`, evidence: r => ({ elementRef: r.elementRef }) },
61
- typed: { text: r => `Typed into ${r.elementRef ?? 'element'}.`, evidence: r => ({ elementRef: r.elementRef }) },
62
- pressed: { text: r => `Key pressed${r.key ? ` (${r.key})` : ''}.`, evidence: r => r },
63
- navigated: { text: r => `Navigated to ${r.url ?? 'page'}.`, evidence: r => ({ url: r.url }) },
64
- scrolled: { text: r => `Scrolled to (${r.x ?? 0}, ${r.y ?? 0}).`, evidence: r => r },
65
- resized: { text: r => `Viewport resized to ${r.width ?? '?'}×${r.height ?? '?'}.`, evidence: r => r },
53
+ hovered: {
54
+ text: (r) => `Hover ${r.hovered ? 'active' : 'failed'} on ${r.elementRef}.`,
55
+ evidence: (r) => r,
56
+ },
57
+ dragged: {
58
+ text: (r) =>
59
+ `Drag ${r.dragged ? 'completed' : 'failed'}: ${r.sourceRef} ${r.destinationRef}.`,
60
+ evidence: (r) => r,
61
+ },
62
+ closed: { text: (r) => `Tab ${r.tabId} closed.`, evidence: (r) => r },
63
+ clicked: {
64
+ text: (r) => `Clicked ${r.elementRef ?? 'element'}.`,
65
+ evidence: (r) => ({ elementRef: r.elementRef }),
66
+ },
67
+ focused: {
68
+ text: (r) => `Focused ${r.elementRef ?? 'element'}.`,
69
+ evidence: (r) => ({ elementRef: r.elementRef }),
70
+ },
71
+ typed: {
72
+ text: (r) => `Typed into ${r.elementRef ?? 'element'}.`,
73
+ evidence: (r) => ({ elementRef: r.elementRef }),
74
+ },
75
+ pressed: {
76
+ text: (r) => `Key pressed${r.key ? ` (${r.key})` : ''}.`,
77
+ evidence: (r) => r,
78
+ },
79
+ navigated: {
80
+ text: (r) => `Navigated to ${r.url ?? 'page'}.`,
81
+ evidence: (r) => ({ url: r.url }),
82
+ },
83
+ scrolled: {
84
+ text: (r) => `Scrolled to (${r.x ?? 0}, ${r.y ?? 0}).`,
85
+ evidence: (r) => r,
86
+ },
87
+ resized: {
88
+ text: (r) => `Viewport resized to ${r.width ?? '?'}×${r.height ?? '?'}.`,
89
+ evidence: (r) => r,
90
+ },
66
91
  };
67
92
 
68
93
  /**
@@ -74,21 +99,30 @@ const ACTION_SUMMARIES = {
74
99
  * @returns {AnnotatedBridgeSummary}
75
100
  */
76
101
  export function annotateBridgeSummary(summary, response) {
77
- const transportBytes = getNumericMetaField(response.meta, 'transport_bytes')
78
- ?? getNumericMetaField(response.meta, 'response_bytes')
79
- ?? estimateJsonPayloadCost(response.ok ? response.result : { error: response.error }).bytes;
80
- const transportTokens = getNumericMetaField(response.meta, 'transport_approx_tokens')
81
- ?? getNumericMetaField(response.meta, 'approx_tokens')
82
- ?? estimateJsonPayloadCost(response.ok ? response.result : { error: response.error }).approxTokens;
83
- const summaryCost = estimateJsonPayloadCost(summary);
102
+ const metaTransportBytes =
103
+ getNumericMetaField(response.meta, 'transport_bytes') ??
104
+ getNumericMetaField(response.meta, 'response_bytes');
105
+ const metaTransportTokens =
106
+ getNumericMetaField(response.meta, 'transport_approx_tokens') ??
107
+ getNumericMetaField(response.meta, 'approx_tokens');
108
+ const transportCost =
109
+ metaTransportBytes == null || metaTransportTokens == null
110
+ ? estimateSerializedPayloadCost(
111
+ serializeJsonPayload(response.ok ? response.result : { error: response.error })
112
+ )
113
+ : null;
114
+ const transportBytes = metaTransportBytes ?? transportCost?.bytes ?? 0;
115
+ const transportTokens = metaTransportTokens ?? transportCost?.approxTokens ?? 0;
116
+ const summaryCost = estimateSerializedPayloadCost(serializeJsonPayload(summary));
84
117
 
85
118
  return {
86
119
  ...summary,
87
120
  transportBytes,
88
121
  transportTokens,
89
- transportCostClass: getMetaCostClass(response.meta, 'transport_cost_class')
90
- ?? getMetaCostClass(response.meta, 'cost_class')
91
- ?? getCostClass(transportTokens),
122
+ transportCostClass:
123
+ getMetaCostClass(response.meta, 'transport_cost_class') ??
124
+ getMetaCostClass(response.meta, 'cost_class') ??
125
+ getCostClass(transportTokens),
92
126
  summaryBytes: summaryCost.bytes,
93
127
  summaryTokens: summaryCost.approxTokens,
94
128
  summaryCostClass: summaryCost.costClass,
@@ -132,28 +166,32 @@ export function summarizeBridgeResponse(response, method) {
132
166
  `${response.error.code}: ${response.error.message}${hint ? ` ${hint}` : ''}`,
133
167
  protocolWarning
134
168
  ),
135
- evidence: response.error.details ?? null
169
+ evidence: response.error.details ?? null,
136
170
  };
137
171
  }
138
172
 
139
173
  const result = toRecord(response.result);
140
174
  if (typeof result.daemon === 'string') {
141
- const access = result.access && typeof result.access === 'object'
142
- ? /** @type {Record<string, unknown>} */ (result.access)
143
- : null;
144
- const accessSummary = access == null
145
- ? ''
146
- : access.enabled
147
- ? ` Access: ${access.routeReady ? `ready on tab ${access.routeTabId}.` : `enabled${typeof access.reason === 'string' ? ` (${access.reason})` : '.'}`}`
148
- : ' Access: disabled.';
175
+ const access =
176
+ result.access && typeof result.access === 'object'
177
+ ? /** @type {Record<string, unknown>} */ (result.access)
178
+ : null;
179
+ const accessSummary =
180
+ access == null
181
+ ? ''
182
+ : access.enabled
183
+ ? ` Access: ${access.routeReady ? `ready on tab ${access.routeTabId}.` : `enabled${typeof access.reason === 'string' ? ` (${access.reason})` : '.'}`}`
184
+ : ' Access: disabled.';
149
185
  const connectedExtensions = Array.isArray(result.connectedExtensions)
150
186
  ? /** @type {Array<Record<string, unknown>>} */ (result.connectedExtensions)
151
187
  : [];
152
188
  const extensionSummary = result.extensionConnected
153
- ? `connected (${connectedExtensions.length}: ${connectedExtensions.map((ext) => {
154
- const label = `${ext.browserName ?? 'unknown'}${ext.profileLabel ? '/' + ext.profileLabel : ''}`;
155
- return ext.accessEnabled ? `${label}*` : label;
156
- }).join(', ')})`
189
+ ? `connected (${connectedExtensions.length}: ${connectedExtensions
190
+ .map((ext) => {
191
+ const label = `${ext.browserName ?? 'unknown'}${ext.profileLabel ? '/' + ext.profileLabel : ''}`;
192
+ return ext.accessEnabled ? `${label}*` : label;
193
+ })
194
+ .join(', ')})`
157
195
  : 'disconnected';
158
196
  return {
159
197
  ok: true,
@@ -161,22 +199,36 @@ export function summarizeBridgeResponse(response, method) {
161
199
  `Daemon: ${result.daemon}. Extension: ${extensionSummary}.${accessSummary}`,
162
200
  protocolWarning
163
201
  ),
164
- evidence: result
202
+ evidence: result,
165
203
  };
166
204
  }
167
205
  if (Array.isArray(result.mcpClients) && Array.isArray(result.skillTargets)) {
168
- const configuredMcp = result.mcpClients.filter((entry) => entry && typeof entry === 'object' && /** @type {Record<string, unknown>} */ (entry).configured).length;
169
- const installedSkills = result.skillTargets.filter((entry) => entry && typeof entry === 'object' && /** @type {Record<string, unknown>} */ (entry).installed).length;
206
+ const configuredMcp = result.mcpClients.filter(
207
+ (entry) =>
208
+ entry &&
209
+ typeof entry === 'object' &&
210
+ /** @type {Record<string, unknown>} */ (entry).configured
211
+ ).length;
212
+ const installedSkills = result.skillTargets.filter(
213
+ (entry) =>
214
+ entry &&
215
+ typeof entry === 'object' &&
216
+ /** @type {Record<string, unknown>} */ (entry).installed
217
+ ).length;
170
218
  return {
171
219
  ok: true,
172
220
  summary: appendProtocolWarning(
173
221
  `Setup: MCP configured for ${configuredMcp}/${result.mcpClients.length} clients; skill installed for ${installedSkills}/${result.skillTargets.length} targets.`,
174
222
  protocolWarning
175
223
  ),
176
- evidence: result
224
+ evidence: result,
177
225
  };
178
226
  }
179
- if (typeof result.url === 'string' && typeof result.title === 'string' && typeof result.origin === 'string') {
227
+ if (
228
+ typeof result.url === 'string' &&
229
+ typeof result.title === 'string' &&
230
+ typeof result.origin === 'string'
231
+ ) {
180
232
  /** @type {string[]} */
181
233
  const hints = [];
182
234
  if (result.hints && typeof result.hints === 'object') {
@@ -190,10 +242,18 @@ export function summarizeBridgeResponse(response, method) {
190
242
  `Page: ${result.title} (${result.origin})${hints.length ? ` [${hints.join(', ')}]` : ''}.`,
191
243
  protocolWarning
192
244
  ),
193
- evidence: { url: result.url, origin: result.origin, title: result.title, hints: result.hints }
245
+ evidence: {
246
+ url: result.url,
247
+ origin: result.origin,
248
+ title: result.title,
249
+ hints: result.hints,
250
+ },
194
251
  };
195
252
  }
196
- if ((typeof result.text === 'string' || typeof result.value === 'string') && typeof result.truncated === 'boolean') {
253
+ if (
254
+ (typeof result.text === 'string' || typeof result.value === 'string') &&
255
+ typeof result.truncated === 'boolean'
256
+ ) {
197
257
  const text = /** @type {string} */ (result.text ?? result.value);
198
258
  const len = typeof result.length === 'number' ? result.length : text.length;
199
259
  const label = method === 'dom.get_text' ? 'Element text' : 'Page text';
@@ -203,7 +263,11 @@ export function summarizeBridgeResponse(response, method) {
203
263
  `${label}: ${len} chars${result.truncated ? ' (truncated)' : ''}.`,
204
264
  protocolWarning
205
265
  ),
206
- evidence: { text: text.slice(0, 500), length: len, truncated: result.truncated }
266
+ evidence: {
267
+ text: text.slice(0, 500),
268
+ length: len,
269
+ truncated: result.truncated,
270
+ },
207
271
  };
208
272
  }
209
273
  if (Array.isArray(result.tabs)) {
@@ -215,14 +279,24 @@ export function summarizeBridgeResponse(response, method) {
215
279
  tabId: tab.tabId,
216
280
  active: tab.active,
217
281
  origin: tab.origin,
218
- title: tab.title
219
- }))
282
+ title: tab.title,
283
+ })),
220
284
  };
221
285
  }
222
- if (Array.isArray(result.nodes) && typeof result.total === 'number' && result.nodes[0]?.role !== undefined) {
286
+ if (
287
+ Array.isArray(result.nodes) &&
288
+ typeof result.total === 'number' &&
289
+ result.nodes[0]?.role !== undefined
290
+ ) {
223
291
  const nodes = /** @type {Array<Record<string, unknown>>} */ (result.nodes);
224
292
  const interactive = nodes.filter((/** @type {Record<string, unknown>} */ n) => n.interactive);
225
- const shown = interactive.length > 0 ? interactive : nodes.filter((/** @type {Record<string, unknown>} */ n) => n.role && n.role !== 'none' && n.role !== 'generic');
293
+ const shown =
294
+ interactive.length > 0
295
+ ? interactive
296
+ : nodes.filter(
297
+ (/** @type {Record<string, unknown>} */ n) =>
298
+ n.role && n.role !== 'none' && n.role !== 'generic'
299
+ );
226
300
  return {
227
301
  ok: true,
228
302
  summary: appendProtocolWarning(
@@ -235,7 +309,7 @@ export function summarizeBridgeResponse(response, method) {
235
309
  if (n.interactive) entry.interactive = true;
236
310
  if (n.value) entry.value = n.value;
237
311
  return entry;
238
- })
312
+ }),
239
313
  };
240
314
  }
241
315
  if (Array.isArray(result.nodes)) {
@@ -247,14 +321,20 @@ export function summarizeBridgeResponse(response, method) {
247
321
  if (typeof n.attrs === 'object' && n.attrs !== null) {
248
322
  const attrs = /** @type {Record<string, unknown>} */ (n.attrs);
249
323
  if (attrs.id) entry.id = attrs.id;
250
- if (typeof attrs.class === 'string') entry.cls = attrs.class.split(' ').slice(0, 3).join(' ');
324
+ if (typeof attrs.class === 'string')
325
+ entry.cls = attrs.class.split(' ').slice(0, 3).join(' ');
251
326
  if (attrs.role) entry.role = attrs.role;
252
327
  if (attrs['aria-label']) entry.label = attrs['aria-label'];
253
328
  if (attrs['data-testid']) entry.testId = attrs['data-testid'];
254
329
  }
255
330
  if (!entry.role && n.role) entry.role = n.role;
256
331
  if (!entry.label && n.name) entry.label = n.name;
257
- const text = typeof n.textExcerpt === 'string' ? n.textExcerpt : typeof n.text === 'string' ? n.text : '';
332
+ const text =
333
+ typeof n.textExcerpt === 'string'
334
+ ? n.textExcerpt
335
+ : typeof n.text === 'string'
336
+ ? n.text
337
+ : '';
258
338
  if (text) {
259
339
  entry.text = text.length > 80 ? `${text.slice(0, 79)}\u2026` : text;
260
340
  }
@@ -275,21 +355,21 @@ export function summarizeBridgeResponse(response, method) {
275
355
  `${label} ${nodes.length} element(s)${nodes.length > 15 ? '; showing first 15' : ''}.`,
276
356
  protocolWarning
277
357
  ),
278
- evidence: compact
358
+ evidence: compact,
279
359
  };
280
360
  }
281
361
  if (typeof result.rolledBack === 'boolean' || typeof result.rolled_back === 'boolean') {
282
362
  return {
283
363
  ok: true,
284
364
  summary: appendProtocolWarning('Patch rolled back.', protocolWarning),
285
- evidence: result
365
+ evidence: result,
286
366
  };
287
367
  }
288
368
  if (typeof result.patchId === 'string') {
289
369
  return {
290
370
  ok: true,
291
371
  summary: appendProtocolWarning(`Patch ${result.patchId} applied.`, protocolWarning),
292
- evidence: result
372
+ evidence: result,
293
373
  };
294
374
  }
295
375
  if (typeof result.found === 'boolean') {
@@ -301,7 +381,10 @@ export function summarizeBridgeResponse(response, method) {
301
381
  : `Element not found (timed out after ${result.duration ?? 0}ms).`,
302
382
  protocolWarning
303
383
  ),
304
- evidence: { elementRef: result.elementRef ?? null, duration: result.duration }
384
+ evidence: {
385
+ elementRef: result.elementRef ?? null,
386
+ duration: result.duration,
387
+ },
305
388
  };
306
389
  }
307
390
  if (typeof result.value !== 'undefined' && typeof result.type === 'string') {
@@ -313,7 +396,11 @@ export function summarizeBridgeResponse(response, method) {
313
396
  repr = '';
314
397
  } else if (typeof result.value === 'string') {
315
398
  repr = result.value.length > 200 ? `${result.value.slice(0, 199)}\u2026` : result.value;
316
- } else if (typeof result.value === 'object' && result.value !== null && Object.keys(result.value).length === 0) {
399
+ } else if (
400
+ typeof result.value === 'object' &&
401
+ result.value !== null &&
402
+ Object.keys(result.value).length === 0
403
+ ) {
317
404
  repr = '(empty - may be a Promise, Map, or non-serializable value)';
318
405
  } else {
319
406
  repr = JSON.stringify(result.value);
@@ -325,20 +412,33 @@ export function summarizeBridgeResponse(response, method) {
325
412
  repr ? `Evaluated to ${typeLabel}: ${repr}` : `Evaluated to ${typeLabel}.`,
326
413
  protocolWarning
327
414
  ),
328
- evidence: result
415
+ evidence: result,
329
416
  };
330
417
  }
331
- if (Array.isArray(result.entries) && result.entries.length > 0 && typeof result.entries[0]?.at === 'string' && typeof result.entries[0]?.method === 'string') {
418
+ if (
419
+ Array.isArray(result.entries) &&
420
+ result.entries.length > 0 &&
421
+ typeof result.entries[0]?.at === 'string' &&
422
+ typeof result.entries[0]?.method === 'string'
423
+ ) {
332
424
  const entries = /** @type {Array<Record<string, unknown>>} */ (result.entries);
333
425
  return {
334
426
  ok: true,
335
427
  summary: appendProtocolWarning(`Log: ${entries.length} entries.`, protocolWarning),
336
428
  evidence: entries.slice(-10).map((/** @type {Record<string, unknown>} */ e) => ({
337
- at: e.at, method: e.method, ok: e.ok, ...(typeof e.source === 'string' && e.source ? { source: e.source } : {})
338
- }))
429
+ at: e.at,
430
+ method: e.method,
431
+ ok: e.ok,
432
+ ...(typeof e.source === 'string' && e.source ? { source: e.source } : {}),
433
+ })),
339
434
  };
340
435
  }
341
- if (Array.isArray(result.entries) && (result.entries.length > 0 ? (result.entries[0]?.type === 'fetch' || result.entries[0]?.type === 'xhr') : method === 'page.get_network')) {
436
+ if (
437
+ Array.isArray(result.entries) &&
438
+ (result.entries.length > 0
439
+ ? result.entries[0]?.type === 'fetch' || result.entries[0]?.type === 'xhr'
440
+ : method === 'page.get_network')
441
+ ) {
342
442
  const entries = /** @type {Array<Record<string, unknown>>} */ (result.entries);
343
443
  return {
344
444
  ok: true,
@@ -347,8 +447,11 @@ export function summarizeBridgeResponse(response, method) {
347
447
  protocolWarning
348
448
  ),
349
449
  evidence: entries.slice(0, 20).map((/** @type {Record<string, unknown>} */ e) => ({
350
- method: e.method, url: truncateUrl(/** @type {string} */ (e.url)), status: e.status, duration: e.duration
351
- }))
450
+ method: e.method,
451
+ url: truncateUrl(/** @type {string} */ (e.url)),
452
+ status: e.status,
453
+ duration: e.duration,
454
+ })),
352
455
  };
353
456
  }
354
457
  if (Array.isArray(result.entries)) {
@@ -366,7 +469,7 @@ export function summarizeBridgeResponse(response, method) {
366
469
  entry.ago = formatRelativeTime(e.ts);
367
470
  }
368
471
  return entry;
369
- })
472
+ }),
370
473
  };
371
474
  }
372
475
  if (typeof result.html === 'string') {
@@ -376,7 +479,10 @@ export function summarizeBridgeResponse(response, method) {
376
479
  `HTML fragment: ${result.html.length} chars${result.truncated ? ' (truncated)' : ''}.`,
377
480
  protocolWarning
378
481
  ),
379
- evidence: { html: result.html.slice(0, 500), truncated: result.truncated }
482
+ evidence: {
483
+ html: result.html.slice(0, 500),
484
+ truncated: result.truncated,
485
+ },
380
486
  };
381
487
  }
382
488
  for (const [field, handler] of Object.entries(ACTION_SUMMARIES)) {
@@ -384,13 +490,12 @@ export function summarizeBridgeResponse(response, method) {
384
490
  return {
385
491
  ok: true,
386
492
  summary: appendProtocolWarning(handler.text(result), protocolWarning),
387
- evidence: handler.evidence(result)
493
+ evidence: handler.evidence(result),
388
494
  };
389
495
  }
390
496
  }
391
497
  if (typeof result.tabId === 'number' && typeof result.url === 'string') {
392
- const actionMethod =
393
- typeof result.method === 'string' ? result.method : method;
498
+ const actionMethod = typeof result.method === 'string' ? result.method : method;
394
499
  if (actionMethod === 'navigation.navigate') {
395
500
  return {
396
501
  ok: true,
@@ -405,16 +510,22 @@ export function summarizeBridgeResponse(response, method) {
405
510
  }
406
511
  return {
407
512
  ok: true,
408
- summary: appendProtocolWarning(`Tab ${result.tabId} created${result.url ? ` (${result.url})` : ''}.`, protocolWarning),
409
- evidence: result
513
+ summary: appendProtocolWarning(
514
+ `Tab ${result.tabId} created${result.url ? ` (${result.url})` : ''}.`,
515
+ protocolWarning
516
+ ),
517
+ evidence: result,
410
518
  };
411
519
  }
412
520
  if (typeof result.metrics === 'object' && result.metrics !== null) {
413
521
  const keys = Object.keys(result.metrics);
414
522
  return {
415
523
  ok: true,
416
- summary: appendProtocolWarning(`Performance: ${keys.length} metrics collected.`, protocolWarning),
417
- evidence: result.metrics
524
+ summary: appendProtocolWarning(
525
+ `Performance: ${keys.length} metrics collected.`,
526
+ protocolWarning
527
+ ),
528
+ evidence: result.metrics,
418
529
  };
419
530
  }
420
531
  if (typeof result.count === 'number' && typeof result.type === 'string' && result.entries) {
@@ -427,16 +538,26 @@ export function summarizeBridgeResponse(response, method) {
427
538
  }
428
539
  return {
429
540
  ok: true,
430
- summary: appendProtocolWarning(`Storage (${result.type}): ${result.count} entries.`, protocolWarning),
431
- evidence: safe
541
+ summary: appendProtocolWarning(
542
+ `Storage (${result.type}): ${result.count} entries.`,
543
+ protocolWarning
544
+ ),
545
+ evidence: safe,
432
546
  };
433
547
  }
434
- if (typeof result.tag === 'string' && typeof result.elementRef === 'string' && typeof result.bbox === 'object') {
548
+ if (
549
+ typeof result.tag === 'string' &&
550
+ typeof result.elementRef === 'string' &&
551
+ typeof result.bbox === 'object'
552
+ ) {
435
553
  const desc = [result.tag];
436
554
  if (result.id) desc[0] += `#${result.id}`;
437
- const textValue = typeof result.text === 'object' && result.text !== null && 'value' in result.text
438
- ? /** @type {{ value: string }} */ (result.text).value
439
- : typeof result.text === 'string' ? result.text : '';
555
+ const textValue =
556
+ typeof result.text === 'object' && result.text !== null && 'value' in result.text
557
+ ? /** @type {{ value: string }} */ (result.text).value
558
+ : typeof result.text === 'string'
559
+ ? result.text
560
+ : '';
440
561
  if (textValue) desc.push(String(textValue).slice(0, 60));
441
562
  const bbox = /** @type {Record<string, number>} */ (result.bbox);
442
563
  if (bbox.width && bbox.height) {
@@ -445,15 +566,29 @@ export function summarizeBridgeResponse(response, method) {
445
566
  return {
446
567
  ok: true,
447
568
  summary: appendProtocolWarning(`Element ${desc.join(', ')}.`, protocolWarning),
448
- evidence: { elementRef: result.elementRef, tag: result.tag, id: result.id, role: result.role, text: textValue, bbox: result.bbox }
569
+ evidence: {
570
+ elementRef: result.elementRef,
571
+ tag: result.tag,
572
+ id: result.id,
573
+ role: result.role,
574
+ text: textValue,
575
+ bbox: result.bbox,
576
+ },
449
577
  };
450
578
  }
451
- if (typeof result.properties === 'object' && result.properties !== null && typeof result.elementRef === 'string') {
579
+ if (
580
+ typeof result.properties === 'object' &&
581
+ result.properties !== null &&
582
+ typeof result.elementRef === 'string'
583
+ ) {
452
584
  const props = Object.keys(/** @type {object} */ (result.properties));
453
585
  return {
454
586
  ok: true,
455
- summary: appendProtocolWarning(`Computed ${props.length} style(s) for ${result.elementRef}.`, protocolWarning),
456
- evidence: result.properties
587
+ summary: appendProtocolWarning(
588
+ `Computed ${props.length} style(s) for ${result.elementRef}.`,
589
+ protocolWarning
590
+ ),
591
+ evidence: result.properties,
457
592
  };
458
593
  }
459
594
  if (method === 'styles.get_computed') {
@@ -461,23 +596,41 @@ export function summarizeBridgeResponse(response, method) {
461
596
  return {
462
597
  ok: true,
463
598
  summary: appendProtocolWarning(`Computed ${props.length} style(s).`, protocolWarning),
464
- evidence: result
599
+ evidence: result,
465
600
  };
466
601
  }
467
602
  if (typeof result.content === 'object' && typeof result.border === 'object') {
468
603
  const c = /** @type {Record<string, number>} */ (result.content);
469
604
  return {
470
605
  ok: true,
471
- summary: appendProtocolWarning(`Box model: ${c.width ?? '?'}×${c.height ?? '?'} at (${c.x ?? 0}, ${c.y ?? 0}).`, protocolWarning),
472
- evidence: result
606
+ summary: appendProtocolWarning(
607
+ `Box model: ${c.width ?? '?'}×${c.height ?? '?'} at (${c.x ?? 0}, ${c.y ?? 0}).`,
608
+ protocolWarning
609
+ ),
610
+ evidence: result,
473
611
  };
474
612
  }
475
- if (typeof result.x === 'number' && typeof result.y === 'number' && typeof result.width === 'number' && typeof result.height === 'number' &&
476
- !('clicked' in result || 'hovered' in result || 'focused' in result || 'resized' in result || 'tag' in result || 'elementRef' in result)) {
613
+ if (
614
+ typeof result.x === 'number' &&
615
+ typeof result.y === 'number' &&
616
+ typeof result.width === 'number' &&
617
+ typeof result.height === 'number' &&
618
+ !(
619
+ 'clicked' in result ||
620
+ 'hovered' in result ||
621
+ 'focused' in result ||
622
+ 'resized' in result ||
623
+ 'tag' in result ||
624
+ 'elementRef' in result
625
+ )
626
+ ) {
477
627
  return {
478
628
  ok: true,
479
- summary: appendProtocolWarning(`Box model: ${result.width}×${result.height} at (${result.x}, ${result.y}).`, protocolWarning),
480
- evidence: result
629
+ summary: appendProtocolWarning(
630
+ `Box model: ${result.width}×${result.height} at (${result.x}, ${result.y}).`,
631
+ protocolWarning
632
+ ),
633
+ evidence: result,
481
634
  };
482
635
  }
483
636
  if (Array.isArray(response.result) && looksLikePatchArray(response.result, method)) {
@@ -485,21 +638,24 @@ export function summarizeBridgeResponse(response, method) {
485
638
  return {
486
639
  ok: true,
487
640
  summary: appendProtocolWarning(`${patches.length} active patch(es).`, protocolWarning),
488
- evidence: patches.slice(0, 10)
641
+ evidence: patches.slice(0, 10),
489
642
  };
490
643
  }
491
644
  if (Array.isArray(result.patches)) {
492
645
  return {
493
646
  ok: true,
494
647
  summary: appendProtocolWarning(`${result.patches.length} active patch(es).`, protocolWarning),
495
- evidence: result.patches.slice(0, 10)
648
+ evidence: result.patches.slice(0, 10),
496
649
  };
497
650
  }
498
651
  const keys = Object.keys(result);
499
652
  return {
500
653
  ok: true,
501
- summary: appendProtocolWarning(`Bridge method succeeded with ${keys.length} top-level fields.`, protocolWarning),
502
- evidence: keys.slice(0, 10)
654
+ summary: appendProtocolWarning(
655
+ `Bridge method succeeded with ${keys.length} top-level fields.`,
656
+ protocolWarning
657
+ ),
658
+ evidence: keys.slice(0, 10),
503
659
  };
504
660
  }
505
661
 
@@ -509,16 +665,15 @@ export function summarizeBridgeResponse(response, method) {
509
665
  */
510
666
  export function summarizeBatchResponseItem({ method, tabId, response, durationMs }) {
511
667
  const summary = annotateBridgeSummary(summarizeBridgeResponse(response, method), response);
512
- const cost = estimateJsonPayloadCost(response.ok ? response.result : { error: response.error });
513
668
  return {
514
669
  method,
515
670
  tabId,
516
671
  ...summary,
517
672
  durationMs,
518
- approxTokens: cost.approxTokens,
673
+ approxTokens: summary.transportTokens,
519
674
  meta: response.meta,
520
675
  error: response.ok ? null : response.error,
521
- response: response.ok ? response.result : null
676
+ response: response.ok ? response.result : null,
522
677
  };
523
678
  }
524
679
 
@@ -535,9 +690,9 @@ export function summarizeBatchErrorItem({ method, tabId, error, durationMs }) {
535
690
  error: {
536
691
  code: 'INTERNAL_ERROR',
537
692
  message,
538
- details: null
693
+ details: null,
539
694
  },
540
- meta: { protocol_version: '1.0' }
695
+ meta: { protocol_version: '1.0' },
541
696
  });
542
697
  const summary = annotateBridgeSummary(summarizeBridgeResponse(response, method), response);
543
698
  return {
@@ -548,7 +703,7 @@ export function summarizeBatchErrorItem({ method, tabId, error, durationMs }) {
548
703
  approxTokens: 0,
549
704
  meta: response.meta,
550
705
  error: response.error,
551
- response: null
706
+ response: null,
552
707
  };
553
708
  }
554
709
 
@@ -557,9 +712,7 @@ export function summarizeBatchErrorItem({ method, tabId, error, durationMs }) {
557
712
  * @returns {Record<string, unknown>}
558
713
  */
559
714
  function toRecord(value) {
560
- return value && typeof value === 'object'
561
- ? /** @type {Record<string, unknown>} */ (value)
562
- : {};
715
+ return value && typeof value === 'object' ? /** @type {Record<string, unknown>} */ (value) : {};
563
716
  }
564
717
 
565
718
  /**
@@ -608,6 +761,9 @@ function summarizeErrorHint(code) {
608
761
  if (code === 'ELEMENT_STALE') {
609
762
  return 'Re-query the current page after navigation or DOM updates.';
610
763
  }
764
+ if (code === 'RESULT_TRUNCATED') {
765
+ return 'Narrow the query or raise the relevant budget if more detail is required.';
766
+ }
611
767
  return '';
612
768
  }
613
769
 
@@ -623,12 +779,7 @@ function looksLikePatchArray(arr, method) {
623
779
  if (!Array.isArray(arr)) return false;
624
780
  if (arr.length === 0) return method === 'patch.list';
625
781
  const first = arr[0];
626
- return (
627
- first !== null &&
628
- typeof first === 'object' &&
629
- 'patchId' in first &&
630
- 'kind' in first
631
- );
782
+ return first !== null && typeof first === 'object' && 'patchId' in first && 'kind' in first;
632
783
  }
633
784
 
634
785
  /**