@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
|
@@ -169,6 +169,10 @@ export function createBridgeMcpServer() {
|
|
|
169
169
|
description: 'Ping the bridge to verify daemon and extension connectivity.',
|
|
170
170
|
inputSchema: {
|
|
171
171
|
destinationId: z.string().optional().describe(DESTINATION_ID_DESCRIPTION),
|
|
172
|
+
intent: z
|
|
173
|
+
.enum(['inspect', 'interact', 'capture', 'navigate', 'debugger', 'general'])
|
|
174
|
+
.optional()
|
|
175
|
+
.describe('Reported operation family shown in the access prompt (defaults to general).'),
|
|
172
176
|
},
|
|
173
177
|
},
|
|
174
178
|
handleHealthTool
|
|
@@ -206,7 +210,7 @@ export function createBridgeMcpServer() {
|
|
|
206
210
|
{
|
|
207
211
|
title: 'Browser DOM',
|
|
208
212
|
description:
|
|
209
|
-
'Query, describe, read, search, or wait for DOM elements. Reuse elementRef from prior results. For full-page text, use browser_page action "text". accessibility_tree is debugger-backed - use query/find first.',
|
|
213
|
+
'Query, describe, read, search, or wait for DOM elements. Reuse elementRef from prior results. For full-page text, use browser_page action "text". accessibility_tree is debugger-backed and depth-limited - use query/find first.',
|
|
210
214
|
inputSchema: {
|
|
211
215
|
action: z
|
|
212
216
|
.enum([
|
|
@@ -230,7 +234,9 @@ export function createBridgeMcpServer() {
|
|
|
230
234
|
selector: z
|
|
231
235
|
.string()
|
|
232
236
|
.optional()
|
|
233
|
-
.describe(
|
|
237
|
+
.describe(
|
|
238
|
+
'CSS selector (used if no elementRef; accessibility_tree requires exactly one match)'
|
|
239
|
+
),
|
|
234
240
|
elementRef: z
|
|
235
241
|
.string()
|
|
236
242
|
.optional()
|
|
@@ -248,6 +254,16 @@ export function createBridgeMcpServer() {
|
|
|
248
254
|
.positive()
|
|
249
255
|
.optional()
|
|
250
256
|
.describe(`Maximum tree depth (default: ${DEFAULT_MAX_DEPTH})`),
|
|
257
|
+
compact: z
|
|
258
|
+
.boolean()
|
|
259
|
+
.optional()
|
|
260
|
+
.describe('Remove ignored, empty, and decorative AX nodes while preserving descendants'),
|
|
261
|
+
interactiveOnly: z
|
|
262
|
+
.boolean()
|
|
263
|
+
.optional()
|
|
264
|
+
.describe(
|
|
265
|
+
'Return semantically interactive AX nodes; actionable state is reported separately'
|
|
266
|
+
),
|
|
251
267
|
textBudget: z
|
|
252
268
|
.number()
|
|
253
269
|
.int()
|
|
@@ -327,7 +343,9 @@ export function createBridgeMcpServer() {
|
|
|
327
343
|
properties: z
|
|
328
344
|
.array(z.string())
|
|
329
345
|
.optional()
|
|
330
|
-
.describe(
|
|
346
|
+
.describe(
|
|
347
|
+
'Style properties to fetch (omitting returns display, position, width, height, and color)'
|
|
348
|
+
),
|
|
331
349
|
x: z
|
|
332
350
|
.number()
|
|
333
351
|
.nonnegative()
|
|
@@ -348,16 +366,18 @@ export function createBridgeMcpServer() {
|
|
|
348
366
|
{
|
|
349
367
|
title: 'Browser Page State',
|
|
350
368
|
description:
|
|
351
|
-
'Read page-level data
|
|
369
|
+
'Read page-level data, wait for load/URL conditions, or explicitly inspect/handle JavaScript dialogs. For element-level reads, use browser_dom. evaluate, performance, handle_dialog, and source=cdp network capture are debugger-backed.',
|
|
352
370
|
inputSchema: {
|
|
353
371
|
action: z
|
|
354
372
|
.enum([
|
|
355
373
|
'state',
|
|
356
374
|
'evaluate',
|
|
357
375
|
'console',
|
|
376
|
+
'handle_dialog',
|
|
358
377
|
'wait_for_load',
|
|
359
378
|
'storage',
|
|
360
379
|
'text',
|
|
380
|
+
'extract_content',
|
|
361
381
|
'network',
|
|
362
382
|
'performance',
|
|
363
383
|
])
|
|
@@ -373,6 +393,23 @@ export function createBridgeMcpServer() {
|
|
|
373
393
|
.optional()
|
|
374
394
|
.describe('JavaScript expression to evaluate (for evaluate action)'),
|
|
375
395
|
awaitPromise: z.boolean().optional().describe('Await returned promises (default: false)'),
|
|
396
|
+
dialogAction: z
|
|
397
|
+
.enum(['inspect', 'accept', 'dismiss'])
|
|
398
|
+
.optional()
|
|
399
|
+
.describe('Dialog operation on the current dialog (default: inspect)'),
|
|
400
|
+
promptText: z
|
|
401
|
+
.string()
|
|
402
|
+
.max(10_000)
|
|
403
|
+
.optional()
|
|
404
|
+
.describe('Prompt response text; only valid when dialogAction is accept'),
|
|
405
|
+
expectedDialogId: z
|
|
406
|
+
.string()
|
|
407
|
+
.min(1)
|
|
408
|
+
.max(128)
|
|
409
|
+
.optional()
|
|
410
|
+
.describe(
|
|
411
|
+
'Optional stale-decision check immediately before CDP dispatch; Chrome cannot atomically bind the action to this identifier'
|
|
412
|
+
),
|
|
376
413
|
timeoutMs: z
|
|
377
414
|
.number()
|
|
378
415
|
.int()
|
|
@@ -412,7 +449,43 @@ export function createBridgeMcpServer() {
|
|
|
412
449
|
.positive()
|
|
413
450
|
.optional()
|
|
414
451
|
.describe(`Max chars for page text (default: ${DEFAULT_PAGE_TEXT_BUDGET})`),
|
|
452
|
+
format: z
|
|
453
|
+
.enum(['text', 'markdown'])
|
|
454
|
+
.optional()
|
|
455
|
+
.describe('Semantic content format (default: text)'),
|
|
456
|
+
selector: z.string().optional().describe('Scope semantic extraction to one CSS selector'),
|
|
457
|
+
includeMetadata: z
|
|
458
|
+
.boolean()
|
|
459
|
+
.optional()
|
|
460
|
+
.describe('Include bounded article metadata (default: true)'),
|
|
461
|
+
consistency: z
|
|
462
|
+
.enum(['best_effort', 'settled'])
|
|
463
|
+
.optional()
|
|
464
|
+
.describe('Optionally wait for a short mutation-quiet interval'),
|
|
465
|
+
settleTimeoutMs: z
|
|
466
|
+
.number()
|
|
467
|
+
.int()
|
|
468
|
+
.positive()
|
|
469
|
+
.optional()
|
|
470
|
+
.describe('Maximum semantic extraction settlement wait (default: 2000)'),
|
|
415
471
|
urlPattern: z.string().optional().describe('Filter network entries by URL pattern'),
|
|
472
|
+
source: z
|
|
473
|
+
.enum(['fetch-xhr', 'cdp'])
|
|
474
|
+
.optional()
|
|
475
|
+
.describe('Network source (default: fetch-xhr; cdp captures all resource types)'),
|
|
476
|
+
capture: z
|
|
477
|
+
.enum(['read', 'start', 'clear', 'stop'])
|
|
478
|
+
.optional()
|
|
479
|
+
.describe('Explicit CDP capture lifecycle action (default: read)'),
|
|
480
|
+
waitForLoad: z
|
|
481
|
+
.boolean()
|
|
482
|
+
.optional()
|
|
483
|
+
.describe('For wait_for_load, also require tab status complete (default: true)'),
|
|
484
|
+
url: z.string().optional().describe('URL condition for wait_for_load'),
|
|
485
|
+
urlMatch: z
|
|
486
|
+
.enum(['exact', 'contains', 'regex'])
|
|
487
|
+
.optional()
|
|
488
|
+
.describe('URL match mode for wait_for_load (default: exact)'),
|
|
416
489
|
},
|
|
417
490
|
},
|
|
418
491
|
handlePageTool
|
|
@@ -467,7 +540,7 @@ export function createBridgeMcpServer() {
|
|
|
467
540
|
{
|
|
468
541
|
title: 'Browser Input',
|
|
469
542
|
description:
|
|
470
|
-
'
|
|
543
|
+
'Dispatch browser input. Targeted click, focus, type, fill, press_key, set_checked, select_option, hover, and drag actions perform actionability checks and report resolution/execution metadata. cdp_press_key and scroll_into_view use separate contracts. Reuse elementRef values and verify application state after mutations.',
|
|
471
544
|
inputSchema: {
|
|
472
545
|
action: z
|
|
473
546
|
.enum([
|
|
@@ -512,6 +585,18 @@ export function createBridgeMcpServer() {
|
|
|
512
585
|
.enum(['auto', 'setter', 'keystrokes'])
|
|
513
586
|
.optional()
|
|
514
587
|
.describe('Fill strategy (default: auto)'),
|
|
588
|
+
executionMode: z
|
|
589
|
+
.enum(['dom', 'cdp'])
|
|
590
|
+
.optional()
|
|
591
|
+
.describe(
|
|
592
|
+
'Input execution path (default: dom; CDP supports click, hover, drag, type, fill)'
|
|
593
|
+
),
|
|
594
|
+
recoverStale: z
|
|
595
|
+
.boolean()
|
|
596
|
+
.optional()
|
|
597
|
+
.describe(
|
|
598
|
+
'Strict bounded same-document stale elementRef recovery for targeted input actions (default: false; not used by cdp_press_key or scroll_into_view)'
|
|
599
|
+
),
|
|
515
600
|
clear: z.boolean().optional().describe('Clear field before typing (default: false)'),
|
|
516
601
|
submit: z.boolean().optional().describe('Press Enter after typing (default: false)'),
|
|
517
602
|
key: z
|
|
@@ -567,7 +652,7 @@ export function createBridgeMcpServer() {
|
|
|
567
652
|
{
|
|
568
653
|
title: 'Browser Patch',
|
|
569
654
|
description:
|
|
570
|
-
'Apply or rollback
|
|
655
|
+
'Apply or rollback document-local style and DOM patches for live prototyping before editing source. commit_baseline keeps current changes but discards rollback history. Set verify=true to get computed results inline.',
|
|
571
656
|
inputSchema: {
|
|
572
657
|
action: z
|
|
573
658
|
.enum(['apply_styles', 'apply_dom', 'list', 'rollback', 'commit_baseline'])
|
|
@@ -652,6 +737,17 @@ export function createBridgeMcpServer() {
|
|
|
652
737
|
})
|
|
653
738
|
.optional()
|
|
654
739
|
.describe('Viewport region for region action (keep crop tight)'),
|
|
740
|
+
format: z
|
|
741
|
+
.enum(['png', 'jpeg', 'webp'])
|
|
742
|
+
.optional()
|
|
743
|
+
.describe('Screenshot encoding (default: png)'),
|
|
744
|
+
quality: z
|
|
745
|
+
.number()
|
|
746
|
+
.int()
|
|
747
|
+
.min(0)
|
|
748
|
+
.max(100)
|
|
749
|
+
.optional()
|
|
750
|
+
.describe('JPEG/WebP quality from 0 to 100; ignored for PNG'),
|
|
655
751
|
},
|
|
656
752
|
},
|
|
657
753
|
handleCaptureTool
|
|
@@ -53,7 +53,7 @@ setProtocolPackageVersion(DAEMON_VERSION);
|
|
|
53
53
|
/** @typedef {import('./config.js').BridgeTransport} BridgeTransport */
|
|
54
54
|
/** @typedef {import('./daemon-logger.js').DaemonLoggerLike} DaemonLoggerLike */
|
|
55
55
|
/** @typedef {'agent' | 'extension'} SocketRole */
|
|
56
|
-
/** @typedef {import('node:net').Socket & { readonly __role?: SocketRole, __clientId?: string, __extensionId?: string, __browserName?: string, __profileLabel?: string, __accessEnabled?: boolean, __lastActiveAt?: number }} ClientSocket */
|
|
56
|
+
/** @typedef {import('node:net').Socket & { readonly __role?: SocketRole, __clientId?: string, __extensionId?: string, __browserName?: string, __profileLabel?: string, __browserExtensionId?: string, __accessEnabled?: boolean, __lastActiveAt?: number }} ClientSocket */
|
|
57
57
|
/** @typedef {{ socket: ClientSocket, timeoutId: NodeJS.Timeout, source?: string, method?: string, protocolVersion?: string, targets: Set<ClientSocket>, lastErrorResponse?: import('../../protocol/src/types.js').BridgeResponse }} PendingEntry */
|
|
58
58
|
/**
|
|
59
59
|
* @typedef {{
|
|
@@ -85,6 +85,151 @@ function compareProtocolVersions(left, right) {
|
|
|
85
85
|
return 0;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
/**
|
|
89
|
+
* @param {unknown} value
|
|
90
|
+
* @returns {string | undefined}
|
|
91
|
+
*/
|
|
92
|
+
function normalizeBrowserExtensionId(value) {
|
|
93
|
+
return typeof value === 'string' && /^[a-p]{32}$/u.test(value) ? value : undefined;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const HEALTH_DIAGNOSTIC_COUNT_LIMIT = 10_000;
|
|
97
|
+
const HEALTH_ACCESS_REASONS = new Set([
|
|
98
|
+
'enabled',
|
|
99
|
+
'access_disabled',
|
|
100
|
+
'enabled_window_missing',
|
|
101
|
+
'no_routable_active_tab',
|
|
102
|
+
'restricted_page',
|
|
103
|
+
]);
|
|
104
|
+
const HEALTH_DEBUGGER_STATUSES = new Set(['idle', 'active']);
|
|
105
|
+
const HEALTH_DEBUGGER_REASONS = new Set([
|
|
106
|
+
'debugger_conflict',
|
|
107
|
+
'debugger_detached',
|
|
108
|
+
'debugger_replaced',
|
|
109
|
+
'debugger_canceled',
|
|
110
|
+
'target_closed',
|
|
111
|
+
]);
|
|
112
|
+
const HEALTH_CAPTURE_STATES = new Set(['stopped', 'armed', 'active', 'stop_failed']);
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @param {unknown} value
|
|
116
|
+
* @returns {Record<string, unknown> | null}
|
|
117
|
+
*/
|
|
118
|
+
function asHealthRecord(value) {
|
|
119
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
120
|
+
? /** @type {Record<string, unknown>} */ (value)
|
|
121
|
+
: null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @param {unknown} value
|
|
126
|
+
* @returns {number | undefined}
|
|
127
|
+
*/
|
|
128
|
+
function normalizeHealthCount(value) {
|
|
129
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0
|
|
130
|
+
? Math.min(HEALTH_DIAGNOSTIC_COUNT_LIMIT, Math.trunc(value))
|
|
131
|
+
: undefined;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Copy only extension-owned health fields into the daemon response. This keeps
|
|
136
|
+
* transport, connectivity, daemon identity, and daemon protocol negotiation
|
|
137
|
+
* authoritative even if an extension response is malformed or hostile.
|
|
138
|
+
*
|
|
139
|
+
* @param {unknown} value
|
|
140
|
+
* @returns {Record<string, unknown>}
|
|
141
|
+
*/
|
|
142
|
+
function normalizeExtensionHealthResult(value) {
|
|
143
|
+
const result = asHealthRecord(value);
|
|
144
|
+
if (!result) return {};
|
|
145
|
+
|
|
146
|
+
/** @type {Record<string, unknown>} */
|
|
147
|
+
const normalized = {};
|
|
148
|
+
if (result.extension === 'ok') normalized.extension = 'ok';
|
|
149
|
+
|
|
150
|
+
const access = asHealthRecord(result.access);
|
|
151
|
+
if (access) {
|
|
152
|
+
/** @type {Record<string, unknown>} */
|
|
153
|
+
const normalizedAccess = {};
|
|
154
|
+
if (typeof access.enabled === 'boolean') normalizedAccess.enabled = access.enabled;
|
|
155
|
+
if (
|
|
156
|
+
access.windowId === null ||
|
|
157
|
+
(Number.isSafeInteger(access.windowId) && Number(access.windowId) > 0)
|
|
158
|
+
) {
|
|
159
|
+
normalizedAccess.windowId = access.windowId;
|
|
160
|
+
}
|
|
161
|
+
if (
|
|
162
|
+
access.routeTabId === null ||
|
|
163
|
+
(Number.isSafeInteger(access.routeTabId) && Number(access.routeTabId) > 0)
|
|
164
|
+
) {
|
|
165
|
+
normalizedAccess.routeTabId = access.routeTabId;
|
|
166
|
+
}
|
|
167
|
+
if (typeof access.routeReady === 'boolean') normalizedAccess.routeReady = access.routeReady;
|
|
168
|
+
if (typeof access.reason === 'string' && HEALTH_ACCESS_REASONS.has(access.reason)) {
|
|
169
|
+
normalizedAccess.reason = access.reason;
|
|
170
|
+
}
|
|
171
|
+
normalized.access = normalizedAccess;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const debuggerDiagnostics = asHealthRecord(result.debugger);
|
|
175
|
+
if (debuggerDiagnostics) {
|
|
176
|
+
/** @type {Record<string, unknown>} */
|
|
177
|
+
const normalizedDebugger = {};
|
|
178
|
+
if (
|
|
179
|
+
typeof debuggerDiagnostics.status === 'string' &&
|
|
180
|
+
HEALTH_DEBUGGER_STATUSES.has(debuggerDiagnostics.status)
|
|
181
|
+
) {
|
|
182
|
+
normalizedDebugger.status = debuggerDiagnostics.status;
|
|
183
|
+
}
|
|
184
|
+
for (const key of ['attachedTabCount', 'heldTabCount', 'pendingTabCount']) {
|
|
185
|
+
const count = normalizeHealthCount(debuggerDiagnostics[key]);
|
|
186
|
+
if (count !== undefined) normalizedDebugger[key] = count;
|
|
187
|
+
}
|
|
188
|
+
if (
|
|
189
|
+
typeof debuggerDiagnostics.recentReason === 'string' &&
|
|
190
|
+
HEALTH_DEBUGGER_REASONS.has(debuggerDiagnostics.recentReason)
|
|
191
|
+
) {
|
|
192
|
+
normalizedDebugger.recentReason = debuggerDiagnostics.recentReason;
|
|
193
|
+
} else if (debuggerDiagnostics.recentReason === null) {
|
|
194
|
+
normalizedDebugger.recentReason = null;
|
|
195
|
+
}
|
|
196
|
+
normalized.debugger = normalizedDebugger;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const capture = asHealthRecord(result.capture);
|
|
200
|
+
if (capture) {
|
|
201
|
+
/** @type {Record<string, unknown>} */
|
|
202
|
+
const normalizedCapture = {};
|
|
203
|
+
if (typeof capture.state === 'string' && HEALTH_CAPTURE_STATES.has(capture.state)) {
|
|
204
|
+
normalizedCapture.state = capture.state;
|
|
205
|
+
}
|
|
206
|
+
for (const key of [
|
|
207
|
+
'activeTabCount',
|
|
208
|
+
'ownershipCount',
|
|
209
|
+
'inflightCount',
|
|
210
|
+
'interceptionActiveTabCount',
|
|
211
|
+
'interceptionRuleCount',
|
|
212
|
+
]) {
|
|
213
|
+
const count = normalizeHealthCount(capture[key]);
|
|
214
|
+
if (count !== undefined) normalizedCapture[key] = count;
|
|
215
|
+
}
|
|
216
|
+
normalized.capture = normalizedCapture;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const extensionSupportedVersions = Array.isArray(result.supported_versions)
|
|
220
|
+
? result.supported_versions
|
|
221
|
+
.filter(
|
|
222
|
+
(version) =>
|
|
223
|
+
typeof version === 'string' &&
|
|
224
|
+
version.length <= 32 &&
|
|
225
|
+
/^\d+\.\d+(?:\.\d+)?(?:[-+][A-Za-z0-9.-]+)?$/u.test(version)
|
|
226
|
+
)
|
|
227
|
+
.slice(0, 10)
|
|
228
|
+
: [];
|
|
229
|
+
normalized.extension_supported_versions = extensionSupportedVersions;
|
|
230
|
+
return normalized;
|
|
231
|
+
}
|
|
232
|
+
|
|
88
233
|
/**
|
|
89
234
|
* @param {string | undefined} requestedVersion
|
|
90
235
|
* @returns {{ supported_versions: readonly string[], deprecated_since?: string, migration_hint?: string }}
|
|
@@ -140,6 +285,7 @@ export function isWindowsNamedPipePath(socketPath) {
|
|
|
140
285
|
* response?: import('../../protocol/src/types.js').BridgeResponse,
|
|
141
286
|
* browserName?: string,
|
|
142
287
|
* profileLabel?: string,
|
|
288
|
+
* browserExtensionId?: string,
|
|
143
289
|
* accessEnabled?: boolean,
|
|
144
290
|
* authToken?: string,
|
|
145
291
|
* at?: number
|
|
@@ -192,7 +338,7 @@ export class BridgeDaemon {
|
|
|
192
338
|
this.pendingTimeoutMs = DEFAULT_DAEMON_PENDING_TIMEOUT_MS;
|
|
193
339
|
/** @type {Record<string, unknown>[]} */
|
|
194
340
|
this.recentLog = [];
|
|
195
|
-
/** @type {Array<{ extensionId: string, browserName: string | null, profileLabel: string | null, accessEnabled: boolean }> | null} */
|
|
341
|
+
/** @type {Array<{ extensionId: string, browserExtensionId: string | null, browserName: string | null, profileLabel: string | null, accessEnabled: boolean }> | null} */
|
|
196
342
|
this.connectedExtensionsCache = null;
|
|
197
343
|
/** @type {Promise<void> | null} */
|
|
198
344
|
this.stopPromise = null;
|
|
@@ -302,7 +448,7 @@ export class BridgeDaemon {
|
|
|
302
448
|
}
|
|
303
449
|
|
|
304
450
|
/**
|
|
305
|
-
* @returns {Array<{ extensionId: string, browserName: string | null, profileLabel: string | null, accessEnabled: boolean }>}
|
|
451
|
+
* @returns {Array<{ extensionId: string, browserExtensionId: string | null, browserName: string | null, profileLabel: string | null, accessEnabled: boolean }>}
|
|
306
452
|
*/
|
|
307
453
|
getConnectedExtensionsSnapshot() {
|
|
308
454
|
if (this.connectedExtensionsCache) {
|
|
@@ -312,6 +458,7 @@ export class BridgeDaemon {
|
|
|
312
458
|
this.connectedExtensionsCache = Array.from(this.extensionSockets.entries()).map(
|
|
313
459
|
([extensionId, extSocket]) => ({
|
|
314
460
|
extensionId,
|
|
461
|
+
browserExtensionId: extSocket.__browserExtensionId ?? null,
|
|
315
462
|
browserName: extSocket.__browserName ?? null,
|
|
316
463
|
profileLabel: extSocket.__profileLabel ?? null,
|
|
317
464
|
accessEnabled: extSocket.__accessEnabled ?? false,
|
|
@@ -373,6 +520,7 @@ export class BridgeDaemon {
|
|
|
373
520
|
typeof message.browserName === 'string' ? message.browserName : undefined;
|
|
374
521
|
socket.__profileLabel =
|
|
375
522
|
typeof message.profileLabel === 'string' ? message.profileLabel : undefined;
|
|
523
|
+
socket.__browserExtensionId = normalizeBrowserExtensionId(message.browserExtensionId);
|
|
376
524
|
socket.__lastActiveAt = Date.now();
|
|
377
525
|
this.extensionSockets.set(extensionId, socket);
|
|
378
526
|
this.invalidateConnectedExtensionsCache();
|
|
@@ -975,6 +1123,11 @@ export class BridgeDaemon {
|
|
|
975
1123
|
changed = changed || socket.__profileLabel !== message.profileLabel;
|
|
976
1124
|
socket.__profileLabel = message.profileLabel;
|
|
977
1125
|
}
|
|
1126
|
+
if (typeof message.browserExtensionId === 'string') {
|
|
1127
|
+
const browserExtensionId = normalizeBrowserExtensionId(message.browserExtensionId);
|
|
1128
|
+
changed = changed || socket.__browserExtensionId !== browserExtensionId;
|
|
1129
|
+
socket.__browserExtensionId = browserExtensionId;
|
|
1130
|
+
}
|
|
978
1131
|
if (changed) {
|
|
979
1132
|
this.invalidateConnectedExtensionsCache();
|
|
980
1133
|
}
|
|
@@ -1061,15 +1214,14 @@ export class BridgeDaemon {
|
|
|
1061
1214
|
socketPath: this.socketPath,
|
|
1062
1215
|
transport: formatBridgeTransport(this.transport),
|
|
1063
1216
|
connectedExtensions: this.getConnectedExtensionsSnapshot(),
|
|
1217
|
+
...normalizeExtensionHealthResult(responseMessage.result),
|
|
1064
1218
|
...getVersionNegotiationPayload(pending.protocolVersion),
|
|
1065
|
-
.../** @type {Record<string, unknown>} */ (responseMessage.result),
|
|
1066
1219
|
daemonVersion: DAEMON_VERSION,
|
|
1067
1220
|
daemon_supported_versions: getSupportedProtocolVersions(),
|
|
1068
1221
|
proxy: this.getProxyStatusPayload(),
|
|
1069
1222
|
},
|
|
1070
1223
|
{
|
|
1071
|
-
|
|
1072
|
-
method: responseMessage.meta?.method ?? pending.method,
|
|
1224
|
+
method: pending.method,
|
|
1073
1225
|
}
|
|
1074
1226
|
)
|
|
1075
1227
|
: responseMessage;
|
|
@@ -28,7 +28,8 @@ import { createNativeMessageReader, createNativeMessageWriter, writeJsonLine } f
|
|
|
28
28
|
* @typedef {{
|
|
29
29
|
* type?: string,
|
|
30
30
|
* browserName?: string,
|
|
31
|
-
* profileLabel?: string
|
|
31
|
+
* profileLabel?: string,
|
|
32
|
+
* browserExtensionId?: string
|
|
32
33
|
* }} HostIdentityMessage
|
|
33
34
|
*/
|
|
34
35
|
|
|
@@ -238,6 +239,7 @@ export async function runNativeHost({
|
|
|
238
239
|
type: 'extension.identity',
|
|
239
240
|
browserName: message.browserName,
|
|
240
241
|
profileLabel: message.profileLabel,
|
|
242
|
+
browserExtensionId: message.browserExtensionId,
|
|
241
243
|
});
|
|
242
244
|
return;
|
|
243
245
|
}
|
|
@@ -11,6 +11,9 @@ export const DEFAULT_MAX_NODES = 25;
|
|
|
11
11
|
export const DEFAULT_MAX_DEPTH = 4;
|
|
12
12
|
export const DEFAULT_TEXT_BUDGET = 600;
|
|
13
13
|
export const DEFAULT_PAGE_TEXT_BUDGET = 8000;
|
|
14
|
+
export const DEFAULT_EXTRACT_SETTLE_TIMEOUT_MS = 2000;
|
|
15
|
+
export const MAX_EXTRACT_SETTLE_TIMEOUT_MS = 10_000;
|
|
16
|
+
export const EXTRACT_SETTLE_QUIET_MS = 100;
|
|
14
17
|
export const DEFAULT_WAIT_TIMEOUT_MS = 5000;
|
|
15
18
|
export const DEFAULT_EVAL_TIMEOUT_MS = 5000;
|
|
16
19
|
export const DEFAULT_NAV_TIMEOUT_MS = 15000;
|
|
@@ -6,6 +6,15 @@ export const ERROR_CODES = Object.freeze({
|
|
|
6
6
|
ACCESS_DENIED: 'ACCESS_DENIED',
|
|
7
7
|
TAB_MISMATCH: 'TAB_MISMATCH',
|
|
8
8
|
ELEMENT_STALE: 'ELEMENT_STALE',
|
|
9
|
+
ELEMENT_AMBIGUOUS: 'ELEMENT_AMBIGUOUS',
|
|
10
|
+
ELEMENT_NOT_ACTIONABLE: 'ELEMENT_NOT_ACTIONABLE',
|
|
11
|
+
ELEMENT_OBSCURED: 'ELEMENT_OBSCURED',
|
|
12
|
+
ELEMENT_NOT_FOUND: 'ELEMENT_NOT_FOUND',
|
|
13
|
+
INPUT_UNSUPPORTED: 'INPUT_UNSUPPORTED',
|
|
14
|
+
INPUT_INVALID_TARGET: 'INPUT_INVALID_TARGET',
|
|
15
|
+
INPUT_FOCUS_CHANGED: 'INPUT_FOCUS_CHANGED',
|
|
16
|
+
DIALOG_NOT_OPEN: 'DIALOG_NOT_OPEN',
|
|
17
|
+
DIALOG_ACTION_CONFLICT: 'DIALOG_ACTION_CONFLICT',
|
|
9
18
|
RESULT_TRUNCATED: 'RESULT_TRUNCATED',
|
|
10
19
|
RATE_LIMITED: 'RATE_LIMITED',
|
|
11
20
|
INTERNAL_ERROR: 'INTERNAL_ERROR',
|
|
@@ -32,7 +41,51 @@ export const ERROR_RECOVERY = Object.freeze({
|
|
|
32
41
|
[ERROR_CODES.ELEMENT_STALE]: {
|
|
33
42
|
retry: false,
|
|
34
43
|
alternativeMethod: 'dom.query',
|
|
35
|
-
hint: 'Element was removed from the DOM. Re-query with the same
|
|
44
|
+
hint: 'Element was removed from the DOM. Re-query for a fresh elementRef, or retry an input once with recoverStale=true when the same page and a strong unique descriptor still apply.',
|
|
45
|
+
},
|
|
46
|
+
[ERROR_CODES.ELEMENT_AMBIGUOUS]: {
|
|
47
|
+
retry: false,
|
|
48
|
+
alternativeMethod: 'dom.query',
|
|
49
|
+
hint: 'Multiple candidates matched, candidates tied, or a bounded stale-recovery scan could not prove uniqueness. Re-query and use a more specific selector or fresh elementRef.',
|
|
50
|
+
},
|
|
51
|
+
[ERROR_CODES.ELEMENT_NOT_ACTIONABLE]: {
|
|
52
|
+
retry: false,
|
|
53
|
+
alternativeMethod: 'dom.describe',
|
|
54
|
+
hint: 'The target is hidden, disabled, inert, or has no rendered area. Inspect it and choose an actionable target.',
|
|
55
|
+
},
|
|
56
|
+
[ERROR_CODES.ELEMENT_OBSCURED]: {
|
|
57
|
+
retry: false,
|
|
58
|
+
alternativeMethod: 'layout.hit_test',
|
|
59
|
+
hint: 'Another element blocks the target point. Inspect the blocker or wait for the overlay to close.',
|
|
60
|
+
},
|
|
61
|
+
[ERROR_CODES.ELEMENT_NOT_FOUND]: {
|
|
62
|
+
retry: false,
|
|
63
|
+
alternativeMethod: 'dom.query',
|
|
64
|
+
hint: 'No element matched the target selector. Check or narrow the selector with dom.query.',
|
|
65
|
+
},
|
|
66
|
+
[ERROR_CODES.INPUT_UNSUPPORTED]: {
|
|
67
|
+
retry: false,
|
|
68
|
+
hint: 'The requested execution mode does not support this input operation. Use executionMode=dom or choose a CDP-supported input method.',
|
|
69
|
+
},
|
|
70
|
+
[ERROR_CODES.INPUT_INVALID_TARGET]: {
|
|
71
|
+
retry: false,
|
|
72
|
+
alternativeMethod: 'dom.describe',
|
|
73
|
+
hint: 'The target is not compatible with this input operation or the requested option does not exist. Inspect the target and choose the appropriate control.',
|
|
74
|
+
},
|
|
75
|
+
[ERROR_CODES.INPUT_FOCUS_CHANGED]: {
|
|
76
|
+
retry: false,
|
|
77
|
+
alternativeMethod: 'dom.describe',
|
|
78
|
+
hint: 'Focus moved away from the resolved editable target before native text dispatch. Inspect focus handlers or target the control that retained focus.',
|
|
79
|
+
},
|
|
80
|
+
[ERROR_CODES.DIALOG_NOT_OPEN]: {
|
|
81
|
+
retry: false,
|
|
82
|
+
alternativeMethod: 'page.handle_dialog',
|
|
83
|
+
hint: 'No JavaScript dialog is currently observable. Inspect or trigger the dialog before trying another explicit action.',
|
|
84
|
+
},
|
|
85
|
+
[ERROR_CODES.DIALOG_ACTION_CONFLICT]: {
|
|
86
|
+
retry: false,
|
|
87
|
+
alternativeMethod: 'page.handle_dialog',
|
|
88
|
+
hint: 'The observed dialog changed before or during CDP dispatch. Do not repeat the action automatically; inspect the current dialog state first.',
|
|
36
89
|
},
|
|
37
90
|
[ERROR_CODES.TAB_MISMATCH]: {
|
|
38
91
|
retry: false,
|