@browserbridge/bbx 1.7.8 → 1.9.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 (44) hide show
  1. package/PRIVACY.md +57 -12
  2. package/README.md +19 -8
  3. package/SECURITY.md +12 -0
  4. package/package.json +15 -6
  5. package/packages/agent-client/src/cli-args.js +59 -0
  6. package/packages/agent-client/src/cli-batch.js +121 -58
  7. package/packages/agent-client/src/cli-output.js +8 -1
  8. package/packages/agent-client/src/cli.js +272 -70
  9. package/packages/agent-client/src/client.js +296 -8
  10. package/packages/agent-client/src/command-registry.js +64 -55
  11. package/packages/agent-client/src/config.js +195 -0
  12. package/packages/agent-client/src/content-extract.js +198 -0
  13. package/packages/agent-client/src/npm-self-update.js +305 -0
  14. package/packages/agent-client/src/remotes.js +4 -2
  15. package/packages/agent-client/src/runtime.js +864 -39
  16. package/packages/agent-client/src/types.ts +169 -6
  17. package/packages/mcp-server/src/guidance.js +1 -1
  18. package/packages/mcp-server/src/handlers-capture.js +69 -6
  19. package/packages/mcp-server/src/handlers-dom.js +61 -4
  20. package/packages/mcp-server/src/handlers-navigation.js +2 -1
  21. package/packages/mcp-server/src/handlers-page.js +211 -54
  22. package/packages/mcp-server/src/handlers-utils.js +101 -10
  23. package/packages/mcp-server/src/handlers.js +14 -7
  24. package/packages/mcp-server/src/server.js +176 -11
  25. package/packages/native-host/src/artifact-store.js +353 -0
  26. package/packages/native-host/src/config.js +9 -1
  27. package/packages/native-host/src/daemon-logger.js +3 -1
  28. package/packages/native-host/src/daemon.js +701 -33
  29. package/packages/native-host/src/native-host.js +28 -3
  30. package/packages/protocol/src/capability-values.js +2 -0
  31. package/packages/protocol/src/defaults.js +31 -0
  32. package/packages/protocol/src/errors.js +102 -6
  33. package/packages/protocol/src/incidental-sanitizer.js +197 -0
  34. package/packages/protocol/src/index.js +2 -0
  35. package/packages/protocol/src/protocol.js +612 -6
  36. package/packages/protocol/src/recovery-telemetry.js +302 -0
  37. package/packages/protocol/src/registry.js +206 -38
  38. package/packages/protocol/src/summary.js +203 -30
  39. package/packages/protocol/src/types.ts +721 -2
  40. package/skills/browser-bridge/SKILL.md +54 -19
  41. package/skills/browser-bridge/references/interaction.md +55 -11
  42. package/skills/browser-bridge/references/patch-workflow.md +2 -0
  43. package/skills/browser-bridge/references/protocol.md +265 -86
  44. package/skills/browser-bridge/references/token-efficiency.md +29 -13
@@ -7,6 +7,7 @@ import {
7
7
  getProtocolVersion,
8
8
  serializeJsonPayload,
9
9
  } from './index.js';
10
+ import { sanitizeIncidentalUrl } from './incidental-sanitizer.js';
10
11
 
11
12
  /** @typedef {import('./types.js').BridgeResponse} BridgeResponse */
12
13
  /** @typedef {import('./types.js').BridgeMethod} SummaryMethod */
@@ -68,16 +69,31 @@ const ACTION_SUMMARIES = {
68
69
  },
69
70
  closed: { text: (r) => `Tab ${r.tabId} closed.`, evidence: (r) => r },
70
71
  clicked: {
71
- text: (r) => `Clicked ${r.elementRef ?? 'element'}.`,
72
- evidence: (r) => ({ elementRef: r.elementRef }),
72
+ text: (r) => {
73
+ const mode = toRecord(r.execution).actualMode;
74
+ return `Clicked ${r.elementRef ?? 'element'}${typeof mode === 'string' ? ` via ${mode}` : ''}.`;
75
+ },
76
+ evidence: (r) => ({
77
+ elementRef: r.elementRef,
78
+ resolution: r.resolution,
79
+ execution: r.execution,
80
+ }),
73
81
  },
74
82
  focused: {
75
83
  text: (r) => `Focused ${r.elementRef ?? 'element'}.`,
76
- evidence: (r) => ({ elementRef: r.elementRef }),
84
+ evidence: (r) => ({
85
+ elementRef: r.elementRef,
86
+ resolution: r.resolution,
87
+ execution: r.execution,
88
+ }),
77
89
  },
