@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
@@ -4,10 +4,21 @@ import type {
4
4
  BridgeMethod,
5
5
  BridgeRequestSource,
6
6
  BridgeResponse,
7
+ RecoveryEventKind,
8
+ RecoveryTelemetrySummary,
9
+ ScreenshotResult,
10
+ SetupStatus,
7
11
  } from '../../protocol/src/types.js';
8
12
  import type { restartBridgeDaemon } from '../../native-host/src/daemon-process.js';
9
13
 
10
- export type { BridgeMeta, BridgeMethod, BridgeRequestSource, BridgeResponse, BridgeTransport };
14
+ export type {
15
+ BridgeMeta,
16
+ BridgeMethod,
17
+ BridgeRequestSource,
18
+ BridgeResponse,
19
+ BridgeTransport,
20
+ ScreenshotResult,
21
+ };
11
22
 
12
23
  export type McpClientName =
13
24
  | 'codex'
@@ -21,6 +32,26 @@ export type McpClientName =
21
32
 
22
33
  export type SupportedTarget = McpClientName;
23
34
 
35
+ export type AutoUpdatePolicy = 'off' | 'compatible';
36
+
37
+ export interface BrowserBridgeConfig {
38
+ autoUpdate: AutoUpdatePolicy;
39
+ [key: string]: unknown;
40
+ }
41
+
42
+ export interface NpmUpdateResult {
43
+ updated: boolean;
44
+ reason:
45
+ | 'updated'
46
+ | 'invalid_extension_version'
47
+ | 'invalid_installed_version'
48
+ | 'extension_not_newer'
49
+ | 'not_global_install'
50
+ | 'no_compatible_update';
51
+ previousVersion?: string;
52
+ version?: string;
53
+ }
54
+
24
55
  export type Detector = () => boolean | Promise<boolean>;
25
56
 
