browser-pilot-cli 0.3.0 → 0.4.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/daemon.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import http2 from "http";
3
3
  import { unlinkSync as unlinkSync2, chmodSync as chmodSync2 } from "fs";
4
4
  import { createHash as createHash5, randomUUID as randomUUID14 } from "crypto";
5
- import { join as join5 } from "path";
5
+ import { join as join6 } from "path";
6
6
 
7
7
  // src/paths.ts
8
8
  import { createHash } from "crypto";
@@ -143,37 +143,37 @@ function isSafeTimestamp(value) {
143
143
  }
144
144
  function validExecutable(value) {
145
145
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
146
- const record = value;
147
- return typeof record.version === "string" && record.version.length > 0 && record.version.length <= 128 && typeof record.path === "string" && record.path.length > 0 && record.path.length <= 4096 && isAbsolute2(record.path) && typeof record.identity === "string" && /^executable:[a-f0-9]{64}$/.test(record.identity);
146
+ const record2 = value;
147
+ return typeof record2.version === "string" && record2.version.length > 0 && record2.version.length <= 128 && typeof record2.path === "string" && record2.path.length > 0 && record2.path.length <= 4096 && isAbsolute2(record2.path) && typeof record2.identity === "string" && /^executable:[a-f0-9]{64}$/.test(record2.identity);
148
148
  }
149
149
  function validProtocolVersion(value) {
150
150
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
151
- const record = value;
152
- return Number.isSafeInteger(record.major) && Number(record.major) >= 0 && Number.isSafeInteger(record.minor) && Number(record.minor) >= 0;
151
+ const record2 = value;
152
+ return Number.isSafeInteger(record2.major) && Number(record2.major) >= 0 && Number.isSafeInteger(record2.minor) && Number(record2.minor) >= 0;
153
153
  }
154
154
  function validProtocolRange(value) {
155
155
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
156
- const record = value;
157
- if (!validProtocolVersion(record.min) || !validProtocolVersion(record.max)) return false;
158
- return record.min.major < record.max.major || record.min.major === record.max.major && record.min.minor <= record.max.minor;
156
+ const record2 = value;
157
+ if (!validProtocolVersion(record2.min) || !validProtocolVersion(record2.max)) return false;
158
+ return record2.min.major < record2.max.major || record2.min.major === record2.max.major && record2.min.minor <= record2.max.minor;
159
159
  }
160
160
  function validLocator(value, paths) {
161
161
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
162
- const record = value;
163
- const common = (record.schemaVersion === 1 || record.schemaVersion === 2) && isSafePid(record.pid) && record.endpoint === paths.endpoint && record.transport === paths.transport && isSafeTimestamp(record.startedAt) && typeof record.brokerProcessIdentity === "string" && record.brokerProcessIdentity.length > 0 && record.brokerProcessIdentity.length <= 256;
162
+ const record2 = value;
163
+ const common = (record2.schemaVersion === 1 || record2.schemaVersion === 2) && isSafePid(record2.pid) && record2.endpoint === paths.endpoint && record2.transport === paths.transport && isSafeTimestamp(record2.startedAt) && typeof record2.brokerProcessIdentity === "string" && record2.brokerProcessIdentity.length > 0 && record2.brokerProcessIdentity.length <= 256;
164
164
  if (!common) return false;
165
- if (record.schemaVersion === 1) return true;
166
- return typeof record.serviceVersion === "string" && record.serviceVersion.length > 0 && record.serviceVersion.length <= 128 && validExecutable(record.executable) && validProtocolRange(record.protocol) && (record.previousExecutable === void 0 || validExecutable(record.previousExecutable));
165
+ if (record2.schemaVersion === 1) return true;
166
+ return typeof record2.serviceVersion === "string" && record2.serviceVersion.length > 0 && record2.serviceVersion.length <= 128 && validExecutable(record2.executable) && validProtocolRange(record2.protocol) && (record2.previousExecutable === void 0 || validExecutable(record2.previousExecutable));
167
167
  }
168
168
  function validVersionHistoryEntry(value) {
169
169
  if (!validExecutable(value)) return false;
170
- const record = value;
171
- return isSafeTimestamp(record.firstSeenAt) && isSafeTimestamp(record.lastSeenAt) && Number(record.firstSeenAt) <= Number(record.lastSeenAt);
170
+ const record2 = value;
171
+ return isSafeTimestamp(record2.firstSeenAt) && isSafeTimestamp(record2.lastSeenAt) && Number(record2.firstSeenAt) <= Number(record2.lastSeenAt);
172
172
  }
173
173
  function validVersionHistory(value) {
174
174
  if (!value || typeof value !== "object" || Array.isArray(value)) return false;
175
- const record = value;
176
- return record.schemaVersion === 1 && validVersionHistoryEntry(record.current) && (record.previous === void 0 || validVersionHistoryEntry(record.previous));
175
+ const record2 = value;
176
+ return record2.schemaVersion === 1 && validVersionHistoryEntry(record2.current) && (record2.previous === void 0 || validVersionHistoryEntry(record2.previous));
177
177
  }
178
178
  function createExecutableMetadataSync(version, path) {
179
179
  if (!version || version.length > 128) throw new Error("Invalid Browser Pilot executable version");
@@ -244,13 +244,13 @@ function atomicWriteJson(path, value) {
244
244
  }
245
245
  }
246
246
  }
