@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.
- package/PRIVACY.md +57 -12
- package/README.md +19 -8
- package/SECURITY.md +12 -0
- package/package.json +15 -6
- package/packages/agent-client/src/cli-args.js +59 -0
- package/packages/agent-client/src/cli-batch.js +121 -58
- package/packages/agent-client/src/cli-output.js +8 -1
- package/packages/agent-client/src/cli.js +272 -70
- package/packages/agent-client/src/client.js +296 -8
- package/packages/agent-client/src/command-registry.js +64 -55
- package/packages/agent-client/src/config.js +195 -0
- package/packages/agent-client/src/content-extract.js +198 -0
- package/packages/agent-client/src/npm-self-update.js +305 -0
- package/packages/agent-client/src/remotes.js +4 -2
- package/packages/agent-client/src/runtime.js +864 -39
- package/packages/agent-client/src/types.ts +169 -6
- package/packages/mcp-server/src/guidance.js +1 -1
- package/packages/mcp-server/src/handlers-capture.js +69 -6
- package/packages/mcp-server/src/handlers-dom.js +61 -4
- package/packages/mcp-server/src/handlers-navigation.js +2 -1
- package/packages/mcp-server/src/handlers-page.js +211 -54
- package/packages/mcp-server/src/handlers-utils.js +101 -10
- package/packages/mcp-server/src/handlers.js +14 -7
- package/packages/mcp-server/src/server.js +176 -11
- package/packages/native-host/src/artifact-store.js +353 -0
- package/packages/native-host/src/config.js +9 -1
- package/packages/native-host/src/daemon-logger.js +3 -1
- package/packages/native-host/src/daemon.js +701 -33
- package/packages/native-host/src/native-host.js +28 -3
- package/packages/protocol/src/capability-values.js +2 -0
- package/packages/protocol/src/defaults.js +31 -0
- package/packages/protocol/src/errors.js +102 -6
- package/packages/protocol/src/incidental-sanitizer.js +197 -0
- package/packages/protocol/src/index.js +2 -0
- package/packages/protocol/src/protocol.js +612 -6
- package/packages/protocol/src/recovery-telemetry.js +302 -0
- package/packages/protocol/src/registry.js +206 -38
- package/packages/protocol/src/summary.js +203 -30
- package/packages/protocol/src/types.ts +721 -2
- package/skills/browser-bridge/SKILL.md +54 -19
- package/skills/browser-bridge/references/interaction.md +55 -11
- package/skills/browser-bridge/references/patch-workflow.md +2 -0
- package/skills/browser-bridge/references/protocol.md +265 -86
- 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) =>
|
|
72
|
-
|
|
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) => ({
|
|
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) => ({
|
|
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 =
|
|
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' ||
|
|
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
|
-
|
|
556
|
-
|
|
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:
|
|
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
|
-
|
|
966
|
+
const sanitized = sanitizeIncidentalUrl(url);
|
|
967
|
+
if (!sanitized || sanitized.length <= 120) return sanitized;
|
|
795
968
|
try {
|
|
796
|
-
const u = new 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
|
|
975
|
+
return sanitized.length > 120 ? `${sanitized.slice(0, 119)}\u2026` : sanitized;
|
|
803
976
|
}
|
|
804
977
|
}
|
|
805
978
|
|