@btraut/browser-bridge 0.13.2 → 0.15.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/dist/api.js CHANGED
@@ -51,14 +51,11 @@ var import_node_path2 = require("node:path");
51
51
  var import_node_fs = require("node:fs");
52
52
  var import_node_path = require("node:path");
53
53
  var DEFAULT_HOST = "127.0.0.1";
54
- var LEGACY_DEFAULT_PORT = 3210;
55
- var DETERMINISTIC_PORT_WINDOW = 2e3;
54
+ var DEFAULT_PORT = 3210;
56
55
  var ENV_CORE_HOST = "BROWSER_BRIDGE_CORE_HOST";
57
56
  var ENV_VISION_HOST = "BROWSER_VISION_CORE_HOST";
58
57
  var ENV_CORE_PORT = "BROWSER_BRIDGE_CORE_PORT";
59
58
  var ENV_VISION_PORT = "BROWSER_VISION_CORE_PORT";
60
- var ENV_ISOLATED_MODE = "BROWSER_BRIDGE_ISOLATED_MODE";
61
- var ENV_VISION_ISOLATED_MODE = "BROWSER_VISION_ISOLATED_MODE";
62
59
  var ENV_BRIDGE_CWD = "BROWSER_BRIDGE_CWD";
63
60
  var ENV_PROCESS_PWD = "PWD";
64
61
  var ENV_PROCESS_INIT_CWD = "INIT_CWD";
@@ -115,22 +112,6 @@ var normalizeHost = (value) => {
115
112
  const trimmed = value.trim();
116
113
  return trimmed.length > 0 ? trimmed : void 0;
117
114
  };
