browser-pilot-cli 0.3.0 → 0.5.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
  }
@@ -291,6 +291,7 @@ var ERROR_CODES = [
291
291
  "action_not_verified",
292
292
  "command_cancelled",
293
293
  "command_expired",
294
+ "wait_timeout",
294
295
  "unknown_outcome",
295
296
  "artifact_not_found",
296
297
  "artifact_expired",
@@ -364,7 +365,8 @@ function asBrowserPilotError(error) {
364
365
  var SUPPORTED_PROTOCOL_VERSIONS = [
365
366
  { major: 1, minor: 0 },
366
367
  { major: 1, minor: 1 },
367
- { major: 1, minor: 2 }
368
+ { major: 1, minor: 2 },
369
+ { major: 1, minor: 3 }
368
370
  ];
369
371
  var CAPABILITIES = [
370
372
  "browser.discovery",
@@ -381,6 +383,12 @@ var CAPABILITIES = [
381
383
  "developer.eval"
382
384
  ];
383
385
  var DEFAULT_CAPABILITIES = CAPABILITIES;
386
+ var PROFILE_IDENTITY_ERROR_CODES = [
387
+ "profile_path_unavailable",
388
+ "profile_path_unverified",
389
+ "local_state_unavailable",
390
+ "profile_metadata_missing"
391
+ ];
384
392
  var BROWSER_OPERATIONS = [
385
393
  "tabs.list",
386
394
  "page.observe",
@@ -963,6 +971,12 @@ var profileRepresentativeTabSchema = objectSchema({
963
971
  var profileContextSchema = objectSchema({
964
972
  profileContextId: opaqueIdSchema,
965
973
  label: stringSchema({ minLength: 1, maxLength: 128 }),
974
+ identityStatus: stringSchema({ enum: ["unidentified", "verified", "unavailable"] }),
975
+ profileName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
976
+ accountName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
977
+ accountEmail: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
978
+ profileDirectory: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
979
+ identityErrorCode: stringSchema({ enum: [...PROFILE_IDENTITY_ERROR_CODES] }),
966
980
  displayName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
967
981
  tabCount: integerSchema({ minimum: 0 }),
968
982
  eligibleTabCount: integerSchema({ minimum: 0 }),
@@ -991,6 +1005,7 @@ var TOOL_DEFINITIONS = [
991
1005
  id: opaqueIdSchema,
992
1006
  product: stringSchema({ minLength: 1, maxLength: 128 }),
993
1007
  channel: stringSchema({ maxLength: 128 }),
1008
+ userDataRoot: sensitive(stringSchema({ maxLength: 4096 }), "browser_data"),
994
1009
  profile: sensitive(stringSchema({ maxLength: 4096 }), "browser_data"),
995
1010
  processState: stringSchema({ enum: ["running", "not_running", "unknown"] }),
996
1011
  remoteDebuggingState: stringSchema({ enum: ["enabled", "disabled", "stale"] }),
@@ -1051,6 +1066,25 @@ var TOOL_DEFINITIONS = [
1051
1066
  sensitivity: { input: [], output: ["browser_data"] },
1052
1067
  artifactKinds: []
1053
1068
  }),
1069
+ tool({
1070
+ name: "browser.profiles.identify",
1071
+ title: "Identify browser Profiles",
1072
+ description: "Explicitly identify live Chrome Profiles using temporary visible chrome://version targets.",
1073
+ context: "workspace",
1074
+ inputSchema: objectSchema({
1075
+ profileContextId: opaqueIdSchema,
1076
+ refresh: booleanSchema()
1077
+ }),
1078
+ outputSchema: resultSchema("workspace", {
1079
+ profiles: arraySchema(profileContextSchema, { maxItems: 128 })
1080
+ }, ["profiles"]),
1081
+ requiredCapabilities: ["browser.control"],
1082
+ mutating: true,
1083
+ idempotency: "non_idempotent",
1084
+ cancellation: "best_effort",
1085
+ sensitivity: { input: [], output: ["browser_data"] },
1086
+ artifactKinds: []
1087
+ }),
1054
1088
  tool({
1055
1089
  name: "browser.profiles.select",
1056
1090
  title: "Select browser Profile",
@@ -1060,6 +1094,12 @@ var TOOL_DEFINITIONS = [
1060
1094
  outputSchema: resultSchema("workspace", {
1061
1095
  profileContextId: opaqueIdSchema,
1062
1096
  label: stringSchema({ minLength: 1, maxLength: 128 }),
1097
+ identityStatus: stringSchema({ enum: ["unidentified", "verified", "unavailable"] }),
1098
+ profileName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
1099
+ accountName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
1100
+ accountEmail: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
1101
+ profileDirectory: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data"),
1102
+ identityErrorCode: stringSchema({ enum: [...PROFILE_IDENTITY_ERROR_CODES] }),
1063
1103
  displayName: sensitive(stringSchema({ minLength: 1, maxLength: 4096 }), "browser_data")
1064
1104
  }, ["profileContextId", "label"]),
1065
1105
  requiredCapabilities: ["browser.control"],
@@ -1488,10 +1528,11 @@ var TOOL_DEFINITIONS = [
1488
1528
  title: sensitive(stringSchema({ maxLength: 4096 }), "browser_data"),
1489
1529
  url: boundedUrl,
1490
1530
  active: booleanSchema(),
1531
+ selected: booleanSchema(),
1491
1532
  origin: stringSchema({ enum: ["managed", "managed_popup", "user_tab"] }),
1492
1533
  managedTabSetId: opaqueIdSchema,
1493
1534
  controlState: stringSchema({ enum: ["available", "controlled", "busy"] })
1494
- }, ["targetId", "title", "url", "active", "origin", "controlState"]))
1535
+ }, ["targetId", "title", "url", "origin", "controlState"]))
1495
1536
  }, ["targets"]),
1496
1537
  requiredCapabilities: ["browser.control"],
1497
1538
  mutating: false,
@@ -1503,7 +1544,7 @@ var TOOL_DEFINITIONS = [
1503
1544
  tool({
1504
1545
  name: "browser.tabs.switch",
1505
1546
  title: "Switch tab",
1506
- description: "Set a managed or user tab as the active controlled target for the current Lease.",
1547
+ description: "Select a managed or user tab as the controlled target for the current Lease.",
1507
1548
  context: "workspace",
1508
1549
  inputSchema: objectSchema({ targetId: opaqueIdSchema }, ["targetId"]),
1509
1550
  outputSchema: resultSchema("target"),
@@ -1835,8 +1876,16 @@ var PROTOCOL_1_2_TOOLS = /* @__PURE__ */ new Set([
1835
1876
  "browser.profiles.list",
1836
1877
  "browser.profiles.select"
1837
1878
  ]);
1879
+ var PROTOCOL_1_3_TOOLS = /* @__PURE__ */ new Set([
1880
+ "browser.profiles.identify"
1881
+ ]);
1882
+ function minimumProtocolMinorForTool(name) {
1883
+ if (PROTOCOL_1_3_TOOLS.has(name)) return 3;
1884
+ if (PROTOCOL_1_2_TOOLS.has(name)) return 2;
1885
+ return 0;
1886
+ }
1838
1887
  function isToolAvailableInProtocol(name, protocol) {
1839
- return protocol.major === 1 && (protocol.minor >= 2 || !PROTOCOL_1_2_TOOLS.has(name));
1888
+ return protocol.major === 1 && protocol.minor >= minimumProtocolMinorForTool(name);
1840
1889
  }
1841
1890
  function schemaSensitivities(schema, result = /* @__PURE__ */ new Set()) {
1842
1891
  for (const value of schema["x-browser-pilot-sensitivity"] ?? []) result.add(value);
@@ -1848,7 +1897,7 @@ function schemaSensitivities(schema, result = /* @__PURE__ */ new Set()) {
1848
1897
  }
1849
1898
  return result;
1850
1899
  }
1851
- function assertToolManifest(definitions = TOOL_DEFINITIONS) {
1900
+ function assertToolDefinitions(definitions = TOOL_DEFINITIONS) {
1852
1901
  const names = /* @__PURE__ */ new Set();
1853
1902
  const knownCapabilities = new Set(CAPABILITIES);
1854
1903
  for (const definition of definitions) {
@@ -1912,13 +1961,7 @@ function validateToolResult(name, value) {
1912
1961
  assertSchemaValue(definition.outputSchema, value, `${name} result`);
1913
1962
  return value;
1914
1963
  }
1915
- function getToolManifest(capabilities, availableTools, protocol) {
1916
- const granted = capabilities ? new Set(capabilities) : null;
1917
- const available = availableTools ? new Set(availableTools) : null;
1918
- const tools = TOOL_DEFINITIONS.filter((definition) => (!granted || definition.requiredCapabilities.every((capability) => granted.has(capability))) && (!available || available.has(definition.name)) && (!protocol || isToolAvailableInProtocol(definition.name, protocol)));
1919
- return { schemaVersion: 1, tools };
1920
- }
1921
- assertToolManifest();
1964
+ assertToolDefinitions();
1922
1965
 
1923
1966
  // src/protocol/validation.ts
1924
1967
  var CLIENT_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{2,127}$/;
@@ -1930,14 +1973,29 @@ var TARGET_ID_PATTERN = /^target:[A-Za-z0-9._:-]+$/;
1930
1973
  var ARTIFACT_ID_PATTERN = /^artifact:[A-Za-z0-9._:-]+$/;
1931
1974
  var COMMAND_ID_PATTERN = /^command:[A-Za-z0-9._:-]+$/;
1932
1975
  var EVENT_CURSOR_PATTERN = /^cursor:(0|[1-9][0-9]{0,15})$/;
1976
+ var COMMAND_STATUSES = /* @__PURE__ */ new Set([
1977
+ "accepted",
1978
+ "dispatched",
1979
+ "completed",
1980
+ "unknown_outcome",
1981
+ "cancelled",
1982
+ "expired"
1983
+ ]);
1984
+ var ARTIFACT_KINDS = /* @__PURE__ */ new Set([
1985
+ "screenshot",
1986
+ "screenshot_preview",
1987
+ "pdf",
1988
+ "download",
1989
+ "upload_input"
1990
+ ]);
1933
1991
  var MIN_NEGOTIATED_TRANSPORT_BYTES = 64 * 1024;
1934
1992
  var MAX_DECLARED_TRANSPORT_BYTES = 1024 * 1024 * 1024;
1935
1993
  function isRecord2(value) {
1936
1994
  return typeof value === "object" && value !== null && !Array.isArray(value);
1937
1995
  }
1938
- function assertOnlyKeys(record, allowed, field = "params") {
1996
+ function assertOnlyKeys(record2, allowed, field = "params") {
1939
1997
  const allowedSet = new Set(allowed);
1940
- const unknown = Object.keys(record).filter((key) => !allowedSet.has(key));
1998
+ const unknown = Object.keys(record2).filter((key) => !allowedSet.has(key));
1941
1999
  if (unknown.length > 0) {
1942
2000
  throw invalidArgument(`Unknown ${field} field: ${unknown[0]}`, `${field}.${unknown[0]}`, field === "message" ? -32600 : -32602);
1943
2001
  }
@@ -1950,8 +2008,8 @@ function isJsonValue(value) {
1950
2008
  if (isRecord2(value)) return Object.values(value).every(isJsonValue);
1951
2009
  return false;
1952
2010
  }
1953
- function requireString(record, key, options = {}) {
1954
- const value = record[key];
2011
+ function requireString(record2, key, options = {}) {
2012
+ const value = record2[key];
1955
2013
  const maxLength = options.maxLength ?? MAX_ID_LENGTH;
1956
2014
  if (typeof value !== "string" || value.length === 0 || value.length > maxLength) {
1957
2015
  throw invalidArgument(`${key} must be a non-empty string no longer than ${maxLength} characters`, key);
@@ -1961,8 +2019,8 @@ function requireString(record, key, options = {}) {
1961
2019
  }
1962
2020
  return value;
1963
2021
  }
1964
- function requireInteger(record, key) {
1965
- const value = record[key];
2022
+ function requireInteger(record2, key) {
2023
+ const value = record2[key];
1966
2024
  if (!Number.isSafeInteger(value) || value < 0) {
1967
2025
  throw invalidArgument(`${key} must be a non-negative safe integer`, key);
1968
2026
  }
@@ -2055,17 +2113,13 @@ function parseTransportLimitPreferences(value) {
2055
2113
  }
2056
2114
  function validateInitializeParams(value) {
2057
2115
  if (!isRecord2(value)) throw invalidArgument("initialize params must be an object", "params");
2058
- assertOnlyKeys(value, ["client", "protocol", "requestedCapabilities", "launchMode", "limits"]);
2116
+ assertOnlyKeys(value, ["client", "protocol", "requestedCapabilities", "limits"]);
2059
2117
  if (!isRecord2(value.client)) throw invalidArgument("client must be an object", "client");
2060
2118
  assertOnlyKeys(value.client, ["id", "name", "version", "instanceId"], "client");
2061
2119
  const requested = value.requestedCapabilities;
2062
2120
  if (!Array.isArray(requested) || requested.some((item) => typeof item !== "string" || item.length === 0 || item.length > 128)) {
2063
2121
  throw invalidArgument("requestedCapabilities must be an array of non-empty strings", "requestedCapabilities");
2064
2122
  }
2065
- const launchMode = value.launchMode;
2066
- if (launchMode !== "one-shot" && launchMode !== "embedded") {
2067
- throw invalidArgument("launchMode must be one-shot or embedded", "launchMode");
2068
- }
2069
2123
  return {
2070
2124
  client: {
2071
2125
  id: requireString(value.client, "id", { pattern: CLIENT_ID_PATTERN, maxLength: 128 }),
@@ -2075,7 +2129,6 @@ function validateInitializeParams(value) {
2075
2129
  },
2076
2130
  protocol: parseProtocolRange(value.protocol),
2077
2131
  requestedCapabilities: [...new Set(requested)],
2078
- launchMode,
2079
2132
  ...value.limits !== void 0 ? { limits: parseTransportLimitPreferences(value.limits) } : {}
2080
2133
  };
2081
2134
  }
@@ -2085,18 +2138,15 @@ function validateOptionalEmptyParams(value) {
2085
2138
  assertOnlyKeys(value, []);
2086
2139
  return {};
2087
2140
  }
2088
- function validateTtlMs(record) {
2089
- if (record.ttlMs === void 0) return void 0;
2090
- if (!Number.isSafeInteger(record.ttlMs)) {
2141
+ function validateTtlMs(record2) {
2142
+ if (record2.ttlMs === void 0) return void 0;
2143
+ if (!Number.isSafeInteger(record2.ttlMs)) {
2091
2144
  throw invalidArgument("ttlMs must be a safe integer", "ttlMs");
2092
2145
  }
2093
- return record.ttlMs;
2094
- }
2095
- function validateOpaqueId(record, key, pattern) {
2096
- return requireString(record, key, { pattern, maxLength: 128 });
2146
+ return record2.ttlMs;
2097
2147
  }
2098
- function validateToolsListParams(value) {
2099
- return validateOptionalEmptyParams(value);
2148
+ function validateOpaqueId(record2, key, pattern) {
2149
+ return requireString(record2, key, { pattern, maxLength: 128 });
2100
2150
  }
2101
2151
  function validateShutdownParams(value) {
2102
2152
  return validateOptionalEmptyParams(value);
@@ -2153,6 +2203,25 @@ function validateCommandAccessParams(value) {
2153
2203
  } : {}
2154
2204
  };
2155
2205
  }
2206
+ function validateCommandListParams(value) {
2207
+ if (!isRecord2(value)) throw invalidArgument("Command list params must be an object", "params");
2208
+ assertOnlyKeys(value, ["workspaceId", "limit", "statuses"]);
2209
+ if (value.limit !== void 0 && (!Number.isSafeInteger(value.limit) || Number(value.limit) < 1 || Number(value.limit) > 100)) {
2210
+ throw invalidArgument("limit must be an integer from 1 through 100", "limit");
2211
+ }
2212
+ if (value.statuses !== void 0 && (!Array.isArray(value.statuses) || value.statuses.length === 0 || value.statuses.some((status) => typeof status !== "string" || !COMMAND_STATUSES.has(status)))) {
2213
+ throw invalidArgument("statuses must contain valid Command statuses", "statuses");
2214
+ }
2215
+ return {
2216
+ workspaceId: validateOpaqueId(
2217
+ value,
2218
+ "workspaceId",
2219
+ WORKSPACE_ID_PATTERN
2220
+ ),
2221
+ ...value.limit !== void 0 ? { limit: Number(value.limit) } : {},
2222
+ ...value.statuses !== void 0 ? { statuses: value.statuses } : {}
2223
+ };
2224
+ }
2156
2225
  function validateEventsPollParams(value) {
2157
2226
  if (!isRecord2(value)) throw invalidArgument("events/poll params must be an object", "params");
2158
2227
  assertOnlyKeys(value, ["workspaceId", "cursor", "limit"]);
@@ -2178,6 +2247,18 @@ function validateArtifactAccessParams(value) {
2178
2247
  artifactId: validateOpaqueId(value, "artifactId", ARTIFACT_ID_PATTERN)
2179
2248
  };
2180
2249
  }
2250
+ function validateArtifactListParams(value) {
2251
+ if (!isRecord2(value)) throw invalidArgument("Artifact list params must be an object", "params");
2252
+ assertOnlyKeys(value, ["workspaceId", "leaseId", "kinds"]);
2253
+ if (value.kinds !== void 0 && (!Array.isArray(value.kinds) || value.kinds.length === 0 || value.kinds.some((kind) => typeof kind !== "string" || !ARTIFACT_KINDS.has(kind)))) {
2254
+ throw invalidArgument("kinds must contain valid Artifact kinds", "kinds");
2255
+ }
2256
+ return {
2257
+ workspaceId: validateOpaqueId(value, "workspaceId", WORKSPACE_ID_PATTERN),
2258
+ leaseId: validateOpaqueId(value, "leaseId", LEASE_ID_PATTERN),
2259
+ ...value.kinds !== void 0 ? { kinds: value.kinds } : {}
2260
+ };
2261
+ }
2181
2262
  function validateArtifactExportParams(value) {
2182
2263
  if (!isRecord2(value)) throw invalidArgument("artifacts/export params must be an object", "params");
2183
2264
  assertOnlyKeys(value, ["workspaceId", "leaseId", "artifactId", "path", "overwrite"]);
@@ -2262,22 +2343,22 @@ function canonical(value) {
2262
2343
  function cloneJson(value) {
2263
2344
  return structuredClone(value);
2264
2345
  }
2265
- function descriptor(record) {
2346
+ function descriptor(record2) {
2266
2347
  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 } : {}
2348
+ id: record2.id,
2349
+ ...record2.workspaceId ? { workspaceId: record2.workspaceId } : {},
2350
+ ...record2.leaseId ? { leaseId: record2.leaseId } : {},
2351
+ ...record2.targetId ? { targetId: record2.targetId } : {},
2352
+ ...record2.browserConnectionGeneration !== void 0 ? { browserConnectionGeneration: record2.browserConnectionGeneration } : {},
2353
+ idempotencyKey: record2.idempotencyKey,
2354
+ method: record2.method,
2355
+ mutating: record2.mutating,
2356
+ status: record2.status,
2357
+ acceptedAt: record2.acceptedAt,
2358
+ deadlineAt: record2.deadlineAt,
2359
+ ...record2.dispatchedAt !== void 0 ? { dispatchedAt: record2.dispatchedAt } : {},
2360
+ ...record2.completedAt !== void 0 ? { completedAt: record2.completedAt } : {},
2361
+ ...record2.cancellationRequested ? { cancellationRequested: true } : {}
2281
2362
  };
2282
2363
  }
2283
2364
  var MemoryCommandRuntime = class {
@@ -2344,13 +2425,13 @@ var MemoryCommandRuntime = class {
2344
2425
  throw new BrowserPilotError("internal_error", "Invalid or duplicate Command ID");
2345
2426
  }
2346
2427
  const acceptedAt = this.now();
2347
- let resolve2;
2428
+ let resolve3;
2348
2429
  let reject;
2349
2430
  const completion = new Promise((resolvePromise, rejectPromise) => {
2350
- resolve2 = resolvePromise;
2431
+ resolve3 = resolvePromise;
2351
2432
  reject = rejectPromise;
2352
2433
  });
2353
- const record = {
2434
+ const record2 = {
2354
2435
  id,
2355
2436
  ...input.workspaceId ? { workspaceId: input.workspaceId } : {},
2356
2437
  ...input.leaseId ? { leaseId: input.leaseId } : {},
@@ -2368,17 +2449,17 @@ var MemoryCommandRuntime = class {
2368
2449
  scopeKey,
2369
2450
  cancellation: input.cancellation,
2370
2451
  abortController: new AbortController(),
2371
- resolve: resolve2,
2452
+ resolve: resolve3,
2372
2453
  reject,
2373
2454
  completion,
2374
2455
  settled: false
2375
2456
  };
2376
- this.records.set(id, record);
2457
+ this.records.set(id, record2);
2377
2458
  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));
2459
+ record2.deadlineTimer = setTimeout(() => this.expire(record2), deadlineMs);
2460
+ record2.deadlineTimer.unref();
2461
+ this.emitStatus(record2);
2462
+ this.enqueue(input.actorKey, () => this.execute(record2, execute));
2382
2463
  return completion;
2383
2464
  }
2384
2465
  subscribe(listener) {
@@ -2389,51 +2470,56 @@ var MemoryCommandRuntime = class {
2389
2470
  this.sweep();
2390
2471
  return this.outcome(this.requireOwned(context));
2391
2472
  }
2473
+ list(context) {
2474
+ this.sweep();
2475
+ const statuses = context.statuses ? new Set(context.statuses) : void 0;
2476
+ return [...this.records.values()].filter((record2) => record2.principalId === context.principalId && record2.workspaceId === context.workspaceId && (!statuses || statuses.has(record2.status))).sort((left, right) => right.acceptedAt - left.acceptedAt).slice(0, context.limit ?? 20).map(descriptor);
2477
+ }
2392
2478
  cancel(context) {
2393
2479
  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") {
2480
+ const record2 = this.requireOwned(context);
2481
+ if (TERMINAL_STATUSES.has(record2.status)) return this.outcome(record2);
2482
+ if (record2.cancellation === "not_applicable") {
2397
2483
  throw invalidArgument("Command does not support cancellation", "commandId");
2398
2484
  }
2399
- if (record.status === "accepted") {
2400
- record.abortController.abort();
2485
+ if (record2.status === "accepted") {
2486
+ record2.abortController.abort();
2401
2487
  this.finishWithError(
2402
- record,
2488
+ record2,
2403
2489
  "cancelled",
2404
2490
  new BrowserPilotError("command_cancelled", "Command was cancelled before dispatch")
2405
2491
  );
2406
- return this.outcome(record);
2492
+ return this.outcome(record2);
2407
2493
  }
2408
- record.cancellationRequested = true;
2409
- this.emitStatus(record);
2410
- if (record.cancellation === "best_effort") record.abortController.abort();
2411
- return this.outcome(record);
2494
+ record2.cancellationRequested = true;
2495
+ this.emitStatus(record2);
2496
+ if (record2.cancellation === "best_effort") record2.abortController.abort();
2497
+ return this.outcome(record2);
2412
2498
  }
2413
2499
  releaseLease(leaseId) {
2414
- for (const record of this.records.values()) {
2415
- if (record.leaseId === leaseId) this.cancelForCleanup(record);
2500
+ for (const record2 of this.records.values()) {
2501
+ if (record2.leaseId === leaseId) this.cancelForCleanup(record2);
2416
2502
  }
2417
2503
  }
2418
2504
  releaseConnection(connectionId) {
2419
- for (const record of this.records.values()) {
2420
- if (record.connectionId === connectionId) this.cancelForCleanup(record);
2505
+ for (const record2 of this.records.values()) {
2506
+ if (record2.connectionId === connectionId) this.cancelForCleanup(record2);
2421
2507
  }
2422
2508
  }
2423
2509
  releaseWorkspace(workspaceId) {
2424
- for (const record of this.records.values()) {
2425
- if (record.workspaceId === workspaceId) this.cancelForCleanup(record);
2510
+ for (const record2 of this.records.values()) {
2511
+ if (record2.workspaceId === workspaceId) this.cancelForCleanup(record2);
2426
2512
  }
2427
2513
  }
2428
2514
  sweep() {
2429
2515
  const now = this.now();
2430
2516
  let removed = 0;
2431
- for (const record of this.records.values()) {
2432
- if (!TERMINAL_STATUSES.has(record.status) && record.deadlineAt <= now) this.expire(record);
2517
+ for (const record2 of this.records.values()) {
2518
+ if (!TERMINAL_STATUSES.has(record2.status) && record2.deadlineAt <= now) this.expire(record2);
2433
2519
  }
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);
2520
+ for (const [id, record2] of this.records) {
2521
+ if (TERMINAL_STATUSES.has(record2.status) && (record2.completedAt ?? record2.deadlineAt) + this.terminalTtlMs <= now) {
2522
+ this.deleteRecord(id, record2);
2437
2523
  removed += 1;
2438
2524
  }
2439
2525
  }
@@ -2442,140 +2528,140 @@ var MemoryCommandRuntime = class {
2442
2528
  size() {
2443
2529
  return this.records.size;
2444
2530
  }
2445
- async execute(record, execute) {
2446
- if (record.status !== "accepted") return;
2447
- if (record.deadlineAt <= this.now()) {
2448
- this.expire(record);
2531
+ async execute(record2, execute) {
2532
+ if (record2.status !== "accepted") return;
2533
+ if (record2.deadlineAt <= this.now()) {
2534
+ this.expire(record2);
2449
2535
  return;
2450
2536
  }
2451
2537
  try {
2452
2538
  const markDispatched = () => {
2453
- if (record.status === "cancelled") {
2539
+ if (record2.status === "cancelled") {
2454
2540
  throw new BrowserPilotError("command_cancelled", "Command was cancelled before dispatch", {
2455
- context: { commandId: record.id }
2541
+ context: { commandId: record2.id }
2456
2542
  });
2457
2543
  }
2458
- if (record.status === "expired") {
2544
+ if (record2.status === "expired") {
2459
2545
  throw new BrowserPilotError("command_expired", "Command expired before dispatch", {
2460
- context: { commandId: record.id }
2546
+ context: { commandId: record2.id }
2461
2547
  });
2462
2548
  }
2463
- if (record.status !== "accepted") return;
2464
- record.status = "dispatched";
2465
- record.dispatchedAt = this.now();
2466
- this.emitStatus(record);
2549
+ if (record2.status !== "accepted") return;
2550
+ record2.status = "dispatched";
2551
+ record2.dispatchedAt = this.now();
2552
+ this.emitStatus(record2);
2467
2553
  };
2468
- const result = await execute({ signal: record.abortController.signal, markDispatched });
2469
- if (TERMINAL_STATUSES.has(record.status)) return;
2554
+ const result = await execute({ signal: record2.abortController.signal, markDispatched });
2555
+ if (TERMINAL_STATUSES.has(record2.status)) return;
2470
2556
  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);
2557
+ record2.status = "completed";
2558
+ record2.completedAt = this.now();
2559
+ record2.result = cloneJson(result);
2560
+ this.clearDeadline(record2);
2561
+ this.emitStatus(record2);
2562
+ this.resolve(record2);
2477
2563
  } 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")) {
2564
+ if (TERMINAL_STATUSES.has(record2.status)) return;
2565
+ const stable = this.withCommandContext(error, record2);
2566
+ if (record2.mutating && record2.dispatchedAt !== void 0 && (stable.code === "browser_disconnected" || stable.code === "internal_error" || stable.code === "unknown_outcome")) {
2481
2567
  if (stable.code === "unknown_outcome") {
2482
- this.finishWithError(record, "unknown_outcome", stable);
2568
+ this.finishWithError(record2, "unknown_outcome", stable);
2483
2569
  return;
2484
2570
  }
2485
2571
  this.finishWithError(
2486
- record,
2572
+ record2,
2487
2573
  "unknown_outcome",
2488
2574
  new BrowserPilotError("unknown_outcome", "Command outcome is unknown after dispatch", {
2489
- context: { commandId: record.id },
2575
+ context: { commandId: record2.id },
2490
2576
  cause: stable
2491
2577
  })
2492
2578
  );
2493
2579
  } else {
2494
- this.finishWithError(record, "completed", stable);
2580
+ this.finishWithError(record2, "completed", stable);
2495
2581
  }
2496
2582
  }
2497
2583
  }
2498
- expire(record) {
2499
- if (TERMINAL_STATUSES.has(record.status)) return;
2500
- record.abortController.abort();
2501
- if (record.status === "dispatched" && record.mutating) {
2584
+ expire(record2) {
2585
+ if (TERMINAL_STATUSES.has(record2.status)) return;
2586
+ record2.abortController.abort();
2587
+ if (record2.status === "dispatched" && record2.mutating) {
2502
2588
  this.finishWithError(
2503
- record,
2589
+ record2,
2504
2590
  "unknown_outcome",
2505
2591
  new BrowserPilotError("unknown_outcome", "Command deadline elapsed after dispatch", {
2506
- context: { commandId: record.id }
2592
+ context: { commandId: record2.id }
2507
2593
  })
2508
2594
  );
2509
2595
  return;
2510
2596
  }
2511
2597
  this.finishWithError(
2512
- record,
2598
+ record2,
2513
2599
  "expired",
2514
2600
  new BrowserPilotError("command_expired", "Command deadline elapsed before a result was known", {
2515
- context: { commandId: record.id }
2601
+ context: { commandId: record2.id }
2516
2602
  })
2517
2603
  );
2518
2604
  }
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();
2605
+ finishWithError(record2, status, error) {
2606
+ if (TERMINAL_STATUSES.has(record2.status)) return;
2607
+ const stable = this.withCommandContext(error, record2);
2608
+ record2.status = status;
2609
+ record2.completedAt = this.now();
2610
+ record2.error = stable.toJsonRpcError();
2611
+ this.clearDeadline(record2);
2612
+ this.emitStatus(record2);
2613
+ if (!record2.settled) {
2614
+ record2.settled = true;
2615
+ record2.reject(stable);
2616
+ }
2617
+ }
2618
+ resolve(record2) {
2619
+ if (record2.settled) return;
2620
+ record2.settled = true;
2621
+ record2.resolve(this.outcome(record2));
2622
+ }
2623
+ cancelForCleanup(record2) {
2624
+ if (TERMINAL_STATUSES.has(record2.status)) return;
2625
+ if (record2.status === "accepted") {
2626
+ record2.abortController.abort();
2541
2627
  this.finishWithError(
2542
- record,
2628
+ record2,
2543
2629
  "cancelled",
2544
2630
  new BrowserPilotError("command_cancelled", "Command was cancelled during resource cleanup")
2545
2631
  );
2546
2632
  return;
2547
2633
  }
2548
- record.cancellationRequested = true;
2549
- this.emitStatus(record);
2550
- record.abortController.abort();
2634
+ record2.cancellationRequested = true;
2635
+ this.emitStatus(record2);
2636
+ record2.abortController.abort();
2551
2637
  }
2552
2638
  requireOwned(context) {
2553
- const record = this.records.get(context.commandId);
2554
- if (!record || record.principalId !== context.principalId || record.workspaceId !== context.workspaceId) {
2639
+ const record2 = this.records.get(context.commandId);
2640
+ if (!record2 || record2.principalId !== context.principalId || record2.workspaceId !== context.workspaceId) {
2555
2641
  throw invalidArgument("Command was not found for this ClientPrincipal and Workspace", "commandId");
2556
2642
  }
2557
- return record;
2643
+ return record2;
2558
2644
  }
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) {
2645
+ assertDuplicate(record2, input, fingerprint, idempotencyKey) {
2646
+ if (record2.principalId !== input.principalId || record2.workspaceId !== input.workspaceId || record2.method !== input.method || record2.fingerprint !== fingerprint || record2.idempotencyKey !== idempotencyKey) {
2561
2647
  throw invalidArgument("Command or idempotency key was reused for a different request", "idempotencyKey");
2562
2648
  }
2563
2649
  }
2564
- outcome(record) {
2650
+ outcome(record2) {
2565
2651
  return {
2566
- command: descriptor(record),
2567
- ...record.result !== void 0 ? { result: cloneJson(record.result) } : {},
2568
- ...record.error ? { error: cloneJson(record.error) } : {}
2652
+ command: descriptor(record2),
2653
+ ...record2.result !== void 0 ? { result: cloneJson(record2.result) } : {},
2654
+ ...record2.error ? { error: cloneJson(record2.error) } : {}
2569
2655
  };
2570
2656
  }
2571
2657
  scopeKey(principalId, connectionId, workspaceId) {
2572
2658
  return workspaceId ? `${principalId}\0${workspaceId}` : `${principalId}\0${connectionId}`;
2573
2659
  }
2574
- withCommandContext(error, record) {
2660
+ withCommandContext(error, record2) {
2575
2661
  const stable = asBrowserPilotError(error);
2576
2662
  return new BrowserPilotError(stable.code, stable.message, {
2577
2663
  retryable: stable.retryable,
2578
- context: { ...stable.context ?? {}, commandId: record.id },
2664
+ context: { ...stable.context ?? {}, commandId: record2.id },
2579
2665
  ...stable.remediation ? { remediation: stable.remediation } : {},
2580
2666
  rpcCode: stable.rpcCode,
2581
2667
  cause: error
@@ -2595,9 +2681,9 @@ var MemoryCommandRuntime = class {
2595
2681
  }
2596
2682
  ensureCapacity() {
2597
2683
  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);
2684
+ const terminal = [...this.records.values()].filter((record2) => TERMINAL_STATUSES.has(record2.status)).sort((left, right) => (left.completedAt ?? left.deadlineAt) - (right.completedAt ?? right.deadlineAt));
2685
+ for (const record2 of terminal) {
2686
+ this.deleteRecord(record2.id, record2);
2601
2687
  if (this.records.size < this.maxCommands) return;
2602
2688
  }
2603
2689
  throw new BrowserPilotError("result_too_large", "Command store capacity is exhausted", {
@@ -2605,19 +2691,19 @@ var MemoryCommandRuntime = class {
2605
2691
  context: { maxCommands: this.maxCommands }
2606
2692
  });
2607
2693
  }
2608
- deleteRecord(id, record) {
2609
- this.clearDeadline(record);
2694
+ deleteRecord(id, record2) {
2695
+ this.clearDeadline(record2);
2610
2696
  this.records.delete(id);
2611
- const index = `${record.scopeKey}\0${record.idempotencyKey}`;
2697
+ const index = `${record2.scopeKey}\0${record2.idempotencyKey}`;
2612
2698
  if (this.commandsByIdempotency.get(index) === id) this.commandsByIdempotency.delete(index);
2613
2699
  }
2614
- clearDeadline(record) {
2615
- if (!record.deadlineTimer) return;
2616
- clearTimeout(record.deadlineTimer);
2617
- record.deadlineTimer = void 0;
2700
+ clearDeadline(record2) {
2701
+ if (!record2.deadlineTimer) return;
2702
+ clearTimeout(record2.deadlineTimer);
2703
+ record2.deadlineTimer = void 0;
2618
2704
  }
2619
- emitStatus(record) {
2620
- const outcome = this.outcome(record);
2705
+ emitStatus(record2) {
2706
+ const outcome = this.outcome(record2);
2621
2707
  for (const listener of this.statusListeners) {
2622
2708
  try {
2623
2709
  listener(outcome);
@@ -2779,13 +2865,21 @@ var DEFAULT_PROTOCOL_LIMITS = {
2779
2865
  function normalizeBrowserCandidate(candidate) {
2780
2866
  const ready = candidate.state === "ready";
2781
2867
  const authorizationRequired = candidate.state === "authorization_required";
2868
+ const userDataRoot = candidate.userDataRoot ?? candidate.profile;
2782
2869
  return {
2783
2870
  ...candidate,
2871
+ ...userDataRoot ? { userDataRoot, profile: userDataRoot } : {},
2784
2872
  processState: candidate.processState ?? (ready || authorizationRequired ? "running" : "unknown"),
2785
2873
  remoteDebuggingState: candidate.remoteDebuggingState ?? (ready || authorizationRequired ? "enabled" : candidate.state === "disconnected" ? "stale" : "disabled"),
2786
2874
  authorizationState: candidate.authorizationState ?? (ready ? "authorized" : authorizationRequired ? "required" : candidate.state === "disconnected" ? "unknown" : "not_applicable")
2787
2875
  };
2788
2876
  }
2877
+ function browserCandidateForProtocol(candidate, protocolMinor) {
2878
+ const { profile, userDataRoot, ...rest } = candidate;
2879
+ const root = userDataRoot ?? profile;
2880
+ if (!root) return rest;
2881
+ return protocolMinor >= 3 ? { ...rest, userDataRoot: root } : { ...rest, profile: root };
2882
+ }
2789
2883
  function asJson(value) {
2790
2884
  return value;
2791
2885
  }
@@ -2795,8 +2889,8 @@ function cloneWorkspace(value) {
2795
2889
  function cloneManagedTabSet(value) {
2796
2890
  return { ...value };
2797
2891
  }
2798
- function runtimeManagedTabSets(record) {
2799
- return [record.managedTabSet, ...record.additionalManagedTabSets.values()];
2892
+ function runtimeManagedTabSets(record2) {
2893
+ return [record2.managedTabSet, ...record2.additionalManagedTabSets.values()];
2800
2894
  }
2801
2895
  function cloneLease(value) {
2802
2896
  return { ...value, capabilities: [...value.capabilities] };
@@ -2843,7 +2937,6 @@ var MemoryBrokerRuntime = class {
2843
2937
  preserveIfGenerationStale: true
2844
2938
  });
2845
2939
  }));
2846
- this.listenerCleanup.push(this.events.subscribe((event) => this.deliverNotification(event)));
2847
2940
  options.toolExecutor?.setEventPublisher?.((event) => {
2848
2941
  this.publishBrowserEvent(event);
2849
2942
  });
@@ -2880,7 +2973,7 @@ var MemoryBrokerRuntime = class {
2880
2973
  limits;
2881
2974
  principals = /* @__PURE__ */ new Map();
2882
2975
  principalIdsByKey = /* @__PURE__ */ new Map();
2883
- connectionsByBridge = /* @__PURE__ */ new Map();
2976
+ connectionsByClientSession = /* @__PURE__ */ new Map();
2884
2977
  connectionsById = /* @__PURE__ */ new Map();
2885
2978
  workspaces = /* @__PURE__ */ new Map();
2886
2979
  leases = /* @__PURE__ */ new Map();
@@ -2903,30 +2996,27 @@ var MemoryBrokerRuntime = class {
2903
2996
  events;
2904
2997
  listenerCleanup = [];
2905
2998
  workspaceCleanup = /* @__PURE__ */ new Map();
2906
- async call(bridgeSessionId, method, params) {
2907
- this.assertBridgeSessionId(bridgeSessionId);
2999
+ async call(clientSessionId, method, params) {
3000
+ this.assertClientSessionId(clientSessionId);
2908
3001
  this.sweepExpiredLeases();
2909
- if (method === "initialize") return this.initialize(bridgeSessionId, params);
2910
- const connection = this.requireConnection(bridgeSessionId);
3002
+ if (method === "initialize") return this.initialize(clientSessionId, params);
3003
+ const connection = this.requireConnection(clientSessionId);
2911
3004
  connection.value.lastSeenAt = this.now();
2912
3005
  switch (method) {
2913
- case "tools/list":
2914
- validateToolsListParams(params);
2915
- return asJson(getToolManifest(
2916
- connection.grantedCapabilities,
2917
- this.options.toolExecutor?.supportedTools,
2918
- connection.value.protocol
2919
- ));
2920
3006
  case "tools/call":
2921
3007
  return this.callTool(connection, params);
2922
3008
  case "commands/get":
2923
3009
  return this.getCommand(connection, params);
3010
+ case "commands/list":
3011
+ return this.listCommands(connection, params);
2924
3012
  case "commands/cancel":
2925
3013
  return this.cancelCommand(connection, params);
2926
3014
  case "events/poll":
2927
3015
  return this.pollEvents(connection, params);
2928
3016
  case "artifacts/get":
2929
3017
  return this.getArtifact(connection, params);
3018
+ case "artifacts/list":
3019
+ return this.listArtifacts(connection, params);
2930
3020
  case "artifacts/export":
2931
3021
  return this.exportArtifact(connection, params);
2932
3022
  case "artifacts/import":
@@ -2957,12 +3047,11 @@ var MemoryBrokerRuntime = class {
2957
3047
  });
2958
3048
  }
2959
3049
  }
2960
- disconnect(bridgeSessionId) {
2961
- const connection = this.connectionsByBridge.get(bridgeSessionId);
3050
+ disconnect(clientSessionId) {
3051
+ const connection = this.connectionsByClientSession.get(clientSessionId);
2962
3052
  if (!connection) return;
2963
3053
  this.commands.releaseConnection(connection.value.id);
2964
- this.resolveNotificationWaiter(connection, void 0);
2965
- this.connectionsByBridge.delete(bridgeSessionId);
3054
+ this.connectionsByClientSession.delete(clientSessionId);
2966
3055
  this.connectionsById.delete(connection.value.id);
2967
3056
  for (const [leaseId, lease] of this.leases) {
2968
3057
  if (lease.connectionId !== connection.value.id) continue;
@@ -2972,40 +3061,11 @@ var MemoryBrokerRuntime = class {
2972
3061
  this.cleanupUnusedPrincipal(connection.value.principalId);
2973
3062
  }
2974
3063
  close() {
2975
- for (const bridgeSessionId of [...this.connectionsByBridge.keys()]) {
2976
- this.disconnect(bridgeSessionId);
3064
+ for (const clientSessionId of [...this.connectionsByClientSession.keys()]) {
3065
+ this.disconnect(clientSessionId);
2977
3066
  }
2978
3067
  for (const cleanup2 of this.listenerCleanup.splice(0)) cleanup2();
2979
3068
  }
2980
- async nextNotification(bridgeSessionId, options = {}) {
2981
- const connection = this.requireConnection(bridgeSessionId);
2982
- const waitMs = options.waitMs ?? 25e3;
2983
- if (!Number.isSafeInteger(waitMs) || waitMs < 0 || waitMs > 3e4) {
2984
- throw invalidArgument("Notification waitMs must be from 0 through 30000", "waitMs");
2985
- }
2986
- const queued = connection.notifications.shift();
2987
- if (queued || waitMs === 0 || options.signal?.aborted) return queued;
2988
- if (connection.notificationWaiter) {
2989
- throw invalidArgument("Only one notification poll may be active per bridge Connection");
2990
- }
2991
- return new Promise((resolve2) => {
2992
- const waiter = { resolve: resolve2, signal: options.signal };
2993
- const finish = () => this.resolveNotificationWaiter(connection, void 0);
2994
- waiter.timer = setTimeout(finish, waitMs);
2995
- waiter.timer.unref();
2996
- if (options.signal) {
2997
- waiter.onAbort = finish;
2998
- options.signal.addEventListener("abort", finish, { once: true });
2999
- }
3000
- connection.notificationWaiter = waiter;
3001
- });
3002
- }
3003
- async *notifications(bridgeSessionId, signal) {
3004
- while (!signal.aborted) {
3005
- const notification = await this.nextNotification(bridgeSessionId, { waitMs: 3e4, signal });
3006
- if (notification) yield notification;
3007
- }
3008
- }
3009
3069
  publishBrowserEvent(input) {
3010
3070
  const workspace = this.workspaces.get(input.workspaceId);
3011
3071
  if (!workspace || workspace.value.state !== "active") return void 0;
@@ -3103,18 +3163,18 @@ var MemoryBrokerRuntime = class {
3103
3163
  expired += 1;
3104
3164
  }
3105
3165
  }
3106
- for (const connection of [...this.connectionsByBridge.values()]) {
3166
+ for (const connection of [...this.connectionsByClientSession.values()]) {
3107
3167
  if (connection.value.lastSeenAt + this.connectionIdleTtlMs <= now) {
3108
- this.disconnect(connection.bridgeSessionId);
3168
+ this.disconnect(connection.clientSessionId);
3109
3169
  }
3110
3170
  }
3111
3171
  const leasedWorkspaceIds = /* @__PURE__ */ new Set();
3112
3172
  for (const lease of this.leases.values()) {
3113
3173
  if (lease.state === "active") leasedWorkspaceIds.add(lease.workspaceId);
3114
3174
  }
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);
3175
+ for (const record2 of this.workspaces.values()) {
3176
+ if (record2.value.state === "active" && record2.value.updatedAt + this.workspaceIdleTtlMs <= now && !leasedWorkspaceIds.has(record2.value.id)) {
3177
+ this.releaseWorkspaceRecord(record2);
3118
3178
  }
3119
3179
  }
3120
3180
  return expired;
@@ -3122,32 +3182,25 @@ var MemoryBrokerRuntime = class {
3122
3182
  stats() {
3123
3183
  return {
3124
3184
  principals: this.principals.size,
3125
- connections: this.connectionsByBridge.size,
3126
- activeWorkspaces: [...this.workspaces.values()].filter((record) => record.value.state === "active").length,
3185
+ connections: this.connectionsByClientSession.size,
3186
+ activeWorkspaces: [...this.workspaces.values()].filter((record2) => record2.value.state === "active").length,
3127
3187
  activeLeases: [...this.leases.values()].filter((lease) => lease.state === "active").length
3128
3188
  };
3129
3189
  }
3130
3190
  lifecycleSummary() {
3131
- let embeddedConnections = 0;
3132
- let oneShotConnections = 0;
3133
- for (const connection of this.connectionsByBridge.values()) {
3134
- if (connection.value.launchMode === "embedded") embeddedConnections += 1;
3135
- else oneShotConnections += 1;
3136
- }
3137
3191
  const stats = this.stats();
3138
3192
  return {
3139
- embeddedConnections,
3140
- oneShotConnections,
3193
+ connections: stats.connections,
3141
3194
  activeWorkspaces: stats.activeWorkspaces,
3142
3195
  activeLeases: stats.activeLeases
3143
3196
  };
3144
3197
  }
3145
- initialize(bridgeSessionId, value) {
3198
+ initialize(clientSessionId, value) {
3146
3199
  const params = validateInitializeParams(value);
3147
- const existing = this.connectionsByBridge.get(bridgeSessionId);
3200
+ const existing = this.connectionsByClientSession.get(clientSessionId);
3148
3201
  if (existing) {
3149
- if (params.launchMode !== "one-shot" || existing.value.launchMode !== "one-shot" || existing.client.id !== params.client.id || existing.client.name !== params.client.name || existing.client.version !== params.client.version || existing.client.instanceId !== params.client.instanceId) {
3150
- throw invalidArgument("This bridge connection is already initialized", "method");
3202
+ if (existing.client.id !== params.client.id || existing.client.name !== params.client.name || existing.client.version !== params.client.version || existing.client.instanceId !== params.client.instanceId) {
3203
+ throw invalidArgument("This client session is already initialized", "method");
3151
3204
  }
3152
3205
  const protocol2 = negotiateProtocol(params.protocol);
3153
3206
  if (params.limits && (protocol2.major < 1 || protocol2.major === 1 && protocol2.minor < 1)) {
@@ -3158,10 +3211,10 @@ var MemoryBrokerRuntime = class {
3158
3211
  const capabilities2 = negotiateCapabilities(params.requestedCapabilities, this.allowedCapabilities);
3159
3212
  const limits2 = negotiateProtocolLimits(params.limits, this.limits);
3160
3213
  if (protocol2.major !== existing.value.protocol.major || protocol2.minor !== existing.value.protocol.minor || capabilities2.granted.length !== existing.grantedCapabilities.length || capabilities2.granted.some((capability, index) => capability !== existing.grantedCapabilities[index]) || limits2.maxMessageBytes !== existing.limits.maxMessageBytes || limits2.maxResultBytes !== existing.limits.maxResultBytes || limits2.maxArtifactBytes !== existing.limits.maxArtifactBytes || limits2.eventJournalSize !== existing.limits.eventJournalSize) {
3161
- throw new BrowserPilotError("protocol_incompatible", "One-shot connection cannot change its negotiated contract", {
3214
+ throw new BrowserPilotError("protocol_incompatible", "Client session cannot change its negotiated contract", {
3162
3215
  remediation: {
3163
3216
  code: "restart_browser_pilot",
3164
- message: "Restart Browser Pilot before using a different one-shot protocol contract.",
3217
+ message: "Restart Browser Pilot before using a different client protocol contract.",
3165
3218
  actionRequired: true
3166
3219
  }
3167
3220
  });
@@ -3175,11 +3228,11 @@ var MemoryBrokerRuntime = class {
3175
3228
  capabilities: capabilities2,
3176
3229
  brokerProcessIdentity: this.options.brokerProcessIdentity,
3177
3230
  connectionId: existing.value.id,
3178
- browsers: this.browserBindings.map((binding) => ({ ...binding.candidate })),
3231
+ browsers: this.browserBindings.map((binding) => browserCandidateForProtocol(binding.candidate, existing.value.protocol.minor)),
3179
3232
  limits: { ...existing.limits }
3180
3233
  });
3181
3234
  }
3182
- if (this.connectionsByBridge.size >= this.maxConnections) {
3235
+ if (this.connectionsByClientSession.size >= this.maxConnections) {
3183
3236
  throw new BrowserPilotError("result_too_large", "Broker connection limit reached", {
3184
3237
  context: { maxConnections: this.maxConnections }
3185
3238
  });
@@ -3199,7 +3252,6 @@ var MemoryBrokerRuntime = class {
3199
3252
  id: connectionId,
3200
3253
  principalId: principal.id,
3201
3254
  clientInstanceId: params.client.instanceId,
3202
- launchMode: params.launchMode,
3203
3255
  protocol,
3204
3256
  connectedAt: now,
3205
3257
  lastSeenAt: now
@@ -3212,18 +3264,17 @@ var MemoryBrokerRuntime = class {
3212
3264
  capabilities,
3213
3265
  brokerProcessIdentity: this.options.brokerProcessIdentity,
3214
3266
  connectionId,
3215
- browsers: this.browserBindings.map((binding) => ({ ...binding.candidate })),
3267
+ browsers: this.browserBindings.map((binding) => browserCandidateForProtocol(binding.candidate, protocol.minor)),
3216
3268
  limits: { ...limits }
3217
3269
  };
3218
3270
  const connection = {
3219
3271
  value: connectionValue,
3220
- bridgeSessionId,
3272
+ clientSessionId,
3221
3273
  client: { ...params.client },
3222
3274
  grantedCapabilities: [...capabilities.granted],
3223
- limits,
3224
- notifications: []
3275
+ limits
3225
3276
  };
3226
- this.connectionsByBridge.set(bridgeSessionId, connection);
3277
+ this.connectionsByClientSession.set(clientSessionId, connection);
3227
3278
  this.connectionsById.set(connectionId, connection);
3228
3279
  return asJson(initializeResult);
3229
3280
  }
@@ -3234,7 +3285,7 @@ var MemoryBrokerRuntime = class {
3234
3285
  throw protocolIncompatible("Keyed Workspace creation requires protocol 1.1 or newer");
3235
3286
  }
3236
3287
  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;
3288
+ 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
3289
  if (existing) {
3239
3290
  if (params.browserId) {
3240
3291
  const requested = this.browserBindings.find((candidate) => candidate.candidate.id === params.browserId);
@@ -3266,7 +3317,7 @@ var MemoryBrokerRuntime = class {
3266
3317
  }
3267
3318
  });
3268
3319
  }
3269
- const activeCount = [...this.workspaces.values()].filter((record) => record.value.principalId === principalId && record.value.state === "active").length;
3320
+ const activeCount = [...this.workspaces.values()].filter((record2) => record2.value.principalId === principalId && record2.value.state === "active").length;
3270
3321
  if (activeCount >= this.maxWorkspacesPerPrincipal) {
3271
3322
  throw new BrowserPilotError("result_too_large", "Workspace limit reached", {
3272
3323
  context: { maxWorkspacesPerPrincipal: this.maxWorkspacesPerPrincipal }
@@ -3313,20 +3364,20 @@ var MemoryBrokerRuntime = class {
3313
3364
  getWorkspace(connection, value) {
3314
3365
  this.assertCapabilities(connection, ["workspace.manage"]);
3315
3366
  const params = validateWorkspaceGetParams(value);
3316
- const record = this.requireWorkspace(connection, params.workspaceId, true);
3367
+ const record2 = this.requireWorkspace(connection, params.workspaceId, true);
3317
3368
  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)
3369
+ workspace: cloneWorkspace(record2.value),
3370
+ managedTabSet: cloneManagedTabSet(record2.managedTabSet),
3371
+ managedTabSets: runtimeManagedTabSets(record2).map(cloneManagedTabSet),
3372
+ eventCursor: this.events.currentCursor(record2.value.id)
3322
3373
  });
3323
3374
  }
3324
3375
  async releaseWorkspace(connection, value) {
3325
3376
  this.assertCapabilities(connection, ["workspace.manage"]);
3326
3377
  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);
3378
+ const record2 = this.requireWorkspace(connection, params.workspaceId, true);
3379
+ if (record2.value.state !== "released") this.releaseWorkspaceRecord(record2);
3380
+ await this.workspaceCleanup.get(record2.value.id);
3330
3381
  return asJson({ workspaceId: params.workspaceId, released: true });
3331
3382
  }
3332
3383
  createLease(connection, value) {
@@ -3415,60 +3466,60 @@ var MemoryBrokerRuntime = class {
3415
3466
  this.principals.set(id, principal);
3416
3467
  return principal;
3417
3468
  }
3418
- requireConnection(bridgeSessionId) {
3419
- const connection = this.connectionsByBridge.get(bridgeSessionId);
3469
+ requireConnection(clientSessionId) {
3470
+ const connection = this.connectionsByClientSession.get(clientSessionId);
3420
3471
  if (connection) return connection;
3421
- throw new BrowserPilotError("not_initialized", "Bridge connection is not initialized", {
3472
+ throw new BrowserPilotError("not_initialized", "Client session is not initialized", {
3422
3473
  rpcCode: -32002
3423
3474
  });
3424
3475
  }
3425
3476
  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") {
3477
+ const record2 = this.workspaces.get(workspaceId);
3478
+ if (!record2 || record2.value.principalId !== connection.value.principalId || !allowReleased && record2.value.state !== "active") {
3428
3479
  throw new BrowserPilotError("workspace_not_found", "Workspace was not found for this ClientPrincipal", {
3429
3480
  context: { workspaceId }
3430
3481
  });
3431
3482
  }
3432
- return record;
3483
+ return record2;
3433
3484
  }
3434
- selectWorkspaceProfileContext(record, profileContextId) {
3435
- if (record.value.state !== "active") {
3485
+ selectWorkspaceProfileContext(record2, profileContextId) {
3486
+ if (record2.value.state !== "active") {
3436
3487
  throw new BrowserPilotError("workspace_not_found", "Workspace is not active", {
3437
- context: { workspaceId: record.value.id }
3488
+ context: { workspaceId: record2.value.id }
3438
3489
  });
3439
3490
  }
3440
- record.value.selectedProfileContextId = profileContextId;
3441
- record.value.updatedAt = this.now();
3491
+ record2.value.selectedProfileContextId = profileContextId;
3492
+ record2.value.updatedAt = this.now();
3442
3493
  }
3443
- managedTabSetForProfile(record, profileContextId) {
3444
- if (record.value.state !== "active") {
3494
+ managedTabSetForProfile(record2, profileContextId) {
3495
+ if (record2.value.state !== "active") {
3445
3496
  throw new BrowserPilotError("workspace_not_found", "Workspace is not active", {
3446
- context: { workspaceId: record.value.id }
3497
+ context: { workspaceId: record2.value.id }
3447
3498
  });
3448
3499
  }
3449
- if (record.managedTabSet.profileContextId === profileContextId) {
3450
- return cloneManagedTabSet(record.managedTabSet);
3500
+ if (record2.managedTabSet.profileContextId === profileContextId) {
3501
+ return cloneManagedTabSet(record2.managedTabSet);
3451
3502
  }
3452
- if (!record.managedTabSet.profileContextId) {
3453
- record.managedTabSet.profileContextId = profileContextId;
3454
- return cloneManagedTabSet(record.managedTabSet);
3503
+ if (!record2.managedTabSet.profileContextId) {
3504
+ record2.managedTabSet.profileContextId = profileContextId;
3505
+ return cloneManagedTabSet(record2.managedTabSet);
3455
3506
  }
3456
- const existing = record.additionalManagedTabSets.get(profileContextId);
3507
+ const existing = record2.additionalManagedTabSets.get(profileContextId);
3457
3508
  if (existing) return cloneManagedTabSet(existing);
3458
- if (record.additionalManagedTabSets.size >= 127) {
3509
+ if (record2.additionalManagedTabSets.size >= 127) {
3459
3510
  throw new BrowserPilotError("result_too_large", "ManagedTabSet Profile context limit reached", {
3460
- context: { workspaceId: record.value.id, maxManagedTabSets: 128 }
3511
+ context: { workspaceId: record2.value.id, maxManagedTabSets: 128 }
3461
3512
  });
3462
3513
  }
3463
3514
  const managedTabSet = {
3464
3515
  id: this.nextManagedTabSetId(),
3465
- workspaceId: record.value.id,
3466
- browserInstanceId: record.value.browserInstanceId,
3516
+ workspaceId: record2.value.id,
3517
+ browserInstanceId: record2.value.browserInstanceId,
3467
3518
  profileContextId,
3468
3519
  createdAt: this.now(),
3469
3520
  state: "active"
3470
3521
  };
3471
- record.additionalManagedTabSets.set(profileContextId, managedTabSet);
3522
+ record2.additionalManagedTabSets.set(profileContextId, managedTabSet);
3472
3523
  return cloneManagedTabSet(managedTabSet);
3473
3524
  }
3474
3525
  requireLease(connection, leaseId, requireActive) {
@@ -3500,52 +3551,52 @@ var MemoryBrokerRuntime = class {
3500
3551
  this.options.toolExecutor?.releaseLease?.(cloneLease(lease));
3501
3552
  this.options.onLeaseReleased?.(cloneLease(lease));
3502
3553
  }
3503
- releaseWorkspaceRecord(record) {
3504
- record.value.state = "releasing";
3505
- this.commands.releaseWorkspace(record.value.id);
3554
+ releaseWorkspaceRecord(record2) {
3555
+ record2.value.state = "releasing";
3556
+ this.commands.releaseWorkspace(record2.value.id);
3506
3557
  for (const lease of this.leases.values()) {
3507
- if (lease.workspaceId === record.value.id && lease.state === "active") {
3558
+ if (lease.workspaceId === record2.value.id && lease.state === "active") {
3508
3559
  this.releaseLeaseRecord(lease, "released");
3509
3560
  }
3510
3561
  }
3511
- const managedTabSets = runtimeManagedTabSets(record);
3562
+ const managedTabSets = runtimeManagedTabSets(record2);
3512
3563
  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);
3564
+ record2.value.updatedAt = this.now();
3565
+ record2.value.state = "released";
3566
+ const principal = this.principals.get(record2.value.principalId);
3516
3567
  if (principal) {
3517
3568
  this.options.toolExecutor?.releaseWorkspace?.(
3518
3569
  { ...principal, capabilities: [...principal.capabilities] },
3519
- cloneWorkspace(record.value),
3570
+ cloneWorkspace(record2.value),
3520
3571
  managedTabSets.map(cloneManagedTabSet)
3521
3572
  );
3522
3573
  }
3523
3574
  this.options.onWorkspaceReleased?.(
3524
- cloneWorkspace(record.value),
3525
- cloneManagedTabSet(record.managedTabSet)
3575
+ cloneWorkspace(record2.value),
3576
+ cloneManagedTabSet(record2.managedTabSet)
3526
3577
  );
3527
- const cleanup2 = this.options.artifactStore?.releaseWorkspace(record.value.id).catch(() => {
3578
+ const cleanup2 = this.options.artifactStore?.releaseWorkspace(record2.value.id).catch(() => {
3528
3579
  }) ?? Promise.resolve();
3529
- this.workspaceCleanup.set(record.value.id, cleanup2);
3580
+ this.workspaceCleanup.set(record2.value.id, cleanup2);
3530
3581
  void cleanup2.finally(() => {
3531
- if (this.workspaceCleanup.get(record.value.id) === cleanup2) {
3532
- this.workspaceCleanup.delete(record.value.id);
3582
+ if (this.workspaceCleanup.get(record2.value.id) === cleanup2) {
3583
+ this.workspaceCleanup.delete(record2.value.id);
3533
3584
  }
3534
3585
  });
3535
3586
  }
3536
3587
  pruneReleasedWorkspaces() {
3537
3588
  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)) {
3589
+ const released = [...this.workspaces.values()].filter((record2) => record2.value.state === "released").sort((left, right) => left.value.updatedAt - right.value.updatedAt);
3590
+ for (const record2 of released) {
3591
+ this.workspaces.delete(record2.value.id);
3592
+ this.events.releaseWorkspace(record2.value.id);
3593
+ for (const managedTabSet of runtimeManagedTabSets(record2)) {
3543
3594
  this.managedTabSetIds.delete(managedTabSet.id);
3544
3595
  }
3545
3596
  for (const [leaseId, lease] of this.leases) {
3546
- if (lease.workspaceId === record.value.id) this.leases.delete(leaseId);
3597
+ if (lease.workspaceId === record2.value.id) this.leases.delete(leaseId);
3547
3598
  }
3548
- this.cleanupUnusedPrincipal(record.value.principalId);
3599
+ this.cleanupUnusedPrincipal(record2.value.principalId);
3549
3600
  if (this.workspaces.size < this.maxWorkspaceRecords) return;
3550
3601
  }
3551
3602
  }
@@ -3558,8 +3609,8 @@ var MemoryBrokerRuntime = class {
3558
3609
  }
3559
3610
  }
3560
3611
  cleanupUnusedPrincipal(principalId) {
3561
- const hasConnection = [...this.connectionsByBridge.values()].some((connection) => connection.value.principalId === principalId);
3562
- const hasWorkspace = [...this.workspaces.values()].some((record) => record.value.principalId === principalId);
3612
+ const hasConnection = [...this.connectionsByClientSession.values()].some((connection) => connection.value.principalId === principalId);
3613
+ const hasWorkspace = [...this.workspaces.values()].some((record2) => record2.value.principalId === principalId);
3563
3614
  if (hasConnection || hasWorkspace) return;
3564
3615
  const principal = this.principals.get(principalId);
3565
3616
  if (!principal) return;
@@ -3582,8 +3633,10 @@ var MemoryBrokerRuntime = class {
3582
3633
  const params = validateToolCallParams(value);
3583
3634
  const definition = getToolDefinition(params.name);
3584
3635
  if (!isToolAvailableInProtocol(definition.name, connection.value.protocol)) {
3585
- throw protocolIncompatible(`${definition.name} requires protocol 1.2 or newer`, {
3636
+ const requiredMinor = minimumProtocolMinorForTool(definition.name);
3637
+ throw protocolIncompatible(`${definition.name} requires protocol 1.${requiredMinor} or newer`, {
3586
3638
  tool: definition.name,
3639
+ requiredProtocol: { major: 1, minor: requiredMinor },
3587
3640
  selectedProtocol: `${connection.value.protocol.major}.${connection.value.protocol.minor}`
3588
3641
  });
3589
3642
  }
@@ -3734,8 +3787,8 @@ var MemoryBrokerRuntime = class {
3734
3787
  }));
3735
3788
  }
3736
3789
  async callBrowserDiscover(connection, params, args, definition) {
3737
- const record = args;
3738
- const filter = typeof record.browser === "string" ? record.browser.toLowerCase() : void 0;
3790
+ const record2 = args;
3791
+ const filter = typeof record2.browser === "string" ? record2.browser.toLowerCase() : void 0;
3739
3792
  const browsers = this.browserBindings.map((binding) => ({ ...binding.candidate })).filter((candidate) => !filter || [candidate.id, candidate.product, candidate.channel].some((value) => value?.toLowerCase().includes(filter)));
3740
3793
  return asJson(await this.commands.run({
3741
3794
  principalId: connection.value.principalId,
@@ -3764,36 +3817,6 @@ var MemoryBrokerRuntime = class {
3764
3817
  this.requireWorkspace(connection, params.workspaceId, true);
3765
3818
  return asJson(this.events.poll(params.workspaceId, params.cursor, params.limit));
3766
3819
  }
3767
- deliverNotification(event) {
3768
- const workspace = this.workspaces.get(event.workspaceId);
3769
- if (!workspace) return;
3770
- const notification = {
3771
- jsonrpc: "2.0",
3772
- method: "events/event",
3773
- params: { event }
3774
- };
3775
- for (const connection of this.connectionsByBridge.values()) {
3776
- if (connection.value.principalId !== workspace.value.principalId || !connection.grantedCapabilities.includes("event.read")) continue;
3777
- if (connection.notificationWaiter) {
3778
- this.resolveNotificationWaiter(connection, structuredClone(notification));
3779
- continue;
3780
- }
3781
- connection.notifications.push(structuredClone(notification));
3782
- if (connection.notifications.length > connection.limits.eventJournalSize) {
3783
- connection.notifications.shift();
3784
- }
3785
- }
3786
- }
3787
- resolveNotificationWaiter(connection, notification) {
3788
- const waiter = connection.notificationWaiter;
3789
- if (!waiter) return;
3790
- connection.notificationWaiter = void 0;
3791
- if (waiter.timer) clearTimeout(waiter.timer);
3792
- if (waiter.signal && waiter.onAbort) {
3793
- waiter.signal.removeEventListener("abort", waiter.onAbort);
3794
- }
3795
- waiter.resolve(notification);
3796
- }
3797
3820
  getCommand(connection, value) {
3798
3821
  const params = validateCommandAccessParams(value);
3799
3822
  if (params.workspaceId) this.requireWorkspace(connection, params.workspaceId, true);
@@ -3803,6 +3826,18 @@ var MemoryBrokerRuntime = class {
3803
3826
  ...params.workspaceId ? { workspaceId: params.workspaceId } : {}
3804
3827
  }));
3805
3828
  }
3829
+ listCommands(connection, value) {
3830
+ const params = validateCommandListParams(value);
3831
+ this.requireWorkspace(connection, params.workspaceId, true);
3832
+ return asJson({
3833
+ commands: this.commands.list({
3834
+ principalId: connection.value.principalId,
3835
+ workspaceId: params.workspaceId,
3836
+ ...params.limit !== void 0 ? { limit: params.limit } : {},
3837
+ ...params.statuses ? { statuses: params.statuses } : {}
3838
+ })
3839
+ });
3840
+ }
3806
3841
  cancelCommand(connection, value) {
3807
3842
  const params = validateCommandAccessParams(value);
3808
3843
  if (params.workspaceId) this.requireWorkspace(connection, params.workspaceId, true);
@@ -3820,8 +3855,15 @@ var MemoryBrokerRuntime = class {
3820
3855
  async getArtifact(connection, value) {
3821
3856
  const params = validateArtifactAccessParams(value);
3822
3857
  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 });
3858
+ const record2 = await this.requireArtifactStore().get(params.workspaceId, params.artifactId);
3859
+ return asJson({ artifact: record2.descriptor, path: record2.path });
3860
+ }
3861
+ async listArtifacts(connection, value) {
3862
+ const params = validateArtifactListParams(value);
3863
+ this.requireArtifactContext(connection, params.workspaceId, params.leaseId);
3864
+ return asJson({
3865
+ artifacts: await this.requireArtifactStore().list(params.workspaceId, params.kinds)
3866
+ });
3825
3867
  }
3826
3868
  async exportArtifact(connection, value) {
3827
3869
  const params = validateArtifactExportParams(value);
@@ -3836,18 +3878,18 @@ var MemoryBrokerRuntime = class {
3836
3878
  async importArtifact(connection, value) {
3837
3879
  const params = validateArtifactImportParams(value);
3838
3880
  this.requireArtifactContext(connection, params.workspaceId, params.leaseId);
3839
- const record = await this.requireArtifactStore().importFile(
3881
+ const record2 = await this.requireArtifactStore().importFile(
3840
3882
  params.workspaceId,
3841
3883
  params.path,
3842
3884
  params.mimeType
3843
3885
  );
3844
- return asJson({ artifact: record.descriptor });
3886
+ return asJson({ artifact: record2.descriptor });
3845
3887
  }
3846
3888
  async retainArtifact(connection, value) {
3847
3889
  const params = validateArtifactAccessParams(value);
3848
3890
  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 });
3891
+ const record2 = await this.requireArtifactStore().retain(params.workspaceId, params.artifactId);
3892
+ return asJson({ artifact: record2.descriptor, path: record2.path });
3851
3893
  }
3852
3894
  async releaseArtifact(connection, value) {
3853
3895
  const params = validateArtifactAccessParams(value);
@@ -3886,9 +3928,9 @@ var MemoryBrokerRuntime = class {
3886
3928
  context: { leaseId: lease.id, missingCapabilities: missing }
3887
3929
  });
3888
3930
  }
3889
- assertBridgeSessionId(value) {
3931
+ assertClientSessionId(value) {
3890
3932
  if (!/^[A-Za-z0-9][A-Za-z0-9._:-]{2,127}$/.test(value)) {
3891
- throw invalidArgument("Invalid internal bridge session ID", "bridgeSessionId");
3933
+ throw invalidArgument("Invalid internal client session ID", "clientSessionId");
3892
3934
  }
3893
3935
  }
3894
3936
  nextId(kind, records) {
@@ -4394,20 +4436,6 @@ var PAGE_DIMENSIONS = `JSON.stringify({
4394
4436
  width: Math.max(document.documentElement.scrollWidth, document.documentElement.clientWidth),
4395
4437
  height: Math.max(document.documentElement.scrollHeight, document.documentElement.clientHeight)
4396
4438
  })`;
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
4439
  function elementRect(selector) {
4412
4440
  return `JSON.stringify((() => {
4413
4441
  const el = document.querySelector(${JSON.stringify(selector)});
@@ -5120,6 +5148,7 @@ import { spawn } from "child_process";
5120
5148
  // src/client.ts
5121
5149
  import http from "http";
5122
5150
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
5151
+ var BROKER_RPC_VERSION = 2;
5123
5152
 
5124
5153
  // src/chrome.ts
5125
5154
  import { createHash as createHash3 } from "crypto";
@@ -5403,7 +5432,7 @@ async function discoverBrowserCandidates(options = {}) {
5403
5432
  id: stableBrowserId(definition, os),
5404
5433
  product: definition.product,
5405
5434
  channel: definition.channel,
5406
- profile: definition.dataDir,
5435
+ userDataRoot: definition.dataDir,
5407
5436
  processState,
5408
5437
  remoteDebuggingState,
5409
5438
  authorizationState,
@@ -5458,15 +5487,9 @@ async function waitForLoad(transport, sessionId, timeout = 3e4) {
5458
5487
  let interactiveSince = null;
5459
5488
  const deadlineReached = /* @__PURE__ */ Symbol("page-load-deadline");
5460
5489
  let deadlineTimer;
5461
- const deadline = new Promise((resolve2) => {
5462
- deadlineTimer = setTimeout(() => resolve2(deadlineReached), timeout);
5490
+ const deadline = new Promise((resolve3) => {
5491
+ deadlineTimer = setTimeout(() => resolve3(deadlineReached), timeout);
5463
5492
  });
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
5493
  try {
5471
5494
  while (true) {
5472
5495
  const evaluation = await Promise.race([
@@ -5480,18 +5503,16 @@ async function waitForLoad(transport, sessionId, timeout = 3e4) {
5480
5503
  ]);
5481
5504
  if (evaluation === deadlineReached) throw new PageLoadTimeoutError(timeout);
5482
5505
  if (evaluation.state === "complete") {
5483
- await injectBorder();
5484
5506
  return;
5485
5507
  }
5486
5508
  if (evaluation.state === "interactive") {
5487
5509
  if (interactiveSince === null) interactiveSince = Date.now();
5488
5510
  if (Date.now() - interactiveSince >= interactiveGrace) {
5489
- await injectBorder();
5490
5511
  return;
5491
5512
  }
5492
5513
  }
5493
5514
  const sleep = await Promise.race([
5494
- new Promise((resolve2) => setTimeout(() => resolve2("poll"), 200)),
5515
+ new Promise((resolve3) => setTimeout(() => resolve3("poll"), 200)),
5495
5516
  deadline
5496
5517
  ]);
5497
5518
  if (sleep === deadlineReached) throw new PageLoadTimeoutError(timeout);
@@ -5675,7 +5696,7 @@ var InputDispatcher = class {
5675
5696
  }
5676
5697
  hooks.afterCharacter?.({ character: char, index });
5677
5698
  index += 1;
5678
- if (delayMs > 0) await new Promise((resolve2) => setTimeout(resolve2, delayMs));
5699
+ if (delayMs > 0) await new Promise((resolve3) => setTimeout(resolve3, delayMs));
5679
5700
  }
5680
5701
  }
5681
5702
  async insertText(text) {
@@ -5719,7 +5740,7 @@ var ObservationService = class {
5719
5740
  }
5720
5741
  async observeAfterAction(limit = OBSERVATION_V1_LIMITS.defaultElements) {
5721
5742
  if (this.settleDelayMs > 0) {
5722
- await new Promise((resolve2) => setTimeout(resolve2, this.settleDelayMs));
5743
+ await new Promise((resolve3) => setTimeout(resolve3, this.settleDelayMs));
5723
5744
  }
5724
5745
  await this.loadWaiter(this.transport, this.sessionId, this.loadTimeoutMs);
5725
5746
  return this.observe(limit);
@@ -5778,24 +5799,24 @@ function parsePointerTargetState(value) {
5778
5799
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
5779
5800
  throw new BrowserPilotError("internal_error", "Chrome returned invalid pointer target state");
5780
5801
  }
5781
- const record = value;
5782
- if (record.status === "ready" && Number.isFinite(record.x) && Number.isFinite(record.y)) {
5802
+ const record2 = value;
5803
+ if (record2.status === "ready" && Number.isFinite(record2.x) && Number.isFinite(record2.y)) {
5783
5804
  return {
5784
5805
  status: "ready",
5785
- x: Number(record.x),
5786
- y: Number(record.y),
5787
- targetState: parseClickTargetState(record.targetState)
5806
+ x: Number(record2.x),
5807
+ y: Number(record2.y),
5808
+ targetState: parseClickTargetState(record2.targetState)
5788
5809
  };
5789
5810
  }
5790
- if (record.status !== "blocked" || !POINTER_FAILURE_REASONS.has(record.reason)) {
5811
+ if (record2.status !== "blocked" || !POINTER_FAILURE_REASONS.has(record2.reason)) {
5791
5812
  throw new BrowserPilotError("internal_error", "Chrome returned invalid pointer target state");
5792
5813
  }
5793
5814
  let obstruction;
5794
- if (record.obstruction !== void 0) {
5795
- if (typeof record.obstruction !== "object" || record.obstruction === null || Array.isArray(record.obstruction)) {
5815
+ if (record2.obstruction !== void 0) {
5816
+ if (typeof record2.obstruction !== "object" || record2.obstruction === null || Array.isArray(record2.obstruction)) {
5796
5817
  throw new BrowserPilotError("internal_error", "Chrome returned invalid pointer target state");
5797
5818
  }
5798
- const candidate = record.obstruction;
5819
+ const candidate = record2.obstruction;
5799
5820
  if (typeof candidate.tagName !== "string" || candidate.tagName.length > 40 || candidate.role !== void 0 && (typeof candidate.role !== "string" || candidate.role.length > 40)) {
5800
5821
  throw new BrowserPilotError("internal_error", "Chrome returned invalid pointer target state");
5801
5822
  }
@@ -5806,7 +5827,7 @@ function parsePointerTargetState(value) {
5806
5827
  }
5807
5828
  return {
5808
5829
  status: "blocked",
5809
- reason: record.reason,
5830
+ reason: record2.reason,
5810
5831
  ...obstruction ? { obstruction } : {}
5811
5832
  };
5812
5833
  }
@@ -5823,18 +5844,18 @@ function parseClickTargetState(value) {
5823
5844
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
5824
5845
  throw new BrowserPilotError("internal_error", "Chrome returned invalid click verification state");
5825
5846
  }
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") {
5847
+ const record2 = value;
5848
+ 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
5849
  throw new BrowserPilotError("internal_error", "Chrome returned invalid click verification state");
5829
5850
  }
5830
5851
  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 } : {}
5852
+ connected: record2.connected,
5853
+ kind: record2.kind,
5854
+ focused: record2.focused,
5855
+ ...record2.checked !== void 0 ? { checked: record2.checked } : {},
5856
+ ...record2.selected !== void 0 ? { selected: record2.selected } : {},
5857
+ ...record2.pressed !== void 0 ? { pressed: record2.pressed } : {},
5858
+ ...record2.expanded !== void 0 ? { expanded: record2.expanded } : {}
5838
5859
  };
5839
5860
  }
5840
5861
  function clickEvidence(before, after, button, clickCount) {
@@ -5880,20 +5901,20 @@ function parseEditableState(value) {
5880
5901
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
5881
5902
  throw new BrowserPilotError("internal_error", "Chrome returned invalid input verification state");
5882
5903
  }
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) {
5904
+ const record2 = value;
5905
+ const kind = record2.kind;
5906
+ const inputType = record2.inputType;
5907
+ const editMode = record2.editMode;
5908
+ const selectionMode = record2.selectionMode;
5909
+ const reason2 = record2.reason;
5910
+ 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
5911
  throw new BrowserPilotError("internal_error", "Chrome returned invalid input verification state");
5891
5912
  }
5892
5913
  return {
5893
5914
  kind,
5894
- value: record.value,
5895
- sensitive: record.sensitive,
5896
- editable: record.editable,
5915
+ value: record2.value,
5916
+ sensitive: record2.sensitive,
5917
+ editable: record2.editable,
5897
5918
  ...inputType !== void 0 ? { inputType } : {},
5898
5919
  ...editMode !== void 0 ? { editMode } : {},
5899
5920
  ...selectionMode !== void 0 ? { selectionMode } : {},
@@ -5928,19 +5949,19 @@ function parsePressTargetState(value, focusToken) {
5928
5949
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
5929
5950
  throw new BrowserPilotError("internal_error", "Chrome returned invalid key verification state");
5930
5951
  }
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") {
5952
+ const record2 = value;
5953
+ 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
5954
  throw new BrowserPilotError("internal_error", "Chrome returned invalid key verification state");
5934
5955
  }
5935
5956
  return {
5936
5957
  ...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 } : {}
5958
+ kind: record2.kind,
5959
+ sensitive: record2.sensitive,
5960
+ ...record2.valueToken !== void 0 ? { valueToken: record2.valueToken } : {},
5961
+ ...record2.selectedToken !== void 0 ? { selectedToken: record2.selectedToken } : {},
5962
+ ...record2.checked !== void 0 ? { checked: record2.checked } : {},
5963
+ ...record2.pressed !== void 0 ? { pressed: record2.pressed } : {},
5964
+ ...record2.expanded !== void 0 ? { expanded: record2.expanded } : {}
5944
5965
  };
5945
5966
  }
5946
5967
  function pressEvidence(before, after) {
@@ -6076,7 +6097,7 @@ var ActionService = class {
6076
6097
  await this.input.click(point.x, point.y, { button, clickCount });
6077
6098
  this.markDispatched(run);
6078
6099
  if (this.readbackDelayMs > 0) {
6079
- await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
6100
+ await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
6080
6101
  }
6081
6102
  let after;
6082
6103
  try {
@@ -6128,7 +6149,7 @@ var ActionService = class {
6128
6149
  await this.input.press(key);
6129
6150
  this.markDispatched(run);
6130
6151
  if (this.readbackDelayMs > 0) {
6131
- await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
6152
+ await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
6132
6153
  }
6133
6154
  let after;
6134
6155
  try {
@@ -6194,7 +6215,7 @@ var ActionService = class {
6194
6215
  this.markDispatched(run);
6195
6216
  }
6196
6217
  if (this.readbackDelayMs > 0) {
6197
- await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
6218
+ await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
6198
6219
  }
6199
6220
  const after = await this.readElementState(objectId);
6200
6221
  evidence = inputEvidence("type", before, after, text, !!options.clear);
@@ -6226,7 +6247,7 @@ var ActionService = class {
6226
6247
  this.willDispatch();
6227
6248
  await this.input.click(location.x, location.y);
6228
6249
  this.markDispatched(run);
6229
- if (this.focusDelayMs > 0) await new Promise((resolve2) => setTimeout(resolve2, this.focusDelayMs));
6250
+ if (this.focusDelayMs > 0) await new Promise((resolve3) => setTimeout(resolve3, this.focusDelayMs));
6230
6251
  }
6231
6252
  await this.captureFocus(run, "capture_input_focus");
6232
6253
  const before = await this.readActiveState();
@@ -6248,7 +6269,7 @@ var ActionService = class {
6248
6269
  },
6249
6270
  afterCharacter: () => this.markDispatched(run)
6250
6271
  });
6251
- if (this.readbackDelayMs > 0) await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
6272
+ if (this.readbackDelayMs > 0) await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
6252
6273
  const after = await this.readActiveState();
6253
6274
  const evidence = inputEvidence("keyboard", before, after, text, !!options.clear);
6254
6275
  this.requireVerification(evidence, options.verification);
@@ -6964,13 +6985,13 @@ var DownloadController = class {
6964
6985
  return;
6965
6986
  }
6966
6987
  if (!this.isTracked(session, download)) return;
6967
- const record = await this.artifacts.ingestDownloadCopy(
6988
+ const record2 = await this.artifacts.ingestDownloadCopy(
6968
6989
  session.context.workspaceId,
6969
6990
  completedFile.path,
6970
6991
  download.fileName
6971
6992
  );
6972
6993
  if (!this.isTracked(session, download)) {
6973
- await this.artifacts.release(session.context.workspaceId, record.descriptor.id);
6994
+ await this.artifacts.release(session.context.workspaceId, record2.descriptor.id);
6974
6995
  return;
6975
6996
  }
6976
6997
  download.state = "terminal";
@@ -6978,7 +6999,7 @@ var DownloadController = class {
6978
6999
  this.publish(session, {
6979
7000
  downloadId: download.id,
6980
7001
  state: "completed",
6981
- artifact: record.descriptor
7002
+ artifact: record2.descriptor
6982
7003
  });
6983
7004
  } catch (error) {
6984
7005
  if (download.state === "terminal") return;
@@ -7069,8 +7090,8 @@ var MAX_PROFILE_CONTEXT_RECORDS = 512;
7069
7090
  function cloneTarget(target) {
7070
7091
  return { ...target };
7071
7092
  }
7072
- function cloneRecord(record) {
7073
- return { ...record, targets: record.targets.map(cloneTarget) };
7093
+ function cloneRecord(record2) {
7094
+ return { ...record2, targets: record2.targets.map(cloneTarget) };
7074
7095
  }
7075
7096
  function contextKey(cdpBrowserContextId) {
7076
7097
  return cdpBrowserContextId ? `cdp:${cdpBrowserContextId}` : DEFAULT_CDP_CONTEXT_KEY;
@@ -7115,22 +7136,23 @@ var MemoryProfileContextRegistry = class {
7115
7136
  });
7116
7137
  }
7117
7138
  for (const id of this.currentByKey.values()) {
7118
- const record = this.recordsById.get(id);
7119
- if (record) record.active = false;
7139
+ const record2 = this.recordsById.get(id);
7140
+ if (record2) record2.active = false;
7120
7141
  }
7121
7142
  this.contextByTarget.clear();
7122
7143
  const activeKeys = new Set(grouped.keys());
7123
7144
  for (const [key, group] of grouped) {
7124
7145
  let id = this.currentByKey.get(key);
7125
- let record = id ? this.recordsById.get(id) : void 0;
7126
- if (!record) {
7146
+ let record2 = id ? this.recordsById.get(id) : void 0;
7147
+ if (!record2) {
7127
7148
  id = this.nextId();
7128
7149
  const rawContextId = group[0]?.cdpBrowserContextId;
7129
- record = {
7150
+ record2 = {
7130
7151
  id,
7131
7152
  browserInstanceId: this.browserInstanceId,
7132
7153
  browserConnectionGeneration,
7133
7154
  label: `Profile ${++this.nextSequence}`,
7155
+ identityStatus: "unidentified",
7134
7156
  tabCount: 0,
7135
7157
  eligibleTabCount: 0,
7136
7158
  sequence: this.nextSequence,
@@ -7138,34 +7160,34 @@ var MemoryProfileContextRegistry = class {
7138
7160
  targets: [],
7139
7161
  ...rawContextId ? { cdpBrowserContextId: rawContextId } : {}
7140
7162
  };
7141
- this.recordsById.set(id, record);
7163
+ this.recordsById.set(id, record2);
7142
7164
  this.recordOrder.push(id);
7143
7165
  this.currentByKey.set(key, id);
7144
7166
  }
7145
7167
  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);
7168
+ record2.active = true;
7169
+ record2.tabCount = sorted.length;
7170
+ record2.eligibleTabCount = sorted.filter((target) => target.eligible).length;
7171
+ record2.targets = sorted;
7172
+ record2.representativeCdpTargetId = sorted[0]?.cdpTargetId;
7173
+ for (const target of sorted) this.contextByTarget.set(target.cdpTargetId, record2.id);
7152
7174
  }
7153
7175
  for (const [key, id] of this.currentByKey) {
7154
7176
  if (activeKeys.has(key)) continue;
7155
- const record = this.recordsById.get(id);
7156
- if (record) record.active = false;
7177
+ const record2 = this.recordsById.get(id);
7178
+ if (record2) record2.active = false;
7157
7179
  this.currentByKey.delete(key);
7158
7180
  }
7159
7181
  this.pruneStaleRecords();
7160
7182
  return this.list(browserConnectionGeneration);
7161
7183
  }
7162
7184
  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);
7185
+ 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
7186
  }
7165
7187
  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) {
7188
+ const record2 = this.recordsById.get(profileContextId);
7189
+ if (!record2) throw invalidArgument("Unknown Profile context", "profileContextId");
7190
+ if (!record2.active || record2.browserInstanceId !== this.browserInstanceId || record2.browserConnectionGeneration !== browserConnectionGeneration) {
7169
7191
  throw new BrowserPilotError("profile_context_stale", "Profile context belongs to a stale browser connection", {
7170
7192
  retryable: true,
7171
7193
  context: {
@@ -7179,7 +7201,31 @@ var MemoryProfileContextRegistry = class {
7179
7201
  }
7180
7202
  });
7181
7203
  }
7182
- return cloneRecord(record);
7204
+ return cloneRecord(record2);
7205
+ }
7206
+ setVerifiedIdentity(profileContextId, browserConnectionGeneration, identity) {
7207
+ const record2 = this.currentRecord(profileContextId, browserConnectionGeneration);
7208
+ record2.identityStatus = "verified";
7209
+ record2.profileName = identity.profileName;
7210
+ record2.displayName = identity.profileName;
7211
+ record2.profileDirectory = identity.profileDirectory;
7212
+ if (identity.accountName) record2.accountName = identity.accountName;
7213
+ else delete record2.accountName;
7214
+ if (identity.accountEmail) record2.accountEmail = identity.accountEmail;
7215
+ else delete record2.accountEmail;
7216
+ delete record2.identityErrorCode;
7217
+ return cloneRecord(record2);
7218
+ }
7219
+ setIdentityUnavailable(profileContextId, browserConnectionGeneration, identityErrorCode) {
7220
+ const record2 = this.currentRecord(profileContextId, browserConnectionGeneration);
7221
+ record2.identityStatus = "unavailable";
7222
+ record2.identityErrorCode = identityErrorCode;
7223
+ delete record2.profileName;
7224
+ delete record2.displayName;
7225
+ delete record2.profileDirectory;
7226
+ delete record2.accountName;
7227
+ delete record2.accountEmail;
7228
+ return cloneRecord(record2);
7183
7229
  }
7184
7230
  forTarget(cdpTargetId, browserConnectionGeneration) {
7185
7231
  const id = this.contextByTarget.get(cdpTargetId);
@@ -7198,19 +7244,23 @@ var MemoryProfileContextRegistry = class {
7198
7244
  }
7199
7245
  invalidateCurrent() {
7200
7246
  for (const id of this.currentByKey.values()) {
7201
- const record = this.recordsById.get(id);
7202
- if (record) record.active = false;
7247
+ const record2 = this.recordsById.get(id);
7248
+ if (record2) record2.active = false;
7203
7249
  }
7204
7250
  this.currentByKey.clear();
7205
7251
  this.contextByTarget.clear();
7206
7252
  }
7253
+ currentRecord(profileContextId, browserConnectionGeneration) {
7254
+ this.resolve(profileContextId, browserConnectionGeneration);
7255
+ return this.recordsById.get(profileContextId);
7256
+ }
7207
7257
  pruneStaleRecords() {
7208
7258
  while (this.recordsById.size > MAX_PROFILE_CONTEXT_RECORDS) {
7209
7259
  const id = this.recordOrder.shift();
7210
7260
  if (!id) return;
7211
- const record = this.recordsById.get(id);
7212
- if (!record || record.active) {
7213
- if (record?.active) this.recordOrder.push(id);
7261
+ const record2 = this.recordsById.get(id);
7262
+ if (!record2 || record2.active) {
7263
+ if (record2?.active) this.recordOrder.push(id);
7214
7264
  continue;
7215
7265
  }
7216
7266
  this.recordsById.delete(id);
@@ -7233,8 +7283,10 @@ function isEligibleUserPage(target) {
7233
7283
  return !(/* @__PURE__ */ new Set([
7234
7284
  "devtools:",
7235
7285
  "chrome:",
7286
+ "chrome-extension:",
7236
7287
  "chrome-untrusted:",
7237
7288
  "edge:",
7289
+ "edge-extension:",
7238
7290
  "brave:",
7239
7291
  "vivaldi:"
7240
7292
  ])).has(url.protocol);
@@ -7242,6 +7294,14 @@ function isEligibleUserPage(target) {
7242
7294
  return target.url === "about:blank";
7243
7295
  }
7244
7296
  }
7297
+ function isExtensionPage(target) {
7298
+ try {
7299
+ const protocol = new URL(target.url).protocol;
7300
+ return protocol === "chrome-extension:" || protocol === "edge-extension:";
7301
+ } catch {
7302
+ return false;
7303
+ }
7304
+ }
7245
7305
  var CdpBrowserTargetCatalog = class {
7246
7306
  constructor(transport, browserInstanceId, identity, options) {
7247
7307
  this.transport = transport;
@@ -7272,6 +7332,7 @@ var CdpBrowserTargetCatalog = class {
7272
7332
  type: typeof value.type === "string" ? value.type : "",
7273
7333
  ...typeof value.openerId === "string" ? { openerCdpTargetId: value.openerId } : {}
7274
7334
  };
7335
+ if (isExtensionPage(candidate)) continue;
7275
7336
  const eligible2 = isEligibleUserPage(candidate) && !this.options.isExcludedTarget(candidate);
7276
7337
  snapshots.push({
7277
7338
  ...candidate,
@@ -7507,8 +7568,8 @@ var BrowserWatchdogService = class {
7507
7568
 
7508
7569
  // src/services/controlled-target-registry.ts
7509
7570
  import { randomUUID as randomUUID8 } from "crypto";
7510
- function cloneRecord2(record) {
7511
- return { ...record };
7571
+ function cloneRecord2(record2) {
7572
+ return { ...record2 };
7512
7573
  }
7513
7574
  function targetKey2(workspaceId, browserInstanceId, cdpTargetId) {
7514
7575
  return `${workspaceId}\0${browserInstanceId}\0${cdpTargetId}`;
@@ -7542,7 +7603,7 @@ var MemoryControlledTargetRegistry = class {
7542
7603
  existing.openerCdpTargetId = input.openerCdpTargetId;
7543
7604
  return cloneRecord2(existing);
7544
7605
  }
7545
- const record = {
7606
+ const record2 = {
7546
7607
  id: this.nextId(),
7547
7608
  principalId: input.principalId,
7548
7609
  workspaceId: input.workspaceId,
@@ -7558,9 +7619,9 @@ var MemoryControlledTargetRegistry = class {
7558
7619
  createdAt: this.now(),
7559
7620
  state: "active"
7560
7621
  };
7561
- this.records.set(record.id, record);
7562
- this.activeKeys.set(key, record.id);
7563
- return cloneRecord2(record);
7622
+ this.records.set(record2.id, record2);
7623
+ this.activeKeys.set(key, record2.id);
7624
+ return cloneRecord2(record2);
7564
7625
  }
7565
7626
  syncUserTargets(context, browserInstanceId, userTargets) {
7566
7627
  const targetsByKey = /* @__PURE__ */ new Map();
@@ -7572,11 +7633,11 @@ var MemoryControlledTargetRegistry = class {
7572
7633
  );
7573
7634
  }
7574
7635
  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);
7636
+ for (const record2 of this.records.values()) {
7637
+ if (record2.state !== "active" || record2.origin !== "user_tab" || record2.principalId !== context.principalId || record2.workspaceId !== context.workspaceId || record2.browserInstanceId !== browserInstanceId) continue;
7638
+ const key = targetKey2(record2.workspaceId, record2.browserInstanceId, record2.cdpTargetId);
7578
7639
  if (!targetsByKey.has(key)) {
7579
- invalidated.push(this.invalidate(record, "target_ineligible"));
7640
+ invalidated.push(this.invalidate(record2, "target_ineligible"));
7580
7641
  }
7581
7642
  }
7582
7643
  const targets = [];
@@ -7594,7 +7655,7 @@ var MemoryControlledTargetRegistry = class {
7594
7655
  targets.push(cloneRecord2(existing));
7595
7656
  continue;
7596
7657
  }
7597
- const record = {
7658
+ const record2 = {
7598
7659
  id: this.nextId(),
7599
7660
  principalId: context.principalId,
7600
7661
  workspaceId: context.workspaceId,
@@ -7608,27 +7669,27 @@ var MemoryControlledTargetRegistry = class {
7608
7669
  createdAt: this.now(),
7609
7670
  state: "active"
7610
7671
  };
7611
- this.records.set(record.id, record);
7612
- this.activeKeys.set(key, record.id);
7613
- targets.push(cloneRecord2(record));
7614
- created.push(cloneRecord2(record));
7672
+ this.records.set(record2.id, record2);
7673
+ this.activeKeys.set(key, record2.id);
7674
+ targets.push(cloneRecord2(record2));
7675
+ created.push(cloneRecord2(record2));
7615
7676
  }
7616
7677
  return { targets, created, invalidated };
7617
7678
  }
7618
7679
  reconcileBrowserTargets(context, browserInstanceId, liveTargets) {
7619
7680
  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);
7681
+ for (const record2 of this.records.values()) {
7682
+ if (record2.state !== "active" || record2.principalId !== context.principalId || record2.workspaceId !== context.workspaceId || record2.browserInstanceId !== browserInstanceId) continue;
7683
+ const live = liveTargets.get(record2.cdpTargetId);
7623
7684
  if (!live) {
7624
- invalidated.push(this.invalidate(record, "target_detached"));
7685
+ invalidated.push(this.invalidate(record2, "target_detached"));
7625
7686
  continue;
7626
7687
  }
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"));
7688
+ record2.title = live.title;
7689
+ record2.url = live.url;
7690
+ record2.openerCdpTargetId = live.openerCdpTargetId;
7691
+ if (record2.profileContextId && live.profileContextId && record2.profileContextId !== live.profileContextId) {
7692
+ invalidated.push(this.invalidate(record2, "target_ineligible"));
7632
7693
  }
7633
7694
  }
7634
7695
  return invalidated;
@@ -7636,38 +7697,38 @@ var MemoryControlledTargetRegistry = class {
7636
7697
  list(context, leaseId, scope = "all") {
7637
7698
  const activeTargetId = this.activeByLease.get(leaseId);
7638
7699
  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")
7700
+ (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
7701
  ).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)
7702
+ return records.map((record2) => ({
7703
+ targetId: record2.id,
7704
+ profileContextId: record2.profileContextId,
7705
+ title: record2.title,
7706
+ url: record2.url,
7707
+ active: record2.id === activeTargetId,
7708
+ origin: record2.origin,
7709
+ ...record2.managedTabSetId ? { managedTabSetId: record2.managedTabSetId } : {},
7710
+ controlState: this.controlState(context, leaseId, record2)
7650
7711
  }));
7651
7712
  }
7652
7713
  get(context, targetId) {
7653
- const record = this.records.get(targetId);
7654
- if (!record || record.state !== "active") {
7714
+ const record2 = this.records.get(targetId);
7715
+ if (!record2 || record2.state !== "active") {
7655
7716
  throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Workspace", {
7656
7717
  context: { targetId }
7657
7718
  });
7658
7719
  }
7659
- this.assertCallerOwns(context, record);
7660
- return cloneRecord2(record);
7720
+ this.assertCallerOwns(context, record2);
7721
+ return cloneRecord2(record2);
7661
7722
  }
7662
7723
  acquire(context, leaseId, targetId) {
7663
- const record = this.records.get(targetId);
7664
- if (!record || record.state !== "active") {
7724
+ const record2 = this.records.get(targetId);
7725
+ if (!record2 || record2.state !== "active") {
7665
7726
  throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Workspace", {
7666
7727
  context: { targetId }
7667
7728
  });
7668
7729
  }
7669
- this.assertCallerOwns(context, record);
7670
- const physicalKey = physicalTargetKey(record.browserInstanceId, record.cdpTargetId);
7730
+ this.assertCallerOwns(context, record2);
7731
+ const physicalKey = physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId);
7671
7732
  const controller = this.controllers.get(physicalKey);
7672
7733
  if (controller && (controller.leaseId !== leaseId || controller.principalId !== context.principalId || controller.workspaceId !== context.workspaceId)) {
7673
7734
  throw new BrowserPilotError("target_busy", "Target is controlled by another Lease", {
@@ -7682,18 +7743,18 @@ var MemoryControlledTargetRegistry = class {
7682
7743
  workspaceId: context.workspaceId,
7683
7744
  targetId
7684
7745
  });
7685
- record.controllerLeaseId = leaseId;
7686
- return { target: cloneRecord2(record), newlyAcquired };
7746
+ record2.controllerLeaseId = leaseId;
7747
+ return { target: cloneRecord2(record2), newlyAcquired };
7687
7748
  }
7688
7749
  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);
7750
+ const record2 = this.records.get(targetId);
7751
+ if (!record2 || record2.state !== "active") return false;
7752
+ this.assertCallerOwns(context, record2);
7753
+ const physicalKey = physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId);
7693
7754
  const controller = this.controllers.get(physicalKey);
7694
7755
  if (controller?.leaseId === leaseId && controller.principalId === context.principalId && controller.workspaceId === context.workspaceId && controller.targetId === targetId) {
7695
7756
  this.controllers.delete(physicalKey);
7696
- record.controllerLeaseId = void 0;
7757
+ record2.controllerLeaseId = void 0;
7697
7758
  if (this.activeByLease.get(leaseId) === targetId) this.activeByLease.delete(leaseId);
7698
7759
  return true;
7699
7760
  }
@@ -7701,128 +7762,128 @@ var MemoryControlledTargetRegistry = class {
7701
7762
  return false;
7702
7763
  }
7703
7764
  controlledByLease(leaseId) {
7704
- return [...this.records.values()].filter((record) => record.state === "active" && record.controllerLeaseId === leaseId).map(cloneRecord2);
7765
+ return [...this.records.values()].filter((record2) => record2.state === "active" && record2.controllerLeaseId === leaseId).map(cloneRecord2);
7705
7766
  }
7706
7767
  releaseLease(leaseId) {
7707
7768
  for (const [key, controller] of this.controllers) {
7708
7769
  if (controller.leaseId === leaseId) this.controllers.delete(key);
7709
7770
  }
7710
- for (const record of this.records.values()) {
7711
- if (record.controllerLeaseId === leaseId) record.controllerLeaseId = void 0;
7771
+ for (const record2 of this.records.values()) {
7772
+ if (record2.controllerLeaseId === leaseId) record2.controllerLeaseId = void 0;
7712
7773
  }
7713
7774
  this.activeByLease.delete(leaseId);
7714
7775
  }
7715
7776
  activeTarget(context, leaseId) {
7716
7777
  const targetId = this.activeByLease.get(leaseId);
7717
7778
  if (!targetId) return void 0;
7718
- const record = this.records.get(targetId);
7719
- if (!record || record.state !== "active") {
7779
+ const record2 = this.records.get(targetId);
7780
+ if (!record2 || record2.state !== "active") {
7720
7781
  this.activeByLease.delete(leaseId);
7721
7782
  return void 0;
7722
7783
  }
7723
- this.assertCallerOwns(context, record);
7724
- return cloneRecord2(record);
7784
+ this.assertCallerOwns(context, record2);
7785
+ return cloneRecord2(record2);
7725
7786
  }
7726
7787
  clearActive(context, leaseId) {
7727
7788
  const targetId = this.activeByLease.get(leaseId);
7728
7789
  if (!targetId) return;
7729
- const record = this.records.get(targetId);
7730
- if (record?.state === "active") this.assertCallerOwns(context, record);
7790
+ const record2 = this.records.get(targetId);
7791
+ if (record2?.state === "active") this.assertCallerOwns(context, record2);
7731
7792
  this.activeByLease.delete(leaseId);
7732
7793
  }
7733
7794
  releaseWorkspace(context) {
7734
7795
  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"));
7796
+ for (const record2 of [...this.records.values()]) {
7797
+ if (record2.state === "active" && record2.principalId === context.principalId && record2.workspaceId === context.workspaceId) {
7798
+ invalidated.push(this.invalidate(record2, "target_detached"));
7738
7799
  }
7739
7800
  }
7740
7801
  return invalidated;
7741
7802
  }
7742
7803
  invalidateBrowserConnection(browserInstanceId) {
7743
7804
  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"));
7805
+ for (const record2 of this.records.values()) {
7806
+ if (record2.state === "active" && record2.browserInstanceId === browserInstanceId) {
7807
+ invalidated.push(this.invalidate(record2, "browser_reconnected"));
7747
7808
  }
7748
7809
  }
7749
7810
  return invalidated;
7750
7811
  }
7751
7812
  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) {
7813
+ const record2 = this.records.get(targetId);
7814
+ const controller = record2 ? this.controllers.get(physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId)) : void 0;
7815
+ if (!record2 || record2.state !== "active" || controller?.leaseId !== leaseId || controller.principalId !== context.principalId || controller.workspaceId !== context.workspaceId || controller.targetId !== targetId) {
7755
7816
  throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Lease", {
7756
7817
  context: { targetId, leaseId }
7757
7818
  });
7758
7819
  }
7759
- this.assertCallerOwns(context, record);
7820
+ this.assertCallerOwns(context, record2);
7760
7821
  this.activeByLease.set(leaseId, targetId);
7761
7822
  }
7762
7823
  managedTargets(context, managedTabSetId) {
7763
7824
  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
7825
+ (record2) => record2.state === "active" && record2.principalId === context.principalId && record2.workspaceId === context.workspaceId && record2.origin !== "user_tab" && record2.managedTabSetId === managedTabSetId
7765
7826
  ).map(cloneRecord2);
7766
7827
  }
7767
7828
  activeRecords(context) {
7768
7829
  return [...this.records.values()].filter(
7769
- (record) => record.state === "active" && record.principalId === context.principalId && record.workspaceId === context.workspaceId
7830
+ (record2) => record2.state === "active" && record2.principalId === context.principalId && record2.workspaceId === context.workspaceId
7770
7831
  ).map(cloneRecord2);
7771
7832
  }
7772
7833
  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;
7834
+ for (const record2 of this.records.values()) {
7835
+ if (record2.browserInstanceId === browserInstanceId && record2.cdpTargetId === cdpTargetId && record2.browserConnectionGeneration === browserConnectionGeneration && record2.origin !== "user_tab") return true;
7775
7836
  }
7776
7837
  return false;
7777
7838
  }
7778
7839
  markClosed(context, targetId) {
7779
- const record = this.records.get(targetId);
7780
- if (!record || record.state !== "active") {
7840
+ const record2 = this.records.get(targetId);
7841
+ if (!record2 || record2.state !== "active") {
7781
7842
  throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Workspace", {
7782
7843
  context: { targetId }
7783
7844
  });
7784
7845
  }
7785
- this.assertCallerOwns(context, record);
7846
+ this.assertCallerOwns(context, record2);
7786
7847
  const invalidated = [];
7787
7848
  for (const candidate of this.records.values()) {
7788
- if (candidate.state === "active" && candidate.browserInstanceId === record.browserInstanceId && candidate.cdpTargetId === record.cdpTargetId) {
7849
+ if (candidate.state === "active" && candidate.browserInstanceId === record2.browserInstanceId && candidate.cdpTargetId === record2.cdpTargetId) {
7789
7850
  invalidated.push(this.invalidate(candidate, "target_closed"));
7790
7851
  }
7791
7852
  }
7792
7853
  return invalidated;
7793
7854
  }
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) {
7855
+ invalidate(record2, reason2) {
7856
+ record2.state = reason2 === "target_closed" ? "closed" : "detached";
7857
+ record2.invalidatedBy = reason2;
7858
+ record2.controllerLeaseId = void 0;
7859
+ const key = targetKey2(record2.workspaceId, record2.browserInstanceId, record2.cdpTargetId);
7860
+ if (this.activeKeys.get(key) === record2.id) this.activeKeys.delete(key);
7861
+ const physicalKey = physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId);
7862
+ if (this.controllers.get(physicalKey)?.targetId === record2.id) {
7802
7863
  this.controllers.delete(physicalKey);
7803
7864
  }
7804
7865
  for (const [leaseId, targetId] of this.activeByLease) {
7805
- if (targetId === record.id) this.activeByLease.delete(leaseId);
7866
+ if (targetId === record2.id) this.activeByLease.delete(leaseId);
7806
7867
  }
7807
7868
  return {
7808
- targetId: record.id,
7809
- workspaceId: record.workspaceId,
7810
- browserConnectionGeneration: record.browserConnectionGeneration,
7869
+ targetId: record2.id,
7870
+ workspaceId: record2.workspaceId,
7871
+ browserConnectionGeneration: record2.browserConnectionGeneration,
7811
7872
  reason: reason2
7812
7873
  };
7813
7874
  }
7814
- assertCallerOwns(context, record) {
7815
- if (record.principalId === context.principalId && record.workspaceId === context.workspaceId) return;
7875
+ assertCallerOwns(context, record2) {
7876
+ if (record2.principalId === context.principalId && record2.workspaceId === context.workspaceId) return;
7816
7877
  throw new BrowserPilotError("target_not_owned", "Target is not controlled by this Workspace", {
7817
- context: { targetId: record.id }
7878
+ context: { targetId: record2.id }
7818
7879
  });
7819
7880
  }
7820
- controlState(context, leaseId, record) {
7881
+ controlState(context, leaseId, record2) {
7821
7882
  const controller = this.controllers.get(
7822
- physicalTargetKey(record.browserInstanceId, record.cdpTargetId)
7883
+ physicalTargetKey(record2.browserInstanceId, record2.cdpTargetId)
7823
7884
  );
7824
7885
  if (!controller) return "available";
7825
- return controller.leaseId === leaseId && controller.principalId === context.principalId && controller.workspaceId === context.workspaceId && controller.targetId === record.id ? "controlled" : "busy";
7886
+ return controller.leaseId === leaseId && controller.principalId === context.principalId && controller.workspaceId === context.workspaceId && controller.targetId === record2.id ? "controlled" : "busy";
7826
7887
  }
7827
7888
  nextId() {
7828
7889
  const id = this.idFactory();
@@ -8098,17 +8159,17 @@ function parseObject(value, label) {
8098
8159
  return value;
8099
8160
  }
8100
8161
  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;
8162
+ const record2 = parseObject(value, "dropdown information");
8163
+ if (record2.ok !== true) throw invalidArgument(String(record2.error || "Dropdown inspection failed"), "target");
8164
+ 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");
8165
+ const { ok: _ok, ...info } = record2;
8105
8166
  return info;
8106
8167
  }
8107
8168
  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;
8169
+ const record2 = parseObject(value, "dropdown selection evidence");
8170
+ if (record2.ok !== true) throw invalidArgument(String(record2.error || "Dropdown selection failed"), "target");
8171
+ 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");
8172
+ const { ok: _ok, ...evidence } = record2;
8112
8173
  return evidence;
8113
8174
  }
8114
8175
  function selectorExpression(selector, functionDeclaration, argument) {
@@ -8323,11 +8384,11 @@ var FrameService = class _FrameService {
8323
8384
 
8324
8385
  // src/services/observation-store.ts
8325
8386
  import { randomUUID as randomUUID9 } from "crypto";
8326
- function clone(record) {
8387
+ function clone(record2) {
8327
8388
  return {
8328
- ...record,
8329
- truncationReasons: [...record.truncationReasons],
8330
- refs: record.refs.map((ref) => ({ ...ref }))
8389
+ ...record2,
8390
+ truncationReasons: [...record2.truncationReasons],
8391
+ refs: record2.refs.map((ref) => ({ ...ref }))
8331
8392
  };
8332
8393
  }
8333
8394
  var MemoryObservationStore = class {
@@ -8368,7 +8429,7 @@ var MemoryObservationStore = class {
8368
8429
  throw new BrowserPilotError("internal_error", "Invalid or duplicate Observation ID");
8369
8430
  }
8370
8431
  const createdAt = this.now();
8371
- const record = {
8432
+ const record2 = {
8372
8433
  id,
8373
8434
  workspaceId: input.workspaceId,
8374
8435
  leaseId: input.leaseId,
@@ -8388,58 +8449,58 @@ var MemoryObservationStore = class {
8388
8449
  truncationReasons: [...input.truncationReasons],
8389
8450
  refs: input.refs.map((ref) => ({ ...ref }))
8390
8451
  };
8391
- this.records.set(id, record);
8392
- return clone(record);
8452
+ this.records.set(id, record2);
8453
+ return clone(record2);
8393
8454
  }
8394
8455
  resolve(input) {
8395
8456
  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) {
8457
+ const record2 = this.records.get(input.observationId);
8458
+ if (!record2 || record2.workspaceId !== input.workspaceId || record2.leaseId !== input.leaseId || record2.targetId !== input.targetId) {
8398
8459
  this.sweepAt(now);
8399
8460
  throw this.stale(input);
8400
8461
  }
8401
- if (record.invalidatedBy) {
8402
- const reason2 = record.invalidatedBy;
8462
+ if (record2.invalidatedBy) {
8463
+ const reason2 = record2.invalidatedBy;
8403
8464
  this.sweepAt(now);
8404
8465
  throw this.stale(input, reason2);
8405
8466
  }
8406
- if (record.expiresAt <= now) {
8407
- this.invalidateRecord(record, "expired");
8408
- this.records.delete(record.id);
8467
+ if (record2.expiresAt <= now) {
8468
+ this.invalidateRecord(record2, "expired");
8469
+ this.records.delete(record2.id);
8409
8470
  this.sweepAt(now);
8410
8471
  throw this.stale(input, "expired");
8411
8472
  }
8412
8473
  this.sweepAt(now);
8413
- if (record.browserProcessIdentity !== input.browserProcessIdentity || record.browserConnectionGeneration !== input.browserConnectionGeneration) {
8414
- this.invalidateRecord(record, "browser_reconnected");
8474
+ if (record2.browserProcessIdentity !== input.browserProcessIdentity || record2.browserConnectionGeneration !== input.browserConnectionGeneration) {
8475
+ this.invalidateRecord(record2, "browser_reconnected");
8415
8476
  throw this.stale(input, "browser_reconnected");
8416
8477
  }
8417
- if (record.sessionId !== input.sessionId) {
8418
- this.invalidateRecord(record, "session_replaced");
8478
+ if (record2.sessionId !== input.sessionId) {
8479
+ this.invalidateRecord(record2, "session_replaced");
8419
8480
  throw this.stale(input, "session_replaced");
8420
8481
  }
8421
- if (record.frameId !== input.frameId) {
8422
- this.invalidateRecord(record, "frame_changed");
8482
+ if (record2.frameId !== input.frameId) {
8483
+ this.invalidateRecord(record2, "frame_changed");
8423
8484
  throw this.stale(input, "frame_changed");
8424
8485
  }
8425
- if (record.loaderId !== input.loaderId) {
8426
- this.invalidateRecord(record, "loader_replaced");
8486
+ if (record2.loaderId !== input.loaderId) {
8487
+ this.invalidateRecord(record2, "loader_replaced");
8427
8488
  throw this.stale(input, "loader_replaced");
8428
8489
  }
8429
- if (record.documentGeneration !== input.documentGeneration) {
8430
- this.invalidateRecord(record, "document_replaced");
8490
+ if (record2.documentGeneration !== input.documentGeneration) {
8491
+ this.invalidateRecord(record2, "document_replaced");
8431
8492
  throw this.stale(input, "document_replaced");
8432
8493
  }
8433
- if (input.ref !== void 0 && (input.ref < 1 || input.ref > record.refs.length)) {
8494
+ if (input.ref !== void 0 && (input.ref < 1 || input.ref > record2.refs.length)) {
8434
8495
  throw this.stale(input);
8435
8496
  }
8436
- return clone(record);
8497
+ return clone(record2);
8437
8498
  }
8438
8499
  latest(workspaceId, leaseId, targetId) {
8439
8500
  this.sweep();
8440
8501
  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;
8502
+ for (const record2 of this.records.values()) {
8503
+ if (record2.workspaceId === workspaceId && record2.leaseId === leaseId && record2.targetId === targetId && !record2.invalidatedBy && (!latest || record2.createdAt >= latest.createdAt)) latest = record2;
8443
8504
  }
8444
8505
  if (latest) return clone(latest);
8445
8506
  throw new BrowserPilotError("stale_ref", "No current Observation exists for this target", {
@@ -8447,25 +8508,25 @@ var MemoryObservationStore = class {
8447
8508
  });
8448
8509
  }
8449
8510
  invalidateTarget(targetId, reason2) {
8450
- for (const record of this.records.values()) {
8451
- if (record.targetId === targetId && !record.invalidatedBy) this.invalidateRecord(record, reason2);
8511
+ for (const record2 of this.records.values()) {
8512
+ if (record2.targetId === targetId && !record2.invalidatedBy) this.invalidateRecord(record2, reason2);
8452
8513
  }
8453
8514
  }
8454
8515
  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);
8516
+ for (const record2 of this.records.values()) {
8517
+ if (record2.sessionId === sessionId && !record2.invalidatedBy) {
8518
+ this.invalidateRecord(record2, reason2);
8458
8519
  }
8459
8520
  }
8460
8521
  }
8461
8522
  releaseLease(leaseId) {
8462
- for (const [id, record] of this.records) {
8463
- if (record.leaseId === leaseId) this.records.delete(id);
8523
+ for (const [id, record2] of this.records) {
8524
+ if (record2.leaseId === leaseId) this.records.delete(id);
8464
8525
  }
8465
8526
  }
8466
8527
  releaseWorkspace(workspaceId) {
8467
- for (const [id, record] of this.records) {
8468
- if (record.workspaceId === workspaceId) this.records.delete(id);
8528
+ for (const [id, record2] of this.records) {
8529
+ if (record2.workspaceId === workspaceId) this.records.delete(id);
8469
8530
  }
8470
8531
  }
8471
8532
  sweep() {
@@ -8473,9 +8534,9 @@ var MemoryObservationStore = class {
8473
8534
  }
8474
8535
  sweepAt(now) {
8475
8536
  let removed = 0;
8476
- for (const [id, record] of this.records) {
8477
- if (record.expiresAt > now) continue;
8478
- this.invalidateRecord(record, "expired");
8537
+ for (const [id, record2] of this.records) {
8538
+ if (record2.expiresAt > now) continue;
8539
+ this.invalidateRecord(record2, "expired");
8479
8540
  this.records.delete(id);
8480
8541
  removed += 1;
8481
8542
  }
@@ -8484,9 +8545,9 @@ var MemoryObservationStore = class {
8484
8545
  size() {
8485
8546
  return this.records.size;
8486
8547
  }
8487
- invalidateRecord(record, reason2) {
8488
- record.invalidatedBy = reason2;
8489
- record.refs = [];
8548
+ invalidateRecord(record2, reason2) {
8549
+ record2.invalidatedBy = reason2;
8550
+ record2.refs = [];
8490
8551
  }
8491
8552
  stale(input, reason2) {
8492
8553
  return new BrowserPilotError("stale_ref", "Observation ref is stale or does not belong to this context", {
@@ -8557,22 +8618,22 @@ var PageContentService = class {
8557
8618
  if (typeof data !== "object" || data === null) {
8558
8619
  throw new BrowserPilotError("internal_error", "Chrome returned invalid page content");
8559
8620
  }
8560
- const record = data;
8561
- if (record.ok !== true) {
8621
+ const record2 = data;
8622
+ if (record2.ok !== true) {
8562
8623
  throw invalidArgument(
8563
- typeof record.error === "string" ? record.error : "Failed to read page content",
8624
+ typeof record2.error === "string" ? record2.error : "Failed to read page content",
8564
8625
  selector ? "selector" : void 0
8565
8626
  );
8566
8627
  }
8567
- if (typeof record.title !== "string" || typeof record.url !== "string" || typeof record.text !== "string" || !Number.isSafeInteger(record.length) || typeof record.truncated !== "boolean") {
8628
+ if (typeof record2.title !== "string" || typeof record2.url !== "string" || typeof record2.text !== "string" || !Number.isSafeInteger(record2.length) || typeof record2.truncated !== "boolean") {
8568
8629
  throw new BrowserPilotError("internal_error", "Chrome returned invalid page content");
8569
8630
  }
8570
8631
  return {
8571
- title: record.title,
8572
- url: record.url,
8573
- text: record.text,
8574
- length: record.length,
8575
- truncated: record.truncated
8632
+ title: record2.title,
8633
+ url: record2.url,
8634
+ text: record2.text,
8635
+ length: record2.length,
8636
+ truncated: record2.truncated
8576
8637
  };
8577
8638
  }
8578
8639
  };
@@ -8951,13 +9012,13 @@ function parseEvidence2(value) {
8951
9012
  if (!value || typeof value !== "object" || Array.isArray(value)) {
8952
9013
  throw new BrowserPilotError("internal_error", "Chrome returned invalid scroll evidence");
8953
9014
  }
8954
- const record = value;
8955
- if (record.ok !== true) throw invalidArgument(String(record.error || "Scroll failed"), "target");
9015
+ const record2 = value;
9016
+ if (record2.ok !== true) throw invalidArgument(String(record2.error || "Scroll failed"), "target");
8956
9017
  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]))) {
9018
+ 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
9019
  throw new BrowserPilotError("internal_error", "Chrome returned invalid scroll evidence");
8959
9020
  }
8960
- const { ok: _ok, ...evidence } = record;
9021
+ const { ok: _ok, ...evidence } = record2;
8961
9022
  return evidence;
8962
9023
  }
8963
9024
  function validateInput(input) {
@@ -9459,7 +9520,7 @@ var TargetInventoryService = class {
9459
9520
  }
9460
9521
  adoptManagedPopups(context, liveTargets) {
9461
9522
  const active = new Map(
9462
- this.registry.activeRecords(context).map((record) => [record.cdpTargetId, record])
9523
+ this.registry.activeRecords(context).map((record2) => [record2.cdpTargetId, record2])
9463
9524
  );
9464
9525
  let adopted = true;
9465
9526
  while (adopted) {
@@ -9468,7 +9529,7 @@ var TargetInventoryService = class {
9468
9529
  if (active.has(target.cdpTargetId) || target.type && target.type !== "page") continue;
9469
9530
  const ancestor = this.findManagedAncestor(target, active, liveTargets);
9470
9531
  if (!ancestor?.managedTabSetId || ancestor.profileContextId && target.profileContextId && ancestor.profileContextId !== target.profileContextId) continue;
9471
- const record = this.registry.registerManaged({
9532
+ const record2 = this.registry.registerManaged({
9472
9533
  principalId: context.principalId,
9473
9534
  workspaceId: context.workspaceId,
9474
9535
  browserInstanceId: this.browserInstanceId,
@@ -9481,9 +9542,9 @@ var TargetInventoryService = class {
9481
9542
  title: target.title,
9482
9543
  url: target.url
9483
9544
  });
9484
- active.set(record.cdpTargetId, record);
9485
- this.notify(this.onTargetAttached, record);
9486
- this.notify(this.onPopup, record);
9545
+ active.set(record2.cdpTargetId, record2);
9546
+ this.notify(this.onTargetAttached, record2);
9547
+ this.notify(this.onPopup, record2);
9487
9548
  adopted = true;
9488
9549
  }
9489
9550
  }
@@ -9520,8 +9581,91 @@ var TargetInventoryService = class {
9520
9581
  }
9521
9582
  };
9522
9583
 
9584
+ // src/services/chrome-profile-identity.ts
9585
+ import { open } from "fs/promises";
9586
+ import { basename as basename2, dirname as dirname2, join as join4, resolve } from "path";
9587
+ var MAX_LOCAL_STATE_BYTES = 16 * 1024 * 1024;
9588
+ var MAX_IDENTITY_TEXT_LENGTH = 4096;
9589
+ var ProfileIdentityError = class extends Error {
9590
+ constructor(code, message) {
9591
+ super(message);
9592
+ this.code = code;
9593
+ this.name = "ProfileIdentityError";
9594
+ }
9595
+ };
9596
+ function record(value) {
9597
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
9598
+ }
9599
+ function identityText(value) {
9600
+ if (typeof value !== "string") return void 0;
9601
+ const normalized = value.trim().slice(0, MAX_IDENTITY_TEXT_LENGTH);
9602
+ return normalized || void 0;
9603
+ }
9604
+ async function readVerifiedChromeProfileIdentity(userDataRoot, reportedProfilePath) {
9605
+ const root = resolve(userDataRoot);
9606
+ const profilePath = resolve(reportedProfilePath);
9607
+ if (dirname2(profilePath) !== root) {
9608
+ throw new ProfileIdentityError(
9609
+ "profile_path_unverified",
9610
+ "Chrome reported a Profile path outside the connected browser user-data root"
9611
+ );
9612
+ }
9613
+ const profileDirectory = basename2(profilePath);
9614
+ if (!profileDirectory || profileDirectory === "." || profileDirectory === "..") {
9615
+ throw new ProfileIdentityError("profile_path_unverified", "Chrome reported an invalid Profile directory");
9616
+ }
9617
+ const localStatePath = join4(root, "Local State");
9618
+ let bytes;
9619
+ let handle;
9620
+ try {
9621
+ handle = await open(localStatePath, "r");
9622
+ const metadata = await handle.stat();
9623
+ if (!metadata.isFile() || metadata.size > MAX_LOCAL_STATE_BYTES) {
9624
+ throw new ProfileIdentityError("local_state_unavailable", "Chrome Local State is missing or too large");
9625
+ }
9626
+ bytes = Buffer.alloc(metadata.size);
9627
+ let offset = 0;
9628
+ while (offset < bytes.length) {
9629
+ const result = await handle.read(bytes, offset, bytes.length - offset, offset);
9630
+ if (result.bytesRead === 0) break;
9631
+ offset += result.bytesRead;
9632
+ }
9633
+ if (offset !== bytes.length) bytes = bytes.subarray(0, offset);
9634
+ } catch (error) {
9635
+ if (error instanceof ProfileIdentityError) throw error;
9636
+ throw new ProfileIdentityError("local_state_unavailable", "Chrome Local State could not be read");
9637
+ } finally {
9638
+ await handle?.close().catch(() => {
9639
+ });
9640
+ }
9641
+ let rootState;
9642
+ try {
9643
+ rootState = record(JSON.parse(bytes.toString("utf8")));
9644
+ } catch {
9645
+ throw new ProfileIdentityError("local_state_unavailable", "Chrome Local State contains invalid JSON");
9646
+ }
9647
+ const profileState = record(rootState?.profile);
9648
+ const infoCache = record(profileState?.info_cache);
9649
+ const entry = record(infoCache?.[profileDirectory]);
9650
+ const profileName = identityText(entry?.name);
9651
+ if (!profileName) {
9652
+ throw new ProfileIdentityError(
9653
+ "profile_metadata_missing",
9654
+ "Chrome Local State has no verified metadata for the reported Profile directory"
9655
+ );
9656
+ }
9657
+ const accountName = identityText(entry?.gaia_name) ?? identityText(entry?.gaia_given_name);
9658
+ const accountEmail = identityText(entry?.user_name);
9659
+ return {
9660
+ profileName,
9661
+ profileDirectory,
9662
+ ...accountName ? { accountName } : {},
9663
+ ...accountEmail ? { accountEmail } : {}
9664
+ };
9665
+ }
9666
+
9523
9667
  // src/services/upload-service.ts
9524
- import { basename as basename2 } from "path";
9668
+ import { basename as basename3 } from "path";
9525
9669
  var FILE_INPUT_BLOCK_REASONS = /* @__PURE__ */ new Set([
9526
9670
  "detached",
9527
9671
  "disabled",
@@ -9547,17 +9691,17 @@ function parseFileInputState(value) {
9547
9691
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
9548
9692
  throw new BrowserPilotError("internal_error", "Chrome returned invalid file input state");
9549
9693
  }
9550
- const record = value;
9551
- if (record.status === "blocked" && FILE_INPUT_BLOCK_REASONS.has(record.reason)) {
9552
- return { status: "blocked", reason: record.reason };
9694
+ const record2 = value;
9695
+ if (record2.status === "blocked" && FILE_INPUT_BLOCK_REASONS.has(record2.reason)) {
9696
+ return { status: "blocked", reason: record2.reason };
9553
9697
  }
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)) {
9698
+ 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
9699
  throw new BrowserPilotError("internal_error", "Chrome returned invalid file input state");
9556
9700
  }
9557
9701
  return {
9558
9702
  status: "ready",
9559
- fileCount: Number(record.fileCount),
9560
- ...record.firstFileName !== void 0 ? { firstFileName: record.firstFileName } : {}
9703
+ fileCount: Number(record2.fileCount),
9704
+ ...record2.firstFileName !== void 0 ? { firstFileName: record2.firstFileName } : {}
9561
9705
  };
9562
9706
  }
9563
9707
  var UploadService = class {
@@ -9644,7 +9788,7 @@ var UploadService = class {
9644
9788
  backendNodeId
9645
9789
  }, this.sessionId);
9646
9790
  if (this.readbackDelayMs > 0) {
9647
- await new Promise((resolve2) => setTimeout(resolve2, this.readbackDelayMs));
9791
+ await new Promise((resolve3) => setTimeout(resolve3, this.readbackDelayMs));
9648
9792
  }
9649
9793
  let after;
9650
9794
  try {
@@ -9666,7 +9810,7 @@ var UploadService = class {
9666
9810
  reason: "target_unavailable"
9667
9811
  };
9668
9812
  }
9669
- const nameMatched = after.firstFileName === basename2(filePath);
9813
+ const nameMatched = after.firstFileName === basename3(filePath);
9670
9814
  const status = after.fileCount === 1 && nameMatched ? "verified" : "mismatch";
9671
9815
  const reason2 = after.fileCount !== 1 ? "file_count_mismatch" : !nameMatched ? "file_name_mismatch" : void 0;
9672
9816
  return {
@@ -9798,39 +9942,39 @@ function publicRule(rule) {
9798
9942
  ...rule.type === "headers" ? { headers: rule.headers } : {}
9799
9943
  };
9800
9944
  }
9801
- function publicRequest(record) {
9802
- const durationMs = record.endedAt === void 0 ? void 0 : Math.max(0, record.endedAt - record.startedAt);
9945
+ function publicRequest(record2) {
9946
+ const durationMs = record2.endedAt === void 0 ? void 0 : Math.max(0, record2.endedAt - record2.startedAt);
9803
9947
  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 } : {},
9948
+ requestId: record2.id,
9949
+ sequence: record2.sequence,
9950
+ method: record2.method,
9951
+ url: record2.url,
9952
+ type: record2.type,
9953
+ requestHeaders: record2.requestHeaders,
9954
+ ...record2.postData !== void 0 ? { postData: record2.postData } : {},
9955
+ postDataTruncated: record2.postDataTruncated,
9956
+ ...record2.status !== void 0 ? { status: record2.status } : {},
9957
+ ...record2.statusText !== void 0 ? { statusText: record2.statusText } : {},
9958
+ ...record2.responseHeaders !== void 0 ? { responseHeaders: record2.responseHeaders } : {},
9959
+ ...record2.mimeType !== void 0 ? { mimeType: record2.mimeType } : {},
9960
+ ...record2.size !== void 0 ? { size: record2.size } : {},
9817
9961
  ...durationMs !== void 0 ? { durationMs } : {},
9818
- ...record.error !== void 0 ? { error: record.error } : {},
9819
- bodyAvailable: record.bodyAvailable
9962
+ ...record2.error !== void 0 ? { error: record2.error } : {},
9963
+ bodyAvailable: record2.bodyAvailable
9820
9964
  };
9821
9965
  }
9822
- function publicRequestSummary(record) {
9823
- const durationMs = record.endedAt === void 0 ? void 0 : Math.max(0, record.endedAt - record.startedAt);
9966
+ function publicRequestSummary(record2) {
9967
+ const durationMs = record2.endedAt === void 0 ? void 0 : Math.max(0, record2.endedAt - record2.startedAt);
9824
9968
  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 } : {},
9969
+ requestId: record2.id,
9970
+ sequence: record2.sequence,
9971
+ method: record2.method,
9972
+ url: record2.url,
9973
+ type: record2.type,
9974
+ ...record2.status !== void 0 ? { status: record2.status } : {},
9975
+ ...record2.size !== void 0 ? { size: record2.size } : {},
9832
9976
  ...durationMs !== void 0 ? { durationMs } : {},
9833
- ...record.error !== void 0 ? { error: record.error } : {}
9977
+ ...record2.error !== void 0 ? { error: record2.error } : {}
9834
9978
  };
9835
9979
  }
9836
9980
  var WorkspaceNetworkController = class {
@@ -9992,15 +10136,15 @@ var WorkspaceNetworkController = class {
9992
10136
  };
9993
10137
  }
9994
10138
  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;
10139
+ const record2 = this.state(workspaceId).requestsById.get(requestId);
10140
+ if (!record2) throw invalidArgument("Network request was not found in this Workspace", "requestId");
10141
+ const base = { request: publicRequest(record2), bodyTruncated: false };
10142
+ if (!includeBody || !record2.bodyAvailable) return base;
9999
10143
  try {
10000
10144
  const result = await this.transport.send(
10001
10145
  "Network.getResponseBody",
10002
- { requestId: record.networkId },
10003
- record.sessionId
10146
+ { requestId: record2.networkId },
10147
+ record2.sessionId
10004
10148
  );
10005
10149
  if (typeof result?.body !== "string") return base;
10006
10150
  const truncated = result.body.length > MAX_BODY_CHARACTERS;
@@ -10008,7 +10152,7 @@ var WorkspaceNetworkController = class {
10008
10152
  ...base,
10009
10153
  body: result.body.slice(0, MAX_BODY_CHARACTERS),
10010
10154
  bodyEncoding: result.base64Encoded === true ? "base64" : "utf8",
10011
- ...record.mimeType ? { mimeType: record.mimeType } : {},
10155
+ ...record2.mimeType ? { mimeType: record2.mimeType } : {},
10012
10156
  bodyTruncated: truncated
10013
10157
  };
10014
10158
  } catch {
@@ -10098,20 +10242,20 @@ var WorkspaceNetworkController = class {
10098
10242
  });
10099
10243
  this.transport.on?.("Network.loadingFinished", (params, sessionId) => {
10100
10244
  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);
10245
+ const record2 = this.findNetworkRequest(sessionId, params?.requestId);
10246
+ if (!record2) return;
10247
+ record2.size = Math.max(0, Math.trunc(Number(params?.encodedDataLength) || 0));
10248
+ record2.endedAt = this.now();
10249
+ record2.bodyAvailable = true;
10250
+ this.refreshRequestSize(record2);
10107
10251
  });
10108
10252
  this.transport.on?.("Network.loadingFailed", (params, sessionId) => {
10109
10253
  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);
10254
+ const record2 = this.findNetworkRequest(sessionId, params?.requestId);
10255
+ if (!record2) return;
10256
+ record2.error = boundedString2(params?.errorText, 4096);
10257
+ record2.endedAt = this.now();
10258
+ this.refreshRequestSize(record2);
10115
10259
  });
10116
10260
  }
10117
10261
  async handleAuthRequired(params, sessionId) {
@@ -10179,7 +10323,7 @@ var WorkspaceNetworkController = class {
10179
10323
  this.publishResponse(redirected);
10180
10324
  }
10181
10325
  const rawPostData = typeof params?.request?.postData === "string" ? params.request.postData : void 0;
10182
- const record = {
10326
+ const record2 = {
10183
10327
  id: this.requestIdFactory(),
10184
10328
  sequence: state.nextSequence++,
10185
10329
  workspaceId: session.workspaceId,
@@ -10198,55 +10342,55 @@ var WorkspaceNetworkController = class {
10198
10342
  bodyAvailable: false,
10199
10343
  retainedByteSize: 0
10200
10344
  };
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);
10345
+ record2.retainedByteSize = Buffer.byteLength(JSON.stringify(publicRequest(record2)), "utf8");
10346
+ state.requests.push(record2);
10347
+ state.requestBytes += record2.retainedByteSize;
10348
+ state.requestsById.set(record2.id, record2);
10349
+ state.requestsByNetworkKey.set(key, record2);
10206
10350
  this.compactRequests(state);
10207
10351
  this.safePublish({
10208
- workspaceId: record.workspaceId,
10209
- leaseId: record.leaseId,
10210
- targetId: record.targetId,
10211
- browserConnectionGeneration: record.browserConnectionGeneration,
10352
+ workspaceId: record2.workspaceId,
10353
+ leaseId: record2.leaseId,
10354
+ targetId: record2.targetId,
10355
+ browserConnectionGeneration: record2.browserConnectionGeneration,
10212
10356
  type: "network.request",
10213
10357
  sensitivity: "browser_data",
10214
10358
  payload: {
10215
- requestId: record.id,
10216
- method: record.method,
10217
- url: sanitizedEventUrl(record.url),
10218
- type: record.type
10359
+ requestId: record2.id,
10360
+ method: record2.method,
10361
+ url: sanitizedEventUrl(record2.url),
10362
+ type: record2.type
10219
10363
  }
10220
10364
  });
10221
10365
  }
10222
10366
  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);
10367
+ const record2 = this.findNetworkRequest(sessionId, params?.requestId);
10368
+ if (!record2) return;
10369
+ this.applyResponse(record2, params?.response);
10370
+ this.refreshRequestSize(record2);
10371
+ this.publishResponse(record2);
10372
+ }
10373
+ applyResponse(record2, response) {
10374
+ record2.status = validStatus(response?.status);
10375
+ record2.statusText = boundedString2(response?.statusText, 4096);
10376
+ record2.responseHeaders = headersFromCdp(response?.headers);
10377
+ record2.mimeType = boundedString2(response?.mimeType, 256);
10378
+ }
10379
+ publishResponse(record2) {
10380
+ const hint = accessBlockedAgentHint(record2.type, record2.status);
10237
10381
  this.safePublish({
10238
- workspaceId: record.workspaceId,
10239
- leaseId: record.leaseId,
10240
- targetId: record.targetId,
10241
- browserConnectionGeneration: record.browserConnectionGeneration,
10382
+ workspaceId: record2.workspaceId,
10383
+ leaseId: record2.leaseId,
10384
+ targetId: record2.targetId,
10385
+ browserConnectionGeneration: record2.browserConnectionGeneration,
10242
10386
  type: "network.response",
10243
10387
  sensitivity: "browser_data",
10244
10388
  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 } : {},
10389
+ requestId: record2.id,
10390
+ method: record2.method,
10391
+ url: sanitizedEventUrl(record2.url),
10392
+ type: record2.type,
10393
+ ...record2.status !== void 0 ? { status: record2.status } : {},
10250
10394
  ...hint ? { hints: [hint] } : {}
10251
10395
  }
10252
10396
  });
@@ -10256,12 +10400,12 @@ var WorkspaceNetworkController = class {
10256
10400
  if (!session || typeof requestId !== "string") return void 0;
10257
10401
  return this.workspaces.get(session.workspaceId)?.requestsByNetworkKey.get(networkKey(sessionId, requestId));
10258
10402
  }
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;
10403
+ refreshRequestSize(record2) {
10404
+ const state = this.workspaces.get(record2.workspaceId);
10405
+ if (!state || state.requestsById.get(record2.id) !== record2) return;
10406
+ const nextSize = Buffer.byteLength(JSON.stringify(publicRequest(record2)), "utf8");
10407
+ state.requestBytes += nextSize - record2.retainedByteSize;
10408
+ record2.retainedByteSize = nextSize;
10265
10409
  this.compactRequests(state);
10266
10410
  }
10267
10411
  compactRequests(state) {
@@ -10301,6 +10445,7 @@ var BASE_BROWSER_TOOL_NAMES = [
10301
10445
  "browser.discover",
10302
10446
  "browser.connect",
10303
10447
  "browser.profiles.list",
10448
+ "browser.profiles.identify",
10304
10449
  "browser.profiles.select",
10305
10450
  "browser.open",
10306
10451
  "browser.observe",
@@ -10365,20 +10510,20 @@ function stateFingerprint(value) {
10365
10510
  return createHash4("sha256").update(JSON.stringify(value) ?? "undefined").digest("hex");
10366
10511
  }
10367
10512
  function elementAddress(value) {
10368
- const record = asRecord(value);
10369
- if (typeof record.selector === "string") return { selector: record.selector };
10513
+ const record2 = asRecord(value);
10514
+ if (typeof record2.selector === "string") return { selector: record2.selector };
10370
10515
  return {
10371
- observationId: record.observationId,
10372
- ref: Number(record.ref)
10516
+ observationId: record2.observationId,
10517
+ ref: Number(record2.ref)
10373
10518
  };
10374
10519
  }
10375
10520
  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 };
10521
+ const record2 = asRecord(value);
10522
+ if (record2.by === "index") return { by: "index", index: Number(record2.index) };
10523
+ if (record2.by === "label") {
10524
+ return { by: "label", label: String(record2.label), exact: record2.exact !== false };
10380
10525
  }
10381
- return { by: "value", value: String(record.value), exact: record.exact !== false };
10526
+ return { by: "value", value: String(record2.value), exact: record2.exact !== false };
10382
10527
  }
10383
10528
  function optionMatches(option, choice) {
10384
10529
  if (choice.by === "index") return option.index === choice.index;
@@ -10417,6 +10562,7 @@ var BrowserToolService = class {
10417
10562
  }
10418
10563
  this.loadWaiter = options.loadWaiter ?? waitForLoad;
10419
10564
  this.connectBrowser = options.connectBrowser;
10565
+ this.readProfileIdentity = options.readProfileIdentity ?? readVerifiedChromeProfileIdentity;
10420
10566
  this.watchdogs = new BrowserWatchdogService((event) => this.publishEvent(event), {
10421
10567
  dialogTimeoutMs: options.dialogTimeoutMs,
10422
10568
  noProgressThreshold: options.noProgressThreshold
@@ -10433,7 +10579,7 @@ var BrowserToolService = class {
10433
10579
  transport,
10434
10580
  binding.instance.id,
10435
10581
  () => ({
10436
- profileIdentity: binding.instance.profilePath,
10582
+ profileIdentity: binding.instance.userDataRoot,
10437
10583
  connectionGeneration: binding.instance.connectionGeneration
10438
10584
  }),
10439
10585
  {
@@ -10524,6 +10670,7 @@ var BrowserToolService = class {
10524
10670
  navigationTimeoutMs;
10525
10671
  loadWaiter;
10526
10672
  connectBrowser;
10673
+ readProfileIdentity;
10527
10674
  eventPublisher;
10528
10675
  setEventPublisher(publisher) {
10529
10676
  this.eventPublisher = publisher;
@@ -10590,11 +10737,13 @@ var BrowserToolService = class {
10590
10737
  const args = asRecord(argsValue);
10591
10738
  switch (definition.name) {
10592
10739
  case "browser.discover":
10593
- return this.discover();
10740
+ return this.discover(context);
10594
10741
  case "browser.connect":
10595
10742
  return this.connect(context, args);
10596
10743
  case "browser.profiles.list":
10597
10744
  return this.listProfiles(context);
10745
+ case "browser.profiles.identify":
10746
+ return this.identifyProfiles(context, args);
10598
10747
  case "browser.profiles.select":
10599
10748
  return this.selectProfile(context, args);
10600
10749
  case "browser.tabs.list":
@@ -10673,6 +10822,9 @@ var BrowserToolService = class {
10673
10822
  }
10674
10823
  actorKey(context, definition, argsValue) {
10675
10824
  const browserId = context.browser.instance.id;
10825
+ if (definition.name === "browser.profiles.identify") {
10826
+ return `${browserId}\0profile-identify`;
10827
+ }
10676
10828
  if (definition.name.startsWith("browser.dialogs.")) {
10677
10829
  return `${browserId}\0dialogs\0${context.workspace?.id ?? context.connection.id}`;
10678
10830
  }
@@ -10735,14 +10887,15 @@ var BrowserToolService = class {
10735
10887
  }
10736
10888
  this.deleteDialogs((dialog) => dialog.workspaceId === workspace.id);
10737
10889
  }
10738
- discover() {
10890
+ discover(context) {
10739
10891
  const candidate = this.binding.candidate;
10892
+ const userDataRoot = candidate.userDataRoot ?? candidate.profile;
10740
10893
  return asJson2({
10741
10894
  browsers: [{
10742
10895
  id: candidate.id,
10743
10896
  product: candidate.product,
10744
10897
  ...candidate.channel !== void 0 ? { channel: candidate.channel } : {},
10745
- ...candidate.profile !== void 0 ? { profile: candidate.profile } : {},
10898
+ ...userDataRoot ? context.connection.protocol.minor >= 3 ? { userDataRoot } : { profile: userDataRoot } : {},
10746
10899
  processState: candidate.processState ?? (candidate.state === "ready" ? "running" : "unknown"),
10747
10900
  remoteDebuggingState: candidate.remoteDebuggingState ?? (candidate.state === "ready" ? "enabled" : candidate.state === "disconnected" ? "stale" : "disabled"),
10748
10901
  authorizationState: candidate.authorizationState ?? (candidate.state === "ready" ? "authorized" : candidate.state === "authorization_required" ? "required" : "not_applicable"),
@@ -10765,7 +10918,10 @@ var BrowserToolService = class {
10765
10918
  context: { workspaceId: workspace.id }
10766
10919
  });
10767
10920
  }
10921
+ this.markDispatched(context);
10768
10922
  await this.connectBrowser();
10923
+ } else {
10924
+ this.markDispatched(context);
10769
10925
  }
10770
10926
  if (this.binding.instance.state !== "connected") {
10771
10927
  throw new BrowserPilotError("browser_disconnected", "Browser connection did not become ready", {
@@ -10773,7 +10929,6 @@ var BrowserToolService = class {
10773
10929
  context: { workspaceId: workspace.id }
10774
10930
  });
10775
10931
  }
10776
- this.markDispatched(context);
10777
10932
  return asJson2({
10778
10933
  workspaceId: workspace.id,
10779
10934
  leaseId: lease.id,
@@ -10789,7 +10944,7 @@ var BrowserToolService = class {
10789
10944
  return asJson2({
10790
10945
  workspaceId: inventoryContext.workspaceId,
10791
10946
  leaseId: inventoryContext.leaseId,
10792
- targets
10947
+ targets: context.connection.protocol.minor >= 3 ? targets.map(({ active, ...target }) => ({ ...target, selected: active })) : targets
10793
10948
  });
10794
10949
  }
10795
10950
  async listProfiles(context) {
@@ -10799,21 +10954,67 @@ var BrowserToolService = class {
10799
10954
  return asJson2({
10800
10955
  workspaceId: inventoryContext.workspaceId,
10801
10956
  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
- }))
10957
+ profiles: profiles.map((profile) => this.profileResult(context, profile, targets))
10958
+ });
10959
+ }
10960
+ async identifyProfiles(context, args) {
10961
+ const inventoryContext = this.inventoryContext(context);
10962
+ const targets = await this.inventory.list(inventoryContext, "all");
10963
+ const requestedId = args.profileContextId;
10964
+ const profiles = requestedId ? [this.profileContexts.resolve(requestedId, inventoryContext.browserConnectionGeneration)] : this.profileContexts.list(inventoryContext.browserConnectionGeneration);
10965
+ const refresh = args.refresh === true;
10966
+ for (const profile of profiles) {
10967
+ if (!refresh && profile.identityStatus !== "unidentified") continue;
10968
+ this.markDispatched(context);
10969
+ try {
10970
+ const identity = await this.probeProfileIdentity(profile);
10971
+ this.profileContexts.setVerifiedIdentity(
10972
+ profile.id,
10973
+ inventoryContext.browserConnectionGeneration,
10974
+ identity
10975
+ );
10976
+ } catch (error) {
10977
+ if (!(error instanceof ProfileIdentityError)) throw error;
10978
+ this.profileContexts.setIdentityUnavailable(
10979
+ profile.id,
10980
+ inventoryContext.browserConnectionGeneration,
10981
+ error.code
10982
+ );
10983
+ }
10984
+ }
10985
+ const current = this.profileContexts.list(inventoryContext.browserConnectionGeneration);
10986
+ return asJson2({
10987
+ workspaceId: inventoryContext.workspaceId,
10988
+ leaseId: inventoryContext.leaseId,
10989
+ profiles: current.filter((profile) => !requestedId || profile.id === requestedId).map((profile) => this.profileResult(context, profile, targets))
10815
10990
  });
10816
10991
  }
10992
+ profileResult(context, profile, targets) {
10993
+ const identity = {};
10994
+ if (context.connection.protocol.minor >= 3) {
10995
+ identity.identityStatus = profile.identityStatus;
10996
+ if (profile.profileName) identity.profileName = profile.profileName;
10997
+ if (profile.accountName) identity.accountName = profile.accountName;
10998
+ if (profile.accountEmail) identity.accountEmail = profile.accountEmail;
10999
+ if (profile.profileDirectory) identity.profileDirectory = profile.profileDirectory;
11000
+ if (profile.identityErrorCode) identity.identityErrorCode = profile.identityErrorCode;
11001
+ } else if (profile.displayName) {
11002
+ identity.displayName = profile.displayName;
11003
+ }
11004
+ return {
11005
+ profileContextId: profile.id,
11006
+ label: profile.label,
11007
+ ...identity,
11008
+ tabCount: profile.tabCount,
11009
+ eligibleTabCount: profile.eligibleTabCount,
11010
+ selected: context.workspace?.selectedProfileContextId === profile.id,
11011
+ representativeTabs: targets.filter((target) => target.profileContextId === profile.id).slice(0, 3).map((target) => ({
11012
+ targetId: target.targetId,
11013
+ title: target.title,
11014
+ url: target.url
11015
+ }))
11016
+ };
11017
+ }
10817
11018
  async selectProfile(context, args) {
10818
11019
  const inventoryContext = this.inventoryContext(context);
10819
11020
  await this.inventory.refresh(inventoryContext);
@@ -10829,7 +11030,14 @@ var BrowserToolService = class {
10829
11030
  leaseId: inventoryContext.leaseId,
10830
11031
  profileContextId: profile.id,
10831
11032
  label: profile.label,
10832
- ...profile.displayName ? { displayName: profile.displayName } : {}
11033
+ ...context.connection.protocol.minor >= 3 ? {
11034
+ identityStatus: profile.identityStatus,
11035
+ ...profile.profileName ? { profileName: profile.profileName } : {},
11036
+ ...profile.accountName ? { accountName: profile.accountName } : {},
11037
+ ...profile.accountEmail ? { accountEmail: profile.accountEmail } : {},
11038
+ ...profile.profileDirectory ? { profileDirectory: profile.profileDirectory } : {},
11039
+ ...profile.identityErrorCode ? { identityErrorCode: profile.identityErrorCode } : {}
11040
+ } : profile.displayName ? { displayName: profile.displayName } : {}
10833
11041
  });
10834
11042
  }
10835
11043
  async switchTab(context, args) {
@@ -10838,10 +11046,10 @@ var BrowserToolService = class {
10838
11046
  await this.inventory.refresh(inventoryContext);
10839
11047
  this.markDispatched(context);
10840
11048
  const resolved = await this.inventory.activate(inventoryContext, targetId);
10841
- const record = this.registry.get(inventoryContext, targetId);
11049
+ const record2 = this.registry.get(inventoryContext, targetId);
10842
11050
  await this.ensureSession(inventoryContext, targetId, resolved.cdpTargetId);
10843
- this.selectWorkspaceProfile(context, record.profileContextId);
10844
- return asJson2(this.targetResult(context, targetId, record.url));
11051
+ this.selectWorkspaceProfile(context, record2.profileContextId);
11052
+ return asJson2(this.targetResult(context, targetId, record2.url));
10845
11053
  }
10846
11054
  async closeTab(context) {
10847
11055
  const inventoryContext = this.inventoryContext(context);
@@ -11079,8 +11287,8 @@ var BrowserToolService = class {
11079
11287
  ...session.activeFrame ? { frameId: session.activeFrame.cdpFrameId } : {}
11080
11288
  }
11081
11289
  ).locate(args.selector);
11082
- const record = this.registry.get(this.inventoryContext(context), targetId);
11083
- return asJson2({ ...this.targetResult(context, targetId, record.url), ...location });
11290
+ const record2 = this.registry.get(this.inventoryContext(context), targetId);
11291
+ return asJson2({ ...this.targetResult(context, targetId, record2.url), ...location });
11084
11292
  }
11085
11293
  async read(context, args) {
11086
11294
  const targetId = this.requireTargetId(context);
@@ -11210,8 +11418,8 @@ var BrowserToolService = class {
11210
11418
  address,
11211
11419
  (objectId) => service.inspectObject(objectId)
11212
11420
  );
11213
- const record = this.registry.get(this.inventoryContext(context), targetId);
11214
- return asJson2({ ...this.targetResult(context, targetId, record.url), ...result });
11421
+ const record2 = this.registry.get(this.inventoryContext(context), targetId);
11422
+ return asJson2({ ...this.targetResult(context, targetId, record2.url), ...result });
11215
11423
  }
11216
11424
  async selectDropdown(context, args) {
11217
11425
  const targetId = this.requireTargetId(context);
@@ -11457,7 +11665,7 @@ var BrowserToolService = class {
11457
11665
  );
11458
11666
  }
11459
11667
  const scale = this.previewScale(media.width, media.height);
11460
- const record = this.registry.get(this.inventoryContext(context), targetId);
11668
+ const record2 = this.registry.get(this.inventoryContext(context), targetId);
11461
11669
  if (scale === void 0) {
11462
11670
  const artifact2 = await this.createArtifact(context, {
11463
11671
  kind: "screenshot",
@@ -11467,7 +11675,7 @@ var BrowserToolService = class {
11467
11675
  ...media.height !== void 0 ? { height: media.height } : {}
11468
11676
  });
11469
11677
  return asJson2({
11470
- ...this.targetResult(context, targetId, record.url),
11678
+ ...this.targetResult(context, targetId, record2.url),
11471
11679
  artifact: artifact2,
11472
11680
  ...annotationPlan ? { annotationCount: annotationPlan.annotations.length } : {}
11473
11681
  });
@@ -11489,7 +11697,7 @@ var BrowserToolService = class {
11489
11697
  ...previewMedia.height !== void 0 ? { height: previewMedia.height } : {}
11490
11698
  });
11491
11699
  return asJson2({
11492
- ...this.targetResult(context, targetId, record.url),
11700
+ ...this.targetResult(context, targetId, record2.url),
11493
11701
  artifact: artifact2,
11494
11702
  ...annotationPlan ? { annotationCount: annotationPlan.annotations.length } : {}
11495
11703
  });
@@ -11511,7 +11719,7 @@ var BrowserToolService = class {
11511
11719
  previewOf: artifact.id
11512
11720
  });
11513
11721
  return asJson2({
11514
- ...this.targetResult(context, targetId, record.url),
11722
+ ...this.targetResult(context, targetId, record2.url),
11515
11723
  artifact,
11516
11724
  preview,
11517
11725
  ...annotationPlan ? { annotationCount: annotationPlan.annotations.length } : {}
@@ -11532,15 +11740,15 @@ var BrowserToolService = class {
11532
11740
  mimeType: media.mimeType,
11533
11741
  bytes: media.bytes
11534
11742
  });
11535
- const record = this.registry.get(this.inventoryContext(context), targetId);
11536
- return asJson2({ ...this.targetResult(context, targetId, record.url), artifact });
11743
+ const record2 = this.registry.get(this.inventoryContext(context), targetId);
11744
+ return asJson2({ ...this.targetResult(context, targetId, record2.url), artifact });
11537
11745
  }
11538
11746
  async cookies(context, args) {
11539
11747
  const targetId = this.requireTargetId(context);
11540
11748
  const session = await this.resolveTargetSession(context, targetId, "cookies.read");
11541
11749
  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 });
11750
+ const record2 = this.registry.get(this.inventoryContext(context), targetId);
11751
+ return asJson2({ ...this.targetResult(context, targetId, record2.url), cookies });
11544
11752
  }
11545
11753
  async setAuth(context, args) {
11546
11754
  const { workspace, lease } = this.requireWorkspaceContext(context);
@@ -11620,7 +11828,7 @@ var BrowserToolService = class {
11620
11828
  "expression"
11621
11829
  );
11622
11830
  }
11623
- const record = this.registry.get(this.inventoryContext(context), targetId);
11831
+ const record2 = this.registry.get(this.inventoryContext(context), targetId);
11624
11832
  let value = result?.value ?? result?.unserializableValue ?? null;
11625
11833
  let truncated = false;
11626
11834
  const serialized = JSON.stringify(value);
@@ -11628,7 +11836,7 @@ var BrowserToolService = class {
11628
11836
  value = serialized.slice(0, 1e6);
11629
11837
  truncated = true;
11630
11838
  }
11631
- return asJson2({ ...this.targetResult(context, targetId, record.url), value, truncated });
11839
+ return asJson2({ ...this.targetResult(context, targetId, record2.url), value, truncated });
11632
11840
  }
11633
11841
  listDialogs(context) {
11634
11842
  const { workspace, lease } = this.requireWorkspaceContext(context);
@@ -11736,6 +11944,89 @@ var BrowserToolService = class {
11736
11944
  throw error;
11737
11945
  }
11738
11946
  }
11947
+ async probeProfileIdentity(profile) {
11948
+ let targetId;
11949
+ let sessionId;
11950
+ let failure;
11951
+ try {
11952
+ try {
11953
+ targetId = await this.tryCreateManagedTarget({
11954
+ url: "about:blank",
11955
+ newWindow: true,
11956
+ ...profile.cdpBrowserContextId ? { browserContextId: profile.cdpBrowserContextId } : {}
11957
+ }, profile);
11958
+ } catch (error) {
11959
+ if (!(error instanceof ManagedTargetCreationRejectedError)) throw error;
11960
+ }
11961
+ if (!targetId) targetId = await this.createManagedProfileWindow(profile);
11962
+ const attached = await this.transport.send("Target.attachToTarget", {
11963
+ targetId,
11964
+ flatten: true
11965
+ });
11966
+ if (typeof attached?.sessionId !== "string") {
11967
+ throw new ProfileIdentityError(
11968
+ "profile_path_unavailable",
11969
+ "Chrome could not attach to the temporary Profile identity target"
11970
+ );
11971
+ }
11972
+ const activeSessionId = attached.sessionId;
11973
+ sessionId = activeSessionId;
11974
+ await this.transport.send("Page.enable", {}, activeSessionId);
11975
+ const navigation = await this.transport.send("Page.navigate", {
11976
+ url: "chrome://version/"
11977
+ }, activeSessionId);
11978
+ if (navigation?.errorText) {
11979
+ throw new ProfileIdentityError(
11980
+ "profile_path_unavailable",
11981
+ "Chrome could not open its version page for Profile identification"
11982
+ );
11983
+ }
11984
+ await this.loadWaiter(this.transport, activeSessionId, this.navigationTimeoutMs);
11985
+ const evaluated = await this.transport.send("Runtime.evaluate", {
11986
+ expression: `(() => {
11987
+ /* browser-pilot.profile-path.v1 */
11988
+ const value = document.querySelector('#profile_path')?.textContent;
11989
+ return typeof value === 'string' ? value.trim() : '';
11990
+ })()`,
11991
+ returnByValue: true
11992
+ }, activeSessionId);
11993
+ const reportedProfilePath = evaluated?.result?.value;
11994
+ if (typeof reportedProfilePath !== "string" || !reportedProfilePath.trim()) {
11995
+ throw new ProfileIdentityError(
11996
+ "profile_path_unavailable",
11997
+ "Chrome did not expose a Profile path on its version page"
11998
+ );
11999
+ }
12000
+ return await this.readProfileIdentity(
12001
+ this.binding.instance.userDataRoot,
12002
+ reportedProfilePath
12003
+ );
12004
+ } catch (error) {
12005
+ failure = error;
12006
+ throw error;
12007
+ } finally {
12008
+ if (sessionId) {
12009
+ await this.transport.send("Target.detachFromTarget", { sessionId }).catch(() => {
12010
+ });
12011
+ }
12012
+ if (targetId && !await this.closeTargetAndVerify(targetId)) {
12013
+ throw new BrowserPilotError(
12014
+ "unknown_outcome",
12015
+ "Temporary Profile identity target could not be safely closed",
12016
+ {
12017
+ retryable: true,
12018
+ context: { profileContextId: profile.id },
12019
+ remediation: {
12020
+ code: "inspect_profile_tabs",
12021
+ message: "List tabs and close any leftover chrome://version window before retrying.",
12022
+ actionRequired: true
12023
+ },
12024
+ ...failure !== void 0 ? { cause: failure } : {}
12025
+ }
12026
+ );
12027
+ }
12028
+ }
12029
+ }
11739
12030
  async tryCreateManagedTarget(params, profile) {
11740
12031
  let targetId;
11741
12032
  try {
@@ -11770,7 +12061,7 @@ var BrowserToolService = class {
11770
12061
  if (Array.isArray(current?.targetInfos) && !current.targetInfos.some((target) => target?.targetId === targetId)) return true;
11771
12062
  } catch {
11772
12063
  }
11773
- if (attempt < 19) await new Promise((resolve2) => setTimeout(resolve2, 50));
12064
+ if (attempt < 19) await new Promise((resolve3) => setTimeout(resolve3, 50));
11774
12065
  }
11775
12066
  return false;
11776
12067
  }
@@ -11918,7 +12209,7 @@ var BrowserToolService = class {
11918
12209
  candidateTargetId = candidate.targetId;
11919
12210
  break;
11920
12211
  }
11921
- await new Promise((resolve2) => setTimeout(resolve2, 50));
12212
+ await new Promise((resolve3) => setTimeout(resolve3, 50));
11922
12213
  }
11923
12214
  if (!candidateTargetId) {
11924
12215
  throw this.managedTargetUnknownOutcome(profile);
@@ -11957,7 +12248,7 @@ var BrowserToolService = class {
11957
12248
  if (result?.result?.value !== void 0) return false;
11958
12249
  } catch {
11959
12250
  }
11960
- if (attempt < 9) await new Promise((resolve2) => setTimeout(resolve2, 25));
12251
+ if (attempt < 9) await new Promise((resolve3) => setTimeout(resolve3, 25));
11961
12252
  }
11962
12253
  return false;
11963
12254
  } catch {
@@ -12022,8 +12313,6 @@ var BrowserToolService = class {
12022
12313
  await this.transport.send("Page.enable", {}, attached.sessionId).catch(() => {
12023
12314
  });
12024
12315
  await this.downloads?.attachSession(session);
12025
- await this.transport.send("Runtime.evaluate", { expression: INJECT_BORDER }, attached.sessionId).catch(() => {
12026
- });
12027
12316
  return session;
12028
12317
  } catch (error) {
12029
12318
  this.retireSession(key, session);
@@ -12040,7 +12329,7 @@ var BrowserToolService = class {
12040
12329
  });
12041
12330
  const snapshot = afterAction ? await service.observeAfterAction(limit) : await service.observe(limit);
12042
12331
  const identity = await this.observationContextIdentity(session);
12043
- const record = this.observations.create({
12332
+ const record2 = this.observations.create({
12044
12333
  workspaceId: context.workspace.id,
12045
12334
  leaseId: context.lease.id,
12046
12335
  targetId,
@@ -12063,7 +12352,7 @@ var BrowserToolService = class {
12063
12352
  previous && previous.frameId === frameId ? previous.guidance : void 0
12064
12353
  );
12065
12354
  this.guidanceBySession.set(session.sessionId, { frameId, guidance: snapshot.guidance });
12066
- return { snapshot, record, hints };
12355
+ return { snapshot, record: record2, hints };
12067
12356
  }
12068
12357
  async resolveObservation(context, targetId, session, observationId, ref) {
12069
12358
  const cdpSessionId = this.activeCdpSessionId(session);
@@ -12559,12 +12848,12 @@ var BrowserToolService = class {
12559
12848
  if (!this.artifactStore || !context.workspace) {
12560
12849
  throw new BrowserPilotError("internal_error", "Browser Artifact storage is unavailable");
12561
12850
  }
12562
- const record = await this.artifactStore.create({
12851
+ const record2 = await this.artifactStore.create({
12563
12852
  workspaceId: context.workspace.id,
12564
12853
  sensitivity: "browser_data",
12565
12854
  ...input
12566
12855
  });
12567
- return record.descriptor;
12856
+ return record2.descriptor;
12568
12857
  }
12569
12858
  previewScale(width, height) {
12570
12859
  if (!width || !height) return void 0;
@@ -13208,10 +13497,10 @@ import {
13208
13497
  writeFile
13209
13498
  } from "fs/promises";
13210
13499
  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";
13500
+ import { basename as basename4, dirname as dirname3, extname, isAbsolute as isAbsolute4, join as join5, relative, resolve as resolve2, sep } from "path";
13212
13501
  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 };
13502
+ function clone2(record2) {
13503
+ return { descriptor: { ...record2.descriptor }, path: record2.path };
13215
13504
  }
13216
13505
  function inferredMimeType(path) {
13217
13506
  const known = {
@@ -13246,7 +13535,7 @@ var ArtifactStore = class {
13246
13535
  initialized = false;
13247
13536
  mutationTail = Promise.resolve();
13248
13537
  constructor(options = {}) {
13249
- this.directory = resolve(options.directory ?? ARTIFACT_DIR);
13538
+ this.directory = resolve2(options.directory ?? ARTIFACT_DIR);
13250
13539
  this.ttlMs = options.ttlMs ?? 15 * 6e4;
13251
13540
  this.retainedTtlMs = options.retainedTtlMs ?? 24 * 60 * 6e4;
13252
13541
  this.maxArtifactBytes = options.maxArtifactBytes ?? 100 * 1024 * 1024;
@@ -13302,17 +13591,24 @@ var ArtifactStore = class {
13302
13591
  async get(workspaceId, artifactId) {
13303
13592
  return this.withLock(async () => clone2(await this.requireRecordUnlocked(workspaceId, artifactId)));
13304
13593
  }
13594
+ async list(workspaceId, kinds) {
13595
+ return this.withLock(async () => {
13596
+ await this.sweepUnlocked();
13597
+ const selectedKinds = kinds ? new Set(kinds) : void 0;
13598
+ return [...this.records.values()].filter((record2) => record2.descriptor.workspaceId === workspaceId && (!selectedKinds || selectedKinds.has(record2.descriptor.kind))).sort((left, right) => right.descriptor.createdAt - left.descriptor.createdAt).map((record2) => ({ ...record2.descriptor }));
13599
+ });
13600
+ }
13305
13601
  async export(workspaceId, artifactId, destination, overwrite = false) {
13306
13602
  if (!isAbsolute4(destination)) throw invalidArgument("Artifact export path must be absolute", "path");
13307
- const target = resolve(destination);
13603
+ const target = resolve2(destination);
13308
13604
  if (this.isInsideStore(target)) {
13309
13605
  throw invalidArgument("Artifact export path must be outside Broker storage", "path");
13310
13606
  }
13311
13607
  return this.withLock(async () => {
13312
- const record = await this.requireRecordUnlocked(workspaceId, artifactId);
13608
+ const record2 = await this.requireRecordUnlocked(workspaceId, artifactId);
13313
13609
  let canonicalParent;
13314
13610
  try {
13315
- canonicalParent = await realpath(dirname2(target));
13611
+ canonicalParent = await realpath(dirname3(target));
13316
13612
  } catch (cause) {
13317
13613
  throw new BrowserPilotError("invalid_argument", "Artifact export directory is not accessible", {
13318
13614
  context: { field: "path" },
@@ -13321,7 +13617,7 @@ var ArtifactStore = class {
13321
13617
  });
13322
13618
  }
13323
13619
  const canonicalStore = await realpath(this.directory);
13324
- if (this.isWithin(canonicalStore, join4(canonicalParent, basename3(target)))) {
13620
+ if (this.isWithin(canonicalStore, join5(canonicalParent, basename4(target)))) {
13325
13621
  throw invalidArgument("Artifact export path must be outside Broker storage", "path");
13326
13622
  }
13327
13623
  if (overwrite) {
@@ -13331,7 +13627,7 @@ var ArtifactStore = class {
13331
13627
  }
13332
13628
  }
13333
13629
  await copyFile(
13334
- record.path,
13630
+ record2.path,
13335
13631
  target,
13336
13632
  overwrite ? 0 : fsConstants.COPYFILE_EXCL
13337
13633
  ).catch((cause) => {
@@ -13343,7 +13639,7 @@ var ArtifactStore = class {
13343
13639
  });
13344
13640
  await chmod(target, 384).catch(() => {
13345
13641
  });
13346
- return { artifact: { ...record.descriptor }, path: target };
13642
+ return { artifact: { ...record2.descriptor }, path: target };
13347
13643
  });
13348
13644
  }
13349
13645
  async retain(workspaceId, artifactId) {
@@ -13357,21 +13653,21 @@ var ArtifactStore = class {
13357
13653
  }
13358
13654
  async release(workspaceId, artifactId) {
13359
13655
  await this.withLock(async () => {
13360
- const record = this.records.get(artifactId);
13361
- if (!record || record.descriptor.workspaceId !== workspaceId) return;
13656
+ const record2 = this.records.get(artifactId);
13657
+ if (!record2 || record2.descriptor.workspaceId !== workspaceId) return;
13362
13658
  this.records.delete(artifactId);
13363
13659
  this.expired.delete(artifactId);
13364
- await this.removeStoredFile(record.path);
13660
+ await this.removeStoredFile(record2.path);
13365
13661
  });
13366
13662
  }
13367
13663
  async releaseWorkspace(workspaceId) {
13368
13664
  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);
13665
+ const records = [...this.records.values()].filter((record2) => record2.descriptor.workspaceId === workspaceId);
13666
+ for (const record2 of records) this.records.delete(record2.descriptor.id);
13371
13667
  for (const [artifactId, tombstone] of this.expired) {
13372
13668
  if (tombstone.workspaceId === workspaceId) this.expired.delete(artifactId);
13373
13669
  }
13374
- await Promise.all(records.map((record) => this.removeStoredFile(record.path)));
13670
+ await Promise.all(records.map((record2) => this.removeStoredFile(record2.path)));
13375
13671
  });
13376
13672
  }
13377
13673
  async sweep() {
@@ -13382,7 +13678,7 @@ var ArtifactStore = class {
13382
13678
  const records = [...this.records.values()];
13383
13679
  this.records.clear();
13384
13680
  this.expired.clear();
13385
- await Promise.all(records.map((record) => this.removeStoredFile(record.path)));
13681
+ await Promise.all(records.map((record2) => this.removeStoredFile(record2.path)));
13386
13682
  });
13387
13683
  }
13388
13684
  size() {
@@ -13399,7 +13695,7 @@ var ArtifactStore = class {
13399
13695
  }
13400
13696
  async ingestFile(input) {
13401
13697
  if (!isAbsolute4(input.sourcePath)) throw invalidArgument("Artifact import path must be absolute", "path");
13402
- const source = resolve(input.sourcePath);
13698
+ const source = resolve2(input.sourcePath);
13403
13699
  if (this.isInsideStore(source)) {
13404
13700
  throw invalidArgument("Artifact import path must be outside Broker storage", "path");
13405
13701
  }
@@ -13447,8 +13743,8 @@ var ArtifactStore = class {
13447
13743
  }
13448
13744
  if (!sourceInfo.isFile()) throw invalidArgument("Artifact import path must identify a regular file", "path");
13449
13745
  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)) {
13746
+ const fileName = input.fileName ?? basename4(source);
13747
+ if (!fileName || fileName === "." || fileName === ".." || basename4(fileName) !== fileName || fileName.length > 4096 || /[\u0000-\u001f\u007f]/.test(fileName)) {
13452
13748
  throw invalidArgument("Artifact import filename is invalid or too long", "path");
13453
13749
  }
13454
13750
  const resolvedMimeType = input.mimeType ?? inferredMimeType(fileName);
@@ -13456,8 +13752,8 @@ var ArtifactStore = class {
13456
13752
  throw invalidArgument("Artifact MIME type is invalid or too long", "mimeType");
13457
13753
  }
13458
13754
  const id = this.nextArtifactId();
13459
- const storageDirectory = join4(this.directory, randomUUID12());
13460
- const destination = join4(storageDirectory, fileName);
13755
+ const storageDirectory = join5(this.directory, randomUUID12());
13756
+ const destination = join5(storageDirectory, fileName);
13461
13757
  await mkdir(storageDirectory, { mode: 448 });
13462
13758
  try {
13463
13759
  await copyFile(canonicalSource, destination, fsConstants.COPYFILE_EXCL);
@@ -13483,9 +13779,9 @@ var ArtifactStore = class {
13483
13779
  expiresAt: createdAt + this.ttlMs,
13484
13780
  retained: false
13485
13781
  };
13486
- const record = { descriptor: descriptor2, path: destination };
13487
- this.records.set(id, record);
13488
- return clone2(record);
13782
+ const record2 = { descriptor: descriptor2, path: destination };
13783
+ this.records.set(id, record2);
13784
+ return clone2(record2);
13489
13785
  } catch (error) {
13490
13786
  await rm(storageDirectory, { recursive: true, force: true }).catch(() => {
13491
13787
  });
@@ -13499,7 +13795,7 @@ var ArtifactStore = class {
13499
13795
  this.assertQuota(input.workspaceId, byteSize);
13500
13796
  await this.ensureDirectory();
13501
13797
  const id = this.nextArtifactId();
13502
- const file = join4(this.directory, `${randomUUID12()}.bin`);
13798
+ const file = join5(this.directory, `${randomUUID12()}.bin`);
13503
13799
  const createdAt = this.now();
13504
13800
  await writeFile(file, input.bytes, { mode: 384, flag: "wx" });
13505
13801
  await chmod(file, 384).catch(() => {
@@ -13519,39 +13815,39 @@ var ArtifactStore = class {
13519
13815
  retained: false,
13520
13816
  ...input.previewOf ? { previewOf: input.previewOf } : {}
13521
13817
  };
13522
- const record = { descriptor: descriptor2, path: file };
13523
- this.records.set(id, record);
13524
- return clone2(record);
13818
+ const record2 = { descriptor: descriptor2, path: file };
13819
+ this.records.set(id, record2);
13820
+ return clone2(record2);
13525
13821
  }
13526
13822
  async requireRecordUnlocked(workspaceId, artifactId) {
13527
13823
  await this.sweepUnlocked();
13528
- const record = this.records.get(artifactId);
13529
- if (!record || record.descriptor.workspaceId !== workspaceId) {
13824
+ const record2 = this.records.get(artifactId);
13825
+ if (!record2 || record2.descriptor.workspaceId !== workspaceId) {
13530
13826
  const tombstone = this.expired.get(artifactId);
13531
13827
  if (tombstone?.workspaceId === workspaceId) throw this.artifactExpired(artifactId);
13532
13828
  throw this.notFound(artifactId);
13533
13829
  }
13534
13830
  try {
13535
- const info = await stat(record.path);
13536
- if (!info.isFile() || info.size !== record.descriptor.byteSize) throw new Error("Artifact file mismatch");
13831
+ const info = await stat(record2.path);
13832
+ if (!info.isFile() || info.size !== record2.descriptor.byteSize) throw new Error("Artifact file mismatch");
13537
13833
  } catch {
13538
13834
  this.records.delete(artifactId);
13539
13835
  throw this.notFound(artifactId);
13540
13836
  }
13541
- return record;
13837
+ return record2;
13542
13838
  }
13543
13839
  async sweepUnlocked() {
13544
13840
  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,
13841
+ const expiredRecords = [...this.records.values()].filter((record2) => record2.descriptor.expiresAt <= now);
13842
+ for (const record2 of expiredRecords) {
13843
+ this.records.delete(record2.descriptor.id);
13844
+ this.expired.set(record2.descriptor.id, {
13845
+ workspaceId: record2.descriptor.workspaceId,
13550
13846
  expiredAt: now
13551
13847
  });
13552
13848
  }
13553
13849
  this.pruneExpiredTombstones(now);
13554
- await Promise.all(expiredRecords.map((record) => this.removeStoredFile(record.path)));
13850
+ await Promise.all(expiredRecords.map((record2) => this.removeStoredFile(record2.path)));
13555
13851
  return expiredRecords.length;
13556
13852
  }
13557
13853
  async ensureDirectory() {
@@ -13559,7 +13855,7 @@ var ArtifactStore = class {
13559
13855
  await mkdir(this.directory, { recursive: true, mode: 448 });
13560
13856
  if (this.purgeOrphansOnInitialize) {
13561
13857
  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(() => {
13858
+ 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
13859
  })));
13564
13860
  }
13565
13861
  this.initialized = true;
@@ -13598,8 +13894,8 @@ var ArtifactStore = class {
13598
13894
  context: { maxArtifactBytes: this.maxArtifactBytes, byteSize }
13599
13895
  });
13600
13896
  }
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);
13897
+ const workspaceBytes = [...this.records.values()].filter((record2) => record2.descriptor.workspaceId === workspaceId).reduce((sum, record2) => sum + record2.descriptor.byteSize, 0);
13898
+ const totalBytes = [...this.records.values()].reduce((sum, record2) => sum + record2.descriptor.byteSize, 0);
13603
13899
  if (workspaceBytes + byteSize > this.maxWorkspaceBytes || totalBytes + byteSize > this.maxTotalBytes) {
13604
13900
  throw new BrowserPilotError("result_too_large", "Artifact Store quota exceeded", {
13605
13901
  context: {
@@ -13613,7 +13909,7 @@ var ArtifactStore = class {
13613
13909
  async removeStoredFile(path) {
13614
13910
  await unlink(path).catch(() => {
13615
13911
  });
13616
- const parent = dirname2(path);
13912
+ const parent = dirname3(path);
13617
13913
  if (parent !== this.directory && this.isInsideStore(parent)) {
13618
13914
  await rm(parent, { recursive: true, force: true }).catch(() => {
13619
13915
  });
@@ -13827,8 +14123,8 @@ var ManagedTargetJanitorClient = class {
13827
14123
  });
13828
14124
  this.worker = worker;
13829
14125
  this.ready = false;
13830
- const ready = new Promise((resolve2, reject) => {
13831
- this.readyResolve = resolve2;
14126
+ const ready = new Promise((resolve3, reject) => {
14127
+ this.readyResolve = resolve3;
13832
14128
  this.readyReject = reject;
13833
14129
  });
13834
14130
  worker.on("message", (value) => this.handleWorkerMessage(worker, value));
@@ -13933,7 +14229,7 @@ var ManagedTargetJanitorClient = class {
13933
14229
  }
13934
14230
  const id = this.nextRequestId++;
13935
14231
  const request = { id, method, params };
13936
- return new Promise((resolve2, reject) => {
14232
+ return new Promise((resolve3, reject) => {
13937
14233
  const timer = setTimeout(() => {
13938
14234
  this.pendingRequests.delete(id);
13939
14235
  reject(method === "create" ? new BrowserPilotError(
@@ -13943,7 +14239,7 @@ var ManagedTargetJanitorClient = class {
13943
14239
  ) : new Error(`Managed browser connection timeout: ${method}`));
13944
14240
  }, REQUEST_TIMEOUT_MS);
13945
14241
  timer.unref();
13946
- this.pendingRequests.set(id, { resolve: resolve2, reject, timer, method });
14242
+ this.pendingRequests.set(id, { resolve: resolve3, reject, timer, method });
13947
14243
  worker.send?.(request, (error) => {
13948
14244
  if (!error) return;
13949
14245
  const pending = this.pendingRequests.get(id);
@@ -13973,19 +14269,19 @@ var ManagedTargetJanitorClient = class {
13973
14269
  this.expectedExits.add(worker);
13974
14270
  if (cleanup2 && worker.stdin && !worker.stdin.destroyed) worker.stdin.end();
13975
14271
  else worker.kill("SIGTERM");
13976
- await new Promise((resolve2) => {
14272
+ await new Promise((resolve3) => {
13977
14273
  if (worker.exitCode !== null || worker.signalCode !== null) {
13978
- resolve2();
14274
+ resolve3();
13979
14275
  return;
13980
14276
  }
13981
14277
  const timer = setTimeout(() => {
13982
14278
  worker.kill("SIGKILL");
13983
- resolve2();
14279
+ resolve3();
13984
14280
  }, STOP_TIMEOUT_MS);
13985
14281
  timer.unref();
13986
14282
  worker.once("exit", () => {
13987
14283
  clearTimeout(timer);
13988
- resolve2();
14284
+ resolve3();
13989
14285
  });
13990
14286
  });
13991
14287
  if (this.worker === worker) this.failWorker(worker, this.unavailableError());
@@ -14015,7 +14311,7 @@ var ManagedTargetJanitorClient = class {
14015
14311
  };
14016
14312
 
14017
14313
  // src/version.ts
14018
- var BROWSER_PILOT_VERSION = "0.3.0";
14314
+ var BROWSER_PILOT_VERSION = "0.5.0";
14019
14315
 
14020
14316
  // src/daemon.ts
14021
14317
  var CLI_EXECUTABLE_PATH = publicExecutablePath(import.meta.url);
@@ -14029,7 +14325,7 @@ var browserProfile = process.argv[4] || "";
14029
14325
  ensureBrokerDirectoriesSync();
14030
14326
  for (const legacyFile of ["state.json", "refs.json"]) {
14031
14327
  try {
14032
- unlinkSync2(join5(STATE_DIR, legacyFile));
14328
+ unlinkSync2(join6(STATE_DIR, legacyFile));
14033
14329
  } catch {
14034
14330
  }
14035
14331
  }
@@ -14049,7 +14345,7 @@ function cleanup(brokerProcessIdentity) {
14049
14345
  removeBrokerLocatorSync(brokerProcessIdentity);
14050
14346
  }
14051
14347
  function readBody(req, maxBytes = 5 * 1024 * 1024) {
14052
- return new Promise((resolve2, reject) => {
14348
+ return new Promise((resolve3, reject) => {
14053
14349
  const chunks = [];
14054
14350
  let byteLength = 0;
14055
14351
  req.on("data", (value) => {
@@ -14062,7 +14358,7 @@ function readBody(req, maxBytes = 5 * 1024 * 1024) {
14062
14358
  }
14063
14359
  chunks.push(chunk);
14064
14360
  });
14065
- req.on("end", () => resolve2(Buffer.concat(chunks).toString("utf8")));
14361
+ req.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
14066
14362
  req.on("error", reject);
14067
14363
  });
14068
14364
  }
@@ -14161,7 +14457,7 @@ async function main() {
14161
14457
  candidate: {
14162
14458
  id: customBrowserId,
14163
14459
  product: browserProduct || "Chromium",
14164
- profile: browserProfile,
14460
+ userDataRoot: browserProfile,
14165
14461
  processState: initialWsUrl ? "running" : "unknown",
14166
14462
  remoteDebuggingState: initialWsUrl ? "enabled" : "disabled",
14167
14463
  authorizationState: initialWsUrl ? "authorized" : "not_applicable",
@@ -14193,7 +14489,7 @@ async function main() {
14193
14489
  } : {
14194
14490
  id: browserId,
14195
14491
  product: browserProduct || "Unavailable browser",
14196
- profile: browserProfile,
14492
+ userDataRoot: browserProfile,
14197
14493
  processState: initialWsUrl ? "running" : "unknown",
14198
14494
  remoteDebuggingState: initialWsUrl ? "enabled" : "disabled",
14199
14495
  authorizationState: initialWsUrl ? "authorized" : "not_applicable",
@@ -14202,7 +14498,7 @@ async function main() {
14202
14498
  instance: {
14203
14499
  id: browserInstanceId,
14204
14500
  product: selectedProduct || "Unavailable browser",
14205
- profilePath: selectedProfile,
14501
+ userDataRoot: selectedProfile,
14206
14502
  processIdentity: initialWsUrl ?? "",
14207
14503
  connectionGeneration: initialWsUrl ? 1 : 0,
14208
14504
  state: initialWsUrl ? "connected" : "disconnected"
@@ -14242,7 +14538,7 @@ async function main() {
14242
14538
  instance: {
14243
14539
  id: instanceId,
14244
14540
  product: discovered.candidate.product,
14245
- profilePath: discovered.dataDir,
14541
+ userDataRoot: discovered.dataDir,
14246
14542
  processIdentity: "",
14247
14543
  connectionGeneration: 0,
14248
14544
  state: "disconnected"
@@ -14298,7 +14594,7 @@ async function main() {
14298
14594
  if (existing) return existing;
14299
14595
  const attempt = (async () => {
14300
14596
  const chrome = discoverChromeAtDataDir(
14301
- controller.binding.instance.profilePath,
14597
+ controller.binding.instance.userDataRoot,
14302
14598
  controller.binding.instance.product
14303
14599
  );
14304
14600
  if (!chrome) {
@@ -14541,7 +14837,7 @@ async function main() {
14541
14837
  res.end(JSON.stringify({
14542
14838
  ok: true,
14543
14839
  wsUrl: currentWsUrl,
14544
- brokerProtocol: 1,
14840
+ brokerProtocol: BROKER_RPC_VERSION,
14545
14841
  brokerProcessIdentity,
14546
14842
  serviceVersion: BROWSER_PILOT_VERSION,
14547
14843
  executableVersion: executable.version,
@@ -14553,7 +14849,7 @@ async function main() {
14553
14849
  id: browserBinding.candidate.id,
14554
14850
  product: selectedProduct,
14555
14851
  ...browserBinding.candidate.channel ? { channel: browserBinding.candidate.channel } : {},
14556
- profile: selectedProfile,
14852
+ userDataRoot: selectedProfile,
14557
14853
  state: browserBinding.instance.state,
14558
14854
  connectionGeneration: browserBinding.instance.connectionGeneration
14559
14855
  } } : {}
@@ -14569,14 +14865,14 @@ async function main() {
14569
14865
  }
14570
14866
  const body = JSON.parse(await readBody(req, DEFAULT_PROTOCOL_LIMITS.maxMessageBytes + 4096));
14571
14867
  if (!isRecord3(body)) throw invalidArgument("Broker RPC body must be an object", "body");
14572
- if (typeof body.bridgeSessionId !== "string") {
14573
- throw invalidArgument("bridgeSessionId is required", "bridgeSessionId");
14868
+ if (typeof body.clientSessionId !== "string") {
14869
+ throw invalidArgument("clientSessionId is required", "clientSessionId");
14574
14870
  }
14575
14871
  if (typeof body.method !== "string" || body.method.length === 0 || body.method.length > 256) {
14576
14872
  throw invalidArgument("method is required", "method");
14577
14873
  }
14578
14874
  const result = await broker.call(
14579
- body.bridgeSessionId,
14875
+ body.clientSessionId,
14580
14876
  body.method,
14581
14877
  body.params
14582
14878
  );
@@ -14589,47 +14885,6 @@ async function main() {
14589
14885
  return;
14590
14886
  }
14591
14887
  }
14592
- if (req.method === "POST" && url.pathname === "/broker/disconnect") {
14593
- try {
14594
- const body = JSON.parse(await readBody(req, 4096));
14595
- if (!isRecord3(body) || typeof body.bridgeSessionId !== "string") {
14596
- throw invalidArgument("bridgeSessionId is required", "bridgeSessionId");
14597
- }
14598
- broker.disconnect(body.bridgeSessionId);
14599
- res.writeHead(200);
14600
- res.end(JSON.stringify({ ok: true }));
14601
- return;
14602
- } catch (error) {
14603
- res.writeHead(200);
14604
- res.end(JSON.stringify({ error: asBrowserPilotError(error).toJsonRpcError() }));
14605
- return;
14606
- }
14607
- }
14608
- if (req.method === "POST" && url.pathname === "/broker/events/next") {
14609
- const abort = new AbortController();
14610
- res.once("close", () => abort.abort());
14611
- try {
14612
- const body = JSON.parse(await readBody(req, 4096));
14613
- if (!isRecord3(body) || typeof body.bridgeSessionId !== "string" || !Number.isSafeInteger(body.waitMs)) {
14614
- throw invalidArgument("bridgeSessionId and integer waitMs are required");
14615
- }
14616
- const notification = await broker.nextNotification(body.bridgeSessionId, {
14617
- waitMs: Number(body.waitMs),
14618
- signal: abort.signal
14619
- });
14620
- if (!res.destroyed) {
14621
- res.writeHead(200);
14622
- res.end(JSON.stringify({ notification }));
14623
- }
14624
- return;
14625
- } catch (error) {
14626
- if (!res.destroyed) {
14627
- res.writeHead(200);
14628
- res.end(JSON.stringify({ error: asBrowserPilotError(error).toJsonRpcError() }));
14629
- }
14630
- return;
14631
- }
14632
- }
14633
14888
  if (req.method === "POST" && url.pathname === "/cdp") {
14634
14889
  const body = await readBody(req);
14635
14890
  const { method, params, sessionId } = JSON.parse(body);
@@ -14710,13 +14965,13 @@ async function main() {
14710
14965
  });
14711
14966
  }
14712
14967
  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", {
14968
+ if (clients.activeLeases > 0) {
14969
+ throw new BrowserPilotError("broker_in_use", "Browser Pilot has other live clients and cannot be stopped", {
14715
14970
  retryable: true,
14716
14971
  context: clients,
14717
14972
  remediation: {
14718
- code: "close_embedded_clients",
14719
- message: "Close the Agent products using Browser Pilot, then retry disconnect.",
14973
+ code: "close_active_clients",
14974
+ message: "Close the other Agent clients using Browser Pilot, then retry disconnect.",
14720
14975
  actionRequired: true
14721
14976
  }
14722
14977
  });