247
- function writeBrokerStartingSync(record, paths = BROWSER_PILOT_PATHS) {
248
- if (!isSafePid(record.pid) || !isSafeTimestamp(record.startedAt) || typeof record.brokerProcessIdentity !== "string" || record.brokerProcessIdentity.length === 0 || record.brokerProcessIdentity.length > 256) {
247
+ function writeBrokerStartingSync(record2, paths = BROWSER_PILOT_PATHS) {
248
+ if (!isSafePid(record2.pid) || !isSafeTimestamp(record2.startedAt) || typeof record2.brokerProcessIdentity !== "string" || record2.brokerProcessIdentity.length === 0 || record2.brokerProcessIdentity.length > 256) {
249
249
  throw new Error("Invalid starting Broker record");
250
250
  }
251
251
  atomicWriteJson(paths.pidFile, {
252
252
  schemaVersion: 2,
253
- ...record,
253
+ ...record2,
254
254
  state: "starting"
255
255
  });
256
256
  }
@@ -364,7 +364,8 @@ function asBrowserPilotError(error) {
364
364
  var SUPPORTED_PROTOCOL_VERSIONS = [
365
365
  { major: 1, minor: 0 },
366
366
  { major: 1, minor: 1 },
367
- { major: 1, minor: 2 }
367
+ { major: 1, minor: 2 },
368
+ { major: 1, minor: 3 }
368
369
  ];
369
370
  var CAPABILITIES = [
370
371
  "browser.discovery",
@@ -381,6 +382,12 @@ var CAPABILITIES = [
381
382
  "developer.eval"
382
383
  ];
383
384
  var DEFAULT_CAPABILITIES = CAPABILITIES;
385
+ var PROFILE_IDENTITY_ERROR_CODES = [
386
+ "profile_path_unavailable",
387
+ "profile_path_unverified",
388
+ "local_state_unavailable",
389
+ "profile_metadata_missing"
390
+ ];
384
391
  var BROWSER_OPERATIONS = [
385
392
  "tabs.list",
386
393
  "page.observe",
@@ -963,6 +970,12 @@ var profileRepresentativeTabSchema = objectSchema({
963
970
  var profileContextSchema = objectSchema({
964
971
  profileContextId: opaqueIdSchema,
965
972
  label: stringSchema({ minLength: 1, maxLength: 128 }),
973
+ identityStatus: stringSchema({ enum: ["unidentified", "verified", "unavailable"] }),
974
+ profileName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
975
+ accountName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
976
+ accountEmail: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
977
+ profileDirectory: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
978
+ identityErrorCode: stringSchema({ enum: [...PROFILE_IDENTITY_ERROR_CODES] }),
966
979
  displayName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
967
980
  tabCount: integerSchema({ minimum: 0 }),
968
981
  eligibleTabCount: integerSchema({ minimum: 0 }),
@@ -991,6 +1004,7 @@ var TOOL_DEFINITIONS = [
991
1004
  id: opaqueIdSchema,
992
1005
  product: stringSchema({ minLength: 1, maxLength: 128 }),
993
1006
  channel: stringSchema({ maxLength: 128 }),
1007
+ userDataRoot: sensitive(stringSchema({ maxLength: 4096 }), "browser_data"),
994
1008
  profile: sensitive(stringSchema({ maxLength: 4096 }), "browser_data"),
995
1009
  processState: stringSchema({ enum: ["running", "not_running", "unknown"] }),
996
1010
  remoteDebuggingState: stringSchema({ enum: ["enabled", "disabled", "stale"] }),
@@ -1051,6 +1065,25 @@ var TOOL_DEFINITIONS = [
1051
1065
  sensitivity: { input: [], output: ["browser_data"] },
1052
1066
  artifactKinds: []
1053
1067
  }),
1068
+ tool({
1069
+ name: "browser.profiles.identify",
1070
+ title: "Identify browser Profiles",
1071
+ description: "Explicitly identify live Chrome Profiles using temporary visible chrome://version targets.",
1072
+ context: "workspace",
1073
+ inputSchema: objectSchema({
1074
+ profileContextId: opaqueIdSchema,
1075
+ refresh: booleanSchema()
1076
+ }),
1077
+ outputSchema: resultSchema("workspace", {
1078
+ profiles: arraySchema(profileContextSchema, { maxItems: 128 })
1079
+ }, ["profiles"]),
1080
+ requiredCapabilities: ["browser.control"],
1081
+ mutating: true,
1082
+ idempotency: "non_idempotent",
1083
+ cancellation: "best_effort",
1084
+ sensitivity: { input: [], output: ["browser_data"] },
1085
+ artifactKinds: []
1086
+ }),
1054
1087
  tool({
1055
1088
  name: "browser.profiles.select",
1056
1089
  title: "Select browser Profile",
@@ -1060,6 +1093,12 @@ var TOOL_DEFINITIONS = [
1060
1093
  outputSchema: resultSchema("workspace", {
1061
1094
  profileContextId: opaqueIdSchema,
1062
1095
  label: stringSchema({ minLength: 1, maxLength: 128 }),
1096
+ identityStatus: stringSchema({ enum: ["unidentified", "verified", "unavailable"] }),
1097
+ profileName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
1098
+ accountName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
1099
+ accountEmail: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
1100
+ profileDirectory: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
1101
+ identityErrorCode: stringSchema({ enum: [...PROFILE_IDENTITY_ERROR_CODES] }),
1063
1102
  displayName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data")
1064
1103
  }, ["profileContextId", "label"]),
1065
1104
  requiredCapabilities: ["browser.control"],
@@ -1488,10 +1527,11 @@ var TOOL_DEFINITIONS = [
1488
1527
  title: sensitive(stringSchema({ maxLength: 4096 }), "browser_data"),
1489
1528
  url: boundedUrl,
1490
1529
  active: booleanSchema(),
1530
+ selected: booleanSchema(),
1491
1531
  origin: stringSchema({ enum: ["managed", "managed_popup", "user_tab"] }),
1492
1532
  managedTabSetId: opaqueIdSchema,
1493
1533
  controlState: stringSchema({ enum: ["available", "controlled", "busy"] })
1494
- }, ["targetId", "title", "url", "active", "origin", "controlState"]))
1534
+ }, ["targetId", "title", "url", "origin", "controlState"]))
1495
1535
  }, ["targets"]),
1496
1536
  requiredCapabilities: ["browser.control"],
1497
1537
  mutating: false,
@@ -1503,7 +1543,7 @@ var TOOL_DEFINITIONS = [
1503
1543
  tool({
1504
1544
  name: "browser.tabs.switch",
1505
1545
  title: "Switch tab",
1506
- description: "Set a managed or user tab as the active controlled target for the current Lease.",
1546
+ description: "Select a managed or user tab as the controlled target for the current Lease.",
1507
1547
  context: "workspace",
1508
1548
  inputSchema: objectSchema({ targetId: opaqueIdSchema }, ["targetId"]),
1509
1549
  outputSchema: resultSchema("target"),
@@ -1835,8 +1875,16 @@ var PROTOCOL_1_2_TOOLS = /* @__PURE__ */ new Set([
1835
1875
  "browser.profiles.list",
1836
1876
  "browser.profiles.select"
1837
1877
  ]);
1878
+ var PROTOCOL_1_3_TOOLS = /* @__PURE__ */ new Set([
1879
+ "browser.profiles.identify"
1880
+ ]);
1881
+ function minimumProtocolMinorForTool(name) {
1882
+ if (PROTOCOL_1_3_TOOLS.has(name)) return 3;
1883
+ if (PROTOCOL_1_2_TOOLS.has(name)) return 2;
1884
+ return 0;
1885
+ }
1838
1886
  function isToolAvailableInProtocol(name, protocol) {
1839
- return protocol.major === 1 && (protocol.minor >= 2 || !PROTOCOL_1_2_TOOLS.has(name));
1887
+ return protocol.major === 1 && protocol.minor >= minimumProtocolMinorForTool(name);
1840
1888
  }
1841
1889
  function schemaSensitivities(schema, result = /* @__PURE__ */ new Set()) {
1842
1890
  for (const value of schema["x-browser-pilot-sensitivity"] ?? []) result.add(value);
@@ -1935,9 +1983,9 @@ var MAX_DECLARED_TRANSPORT_BYTES = 1024 * 1024 * 1024;
1935
1983
  function isRecord2(value) {
1936
1984
  return typeof value === "object" && value !== null && !Array.isArray(value);
1937
1985
  }
1938
- function assertOnlyKeys(record, allowed, field = "params") {
1986
+ function assertOnlyKeys(record2, allowed, field = "params") {
1939
1987
  const allowedSet = new Set(allowed);
1940
- const unknown = Object.keys(record).filter((key) => !allowedSet.has(key));
1988
+ const unknown = Object.keys(record2).filter((key) => !allowedSet.has(key));
1941
1989
  if (unknown.length > 0) {
1942
1990
  throw invalidArgument(`Unknown ${field} field: ${unknown[0]}`, `${field}.${unknown[0]}`, field === "message" ? -32600 : -32602);
1943
1991
  }
@@ -1950,8 +1998,8 @@ function isJsonValue(value) {
1950
1998
  if (isRecord2(value)) return Object.values(value).every(isJsonValue);
1951
1999
  return false;
1952
2000
  }
1953
- function requireString(record, key, options = {}) {
1954
- const value = record[key];
2001
+ function requireString(record2, key, options = {}) {
2002
+ const value = record2[key];
1955
2003
  const maxLength = options.maxLength ?? MAX_ID_LENGTH;
1956
2004
  if (typeof value !== "string" || value.length === 0 || value.length > maxLength) {
1957
2005
  throw invalidArgument(`${key} must be a non-empty string no longer than ${maxLength} characters`, key);
@@ -1961,8 +2009,8 @@ function requireString(record, key, options = {}) {
1961
2009
  }
1962
2010
  return value;
1963
2011
  }
1964
- function requireInteger(record, key) {
1965
- const value = record[key];
2012
+ function requireInteger(record2, key) {
2013
+ const value = record2[key];
1966
2014
  if (!Number.isSafeInteger(value) || value < 0) {
1967
2015
  throw invalidArgument(`${key} must be a non-negative safe integer`, key);
1968
2016
  }
@@ -2085,15 +2133,15 @@ function validateOptionalEmptyParams(value) {
2085
2133
  assertOnlyKeys(value, []);
2086
2134
  return {};
2087
2135
  }
2088
- function validateTtlMs(record) {
2089
- if (record.ttlMs === void 0) return void 0;
2090
- if (!Number.isSafeInteger(record.ttlMs)) {
2136
+ function validateTtlMs(record2) {
2137
+ if (record2.ttlMs === void 0) return void 0;
2138
+ if (!Number.isSafeInteger(record2.ttlMs)) {
2091
2139
  throw invalidArgument("ttlMs must be a safe integer", "ttlMs");
2092
2140
  }
2093
- return record.ttlMs;
2141
+ return record2.ttlMs;
2094
2142
  }
2095
- function validateOpaqueId(record, key, pattern) {
2096
- return requireString(record, key, { pattern, maxLength: 128 });
2143
+ function validateOpaqueId(record2, key, pattern) {
2144
+ return requireString(record2, key, { pattern, maxLength: 128 });
2097
2145
  }
2098
2146
  function validateToolsListParams(value) {
2099
2147
  return validateOptionalEmptyParams(value);
@@ -2262,22 +2310,22 @@ function canonical(value) {
2262
2310
  function cloneJson(value) {
2263
2311
  return structuredClone(value);
2264
2312
  }
2265
- function descriptor(record) {
2313
+ function descriptor(record2) {
2266
2314
  return {
2267
- id: record.id,
2268
- ...record.workspaceId ? { workspaceId: record.workspaceId } : {},
2269
- ...record.leaseId ? { leaseId: record.leaseId } : {},
2270
- ...record.targetId ? { targetId: record.targetId } : {},
2271
- ...record.browserConnectionGeneration !== void 0 ? { browserConnectionGeneration: record.browserConnectionGeneration } : {},
2272
- idempotencyKey: record.idempotencyKey,
2273
- method: record.method,
2274
- mutating: record.mutating,
2275
- status: record.status,
2276
- acceptedAt: record.acceptedAt,
2277
- deadlineAt: record.deadlineAt,
2278
- ...record.dispatchedAt !== void 0 ? { dispatchedAt: record.dispatchedAt } : {},
2279
- ...record.completedAt !== void 0 ? { completedAt: record.completedAt } : {},
2280
- ...record.cancellationRequested ? { cancellationRequested: true } : {}
2315
+ id: record2.id,
2316
+ ...record2.workspaceId ? { workspaceId: record2.workspaceId } : {},
2317
+ ...record2.leaseId ? { leaseId: record2.leaseId } : {},
2318
+ ...record2.targetId ? { targetId: record2.targetId } : {},
2319
+ ...record2.browserConnectionGeneration !== void 0 ? { browserConnectionGeneration: record2.browserConnectionGeneration } : {},
2320
+ idempotencyKey: record2.idempotencyKey,
2321
+ method: record2.method,
2322
+ mutating: record2.mutating,
2323
+ status: record2.status,
2324
+ acceptedAt: record2.acceptedAt,
2325
+ deadlineAt: record2.deadlineAt,
2326
+ ...record2.dispatchedAt !== void 0 ? { dispatchedAt: record2.dispatchedAt } : {},
2327
+ ...record2.completedAt !== void 0 ? { completedAt: record2.completedAt } : {},
2328
+ ...record2.cancellationRequested ? { cancellationRequested: true } : {}
2281
2329
  };
2282
2330
  }
2283
2331
  var MemoryCommandRuntime = class {
@@ -2344,13 +2392,13 @@ var MemoryCommandRuntime = class {
2344
2392
  throw new BrowserPilotError("internal_error", "Invalid or duplicate Command ID");
2345
2393
  }
2346
2394
  const acceptedAt = this.now();
2347
- let resolve2;
2395
+ let resolve3;
2348
2396
  let reject;
2349
2397
  const completion = new Promise((resolvePromise, rejectPromise) => {
2350
- resolve2 = resolvePromise;
2398
+ resolve3 = resolvePromise;
2351
2399
  reject = rejectPromise;
2352
2400
  });
2353
- const record = {
2401
+ const record2 = {
2354
2402
  id,
2355
2403
  ...input.workspaceId ? { workspaceId: input.workspaceId } : {},
2356
2404
  ...input.leaseId ? { leaseId: input.leaseId } : {},
@@ -2368,17 +2416,17 @@ var MemoryCommandRuntime = class {
2368
2416
  scopeKey,
2369
2417
  cancellation: input.cancellation,
2370
2418
  abortController: new AbortController(),
2371
- resolve: resolve2,
2419
+ resolve: resolve3,
2372
2420
  reject,
2373
2421
  completion,
2374
2422
  settled: false
2375
2423
  };
2376
- this.records.set(id, record);
2424
+ this.records.set(id, record2);
2377
2425
  this.commandsByIdempotency.set(idempotencyIndex, id);
2378
- record.deadlineTimer = setTimeout(() => this.expire(record), deadlineMs);
2379
- record.deadlineTimer.unref();
2380
- this.emitStatus(record);
2381
- this.enqueue(input.actorKey, () => this.execute(record, execute));
2426
+ record2.deadlineTimer = setTimeout(() => this.expire(record2), deadlineMs);
2427
+ record2.deadlineTimer.unref();
2428
+ this.emitStatus(record2);
2429
+ this.enqueue(input.actorKey, () => this.execute(record2, execute));
2382
2430
  return completion;
2383
2431
  }
2384
2432
  subscribe(listener) {
@@ -2391,49 +2439,49 @@ var MemoryCommandRuntime = class {
2391
2439
  }
2392
2440
  cancel(context) {
2393
2441
  this.sweep();
2394
- const record = this.requireOwned(context);
2395
- if (TERMINAL_STATUSES.has(record.status)) return this.outcome(record);
2396
- if (record.cancellation === "not_applicable") {
2442
+ const record2 = this.requireOwned(context);
2443
+ if (TERMINAL_STATUSES.has(record2.status)) return this.outcome(record2);
2444
+ if (record2.cancellation === "not_applicable") {
2397
2445
  throw invalidArgument("Command does not support cancellation", "commandId");
2398
2446
  }
2399
- if (record.status === "accepted") {
2400
- record.abortController.abort();
2447
+ if (record2.status === "accepted") {
2448
+ record2.abortController.abort();
2401
2449
  this.finishWithError(
2402
- record,
2450
+ record2,
2403
2451
  "cancelled",
2404
2452
  new BrowserPilotError("command_cancelled", "Command was cancelled before dispatch")
2405
2453
  );
2406
- return this.outcome(record);
2454
+ return this.outcome(record2);
2407
2455
  }
2408
- record.cancellationRequested = true;
2409
- this.emitStatus(record);
2410
- if (record.cancellation === "best_effort") record.abortController.abort();
2411
- return this.outcome(record);
2456
+ record2.cancellationRequested = true;
2457
+ this.emitStatus(record2);
2458
+ if (record2.cancellation === "best_effort") record2.abortController.abort();
2459
+ return this.outcome(record2);
2412
2460
  }
2413
2461
  releaseLease(leaseId) {
2414
- for (const record of this.records.values()) {
2415
- if (record.leaseId === leaseId) this.cancelForCleanup(record);
2462
+ for (const record2 of this.records.values()) {
2463
+ if (record2.leaseId === leaseId) this.cancelForCleanup(record2);
2416
2464
  }
2417
2465
  }
2418
2466
  releaseConnection(connectionId) {
2419
- for (const record of this.records.values()) {
2420
- if (record.connectionId === connectionId) this.cancelForCleanup(record);
2467
+ for (const record2 of this.records.values()) {
2468
+ if (record2.connectionId === connectionId) this.cancelForCleanup(record2);
2421
2469
  }
2422
2470
  }
2423
2471
  releaseWorkspace(workspaceId) {
2424
- for (const record of this.records.values()) {
2425
- if (record.workspaceId === workspaceId) this.cancelForCleanup(record);
2472
+ for (const record2 of this.records.values()) {
2473
+ if (record2.workspaceId === workspaceId) this.cancelForCleanup(record2);
2426
2474
  }
2427
2475
  }
2428
2476
  sweep() {
2429
2477
  const now = this.now();
2430
2478
  let removed = 0;
2431
- for (const record of this.records.values()) {
2432
- if (!TERMINAL_STATUSES.has(record.status) && record.deadlineAt <= now) this.expire(record);
2479
+ for (const record2 of this.records.values()) {
2480
+ if (!TERMINAL_STATUSES.has(record2.status) && record2.deadlineAt <= now) this.expire(record2);
2433
2481
  }
2434
- for (const [id, record] of this.records) {
2435
- if (TERMINAL_STATUSES.has(record.status) && (record.completedAt ?? record.deadlineAt) + this.terminalTtlMs <= now) {
2436
- this.deleteRecord(id, record);
2482
+ for (const [id, record2] of this.records) {
2483
+ if (TERMINAL_STATUSES.has(record2.status) && (record2.completedAt ?? record2.deadlineAt) + this.terminalTtlMs <= now) {
2484
+ this.deleteRecord(id, record2);
2437
2485
  removed += 1;
2438
2486
  }
2439
2487
  }
@@ -2442,140 +2490,140 @@ var MemoryCommandRuntime = class {
2442
2490
  size() {
2443
2491
  return this.records.size;
2444
2492
  }
2445
- async execute(record, execute) {
2446
- if (record.status !== "accepted") return;
2447
- if (record.deadlineAt <= this.now()) {
2448
- this.expire(record);
2493
+ async execute(record2, execute) {
2494
+ if (record2.status !== "accepted") return;
2495
+ if (record2.deadlineAt <= this.now()) {
2496
+ this.expire(record2);
2449
2497
  return;
2450
2498
  }
2451
2499
  try {
2452
2500
  const markDispatched = () => {
2453
- if (record.status === "cancelled") {
2501
+ if (record2.status === "cancelled") {
2454
2502
  throw new BrowserPilotError("command_cancelled", "Command was cancelled before dispatch", {
2455
- context: { commandId: record.id }
2503
+ context: { commandId: record2.id }
2456
2504
  });
2457
2505
  }
2458
- if (record.status === "expired") {
2506
+ if (record2.status === "expired") {
2459
2507
  throw new BrowserPilotError("command_expired", "Command expired before dispatch", {
2460
- context: { commandId: record.id }
2508
+ context: { commandId: record2.id }
2461
2509
  });
2462
2510
  }
2463
- if (record.status !== "accepted") return;
2464
- record.status = "dispatched";
2465
- record.dispatchedAt = this.now();
2466
- this.emitStatus(record);
2511
+ if (record2.status !== "accepted") return;
2512
+ record2.status = "dispatched";
2513
+ record2.dispatchedAt = this.now();
2514
+ this.emitStatus(record2);
2467
2515
  };
2468
- const result = await execute({ signal: record.abortController.signal, markDispatched });
2469
- if (TERMINAL_STATUSES.has(record.status)) return;
2516
+ const result = await execute({ signal: record2.abortController.signal, markDispatched });
2517
+ if (TERMINAL_STATUSES.has(record2.status)) return;
2470
2518
  markDispatched();
2471
- record.status = "completed";
2472
- record.completedAt = this.now();
2473
- record.result = cloneJson(result);
2474
- this.clearDeadline(record);
2475
- this.emitStatus(record);
2476
- this.resolve(record);
2519
+ record2.status = "completed";
2520
+ record2.completedAt = this.now();
2521
+ record2.result = cloneJson(result);
2522
+ this.clearDeadline(record2);
2523
+ this.emitStatus(record2);
2524
+ this.resolve(record2);
2477
2525
  } catch (error) {
2478
- if (TERMINAL_STATUSES.has(record.status)) return;
2479
- const stable = this.withCommandContext(error, record);
2480
- if (record.mutating && record.dispatchedAt !== void 0 && (stable.code === "browser_disconnected" || stable.code === "internal_error" || stable.code === "unknown_outcome")) {
2526
+ if (TERMINAL_STATUSES.has(record2.status)) return;
2527
+ const stable = this.withCommandContext(error, record2);
2528
+ if (record2.mutating && record2.dispatchedAt !== void 0 && (stable.code === "browser_disconnected" || stable.code === "internal_error" || stable.code === "unknown_outcome")) {
2481
2529
  if (stable.code === "unknown_outcome") {
2482
- this.finishWithError(record, "unknown_outcome", stable);
2530
+ this.finishWithError(record2, "unknown_outcome", stable);
2483
2531
  return;
2484
2532
  }
2485
2533
  this.finishWithError(
2486
- record,
2534
+ record2,
2487
2535
  "unknown_outcome",
2488
2536
  new BrowserPilotError("unknown_outcome", "Command outcome is unknown after dispatch", {
2489
- context: { commandId: record.id },
2537
+ context: { commandId: record2.id },
2490
2538
  cause: stable
2491
2539
  })
2492
2540
  );
2493
2541
  } else {
2494
- this.finishWithError(record, "completed", stable);
2542
+ this.finishWithError(record2, "completed", stable);
2495
2543
  }
2496
2544
  }
2497
2545
  }
2498
- expire(record) {
2499
- if (TERMINAL_STATUSES.has(record.status)) return;
2500
- record.abortController.abort();
2501
- if (record.status === "dispatched" && record.mutating) {
2546
+ expire(record2) {
2547
+ if (TERMINAL_STATUSES.has(record2.status)) return;
2548
+ record2.abortController.abort();
2549
+ if (record2.status === "dispatched" && record2.mutating) {
2502
2550
  this.finishWithError(
2503
- record,
2551
+ record2,
2504
2552
  "unknown_outcome",
2505
2553
  new BrowserPilotError("unknown_outcome", "Command deadline elapsed after dispatch", {
2506
- context: { commandId: record.id }
2554
+ context: { commandId: record2.id }
2507
2555
  })
2508
2556
  );
2509
2557
  return;
2510
2558
  }
2511
2559
  this.finishWithError(
2512
- record,
2560
+ record2,
2513
2561
  "expired",
2514
2562
  new BrowserPilotError("command_expired", "Command deadline elapsed before a result was known", {
2515
- context: { commandId: record.id }
2563
+ context: { commandId: record2.id }
2516
2564
  })
2517
2565
  );
2518
2566
  }
2519
- finishWithError(record, status, error) {
2520
- if (TERMINAL_STATUSES.has(record.status)) return;
2521
- const stable = this.withCommandContext(error, record);
2522
- record.status = status;
2523
- record.completedAt = this.now();
2524
- record.error = stable.toJsonRpcError();
2525
- this.clearDeadline(record);
2526
- this.emitStatus(record);
2527
- if (!record.settled) {
2528
- record.settled = true;
2529
- record.reject(stable);
2530
- }
2531
- }
2532
- resolve(record) {
2533
- if (record.settled) return;
2534
- record.settled = true;
2535
- record.resolve(this.outcome(record));
2536
- }
2537
- cancelForCleanup(record) {
2538
- if (TERMINAL_STATUSES.has(record.status)) return;
2539
- if (record.status === "accepted") {
2540
- record.abortController.abort();
2567
+ finishWithError(record2, status, error) {
2568
+ if (TERMINAL_STATUSES.has(record2.status)) return;
2569
+ const stable = this.withCommandContext(error, record2);
2570
+ record2.status = status;
2571
+ record2.completedAt = this.now();
2572
+ record2.error = stable.toJsonRpcError();
2573
+ this.clearDeadline(record2);
2574
+ this.emitStatus(record2);
2575
+ if (!record2.settled) {
2576
+ record2.settled = true;
2577
+ record2.reject(stable);
2578
+ }
2579
+ }
2580
+ resolve(record2) {
2581
+ if (record2.settled) return;
2582
+ record2.settled = true;
2583
+ record2.resolve(this.outcome(record2));
2584
+ }
2585
+ cancelForCleanup(record2) {
2586
+ if (TERMINAL_STATUSES.has(record2.status)) return;
2587
+ if (record2.status === "accepted") {
2588
+ record2.abortController.abort();
2541
2589
  this.finishWithError(
2542
- record,
2590
+ record2,
2543
2591
  "cancelled",
2544
2592
  new BrowserPilotError("command_cancelled", "Command was cancelled during resource cleanup")
2545
2593
  );
2546
2594
  return;
2547
2595
  }
2548
- record.cancellationRequested = true;
2549
- this.emitStatus(record);
2550
- record.abortController.abort();
2596
+ record2.cancellationRequested = true;
2597
+ this.emitStatus(record2);
2598
+ record2.abortController.abort();
2551
2599
  }
2552
2600
  requireOwned(context) {
2553
- const record = this.records.get(context.commandId);
2554
- if (!record || record.principalId !== context.principalId || record.workspaceId !== context.workspaceId) {
2601
+ const record2 = this.records.get(context.commandId);
2602
+ if (!record2 || record2.principalId !== context.principalId || record2.workspaceId !== context.workspaceId) {
2555
2603
  throw invalidArgument("Command was not found for this ClientPrincipal and Workspace", "commandId");
2556
2604
  }
2557
- return record;
2605
+ return record2;
2558
2606
  }
2559
- assertDuplicate(record, input, fingerprint, idempotencyKey) {
2560
- if (record.principalId !== input.principalId || record.workspaceId !== input.workspaceId || record.method !== input.method || record.fingerprint !== fingerprint || record.idempotencyKey !== idempotencyKey) {
2607
+ assertDuplicate(record2, input, fingerprint, idempotencyKey) {
2608
+ if (record2.principalId !== input.principalId || record2.workspaceId !== input.workspaceId || record2.method !== input.method || record2.fingerprint !== fingerprint || record2.idempotencyKey !== idempotencyKey) {
2561
2609
  throw invalidArgument("Command or idempotency key was reused for a different request", "idempotencyKey");
2562
2610
  }
2563
2611
  }
2564
- outcome(record) {
2612
+ outcome(record2) {
2565
2613
  return {
2566
- command: descriptor(record),
2567
- ...record.result !== void 0 ? { result: cloneJson(record.result) } : {},
2568
- ...record.error ? { error: cloneJson(record.error) } : {}
2614
+ command: descriptor(record2),
2615
+ ...record2.result !== void 0 ? { result: cloneJson(record2.result) } : {},
2616
+ ...record2.error ? { error: cloneJson(record2.error) } : {}
2569
2617
  };
2570
2618
  }
2571
2619
  scopeKey(principalId, connectionId, workspaceId) {
2572
2620
  return workspaceId ? `${principalId}\0${workspaceId}` : `${principalId}\0${connectionId}`;
2573
2621
  }
2574
- withCommandContext(error, record) {
2622
+ withCommandContext(error, record2) {
2575
2623
  const stable = asBrowserPilotError(error);
2576
2624
  return new BrowserPilotError(stable.code, stable.message, {
2577
2625
  retryable: stable.retryable,
2578
- context: { ...stable.context ?? {}, commandId: record.id },
2626
+ context: { ...stable.context ?? {}, commandId: record2.id },
2579
2627
  ...stable.remediation ? { remediation: stable.remediation } : {},
2580
2628
  rpcCode: stable.rpcCode,
2581
2629
  cause: error
@@ -2595,9 +2643,9 @@ var MemoryCommandRuntime = class {
2595
2643
  }
2596
2644
  ensureCapacity() {
2597
2645
  if (this.records.size < this.maxCommands) return;
2598
- const terminal = [...this.records.values()].filter((record) => TERMINAL_STATUSES.has(record.status)).sort((left, right) => (left.completedAt ?? left.deadlineAt) - (right.completedAt ?? right.deadlineAt));
2599
- for (const record of terminal) {
2600
- this.deleteRecord(record.id, record);
2646
+ const terminal = [...this.records.values()].filter((record2) => TERMINAL_STATUSES.has(record2.status)).sort((left, right) => (left.completedAt ?? left.deadlineAt) - (right.completedAt ?? right.deadlineAt));
2647
+ for (const record2 of terminal) {
2648
+ this.deleteRecord(record2.id, record2);
2601
2649
  if (this.records.size < this.maxCommands) return;
2602
2650
  }
2603
2651
  throw new BrowserPilotError("result_too_large", "Command store capacity is exhausted", {
@@ -2605,19 +2653,19 @@ var MemoryCommandRuntime = class {
2605
2653
  context: { maxCommands: this.maxCommands }
2606
2654
  });
2607
2655
  }
2608
- deleteRecord(id, record) {
2609
- this.clearDeadline(record);
2656
+ deleteRecord(id, record2) {
2657
+ this.clearDeadline(record2);
2610
2658
  this.records.delete(id);
2611
- const index = `${record.scopeKey}\0${record.idempotencyKey}`;
2659
+ const index = `${record2.scopeKey}\0${record2.idempotencyKey}`;
2612
2660
  if (this.commandsByIdempotency.get(index) === id) this.commandsByIdempotency.delete(index);
2613
2661
  }
2614
- clearDeadline(record) {
2615
- if (!record.deadlineTimer) return;
2616
- clearTimeout(record.deadlineTimer);
2617
- record.deadlineTimer = void 0;
2662
+ clearDeadline(record2) {
2663
+ if (!record2.deadlineTimer) return;
2664
+ clearTimeout(record2.deadlineTimer);
2665
+ record2.deadlineTimer = void 0;
2618
2666
  }
2619
- emitStatus(record) {
2620
- const outcome = this.outcome(record);
2667
+ emitStatus(record2) {
2668
+ const outcome = this.outcome(record2);
2621
2669
  for (const listener of this.statusListeners) {
2622
2670
  try {
2623
2671
  listener(outcome);
@@ -2779,13 +2827,21 @@ var DEFAULT_PROTOCOL_LIMITS = {
2779
2827
  function normalizeBrowserCandidate(candidate) {
2780
2828
  const ready = candidate.state === "ready";
2781
2829
  const authorizationRequired = candidate.state === "authorization_required";
2830
+ const userDataRoot = candidate.userDataRoot ?? candidate.profile;
2782
2831
  return {
2783
2832
  ...candidate,
2833
+ ...userDataRoot ? { userDataRoot, profile: userDataRoot } : {},
2784
2834
  processState: candidate.processState ?? (ready || authorizationRequired ? "running" : "unknown"),
2785
2835
  remoteDebuggingState: candidate.remoteDebuggingState ?? (ready || authorizationRequired ? "enabled" : candidate.state === "disconnected" ? "stale" : "disabled"),
2786
2836
  authorizationState: candidate.authorizationState ?? (ready ? "authorized" : authorizationRequired ? "required" : candidate.state === "disconnected" ? "unknown" : "not_applicable")
2787
2837
  };
2788
2838
  }
2839
+ function browserCandidateForProtocol(candidate, protocolMinor) {
2840
+ const { profile, userDataRoot, ...rest } = candidate;
2841
+ const root = userDataRoot ?? profile;
2842
+ if (!root) return rest;
2843
+ return protocolMinor >= 3 ? { ...rest, userDataRoot: root } : { ...rest, profile: root };
2844
+ }
2789
2845
  function asJson(value) {
2790
2846
  return value;
2791
2847
  }
@@ -2795,8 +2851,8 @@ function cloneWorkspace(value) {
2795
2851
  function cloneManagedTabSet(value) {
2796
2852
  return { ...value };
2797
2853
  }
2798
- function runtimeManagedTabSets(record) {
2799
- return [record.managedTabSet, ...record.additionalManagedTabSets.values()];
2854
+ function runtimeManagedTabSets(record2) {
2855
+ return [record2.managedTabSet, ...record2.additionalManagedTabSets.values()];
2800
2856
  }
2801
2857
  function cloneLease(value) {
2802
2858
  return { ...value, capabilities: [...value.capabilities] };
@@ -2988,8 +3044,8 @@ var MemoryBrokerRuntime = class {
2988
3044
  if (connection.notificationWaiter) {
2989
3045
  throw invalidArgument("Only one notification poll may be active per bridge Connection");
2990
3046
  }
2991
- return new Promise((resolve2) => {
2992
- const waiter = { resolve: resolve2, signal: options.signal };
3047
+ return new Promise((resolve3) => {
3048
+ const waiter = { resolve: resolve3, signal: options.signal };
2993
3049
  const finish = () => this.resolveNotificationWaiter(connection, void 0);
2994
3050
  waiter.timer = setTimeout(finish, waitMs);
2995
3051
  waiter.timer.unref();
@@ -3112,9 +3168,9 @@ var MemoryBrokerRuntime = class {
3112
3168
  for (const lease of this.leases.values()) {
3113
3169
  if (lease.state === "active") leasedWorkspaceIds.add(lease.workspaceId);
3114
3170
  }
3115
- for (const record of this.workspaces.values()) {
3116
- if (record.value.state === "active" && record.value.updatedAt + this.workspaceIdleTtlMs <= now && !leasedWorkspaceIds.has(record.value.id)) {
3117
- this.releaseWorkspaceRecord(record);
3171
+ for (const record2 of this.workspaces.values()) {
3172
+ if (record2.value.state === "active" && record2.value.updatedAt + this.workspaceIdleTtlMs <= now && !leasedWorkspaceIds.has(record2.value.id)) {
3173
+ this.releaseWorkspaceRecord(record2);
3118
3174
  }
3119
3175
  }
3120
3176
  return expired;
@@ -3123,7 +3179,7 @@ var MemoryBrokerRuntime = class {
3123
3179
  return {
3124
3180
  principals: this.principals.size,
3125
3181
  connections: this.connectionsByBridge.size,
3126
- activeWorkspaces: [...this.workspaces.values()].filter((record) => record.value.state === "active").length,
3182
+ activeWorkspaces: [...this.workspaces.values()].filter((record2) => record2.value.state === "active").length,
3127
3183
  activeLeases: [...this.leases.values()].filter((lease) => lease.state === "active").length
3128
3184
  };
3129
3185
  }
@@ -3175,7 +3231,7 @@ var MemoryBrokerRuntime = class {
3175
3231
  capabilities: capabilities2,
3176
3232
  brokerProcessIdentity: this.options.brokerProcessIdentity,
3177
3233
  connectionId: existing.value.id,
3178
- browsers: this.browserBindings.map((binding) => ({ ...binding.candidate })),
3234
+ browsers: this.browserBindings.map((binding) => browserCandidateForProtocol(binding.candidate, existing.value.protocol.minor)),
3179
3235
  limits: { ...existing.limits }
3180
3236
  });
3181
3237
  }
@@ -3212,7 +3268,7 @@ var MemoryBrokerRuntime = class {
3212
3268
  capabilities,
3213
3269
  brokerProcessIdentity: this.options.brokerProcessIdentity,
3214
3270
  connectionId,
3215
- browsers: this.browserBindings.map((binding) => ({ ...binding.candidate })),
3271
+ browsers: this.browserBindings.map((binding) => browserCandidateForProtocol(binding.candidate, protocol.minor)),
3216
3272
  limits: { ...limits }
3217
3273
  };
3218
3274
  const connection = {
@@ -3234,7 +3290,7 @@ var MemoryBrokerRuntime = class {
3234
3290
  throw protocolIncompatible("Keyed Workspace creation requires protocol 1.1 or newer");
3235
3291
  }
3236
3292
  const principalId = connection.value.principalId;
3237
- const existing = params.clientKey ? [...this.workspaces.values()].find((record) => record.value.principalId === principalId && record.value.state === "active" && record.value.clientKey === params.clientKey) : void 0;
3293
+ const existing = params.clientKey ? [...this.workspaces.values()].find((record2) => record2.value.principalId === principalId && record2.value.state === "active" && record2.value.clientKey === params.clientKey) : void 0;
3238
3294
  if (existing) {
3239
3295
  if (params.browserId) {
3240
3296
  const requested = this.browserBindings.find((candidate) => candidate.candidate.id === params.browserId);
@@ -3266,7 +3322,7 @@ var MemoryBrokerRuntime = class {
3266
3322
  }
3267
3323
  });
3268
3324
  }
3269
- const activeCount = [...this.workspaces.values()].filter((record) => record.value.principalId === principalId && record.value.state === "active").length;
3325
+ const activeCount = [...this.workspaces.values()].filter((record2) => record2.value.principalId === principalId && record2.value.state === "active").length;
3270
3326
  if (activeCount >= this.maxWorkspacesPerPrincipal) {
3271
3327
  throw new BrowserPilotError("result_too_large", "Workspace limit reached", {
3272
3328
  context: { maxWorkspacesPerPrincipal: this.maxWorkspacesPerPrincipal }
@@ -3313,20 +3369,20 @@ var MemoryBrokerRuntime = class {
3313
3369
  getWorkspace(connection, value) {
3314
3370
  this.assertCapabilities(connection, ["workspace.manage"]);
3315
3371
  const params = validateWorkspaceGetParams(value);
3316
- const record = this.requireWorkspace(connection, params.workspaceId, true);
3372
+ const record2 = this.requireWorkspace(connection, params.workspaceId, true);
3317
3373
  return asJson({
3318
- workspace: cloneWorkspace(record.value),
3319
- managedTabSet: cloneManagedTabSet(record.managedTabSet),
3320
- managedTabSets: runtimeManagedTabSets(record).map(cloneManagedTabSet),
3321
- eventCursor: this.events.currentCursor(record.value.id)
3374
+ workspace: cloneWorkspace(record2.value),
3375
+ managedTabSet: cloneManagedTabSet(record2.managedTabSet),
3376
+ managedTabSets: runtimeManagedTabSets(record2).map(cloneManagedTabSet),
3377
+ eventCursor: this.events.currentCursor(record2.value.id)
3322
3378
  });
3323
3379
  }
3324
3380
  async releaseWorkspace(connection, value) {
3325
3381
  this.assertCapabilities(connection, ["workspace.manage"]);
3326
3382
  const params = validateWorkspaceReleaseParams(value);
3327
- const record = this.requireWorkspace(connection, params.workspaceId, true);
3328
- if (record.value.state !== "released") this.releaseWorkspaceRecord(record);
3329
- await this.workspaceCleanup.get(record.value.id);
3383
+ const record2 = this.requireWorkspace(connection, params.workspaceId, true);
3384
+ if (record2.value.state !== "released") this.releaseWorkspaceRecord(record2);
3385
+ await this.workspaceCleanup.get(record2.value.id);
3330
3386
  return asJson({ workspaceId: params.workspaceId, released: true });
3331
3387
  }
3332
3388
  createLease(connection, value) {
@@ -3423,52 +3479,52 @@ var MemoryBrokerRuntime = class {
3423
3479
  });
3424
3480
  }
3425
3481
  requireWorkspace(connection, workspaceId, allowReleased) {
3426
- const record = this.workspaces.get(workspaceId);
3427
- if (!record || record.value.principalId !== connection.value.principalId || !allowReleased && record.value.state !== "active") {
3482
+ const record2 = this.workspaces.get(workspaceId);
3483
+ if (!record2 || record2.value.principalId !== connection.value.principalId || !allowReleased && record2.value.state !== "active") {
3428
3484
  throw new BrowserPilotError("workspace_not_found", "Workspace was not found for this ClientPrincipal", {
3429
3485
  context: { workspaceId }
3430
3486
  });
3431
3487
  }
3432
- return record;
3488
+ return record2;
3433
3489
  }
3434
- selectWorkspaceProfileContext(record, profileContextId) {
3435
- if (record.value.state !== "active") {
3490
+ selectWorkspaceProfileContext(record2, profileContextId) {
3491
+ if (record2.value.state !== "active") {
3436
3492
  throw new BrowserPilotError("workspace_not_found", "Workspace is not active", {
3437
- context: { workspaceId: record.value.id }
3493
+ context: { workspaceId: record2.value.id }
3438
3494
  });
3439
3495
  }
3440
- record.value.selectedProfileContextId = profileContextId;
3441
- record.value.updatedAt = this.now();
3496
+ record2.value.selectedProfileContextId = profileContextId;
3497
+ record2.value.updatedAt = this.now();
3442
3498
  }
3443
- managedTabSetForProfile(record, profileContextId) {
3444
- if (record.value.state !== "active") {
3499
+ managedTabSetForProfile(record2, profileContextId) {
3500
+ if (record2.value.state !== "active") {
3445
3501
  throw new BrowserPilotError("workspace_not_found", "Workspace is not active", {
3446
- context: { workspaceId: record.value.id }
3502
+ context: { workspaceId: record2.value.id }
3447
3503
  });
3448
3504
  }
3449
- if (record.managedTabSet.profileContextId === profileContextId) {
3450
- return cloneManagedTabSet(record.managedTabSet);
3505
+ if (record2.managedTabSet.profileContextId === profileContextId) {
3506
+ return cloneManagedTabSet(record2.managedTabSet);
3451
3507
  }
3452
- if (!record.managedTabSet.profileContextId) {
3453
- record.managedTabSet.profileContextId = profileContextId;
3454
- return cloneManagedTabSet(record.managedTabSet);
3508
+ if (!record2.managedTabSet.profileContextId) {
3509
+ record2.managedTabSet.profileContextId = profileContextId;
3510
+ return cloneManagedTabSet(record2.managedTabSet);
3455
3511
  }
3456
- const existing = record.additionalManagedTabSets.get(profileContextId);
3512
+ const existing = record2.additionalManagedTabSets.get(profileContextId);
3457
3513
  if (existing) return cloneManagedTabSet(existing);
3458
- if (record.additionalManagedTabSets.size >= 127) {
3514
+ if (record2.additionalManagedTabSets.size >= 127) {
3459
3515
  throw new BrowserPilotError("result_too_large", "ManagedTabSet Profile context limit reached", {
3460
- context: { workspaceId: record.value.id, maxManagedTabSets: 128 }
3516
+ context: { workspaceId: record2.value.id, maxManagedTabSets: 128 }
3461
3517
  });
3462
3518
  }
3463
3519
  const managedTabSet = {
3464
3520
  id: this.nextManagedTabSetId(),
3465
- workspaceId: record.value.id,
3466
- browserInstanceId: record.value.browserInstanceId,
3521
+ workspaceId: record2.value.id,
3522
+ browserInstanceId: record2.value.browserInstanceId,
3467
3523
  profileContextId,
3468
3524
  createdAt: this.now(),
3469
3525
  state: "active"
3470
3526
  };
3471
- record.additionalManagedTabSets.set(profileContextId, managedTabSet);
3527
+ record2.additionalManagedTabSets.set(profileContextId, managedTabSet);
3472
3528
  return cloneManagedTabSet(managedTabSet);
3473
3529
  }
3474
3530
  requireLease(connection, leaseId, requireActive) {
@@ -3500,52 +3556,52 @@ var MemoryBrokerRuntime = class {
3500
3556
  this.options.toolExecutor?.releaseLease?.(cloneLease(lease));
3501
3557
  this.options.onLeaseReleased?.(cloneLease(lease));
3502
3558
  }
3503
- releaseWorkspaceRecord(record) {
3504
- record.value.state = "releasing";
3505
- this.commands.releaseWorkspace(record.value.id);
3559
+ releaseWorkspaceRecord(record2) {
3560
+ record2.value.state = "releasing";
3561
+ this.commands.releaseWorkspace(record2.value.id);
3506
3562
  for (const lease of this.leases.values()) {
3507
- if (lease.workspaceId === record.value.id && lease.state === "active") {
3563
+ if (lease.workspaceId === record2.value.id && lease.state === "active") {
3508
3564
  this.releaseLeaseRecord(lease, "released");
3509
3565
  }
3510
3566
  }
3511
- const managedTabSets = runtimeManagedTabSets(record);
3567
+ const managedTabSets = runtimeManagedTabSets(record2);
3512
3568
  for (const managedTabSet of managedTabSets) managedTabSet.state = "closed";
3513
- record.value.updatedAt = this.now();
3514
- record.value.state = "released";
3515
- const principal = this.principals.get(record.value.principalId);
3569
+ record2.value.updatedAt = this.now();
3570
+ record2.value.state = "released";
3571
+ const principal = this.principals.get(record2.value.principalId);
3516
3572
  if (principal) {
3517
3573
  this.options.toolExecutor?.releaseWorkspace?.(
3518
3574
  { ...principal, capabilities: [...principal.capabilities] },
3519
- cloneWorkspace(record.value),
3575
+ cloneWorkspace(record2.value),
3520
3576
  managedTabSets.map(cloneManagedTabSet)
3521
3577
  );
3522
3578
  }
3523
3579
  this.options.onWorkspaceReleased?.(
3524
- cloneWorkspace(record.value),
3525
- cloneManagedTabSet(record.managedTabSet)
3580
+ cloneWorkspace(record2.value),
3581
+ cloneManagedTabSet(record2.managedTabSet)
3526
3582
  );
3527
- const cleanup2 = this.options.artifactStore?.releaseWorkspace(record.value.id).catch(() => {
3583
+ const cleanup2 = this.options.artifactStore?.releaseWorkspace(record2.value.id).catch(() => {
3528
3584
  }) ?? Promise.resolve();
3529
- this.workspaceCleanup.set(record.value.id, cleanup2);
3585
+ this.workspaceCleanup.set(record2.value.id, cleanup2);
3530
3586
  void cleanup2.finally(() => {
3531
- if (this.workspaceCleanup.get(record.value.id) === cleanup2) {
3532
- this.workspaceCleanup.delete(record.value.id);
3587
+ if (this.workspaceCleanup.get(record2.value.id) === cleanup2) {
3588
+ this.workspaceCleanup.delete(record2.value.id);
3533
3589
  }
3534
3590
  });
3535
3591
  }
3536
3592
  pruneReleasedWorkspaces() {
3537
3593
  if (this.workspaces.size < this.maxWorkspaceRecords) return;
3538
- const released = [...this.workspaces.values()].filter((record) => record.value.state === "released").sort((left, right) => left.value.updatedAt - right.value.updatedAt);
3539
- for (const record of released) {
3540
- this.workspaces.delete(record.value.id);
3541
- this.events.releaseWorkspace(record.value.id);
3542
- for (const managedTabSet of runtimeManagedTabSets(record)) {
3594
+ const released = [...this.workspaces.values()].filter((record2) => record2.value.state === "released").sort((left, right) => left.value.updatedAt - right.value.updatedAt);
3595
+ for (const record2 of released) {
3596
+ this.workspaces.delete(record2.value.id);
3597
+ this.events.releaseWorkspace(record2.value.id);
3598
+ for (const managedTabSet of runtimeManagedTabSets(record2)) {
3543
3599
  this.managedTabSetIds.delete(managedTabSet.id);
3544
3600
  }
3545
3601
  for (const [leaseId, lease] of this.leases) {
3546
- if (lease.workspaceId === record.value.id) this.leases.delete(leaseId);
3602
+ if (lease.workspaceId === record2.value.id) this.leases.delete(leaseId);
3547
3603
  }
3548
- this.cleanupUnusedPrincipal(record.value.principalId);
3604
+ this.cleanupUnusedPrincipal(record2.value.principalId);
3549
3605
  if (this.workspaces.size < this.maxWorkspaceRecords) return;
3550
3606
  }
3551
3607
  }
@@ -3559,7 +3615,7 @@ var MemoryBrokerRuntime = class {
3559
3615
  }
3560
3616
  cleanupUnusedPrincipal(principalId) {
3561
3617
  const hasConnection = [...this.connectionsByBridge.values()].some((connection) => connection.value.principalId === principalId);
3562
- const hasWorkspace = [...this.workspaces.values()].some((record) => record.value.principalId === principalId);
3618
+ const hasWorkspace = [...this.workspaces.values()].some((record2) => record2.value.principalId === principalId);
3563
3619
  if (hasConnection || hasWorkspace) return;
3564
3620
  const principal = this.principals.get(principalId);
3565
3621
  if (!principal) return;
@@ -3582,8 +3638,10 @@ var MemoryBrokerRuntime = class {
3582
3638
  const params = validateToolCallParams(value);
3583
3639
  const definition = getToolDefinition(params.name);
3584
3640
  if (!isToolAvailableInProtocol(definition.name, connection.value.protocol)) {
3585
- throw protocolIncompatible(`${definition.name} requires protocol 1.2 or newer`, {
3641
+ const requiredMinor = minimumProtocolMinorForTool(definition.name);
3642
+ throw protocolIncompatible(`${definition.name} requires protocol 1.${requiredMinor} or newer`, {
3586
3643
  tool: definition.name,
3644
+ requiredProtocol: { major: 1, minor: requiredMinor },
3587
3645
  selectedProtocol: `${connection.value.protocol.major}.${connection.value.protocol.minor}`
3588
3646
  });
3589
3647
  }
@@ -3734,8 +3792,8 @@ var MemoryBrokerRuntime = class {
3734
3792
  }));
3735
3793
  }
3736
3794
  async callBrowserDiscover(connection, params, args, definition) {
3737
- const record = args;
3738
- const filter = typeof record.browser === "string" ? record.browser.toLowerCase() : void 0;
3795
+ const record2 = args;
3796
+ const filter = typeof record2.browser === "string" ? record2.browser.toLowerCase() : void 0;
3739
3797
  const browsers = this.browserBindings.map((binding) => ({ ...binding.candidate })).filter((candidate) => !filter || [candidate.id, candidate.product, candidate.channel].some((value) => value?.toLowerCase().includes(filter)));
3740
3798
  return asJson(await this.commands.run({
3741
3799
  principalId: connection.value.principalId,
@@ -3820,8 +3878,8 @@ var MemoryBrokerRuntime = class {
3820
3878
  async getArtifact(connection, value) {
3821
3879
  const params = validateArtifactAccessParams(value);
3822
3880
  this.requireArtifactContext(connection, params.workspaceId, params.leaseId);
3823
- const record = await this.requireArtifactStore().get(params.workspaceId, params.artifactId);
3824
- return asJson({ artifact: record.descriptor, path: record.path });
3881
+ const record2 = await this.requireArtifactStore().get(params.workspaceId, params.artifactId);
3882
+ return asJson({ artifact: record2.descriptor, path: record2.path });
3825
3883
  }
3826
3884
  async exportArtifact(connection, value) {
3827
3885
  const params = validateArtifactExportParams(value);
@@ -3836,18 +3894,18 @@ var MemoryBrokerRuntime = class {
3836
3894
  async importArtifact(connection, value) {
3837
3895
  const params = validateArtifactImportParams(value);
3838
3896
  this.requireArtifactContext(connection, params.workspaceId, params.leaseId);
3839
- const record = await this.requireArtifactStore().importFile(
3897
+ const record2 = await this.requireArtifactStore().importFile(
3840
3898
  params.workspaceId,
3841
3899
  params.path,
3842
3900
  params.mimeType
3843
3901
  );
3844
- return asJson({ artifact: record.descriptor });
3902
+ return asJson({ artifact: record2.descriptor });
3845
3903
  }
3846
3904
  async retainArtifact(connection, value) {
3847
3905
  const params = validateArtifactAccessParams(value);
3848
3906
  this.requireArtifactContext(connection, params.workspaceId, params.leaseId);
3849
- const record = await this.requireArtifactStore().retain(params.workspaceId, params.artifactId);
3850
- return asJson({ artifact: record.descriptor, path: record.path });
3907
+ const record2 = await this.requireArtifactStore().retain(params.workspaceId, params.artifactId);
3908
+ return asJson({ artifact: record2.descriptor, path: record2.path });
3851
3909
  }
3852
3910
  async releaseArtifact(connection, value) {
3853
3911
  const params = validateArtifactAccessParams(value);
@@ -4394,20 +4452,6 @@ var PAGE_DIMENSIONS = `JSON.stringify({
4394
4452
  width: Math.max(document.documentElement.scrollWidth, document.documentElement.clientWidth),
4395
4453
  height: Math.max(document.documentElement.scrollHeight, document.documentElement.clientHeight)
4396
4454
  })`;
4397
- var INJECT_BORDER = `(() => {
4398
- if (document.getElementById('__bp_overlay')) return;
4399
- const d = document.createElement('div');
4400
- d.id = '__bp_overlay';
4401
- d.setAttribute('aria-hidden','true');
4402
- d.setAttribute('role','presentation');
4403
- Object.assign(d.style, {position:'fixed',inset:'0',zIndex:'2147483647',pointerEvents:'none'});
4404
- document.documentElement.appendChild(d);
4405
- try{d.animate([
4406
- {boxShadow:'inset 0 0 20px rgba(59,130,246,.8),inset 0 0 40px rgba(59,130,246,.4),inset 0 0 80px rgba(59,130,246,.15)'},
4407
- {boxShadow:'inset 0 0 30px rgba(59,130,246,1),inset 0 0 60px rgba(59,130,246,.5),inset 0 0 100px rgba(59,130,246,.2)'},
4408
- {boxShadow:'inset 0 0 20px rgba(59,130,246,.8),inset 0 0 40px rgba(59,130,246,.4),inset 0 0 80px rgba(59,130,246,.15)'},
4409
- ],{duration:2500,iterations:Infinity,easing:'ease-in-out'})}catch(e){}
4410
- })()`;
4411
4455
  function elementRect(selector) {
4412
4456
  return `JSON.stringify((() => {
4413
4457
  const el = document.querySelector(${JSON.stringify(selector)});
@@ -5403,7 +5447,7 @@ async function discoverBrowserCandidates(options = {}) {
5403
5447
  id: stableBrowserId(definition, os),
5404
5448
  product: definition.product,
5405
5449
  channel: definition.channel,
5406
- profile: definition.dataDir,
5450
+ userDataRoot: definition.dataDir,
5407
5451
  processState,
5408
5452
  remoteDebuggingState,
5409
5453
  authorizationState,
@@ -5458,15 +5502,9 @@ async function waitForLoad(transport, sessionId, timeout = 3e4) {
5458
5502
  let interactiveSince = null;
5459
5503
  const deadlineReached = /* @__PURE__ */ Symbol("page-load-deadline");
5460
5504
  let deadlineTimer;
5461
- const deadline = new Promise((resolve2) => {
5462
- deadlineTimer = setTimeout(() => resolve2(deadlineReached), timeout);
5505
+ const deadline = new Promise((resolve3) => {
5506
+ deadlineTimer = setTimeout(() => resolve3(deadlineReached), timeout);
5463
5507
  });
5464
- const injectBorder = async () => {
5465
- await Promise.race([
5466
- transport.send("Runtime.evaluate", { expression: INJECT_BORDER }, sessionId).catch(() => void 0),
5467
- deadline
5468
- ]);
5469
- };
5470
5508
  try {
5471
5509
  while (true) {
5472
5510
  const evaluation = await Promise.race([
@@ -5480,18 +5518,16 @@ async function waitForLoad(transport, sessionId, timeout = 3e4) {
5480
5518
  ]);
5481
5519
  if (evaluation === deadlineReached) throw new PageLoadTimeoutError(timeout);
5482
5520
  if (evaluation.state === "complete") {
5483
- await injectBorder();
5484
5521
  return;
5485
5522
  }
5486
5523
  if (evaluation.state === "interactive") {
5487
5524
  if (interactiveSince === null) interactiveSince = Date.now();
5488
5525
  if (Date.now() - interactiveSince >= interactiveGrace) {
5489
- await injectBorder();
5490
5526
  return;
5491
5527
  }
5492
5528
  }
5493
5529
  const sleep = await Promise.race([
5494
- new Promise((resolve2) => setTimeout(() => resolve2("poll"), 200)),
5530
+ new Promise((resolve3) => setTimeout(() => resolve3("poll"), 200)),
5495
5531
  deadline
5496
5532
  ]);
5497
5533
  if (sleep === deadlineReached) throw new PageLoadTimeoutError(timeout);
@@ -5675,7 +5711,7 @@ var InputDispatcher = class {
5675
5711
  }
5676
5712
  hooks.afterCharacter?.({ character: char, index });
5677
5713
  index += 1;
5678
- if (delayMs > 0) await new Promise((resolve2) => setTimeout(resolve2, delayMs));
5714
+ if (delayMs > 0) await new Promise((resolve3) => setTimeout(resolve3, delayMs));
5679
5715
  }
5680
5716
  }
5681
5717
  async insertText(text) {
@@ -5719,7 +5755,7 @@ var ObservationService = class {
5719
5755
  }
5720
5756
  async observeAfterAction(limit = OBSERVATION_V1_LIMITS.defaultElements) {
5721
5757
  if (this.settleDelayMs > 0) {
5722
- await new Promise((resolve2) => setTimeout(resolve2, this.settleDelayMs));
5758
+ await new Promise((resolve3) => setTimeout(resolve3, this.settleDelayMs));
5723
5759
  }
5724
5760
  await this.loadWaiter(this.transport, this.sessionId, this.loadTimeoutMs);
5725
5761
  return this.observe(limit);
@@ -5778,24 +5814,24 @@ function parsePointerTargetState(value) {
5778
5814
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
5779
5815
  throw new BrowserPilotError("internal_error", "Chrome returned invalid pointer target state");
5780
5816
  }
5781
- const record = value;
5782
- if (record.status === "ready" && Number.isFinite(record.x) && Number.isFinite(record.y)) {
5817
+ const record2 = value;
5818
+ if (record2.status === "ready" && Number.isFinite(record2.x) && Number.isFinite(record2.y)) {
5783
5819
  return {
5784
5820
  status: "ready",
5785
- x: Number(record.x),
5786
- y: Number(record.y),
5787
- targetState: parseClickTargetState(record.targetState)
5821
+ x: Number(record2.x),
5822
+ y: Number(record2.y),
5823
+ targetState: parseClickTargetState(record2.targetState)
5788
5824
  };
5789
5825
  }
5790
- if (record.status !== "blocked" || !POINTER_FAILURE_REASONS.has(record.reason)) {
5826
+ if (record2.status !== "blocked" || !POINTER_FAILURE_REASONS.has(record2.reason)) {
5791
5827
  throw new BrowserPilotError("internal_error", "Chrome returned invalid pointer target state");
5792
5828
  }
5793
5829
  let obstruction;
5794
- if (record.obstruction !== void 0) {
5795
- if (typeof record.obstruction !== "object" || record.obstruction === null || Array.isArray(record.obstruction)) {
5830
+ if (record2.obstruction !== void 0) {
5831
+ if (typeof record2.obstruction !== "object" || record2.obstruction === null || Array.isArray(record2.obstruction)) {
5796
5832
  throw new BrowserPilotError("internal_error", "Chrome returned invalid pointer target state");
5797
5833
  }
5798
- const candidate = record.obstruction;
5834
+ const candidate = record2.obstruction;
5799
5835
  if (typeof candidate.tagName !== "string" || candidate.tagName.length > 40 || candidate.role !== void 0 && (typeof candidate.role !== "string" || candidate.role.length > 40)) {
5800
5836
  throw new BrowserPilotError("internal_error", "Chrome returned invalid pointer target state");
5801
5837
  }
@@ -5806,7 +5842,7 @@ function parsePointerTargetState(value) {
5806
5842
  }
5807
5843
  return {
5808
5844
  status: "blocked",
5809
- reason: record.reason,
5845
+ reason: record2.reason,
5810
5846
  ...obstruction ? { obstruction } : {}
5811
5847
  };
5812
5848
  }
@@ -5823,18 +5859,18 @@ function parseClickTargetState(value) {
5823
5859
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
5824
5860
  throw new BrowserPilotError("internal_error", "Chrome returned invalid click verification state");
5825
5861
  }
5826
- const record = value;
5827
- if (typeof record.connected !== "boolean" || !CLICK_TARGET_KINDS.has(record.kind) || typeof record.focused !== "boolean" || record.checked !== void 0 && typeof record.checked !== "boolean" && record.checked !== "mixed" || record.selected !== void 0 && typeof record.selected !== "boolean" || record.pressed !== void 0 && typeof record.pressed !== "boolean" && record.pressed !== "mixed" || record.expanded !== void 0 && typeof record.expanded !== "boolean") {
5862
+ const record2 = value;
5863
+ if (typeof record2.connected !== "boolean" || !CLICK_TARGET_KINDS.has(record2.kind) || typeof record2.focused !== "boolean" || record2.checked !== void 0 && typeof record2.checked !== "boolean" && record2.checked !== "mixed" || record2.selected !== void 0 && typeof record2.selected !== "boolean" || record2.pressed !== void 0 && typeof record2.pressed !== "boolean" && record2.pressed !== "mixed" || record2.expanded !== void 0 && typeof record2.expanded !== "boolean") {
5828
5864
  throw new BrowserPilotError("internal_error", "Chrome returned invalid click verification state");
5829
5865
  }
5830
5866
  return {
5831
- connected: record.connected,
5832
- kind: record.kind,
5833
- focused: record.focused,
5834
- ...record.checked !== void 0 ? { checked: record.checked } : {},
5835
- ...record.selected !== void 0 ? { selected: record.selected } : {},
5836
- ...record.pressed !== void 0 ? { pressed: record.pressed } : {},
5837
- ...record.expanded !== void 0 ? { expanded: record.expanded } : {}
5867
+ connected: record2.connected,
5868
+ kind: record2.kind,
5869
+ focused: record2.focused,
5870
+ ...record2.checked !== void 0 ? { checked: record2.checked } : {},
5871
+ ...record2.selected !== void 0 ? { selected: record2.selected } : {},
5872
+ ...record2.pressed !== void 0 ? { pressed: record2.pressed } : {},
5873
+ ...record2.expanded !== void 0 ? { expanded: record2.expanded } : {}
5838
5874
  };
5839
5875
  }
5840
5876
  function clickEvidence(before, after, button, clickCount) {
@@ -5880,20 +5916,20 @@ function parseEditableState(value) {
5880
5916
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
5881
5917
  throw new BrowserPilotError("internal_error", "Chrome returned invalid input verification state");
5882
5918
  }
5883
- const record = value;
5884
- const kind = record.kind;
5885
- const inputType = record.inputType;
5886
- const editMode = record.editMode;
5887
- const selectionMode = record.selectionMode;
5888
- const reason2 = record.reason;
5889
- if (!["input", "contenteditable", "unsupported"].includes(String(kind)) || typeof record.value !== "string" || typeof record.sensitive !== "boolean" || typeof record.editable !== "boolean" || inputType !== void 0 && (typeof inputType !== "string" || inputType.length > 64) || editMode !== void 0 && editMode !== "text" && editMode !== "value" || selectionMode !== void 0 && selectionMode !== "range" && selectionMode !== "select" || reason2 !== void 0 && !EDITABLE_BLOCK_REASONS.has(reason2) || record.editable && (kind === "unsupported" || editMode === void 0 || reason2 !== void 0) || !record.editable && reason2 === void 0 || kind === "input" && inputType === void 0 || editMode === "text" && selectionMode === void 0 || editMode === "value" && selectionMode !== void 0) {
5919
+ const record2 = value;
5920
+ const kind = record2.kind;
5921
+ const inputType = record2.inputType;
5922
+ const editMode = record2.editMode;
5923
+ const selectionMode = record2.selectionMode;
5924
+ const reason2 = record2.reason;
5925
+ if (!["input", "contenteditable", "unsupported"].includes(String(kind)) || typeof record2.value !== "string" || typeof record2.sensitive !== "boolean" || typeof record2.editable !== "boolean" || inputType !== void 0 && (typeof inputType !== "string" || inputType.length > 64) || editMode !== void 0 && editMode !== "text" && editMode !== "value" || selectionMode !== void 0 && selectionMode !== "range" && selectionMode !== "select" || reason2 !== void 0 && !EDITABLE_BLOCK_REASONS.has(reason2) || record2.editable && (kind === "unsupported" || editMode === void 0 || reason2 !== void 0) || !record2.editable && reason2 === void 0 || kind === "input" && inputType === void 0 || editMode === "text" && selectionMode === void 0 || editMode === "value" && selectionMode !== void 0) {
5890
5926
  throw new BrowserPilotError("internal_error", "Chrome returned invalid input verification state");
5891
5927
  }
5892
5928
  return {
5893
5929
  kind,
5894
- value: record.value,
5895
- sensitive: record.sensitive,
5896
- editable: record.editable,
5930
+ value: record2.value,
5931
+ sensitive: record2.sensitive,
5932
+ editable: record2.editable,
5897
5933
  ...inputType !== void 0 ? { inputType } : {},
5898
5934
  ...editMode !== void 0 ? { editMode } : {},
5899
5935
  ...selectionMode !== void 0 ? { selectionMode } : {},
@@ -5928,19 +5964,19 @@ function parsePressTargetState(value, focusToken) {
5928
5964
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
5929
5965
  throw new BrowserPilotError("internal_error", "Chrome returned invalid key verification state");
5930
5966
  }
5931
- const record = value;
5932
- if (!PRESS_TARGET_KINDS.has(record.kind) || typeof record.sensitive !== "boolean" || record.valueToken !== void 0 && (typeof record.valueToken !== "string" || record.valueToken.length > 64) || record.selectedToken !== void 0 && (typeof record.selectedToken !== "string" || record.selectedToken.length > 64) || record.checked !== void 0 && typeof record.checked !== "boolean" && record.checked !== "mixed" || record.pressed !== void 0 && typeof record.pressed !== "boolean" && record.pressed !== "mixed" || record.expanded !== void 0 && typeof record.expanded !== "boolean") {
5967
+ const record2 = value;
5968
+ if (!PRESS_TARGET_KINDS.has(record2.kind) || typeof record2.sensitive !== "boolean" || record2.valueToken !== void 0 && (typeof record2.valueToken !== "string" || record2.valueToken.length > 64) || record2.selectedToken !== void 0 && (typeof record2.selectedToken !== "string" || record2.selectedToken.length > 64) || record2.checked !== void 0 && typeof record2.checked !== "boolean" && record2.checked !== "mixed" || record2.pressed !== void 0 && typeof record2.pressed !== "boolean" && record2.pressed !== "mixed" || record2.expanded !== void 0 && typeof record2.expanded !== "boolean") {
5933
5969
  throw new BrowserPilotError("internal_error", "Chrome returned invalid key verification state");
5934
5970
  }
5935
5971
  return {
5936
5972
  ...focusToken ? { focusToken } : {},
5937
- kind: record.kind,
5938
- sensitive: record.sensitive,
5939
- ...record.valueToken !== void 0 ? { valueToken: record.valueToken } : {},
5940
- ...record.selectedToken !== void 0 ? { selectedToken: record.selectedToken } : {},
5941
- ...record.checked !== void 0 ? { checked: record.checked } : {},
5942
- ...record.pressed !== void 0 ? { pressed: record.pressed } : {},
5943
- ...record.expanded !== void 0 ? { expanded: record.expanded } : {}
5973
+ kind: record2.kind,
5974
+ sensitive: record2.sensitive,
5975
+ ...record2.valueToken !== void 0 ? { valueToken: record2.valueToken } : {},
5976
+ ...record2.selectedToken !== void 0 ? { selectedToken: record2.selectedToken } : {},
5977
+ ...record2.checked !== void 0 ? { checked: record2.checked } : {},
5978
+ ...record2.pressed !== void 0 ? { pressed: record2.pressed } : {},
5979
+ ...record2.expanded !== void 0 ? { expanded: record2.expanded } : {}
5944
5980
  };
5945
5981
  }
5946
5982
  function pressEvidence(before, after) {
@@ -6076,7 +6112,7 @@ var ActionService = class {
6076
6112
  await this.input.click(point.x, point.y, { button, clickCount });
6077
6113
  this.markDispatched(run);
6078
6114
  if (this.readbackDelayMs > 0) {
6079
- await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
6115
+ await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
6080
6116
  }
6081
6117
  let after;
6082
6118
  try {
@@ -6128,7 +6164,7 @@ var ActionService = class {
6128
6164
  await this.input.press(key);
6129
6165
  this.markDispatched(run);
6130
6166
  if (this.readbackDelayMs > 0) {
6131
- await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
6167
+ await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
6132
6168
  }
6133
6169
  let after;
6134
6170
  try {
@@ -6194,7 +6230,7 @@ var ActionService = class {
6194
6230
  this.markDispatched(run);
6195
6231
  }
6196
6232
  if (this.readbackDelayMs > 0) {
6197
- await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
6233
+ await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
6198
6234
  }
6199
6235
  const after = await this.readElementState(objectId);
6200
6236
  evidence = inputEvidence("type", before, after, text, !!options.clear);
@@ -6226,7 +6262,7 @@ var ActionService = class {
6226
6262
  this.willDispatch();
6227
6263
  await this.input.click(location.x, location.y);
6228
6264
  this.markDispatched(run);
6229
- if (this.focusDelayMs > 0) await new Promise((resolve2) => setTimeout(resolve2, this.focusDelayMs));
6265
+ if (this.focusDelayMs > 0) await new Promise((resolve3) => setTimeout(resolve3, this.focusDelayMs));
6230
6266
  }
6231
6267
  await this.captureFocus(run, "capture_input_focus");
6232
6268
  const before = await this.readActiveState();
@@ -6248,7 +6284,7 @@ var ActionService = class {
6248
6284
  },
6249
6285
  afterCharacter: () => this.markDispatched(run)
6250
6286
  });
6251
- if (this.readbackDelayMs > 0) await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
6287
+ if (this.readbackDelayMs > 0) await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
6252
6288
  const after = await this.readActiveState();
6253
6289
  const evidence = inputEvidence("keyboard", before, after, text, !!options.clear);
6254
6290
  this.requireVerification(evidence, options.verification);
@@ -6964,13 +7000,13 @@ var DownloadController = class {
6964
7000
  return;
6965
7001
  }
6966
7002
  if (!this.isTracked(session, download)) return;
6967
- const record = await this.artifacts.ingestDownloadCopy(
7003
+ const record2 = await this.artifacts.ingestDownloadCopy(
6968
7004
  session.context.workspaceId,
6969
7005
  completedFile.path,
6970
7006
  download.fileName
6971
7007
  );
6972
7008
  if (!this.isTracked(session, download)) {
6973
- await this.artifacts.release(session.context.workspaceId, record.descriptor.id);
7009
+ await this.artifacts.release(session.context.workspaceId, record2.descriptor.id);
6974
7010
  return;
6975
7011
  }
6976
7012
  download.state = "terminal";
@@ -6978,7 +7014,7 @@ var DownloadController = class {
6978
7014
  this.publish(session, {
6979
7015
  downloadId: download.id,
6980
7016
  state: "completed",
6981
- artifact: record.descriptor
7017
+ artifact: record2.descriptor
6982
7018
  });
6983
7019
  } catch (error) {
6984
7020
  if (download.state === "terminal") return;
@@ -7069,8 +7105,8 @@ var MAX_PROFILE_CONTEXT_RECORDS = 512;
7069
7105
  function cloneTarget(target) {
7070
7106
  return { ...target };
7071
7107
  }
7072
- function cloneRecord(record) {
7073
- return { ...record, targets: record.targets.map(cloneTarget) };
7108
+ function cloneRecord(record2) {
7109
+ return { ...record2, targets: record2.targets.map(cloneTarget) };
7074
7110
  }
7075
7111
  function contextKey(cdpBrowserContextId) {
7076
7112
  return cdpBrowserContextId ? `cdp:${cdpBrowserContextId}` : DEFAULT_CDP_CONTEXT_KEY;
@@ -7115,22 +7151,23 @@ var MemoryProfileContextRegistry = class {
7115
7151
  });
7116
7152
  }
7117
7153
  for (const id of this.currentByKey.values()) {
7118
- const record = this.recordsById.get(id);
7119
- if (record) record.active = false;
7154
+ const record2 = this.recordsById.get(id);
7155
+ if (record2) record2.active = false;
7120
7156
  }
7121
7157
  this.contextByTarget.clear();
7122
7158
  const activeKeys = new Set(grouped.keys());
7123
7159
  for (const [key, group] of grouped) {
7124
7160
  let id = this.currentByKey.get(key);
7125
- let record = id ? this.recordsById.get(id) : void 0;
7126
- if (!record) {
7161
+ let record2 = id ? this.recordsById.get(id) : void 0;
7162
+ if (!record2) {
7127
7163
  id = this.nextId();
7128
7164
  const rawContextId = group[0]?.cdpBrowserContextId;
7129
- record = {
7165
+ record2 = {
7130
7166
  id,
7131
7167
  browserInstanceId: this.browserInstanceId,
7132
7168
  browserConnectionGeneration,
7133
7169
  label: `Profile ${++this.nextSequence}`,
7170
+ identityStatus: "unidentified",
7134
7171
  tabCount: 0,
7135
7172
  eligibleTabCount: 0,
7136
7173
  sequence: this.nextSequence,
@@ -7138,34 +7175,34 @@ var MemoryProfileContextRegistry = class {
7138
7175
  targets: [],
7139
7176
  ...rawContextId ? { cdpBrowserContextId: rawContextId } : {}
7140
7177
  };
7141
- this.recordsById.set(id, record);
7178
+ this.recordsById.set(id, record2);
7142
7179
  this.recordOrder.push(id);
7143
7180
  this.currentByKey.set(key, id);
7144
7181
  }
7145
7182
  const sorted = [...group].sort((left, right) => Number(right.eligible) - Number(left.eligible) || left.cdpTargetId.localeCompare(right.cdpTargetId));
7146
- record.active = true;
7147
- record.tabCount = sorted.length;
7148
- record.eligibleTabCount = sorted.filter((target) => target.eligible).length;
7149
- record.targets = sorted;
7150
- record.representativeCdpTargetId = sorted[0]?.cdpTargetId;
7151
- for (const target of sorted) this.contextByTarget.set(target.cdpTargetId, record.id);
7183
+ record2.active = true;
7184
+ record2.tabCount = sorted.length;
7185
+ record2.eligibleTabCount = sorted.filter((target) => target.eligible).length;
7186
+ record2.targets = sorted;
7187
+ record2.representativeCdpTargetId = sorted[0]?.cdpTargetId;
7188
+ for (const target of sorted) this.contextByTarget.set(target.cdpTargetId, record2.id);
7152
7189
  }
7153
7190
  for (const [key, id] of this.currentByKey) {
7154
7191
  if (activeKeys.has(key)) continue;
7155
- const record = this.recordsById.get(id);
7156
- if (record) record.active = false;
7192
+ const record2 = this.recordsById.get(id);
7193
+ if (record2) record2.active = false;
7157
7194
  this.currentByKey.delete(key);
7158
7195
  }
7159
7196
  this.pruneStaleRecords();
7160
7197
  return this.list(browserConnectionGeneration);
7161
7198
  }
7162
7199
  list(browserConnectionGeneration) {
7163
- return [...this.currentByKey.values()].map((id) => this.recordsById.get(id)).filter((record) => record !== void 0 && record.active && record.browserConnectionGeneration === browserConnectionGeneration).sort((left, right) => left.sequence - right.sequence).map(cloneRecord);
7200
+ return [...this.currentByKey.values()].map((id) => this.recordsById.get(id)).filter((record2) => record2 !== void 0 && record2.active && record2.browserConnectionGeneration === browserConnectionGeneration).sort((left, right) => left.sequence - right.sequence).map(cloneRecord);
7164
7201
  }
7165
7202
  resolve(profileContextId, browserConnectionGeneration) {
7166
- const record = this.recordsById.get(profileContextId);
7167
- if (!record) throw invalidArgument("Unknown Profile context", "profileContextId");
7168
- if (!record.active || record.browserInstanceId !== this.browserInstanceId || record.browserConnectionGeneration !== browserConnectionGeneration) {
7203
+ const record2 = this.recordsById.get(profileContextId);
7204
+ if (!record2) throw invalidArgument("Unknown Profile context", "profileContextId");
7205
+ if (!record2.active || record2.browserInstanceId !== this.browserInstanceId || record2.browserConnectionGeneration !== browserConnectionGeneration) {
7169
7206
  throw new BrowserPilotError("profile_context_stale", "Profile context belongs to a stale browser connection", {
7170
7207
  retryable: true,
7171
7208
  context: {
@@ -7179,7 +7216,31 @@ var MemoryProfileContextRegistry = class {
7179
7216
  }
7180
7217
  });
7181
7218
  }
7182
- return cloneRecord(record);
7219
+ return cloneRecord(record2);
7220
+ }
7221
+ setVerifiedIdentity(profileContextId, browserConnectionGeneration, identity) {
7222
+ const record2 = this.currentRecord(profileContextId, browserConnectionGeneration);
7223
+ record2.identityStatus = "verified";
7224
+ record2.profileName = identity.profileName;
7225
+ record2.displayName = identity.profileName;
7226
+ record2.profileDirectory = identity.profileDirectory;
7227
+ if (identity.accountName) record2.accountName = identity.accountName;
7228
+ else delete record2.accountName;
7229
+ if (identity.accountEmail) record2.accountEmail = identity.accountEmail;
7230
+ else delete record2.accountEmail;
7231
+ delete record2.identityErrorCode;
7232
+ return cloneRecord(record2);
7233
+ }
7234
+ setIdentityUnavailable(profileContextId, browserConnectionGeneration, identityErrorCode) {
7235
+ const record2 = this.currentRecord(profileContextId, browserConnectionGeneration);
7236
+ record2.identityStatus = "unavailable";
7237
+ record2.identityErrorCode = identityErrorCode;
7238
+ delete record2.profileName;
7239
+ delete record2.displayName;
7240
+ delete record2.profileDirectory;
7241
+ delete record2.accountName;
7242
+ delete record2.accountEmail;
7243
+ return cloneRecord(record2);
7183
7244
  }
7184
7245
  forTarget(cdpTargetId, browserConnectionGeneration) {
7185
7246
  const id = this.contextByTarget.get(cdpTargetId);
@@ -7198,19 +7259,23 @@ var MemoryProfileContextRegistry = class {
7198
7259
  }
7199
7260
  invalidateCurrent() {
7200
7261
  for (const id of this.currentByKey.values()) {
7201
- const record = this.recordsById.get(id);
7202
- if (record) record.active = false;
7262
+ const record2 = this.recordsById.get(id);
7263
+ if (record2) record2.active = false;
7203
7264
  }
7204
7265
  this.currentByKey.clear();
7205
7266
  this.contextByTarget.clear();
7206
7267
  }
7268
+ currentRecord(profileContextId, browserConnectionGeneration) {
7269
+ this.resolve(profileContextId, browserConnectionGeneration);
7270
+ return this.recordsById.get(profileContextId);
7271
+ }
7207
7272
  pruneStaleRecords() {
7208
7273
  while (this.recordsById.size > MAX_PROFILE_CONTEXT_RECORDS) {
7209
7274
  const id = this.recordOrder.shift();
7210
7275
  if (!id) return;
7211
- const record = this.recordsById.get(id);
7212
- if (!record || record.active) {
7213
- if (record?.active) this.recordOrder.push(id);
7276
+ const record2 = this.recordsById.get(id);
7277
+ if (!record2 || record2.active) {
7278
+ if (record2?.active) this.recordOrder.push(id);
7214
7279
  continue;
7215
7280
  }
7216
7281
  this.recordsById.delete(id);
@@ -7233,8 +7298,10 @@ function isEligibleUserPage(target) {
7233
7298
  return !(/* @__PURE__ */ new Set([
7234
7299
  "devtools:",
7235
7300
  "chrome:",
7301
+ "chrome-extension:",
7236
7302
  "chrome-untrusted:",
7237
7303
  "edge:",
7304
+ "edge-extension:",
7238
7305
  "brave:",
7239
7306
  "vivaldi:"
7240
7307
  ])).has(url.protocol);
@@ -7242,6 +7309,14 @@ function isEligibleUserPage(target) {
7242
7309
  return target.url === "about:blank";
7243
7310
  }
7244
7311
  }
7312
+ function isExtensionPage(target) {
7313
+ try {
7314
+ const protocol = new URL(target.url).protocol;
7315
+ return protocol === "chrome-extension:" || protocol === "edge-extension:";
7316
+ } catch {
7317
+ return false;
7318
+ }
7319
+ }
7245
7320
  var CdpBrowserTargetCatalog = class {
7246
7321
  constructor(transport, browserInstanceId, identity, options) {
7247
7322
  this.transport = transport;
@@ -7272,6 +7347,7 @@ var CdpBrowserTargetCatalog = class {
7272
7347
  type: typeof value.type === "string" ? value.type : "",
7273
7348
  ...typeof value.openerId === "string" ? { openerCdpTargetId: value.openerId } : {}
7274
7349
  };
7350
+ if (isExtensionPage(candidate)) continue;
7275
7351
  const eligible2 = isEligibleUserPage(candidate) && !this.options.isExcludedTarget(candidate);
7276
7352
  snapshots.push({
7277
7353
  ...candidate,
@@ -7507,8 +7583,8 @@ var BrowserWatchdogService = class {
7507
7583
 
7508
7584
  // src/services/controlled-target-registry.ts
7509
7585
  import { randomUUID as randomUUID8 } from "crypto";
7510
- function cloneRecord2(record) {
7511
- return { ...record };
7586
+ function cloneRecord2(record2) {
7587
+ return { ...record2 };
7512
7588
  }
7513
7589
  function targetKey2(workspaceId, browserInstanceId, cdpTargetId) {
7514
7590
  return `${workspaceId}\0${browserInstanceId}\0${cdpTargetId}`;
@@ -7542,7 +7618,7 @@ var MemoryControlledTargetRegistry = class {
7542
7618
  existing.openerCdpTargetId = input.openerCdpTargetId;
7543
7619
  return cloneRecord2(existing);
7544
7620
  }
7545
- const record = {
7621
+ const record2 = {
7546
7622
  id: this.nextId(),
7547
7623
  principalId: input.principalId,
7548
7624
  workspaceId: input.workspaceId,
@@ -7558,9 +7634,9 @@ var MemoryControlledTargetRegistry = class {
7558
7634
  createdAt: this.now(),
7559
7635
  state: "active"
7560
7636
  };
7561
- this.records.set(record.id, record);
7562
- this.activeKeys.set(key, record.id);
7563
- return cloneRecord2(record);
7637
+ this.records.set(record2.id, record2);
7638
+ this.activeKeys.set(key, record2.id);
7639
+ return cloneRecord2(record2);
7564
7640
  }
7565
7641
  syncUserTargets(context, browserInstanceId, userTargets) {
7566
7642
  const targetsByKey = /* @__PURE__ */ new Map();
@@ -7572,11 +7648,11 @@ var MemoryControlledTargetRegistry = class {
7572
7648
  );
7573
7649
  }
7574
7650
  const invalidated = [];
7575
- for (const record of this.records.values()) {
7576
- if (record.state !== "active" || record.origin !== "user_tab" || record.principalId !== context.principalId || record.workspaceId !== context.workspaceId || record.browserInstanceId !== browserInstanceId) continue;
7577
- const key = targetKey2(record.workspaceId, record.browserInstanceId, record.cdpTargetId);
7651
+ for (const record2 of this.records.values()) {
7652
+ if (record2.state !== "active" || record2.origin !== "user_tab" || record2.principalId !== context.principalId || record2.workspaceId !== context.workspaceId || record2.browserInstanceId !== browserInstanceId) continue;
7653
+ const key = targetKey2(record2.workspaceId, record2.browserInstanceId, record2.cdpTargetId);
7578
7654
  if (!targetsByKey.has(key)) {
7579
- invalidated.push(this.invalidate(record, "target_ineligible"));
7655
+ invalidated.push(this.invalidate(record2, "target_ineligible"));
7580
7656
  }
7581
7657
  }
7582
7658
  const targets = [];
@@ -7594,7 +7670,7 @@ var MemoryControlledTargetRegistry = class {
7594
7670
  targets.push(cloneRecord2(existing));
7595
7671
  continue;
7596
7672
  }
7597
- const record = {
7673
+ const record2 = {
7598
7674
  id: this.nextId(),
7599
7675
  principalId: context.principalId,
7600
7676
  workspaceId: context.workspaceId,
@@ -7608,27 +7684,27 @@ var MemoryControlledTargetRegistry = class {
7608
7684
  createdAt: this.now(),
7609
7685
  state: "active"
7610
7686
  };
7611
- this.records.set(record.id, record);
7612
- this.activeKeys.set(key, record.id);
7613
- targets.push(cloneRecord2(record));
7614
- created.push(cloneRecord2(record));
7687
+ this.records.set(record2.id, record2);
7688
+ this.activeKeys.set(key, record2.id);
7689
+ targets.push(cloneRecord2(record2));
7690
+ created.push(cloneRecord2(record2));
7615
7691
  }
7616
7692
  return { targets, created, invalidated };
7617
7693
  }
7618
7694
  reconcileBrowserTargets(context, browserInstanceId, liveTargets) {
7619
7695
  const invalidated = [];
7620
- for (const record of this.records.values()) {
7621
- if (record.state !== "active" || record.principalId !== context.principalId || record.workspaceId !== context.workspaceId || record.browserInstanceId !== browserInstanceId) continue;
7622
- const live = liveTargets.get(record.cdpTargetId);
7696
+ for (const record2 of this.records.values()) {
7697
+ if (record2.state !== "active" || record2.principalId !== context.principalId || record2.workspaceId !== context.workspaceId || record2.browserInstanceId !== browserInstanceId) continue;
7698
+ const live = liveTargets.get(record2.cdpTargetId);
7623
7699
  if (!live) {
7624
- invalidated.push(this.invalidate(record, "target_detached"));
7700
+ invalidated.push(this.invalidate(record2, "target_detached"));
7625
7701
  continue;
7626
7702
  }
7627
- record.title = live.title;
7628
- record.url = live.url;
7629
- record.openerCdpTargetId = live.openerCdpTargetId;
7630
- if (record.profileContextId && live.profileContextId && record.profileContextId !== live.profileContextId) {
7631
- invalidated.push(this.invalidate(record, "target_ineligible"));
7703
+ record2.title = live.title;
7704
+ record2.url = live.url;
7705
+ record2.openerCdpTargetId = live.openerCdpTargetId;
7706
+ if (record2.profileContextId && live.profileContextId && record2.profileContextId !== live.profileContextId) {
7707
+ invalidated.push(this.invalidate(record2, "target_ineligible"));
7632
7708
  }
7633
7709
  }
7634
7710
  return invalidated;
@@ -7636,38 +7712,38 @@ var MemoryControlledTargetRegistry = class {
7636
7712
  list(context, leaseId, scope = "all") {
7637
7713
  const activeTargetId = this.activeByLease.get(leaseId);
7638
7714
  const records = [...this.records.values()].filter(
7639
- (record) => record.state === "active" && record.principalId === context.principalId && record.workspaceId === context.workspaceId && (scope === "all" || scope === "managed_only" && record.origin !== "user_tab" || scope === "user_tabs" && record.origin === "user_tab")
7715
+ (record2) => record2.state === "active" && record2.principalId === context.principalId && record2.workspaceId === context.workspaceId && (scope === "all" || scope === "managed_only" && record2.origin !== "user_tab" || scope === "user_tabs" && record2.origin === "user_tab")
7640
7716
  ).sort((left, right) => left.createdAt - right.createdAt || left.id.localeCompare(right.id));
7641
- return records.map((record) => ({
7642
- targetId: record.id,
7643
- profileContextId: record.profileContextId,
7644
- title: record.title,
7645
- url: record.url,
7646
- active: record.id === activeTargetId,
7647
- origin: record.origin,
7648
- ...record.managedTabSetId ? { managedTabSetId: record.managedTabSetId } : {},
7649
- controlState: this.controlState(context, leaseId, record)
7717
+ return records.map((record2) => ({
7718
+ targetId: record2.id,
7719
+ profileContextId: record2.profileContextId,
7720
+ title: record2.title,
7721
+ url: record2.url,
7722
+ active: record2.id === activeTargetId,
7723
+ origin: record2.origin,
7724
+ ...record2.managedTabSetId ? { managedTabSetId: record2.managedTabSetId } : {},
7725
+ controlState: this.controlState(context, leaseId, record2)
7650
7726
  }));
7651
7727
  }
7652
7728
  get(context, targetId) {
7653
- const record = this.records.get(targetId);
7654
- if (!record || record.state !== "active") {
7729
+ const record2 = this.records.get(targetId);
7730
+ if (!record2 || record2.state !== "active") {
7655
7731
  throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Workspace", {
7656
7732
  context: { targetId }
7657
7733
  });
7658
7734
  }
7659
- this.assertCallerOwns(context, record);
7660
- return cloneRecord2(record);
7735
+ this.assertCallerOwns(context, record2);
7736
+ return cloneRecord2(record2);
7661
7737
  }
7662
7738
  acquire(context, leaseId, targetId) {
7663
- const record = this.records.get(targetId);
7664
- if (!record || record.state !== "active") {
7739
+ const record2 = this.records.get(targetId);
7740
+ if (!record2 || record2.state !== "active") {
7665
7741
  throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Workspace", {
7666
7742
  context: { targetId }
7667
7743
  });
7668
7744
  }
7669
- this.assertCallerOwns(context, record);
7670
- const physicalKey = physicalTargetKey(record.browserInstanceId, record.cdpTargetId);
7745
+ this.assertCallerOwns(context, record2);
7746
+ const physicalKey = physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId);
7671
7747
  const controller = this.controllers.get(physicalKey);
7672
7748
  if (controller && (controller.leaseId !== leaseId || controller.principalId !== context.principalId || controller.workspaceId !== context.workspaceId)) {
7673
7749
  throw new BrowserPilotError("target_busy", "Target is controlled by another Lease", {
@@ -7682,18 +7758,18 @@ var MemoryControlledTargetRegistry = class {
7682
7758
  workspaceId: context.workspaceId,
7683
7759
  targetId
7684
7760
  });
7685
- record.controllerLeaseId = leaseId;
7686
- return { target: cloneRecord2(record), newlyAcquired };
7761
+ record2.controllerLeaseId = leaseId;
7762
+ return { target: cloneRecord2(record2), newlyAcquired };
7687
7763
  }
7688
7764
  release(context, leaseId, targetId) {
7689
- const record = this.records.get(targetId);
7690
- if (!record || record.state !== "active") return false;
7691
- this.assertCallerOwns(context, record);
7692
- const physicalKey = physicalTargetKey(record.browserInstanceId, record.cdpTargetId);
7765
+ const record2 = this.records.get(targetId);
7766
+ if (!record2 || record2.state !== "active") return false;
7767
+ this.assertCallerOwns(context, record2);
7768
+ const physicalKey = physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId);
7693
7769
  const controller = this.controllers.get(physicalKey);
7694
7770
  if (controller?.leaseId === leaseId && controller.principalId === context.principalId && controller.workspaceId === context.workspaceId && controller.targetId === targetId) {
7695
7771
  this.controllers.delete(physicalKey);
7696
- record.controllerLeaseId = void 0;
7772
+ record2.controllerLeaseId = void 0;
7697
7773
  if (this.activeByLease.get(leaseId) === targetId) this.activeByLease.delete(leaseId);
7698
7774
  return true;
7699
7775
  }
@@ -7701,128 +7777,128 @@ var MemoryControlledTargetRegistry = class {
7701
7777
  return false;
7702
7778
  }
7703
7779
  controlledByLease(leaseId) {
7704
- return [...this.records.values()].filter((record) => record.state === "active" && record.controllerLeaseId === leaseId).map(cloneRecord2);
7780
+ return [...this.records.values()].filter((record2) => record2.state === "active" && record2.controllerLeaseId === leaseId).map(cloneRecord2);
7705
7781
  }
7706
7782
  releaseLease(leaseId) {
7707
7783
  for (const [key, controller] of this.controllers) {
7708
7784
  if (controller.leaseId === leaseId) this.controllers.delete(key);
7709
7785
  }
7710
- for (const record of this.records.values()) {
7711
- if (record.controllerLeaseId === leaseId) record.controllerLeaseId = void 0;
7786
+ for (const record2 of this.records.values()) {
7787
+ if (record2.controllerLeaseId === leaseId) record2.controllerLeaseId = void 0;
7712
7788
  }
7713
7789
  this.activeByLease.delete(leaseId);
7714
7790
  }
7715
7791
  activeTarget(context, leaseId) {
7716
7792
  const targetId = this.activeByLease.get(leaseId);
7717
7793
  if (!targetId) return void 0;
7718
- const record = this.records.get(targetId);
7719
- if (!record || record.state !== "active") {
7794
+ const record2 = this.records.get(targetId);
7795
+ if (!record2 || record2.state !== "active") {
7720
7796
  this.activeByLease.delete(leaseId);
7721
7797
  return void 0;
7722
7798
  }
7723
- this.assertCallerOwns(context, record);
7724
- return cloneRecord2(record);
7799
+ this.assertCallerOwns(context, record2);
7800
+ return cloneRecord2(record2);
7725
7801
  }
7726
7802
  clearActive(context, leaseId) {
7727
7803
  const targetId = this.activeByLease.get(leaseId);
7728
7804
  if (!targetId) return;
7729
- const record = this.records.get(targetId);
7730
- if (record?.state === "active") this.assertCallerOwns(context, record);
7805
+ const record2 = this.records.get(targetId);
7806
+ if (record2?.state === "active") this.assertCallerOwns(context, record2);
7731
7807
  this.activeByLease.delete(leaseId);
7732
7808
  }
7733
7809
  releaseWorkspace(context) {
7734
7810
  const invalidated = [];
7735
- for (const record of [...this.records.values()]) {
7736
- if (record.state === "active" && record.principalId === context.principalId && record.workspaceId === context.workspaceId) {
7737
- invalidated.push(this.invalidate(record, "target_detached"));
7811
+ for (const record2 of [...this.records.values()]) {
7812
+ if (record2.state === "active" && record2.principalId === context.principalId && record2.workspaceId === context.workspaceId) {
7813
+ invalidated.push(this.invalidate(record2, "target_detached"));
7738
7814
  }
7739
7815
  }
7740
7816
  return invalidated;
7741
7817
  }
7742
7818
  invalidateBrowserConnection(browserInstanceId) {
7743
7819
  const invalidated = [];
7744
- for (const record of this.records.values()) {
7745
- if (record.state === "active" && record.browserInstanceId === browserInstanceId) {
7746
- invalidated.push(this.invalidate(record, "browser_reconnected"));
7820
+ for (const record2 of this.records.values()) {
7821
+ if (record2.state === "active" && record2.browserInstanceId === browserInstanceId) {
7822
+ invalidated.push(this.invalidate(record2, "browser_reconnected"));
7747
7823
  }
7748
7824
  }
7749
7825
  return invalidated;
7750
7826
  }
7751
7827
  setActive(context, leaseId, targetId) {
7752
- const record = this.records.get(targetId);
7753
- const controller = record ? this.controllers.get(physicalTargetKey(record.browserInstanceId, record.cdpTargetId)) : void 0;
7754
- if (!record || record.state !== "active" || controller?.leaseId !== leaseId || controller.principalId !== context.principalId || controller.workspaceId !== context.workspaceId || controller.targetId !== targetId) {
7828
+ const record2 = this.records.get(targetId);
7829
+ const controller = record2 ? this.controllers.get(physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId)) : void 0;
7830
+ if (!record2 || record2.state !== "active" || controller?.leaseId !== leaseId || controller.principalId !== context.principalId || controller.workspaceId !== context.workspaceId || controller.targetId !== targetId) {
7755
7831
  throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Lease", {
7756
7832
  context: { targetId, leaseId }
7757
7833
  });
7758
7834
  }
7759
- this.assertCallerOwns(context, record);
7835
+ this.assertCallerOwns(context, record2);
7760
7836
  this.activeByLease.set(leaseId, targetId);
7761
7837
  }
7762
7838
  managedTargets(context, managedTabSetId) {
7763
7839
  return [...this.records.values()].filter(
7764
- (record) => record.state === "active" && record.principalId === context.principalId && record.workspaceId === context.workspaceId && record.origin !== "user_tab" && record.managedTabSetId === managedTabSetId
7840
+ (record2) => record2.state === "active" && record2.principalId === context.principalId && record2.workspaceId === context.workspaceId && record2.origin !== "user_tab" && record2.managedTabSetId === managedTabSetId
7765
7841
  ).map(cloneRecord2);
7766
7842
  }
7767
7843
  activeRecords(context) {
7768
7844
  return [...this.records.values()].filter(
7769
- (record) => record.state === "active" && record.principalId === context.principalId && record.workspaceId === context.workspaceId
7845
+ (record2) => record2.state === "active" && record2.principalId === context.principalId && record2.workspaceId === context.workspaceId
7770
7846
  ).map(cloneRecord2);
7771
7847
  }
7772
7848
  isManagedCdpTarget(browserInstanceId, cdpTargetId, browserConnectionGeneration) {
7773
- for (const record of this.records.values()) {
7774
- if (record.browserInstanceId === browserInstanceId && record.cdpTargetId === cdpTargetId && record.browserConnectionGeneration === browserConnectionGeneration && record.origin !== "user_tab") return true;
7849
+ for (const record2 of this.records.values()) {
7850
+ if (record2.browserInstanceId === browserInstanceId && record2.cdpTargetId === cdpTargetId && record2.browserConnectionGeneration === browserConnectionGeneration && record2.origin !== "user_tab") return true;
7775
7851
  }
7776
7852
  return false;
7777
7853
  }
7778
7854
  markClosed(context, targetId) {
7779
- const record = this.records.get(targetId);
7780
- if (!record || record.state !== "active") {
7855
+ const record2 = this.records.get(targetId);
7856
+ if (!record2 || record2.state !== "active") {
7781
7857
  throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Workspace", {
7782
7858
  context: { targetId }
7783
7859
  });
7784
7860
  }
7785
- this.assertCallerOwns(context, record);
7861
+ this.assertCallerOwns(context, record2);
7786
7862
  const invalidated = [];
7787
7863
  for (const candidate of this.records.values()) {
7788
- if (candidate.state === "active" && candidate.browserInstanceId === record.browserInstanceId && candidate.cdpTargetId === record.cdpTargetId) {
7864
+ if (candidate.state === "active" && candidate.browserInstanceId === record2.browserInstanceId && candidate.cdpTargetId === record2.cdpTargetId) {
7789
7865
  invalidated.push(this.invalidate(candidate, "target_closed"));
7790
7866
  }
7791
7867
  }
7792
7868
  return invalidated;
7793
7869
  }
7794
- invalidate(record, reason2) {
7795
- record.state = reason2 === "target_closed" ? "closed" : "detached";
7796
- record.invalidatedBy = reason2;
7797
- record.controllerLeaseId = void 0;
7798
- const key = targetKey2(record.workspaceId, record.browserInstanceId, record.cdpTargetId);
7799
- if (this.activeKeys.get(key) === record.id) this.activeKeys.delete(key);
7800
- const physicalKey = physicalTargetKey(record.browserInstanceId, record.cdpTargetId);
7801
- if (this.controllers.get(physicalKey)?.targetId === record.id) {
7870
+ invalidate(record2, reason2) {
7871
+ record2.state = reason2 === "target_closed" ? "closed" : "detached";
7872
+ record2.invalidatedBy = reason2;
7873
+ record2.controllerLeaseId = void 0;
7874
+ const key = targetKey2(record2.workspaceId, record2.browserInstanceId, record2.cdpTargetId);
7875
+ if (this.activeKeys.get(key) === record2.id) this.activeKeys.delete(key);
7876
+ const physicalKey = physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId);
7877
+ if (this.controllers.get(physicalKey)?.targetId === record2.id) {
7802
7878
  this.controllers.delete(physicalKey);
7803
7879
  }
7804
7880
  for (const [leaseId, targetId] of this.activeByLease) {
7805
- if (targetId === record.id) this.activeByLease.delete(leaseId);
7881
+ if (targetId === record2.id) this.activeByLease.delete(leaseId);
7806
7882
  }
7807
7883
  return {
7808
- targetId: record.id,
7809
- workspaceId: record.workspaceId,
7810
- browserConnectionGeneration: record.browserConnectionGeneration,
7884
+ targetId: record2.id,
7885
+ workspaceId: record2.workspaceId,
7886
+ browserConnectionGeneration: record2.browserConnectionGeneration,
7811
7887
  reason: reason2
7812
7888
  };
7813
7889
  }
7814
- assertCallerOwns(context, record) {
7815
- if (record.principalId === context.principalId && record.workspaceId === context.workspaceId) return;
7890
+ assertCallerOwns(context, record2) {
7891
+ if (record2.principalId === context.principalId && record2.workspaceId === context.workspaceId) return;
7816
7892
  throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Workspace", {
7817
- context: { targetId: record.id }
7893
+ context: { targetId: record2.id }
7818
7894
  });
7819
7895
  }
7820
- controlState(context, leaseId, record) {
7896
+ controlState(context, leaseId, record2) {
7821
7897
  const controller = this.controllers.get(
7822
- physicalTargetKey(record.browserInstanceId, record.cdpTargetId)
7898
+ physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId)
7823
7899
  );
7824
7900
  if (!controller) return "available";
7825
- return controller.leaseId === leaseId && controller.principalId === context.principalId && controller.workspaceId === context.workspaceId && controller.targetId === record.id ? "controlled" : "busy";
7901
+ return controller.leaseId === leaseId && controller.principalId === context.principalId && controller.workspaceId === context.workspaceId && controller.targetId === record2.id ? "controlled" : "busy";
7826
7902
  }
7827
7903
  nextId() {
7828
7904
  const id = this.idFactory();
@@ -8098,17 +8174,17 @@ function parseObject(value, label) {
8098
8174
  return value;
8099
8175
  }
8100
8176
  function parseDropdown(value) {
8101
- const record = parseObject(value, "dropdown information");
8102
- if (record.ok !== true) throw invalidArgument(String(record.error || "Dropdown inspection failed"), "target");
8103
- if (!["native", "aria"].includes(String(record.kind)) || typeof record.expanded !== "boolean" || typeof record.multiple !== "boolean" || typeof record.requiresOpen !== "boolean" || !Array.isArray(record.options) || typeof record.truncated !== "boolean") throw new BrowserPilotError("internal_error", "Chrome returned invalid dropdown information");
8104
- const { ok: _ok, ...info } = record;
8177
+ const record2 = parseObject(value, "dropdown information");
8178
+ if (record2.ok !== true) throw invalidArgument(String(record2.error || "Dropdown inspection failed"), "target");
8179
+ if (!["native", "aria"].includes(String(record2.kind)) || typeof record2.expanded !== "boolean" || typeof record2.multiple !== "boolean" || typeof record2.requiresOpen !== "boolean" || !Array.isArray(record2.options) || typeof record2.truncated !== "boolean") throw new BrowserPilotError("internal_error", "Chrome returned invalid dropdown information");
8180
+ const { ok: _ok, ...info } = record2;
8105
8181
  return info;
8106
8182
  }
8107
8183
  function parseEvidence(value) {
8108
- const record = parseObject(value, "dropdown selection evidence");
8109
- if (record.ok !== true) throw invalidArgument(String(record.error || "Dropdown selection failed"), "target");
8110
- if (record.action !== "select" || !["verified", "mismatch", "unavailable"].includes(String(record.status)) || !["native", "aria"].includes(String(record.kind)) || !Array.isArray(record.selected)) throw new BrowserPilotError("internal_error", "Chrome returned invalid dropdown selection evidence");
8111
- const { ok: _ok, ...evidence } = record;
8184
+ const record2 = parseObject(value, "dropdown selection evidence");
8185
+ if (record2.ok !== true) throw invalidArgument(String(record2.error || "Dropdown selection failed"), "target");
8186
+ if (record2.action !== "select" || !["verified", "mismatch", "unavailable"].includes(String(record2.status)) || !["native", "aria"].includes(String(record2.kind)) || !Array.isArray(record2.selected)) throw new BrowserPilotError("internal_error", "Chrome returned invalid dropdown selection evidence");
8187
+ const { ok: _ok, ...evidence } = record2;
8112
8188
  return evidence;
8113
8189
  }
8114
8190
  function selectorExpression(selector, functionDeclaration, argument) {
@@ -8323,11 +8399,11 @@ var FrameService = class _FrameService {
8323
8399
 
8324
8400
  // src/services/observation-store.ts
8325
8401
  import { randomUUID as randomUUID9 } from "crypto";
8326
- function clone(record) {
8402
+ function clone(record2) {
8327
8403
  return {
8328
- ...record,
8329
- truncationReasons: [...record.truncationReasons],
8330
- refs: record.refs.map((ref) => ({ ...ref }))
8404
+ ...record2,
8405
+ truncationReasons: [...record2.truncationReasons],
8406
+ refs: record2.refs.map((ref) => ({ ...ref }))
8331
8407
  };
8332
8408
  }
8333
8409
  var MemoryObservationStore = class {
@@ -8368,7 +8444,7 @@ var MemoryObservationStore = class {
8368
8444
  throw new BrowserPilotError("internal_error", "Invalid or duplicate Observation ID");
8369
8445
  }
8370
8446
  const createdAt = this.now();
8371
- const record = {
8447
+ const record2 = {
8372
8448
  id,
8373
8449
  workspaceId: input.workspaceId,
8374
8450
  leaseId: input.leaseId,
@@ -8388,58 +8464,58 @@ var MemoryObservationStore = class {
8388
8464
  truncationReasons: [...input.truncationReasons],
8389
8465
  refs: input.refs.map((ref) => ({ ...ref }))
8390
8466
  };
8391
- this.records.set(id, record);
8392
- return clone(record);
8467
+ this.records.set(id, record2);
8468
+ return clone(record2);
8393
8469
  }
8394
8470
  resolve(input) {
8395
8471
  const now = this.now();
8396
- const record = this.records.get(input.observationId);
8397
- if (!record || record.workspaceId !== input.workspaceId || record.leaseId !== input.leaseId || record.targetId !== input.targetId) {
8472
+ const record2 = this.records.get(input.observationId);
8473
+ if (!record2 || record2.workspaceId !== input.workspaceId || record2.leaseId !== input.leaseId || record2.targetId !== input.targetId) {
8398
8474
  this.sweepAt(now);
8399
8475
  throw this.stale(input);
8400
8476
  }
8401
- if (record.invalidatedBy) {
8402
- const reason2 = record.invalidatedBy;
8477
+ if (record2.invalidatedBy) {
8478
+ const reason2 = record2.invalidatedBy;
8403
8479
  this.sweepAt(now);
8404
8480
  throw this.stale(input, reason2);
8405
8481
  }
8406
- if (record.expiresAt <= now) {
8407
- this.invalidateRecord(record, "expired");
8408
- this.records.delete(record.id);
8482
+ if (record2.expiresAt <= now) {
8483
+ this.invalidateRecord(record2, "expired");
8484
+ this.records.delete(record2.id);
8409
8485
  this.sweepAt(now);
8410
8486
  throw this.stale(input, "expired");
8411
8487
  }
8412
8488
  this.sweepAt(now);
8413
- if (record.browserProcessIdentity !== input.browserProcessIdentity || record.browserConnectionGeneration !== input.browserConnectionGeneration) {
8414
- this.invalidateRecord(record, "browser_reconnected");
8489
+ if (record2.browserProcessIdentity !== input.browserProcessIdentity || record2.browserConnectionGeneration !== input.browserConnectionGeneration) {
8490
+ this.invalidateRecord(record2, "browser_reconnected");
8415
8491
  throw this.stale(input, "browser_reconnected");
8416
8492
  }
8417
- if (record.sessionId !== input.sessionId) {
8418
- this.invalidateRecord(record, "session_replaced");
8493
+ if (record2.sessionId !== input.sessionId) {
8494
+ this.invalidateRecord(record2, "session_replaced");
8419
8495
  throw this.stale(input, "session_replaced");
8420
8496
  }
8421
- if (record.frameId !== input.frameId) {
8422
- this.invalidateRecord(record, "frame_changed");
8497
+ if (record2.frameId !== input.frameId) {
8498
+ this.invalidateRecord(record2, "frame_changed");
8423
8499
  throw this.stale(input, "frame_changed");
8424
8500
  }
8425
- if (record.loaderId !== input.loaderId) {
8426
- this.invalidateRecord(record, "loader_replaced");
8501
+ if (record2.loaderId !== input.loaderId) {
8502
+ this.invalidateRecord(record2, "loader_replaced");
8427
8503
  throw this.stale(input, "loader_replaced");
8428
8504
  }
8429
- if (record.documentGeneration !== input.documentGeneration) {
8430
- this.invalidateRecord(record, "document_replaced");
8505
+ if (record2.documentGeneration !== input.documentGeneration) {
8506
+ this.invalidateRecord(record2, "document_replaced");
8431
8507
  throw this.stale(input, "document_replaced");
8432
8508
  }
8433
- if (input.ref !== void 0 && (input.ref < 1 || input.ref > record.refs.length)) {
8509
+ if (input.ref !== void 0 && (input.ref < 1 || input.ref > record2.refs.length)) {
8434
8510
  throw this.stale(input);
8435
8511
  }
8436
- return clone(record);
8512
+ return clone(record2);
8437
8513
  }
8438
8514
  latest(workspaceId, leaseId, targetId) {
8439
8515
  this.sweep();
8440
8516
  let latest;
8441
- for (const record of this.records.values()) {
8442
- if (record.workspaceId === workspaceId && record.leaseId === leaseId && record.targetId === targetId && !record.invalidatedBy && (!latest || record.createdAt >= latest.createdAt)) latest = record;
8517
+ for (const record2 of this.records.values()) {
8518
+ if (record2.workspaceId === workspaceId && record2.leaseId === leaseId && record2.targetId === targetId && !record2.invalidatedBy && (!latest || record2.createdAt >= latest.createdAt)) latest = record2;
8443
8519
  }
8444
8520
  if (latest) return clone(latest);
8445
8521
  throw new BrowserPilotError("stale_ref", "No current Observation exists for this target", {
@@ -8447,25 +8523,25 @@ var MemoryObservationStore = class {
8447
8523
  });
8448
8524
  }
8449
8525
  invalidateTarget(targetId, reason2) {
8450
- for (const record of this.records.values()) {
8451
- if (record.targetId === targetId && !record.invalidatedBy) this.invalidateRecord(record, reason2);
8526
+ for (const record2 of this.records.values()) {
8527
+ if (record2.targetId === targetId && !record2.invalidatedBy) this.invalidateRecord(record2, reason2);
8452
8528
  }
8453
8529
  }
8454
8530
  invalidateSession(sessionId, reason2 = "session_replaced") {
8455
- for (const record of this.records.values()) {
8456
- if (record.sessionId === sessionId && !record.invalidatedBy) {
8457
- this.invalidateRecord(record, reason2);
8531
+ for (const record2 of this.records.values()) {
8532
+ if (record2.sessionId === sessionId && !record2.invalidatedBy) {
8533
+ this.invalidateRecord(record2, reason2);
8458
8534
  }
8459
8535
  }
8460
8536
  }
8461
8537
  releaseLease(leaseId) {
8462
- for (const [id, record] of this.records) {
8463
- if (record.leaseId === leaseId) this.records.delete(id);
8538
+ for (const [id, record2] of this.records) {
8539
+ if (record2.leaseId === leaseId) this.records.delete(id);
8464
8540
  }
8465
8541
  }
8466
8542
  releaseWorkspace(workspaceId) {
8467
- for (const [id, record] of this.records) {
8468
- if (record.workspaceId === workspaceId) this.records.delete(id);
8543
+ for (const [id, record2] of this.records) {
8544
+ if (record2.workspaceId === workspaceId) this.records.delete(id);
8469
8545
  }
8470
8546
  }
8471
8547
  sweep() {
@@ -8473,9 +8549,9 @@ var MemoryObservationStore = class {
8473
8549
  }
8474
8550
  sweepAt(now) {
8475
8551
  let removed = 0;
8476
- for (const [id, record] of this.records) {
8477
- if (record.expiresAt > now) continue;
8478
- this.invalidateRecord(record, "expired");
8552
+ for (const [id, record2] of this.records) {
8553
+ if (record2.expiresAt > now) continue;
8554
+ this.invalidateRecord(record2, "expired");
8479
8555
  this.records.delete(id);
8480
8556
  removed += 1;
8481
8557
  }
@@ -8484,9 +8560,9 @@ var MemoryObservationStore = class {
8484
8560
  size() {
8485
8561
  return this.records.size;
8486
8562
  }
8487
- invalidateRecord(record, reason2) {
8488
- record.invalidatedBy = reason2;
8489
- record.refs = [];
8563
+ invalidateRecord(record2, reason2) {
8564
+ record2.invalidatedBy = reason2;
8565
+ record2.refs = [];
8490
8566
  }
8491
8567
  stale(input, reason2) {
8492
8568
  return new BrowserPilotError("stale_ref", "Observation ref is stale or does not belong to this context", {
@@ -8557,22 +8633,22 @@ var PageContentService = class {
8557
8633
  if (typeof data !== "object" || data === null) {
8558
8634
  throw new BrowserPilotError("internal_error", "Chrome returned invalid page content");
8559
8635
  }
8560
- const record = data;
8561
- if (record.ok !== true) {
8636
+ const record2 = data;
8637
+ if (record2.ok !== true) {
8562
8638
  throw invalidArgument(
8563
- typeof record.error === "string" ? record.error : "Failed to read page content",
8639
+ typeof record2.error === "string" ? record2.error : "Failed to read page content",
8564
8640
  selector ? "selector" : void 0
8565
8641
  );
8566
8642
  }
8567
- if (typeof record.title !== "string" || typeof record.url !== "string" || typeof record.text !== "string" || !Number.isSafeInteger(record.length) || typeof record.truncated !== "boolean") {
8643
+ if (typeof record2.title !== "string" || typeof record2.url !== "string" || typeof record2.text !== "string" || !Number.isSafeInteger(record2.length) || typeof record2.truncated !== "boolean") {
8568
8644
  throw new BrowserPilotError("internal_error", "Chrome returned invalid page content");
8569
8645
  }
8570
8646
  return {
8571
- title: record.title,
8572
- url: record.url,
8573
- text: record.text,
8574
- length: record.length,
8575
- truncated: record.truncated
8647
+ title: record2.title,
8648
+ url: record2.url,
8649
+ text: record2.text,
8650
+ length: record2.length,
8651
+ truncated: record2.truncated
8576
8652
  };
8577
8653
  }
8578
8654
  };
@@ -8951,13 +9027,13 @@ function parseEvidence2(value) {
8951
9027
  if (!value || typeof value !== "object" || Array.isArray(value)) {
8952
9028
  throw new BrowserPilotError("internal_error", "Chrome returned invalid scroll evidence");
8953
9029
  }
8954
- const record = value;
8955
- if (record.ok !== true) throw invalidArgument(String(record.error || "Scroll failed"), "target");
9030
+ const record2 = value;
9031
+ if (record2.ok !== true) throw invalidArgument(String(record2.error || "Scroll failed"), "target");
8956
9032
  const numeric = ["deltaX", "deltaY", "beforeX", "beforeY", "afterX", "afterY"];
8957
- if (record.action !== "scroll" || !["verified", "mismatch"].includes(String(record.status)) || !["relative", "position", "text"].includes(String(record.mode)) || !["page", "element", "text"].includes(String(record.target)) || typeof record.moved !== "boolean" || numeric.some((key) => !Number.isFinite(record[key]))) {
9033
+ if (record2.action !== "scroll" || !["verified", "mismatch"].includes(String(record2.status)) || !["relative", "position", "text"].includes(String(record2.mode)) || !["page", "element", "text"].includes(String(record2.target)) || typeof record2.moved !== "boolean" || numeric.some((key) => !Number.isFinite(record2[key]))) {
8958
9034
  throw new BrowserPilotError("internal_error", "Chrome returned invalid scroll evidence");
8959
9035
  }
8960
- const { ok: _ok, ...evidence } = record;
9036
+ const { ok: _ok, ...evidence } = record2;
8961
9037
  return evidence;
8962
9038
  }
8963
9039
  function validateInput(input) {
@@ -9459,7 +9535,7 @@ var TargetInventoryService = class {
9459
9535
  }
9460
9536
  adoptManagedPopups(context, liveTargets) {
9461
9537
  const active = new Map(
9462
- this.registry.activeRecords(context).map((record) => [record.cdpTargetId, record])
9538
+ this.registry.activeRecords(context).map((record2) => [record2.cdpTargetId, record2])
9463
9539
  );
9464
9540
  let adopted = true;
9465
9541
  while (adopted) {
@@ -9468,7 +9544,7 @@ var TargetInventoryService = class {
9468
9544
  if (active.has(target.cdpTargetId) || target.type && target.type !== "page") continue;
9469
9545
  const ancestor = this.findManagedAncestor(target, active, liveTargets);
9470
9546
  if (!ancestor?.managedTabSetId || ancestor.profileContextId && target.profileContextId && ancestor.profileContextId !== target.profileContextId) continue;
9471
- const record = this.registry.registerManaged({
9547
+ const record2 = this.registry.registerManaged({
9472
9548
  principalId: context.principalId,
9473
9549
  workspaceId: context.workspaceId,
9474
9550
  browserInstanceId: this.browserInstanceId,
@@ -9481,9 +9557,9 @@ var TargetInventoryService = class {
9481
9557
  title: target.title,
9482
9558
  url: target.url
9483
9559
  });
9484
- active.set(record.cdpTargetId, record);
9485
- this.notify(this.onTargetAttached, record);
9486
- this.notify(this.onPopup, record);
9560
+ active.set(record2.cdpTargetId, record2);
9561
+ this.notify(this.onTargetAttached, record2);
9562
+ this.notify(this.onPopup, record2);
9487
9563
  adopted = true;
9488
9564
  }
9489
9565
  }
@@ -9520,8 +9596,91 @@ var TargetInventoryService = class {
9520
9596
  }
9521
9597
  };
9522
9598
 
9599
+ // src/services/chrome-profile-identity.ts
9600
+ import { open } from "fs/promises";
9601
+ import { basename as basename2, dirname as dirname2, join as join4, resolve } from "path";
9602
+ var MAX_LOCAL_STATE_BYTES = 16 * 1024 * 1024;
9603
+ var MAX_IDENTITY_TEXT_LENGTH = 4096;
9604
+ var ProfileIdentityError = class extends Error {
9605
+ constructor(code, message) {
9606
+ super(message);
9607
+ this.code = code;
9608
+ this.name = "ProfileIdentityError";
9609
+ }
9610
+ };
9611
+ function record(value) {
9612
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
9613
+ }
9614
+ function identityText(value) {
9615
+ if (typeof value !== "string") return void 0;
9616
+ const normalized = value.trim().slice(0, MAX_IDENTITY_TEXT_LENGTH);
9617
+ return normalized || void 0;
9618
+ }
9619
+ async function readVerifiedChromeProfileIdentity(userDataRoot, reportedProfilePath) {
9620
+ const root = resolve(userDataRoot);
9621
+ const profilePath = resolve(reportedProfilePath);
9622
+ if (dirname2(profilePath) !== root) {
9623
+ throw new ProfileIdentityError(
9624
+ "profile_path_unverified",
9625
+ "Chrome reported a Profile path outside the connected browser user-data root"
9626
+ );
9627
+ }
9628
+ const profileDirectory = basename2(profilePath);
9629
+ if (!profileDirectory || profileDirectory === "." || profileDirectory === "..") {
9630
+ throw new ProfileIdentityError("profile_path_unverified", "Chrome reported an invalid Profile directory");
9631
+ }
9632
+ const localStatePath = join4(root, "Local State");
9633
+ let bytes;
9634
+ let handle;
9635
+ try {
9636
+ handle = await open(localStatePath, "r");
9637
+ const metadata = await handle.stat();
9638
+ if (!metadata.isFile() || metadata.size > MAX_LOCAL_STATE_BYTES) {
9639
+ throw new ProfileIdentityError("local_state_unavailable", "Chrome Local State is missing or too large");
9640
+ }
9641
+ bytes = Buffer.alloc(metadata.size);
9642
+ let offset = 0;
9643
+ while (offset < bytes.length) {
9644
+ const result = await handle.read(bytes, offset, bytes.length - offset, offset);
9645
+ if (result.bytesRead === 0) break;
9646
+ offset += result.bytesRead;
9647
+ }
9648
+ if (offset !== bytes.length) bytes = bytes.subarray(0, offset);
9649
+ } catch (error) {
9650
+ if (error instanceof ProfileIdentityError) throw error;
9651
+ throw new ProfileIdentityError("local_state_unavailable", "Chrome Local State could not be read");
9652
+ } finally {
9653
+ await handle?.close().catch(() => {
9654
+ });
9655
+ }
9656
+ let rootState;
9657
+ try {
9658
+ rootState = record(JSON.parse(bytes.toString("utf8")));
9659
+ } catch {
9660
+ throw new ProfileIdentityError("local_state_unavailable", "Chrome Local State contains invalid JSON");
9661
+ }
9662
+ const profileState = record(rootState?.profile);
9663
+ const infoCache = record(profileState?.info_cache);
9664
+ const entry = record(infoCache?.[profileDirectory]);
9665
+ const profileName = identityText(entry?.name);
9666
+ if (!profileName) {
9667
+ throw new ProfileIdentityError(
9668
+ "profile_metadata_missing",
9669
+ "Chrome Local State has no verified metadata for the reported Profile directory"
9670
+ );
9671
+ }
9672
+ const accountName = identityText(entry?.gaia_name) ?? identityText(entry?.gaia_given_name);
9673
+ const accountEmail = identityText(entry?.user_name);
9674
+ return {
9675
+ profileName,
9676
+ profileDirectory,
9677
+ ...accountName ? { accountName } : {},
9678
+ ...accountEmail ? { accountEmail } : {}
9679
+ };
9680
+ }
9681
+
9523
9682
  // src/services/upload-service.ts
9524
- import { basename as basename2 } from "path";
9683
+ import { basename as basename3 } from "path";
9525
9684
  var FILE_INPUT_BLOCK_REASONS = /* @__PURE__ */ new Set([
9526
9685
  "detached",
9527
9686
  "disabled",
@@ -9547,17 +9706,17 @@ function parseFileInputState(value) {
9547
9706
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
9548
9707
  throw new BrowserPilotError("internal_error", "Chrome returned invalid file input state");
9549
9708
  }
9550
- const record = value;
9551
- if (record.status === "blocked" && FILE_INPUT_BLOCK_REASONS.has(record.reason)) {
9552
- return { status: "blocked", reason: record.reason };
9709
+ const record2 = value;
9710
+ if (record2.status === "blocked" && FILE_INPUT_BLOCK_REASONS.has(record2.reason)) {
9711
+ return { status: "blocked", reason: record2.reason };
9553
9712
  }
9554
- if (record.status !== "ready" || !Number.isSafeInteger(record.fileCount) || Number(record.fileCount) < 0 || record.firstFileName !== void 0 && (typeof record.firstFileName !== "string" || record.firstFileName.length > 4096)) {
9713
+ if (record2.status !== "ready" || !Number.isSafeInteger(record2.fileCount) || Number(record2.fileCount) < 0 || record2.firstFileName !== void 0 && (typeof record2.firstFileName !== "string" || record2.firstFileName.length > 4096)) {
9555
9714
  throw new BrowserPilotError("internal_error", "Chrome returned invalid file input state");
9556
9715
  }
9557
9716
  return {
9558
9717
  status: "ready",
9559
- fileCount: Number(record.fileCount),
9560
- ...record.firstFileName !== void 0 ? { firstFileName: record.firstFileName } : {}
9718
+ fileCount: Number(record2.fileCount),
9719
+ ...record2.firstFileName !== void 0 ? { firstFileName: record2.firstFileName } : {}
9561
9720
  };
9562
9721
  }
9563
9722
  var UploadService = class {
@@ -9644,7 +9803,7 @@ var UploadService = class {
9644
9803
  backendNodeId
9645
9804
  }, this.sessionId);
9646
9805
  if (this.readbackDelayMs > 0) {
9647
- await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
9806
+ await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
9648
9807
  }
9649
9808
  let after;
9650
9809
  try {
@@ -9666,7 +9825,7 @@ var UploadService = class {
9666
9825
  reason: "target_unavailable"
9667
9826
  };
9668
9827
  }
9669
- const nameMatched = after.firstFileName === basename2(filePath);
9828
+ const nameMatched = after.firstFileName === basename3(filePath);
9670
9829
  const status = after.fileCount === 1 && nameMatched ? "verified" : "mismatch";
9671
9830
  const reason2 = after.fileCount !== 1 ? "file_count_mismatch" : !nameMatched ? "file_name_mismatch" : void 0;
9672
9831
  return {
@@ -9798,39 +9957,39 @@ function publicRule(rule) {
9798
9957
  ...rule.type === "headers" ? { headers: rule.headers } : {}
9799
9958
  };
9800
9959
  }
9801
- function publicRequest(record) {
9802
- const durationMs = record.endedAt === void 0 ? void 0 : Math.max(0, record.endedAt - record.startedAt);
9960
+ function publicRequest(record2) {
9961
+ const durationMs = record2.endedAt === void 0 ? void 0 : Math.max(0, record2.endedAt - record2.startedAt);
9803
9962
  return {
9804
- requestId: record.id,
9805
- sequence: record.sequence,
9806
- method: record.method,
9807
- url: record.url,
9808
- type: record.type,
9809
- requestHeaders: record.requestHeaders,
9810
- ...record.postData !== void 0 ? { postData: record.postData } : {},
9811
- postDataTruncated: record.postDataTruncated,
9812
- ...record.status !== void 0 ? { status: record.status } : {},
9813
- ...record.statusText !== void 0 ? { statusText: record.statusText } : {},
9814
- ...record.responseHeaders !== void 0 ? { responseHeaders: record.responseHeaders } : {},
9815
- ...record.mimeType !== void 0 ? { mimeType: record.mimeType } : {},
9816
- ...record.size !== void 0 ? { size: record.size } : {},
9963
+ requestId: record2.id,
9964
+ sequence: record2.sequence,
9965
+ method: record2.method,
9966
+ url: record2.url,
9967
+ type: record2.type,
9968
+ requestHeaders: record2.requestHeaders,
9969
+ ...record2.postData !== void 0 ? { postData: record2.postData } : {},
9970
+ postDataTruncated: record2.postDataTruncated,
9971
+ ...record2.status !== void 0 ? { status: record2.status } : {},
9972
+ ...record2.statusText !== void 0 ? { statusText: record2.statusText } : {},
9973
+ ...record2.responseHeaders !== void 0 ? { responseHeaders: record2.responseHeaders } : {},
9974
+ ...record2.mimeType !== void 0 ? { mimeType: record2.mimeType } : {},
9975
+ ...record2.size !== void 0 ? { size: record2.size } : {},
9817
9976
  ...durationMs !== void 0 ? { durationMs } : {},
9818
- ...record.error !== void 0 ? { error: record.error } : {},
9819
- bodyAvailable: record.bodyAvailable
9977
+ ...record2.error !== void 0 ? { error: record2.error } : {},
9978
+ bodyAvailable: record2.bodyAvailable
9820
9979
  };
9821
9980
  }
9822
- function publicRequestSummary(record) {
9823
- const durationMs = record.endedAt === void 0 ? void 0 : Math.max(0, record.endedAt - record.startedAt);
9981
+ function publicRequestSummary(record2) {
9982
+ const durationMs = record2.endedAt === void 0 ? void 0 : Math.max(0, record2.endedAt - record2.startedAt);
9824
9983
  return {
9825
- requestId: record.id,
9826
- sequence: record.sequence,
9827
- method: record.method,
9828
- url: record.url,
9829
- type: record.type,
9830
- ...record.status !== void 0 ? { status: record.status } : {},
9831
- ...record.size !== void 0 ? { size: record.size } : {},
9984
+ requestId: record2.id,
9985
+ sequence: record2.sequence,
9986
+ method: record2.method,
9987
+ url: record2.url,
9988
+ type: record2.type,
9989
+ ...record2.status !== void 0 ? { status: record2.status } : {},
9990
+ ...record2.size !== void 0 ? { size: record2.size } : {},
9832
9991
  ...durationMs !== void 0 ? { durationMs } : {},
9833
- ...record.error !== void 0 ? { error: record.error } : {}
9992
+ ...record2.error !== void 0 ? { error: record2.error } : {}
9834
9993
  };
9835
9994
  }
9836
9995
  var WorkspaceNetworkController = class {
@@ -9992,15 +10151,15 @@ var WorkspaceNetworkController = class {
9992
10151
  };
9993
10152
  }
9994
10153
  async request(workspaceId, requestId, includeBody) {
9995
- const record = this.state(workspaceId).requestsById.get(requestId);
9996
- if (!record) throw invalidArgument("Network request was not found in this Workspace", "requestId");
9997
- const base = { request: publicRequest(record), bodyTruncated: false };
9998
- if (!includeBody || !record.bodyAvailable) return base;
10154
+ const record2 = this.state(workspaceId).requestsById.get(requestId);
10155
+ if (!record2) throw invalidArgument("Network request was not found in this Workspace", "requestId");
10156
+ const base = { request: publicRequest(record2), bodyTruncated: false };
10157
+ if (!includeBody || !record2.bodyAvailable) return base;
9999
10158
  try {
10000
10159
  const result = await this.transport.send(
10001
10160
  "Network.getResponseBody",
10002
- { requestId: record.networkId },
10003
- record.sessionId
10161
+ { requestId: record2.networkId },
10162
+ record2.sessionId
10004
10163
  );
10005
10164
  if (typeof result?.body !== "string") return base;
10006
10165
  const truncated = result.body.length > MAX_BODY_CHARACTERS;
@@ -10008,7 +10167,7 @@ var WorkspaceNetworkController = class {
10008
10167
  ...base,
10009
10168
  body: result.body.slice(0, MAX_BODY_CHARACTERS),
10010
10169
  bodyEncoding: result.base64Encoded === true ? "base64" : "utf8",
10011
- ...record.mimeType ? { mimeType: record.mimeType } : {},
10170
+ ...record2.mimeType ? { mimeType: record2.mimeType } : {},
10012
10171
  bodyTruncated: truncated
10013
10172
  };
10014
10173
  } catch {
@@ -10098,20 +10257,20 @@ var WorkspaceNetworkController = class {
10098
10257
  });
10099
10258
  this.transport.on?.("Network.loadingFinished", (params, sessionId) => {
10100
10259
  if (!sessionId) return;
10101
- const record = this.findNetworkRequest(sessionId, params?.requestId);
10102
- if (!record) return;
10103
- record.size = Math.max(0, Math.trunc(Number(params?.encodedDataLength) || 0));
10104
- record.endedAt = this.now();
10105
- record.bodyAvailable = true;
10106
- this.refreshRequestSize(record);
10260
+ const record2 = this.findNetworkRequest(sessionId, params?.requestId);
10261
+ if (!record2) return;
10262
+ record2.size = Math.max(0, Math.trunc(Number(params?.encodedDataLength) || 0));
10263
+ record2.endedAt = this.now();
10264
+ record2.bodyAvailable = true;
10265
+ this.refreshRequestSize(record2);
10107
10266
  });
10108
10267
  this.transport.on?.("Network.loadingFailed", (params, sessionId) => {
10109
10268
  if (!sessionId) return;
10110
- const record = this.findNetworkRequest(sessionId, params?.requestId);
10111
- if (!record) return;
10112
- record.error = boundedString2(params?.errorText, 4096);
10113
- record.endedAt = this.now();
10114
- this.refreshRequestSize(record);
10269
+ const record2 = this.findNetworkRequest(sessionId, params?.requestId);
10270
+ if (!record2) return;
10271
+ record2.error = boundedString2(params?.errorText, 4096);
10272
+ record2.endedAt = this.now();
10273
+ this.refreshRequestSize(record2);
10115
10274
  });
10116
10275
  }
10117
10276
  async handleAuthRequired(params, sessionId) {
@@ -10179,7 +10338,7 @@ var WorkspaceNetworkController = class {
10179
10338
  this.publishResponse(redirected);
10180
10339
  }
10181
10340
  const rawPostData = typeof params?.request?.postData === "string" ? params.request.postData : void 0;
10182
- const record = {
10341
+ const record2 = {
10183
10342
  id: this.requestIdFactory(),
10184
10343
  sequence: state.nextSequence++,
10185
10344
  workspaceId: session.workspaceId,
@@ -10198,55 +10357,55 @@ var WorkspaceNetworkController = class {
10198
10357
  bodyAvailable: false,
10199
10358
  retainedByteSize: 0
10200
10359
  };
10201
- record.retainedByteSize = Buffer.byteLength(JSON.stringify(publicRequest(record)), "utf8");
10202
- state.requests.push(record);
10203
- state.requestBytes += record.retainedByteSize;
10204
- state.requestsById.set(record.id, record);
10205
- state.requestsByNetworkKey.set(key, record);
10360
+ record2.retainedByteSize = Buffer.byteLength(JSON.stringify(publicRequest(record2)), "utf8");
10361
+ state.requests.push(record2);
10362
+ state.requestBytes += record2.retainedByteSize;
10363
+ state.requestsById.set(record2.id, record2);
10364
+ state.requestsByNetworkKey.set(key, record2);
10206
10365
  this.compactRequests(state);
10207
10366
  this.safePublish({
10208
- workspaceId: record.workspaceId,
10209
- leaseId: record.leaseId,
10210
- targetId: record.targetId,
10211
- browserConnectionGeneration: record.browserConnectionGeneration,
10367
+ workspaceId: record2.workspaceId,
10368
+ leaseId: record2.leaseId,
10369
+ targetId: record2.targetId,
10370
+ browserConnectionGeneration: record2.browserConnectionGeneration,
10212
10371
  type: "network.request",
10213
10372
  sensitivity: "browser_data",
10214
10373
  payload: {
10215
- requestId: record.id,
10216
- method: record.method,
10217
- url: sanitizedEventUrl(record.url),
10218
- type: record.type
10374
+ requestId: record2.id,
10375
+ method: record2.method,
10376
+ url: sanitizedEventUrl(record2.url),
10377
+ type: record2.type
10219
10378
  }
10220
10379
  });
10221
10380
  }
10222
10381
  trackResponse(params, sessionId) {
10223
- const record = this.findNetworkRequest(sessionId, params?.requestId);
10224
- if (!record) return;
10225
- this.applyResponse(record, params?.response);
10226
- this.refreshRequestSize(record);
10227
- this.publishResponse(record);
10228
- }
10229
- applyResponse(record, response) {
10230
- record.status = validStatus(response?.status);
10231
- record.statusText = boundedString2(response?.statusText, 4096);
10232
- record.responseHeaders = headersFromCdp(response?.headers);
10233
- record.mimeType = boundedString2(response?.mimeType, 256);
10234
- }
10235
- publishResponse(record) {
10236
- const hint = accessBlockedAgentHint(record.type, record.status);
10382
+ const record2 = this.findNetworkRequest(sessionId, params?.requestId);
10383
+ if (!record2) return;
10384
+ this.applyResponse(record2, params?.response);
10385
+ this.refreshRequestSize(record2);
10386
+ this.publishResponse(record2);
10387
+ }
10388
+ applyResponse(record2, response) {
10389
+ record2.status = validStatus(response?.status);
10390
+ record2.statusText = boundedString2(response?.statusText, 4096);
10391
+ record2.responseHeaders = headersFromCdp(response?.headers);
10392
+ record2.mimeType = boundedString2(response?.mimeType, 256);
10393
+ }
10394
+ publishResponse(record2) {
10395
+ const hint = accessBlockedAgentHint(record2.type, record2.status);
10237
10396
  this.safePublish({
10238
- workspaceId: record.workspaceId,
10239
- leaseId: record.leaseId,
10240
- targetId: record.targetId,
10241
- browserConnectionGeneration: record.browserConnectionGeneration,
10397
+ workspaceId: record2.workspaceId,
10398
+ leaseId: record2.leaseId,
10399
+ targetId: record2.targetId,
10400
+ browserConnectionGeneration: record2.browserConnectionGeneration,
10242
10401
  type: "network.response",
10243
10402
  sensitivity: "browser_data",
10244
10403
  payload: {
10245
- requestId: record.id,
10246
- method: record.method,
10247
- url: sanitizedEventUrl(record.url),
10248
- type: record.type,
10249
- ...record.status !== void 0 ? { status: record.status } : {},
10404
+ requestId: record2.id,
10405
+ method: record2.method,
10406
+ url: sanitizedEventUrl(record2.url),
10407
+ type: record2.type,
10408
+ ...record2.status !== void 0 ? { status: record2.status } : {},
10250
10409
  ...hint ? { hints: [hint] } : {}
10251
10410
  }
10252
10411
  });
@@ -10256,12 +10415,12 @@ var WorkspaceNetworkController = class {
10256
10415
  if (!session || typeof requestId !== "string") return void 0;
10257
10416
  return this.workspaces.get(session.workspaceId)?.requestsByNetworkKey.get(networkKey(sessionId, requestId));
10258
10417
  }
10259
- refreshRequestSize(record) {
10260
- const state = this.workspaces.get(record.workspaceId);
10261
- if (!state || state.requestsById.get(record.id) !== record) return;
10262
- const nextSize = Buffer.byteLength(JSON.stringify(publicRequest(record)), "utf8");
10263
- state.requestBytes += nextSize - record.retainedByteSize;
10264
- record.retainedByteSize = nextSize;
10418
+ refreshRequestSize(record2) {
10419
+ const state = this.workspaces.get(record2.workspaceId);
10420
+ if (!state || state.requestsById.get(record2.id) !== record2) return;
10421
+ const nextSize = Buffer.byteLength(JSON.stringify(publicRequest(record2)), "utf8");
10422
+ state.requestBytes += nextSize - record2.retainedByteSize;
10423
+ record2.retainedByteSize = nextSize;
10265
10424
  this.compactRequests(state);
10266
10425
  }
10267
10426
  compactRequests(state) {
@@ -10301,6 +10460,7 @@ var BASE_BROWSER_TOOL_NAMES = [
10301
10460
  "browser.discover",
10302
10461
  "browser.connect",
10303
10462
  "browser.profiles.list",
10463
+ "browser.profiles.identify",
10304
10464
  "browser.profiles.select",
10305
10465
  "browser.open",
10306
10466
  "browser.observe",
@@ -10365,20 +10525,20 @@ function stateFingerprint(value) {
10365
10525
  return createHash4("sha256").update(JSON.stringify(value) ?? "undefined").digest("hex");
10366
10526
  }
10367
10527
  function elementAddress(value) {
10368
- const record = asRecord(value);
10369
- if (typeof record.selector === "string") return { selector: record.selector };
10528
+ const record2 = asRecord(value);
10529
+ if (typeof record2.selector === "string") return { selector: record2.selector };
10370
10530
  return {
10371
- observationId: record.observationId,
10372
- ref: Number(record.ref)
10531
+ observationId: record2.observationId,
10532
+ ref: Number(record2.ref)
10373
10533
  };
10374
10534
  }
10375
10535
  function dropdownChoice(value) {
10376
- const record = asRecord(value);
10377
- if (record.by === "index") return { by: "index", index: Number(record.index) };
10378
- if (record.by === "label") {
10379
- return { by: "label", label: String(record.label), exact: record.exact !== false };
10536
+ const record2 = asRecord(value);
10537
+ if (record2.by === "index") return { by: "index", index: Number(record2.index) };
10538
+ if (record2.by === "label") {
10539
+ return { by: "label", label: String(record2.label), exact: record2.exact !== false };
10380
10540
  }
10381
- return { by: "value", value: String(record.value), exact: record.exact !== false };
10541
+ return { by: "value", value: String(record2.value), exact: record2.exact !== false };
10382
10542
  }
10383
10543
  function optionMatches(option, choice) {
10384
10544
  if (choice.by === "index") return option.index === choice.index;
@@ -10417,6 +10577,7 @@ var BrowserToolService = class {
10417
10577
  }
10418
10578
  this.loadWaiter = options.loadWaiter ?? waitForLoad;
10419
10579
  this.connectBrowser = options.connectBrowser;
10580
+ this.readProfileIdentity = options.readProfileIdentity ?? readVerifiedChromeProfileIdentity;
10420
10581
  this.watchdogs = new BrowserWatchdogService((event) => this.publishEvent(event), {
10421
10582
  dialogTimeoutMs: options.dialogTimeoutMs,
10422
10583
  noProgressThreshold: options.noProgressThreshold
@@ -10433,7 +10594,7 @@ var BrowserToolService = class {
10433
10594
  transport,
10434
10595
  binding.instance.id,
10435
10596
  () => ({
10436
- profileIdentity: binding.instance.profilePath,
10597
+ profileIdentity: binding.instance.userDataRoot,
10437
10598
  connectionGeneration: binding.instance.connectionGeneration
10438
10599
  }),
10439
10600
  {
@@ -10524,6 +10685,7 @@ var BrowserToolService = class {
10524
10685
  navigationTimeoutMs;
10525
10686
  loadWaiter;
10526
10687
  connectBrowser;
10688
+ readProfileIdentity;
10527
10689
  eventPublisher;
10528
10690
  setEventPublisher(publisher) {
10529
10691
  this.eventPublisher = publisher;
@@ -10590,11 +10752,13 @@ var BrowserToolService = class {
10590
10752
  const args = asRecord(argsValue);
10591
10753
  switch (definition.name) {
10592
10754
  case "browser.discover":
10593
- return this.discover();
10755
+ return this.discover(context);
10594
10756
  case "browser.connect":
10595
10757
  return this.connect(context, args);
10596
10758
  case "browser.profiles.list":
10597
10759
  return this.listProfiles(context);
10760
+ case "browser.profiles.identify":
10761
+ return this.identifyProfiles(context, args);
10598
10762
  case "browser.profiles.select":
10599
10763
  return this.selectProfile(context, args);
10600
10764
  case "browser.tabs.list":
@@ -10673,6 +10837,9 @@ var BrowserToolService = class {
10673
10837
  }
10674
10838
  actorKey(context, definition, argsValue) {
10675
10839
  const browserId = context.browser.instance.id;
10840
+ if (definition.name === "browser.profiles.identify") {
10841
+ return `${browserId}\0profile-identify`;
10842
+ }
10676
10843
  if (definition.name.startsWith("browser.dialogs.")) {
10677
10844
  return `${browserId}\0dialogs\0${context.workspace?.id ?? context.connection.id}`;
10678
10845
  }
@@ -10735,14 +10902,15 @@ var BrowserToolService = class {
10735
10902
  }
10736
10903
  this.deleteDialogs((dialog) => dialog.workspaceId === workspace.id);
10737
10904
  }
10738
- discover() {
10905
+ discover(context) {
10739
10906
  const candidate = this.binding.candidate;
10907
+ const userDataRoot = candidate.userDataRoot ?? candidate.profile;
10740
10908
  return asJson2({
10741
10909
  browsers: [{
10742
10910
  id: candidate.id,
10743
10911
  product: candidate.product,
10744
10912
  ...candidate.channel !== void 0 ? { channel: candidate.channel } : {},
10745
- ...candidate.profile !== void 0 ? { profile: candidate.profile } : {},
10913
+ ...userDataRoot ? context.connection.protocol.minor >= 3 ? { userDataRoot } : { profile: userDataRoot } : {},
10746
10914
  processState: candidate.processState ?? (candidate.state === "ready" ? "running" : "unknown"),
10747
10915
  remoteDebuggingState: candidate.remoteDebuggingState ?? (candidate.state === "ready" ? "enabled" : candidate.state === "disconnected" ? "stale" : "disabled"),
10748
10916
  authorizationState: candidate.authorizationState ?? (candidate.state === "ready" ? "authorized" : candidate.state === "authorization_required" ? "required" : "not_applicable"),
@@ -10789,7 +10957,7 @@ var BrowserToolService = class {
10789
10957
  return asJson2({
10790
10958
  workspaceId: inventoryContext.workspaceId,
10791
10959
  leaseId: inventoryContext.leaseId,
10792
- targets
10960
+ targets: context.connection.protocol.minor >= 3 ? targets.map(({ active, ...target }) => ({ ...target, selected: active })) : targets
10793
10961
  });
10794
10962
  }
10795
10963
  async listProfiles(context) {
@@ -10799,21 +10967,67 @@ var BrowserToolService = class {
10799
10967
  return asJson2({
10800
10968
  workspaceId: inventoryContext.workspaceId,
10801
10969
  leaseId: inventoryContext.leaseId,
10802
- profiles: profiles.map((profile) => ({
10803
- profileContextId: profile.id,
10804
- label: profile.label,
10805
- ...profile.displayName ? { displayName: profile.displayName } : {},
10806
- tabCount: profile.tabCount,
10807
- eligibleTabCount: profile.eligibleTabCount,
10808
- selected: context.workspace?.selectedProfileContextId === profile.id,
10809
- representativeTabs: targets.filter((target) => target.profileContextId === profile.id).slice(0, 3).map((target) => ({
10810
- targetId: target.targetId,
10811
- title: target.title,
10812
- url: target.url
10813
- }))
10814
- }))
10970
+ profiles: profiles.map((profile) => this.profileResult(context, profile, targets))
10815
10971
  });
10816
10972
  }
10973
+ async identifyProfiles(context, args) {
10974
+ const inventoryContext = this.inventoryContext(context);
10975
+ const targets = await this.inventory.list(inventoryContext, "all");
10976
+ const requestedId = args.profileContextId;
10977
+ const profiles = requestedId ? [this.profileContexts.resolve(requestedId, inventoryContext.browserConnectionGeneration)] : this.profileContexts.list(inventoryContext.browserConnectionGeneration);
10978
+ const refresh = args.refresh === true;
10979
+ for (const profile of profiles) {
10980
+ if (!refresh && profile.identityStatus !== "unidentified") continue;
10981
+ this.markDispatched(context);
10982
+ try {
10983
+ const identity = await this.probeProfileIdentity(profile);
10984
+ this.profileContexts.setVerifiedIdentity(
10985
+ profile.id,
10986
+ inventoryContext.browserConnectionGeneration,
10987
+ identity
10988
+ );
10989
+ } catch (error) {
10990
+ if (!(error instanceof ProfileIdentityError)) throw error;
10991
+ this.profileContexts.setIdentityUnavailable(
10992
+ profile.id,
10993
+ inventoryContext.browserConnectionGeneration,
10994
+ error.code
10995
+ );
10996
+ }
10997
+ }
10998
+ const current = this.profileContexts.list(inventoryContext.browserConnectionGeneration);
10999
+ return asJson2({
11000
+ workspaceId: inventoryContext.workspaceId,
11001
+ leaseId: inventoryContext.leaseId,
11002
+ profiles: current.filter((profile) => !requestedId || profile.id === requestedId).map((profile) => this.profileResult(context, profile, targets))
11003
+ });
11004
+ }
11005
+ profileResult(context, profile, targets) {
11006
+ const identity = {};
11007
+ if (context.connection.protocol.minor >= 3) {
11008
+ identity.identityStatus = profile.identityStatus;
11009
+ if (profile.profileName) identity.profileName = profile.profileName;
11010
+ if (profile.accountName) identity.accountName = profile.accountName;
11011
+ if (profile.accountEmail) identity.accountEmail = profile.accountEmail;
11012
+ if (profile.profileDirectory) identity.profileDirectory = profile.profileDirectory;
11013
+ if (profile.identityErrorCode) identity.identityErrorCode = profile.identityErrorCode;
11014
+ } else if (profile.displayName) {
11015
+ identity.displayName = profile.displayName;
11016
+ }
11017
+ return {
11018
+ profileContextId: profile.id,
11019
+ label: profile.label,
11020
+ ...identity,
11021
+ tabCount: profile.tabCount,
11022
+ eligibleTabCount: profile.eligibleTabCount,
11023
+ selected: context.workspace?.selectedProfileContextId === profile.id,
11024
+ representativeTabs: targets.filter((target) => target.profileContextId === profile.id).slice(0, 3).map((target) => ({
11025
+ targetId: target.targetId,
11026
+ title: target.title,
11027
+ url: target.url
11028
+ }))
11029
+ };
11030
+ }
10817
11031
  async selectProfile(context, args) {
10818
11032
  const inventoryContext = this.inventoryContext(context);
10819
11033
  await this.inventory.refresh(inventoryContext);
@@ -10829,7 +11043,14 @@ var BrowserToolService = class {
10829
11043
  leaseId: inventoryContext.leaseId,
10830
11044
  profileContextId: profile.id,
10831
11045
  label: profile.label,
10832
- ...profile.displayName ? { displayName: profile.displayName } : {}
11046
+ ...context.connection.protocol.minor >= 3 ? {
11047
+ identityStatus: profile.identityStatus,
11048
+ ...profile.profileName ? { profileName: profile.profileName } : {},
11049
+ ...profile.accountName ? { accountName: profile.accountName } : {},
11050
+ ...profile.accountEmail ? { accountEmail: profile.accountEmail } : {},
11051
+ ...profile.profileDirectory ? { profileDirectory: profile.profileDirectory } : {},
11052
+ ...profile.identityErrorCode ? { identityErrorCode: profile.identityErrorCode } : {}
11053
+ } : profile.displayName ? { displayName: profile.displayName } : {}
10833
11054
  });
10834
11055
  }
10835
11056
  async switchTab(context, args) {
@@ -10838,10 +11059,10 @@ var BrowserToolService = class {
10838
11059
  await this.inventory.refresh(inventoryContext);
10839
11060
  this.markDispatched(context);
10840
11061
  const resolved = await this.inventory.activate(inventoryContext, targetId);
10841
- const record = this.registry.get(inventoryContext, targetId);
11062
+ const record2 = this.registry.get(inventoryContext, targetId);
10842
11063
  await this.ensureSession(inventoryContext, targetId, resolved.cdpTargetId);
10843
- this.selectWorkspaceProfile(context, record.profileContextId);
10844
- return asJson2(this.targetResult(context, targetId, record.url));
11064
+ this.selectWorkspaceProfile(context, record2.profileContextId);
11065
+ return asJson2(this.targetResult(context, targetId, record2.url));
10845
11066
  }
10846
11067
  async closeTab(context) {
10847
11068
  const inventoryContext = this.inventoryContext(context);
@@ -11079,8 +11300,8 @@ var BrowserToolService = class {
11079
11300
  ...session.activeFrame ? { frameId: session.activeFrame.cdpFrameId } : {}
11080
11301
  }
11081
11302
  ).locate(args.selector);
11082
- const record = this.registry.get(this.inventoryContext(context), targetId);
11083
- return asJson2({ ...this.targetResult(context, targetId, record.url), ...location });
11303
+ const record2 = this.registry.get(this.inventoryContext(context), targetId);
11304
+ return asJson2({ ...this.targetResult(context, targetId, record2.url), ...location });
11084
11305
  }
11085
11306
  async read(context, args) {
11086
11307
  const targetId = this.requireTargetId(context);
@@ -11210,8 +11431,8 @@ var BrowserToolService = class {
11210
11431
  address,
11211
11432
  (objectId) => service.inspectObject(objectId)
11212
11433
  );
11213
- const record = this.registry.get(this.inventoryContext(context), targetId);
11214
- return asJson2({ ...this.targetResult(context, targetId, record.url), ...result });
11434
+ const record2 = this.registry.get(this.inventoryContext(context), targetId);
11435
+ return asJson2({ ...this.targetResult(context, targetId, record2.url), ...result });
11215
11436
  }
11216
11437
  async selectDropdown(context, args) {
11217
11438
  const targetId = this.requireTargetId(context);
@@ -11457,7 +11678,7 @@ var BrowserToolService = class {
11457
11678
  );
11458
11679
  }
11459
11680
  const scale = this.previewScale(media.width, media.height);
11460
- const record = this.registry.get(this.inventoryContext(context), targetId);
11681
+ const record2 = this.registry.get(this.inventoryContext(context), targetId);
11461
11682
  if (scale === void 0) {
11462
11683
  const artifact2 = await this.createArtifact(context, {
11463
11684
  kind: "screenshot",
@@ -11467,7 +11688,7 @@ var BrowserToolService = class {
11467
11688
  ...media.height !== void 0 ? { height: media.height } : {}
11468
11689
  });
11469
11690
  return asJson2({
11470
- ...this.targetResult(context, targetId, record.url),
11691
+ ...this.targetResult(context, targetId, record2.url),
11471
11692
  artifact: artifact2,
11472
11693
  ...annotationPlan ? { annotationCount: annotationPlan.annotations.length } : {}
11473
11694
  });
@@ -11489,7 +11710,7 @@ var BrowserToolService = class {
11489
11710
  ...previewMedia.height !== void 0 ? { height: previewMedia.height } : {}
11490
11711
  });
11491
11712
  return asJson2({
11492
- ...this.targetResult(context, targetId, record.url),
11713
+ ...this.targetResult(context, targetId, record2.url),
11493
11714
  artifact: artifact2,
11494
11715
  ...annotationPlan ? { annotationCount: annotationPlan.annotations.length } : {}
11495
11716
  });
@@ -11511,7 +11732,7 @@ var BrowserToolService = class {
11511
11732
  previewOf: artifact.id
11512
11733
  });
11513
11734
  return asJson2({
11514
- ...this.targetResult(context, targetId, record.url),
11735
+ ...this.targetResult(context, targetId, record2.url),
11515
11736
  artifact,
11516
11737
  preview,
11517
11738
  ...annotationPlan ? { annotationCount: annotationPlan.annotations.length } : {}
@@ -11532,15 +11753,15 @@ var BrowserToolService = class {
11532
11753
  mimeType: media.mimeType,
11533
11754
  bytes: media.bytes
11534
11755
  });
11535
- const record = this.registry.get(this.inventoryContext(context), targetId);
11536
- return asJson2({ ...this.targetResult(context, targetId, record.url), artifact });
11756
+ const record2 = this.registry.get(this.inventoryContext(context), targetId);
11757
+ return asJson2({ ...this.targetResult(context, targetId, record2.url), artifact });
11537
11758
  }
11538
11759
  async cookies(context, args) {
11539
11760
  const targetId = this.requireTargetId(context);
11540
11761
  const session = await this.resolveTargetSession(context, targetId, "cookies.read");
11541
11762
  const cookies = await new CookieService(this.transport, session.sessionId).list(args.domain);
11542
- const record = this.registry.get(this.inventoryContext(context), targetId);
11543
- return asJson2({ ...this.targetResult(context, targetId, record.url), cookies });
11763
+ const record2 = this.registry.get(this.inventoryContext(context), targetId);
11764
+ return asJson2({ ...this.targetResult(context, targetId, record2.url), cookies });
11544
11765
  }
11545
11766
  async setAuth(context, args) {
11546
11767
  const { workspace, lease } = this.requireWorkspaceContext(context);
@@ -11620,7 +11841,7 @@ var BrowserToolService = class {
11620
11841
  "expression"
11621
11842
  );
11622
11843
  }
11623
- const record = this.registry.get(this.inventoryContext(context), targetId);
11844
+ const record2 = this.registry.get(this.inventoryContext(context), targetId);
11624
11845
  let value = result?.value ?? result?.unserializableValue ?? null;
11625
11846
  let truncated = false;
11626
11847
  const serialized = JSON.stringify(value);
@@ -11628,7 +11849,7 @@ var BrowserToolService = class {
11628
11849
  value = serialized.slice(0, 1e6);
11629
11850
  truncated = true;
11630
11851
  }
11631
- return asJson2({ ...this.targetResult(context, targetId, record.url), value, truncated });
11852
+ return asJson2({ ...this.targetResult(context, targetId, record2.url), value, truncated });
11632
11853
  }
11633
11854
  listDialogs(context) {
11634
11855
  const { workspace, lease } = this.requireWorkspaceContext(context);
@@ -11736,6 +11957,89 @@ var BrowserToolService = class {
11736
11957
  throw error;
11737
11958
  }
11738
11959
  }
11960
+ async probeProfileIdentity(profile) {
11961
+ let targetId;
11962
+ let sessionId;
11963
+ let failure;
11964
+ try {
11965
+ try {
11966
+ targetId = await this.tryCreateManagedTarget({
11967
+ url: "about:blank",
11968
+ newWindow: true,
11969
+ ...profile.cdpBrowserContextId ? { browserContextId: profile.cdpBrowserContextId } : {}
11970
+ }, profile);
11971
+ } catch (error) {
11972
+ if (!(error instanceof ManagedTargetCreationRejectedError)) throw error;
11973
+ }
11974
+ if (!targetId) targetId = await this.createManagedProfileWindow(profile);
11975
+ const attached = await this.transport.send("Target.attachToTarget", {
11976
+ targetId,
11977
+ flatten: true
11978
+ });
11979
+ if (typeof attached?.sessionId !== "string") {
11980
+ throw new ProfileIdentityError(
11981
+ "profile_path_unavailable",
11982
+ "Chrome could not attach to the temporary Profile identity target"
11983
+ );
11984
+ }
11985
+ const activeSessionId = attached.sessionId;
11986
+ sessionId = activeSessionId;
11987
+ await this.transport.send("Page.enable", {}, activeSessionId);
11988
+ const navigation = await this.transport.send("Page.navigate", {
11989
+ url: "chrome://version/"
11990
+ }, activeSessionId);
11991
+ if (navigation?.errorText) {
11992
+ throw new ProfileIdentityError(
11993
+ "profile_path_unavailable",
11994
+ "Chrome could not open its version page for Profile identification"
11995
+ );
11996
+ }
11997
+ await this.loadWaiter(this.transport, activeSessionId, this.navigationTimeoutMs);
11998
+ const evaluated = await this.transport.send("Runtime.evaluate", {
11999
+ expression: `(() => {
12000
+ /* browser-pilot.profile-path.v1 */
12001
+ const value = document.querySelector('#profile_path')?.textContent;
12002
+ return typeof value === 'string' ? value.trim() : '';
12003
+ })()`,
12004
+ returnByValue: true
12005
+ }, activeSessionId);
12006
+ const reportedProfilePath = evaluated?.result?.value;
12007
+ if (typeof reportedProfilePath !== "string" || !reportedProfilePath.trim()) {
12008
+ throw new ProfileIdentityError(
12009
+ "profile_path_unavailable",
12010
+ "Chrome did not expose a Profile path on its version page"
12011
+ );
12012
+ }
12013
+ return await this.readProfileIdentity(
12014
+ this.binding.instance.userDataRoot,
12015
+ reportedProfilePath
12016
+ );
12017
+ } catch (error) {
12018
+ failure = error;
12019
+ throw error;
12020
+ } finally {
12021
+ if (sessionId) {
12022
+ await this.transport.send("Target.detachFromTarget", { sessionId }).catch(() => {
12023
+ });
12024
+ }
12025
+ if (targetId && !await this.closeTargetAndVerify(targetId)) {
12026
+ throw new BrowserPilotError(
12027
+ "unknown_outcome",
12028
+ "Temporary Profile identity target could not be safely closed",
12029
+ {
12030
+ retryable: true,
12031
+ context: { profileContextId: profile.id },
12032
+ remediation: {
12033
+ code: "inspect_profile_tabs",
12034
+ message: "List tabs and close any leftover chrome://version window before retrying.",
12035
+ actionRequired: true
12036
+ },
12037
+ ...failure !== void 0 ? { cause: failure } : {}
12038
+ }
12039
+ );
12040
+ }
12041
+ }
12042
+ }
11739
12043
  async tryCreateManagedTarget(params, profile) {
11740
12044
  let targetId;
11741
12045
  try {
@@ -11770,7 +12074,7 @@ var BrowserToolService = class {
11770
12074
  if (Array.isArray(current?.targetInfos) && !current.targetInfos.some((target) => target?.targetId === targetId)) return true;
11771
12075
  } catch {
11772
12076
  }
11773
- if (attempt < 19) await new Promise((resolve2) => setTimeout(resolve2, 50));
12077
+ if (attempt < 19) await new Promise((resolve3) => setTimeout(resolve3, 50));
11774
12078
  }
11775
12079
  return false;
11776
12080
  }
@@ -11918,7 +12222,7 @@ var BrowserToolService = class {
11918
12222
  candidateTargetId = candidate.targetId;
11919
12223
  break;
11920
12224
  }
11921
- await new Promise((resolve2) => setTimeout(resolve2, 50));
12225
+ await new Promise((resolve3) => setTimeout(resolve3, 50));
11922
12226
  }
11923
12227
  if (!candidateTargetId) {
11924
12228
  throw this.managedTargetUnknownOutcome(profile);
@@ -11957,7 +12261,7 @@ var BrowserToolService = class {
11957
12261
  if (result?.result?.value !== void 0) return false;
11958
12262
  } catch {
11959
12263
  }
11960
- if (attempt < 9) await new Promise((resolve2) => setTimeout(resolve2, 25));
12264
+ if (attempt < 9) await new Promise((resolve3) => setTimeout(resolve3, 25));
11961
12265
  }
11962
12266
  return false;
11963
12267
  } catch {
@@ -12022,8 +12326,6 @@ var BrowserToolService = class {
12022
12326
  await this.transport.send("Page.enable", {}, attached.sessionId).catch(() => {
12023
12327
  });
12024
12328
  await this.downloads?.attachSession(session);
12025
- await this.transport.send("Runtime.evaluate", { expression: INJECT_BORDER }, attached.sessionId).catch(() => {
12026
- });
12027
12329
  return session;
12028
12330
  } catch (error) {
12029
12331
  this.retireSession(key, session);
@@ -12040,7 +12342,7 @@ var BrowserToolService = class {
12040
12342
  });
12041
12343
  const snapshot = afterAction ? await service.observeAfterAction(limit) : await service.observe(limit);
12042
12344
  const identity = await this.observationContextIdentity(session);
12043
- const record = this.observations.create({
12345
+ const record2 = this.observations.create({
12044
12346
  workspaceId: context.workspace.id,
12045
12347
  leaseId: context.lease.id,
12046
12348
  targetId,
@@ -12063,7 +12365,7 @@ var BrowserToolService = class {
12063
12365
  previous && previous.frameId === frameId ? previous.guidance : void 0
12064
12366
  );
12065
12367
  this.guidanceBySession.set(session.sessionId, { frameId, guidance: snapshot.guidance });
12066
- return { snapshot, record, hints };
12368
+ return { snapshot, record: record2, hints };
12067
12369
  }
12068
12370
  async resolveObservation(context, targetId, session, observationId, ref) {
12069
12371
  const cdpSessionId = this.activeCdpSessionId(session);
@@ -12559,12 +12861,12 @@ var BrowserToolService = class {
12559
12861
  if (!this.artifactStore || !context.workspace) {
12560
12862
  throw new BrowserPilotError("internal_error", "Browser Artifact storage is unavailable");
12561
12863
  }
12562
- const record = await this.artifactStore.create({
12864
+ const record2 = await this.artifactStore.create({
12563
12865
  workspaceId: context.workspace.id,
12564
12866
  sensitivity: "browser_data",
12565
12867
  ...input
12566
12868
  });
12567
- return record.descriptor;
12869
+ return record2.descriptor;
12568
12870
  }
12569
12871
  previewScale(width, height) {
12570
12872
  if (!width || !height) return void 0;
@@ -13208,10 +13510,10 @@ import {
13208
13510
  writeFile
13209
13511
  } from "fs/promises";
13210
13512
  import { constants as fsConstants } from "fs";
13211
- import { basename as basename3, dirname as dirname2, extname, isAbsolute as isAbsolute4, join as join4, relative, resolve, sep } from "path";
13513
+ import { basename as basename4, dirname as dirname3, extname, isAbsolute as isAbsolute4, join as join5, relative, resolve as resolve2, sep } from "path";
13212
13514
  var ARTIFACT_ID_PATTERN2 = /^artifact:[A-Za-z0-9][A-Za-z0-9._:-]{0,118}$/;
13213
- function clone2(record) {
13214
- return { descriptor: { ...record.descriptor }, path: record.path };
13515
+ function clone2(record2) {
13516
+ return { descriptor: { ...record2.descriptor }, path: record2.path };
13215
13517
  }
13216
13518
  function inferredMimeType(path) {
13217
13519
  const known = {
@@ -13246,7 +13548,7 @@ var ArtifactStore = class {
13246
13548
  initialized = false;
13247
13549
  mutationTail = Promise.resolve();
13248
13550
  constructor(options = {}) {
13249
- this.directory = resolve(options.directory ?? ARTIFACT_DIR);
13551
+ this.directory = resolve2(options.directory ?? ARTIFACT_DIR);
13250
13552
  this.ttlMs = options.ttlMs ?? 15 * 6e4;
13251
13553
  this.retainedTtlMs = options.retainedTtlMs ?? 24 * 60 * 6e4;
13252
13554
  this.maxArtifactBytes = options.maxArtifactBytes ?? 100 * 1024 * 1024;
@@ -13304,15 +13606,15 @@ var ArtifactStore = class {
13304
13606
  }
13305
13607
  async export(workspaceId, artifactId, destination, overwrite = false) {
13306
13608
  if (!isAbsolute4(destination)) throw invalidArgument("Artifact export path must be absolute", "path");
13307
- const target = resolve(destination);
13609
+ const target = resolve2(destination);
13308
13610
  if (this.isInsideStore(target)) {
13309
13611
  throw invalidArgument("Artifact export path must be outside Broker storage", "path");
13310
13612
  }
13311
13613
  return this.withLock(async () => {
13312
- const record = await this.requireRecordUnlocked(workspaceId, artifactId);
13614
+ const record2 = await this.requireRecordUnlocked(workspaceId, artifactId);
13313
13615
  let canonicalParent;
13314
13616
  try {
13315
- canonicalParent = await realpath(dirname2(target));
13617
+ canonicalParent = await realpath(dirname3(target));
13316
13618
  } catch (cause) {
13317
13619
  throw new BrowserPilotError("invalid_argument", "Artifact export directory is not accessible", {
13318
13620
  context: { field: "path" },
@@ -13321,7 +13623,7 @@ var ArtifactStore = class {
13321
13623
  });
13322
13624
  }
13323
13625
  const canonicalStore = await realpath(this.directory);
13324
- if (this.isWithin(canonicalStore, join4(canonicalParent, basename3(target)))) {
13626
+ if (this.isWithin(canonicalStore, join5(canonicalParent, basename4(target)))) {
13325
13627
  throw invalidArgument("Artifact export path must be outside Broker storage", "path");
13326
13628
  }
13327
13629
  if (overwrite) {
@@ -13331,7 +13633,7 @@ var ArtifactStore = class {
13331
13633
  }
13332
13634
  }
13333
13635
  await copyFile(
13334
- record.path,
13636
+ record2.path,
13335
13637
  target,
13336
13638
  overwrite ? 0 : fsConstants.COPYFILE_EXCL
13337
13639
  ).catch((cause) => {
@@ -13343,7 +13645,7 @@ var ArtifactStore = class {
13343
13645
  });
13344
13646
  await chmod(target, 384).catch(() => {
13345
13647
  });
13346
- return { artifact: { ...record.descriptor }, path: target };
13648
+ return { artifact: { ...record2.descriptor }, path: target };
13347
13649
  });
13348
13650
  }
13349
13651
  async retain(workspaceId, artifactId) {
@@ -13357,21 +13659,21 @@ var ArtifactStore = class {
13357
13659
  }
13358
13660
  async release(workspaceId, artifactId) {
13359
13661
  await this.withLock(async () => {
13360
- const record = this.records.get(artifactId);
13361
- if (!record || record.descriptor.workspaceId !== workspaceId) return;
13662
+ const record2 = this.records.get(artifactId);
13663
+ if (!record2 || record2.descriptor.workspaceId !== workspaceId) return;
13362
13664
  this.records.delete(artifactId);
13363
13665
  this.expired.delete(artifactId);
13364
- await this.removeStoredFile(record.path);
13666
+ await this.removeStoredFile(record2.path);
13365
13667
  });
13366
13668
  }
13367
13669
  async releaseWorkspace(workspaceId) {
13368
13670
  await this.withLock(async () => {
13369
- const records = [...this.records.values()].filter((record) => record.descriptor.workspaceId === workspaceId);
13370
- for (const record of records) this.records.delete(record.descriptor.id);
13671
+ const records = [...this.records.values()].filter((record2) => record2.descriptor.workspaceId === workspaceId);
13672
+ for (const record2 of records) this.records.delete(record2.descriptor.id);
13371
13673
  for (const [artifactId, tombstone] of this.expired) {
13372
13674
  if (tombstone.workspaceId === workspaceId) this.expired.delete(artifactId);
13373
13675
  }
13374
- await Promise.all(records.map((record) => this.removeStoredFile(record.path)));
13676
+ await Promise.all(records.map((record2) => this.removeStoredFile(record2.path)));
13375
13677
  });
13376
13678
  }
13377
13679
  async sweep() {
@@ -13382,7 +13684,7 @@ var ArtifactStore = class {
13382
13684
  const records = [...this.records.values()];
13383
13685
  this.records.clear();
13384
13686
  this.expired.clear();
13385
- await Promise.all(records.map((record) => this.removeStoredFile(record.path)));
13687
+ await Promise.all(records.map((record2) => this.removeStoredFile(record2.path)));
13386
13688
  });
13387
13689
  }
13388
13690
  size() {
@@ -13399,7 +13701,7 @@ var ArtifactStore = class {
13399
13701
  }
13400
13702
  async ingestFile(input) {
13401
13703
  if (!isAbsolute4(input.sourcePath)) throw invalidArgument("Artifact import path must be absolute", "path");
13402
- const source = resolve(input.sourcePath);
13704
+ const source = resolve2(input.sourcePath);
13403
13705
  if (this.isInsideStore(source)) {
13404
13706
  throw invalidArgument("Artifact import path must be outside Broker storage", "path");
13405
13707
  }
@@ -13447,8 +13749,8 @@ var ArtifactStore = class {
13447
13749
  }
13448
13750
  if (!sourceInfo.isFile()) throw invalidArgument("Artifact import path must identify a regular file", "path");
13449
13751
  this.assertQuota(input.workspaceId, sourceInfo.size);
13450
- const fileName = input.fileName ?? basename3(source);
13451
- if (!fileName || fileName === "." || fileName === ".." || basename3(fileName) !== fileName || fileName.length > 4096 || /[\u0000-\u001f\u007f]/.test(fileName)) {
13752
+ const fileName = input.fileName ?? basename4(source);
13753
+ if (!fileName || fileName === "." || fileName === ".." || basename4(fileName) !== fileName || fileName.length > 4096 || /[\u0000-\u001f\u007f]/.test(fileName)) {
13452
13754
  throw invalidArgument("Artifact import filename is invalid or too long", "path");
13453
13755
  }
13454
13756
  const resolvedMimeType = input.mimeType ?? inferredMimeType(fileName);
@@ -13456,8 +13758,8 @@ var ArtifactStore = class {
13456
13758
  throw invalidArgument("Artifact MIME type is invalid or too long", "mimeType");
13457
13759
  }
13458
13760
  const id = this.nextArtifactId();
13459
- const storageDirectory = join4(this.directory, randomUUID12());
13460
- const destination = join4(storageDirectory, fileName);
13761
+ const storageDirectory = join5(this.directory, randomUUID12());
13762
+ const destination = join5(storageDirectory, fileName);
13461
13763
  await mkdir(storageDirectory, { mode: 448 });
13462
13764
  try {
13463
13765
  await copyFile(canonicalSource, destination, fsConstants.COPYFILE_EXCL);
@@ -13483,9 +13785,9 @@ var ArtifactStore = class {
13483
13785
  expiresAt: createdAt + this.ttlMs,
13484
13786
  retained: false
13485
13787
  };
13486
- const record = { descriptor: descriptor2, path: destination };
13487
- this.records.set(id, record);
13488
- return clone2(record);
13788
+ const record2 = { descriptor: descriptor2, path: destination };
13789
+ this.records.set(id, record2);
13790
+ return clone2(record2);
13489
13791
  } catch (error) {
13490
13792
  await rm(storageDirectory, { recursive: true, force: true }).catch(() => {
13491
13793
  });
@@ -13499,7 +13801,7 @@ var ArtifactStore = class {
13499
13801
  this.assertQuota(input.workspaceId, byteSize);
13500
13802
  await this.ensureDirectory();
13501
13803
  const id = this.nextArtifactId();
13502
- const file = join4(this.directory, `${randomUUID12()}.bin`);
13804
+ const file = join5(this.directory, `${randomUUID12()}.bin`);
13503
13805
  const createdAt = this.now();
13504
13806
  await writeFile(file, input.bytes, { mode: 384, flag: "wx" });
13505
13807
  await chmod(file, 384).catch(() => {
@@ -13519,39 +13821,39 @@ var ArtifactStore = class {
13519
13821
  retained: false,
13520
13822
  ...input.previewOf ? { previewOf: input.previewOf } : {}
13521
13823
  };
13522
- const record = { descriptor: descriptor2, path: file };
13523
- this.records.set(id, record);
13524
- return clone2(record);
13824
+ const record2 = { descriptor: descriptor2, path: file };
13825
+ this.records.set(id, record2);
13826
+ return clone2(record2);
13525
13827
  }
13526
13828
  async requireRecordUnlocked(workspaceId, artifactId) {
13527
13829
  await this.sweepUnlocked();
13528
- const record = this.records.get(artifactId);
13529
- if (!record || record.descriptor.workspaceId !== workspaceId) {
13830
+ const record2 = this.records.get(artifactId);
13831
+ if (!record2 || record2.descriptor.workspaceId !== workspaceId) {
13530
13832
  const tombstone = this.expired.get(artifactId);
13531
13833
  if (tombstone?.workspaceId === workspaceId) throw this.artifactExpired(artifactId);
13532
13834
  throw this.notFound(artifactId);
13533
13835
  }
13534
13836
  try {
13535
- const info = await stat(record.path);
13536
- if (!info.isFile() || info.size !== record.descriptor.byteSize) throw new Error("Artifact file mismatch");
13837
+ const info = await stat(record2.path);
13838
+ if (!info.isFile() || info.size !== record2.descriptor.byteSize) throw new Error("Artifact file mismatch");
13537
13839
  } catch {
13538
13840
  this.records.delete(artifactId);
13539
13841
  throw this.notFound(artifactId);
13540
13842
  }
13541
- return record;
13843
+ return record2;
13542
13844
  }
13543
13845
  async sweepUnlocked() {
13544
13846
  const now = this.now();
13545
- const expiredRecords = [...this.records.values()].filter((record) => record.descriptor.expiresAt <= now);
13546
- for (const record of expiredRecords) {
13547
- this.records.delete(record.descriptor.id);
13548
- this.expired.set(record.descriptor.id, {
13549
- workspaceId: record.descriptor.workspaceId,
13847
+ const expiredRecords = [...this.records.values()].filter((record2) => record2.descriptor.expiresAt <= now);
13848
+ for (const record2 of expiredRecords) {
13849
+ this.records.delete(record2.descriptor.id);
13850
+ this.expired.set(record2.descriptor.id, {
13851
+ workspaceId: record2.descriptor.workspaceId,
13550
13852
  expiredAt: now
13551
13853
  });
13552
13854
  }
13553
13855
  this.pruneExpiredTombstones(now);
13554
- await Promise.all(expiredRecords.map((record) => this.removeStoredFile(record.path)));
13856
+ await Promise.all(expiredRecords.map((record2) => this.removeStoredFile(record2.path)));
13555
13857
  return expiredRecords.length;
13556
13858
  }
13557
13859
  async ensureDirectory() {
@@ -13559,7 +13861,7 @@ var ArtifactStore = class {
13559
13861
  await mkdir(this.directory, { recursive: true, mode: 448 });
13560
13862
  if (this.purgeOrphansOnInitialize) {
13561
13863
  const entries = await readdir(this.directory, { withFileTypes: true });
13562
- await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".bin") || entry.isDirectory()).map((entry) => rm(join4(this.directory, entry.name), { recursive: true, force: true }).catch(() => {
13864
+ await Promise.all(entries.filter((entry) => entry.isFile() && entry.name.endsWith(".bin") || entry.isDirectory()).map((entry) => rm(join5(this.directory, entry.name), { recursive: true, force: true }).catch(() => {
13563
13865
  })));
13564
13866
  }
13565
13867
  this.initialized = true;
@@ -13598,8 +13900,8 @@ var ArtifactStore = class {
13598
13900
  context: { maxArtifactBytes: this.maxArtifactBytes, byteSize }
13599
13901
  });
13600
13902
  }
13601
- const workspaceBytes = [...this.records.values()].filter((record) => record.descriptor.workspaceId === workspaceId).reduce((sum, record) => sum + record.descriptor.byteSize, 0);
13602
- const totalBytes = [...this.records.values()].reduce((sum, record) => sum + record.descriptor.byteSize, 0);
13903
+ const workspaceBytes = [...this.records.values()].filter((record2) => record2.descriptor.workspaceId === workspaceId).reduce((sum, record2) => sum + record2.descriptor.byteSize, 0);
13904
+ const totalBytes = [...this.records.values()].reduce((sum, record2) => sum + record2.descriptor.byteSize, 0);
13603
13905
  if (workspaceBytes + byteSize > this.maxWorkspaceBytes || totalBytes + byteSize > this.maxTotalBytes) {
13604
13906
  throw new BrowserPilotError("result_too_large", "Artifact Store quota exceeded", {
13605
13907
  context: {
@@ -13613,7 +13915,7 @@ var ArtifactStore = class {
13613
13915
  async removeStoredFile(path) {
13614
13916
  await unlink(path).catch(() => {
13615
13917
  });
13616
- const parent = dirname2(path);
13918
+ const parent = dirname3(path);
13617
13919
  if (parent !== this.directory && this.isInsideStore(parent)) {
13618
13920
  await rm(parent, { recursive: true, force: true }).catch(() => {
13619
13921
  });
@@ -13827,8 +14129,8 @@ var ManagedTargetJanitorClient = class {
13827
14129
  });
13828
14130
  this.worker = worker;
13829
14131
  this.ready = false;
13830
- const ready = new Promise((resolve2, reject) => {
13831
- this.readyResolve = resolve2;
14132
+ const ready = new Promise((resolve3, reject) => {
14133
+ this.readyResolve = resolve3;
13832
14134
  this.readyReject = reject;
13833
14135
  });
13834
14136
  worker.on("message", (value) => this.handleWorkerMessage(worker, value));
@@ -13933,7 +14235,7 @@ var ManagedTargetJanitorClient = class {
13933
14235
  }
13934
14236
  const id = this.nextRequestId++;
13935
14237
  const request = { id, method, params };
13936
- return new Promise((resolve2, reject) => {
14238
+ return new Promise((resolve3, reject) => {
13937
14239
  const timer = setTimeout(() => {
13938
14240
  this.pendingRequests.delete(id);
13939
14241
  reject(method === "create" ? new BrowserPilotError(
@@ -13943,7 +14245,7 @@ var ManagedTargetJanitorClient = class {
13943
14245
  ) : new Error(`Managed browser connection timeout: ${method}`));
13944
14246
  }, REQUEST_TIMEOUT_MS);
13945
14247
  timer.unref();
13946
- this.pendingRequests.set(id, { resolve: resolve2, reject, timer, method });
14248
+ this.pendingRequests.set(id, { resolve: resolve3, reject, timer, method });
13947
14249
  worker.send?.(request, (error) => {
13948
14250
  if (!error) return;
13949
14251
  const pending = this.pendingRequests.get(id);
@@ -13973,19 +14275,19 @@ var ManagedTargetJanitorClient = class {
13973
14275
  this.expectedExits.add(worker);
13974
14276
  if (cleanup2 && worker.stdin && !worker.stdin.destroyed) worker.stdin.end();
13975
14277
  else worker.kill("SIGTERM");
13976
- await new Promise((resolve2) => {
14278
+ await new Promise((resolve3) => {
13977
14279
  if (worker.exitCode !== null || worker.signalCode !== null) {
13978
- resolve2();
14280
+ resolve3();
13979
14281
  return;
13980
14282
  }
13981
14283
  const timer = setTimeout(() => {
13982
14284
  worker.kill("SIGKILL");
13983
- resolve2();
14285
+ resolve3();
13984
14286
  }, STOP_TIMEOUT_MS);
13985
14287
  timer.unref();
13986
14288
  worker.once("exit", () => {
13987
14289
  clearTimeout(timer);
13988
- resolve2();
14290
+ resolve3();
13989
14291
  });
13990
14292
  });
13991
14293
  if (this.worker === worker) this.failWorker(worker, this.unavailableError());
@@ -14015,7 +14317,7 @@ var ManagedTargetJanitorClient = class {
14015
14317
  };
14016
14318
 
14017
14319
  // src/version.ts
14018
- var BROWSER_PILOT_VERSION = "0.3.0";
14320
+ var BROWSER_PILOT_VERSION = "0.4.0";
14019
14321
 
14020
14322
  // src/daemon.ts
14021
14323
  var CLI_EXECUTABLE_PATH = publicExecutablePath(import.meta.url);
@@ -14029,7 +14331,7 @@ var browserProfile = process.argv[4] || "";
14029
14331
  ensureBrokerDirectoriesSync();
14030
14332
  for (const legacyFile of ["state.json", "refs.json"]) {
14031
14333
  try {
14032
- unlinkSync2(join5(STATE_DIR, legacyFile));
14334
+ unlinkSync2(join6(STATE_DIR, legacyFile));
14033
14335
  } catch {
14034
14336
  }
14035
14337
  }
@@ -14049,7 +14351,7 @@ function cleanup(brokerProcessIdentity) {
14049
14351
  removeBrokerLocatorSync(brokerProcessIdentity);
14050
14352
  }
14051
14353
  function readBody(req, maxBytes = 5 * 1024 * 1024) {
14052
- return new Promise((resolve2, reject) => {
14354
+ return new Promise((resolve3, reject) => {
14053
14355
  const chunks = [];
14054
14356
  let byteLength = 0;
14055
14357
  req.on("data", (value) => {
@@ -14062,7 +14364,7 @@ function readBody(req, maxBytes = 5 * 1024 * 1024) {
14062
14364
  }
14063
14365
  chunks.push(chunk);
14064
14366
  });
14065
- req.on("end", () => resolve2(Buffer.concat(chunks).toString("utf8")));
14367
+ req.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
14066
14368
  req.on("error", reject);
14067
14369
  });
14068
14370
  }
@@ -14161,7 +14463,7 @@ async function main() {
14161
14463
  candidate: {
14162
14464
  id: customBrowserId,
14163
14465
  product: browserProduct || "Chromium",
14164
- profile: browserProfile,
14466
+ userDataRoot: browserProfile,
14165
14467
  processState: initialWsUrl ? "running" : "unknown",
14166
14468
  remoteDebuggingState: initialWsUrl ? "enabled" : "disabled",
14167
14469
  authorizationState: initialWsUrl ? "authorized" : "not_applicable",
@@ -14193,7 +14495,7 @@ async function main() {
14193
14495
  } : {
14194
14496
  id: browserId,
14195
14497
  product: browserProduct || "Unavailable browser",
14196
- profile: browserProfile,
14498
+ userDataRoot: browserProfile,
14197
14499
  processState: initialWsUrl ? "running" : "unknown",
14198
14500
  remoteDebuggingState: initialWsUrl ? "enabled" : "disabled",
14199
14501
  authorizationState: initialWsUrl ? "authorized" : "not_applicable",
@@ -14202,7 +14504,7 @@ async function main() {
14202
14504
  instance: {
14203
14505
  id: browserInstanceId,
14204
14506
  product: selectedProduct || "Unavailable browser",
14205
- profilePath: selectedProfile,
14507
+ userDataRoot: selectedProfile,
14206
14508
  processIdentity: initialWsUrl ?? "",
14207
14509
  connectionGeneration: initialWsUrl ? 1 : 0,
14208
14510
  state: initialWsUrl ? "connected" : "disconnected"
@@ -14242,7 +14544,7 @@ async function main() {
14242
14544
  instance: {
14243
14545
  id: instanceId,
14244
14546
  product: discovered.candidate.product,
14245
- profilePath: discovered.dataDir,
14547
+ userDataRoot: discovered.dataDir,
14246
14548
  processIdentity: "",
14247
14549
  connectionGeneration: 0,
14248
14550
  state: "disconnected"
@@ -14298,7 +14600,7 @@ async function main() {
14298
14600
  if (existing) return existing;
14299
14601
  const attempt = (async () => {
14300
14602
  const chrome = discoverChromeAtDataDir(
14301
- controller.binding.instance.profilePath,
14603
+ controller.binding.instance.userDataRoot,
14302
14604
  controller.binding.instance.product
14303
14605
  );
14304
14606
  if (!chrome) {
@@ -14553,7 +14855,7 @@ async function main() {
14553
14855
  id: browserBinding.candidate.id,
14554
14856
  product: selectedProduct,
14555
14857
  ...browserBinding.candidate.channel ? { channel: browserBinding.candidate.channel } : {},
14556
- profile: selectedProfile,
14858
+ userDataRoot: selectedProfile,
14557
14859
  state: browserBinding.instance.state,
14558
14860
  connectionGeneration: browserBinding.instance.connectionGeneration
14559
14861
  } } : {}
@@ -14710,13 +15012,13 @@ async function main() {
14710
15012
  });
14711
15013
  }
14712
15014
  const clients = broker.lifecycleSummary();
14713
- if (clients.embeddedConnections > 0) {
14714
- throw new BrowserPilotError("broker_in_use", "Browser Pilot has live embedded clients and cannot be stopped", {
15015
+ if (clients.embeddedConnections > 0 || clients.activeLeases > 0) {
15016
+ throw new BrowserPilotError("broker_in_use", "Browser Pilot has other live clients and cannot be stopped", {
14715
15017
  retryable: true,
14716
15018
  context: clients,
14717
15019
  remediation: {
14718
- code: "close_embedded_clients",
14719
- message: "Close the Agent products using Browser Pilot, then retry disconnect.",
15020
+ code: "close_active_clients",
15021
+ message: "Close the other Agent clients using Browser Pilot, then retry disconnect.",
14720
15022
  actionRequired: true
14721
15023
  }
14722
15024
  });