118
- var parseBoolean = (value) => {
119
- if (typeof value === "boolean") {
120
- return value;
121
- }
122
- if (typeof value !== "string") {
123
- return void 0;
124
- }
125
- const normalized = value.trim().toLowerCase();
126
- if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
127
- return true;
128
- }
129
- if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
130
- return false;
131
- }
132
- return void 0;
133
- };
134
115
  var parsePort = (value, label, invalidPolicy) => {
135
116
  if (value === void 0 || value === null) {
136
117
  return void 0;
@@ -147,60 +128,6 @@ var parsePort = (value, label, invalidPolicy) => {
147
128
  }
148
129
  return void 0;
149
130
  };
150
- var hashString = (value) => {
151
- let hash = 2166136261;
152
- for (let index = 0; index < value.length; index += 1) {
153
- hash ^= value.charCodeAt(index);
154
- hash = Math.imul(hash, 16777619);
155
- }
156
- return hash >>> 0;
157
- };
158
- var normalizePathForHash = (value) => value.replace(/\\/g, "/").toLowerCase();
159
- var sanitizeToken = (value) => value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
160
- var fallbackWorktreeId = (gitRoot) => {
161
- const hash = hashString(normalizePathForHash(gitRoot)).toString(16).padStart(8, "0");
162
- return `wt-${hash}`;
163
- };
164
- var extractWorktreeIdFromGitDir = (gitDir) => {
165
- const normalized = gitDir.replace(/\\/g, "/");
166
- const marker = "/.git/worktrees/";
167
- const markerIndex = normalized.lastIndexOf(marker);
168
- if (markerIndex < 0) {
169
- return null;
170
- }
171
- const remainder = normalized.slice(markerIndex + marker.length);
172
- const rawId = remainder.split("/")[0];
173
- if (!rawId) {
174
- return null;
175
- }
176
- const sanitized = sanitizeToken(rawId);
177
- return sanitized.length > 0 ? sanitized : null;
178
- };
179
- var readWorktreeGitDir = (gitRoot) => {
180
- const gitPath = (0, import_node_path.join)(gitRoot, ".git");
181
- try {
182
- const stats = (0, import_node_fs.statSync)(gitPath);
183
- if (stats.isDirectory()) {
184
- return gitPath;
185
- }
186
- if (!stats.isFile()) {
187
- return null;
188
- }
189
- } catch {
190
- return null;
191
- }
192
- try {
193
- const raw = (0, import_node_fs.readFileSync)(gitPath, "utf8");
194
- const match = raw.match(/^gitdir:\s*(.+)$/m);
195
- if (!match?.[1]) {
196
- return null;
197
- }
198
- const candidate = match[1].trim();
199
- return (0, import_node_path.isAbsolute)(candidate) ? candidate : (0, import_node_path.resolve)(gitRoot, candidate);
200
- } catch {
201
- return null;
202
- }
203
- };
204
131
  var resolveEnvHost = (env) => {
205
132
  const bridgeHost = normalizeHost(env[ENV_CORE_HOST]);
206
133
  if (bridgeHost) {
@@ -214,35 +141,18 @@ var resolveEnvPortRaw = (env) => {
214
141
  }
215
142
  return env[ENV_VISION_PORT];
216
143
  };
217
- var resolveEnvIsolatedMode = (env) => {
218
- const bridge = parseBoolean(env[ENV_ISOLATED_MODE]);
219
- if (bridge !== void 0) {
220
- return bridge;
221
- }
222
- return parseBoolean(env[ENV_VISION_ISOLATED_MODE]);
223
- };
224
144
  var sanitizeMetadata = (raw) => {
225
145
  if (!raw || typeof raw !== "object") {
226
146
  return null;
227
147
  }
228
148
  const candidate = raw;
229
- const host = normalizeHost(candidate.host);
230
- const port = parsePort(candidate.port, "port", "ignore");
231
- const gitRoot = normalizeHost(candidate.git_root);
232
- const worktreeId = normalizeHost(candidate.worktree_id);
233
149
  const extensionId = normalizeHost(candidate.extension_id);
234
- const isolatedMode = parseBoolean(candidate.isolated_mode);
235
150
  const updatedAt = normalizeHost(candidate.updated_at);
236
- if (!host && port === void 0 && !gitRoot && !worktreeId && !extensionId && isolatedMode === void 0 && !updatedAt) {
151
+ if (!extensionId && !updatedAt) {
237
152
  return null;
238
153
  }
239
154
  return {
240
- host,
241
- port,
242
- git_root: gitRoot,
243
- worktree_id: worktreeId,
244
155
  extension_id: extensionId,
245
- isolated_mode: isolatedMode,
246
156
  updated_at: updatedAt
247
157
  };
248
158
  };
@@ -259,32 +169,6 @@ var findGitRoot = (cwd) => {
259
169
  current = parent;
260
170
  }
261
171
  };
262
- var resolveWorktreeId = ({
263
- cwd,
264
- gitRoot
265
- } = {}) => {
266
- const resolvedGitRoot = gitRoot ?? findGitRoot(resolveCwd(cwd));
267
- if (!resolvedGitRoot) {
268
- return null;
269
- }
270
- const gitDir = readWorktreeGitDir(resolvedGitRoot);
271
- const parsedId = gitDir ? extractWorktreeIdFromGitDir(gitDir) : null;
272
- if (parsedId) {
273
- return parsedId;
274
- }
275
- return fallbackWorktreeId(resolvedGitRoot);
276
- };
277
- var resolveDeterministicCorePort = ({
278
- cwd,
279
- gitRoot
280
- } = {}) => {
281
- const resolvedGitRoot = gitRoot ?? findGitRoot(resolveCwd(cwd));
282
- if (!resolvedGitRoot) {
283
- return LEGACY_DEFAULT_PORT;
284
- }
285
- const seed = normalizePathForHash(resolvedGitRoot);
286
- return LEGACY_DEFAULT_PORT + hashString(seed) % DETERMINISTIC_PORT_WINDOW;
287
- };
288
172
  var resolveRuntimeMetadataPath = ({
289
173
  cwd,
290
174
  gitRoot,
@@ -342,26 +226,6 @@ var writeRuntimeMetadata = (metadata, {
342
226
  `, "utf8");
343
227
  return path3;
344
228
  };
345
- var createBoundedPortProbeSequence = (startPort, maxAttempts = 20, maxPort = 65535) => {
346
- const normalizedStart = parsePort(startPort, "startPort", "throw");
347
- if (normalizedStart === void 0) {
348
- throw new Error(`Invalid startPort: ${String(startPort)}`);
349
- }
350
- const attempts = Math.max(1, Math.floor(maxAttempts));
351
- const ceiling = Math.min(65535, Math.max(1, Math.floor(maxPort)));
352
- if (normalizedStart > ceiling) {
353
- throw new Error(`startPort ${normalizedStart} exceeds maxPort ${ceiling}.`);
354
- }
355
- const sequence = [];
356
- for (let offset = 0; offset < attempts; offset += 1) {
357
- const port = normalizedStart + offset;
358
- if (port > ceiling) {
359
- break;
360
- }
361
- sequence.push(port);
362
- }
363
- return sequence;
364
- };
365
229
  var resolveCoreRuntime = (options = {}) => {
366
230
  const env = options.env ?? process.env;
367
231
  const resolvedCwd = resolveCwd(options.cwd);
@@ -372,45 +236,18 @@ var resolveCoreRuntime = (options = {}) => {
372
236
  metadataPath: options.metadataPath
373
237
  });
374
238
  const metadata = options.metadata === void 0 ? readRuntimeMetadata({ metadataPath }) : sanitizeMetadata(options.metadata);
375
- const deterministicPort = resolveDeterministicCorePort({
376
- cwd: resolvedCwd,
377
- gitRoot
378
- });
379
239
  const optionHost = normalizeHost(options.host);
380
240
  const envHost = resolveEnvHost(env);
381
- const metadataHost = normalizeHost(metadata?.host);
382
- const host = optionHost ?? envHost ?? metadataHost ?? DEFAULT_HOST;
383
- const hostSource = optionHost ? "option" : envHost ? "env" : metadataHost ? "metadata" : "default";
241
+ const host = optionHost ?? envHost ?? DEFAULT_HOST;
242
+ const hostSource = optionHost ? "option" : envHost ? "env" : "default";
384
243
  const optionPort = parsePort(options.port, "port", "throw");
385
244
  const envPort = parsePort(
386
245
  resolveEnvPortRaw(env),
387
246
  "port",
388
247
  options.strictEnvPort ? "throw" : "ignore"
389
248
  );
390
- const metadataPort = parsePort(metadata?.port, "port", "ignore");
391
- const optionIsolatedMode = options.isolatedMode;
392
- const envIsolatedMode = resolveEnvIsolatedMode(env);
393
- const metadataIsolatedMode = metadata?.isolated_mode;
394
- const isolatedMode = optionIsolatedMode ?? envIsolatedMode ?? metadataIsolatedMode ?? false;
395
- const isolatedModeSource = optionIsolatedMode !== void 0 ? "option" : envIsolatedMode !== void 0 ? "env" : metadataIsolatedMode !== void 0 ? "metadata" : "default";
396
- let port;
397
- let portSource;
398
- if (optionPort !== void 0) {
399
- port = optionPort;
400
- portSource = "option";
401
- } else if (envPort !== void 0) {
402
- port = envPort;
403
- portSource = "env";
404
- } else if (metadataPort !== void 0 && isolatedMode) {
405
- port = metadataPort;
406
- portSource = "metadata";
407
- } else if (isolatedMode) {
408
- port = deterministicPort;
409
- portSource = "deterministic";
410
- } else {
411
- port = LEGACY_DEFAULT_PORT;
412
- portSource = "default";
413
- }
249
+ const port = optionPort ?? envPort ?? DEFAULT_PORT;
250
+ const portSource = optionPort !== void 0 ? "option" : envPort !== void 0 ? "env" : "default";
414
251
  return {
415
252
  host,
416
253
  port,
@@ -418,11 +255,7 @@ var resolveCoreRuntime = (options = {}) => {
418
255
  portSource,
419
256
  metadataPath,
420
257
  metadata,
421
- gitRoot,
422
- worktreeId: resolveWorktreeId({ cwd: resolvedCwd, gitRoot }),
423
- deterministicPort,
424
- isolatedMode,
425
- isolatedModeSource
258
+ gitRoot
426
259
  };
427
260
  };
428
261
 
@@ -841,7 +674,7 @@ var createCoreReadinessController = (options = {}) => {
841
674
  }
842
675
  if (portOccupied) {
843
676
  throw new Error(
844
- `Core daemon failed to start on ${runtime.host}:${runtime.port}. A process is already listening on this port but did not pass Browser Bridge health checks. Retry with --no-daemon to reuse it, or enable isolated mode (BROWSER_BRIDGE_ISOLATED_MODE=1) for per-worktree ports.`
677
+ `Core daemon failed to start on ${runtime.host}:${runtime.port}. A process is already listening on this port but did not pass Browser Bridge health checks. Retry with --no-daemon to reuse the existing process or stop whatever is already bound to that port.`
845
678
  );
846
679
  }
847
680
  throw new Error(
@@ -854,9 +687,10 @@ var createCoreReadinessController = (options = {}) => {
854
687
  return;
855
688
  }
856
689
  if (!ensurePromise) {
857
- ensurePromise = ensureCoreRunning().catch((error) => {
690
+ ensurePromise = (async () => {
691
+ await ensureCoreRunning();
692
+ })().finally(() => {
858
693
  ensurePromise = null;
859
- throw error;
860
694
  });
861
695
  }
862
696
  await ensurePromise;
@@ -1169,9 +1003,7 @@ var DiagnosticsRuntimeEndpointSchema = import_zod3.z.object({
1169
1003
  port: import_zod3.z.number().finite().optional(),
1170
1004
  base_url: import_zod3.z.string().optional(),
1171
1005
  host_source: import_zod3.z.string().optional(),
1172
- port_source: import_zod3.z.string().optional(),
1173
- metadata_path: import_zod3.z.string().optional(),
1174
- isolated_mode: import_zod3.z.boolean().optional()
1006
+ port_source: import_zod3.z.string().optional()
1175
1007
  });
1176
1008
  var DiagnosticsRuntimeProcessSchema = import_zod3.z.object({
1177
1009
  component: import_zod3.z.enum(["cli", "mcp", "core"]).optional(),
@@ -1192,12 +1024,13 @@ var DiagnosticsRuntimeContextSchema = import_zod3.z.object({
1192
1024
  process: DiagnosticsRuntimeProcessSchema.optional()
1193
1025
  }).optional(),
1194
1026
  extension: import_zod3.z.object({
1027
+ extension_id: import_zod3.z.string().optional(),
1195
1028
  version: import_zod3.z.string().optional(),
1196
1029
  protocol_version: import_zod3.z.string().optional(),
1197
1030
  capability_negotiated: import_zod3.z.boolean().optional(),
1198
1031
  capabilities: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.boolean()).optional(),
1199
1032
  endpoint: DiagnosticsRuntimeEndpointSchema.optional(),
1200
- port_source: import_zod3.z.enum(["default", "storage"]).optional()
1033
+ port_source: import_zod3.z.enum(["default"]).optional()
1201
1034
  }).optional()
1202
1035
  });
1203
1036
  var DiagnosticReportSchema = import_zod3.z.object({
@@ -1264,6 +1097,63 @@ var SessionCloseInputSchema = SessionIdSchema;
1264
1097
  var SessionCloseOutputSchema = import_zod3.z.object({
1265
1098
  ok: import_zod3.z.boolean()
1266
1099
  });
1100
+ var PermissionsModeSchema = import_zod3.z.enum(["granular", "bypass"]);
1101
+ var PermissionsRequestSourceSchema = import_zod3.z.enum(["cli", "mcp", "api"]);
1102
+ var PermissionsPendingRequestKindSchema = import_zod3.z.enum([
1103
+ "allow_site",
1104
+ "revoke_site",
1105
+ "set_mode"
1106
+ ]);
1107
+ var PermissionsPendingRequestStatusSchema = import_zod3.z.enum([
1108
+ "pending",
1109
+ "approved",
1110
+ "denied",
1111
+ "timed_out"
1112
+ ]);
1113
+ var PermissionsSiteEntrySchema = import_zod3.z.object({
1114
+ site: import_zod3.z.string().min(1),
1115
+ created_at: import_zod3.z.string().datetime(),
1116
+ last_used_at: import_zod3.z.string().datetime()
1117
+ });
1118
+ var PermissionsListInputSchema = import_zod3.z.object({}).strict().default({});
1119
+ var PermissionsListOutputSchema = import_zod3.z.object({
1120
+ sites: import_zod3.z.array(PermissionsSiteEntrySchema)
1121
+ });
1122
+ var PermissionsGetModeInputSchema = import_zod3.z.object({}).strict().default({});
1123
+ var PermissionsGetModeOutputSchema = import_zod3.z.object({
1124
+ mode: PermissionsModeSchema
1125
+ });
1126
+ var PermissionsPendingRequestSchema = import_zod3.z.object({
1127
+ request_id: import_zod3.z.string().min(1),
1128
+ kind: PermissionsPendingRequestKindSchema,
1129
+ status: PermissionsPendingRequestStatusSchema,
1130
+ requested_at: import_zod3.z.string().datetime(),
1131
+ site: import_zod3.z.string().min(1).optional(),
1132
+ mode: PermissionsModeSchema.optional(),
1133
+ source: PermissionsRequestSourceSchema.optional(),
1134
+ warning: import_zod3.z.string().optional(),
1135
+ message: import_zod3.z.string().optional()
1136
+ });
1137
+ var PermissionsListPendingRequestsInputSchema = import_zod3.z.object({}).strict().default({});
1138
+ var PermissionsListPendingRequestsOutputSchema = import_zod3.z.object({
1139
+ requests: import_zod3.z.array(PermissionsPendingRequestSchema)
1140
+ });
1141
+ var PermissionsRequestAllowSiteInputSchema = import_zod3.z.object({
1142
+ site: import_zod3.z.string().min(1),
1143
+ timeout_ms: import_zod3.z.number().int().positive().max(3e5).optional(),
1144
+ source: PermissionsRequestSourceSchema.optional()
1145
+ });
1146
+ var PermissionsRequestRevokeSiteInputSchema = import_zod3.z.object({
1147
+ site: import_zod3.z.string().min(1),
1148
+ timeout_ms: import_zod3.z.number().int().positive().max(3e5).optional(),
1149
+ source: PermissionsRequestSourceSchema.optional()
1150
+ });
1151
+ var PermissionsRequestSetModeInputSchema = import_zod3.z.object({
1152
+ mode: PermissionsModeSchema,
1153
+ timeout_ms: import_zod3.z.number().int().positive().max(3e5).optional(),
1154
+ source: PermissionsRequestSourceSchema.optional()
1155
+ });
1156
+ var PermissionsRequestOutputSchema = PermissionsPendingRequestSchema;
1267
1157
  var DriveWaitConditionSchema = import_zod3.z.object({
1268
1158
  kind: import_zod3.z.enum(["locator_visible", "text_present", "url_matches"]),
1269
1159
  value: import_zod3.z.string().min(1)
@@ -1272,7 +1162,7 @@ var DriveNavigateInputSchema = import_zod3.z.object({
1272
1162
  session_id: import_zod3.z.string().min(1).optional(),
1273
1163
  url: import_zod3.z.string().min(1),
1274
1164
  tab_id: import_zod3.z.number().finite().optional(),
1275
- wait: import_zod3.z.enum(["none", "domcontentloaded"]).default("domcontentloaded")
1165
+ wait: import_zod3.z.enum(["none", "domcontentloaded", "networkidle"]).default("domcontentloaded")
1276
1166
  });
1277
1167
  var DriveNavigateOutputSchema = OpResultSchema.extend({
1278
1168
  session_id: import_zod3.z.string().min(1)
@@ -1441,6 +1331,8 @@ var InspectConsistencySchema = import_zod3.z.enum(["best_effort", "quiesce"]);
1441
1331
  var TargetHintSchema = import_zod3.z.object({
1442
1332
  url: import_zod3.z.string().min(1).optional(),
1443
1333
  title: import_zod3.z.string().min(1).optional(),
1334
+ tab_id: import_zod3.z.number().finite().optional(),
1335
+ tabId: import_zod3.z.number().finite().optional(),
1444
1336
  last_active_at: import_zod3.z.string().optional(),
1445
1337
  lastActiveAt: import_zod3.z.string().optional()
1446
1338
  });
@@ -1448,6 +1340,12 @@ var FormFieldInfoSchema = import_zod3.z.object({
1448
1340
  name: import_zod3.z.string(),
1449
1341
  type: import_zod3.z.string(),
1450
1342
  value: import_zod3.z.string(),
1343
+ selector: import_zod3.z.string().optional(),
1344
+ label: import_zod3.z.string().optional(),
1345
+ placeholder: import_zod3.z.string().optional(),
1346
+ checked: import_zod3.z.boolean().optional(),
1347
+ disabled: import_zod3.z.boolean().optional(),
1348
+ visible: import_zod3.z.boolean().optional(),
1451
1349
  options: import_zod3.z.array(import_zod3.z.string()).optional()
1452
1350
  });
1453
1351
  var FormInfoSchema = import_zod3.z.object({
@@ -1460,7 +1358,31 @@ var StorageEntrySchema = import_zod3.z.object({
1460
1358
  key: import_zod3.z.string(),
1461
1359
  value: import_zod3.z.string()
1462
1360
  });
1361
+ var FocusedElementSchema = import_zod3.z.object({
1362
+ selector: import_zod3.z.string().optional(),
1363
+ name: import_zod3.z.string().optional(),
1364
+ label: import_zod3.z.string().optional(),
1365
+ role: import_zod3.z.string().optional(),
1366
+ type: import_zod3.z.string().optional(),
1367
+ text: import_zod3.z.string().optional()
1368
+ });
1369
+ var PageActionSchema = import_zod3.z.object({
1370
+ selector: import_zod3.z.string(),
1371
+ role: import_zod3.z.string(),
1372
+ name: import_zod3.z.string()
1373
+ });
1374
+ var StorageSummarySchema = import_zod3.z.object({
1375
+ localStorageCount: import_zod3.z.number().int().nonnegative(),
1376
+ sessionStorageCount: import_zod3.z.number().int().nonnegative(),
1377
+ cookieCount: import_zod3.z.number().int().nonnegative()
1378
+ });
1463
1379
  var PageStateSchema = import_zod3.z.object({
1380
+ url: import_zod3.z.string().optional(),
1381
+ title: import_zod3.z.string().optional(),
1382
+ readyState: import_zod3.z.string().optional(),
1383
+ focused: FocusedElementSchema.optional(),
1384
+ primaryActions: import_zod3.z.array(PageActionSchema).optional(),
1385
+ storageSummary: StorageSummarySchema.optional(),
1464
1386
  forms: import_zod3.z.array(FormInfoSchema),
1465
1387
  localStorage: import_zod3.z.array(StorageEntrySchema),
1466
1388
  sessionStorage: import_zod3.z.array(StorageEntrySchema),
@@ -1523,6 +1445,7 @@ var InspectFindOutputSchema = import_zod3.z.object({
1523
1445
  warnings: import_zod3.z.array(import_zod3.z.string()).optional()
1524
1446
  });
1525
1447
  var InspectPageStateInputSchema = SessionIdSchema.extend({
1448
+ include_values: import_zod3.z.boolean().default(false),
1526
1449
  target: TargetHintSchema.optional()
1527
1450
  });
1528
1451
  var InspectPageStateOutputSchema = PageStateSchema;
@@ -1533,6 +1456,7 @@ var InspectExtractContentFormatSchema = import_zod3.z.enum([
1533
1456
  ]);
1534
1457
  var InspectExtractContentInputSchema = SessionIdSchema.extend({
1535
1458
  format: InspectExtractContentFormatSchema.default("markdown"),
1459
+ consistency: InspectConsistencySchema.default("quiesce"),
1536
1460
  include_metadata: import_zod3.z.boolean().default(true),
1537
1461
  target: TargetHintSchema.optional()
1538
1462
  });
@@ -1545,6 +1469,7 @@ var InspectExtractContentOutputSchema = import_zod3.z.object({
1545
1469
  warnings: import_zod3.z.array(import_zod3.z.string()).optional()
1546
1470
  });
1547
1471
  var InspectConsoleListInputSchema = SessionIdSchema.extend({
1472
+ since: import_zod3.z.string().optional(),
1548
1473
  target: TargetHintSchema.optional()
1549
1474
  });
1550
1475
  var ConsoleSourceLocationSchema = import_zod3.z.object({
@@ -1635,6 +1560,7 @@ var HealthCheckOutputSchema = import_zod3.z.object({
1635
1560
  }).passthrough(),
1636
1561
  extension: import_zod3.z.object({
1637
1562
  connected: import_zod3.z.boolean(),
1563
+ extension_id: import_zod3.z.string().optional(),
1638
1564
  last_seen_at: import_zod3.z.string().min(1).optional()
1639
1565
  }).passthrough()
1640
1566
  }).passthrough();
@@ -1744,7 +1670,8 @@ var SessionRegistry = class {
1744
1670
  state: "INIT" /* INIT */,
1745
1671
  createdAt: now,
1746
1672
  updatedAt: now,
1747
- lastAccessedAt: now
1673
+ lastAccessedAt: now,
1674
+ selectedTabId: void 0
1748
1675
  };
1749
1676
  this.sessions.set(id, session);
1750
1677
  return session;
@@ -1843,6 +1770,18 @@ var SessionRegistry = class {
1843
1770
  session.updatedAt = /* @__PURE__ */ new Date();
1844
1771
  return session;
1845
1772
  }
1773
+ setSelectedTab(sessionId, tabId) {
1774
+ const session = this.require(sessionId);
1775
+ session.selectedTabId = tabId;
1776
+ session.updatedAt = /* @__PURE__ */ new Date();
1777
+ return session;
1778
+ }
1779
+ clearSelectedTab(sessionId) {
1780
+ const session = this.require(sessionId);
1781
+ session.selectedTabId = void 0;
1782
+ session.updatedAt = /* @__PURE__ */ new Date();
1783
+ return session;
1784
+ }
1846
1785
  };
1847
1786
 
1848
1787
  // packages/core/src/routes/shared.ts
@@ -1883,13 +1822,14 @@ var deriveHintFromTabs = (tabs) => {
1883
1822
  if (!Array.isArray(tabs) || tabs.length === 0) {
1884
1823
  return void 0;
1885
1824
  }
1825
+ const candidatePool = tabs.some((tab) => tab.active === true) ? tabs.filter((tab) => tab.active === true) : tabs;
1886
1826
  let best;
1887
1827
  let bestTime = -Infinity;
1888
- for (const tab of tabs) {
1828
+ for (const tab of candidatePool) {
1889
1829
  const raw = tab.last_active_at;
1890
1830
  const time = raw ? Date.parse(raw) : NaN;
1891
1831
  const score = Number.isFinite(time) ? time : -Infinity;
1892
- if (!best || score > bestTime) {
1832
+ if (!best || score > bestTime || score === bestTime && tab.tab_id < best.tab_id) {
1893
1833
  best = tab;
1894
1834
  bestTime = score;
1895
1835
  }
@@ -1965,7 +1905,8 @@ var createSessionRouter = (registry, options = {}) => {
1965
1905
  return sendResult(res, {
1966
1906
  session_id: session.id,
1967
1907
  state: session.state,
1968
- updated_at: session.updatedAt.toISOString()
1908
+ updated_at: session.updatedAt.toISOString(),
1909
+ ...typeof session.selectedTabId === "number" ? { selected_tab_id: session.selectedTabId } : {}
1969
1910
  });
1970
1911
  } catch (error) {
1971
1912
  if (error instanceof SessionError) {
@@ -2103,9 +2044,6 @@ var InspectError = class extends Error {
2103
2044
  var import_crypto3 = require("crypto");
2104
2045
  var import_promises3 = require("node:fs/promises");
2105
2046
  var import_node_path4 = __toESM(require("node:path"));
2106
- var import_readability = require("@mozilla/readability");
2107
- var import_jsdom = require("jsdom");
2108
- var import_turndown = __toESM(require("turndown"));
2109
2047
 
2110
2048
  // packages/core/src/artifacts.ts
2111
2049
  var import_promises2 = require("node:fs/promises");
@@ -2136,6 +2074,7 @@ var ExtensionBridge = class {
2136
2074
  constructor(options = {}) {
2137
2075
  this.socket = null;
2138
2076
  this.pending = /* @__PURE__ */ new Map();
2077
+ this.readyWaiters = /* @__PURE__ */ new Set();
2139
2078
  this.connected = false;
2140
2079
  this.capabilityNegotiated = false;
2141
2080
  this.capabilities = {};
@@ -2172,6 +2111,7 @@ var ExtensionBridge = class {
2172
2111
  return {
2173
2112
  connected: this.connected,
2174
2113
  lastSeenAt: this.lastSeenAt,
2114
+ extensionId: this.extensionId,
2175
2115
  version: this.version,
2176
2116
  protocolVersion: this.protocolVersion,
2177
2117
  protocolMismatch: this.protocolMismatch,
@@ -2183,6 +2123,36 @@ var ExtensionBridge = class {
2183
2123
  tabs: this.tabs
2184
2124
  };
2185
2125
  }
2126
+ async waitForReady(timeoutMs = 1500) {
2127
+ if (this.isReadyForRequests()) {
2128
+ return true;
2129
+ }
2130
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
2131
+ return this.isReadyForRequests();
2132
+ }
2133
+ return await new Promise((resolve3) => {
2134
+ let settled = false;
2135
+ const finish = (value) => {
2136
+ if (settled) {
2137
+ return;
2138
+ }
2139
+ settled = true;
2140
+ clearTimeout(timeout);
2141
+ this.readyWaiters.delete(waiter);
2142
+ resolve3(value);
2143
+ };
2144
+ const waiter = () => {
2145
+ if (this.isReadyForRequests()) {
2146
+ finish(true);
2147
+ }
2148
+ };
2149
+ const timeout = setTimeout(() => {
2150
+ finish(this.isReadyForRequests());
2151
+ }, timeoutMs);
2152
+ this.readyWaiters.add(waiter);
2153
+ waiter();
2154
+ });
2155
+ }
2186
2156
  async request(action, params, timeoutMs = 3e4) {
2187
2157
  const response = await this.requestInternal(action, params, timeoutMs);
2188
2158
  return response;
@@ -2254,6 +2224,7 @@ var ExtensionBridge = class {
2254
2224
  status: "request",
2255
2225
  params
2256
2226
  };
2227
+ const envelope2 = request;
2257
2228
  const response = await new Promise((resolve3, reject) => {
2258
2229
  const timeout = setTimeout(() => {
2259
2230
  this.pending.delete(id);
@@ -2271,7 +2242,7 @@ var ExtensionBridge = class {
2271
2242
  reject,
2272
2243
  timeout
2273
2244
  });
2274
- this.socket?.send(JSON.stringify(request));
2245
+ this.socket?.send(JSON.stringify(envelope2));
2275
2246
  });
2276
2247
  return response;
2277
2248
  }
@@ -2352,6 +2323,7 @@ var ExtensionBridge = class {
2352
2323
  this.stopHeartbeat();
2353
2324
  this.connected = false;
2354
2325
  this.socket = null;
2326
+ this.extensionId = void 0;
2355
2327
  this.version = void 0;
2356
2328
  this.protocolVersion = void 0;
2357
2329
  this.protocolMismatch = void 0;
@@ -2414,6 +2386,11 @@ var ExtensionBridge = class {
2414
2386
  this.tabs = tabs;
2415
2387
  }
2416
2388
  if (message.action === "drive.hello") {
2389
+ if (typeof params?.extension_id === "string") {
2390
+ this.extensionId = params.extension_id;
2391
+ } else {
2392
+ this.extensionId = void 0;
2393
+ }
2417
2394
  if (typeof params?.version === "string") {
2418
2395
  this.version = params.version;
2419
2396
  }
@@ -2436,7 +2413,7 @@ var ExtensionBridge = class {
2436
2413
  if (typeof params?.core_port === "number" && Number.isFinite(params.core_port)) {
2437
2414
  this.corePort = params.core_port;
2438
2415
  }
2439
- if (params?.core_port_source === "default" || params?.core_port_source === "storage") {
2416
+ if (params?.core_port_source === "default") {
2440
2417
  this.corePortSource = params.core_port_source;
2441
2418
  }
2442
2419
  const capabilities = params?.capabilities;
@@ -2456,6 +2433,7 @@ var ExtensionBridge = class {
2456
2433
  expected: "drive.hello.capabilities"
2457
2434
  };
2458
2435
  }
2436
+ this.flushReadyWaiters();
2459
2437
  }
2460
2438
  }
2461
2439
  if (typeof message.action === "string" && message.action.startsWith("debugger.")) {
@@ -2471,6 +2449,23 @@ var ExtensionBridge = class {
2471
2449
  }
2472
2450
  }
2473
2451
  }
2452
+ isReadyForRequests() {
2453
+ return this.connected && this.capabilityNegotiated;
2454
+ }
2455
+ flushReadyWaiters() {
2456
+ if (!this.isReadyForRequests() || this.readyWaiters.size === 0) {
2457
+ return;
2458
+ }
2459
+ const waiters = Array.from(this.readyWaiters);
2460
+ this.readyWaiters.clear();
2461
+ for (const waiter of waiters) {
2462
+ try {
2463
+ waiter();
2464
+ } catch (error) {
2465
+ console.debug("Extension ready waiter failed.", error);
2466
+ }
2467
+ }
2468
+ }
2474
2469
  applyDriveConnected() {
2475
2470
  if (!this.registry) {
2476
2471
  return;
@@ -2524,7 +2519,27 @@ var toDriveError = (error) => ({
2524
2519
 
2525
2520
  // packages/core/src/drive.ts
2526
2521
  var LOOPBACK_NAVIGATION_PREFLIGHT_TIMEOUT_MS = 1200;
2522
+ var POST_CLICK_SETTLE_MS = 75;
2523
+ var TRANSIENT_LOCATOR_RETRY_DELAYS_MS = [150, 300, 600];
2524
+ var EXTENSION_READY_WAIT_MS = 1500;
2527
2525
  var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
2526
+ var ACTIONS_WITH_OPTIONAL_TAB_ID = /* @__PURE__ */ new Set([
2527
+ "drive.navigate",
2528
+ "drive.go_back",
2529
+ "drive.go_forward",
2530
+ "drive.click",
2531
+ "drive.hover",
2532
+ "drive.select",
2533
+ "drive.type",
2534
+ "drive.fill_form",
2535
+ "drive.drag",
2536
+ "drive.handle_dialog",
2537
+ "drive.key",
2538
+ "drive.key_press",
2539
+ "drive.scroll",
2540
+ "drive.screenshot",
2541
+ "drive.wait_for"
2542
+ ]);
2528
2543
  var isLikelyUnreachableLoopbackError = (message) => {
2529
2544
  const normalized = message.toLowerCase();
2530
2545
  return normalized.includes("connection refused") || normalized.includes("econnrefused") || normalized.includes("err_connection_refused") || normalized.includes("enotfound") || normalized.includes("err_name_not_resolved") || normalized.includes("eai_again") || normalized.includes("ehostunreach") || normalized.includes("enetunreach");
@@ -2621,10 +2636,36 @@ var DriveController = class {
2621
2636
  this.lastError = void 0;
2622
2637
  this.lastErrorAt = void 0;
2623
2638
  }
2639
+ async sleep(ms) {
2640
+ await new Promise((resolve3) => setTimeout(resolve3, ms));
2641
+ }
2642
+ async waitForBridgeReady() {
2643
+ if (this.bridge.isConnected()) {
2644
+ return true;
2645
+ }
2646
+ const waitForReady = this.bridge.waitForReady;
2647
+ if (typeof waitForReady === "function") {
2648
+ return await waitForReady.call(this.bridge, EXTENSION_READY_WAIT_MS);
2649
+ }
2650
+ return this.bridge.isConnected();
2651
+ }
2652
+ isTransientLocatorError(action, error) {
2653
+ if (action !== "drive.click") {
2654
+ return false;
2655
+ }
2656
+ if (error.code.toUpperCase() !== "NOT_FOUND") {
2657
+ return false;
2658
+ }
2659
+ const reason = error.details?.reason;
2660
+ const legacyCode = error.details?.legacy_code;
2661
+ const resource = error.details?.resource;
2662
+ return reason === "locator_not_found" || legacyCode === "LOCATOR_NOT_FOUND" || resource === "locator";
2663
+ }
2624
2664
  async execute(sessionId, action, params, timeoutMs) {
2625
2665
  return await driveMutex.runExclusive(async () => {
2666
+ let selectedTabId;
2626
2667
  try {
2627
- this.registry.require(sessionId);
2668
+ selectedTabId = this.registry.require(sessionId).selectedTabId;
2628
2669
  } catch (error) {
2629
2670
  if (error instanceof SessionError) {
2630
2671
  const errorInfo2 = {
@@ -2649,7 +2690,7 @@ var DriveController = class {
2649
2690
  error: errorInfo
2650
2691
  };
2651
2692
  }
2652
- if (!this.bridge.isConnected()) {
2693
+ if (!await this.waitForBridgeReady()) {
2653
2694
  const errorInfo = {
2654
2695
  code: "EXTENSION_DISCONNECTED",
2655
2696
  message: "Extension is not connected. Open Chrome with the Browser Bridge extension enabled, then retry.",
@@ -2668,7 +2709,12 @@ var DriveController = class {
2668
2709
  return { ok: false, error: errorInfo };
2669
2710
  }
2670
2711
  this.ensureDriveReady(sessionId);
2671
- const preflightError = await preflightLoopbackNavigation(action, params);
2712
+ const prepared = this.prepareRequestParams(action, params, selectedTabId);
2713
+ const requestParams = prepared.params;
2714
+ const preflightError = await preflightLoopbackNavigation(
2715
+ action,
2716
+ requestParams
2717
+ );
2672
2718
  if (preflightError) {
2673
2719
  this.recordError(preflightError);
2674
2720
  return {
@@ -2681,10 +2727,19 @@ var DriveController = class {
2681
2727
  try {
2682
2728
  const response = await this.bridge.request(
2683
2729
  action,
2684
- params,
2730
+ requestParams,
2685
2731
  timeoutMs
2686
2732
  );
2687
2733
  if (response.status === "ok") {
2734
+ if (action === "drive.click") {
2735
+ await this.sleep(POST_CLICK_SETTLE_MS);
2736
+ }
2737
+ this.applySessionTargetOnSuccess(
2738
+ sessionId,
2739
+ action,
2740
+ requestParams,
2741
+ response.result
2742
+ );
2688
2743
  this.clearLastError();
2689
2744
  return {
2690
2745
  ok: true,
@@ -2701,6 +2756,12 @@ var DriveController = class {
2701
2756
  max_attempts: 1
2702
2757
  }
2703
2758
  };
2759
+ if (attempt < TRANSIENT_LOCATOR_RETRY_DELAYS_MS.length && this.isTransientLocatorError(action, errorInfo)) {
2760
+ const delayMs = TRANSIENT_LOCATOR_RETRY_DELAYS_MS[attempt] ?? 0;
2761
+ attempt += 1;
2762
+ await this.sleep(delayMs);
2763
+ continue;
2764
+ }
2704
2765
  if (shouldRetryDriveOp({
2705
2766
  attempt,
2706
2767
  retryable: errorInfo.retryable,
@@ -2709,6 +2770,9 @@ var DriveController = class {
2709
2770
  attempt += 1;
2710
2771
  continue;
2711
2772
  }
2773
+ if (prepared.injectedSessionTabId && this.isMissingTabError(errorInfo)) {
2774
+ this.clearSessionTarget(sessionId);
2775
+ }
2712
2776
  this.recordError(errorInfo);
2713
2777
  return { ok: false, error: errorInfo };
2714
2778
  } catch (error) {
@@ -2725,6 +2789,9 @@ var DriveController = class {
2725
2789
  attempt += 1;
2726
2790
  continue;
2727
2791
  }
2792
+ if (prepared.injectedSessionTabId && this.isMissingTabError(errorInfo2)) {
2793
+ this.clearSessionTarget(sessionId);
2794
+ }
2728
2795
  this.recordError(errorInfo2);
2729
2796
  return { ok: false, error: errorInfo2 };
2730
2797
  }
@@ -2742,6 +2809,74 @@ var DriveController = class {
2742
2809
  }
2743
2810
  });
2744
2811
  }
2812
+ readTabId(params) {
2813
+ const value = params?.tab_id;
2814
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2815
+ }
2816
+ readTabIdFromResult(result) {
2817
+ if (!result || typeof result !== "object" || Array.isArray(result)) {
2818
+ return void 0;
2819
+ }
2820
+ const value = result.tab_id;
2821
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
2822
+ }
2823
+ prepareRequestParams(action, params, selectedTabId) {
2824
+ if (!ACTIONS_WITH_OPTIONAL_TAB_ID.has(action)) {
2825
+ return { params, injectedSessionTabId: false };
2826
+ }
2827
+ if (!selectedTabId || selectedTabId <= 0) {
2828
+ return { params, injectedSessionTabId: false };
2829
+ }
2830
+ const explicitTabId = this.readTabId(params);
2831
+ if (explicitTabId !== void 0) {
2832
+ return { params, injectedSessionTabId: false };
2833
+ }
2834
+ return {
2835
+ params: { ...params ?? {}, tab_id: selectedTabId },
2836
+ injectedSessionTabId: true
2837
+ };
2838
+ }
2839
+ applySessionTargetOnSuccess(sessionId, action, params, result) {
2840
+ const tabId = this.readTabIdFromResult(result) ?? this.readTabId(params);
2841
+ if (tabId === void 0) {
2842
+ return;
2843
+ }
2844
+ try {
2845
+ if (action === "drive.tab_close") {
2846
+ const session = this.registry.require(sessionId);
2847
+ if (session.selectedTabId === tabId) {
2848
+ this.registry.clearSelectedTab(sessionId);
2849
+ }
2850
+ return;
2851
+ }
2852
+ if (action === "drive.tab_activate" || ACTIONS_WITH_OPTIONAL_TAB_ID.has(action)) {
2853
+ this.registry.setSelectedTab(sessionId, tabId);
2854
+ }
2855
+ } catch (error) {
2856
+ console.debug(
2857
+ `Drive target update ignored for session ${sessionId}.`,
2858
+ error
2859
+ );
2860
+ }
2861
+ }
2862
+ isMissingTabError(error) {
2863
+ const code = error.code.toUpperCase();
2864
+ if (code === "TAB_NOT_FOUND") {
2865
+ return true;
2866
+ }
2867
+ const resource = error.details?.resource;
2868
+ return code === "NOT_FOUND" && (resource === void 0 || resource === "tab");
2869
+ }
2870
+ clearSessionTarget(sessionId) {
2871
+ try {
2872
+ this.registry.clearSelectedTab(sessionId);
2873
+ } catch (error) {
2874
+ console.debug(
2875
+ `Drive target clear ignored for session ${sessionId}.`,
2876
+ error
2877
+ );
2878
+ }
2879
+ }
2745
2880
  ensureDriveReady(sessionId) {
2746
2881
  try {
2747
2882
  const session = this.registry.require(sessionId);
@@ -2781,6 +2916,10 @@ var INTERACTIVE_AX_ROLES = /* @__PURE__ */ new Set([
2781
2916
  "textbox",
2782
2917
  "combobox",
2783
2918
  "listbox",
2919
+ "menu",
2920
+ "menuitem",
2921
+ "menuitemcheckbox",
2922
+ "menuitemradio",
2784
2923
  "checkbox",
2785
2924
  "radio",
2786
2925
  "switch",
@@ -2898,6 +3037,10 @@ var filterAxSnapshot = (snapshot, predicate) => {
2898
3037
  }
2899
3038
  return replaceAxNodes(snapshot, filtered);
2900
3039
  };
3040
+ var filterAxSnapshotByRefs = (snapshot, allowedRefs) => filterAxSnapshot(snapshot, (node) => {
3041
+ const ref = node.ref;
3042
+ return typeof ref === "string" && allowedRefs.has(ref);
3043
+ });
2901
3044
  var collectKeptDescendants = (nodeId, nodeById, keepIds, visited = /* @__PURE__ */ new Set()) => {
2902
3045
  if (visited.has(nodeId)) {
2903
3046
  return [];
@@ -3238,6 +3381,136 @@ var toConsoleEntry = (event) => {
3238
3381
  }
3239
3382
  };
3240
3383
 
3384
+ // packages/core/src/inspect/extract-content-policy.ts
3385
+ var import_readability = require("@mozilla/readability");
3386
+ var import_jsdom = require("jsdom");
3387
+ var import_turndown = __toESM(require("turndown"));
3388
+ var normalizeExtractedText = (value) => value.replace(/\s+/g, " ").trim();
3389
+ var collapseRepeatedMarkdownBlocks = (markdown) => {
3390
+ const blocks = markdown.split(/\n{2,}/).map((block) => block.trim()).filter((block) => block.length > 0);
3391
+ for (let sequenceLength = Math.floor(blocks.length / 2); sequenceLength >= 1; sequenceLength -= 1) {
3392
+ for (let start = 0; start + sequenceLength * 2 <= blocks.length; start += 1) {
3393
+ const first = blocks.slice(start, start + sequenceLength).map((block) => normalizeExtractedText(block)).join("\n");
3394
+ const second = blocks.slice(start + sequenceLength, start + sequenceLength * 2).map((block) => normalizeExtractedText(block)).join("\n");
3395
+ if (first.length === 0 || first !== second) {
3396
+ continue;
3397
+ }
3398
+ blocks.splice(start + sequenceLength, sequenceLength);
3399
+ sequenceLength = Math.min(
3400
+ sequenceLength + 1,
3401
+ Math.floor(blocks.length / 2)
3402
+ );
3403
+ start = Math.max(start - 1, -1);
3404
+ }
3405
+ }
3406
+ return blocks.join("\n\n").trim();
3407
+ };
3408
+ var extractSemanticMainCandidate = (document) => {
3409
+ const candidates = Array.from(
3410
+ document.querySelectorAll('main, [role="main"], article')
3411
+ );
3412
+ let best = candidates[0] ?? null;
3413
+ let bestLength = 0;
3414
+ for (const candidate of candidates) {
3415
+ const text = normalizeExtractedText(candidate.textContent ?? "");
3416
+ if (text.length <= bestLength) {
3417
+ continue;
3418
+ }
3419
+ best = candidate;
3420
+ bestLength = text.length;
3421
+ }
3422
+ if (!best || bestLength === 0) {
3423
+ return null;
3424
+ }
3425
+ return {
3426
+ html: best.innerHTML,
3427
+ text: normalizeExtractedText(best.textContent ?? ""),
3428
+ tagName: best.tagName.toLowerCase()
3429
+ };
3430
+ };
3431
+ var shouldPreferSemanticMainCandidate = (options) => {
3432
+ const articleLength = normalizeExtractedText(options.articleText).length;
3433
+ const mainLength = normalizeExtractedText(options.mainText).length;
3434
+ if (mainLength === 0) {
3435
+ return false;
3436
+ }
3437
+ if (articleLength === 0) {
3438
+ return true;
3439
+ }
3440
+ if (options.mainTagName === "article") {
3441
+ return false;
3442
+ }
3443
+ return articleLength < 160 && mainLength > articleLength + 20;
3444
+ };
3445
+ var parseExtractContentSource = (options) => {
3446
+ let article = null;
3447
+ let semanticMainCandidate = null;
3448
+ try {
3449
+ const dom = new import_jsdom.JSDOM(options.html, { url: options.url });
3450
+ const reader = new import_readability.Readability(dom.window.document);
3451
+ article = reader.parse();
3452
+ semanticMainCandidate = extractSemanticMainCandidate(dom.window.document);
3453
+ } catch {
3454
+ throw new InspectError(
3455
+ "EVALUATION_FAILED",
3456
+ "Failed to parse page content.",
3457
+ {
3458
+ retryable: false
3459
+ }
3460
+ );
3461
+ }
3462
+ if (!article) {
3463
+ throw new InspectError(
3464
+ "NOT_SUPPORTED",
3465
+ "Readability could not extract content.",
3466
+ {
3467
+ retryable: false
3468
+ }
3469
+ );
3470
+ }
3471
+ return {
3472
+ article,
3473
+ semanticMainCandidate
3474
+ };
3475
+ };
3476
+ var renderExtractContent = (options) => {
3477
+ let content = "";
3478
+ if (options.format === "article_json") {
3479
+ content = JSON.stringify(options.article, null, 2);
3480
+ } else if (options.format === "text") {
3481
+ const articleText = options.article.textContent ?? "";
3482
+ if (options.semanticMainCandidate && shouldPreferSemanticMainCandidate({
3483
+ articleText,
3484
+ mainText: options.semanticMainCandidate.text,
3485
+ mainTagName: options.semanticMainCandidate.tagName
3486
+ })) {
3487
+ content = options.semanticMainCandidate.text;
3488
+ } else {
3489
+ content = articleText;
3490
+ }
3491
+ } else {
3492
+ const turndown = new import_turndown.default();
3493
+ const articleText = options.article.textContent ?? "";
3494
+ const sourceHtml = options.semanticMainCandidate && shouldPreferSemanticMainCandidate({
3495
+ articleText,
3496
+ mainText: options.semanticMainCandidate.text,
3497
+ mainTagName: options.semanticMainCandidate.tagName
3498
+ }) ? options.semanticMainCandidate.html : options.article.content ?? "";
3499
+ content = collapseRepeatedMarkdownBlocks(turndown.turndown(sourceHtml));
3500
+ }
3501
+ const includeMetadata = options.includeMetadata ?? true;
3502
+ return {
3503
+ content,
3504
+ ...includeMetadata ? {
3505
+ title: options.article.title ?? void 0,
3506
+ byline: options.article.byline ?? void 0,
3507
+ excerpt: options.article.excerpt ?? void 0,
3508
+ siteName: options.article.siteName ?? void 0
3509
+ } : {},
3510
+ ...options.warnings && options.warnings.length > 0 ? { warnings: options.warnings } : {}
3511
+ };
3512
+ };
3513
+
3241
3514
  // packages/core/src/inspect/har.ts
3242
3515
  var buildHar = (events, title) => {
3243
3516
  const requests = /* @__PURE__ */ new Map();
@@ -3398,7 +3671,8 @@ var captureHtml = async (tabId, options) => {
3398
3671
  const result = await options.debuggerCommand(tabId, "Runtime.evaluate", {
3399
3672
  expression,
3400
3673
  returnByValue: true,
3401
- awaitPromise: true
3674
+ awaitPromise: true,
3675
+ ...typeof options.executionContextId === "number" ? { contextId: options.executionContextId } : {}
3402
3676
  });
3403
3677
  if (result && typeof result === "object" && "exceptionDetails" in result) {
3404
3678
  return options.onEvaluationFailed();
@@ -3539,11 +3813,19 @@ var SnapshotHistory = class {
3539
3813
 
3540
3814
  // packages/core/src/inspect/snapshot-refs.ts
3541
3815
  var SNAPSHOT_REF_ATTRIBUTE = "data-bv-ref";
3816
+ var SNAPSHOT_REF_REGISTRY_ID = "__bb_snapshot_ref_registry__";
3542
3817
  var MAX_REF_ASSIGNMENTS = 500;
3543
3818
  var MAX_REF_WARNINGS = 5;
3544
3819
  var isInspectError = (error) => Boolean(
3545
3820
  error && typeof error === "object" && "name" in error && error.name === "InspectError" && "message" in error && typeof error.message === "string"
3546
3821
  );
3822
+ var isExpectedSnapshotRefMiss = (error) => {
3823
+ if (!isInspectError(error)) {
3824
+ return false;
3825
+ }
3826
+ const message = error.message.toLowerCase();
3827
+ return message.includes("could not find node") || message.includes("no node with given id") || message.includes("does not belong to the document");
3828
+ };
3547
3829
  var assignRefsToAxSnapshot = (snapshot) => {
3548
3830
  const nodes = getAxNodes(snapshot);
3549
3831
  const refs = /* @__PURE__ */ new Map();
@@ -3562,31 +3844,61 @@ var assignRefsToAxSnapshot = (snapshot) => {
3562
3844
  const ref = `@e${index}`;
3563
3845
  index += 1;
3564
3846
  node.ref = ref;
3565
- refs.set(backendId, ref);
3847
+ refs.set(backendId, {
3848
+ ref,
3849
+ ...getAxRole(node) ? { role: getAxRole(node) } : {},
3850
+ ...getAxName(node) ? { name: getAxName(node) } : {},
3851
+ ...extractUrl(node) ? { url: extractUrl(node) } : {}
3852
+ });
3566
3853
  }
3567
3854
  return refs;
3568
3855
  };
3569
- var clearSnapshotRefs = async (tabId, debuggerCommand) => {
3570
- await debuggerCommand(tabId, "Runtime.evaluate", {
3571
- expression: `document.querySelectorAll('[${SNAPSHOT_REF_ATTRIBUTE}]').forEach((el) => el.removeAttribute('${SNAPSHOT_REF_ATTRIBUTE}'))`,
3572
- returnByValue: true,
3573
- awaitPromise: true
3574
- });
3856
+ var extractUrl = (node) => {
3857
+ if (!Array.isArray(node.properties)) {
3858
+ return void 0;
3859
+ }
3860
+ for (const prop of node.properties) {
3861
+ if (!prop || typeof prop !== "object") {
3862
+ continue;
3863
+ }
3864
+ const name = prop.name;
3865
+ if (name !== "url") {
3866
+ continue;
3867
+ }
3868
+ const value = prop.value?.value;
3869
+ if (typeof value === "string" && value.length > 0) {
3870
+ return value;
3871
+ }
3872
+ }
3873
+ return void 0;
3575
3874
  };
3576
- var applySnapshotRefs = async (tabId, refs, debuggerCommand) => {
3577
- const warnings = [];
3578
- await debuggerCommand(tabId, "DOM.enable", {});
3579
- await debuggerCommand(tabId, "Runtime.enable", {});
3875
+ var clearSnapshotRefArtifacts = async (tabId, debuggerCommand) => {
3580
3876
  try {
3581
- await clearSnapshotRefs(tabId, debuggerCommand);
3877
+ await debuggerCommand(tabId, "Runtime.evaluate", {
3878
+ expression: `(() => {
3879
+ document.querySelectorAll('[${SNAPSHOT_REF_ATTRIBUTE}]').forEach((el) => el.removeAttribute('${SNAPSHOT_REF_ATTRIBUTE}'));
3880
+ document.getElementById('${SNAPSHOT_REF_REGISTRY_ID}')?.remove();
3881
+ })()`,
3882
+ returnByValue: true,
3883
+ awaitPromise: true
3884
+ });
3885
+ return { warnings: [] };
3582
3886
  } catch {
3583
- warnings.push("Failed to clear prior snapshot refs.");
3887
+ return { warnings: ["Failed to clear prior snapshot refs."] };
3584
3888
  }
3889
+ };
3890
+ var applySnapshotRefAttributes = async (tabId, refs, debuggerCommand) => {
3891
+ const warnings = [];
3892
+ const appliedRefs = /* @__PURE__ */ new Set();
3893
+ const appliedBindings = [];
3894
+ await debuggerCommand(tabId, "DOM.enable", {});
3895
+ await debuggerCommand(tabId, "Runtime.enable", {});
3585
3896
  if (refs.size === 0) {
3586
- return warnings;
3897
+ return { warnings, appliedRefs, appliedBindings };
3587
3898
  }
3588
3899
  let applied = 0;
3589
- for (const [backendNodeId, ref] of refs) {
3900
+ for (const [backendNodeId, binding] of refs) {
3901
+ const ref = binding.ref;
3590
3902
  if (applied >= MAX_REF_ASSIGNMENTS) {
3591
3903
  warnings.push(
3592
3904
  `Snapshot refs truncated at ${MAX_REF_ASSIGNMENTS} elements.`
@@ -3599,9 +3911,6 @@ var applySnapshotRefs = async (tabId, refs, debuggerCommand) => {
3599
3911
  });
3600
3912
  const node = described.node;
3601
3913
  if (!node || node.nodeType !== 1 || typeof node.nodeId !== "number") {
3602
- if (warnings.length < MAX_REF_WARNINGS) {
3603
- warnings.push(`Ref ${ref} could not be applied to a DOM element.`);
3604
- }
3605
3914
  continue;
3606
3915
  }
3607
3916
  await debuggerCommand(tabId, "DOM.setAttributeValue", {
@@ -3610,25 +3919,64 @@ var applySnapshotRefs = async (tabId, refs, debuggerCommand) => {
3610
3919
  value: ref
3611
3920
  });
3612
3921
  applied += 1;
3613
- } catch {
3922
+ appliedRefs.add(ref);
3923
+ appliedBindings.push(binding);
3924
+ } catch (error) {
3925
+ if (isExpectedSnapshotRefMiss(error)) {
3926
+ continue;
3927
+ }
3614
3928
  if (warnings.length < MAX_REF_WARNINGS) {
3615
3929
  warnings.push(`Ref ${ref} could not be applied.`);
3616
3930
  }
3617
3931
  }
3618
3932
  }
3619
- return warnings;
3933
+ return { warnings, appliedRefs, appliedBindings };
3620
3934
  };
3621
- var resolveNodeIdForSelector = async (tabId, selector, debuggerCommand) => {
3622
- await debuggerCommand(tabId, "DOM.enable", {});
3623
- const document = await debuggerCommand(tabId, "DOM.getDocument", {
3624
- depth: 1
3625
- });
3626
- const rootNodeId = document.root?.nodeId;
3627
- if (typeof rootNodeId !== "number") {
3628
- return { warnings: ["Failed to resolve DOM root for selector."] };
3629
- }
3935
+ var persistSnapshotRefRegistry = async (tabId, bindings, debuggerCommand) => {
3630
3936
  try {
3631
- const result = await debuggerCommand(tabId, "DOM.querySelector", {
3937
+ const registry = JSON.stringify(bindings);
3938
+ await debuggerCommand(tabId, "Runtime.evaluate", {
3939
+ expression: `(() => {
3940
+ const id = ${JSON.stringify(SNAPSHOT_REF_REGISTRY_ID)};
3941
+ let el = document.getElementById(id);
3942
+ if (!el) {
3943
+ el = document.createElement('script');
3944
+ el.id = id;
3945
+ el.type = 'application/json';
3946
+ document.documentElement.appendChild(el);
3947
+ }
3948
+ el.textContent = ${JSON.stringify(registry)};
3949
+ })()`,
3950
+ returnByValue: true,
3951
+ awaitPromise: true
3952
+ });
3953
+ return { warnings: [] };
3954
+ } catch {
3955
+ return { warnings: ["Snapshot ref registry could not be applied."] };
3956
+ }
3957
+ };
3958
+ var pruneUnappliedRefsFromSnapshot = (snapshot, appliedRefs) => {
3959
+ for (const node of getAxNodes(snapshot)) {
3960
+ if (!node || typeof node !== "object") {
3961
+ continue;
3962
+ }
3963
+ const ref = node.ref;
3964
+ if (typeof ref === "string" && !appliedRefs.has(ref)) {
3965
+ delete node.ref;
3966
+ }
3967
+ }
3968
+ };
3969
+ var resolveNodeIdForSelector = async (tabId, selector, debuggerCommand) => {
3970
+ await debuggerCommand(tabId, "DOM.enable", {});
3971
+ const document = await debuggerCommand(tabId, "DOM.getDocument", {
3972
+ depth: 1
3973
+ });
3974
+ const rootNodeId = document.root?.nodeId;
3975
+ if (typeof rootNodeId !== "number") {
3976
+ return { warnings: ["Failed to resolve DOM root for selector."] };
3977
+ }
3978
+ try {
3979
+ const result = await debuggerCommand(tabId, "DOM.querySelector", {
3632
3980
  nodeId: rootNodeId,
3633
3981
  selector
3634
3982
  });
@@ -3645,107 +3993,6 @@ var resolveNodeIdForSelector = async (tabId, selector, debuggerCommand) => {
3645
3993
  }
3646
3994
  };
3647
3995
 
3648
- // packages/core/src/page-state-script.ts
3649
- var PAGE_STATE_SCRIPT = [
3650
- "(() => {",
3651
- " const escape = (value) => {",
3652
- " if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {",
3653
- " return CSS.escape(value);",
3654
- " }",
3655
- ` return String(value).replace(/["'\\\\]/g, '\\\\$&');`,
3656
- " };",
3657
- " const truncate = (value, max) => {",
3658
- " const text = String(value ?? '');",
3659
- " return text.length > max ? text.slice(0, max) : text;",
3660
- " };",
3661
- " const selectorFor = (element) => {",
3662
- " if (element.id) {",
3663
- " return `#${escape(element.id)}`;",
3664
- " }",
3665
- " const name = element.getAttribute('name');",
3666
- " if (name) {",
3667
- ' return `${element.tagName.toLowerCase()}[name="${escape(name)}"]`;',
3668
- " }",
3669
- " const parts = [];",
3670
- " let node = element;",
3671
- " while (node && node.nodeType === 1 && parts.length < 4) {",
3672
- " let part = node.tagName.toLowerCase();",
3673
- " const parent = node.parentElement;",
3674
- " if (parent) {",
3675
- " const siblings = Array.from(parent.children).filter(",
3676
- " (child) => child.tagName === node.tagName",
3677
- " );",
3678
- " if (siblings.length > 1) {",
3679
- " part += `:nth-of-type(${siblings.indexOf(node) + 1})`;",
3680
- " }",
3681
- " }",
3682
- " parts.unshift(part);",
3683
- " node = parent;",
3684
- " }",
3685
- " return parts.join('>');",
3686
- " };",
3687
- " const readStorage = (storage, limit) => {",
3688
- " try {",
3689
- " return Object.keys(storage)",
3690
- " .slice(0, limit)",
3691
- " .map((key) => ({",
3692
- " key,",
3693
- " value: truncate(storage.getItem(key), 500),",
3694
- " }));",
3695
- " } catch {",
3696
- " return [];",
3697
- " }",
3698
- " };",
3699
- " const forms = Array.from(document.querySelectorAll('form')).map((form) => {",
3700
- " const fields = Array.from(form.elements)",
3701
- " .filter((element) => element && element.tagName)",
3702
- " .map((element) => {",
3703
- " const tag = element.tagName.toLowerCase();",
3704
- " const type = 'type' in element && element.type ? element.type : tag;",
3705
- " const name = element.name || element.getAttribute('name') || element.id || '';",
3706
- " let value = '';",
3707
- " let options;",
3708
- " if (tag === 'select') {",
3709
- " const select = element;",
3710
- " value = select.value ?? '';",
3711
- " options = Array.from(select.options).map((option) => option.text);",
3712
- " } else if (tag === 'input' && element.type === 'password') {",
3713
- " value = '[redacted]';",
3714
- " } else if (tag === 'input' || tag === 'textarea') {",
3715
- " value = element.value ?? '';",
3716
- " } else if (element.isContentEditable) {",
3717
- " value = element.textContent ?? '';",
3718
- " } else if ('value' in element) {",
3719
- " value = element.value ?? '';",
3720
- " }",
3721
- " return {",
3722
- " name,",
3723
- " type,",
3724
- " value: truncate(value, 500),",
3725
- " ...(options ? { options } : {}),",
3726
- " };",
3727
- " });",
3728
- " return {",
3729
- " selector: selectorFor(form),",
3730
- " action: form.getAttribute('action') || undefined,",
3731
- " method: form.getAttribute('method') || undefined,",
3732
- " fields,",
3733
- " };",
3734
- " });",
3735
- " const localStorage = readStorage(window.localStorage, 100);",
3736
- " const sessionStorage = readStorage(window.sessionStorage, 100);",
3737
- " const cookies = (document.cookie ? document.cookie.split(';') : [])",
3738
- " .map((entry) => entry.trim())",
3739
- " .filter((entry) => entry.length > 0)",
3740
- " .slice(0, 50)",
3741
- " .map((entry) => {",
3742
- " const [key, ...rest] = entry.split('=');",
3743
- " return { key, value: truncate(rest.join('='), 500) };",
3744
- " });",
3745
- " return { forms, localStorage, sessionStorage, cookies };",
3746
- "})()"
3747
- ].join("\n");
3748
-
3749
3996
  // packages/core/src/target-matching.ts
3750
3997
  var normalizeText = (value) => (value ?? "").trim().toLowerCase();
3751
3998
  var normalizeUrl = (value) => {
@@ -3851,11 +4098,302 @@ var pickBestTarget = (candidates, hint, now = Date.now()) => {
3851
4098
  return ranked.length > 0 ? ranked[0] : null;
3852
4099
  };
3853
4100
 
4101
+ // packages/core/src/inspect/target-selection.ts
4102
+ var readTargetHintInput = (target) => {
4103
+ if (!target) {
4104
+ return void 0;
4105
+ }
4106
+ const url = typeof target.url === "string" ? target.url : void 0;
4107
+ const title = typeof target.title === "string" ? target.title : void 0;
4108
+ const tabIdRaw = target.tab_id ?? target.tabId;
4109
+ const tabId = typeof tabIdRaw === "number" ? tabIdRaw : void 0;
4110
+ const lastActiveAtRaw = target.last_active_at ?? target.lastActiveAt;
4111
+ const lastActiveAt = typeof lastActiveAtRaw === "string" ? lastActiveAtRaw : void 0;
4112
+ if (!url && !title && tabId === void 0 && !lastActiveAt) {
4113
+ return void 0;
4114
+ }
4115
+ return { url, title, tabId, lastActiveAt };
4116
+ };
4117
+ var readSessionTargetHint = (registry, sessionId) => {
4118
+ if (typeof sessionId !== "string") {
4119
+ return void 0;
4120
+ }
4121
+ const selectedTabId = registry.get(sessionId)?.selectedTabId;
4122
+ return typeof selectedTabId === "number" && Number.isFinite(selectedTabId) ? { tabId: selectedTabId } : void 0;
4123
+ };
4124
+ var resolveInspectTargetHint = (options) => {
4125
+ const explicit = readTargetHintInput(options.target);
4126
+ if (explicit) {
4127
+ return explicit;
4128
+ }
4129
+ const sessionHint = readSessionTargetHint(
4130
+ options.registry,
4131
+ options.sessionId
4132
+ );
4133
+ if (sessionHint) {
4134
+ return sessionHint;
4135
+ }
4136
+ const tabs = options.extensionBridge?.getStatus().tabs ?? [];
4137
+ return deriveHintFromTabs(tabs);
4138
+ };
4139
+ var selectInspectTab = (options) => {
4140
+ if (!options.extensionBridge || !options.extensionBridge.isConnected()) {
4141
+ throw new InspectError(
4142
+ "EXTENSION_DISCONNECTED",
4143
+ "Extension is not connected.",
4144
+ {
4145
+ retryable: true
4146
+ }
4147
+ );
4148
+ }
4149
+ const tabs = options.extensionBridge.getStatus().tabs ?? [];
4150
+ if (!Array.isArray(tabs) || tabs.length === 0) {
4151
+ throw new InspectError("TAB_NOT_FOUND", "No tabs available to inspect.");
4152
+ }
4153
+ const effectiveHint = options.targetHint ?? readSessionTargetHint(options.registry, options.sessionId);
4154
+ if (typeof effectiveHint?.tabId === "number" && Number.isFinite(effectiveHint.tabId)) {
4155
+ const tab2 = tabs.find((entry) => entry.tab_id === effectiveHint.tabId);
4156
+ if (!tab2) {
4157
+ throw new InspectError(
4158
+ "TAB_NOT_FOUND",
4159
+ `No matching tab found for tab_id ${effectiveHint.tabId}.`,
4160
+ { details: { tab_id: effectiveHint.tabId } }
4161
+ );
4162
+ }
4163
+ return { tabId: effectiveHint.tabId, tab: tab2 };
4164
+ }
4165
+ const candidates = tabs.map((tab2) => ({
4166
+ id: String(tab2.tab_id),
4167
+ url: tab2.url ?? "",
4168
+ title: tab2.title,
4169
+ lastSeenAt: tab2.last_active_at ? Date.parse(tab2.last_active_at) : void 0
4170
+ }));
4171
+ const ranked = pickBestTarget(candidates, effectiveHint);
4172
+ if (!ranked) {
4173
+ throw new InspectError("TAB_NOT_FOUND", "No matching tab found.");
4174
+ }
4175
+ const tabId = Number(ranked.candidate.id);
4176
+ if (!Number.isFinite(tabId)) {
4177
+ throw new InspectError("TAB_NOT_FOUND", "Resolved tab id is invalid.");
4178
+ }
4179
+ const tab = tabs.find((entry) => entry.tab_id === tabId) ?? tabs[0];
4180
+ const warnings = [];
4181
+ if (!effectiveHint) {
4182
+ warnings.push("No target hint provided; using the most recent tab.");
4183
+ } else if (ranked.score < 20) {
4184
+ warnings.push("Weak target match; using best available tab.");
4185
+ }
4186
+ return {
4187
+ tabId,
4188
+ tab,
4189
+ warnings: warnings.length > 0 ? warnings : void 0
4190
+ };
4191
+ };
4192
+
4193
+ // packages/core/src/page-state-script.ts
4194
+ var buildPageStateScript = (options) => {
4195
+ const includeValues = options?.includeValues === true;
4196
+ return [
4197
+ "(() => {",
4198
+ ` const includeValues = ${includeValues ? "true" : "false"};`,
4199
+ " const escape = (value) => {",
4200
+ " if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {",
4201
+ " return CSS.escape(value);",
4202
+ " }",
4203
+ ` return String(value).replace(/["'\\\\]/g, '\\\\$&');`,
4204
+ " };",
4205
+ " const truncate = (value, max) => {",
4206
+ " const text = String(value ?? '');",
4207
+ " return text.length > max ? text.slice(0, max) : text;",
4208
+ " };",
4209
+ ' const redact = (value) => (includeValues ? truncate(value, 500) : "[redacted]");',
4210
+ ' const textFor = (element) => truncate((element?.textContent || "").replace(/\\s+/g, " ").trim(), 120);',
4211
+ " const isVisible = (element) => {",
4212
+ ' if (!element || typeof element.getClientRects !== "function") {',
4213
+ " return false;",
4214
+ " }",
4215
+ " const style = window.getComputedStyle(element);",
4216
+ ' if (style.display === "none" || style.visibility === "hidden") {',
4217
+ " return false;",
4218
+ " }",
4219
+ ' if (element.hidden || element.getAttribute("aria-hidden") === "true") {',
4220
+ " return false;",
4221
+ " }",
4222
+ " return element.getClientRects().length > 0;",
4223
+ " };",
4224
+ " const selectorFor = (element) => {",
4225
+ " if (!element || element.nodeType !== 1) {",
4226
+ ' return "";',
4227
+ " }",
4228
+ " if (element.id) {",
4229
+ " return `#${escape(element.id)}`;",
4230
+ " }",
4231
+ ' const testId = element.getAttribute("data-testid");',
4232
+ " if (testId) {",
4233
+ ' return `[data-testid="${escape(testId)}"]`;',
4234
+ " }",
4235
+ ' const name = element.getAttribute("name");',
4236
+ " if (name) {",
4237
+ ' return `${element.tagName.toLowerCase()}[name="${escape(name)}"]`;',
4238
+ " }",
4239
+ ' const ariaLabel = element.getAttribute("aria-label");',
4240
+ " if (ariaLabel) {",
4241
+ ' return `${element.tagName.toLowerCase()}[aria-label="${escape(ariaLabel)}"]`;',
4242
+ " }",
4243
+ " const parts = [];",
4244
+ " let node = element;",
4245
+ " while (node && node.nodeType === 1 && parts.length < 4) {",
4246
+ " let part = node.tagName.toLowerCase();",
4247
+ " const parent = node.parentElement;",
4248
+ " if (parent) {",
4249
+ " const siblings = Array.from(parent.children).filter(",
4250
+ " (child) => child.tagName === node.tagName",
4251
+ " );",
4252
+ " if (siblings.length > 1) {",
4253
+ " part += `:nth-of-type(${siblings.indexOf(node) + 1})`;",
4254
+ " }",
4255
+ " }",
4256
+ " parts.unshift(part);",
4257
+ " node = parent;",
4258
+ " }",
4259
+ " return parts.join('>');",
4260
+ " };",
4261
+ " const labelFor = (element) => {",
4262
+ " if (!element) {",
4263
+ " return undefined;",
4264
+ " }",
4265
+ ' const ariaLabel = element.getAttribute("aria-label");',
4266
+ " if (ariaLabel) {",
4267
+ " return truncate(ariaLabel, 120);",
4268
+ " }",
4269
+ " if (element.id) {",
4270
+ ' const explicitLabel = document.querySelector(`label[for="${escape(element.id)}"]`);',
4271
+ " if (explicitLabel) {",
4272
+ " return textFor(explicitLabel);",
4273
+ " }",
4274
+ " }",
4275
+ ' const parentLabel = element.closest("label");',
4276
+ " if (parentLabel) {",
4277
+ " return textFor(parentLabel);",
4278
+ " }",
4279
+ " return undefined;",
4280
+ " };",
4281
+ " const readStorage = (storage, limit) => {",
4282
+ " try {",
4283
+ " const keys = Object.keys(storage);",
4284
+ " return {",
4285
+ " count: keys.length,",
4286
+ " entries: keys.slice(0, limit).map((key) => ({",
4287
+ " key,",
4288
+ " value: redact(storage.getItem(key)),",
4289
+ " })),",
4290
+ " };",
4291
+ " } catch {",
4292
+ " return { count: 0, entries: [] };",
4293
+ " }",
4294
+ " };",
4295
+ ' const forms = Array.from(document.querySelectorAll("form"))',
4296
+ " .filter((form) => isVisible(form))",
4297
+ " .map((form) => {",
4298
+ " const fields = Array.from(form.elements)",
4299
+ " .filter((element) => element && element.tagName)",
4300
+ " .map((element) => {",
4301
+ " const tag = element.tagName.toLowerCase();",
4302
+ ' const type = "type" in element && element.type ? element.type : tag;',
4303
+ ' const name = element.name || element.getAttribute("name") || element.id || "";',
4304
+ ' let value = "";',
4305
+ " let options;",
4306
+ ' if (tag === "select") {',
4307
+ " const select = element;",
4308
+ ' value = redact(select.value ?? "");',
4309
+ " options = Array.from(select.options).map((option) => truncate(option.text, 120));",
4310
+ ' } else if (tag === "input" && element.type === "password") {',
4311
+ ' value = "[redacted]";',
4312
+ ' } else if (tag === "input" || tag === "textarea") {',
4313
+ ' value = redact(element.value ?? "");',
4314
+ " } else if (element.isContentEditable) {",
4315
+ ' value = redact(element.textContent ?? "");',
4316
+ ' } else if ("value" in element) {',
4317
+ ' value = redact(element.value ?? "");',
4318
+ " }",
4319
+ " return {",
4320
+ " name,",
4321
+ " type,",
4322
+ " value,",
4323
+ " selector: selectorFor(element),",
4324
+ " label: labelFor(element),",
4325
+ ' placeholder: truncate(element.getAttribute?.("placeholder") ?? "", 120) || undefined,',
4326
+ ' checked: typeof element.checked === "boolean" ? element.checked : undefined,',
4327
+ " disabled: !!element.disabled,",
4328
+ " visible: isVisible(element),",
4329
+ " ...(options ? { options } : {}),",
4330
+ " };",
4331
+ " });",
4332
+ " return {",
4333
+ " selector: selectorFor(form),",
4334
+ ' action: form.getAttribute("action") || undefined,',
4335
+ ' method: form.getAttribute("method") || undefined,',
4336
+ " fields,",
4337
+ " };",
4338
+ " });",
4339
+ " const localStorageData = readStorage(window.localStorage, 25);",
4340
+ " const sessionStorageData = readStorage(window.sessionStorage, 25);",
4341
+ ' const cookies = (document.cookie ? document.cookie.split(";") : [])',
4342
+ " .map((entry) => entry.trim())",
4343
+ " .filter((entry) => entry.length > 0)",
4344
+ " .slice(0, 25)",
4345
+ " .map((entry) => {",
4346
+ ' const [key, ...rest] = entry.split("=");',
4347
+ " return {",
4348
+ " key,",
4349
+ ' value: includeValues ? truncate(rest.join("="), 500) : "[redacted]",',
4350
+ " };",
4351
+ " });",
4352
+ " const focused = document.activeElement && document.activeElement !== document.body",
4353
+ " ? {",
4354
+ " selector: selectorFor(document.activeElement),",
4355
+ ' name: document.activeElement.getAttribute?.("name") || document.activeElement.id || undefined,',
4356
+ " label: labelFor(document.activeElement),",
4357
+ ' role: document.activeElement.getAttribute?.("role") || document.activeElement.tagName?.toLowerCase(),',
4358
+ ' type: document.activeElement.getAttribute?.("type") || undefined,',
4359
+ " text: textFor(document.activeElement) || undefined,",
4360
+ " }",
4361
+ " : undefined;",
4362
+ ' const primaryActions = Array.from(document.querySelectorAll("button, a[href], input[type=button], input[type=submit]"))',
4363
+ " .filter((element) => isVisible(element))",
4364
+ " .map((element) => ({",
4365
+ " selector: selectorFor(element),",
4366
+ ' role: element.getAttribute("role") || element.tagName.toLowerCase(),',
4367
+ ' name: truncate(element.getAttribute("aria-label") || element.innerText || element.value || "", 120),',
4368
+ " }))",
4369
+ " .filter((entry) => entry.name.length > 0)",
4370
+ " .slice(0, 12);",
4371
+ " return {",
4372
+ " url: location.href,",
4373
+ " title: document.title || undefined,",
4374
+ " readyState: document.readyState || undefined,",
4375
+ " focused,",
4376
+ " primaryActions,",
4377
+ " storageSummary: {",
4378
+ " localStorageCount: localStorageData.count,",
4379
+ " sessionStorageCount: sessionStorageData.count,",
4380
+ " cookieCount: cookies.length,",
4381
+ " },",
4382
+ " forms,",
4383
+ " localStorage: localStorageData.entries,",
4384
+ " sessionStorage: sessionStorageData.entries,",
4385
+ " cookies,",
4386
+ " };",
4387
+ "})()"
4388
+ ].join("\n");
4389
+ };
4390
+
3854
4391
  // packages/core/src/inspect/service.ts
3855
4392
  var DEFAULT_MAX_SNAPSHOTS_PER_SESSION = 20;
3856
4393
  var DEFAULT_MAX_SNAPSHOT_HISTORY = 100;
3857
4394
  var InspectService = class {
3858
4395
  constructor(options) {
4396
+ this.consoleSinceBySessionTab = /* @__PURE__ */ new Map();
3859
4397
  this.registry = options.registry;
3860
4398
  this.debugger = options.debuggerBridge;
3861
4399
  this.extensionBridge = options.extensionBridge;
@@ -3895,7 +4433,7 @@ var InspectService = class {
3895
4433
  async reconnect(sessionId) {
3896
4434
  try {
3897
4435
  this.requireSession(sessionId);
3898
- const selection = await this.resolveTab();
4436
+ const selection = await this.resolveTab(sessionId);
3899
4437
  const debuggerBridge = this.ensureDebugger();
3900
4438
  const result = await debuggerBridge.attach(selection.tabId);
3901
4439
  if (result.ok) {
@@ -3914,13 +4452,17 @@ var InspectService = class {
3914
4452
  }
3915
4453
  async domSnapshot(input) {
3916
4454
  this.requireSession(input.sessionId);
3917
- const selection = await this.resolveTab(input.targetHint);
4455
+ const selection = await this.resolveTab(input.sessionId, input.targetHint);
3918
4456
  const debuggerCommand = this.debuggerCommand.bind(this);
4457
+ const executionContextId = await this.resolveMainFrameExecutionContextId(
4458
+ selection.tabId
4459
+ );
3919
4460
  const work = async () => {
3920
4461
  if (input.format === "html") {
3921
4462
  const html = await captureHtml(selection.tabId, {
3922
4463
  selector: input.selector,
3923
4464
  debuggerCommand,
4465
+ executionContextId,
3924
4466
  onEvaluationFailed: () => {
3925
4467
  const error = new InspectError(
3926
4468
  "EVALUATION_FAILED",
@@ -3964,20 +4506,11 @@ var InspectService = class {
3964
4506
  );
3965
4507
  selectorWarnings.push(...resolved.warnings ?? []);
3966
4508
  if (!resolved.nodeId) {
3967
- let refWarnings2 = [];
3968
- try {
3969
- refWarnings2 = await applySnapshotRefs(
3970
- selection.tabId,
3971
- /* @__PURE__ */ new Map(),
3972
- debuggerCommand
3973
- );
3974
- } catch {
3975
- refWarnings2 = ["Failed to clear prior snapshot refs."];
3976
- }
4509
+ const refWarnings = (await clearSnapshotRefArtifacts(selection.tabId, debuggerCommand)).warnings;
3977
4510
  const warnings2 = [
3978
4511
  ...selection.warnings ?? [],
3979
4512
  ...selectorWarnings,
3980
- ...refWarnings2
4513
+ ...refWarnings
3981
4514
  ];
3982
4515
  return {
3983
4516
  format: "ax",
@@ -4014,16 +4547,46 @@ var InspectService = class {
4014
4547
  }
4015
4548
  }
4016
4549
  const refMap = assignRefsToAxSnapshot(snapshot);
4017
- const refWarnings = await applySnapshotRefs(
4550
+ const clearResult = await clearSnapshotRefArtifacts(
4551
+ selection.tabId,
4552
+ debuggerCommand
4553
+ );
4554
+ const refResult = await applySnapshotRefAttributes(
4018
4555
  selection.tabId,
4019
4556
  refMap,
4020
4557
  debuggerCommand
4021
4558
  );
4559
+ let actionableRefs = refResult.appliedRefs;
4560
+ let actionableBindings = refResult.appliedBindings;
4561
+ const actionabilityWarnings = [];
4562
+ if (input.interactive && refResult.appliedBindings.length > 0) {
4563
+ const actionableResult = await this.collectActionableSnapshotRefs(
4564
+ selection.tabId,
4565
+ refResult.appliedBindings
4566
+ );
4567
+ actionabilityWarnings.push(...actionableResult.warnings);
4568
+ if (actionableResult.actionableRefs) {
4569
+ actionableRefs = actionableResult.actionableRefs;
4570
+ actionableBindings = refResult.appliedBindings.filter(
4571
+ (binding) => actionableRefs.has(binding.ref)
4572
+ );
4573
+ snapshot = filterAxSnapshotByRefs(snapshot, actionableRefs);
4574
+ }
4575
+ }
4576
+ const persistResult = refMap.size === 0 ? { warnings: [] } : await persistSnapshotRefRegistry(
4577
+ selection.tabId,
4578
+ actionableBindings,
4579
+ debuggerCommand
4580
+ );
4581
+ pruneUnappliedRefsFromSnapshot(snapshot, actionableRefs);
4022
4582
  const warnings = [
4023
4583
  ...selection.warnings ?? [],
4024
4584
  ...selectorWarnings,
4025
4585
  ...truncationWarnings,
4026
- ...refWarnings ?? []
4586
+ ...clearResult.warnings,
4587
+ ...refResult.warnings,
4588
+ ...actionabilityWarnings,
4589
+ ...persistResult.warnings
4027
4590
  ];
4028
4591
  return {
4029
4592
  format: "ax",
@@ -4044,6 +4607,7 @@ var InspectService = class {
4044
4607
  const html = await captureHtml(selection.tabId, {
4045
4608
  selector: input.selector,
4046
4609
  debuggerCommand,
4610
+ executionContextId,
4047
4611
  onEvaluationFailed: () => {
4048
4612
  const error2 = new InspectError(
4049
4613
  "EVALUATION_FAILED",
@@ -4148,11 +4712,27 @@ var InspectService = class {
4148
4712
  };
4149
4713
  }
4150
4714
  async consoleList(input) {
4151
- this.requireSession(input.sessionId);
4152
- const selection = await this.resolveTab(input.targetHint);
4715
+ const session = this.requireSession(input.sessionId);
4716
+ const tabCount = this.extensionBridge?.getStatus().tabs.length ?? 0;
4717
+ if (!input.targetHint && typeof session.selectedTabId !== "number" && tabCount > 1) {
4718
+ const error = new InspectError(
4719
+ "TAB_NOT_FOUND",
4720
+ "Console inspection requires an explicit target or a session-selected tab.",
4721
+ { retryable: false }
4722
+ );
4723
+ this.recordError(error);
4724
+ throw error;
4725
+ }
4726
+ const selection = await this.resolveTab(input.sessionId, input.targetHint);
4153
4727
  await this.enableConsole(selection.tabId);
4728
+ const since = this.resolveConsoleSince({
4729
+ session,
4730
+ tabId: selection.tabId,
4731
+ requestedSince: input.since,
4732
+ tabLastActiveAt: selection.tab.last_active_at
4733
+ });
4154
4734
  const events = this.ensureDebugger().getConsoleEvents(selection.tabId);
4155
- const entries = events.map((event) => toConsoleEntry(event)).filter((entry) => entry !== null);
4735
+ const entries = events.filter((event) => this.isEventOnOrAfter(event.timestamp, since)).map((event) => toConsoleEntry(event)).filter((entry) => entry !== null);
4156
4736
  const result = {
4157
4737
  entries,
4158
4738
  warnings: selection.warnings
@@ -4162,7 +4742,7 @@ var InspectService = class {
4162
4742
  }
4163
4743
  async networkHar(input) {
4164
4744
  this.requireSession(input.sessionId);
4165
- const selection = await this.resolveTab(input.targetHint);
4745
+ const selection = await this.resolveTab(input.sessionId, input.targetHint);
4166
4746
  await this.enableNetwork(selection.tabId);
4167
4747
  const events = this.ensureDebugger().getNetworkEvents(selection.tabId);
4168
4748
  const har = buildHar(events, selection.tab.title);
@@ -4189,18 +4769,9 @@ var InspectService = class {
4189
4769
  }
4190
4770
  async evaluate(input) {
4191
4771
  this.requireSession(input.sessionId);
4192
- const selection = await this.resolveTab(input.targetHint);
4772
+ const selection = await this.resolveTab(input.sessionId, input.targetHint);
4193
4773
  const expression = input.expression ?? "undefined";
4194
- await this.debuggerCommand(selection.tabId, "Runtime.enable", {});
4195
- const result = await this.debuggerCommand(
4196
- selection.tabId,
4197
- "Runtime.evaluate",
4198
- {
4199
- expression,
4200
- returnByValue: true,
4201
- awaitPromise: true
4202
- }
4203
- );
4774
+ const result = await this.evaluateInMainFrame(selection.tabId, expression);
4204
4775
  if (result && typeof result === "object" && "exceptionDetails" in result) {
4205
4776
  const output2 = {
4206
4777
  exception: result.exceptionDetails,
@@ -4218,81 +4789,59 @@ var InspectService = class {
4218
4789
  }
4219
4790
  async extractContent(input) {
4220
4791
  this.requireSession(input.sessionId);
4221
- const selection = await this.resolveTab(input.targetHint);
4792
+ const selection = await this.resolveTab(input.sessionId, input.targetHint);
4793
+ const consistency = input.consistency ?? "quiesce";
4222
4794
  const debuggerCommand = this.debuggerCommand.bind(this);
4223
- const html = await captureHtml(selection.tabId, {
4224
- debuggerCommand,
4225
- onEvaluationFailed: () => {
4226
- const error = new InspectError(
4227
- "EVALUATION_FAILED",
4228
- "Failed to evaluate HTML snapshot.",
4229
- { retryable: false }
4230
- );
4231
- this.recordError(error);
4795
+ const executionContextId = await this.resolveMainFrameExecutionContextId(
4796
+ selection.tabId
4797
+ );
4798
+ const url = selection.tab.url ?? "about:blank";
4799
+ const work = async () => {
4800
+ if (consistency === "quiesce") {
4801
+ await this.waitForDomSettled(selection.tabId, executionContextId);
4802
+ }
4803
+ const html = await captureHtml(selection.tabId, {
4804
+ debuggerCommand,
4805
+ executionContextId,
4806
+ onEvaluationFailed: () => {
4807
+ const error = new InspectError(
4808
+ "EVALUATION_FAILED",
4809
+ "Failed to evaluate HTML snapshot.",
4810
+ { retryable: false }
4811
+ );
4812
+ this.recordError(error);
4813
+ throw error;
4814
+ }
4815
+ });
4816
+ try {
4817
+ const { article, semanticMainCandidate } = parseExtractContentSource({
4818
+ html,
4819
+ url
4820
+ });
4821
+ return renderExtractContent({
4822
+ format: input.format,
4823
+ article,
4824
+ semanticMainCandidate,
4825
+ includeMetadata: input.includeMetadata,
4826
+ warnings: selection.warnings
4827
+ });
4828
+ } catch (error) {
4829
+ if (error instanceof InspectError) {
4830
+ this.recordError(error);
4831
+ }
4232
4832
  throw error;
4233
4833
  }
4234
- });
4235
- const url = selection.tab.url ?? "about:blank";
4236
- let article = null;
4237
- try {
4238
- const dom = new import_jsdom.JSDOM(html, { url });
4239
- const reader = new import_readability.Readability(dom.window.document);
4240
- article = reader.parse();
4241
- } catch {
4242
- const err = new InspectError(
4243
- "EVALUATION_FAILED",
4244
- "Failed to parse page content.",
4245
- { retryable: false }
4246
- );
4247
- this.recordError(err);
4248
- throw err;
4249
- }
4250
- if (!article) {
4251
- const err = new InspectError(
4252
- "NOT_SUPPORTED",
4253
- "Readability could not extract content.",
4254
- { retryable: false }
4255
- );
4256
- this.recordError(err);
4257
- throw err;
4258
- }
4259
- let content = "";
4260
- if (input.format === "article_json") {
4261
- content = JSON.stringify(article, null, 2);
4262
- } else if (input.format === "text") {
4263
- content = article.textContent ?? "";
4264
- } else {
4265
- const turndown = new import_turndown.default();
4266
- content = turndown.turndown(article.content ?? "");
4267
- }
4268
- const warnings = selection.warnings ?? [];
4269
- const includeMetadata = input.includeMetadata ?? true;
4270
- const output = {
4271
- content,
4272
- ...includeMetadata ? {
4273
- title: article.title ?? void 0,
4274
- byline: article.byline ?? void 0,
4275
- excerpt: article.excerpt ?? void 0,
4276
- siteName: article.siteName ?? void 0
4277
- } : {},
4278
- ...warnings.length > 0 ? { warnings } : {}
4279
4834
  };
4835
+ const output = consistency === "quiesce" ? await driveMutex.runExclusive(work) : await work();
4280
4836
  this.markInspectConnected(input.sessionId);
4281
4837
  return output;
4282
4838
  }
4283
4839
  async pageState(input) {
4284
4840
  this.requireSession(input.sessionId);
4285
- const selection = await this.resolveTab(input.targetHint);
4286
- await this.debuggerCommand(selection.tabId, "Runtime.enable", {});
4287
- const expression = PAGE_STATE_SCRIPT;
4288
- const result = await this.debuggerCommand(
4841
+ const selection = await this.resolveTab(input.sessionId, input.targetHint);
4842
+ const result = await this.evaluateInMainFrame(
4289
4843
  selection.tabId,
4290
- "Runtime.evaluate",
4291
- {
4292
- expression,
4293
- returnByValue: true,
4294
- awaitPromise: true
4295
- }
4844
+ buildPageStateScript({ includeValues: input.includeValues })
4296
4845
  );
4297
4846
  if (result && typeof result === "object" && "exceptionDetails" in result) {
4298
4847
  const error = new InspectError(
@@ -4304,16 +4853,22 @@ var InspectService = class {
4304
4853
  throw error;
4305
4854
  }
4306
4855
  const value = result?.result?.value;
4307
- const raw = value && typeof value === "object" ? value : {};
4856
+ const parsed = PageStateSchema.safeParse(value);
4857
+ if (!parsed.success) {
4858
+ const error = new InspectError(
4859
+ "EVALUATION_FAILED",
4860
+ "Captured page state did not match the expected schema.",
4861
+ { retryable: false }
4862
+ );
4863
+ this.recordError(error);
4864
+ throw error;
4865
+ }
4308
4866
  const warnings = [
4309
- ...Array.isArray(raw.warnings) ? raw.warnings : [],
4867
+ ...parsed.data.warnings ?? [],
4310
4868
  ...selection.warnings ?? []
4311
4869
  ];
4312
4870
  const output = {
4313
- forms: Array.isArray(raw.forms) ? raw.forms : [],
4314
- localStorage: Array.isArray(raw.localStorage) ? raw.localStorage : [],
4315
- sessionStorage: Array.isArray(raw.sessionStorage) ? raw.sessionStorage : [],
4316
- cookies: Array.isArray(raw.cookies) ? raw.cookies : [],
4871
+ ...parsed.data,
4317
4872
  ...warnings.length > 0 ? { warnings } : {}
4318
4873
  };
4319
4874
  this.markInspectConnected(input.sessionId);
@@ -4321,7 +4876,7 @@ var InspectService = class {
4321
4876
  }
4322
4877
  async performanceMetrics(input) {
4323
4878
  this.requireSession(input.sessionId);
4324
- const selection = await this.resolveTab(input.targetHint);
4879
+ const selection = await this.resolveTab(input.sessionId, input.targetHint);
4325
4880
  await this.debuggerCommand(selection.tabId, "Performance.enable", {});
4326
4881
  const result = await this.debuggerCommand(
4327
4882
  selection.tabId,
@@ -4338,9 +4893,13 @@ var InspectService = class {
4338
4893
  }
4339
4894
  async screenshot(input) {
4340
4895
  this.requireSession(input.sessionId);
4341
- const selection = await this.resolveTab(input.targetHint);
4896
+ const selection = await this.resolveTab(input.sessionId, input.targetHint);
4342
4897
  const format = input.format ?? "png";
4343
- const writeArtifact = async (data2) => {
4898
+ const createScreenshotError = (code, message, retryable = false, details) => new InspectError(code, message, {
4899
+ retryable,
4900
+ ...details ? { details } : {}
4901
+ });
4902
+ const writeArtifact = async (data) => {
4344
4903
  try {
4345
4904
  const rootDir = await ensureArtifactRootDir(input.sessionId);
4346
4905
  const artifactId = (0, import_crypto3.randomUUID)();
@@ -4349,7 +4908,7 @@ var InspectService = class {
4349
4908
  rootDir,
4350
4909
  `screenshot-${artifactId}.${extension}`
4351
4910
  );
4352
- await (0, import_promises3.writeFile)(filePath, Buffer.from(data2, "base64"));
4911
+ await (0, import_promises3.writeFile)(filePath, Buffer.from(data, "base64"));
4353
4912
  const mime = format === "jpeg" ? "image/jpeg" : `image/${format}`;
4354
4913
  const output = {
4355
4914
  artifact_id: artifactId,
@@ -4367,143 +4926,138 @@ var InspectService = class {
4367
4926
  throw error;
4368
4927
  }
4369
4928
  };
4370
- if (input.selector) {
4929
+ const captureViaExtension = async (mode, failureMessage) => {
4371
4930
  if (!this.extensionBridge?.request) {
4372
- const error = new InspectError(
4931
+ throw createScreenshotError(
4373
4932
  "NOT_SUPPORTED",
4374
- "Element screenshots require an extension that supports drive.screenshot."
4933
+ "Screenshots require an extension that supports drive.screenshot."
4375
4934
  );
4376
- this.recordError(error);
4377
- throw error;
4378
4935
  }
4379
4936
  const response = await this.extensionBridge.request(
4380
4937
  "drive.screenshot",
4381
4938
  {
4382
4939
  tab_id: selection.tabId,
4383
- mode: "element",
4384
- selector: input.selector,
4940
+ mode,
4941
+ ...input.selector ? { selector: input.selector } : {},
4385
4942
  format,
4386
4943
  ...typeof input.quality === "number" ? { quality: input.quality } : {}
4387
4944
  },
4388
4945
  12e4
4389
4946
  );
4390
4947
  if (response.status === "error") {
4391
- const error = new InspectError(
4948
+ const error = createScreenshotError(
4392
4949
  response.error?.code ?? "INSPECT_UNAVAILABLE",
4393
- response.error?.message ?? "Failed to capture element screenshot.",
4394
- {
4395
- retryable: response.error?.retryable ?? false,
4396
- ...response.error?.details ? { details: response.error.details } : {}
4397
- }
4950
+ response.error?.message ?? failureMessage,
4951
+ response.error?.retryable ?? false,
4952
+ response.error?.details
4398
4953
  );
4399
4954
  this.recordError(error);
4400
4955
  throw error;
4401
4956
  }
4402
- const result2 = response.result;
4403
- if (!result2?.data_base64 || typeof result2.data_base64 !== "string") {
4957
+ const result = response.result;
4958
+ if (!result?.data_base64 || typeof result.data_base64 !== "string") {
4959
+ const error = createScreenshotError(
4960
+ "INSPECT_UNAVAILABLE",
4961
+ failureMessage
4962
+ );
4963
+ this.recordError(error);
4964
+ throw error;
4965
+ }
4966
+ return await writeArtifact(result.data_base64);
4967
+ };
4968
+ const shouldFallbackFromExtensionScreenshot = (error) => [
4969
+ "NOT_SUPPORTED",
4970
+ "NOT_IMPLEMENTED",
4971
+ "INSPECT_UNAVAILABLE",
4972
+ "PERMISSION_REQUIRED",
4973
+ "RATE_LIMITED"
4974
+ ].includes(error.code);
4975
+ const shouldPreserveExtensionScreenshotError = (fallbackError) => [
4976
+ "INSPECT_UNAVAILABLE",
4977
+ "ATTACH_DENIED",
4978
+ "NOT_SUPPORTED",
4979
+ "NOT_IMPLEMENTED"
4980
+ ].includes(fallbackError.code);
4981
+ const captureViaDebugger = async () => {
4982
+ await this.debuggerCommand(selection.tabId, "Page.enable", {});
4983
+ let captureParams = {
4984
+ format,
4985
+ fromSurface: true
4986
+ };
4987
+ if (format !== "png" && typeof input.quality === "number") {
4988
+ captureParams = { ...captureParams, quality: input.quality };
4989
+ }
4990
+ if (input.target === "full") {
4991
+ const layout = await this.debuggerCommand(
4992
+ selection.tabId,
4993
+ "Page.getLayoutMetrics",
4994
+ {}
4995
+ );
4996
+ const contentSize = layout?.contentSize;
4997
+ if (contentSize) {
4998
+ captureParams = {
4999
+ ...captureParams,
5000
+ clip: {
5001
+ x: 0,
5002
+ y: 0,
5003
+ width: contentSize.width,
5004
+ height: contentSize.height,
5005
+ scale: 1
5006
+ }
5007
+ };
5008
+ } else {
5009
+ captureParams = { ...captureParams, captureBeyondViewport: true };
5010
+ }
5011
+ }
5012
+ const result = await this.debuggerCommand(
5013
+ selection.tabId,
5014
+ "Page.captureScreenshot",
5015
+ captureParams
5016
+ );
5017
+ const data = result.data;
5018
+ if (!data) {
4404
5019
  const error = new InspectError(
4405
5020
  "INSPECT_UNAVAILABLE",
4406
- "Failed to capture element screenshot."
5021
+ "Failed to capture screenshot.",
5022
+ { retryable: false }
4407
5023
  );
4408
5024
  this.recordError(error);
4409
5025
  throw error;
4410
5026
  }
4411
- return await writeArtifact(result2.data_base64);
5027
+ return await writeArtifact(data);
5028
+ };
5029
+ if (input.selector) {
5030
+ return await captureViaExtension(
5031
+ "element",
5032
+ "Failed to capture element screenshot."
5033
+ );
4412
5034
  }
4413
- if (input.target === "full" && this.extensionBridge?.request) {
5035
+ let extensionScreenshotError;
5036
+ if (this.extensionBridge?.request) {
4414
5037
  try {
4415
- const response = await this.extensionBridge.request(
4416
- "drive.screenshot",
4417
- {
4418
- tab_id: selection.tabId,
4419
- mode: "full_page",
4420
- format,
4421
- ...typeof input.quality === "number" ? { quality: input.quality } : {}
4422
- },
4423
- 12e4
5038
+ return await captureViaExtension(
5039
+ input.target === "full" ? "full_page" : "viewport",
5040
+ input.target === "full" ? "Failed to capture full page screenshot." : "Failed to capture viewport screenshot."
4424
5041
  );
4425
- if (response.status === "error") {
4426
- const error = new InspectError(
4427
- response.error?.code ?? "INSPECT_UNAVAILABLE",
4428
- response.error?.message ?? "Failed to capture full page screenshot.",
4429
- {
4430
- retryable: response.error?.retryable ?? false,
4431
- ...response.error?.details ? { details: response.error.details } : {}
4432
- }
4433
- );
4434
- this.recordError(error);
5042
+ } catch (error) {
5043
+ if (!(error instanceof InspectError)) {
4435
5044
  throw error;
4436
5045
  }
4437
- const result2 = response.result;
4438
- if (!result2?.data_base64 || typeof result2.data_base64 !== "string") {
4439
- const error = new InspectError(
4440
- "INSPECT_UNAVAILABLE",
4441
- "Failed to capture full page screenshot."
4442
- );
4443
- this.recordError(error);
5046
+ if (!shouldFallbackFromExtensionScreenshot(error)) {
4444
5047
  throw error;
4445
5048
  }
4446
- return await writeArtifact(result2.data_base64);
4447
- } catch (error) {
4448
- if (error instanceof InspectError) {
4449
- const code = String(error.code);
4450
- if (!error.retryable && ![
4451
- "NOT_SUPPORTED",
4452
- "NOT_IMPLEMENTED",
4453
- "INSPECT_UNAVAILABLE",
4454
- "RATE_LIMITED"
4455
- ].includes(code)) {
4456
- throw error;
4457
- }
4458
- }
5049
+ extensionScreenshotError = error;
4459
5050
  }
4460
5051
  }
4461
- await this.debuggerCommand(selection.tabId, "Page.enable", {});
4462
- let captureParams = {
4463
- format,
4464
- fromSurface: true
4465
- };
4466
- if (format !== "png" && typeof input.quality === "number") {
4467
- captureParams = { ...captureParams, quality: input.quality };
4468
- }
4469
- if (input.target === "full") {
4470
- const layout = await this.debuggerCommand(
4471
- selection.tabId,
4472
- "Page.getLayoutMetrics",
4473
- {}
4474
- );
4475
- const contentSize = layout?.contentSize;
4476
- if (contentSize) {
4477
- captureParams = {
4478
- ...captureParams,
4479
- clip: {
4480
- x: 0,
4481
- y: 0,
4482
- width: contentSize.width,
4483
- height: contentSize.height,
4484
- scale: 1
4485
- }
4486
- };
4487
- } else {
4488
- captureParams = { ...captureParams, captureBeyondViewport: true };
5052
+ try {
5053
+ return await captureViaDebugger();
5054
+ } catch (error) {
5055
+ if (extensionScreenshotError && error instanceof InspectError && shouldPreserveExtensionScreenshotError(error)) {
5056
+ this.recordError(extensionScreenshotError);
5057
+ throw extensionScreenshotError;
4489
5058
  }
4490
- }
4491
- const result = await this.debuggerCommand(
4492
- selection.tabId,
4493
- "Page.captureScreenshot",
4494
- captureParams
4495
- );
4496
- const data = result.data;
4497
- if (!data) {
4498
- const error = new InspectError(
4499
- "INSPECT_UNAVAILABLE",
4500
- "Failed to capture screenshot.",
4501
- { retryable: false }
4502
- );
4503
- this.recordError(error);
4504
5059
  throw error;
4505
5060
  }
4506
- return await writeArtifact(data);
4507
5061
  }
4508
5062
  ensureDebugger() {
4509
5063
  if (!this.debugger) {
@@ -4513,58 +5067,20 @@ var InspectService = class {
4513
5067
  }
4514
5068
  return this.debugger;
4515
5069
  }
4516
- async resolveTab(hint) {
4517
- if (!this.extensionBridge || !this.extensionBridge.isConnected()) {
4518
- const error = new InspectError(
4519
- "EXTENSION_DISCONNECTED",
4520
- "Extension is not connected.",
4521
- { retryable: true }
4522
- );
4523
- this.recordError(error);
4524
- throw error;
4525
- }
4526
- const tabs = this.extensionBridge.getStatus().tabs ?? [];
4527
- if (!Array.isArray(tabs) || tabs.length === 0) {
4528
- const error = new InspectError(
4529
- "TAB_NOT_FOUND",
4530
- "No tabs available to inspect."
4531
- );
4532
- this.recordError(error);
4533
- throw error;
4534
- }
4535
- const candidates = tabs.map((tab2) => ({
4536
- id: String(tab2.tab_id),
4537
- url: tab2.url ?? "",
4538
- title: tab2.title,
4539
- lastSeenAt: tab2.last_active_at ? Date.parse(tab2.last_active_at) : void 0
4540
- }));
4541
- const ranked = pickBestTarget(candidates, hint);
4542
- if (!ranked) {
4543
- const error = new InspectError("TAB_NOT_FOUND", "No matching tab found.");
4544
- this.recordError(error);
4545
- throw error;
4546
- }
4547
- const tabId = Number(ranked.candidate.id);
4548
- if (!Number.isFinite(tabId)) {
4549
- const error = new InspectError(
4550
- "TAB_NOT_FOUND",
4551
- "Resolved tab id is invalid."
4552
- );
4553
- this.recordError(error);
5070
+ async resolveTab(sessionId, hint) {
5071
+ try {
5072
+ return selectInspectTab({
5073
+ sessionId,
5074
+ targetHint: hint,
5075
+ registry: this.registry,
5076
+ extensionBridge: this.extensionBridge
5077
+ });
5078
+ } catch (error) {
5079
+ if (error instanceof InspectError) {
5080
+ this.recordError(error);
5081
+ }
4554
5082
  throw error;
4555
5083
  }
4556
- const tab = tabs.find((entry) => entry.tab_id === tabId) ?? tabs[0];
4557
- const warnings = [];
4558
- if (!hint) {
4559
- warnings.push("No target hint provided; using the most recent tab.");
4560
- } else if (ranked.score < 20) {
4561
- warnings.push("Weak target match; using best available tab.");
4562
- }
4563
- return {
4564
- tabId,
4565
- tab,
4566
- warnings: warnings.length > 0 ? warnings : void 0
4567
- };
4568
5084
  }
4569
5085
  async enableConsole(tabId) {
4570
5086
  await this.debuggerCommand(tabId, "Runtime.enable", {});
@@ -4576,6 +5092,186 @@ var InspectService = class {
4576
5092
  async enableAccessibility(tabId) {
4577
5093
  await this.debuggerCommand(tabId, "Accessibility.enable", {});
4578
5094
  }
5095
+ async collectActionableSnapshotRefs(tabId, bindings) {
5096
+ try {
5097
+ const result = await this.debuggerCommand(tabId, "Runtime.evaluate", {
5098
+ expression: `(() => {
5099
+ const refs = new Set(${JSON.stringify(
5100
+ bindings.map((binding) => binding.ref)
5101
+ )});
5102
+ const isVisible = (element) => {
5103
+ if (!(element instanceof HTMLElement)) {
5104
+ return false;
5105
+ }
5106
+ const style = window.getComputedStyle(element);
5107
+ if (style.visibility === 'hidden' || style.display === 'none') {
5108
+ return false;
5109
+ }
5110
+ const rect = element.getBoundingClientRect();
5111
+ if (rect.width === 0 && rect.height === 0) {
5112
+ return false;
5113
+ }
5114
+ if (
5115
+ element.offsetWidth === 0 &&
5116
+ element.offsetHeight === 0 &&
5117
+ element.getClientRects().length === 0
5118
+ ) {
5119
+ return false;
5120
+ }
5121
+ let current = element;
5122
+ while (current) {
5123
+ const currentStyle = window.getComputedStyle(current);
5124
+ if (currentStyle.display === 'none') {
5125
+ return false;
5126
+ }
5127
+ if (
5128
+ currentStyle.visibility === 'hidden' ||
5129
+ currentStyle.visibility === 'collapse'
5130
+ ) {
5131
+ return false;
5132
+ }
5133
+ const opacity = Number.parseFloat(currentStyle.opacity ?? '1');
5134
+ if (Number.isFinite(opacity) && opacity <= 0) {
5135
+ return false;
5136
+ }
5137
+ if (currentStyle.pointerEvents === 'none') {
5138
+ return false;
5139
+ }
5140
+ current = current.parentElement;
5141
+ }
5142
+ return true;
5143
+ };
5144
+
5145
+ return Array.from(document.querySelectorAll('[data-bv-ref]'))
5146
+ .filter((element) => {
5147
+ const ref = element.getAttribute('data-bv-ref');
5148
+ return typeof ref === 'string' && refs.has(ref) && isVisible(element);
5149
+ })
5150
+ .map((element) => element.getAttribute('data-bv-ref'));
5151
+ })() /* browser-bridge:collect-actionable-snapshot-refs */`,
5152
+ returnByValue: true,
5153
+ awaitPromise: true
5154
+ });
5155
+ const rawRefs = result.value;
5156
+ if (!Array.isArray(rawRefs)) {
5157
+ return {
5158
+ warnings: [
5159
+ "Interactive AX snapshot could not verify live actionability."
5160
+ ]
5161
+ };
5162
+ }
5163
+ const refs = rawRefs.filter(
5164
+ (ref) => typeof ref === "string"
5165
+ );
5166
+ return {
5167
+ actionableRefs: new Set(refs),
5168
+ warnings: []
5169
+ };
5170
+ } catch {
5171
+ return {
5172
+ warnings: ["Interactive AX snapshot could not prune hidden controls."]
5173
+ };
5174
+ }
5175
+ }
5176
+ async waitForDomSettled(tabId, executionContextId) {
5177
+ const result = await this.debuggerCommand(tabId, "Runtime.evaluate", {
5178
+ expression: `(() => {
5179
+ const quietMs = 100;
5180
+ const timeoutMs = 2000;
5181
+ return new Promise((resolve) => {
5182
+ const doc = document;
5183
+ const root = doc.documentElement || doc.body || doc;
5184
+ let finished = false;
5185
+ let quietTimer;
5186
+ let timeoutTimer;
5187
+ let raf1 = 0;
5188
+ let raf2 = 0;
5189
+ const finish = () => {
5190
+ if (finished) {
5191
+ return;
5192
+ }
5193
+ finished = true;
5194
+ if (observer) {
5195
+ observer.disconnect();
5196
+ }
5197
+ clearTimeout(quietTimer);
5198
+ clearTimeout(timeoutTimer);
5199
+ if (raf1) cancelAnimationFrame(raf1);
5200
+ if (raf2) cancelAnimationFrame(raf2);
5201
+ resolve(true);
5202
+ };
5203
+ const scheduleQuiet = () => {
5204
+ clearTimeout(quietTimer);
5205
+ quietTimer = setTimeout(() => {
5206
+ raf1 = requestAnimationFrame(() => {
5207
+ raf2 = requestAnimationFrame(finish);
5208
+ });
5209
+ }, quietMs);
5210
+ };
5211
+ const observer =
5212
+ typeof MutationObserver === 'function'
5213
+ ? new MutationObserver(() => {
5214
+ scheduleQuiet();
5215
+ })
5216
+ : null;
5217
+ if (observer && root) {
5218
+ observer.observe(root, {
5219
+ subtree: true,
5220
+ childList: true,
5221
+ attributes: true,
5222
+ characterData: true,
5223
+ });
5224
+ }
5225
+ scheduleQuiet();
5226
+ timeoutTimer = setTimeout(finish, timeoutMs);
5227
+ });
5228
+ })()`,
5229
+ returnByValue: true,
5230
+ awaitPromise: true,
5231
+ ...typeof executionContextId === "number" ? { contextId: executionContextId } : {}
5232
+ });
5233
+ if (result && typeof result === "object" && "exceptionDetails" in result) {
5234
+ const error = new InspectError(
5235
+ "EVALUATION_FAILED",
5236
+ "Failed while waiting for the page to settle.",
5237
+ { retryable: false }
5238
+ );
5239
+ this.recordError(error);
5240
+ throw error;
5241
+ }
5242
+ }
5243
+ async evaluateInMainFrame(tabId, expression) {
5244
+ await this.debuggerCommand(tabId, "Runtime.enable", {});
5245
+ const executionContextId = await this.resolveMainFrameExecutionContextId(tabId);
5246
+ return await this.debuggerCommand(tabId, "Runtime.evaluate", {
5247
+ expression,
5248
+ returnByValue: true,
5249
+ awaitPromise: true,
5250
+ ...typeof executionContextId === "number" ? { contextId: executionContextId } : {}
5251
+ });
5252
+ }
5253
+ async resolveMainFrameExecutionContextId(tabId) {
5254
+ const debuggerBridge = this.ensureDebugger();
5255
+ const runOptional = async (method, params) => {
5256
+ const result = await debuggerBridge.command(tabId, method, params);
5257
+ if (!result.ok) {
5258
+ return void 0;
5259
+ }
5260
+ return result.result;
5261
+ };
5262
+ await runOptional("Page.enable", {});
5263
+ const frameTree = await runOptional("Page.getFrameTree", {});
5264
+ const frameId = frameTree?.frameTree?.frame?.id;
5265
+ if (typeof frameId !== "string" || frameId.length === 0) {
5266
+ return void 0;
5267
+ }
5268
+ const isolatedWorld = await runOptional("Page.createIsolatedWorld", {
5269
+ frameId,
5270
+ worldName: "browser_bridge_inspect"
5271
+ });
5272
+ const contextId = isolatedWorld?.executionContextId;
5273
+ return typeof contextId === "number" ? contextId : void 0;
5274
+ }
4579
5275
  async debuggerCommand(tabId, method, params, timeoutMs) {
4580
5276
  const debuggerBridge = this.ensureDebugger();
4581
5277
  const result = await debuggerBridge.command(
@@ -4654,6 +5350,32 @@ var InspectService = class {
4654
5350
  throw wrapped;
4655
5351
  }
4656
5352
  }
5353
+ resolveConsoleSince(options) {
5354
+ if (typeof options.requestedSince === "string") {
5355
+ return options.requestedSince;
5356
+ }
5357
+ const key = `${options.session.id}:${options.tabId}`;
5358
+ const existing = this.consoleSinceBySessionTab.get(key);
5359
+ if (existing) {
5360
+ return existing;
5361
+ }
5362
+ const candidates = [options.session.createdAt.toISOString()];
5363
+ if (typeof options.tabLastActiveAt === "string") {
5364
+ candidates.push(options.tabLastActiveAt);
5365
+ }
5366
+ const baseline = candidates.map((value) => ({ value, time: Date.parse(value) })).filter((entry) => Number.isFinite(entry.time)).sort((a, b) => b.time - a.time)[0]?.value;
5367
+ const resolved = baseline ?? options.session.createdAt.toISOString();
5368
+ this.consoleSinceBySessionTab.set(key, resolved);
5369
+ return resolved;
5370
+ }
5371
+ isEventOnOrAfter(timestamp, since) {
5372
+ const eventTime = Date.parse(timestamp);
5373
+ const sinceTime = Date.parse(since);
5374
+ if (!Number.isFinite(eventTime) || !Number.isFinite(sinceTime)) {
5375
+ return true;
5376
+ }
5377
+ return eventTime >= sinceTime;
5378
+ }
4657
5379
  };
4658
5380
  var createInspectService = (options) => new InspectService(options);
4659
5381
 
@@ -4667,7 +5389,11 @@ var sendArtifactsError = (res, code, message, details, retryable = false) => {
4667
5389
  });
4668
5390
  };
4669
5391
  var registerArtifactsRoutes = (router, options = {}) => {
4670
- const inspect = options.inspectService ?? (options.registry ? createInspectService({ registry: options.registry }) : void 0);
5392
+ const inspect = options.inspectService ?? (options.registry ? createInspectService({
5393
+ registry: options.registry,
5394
+ extensionBridge: options.extensionBridge,
5395
+ debuggerBridge: options.debuggerBridge
5396
+ }) : void 0);
4671
5397
  router.post("/artifacts/screenshot", async (req, res) => {
4672
5398
  if (!isRecord2(req.body)) {
4673
5399
  sendArtifactsError(
@@ -4761,6 +5487,7 @@ var readCapability = (capabilities, name) => {
4761
5487
  const candidate = capabilities[name];
4762
5488
  return typeof candidate === "boolean" ? candidate : void 0;
4763
5489
  };
5490
+ var hasCapturePermissionErrorReason = (details) => typeof details?.reason === "string" && details.reason === "capture_visible_tab_permission_required";
4764
5491
  var hasEndpoint = (endpoint) => Boolean(
4765
5492
  endpoint && typeof endpoint.host === "string" && endpoint.host.length > 0 && typeof endpoint.port === "number" && Number.isFinite(endpoint.port)
4766
5493
  );
@@ -4773,9 +5500,7 @@ var toRuntimeEndpoint = (endpoint) => {
4773
5500
  port: endpoint.port,
4774
5501
  base_url: endpoint.baseUrl,
4775
5502
  host_source: endpoint.hostSource,
4776
- port_source: endpoint.portSource,
4777
- metadata_path: endpoint.metadataPath,
4778
- isolated_mode: endpoint.isolatedMode
5503
+ port_source: endpoint.portSource
4779
5504
  };
4780
5505
  };
4781
5506
  var toRuntimeProcess = (process2) => {
@@ -4839,23 +5564,6 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
4839
5564
  }
4840
5565
  });
4841
5566
  }
4842
- if (callerEndpoint?.metadataPath && coreEndpoint?.metadataPath) {
4843
- const matches = callerEndpoint.metadataPath === coreEndpoint.metadataPath;
4844
- checks.push({
4845
- name: "runtime.caller.metadata_path_match",
4846
- ok: matches,
4847
- message: matches ? "Caller metadata path matches the active core runtime metadata path." : "Caller metadata path differs from core runtime metadata path (shared core across worktrees).",
4848
- details: {
4849
- caller_metadata_path: callerEndpoint.metadataPath,
4850
- core_metadata_path: coreEndpoint.metadataPath
4851
- }
4852
- });
4853
- if (!matches) {
4854
- warnings.push(
4855
- "CLI is talking to a core process started from a different worktree metadata path. If daemon auto-start fails, retry with --no-daemon or enable isolated mode."
4856
- );
4857
- }
4858
- }
4859
5567
  if (extensionConnected && hasEndpoint(coreEndpoint) && hasEndpoint(extensionEndpoint)) {
4860
5568
  const matches = coreEndpoint.host === extensionEndpoint.host && coreEndpoint.port === extensionEndpoint.port;
4861
5569
  checks.push({
@@ -4903,7 +5611,7 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
4903
5611
  checks.push({
4904
5612
  name: "inspect.capability",
4905
5613
  ok: capabilityNegotiated && inspectEnabled,
4906
- message: capabilityNegotiated && inspectEnabled ? "Inspect debugger capability is enabled." : capabilityNegotiated ? "Inspect debugger capability is disabled in extension options." : "Inspect capability is unknown until extension capability negotiation completes.",
5614
+ message: capabilityNegotiated && inspectEnabled ? "Inspect capability is available." : capabilityNegotiated ? "Inspect capability is unavailable from the connected extension/runtime." : "Inspect capability is unknown until extension capability negotiation completes.",
4907
5615
  details: {
4908
5616
  required_capabilities: ["debugger.attach", "debugger.command"],
4909
5617
  debugger_attach: inspectAttachCapability,
@@ -4912,7 +5620,7 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
4912
5620
  });
4913
5621
  if (capabilityNegotiated && !inspectEnabled) {
4914
5622
  warnings.push(
4915
- "Inspect commands require debugger capability. Enable debugger-based inspect in extension options to use inspect.* routes."
5623
+ "Inspect commands are unavailable from the connected extension/runtime. Reload or update the extension, then verify inspect.capability before using inspect.* routes."
4916
5624
  );
4917
5625
  }
4918
5626
  }
@@ -4945,9 +5653,15 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
4945
5653
  code: context.driveLastError.code,
4946
5654
  retryable: context.driveLastError.retryable,
4947
5655
  at: context.driveLastError.at,
5656
+ ...context.driveLastError.details ?? {},
4948
5657
  ...ageMs !== void 0 ? { age_ms: ageMs } : {}
4949
5658
  }
4950
5659
  });
5660
+ if (hasCapturePermissionErrorReason(context.driveLastError.details)) {
5661
+ warnings.push(
5662
+ "Screenshot capture permission is missing. Reload the Browser Bridge extension in chrome://extensions and retry screenshot capture."
5663
+ );
5664
+ }
4951
5665
  }
4952
5666
  if (context.inspectLastError) {
4953
5667
  const ageMs = getErrorAgeMs(context.inspectLastError.at);
@@ -4965,9 +5679,15 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
4965
5679
  code: context.inspectLastError.code,
4966
5680
  retryable: context.inspectLastError.retryable,
4967
5681
  at: context.inspectLastError.at,
5682
+ ...context.inspectLastError.details ?? {},
4968
5683
  ...ageMs !== void 0 ? { age_ms: ageMs } : {}
4969
5684
  }
4970
5685
  });
5686
+ if (hasCapturePermissionErrorReason(context.inspectLastError.details)) {
5687
+ warnings.push(
5688
+ "Screenshot capture permission is missing. Reload the Browser Bridge extension in chrome://extensions and retry screenshot capture."
5689
+ );
5690
+ }
4971
5691
  }
4972
5692
  if (context.recoveryAttempt) {
4973
5693
  checks.push({
@@ -5001,6 +5721,7 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
5001
5721
  } : void 0,
5002
5722
  extension: {
5003
5723
  connected: extensionConnected,
5724
+ extension_id: context.extension?.extensionId,
5004
5725
  version: context.extension?.version,
5005
5726
  last_seen_at: context.extension?.lastSeenAt
5006
5727
  },
@@ -5037,6 +5758,7 @@ var buildDiagnosticReport = (sessionId, context = {}) => {
5037
5758
  process: toRuntimeProcess(context.runtime.core.process)
5038
5759
  } : void 0,
5039
5760
  extension: context.runtime.extension ? {
5761
+ extension_id: context.runtime.extension.extensionId,
5040
5762
  version: context.runtime.extension.version,
5041
5763
  protocol_version: context.runtime.extension.protocolVersion,
5042
5764
  capability_negotiated: context.runtime.extension.capabilityNegotiated,
@@ -5079,17 +5801,84 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
5079
5801
  const extensionStatus = options.extensionBridge?.getStatus();
5080
5802
  sendResult(res, {
5081
5803
  started_at: PROCESS_STARTED_AT,
5804
+ pid: process.pid,
5082
5805
  uptime_ms: Math.floor(process.uptime() * 1e3),
5083
5806
  memory: process.memoryUsage(),
5084
5807
  sessions: { active: sessionsActive },
5085
5808
  extension: {
5086
5809
  connected: extensionStatus?.connected ?? false,
5810
+ ...extensionStatus?.extensionId ? { extension_id: extensionStatus.extensionId } : {},
5087
5811
  ...extensionStatus?.lastSeenAt ? { last_seen_at: extensionStatus.lastSeenAt } : {}
5088
5812
  }
5089
5813
  });
5090
5814
  };
5091
5815
  router.post("/health/check", handleHealthCheck);
5092
5816
  router.post("/health_check", handleHealthCheck);
5817
+ router.post("/diagnostics/enable_inspect", async (req, res) => {
5818
+ const body = req.body ?? {};
5819
+ if (!isRecord2(body)) {
5820
+ sendError(res, 400, {
5821
+ code: "INVALID_ARGUMENT",
5822
+ message: "Request body must be an object.",
5823
+ retryable: false
5824
+ });
5825
+ return;
5826
+ }
5827
+ const extensionIdRaw = body.extension_id;
5828
+ if (extensionIdRaw !== void 0 && typeof extensionIdRaw !== "string") {
5829
+ sendError(res, 400, {
5830
+ code: "INVALID_ARGUMENT",
5831
+ message: "extension_id must be a string when provided.",
5832
+ retryable: false,
5833
+ details: { field: "extension_id" }
5834
+ });
5835
+ return;
5836
+ }
5837
+ if (!options.extensionBridge) {
5838
+ sendError(res, 503, {
5839
+ code: "EXTENSION_DISCONNECTED",
5840
+ message: "Extension bridge is unavailable.",
5841
+ retryable: true
5842
+ });
5843
+ return;
5844
+ }
5845
+ try {
5846
+ const envelope2 = await options.extensionBridge.request("drive.set_debugger_capability", {
5847
+ enabled: true,
5848
+ ...extensionIdRaw ? { extension_id: extensionIdRaw } : {}
5849
+ });
5850
+ if (envelope2.status === "error") {
5851
+ const error = envelope2.error ?? {
5852
+ code: "INTERNAL",
5853
+ message: "Extension request failed.",
5854
+ retryable: false
5855
+ };
5856
+ sendError(res, 409, {
5857
+ code: error.code,
5858
+ message: error.message,
5859
+ retryable: error.retryable,
5860
+ details: error.details
5861
+ });
5862
+ return;
5863
+ }
5864
+ sendResult(res, envelope2.result ?? { ok: true, enabled: true });
5865
+ } catch (error) {
5866
+ if (error instanceof ExtensionBridgeError) {
5867
+ sendError(res, 409, {
5868
+ code: error.code,
5869
+ message: error.message,
5870
+ retryable: error.retryable,
5871
+ details: error.details
5872
+ });
5873
+ return;
5874
+ }
5875
+ sendError(res, 500, {
5876
+ code: "INTERNAL",
5877
+ message: "Unexpected inspect enablement error.",
5878
+ retryable: false
5879
+ });
5880
+ }
5881
+ });
5093
5882
  router.post("/diagnostics/doctor", (req, res) => {
5094
5883
  const body = req.body ?? {};
5095
5884
  if (!isRecord2(body)) {
@@ -5121,9 +5910,7 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
5121
5910
  port: parsedDoctorInput.data.caller.endpoint.port,
5122
5911
  baseUrl: parsedDoctorInput.data.caller.endpoint.base_url,
5123
5912
  hostSource: parsedDoctorInput.data.caller.endpoint.host_source,
5124
- portSource: parsedDoctorInput.data.caller.endpoint.port_source,
5125
- metadataPath: parsedDoctorInput.data.caller.endpoint.metadata_path,
5126
- isolatedMode: parsedDoctorInput.data.caller.endpoint.isolated_mode
5913
+ portSource: parsedDoctorInput.data.caller.endpoint.port_source
5127
5914
  } : void 0,
5128
5915
  process: parsedDoctorInput.data.caller.process ? {
5129
5916
  component: parsedDoctorInput.data.caller.process.component,
@@ -5140,9 +5927,7 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
5140
5927
  port: options.coreRuntime.port,
5141
5928
  baseUrl: `http://${options.coreRuntime.host}:${options.coreRuntime.port}`,
5142
5929
  hostSource: options.coreRuntime.hostSource,
5143
- portSource: options.coreRuntime.portSource,
5144
- metadataPath: options.coreRuntime.metadataPath,
5145
- isolatedMode: options.coreRuntime.isolatedMode
5930
+ portSource: options.coreRuntime.portSource
5146
5931
  } : void 0,
5147
5932
  process: {
5148
5933
  component: "core",
@@ -5185,11 +5970,13 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
5185
5970
  const status = options.extensionBridge.getStatus();
5186
5971
  context.extension = {
5187
5972
  connected: status.connected,
5973
+ extensionId: status.extensionId,
5188
5974
  version: status.version,
5189
5975
  lastSeenAt: status.lastSeenAt
5190
5976
  };
5191
5977
  if (status.connected) {
5192
5978
  context.runtime.extension = {
5979
+ extensionId: status.extensionId,
5193
5980
  version: status.version,
5194
5981
  protocolVersion: status.protocolVersion,
5195
5982
  capabilityNegotiated: status.capabilityNegotiated,
@@ -5226,7 +6013,8 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
5226
6013
  code: lastError.error.code,
5227
6014
  message: lastError.error.message,
5228
6015
  retryable: lastError.error.retryable,
5229
- at: lastError.at
6016
+ at: lastError.at,
6017
+ details: lastError.error.details
5230
6018
  };
5231
6019
  }
5232
6020
  }
@@ -5237,7 +6025,8 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
5237
6025
  code: lastError.error.code,
5238
6026
  message: lastError.error.message,
5239
6027
  retryable: lastError.error.retryable,
5240
- at: lastError.at
6028
+ at: lastError.at,
6029
+ details: lastError.error.details
5241
6030
  };
5242
6031
  }
5243
6032
  }
@@ -5267,6 +6056,11 @@ var registerDiagnosticsRoutes = (router, options = {}) => {
5267
6056
  };
5268
6057
 
5269
6058
  // packages/core/src/routes/drive.ts
6059
+ var SUPPORTED_NAVIGATE_WAIT_MODES = [
6060
+ "none",
6061
+ "domcontentloaded",
6062
+ "networkidle"
6063
+ ];
5270
6064
  var parseBody = (schema, body) => {
5271
6065
  const result = schema.safeParse(body);
5272
6066
  if (result.success) {
@@ -5283,6 +6077,24 @@ var parseBody = (schema, body) => {
5283
6077
  };
5284
6078
  var makeNavigateHandler = (schema, drive, registry) => {
5285
6079
  return (req, res) => {
6080
+ if (isRecord2(req.body) && req.body.wait !== void 0) {
6081
+ const waitMode = req.body.wait;
6082
+ if (typeof waitMode !== "string" || !SUPPORTED_NAVIGATE_WAIT_MODES.includes(
6083
+ waitMode
6084
+ )) {
6085
+ sendError(res, errorStatus("INVALID_ARGUMENT"), {
6086
+ code: "INVALID_ARGUMENT",
6087
+ message: `Unsupported wait mode: ${String(waitMode)}.`,
6088
+ retryable: false,
6089
+ details: {
6090
+ field: "wait",
6091
+ supported_wait_modes: SUPPORTED_NAVIGATE_WAIT_MODES,
6092
+ mapped_wait_mode: "domcontentloaded"
6093
+ }
6094
+ });
6095
+ return;
6096
+ }
6097
+ }
5286
6098
  const parsed = parseBody(schema, req.body ?? {});
5287
6099
  if (parsed.error) {
5288
6100
  sendError(res, errorStatus("INVALID_ARGUMENT"), {
@@ -5539,27 +6351,6 @@ var parseBody2 = (schema, body) => {
5539
6351
  }
5540
6352
  };
5541
6353
  };
5542
- var readTargetHint = (target) => {
5543
- if (!target) {
5544
- return void 0;
5545
- }
5546
- const url = typeof target.url === "string" ? target.url : void 0;
5547
- const title = typeof target.title === "string" ? target.title : void 0;
5548
- const lastActiveAtRaw = target.last_active_at ?? target.lastActiveAt;
5549
- const lastActiveAt = typeof lastActiveAtRaw === "string" ? lastActiveAtRaw : void 0;
5550
- if (!url && !title && !lastActiveAt) {
5551
- return void 0;
5552
- }
5553
- return { url, title, lastActiveAt };
5554
- };
5555
- var resolveTargetHint = (target, options) => {
5556
- const explicit = readTargetHint(target);
5557
- if (explicit) {
5558
- return explicit;
5559
- }
5560
- const tabs = options.extensionBridge?.getStatus().tabs ?? [];
5561
- return deriveHintFromTabs(tabs);
5562
- };
5563
6354
  var makeHandler2 = (schema, handler) => async (req, res) => {
5564
6355
  const parsed = parseBody2(schema, req.body ?? {});
5565
6356
  if (parsed.error) {
@@ -5605,7 +6396,12 @@ var registerInspectRoutes = (router, options) => {
5605
6396
  compact: body.compact,
5606
6397
  maxNodes: body.max_nodes,
5607
6398
  selector: body.selector,
5608
- targetHint: resolveTargetHint(body.target, options)
6399
+ targetHint: resolveInspectTargetHint({
6400
+ sessionId: body.session_id,
6401
+ target: body.target,
6402
+ registry: options.registry,
6403
+ extensionBridge: options.extensionBridge
6404
+ })
5609
6405
  });
5610
6406
  })
5611
6407
  );
@@ -5618,7 +6414,12 @@ var registerInspectRoutes = (router, options) => {
5618
6414
  router.post(
5619
6415
  "/inspect/find",
5620
6416
  makeHandler2(InspectFindInputSchema, async (body) => {
5621
- const targetHint = resolveTargetHint(body.target, options);
6417
+ const targetHint = resolveInspectTargetHint({
6418
+ sessionId: body.session_id,
6419
+ target: body.target,
6420
+ registry: options.registry,
6421
+ extensionBridge: options.extensionBridge
6422
+ });
5622
6423
  if (body.kind === "role") {
5623
6424
  return await inspect.find({
5624
6425
  sessionId: body.session_id,
@@ -5650,8 +6451,14 @@ var registerInspectRoutes = (router, options) => {
5650
6451
  return await inspect.extractContent({
5651
6452
  sessionId: body.session_id,
5652
6453
  format: body.format,
6454
+ consistency: body.consistency,
5653
6455
  includeMetadata: body.include_metadata,
5654
- targetHint: resolveTargetHint(body.target, options)
6456
+ targetHint: resolveInspectTargetHint({
6457
+ sessionId: body.session_id,
6458
+ target: body.target,
6459
+ registry: options.registry,
6460
+ extensionBridge: options.extensionBridge
6461
+ })
5655
6462
  });
5656
6463
  })
5657
6464
  );
@@ -5660,7 +6467,13 @@ var registerInspectRoutes = (router, options) => {
5660
6467
  makeHandler2(InspectPageStateInputSchema, async (body) => {
5661
6468
  return await inspect.pageState({
5662
6469
  sessionId: body.session_id,
5663
- targetHint: resolveTargetHint(body.target, options)
6470
+ includeValues: body.include_values,
6471
+ targetHint: resolveInspectTargetHint({
6472
+ sessionId: body.session_id,
6473
+ target: body.target,
6474
+ registry: options.registry,
6475
+ extensionBridge: options.extensionBridge
6476
+ })
5664
6477
  });
5665
6478
  })
5666
6479
  );
@@ -5669,7 +6482,13 @@ var registerInspectRoutes = (router, options) => {
5669
6482
  makeHandler2(InspectConsoleListInputSchema, async (body) => {
5670
6483
  return await inspect.consoleList({
5671
6484
  sessionId: body.session_id,
5672
- targetHint: resolveTargetHint(body.target, options)
6485
+ since: body.since,
6486
+ targetHint: resolveInspectTargetHint({
6487
+ sessionId: body.session_id,
6488
+ target: body.target,
6489
+ registry: options.registry,
6490
+ extensionBridge: options.extensionBridge
6491
+ })
5673
6492
  });
5674
6493
  })
5675
6494
  );
@@ -5678,7 +6497,12 @@ var registerInspectRoutes = (router, options) => {
5678
6497
  makeHandler2(InspectNetworkHarInputSchema, async (body) => {
5679
6498
  return await inspect.networkHar({
5680
6499
  sessionId: body.session_id,
5681
- targetHint: resolveTargetHint(body.target, options)
6500
+ targetHint: resolveInspectTargetHint({
6501
+ sessionId: body.session_id,
6502
+ target: body.target,
6503
+ registry: options.registry,
6504
+ extensionBridge: options.extensionBridge
6505
+ })
5682
6506
  });
5683
6507
  })
5684
6508
  );
@@ -5688,7 +6512,12 @@ var registerInspectRoutes = (router, options) => {
5688
6512
  return await inspect.evaluate({
5689
6513
  sessionId: body.session_id,
5690
6514
  expression: body.expression,
5691
- targetHint: resolveTargetHint(body.target, options)
6515
+ targetHint: resolveInspectTargetHint({
6516
+ sessionId: body.session_id,
6517
+ target: body.target,
6518
+ registry: options.registry,
6519
+ extensionBridge: options.extensionBridge
6520
+ })
5692
6521
  });
5693
6522
  })
5694
6523
  );
@@ -5697,12 +6526,168 @@ var registerInspectRoutes = (router, options) => {
5697
6526
  makeHandler2(InspectPerformanceMetricsInputSchema, async (body) => {
5698
6527
  return await inspect.performanceMetrics({
5699
6528
  sessionId: body.session_id,
5700
- targetHint: resolveTargetHint(body.target, options)
6529
+ targetHint: resolveInspectTargetHint({
6530
+ sessionId: body.session_id,
6531
+ target: body.target,
6532
+ registry: options.registry,
6533
+ extensionBridge: options.extensionBridge
6534
+ })
5701
6535
  });
5702
6536
  })
5703
6537
  );
5704
6538
  };
5705
6539
 
6540
+ // packages/core/src/routes/permissions.ts
6541
+ var EXTENSION_READY_WAIT_MS2 = 1500;
6542
+ var parseBody3 = (schema, body) => {
6543
+ const result = schema.safeParse(body);
6544
+ if (result.success) {
6545
+ return { data: result.data };
6546
+ }
6547
+ const issue = result.error.issues[0];
6548
+ const details = issue && issue.path.length > 0 ? { field: issue.path.map((part) => part.toString()).join(".") } : void 0;
6549
+ return {
6550
+ error: {
6551
+ message: issue?.message ?? "Request body is invalid.",
6552
+ ...details ? { details } : {}
6553
+ }
6554
+ };
6555
+ };
6556
+ var sendBridgeUnavailable = (res) => {
6557
+ sendError(res, errorStatus("EXTENSION_DISCONNECTED"), {
6558
+ code: "EXTENSION_DISCONNECTED",
6559
+ message: "Extension bridge is unavailable.",
6560
+ retryable: true
6561
+ });
6562
+ };
6563
+ var waitForExtensionReady = async (extensionBridge) => {
6564
+ if (extensionBridge.isConnected()) {
6565
+ return true;
6566
+ }
6567
+ const waitForReady = extensionBridge.waitForReady;
6568
+ if (typeof waitForReady !== "function") {
6569
+ return extensionBridge.isConnected();
6570
+ }
6571
+ return await waitForReady.call(extensionBridge, EXTENSION_READY_WAIT_MS2);
6572
+ };
6573
+ var makePermissionsHandler = (schema, action, options) => {
6574
+ return (req, res) => {
6575
+ const parsed = parseBody3(schema, req.body ?? {});
6576
+ if (parsed.error) {
6577
+ sendError(res, errorStatus("INVALID_ARGUMENT"), {
6578
+ code: "INVALID_ARGUMENT",
6579
+ message: parsed.error.message,
6580
+ retryable: false,
6581
+ ...parsed.error.details ? { details: parsed.error.details } : {}
6582
+ });
6583
+ return;
6584
+ }
6585
+ if (!options.extensionBridge) {
6586
+ sendBridgeUnavailable(res);
6587
+ return;
6588
+ }
6589
+ const extensionBridge = options.extensionBridge;
6590
+ void waitForExtensionReady(extensionBridge).then(async (ready) => {
6591
+ if (!ready) {
6592
+ throw new ExtensionBridgeError(
6593
+ "EXTENSION_DISCONNECTED",
6594
+ "Extension is not connected.",
6595
+ true
6596
+ );
6597
+ }
6598
+ return await extensionBridge.request(
6599
+ action,
6600
+ parsed.data
6601
+ );
6602
+ }).then((envelope2) => {
6603
+ if (envelope2.status === "error") {
6604
+ const error = envelope2.error ?? {
6605
+ code: "INTERNAL",
6606
+ message: "Extension request failed.",
6607
+ retryable: false
6608
+ };
6609
+ sendError(res, errorStatus(error.code), {
6610
+ code: error.code,
6611
+ message: error.message,
6612
+ retryable: error.retryable,
6613
+ details: error.details
6614
+ });
6615
+ return;
6616
+ }
6617
+ sendResult(res, envelope2.result ?? {});
6618
+ }).catch((error) => {
6619
+ if (error instanceof ExtensionBridgeError) {
6620
+ sendError(res, errorStatus(error.code), {
6621
+ code: error.code,
6622
+ message: error.message,
6623
+ retryable: error.retryable,
6624
+ details: error.details
6625
+ });
6626
+ return;
6627
+ }
6628
+ sendError(res, errorStatus("INTERNAL"), {
6629
+ code: "INTERNAL",
6630
+ message: "Unexpected permissions route error.",
6631
+ retryable: false,
6632
+ details: {
6633
+ hint: error instanceof Error ? error.message : "Unknown error.",
6634
+ action
6635
+ }
6636
+ });
6637
+ });
6638
+ };
6639
+ };
6640
+ var registerPermissionsRoutes = (router, options) => {
6641
+ router.post(
6642
+ "/permissions/list",
6643
+ makePermissionsHandler(
6644
+ PermissionsListInputSchema,
6645
+ "permissions.list",
6646
+ options
6647
+ )
6648
+ );
6649
+ router.post(
6650
+ "/permissions/get_mode",
6651
+ makePermissionsHandler(
6652
+ PermissionsGetModeInputSchema,
6653
+ "permissions.get_mode",
6654
+ options
6655
+ )
6656
+ );
6657
+ router.post(
6658
+ "/permissions/list_pending_requests",
6659
+ makePermissionsHandler(
6660
+ PermissionsListPendingRequestsInputSchema,
6661
+ "permissions.list_pending_requests",
6662
+ options
6663
+ )
6664
+ );
6665
+ router.post(
6666
+ "/permissions/request_allow_site",
6667
+ makePermissionsHandler(
6668
+ PermissionsRequestAllowSiteInputSchema,
6669
+ "permissions.request_allow_site",
6670
+ options
6671
+ )
6672
+ );
6673
+ router.post(
6674
+ "/permissions/request_revoke_site",
6675
+ makePermissionsHandler(
6676
+ PermissionsRequestRevokeSiteInputSchema,
6677
+ "permissions.request_revoke_site",
6678
+ options
6679
+ )
6680
+ );
6681
+ router.post(
6682
+ "/permissions/request_set_mode",
6683
+ makePermissionsHandler(
6684
+ PermissionsRequestSetModeInputSchema,
6685
+ "permissions.request_set_mode",
6686
+ options
6687
+ )
6688
+ );
6689
+ };
6690
+
5706
6691
  // packages/core/src/recovery.ts
5707
6692
  var RecoveryTracker = class {
5708
6693
  constructor() {
@@ -6118,6 +7103,9 @@ var createCoreServer = (options = {}) => {
6118
7103
  })
6119
7104
  );
6120
7105
  registerDriveRoutes(app, { drive, registry });
7106
+ registerPermissionsRoutes(app, {
7107
+ extensionBridge
7108
+ });
6121
7109
  registerInspectRoutes(app, {
6122
7110
  registry,
6123
7111
  extensionBridge,
@@ -6126,6 +7114,7 @@ var createCoreServer = (options = {}) => {
6126
7114
  registerArtifactsRoutes(app, {
6127
7115
  registry,
6128
7116
  extensionBridge,
7117
+ debuggerBridge,
6129
7118
  inspectService: inspect
6130
7119
  });
6131
7120
  registerDiagnosticsRoutes(app, {
@@ -6148,27 +7137,12 @@ var createCoreServer = (options = {}) => {
6148
7137
  recoveryTracker
6149
7138
  };
6150
7139
  };
6151
- var CORE_PORT_PROBE_ATTEMPTS = 20;
6152
- var buildRuntimeMetadataForPersist = (runtime, resolvedPort) => ({
7140
+ var buildRuntimeMetadataForPersist = (runtime) => ({
6153
7141
  ...runtime.metadata ?? {},
6154
- host: runtime.host,
6155
- port: resolvedPort,
6156
- git_root: runtime.gitRoot ?? runtime.metadata?.git_root,
6157
- worktree_id: runtime.worktreeId ?? runtime.metadata?.worktree_id,
7142
+ extension_id: runtime.metadata?.extension_id,
6158
7143
  updated_at: (/* @__PURE__ */ new Date()).toISOString()
6159
7144
  });
6160
- var resolveProbePortsForRuntime = (runtime) => {
6161
- if (!runtime.isolatedMode) {
6162
- return [runtime.port];
6163
- }
6164
- if (runtime.portSource === "metadata" || runtime.portSource === "deterministic") {
6165
- return createBoundedPortProbeSequence(
6166
- runtime.port,
6167
- CORE_PORT_PROBE_ATTEMPTS
6168
- );
6169
- }
6170
- return [runtime.port];
6171
- };
7145
+ var resolveProbePortsForRuntime = (runtime) => [runtime.port];
6172
7146
  var resolveSessionTtlMs = () => {
6173
7147
  const env = process.env.BROWSER_BRIDGE_SESSION_TTL_MS || process.env.BROWSER_VISION_SESSION_TTL_MS;
6174
7148
  if (env) {
@@ -6249,11 +7223,7 @@ var startCoreServer = async (options = {}) => {
6249
7223
  host: runtime.host,
6250
7224
  port: runtime.port,
6251
7225
  host_source: runtime.hostSource,
6252
- port_source: runtime.portSource,
6253
- metadata_path: runtime.metadataPath,
6254
- git_root: runtime.gitRoot,
6255
- worktree_id: runtime.worktreeId,
6256
- deterministic_port: runtime.deterministicPort
7226
+ port_source: runtime.portSource
6257
7227
  });
6258
7228
  const { app, registry, extensionBridge } = createCoreServer({
6259
7229
  registry: options.registry,
@@ -6283,10 +7253,9 @@ var startCoreServer = async (options = {}) => {
6283
7253
  duration_ms: Number(startupDurationMs.toFixed(3))
6284
7254
  });
6285
7255
  try {
6286
- writeRuntimeMetadata(
6287
- buildRuntimeMetadataForPersist(runtime, resolvedPort),
6288
- { metadataPath: runtime.metadataPath }
6289
- );
7256
+ writeRuntimeMetadata(buildRuntimeMetadataForPersist(runtime), {
7257
+ metadataPath: runtime.metadataPath
7258
+ });
6290
7259
  logger.info("core.runtime_metadata.persisted", {
6291
7260
  metadata_path: runtime.metadataPath,
6292
7261
  host: runtime.host,
@@ -6495,9 +7464,7 @@ startCoreServer({ ${startOptions.join(
6495
7464
  port: readiness.runtime.port,
6496
7465
  base_url: readiness.baseUrl,
6497
7466
  host_source: readiness.runtime.hostSource,
6498
- port_source: readiness.runtime.portSource,
6499
- metadata_path: readiness.runtime.metadataPath,
6500
- isolated_mode: readiness.runtime.isolatedMode
7467
+ port_source: readiness.runtime.portSource
6501
7468
  },
6502
7469
  process: {
6503
7470
  component: "mcp",
@@ -6543,6 +7510,74 @@ var toInternalErrorEnvelope = (error) => ({
6543
7510
  });
6544
7511
  var envelope = (schema) => successEnvelopeSchema(schema);
6545
7512
  var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
7513
+ var readSessionId2 = (args) => {
7514
+ if (!isRecord3(args)) {
7515
+ return void 0;
7516
+ }
7517
+ const sessionId = args.session_id;
7518
+ return typeof sessionId === "string" && sessionId.length > 0 ? sessionId : void 0;
7519
+ };
7520
+ var withPermissionsSource = (source) => (args) => isRecord3(args) ? {
7521
+ ...args,
7522
+ source
7523
+ } : args;
7524
+ var supportsSessionMigration = (corePath) => corePath.startsWith("/drive/") || corePath.startsWith("/inspect/") || corePath.startsWith("/artifacts/") || corePath === "/diagnostics/doctor";
7525
+ var isSessionNotFoundEnvelope = (envelopeResult) => {
7526
+ if (envelopeResult.ok) {
7527
+ return false;
7528
+ }
7529
+ const details = envelopeResult.error.details;
7530
+ return envelopeResult.error.code === "NOT_FOUND" && isRecord3(details) && details.reason === "session_not_found";
7531
+ };
7532
+ var addSessionRecoveryHint = (envelopeResult) => {
7533
+ if (envelopeResult.ok) {
7534
+ return envelopeResult;
7535
+ }
7536
+ return {
7537
+ ok: false,
7538
+ error: {
7539
+ ...envelopeResult.error,
7540
+ details: {
7541
+ ...isRecord3(envelopeResult.error.details) ? envelopeResult.error.details : {},
7542
+ recover_action: "session.create"
7543
+ }
7544
+ }
7545
+ };
7546
+ };
7547
+ var addSessionMigrationNotice = (envelopeResult, staleSessionId, replacementSessionId) => {
7548
+ if (envelopeResult.ok) {
7549
+ if (!isRecord3(envelopeResult.result)) {
7550
+ return envelopeResult;
7551
+ }
7552
+ const warning = `Session ${staleSessionId} became stale after runtime switch; retried with ${replacementSessionId}.`;
7553
+ const existingWarnings = Array.isArray(envelopeResult.result.warnings) ? envelopeResult.result.warnings.filter(
7554
+ (item) => typeof item === "string"
7555
+ ) : [];
7556
+ return {
7557
+ ok: true,
7558
+ result: {
7559
+ ...envelopeResult.result,
7560
+ warnings: existingWarnings.includes(warning) ? existingWarnings : [...existingWarnings, warning],
7561
+ session_migration: {
7562
+ stale_session_id: staleSessionId,
7563
+ replacement_session_id: replacementSessionId
7564
+ }
7565
+ }
7566
+ };
7567
+ }
7568
+ return {
7569
+ ok: false,
7570
+ error: {
7571
+ ...envelopeResult.error,
7572
+ details: {
7573
+ ...isRecord3(envelopeResult.error.details) ? envelopeResult.error.details : {},
7574
+ stale_session_id: staleSessionId,
7575
+ replacement_session_id: replacementSessionId,
7576
+ recover_action: "session.recover"
7577
+ }
7578
+ }
7579
+ };
7580
+ };
6546
7581
  var addDeprecatedAliasWarning = (envelopeResult, deprecationAlias) => {
6547
7582
  if (!deprecationAlias || !envelopeResult.ok || typeof envelopeResult.result !== "object" || !envelopeResult.result) {
6548
7583
  return envelopeResult;
@@ -6599,6 +7634,69 @@ var TOOL_DEFINITIONS = [
6599
7634
  corePath: "/session/close"
6600
7635
  }
6601
7636
  },
7637
+ {
7638
+ name: "permissions.list",
7639
+ config: {
7640
+ title: "Permissions List",
7641
+ description: "List allowlisted Browser Bridge sites.",
7642
+ inputSchema: PermissionsListInputSchema,
7643
+ outputSchema: envelope(PermissionsListOutputSchema),
7644
+ corePath: "/permissions/list"
7645
+ }
7646
+ },
7647
+ {
7648
+ name: "permissions.get_mode",
7649
+ config: {
7650
+ title: "Permissions Get Mode",
7651
+ description: "Read the current Browser Bridge permissions mode.",
7652
+ inputSchema: PermissionsGetModeInputSchema,
7653
+ outputSchema: envelope(PermissionsGetModeOutputSchema),
7654
+ corePath: "/permissions/get_mode"
7655
+ }
7656
+ },
7657
+ {
7658
+ name: "permissions.list_pending_requests",
7659
+ config: {
7660
+ title: "Permissions List Pending Requests",
7661
+ description: "List pending external Browser Bridge permission-change requests.",
7662
+ inputSchema: PermissionsListPendingRequestsInputSchema,
7663
+ outputSchema: envelope(PermissionsListPendingRequestsOutputSchema),
7664
+ corePath: "/permissions/list_pending_requests"
7665
+ }
7666
+ },
7667
+ {
7668
+ name: "permissions.request_allow_site",
7669
+ config: {
7670
+ title: "Permissions Request Allow Site",
7671
+ description: "Request allowlisting a site. A human must approve the change in Chrome before it applies.",
7672
+ inputSchema: PermissionsRequestAllowSiteInputSchema,
7673
+ outputSchema: envelope(PermissionsRequestOutputSchema),
7674
+ corePath: "/permissions/request_allow_site",
7675
+ transformInput: withPermissionsSource("mcp")
7676
+ }
7677
+ },
7678
+ {
7679
+ name: "permissions.request_revoke_site",
7680
+ config: {
7681
+ title: "Permissions Request Revoke Site",
7682
+ description: "Request revoking a site from the allowlist. A human must approve the change in Chrome before it applies.",
7683
+ inputSchema: PermissionsRequestRevokeSiteInputSchema,
7684
+ outputSchema: envelope(PermissionsRequestOutputSchema),
7685
+ corePath: "/permissions/request_revoke_site",
7686
+ transformInput: withPermissionsSource("mcp")
7687
+ }
7688
+ },
7689
+ {
7690
+ name: "permissions.request_set_mode",
7691
+ config: {
7692
+ title: "Permissions Request Set Mode",
7693
+ description: "Request changing Browser Bridge permission mode. A human must approve the change in Chrome before it applies.",
7694
+ inputSchema: PermissionsRequestSetModeInputSchema,
7695
+ outputSchema: envelope(PermissionsRequestOutputSchema),
7696
+ corePath: "/permissions/request_set_mode",
7697
+ transformInput: withPermissionsSource("mcp")
7698
+ }
7699
+ },
6602
7700
  {
6603
7701
  name: "drive.navigate",
6604
7702
  config: {
@@ -6953,10 +8051,26 @@ var createToolHandler = (clientProvider, corePath, deprecationAlias, transformIn
6953
8051
  void _extra;
6954
8052
  try {
6955
8053
  const client = typeof clientProvider === "function" ? await clientProvider() : clientProvider;
6956
- const envelopeResult = await client.post(
6957
- corePath,
6958
- transformInput ? transformInput(args) : args
6959
- );
8054
+ const transformedArgs = transformInput ? transformInput(args) : args;
8055
+ const staleSessionId = readSessionId2(transformedArgs);
8056
+ let envelopeResult = await client.post(corePath, transformedArgs);
8057
+ if (staleSessionId && supportsSessionMigration(corePath) && isSessionNotFoundEnvelope(envelopeResult) && isRecord3(transformedArgs)) {
8058
+ const createdSession = await client.post("/session/create", {});
8059
+ if (createdSession.ok && isRecord3(createdSession.result) && typeof createdSession.result.session_id === "string" && createdSession.result.session_id.length > 0) {
8060
+ const replacementSessionId = createdSession.result.session_id;
8061
+ const retryArgs = {
8062
+ ...transformedArgs,
8063
+ session_id: replacementSessionId
8064
+ };
8065
+ envelopeResult = addSessionMigrationNotice(
8066
+ await client.post(corePath, retryArgs),
8067
+ staleSessionId,
8068
+ replacementSessionId
8069
+ );
8070
+ } else {
8071
+ envelopeResult = addSessionRecoveryHint(envelopeResult);
8072
+ }
8073
+ }
6960
8074
  return toToolResult(
6961
8075
  addDeprecatedAliasWarning(envelopeResult, deprecationAlias)
6962
8076
  );
@@ -7097,7 +8211,7 @@ var DEFAULT_SERVER_NAME = "browser-bridge";
7097
8211
  var DEFAULT_SERVER_VERSION = "0.0.0";
7098
8212
  var ENV_MCP_EAGER = "BROWSER_BRIDGE_MCP_EAGER";
7099
8213
  var ENV_LEGACY_MCP_EAGER = "BROWSER_VISION_MCP_EAGER";
7100
- var parseBoolean2 = (value) => {
8214
+ var parseBoolean = (value) => {
7101
8215
  if (value === void 0) {
7102
8216
  return void 0;
7103
8217
  }
@@ -7114,7 +8228,7 @@ var resolveEagerMode = (explicit) => {
7114
8228
  if (typeof explicit === "boolean") {
7115
8229
  return explicit;
7116
8230
  }
7117
- const envValue = parseBoolean2(process.env[ENV_MCP_EAGER]) ?? parseBoolean2(process.env[ENV_LEGACY_MCP_EAGER]);
8231
+ const envValue = parseBoolean(process.env[ENV_MCP_EAGER]) ?? parseBoolean(process.env[ENV_LEGACY_MCP_EAGER]);
7118
8232
  return envValue ?? false;
7119
8233
  };
7120
8234
  var toCoreClientOptions = (options, logger) => ({