@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/index.js CHANGED
@@ -38,14 +38,11 @@ var import_node_path2 = require("node:path");
38
38
  var import_node_fs = require("node:fs");
39
39
  var import_node_path = require("node:path");
40
40
  var DEFAULT_HOST = "127.0.0.1";
41
- var LEGACY_DEFAULT_PORT = 3210;
42
- var DETERMINISTIC_PORT_WINDOW = 2e3;
41
+ var DEFAULT_PORT = 3210;
43
42
  var ENV_CORE_HOST = "BROWSER_BRIDGE_CORE_HOST";
44
43
  var ENV_VISION_HOST = "BROWSER_VISION_CORE_HOST";
45
44
  var ENV_CORE_PORT = "BROWSER_BRIDGE_CORE_PORT";
46
45
  var ENV_VISION_PORT = "BROWSER_VISION_CORE_PORT";
47
- var ENV_ISOLATED_MODE = "BROWSER_BRIDGE_ISOLATED_MODE";
48
- var ENV_VISION_ISOLATED_MODE = "BROWSER_VISION_ISOLATED_MODE";
49
46
  var ENV_BRIDGE_CWD = "BROWSER_BRIDGE_CWD";
50
47
  var ENV_PROCESS_PWD = "PWD";
51
48
  var ENV_PROCESS_INIT_CWD = "INIT_CWD";
@@ -102,22 +99,6 @@ var normalizeHost = (value) => {
102
99
  const trimmed = value.trim();
103
100
  return trimmed.length > 0 ? trimmed : void 0;
104
101
  };