78
90
  typed: {
79
91
  text: (r) => `Typed into ${r.elementRef ?? 'element'}.`,
80
- evidence: (r) => ({ elementRef: r.elementRef }),
92
+ evidence: (r) => ({
93
+ elementRef: r.elementRef,
94
+ resolution: r.resolution,
95
+ execution: r.execution,
96
+ }),
81
97
  },
82
98
  pressed: {
83
99
  text: (r) => `Key pressed${r.key ? ` (${r.key})` : ''}.`,
@@ -106,6 +122,7 @@ const ACTION_SUMMARIES = {
106
122
  * @returns {AnnotatedBridgeSummary}
107
123
  */
108
124
  export function annotateBridgeSummary(summary, response) {
125
+ const sensitiveRead = response.meta?.method === 'sensitive.read';
109
126
  const metaTransportBytes =
110
127
  getNumericMetaField(response.meta, 'transport_bytes') ??
111
128
  getNumericMetaField(response.meta, 'response_bytes');
@@ -113,13 +130,15 @@ export function annotateBridgeSummary(summary, response) {
113
130
  getNumericMetaField(response.meta, 'transport_approx_tokens') ??
114
131
  getNumericMetaField(response.meta, 'approx_tokens');
115
132
  const transportCost =
116
- metaTransportBytes == null || metaTransportTokens == null
133
+ !sensitiveRead && (metaTransportBytes == null || metaTransportTokens == null)
117
134
  ? estimateSerializedPayloadCost(
118
135
  serializeJsonPayload(response.ok ? response.result : { error: response.error })
119
136
  )
120
137
  : null;
121
- const transportBytes = metaTransportBytes ?? transportCost?.bytes ?? 0;
122
- const transportTokens = metaTransportTokens ?? transportCost?.approxTokens ?? 0;
138
+ const transportBytes = sensitiveRead ? 0 : (metaTransportBytes ?? transportCost?.bytes ?? 0);
139
+ const transportTokens = sensitiveRead
140
+ ? 0
141
+ : (metaTransportTokens ?? transportCost?.approxTokens ?? 0);
123
142
  const summaryCost = estimateSerializedPayloadCost(serializeJsonPayload(summary));
124
143
 
125
144
  return {
@@ -179,6 +198,90 @@ export function summarizeBridgeResponse(response, method) {
179
198
  }
180
199
 
181
200
  const result = toRecord(response.result);
201
+ if (method === 'dom.baseline.compare') {
202
+ const counts = toRecord(result.counts);
203
+ const added = typeof counts.added === 'number' ? counts.added : 0;
204
+ const removed = typeof counts.removed === 'number' ? counts.removed : 0;
205
+ const changed = typeof counts.changed === 'number' ? counts.changed : 0;
206
+ const moved = typeof counts.moved === 'number' ? counts.moved : 0;
207
+ return {
208
+ ok: true,
209
+ summary: appendProtocolWarning(
210
+ result.equal === true
211
+ ? `DOM baseline ${result.baselineId ?? ''} matches the current DOM.`
212
+ : `DOM baseline ${result.baselineId ?? ''} differs: ${added} added, ${removed} removed, ${changed} changed, ${moved} moved${result.truncated === true ? `; ${result.omittedChanges ?? 0} omitted` : ''}.`,
213
+ protocolWarning
214
+ ),
215
+ evidence: result,
216
+ };
217
+ }
218
+ if (method === 'dom.baseline.release') {
219
+ return {
220
+ ok: true,
221
+ summary: appendProtocolWarning(
222
+ `DOM baseline ${result.baselineId ?? ''} ${result.released === true ? 'released' : 'was not released'}.`,
223
+ protocolWarning
224
+ ),
225
+ evidence: result,
226
+ };
227
+ }
228
+ if (method === 'dom.baseline.create' || method === 'dom.baseline.describe') {
229
+ const snapshot = toRecord(result.snapshot);
230
+ return {
231
+ ok: true,
232
+ summary: appendProtocolWarning(
233
+ `DOM baseline ${result.baselineId ?? ''} ${method === 'dom.baseline.create' ? 'created' : 'described'}: ${snapshot.nodeCount ?? 0} node(s), ${snapshot.byteLength ?? 0} bytes.`,
234
+ protocolWarning
235
+ ),
236
+ evidence: result,
237
+ };
238
+ }
239
+ if (method === 'page.handle_dialog') {
240
+ const action = typeof result.action === 'string' ? result.action : 'inspect';
241
+ return {
242
+ ok: true,
243
+ summary: appendProtocolWarning(
244
+ result.commandDispatched === true
245
+ ? `JavaScript dialog ${action} command dispatched to the current dialog; Chrome did not atomically bind it to the observation identifier.`
246
+ : `JavaScript ${result.type ?? 'unknown'} dialog is open.`,
247
+ protocolWarning
248
+ ),
249
+ evidence: result,
250
+ };
251
+ }
252
+ if (method === 'page.wait_for_load_state' && typeof result.finalUrl === 'string') {
253
+ return {
254
+ ok: true,
255
+ summary: appendProtocolWarning(
256
+ `URL matched via ${result.observedNavigationKind ?? 'unknown'} after ${result.elapsedMs ?? 0}ms.`,
257
+ protocolWarning
258
+ ),
259
+ evidence: result,
260
+ };
261
+ }
262
+ if (method === 'network.export_har') {
263
+ const delivery = result.delivery === 'artifact' ? 'artifact' : 'inline';
264
+ const entryCount = typeof result.entryCount === 'number' ? result.entryCount : 0;
265
+ const totalEntries = typeof result.totalEntries === 'number' ? result.totalEntries : entryCount;
266
+ const dropped = typeof result.dropped === 'number' ? result.dropped : 0;
267
+ const abandoned = typeof result.abandoned === 'number' ? result.abandoned : 0;
268
+ const inflight = typeof result.inflight === 'number' ? result.inflight : 0;
269
+ return {
270
+ ok: true,
271
+ summary: appendProtocolWarning(
272
+ `HAR export delivered ${delivery}: ${entryCount}/${totalEntries} entries (${dropped} dropped, ${abandoned} abandoned, ${inflight} inflight).`,
273
+ protocolWarning
274
+ ),
275
+ evidence: /** @type {import('./types.js').HarExportEvidence} */ ({
276
+ delivery,
277
+ entryCount,
278
+ totalEntries,
279
+ dropped,
280
+ abandoned,
281
+ inflight,
282
+ }),
283
+ };
284
+ }
182
285
  if (typeof result.daemon === 'string') {
183
286
  const access =
184
287
  result.access && typeof result.access === 'object'
@@ -232,6 +335,23 @@ export function summarizeBridgeResponse(response, method) {
232
335
  evidence: result,
233
336
  };
234
337
  }
338
+ if (typeof result.content === 'string' && typeof result.source === 'string') {
339
+ return {
340
+ ok: true,
341
+ summary: appendProtocolWarning(
342
+ `Extracted ${result.source} ${result.format ?? 'text'}: ${result.length ?? result.content.length} chars${result.truncated ? ' (truncated)' : ''}.`,
343
+ protocolWarning
344
+ ),
345
+ evidence: {
346
+ content: result.content.slice(0, 500),
347
+ format: result.format,
348
+ source: result.source,
349
+ length: result.length,
350
+ truncated: result.truncated,
351
+ settlement: result.settlement,
352
+ },
353
+ };
354
+ }
235
355
  if (
236
356
  typeof result.url === 'string' &&
237
357
  typeof result.title === 'string' &&
@@ -466,7 +586,9 @@ export function summarizeBridgeResponse(response, method) {
466
586
  if (
467
587
  Array.isArray(result.entries) &&
468
588
  (result.entries.length > 0
469
- ? result.entries[0]?.type === 'fetch' || result.entries[0]?.type === 'xhr'
589
+ ? result.entries[0]?.type === 'fetch' ||
590
+ result.entries[0]?.type === 'xhr' ||
591
+ typeof result.entries[0]?.resourceType === 'string'
470
592
  : method === 'page.get_network')
471
593
  ) {
472
594
  const entries = /** @type {Array<Record<string, unknown>>} */ (result.entries);
@@ -481,9 +603,50 @@ export function summarizeBridgeResponse(response, method) {
481
603
  url: truncateUrl(/** @type {string} */ (e.url)),
482
604
  status: e.status,
483
605
  duration: e.duration,
606
+ ...(typeof e.resourceType === 'string' ? { resourceType: e.resourceType } : {}),
607
+ ...(typeof e.failureReason === 'string' && e.failureReason
608
+ ? { failureReason: e.failureReason }
609
+ : {}),
484
610
  })),
485
611
  };
486
612
  }
613
+ if (
614
+ result.exact === true &&
615
+ typeof result.source === 'string' &&
616
+ typeof result.value === 'string'
617
+ ) {
618
+ return {
619
+ ok: true,
620
+ summary: appendProtocolWarning('Sensitive storage value read exactly.', protocolWarning),
621
+ evidence: { source: result.source, exact: true },
622
+ };
623
+ }
624
+ if (typeof result.count === 'number' && typeof result.type === 'string' && result.entries) {
625
+ const entries = Array.isArray(result.entries)
626
+ ? result.entries.map((entry) => {
627
+ const candidate =
628
+ entry && typeof entry === 'object'
629
+ ? /** @type {Record<string, unknown>} */ (entry)
630
+ : {};
631
+ return {
632
+ key: typeof candidate.key === 'string' ? candidate.key : '',
633
+ present: candidate.present === true,
634
+ };
635
+ })
636
+ : [];
637
+ return {
638
+ ok: true,
639
+ summary: appendProtocolWarning(
640
+ `Storage (${result.type}): ${result.count} entries.`,
641
+ protocolWarning
642
+ ),
643
+ evidence: {
644
+ entries,
645
+ total: typeof result.total === 'number' ? result.total : result.count,
646
+ truncated: result.truncated === true,
647
+ },
648
+ };
649
+ }
487
650
  if (Array.isArray(result.entries)) {
488
651
  const consoleEntries = /** @type {Array<Record<string, unknown>>} */ (result.entries);
489
652
  return {
@@ -524,6 +687,27 @@ export function summarizeBridgeResponse(response, method) {
524
687
  };
525
688
  }
526
689
  }
690
+ if (method?.startsWith('input.') && result.execution && typeof result.execution === 'object') {
691
+ const execution = toRecord(result.execution);
692
+ return {
693
+ ok: true,
694
+ summary: appendProtocolWarning(
695
+ `${method} completed via ${execution.actualMode ?? 'dom'}${result.elementRef ? ` on ${result.elementRef}` : ''}.`,
696
+ protocolWarning
697
+ ),
698
+ evidence: {
699
+ elementRef: result.elementRef ?? null,
700
+ sourceRef: result.sourceRef ?? null,
701
+ destinationRef: result.destinationRef ?? null,
702
+ typed: result.typed,
703
+ checked: result.checked,
704
+ selectedValues: result.selectedValues,
705
+ mode: result.mode,
706
+ resolution: result.resolution,
707
+ execution: result.execution,
708
+ },
709
+ };
710
+ }
527
711
  if (typeof result.tabId === 'number' && typeof result.url === 'string') {
528
712
  const actionMethod = typeof result.method === 'string' ? result.method : method;
529
713
  if (actionMethod === 'navigation.navigate') {
@@ -549,30 +733,18 @@ export function summarizeBridgeResponse(response, method) {
549
733
  }
550
734
  if (typeof result.metrics === 'object' && result.metrics !== null) {
551
735
  const keys = Object.keys(result.metrics);
736
+ const measurement = toRecord(result.measurement);
737
+ const rawCdpCounters =
738
+ method === 'performance.get_metrics' || measurement.kind === 'raw_cdp_counters';
552
739
  return {
553
740
  ok: true,
554
741
  summary: appendProtocolWarning(
555
- `Performance: ${keys.length} metrics collected.`,
556
- protocolWarning
557
- ),
558
- evidence: result.metrics,
559
- };
560
- }
561
- if (typeof result.count === 'number' && typeof result.type === 'string' && result.entries) {
562
- const entries = /** @type {Record<string, unknown>} */ (result.entries);
563
- /** @type {Record<string, string>} */
564
- const safe = {};
565
- for (const [k, v] of Object.entries(entries)) {
566
- const s = typeof v === 'string' ? v : JSON.stringify(v);
567
- safe[k] = s.length > 80 ? `${s.slice(0, 79)}…` : s;
568
- }
569
- return {
570
- ok: true,
571
- summary: appendProtocolWarning(
572
- `Storage (${result.type}): ${result.count} entries.`,
742
+ rawCdpCounters
743
+ ? `Raw CDP performance counters: ${keys.length} collected in a browser-maintained point sample; Web Vitals not measured.`
744
+ : `Performance: ${keys.length} metrics collected.`,
573
745
  protocolWarning
574
746
  ),
575
- evidence: safe,
747
+ evidence: rawCdpCounters ? { metrics: result.metrics, measurement } : result.metrics,
576
748
  };
577
749
  }
578
750
  if (
@@ -791,15 +963,16 @@ function appendProtocolWarning(summary, warning) {
791
963
  * @returns {string}
792
964
  */
793
965
  function truncateUrl(url) {
794
- if (!url || url.length <= 120) return url;
966
+ const sanitized = sanitizeIncidentalUrl(url);
967
+ if (!sanitized || sanitized.length <= 120) return sanitized;
795
968
  try {
796
- const u = new URL(url);
969
+ const u = new URL(sanitized);
797
970
  const base = `${u.origin}${u.pathname}`;
798
971
  if (base.length > 120) return `${base.slice(0, 119)}\u2026`;
799
972
  if (u.search) return `${base}?…`;
800
973
  return base;
801
974
  } catch {
802
- return url.length > 120 ? `${url.slice(0, 119)}\u2026` : url;
975
+ return sanitized.length > 120 ? `${sanitized.slice(0, 119)}\u2026` : sanitized;
803
976
  }
804
977
  }
805
978