@browserbridge/bbx 1.7.7 → 1.8.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 +111 -0
- package/README.md +13 -5
- package/RESPONSIBLE_USE.md +16 -0
- package/SECURITY.md +52 -0
- package/package.json +12 -6
- package/packages/agent-client/src/cli-args.js +24 -0
- package/packages/agent-client/src/cli.js +46 -52
- package/packages/agent-client/src/client.js +140 -5
- package/packages/agent-client/src/command-registry.js +56 -53
- package/packages/agent-client/src/content-extract.js +198 -0
- package/packages/agent-client/src/remotes.js +1 -1
- package/packages/agent-client/src/runtime.js +768 -35
- package/packages/agent-client/src/types.ts +131 -6
- package/packages/mcp-server/src/guidance.js +1 -1
- package/packages/mcp-server/src/handlers-capture.js +41 -5
- package/packages/mcp-server/src/handlers-dom.js +8 -2
- package/packages/mcp-server/src/handlers-navigation.js +2 -1
- package/packages/mcp-server/src/handlers-page.js +41 -4
- package/packages/mcp-server/src/handlers-utils.js +67 -7
- package/packages/mcp-server/src/handlers.js +4 -4
- package/packages/mcp-server/src/server.js +102 -6
- package/packages/native-host/src/daemon.js +158 -6
- package/packages/native-host/src/native-host.js +3 -1
- package/packages/protocol/src/defaults.js +3 -0
- package/packages/protocol/src/errors.js +54 -1
- package/packages/protocol/src/protocol.js +297 -1
- package/packages/protocol/src/registry.js +66 -35
- package/packages/protocol/src/summary.js +87 -5
- package/packages/protocol/src/types.ts +234 -0
- package/skills/browser-bridge/SKILL.md +34 -13
- 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 +157 -82
- package/skills/browser-bridge/references/token-efficiency.md +19 -13
|
@@ -4,10 +4,19 @@ import type {
|
|
|
4
4
|
BridgeMethod,
|
|
5
5
|
BridgeRequestSource,
|
|
6
6
|
BridgeResponse,
|
|
7
|
+
ScreenshotResult,
|
|
8
|
+
SetupStatus,
|
|
7
9
|
} from '../../protocol/src/types.js';
|
|
8
10
|
import type { restartBridgeDaemon } from '../../native-host/src/daemon-process.js';
|
|
9
11
|
|
|
10
|
-
export type {
|
|
12
|
+
export type {
|
|
13
|
+
BridgeMeta,
|
|
14
|
+
BridgeMethod,
|
|
15
|
+
BridgeRequestSource,
|
|
16
|
+
BridgeResponse,
|
|
17
|
+
BridgeTransport,
|
|
18
|
+
ScreenshotResult,
|
|
19
|
+
};
|
|
11
20
|
|
|
12
21
|
export type McpClientName =
|
|
13
22
|
| 'codex'
|
|
@@ -43,6 +52,7 @@ export interface SetupStatusOptions {
|
|
|
43
52
|
export interface ProtocolHealthResult {
|
|
44
53
|
extensionConnected?: boolean;
|
|
45
54
|
supported_versions?: string[];
|
|
55
|
+
extension_supported_versions?: string[];
|
|
46
56
|
daemon_supported_versions?: string[];
|
|
47
57
|
deprecated_since?: string;
|
|
48
58
|
migration_hint?: string;
|
|
@@ -78,6 +88,7 @@ export interface BridgeClientOptions {
|
|
|
78
88
|
clientId?: string;
|
|
79
89
|
defaultTimeoutMs?: number;
|
|
80
90
|
autoReconnect?: boolean;
|
|
91
|
+
checkProtocolOnConnect?: boolean;
|
|
81
92
|
restartDaemonOnVersionMismatch?: boolean;
|
|
82
93
|
restartDaemonFn?: typeof restartBridgeDaemon;
|
|
83
94
|
authToken?: string | null;
|
|
@@ -110,6 +121,106 @@ export interface NativeHostManifestIssue {
|
|
|
110
121
|
message: string;
|
|
111
122
|
}
|
|
112
123
|
|
|
124
|
+
export interface DoctorTransportDiagnostics {
|
|
125
|
+
kind: 'socket' | 'tcp' | 'unknown';
|
|
126
|
+
local: true;
|
|
127
|
+
status: 'reachable' | 'offline' | 'authentication_failed';
|
|
128
|
+
proxyConfigured: boolean;
|
|
129
|
+
proxyExposed: boolean | null;
|
|
130
|
+
credentials: 'not_required' | 'accepted' | 'rejected' | 'unknown';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface DoctorConnectionDiagnostics {
|
|
134
|
+
extensionCount: number;
|
|
135
|
+
profileCount: number;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface DoctorProtocolDiagnostics {
|
|
139
|
+
clientVersion: string;
|
|
140
|
+
daemonVersion: string | null;
|
|
141
|
+
daemonSupportedVersions: string[];
|
|
142
|
+
extensionSupportedVersions: string[];
|
|
143
|
+
daemonCompatible: boolean | null;
|
|
144
|
+
extensionCompatible: boolean | null;
|
|
145
|
+
compatible: boolean | null;
|
|
146
|
+
migration: 'none' | 'update_client' | 'restart_daemon' | 'update_extension' | 'unknown';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface DoctorDebuggerDiagnostics {
|
|
150
|
+
state: 'idle' | 'active' | 'conflict' | 'detached' | 'unknown';
|
|
151
|
+
attachedTabCount: number | null;
|
|
152
|
+
heldTabCount: number | null;
|
|
153
|
+
pendingTabCount: number | null;
|
|
154
|
+
recentReason:
|
|
155
|
+
| 'debugger_conflict'
|
|
156
|
+
| 'debugger_detached'
|
|
157
|
+
| 'debugger_replaced'
|
|
158
|
+
| 'debugger_canceled'
|
|
159
|
+
| 'target_closed'
|
|
160
|
+
| null;
|
|
161
|
+
captureState:
|
|
162
|
+
| 'idle'
|
|
163
|
+
| 'stopped'
|
|
164
|
+
| 'armed'
|
|
165
|
+
| 'active'
|
|
166
|
+
| 'capturing'
|
|
167
|
+
| 'stop_failed'
|
|
168
|
+
| 'unavailable'
|
|
169
|
+
| 'unknown';
|
|
170
|
+
captureActiveTabCount: number | null;
|
|
171
|
+
captureOwnershipCount: number | null;
|
|
172
|
+
captureInflightCount: number | null;
|
|
173
|
+
interceptionActiveTabCount: number | null;
|
|
174
|
+
interceptionRuleCount: number | null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export interface DoctorDaemonMetrics {
|
|
178
|
+
uptimeMs: number;
|
|
179
|
+
activeAgents: number;
|
|
180
|
+
activeExtensions: number;
|
|
181
|
+
pendingRequests: number;
|
|
182
|
+
requestsProcessed: number;
|
|
183
|
+
requestsFailed: number;
|
|
184
|
+
avgResponseTimeMs: number;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface DoctorRecentEvent {
|
|
188
|
+
at?: string;
|
|
189
|
+
method?: BridgeMethod;
|
|
190
|
+
ok?: boolean;
|
|
191
|
+
source?: BridgeRequestSource;
|
|
192
|
+
cause?:
|
|
193
|
+
| 'debugger_conflict'
|
|
194
|
+
| 'debugger_detached'
|
|
195
|
+
| 'debugger_replaced'
|
|
196
|
+
| 'debugger_canceled'
|
|
197
|
+
| 'target_closed'
|
|
198
|
+
| 'dialog_conflict'
|
|
199
|
+
| 'extension_disconnected'
|
|
200
|
+
| 'wrong_window';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export interface DoctorSetupSummary {
|
|
204
|
+
source: 'daemon' | 'direct';
|
|
205
|
+
scope: 'global' | 'local';
|
|
206
|
+
mcp: {
|
|
207
|
+
detected: number;
|
|
208
|
+
configured: number;
|
|
209
|
+
};
|
|
210
|
+
skills: {
|
|
211
|
+
detected: number;
|
|
212
|
+
installed: number;
|
|
213
|
+
managed: number;
|
|
214
|
+
updatesAvailable: number;
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface DoctorRemoteDiagnostics {
|
|
219
|
+
configuredCount: number;
|
|
220
|
+
status: 'not_configured' | 'not_probed_local_only' | 'config_unavailable';
|
|
221
|
+
credentials: 'not_configured' | 'unverified';
|
|
222
|
+
}
|
|
223
|
+
|
|
113
224
|
export interface DoctorReport {
|
|
114
225
|
manifestInstalled: boolean;
|
|
115
226
|
manifestPath: string;
|
|
@@ -117,6 +228,7 @@ export interface DoctorReport {
|
|
|
117
228
|
defaultExtensionId: string | null;
|
|
118
229
|
defaultExtensionIdSource: string;
|
|
119
230
|
daemonReachable: boolean;
|
|
231
|
+
healthAvailable: boolean;
|
|
120
232
|
extensionConnected: boolean;
|
|
121
233
|
accessEnabled: boolean;
|
|
122
234
|
enabledWindowId: number | null;
|
|
@@ -130,6 +242,16 @@ export interface DoctorReport {
|
|
|
130
242
|
issues: string[];
|
|
131
243
|
nextSteps: string[];
|
|
132
244
|
browserManifests: BrowserManifestStatus[];
|
|
245
|
+
transport: DoctorTransportDiagnostics;
|
|
246
|
+
connections: DoctorConnectionDiagnostics;
|
|
247
|
+
protocol: DoctorProtocolDiagnostics;
|
|
248
|
+
debugger: DoctorDebuggerDiagnostics;
|
|
249
|
+
metrics: DoctorDaemonMetrics | null;
|
|
250
|
+
recentEvents: DoctorRecentEvent[];
|
|
251
|
+
recentCauses: DoctorRecentEvent['cause'][];
|
|
252
|
+
setup: DoctorSetupSummary | null;
|
|
253
|
+
remoteDestinations: DoctorRemoteDiagnostics;
|
|
254
|
+
diagnosticFailures: string[];
|
|
133
255
|
}
|
|
134
256
|
|
|
135
257
|
export interface DoctorReportOptions {
|
|
@@ -145,6 +267,14 @@ export interface DoctorReportOptions {
|
|
|
145
267
|
checkNativeHostManifestHealth?: (
|
|
146
268
|
browserManifests: BrowserManifestStatus[]
|
|
147
269
|
) => Promise<NativeHostManifestIssue[]>;
|
|
270
|
+
readInstalledExtensionIds?: (browserManifests: BrowserManifestStatus[]) => Promise<string[]>;
|
|
271
|
+
collectSetupStatus?: () => Promise<SetupStatus>;
|
|
272
|
+
readRemoteConfig?: () => Promise<{
|
|
273
|
+
remotes: Array<{ id: string; host: string; port: number; token: string }>;
|
|
274
|
+
}>;
|
|
275
|
+
getLocalTransport?: () => BridgeTransport;
|
|
276
|
+
readProxyConfig?: () => { enabled: boolean } | null;
|
|
277
|
+
includeSetupStatus?: boolean;
|
|
148
278
|
}
|
|
149
279
|
|
|
150
280
|
export type BridgeClientRequest = (options: {
|
|
@@ -154,8 +284,3 @@ export type BridgeClientRequest = (options: {
|
|
|
154
284
|
meta?: BridgeMeta;
|
|
155
285
|
timeoutMs?: number;
|
|
156
286
|
}) => Promise<BridgeResponse>;
|
|
157
|
-
|
|
158
|
-
export interface ScreenshotResult {
|
|
159
|
-
image: string;
|
|
160
|
-
rect: Record<string, unknown>;
|
|
161
|
-
}
|
|
@@ -5,7 +5,7 @@ export const MCP_SERVER_INSTRUCTIONS = [
|
|
|
5
5
|
'In permission-ask hosts, use browser_call as the default Browser Bridge MCP tool so the user can approve one BBX tool instead of separate browser_status, browser_page, browser_dom, browser_input, and other tools.',
|
|
6
6
|
'Prefer Browser Bridge MCP tools over shelling out to bbx. Use bbx only for explicit CLI setup, doctor, logs, or raw debugging requests.',
|
|
7
7
|
'Start with browser_call method health.ping. If window access is disabled, call browser_call method access.request once, ask the user to click Enable in the Browser Bridge popup or side panel, then retry once.',
|
|
8
|
-
'Use structured reads first through browser_call: page.get_state, dom.query, page.get_text, styles.get_computed, or layout.get_box_model. Use browser_batch only for parallel reads; run mutations sequentially and keep limits tight before widening.',
|
|
8
|
+
'Use structured reads first through browser_call: page.get_state, dom.query, page.get_text, page.extract_content, styles.get_computed, or layout.get_box_model. Prefer semantic extraction for articles and documentation. Use browser_batch only for parallel reads; run mutations sequentially and keep limits tight before widening.',
|
|
9
9
|
'Reuse elementRef values returned by DOM tools. Use attribute allowlists for focused DOM reads.',
|
|
10
10
|
'Escalate to screenshot.capture_element, screenshot.capture_region, dom.get_accessibility_tree, page.evaluate, viewport.resize, or CDP only when structured reads cannot answer the question.',
|
|
11
11
|
'Use patch.apply_styles or patch.apply_dom for temporary experiments, and rollback patches before finishing unless the user asks to keep them.',
|
|
@@ -22,17 +22,28 @@ export const CAPTURE_ACTIONS = {
|
|
|
22
22
|
element: {
|
|
23
23
|
ref: true,
|
|
24
24
|
method: 'screenshot.capture_element',
|
|
25
|
-
params: (
|
|
25
|
+
params: (a, r) => ({
|
|
26
|
+
elementRef: r,
|
|
27
|
+
...(a.format ? { format: a.format } : {}),
|
|
28
|
+
...(typeof a.quality === 'number' ? { quality: a.quality } : {}),
|
|
29
|
+
}),
|
|
26
30
|
},
|
|
27
31
|
region: {
|
|
28
32
|
ref: false,
|
|
29
33
|
method: 'screenshot.capture_region',
|
|
30
|
-
params: (a) =>
|
|
34
|
+
params: (a) => ({
|
|
35
|
+
.../** @type {Record<string, unknown>} */ (a.rect || {}),
|
|
36
|
+
...(a.format ? { format: a.format } : {}),
|
|
37
|
+
...(typeof a.quality === 'number' ? { quality: a.quality } : {}),
|
|
38
|
+
}),
|
|
31
39
|
},
|
|
32
40
|
full_page: {
|
|
33
41
|
ref: false,
|
|
34
42
|
method: 'screenshot.capture_full_page',
|
|
35
|
-
params: () => ({
|
|
43
|
+
params: (a) => ({
|
|
44
|
+
...(a.format ? { format: a.format } : {}),
|
|
45
|
+
...(typeof a.quality === 'number' ? { quality: a.quality } : {}),
|
|
46
|
+
}),
|
|
36
47
|
},
|
|
37
48
|
cdp_document: { ref: false, method: 'cdp.get_document', params: () => ({}) },
|
|
38
49
|
cdp_dom_snapshot: {
|
|
@@ -82,7 +93,7 @@ function isValidCaptureRegion(rect) {
|
|
|
82
93
|
}
|
|
83
94
|
|
|
84
95
|
/**
|
|
85
|
-
* @param {{ action: string, elementRef?: string, selector?: string, rect?: Record<string, unknown>, nodeId?: number, tabId?: number, destinationId?: string, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
96
|
+
* @param {{ action: string, elementRef?: string, selector?: string, rect?: Record<string, unknown>, format?: 'png' | 'jpeg' | 'webp', quality?: number, nodeId?: number, tabId?: number, destinationId?: string, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
86
97
|
* @returns {Promise<ToolResult>}
|
|
87
98
|
*/
|
|
88
99
|
export async function handleCaptureTool(args) {
|
|
@@ -148,6 +159,9 @@ export function createScreenshotResult(response, method) {
|
|
|
148
159
|
mimeType: image.mimeType,
|
|
149
160
|
byteLength: image.byteLength,
|
|
150
161
|
...(rect ? { rect } : {}),
|
|
162
|
+
format: result.format ?? image.mimeType.replace('image/', ''),
|
|
163
|
+
complete: result.complete === true,
|
|
164
|
+
clipped: result.clipped === true,
|
|
151
165
|
},
|
|
152
166
|
false,
|
|
153
167
|
[{ type: 'image', data: image.data, mimeType: image.mimeType }]
|
|
@@ -294,7 +308,20 @@ function createInputTarget(elementRef, selector) {
|
|
|
294
308
|
}
|
|
295
309
|
|
|
296
310
|
/**
|
|
297
|
-
*
|
|
311
|
+
* Avoid changing legacy handler payloads when new options are omitted.
|
|
312
|
+
*
|
|
313
|
+
* @param {{ executionMode?: 'dom' | 'cdp', recoverStale?: boolean }} args
|
|
314
|
+
* @returns {{ executionMode?: 'dom' | 'cdp', recoverStale?: boolean }}
|
|
315
|
+
*/
|
|
316
|
+
function createInputOptions(args) {
|
|
317
|
+
return {
|
|
318
|
+
...(args.executionMode ? { executionMode: args.executionMode } : {}),
|
|
319
|
+
...(typeof args.recoverStale === 'boolean' ? { recoverStale: args.recoverStale } : {}),
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* @param {{ action: string, elementRef?: string, selector?: string, button?: string, clickCount?: number, text?: string, value?: string, mode?: 'auto' | 'setter' | 'keystrokes', executionMode?: 'dom' | 'cdp', recoverStale?: boolean, clear?: boolean, submit?: boolean, key?: string, code?: string, modifiers?: string[], checked?: boolean, values?: string[], labels?: string[], indexes?: number[], duration?: number, sourceElementRef?: string, sourceSelector?: string, destinationElementRef?: string, destinationSelector?: string, offsetX?: number, offsetY?: number, tabId?: number, destinationId?: string, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
298
325
|
* @returns {Promise<ToolResult>}
|
|
299
326
|
*/
|
|
300
327
|
export async function handleInputTool(args) {
|
|
@@ -337,6 +364,7 @@ export async function handleInputTool(args) {
|
|
|
337
364
|
button: args.button,
|
|
338
365
|
clickCount: args.clickCount,
|
|
339
366
|
modifiers: args.modifiers,
|
|
367
|
+
...createInputOptions(args),
|
|
340
368
|
},
|
|
341
369
|
{
|
|
342
370
|
tabId: requestedTabId,
|
|
@@ -352,6 +380,7 @@ export async function handleInputTool(args) {
|
|
|
352
380
|
'input.focus',
|
|
353
381
|
{
|
|
354
382
|
target: elementTarget(),
|
|
383
|
+
...createInputOptions(args),
|
|
355
384
|
},
|
|
356
385
|
{
|
|
357
386
|
tabId: requestedTabId,
|
|
@@ -371,6 +400,7 @@ export async function handleInputTool(args) {
|
|
|
371
400
|
clear: args.clear,
|
|
372
401
|
submit: args.submit,
|
|
373
402
|
modifiers: args.modifiers,
|
|
403
|
+
...createInputOptions(args),
|
|
374
404
|
},
|
|
375
405
|
{
|
|
376
406
|
tabId: requestedTabId,
|
|
@@ -388,6 +418,7 @@ export async function handleInputTool(args) {
|
|
|
388
418
|
target: elementTarget(),
|
|
389
419
|
value: args.value,
|
|
390
420
|
mode: args.mode,
|
|
421
|
+
...createInputOptions(args),
|
|
391
422
|
},
|
|
392
423
|
{
|
|
393
424
|
tabId: requestedTabId,
|
|
@@ -406,6 +437,7 @@ export async function handleInputTool(args) {
|
|
|
406
437
|
target,
|
|
407
438
|
key: args.key,
|
|
408
439
|
modifiers: args.modifiers,
|
|
440
|
+
...createInputOptions(args),
|
|
409
441
|
},
|
|
410
442
|
{
|
|
411
443
|
tabId: requestedTabId,
|
|
@@ -439,6 +471,7 @@ export async function handleInputTool(args) {
|
|
|
439
471
|
{
|
|
440
472
|
target: elementTarget(),
|
|
441
473
|
checked: args.checked,
|
|
474
|
+
...createInputOptions(args),
|
|
442
475
|
},
|
|
443
476
|
{
|
|
444
477
|
tabId: requestedTabId,
|
|
@@ -457,6 +490,7 @@ export async function handleInputTool(args) {
|
|
|
457
490
|
values: args.values,
|
|
458
491
|
labels: args.labels,
|
|
459
492
|
indexes: args.indexes,
|
|
493
|
+
...createInputOptions(args),
|
|
460
494
|
},
|
|
461
495
|
{
|
|
462
496
|
tabId: requestedTabId,
|
|
@@ -474,6 +508,7 @@ export async function handleInputTool(args) {
|
|
|
474
508
|
target: elementTarget(),
|
|
475
509
|
duration: args.duration,
|
|
476
510
|
modifiers: args.modifiers,
|
|
511
|
+
...createInputOptions(args),
|
|
477
512
|
},
|
|
478
513
|
{
|
|
479
514
|
tabId: requestedTabId,
|
|
@@ -502,6 +537,7 @@ export async function handleInputTool(args) {
|
|
|
502
537
|
destination,
|
|
503
538
|
offsetX: args.offsetX,
|
|
504
539
|
offsetY: args.offsetY,
|
|
540
|
+
...createInputOptions(args),
|
|
505
541
|
},
|
|
506
542
|
{
|
|
507
543
|
tabId: requestedTabId,
|
|
@@ -85,12 +85,18 @@ export const DOM_ACTIONS = {
|
|
|
85
85
|
accessibility_tree: {
|
|
86
86
|
ref: false,
|
|
87
87
|
method: 'dom.get_accessibility_tree',
|
|
88
|
-
params: (a) => ({
|
|
88
|
+
params: (a) => ({
|
|
89
|
+
selector: a.selector,
|
|
90
|
+
maxNodes: a.maxNodes,
|
|
91
|
+
maxDepth: a.maxDepth,
|
|
92
|
+
compact: a.compact,
|
|
93
|
+
interactiveOnly: a.interactiveOnly,
|
|
94
|
+
}),
|
|
89
95
|
},
|
|
90
96
|
};
|
|
91
97
|
|
|
92
98
|
/**
|
|
93
|
-
* @param {{ action: string, selector?: string, elementRef?: string, withinRef?: string, maxNodes?: number, maxDepth?: number, textBudget?: number, includeBbox?: boolean, attributeAllowlist?: string[], attributes?: string[], text?: string, exact?: boolean, maxResults?: number, role?: string, name?: string, state?: string, timeoutMs?: number, outer?: boolean, maxLength?: number, tabId?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
99
|
+
* @param {{ action: string, selector?: string, elementRef?: string, withinRef?: string, maxNodes?: number, maxDepth?: number, compact?: boolean, interactiveOnly?: boolean, textBudget?: number, includeBbox?: boolean, attributeAllowlist?: string[], attributes?: string[], text?: string, exact?: boolean, maxResults?: number, role?: string, name?: string, state?: string, timeoutMs?: number, outer?: boolean, maxLength?: number, tabId?: number, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
94
100
|
* @returns {Promise<ToolResult>}
|
|
95
101
|
*/
|
|
96
102
|
export async function handleDomTool(args) {
|
|
@@ -32,7 +32,8 @@ export async function handleTabsTool(args) {
|
|
|
32
32
|
let client = null;
|
|
33
33
|
try {
|
|
34
34
|
client = await createBridgeClientForDestination(
|
|
35
|
-
destination.local ? null : destination.id
|
|
35
|
+
destination.local ? null : destination.id,
|
|
36
|
+
{ checkProtocolOnConnect: false }
|
|
36
37
|
);
|
|
37
38
|
await client.connect();
|
|
38
39
|
const response = await requestBridgeWithRetry(
|
|
@@ -49,6 +49,7 @@ const BATCH_SAFE_METHODS = new Set([
|
|
|
49
49
|
'page.wait_for_load_state',
|
|
50
50
|
'page.get_storage',
|
|
51
51
|
'page.get_text',
|
|
52
|
+
'page.extract_content',
|
|
52
53
|
'page.get_network',
|
|
53
54
|
'dom.query',
|
|
54
55
|
'dom.describe',
|
|
@@ -78,6 +79,14 @@ export function isBatchSafeBridgeCall(method, params) {
|
|
|
78
79
|
if ((method === 'page.get_console' || method === 'page.get_network') && params.clear === true) {
|
|
79
80
|
return false;
|
|
80
81
|
}
|
|
82
|
+
if (
|
|
83
|
+
method === 'page.get_network' &&
|
|
84
|
+
params.source === 'cdp' &&
|
|
85
|
+
params.capture !== undefined &&
|
|
86
|
+
params.capture !== 'read'
|
|
87
|
+
) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
81
90
|
return true;
|
|
82
91
|
}
|
|
83
92
|
|
|
@@ -97,9 +106,22 @@ export const PAGE_ACTIONS = {
|
|
|
97
106
|
method: 'page.get_console',
|
|
98
107
|
params: (a) => ({ level: a.level, clear: a.clear, limit: a.limit }),
|
|
99
108
|
},
|
|
109
|
+
handle_dialog: {
|
|
110
|
+
method: 'page.handle_dialog',
|
|
111
|
+
params: (a) => ({
|
|
112
|
+
action: a.dialogAction,
|
|
113
|
+
promptText: a.promptText,
|
|
114
|
+
expectedDialogId: a.expectedDialogId,
|
|
115
|
+
}),
|
|
116
|
+
},
|
|
100
117
|
wait_for_load: {
|
|
101
118
|
method: 'page.wait_for_load_state',
|
|
102
|
-
params: (a) => ({
|
|
119
|
+
params: (a) => ({
|
|
120
|
+
timeoutMs: a.timeoutMs,
|
|
121
|
+
waitForLoad: a.waitForLoad,
|
|
122
|
+
url: a.url,
|
|
123
|
+
urlMatch: a.urlMatch,
|
|
124
|
+
}),
|
|
103
125
|
},
|
|
104
126
|
storage: {
|
|
105
127
|
method: 'page.get_storage',
|
|
@@ -109,24 +131,37 @@ export const PAGE_ACTIONS = {
|
|
|
109
131
|
method: 'page.get_text',
|
|
110
132
|
params: (a) => ({ textBudget: a.textBudget }),
|
|
111
133
|
},
|
|
134
|
+
extract_content: {
|
|
135
|
+
method: 'page.extract_content',
|
|
136
|
+
params: (a) => ({
|
|
137
|
+
format: a.format,
|
|
138
|
+
selector: a.selector,
|
|
139
|
+
includeMetadata: a.includeMetadata,
|
|
140
|
+
consistency: a.consistency,
|
|
141
|
+
textBudget: a.textBudget,
|
|
142
|
+
settleTimeoutMs: a.settleTimeoutMs,
|
|
143
|
+
}),
|
|
144
|
+
},
|
|
112
145
|
network: {
|
|
113
146
|
method: 'page.get_network',
|
|
114
147
|
params: (a) => ({
|
|
115
148
|
clear: a.clear,
|
|
116
149
|
limit: a.limit,
|
|
117
150
|
urlPattern: a.urlPattern,
|
|
151
|
+
source: a.source,
|
|
152
|
+
capture: a.capture,
|
|
118
153
|
}),
|
|
119
154
|
},
|
|
120
155
|
performance: { method: 'performance.get_metrics', params: () => ({}) },
|
|
121
156
|
};
|
|
122
157
|
|
|
123
158
|
/**
|
|
124
|
-
* @param {{ action: string, expression?: string, awaitPromise?: boolean, timeoutMs?: number, returnByValue?: boolean, level?: string, clear?: boolean, limit?: number, type?: string, keys?: string[], textBudget?: number, urlPattern?: string, tabId?: number, destinationId?: string, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
159
|
+
* @param {{ action: string, expression?: string, awaitPromise?: boolean, timeoutMs?: number, returnByValue?: boolean, level?: string, clear?: boolean, limit?: number, type?: string, keys?: string[], textBudget?: number, format?: 'text' | 'markdown', selector?: string, includeMetadata?: boolean, consistency?: 'best_effort' | 'settled', settleTimeoutMs?: number, urlPattern?: string, source?: 'fetch-xhr' | 'cdp', capture?: 'read' | 'start' | 'clear' | 'stop', dialogAction?: string, promptText?: string, expectedDialogId?: string, waitForLoad?: boolean, url?: string, urlMatch?: string, tabId?: number, destinationId?: string, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
|
|
125
160
|
* @returns {Promise<ToolResult>}
|
|
126
161
|
*/
|
|
127
162
|
export async function handlePageTool(args) {
|
|
128
163
|
let normalizedArgs = args;
|
|
129
|
-
if (args.action === 'text') {
|
|
164
|
+
if (args.action === 'text' || args.action === 'extract_content') {
|
|
130
165
|
normalizedArgs = applyPageTextBudgetPreset(args);
|
|
131
166
|
} else if (args.action === 'console') {
|
|
132
167
|
normalizedArgs = applyLimitBudgetPreset(args, {
|
|
@@ -201,7 +236,9 @@ export async function handleBatchTool(args) {
|
|
|
201
236
|
/** @type {unknown} */
|
|
202
237
|
let callError = null;
|
|
203
238
|
try {
|
|
204
|
-
callClient = await createBridgeClientForDestination(destinationId
|
|
239
|
+
callClient = await createBridgeClientForDestination(destinationId, {
|
|
240
|
+
checkProtocolOnConnect: false,
|
|
241
|
+
});
|
|
205
242
|
await callClient.connect();
|
|
206
243
|
const params = applyMethodBudgetPreset(method, call.params || {}, call.budgetPreset);
|
|
207
244
|
response = await requestBridgeWithRetry(callClient, method, params, {
|
|
@@ -45,6 +45,7 @@ const RETRY_SAFE_METHODS = new Set([
|
|
|
45
45
|
'page.get_state',
|
|
46
46
|
'page.get_storage',
|
|
47
47
|
'page.get_text',
|
|
48
|
+
'page.extract_content',
|
|
48
49
|
'dom.query',
|
|
49
50
|
'dom.describe',
|
|
50
51
|
'dom.get_text',
|
|
@@ -75,7 +76,13 @@ const RETRY_SAFE_METHODS = new Set([
|
|
|
75
76
|
*/
|
|
76
77
|
export function isRetrySafeBridgeMethod(method, params) {
|
|
77
78
|
if (method === 'page.get_console' || method === 'page.get_network') {
|
|
78
|
-
return
|
|
79
|
+
return (
|
|
80
|
+
params.clear !== true &&
|
|
81
|
+
(method !== 'page.get_network' ||
|
|
82
|
+
params.source !== 'cdp' ||
|
|
83
|
+
params.capture === undefined ||
|
|
84
|
+
params.capture === 'read')
|
|
85
|
+
);
|
|
79
86
|
}
|
|
80
87
|
return RETRY_SAFE_METHODS.has(method);
|
|
81
88
|
}
|
|
@@ -136,6 +143,7 @@ export function summarizeToolResponse(response, method, params = {}) {
|
|
|
136
143
|
if (response.ok) {
|
|
137
144
|
const evidence = getRequestAwareEvidence(response.result, method, params, summary.evidence);
|
|
138
145
|
summary.evidence = evidence.value;
|
|
146
|
+
if (evidence.metadata) summaryRecord.evidenceMeta = evidence.metadata;
|
|
139
147
|
if (evidence.truncated) {
|
|
140
148
|
summaryRecord.outputTruncated = true;
|
|
141
149
|
summaryRecord.outputLimit = evidence.limit;
|
|
@@ -190,9 +198,11 @@ export function summarizeToolError(error) {
|
|
|
190
198
|
export async function withToolClient(callback, options = {}) {
|
|
191
199
|
try {
|
|
192
200
|
if (!options.destinationId) {
|
|
193
|
-
return await withBridgeClient(callback);
|
|
201
|
+
return await withBridgeClient(callback, { checkProtocolOnConnect: false });
|
|
194
202
|
}
|
|
195
|
-
const client = await createBridgeClientForDestination(options.destinationId
|
|
203
|
+
const client = await createBridgeClientForDestination(options.destinationId, {
|
|
204
|
+
checkProtocolOnConnect: false,
|
|
205
|
+
});
|
|
196
206
|
await client.connect();
|
|
197
207
|
try {
|
|
198
208
|
return await callback(client);
|
|
@@ -364,7 +374,7 @@ export function applyMethodBudgetPreset(method, params, budgetPreset) {
|
|
|
364
374
|
normalized = applyTextBudgetPreset(args);
|
|
365
375
|
} else if (method === 'dom.get_html') {
|
|
366
376
|
normalized = applyHtmlBudgetPreset(args);
|
|
367
|
-
} else if (method === 'page.get_text') {
|
|
377
|
+
} else if (method === 'page.get_text' || method === 'page.extract_content') {
|
|
368
378
|
normalized = applyPageTextBudgetPreset(args);
|
|
369
379
|
} else if (method === 'page.get_console') {
|
|
370
380
|
normalized = applyLimitBudgetPreset(args, {
|
|
@@ -392,7 +402,7 @@ export function applyMethodBudgetPreset(method, params, budgetPreset) {
|
|
|
392
402
|
/**
|
|
393
403
|
* @param {unknown} input
|
|
394
404
|
* @param {{ maxEntries?: number, maxCharacters?: number, maxStringLength?: number }} [options]
|
|
395
|
-
* @returns {{ value: unknown, truncated: boolean, limit: Record<string, number> }}
|
|
405
|
+
* @returns {{ value: unknown, truncated: boolean, limit: Record<string, number>, metadata?: Record<string, unknown> }}
|
|
396
406
|
*/
|
|
397
407
|
export function boundToolValue(input, options = {}) {
|
|
398
408
|
const maxEntries = options.maxEntries ?? 500;
|
|
@@ -451,13 +461,27 @@ export function boundToolValue(input, options = {}) {
|
|
|
451
461
|
* @param {string | undefined} method
|
|
452
462
|
* @param {Record<string, unknown>} params
|
|
453
463
|
* @param {unknown} fallback
|
|
454
|
-
* @returns {{ value: unknown, truncated: boolean, limit: Record<string, number> }}
|
|
464
|
+
* @returns {{ value: unknown, truncated: boolean, limit: Record<string, number>, metadata?: Record<string, unknown> }}
|
|
455
465
|
*/
|
|
456
466
|
function getRequestAwareEvidence(rawResult, method, params, fallback) {
|
|
457
467
|
const result =
|
|
458
468
|
rawResult && typeof rawResult === 'object' && !Array.isArray(rawResult)
|
|
459
469
|
? /** @type {Record<string, unknown>} */ (rawResult)
|
|
460
470
|
: {};
|
|
471
|
+
if (method === 'page.extract_content' && typeof result.content === 'string') {
|
|
472
|
+
const requested = positiveInteger(params.textBudget) ?? DEFAULT_PAGE_TEXT_BUDGET;
|
|
473
|
+
const maxCharacters = Math.min(requested, 16_000);
|
|
474
|
+
const content = result.content.slice(0, maxCharacters);
|
|
475
|
+
return {
|
|
476
|
+
value: {
|
|
477
|
+
...result,
|
|
478
|
+
content,
|
|
479
|
+
returnedChars: content.length,
|
|
480
|
+
},
|
|
481
|
+
truncated: result.content.length > maxCharacters,
|
|
482
|
+
limit: { maxCharacters },
|
|
483
|
+
};
|
|
484
|
+
}
|
|
461
485
|
if (method === 'page.get_text' || method === 'dom.get_text') {
|
|
462
486
|
const text = typeof result.text === 'string' ? result.text : result.value;
|
|
463
487
|
if (typeof text === 'string') {
|
|
@@ -506,14 +530,30 @@ function getRequestAwareEvidence(rawResult, method, params, fallback) {
|
|
|
506
530
|
const node =
|
|
507
531
|
value && typeof value === 'object' ? /** @type {Record<string, unknown>} */ (value) : {};
|
|
508
532
|
return {
|
|
533
|
+
nodeId: node.nodeId,
|
|
509
534
|
role: node.role,
|
|
510
535
|
name: node.name,
|
|
511
536
|
...(node.interactive === true ? { interactive: true } : {}),
|
|
537
|
+
...(node.semanticInteractive === true ? { semanticInteractive: true } : {}),
|
|
538
|
+
...(node.focusableAndEnabled === true ? { focusableAndEnabled: true } : {}),
|
|
512
539
|
...(node.value !== undefined ? { value: node.value } : {}),
|
|
540
|
+
childIds: Array.isArray(node.childIds) ? node.childIds : [],
|
|
513
541
|
};
|
|
514
542
|
});
|
|
515
543
|
return {
|
|
516
544
|
value: nodes,
|
|
545
|
+
metadata: {
|
|
546
|
+
rootIds: Array.isArray(result.rootIds) ? result.rootIds : [],
|
|
547
|
+
count: result.count,
|
|
548
|
+
total: result.total,
|
|
549
|
+
rawTotal: result.rawTotal,
|
|
550
|
+
source: result.source,
|
|
551
|
+
compact: result.compact,
|
|
552
|
+
interactiveOnly: result.interactiveOnly,
|
|
553
|
+
truncated: result.truncated,
|
|
554
|
+
truncation: result.truncation,
|
|
555
|
+
continuationHint: result.continuationHint,
|
|
556
|
+
},
|
|
517
557
|
truncated: result.truncated === true || result.nodes.length > maxEntries,
|
|
518
558
|
limit: { maxEntries },
|
|
519
559
|
};
|
|
@@ -535,7 +575,27 @@ function getRequestAwareEvidence(rawResult, method, params, fallback) {
|
|
|
535
575
|
: result.entries.slice(0, maxEntries);
|
|
536
576
|
const bounded = boundToolValue(selected, { maxEntries: 500, maxCharacters: 20_000 });
|
|
537
577
|
return {
|
|
538
|
-
value:
|
|
578
|
+
value:
|
|
579
|
+
method === 'page.get_network'
|
|
580
|
+
? {
|
|
581
|
+
entries: bounded.value,
|
|
582
|
+
count: result.count,
|
|
583
|
+
total: result.total,
|
|
584
|
+
filteredTotal: result.filteredTotal,
|
|
585
|
+
dropped: result.dropped,
|
|
586
|
+
abandoned: result.abandoned,
|
|
587
|
+
source: result.source,
|
|
588
|
+
capture: result.capture,
|
|
589
|
+
armed: result.armed,
|
|
590
|
+
armedDuringCapture: result.armedDuringCapture,
|
|
591
|
+
captureState: result.captureState,
|
|
592
|
+
startedAt: result.startedAt,
|
|
593
|
+
inflight: result.inflight,
|
|
594
|
+
ownershipHeld: result.ownershipHeld,
|
|
595
|
+
truncated: result.truncated,
|
|
596
|
+
truncation: result.truncation,
|
|
597
|
+
}
|
|
598
|
+
: bounded.value,
|
|
539
599
|
truncated: result.entries.length > maxEntries || bounded.truncated,
|
|
540
600
|
limit: { maxEntries, maxCharacters: 20_000 },
|
|
541
601
|
};
|
|
@@ -69,7 +69,7 @@ export {
|
|
|
69
69
|
const HOME_DIR = os.homedir();
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
* @param {{ destinationId?: string }} [args]
|
|
72
|
+
* @param {{ destinationId?: string, intent?: import('../../protocol/src/types.js').AccessIntent }} [args]
|
|
73
73
|
* @returns {Promise<ToolResult>}
|
|
74
74
|
*/
|
|
75
75
|
export async function handleStatusTool(args = {}) {
|
|
@@ -155,7 +155,7 @@ async function callRemoteHealth(destinationId) {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
/**
|
|
158
|
-
* @param {{ destinationId?: string }} [args]
|
|
158
|
+
* @param {{ destinationId?: string, intent?: import('../../protocol/src/types.js').AccessIntent }} [args]
|
|
159
159
|
* @returns {Promise<ToolResult>}
|
|
160
160
|
*/
|
|
161
161
|
export async function handleSkillTool(args = {}) {
|
|
@@ -231,13 +231,13 @@ export async function handleHealthTool(args = {}) {
|
|
|
231
231
|
}
|
|
232
232
|
|
|
233
233
|
/**
|
|
234
|
-
* @param {{ destinationId?: string }} [args]
|
|
234
|
+
* @param {{ destinationId?: string, intent?: import('../../protocol/src/types.js').AccessIntent }} [args]
|
|
235
235
|
* @returns {Promise<ToolResult>}
|
|
236
236
|
*/
|
|
237
237
|
export async function handleAccessTool(args = {}) {
|
|
238
238
|
return callBridgeTool(
|
|
239
239
|
'access.request',
|
|
240
|
-
{},
|
|
240
|
+
{ intent: args.intent },
|
|
241
241
|
{
|
|
242
242
|
destinationId: typeof args.destinationId === 'string' ? args.destinationId : null,
|
|
243
243
|
}
|