105
- var parseBoolean = (value) => {
106
- if (typeof value === "boolean") {
107
- return value;
108
- }
109
- if (typeof value !== "string") {
110
- return void 0;
111
- }
112
- const normalized = value.trim().toLowerCase();
113
- if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
114
- return true;
115
- }
116
- if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
117
- return false;
118
- }
119
- return void 0;
120
- };
121
102
  var parsePort = (value, label, invalidPolicy) => {
122
103
  if (value === void 0 || value === null) {
123
104
  return void 0;
@@ -134,60 +115,6 @@ var parsePort = (value, label, invalidPolicy) => {
134
115
  }
135
116
  return void 0;
136
117
  };
137
- var hashString = (value) => {
138
- let hash = 2166136261;
139
- for (let index = 0; index < value.length; index += 1) {
140
- hash ^= value.charCodeAt(index);
141
- hash = Math.imul(hash, 16777619);
142
- }
143
- return hash >>> 0;
144
- };
145
- var normalizePathForHash = (value) => value.replace(/\\/g, "/").toLowerCase();
146
- var sanitizeToken = (value) => value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
147
- var fallbackWorktreeId = (gitRoot) => {
148
- const hash = hashString(normalizePathForHash(gitRoot)).toString(16).padStart(8, "0");
149
- return `wt-${hash}`;
150
- };
151
- var extractWorktreeIdFromGitDir = (gitDir) => {
152
- const normalized = gitDir.replace(/\\/g, "/");
153
- const marker = "/.git/worktrees/";
154
- const markerIndex = normalized.lastIndexOf(marker);
155
- if (markerIndex < 0) {
156
- return null;
157
- }
158
- const remainder = normalized.slice(markerIndex + marker.length);
159
- const rawId = remainder.split("/")[0];
160
- if (!rawId) {
161
- return null;
162
- }
163
- const sanitized = sanitizeToken(rawId);
164
- return sanitized.length > 0 ? sanitized : null;
165
- };
166
- var readWorktreeGitDir = (gitRoot) => {
167
- const gitPath = (0, import_node_path.join)(gitRoot, ".git");
168
- try {
169
- const stats = (0, import_node_fs.statSync)(gitPath);
170
- if (stats.isDirectory()) {
171
- return gitPath;
172
- }
173
- if (!stats.isFile()) {
174
- return null;
175
- }
176
- } catch {
177
- return null;
178
- }
179
- try {
180
- const raw = (0, import_node_fs.readFileSync)(gitPath, "utf8");
181
- const match = raw.match(/^gitdir:\s*(.+)$/m);
182
- if (!match?.[1]) {
183
- return null;
184
- }
185
- const candidate = match[1].trim();
186
- return (0, import_node_path.isAbsolute)(candidate) ? candidate : (0, import_node_path.resolve)(gitRoot, candidate);
187
- } catch {
188
- return null;
189
- }
190
- };
191
118
  var resolveEnvHost = (env) => {
192
119
  const bridgeHost = normalizeHost(env[ENV_CORE_HOST]);
193
120
  if (bridgeHost) {
@@ -201,35 +128,18 @@ var resolveEnvPortRaw = (env) => {
201
128
  }
202
129
  return env[ENV_VISION_PORT];
203
130
  };
204
- var resolveEnvIsolatedMode = (env) => {
205
- const bridge = parseBoolean(env[ENV_ISOLATED_MODE]);
206
- if (bridge !== void 0) {
207
- return bridge;
208
- }
209
- return parseBoolean(env[ENV_VISION_ISOLATED_MODE]);
210
- };
211
131
  var sanitizeMetadata = (raw) => {
212
132
  if (!raw || typeof raw !== "object") {
213
133
  return null;
214
134
  }
215
135
  const candidate = raw;
216
- const host = normalizeHost(candidate.host);
217
- const port = parsePort(candidate.port, "port", "ignore");
218
- const gitRoot = normalizeHost(candidate.git_root);
219
- const worktreeId = normalizeHost(candidate.worktree_id);
220
136
  const extensionId = normalizeHost(candidate.extension_id);
221
- const isolatedMode = parseBoolean(candidate.isolated_mode);
222
137
  const updatedAt = normalizeHost(candidate.updated_at);
223
- if (!host && port === void 0 && !gitRoot && !worktreeId && !extensionId && isolatedMode === void 0 && !updatedAt) {
138
+ if (!extensionId && !updatedAt) {
224
139
  return null;
225
140
  }
226
141
  return {
227
- host,
228
- port,
229
- git_root: gitRoot,
230
- worktree_id: worktreeId,
231
142
  extension_id: extensionId,
232
- isolated_mode: isolatedMode,
233
143
  updated_at: updatedAt
234
144
  };
235
145
  };
@@ -246,32 +156,6 @@ var findGitRoot = (cwd) => {
246
156
  current = parent;
247
157
  }
248
158
  };
249
- var resolveWorktreeId = ({
250
- cwd,
251
- gitRoot
252
- } = {}) => {
253
- const resolvedGitRoot = gitRoot ?? findGitRoot(resolveCwd(cwd));
254
- if (!resolvedGitRoot) {
255
- return null;
256
- }
257
- const gitDir = readWorktreeGitDir(resolvedGitRoot);
258
- const parsedId = gitDir ? extractWorktreeIdFromGitDir(gitDir) : null;
259
- if (parsedId) {
260
- return parsedId;
261
- }
262
- return fallbackWorktreeId(resolvedGitRoot);
263
- };
264
- var resolveDeterministicCorePort = ({
265
- cwd,
266
- gitRoot
267
- } = {}) => {
268
- const resolvedGitRoot = gitRoot ?? findGitRoot(resolveCwd(cwd));
269
- if (!resolvedGitRoot) {
270
- return LEGACY_DEFAULT_PORT;
271
- }
272
- const seed = normalizePathForHash(resolvedGitRoot);
273
- return LEGACY_DEFAULT_PORT + hashString(seed) % DETERMINISTIC_PORT_WINDOW;
274
- };
275
159
  var resolveRuntimeMetadataPath = ({
276
160
  cwd,
277
161
  gitRoot,
@@ -318,17 +202,6 @@ var readRuntimeMetadata = ({
318
202
  return null;
319
203
  }
320
204
  };
321
- var writeRuntimeMetadata = (metadata, {
322
- cwd,
323
- gitRoot,
324
- metadataPath
325
- } = {}) => {
326
- const path9 = resolveRuntimeMetadataPath({ cwd, gitRoot, metadataPath });
327
- (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path9), { recursive: true });
328
- (0, import_node_fs.writeFileSync)(path9, `${JSON.stringify(metadata, null, 2)}
329
- `, "utf8");
330
- return path9;
331
- };
332
205
  var resolveCoreRuntime = (options = {}) => {
333
206
  const env = options.env ?? process.env;
334
207
  const resolvedCwd = resolveCwd(options.cwd);
@@ -339,45 +212,18 @@ var resolveCoreRuntime = (options = {}) => {
339
212
  metadataPath: options.metadataPath
340
213
  });
341
214
  const metadata = options.metadata === void 0 ? readRuntimeMetadata({ metadataPath }) : sanitizeMetadata(options.metadata);
342
- const deterministicPort = resolveDeterministicCorePort({
343
- cwd: resolvedCwd,
344
- gitRoot
345
- });
346
215
  const optionHost = normalizeHost(options.host);
347
216
  const envHost = resolveEnvHost(env);
348
- const metadataHost = normalizeHost(metadata?.host);
349
- const host = optionHost ?? envHost ?? metadataHost ?? DEFAULT_HOST;
350
- const hostSource = optionHost ? "option" : envHost ? "env" : metadataHost ? "metadata" : "default";
217
+ const host = optionHost ?? envHost ?? DEFAULT_HOST;
218
+ const hostSource = optionHost ? "option" : envHost ? "env" : "default";
351
219
  const optionPort = parsePort(options.port, "port", "throw");
352
220
  const envPort = parsePort(
353
221
  resolveEnvPortRaw(env),
354
222
  "port",
355
223
  options.strictEnvPort ? "throw" : "ignore"
356
224
  );
357
- const metadataPort = parsePort(metadata?.port, "port", "ignore");
358
- const optionIsolatedMode = options.isolatedMode;
359
- const envIsolatedMode = resolveEnvIsolatedMode(env);
360
- const metadataIsolatedMode = metadata?.isolated_mode;
361
- const isolatedMode = optionIsolatedMode ?? envIsolatedMode ?? metadataIsolatedMode ?? false;
362
- const isolatedModeSource = optionIsolatedMode !== void 0 ? "option" : envIsolatedMode !== void 0 ? "env" : metadataIsolatedMode !== void 0 ? "metadata" : "default";
363
- let port;
364
- let portSource;
365
- if (optionPort !== void 0) {
366
- port = optionPort;
367
- portSource = "option";
368
- } else if (envPort !== void 0) {
369
- port = envPort;
370
- portSource = "env";
371
- } else if (metadataPort !== void 0 && isolatedMode) {
372
- port = metadataPort;
373
- portSource = "metadata";
374
- } else if (isolatedMode) {
375
- port = deterministicPort;
376
- portSource = "deterministic";
377
- } else {
378
- port = LEGACY_DEFAULT_PORT;
379
- portSource = "default";
380
- }
225
+ const port = optionPort ?? envPort ?? DEFAULT_PORT;
226
+ const portSource = optionPort !== void 0 ? "option" : envPort !== void 0 ? "env" : "default";
381
227
  return {
382
228
  host,
383
229
  port,
@@ -385,11 +231,7 @@ var resolveCoreRuntime = (options = {}) => {
385
231
  portSource,
386
232
  metadataPath,
387
233
  metadata,
388
- gitRoot,
389
- worktreeId: resolveWorktreeId({ cwd: resolvedCwd, gitRoot }),
390
- deterministicPort,
391
- isolatedMode,
392
- isolatedModeSource
234
+ gitRoot
393
235
  };
394
236
  };
395
237
 
@@ -808,7 +650,7 @@ var createCoreReadinessController = (options = {}) => {
808
650
  }
809
651
  if (portOccupied) {
810
652
  throw new Error(
811
- `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.`
653
+ `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.`
812
654
  );
813
655
  }
814
656
  throw new Error(
@@ -821,9 +663,10 @@ var createCoreReadinessController = (options = {}) => {
821
663
  return;
822
664
  }
823
665
  if (!ensurePromise) {
824
- ensurePromise = ensureCoreRunning().catch((error) => {
666
+ ensurePromise = (async () => {
667
+ await ensureCoreRunning();
668
+ })().finally(() => {
825
669
  ensurePromise = null;
826
- throw error;
827
670
  });
828
671
  }
829
672
  await ensurePromise;
@@ -1002,9 +845,7 @@ var DiagnosticsRuntimeEndpointSchema = import_zod3.z.object({
1002
845
  port: import_zod3.z.number().finite().optional(),
1003
846
  base_url: import_zod3.z.string().optional(),
1004
847
  host_source: import_zod3.z.string().optional(),
1005
- port_source: import_zod3.z.string().optional(),
1006
- metadata_path: import_zod3.z.string().optional(),
1007
- isolated_mode: import_zod3.z.boolean().optional()
848
+ port_source: import_zod3.z.string().optional()
1008
849
  });
1009
850
  var DiagnosticsRuntimeProcessSchema = import_zod3.z.object({
1010
851
  component: import_zod3.z.enum(["cli", "mcp", "core"]).optional(),
@@ -1025,12 +866,13 @@ var DiagnosticsRuntimeContextSchema = import_zod3.z.object({
1025
866
  process: DiagnosticsRuntimeProcessSchema.optional()
1026
867
  }).optional(),
1027
868
  extension: import_zod3.z.object({
869
+ extension_id: import_zod3.z.string().optional(),
1028
870
  version: import_zod3.z.string().optional(),
1029
871
  protocol_version: import_zod3.z.string().optional(),
1030
872
  capability_negotiated: import_zod3.z.boolean().optional(),
1031
873
  capabilities: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.boolean()).optional(),
1032
874
  endpoint: DiagnosticsRuntimeEndpointSchema.optional(),
1033
- port_source: import_zod3.z.enum(["default", "storage"]).optional()
875
+ port_source: import_zod3.z.enum(["default"]).optional()
1034
876
  }).optional()
1035
877
  });
1036
878
  var DiagnosticReportSchema = import_zod3.z.object({
@@ -1097,6 +939,63 @@ var SessionCloseInputSchema = SessionIdSchema;
1097
939
  var SessionCloseOutputSchema = import_zod3.z.object({
1098
940
  ok: import_zod3.z.boolean()
1099
941
  });
942
+ var PermissionsModeSchema = import_zod3.z.enum(["granular", "bypass"]);
943
+ var PermissionsRequestSourceSchema = import_zod3.z.enum(["cli", "mcp", "api"]);
944
+ var PermissionsPendingRequestKindSchema = import_zod3.z.enum([
945
+ "allow_site",
946
+ "revoke_site",
947
+ "set_mode"
948
+ ]);
949
+ var PermissionsPendingRequestStatusSchema = import_zod3.z.enum([
950
+ "pending",
951
+ "approved",
952
+ "denied",
953
+ "timed_out"
954
+ ]);
955
+ var PermissionsSiteEntrySchema = import_zod3.z.object({
956
+ site: import_zod3.z.string().min(1),
957
+ created_at: import_zod3.z.string().datetime(),
958
+ last_used_at: import_zod3.z.string().datetime()
959
+ });
960
+ var PermissionsListInputSchema = import_zod3.z.object({}).strict().default({});
961
+ var PermissionsListOutputSchema = import_zod3.z.object({
962
+ sites: import_zod3.z.array(PermissionsSiteEntrySchema)
963
+ });
964
+ var PermissionsGetModeInputSchema = import_zod3.z.object({}).strict().default({});
965
+ var PermissionsGetModeOutputSchema = import_zod3.z.object({
966
+ mode: PermissionsModeSchema
967
+ });
968
+ var PermissionsPendingRequestSchema = import_zod3.z.object({
969
+ request_id: import_zod3.z.string().min(1),
970
+ kind: PermissionsPendingRequestKindSchema,
971
+ status: PermissionsPendingRequestStatusSchema,
972
+ requested_at: import_zod3.z.string().datetime(),
973
+ site: import_zod3.z.string().min(1).optional(),
974
+ mode: PermissionsModeSchema.optional(),
975
+ source: PermissionsRequestSourceSchema.optional(),
976
+ warning: import_zod3.z.string().optional(),
977
+ message: import_zod3.z.string().optional()
978
+ });
979
+ var PermissionsListPendingRequestsInputSchema = import_zod3.z.object({}).strict().default({});
980
+ var PermissionsListPendingRequestsOutputSchema = import_zod3.z.object({
981
+ requests: import_zod3.z.array(PermissionsPendingRequestSchema)
982
+ });
983
+ var PermissionsRequestAllowSiteInputSchema = import_zod3.z.object({
984
+ site: import_zod3.z.string().min(1),
985
+ timeout_ms: import_zod3.z.number().int().positive().max(3e5).optional(),
986
+ source: PermissionsRequestSourceSchema.optional()
987
+ });
988
+ var PermissionsRequestRevokeSiteInputSchema = import_zod3.z.object({
989
+ site: import_zod3.z.string().min(1),
990
+ timeout_ms: import_zod3.z.number().int().positive().max(3e5).optional(),
991
+ source: PermissionsRequestSourceSchema.optional()
992
+ });
993
+ var PermissionsRequestSetModeInputSchema = import_zod3.z.object({
994
+ mode: PermissionsModeSchema,
995
+ timeout_ms: import_zod3.z.number().int().positive().max(3e5).optional(),
996
+ source: PermissionsRequestSourceSchema.optional()
997
+ });
998
+ var PermissionsRequestOutputSchema = PermissionsPendingRequestSchema;
1100
999
  var DriveWaitConditionSchema = import_zod3.z.object({
1101
1000
  kind: import_zod3.z.enum(["locator_visible", "text_present", "url_matches"]),
1102
1001
  value: import_zod3.z.string().min(1)
@@ -1105,7 +1004,7 @@ var DriveNavigateInputSchema = import_zod3.z.object({
1105
1004
  session_id: import_zod3.z.string().min(1).optional(),
1106
1005
  url: import_zod3.z.string().min(1),
1107
1006
  tab_id: import_zod3.z.number().finite().optional(),
1108
- wait: import_zod3.z.enum(["none", "domcontentloaded"]).default("domcontentloaded")
1007
+ wait: import_zod3.z.enum(["none", "domcontentloaded", "networkidle"]).default("domcontentloaded")
1109
1008
  });
1110
1009
  var DriveNavigateOutputSchema = OpResultSchema.extend({
1111
1010
  session_id: import_zod3.z.string().min(1)
@@ -1274,6 +1173,8 @@ var InspectConsistencySchema = import_zod3.z.enum(["best_effort", "quiesce"]);
1274
1173
  var TargetHintSchema = import_zod3.z.object({
1275
1174
  url: import_zod3.z.string().min(1).optional(),
1276
1175
  title: import_zod3.z.string().min(1).optional(),
1176
+ tab_id: import_zod3.z.number().finite().optional(),
1177
+ tabId: import_zod3.z.number().finite().optional(),
1277
1178
  last_active_at: import_zod3.z.string().optional(),
1278
1179
  lastActiveAt: import_zod3.z.string().optional()
1279
1180
  });
@@ -1281,6 +1182,12 @@ var FormFieldInfoSchema = import_zod3.z.object({
1281
1182
  name: import_zod3.z.string(),
1282
1183
  type: import_zod3.z.string(),
1283
1184
  value: import_zod3.z.string(),
1185
+ selector: import_zod3.z.string().optional(),
1186
+ label: import_zod3.z.string().optional(),
1187
+ placeholder: import_zod3.z.string().optional(),
1188
+ checked: import_zod3.z.boolean().optional(),
1189
+ disabled: import_zod3.z.boolean().optional(),
1190
+ visible: import_zod3.z.boolean().optional(),
1284
1191
  options: import_zod3.z.array(import_zod3.z.string()).optional()
1285
1192
  });
1286
1193
  var FormInfoSchema = import_zod3.z.object({
@@ -1293,7 +1200,31 @@ var StorageEntrySchema = import_zod3.z.object({
1293
1200
  key: import_zod3.z.string(),
1294
1201
  value: import_zod3.z.string()
1295
1202
  });
1203
+ var FocusedElementSchema = import_zod3.z.object({
1204
+ selector: import_zod3.z.string().optional(),
1205
+ name: import_zod3.z.string().optional(),
1206
+ label: import_zod3.z.string().optional(),
1207
+ role: import_zod3.z.string().optional(),
1208
+ type: import_zod3.z.string().optional(),
1209
+ text: import_zod3.z.string().optional()
1210
+ });
1211
+ var PageActionSchema = import_zod3.z.object({
1212
+ selector: import_zod3.z.string(),
1213
+ role: import_zod3.z.string(),
1214
+ name: import_zod3.z.string()
1215
+ });
1216
+ var StorageSummarySchema = import_zod3.z.object({
1217
+ localStorageCount: import_zod3.z.number().int().nonnegative(),
1218
+ sessionStorageCount: import_zod3.z.number().int().nonnegative(),
1219
+ cookieCount: import_zod3.z.number().int().nonnegative()
1220
+ });
1296
1221
  var PageStateSchema = import_zod3.z.object({
1222
+ url: import_zod3.z.string().optional(),
1223
+ title: import_zod3.z.string().optional(),
1224
+ readyState: import_zod3.z.string().optional(),
1225
+ focused: FocusedElementSchema.optional(),
1226
+ primaryActions: import_zod3.z.array(PageActionSchema).optional(),
1227
+ storageSummary: StorageSummarySchema.optional(),
1297
1228
  forms: import_zod3.z.array(FormInfoSchema),
1298
1229
  localStorage: import_zod3.z.array(StorageEntrySchema),
1299
1230
  sessionStorage: import_zod3.z.array(StorageEntrySchema),
@@ -1356,6 +1287,7 @@ var InspectFindOutputSchema = import_zod3.z.object({
1356
1287
  warnings: import_zod3.z.array(import_zod3.z.string()).optional()
1357
1288
  });
1358
1289
  var InspectPageStateInputSchema = SessionIdSchema.extend({
1290
+ include_values: import_zod3.z.boolean().default(false),
1359
1291
  target: TargetHintSchema.optional()
1360
1292
  });
1361
1293
  var InspectPageStateOutputSchema = PageStateSchema;
@@ -1366,6 +1298,7 @@ var InspectExtractContentFormatSchema = import_zod3.z.enum([
1366
1298
  ]);
1367
1299
  var InspectExtractContentInputSchema = SessionIdSchema.extend({
1368
1300
  format: InspectExtractContentFormatSchema.default("markdown"),
1301
+ consistency: InspectConsistencySchema.default("quiesce"),
1369
1302
  include_metadata: import_zod3.z.boolean().default(true),
1370
1303
  target: TargetHintSchema.optional()
1371
1304
  });
@@ -1378,6 +1311,7 @@ var InspectExtractContentOutputSchema = import_zod3.z.object({
1378
1311
  warnings: import_zod3.z.array(import_zod3.z.string()).optional()
1379
1312
  });
1380
1313
  var InspectConsoleListInputSchema = SessionIdSchema.extend({
1314
+ since: import_zod3.z.string().optional(),
1381
1315
  target: TargetHintSchema.optional()
1382
1316
  });
1383
1317
  var ConsoleSourceLocationSchema = import_zod3.z.object({
@@ -1468,6 +1402,7 @@ var HealthCheckOutputSchema = import_zod3.z.object({
1468
1402
  }).passthrough(),
1469
1403
  extension: import_zod3.z.object({
1470
1404
  connected: import_zod3.z.boolean(),
1405
+ extension_id: import_zod3.z.string().optional(),
1471
1406
  last_seen_at: import_zod3.z.string().min(1).optional()
1472
1407
  }).passthrough()
1473
1408
  }).passthrough();
@@ -1482,6 +1417,7 @@ var import_zod4 = require("zod");
1482
1417
 
1483
1418
  // packages/cli/src/core-client.ts
1484
1419
  var import_node_child_process = require("node:child_process");
1420
+ var import_node_fs3 = require("node:fs");
1485
1421
  var import_node_path3 = require("node:path");
1486
1422
  var CoreClientError = class extends Error {
1487
1423
  constructor(info) {
@@ -1504,6 +1440,50 @@ var resolveTimeoutMs2 = (timeoutMs) => {
1504
1440
  };
1505
1441
  var normalizePath = (path9) => path9.startsWith("/") ? path9 : `/${path9}`;
1506
1442
  var durationMs = (startedAt) => Number((Number(process.hrtime.bigint() - startedAt) / 1e6).toFixed(3));
1443
+ var MAX_RESPONSE_PREVIEW_LENGTH = 200;
1444
+ var STALE_DAEMON_GRACE_MS = 1e3;
1445
+ var normalizeResponsePreview = (raw) => {
1446
+ const normalized = raw.replace(/\s+/g, " ").trim();
1447
+ if (normalized.length <= MAX_RESPONSE_PREVIEW_LENGTH) {
1448
+ return normalized;
1449
+ }
1450
+ return `${normalized.slice(0, MAX_RESPONSE_PREVIEW_LENGTH)}...`;
1451
+ };
1452
+ var getHeaderValue = (headers, name) => {
1453
+ if (!headers || typeof headers.get !== "function") {
1454
+ return void 0;
1455
+ }
1456
+ const value = headers.get(name);
1457
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
1458
+ };
1459
+ var buildInvalidCoreResponseError = (options) => {
1460
+ const details = {
1461
+ base_url: options.baseUrl,
1462
+ path: options.path,
1463
+ status: options.status,
1464
+ reason: options.kind === "empty" ? "core_empty_response" : "core_invalid_json_response",
1465
+ next_step: "Verify Browser Bridge core is reachable on the expected host and port, then retry."
1466
+ };
1467
+ if (options.contentType) {
1468
+ details.content_type = options.contentType;
1469
+ }
1470
+ if (options.responseText && options.responseText.trim().length > 0) {
1471
+ details.response_preview = normalizeResponsePreview(options.responseText);
1472
+ }
1473
+ const message = options.kind === "empty" ? "Core returned an empty response." : options.contentType?.toLowerCase().includes("text/html") ? "Core returned HTML instead of JSON." : "Core returned an invalid JSON response.";
1474
+ return new CoreClientError({
1475
+ code: "UNAVAILABLE",
1476
+ message,
1477
+ retryable: true,
1478
+ retry: {
1479
+ retryable: true,
1480
+ reason: options.kind === "empty" ? "core_empty_response" : "core_invalid_json_response",
1481
+ retry_after_ms: 250,
1482
+ max_attempts: 1
1483
+ },
1484
+ details
1485
+ });
1486
+ };
1507
1487
  var createCoreClient = (options = {}) => {
1508
1488
  const logger = options.logger ?? createJsonlLogger({
1509
1489
  stream: "cli",
@@ -1513,6 +1493,15 @@ var createCoreClient = (options = {}) => {
1513
1493
  const spawnImpl = options.spawnImpl ?? import_node_child_process.spawn;
1514
1494
  const timeoutMs = resolveTimeoutMs2(options.timeoutMs);
1515
1495
  const componentVersion = process.env.BROWSER_BRIDGE_VERSION ?? process.env.npm_package_version;
1496
+ const coreEntry = (0, import_node_path3.resolve)(__dirname, "api.js");
1497
+ const currentBuildTimeMs = options.currentBuildTimeMs ?? (() => {
1498
+ try {
1499
+ return (0, import_node_fs3.statSync)(coreEntry).mtimeMs;
1500
+ } catch {
1501
+ return void 0;
1502
+ }
1503
+ })();
1504
+ const killProcess = options.killProcess ?? ((pid) => process.kill(pid));
1516
1505
  const readiness = createCoreReadinessController({
1517
1506
  host: options.host,
1518
1507
  port: options.port,
@@ -1524,7 +1513,7 @@ var createCoreClient = (options = {}) => {
1524
1513
  logger,
1525
1514
  logPrefix: "cli.core",
1526
1515
  spawnDaemon: (runtime) => {
1527
- const coreEntry = (0, import_node_path3.resolve)(__dirname, "api.js");
1516
+ const coreEntry2 = (0, import_node_path3.resolve)(__dirname, "api.js");
1528
1517
  const startOptions = [];
1529
1518
  if (runtime.hostSource === "option" || runtime.hostSource === "env") {
1530
1519
  startOptions.push(`host: ${JSON.stringify(runtime.host)}`);
@@ -1533,7 +1522,7 @@ var createCoreClient = (options = {}) => {
1533
1522
  startOptions.push(`port: ${runtime.port}`);
1534
1523
  }
1535
1524
  const script = `const { startCoreServer } = require(${JSON.stringify(
1536
- coreEntry
1525
+ coreEntry2
1537
1526
  )});
1538
1527
  startCoreServer({ ${startOptions.join(
1539
1528
  ", "
@@ -1617,6 +1606,7 @@ startCoreServer({ ${startOptions.join(
1617
1606
  });
1618
1607
  throw error;
1619
1608
  }
1609
+ const contentType = getHeaderValue(response.headers, "content-type");
1620
1610
  const raw = await response.text();
1621
1611
  if (!raw) {
1622
1612
  logger.warn("cli.core.request.empty_response", {
@@ -1624,9 +1614,16 @@ startCoreServer({ ${startOptions.join(
1624
1614
  path: requestPath,
1625
1615
  base_url: readiness.baseUrl,
1626
1616
  status: response.status,
1617
+ content_type: contentType,
1627
1618
  duration_ms: durationMs(startedAt)
1628
1619
  });
1629
- throw new Error(`Empty response from Core (${response.status}).`);
1620
+ throw buildInvalidCoreResponseError({
1621
+ kind: "empty",
1622
+ status: response.status,
1623
+ baseUrl: readiness.baseUrl,
1624
+ path: requestPath,
1625
+ contentType
1626
+ });
1630
1627
  }
1631
1628
  try {
1632
1629
  const parsed = JSON.parse(raw);
@@ -1639,23 +1636,83 @@ startCoreServer({ ${startOptions.join(
1639
1636
  });
1640
1637
  return parsed;
1641
1638
  } catch (error) {
1642
- const message = error instanceof Error ? error.message : "Unknown JSON parse error";
1643
1639
  logger.error("cli.core.request.invalid_json", {
1644
1640
  method,
1645
1641
  path: requestPath,
1646
1642
  base_url: readiness.baseUrl,
1647
1643
  status: response.status,
1644
+ content_type: contentType,
1645
+ response_preview: normalizeResponsePreview(raw),
1648
1646
  duration_ms: durationMs(startedAt),
1649
1647
  error
1650
1648
  });
1651
- throw new Error(`Failed to parse Core response: ${message}`);
1649
+ throw buildInvalidCoreResponseError({
1650
+ kind: "invalid_json",
1651
+ status: response.status,
1652
+ baseUrl: readiness.baseUrl,
1653
+ path: requestPath,
1654
+ contentType,
1655
+ responseText: raw
1656
+ });
1652
1657
  }
1653
1658
  } finally {
1654
1659
  clearTimeout(timeout);
1655
1660
  }
1656
1661
  };
1657
- const post = async (path9, body) => {
1662
+ const maybeRestartStaleDaemon = async () => {
1663
+ if (currentBuildTimeMs === void 0) {
1664
+ return;
1665
+ }
1666
+ let payload;
1667
+ try {
1668
+ payload = await requestJson(
1669
+ "POST",
1670
+ "/health/check",
1671
+ {}
1672
+ );
1673
+ } catch {
1674
+ return;
1675
+ }
1676
+ if (!payload.ok || !payload.result?.started_at) {
1677
+ return;
1678
+ }
1679
+ const startedAtMs = Date.parse(payload.result.started_at);
1680
+ if (!Number.isFinite(startedAtMs)) {
1681
+ return;
1682
+ }
1683
+ if (startedAtMs + STALE_DAEMON_GRACE_MS >= currentBuildTimeMs) {
1684
+ return;
1685
+ }
1686
+ const pidValue = payload.result.pid;
1687
+ if (!Number.isInteger(pidValue) || pidValue === process.pid) {
1688
+ return;
1689
+ }
1690
+ const pid = pidValue;
1691
+ logger.warn("cli.core.ensure_ready.stale_daemon", {
1692
+ base_url: readiness.baseUrl,
1693
+ pid,
1694
+ started_at: payload.result.started_at,
1695
+ current_build_time_ms: currentBuildTimeMs
1696
+ });
1697
+ try {
1698
+ killProcess(pid);
1699
+ } catch (error) {
1700
+ logger.warn("cli.core.ensure_ready.stale_daemon_kill_failed", {
1701
+ base_url: readiness.baseUrl,
1702
+ pid,
1703
+ error
1704
+ });
1705
+ return;
1706
+ }
1707
+ await new Promise((resolve4) => setTimeout(resolve4, 150));
1658
1708
  await readiness.ensureReady();
1709
+ };
1710
+ const ensureReady = async () => {
1711
+ await readiness.ensureReady();
1712
+ await maybeRestartStaleDaemon();
1713
+ };
1714
+ const post = async (path9, body) => {
1715
+ await ensureReady();
1659
1716
  readiness.refreshRuntime();
1660
1717
  const payload = path9 === "/diagnostics/doctor" && (!body || typeof body === "object" && !Array.isArray(body)) ? {
1661
1718
  ...body && typeof body === "object" ? body : {},
@@ -1665,9 +1722,7 @@ startCoreServer({ ${startOptions.join(
1665
1722
  port: readiness.runtime.port,
1666
1723
  base_url: readiness.baseUrl,
1667
1724
  host_source: readiness.runtime.hostSource,
1668
- port_source: readiness.runtime.portSource,
1669
- metadata_path: readiness.runtime.metadataPath,
1670
- isolated_mode: readiness.runtime.isolatedMode
1725
+ port_source: readiness.runtime.portSource
1671
1726
  },
1672
1727
  process: {
1673
1728
  component: "cli",
@@ -1685,7 +1740,7 @@ startCoreServer({ ${startOptions.join(
1685
1740
  get baseUrl() {
1686
1741
  return readiness.baseUrl;
1687
1742
  },
1688
- ensureReady: readiness.ensureReady,
1743
+ ensureReady,
1689
1744
  post
1690
1745
  };
1691
1746
  };
@@ -1962,35 +2017,9 @@ var registerDiagnosticsCommands = (program2) => {
1962
2017
  });
1963
2018
  };
1964
2019
 
1965
- // packages/cli/src/open-path.ts
1966
- var import_node_child_process2 = require("node:child_process");
1967
- var openPath = async (target) => {
1968
- const platform = process.platform;
1969
- if (platform === "darwin") {
1970
- const child2 = (0, import_node_child_process2.spawn)("open", [target], { detached: true, stdio: "ignore" });
1971
- child2.unref();
1972
- return;
1973
- }
1974
- if (platform === "win32") {
1975
- const child2 = (0, import_node_child_process2.spawn)("cmd", ["/c", "start", "", target], {
1976
- detached: true,
1977
- stdio: "ignore"
1978
- });
1979
- child2.unref();
1980
- return;
1981
- }
1982
- const child = (0, import_node_child_process2.spawn)("xdg-open", [target], {
1983
- detached: true,
1984
- stdio: "ignore"
1985
- });
1986
- child.unref();
1987
- };
1988
-
1989
2020
  // packages/cli/src/commands/dev.ts
1990
2021
  var ENV_EXTENSION_ID = "BROWSER_BRIDGE_EXTENSION_ID";
1991
- var ACTIVATION_FLAG_PARAM = "bb_activate";
1992
- var ACTIVATION_PORT_PARAM = "corePort";
1993
- var ACTIVATION_WORKTREE_PARAM = "worktreeId";
2022
+ var INSPECT_PROBE_RETRY_DELAYS_MS = [150, 300, 600];
1994
2023
  var normalizeToken = (value) => {
1995
2024
  if (typeof value !== "string") {
1996
2025
  return void 0;
@@ -2007,45 +2036,102 @@ var resolveActivationExtensionId = (options) => {
2007
2036
  if (envId) {
2008
2037
  return { extensionId: envId, source: "env" };
2009
2038
  }
2010
- const metadataId = normalizeToken(options.metadataExtensionId);
2011
- if (metadataId) {
2012
- return { extensionId: metadataId, source: "metadata" };
2013
- }
2014
2039
  return null;
2015
2040
  };
2016
- var buildActivationOptionsUrl = (options) => {
2017
- const search = new URLSearchParams();
2018
- search.set(ACTIVATION_FLAG_PARAM, "1");
2019
- search.set(ACTIVATION_PORT_PARAM, String(options.corePort));
2020
- if (options.worktreeId) {
2021
- search.set(ACTIVATION_WORKTREE_PARAM, options.worktreeId);
2022
- }
2023
- return `chrome-extension://${options.extensionId}/options.html?${search.toString()}`;
2024
- };
2025
- var buildPersistedRuntimeMetadata = (runtime, extensionId) => ({
2026
- ...runtime.metadata ?? {},
2027
- host: runtime.host,
2028
- port: runtime.port,
2029
- git_root: runtime.gitRoot ?? runtime.metadata?.git_root,
2030
- worktree_id: runtime.worktreeId ?? runtime.metadata?.worktree_id,
2031
- extension_id: extensionId,
2032
- isolated_mode: true,
2033
- updated_at: (/* @__PURE__ */ new Date()).toISOString()
2034
- });
2035
- var resolveRuntimeForCommand = (options, overrides = {}) => {
2041
+ var resolveRuntimeForCommand = (options) => {
2036
2042
  const runtimeOptions = {
2037
2043
  host: options.host,
2038
2044
  port: options.port,
2039
2045
  strictEnvPort: true
2040
2046
  };
2041
- if (overrides.isolatedMode !== void 0) {
2042
- runtimeOptions.isolatedMode = overrides.isolatedMode;
2043
- }
2044
2047
  return resolveCoreRuntime(runtimeOptions);
2045
2048
  };
2049
+ var hasPassingCheck = (report, name) => report?.checks?.some((check) => check.name === name && check.ok) ?? false;
2050
+ var getReportedExtensionId = (report) => normalizeToken(report?.runtime?.extension?.extension_id);
2051
+ var inspectCapabilityReady = (report, expectedExtensionId) => {
2052
+ const reportedExtensionId = getReportedExtensionId(report);
2053
+ const extensionIdMatch = !expectedExtensionId || !reportedExtensionId || reportedExtensionId === expectedExtensionId;
2054
+ return extensionIdMatch && hasPassingCheck(report, "inspect.capability");
2055
+ };
2056
+ var sleep = async (ms) => {
2057
+ await new Promise((resolve4) => setTimeout(resolve4, ms));
2058
+ };
2059
+ var shouldRetryInspectProbe = (report, expectedExtensionId) => {
2060
+ if (inspectCapabilityReady(report, expectedExtensionId)) {
2061
+ return false;
2062
+ }
2063
+ const observedExtensionId = getReportedExtensionId(report);
2064
+ if (expectedExtensionId && observedExtensionId && observedExtensionId !== expectedExtensionId) {
2065
+ return false;
2066
+ }
2067
+ const extensionConnected = report?.extension?.connected ?? false;
2068
+ const capabilityNegotiated = report?.runtime?.extension?.capability_negotiated === true || hasPassingCheck(report, "runtime.extension.capability_negotiated");
2069
+ return !extensionConnected || !capabilityNegotiated;
2070
+ };
2071
+ var readDiagnosticReport = async (runtime) => {
2072
+ const client = createCoreClient({
2073
+ host: runtime.host,
2074
+ port: runtime.port,
2075
+ ensureDaemon: true
2076
+ });
2077
+ const envelope2 = await client.post(
2078
+ "/diagnostics/doctor",
2079
+ {}
2080
+ );
2081
+ return envelope2.ok ? envelope2.result : void 0;
2082
+ };
2083
+ var readDiagnosticReportWithRetry = async (runtime, expectedExtensionId) => {
2084
+ let report;
2085
+ for (let attempt = 0; attempt <= INSPECT_PROBE_RETRY_DELAYS_MS.length; attempt += 1) {
2086
+ if (attempt > 0) {
2087
+ const delayMs = INSPECT_PROBE_RETRY_DELAYS_MS[attempt - 1] ?? 0;
2088
+ if (delayMs > 0) {
2089
+ await sleep(delayMs);
2090
+ }
2091
+ }
2092
+ report = await readDiagnosticReport(runtime);
2093
+ if (!shouldRetryInspectProbe(report, expectedExtensionId)) {
2094
+ return report;
2095
+ }
2096
+ }
2097
+ return report;
2098
+ };
2099
+ var buildInspectCapabilityError = (runtime, report, expectedExtension) => {
2100
+ const observedExtensionId = getReportedExtensionId(report);
2101
+ if (expectedExtension && observedExtensionId && observedExtensionId !== expectedExtension.extensionId) {
2102
+ return new CliError({
2103
+ code: "FAILED_PRECONDITION",
2104
+ message: "Inspect capability is available, but the connected extension does not match the requested extension id.",
2105
+ retryable: false,
2106
+ details: {
2107
+ host: runtime.host,
2108
+ port: runtime.port,
2109
+ expectedExtensionId: expectedExtension.extensionId,
2110
+ expectedExtensionIdSource: expectedExtension.source,
2111
+ observedExtensionId,
2112
+ next_step: "Clear the stale extension-id override or reload the intended Browser Bridge extension, then retry."
2113
+ }
2114
+ });
2115
+ }
2116
+ return new CliError({
2117
+ code: "FAILED_PRECONDITION",
2118
+ message: "Inspect capability is unavailable in a build where it should already be enabled.",
2119
+ retryable: true,
2120
+ details: {
2121
+ host: runtime.host,
2122
+ port: runtime.port,
2123
+ expectedExtensionId: expectedExtension?.extensionId,
2124
+ expectedExtensionIdSource: expectedExtension?.source,
2125
+ observedConnected: report?.extension?.connected ?? false,
2126
+ observedExtensionId,
2127
+ observedInspectCapability: hasPassingCheck(report, "inspect.capability"),
2128
+ next_step: "Restart the Browser Bridge core daemon, then reload or update the Browser Bridge extension and retry."
2129
+ }
2130
+ });
2131
+ };
2046
2132
  var registerDevCommands = (program2) => {
2047
2133
  const dev = program2.command("dev").description("Development commands");
2048
- dev.command("info").description("Print resolved runtime details for the current worktree").action(async (_options, command) => {
2134
+ dev.command("info").description("Print resolved runtime details for the current environment").action(async (_options, command) => {
2049
2135
  await runLocal(command, async (globalOptions) => {
2050
2136
  const runtime = resolveRuntimeForCommand(globalOptions);
2051
2137
  const logDir = resolveLogDirectory({ gitRoot: runtime.gitRoot });
@@ -2056,8 +2142,6 @@ var registerDevCommands = (program2) => {
2056
2142
  hostSource: runtime.hostSource,
2057
2143
  port: runtime.port,
2058
2144
  portSource: runtime.portSource,
2059
- deterministicPort: runtime.deterministicPort,
2060
- worktreeId: runtime.worktreeId,
2061
2145
  metadataPath: runtime.metadataPath,
2062
2146
  logDir,
2063
2147
  metadataSnapshot: runtime.metadata
@@ -2065,51 +2149,43 @@ var registerDevCommands = (program2) => {
2065
2149
  };
2066
2150
  });
2067
2151
  });
2068
- dev.command("activate").description(
2069
- "Enable isolated worktree routing and open extension options for activation"
2070
- ).option(
2152
+ dev.command("enable-inspect").description("Compatibility helper that verifies inspect capability").option(
2071
2153
  "--extension-id <id>",
2072
- "Chrome extension id to activate for isolated worktree routing"
2154
+ "Chrome extension id to verify against while checking inspect capability"
2073
2155
  ).action(async (options, command) => {
2074
2156
  await runLocal(command, async (globalOptions) => {
2075
- const runtime = resolveRuntimeForCommand(globalOptions, {
2076
- isolatedMode: true
2077
- });
2078
- const extension = resolveActivationExtensionId({
2157
+ const runtime = resolveRuntimeForCommand(globalOptions);
2158
+ let resolvedExtension = resolveActivationExtensionId({
2079
2159
  optionExtensionId: options.extensionId,
2080
- envExtensionId: process.env[ENV_EXTENSION_ID],
2081
- metadataExtensionId: runtime.metadata?.extension_id
2160
+ envExtensionId: process.env[ENV_EXTENSION_ID]
2082
2161
  });
2083
- if (!extension) {
2084
- throw new CliError({
2085
- code: "INVALID_ARGUMENT",
2086
- message: "Missing extension id. Provide --extension-id <id>, set BROWSER_BRIDGE_EXTENSION_ID, or persist extension_id in metadata by running dev activate with --extension-id once (only needed for isolated worktree routing).",
2087
- retryable: false,
2088
- details: {
2089
- metadataPath: runtime.metadataPath
2090
- }
2091
- });
2092
- }
2093
- const metadataPath = writeRuntimeMetadata(
2094
- buildPersistedRuntimeMetadata(runtime, extension.extensionId),
2095
- { metadataPath: runtime.metadataPath }
2162
+ const report = await readDiagnosticReportWithRetry(
2163
+ runtime,
2164
+ resolvedExtension?.extensionId
2096
2165
  );
2097
- const activationUrl = buildActivationOptionsUrl({
2098
- extensionId: extension.extensionId,
2099
- corePort: runtime.port,
2100
- worktreeId: runtime.worktreeId
2101
- });
2102
- await openPath(activationUrl);
2166
+ if (!resolvedExtension) {
2167
+ const reportedExtensionId = getReportedExtensionId(report);
2168
+ if (reportedExtensionId) {
2169
+ resolvedExtension = {
2170
+ extensionId: reportedExtensionId,
2171
+ source: "connected"
2172
+ };
2173
+ }
2174
+ }
2175
+ if (!inspectCapabilityReady(report, resolvedExtension?.extensionId)) {
2176
+ throw buildInspectCapabilityError(runtime, report, resolvedExtension);
2177
+ }
2103
2178
  return {
2104
2179
  ok: true,
2105
2180
  result: {
2106
- extensionId: extension.extensionId,
2107
- extensionIdSource: extension.source,
2108
2181
  host: runtime.host,
2109
2182
  port: runtime.port,
2110
- isolatedMode: true,
2111
- metadataPath,
2112
- activationUrl
2183
+ inspectAlwaysEnabled: true,
2184
+ checkedWithDiagnostics: true,
2185
+ ...resolvedExtension ? {
2186
+ extensionId: resolvedExtension.extensionId,
2187
+ extensionIdSource: resolvedExtension.source
2188
+ } : {}
2113
2189
  }
2114
2190
  };
2115
2191
  });
@@ -2198,7 +2274,7 @@ var registerDriveCommands = (program2) => {
2198
2274
  drive.command("navigate").description("Navigate to a URL").option(
2199
2275
  "--session-id <id>",
2200
2276
  "Session identifier (auto-created when omitted)"
2201
- ).requiredOption("--url <url>", "URL to navigate to").option("--tab-id <id>", "Tab identifier (defaults to agent window/tab)").option("--wait <mode>", "Wait mode (none, domcontentloaded)").action(async (options, command) => {
2277
+ ).requiredOption("--url <url>", "URL to navigate to").option("--tab-id <id>", "Tab identifier (defaults to agent window/tab)").option("--wait <mode>", "Wait mode (none, domcontentloaded, networkidle)").action(async (options, command) => {
2202
2278
  await runCommand(command, (client) => {
2203
2279
  const payload = parseInput(DriveNavigateInputSchema, {
2204
2280
  ...options.sessionId ? { session_id: options.sessionId } : {},
@@ -2476,9 +2552,23 @@ var registerDriveCommands = (program2) => {
2476
2552
  };
2477
2553
 
2478
2554
  // packages/cli/src/commands/inspect.ts
2555
+ var parseNumber4 = (value) => {
2556
+ if (value === void 0 || value === null || value === "") {
2557
+ return void 0;
2558
+ }
2559
+ const parsed = Number(value);
2560
+ return Number.isFinite(parsed) ? parsed : Number.NaN;
2561
+ };
2562
+ var buildTargetHint = (options) => {
2563
+ const tabId = parseNumber4(options.tabId);
2564
+ if (tabId === void 0) {
2565
+ return void 0;
2566
+ }
2567
+ return { tab_id: tabId };
2568
+ };
2479
2569
  var registerInspectCommands = (program2) => {
2480
2570
  const inspect = program2.command("inspect").description("Inspect commands");
2481
- inspect.command("dom-snapshot").description("Fetch a DOM snapshot").requiredOption("--session-id <id>", "Session identifier").option("--format <format>", "Snapshot format (ax, html)").option("--consistency <mode>", "Consistency mode (best_effort, quiesce)").option("-i, --interactive", "Only include interactive elements").option("-c, --compact", "Remove empty/decorative nodes").option("--max-nodes <n>", "Limit AX snapshot to at most n nodes").option("-s, --selector <selector>", "Limit snapshot to selector").action(async (options, command) => {
2571
+ inspect.command("dom-snapshot").description("Fetch a DOM snapshot").requiredOption("--session-id <id>", "Session identifier").option("--format <format>", "Snapshot format (ax, html)").option("--consistency <mode>", "Consistency mode (best_effort, quiesce)").option("-i, --interactive", "Only include interactive elements").option("-c, --compact", "Remove empty/decorative nodes").option("--max-nodes <n>", "Limit AX snapshot to at most n nodes").option("-s, --selector <selector>", "Limit snapshot to selector").option("--tab-id <id>", "Explicit tab identifier").action(async (options, command) => {
2482
2572
  await runCommand(command, (client) => {
2483
2573
  const payload = parseInput(InspectDomSnapshotInputSchema, {
2484
2574
  session_id: options.sessionId,
@@ -2487,7 +2577,8 @@ var registerInspectCommands = (program2) => {
2487
2577
  interactive: options.interactive,
2488
2578
  compact: options.compact,
2489
2579
  max_nodes: options.maxNodes,
2490
- selector: options.selector
2580
+ selector: options.selector,
2581
+ target: buildTargetHint(options)
2491
2582
  });
2492
2583
  return client.post("/inspect/dom_snapshot", payload);
2493
2584
  });
@@ -2500,7 +2591,7 @@ var registerInspectCommands = (program2) => {
2500
2591
  return client.post("/inspect/dom_diff", payload);
2501
2592
  });
2502
2593
  });
2503
- inspect.command("find").description("Find elements and return refs").requiredOption("--session-id <id>", "Session identifier").argument("<kind>", "Find kind (role, text, label)").argument("<value>", "Role name or text to match").option("--name <name>", "Accessible name to match (role only)").action(async (kind, value, options, command) => {
2594
+ inspect.command("find").description("Find elements and return refs").requiredOption("--session-id <id>", "Session identifier").argument("<kind>", "Find kind (role, text, label)").argument("<value>", "Role name or text to match").option("--name <name>", "Accessible name to match (role only)").option("--tab-id <id>", "Explicit tab identifier").action(async (kind, value, options, command) => {
2504
2595
  await runCommand(command, (client) => {
2505
2596
  const normalizedKind = String(kind ?? "").toLowerCase();
2506
2597
  let payload;
@@ -2509,19 +2600,22 @@ var registerInspectCommands = (program2) => {
2509
2600
  session_id: options.sessionId,
2510
2601
  kind: "role",
2511
2602
  role: value,
2512
- name: options.name
2603
+ name: options.name,
2604
+ target: buildTargetHint(options)
2513
2605
  };
2514
2606
  } else if (normalizedKind === "text") {
2515
2607
  payload = {
2516
2608
  session_id: options.sessionId,
2517
2609
  kind: "text",
2518
- text: value
2610
+ text: value,
2611
+ target: buildTargetHint(options)
2519
2612
  };
2520
2613
  } else if (normalizedKind === "label") {
2521
2614
  payload = {
2522
2615
  session_id: options.sessionId,
2523
2616
  kind: "label",
2524
- label: value
2617
+ label: value,
2618
+ target: buildTargetHint(options)
2525
2619
  };
2526
2620
  } else {
2527
2621
  throw new CliError({
@@ -2535,53 +2629,65 @@ var registerInspectCommands = (program2) => {
2535
2629
  return client.post("/inspect/find", parsed);
2536
2630
  });
2537
2631
  });
2538
- inspect.command("extract-content").description("Extract main content from the page").requiredOption("--session-id <id>", "Session identifier").option("--format <format>", "Output format (markdown, text, article_json)").option("--include-metadata", "Include article metadata").option("--no-include-metadata", "Exclude article metadata").action(async (options, command) => {
2632
+ inspect.command("extract-content").description("Extract main content from the page").requiredOption("--session-id <id>", "Session identifier").option("--format <format>", "Output format (markdown, text, article_json)").option(
2633
+ "--consistency <mode>",
2634
+ "Capture consistency (best_effort, quiesce)"
2635
+ ).option("--include-metadata", "Include article metadata").option("--no-include-metadata", "Exclude article metadata").option("--tab-id <id>", "Explicit tab identifier").action(async (options, command) => {
2539
2636
  await runCommand(command, (client) => {
2540
2637
  const payload = parseInput(InspectExtractContentInputSchema, {
2541
2638
  session_id: options.sessionId,
2542
2639
  format: options.format,
2543
- include_metadata: options.includeMetadata
2640
+ consistency: options.consistency,
2641
+ include_metadata: options.includeMetadata,
2642
+ target: buildTargetHint(options)
2544
2643
  });
2545
2644
  return client.post("/inspect/extract_content", payload);
2546
2645
  });
2547
2646
  });
2548
- inspect.command("page-state").description("Capture form, storage, and cookie state").requiredOption("--session-id <id>", "Session identifier").action(async (options, command) => {
2647
+ inspect.command("page-state").description("Capture form, storage, and cookie state").requiredOption("--session-id <id>", "Session identifier").option("--include-values", "Include captured values instead of redacting").option("--tab-id <id>", "Explicit tab identifier").action(async (options, command) => {
2549
2648
  await runCommand(command, (client) => {
2550
2649
  const payload = parseInput(InspectPageStateInputSchema, {
2551
- session_id: options.sessionId
2650
+ session_id: options.sessionId,
2651
+ include_values: options.includeValues,
2652
+ target: buildTargetHint(options)
2552
2653
  });
2553
2654
  return client.post("/inspect/page_state", payload);
2554
2655
  });
2555
2656
  });
2556
- inspect.command("console-list").description("Fetch console entries").requiredOption("--session-id <id>", "Session identifier").action(async (options, command) => {
2657
+ inspect.command("console-list").description("Fetch console entries").requiredOption("--session-id <id>", "Session identifier").option("--since <iso>", "Only include entries at or after this timestamp").option("--tab-id <id>", "Explicit tab identifier").action(async (options, command) => {
2557
2658
  await runCommand(command, (client) => {
2558
2659
  const payload = parseInput(InspectConsoleListInputSchema, {
2559
- session_id: options.sessionId
2660
+ session_id: options.sessionId,
2661
+ since: options.since,
2662
+ target: buildTargetHint(options)
2560
2663
  });
2561
2664
  return client.post("/inspect/console_list", payload);
2562
2665
  });
2563
2666
  });
2564
- inspect.command("network-har").description("Fetch network HAR").requiredOption("--session-id <id>", "Session identifier").action(async (options, command) => {
2667
+ inspect.command("network-har").description("Fetch network HAR").requiredOption("--session-id <id>", "Session identifier").option("--tab-id <id>", "Explicit tab identifier").action(async (options, command) => {
2565
2668
  await runCommand(command, (client) => {
2566
2669
  const payload = parseInput(InspectNetworkHarInputSchema, {
2567
- session_id: options.sessionId
2670
+ session_id: options.sessionId,
2671
+ target: buildTargetHint(options)
2568
2672
  });
2569
2673
  return client.post("/inspect/network_har", payload);
2570
2674
  });
2571
2675
  });
2572
- inspect.command("evaluate").description("Evaluate a JavaScript expression").requiredOption("--session-id <id>", "Session identifier").option("--expression <expr>", "Expression to evaluate").action(async (options, command) => {
2676
+ inspect.command("evaluate").description("Evaluate a JavaScript expression").requiredOption("--session-id <id>", "Session identifier").option("--expression <expr>", "Expression to evaluate").option("--tab-id <id>", "Explicit tab identifier").action(async (options, command) => {
2573
2677
  await runCommand(command, (client) => {
2574
2678
  const payload = parseInput(InspectEvaluateInputSchema, {
2575
2679
  session_id: options.sessionId,
2576
- expression: options.expression
2680
+ expression: options.expression,
2681
+ target: buildTargetHint(options)
2577
2682
  });
2578
2683
  return client.post("/inspect/evaluate", payload);
2579
2684
  });
2580
2685
  });
2581
- inspect.command("performance-metrics").description("Fetch performance metrics").requiredOption("--session-id <id>", "Session identifier").action(async (options, command) => {
2686
+ inspect.command("performance-metrics").description("Fetch performance metrics").requiredOption("--session-id <id>", "Session identifier").option("--tab-id <id>", "Explicit tab identifier").action(async (options, command) => {
2582
2687
  await runCommand(command, (client) => {
2583
2688
  const payload = parseInput(InspectPerformanceMetricsInputSchema, {
2584
- session_id: options.sessionId
2689
+ session_id: options.sessionId,
2690
+ target: buildTargetHint(options)
2585
2691
  });
2586
2692
  return client.post("/inspect/performance_metrics", payload);
2587
2693
  });
@@ -2589,7 +2695,7 @@ var registerInspectCommands = (program2) => {
2589
2695
  };
2590
2696
 
2591
2697
  // packages/mcp-adapter/src/core-client.ts
2592
- var import_node_child_process3 = require("node:child_process");
2698
+ var import_node_child_process2 = require("node:child_process");
2593
2699
  var import_node_path4 = require("node:path");
2594
2700
  var DEFAULT_TIMEOUT_MS3 = 3e4;
2595
2701
  var normalizePath2 = (path9) => path9.startsWith("/") ? path9 : `/${path9}`;
@@ -2618,7 +2724,7 @@ var createCoreClient2 = (options = {}) => {
2618
2724
  }).child({ scope: "core-client" });
2619
2725
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS3;
2620
2726
  const fetchImpl = options.fetchImpl ?? fetch;
2621
- const spawnImpl = options.spawnImpl ?? import_node_child_process3.spawn;
2727
+ const spawnImpl = options.spawnImpl ?? import_node_child_process2.spawn;
2622
2728
  const ensureDaemon = options.ensureDaemon ?? false;
2623
2729
  const componentVersion = options.componentVersion ?? process.env.BROWSER_BRIDGE_VERSION ?? process.env.npm_package_version;
2624
2730
  const readiness = createCoreReadinessController({
@@ -2750,9 +2856,7 @@ startCoreServer({ ${startOptions.join(
2750
2856
  port: readiness.runtime.port,
2751
2857
  base_url: readiness.baseUrl,
2752
2858
  host_source: readiness.runtime.hostSource,
2753
- port_source: readiness.runtime.portSource,
2754
- metadata_path: readiness.runtime.metadataPath,
2755
- isolated_mode: readiness.runtime.isolatedMode
2859
+ port_source: readiness.runtime.portSource
2756
2860
  },
2757
2861
  process: {
2758
2862
  component: "mcp",
@@ -2798,6 +2902,74 @@ var toInternalErrorEnvelope = (error) => ({
2798
2902
  });
2799
2903
  var envelope = (schema) => successEnvelopeSchema(schema);
2800
2904
  var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2905
+ var readSessionId = (args) => {
2906
+ if (!isRecord(args)) {
2907
+ return void 0;
2908
+ }
2909
+ const sessionId = args.session_id;
2910
+ return typeof sessionId === "string" && sessionId.length > 0 ? sessionId : void 0;
2911
+ };
2912
+ var withPermissionsSource = (source) => (args) => isRecord(args) ? {
2913
+ ...args,
2914
+ source
2915
+ } : args;
2916
+ var supportsSessionMigration = (corePath) => corePath.startsWith("/drive/") || corePath.startsWith("/inspect/") || corePath.startsWith("/artifacts/") || corePath === "/diagnostics/doctor";
2917
+ var isSessionNotFoundEnvelope = (envelopeResult) => {
2918
+ if (envelopeResult.ok) {
2919
+ return false;
2920
+ }
2921
+ const details = envelopeResult.error.details;
2922
+ return envelopeResult.error.code === "NOT_FOUND" && isRecord(details) && details.reason === "session_not_found";
2923
+ };
2924
+ var addSessionRecoveryHint = (envelopeResult) => {
2925
+ if (envelopeResult.ok) {
2926
+ return envelopeResult;
2927
+ }
2928
+ return {
2929
+ ok: false,
2930
+ error: {
2931
+ ...envelopeResult.error,
2932
+ details: {
2933
+ ...isRecord(envelopeResult.error.details) ? envelopeResult.error.details : {},
2934
+ recover_action: "session.create"
2935
+ }
2936
+ }
2937
+ };
2938
+ };
2939
+ var addSessionMigrationNotice = (envelopeResult, staleSessionId, replacementSessionId) => {
2940
+ if (envelopeResult.ok) {
2941
+ if (!isRecord(envelopeResult.result)) {
2942
+ return envelopeResult;
2943
+ }
2944
+ const warning = `Session ${staleSessionId} became stale after runtime switch; retried with ${replacementSessionId}.`;
2945
+ const existingWarnings = Array.isArray(envelopeResult.result.warnings) ? envelopeResult.result.warnings.filter(
2946
+ (item) => typeof item === "string"
2947
+ ) : [];
2948
+ return {
2949
+ ok: true,
2950
+ result: {
2951
+ ...envelopeResult.result,
2952
+ warnings: existingWarnings.includes(warning) ? existingWarnings : [...existingWarnings, warning],
2953
+ session_migration: {
2954
+ stale_session_id: staleSessionId,
2955
+ replacement_session_id: replacementSessionId
2956
+ }
2957
+ }
2958
+ };
2959
+ }
2960
+ return {
2961
+ ok: false,
2962
+ error: {
2963
+ ...envelopeResult.error,
2964
+ details: {
2965
+ ...isRecord(envelopeResult.error.details) ? envelopeResult.error.details : {},
2966
+ stale_session_id: staleSessionId,
2967
+ replacement_session_id: replacementSessionId,
2968
+ recover_action: "session.recover"
2969
+ }
2970
+ }
2971
+ };
2972
+ };
2801
2973
  var addDeprecatedAliasWarning2 = (envelopeResult, deprecationAlias) => {
2802
2974
  if (!deprecationAlias || !envelopeResult.ok || typeof envelopeResult.result !== "object" || !envelopeResult.result) {
2803
2975
  return envelopeResult;
@@ -2854,6 +3026,69 @@ var TOOL_DEFINITIONS = [
2854
3026
  corePath: "/session/close"
2855
3027
  }
2856
3028
  },
3029
+ {
3030
+ name: "permissions.list",
3031
+ config: {
3032
+ title: "Permissions List",
3033
+ description: "List allowlisted Browser Bridge sites.",
3034
+ inputSchema: PermissionsListInputSchema,
3035
+ outputSchema: envelope(PermissionsListOutputSchema),
3036
+ corePath: "/permissions/list"
3037
+ }
3038
+ },
3039
+ {
3040
+ name: "permissions.get_mode",
3041
+ config: {
3042
+ title: "Permissions Get Mode",
3043
+ description: "Read the current Browser Bridge permissions mode.",
3044
+ inputSchema: PermissionsGetModeInputSchema,
3045
+ outputSchema: envelope(PermissionsGetModeOutputSchema),
3046
+ corePath: "/permissions/get_mode"
3047
+ }
3048
+ },
3049
+ {
3050
+ name: "permissions.list_pending_requests",
3051
+ config: {
3052
+ title: "Permissions List Pending Requests",
3053
+ description: "List pending external Browser Bridge permission-change requests.",
3054
+ inputSchema: PermissionsListPendingRequestsInputSchema,
3055
+ outputSchema: envelope(PermissionsListPendingRequestsOutputSchema),
3056
+ corePath: "/permissions/list_pending_requests"
3057
+ }
3058
+ },
3059
+ {
3060
+ name: "permissions.request_allow_site",
3061
+ config: {
3062
+ title: "Permissions Request Allow Site",
3063
+ description: "Request allowlisting a site. A human must approve the change in Chrome before it applies.",
3064
+ inputSchema: PermissionsRequestAllowSiteInputSchema,
3065
+ outputSchema: envelope(PermissionsRequestOutputSchema),
3066
+ corePath: "/permissions/request_allow_site",
3067
+ transformInput: withPermissionsSource("mcp")
3068
+ }
3069
+ },
3070
+ {
3071
+ name: "permissions.request_revoke_site",
3072
+ config: {
3073
+ title: "Permissions Request Revoke Site",
3074
+ description: "Request revoking a site from the allowlist. A human must approve the change in Chrome before it applies.",
3075
+ inputSchema: PermissionsRequestRevokeSiteInputSchema,
3076
+ outputSchema: envelope(PermissionsRequestOutputSchema),
3077
+ corePath: "/permissions/request_revoke_site",
3078
+ transformInput: withPermissionsSource("mcp")
3079
+ }
3080
+ },
3081
+ {
3082
+ name: "permissions.request_set_mode",
3083
+ config: {
3084
+ title: "Permissions Request Set Mode",
3085
+ description: "Request changing Browser Bridge permission mode. A human must approve the change in Chrome before it applies.",
3086
+ inputSchema: PermissionsRequestSetModeInputSchema,
3087
+ outputSchema: envelope(PermissionsRequestOutputSchema),
3088
+ corePath: "/permissions/request_set_mode",
3089
+ transformInput: withPermissionsSource("mcp")
3090
+ }
3091
+ },
2857
3092
  {
2858
3093
  name: "drive.navigate",
2859
3094
  config: {
@@ -3208,10 +3443,26 @@ var createToolHandler = (clientProvider, corePath, deprecationAlias, transformIn
3208
3443
  void _extra;
3209
3444
  try {
3210
3445
  const client = typeof clientProvider === "function" ? await clientProvider() : clientProvider;
3211
- const envelopeResult = await client.post(
3212
- corePath,
3213
- transformInput ? transformInput(args) : args
3214
- );
3446
+ const transformedArgs = transformInput ? transformInput(args) : args;
3447
+ const staleSessionId = readSessionId(transformedArgs);
3448
+ let envelopeResult = await client.post(corePath, transformedArgs);
3449
+ if (staleSessionId && supportsSessionMigration(corePath) && isSessionNotFoundEnvelope(envelopeResult) && isRecord(transformedArgs)) {
3450
+ const createdSession = await client.post("/session/create", {});
3451
+ if (createdSession.ok && isRecord(createdSession.result) && typeof createdSession.result.session_id === "string" && createdSession.result.session_id.length > 0) {
3452
+ const replacementSessionId = createdSession.result.session_id;
3453
+ const retryArgs = {
3454
+ ...transformedArgs,
3455
+ session_id: replacementSessionId
3456
+ };
3457
+ envelopeResult = addSessionMigrationNotice(
3458
+ await client.post(corePath, retryArgs),
3459
+ staleSessionId,
3460
+ replacementSessionId
3461
+ );
3462
+ } else {
3463
+ envelopeResult = addSessionRecoveryHint(envelopeResult);
3464
+ }
3465
+ }
3215
3466
  return toToolResult(
3216
3467
  addDeprecatedAliasWarning2(envelopeResult, deprecationAlias)
3217
3468
  );
@@ -3357,7 +3608,7 @@ var DEFAULT_HTTP_PATH = "/mcp";
3357
3608
  var ENV_MCP_EAGER = "BROWSER_BRIDGE_MCP_EAGER";
3358
3609
  var ENV_LEGACY_MCP_EAGER = "BROWSER_VISION_MCP_EAGER";
3359
3610
  var durationMs4 = (startedAt) => Number((Number(process.hrtime.bigint() - startedAt) / 1e6).toFixed(3));
3360
- var parseBoolean2 = (value) => {
3611
+ var parseBoolean = (value) => {
3361
3612
  if (value === void 0) {
3362
3613
  return void 0;
3363
3614
  }
@@ -3374,7 +3625,7 @@ var resolveEagerMode = (explicit) => {
3374
3625
  if (typeof explicit === "boolean") {
3375
3626
  return explicit;
3376
3627
  }
3377
- const envValue = parseBoolean2(process.env[ENV_MCP_EAGER]) ?? parseBoolean2(process.env[ENV_LEGACY_MCP_EAGER]);
3628
+ const envValue = parseBoolean(process.env[ENV_MCP_EAGER]) ?? parseBoolean(process.env[ENV_LEGACY_MCP_EAGER]);
3378
3629
  return envValue ?? false;
3379
3630
  };
3380
3631
  var toCoreClientOptions = (options, logger) => ({
@@ -3561,7 +3812,7 @@ var readJsonBody = async (req, maxBytes = 5 * 1024 * 1024) => {
3561
3812
  }
3562
3813
  return JSON.parse(raw);
3563
3814
  };
3564
- var getHeaderValue = (value) => {
3815
+ var getHeaderValue2 = (value) => {
3565
3816
  if (typeof value === "string") {
3566
3817
  return value;
3567
3818
  }
@@ -3627,7 +3878,7 @@ var startMcpHttpServer = async (options = {}) => {
3627
3878
  });
3628
3879
  return;
3629
3880
  }
3630
- const sessionId = getHeaderValue(req.headers["mcp-session-id"]);
3881
+ const sessionId = getHeaderValue2(req.headers["mcp-session-id"]);
3631
3882
  requestLogger.debug("mcp.http.request.session", {
3632
3883
  session_id: sessionId ?? null
3633
3884
  });
@@ -3786,7 +4037,7 @@ var checkboxPrompt = async (options) => {
3786
4037
  };
3787
4038
 
3788
4039
  // packages/cli/src/installer/mcp-install.ts
3789
- var import_node_child_process4 = require("node:child_process");
4040
+ var import_node_child_process3 = require("node:child_process");
3790
4041
 
3791
4042
  // packages/cli/src/installer/cursor-mcp.ts
3792
4043
  var import_promises2 = __toESM(require("node:fs/promises"));
@@ -3857,7 +4108,7 @@ var installCursorMcp = async (settingsPath) => {
3857
4108
  // packages/cli/src/installer/mcp-install.ts
3858
4109
  var runQuiet = async (cmd, args) => {
3859
4110
  await new Promise((resolve4, reject) => {
3860
- const child = (0, import_node_child_process4.spawn)(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
4111
+ const child = (0, import_node_child_process3.spawn)(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
3861
4112
  let stderr = "";
3862
4113
  child.stderr?.on("data", (chunk) => {
3863
4114
  stderr += String(chunk);
@@ -4026,11 +4277,6 @@ var import_express2 = __toESM(require("express"));
4026
4277
  // packages/core/src/routes/session.ts
4027
4278
  var import_express = require("express");
4028
4279
 
4029
- // packages/core/src/inspect/service.ts
4030
- var import_readability = require("@mozilla/readability");
4031
- var import_jsdom = require("jsdom");
4032
- var import_turndown = __toESM(require("turndown"));
4033
-
4034
4280
  // packages/core/src/artifacts.ts
4035
4281
  var import_promises3 = require("node:fs/promises");
4036
4282
  var import_node_os2 = __toESM(require("node:os"));
@@ -4063,106 +4309,10 @@ var DriveMutex = class {
4063
4309
  };
4064
4310
  var driveMutex = new DriveMutex();
4065
4311
 
4066
- // packages/core/src/page-state-script.ts
4067
- var PAGE_STATE_SCRIPT = [
4068
- "(() => {",
4069
- " const escape = (value) => {",
4070
- " if (typeof CSS !== 'undefined' && typeof CSS.escape === 'function') {",
4071
- " return CSS.escape(value);",
4072
- " }",
4073
- ` return String(value).replace(/["'\\\\]/g, '\\\\$&');`,
4074
- " };",
4075
- " const truncate = (value, max) => {",
4076
- " const text = String(value ?? '');",
4077
- " return text.length > max ? text.slice(0, max) : text;",
4078
- " };",
4079
- " const selectorFor = (element) => {",
4080
- " if (element.id) {",
4081
- " return `#${escape(element.id)}`;",
4082
- " }",
4083
- " const name = element.getAttribute('name');",
4084
- " if (name) {",
4085
- ' return `${element.tagName.toLowerCase()}[name="${escape(name)}"]`;',
4086
- " }",
4087
- " const parts = [];",
4088
- " let node = element;",
4089
- " while (node && node.nodeType === 1 && parts.length < 4) {",
4090
- " let part = node.tagName.toLowerCase();",
4091
- " const parent = node.parentElement;",
4092
- " if (parent) {",
4093
- " const siblings = Array.from(parent.children).filter(",
4094
- " (child) => child.tagName === node.tagName",
4095
- " );",
4096
- " if (siblings.length > 1) {",
4097
- " part += `:nth-of-type(${siblings.indexOf(node) + 1})`;",
4098
- " }",
4099
- " }",
4100
- " parts.unshift(part);",
4101
- " node = parent;",
4102
- " }",
4103
- " return parts.join('>');",
4104
- " };",
4105
- " const readStorage = (storage, limit) => {",
4106
- " try {",
4107
- " return Object.keys(storage)",
4108
- " .slice(0, limit)",
4109
- " .map((key) => ({",
4110
- " key,",
4111
- " value: truncate(storage.getItem(key), 500),",
4112
- " }));",
4113
- " } catch {",
4114
- " return [];",
4115
- " }",
4116
- " };",
4117
- " const forms = Array.from(document.querySelectorAll('form')).map((form) => {",
4118
- " const fields = Array.from(form.elements)",
4119
- " .filter((element) => element && element.tagName)",
4120
- " .map((element) => {",
4121
- " const tag = element.tagName.toLowerCase();",
4122
- " const type = 'type' in element && element.type ? element.type : tag;",
4123
- " const name = element.name || element.getAttribute('name') || element.id || '';",
4124
- " let value = '';",
4125
- " let options;",
4126
- " if (tag === 'select') {",
4127
- " const select = element;",
4128
- " value = select.value ?? '';",
4129
- " options = Array.from(select.options).map((option) => option.text);",
4130
- " } else if (tag === 'input' && element.type === 'password') {",
4131
- " value = '[redacted]';",
4132
- " } else if (tag === 'input' || tag === 'textarea') {",
4133
- " value = element.value ?? '';",
4134
- " } else if (element.isContentEditable) {",
4135
- " value = element.textContent ?? '';",
4136
- " } else if ('value' in element) {",
4137
- " value = element.value ?? '';",
4138
- " }",
4139
- " return {",
4140
- " name,",
4141
- " type,",
4142
- " value: truncate(value, 500),",
4143
- " ...(options ? { options } : {}),",
4144
- " };",
4145
- " });",
4146
- " return {",
4147
- " selector: selectorFor(form),",
4148
- " action: form.getAttribute('action') || undefined,",
4149
- " method: form.getAttribute('method') || undefined,",
4150
- " fields,",
4151
- " };",
4152
- " });",
4153
- " const localStorage = readStorage(window.localStorage, 100);",
4154
- " const sessionStorage = readStorage(window.sessionStorage, 100);",
4155
- " const cookies = (document.cookie ? document.cookie.split(';') : [])",
4156
- " .map((entry) => entry.trim())",
4157
- " .filter((entry) => entry.length > 0)",
4158
- " .slice(0, 50)",
4159
- " .map((entry) => {",
4160
- " const [key, ...rest] = entry.split('=');",
4161
- " return { key, value: truncate(rest.join('='), 500) };",
4162
- " });",
4163
- " return { forms, localStorage, sessionStorage, cookies };",
4164
- "})()"
4165
- ].join("\n");
4312
+ // packages/core/src/inspect/extract-content-policy.ts
4313
+ var import_readability = require("@mozilla/readability");
4314
+ var import_jsdom = require("jsdom");
4315
+ var import_turndown = __toESM(require("turndown"));
4166
4316
 
4167
4317
  // packages/core/src/diagnostics.ts
4168
4318
  var STALE_ERROR_THRESHOLD_MS = 2 * 60 * 1e3;
@@ -4172,6 +4322,32 @@ var PROCESS_STARTED_AT = new Date(
4172
4322
  Date.now() - Math.floor(process.uptime() * 1e3)
4173
4323
  ).toISOString();
4174
4324
 
4325
+ // packages/cli/src/open-path.ts
4326
+ var import_node_child_process4 = require("node:child_process");
4327
+ var shouldOpenInChrome = (target) => /^chrome(?:-extension)?:\/\//.test(target);
4328
+ var openPath = async (target) => {
4329
+ const platform = process.platform;
4330
+ if (platform === "darwin") {
4331
+ const args = shouldOpenInChrome(target) ? ["-a", "Google Chrome", target] : [target];
4332
+ const child2 = (0, import_node_child_process4.spawn)("open", args, { detached: true, stdio: "ignore" });
4333
+ child2.unref();
4334
+ return;
4335
+ }
4336
+ if (platform === "win32") {
4337
+ const child2 = (0, import_node_child_process4.spawn)("cmd", ["/c", "start", "", target], {
4338
+ detached: true,
4339
+ stdio: "ignore"
4340
+ });
4341
+ child2.unref();
4342
+ return;
4343
+ }
4344
+ const child = (0, import_node_child_process4.spawn)("xdg-open", [target], {
4345
+ detached: true,
4346
+ stdio: "ignore"
4347
+ });
4348
+ child.unref();
4349
+ };
4350
+
4175
4351
  // packages/cli/src/commands/open-artifacts.ts
4176
4352
  var registerOpenArtifactsCommand = (program2) => {
4177
4353
  program2.command("open-artifacts").description("Open the artifact folder for a session").requiredOption("--session-id <id>", "Session identifier").action(async (options, command) => {
@@ -4186,6 +4362,78 @@ var registerOpenArtifactsCommand = (program2) => {
4186
4362
  });
4187
4363
  };
4188
4364
 
4365
+ // packages/cli/src/commands/permissions.ts
4366
+ var parseNumber5 = (value) => {
4367
+ if (value === void 0 || value === null || value === "") {
4368
+ return void 0;
4369
+ }
4370
+ const parsed = Number(value);
4371
+ return Number.isFinite(parsed) ? parsed : Number.NaN;
4372
+ };
4373
+ var registerPermissionsCommands = (program2) => {
4374
+ const permissions = program2.command("permissions").description("Manage Browser Bridge permissions");
4375
+ permissions.command("list").description("List allowlisted sites").action(async (_options, command) => {
4376
+ await runCommand(command, (client) => {
4377
+ const payload = parseInput(PermissionsListInputSchema, {});
4378
+ return client.post("/permissions/list", payload);
4379
+ });
4380
+ });
4381
+ permissions.command("mode").description("Show the current permissions mode").action(async (_options, command) => {
4382
+ await runCommand(command, (client) => {
4383
+ const payload = parseInput(PermissionsGetModeInputSchema, {});
4384
+ return client.post("/permissions/get_mode", payload);
4385
+ });
4386
+ });
4387
+ permissions.command("pending").description("List pending external permission-change requests").action(async (_options, command) => {
4388
+ await runCommand(command, (client) => {
4389
+ const payload = parseInput(
4390
+ PermissionsListPendingRequestsInputSchema,
4391
+ {}
4392
+ );
4393
+ return client.post("/permissions/list_pending_requests", payload);
4394
+ });
4395
+ });
4396
+ permissions.command("allow-site").description("Request allowlisting a site").requiredOption("--site <site>", "Site hostname[:port] to allowlist").option(
4397
+ "--timeout-ms <ms>",
4398
+ "How long to wait for approval before returning"
4399
+ ).action(async (options, command) => {
4400
+ await runCommand(command, (client) => {
4401
+ const payload = parseInput(PermissionsRequestAllowSiteInputSchema, {
4402
+ site: options.site,
4403
+ timeout_ms: parseNumber5(options.timeoutMs),
4404
+ source: "cli"
4405
+ });
4406
+ return client.post("/permissions/request_allow_site", payload);
4407
+ });
4408
+ });
4409
+ permissions.command("revoke-site").description("Request revoking a site from the allowlist").requiredOption("--site <site>", "Site hostname[:port] to revoke").option(
4410
+ "--timeout-ms <ms>",
4411
+ "How long to wait for approval before returning"
4412
+ ).action(async (options, command) => {
4413
+ await runCommand(command, (client) => {
4414
+ const payload = parseInput(PermissionsRequestRevokeSiteInputSchema, {
4415
+ site: options.site,
4416
+ timeout_ms: parseNumber5(options.timeoutMs),
4417
+ source: "cli"
4418
+ });
4419
+ return client.post("/permissions/request_revoke_site", payload);
4420
+ });
4421
+ });
4422
+ permissions.command("set-mode").description("Request changing the permissions mode").requiredOption("--mode <mode>", "Mode to request (granular or bypass)").option(
4423
+ "--timeout-ms <ms>",
4424
+ "How long to wait for approval before returning"
4425
+ ).action(async (options, command) => {
4426
+ await runCommand(command, (client) => {
4427
+ const payload = parseInput(PermissionsRequestSetModeInputSchema, {
4428
+ mode: options.mode,
4429
+ timeout_ms: parseNumber5(options.timeoutMs),
4430
+ source: "cli"
4431
+ });
4432
+ return client.post("/permissions/request_set_mode", payload);
4433
+ });
4434
+ });
4435
+ };
4436
+
4189
4437
  // packages/cli/src/commands/session.ts
4190
4438
  var registerSessionCommands = (program2) => {
4191
4439
  const session = program2.command("session").description("Manage Core sessions");
@@ -4331,14 +4579,14 @@ var resolveSkillSourceDir = async () => {
4331
4579
  } catch {
4332
4580
  }
4333
4581
  const repoRoot = import_node_path8.default.resolve(rootDir, "..", "..");
4334
- const docsSkill = import_node_path8.default.join(repoRoot, "docs", "skills", "browser-bridge");
4582
+ const repoSkill = import_node_path8.default.join(repoRoot, ".agents", "skills", "browser-bridge");
4335
4583
  try {
4336
- await import_promises4.default.stat(docsSkill);
4337
- return docsSkill;
4584
+ await import_promises4.default.stat(repoSkill);
4585
+ return repoSkill;
4338
4586
  } catch {
4339
4587
  }
4340
4588
  throw new Error(
4341
- `Unable to locate packaged skill. Expected ${packaged} (npm install) or ${docsSkill} (repo dev).`
4589
+ `Unable to locate packaged skill. Expected ${packaged} (npm install) or ${repoSkill} (repo dev).`
4342
4590
  );
4343
4591
  };
4344
4592
 
@@ -4635,6 +4883,7 @@ var main = async () => {
4635
4883
  "Output Browser Bridge CLI version"
4636
4884
  ).option("--host <host>", "Core host (default: 127.0.0.1)").option("--port <port>", "Core port (default: 3210)").option("--json", "Output JSON").option("--no-daemon", "Disable auto-starting Core");
4637
4885
  registerSessionCommands(program);
4886
+ registerPermissionsCommands(program);
4638
4887
  registerDriveCommands(program);
4639
4888
  registerInspectCommands(program);
4640
4889
  registerArtifactsCommands(program);