@browserbridge/bbx 1.0.0 → 1.0.1
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/README.md +3 -1
- package/docs/api-reference.md +33 -33
- package/docs/mcp-vs-cli.md +104 -104
- package/docs/publishing.md +1 -3
- package/docs/quickstart.md +6 -6
- package/docs/unpacked-extension.md +72 -0
- package/manifest.json +3 -17
- package/package.json +44 -42
- package/packages/agent-client/src/cli-helpers.js +10 -5
- package/packages/agent-client/src/cli.js +65 -135
- package/packages/agent-client/src/client.js +37 -17
- package/packages/agent-client/src/command-registry.js +101 -69
- package/packages/agent-client/src/detect.js +3 -6
- package/packages/agent-client/src/install.js +10 -27
- package/packages/agent-client/src/mcp-config.js +11 -30
- package/packages/agent-client/src/runtime.js +41 -20
- package/packages/agent-client/src/setup-status.js +13 -28
- package/packages/extension/src/background-helpers.js +51 -36
- package/packages/extension/src/background-routing.js +11 -13
- package/packages/extension/src/background.js +562 -299
- package/packages/extension/src/content-script-helpers.js +17 -16
- package/packages/extension/src/content-script.js +175 -109
- package/packages/extension/src/sidepanel-helpers.js +3 -1
- package/packages/extension/ui/popup.js +39 -20
- package/packages/extension/ui/sidepanel.js +108 -191
- package/packages/extension/ui/ui.css +2 -1
- package/packages/mcp-server/src/handlers.js +546 -250
- package/packages/mcp-server/src/server.js +558 -257
- package/packages/native-host/bin/bridge-daemon.js +6 -2
- package/packages/native-host/bin/install-manifest.js +2 -2
- package/packages/native-host/bin/postinstall.js +4 -2
- package/packages/native-host/src/config.js +11 -7
- package/packages/native-host/src/daemon.js +143 -92
- package/packages/native-host/src/install-manifest.js +73 -22
- package/packages/native-host/src/native-host.js +55 -40
- package/packages/protocol/src/budget.js +3 -7
- package/packages/protocol/src/capabilities.js +3 -3
- package/packages/protocol/src/errors.js +11 -11
- package/packages/protocol/src/protocol.js +104 -71
- package/packages/protocol/src/registry.js +300 -45
- package/packages/protocol/src/summary.js +249 -106
- package/packages/protocol/src/types.js +1 -1
- package/skills/browser-bridge/SKILL.md +1 -1
- package/skills/browser-bridge/agents/openai.yaml +3 -3
- package/skills/browser-bridge/references/interaction.md +33 -11
- package/skills/browser-bridge/references/patch-workflow.md +3 -0
- package/skills/browser-bridge/references/protocol.md +125 -70
- package/skills/browser-bridge/references/tailwind.md +12 -11
- package/skills/browser-bridge/references/token-efficiency.md +23 -22
- package/skills/browser-bridge/references/ui-workflows.md +8 -0
- package/packages/extension/ui/offscreen.html +0 -6
- package/packages/extension/ui/offscreen.js +0 -61
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
estimateJsonPayloadCost,
|
|
5
|
-
getCostClass,
|
|
6
|
-
} from './index.js';
|
|
3
|
+
import { estimateJsonPayloadCost, getCostClass } 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:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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,24 @@ const ACTION_SUMMARIES = {
|
|
|
74
99
|
* @returns {AnnotatedBridgeSummary}
|
|
75
100
|
*/
|
|
76
101
|
export function annotateBridgeSummary(summary, response) {
|
|
77
|
-
const transportBytes =
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
102
|
+
const transportBytes =
|
|
103
|
+
getNumericMetaField(response.meta, 'transport_bytes') ??
|
|
104
|
+
getNumericMetaField(response.meta, 'response_bytes') ??
|
|
105
|
+
estimateJsonPayloadCost(response.ok ? response.result : { error: response.error }).bytes;
|
|
106
|
+
const transportTokens =
|
|
107
|
+
getNumericMetaField(response.meta, 'transport_approx_tokens') ??
|
|
108
|
+
getNumericMetaField(response.meta, 'approx_tokens') ??
|
|
109
|
+
estimateJsonPayloadCost(response.ok ? response.result : { error: response.error }).approxTokens;
|
|
83
110
|
const summaryCost = estimateJsonPayloadCost(summary);
|
|
84
111
|
|
|
85
112
|
return {
|
|
86
113
|
...summary,
|
|
87
114
|
transportBytes,
|
|
88
115
|
transportTokens,
|
|
89
|
-
transportCostClass:
|
|
90
|
-
|
|
91
|
-
|
|
116
|
+
transportCostClass:
|
|
117
|
+
getMetaCostClass(response.meta, 'transport_cost_class') ??
|
|
118
|
+
getMetaCostClass(response.meta, 'cost_class') ??
|
|
119
|
+
getCostClass(transportTokens),
|
|
92
120
|
summaryBytes: summaryCost.bytes,
|
|
93
121
|
summaryTokens: summaryCost.approxTokens,
|
|
94
122
|
summaryCostClass: summaryCost.costClass,
|
|
@@ -132,28 +160,32 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
132
160
|
`${response.error.code}: ${response.error.message}${hint ? ` ${hint}` : ''}`,
|
|
133
161
|
protocolWarning
|
|
134
162
|
),
|
|
135
|
-
evidence: response.error.details ?? null
|
|
163
|
+
evidence: response.error.details ?? null,
|
|
136
164
|
};
|
|
137
165
|
}
|
|
138
166
|
|
|
139
167
|
const result = toRecord(response.result);
|
|
140
168
|
if (typeof result.daemon === 'string') {
|
|
141
|
-
const access =
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
?
|
|
148
|
-
:
|
|
169
|
+
const access =
|
|
170
|
+
result.access && typeof result.access === 'object'
|
|
171
|
+
? /** @type {Record<string, unknown>} */ (result.access)
|
|
172
|
+
: null;
|
|
173
|
+
const accessSummary =
|
|
174
|
+
access == null
|
|
175
|
+
? ''
|
|
176
|
+
: access.enabled
|
|
177
|
+
? ` Access: ${access.routeReady ? `ready on tab ${access.routeTabId}.` : `enabled${typeof access.reason === 'string' ? ` (${access.reason})` : '.'}`}`
|
|
178
|
+
: ' Access: disabled.';
|
|
149
179
|
const connectedExtensions = Array.isArray(result.connectedExtensions)
|
|
150
180
|
? /** @type {Array<Record<string, unknown>>} */ (result.connectedExtensions)
|
|
151
181
|
: [];
|
|
152
182
|
const extensionSummary = result.extensionConnected
|
|
153
|
-
? `connected (${connectedExtensions.length}: ${connectedExtensions
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
183
|
+
? `connected (${connectedExtensions.length}: ${connectedExtensions
|
|
184
|
+
.map((ext) => {
|
|
185
|
+
const label = `${ext.browserName ?? 'unknown'}${ext.profileLabel ? '/' + ext.profileLabel : ''}`;
|
|
186
|
+
return ext.accessEnabled ? `${label}*` : label;
|
|
187
|
+
})
|
|
188
|
+
.join(', ')})`
|
|
157
189
|
: 'disconnected';
|
|
158
190
|
return {
|
|
159
191
|
ok: true,
|
|
@@ -161,22 +193,36 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
161
193
|
`Daemon: ${result.daemon}. Extension: ${extensionSummary}.${accessSummary}`,
|
|
162
194
|
protocolWarning
|
|
163
195
|
),
|
|
164
|
-
evidence: result
|
|
196
|
+
evidence: result,
|
|
165
197
|
};
|
|
166
198
|
}
|
|
167
199
|
if (Array.isArray(result.mcpClients) && Array.isArray(result.skillTargets)) {
|
|
168
|
-
const configuredMcp = result.mcpClients.filter(
|
|
169
|
-
|
|
200
|
+
const configuredMcp = result.mcpClients.filter(
|
|
201
|
+
(entry) =>
|
|
202
|
+
entry &&
|
|
203
|
+
typeof entry === 'object' &&
|
|
204
|
+
/** @type {Record<string, unknown>} */ (entry).configured
|
|
205
|
+
).length;
|
|
206
|
+
const installedSkills = result.skillTargets.filter(
|
|
207
|
+
(entry) =>
|
|
208
|
+
entry &&
|
|
209
|
+
typeof entry === 'object' &&
|
|
210
|
+
/** @type {Record<string, unknown>} */ (entry).installed
|
|
211
|
+
).length;
|
|
170
212
|
return {
|
|
171
213
|
ok: true,
|
|
172
214
|
summary: appendProtocolWarning(
|
|
173
215
|
`Setup: MCP configured for ${configuredMcp}/${result.mcpClients.length} clients; skill installed for ${installedSkills}/${result.skillTargets.length} targets.`,
|
|
174
216
|
protocolWarning
|
|
175
217
|
),
|
|
176
|
-
evidence: result
|
|
218
|
+
evidence: result,
|
|
177
219
|
};
|
|
178
220
|
}
|
|
179
|
-
if (
|
|
221
|
+
if (
|
|
222
|
+
typeof result.url === 'string' &&
|
|
223
|
+
typeof result.title === 'string' &&
|
|
224
|
+
typeof result.origin === 'string'
|
|
225
|
+
) {
|
|
180
226
|
/** @type {string[]} */
|
|
181
227
|
const hints = [];
|
|
182
228
|
if (result.hints && typeof result.hints === 'object') {
|
|
@@ -190,10 +236,18 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
190
236
|
`Page: ${result.title} (${result.origin})${hints.length ? ` [${hints.join(', ')}]` : ''}.`,
|
|
191
237
|
protocolWarning
|
|
192
238
|
),
|
|
193
|
-
evidence: {
|
|
239
|
+
evidence: {
|
|
240
|
+
url: result.url,
|
|
241
|
+
origin: result.origin,
|
|
242
|
+
title: result.title,
|
|
243
|
+
hints: result.hints,
|
|
244
|
+
},
|
|
194
245
|
};
|
|
195
246
|
}
|
|
196
|
-
if (
|
|
247
|
+
if (
|
|
248
|
+
(typeof result.text === 'string' || typeof result.value === 'string') &&
|
|
249
|
+
typeof result.truncated === 'boolean'
|
|
250
|
+
) {
|
|
197
251
|
const text = /** @type {string} */ (result.text ?? result.value);
|
|
198
252
|
const len = typeof result.length === 'number' ? result.length : text.length;
|
|
199
253
|
const label = method === 'dom.get_text' ? 'Element text' : 'Page text';
|
|
@@ -203,7 +257,11 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
203
257
|
`${label}: ${len} chars${result.truncated ? ' (truncated)' : ''}.`,
|
|
204
258
|
protocolWarning
|
|
205
259
|
),
|
|
206
|
-
evidence: {
|
|
260
|
+
evidence: {
|
|
261
|
+
text: text.slice(0, 500),
|
|
262
|
+
length: len,
|
|
263
|
+
truncated: result.truncated,
|
|
264
|
+
},
|
|
207
265
|
};
|
|
208
266
|
}
|
|
209
267
|
if (Array.isArray(result.tabs)) {
|
|
@@ -215,14 +273,24 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
215
273
|
tabId: tab.tabId,
|
|
216
274
|
active: tab.active,
|
|
217
275
|
origin: tab.origin,
|
|
218
|
-
title: tab.title
|
|
219
|
-
}))
|
|
276
|
+
title: tab.title,
|
|
277
|
+
})),
|
|
220
278
|
};
|
|
221
279
|
}
|
|
222
|
-
if (
|
|
280
|
+
if (
|
|
281
|
+
Array.isArray(result.nodes) &&
|
|
282
|
+
typeof result.total === 'number' &&
|
|
283
|
+
result.nodes[0]?.role !== undefined
|
|
284
|
+
) {
|
|
223
285
|
const nodes = /** @type {Array<Record<string, unknown>>} */ (result.nodes);
|
|
224
286
|
const interactive = nodes.filter((/** @type {Record<string, unknown>} */ n) => n.interactive);
|
|
225
|
-
const shown =
|
|
287
|
+
const shown =
|
|
288
|
+
interactive.length > 0
|
|
289
|
+
? interactive
|
|
290
|
+
: nodes.filter(
|
|
291
|
+
(/** @type {Record<string, unknown>} */ n) =>
|
|
292
|
+
n.role && n.role !== 'none' && n.role !== 'generic'
|
|
293
|
+
);
|
|
226
294
|
return {
|
|
227
295
|
ok: true,
|
|
228
296
|
summary: appendProtocolWarning(
|
|
@@ -235,7 +303,7 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
235
303
|
if (n.interactive) entry.interactive = true;
|
|
236
304
|
if (n.value) entry.value = n.value;
|
|
237
305
|
return entry;
|
|
238
|
-
})
|
|
306
|
+
}),
|
|
239
307
|
};
|
|
240
308
|
}
|
|
241
309
|
if (Array.isArray(result.nodes)) {
|
|
@@ -247,14 +315,20 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
247
315
|
if (typeof n.attrs === 'object' && n.attrs !== null) {
|
|
248
316
|
const attrs = /** @type {Record<string, unknown>} */ (n.attrs);
|
|
249
317
|
if (attrs.id) entry.id = attrs.id;
|
|
250
|
-
if (typeof attrs.class === 'string')
|
|
318
|
+
if (typeof attrs.class === 'string')
|
|
319
|
+
entry.cls = attrs.class.split(' ').slice(0, 3).join(' ');
|
|
251
320
|
if (attrs.role) entry.role = attrs.role;
|
|
252
321
|
if (attrs['aria-label']) entry.label = attrs['aria-label'];
|
|
253
322
|
if (attrs['data-testid']) entry.testId = attrs['data-testid'];
|
|
254
323
|
}
|
|
255
324
|
if (!entry.role && n.role) entry.role = n.role;
|
|
256
325
|
if (!entry.label && n.name) entry.label = n.name;
|
|
257
|
-
const text =
|
|
326
|
+
const text =
|
|
327
|
+
typeof n.textExcerpt === 'string'
|
|
328
|
+
? n.textExcerpt
|
|
329
|
+
: typeof n.text === 'string'
|
|
330
|
+
? n.text
|
|
331
|
+
: '';
|
|
258
332
|
if (text) {
|
|
259
333
|
entry.text = text.length > 80 ? `${text.slice(0, 79)}\u2026` : text;
|
|
260
334
|
}
|
|
@@ -275,21 +349,21 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
275
349
|
`${label} ${nodes.length} element(s)${nodes.length > 15 ? '; showing first 15' : ''}.`,
|
|
276
350
|
protocolWarning
|
|
277
351
|
),
|
|
278
|
-
evidence: compact
|
|
352
|
+
evidence: compact,
|
|
279
353
|
};
|
|
280
354
|
}
|
|
281
355
|
if (typeof result.rolledBack === 'boolean' || typeof result.rolled_back === 'boolean') {
|
|
282
356
|
return {
|
|
283
357
|
ok: true,
|
|
284
358
|
summary: appendProtocolWarning('Patch rolled back.', protocolWarning),
|
|
285
|
-
evidence: result
|
|
359
|
+
evidence: result,
|
|
286
360
|
};
|
|
287
361
|
}
|
|
288
362
|
if (typeof result.patchId === 'string') {
|
|
289
363
|
return {
|
|
290
364
|
ok: true,
|
|
291
365
|
summary: appendProtocolWarning(`Patch ${result.patchId} applied.`, protocolWarning),
|
|
292
|
-
evidence: result
|
|
366
|
+
evidence: result,
|
|
293
367
|
};
|
|
294
368
|
}
|
|
295
369
|
if (typeof result.found === 'boolean') {
|
|
@@ -301,7 +375,10 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
301
375
|
: `Element not found (timed out after ${result.duration ?? 0}ms).`,
|
|
302
376
|
protocolWarning
|
|
303
377
|
),
|
|
304
|
-
evidence: {
|
|
378
|
+
evidence: {
|
|
379
|
+
elementRef: result.elementRef ?? null,
|
|
380
|
+
duration: result.duration,
|
|
381
|
+
},
|
|
305
382
|
};
|
|
306
383
|
}
|
|
307
384
|
if (typeof result.value !== 'undefined' && typeof result.type === 'string') {
|
|
@@ -313,7 +390,11 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
313
390
|
repr = '';
|
|
314
391
|
} else if (typeof result.value === 'string') {
|
|
315
392
|
repr = result.value.length > 200 ? `${result.value.slice(0, 199)}\u2026` : result.value;
|
|
316
|
-
} else if (
|
|
393
|
+
} else if (
|
|
394
|
+
typeof result.value === 'object' &&
|
|
395
|
+
result.value !== null &&
|
|
396
|
+
Object.keys(result.value).length === 0
|
|
397
|
+
) {
|
|
317
398
|
repr = '(empty - may be a Promise, Map, or non-serializable value)';
|
|
318
399
|
} else {
|
|
319
400
|
repr = JSON.stringify(result.value);
|
|
@@ -325,20 +406,33 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
325
406
|
repr ? `Evaluated to ${typeLabel}: ${repr}` : `Evaluated to ${typeLabel}.`,
|
|
326
407
|
protocolWarning
|
|
327
408
|
),
|
|
328
|
-
evidence: result
|
|
409
|
+
evidence: result,
|
|
329
410
|
};
|
|
330
411
|
}
|
|
331
|
-
if (
|
|
412
|
+
if (
|
|
413
|
+
Array.isArray(result.entries) &&
|
|
414
|
+
result.entries.length > 0 &&
|
|
415
|
+
typeof result.entries[0]?.at === 'string' &&
|
|
416
|
+
typeof result.entries[0]?.method === 'string'
|
|
417
|
+
) {
|
|
332
418
|
const entries = /** @type {Array<Record<string, unknown>>} */ (result.entries);
|
|
333
419
|
return {
|
|
334
420
|
ok: true,
|
|
335
421
|
summary: appendProtocolWarning(`Log: ${entries.length} entries.`, protocolWarning),
|
|
336
422
|
evidence: entries.slice(-10).map((/** @type {Record<string, unknown>} */ e) => ({
|
|
337
|
-
at: e.at,
|
|
338
|
-
|
|
423
|
+
at: e.at,
|
|
424
|
+
method: e.method,
|
|
425
|
+
ok: e.ok,
|
|
426
|
+
...(typeof e.source === 'string' && e.source ? { source: e.source } : {}),
|
|
427
|
+
})),
|
|
339
428
|
};
|
|
340
429
|
}
|
|
341
|
-
if (
|
|
430
|
+
if (
|
|
431
|
+
Array.isArray(result.entries) &&
|
|
432
|
+
(result.entries.length > 0
|
|
433
|
+
? result.entries[0]?.type === 'fetch' || result.entries[0]?.type === 'xhr'
|
|
434
|
+
: method === 'page.get_network')
|
|
435
|
+
) {
|
|
342
436
|
const entries = /** @type {Array<Record<string, unknown>>} */ (result.entries);
|
|
343
437
|
return {
|
|
344
438
|
ok: true,
|
|
@@ -347,8 +441,11 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
347
441
|
protocolWarning
|
|
348
442
|
),
|
|
349
443
|
evidence: entries.slice(0, 20).map((/** @type {Record<string, unknown>} */ e) => ({
|
|
350
|
-
method: e.method,
|
|
351
|
-
|
|
444
|
+
method: e.method,
|
|
445
|
+
url: truncateUrl(/** @type {string} */ (e.url)),
|
|
446
|
+
status: e.status,
|
|
447
|
+
duration: e.duration,
|
|
448
|
+
})),
|
|
352
449
|
};
|
|
353
450
|
}
|
|
354
451
|
if (Array.isArray(result.entries)) {
|
|
@@ -366,7 +463,7 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
366
463
|
entry.ago = formatRelativeTime(e.ts);
|
|
367
464
|
}
|
|
368
465
|
return entry;
|
|
369
|
-
})
|
|
466
|
+
}),
|
|
370
467
|
};
|
|
371
468
|
}
|
|
372
469
|
if (typeof result.html === 'string') {
|
|
@@ -376,7 +473,10 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
376
473
|
`HTML fragment: ${result.html.length} chars${result.truncated ? ' (truncated)' : ''}.`,
|
|
377
474
|
protocolWarning
|
|
378
475
|
),
|
|
379
|
-
evidence: {
|
|
476
|
+
evidence: {
|
|
477
|
+
html: result.html.slice(0, 500),
|
|
478
|
+
truncated: result.truncated,
|
|
479
|
+
},
|
|
380
480
|
};
|
|
381
481
|
}
|
|
382
482
|
for (const [field, handler] of Object.entries(ACTION_SUMMARIES)) {
|
|
@@ -384,13 +484,12 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
384
484
|
return {
|
|
385
485
|
ok: true,
|
|
386
486
|
summary: appendProtocolWarning(handler.text(result), protocolWarning),
|
|
387
|
-
evidence: handler.evidence(result)
|
|
487
|
+
evidence: handler.evidence(result),
|
|
388
488
|
};
|
|
389
489
|
}
|
|
390
490
|
}
|
|
391
491
|
if (typeof result.tabId === 'number' && typeof result.url === 'string') {
|
|
392
|
-
const actionMethod =
|
|
393
|
-
typeof result.method === 'string' ? result.method : method;
|
|
492
|
+
const actionMethod = typeof result.method === 'string' ? result.method : method;
|
|
394
493
|
if (actionMethod === 'navigation.navigate') {
|
|
395
494
|
return {
|
|
396
495
|
ok: true,
|
|
@@ -405,16 +504,22 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
405
504
|
}
|
|
406
505
|
return {
|
|
407
506
|
ok: true,
|
|
408
|
-
summary: appendProtocolWarning(
|
|
409
|
-
|
|
507
|
+
summary: appendProtocolWarning(
|
|
508
|
+
`Tab ${result.tabId} created${result.url ? ` (${result.url})` : ''}.`,
|
|
509
|
+
protocolWarning
|
|
510
|
+
),
|
|
511
|
+
evidence: result,
|
|
410
512
|
};
|
|
411
513
|
}
|
|
412
514
|
if (typeof result.metrics === 'object' && result.metrics !== null) {
|
|
413
515
|
const keys = Object.keys(result.metrics);
|
|
414
516
|
return {
|
|
415
517
|
ok: true,
|
|
416
|
-
summary: appendProtocolWarning(
|
|
417
|
-
|
|
518
|
+
summary: appendProtocolWarning(
|
|
519
|
+
`Performance: ${keys.length} metrics collected.`,
|
|
520
|
+
protocolWarning
|
|
521
|
+
),
|
|
522
|
+
evidence: result.metrics,
|
|
418
523
|
};
|
|
419
524
|
}
|
|
420
525
|
if (typeof result.count === 'number' && typeof result.type === 'string' && result.entries) {
|
|
@@ -427,16 +532,26 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
427
532
|
}
|
|
428
533
|
return {
|
|
429
534
|
ok: true,
|
|
430
|
-
summary: appendProtocolWarning(
|
|
431
|
-
|
|
535
|
+
summary: appendProtocolWarning(
|
|
536
|
+
`Storage (${result.type}): ${result.count} entries.`,
|
|
537
|
+
protocolWarning
|
|
538
|
+
),
|
|
539
|
+
evidence: safe,
|
|
432
540
|
};
|
|
433
541
|
}
|
|
434
|
-
if (
|
|
542
|
+
if (
|
|
543
|
+
typeof result.tag === 'string' &&
|
|
544
|
+
typeof result.elementRef === 'string' &&
|
|
545
|
+
typeof result.bbox === 'object'
|
|
546
|
+
) {
|
|
435
547
|
const desc = [result.tag];
|
|
436
548
|
if (result.id) desc[0] += `#${result.id}`;
|
|
437
|
-
const textValue =
|
|
438
|
-
|
|
439
|
-
|
|
549
|
+
const textValue =
|
|
550
|
+
typeof result.text === 'object' && result.text !== null && 'value' in result.text
|
|
551
|
+
? /** @type {{ value: string }} */ (result.text).value
|
|
552
|
+
: typeof result.text === 'string'
|
|
553
|
+
? result.text
|
|
554
|
+
: '';
|
|
440
555
|
if (textValue) desc.push(String(textValue).slice(0, 60));
|
|
441
556
|
const bbox = /** @type {Record<string, number>} */ (result.bbox);
|
|
442
557
|
if (bbox.width && bbox.height) {
|
|
@@ -445,15 +560,29 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
445
560
|
return {
|
|
446
561
|
ok: true,
|
|
447
562
|
summary: appendProtocolWarning(`Element ${desc.join(', ')}.`, protocolWarning),
|
|
448
|
-
evidence: {
|
|
563
|
+
evidence: {
|
|
564
|
+
elementRef: result.elementRef,
|
|
565
|
+
tag: result.tag,
|
|
566
|
+
id: result.id,
|
|
567
|
+
role: result.role,
|
|
568
|
+
text: textValue,
|
|
569
|
+
bbox: result.bbox,
|
|
570
|
+
},
|
|
449
571
|
};
|
|
450
572
|
}
|
|
451
|
-
if (
|
|
573
|
+
if (
|
|
574
|
+
typeof result.properties === 'object' &&
|
|
575
|
+
result.properties !== null &&
|
|
576
|
+
typeof result.elementRef === 'string'
|
|
577
|
+
) {
|
|
452
578
|
const props = Object.keys(/** @type {object} */ (result.properties));
|
|
453
579
|
return {
|
|
454
580
|
ok: true,
|
|
455
|
-
summary: appendProtocolWarning(
|
|
456
|
-
|
|
581
|
+
summary: appendProtocolWarning(
|
|
582
|
+
`Computed ${props.length} style(s) for ${result.elementRef}.`,
|
|
583
|
+
protocolWarning
|
|
584
|
+
),
|
|
585
|
+
evidence: result.properties,
|
|
457
586
|
};
|
|
458
587
|
}
|
|
459
588
|
if (method === 'styles.get_computed') {
|
|
@@ -461,23 +590,41 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
461
590
|
return {
|
|
462
591
|
ok: true,
|
|
463
592
|
summary: appendProtocolWarning(`Computed ${props.length} style(s).`, protocolWarning),
|
|
464
|
-
evidence: result
|
|
593
|
+
evidence: result,
|
|
465
594
|
};
|
|
466
595
|
}
|
|
467
596
|
if (typeof result.content === 'object' && typeof result.border === 'object') {
|
|
468
597
|
const c = /** @type {Record<string, number>} */ (result.content);
|
|
469
598
|
return {
|
|
470
599
|
ok: true,
|
|
471
|
-
summary: appendProtocolWarning(
|
|
472
|
-
|
|
600
|
+
summary: appendProtocolWarning(
|
|
601
|
+
`Box model: ${c.width ?? '?'}×${c.height ?? '?'} at (${c.x ?? 0}, ${c.y ?? 0}).`,
|
|
602
|
+
protocolWarning
|
|
603
|
+
),
|
|
604
|
+
evidence: result,
|
|
473
605
|
};
|
|
474
606
|
}
|
|
475
|
-
if (
|
|
476
|
-
|
|
607
|
+
if (
|
|
608
|
+
typeof result.x === 'number' &&
|
|
609
|
+
typeof result.y === 'number' &&
|
|
610
|
+
typeof result.width === 'number' &&
|
|
611
|
+
typeof result.height === 'number' &&
|
|
612
|
+
!(
|
|
613
|
+
'clicked' in result ||
|
|
614
|
+
'hovered' in result ||
|
|
615
|
+
'focused' in result ||
|
|
616
|
+
'resized' in result ||
|
|
617
|
+
'tag' in result ||
|
|
618
|
+
'elementRef' in result
|
|
619
|
+
)
|
|
620
|
+
) {
|
|
477
621
|
return {
|
|
478
622
|
ok: true,
|
|
479
|
-
summary: appendProtocolWarning(
|
|
480
|
-
|
|
623
|
+
summary: appendProtocolWarning(
|
|
624
|
+
`Box model: ${result.width}×${result.height} at (${result.x}, ${result.y}).`,
|
|
625
|
+
protocolWarning
|
|
626
|
+
),
|
|
627
|
+
evidence: result,
|
|
481
628
|
};
|
|
482
629
|
}
|
|
483
630
|
if (Array.isArray(response.result) && looksLikePatchArray(response.result, method)) {
|
|
@@ -485,21 +632,24 @@ export function summarizeBridgeResponse(response, method) {
|
|
|
485
632
|
return {
|
|
486
633
|
ok: true,
|
|
487
634
|
summary: appendProtocolWarning(`${patches.length} active patch(es).`, protocolWarning),
|
|
488
|
-
evidence: patches.slice(0, 10)
|
|
635
|
+
evidence: patches.slice(0, 10),
|
|
489
636
|
};
|
|
490
637
|
}
|
|
491
638
|
if (Array.isArray(result.patches)) {
|
|
492
639
|
return {
|
|
493
640
|
ok: true,
|
|
494
641
|
summary: appendProtocolWarning(`${result.patches.length} active patch(es).`, protocolWarning),
|
|
495
|
-
evidence: result.patches.slice(0, 10)
|
|
642
|
+
evidence: result.patches.slice(0, 10),
|
|
496
643
|
};
|
|
497
644
|
}
|
|
498
645
|
const keys = Object.keys(result);
|
|
499
646
|
return {
|
|
500
647
|
ok: true,
|
|
501
|
-
summary: appendProtocolWarning(
|
|
502
|
-
|
|
648
|
+
summary: appendProtocolWarning(
|
|
649
|
+
`Bridge method succeeded with ${keys.length} top-level fields.`,
|
|
650
|
+
protocolWarning
|
|
651
|
+
),
|
|
652
|
+
evidence: keys.slice(0, 10),
|
|
503
653
|
};
|
|
504
654
|
}
|
|
505
655
|
|
|
@@ -518,7 +668,7 @@ export function summarizeBatchResponseItem({ method, tabId, response, durationMs
|
|
|
518
668
|
approxTokens: cost.approxTokens,
|
|
519
669
|
meta: response.meta,
|
|
520
670
|
error: response.ok ? null : response.error,
|
|
521
|
-
response: response.ok ? response.result : null
|
|
671
|
+
response: response.ok ? response.result : null,
|
|
522
672
|
};
|
|
523
673
|
}
|
|
524
674
|
|
|
@@ -535,9 +685,9 @@ export function summarizeBatchErrorItem({ method, tabId, error, durationMs }) {
|
|
|
535
685
|
error: {
|
|
536
686
|
code: 'INTERNAL_ERROR',
|
|
537
687
|
message,
|
|
538
|
-
details: null
|
|
688
|
+
details: null,
|
|
539
689
|
},
|
|
540
|
-
meta: { protocol_version: '1.0' }
|
|
690
|
+
meta: { protocol_version: '1.0' },
|
|
541
691
|
});
|
|
542
692
|
const summary = annotateBridgeSummary(summarizeBridgeResponse(response, method), response);
|
|
543
693
|
return {
|
|
@@ -548,7 +698,7 @@ export function summarizeBatchErrorItem({ method, tabId, error, durationMs }) {
|
|
|
548
698
|
approxTokens: 0,
|
|
549
699
|
meta: response.meta,
|
|
550
700
|
error: response.error,
|
|
551
|
-
response: null
|
|
701
|
+
response: null,
|
|
552
702
|
};
|
|
553
703
|
}
|
|
554
704
|
|
|
@@ -557,9 +707,7 @@ export function summarizeBatchErrorItem({ method, tabId, error, durationMs }) {
|
|
|
557
707
|
* @returns {Record<string, unknown>}
|
|
558
708
|
*/
|
|
559
709
|
function toRecord(value) {
|
|
560
|
-
return value && typeof value === 'object'
|
|
561
|
-
? /** @type {Record<string, unknown>} */ (value)
|
|
562
|
-
: {};
|
|
710
|
+
return value && typeof value === 'object' ? /** @type {Record<string, unknown>} */ (value) : {};
|
|
563
711
|
}
|
|
564
712
|
|
|
565
713
|
/**
|
|
@@ -623,12 +771,7 @@ function looksLikePatchArray(arr, method) {
|
|
|
623
771
|
if (!Array.isArray(arr)) return false;
|
|
624
772
|
if (arr.length === 0) return method === 'patch.list';
|
|
625
773
|
const first = arr[0];
|
|
626
|
-
return
|
|
627
|
-
first !== null &&
|
|
628
|
-
typeof first === 'object' &&
|
|
629
|
-
'patchId' in first &&
|
|
630
|
-
'kind' in first
|
|
631
|
-
);
|
|
774
|
+
return first !== null && typeof first === 'object' && 'patchId' in first && 'kind' in first;
|
|
632
775
|
}
|
|
633
776
|
|
|
634
777
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: browser-bridge
|
|
3
|
-
description:
|
|
3
|
+
description: 'Token-efficient Chrome tab inspection, interaction, and patching via local bridge extension (CLI: bbx). Reads live DOM, styles, console, network, and storage from a real Chrome tab with lower token cost than screenshots.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Browser Bridge
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
|
-
display_name:
|
|
3
|
-
short_description:
|
|
4
|
-
default_prompt:
|
|
2
|
+
display_name: 'Browser Bridge'
|
|
3
|
+
short_description: 'Token-efficient Chrome tab inspection, interaction, and patching via local bridge'
|
|
4
|
+
default_prompt: 'Use the browser-bridge skill for Chrome tab inspection, interaction, and patching. Start with `bbx status` to confirm connectivity. If ACCESS_DENIED, ask the user to click Enable in the extension popup/side panel, then retry. Default routing follows the active tab; use `--tab` only for a different tab. Prefer structured reads (`dom.query`, `styles.get_computed`) over screenshots. Batch independent reads with `bbx batch`. Use `bbx skill` for runtime presets.'
|