26
57
  export interface InstallAgentOptions {
@@ -42,7 +73,10 @@ export interface SetupStatusOptions {
42
73
 
43
74
  export interface ProtocolHealthResult {
44
75
  extensionConnected?: boolean;
76
+ extensionVersion?: string;
77
+ daemonVersion?: string;
45
78
  supported_versions?: string[];
79
+ extension_supported_versions?: string[];
46
80
  daemon_supported_versions?: string[];
47
81
  deprecated_since?: string;
48
82
  migration_hint?: string;
@@ -78,8 +112,15 @@ export interface BridgeClientOptions {
78
112
  clientId?: string;
79
113
  defaultTimeoutMs?: number;
80
114
  autoReconnect?: boolean;
115
+ checkProtocolOnConnect?: boolean;
81
116
  restartDaemonOnVersionMismatch?: boolean;
82
117
  restartDaemonFn?: typeof restartBridgeDaemon;
118
+ updateNpmOnCompatibleVersion?: boolean;
119
+ exitProcessOnNpmUpdate?: boolean;
120
+ updateCompatibleNpmPackageFn?: (options: {
121
+ extensionVersion: string;
122
+ supportedVersions: readonly string[];
123
+ }) => Promise<NpmUpdateResult>;
83
124
  authToken?: string | null;
84
125
  }
85
126
 
@@ -110,6 +151,113 @@ export interface NativeHostManifestIssue {
110
151
  message: string;
111
152
  }
112
153
 
154
+ export interface DoctorTransportDiagnostics {
155
+ kind: 'socket' | 'tcp' | 'unknown';
156
+ local: true;
157
+ status: 'reachable' | 'offline' | 'authentication_failed';
158
+ proxyConfigured: boolean;
159
+ proxyExposed: boolean | null;
160
+ credentials: 'not_required' | 'accepted' | 'rejected' | 'unknown';
161
+ }
162
+
163
+ export interface DoctorConnectionDiagnostics {
164
+ extensionCount: number;
165
+ profileCount: number;
166
+ }
167
+
168
+ export interface DoctorProtocolDiagnostics {
169
+ clientVersion: string;
170
+ daemonVersion: string | null;
171
+ daemonSupportedVersions: string[];
172
+ extensionSupportedVersions: string[];
173
+ daemonCompatible: boolean | null;
174
+ extensionCompatible: boolean | null;
175
+ compatible: boolean | null;
176
+ migration: 'none' | 'update_client' | 'restart_daemon' | 'update_extension' | 'unknown';
177
+ }
178
+
179
+ export interface DoctorDebuggerDiagnostics {
180
+ state: 'idle' | 'active' | 'conflict' | 'detached' | 'unknown';
181
+ attachedTabCount: number | null;
182
+ heldTabCount: number | null;
183
+ pendingTabCount: number | null;
184
+ recentReason:
185
+ | 'debugger_conflict'
186
+ | 'debugger_detached'
187
+ | 'debugger_replaced'
188
+ | 'debugger_canceled'
189
+ | 'target_closed'
190
+ | null;
191
+ captureState:
192
+ | 'idle'
193
+ | 'stopped'
194
+ | 'armed'
195
+ | 'active'
196
+ | 'capturing'
197
+ | 'stop_failed'
198
+ | 'unavailable'
199
+ | 'unknown';
200
+ captureActiveTabCount: number | null;
201
+ captureOwnershipCount: number | null;
202
+ captureInflightCount: number | null;
203
+ interceptionActiveTabCount: number | null;
204
+ interceptionRuleCount: number | null;
205
+ }
206
+
207
+ export interface DoctorDaemonMetrics {
208
+ uptimeMs: number;
209
+ activeAgents: number;
210
+ activeExtensions: number;
211
+ pendingRequests: number;
212
+ requestsProcessed: number;
213
+ requestsFailed: number;
214
+ avgResponseTimeMs: number;
215
+ recovery: RecoveryTelemetrySummary | null;
216
+ }
217
+
218
+ export interface DoctorRecoveryDiagnostics {
219
+ daemon: RecoveryTelemetrySummary | null;
220
+ routedExtension: RecoveryTelemetrySummary | null;
221
+ activeLoops: RecoveryEventKind[];
222
+ }
223
+
224
+ export interface DoctorRecentEvent {
225
+ at?: string;
226
+ method?: BridgeMethod;
227
+ ok?: boolean;
228
+ source?: BridgeRequestSource;
229
+ cause?:
230
+ | 'debugger_conflict'
231
+ | 'debugger_detached'
232
+ | 'debugger_replaced'
233
+ | 'debugger_canceled'
234
+ | 'target_closed'
235
+ | 'dialog_conflict'
236
+ | 'extension_disconnected'
237
+ | 'wrong_window';
238
+ }
239
+
240
+ export interface DoctorSetupSummary {
241
+ source: 'daemon' | 'direct';
242
+ scope: 'global' | 'local';
243
+ mcp: {
244
+ detected: number;
245
+ configured: number;
246
+ };
247
+ skills: {
248
+ detected: number;
249
+ installed: number;
250
+ managed: number;
251
+ updatesAvailable: number;
252
+ };
253
+ }
254
+
255
+ export interface DoctorRemoteDiagnostics {
256
+ configuredCount: number;
257
+ status: 'not_configured' | 'not_probed_local_only' | 'config_unavailable';
258
+ credentials: 'not_configured' | 'unverified';
259
+ }
260
+
113
261
  export interface DoctorReport {
114
262
  manifestInstalled: boolean;
115
263
  manifestPath: string;
@@ -117,6 +265,7 @@ export interface DoctorReport {
117
265
  defaultExtensionId: string | null;
118
266
  defaultExtensionIdSource: string;
119
267
  daemonReachable: boolean;
268
+ healthAvailable: boolean;
120
269
  extensionConnected: boolean;
121
270
  accessEnabled: boolean;
122
271
  enabledWindowId: number | null;
@@ -130,6 +279,17 @@ export interface DoctorReport {
130
279
  issues: string[];
131
280
  nextSteps: string[];
132
281
  browserManifests: BrowserManifestStatus[];
282
+ transport: DoctorTransportDiagnostics;
283
+ connections: DoctorConnectionDiagnostics;
284
+ protocol: DoctorProtocolDiagnostics;
285
+ debugger: DoctorDebuggerDiagnostics;
286
+ recovery: DoctorRecoveryDiagnostics;
287
+ metrics: DoctorDaemonMetrics | null;
288
+ recentEvents: DoctorRecentEvent[];
289
+ recentCauses: DoctorRecentEvent['cause'][];
290
+ setup: DoctorSetupSummary | null;
291
+ remoteDestinations: DoctorRemoteDiagnostics;
292
+ diagnosticFailures: string[];
133
293
  }
134
294
 
135
295
  export interface DoctorReportOptions {
@@ -145,6 +305,14 @@ export interface DoctorReportOptions {
145
305
  checkNativeHostManifestHealth?: (
146
306
  browserManifests: BrowserManifestStatus[]
147
307
  ) => Promise<NativeHostManifestIssue[]>;
308
+ readInstalledExtensionIds?: (browserManifests: BrowserManifestStatus[]) => Promise<string[]>;
309
+ collectSetupStatus?: () => Promise<SetupStatus>;
310
+ readRemoteConfig?: () => Promise<{
311
+ remotes: Array<{ id: string; host: string; port: number; token: string }>;
312
+ }>;
313
+ getLocalTransport?: () => BridgeTransport;
314
+ readProxyConfig?: () => { enabled: boolean } | null;
315
+ includeSetupStatus?: boolean;
148
316
  }
149
317
 
150
318
  export type BridgeClientRequest = (options: {
@@ -154,8 +322,3 @@ export type BridgeClientRequest = (options: {
154
322
  meta?: BridgeMeta;
155
323
  timeoutMs?: number;
156
324
  }) => 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,23 +22,40 @@ export const CAPTURE_ACTIONS = {
22
22
  element: {
23
23
  ref: true,
24
24
  method: 'screenshot.capture_element',
25
- params: (_, r) => ({ elementRef: r }),
25
+ params: (a, r) => ({
26
+ elementRef: r,
27
+ ...(a.format ? { format: a.format } : {}),
28
+ ...(typeof a.quality === 'number' ? { quality: a.quality } : {}),
29
+ delivery: a.delivery ?? 'auto',
30
+ ...(typeof a.scale === 'number' ? { scale: a.scale } : {}),
31
+ }),
26
32
  },
27
33
  region: {
28
34
  ref: false,
29
35
  method: 'screenshot.capture_region',
30
- params: (a) => /** @type {Record<string, unknown>} */ (a.rect || {}),
36
+ params: (a) => ({
37
+ .../** @type {Record<string, unknown>} */ (a.rect || {}),
38
+ ...(a.format ? { format: a.format } : {}),
39
+ ...(typeof a.quality === 'number' ? { quality: a.quality } : {}),
40
+ delivery: a.delivery ?? 'auto',
41
+ ...(typeof a.scale === 'number' ? { scale: a.scale } : {}),
42
+ }),
31
43
  },
32
44
  full_page: {
33
45
  ref: false,
34
46
  method: 'screenshot.capture_full_page',
35
- params: () => ({}),
47
+ params: (a) => ({
48
+ ...(a.format ? { format: a.format } : {}),
49
+ ...(typeof a.quality === 'number' ? { quality: a.quality } : {}),
50
+ delivery: a.delivery ?? 'auto',
51
+ ...(typeof a.scale === 'number' ? { scale: a.scale } : {}),
52
+ }),
36
53
  },
37
54
  cdp_document: { ref: false, method: 'cdp.get_document', params: () => ({}) },
38
55
  cdp_dom_snapshot: {
39
56
  ref: false,
40
57
  method: 'cdp.get_dom_snapshot',
41
- params: () => ({}),
58
+ params: (a) => ({ computedStyles: a.computedStyles }),
42
59
  },
43
60
  cdp_box_model: {
44
61
  ref: false,
@@ -82,7 +99,7 @@ function isValidCaptureRegion(rect) {
82
99
  }
83
100
 
84
101
  /**
85
- * @param {{ action: string, elementRef?: string, selector?: string, rect?: Record<string, unknown>, nodeId?: number, tabId?: number, destinationId?: string, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
102
+ * @param {{ action: string, elementRef?: string, selector?: string, rect?: Record<string, unknown>, format?: 'png' | 'jpeg' | 'webp', quality?: number, delivery?: 'auto' | 'inline' | 'artifact', scale?: number, nodeId?: number, computedStyles?: string[], tabId?: number, destinationId?: string, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
86
103
  * @returns {Promise<ToolResult>}
87
104
  */
88
105
  export async function handleCaptureTool(args) {
@@ -130,6 +147,27 @@ export async function handleCaptureTool(args) {
130
147
  */
131
148
  export function createScreenshotResult(response, method) {
132
149
  const result = toRecord(response.result);
150
+ if (result.delivery === 'artifact') {
151
+ const artifact = toRecord(result.artifact);
152
+ if (typeof artifact.artifactId !== 'string' || typeof artifact.byteLength !== 'number') {
153
+ return summarizeToolError(new Error(`${method} returned invalid artifact metadata.`));
154
+ }
155
+ return createToolResult(
156
+ `Captured screenshot artifact (${artifact.byteLength} bytes, expires ${artifact.expiresAt ?? 'soon'}).`,
157
+ {
158
+ ok: true,
159
+ method,
160
+ delivery: 'artifact',
161
+ artifact,
162
+ mimeType: result.mimeType,
163
+ byteLength: result.byteLength,
164
+ dimensions: result.dimensions,
165
+ rect: boundedRect(result.rect),
166
+ complete: result.complete === true,
167
+ clipped: result.clipped === true,
168
+ }
169
+ );
170
+ }
133
171
  if (typeof result.image !== 'string') {
134
172
  return summarizeToolError(new Error(`${method} returned no image data.`));
135
173
  }
@@ -148,6 +186,9 @@ export function createScreenshotResult(response, method) {
148
186
  mimeType: image.mimeType,
149
187
  byteLength: image.byteLength,
150
188
  ...(rect ? { rect } : {}),
189
+ format: result.format ?? image.mimeType.replace('image/', ''),
190
+ complete: result.complete === true,
191
+ clipped: result.clipped === true,
151
192
  },
152
193
  false,
153
194
  [{ type: 'image', data: image.data, mimeType: image.mimeType }]
@@ -294,7 +335,20 @@ function createInputTarget(elementRef, selector) {
294
335
  }
295
336
 
296
337
  /**
297
- * @param {{ action: string, elementRef?: string, selector?: string, button?: string, clickCount?: number, text?: string, value?: string, mode?: 'auto' | 'setter' | 'keystrokes', 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
338
+ * Avoid changing legacy handler payloads when new options are omitted.
339
+ *
340
+ * @param {{ executionMode?: 'dom' | 'cdp', recoverStale?: boolean }} args
341
+ * @returns {{ executionMode?: 'dom' | 'cdp', recoverStale?: boolean }}
342
+ */
343
+ function createInputOptions(args) {
344
+ return {
345
+ ...(args.executionMode ? { executionMode: args.executionMode } : {}),
346
+ ...(typeof args.recoverStale === 'boolean' ? { recoverStale: args.recoverStale } : {}),
347
+ };
348
+ }
349
+
350
+ /**
351
+ * @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
352
  * @returns {Promise<ToolResult>}
299
353
  */
300
354
  export async function handleInputTool(args) {
@@ -337,6 +391,7 @@ export async function handleInputTool(args) {
337
391
  button: args.button,
338
392
  clickCount: args.clickCount,
339
393
  modifiers: args.modifiers,
394
+ ...createInputOptions(args),
340
395
  },
341
396
  {
342
397
  tabId: requestedTabId,
@@ -352,6 +407,7 @@ export async function handleInputTool(args) {
352
407
  'input.focus',
353
408
  {
354
409
  target: elementTarget(),
410
+ ...createInputOptions(args),
355
411
  },
356
412
  {
357
413
  tabId: requestedTabId,
@@ -371,6 +427,7 @@ export async function handleInputTool(args) {
371
427
  clear: args.clear,
372
428
  submit: args.submit,
373
429
  modifiers: args.modifiers,
430
+ ...createInputOptions(args),
374
431
  },
375
432
  {
376
433
  tabId: requestedTabId,
@@ -388,6 +445,7 @@ export async function handleInputTool(args) {
388
445
  target: elementTarget(),
389
446
  value: args.value,
390
447
  mode: args.mode,
448
+ ...createInputOptions(args),
391
449
  },
392
450
  {
393
451
  tabId: requestedTabId,
@@ -406,6 +464,7 @@ export async function handleInputTool(args) {
406
464
  target,
407
465
  key: args.key,
408
466
  modifiers: args.modifiers,
467
+ ...createInputOptions(args),
409
468
  },
410
469
  {
411
470
  tabId: requestedTabId,
@@ -439,6 +498,7 @@ export async function handleInputTool(args) {
439
498
  {
440
499
  target: elementTarget(),
441
500
  checked: args.checked,
501
+ ...createInputOptions(args),
442
502
  },
443
503
  {
444
504
  tabId: requestedTabId,
@@ -457,6 +517,7 @@ export async function handleInputTool(args) {
457
517
  values: args.values,
458
518
  labels: args.labels,
459
519
  indexes: args.indexes,
520
+ ...createInputOptions(args),
460
521
  },
461
522
  {
462
523
  tabId: requestedTabId,
@@ -474,6 +535,7 @@ export async function handleInputTool(args) {
474
535
  target: elementTarget(),
475
536
  duration: args.duration,
476
537
  modifiers: args.modifiers,
538
+ ...createInputOptions(args),
477
539
  },
478
540
  {
479
541
  tabId: requestedTabId,
@@ -502,6 +564,7 @@ export async function handleInputTool(args) {
502
564
  destination,
503
565
  offsetX: args.offsetX,
504
566
  offsetY: args.offsetY,
567
+ ...createInputOptions(args),
505
568
  },
506
569
  {
507
570
  tabId: requestedTabId,
@@ -15,6 +15,32 @@ import {
15
15
 
16
16
  /** @type {Record<string, ToolAction>} */
17
17
  export const DOM_ACTIONS = {
18
+ baseline_create: {
19
+ ref: false,
20
+ method: 'dom.baseline.create',
21
+ params: (a) => ({
22
+ selector: a.selector,
23
+ maxNodes: a.maxNodes,
24
+ maxDepth: a.maxDepth,
25
+ textBudget: a.textBudget,
26
+ attributeAllowlist: a.attributeAllowlist,
27
+ }),
28
+ },
29
+ baseline_compare: {
30
+ ref: false,
31
+ method: 'dom.baseline.compare',
32
+ params: (a) => ({ baselineId: a.baselineId, maxChanges: a.maxChanges }),
33
+ },
34
+ baseline_describe: {
35
+ ref: false,
36
+ method: 'dom.baseline.describe',
37
+ params: (a) => ({ baselineId: a.baselineId }),
38
+ },
39
+ baseline_release: {
40
+ ref: false,
41
+ method: 'dom.baseline.release',
42
+ params: (a) => ({ baselineId: a.baselineId }),
43
+ },
18
44
  query: {
19
45
  ref: false,
20
46
  method: 'dom.query',
@@ -85,12 +111,18 @@ export const DOM_ACTIONS = {
85
111
  accessibility_tree: {
86
112
  ref: false,
87
113
  method: 'dom.get_accessibility_tree',
88
- params: (a) => ({ maxNodes: a.maxNodes, maxDepth: a.maxDepth }),
114
+ params: (a) => ({
115
+ selector: a.selector,
116
+ maxNodes: a.maxNodes,
117
+ maxDepth: a.maxDepth,
118
+ compact: a.compact,
119
+ interactiveOnly: a.interactiveOnly,
120
+ }),
89
121
  },
90
122
  };
91
123
 
92
124
  /**
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
125
+ * @param {{ action: string, baselineId?: string, maxChanges?: number, 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, destinationId?: string, budgetPreset?: 'quick' | 'normal' | 'deep' }} args
94
126
  * @returns {Promise<ToolResult>}
95
127
  */
96
128
  export async function handleDomTool(args) {
@@ -103,7 +135,17 @@ export async function handleDomTool(args) {
103
135
  if (args.action === 'find_role' && !hasText(args.role)) {
104
136
  return summarizeToolError('role is required for dom.find_by_role.');
105
137
  }
106
- if (args.action === 'query' || args.action === 'accessibility_tree') {
138
+ if (
139
+ ['baseline_compare', 'baseline_describe', 'baseline_release'].includes(args.action) &&
140
+ !hasText(args.baselineId)
141
+ ) {
142
+ return summarizeToolError('baselineId is required for this DOM baseline action.');
143
+ }
144
+ if (
145
+ args.action === 'query' ||
146
+ args.action === 'accessibility_tree' ||
147
+ args.action === 'baseline_create'
148
+ ) {
107
149
  const inferred =
108
150
  args.action === 'accessibility_tree' ? 'normal' : inferBudgetFromSelector(args);
109
151
  const withBudget = inferred ? { ...args, budgetPreset: args.budgetPreset ?? inferred } : args;
@@ -112,6 +154,18 @@ export async function handleDomTool(args) {
112
154
  if (args.action === 'text') {
113
155
  return dispatchToolAction(DOM_ACTIONS, applyTextBudgetPreset(args), 'DOM');
114
156
  }
157
+ if (args.action === 'baseline_compare') {
158
+ const byPreset = { quick: 10, normal: 50, deep: 100 };
159
+ return dispatchToolAction(
160
+ DOM_ACTIONS,
161
+ {
162
+ ...args,
163
+ maxChanges:
164
+ args.maxChanges ?? (args.budgetPreset ? byPreset[args.budgetPreset] : undefined),
165
+ },
166
+ 'DOM'
167
+ );
168
+ }
115
169
  if (args.action === 'html') {
116
170
  return dispatchToolAction(DOM_ACTIONS, applyHtmlBudgetPreset(args), 'DOM');
117
171
  }
@@ -183,11 +237,14 @@ export const PATCH_ACTIONS = {
183
237
  removeAttribute: 'remove_attribute',
184
238
  addClass: 'add_class',
185
239
  removeClass: 'remove_class',
240
+ toggleClass: 'toggle_class',
186
241
  setTextContent: 'set_text',
187
242
  };
188
243
  const normalizedOperation = opMap[operation] || operation;
189
244
  const value =
190
- normalizedOperation === 'add_class' || normalizedOperation === 'remove_class'
245
+ normalizedOperation === 'add_class' ||
246
+ normalizedOperation === 'remove_class' ||
247
+ normalizedOperation === 'toggle_class'
191
248
  ? (a.value ?? a.name)
192
249
  : a.value;
193
250
  return {
@@ -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(