opensteer 0.6.11 → 0.6.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -475,6 +475,7 @@ __export(index_exports, {
475
475
  createCuaClient: () => createCuaClient,
476
476
  createEmptyRegistry: () => createEmptyRegistry,
477
477
  createExtractCallback: () => createExtractCallback,
478
+ createIsolatedRuntimeProfile: () => createIsolatedRuntimeProfile,
478
479
  createResolveCallback: () => createResolveCallback,
479
480
  createTab: () => createTab,
480
481
  detectChromePaths: () => detectChromePaths,
@@ -490,8 +491,10 @@ __export(index_exports, {
490
491
  getElementValue: () => getElementValue,
491
492
  getModelProvider: () => getModelProvider,
492
493
  getOrCreatePersistentProfile: () => getOrCreatePersistentProfile,
494
+ getOwnedRealBrowserProcessPolicy: () => getOwnedRealBrowserProcessPolicy,
493
495
  getPageHtml: () => getPageHtml,
494
496
  getPageTitle: () => getPageTitle,
497
+ hasActiveRuntimeProfileCreations: () => hasActiveRuntimeProfileCreations,
495
498
  importCookies: () => importCookies,
496
499
  isCloudActionMethod: () => isCloudActionMethod,
497
500
  isCloudErrorCode: () => isCloudErrorCode,
@@ -507,6 +510,7 @@ __export(index_exports, {
507
510
  performInput: () => performInput,
508
511
  performScroll: () => performScroll,
509
512
  performSelect: () => performSelect,
513
+ persistIsolatedRuntimeProfile: () => persistIsolatedRuntimeProfile,
510
514
  planSnappyCursorMotion: () => planSnappyCursorMotion,
511
515
  prepareSnapshot: () => prepareSnapshot,
512
516
  pressKey: () => pressKey,
@@ -529,10 +533,7 @@ module.exports = __toCommonJS(index_exports);
529
533
  var import_crypto = require("crypto");
530
534
 
531
535
  // src/browser/pool.ts
532
- var import_node_child_process = require("child_process");
533
- var import_promises2 = require("fs/promises");
534
- var import_node_net = require("net");
535
- var import_playwright = require("playwright");
536
+ var import_playwright2 = require("playwright");
536
537
 
537
538
  // src/browser/cdp-proxy.ts
538
539
  var import_ws = __toESM(require("ws"), 1);
@@ -1025,281 +1026,2644 @@ function listLocalChromeProfiles(userDataDir = detectChromePaths().defaultUserDa
1025
1026
  }
1026
1027
 
1027
1028
  // src/browser/persistent-profile.ts
1029
+ var import_node_crypto3 = require("crypto");
1030
+ var import_node_child_process2 = require("child_process");
1031
+ var import_node_fs3 = require("fs");
1032
+ var import_promises4 = require("fs/promises");
1033
+ var import_node_os = require("os");
1034
+ var import_node_path4 = require("path");
1035
+ var import_node_util2 = require("util");
1036
+
1037
+ // src/browser/persistent-profile-coordination.ts
1038
+ var import_node_path2 = require("path");
1039
+
1040
+ // src/browser/dir-lock.ts
1028
1041
  var import_node_crypto = require("crypto");
1029
1042
  var import_node_fs = require("fs");
1030
- var import_promises = require("fs/promises");
1031
- var import_node_os = require("os");
1043
+ var import_promises2 = require("fs/promises");
1032
1044
  var import_node_path = require("path");
1033
- var OPENSTEER_META_FILE = ".opensteer-meta.json";
1045
+
1046
+ // src/browser/process-owner.ts
1047
+ var import_node_child_process = require("child_process");
1048
+ var import_promises = require("fs/promises");
1049
+ var import_node_util = require("util");
1050
+ var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
1034
1051
  var PROCESS_STARTED_AT_MS = Math.floor(Date.now() - process.uptime() * 1e3);
1035
1052
  var PROCESS_START_TIME_TOLERANCE_MS = 1e3;
1036
- var CHROME_SINGLETON_ENTRIES = /* @__PURE__ */ new Set([
1037
- "SingletonCookie",
1038
- "SingletonLock",
1039
- "SingletonSocket",
1040
- "DevToolsActivePort",
1041
- "lockfile"
1042
- ]);
1043
- var COPY_SKIP_ENTRIES = /* @__PURE__ */ new Set([
1044
- ...CHROME_SINGLETON_ENTRIES,
1045
- OPENSTEER_META_FILE
1046
- ]);
1047
- var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
1048
- "Crash Reports",
1049
- "Crashpad",
1050
- "BrowserMetrics",
1051
- "GrShaderCache",
1052
- "ShaderCache",
1053
- "GraphiteDawnCache",
1054
- "component_crx_cache",
1055
- "Crowd Deny",
1056
- "hyphen-data",
1057
- "OnDeviceHeadSuggestModel",
1058
- "OptimizationGuidePredictionModels",
1059
- "Segmentation Platform",
1060
- "SmartCardDeviceNames",
1061
- "WidevineCdm",
1062
- "pnacl"
1063
- ]);
1064
- async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory, profilesRootDir = defaultPersistentProfilesRootDir()) {
1065
- const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
1066
- const targetUserDataDir = (0, import_node_path.join)(
1067
- expandHome(profilesRootDir),
1068
- buildPersistentProfileKey(resolvedSourceUserDataDir, profileDirectory)
1069
- );
1070
- const sourceProfileDir = (0, import_node_path.join)(resolvedSourceUserDataDir, profileDirectory);
1071
- const metadata = buildPersistentProfileMetadata(
1072
- resolvedSourceUserDataDir,
1073
- profileDirectory
1074
- );
1075
- await (0, import_promises.mkdir)((0, import_node_path.dirname)(targetUserDataDir), { recursive: true });
1076
- await cleanOrphanedTempDirs(
1077
- (0, import_node_path.dirname)(targetUserDataDir),
1078
- (0, import_node_path.basename)(targetUserDataDir)
1079
- );
1080
- if (!(0, import_node_fs.existsSync)(sourceProfileDir)) {
1081
- throw new Error(
1082
- `Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
1083
- );
1053
+ var PROCESS_LIST_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
1054
+ var PS_COMMAND_ENV = { ...process.env, LC_ALL: "C" };
1055
+ var LINUX_STAT_START_TIME_FIELD_INDEX = 19;
1056
+ var CURRENT_PROCESS_OWNER = {
1057
+ pid: process.pid,
1058
+ processStartedAtMs: PROCESS_STARTED_AT_MS
1059
+ };
1060
+ var linuxClockTicksPerSecondPromise = null;
1061
+ function parseProcessOwner(value) {
1062
+ if (!value || typeof value !== "object") {
1063
+ return null;
1064
+ }
1065
+ const parsed = value;
1066
+ const pid = Number(parsed.pid);
1067
+ const processStartedAtMs = Number(parsed.processStartedAtMs);
1068
+ if (!Number.isInteger(pid) || pid <= 0) {
1069
+ return null;
1070
+ }
1071
+ if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
1072
+ return null;
1084
1073
  }
1085
- const created = await createPersistentProfileClone(
1086
- resolvedSourceUserDataDir,
1087
- sourceProfileDir,
1088
- targetUserDataDir,
1089
- profileDirectory,
1090
- metadata
1091
- );
1092
- await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
1093
1074
  return {
1094
- created,
1095
- userDataDir: targetUserDataDir
1075
+ pid,
1076
+ processStartedAtMs
1096
1077
  };
1097
1078
  }
1098
- async function clearPersistentProfileSingletons(userDataDir) {
1099
- await Promise.all(
1100
- [...CHROME_SINGLETON_ENTRIES].map(
1101
- (entry) => (0, import_promises.rm)((0, import_node_path.join)(userDataDir, entry), {
1102
- force: true,
1103
- recursive: true
1104
- }).catch(() => void 0)
1105
- )
1106
- );
1079
+ function processOwnersEqual(left, right) {
1080
+ if (!left || !right) {
1081
+ return left === right;
1082
+ }
1083
+ return left.pid === right.pid && left.processStartedAtMs === right.processStartedAtMs;
1107
1084
  }
1108
- function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
1109
- const hash = (0, import_node_crypto.createHash)("sha256").update(`${sourceUserDataDir}\0${profileDirectory}`).digest("hex").slice(0, 16);
1110
- const sourceLabel = sanitizePathSegment((0, import_node_path.basename)(sourceUserDataDir) || "user-data");
1111
- const profileLabel = sanitizePathSegment(profileDirectory || "Default");
1112
- return `${sourceLabel}-${profileLabel}-${hash}`;
1085
+ async function getProcessLiveness(owner) {
1086
+ if (owner.pid === process.pid && hasMatchingProcessStartTime(
1087
+ owner.processStartedAtMs,
1088
+ PROCESS_STARTED_AT_MS
1089
+ )) {
1090
+ return "live";
1091
+ }
1092
+ const startedAtMs = await readProcessStartedAtMs(owner.pid);
1093
+ if (typeof startedAtMs === "number") {
1094
+ return hasMatchingProcessStartTime(
1095
+ owner.processStartedAtMs,
1096
+ startedAtMs
1097
+ ) ? "live" : "dead";
1098
+ }
1099
+ return isProcessRunning(owner.pid) ? "unknown" : "dead";
1113
1100
  }
1114
- function defaultPersistentProfilesRootDir() {
1115
- return (0, import_node_path.join)((0, import_node_os.homedir)(), ".opensteer", "real-browser-profiles");
1101
+ function isProcessRunning(pid) {
1102
+ try {
1103
+ process.kill(pid, 0);
1104
+ return true;
1105
+ } catch (error) {
1106
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
1107
+ return code !== "ESRCH";
1108
+ }
1116
1109
  }
1117
- function sanitizePathSegment(value) {
1118
- const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-");
1119
- return sanitized.replace(/^-|-$/g, "") || "profile";
1110
+ async function readProcessOwner(pid) {
1111
+ const processStartedAtMs = await readProcessStartedAtMs(pid);
1112
+ if (processStartedAtMs === null) {
1113
+ return null;
1114
+ }
1115
+ return {
1116
+ pid,
1117
+ processStartedAtMs
1118
+ };
1120
1119
  }
1121
- function isProfileDirectory(userDataDir, entry) {
1122
- return (0, import_node_fs.existsSync)((0, import_node_path.join)(userDataDir, entry, "Preferences"));
1120
+ function hasMatchingProcessStartTime(expectedStartedAtMs, actualStartedAtMs) {
1121
+ return Math.abs(expectedStartedAtMs - actualStartedAtMs) <= PROCESS_START_TIME_TOLERANCE_MS;
1123
1122
  }
1124
- async function copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, targetProfileDirectory) {
1125
- let entries;
1123
+ async function readProcessStartedAtMs(pid) {
1124
+ if (pid <= 0) {
1125
+ return null;
1126
+ }
1127
+ if (process.platform === "linux") {
1128
+ return await readLinuxProcessStartedAtMs(pid);
1129
+ }
1130
+ if (process.platform === "win32") {
1131
+ return await readWindowsProcessStartedAtMs(pid);
1132
+ }
1133
+ return await readPsProcessStartedAtMs(pid);
1134
+ }
1135
+ async function readLinuxProcessStartedAtMs(pid) {
1136
+ let statRaw;
1126
1137
  try {
1127
- entries = await (0, import_promises.readdir)(sourceUserDataDir);
1138
+ statRaw = await (0, import_promises.readFile)(`/proc/${pid}/stat`, "utf8");
1128
1139
  } catch {
1129
- return;
1140
+ return null;
1130
1141
  }
1131
- const copyTasks = [];
1132
- for (const entry of entries) {
1133
- if (COPY_SKIP_ENTRIES.has(entry)) continue;
1134
- if (entry === targetProfileDirectory) continue;
1135
- const sourcePath = (0, import_node_path.join)(sourceUserDataDir, entry);
1136
- const targetPath = (0, import_node_path.join)(targetUserDataDir, entry);
1137
- if ((0, import_node_fs.existsSync)(targetPath)) continue;
1138
- let entryStat;
1139
- try {
1140
- entryStat = await (0, import_promises.stat)(sourcePath);
1141
- } catch {
1142
- continue;
1143
- }
1144
- if (entryStat.isFile()) {
1145
- copyTasks.push((0, import_promises.copyFile)(sourcePath, targetPath).catch(() => void 0));
1146
- } else if (entryStat.isDirectory()) {
1147
- if (isProfileDirectory(sourceUserDataDir, entry)) continue;
1148
- if (SKIPPED_ROOT_DIRECTORIES.has(entry)) continue;
1149
- copyTasks.push(
1150
- (0, import_promises.cp)(sourcePath, targetPath, { recursive: true }).catch(
1151
- () => void 0
1152
- )
1153
- );
1154
- }
1142
+ const startTicks = parseLinuxProcessStartTicks(statRaw);
1143
+ if (startTicks === null) {
1144
+ return null;
1155
1145
  }
1156
- await Promise.all(copyTasks);
1157
- }
1158
- async function writePersistentProfileMetadata(userDataDir, metadata) {
1159
- await (0, import_promises.writeFile)(
1160
- (0, import_node_path.join)(userDataDir, OPENSTEER_META_FILE),
1161
- JSON.stringify(metadata, null, 2)
1146
+ const [bootTimeMs, clockTicksPerSecond] = await Promise.all([
1147
+ readLinuxBootTimeMs(),
1148
+ readLinuxClockTicksPerSecond()
1149
+ ]);
1150
+ if (bootTimeMs === null || clockTicksPerSecond === null) {
1151
+ return null;
1152
+ }
1153
+ return Math.floor(
1154
+ bootTimeMs + startTicks * 1e3 / clockTicksPerSecond
1162
1155
  );
1163
1156
  }
1164
- function buildPersistentProfileMetadata(sourceUserDataDir, profileDirectory) {
1165
- return {
1166
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1167
- profileDirectory,
1168
- source: sourceUserDataDir
1169
- };
1157
+ function parseLinuxProcessStartTicks(statRaw) {
1158
+ const closingParenIndex = statRaw.lastIndexOf(")");
1159
+ if (closingParenIndex === -1) {
1160
+ return null;
1161
+ }
1162
+ const fields = statRaw.slice(closingParenIndex + 2).trim().split(/\s+/);
1163
+ const startTicks = Number(fields[LINUX_STAT_START_TIME_FIELD_INDEX]);
1164
+ return Number.isFinite(startTicks) && startTicks >= 0 ? startTicks : null;
1170
1165
  }
1171
- async function createPersistentProfileClone(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
1172
- if ((0, import_node_fs.existsSync)(targetUserDataDir)) {
1173
- return false;
1166
+ async function readLinuxBootTimeMs() {
1167
+ try {
1168
+ const statRaw = await (0, import_promises.readFile)("/proc/stat", "utf8");
1169
+ const bootTimeLine = statRaw.split("\n").find((line) => line.startsWith("btime "));
1170
+ if (!bootTimeLine) {
1171
+ return null;
1172
+ }
1173
+ const bootTimeSeconds = Number.parseInt(
1174
+ bootTimeLine.slice("btime ".length),
1175
+ 10
1176
+ );
1177
+ return Number.isFinite(bootTimeSeconds) ? bootTimeSeconds * 1e3 : null;
1178
+ } catch {
1179
+ return null;
1174
1180
  }
1175
- const tempUserDataDir = await (0, import_promises.mkdtemp)(
1176
- buildPersistentProfileTempDirPrefix(targetUserDataDir)
1177
- );
1178
- let published = false;
1181
+ }
1182
+ async function readLinuxClockTicksPerSecond() {
1183
+ if (!linuxClockTicksPerSecondPromise) {
1184
+ linuxClockTicksPerSecondPromise = execFileAsync("getconf", ["CLK_TCK"], {
1185
+ encoding: "utf8",
1186
+ maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
1187
+ }).then(({ stdout }) => {
1188
+ const value = Number.parseInt(stdout.trim(), 10);
1189
+ return Number.isFinite(value) && value > 0 ? value : null;
1190
+ }).catch(() => null);
1191
+ }
1192
+ return await linuxClockTicksPerSecondPromise;
1193
+ }
1194
+ async function readWindowsProcessStartedAtMs(pid) {
1179
1195
  try {
1180
- await (0, import_promises.cp)(sourceProfileDir, (0, import_node_path.join)(tempUserDataDir, profileDirectory), {
1181
- recursive: true
1182
- });
1183
- await copyRootLevelEntries(
1184
- sourceUserDataDir,
1185
- tempUserDataDir,
1186
- profileDirectory
1196
+ const { stdout } = await execFileAsync(
1197
+ "powershell.exe",
1198
+ [
1199
+ "-NoProfile",
1200
+ "-Command",
1201
+ `(Get-Process -Id ${pid}).StartTime.ToUniversalTime().ToString("o")`
1202
+ ],
1203
+ {
1204
+ encoding: "utf8",
1205
+ maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
1206
+ }
1187
1207
  );
1188
- await writePersistentProfileMetadata(tempUserDataDir, metadata);
1189
- try {
1190
- await (0, import_promises.rename)(tempUserDataDir, targetUserDataDir);
1191
- } catch (error) {
1192
- if (wasProfilePublishedByAnotherProcess(error, targetUserDataDir)) {
1193
- return false;
1208
+ const isoTimestamp = stdout.trim();
1209
+ if (!isoTimestamp) {
1210
+ return null;
1211
+ }
1212
+ const startedAtMs = Date.parse(isoTimestamp);
1213
+ return Number.isFinite(startedAtMs) ? startedAtMs : null;
1214
+ } catch {
1215
+ return null;
1216
+ }
1217
+ }
1218
+ async function readPsProcessStartedAtMs(pid) {
1219
+ try {
1220
+ const { stdout } = await execFileAsync(
1221
+ "ps",
1222
+ ["-o", "lstart=", "-p", String(pid)],
1223
+ {
1224
+ encoding: "utf8",
1225
+ env: PS_COMMAND_ENV,
1226
+ maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
1194
1227
  }
1195
- throw error;
1228
+ );
1229
+ const startedAt = stdout.trim();
1230
+ if (!startedAt) {
1231
+ return null;
1196
1232
  }
1197
- published = true;
1198
- return true;
1233
+ const startedAtMs = Date.parse(startedAt.replace(/\s+/g, " "));
1234
+ return Number.isFinite(startedAtMs) ? startedAtMs : null;
1235
+ } catch {
1236
+ return null;
1237
+ }
1238
+ }
1239
+
1240
+ // src/browser/dir-lock.ts
1241
+ var LOCK_OWNER_FILE = "owner.json";
1242
+ var LOCK_RECLAIMER_DIR = "reclaimer";
1243
+ var LOCK_RETRY_DELAY_MS = 50;
1244
+ async function withDirLock(lockDirPath, action) {
1245
+ const releaseLock = await acquireDirLock(lockDirPath);
1246
+ try {
1247
+ return await action();
1199
1248
  } finally {
1200
- if (!published) {
1201
- await (0, import_promises.rm)(tempUserDataDir, {
1249
+ await releaseLock();
1250
+ }
1251
+ }
1252
+ async function acquireDirLock(lockDirPath) {
1253
+ while (true) {
1254
+ const releaseLock = await tryAcquireDirLock(lockDirPath);
1255
+ if (releaseLock) {
1256
+ return releaseLock;
1257
+ }
1258
+ await sleep(LOCK_RETRY_DELAY_MS);
1259
+ }
1260
+ }
1261
+ async function tryAcquireDirLock(lockDirPath) {
1262
+ await (0, import_promises2.mkdir)((0, import_node_path.dirname)(lockDirPath), { recursive: true });
1263
+ while (true) {
1264
+ const tempLockDirPath = `${lockDirPath}-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto.randomUUID)()}`;
1265
+ try {
1266
+ await (0, import_promises2.mkdir)(tempLockDirPath);
1267
+ await writeLockOwner(tempLockDirPath, CURRENT_PROCESS_OWNER);
1268
+ try {
1269
+ await (0, import_promises2.rename)(tempLockDirPath, lockDirPath);
1270
+ break;
1271
+ } catch (error) {
1272
+ if (!wasDirPublishedByAnotherProcess(error, lockDirPath)) {
1273
+ throw error;
1274
+ }
1275
+ }
1276
+ } finally {
1277
+ await (0, import_promises2.rm)(tempLockDirPath, {
1202
1278
  recursive: true,
1203
1279
  force: true
1204
1280
  }).catch(() => void 0);
1205
1281
  }
1282
+ const owner = await readLockOwner(lockDirPath);
1283
+ if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
1284
+ continue;
1285
+ }
1286
+ return null;
1206
1287
  }
1288
+ return async () => {
1289
+ await (0, import_promises2.rm)(lockDirPath, {
1290
+ recursive: true,
1291
+ force: true
1292
+ }).catch(() => void 0);
1293
+ };
1207
1294
  }
1208
- async function ensurePersistentProfileMetadata(userDataDir, metadata) {
1209
- if ((0, import_node_fs.existsSync)((0, import_node_path.join)(userDataDir, OPENSTEER_META_FILE))) {
1210
- return;
1295
+ async function isDirLockHeld(lockDirPath) {
1296
+ if (!(0, import_node_fs.existsSync)(lockDirPath)) {
1297
+ return false;
1211
1298
  }
1212
- await writePersistentProfileMetadata(userDataDir, metadata);
1299
+ const owner = await readLockOwner(lockDirPath);
1300
+ if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
1301
+ return false;
1302
+ }
1303
+ return (0, import_node_fs.existsSync)(lockDirPath);
1213
1304
  }
1214
- function wasProfilePublishedByAnotherProcess(error, targetUserDataDir) {
1215
- const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
1216
- return (0, import_node_fs.existsSync)(targetUserDataDir) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
1305
+ function getErrorCode(error) {
1306
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
1217
1307
  }
1218
- function buildPersistentProfileTempDirPrefix(targetUserDataDir) {
1219
- return (0, import_node_path.join)(
1220
- (0, import_node_path.dirname)(targetUserDataDir),
1221
- `${(0, import_node_path.basename)(targetUserDataDir)}-tmp-${process.pid}-${PROCESS_STARTED_AT_MS}-`
1308
+ function wasDirPublishedByAnotherProcess(error, targetDirPath) {
1309
+ const code = getErrorCode(error);
1310
+ return (0, import_node_fs.existsSync)(targetDirPath) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
1311
+ }
1312
+ async function writeLockOwner(lockDirPath, owner) {
1313
+ await (0, import_promises2.writeFile)((0, import_node_path.join)(lockDirPath, LOCK_OWNER_FILE), JSON.stringify(owner));
1314
+ }
1315
+ async function readLockOwner(lockDirPath) {
1316
+ return await readLockParticipant((0, import_node_path.join)(lockDirPath, LOCK_OWNER_FILE));
1317
+ }
1318
+ async function readLockParticipant(filePath) {
1319
+ return (await readLockParticipantRecord(filePath)).owner;
1320
+ }
1321
+ async function readLockParticipantRecord(filePath) {
1322
+ try {
1323
+ const raw = await (0, import_promises2.readFile)(filePath, "utf8");
1324
+ const owner = parseProcessOwner(JSON.parse(raw));
1325
+ return {
1326
+ exists: true,
1327
+ owner
1328
+ };
1329
+ } catch (error) {
1330
+ return {
1331
+ exists: getErrorCode(error) !== "ENOENT",
1332
+ owner: null
1333
+ };
1334
+ }
1335
+ }
1336
+ async function readLockReclaimerRecord(lockDirPath) {
1337
+ return await readLockParticipantRecord(
1338
+ (0, import_node_path.join)(buildLockReclaimerDirPath(lockDirPath), LOCK_OWNER_FILE)
1222
1339
  );
1223
1340
  }
1224
- async function cleanOrphanedTempDirs(profilesDir, targetBaseName) {
1225
- let entries;
1341
+ async function tryReclaimStaleLock(lockDirPath, expectedOwner) {
1342
+ if (!await tryAcquireLockReclaimer(lockDirPath)) {
1343
+ return false;
1344
+ }
1345
+ let reclaimed = false;
1226
1346
  try {
1227
- entries = await (0, import_promises.readdir)(profilesDir, {
1228
- encoding: "utf8",
1229
- withFileTypes: true
1230
- });
1231
- } catch {
1232
- return;
1347
+ const owner = await readLockOwner(lockDirPath);
1348
+ if (!processOwnersEqual(owner, expectedOwner)) {
1349
+ return false;
1350
+ }
1351
+ if (owner && await getProcessLiveness(owner) !== "dead") {
1352
+ return false;
1353
+ }
1354
+ await (0, import_promises2.rm)(lockDirPath, {
1355
+ recursive: true,
1356
+ force: true
1357
+ }).catch(() => void 0);
1358
+ reclaimed = !(0, import_node_fs.existsSync)(lockDirPath);
1359
+ return reclaimed;
1360
+ } finally {
1361
+ if (!reclaimed) {
1362
+ await (0, import_promises2.rm)(buildLockReclaimerDirPath(lockDirPath), {
1363
+ recursive: true,
1364
+ force: true
1365
+ }).catch(() => void 0);
1366
+ }
1233
1367
  }
1234
- const tempDirPrefix = `${targetBaseName}-tmp-`;
1235
- await Promise.all(
1236
- entries.map(async (entry) => {
1237
- if (!entry.isDirectory() || !entry.name.startsWith(tempDirPrefix)) {
1238
- return;
1239
- }
1240
- if (isTempDirOwnedByLiveProcess(entry.name, tempDirPrefix)) {
1241
- return;
1368
+ }
1369
+ async function tryAcquireLockReclaimer(lockDirPath) {
1370
+ const reclaimerDirPath = buildLockReclaimerDirPath(lockDirPath);
1371
+ while (true) {
1372
+ const tempReclaimerDirPath = `${reclaimerDirPath}-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto.randomUUID)()}`;
1373
+ try {
1374
+ await (0, import_promises2.mkdir)(tempReclaimerDirPath);
1375
+ await writeLockOwner(tempReclaimerDirPath, CURRENT_PROCESS_OWNER);
1376
+ try {
1377
+ await (0, import_promises2.rename)(tempReclaimerDirPath, reclaimerDirPath);
1378
+ return true;
1379
+ } catch (error) {
1380
+ if (getErrorCode(error) === "ENOENT") {
1381
+ return false;
1382
+ }
1383
+ if (!wasDirPublishedByAnotherProcess(error, reclaimerDirPath)) {
1384
+ throw error;
1385
+ }
1386
+ }
1387
+ } catch (error) {
1388
+ const code = getErrorCode(error);
1389
+ if (code === "ENOENT") {
1390
+ return false;
1391
+ }
1392
+ throw error;
1393
+ } finally {
1394
+ await (0, import_promises2.rm)(tempReclaimerDirPath, {
1395
+ recursive: true,
1396
+ force: true
1397
+ }).catch(() => void 0);
1398
+ }
1399
+ const reclaimerRecord = await readLockReclaimerRecord(lockDirPath);
1400
+ if (!reclaimerRecord.exists || !reclaimerRecord.owner) {
1401
+ return false;
1402
+ }
1403
+ if (await getProcessLiveness(reclaimerRecord.owner) !== "dead") {
1404
+ return false;
1405
+ }
1406
+ await (0, import_promises2.rm)(reclaimerDirPath, {
1407
+ recursive: true,
1408
+ force: true
1409
+ }).catch(() => void 0);
1410
+ }
1411
+ }
1412
+ function buildLockReclaimerDirPath(lockDirPath) {
1413
+ return (0, import_node_path.join)(lockDirPath, LOCK_RECLAIMER_DIR);
1414
+ }
1415
+ async function sleep(ms) {
1416
+ await new Promise((resolve) => setTimeout(resolve, ms));
1417
+ }
1418
+
1419
+ // src/browser/persistent-profile-coordination.ts
1420
+ var PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS = 50;
1421
+ async function withPersistentProfileControlLock(targetUserDataDir, action) {
1422
+ return await withDirLock(
1423
+ buildPersistentProfileControlLockDirPath(targetUserDataDir),
1424
+ action
1425
+ );
1426
+ }
1427
+ async function acquirePersistentProfileWriteLock(targetUserDataDir) {
1428
+ const controlLockDirPath = buildPersistentProfileControlLockDirPath(targetUserDataDir);
1429
+ const writeLockDirPath = buildPersistentProfileWriteLockDirPath(
1430
+ targetUserDataDir
1431
+ );
1432
+ while (true) {
1433
+ let releaseWriteLock = null;
1434
+ const releaseControlLock = await acquireDirLock(controlLockDirPath);
1435
+ try {
1436
+ releaseWriteLock = await tryAcquireDirLock(writeLockDirPath);
1437
+ } finally {
1438
+ await releaseControlLock();
1439
+ }
1440
+ if (releaseWriteLock) {
1441
+ return releaseWriteLock;
1442
+ }
1443
+ await sleep2(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
1444
+ }
1445
+ }
1446
+ async function isPersistentProfileWriteLocked(targetUserDataDir) {
1447
+ return await isDirLockHeld(
1448
+ buildPersistentProfileWriteLockDirPath(targetUserDataDir)
1449
+ );
1450
+ }
1451
+ function buildPersistentProfileWriteLockDirPath(targetUserDataDir) {
1452
+ return (0, import_node_path2.join)((0, import_node_path2.dirname)(targetUserDataDir), `${(0, import_node_path2.basename)(targetUserDataDir)}.lock`);
1453
+ }
1454
+ function buildPersistentProfileControlLockDirPath(targetUserDataDir) {
1455
+ return (0, import_node_path2.join)(
1456
+ (0, import_node_path2.dirname)(targetUserDataDir),
1457
+ `${(0, import_node_path2.basename)(targetUserDataDir)}.control.lock`
1458
+ );
1459
+ }
1460
+ async function sleep2(ms) {
1461
+ await new Promise((resolve) => setTimeout(resolve, ms));
1462
+ }
1463
+
1464
+ // src/browser/shared-real-browser-session-state.ts
1465
+ var import_node_crypto2 = require("crypto");
1466
+ var import_node_fs2 = require("fs");
1467
+ var import_promises3 = require("fs/promises");
1468
+ var import_node_path3 = require("path");
1469
+ var SHARED_SESSION_METADATA_FILE = "session.json";
1470
+ var SHARED_SESSION_CLIENTS_DIR = "clients";
1471
+ var SHARED_SESSION_RETRY_DELAY_MS = 50;
1472
+ var SHARED_SESSION_METADATA_TEMP_FILE_PREFIX = `${SHARED_SESSION_METADATA_FILE}.`;
1473
+ var SHARED_SESSION_METADATA_TEMP_FILE_SUFFIX = ".tmp";
1474
+ function buildSharedSessionDirPath(persistentUserDataDir) {
1475
+ return (0, import_node_path3.join)(
1476
+ (0, import_node_path3.dirname)(persistentUserDataDir),
1477
+ `${(0, import_node_path3.basename)(persistentUserDataDir)}.session`
1478
+ );
1479
+ }
1480
+ function buildSharedSessionLockPath(persistentUserDataDir) {
1481
+ return `${buildSharedSessionDirPath(persistentUserDataDir)}.lock`;
1482
+ }
1483
+ function buildSharedSessionClientsDirPath(persistentUserDataDir) {
1484
+ return (0, import_node_path3.join)(
1485
+ buildSharedSessionDirPath(persistentUserDataDir),
1486
+ SHARED_SESSION_CLIENTS_DIR
1487
+ );
1488
+ }
1489
+ function buildSharedSessionClientPath(persistentUserDataDir, clientId) {
1490
+ return (0, import_node_path3.join)(
1491
+ buildSharedSessionClientsDirPath(persistentUserDataDir),
1492
+ `${clientId}.json`
1493
+ );
1494
+ }
1495
+ async function readSharedSessionMetadata(persistentUserDataDir) {
1496
+ return (await readSharedSessionMetadataRecord(persistentUserDataDir)).metadata;
1497
+ }
1498
+ async function writeSharedSessionMetadata(persistentUserDataDir, metadata) {
1499
+ const sessionDirPath = buildSharedSessionDirPath(persistentUserDataDir);
1500
+ const metadataPath = buildSharedSessionMetadataPath(persistentUserDataDir);
1501
+ const tempPath = buildSharedSessionMetadataTempPath(sessionDirPath);
1502
+ await (0, import_promises3.mkdir)(sessionDirPath, { recursive: true });
1503
+ try {
1504
+ await (0, import_promises3.writeFile)(tempPath, JSON.stringify(metadata, null, 2));
1505
+ await (0, import_promises3.rename)(tempPath, metadataPath);
1506
+ } finally {
1507
+ await (0, import_promises3.rm)(tempPath, { force: true }).catch(() => void 0);
1508
+ }
1509
+ }
1510
+ async function hasLiveSharedRealBrowserSession(persistentUserDataDir) {
1511
+ const sessionDirPath = buildSharedSessionDirPath(persistentUserDataDir);
1512
+ const metadataRecord = await readSharedSessionMetadataRecord(
1513
+ persistentUserDataDir
1514
+ );
1515
+ if (!metadataRecord.exists) {
1516
+ return await hasLiveSharedSessionPublisherOrClients(sessionDirPath);
1517
+ }
1518
+ if (!metadataRecord.metadata) {
1519
+ return true;
1520
+ }
1521
+ if (await getProcessLiveness(metadataRecord.metadata.browserOwner) === "dead") {
1522
+ await (0, import_promises3.rm)(sessionDirPath, {
1523
+ force: true,
1524
+ recursive: true
1525
+ }).catch(() => void 0);
1526
+ return false;
1527
+ }
1528
+ return true;
1529
+ }
1530
+ async function waitForSharedRealBrowserSessionToDrain(persistentUserDataDir) {
1531
+ while (true) {
1532
+ if (!await hasLiveSharedRealBrowserSession(persistentUserDataDir)) {
1533
+ return;
1534
+ }
1535
+ await sleep3(SHARED_SESSION_RETRY_DELAY_MS);
1536
+ }
1537
+ }
1538
+ async function readSharedSessionMetadataRecord(persistentUserDataDir) {
1539
+ try {
1540
+ const raw = await (0, import_promises3.readFile)(
1541
+ buildSharedSessionMetadataPath(persistentUserDataDir),
1542
+ "utf8"
1543
+ );
1544
+ return {
1545
+ exists: true,
1546
+ metadata: parseSharedSessionMetadata(JSON.parse(raw))
1547
+ };
1548
+ } catch (error) {
1549
+ return {
1550
+ exists: getErrorCode2(error) !== "ENOENT",
1551
+ metadata: null
1552
+ };
1553
+ }
1554
+ }
1555
+ async function hasLiveSharedSessionPublisherOrClients(sessionDirPath) {
1556
+ if (!(0, import_node_fs2.existsSync)(sessionDirPath)) {
1557
+ return false;
1558
+ }
1559
+ let entries;
1560
+ try {
1561
+ entries = await readDirNames(sessionDirPath);
1562
+ } catch (error) {
1563
+ return getErrorCode2(error) !== "ENOENT";
1564
+ }
1565
+ let hasUnknownEntries = false;
1566
+ for (const entry of entries) {
1567
+ if (entry === SHARED_SESSION_METADATA_FILE) {
1568
+ return true;
1569
+ }
1570
+ if (entry === SHARED_SESSION_CLIENTS_DIR) {
1571
+ if (await hasDirectoryEntries((0, import_node_path3.join)(sessionDirPath, entry))) {
1572
+ return true;
1573
+ }
1574
+ continue;
1575
+ }
1576
+ const owner = parseSharedSessionMetadataTempOwner(entry);
1577
+ if (!owner) {
1578
+ if (isSharedSessionMetadataTempFile(entry)) {
1579
+ continue;
1580
+ }
1581
+ hasUnknownEntries = true;
1582
+ continue;
1583
+ }
1584
+ if (await getProcessLiveness(owner) !== "dead") {
1585
+ return true;
1586
+ }
1587
+ }
1588
+ if (hasUnknownEntries) {
1589
+ return true;
1590
+ }
1591
+ await (0, import_promises3.rm)(sessionDirPath, {
1592
+ force: true,
1593
+ recursive: true
1594
+ }).catch(() => void 0);
1595
+ return false;
1596
+ }
1597
+ function buildSharedSessionMetadataPath(persistentUserDataDir) {
1598
+ return (0, import_node_path3.join)(
1599
+ buildSharedSessionDirPath(persistentUserDataDir),
1600
+ SHARED_SESSION_METADATA_FILE
1601
+ );
1602
+ }
1603
+ function buildSharedSessionMetadataTempPath(sessionDirPath) {
1604
+ return (0, import_node_path3.join)(
1605
+ sessionDirPath,
1606
+ [
1607
+ SHARED_SESSION_METADATA_FILE,
1608
+ CURRENT_PROCESS_OWNER.pid,
1609
+ CURRENT_PROCESS_OWNER.processStartedAtMs,
1610
+ (0, import_node_crypto2.randomUUID)(),
1611
+ "tmp"
1612
+ ].join(".")
1613
+ );
1614
+ }
1615
+ function parseSharedSessionMetadata(value) {
1616
+ if (!value || typeof value !== "object") {
1617
+ return null;
1618
+ }
1619
+ const parsed = value;
1620
+ const browserOwner = parseProcessOwner(parsed.browserOwner);
1621
+ const stateOwner = parseProcessOwner(parsed.stateOwner);
1622
+ const state = parsed.state === "launching" || parsed.state === "ready" || parsed.state === "closing" ? parsed.state : null;
1623
+ if (!browserOwner || !stateOwner || typeof parsed.createdAt !== "string" || typeof parsed.debugPort !== "number" || typeof parsed.executablePath !== "string" || typeof parsed.headless !== "boolean" || typeof parsed.persistentUserDataDir !== "string" || typeof parsed.profileDirectory !== "string" || typeof parsed.sessionId !== "string" || !state) {
1624
+ return null;
1625
+ }
1626
+ return {
1627
+ browserOwner,
1628
+ createdAt: parsed.createdAt,
1629
+ debugPort: parsed.debugPort,
1630
+ executablePath: parsed.executablePath,
1631
+ headless: parsed.headless,
1632
+ persistentUserDataDir: parsed.persistentUserDataDir,
1633
+ profileDirectory: parsed.profileDirectory,
1634
+ sessionId: parsed.sessionId,
1635
+ state,
1636
+ stateOwner
1637
+ };
1638
+ }
1639
+ function parseSharedSessionMetadataTempOwner(entryName) {
1640
+ if (!isSharedSessionMetadataTempFile(entryName)) {
1641
+ return null;
1642
+ }
1643
+ const segments = entryName.split(".");
1644
+ if (segments.length < 5) {
1645
+ return null;
1646
+ }
1647
+ return parseProcessOwner({
1648
+ pid: Number.parseInt(segments[2] ?? "", 10),
1649
+ processStartedAtMs: Number.parseInt(segments[3] ?? "", 10)
1650
+ });
1651
+ }
1652
+ function isSharedSessionMetadataTempFile(entryName) {
1653
+ return entryName.startsWith(SHARED_SESSION_METADATA_TEMP_FILE_PREFIX) && entryName.endsWith(SHARED_SESSION_METADATA_TEMP_FILE_SUFFIX);
1654
+ }
1655
+ function getErrorCode2(error) {
1656
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
1657
+ }
1658
+ async function hasDirectoryEntries(dirPath) {
1659
+ try {
1660
+ return (await readDirNames(dirPath)).length > 0;
1661
+ } catch (error) {
1662
+ return getErrorCode2(error) !== "ENOENT";
1663
+ }
1664
+ }
1665
+ async function readDirNames(dirPath) {
1666
+ return await (0, import_promises3.readdir)(dirPath, { encoding: "utf8" });
1667
+ }
1668
+ async function sleep3(ms) {
1669
+ await new Promise((resolve) => setTimeout(resolve, ms));
1670
+ }
1671
+
1672
+ // src/browser/persistent-profile.ts
1673
+ var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
1674
+ var OPENSTEER_META_FILE = ".opensteer-meta.json";
1675
+ var OPENSTEER_RUNTIME_META_FILE = ".opensteer-runtime.json";
1676
+ var OPENSTEER_RUNTIME_CREATING_FILE = ".opensteer-runtime-creating.json";
1677
+ var PROCESS_LIST_MAX_BUFFER_BYTES2 = 16 * 1024 * 1024;
1678
+ var PS_COMMAND_ENV2 = { ...process.env, LC_ALL: "C" };
1679
+ var CHROME_SINGLETON_ENTRIES = /* @__PURE__ */ new Set([
1680
+ "SingletonCookie",
1681
+ "SingletonLock",
1682
+ "SingletonSocket",
1683
+ "DevToolsActivePort",
1684
+ "lockfile"
1685
+ ]);
1686
+ var COPY_SKIP_ENTRIES = /* @__PURE__ */ new Set([
1687
+ ...CHROME_SINGLETON_ENTRIES,
1688
+ OPENSTEER_META_FILE,
1689
+ OPENSTEER_RUNTIME_META_FILE,
1690
+ OPENSTEER_RUNTIME_CREATING_FILE
1691
+ ]);
1692
+ var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
1693
+ "Crash Reports",
1694
+ "Crashpad",
1695
+ "BrowserMetrics",
1696
+ "GrShaderCache",
1697
+ "ShaderCache",
1698
+ "GraphiteDawnCache",
1699
+ "component_crx_cache",
1700
+ "Crowd Deny",
1701
+ "hyphen-data",
1702
+ "OnDeviceHeadSuggestModel",
1703
+ "OptimizationGuidePredictionModels",
1704
+ "Segmentation Platform",
1705
+ "SmartCardDeviceNames",
1706
+ "WidevineCdm",
1707
+ "pnacl"
1708
+ ]);
1709
+ async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory, profilesRootDir = defaultPersistentProfilesRootDir()) {
1710
+ const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
1711
+ const targetUserDataDir = (0, import_node_path4.join)(
1712
+ expandHome(profilesRootDir),
1713
+ buildPersistentProfileKey(resolvedSourceUserDataDir, profileDirectory)
1714
+ );
1715
+ const sourceProfileDir = (0, import_node_path4.join)(resolvedSourceUserDataDir, profileDirectory);
1716
+ const metadata = buildPersistentProfileMetadata(
1717
+ resolvedSourceUserDataDir,
1718
+ profileDirectory
1719
+ );
1720
+ await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(targetUserDataDir), { recursive: true });
1721
+ if (await isHealthyPersistentProfile(
1722
+ targetUserDataDir,
1723
+ resolvedSourceUserDataDir,
1724
+ profileDirectory
1725
+ ) && !await isPersistentProfileWriteLocked(targetUserDataDir)) {
1726
+ return {
1727
+ created: false,
1728
+ userDataDir: targetUserDataDir
1729
+ };
1730
+ }
1731
+ return await withPersistentProfileWriteAccess(targetUserDataDir, async () => {
1732
+ await recoverPersistentProfileBackup(targetUserDataDir);
1733
+ await cleanOrphanedOwnedDirs(
1734
+ (0, import_node_path4.dirname)(targetUserDataDir),
1735
+ buildPersistentProfileTempDirNamePrefix(targetUserDataDir)
1736
+ );
1737
+ if (!(0, import_node_fs3.existsSync)(sourceProfileDir)) {
1738
+ throw new Error(
1739
+ `Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
1740
+ );
1741
+ }
1742
+ const created = await createPersistentProfileClone(
1743
+ resolvedSourceUserDataDir,
1744
+ sourceProfileDir,
1745
+ targetUserDataDir,
1746
+ profileDirectory,
1747
+ metadata
1748
+ );
1749
+ await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
1750
+ return {
1751
+ created,
1752
+ userDataDir: targetUserDataDir
1753
+ };
1754
+ });
1755
+ }
1756
+ async function clearPersistentProfileSingletons(userDataDir) {
1757
+ await Promise.all(
1758
+ [...CHROME_SINGLETON_ENTRIES].map(
1759
+ (entry) => (0, import_promises4.rm)((0, import_node_path4.join)(userDataDir, entry), {
1760
+ force: true,
1761
+ recursive: true
1762
+ }).catch(() => void 0)
1763
+ )
1764
+ );
1765
+ }
1766
+ async function createIsolatedRuntimeProfile(sourceUserDataDir, runtimesRootDir = defaultRuntimeProfilesRootDir()) {
1767
+ const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
1768
+ const runtimeRootDir = expandHome(runtimesRootDir);
1769
+ await (0, import_promises4.mkdir)(runtimeRootDir, { recursive: true });
1770
+ const sourceMetadata = await requirePersistentProfileMetadata(
1771
+ resolvedSourceUserDataDir
1772
+ );
1773
+ const runtimeProfile = await reserveRuntimeProfileCreation(
1774
+ resolvedSourceUserDataDir,
1775
+ runtimeRootDir,
1776
+ sourceMetadata.profileDirectory
1777
+ );
1778
+ try {
1779
+ await cleanOrphanedRuntimeProfileDirs(
1780
+ runtimeRootDir,
1781
+ buildRuntimeProfileDirNamePrefix(resolvedSourceUserDataDir)
1782
+ );
1783
+ await copyUserDataDirSnapshot(
1784
+ resolvedSourceUserDataDir,
1785
+ runtimeProfile.userDataDir
1786
+ );
1787
+ const currentSourceMetadata = await readPersistentProfileMetadata(
1788
+ resolvedSourceUserDataDir
1789
+ );
1790
+ await writeRuntimeProfileMetadata(
1791
+ runtimeProfile.userDataDir,
1792
+ await buildRuntimeProfileMetadata(
1793
+ runtimeProfile.userDataDir,
1794
+ resolvedSourceUserDataDir,
1795
+ currentSourceMetadata?.profileDirectory ?? sourceMetadata.profileDirectory
1796
+ )
1797
+ );
1798
+ await clearRuntimeProfileCreationState(
1799
+ runtimeProfile.userDataDir,
1800
+ resolvedSourceUserDataDir
1801
+ );
1802
+ return {
1803
+ persistentUserDataDir: resolvedSourceUserDataDir,
1804
+ userDataDir: runtimeProfile.userDataDir
1805
+ };
1806
+ } catch (error) {
1807
+ await clearRuntimeProfileCreationState(
1808
+ runtimeProfile.userDataDir,
1809
+ resolvedSourceUserDataDir
1810
+ );
1811
+ await (0, import_promises4.rm)(runtimeProfile.userDataDir, {
1812
+ recursive: true,
1813
+ force: true
1814
+ }).catch(() => void 0);
1815
+ throw error;
1816
+ }
1817
+ }
1818
+ async function persistIsolatedRuntimeProfile(runtimeUserDataDir, persistentUserDataDir) {
1819
+ const resolvedRuntimeUserDataDir = expandHome(runtimeUserDataDir);
1820
+ const resolvedPersistentUserDataDir = expandHome(persistentUserDataDir);
1821
+ let claimedRuntimeUserDataDir = null;
1822
+ try {
1823
+ await withPersistentProfileWriteAccess(
1824
+ resolvedPersistentUserDataDir,
1825
+ async () => {
1826
+ await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(resolvedPersistentUserDataDir), {
1827
+ recursive: true
1828
+ });
1829
+ await recoverPersistentProfileBackup(resolvedPersistentUserDataDir);
1830
+ await cleanOrphanedOwnedDirs(
1831
+ (0, import_node_path4.dirname)(resolvedPersistentUserDataDir),
1832
+ buildPersistentProfileTempDirNamePrefix(
1833
+ resolvedPersistentUserDataDir
1834
+ )
1835
+ );
1836
+ const metadata = await requirePersistentProfileMetadata(
1837
+ resolvedPersistentUserDataDir
1838
+ );
1839
+ claimedRuntimeUserDataDir = await claimRuntimeProfileForPersist(
1840
+ resolvedRuntimeUserDataDir
1841
+ );
1842
+ const runtimeMetadata = await requireRuntimeProfileMetadata(
1843
+ claimedRuntimeUserDataDir,
1844
+ resolvedPersistentUserDataDir,
1845
+ metadata.profileDirectory,
1846
+ resolvedRuntimeUserDataDir
1847
+ );
1848
+ await mergePersistentProfileSnapshot(
1849
+ claimedRuntimeUserDataDir,
1850
+ resolvedPersistentUserDataDir,
1851
+ metadata,
1852
+ runtimeMetadata
1853
+ );
1854
+ }
1855
+ );
1856
+ } catch (error) {
1857
+ if (claimedRuntimeUserDataDir) {
1858
+ try {
1859
+ await restoreClaimedRuntimeProfile(
1860
+ claimedRuntimeUserDataDir,
1861
+ resolvedRuntimeUserDataDir
1862
+ );
1863
+ } catch (restoreError) {
1864
+ throw new AggregateError(
1865
+ [error, restoreError],
1866
+ `Failed to restore runtime profile "${resolvedRuntimeUserDataDir}" after persistence failed.`
1867
+ );
1868
+ }
1869
+ }
1870
+ throw error;
1871
+ }
1872
+ if (claimedRuntimeUserDataDir) {
1873
+ await (0, import_promises4.rm)(claimedRuntimeUserDataDir, {
1874
+ recursive: true,
1875
+ force: true
1876
+ }).catch(() => void 0);
1877
+ }
1878
+ }
1879
+ function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
1880
+ const hash = (0, import_node_crypto3.createHash)("sha256").update(`${sourceUserDataDir}\0${profileDirectory}`).digest("hex").slice(0, 16);
1881
+ const sourceLabel = sanitizePathSegment((0, import_node_path4.basename)(sourceUserDataDir) || "user-data");
1882
+ const profileLabel = sanitizePathSegment(profileDirectory || "Default");
1883
+ return `${sourceLabel}-${profileLabel}-${hash}`;
1884
+ }
1885
+ function defaultPersistentProfilesRootDir() {
1886
+ return (0, import_node_path4.join)((0, import_node_os.homedir)(), ".opensteer", "real-browser-profiles");
1887
+ }
1888
+ function defaultRuntimeProfilesRootDir() {
1889
+ return (0, import_node_path4.join)((0, import_node_os.tmpdir)(), "opensteer-real-browser-runtimes");
1890
+ }
1891
+ function sanitizePathSegment(value) {
1892
+ const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-");
1893
+ return sanitized.replace(/^-|-$/g, "") || "profile";
1894
+ }
1895
+ function isProfileDirectory(userDataDir, entry) {
1896
+ return (0, import_node_fs3.existsSync)((0, import_node_path4.join)(userDataDir, entry, "Preferences"));
1897
+ }
1898
+ async function copyUserDataDirSnapshot(sourceUserDataDir, targetUserDataDir) {
1899
+ await (0, import_promises4.cp)(sourceUserDataDir, targetUserDataDir, {
1900
+ recursive: true,
1901
+ filter: (candidatePath) => shouldCopyRuntimeSnapshotEntry(sourceUserDataDir, candidatePath)
1902
+ });
1903
+ }
1904
+ function shouldCopyRuntimeSnapshotEntry(userDataDir, candidatePath) {
1905
+ const candidateRelativePath = (0, import_node_path4.relative)(userDataDir, candidatePath);
1906
+ if (!candidateRelativePath) {
1907
+ return true;
1908
+ }
1909
+ const segments = candidateRelativePath.split(import_node_path4.sep).filter(Boolean);
1910
+ if (segments.length !== 1) {
1911
+ return true;
1912
+ }
1913
+ return !COPY_SKIP_ENTRIES.has(segments[0]);
1914
+ }
1915
+ async function copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, targetProfileDirectory) {
1916
+ let entries;
1917
+ try {
1918
+ entries = await (0, import_promises4.readdir)(sourceUserDataDir);
1919
+ } catch {
1920
+ return;
1921
+ }
1922
+ const copyTasks = [];
1923
+ for (const entry of entries) {
1924
+ if (COPY_SKIP_ENTRIES.has(entry)) continue;
1925
+ if (entry === targetProfileDirectory) continue;
1926
+ const sourcePath = (0, import_node_path4.join)(sourceUserDataDir, entry);
1927
+ const targetPath = (0, import_node_path4.join)(targetUserDataDir, entry);
1928
+ if ((0, import_node_fs3.existsSync)(targetPath)) continue;
1929
+ let entryStat;
1930
+ try {
1931
+ entryStat = await (0, import_promises4.stat)(sourcePath);
1932
+ } catch {
1933
+ continue;
1934
+ }
1935
+ if (entryStat.isFile()) {
1936
+ copyTasks.push((0, import_promises4.copyFile)(sourcePath, targetPath).catch(() => void 0));
1937
+ } else if (entryStat.isDirectory()) {
1938
+ if (isProfileDirectory(sourceUserDataDir, entry)) continue;
1939
+ if (SKIPPED_ROOT_DIRECTORIES.has(entry)) continue;
1940
+ copyTasks.push(
1941
+ (0, import_promises4.cp)(sourcePath, targetPath, { recursive: true }).catch(
1942
+ () => void 0
1943
+ )
1944
+ );
1945
+ }
1946
+ }
1947
+ await Promise.all(copyTasks);
1948
+ }
1949
+ async function writePersistentProfileMetadata(userDataDir, metadata) {
1950
+ await (0, import_promises4.writeFile)(
1951
+ (0, import_node_path4.join)(userDataDir, OPENSTEER_META_FILE),
1952
+ JSON.stringify(metadata, null, 2)
1953
+ );
1954
+ }
1955
+ function buildPersistentProfileMetadata(sourceUserDataDir, profileDirectory) {
1956
+ return {
1957
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1958
+ profileDirectory,
1959
+ source: sourceUserDataDir
1960
+ };
1961
+ }
1962
+ async function createPersistentProfileClone(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
1963
+ if ((0, import_node_fs3.existsSync)(targetUserDataDir)) {
1964
+ return false;
1965
+ }
1966
+ const tempUserDataDir = await (0, import_promises4.mkdtemp)(
1967
+ buildPersistentProfileTempDirPrefix(targetUserDataDir)
1968
+ );
1969
+ let published = false;
1970
+ try {
1971
+ await materializePersistentProfileSnapshot(
1972
+ sourceUserDataDir,
1973
+ sourceProfileDir,
1974
+ tempUserDataDir,
1975
+ profileDirectory,
1976
+ metadata
1977
+ );
1978
+ try {
1979
+ await (0, import_promises4.rename)(tempUserDataDir, targetUserDataDir);
1980
+ } catch (error) {
1981
+ if (wasDirPublishedByAnotherProcess2(error, targetUserDataDir)) {
1982
+ return false;
1983
+ }
1984
+ throw error;
1985
+ }
1986
+ published = true;
1987
+ return true;
1988
+ } finally {
1989
+ if (!published) {
1990
+ await (0, import_promises4.rm)(tempUserDataDir, {
1991
+ recursive: true,
1992
+ force: true
1993
+ }).catch(() => void 0);
1994
+ }
1995
+ }
1996
+ }
1997
+ async function materializePersistentProfileSnapshot(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
1998
+ if (!(0, import_node_fs3.existsSync)(sourceProfileDir)) {
1999
+ throw new Error(
2000
+ `Chrome profile "${profileDirectory}" was not found in "${sourceUserDataDir}".`
2001
+ );
2002
+ }
2003
+ await (0, import_promises4.cp)(sourceProfileDir, (0, import_node_path4.join)(targetUserDataDir, profileDirectory), {
2004
+ recursive: true
2005
+ });
2006
+ await copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, profileDirectory);
2007
+ await writePersistentProfileMetadata(targetUserDataDir, metadata);
2008
+ }
2009
+ async function mergePersistentProfileSnapshot(runtimeUserDataDir, persistentUserDataDir, metadata, runtimeMetadata) {
2010
+ const tempUserDataDir = await (0, import_promises4.mkdtemp)(
2011
+ buildPersistentProfileTempDirPrefix(persistentUserDataDir)
2012
+ );
2013
+ let published = false;
2014
+ try {
2015
+ const baseEntries = deserializeSnapshotManifestEntries(
2016
+ runtimeMetadata.baseEntries
2017
+ );
2018
+ const currentEntries = await collectPersistentSnapshotEntries(
2019
+ persistentUserDataDir,
2020
+ metadata.profileDirectory
2021
+ );
2022
+ const runtimeEntries = await collectPersistentSnapshotEntries(
2023
+ runtimeUserDataDir,
2024
+ metadata.profileDirectory
2025
+ );
2026
+ const mergedEntries = resolveMergedSnapshotEntries(
2027
+ baseEntries,
2028
+ currentEntries,
2029
+ runtimeEntries
2030
+ );
2031
+ await materializeMergedPersistentProfileSnapshot(
2032
+ tempUserDataDir,
2033
+ currentEntries,
2034
+ runtimeEntries,
2035
+ mergedEntries
2036
+ );
2037
+ await writePersistentProfileMetadata(tempUserDataDir, metadata);
2038
+ await replaceProfileDirectory(persistentUserDataDir, tempUserDataDir);
2039
+ published = true;
2040
+ } finally {
2041
+ if (!published) {
2042
+ await (0, import_promises4.rm)(tempUserDataDir, {
2043
+ recursive: true,
2044
+ force: true
2045
+ }).catch(() => void 0);
2046
+ }
2047
+ }
2048
+ }
2049
+ async function buildRuntimeProfileMetadata(runtimeUserDataDir, persistentUserDataDir, profileDirectory) {
2050
+ const baseEntries = profileDirectory ? serializeSnapshotManifestEntries(
2051
+ await collectPersistentSnapshotEntries(
2052
+ runtimeUserDataDir,
2053
+ profileDirectory
2054
+ )
2055
+ ) : {};
2056
+ return {
2057
+ baseEntries,
2058
+ creator: CURRENT_PROCESS_OWNER,
2059
+ persistentUserDataDir,
2060
+ profileDirectory
2061
+ };
2062
+ }
2063
+ async function writeRuntimeProfileMetadata(userDataDir, metadata) {
2064
+ await (0, import_promises4.writeFile)(
2065
+ (0, import_node_path4.join)(userDataDir, OPENSTEER_RUNTIME_META_FILE),
2066
+ JSON.stringify(metadata, null, 2)
2067
+ );
2068
+ }
2069
+ async function writeRuntimeProfileCreationMarker(userDataDir, marker) {
2070
+ await (0, import_promises4.writeFile)(
2071
+ (0, import_node_path4.join)(userDataDir, OPENSTEER_RUNTIME_CREATING_FILE),
2072
+ JSON.stringify(marker, null, 2)
2073
+ );
2074
+ }
2075
+ async function readRuntimeProfileMetadata(userDataDir) {
2076
+ try {
2077
+ const raw = await (0, import_promises4.readFile)(
2078
+ (0, import_node_path4.join)(userDataDir, OPENSTEER_RUNTIME_META_FILE),
2079
+ "utf8"
2080
+ );
2081
+ const parsed = JSON.parse(raw);
2082
+ const creator = parseProcessOwner(parsed.creator);
2083
+ const persistentUserDataDir = typeof parsed.persistentUserDataDir === "string" ? parsed.persistentUserDataDir : void 0;
2084
+ const profileDirectory = parsed.profileDirectory === null ? null : typeof parsed.profileDirectory === "string" ? parsed.profileDirectory : void 0;
2085
+ if (!creator || persistentUserDataDir === void 0 || profileDirectory === void 0 || typeof parsed.baseEntries !== "object" || parsed.baseEntries === null || Array.isArray(parsed.baseEntries)) {
2086
+ return null;
2087
+ }
2088
+ const baseEntries = deserializeSnapshotManifestEntries(
2089
+ parsed.baseEntries
2090
+ );
2091
+ return {
2092
+ baseEntries: Object.fromEntries(baseEntries),
2093
+ creator,
2094
+ persistentUserDataDir,
2095
+ profileDirectory
2096
+ };
2097
+ } catch {
2098
+ return null;
2099
+ }
2100
+ }
2101
+ async function readRuntimeProfileCreationMarker(userDataDir) {
2102
+ try {
2103
+ const raw = await (0, import_promises4.readFile)(
2104
+ (0, import_node_path4.join)(userDataDir, OPENSTEER_RUNTIME_CREATING_FILE),
2105
+ "utf8"
2106
+ );
2107
+ return parseRuntimeProfileCreationMarker(JSON.parse(raw));
2108
+ } catch {
2109
+ return null;
2110
+ }
2111
+ }
2112
+ async function requireRuntimeProfileMetadata(userDataDir, expectedPersistentUserDataDir, expectedProfileDirectory, displayUserDataDir = userDataDir) {
2113
+ const metadata = await readRuntimeProfileMetadata(userDataDir);
2114
+ if (!metadata) {
2115
+ throw new Error(
2116
+ `Runtime profile metadata was not found for "${displayUserDataDir}".`
2117
+ );
2118
+ }
2119
+ if (metadata.profileDirectory !== expectedProfileDirectory) {
2120
+ throw new Error(
2121
+ `Runtime profile "${displayUserDataDir}" was created for profile "${metadata.profileDirectory ?? "unknown"}", expected "${expectedProfileDirectory}".`
2122
+ );
2123
+ }
2124
+ if (metadata.persistentUserDataDir !== expectedPersistentUserDataDir) {
2125
+ throw new Error(
2126
+ `Runtime profile "${displayUserDataDir}" does not belong to persistent profile "${expectedPersistentUserDataDir}".`
2127
+ );
2128
+ }
2129
+ return metadata;
2130
+ }
2131
+ async function collectPersistentSnapshotEntries(userDataDir, profileDirectory) {
2132
+ let rootEntries;
2133
+ try {
2134
+ rootEntries = await (0, import_promises4.readdir)(userDataDir, {
2135
+ encoding: "utf8",
2136
+ withFileTypes: true
2137
+ });
2138
+ } catch {
2139
+ return /* @__PURE__ */ new Map();
2140
+ }
2141
+ rootEntries.sort((left, right) => left.name.localeCompare(right.name));
2142
+ const collected = /* @__PURE__ */ new Map();
2143
+ for (const entry of rootEntries) {
2144
+ if (!shouldIncludePersistentRootEntry(
2145
+ userDataDir,
2146
+ profileDirectory,
2147
+ entry.name
2148
+ )) {
2149
+ continue;
2150
+ }
2151
+ await collectSnapshotEntry(
2152
+ (0, import_node_path4.join)(userDataDir, entry.name),
2153
+ entry.name,
2154
+ collected
2155
+ );
2156
+ }
2157
+ return collected;
2158
+ }
2159
+ function shouldIncludePersistentRootEntry(userDataDir, profileDirectory, entry) {
2160
+ if (entry === profileDirectory) {
2161
+ return true;
2162
+ }
2163
+ if (COPY_SKIP_ENTRIES.has(entry)) {
2164
+ return false;
2165
+ }
2166
+ if (SKIPPED_ROOT_DIRECTORIES.has(entry)) {
2167
+ return false;
2168
+ }
2169
+ return !isProfileDirectory(userDataDir, entry);
2170
+ }
2171
+ async function collectSnapshotEntry(sourcePath, relativePath, collected) {
2172
+ let entryStat;
2173
+ try {
2174
+ entryStat = await (0, import_promises4.stat)(sourcePath);
2175
+ } catch {
2176
+ return;
2177
+ }
2178
+ if (entryStat.isDirectory()) {
2179
+ collected.set(relativePath, {
2180
+ kind: "directory",
2181
+ hash: null,
2182
+ sourcePath
2183
+ });
2184
+ let children;
2185
+ try {
2186
+ children = await (0, import_promises4.readdir)(sourcePath, {
2187
+ encoding: "utf8",
2188
+ withFileTypes: true
2189
+ });
2190
+ } catch {
2191
+ return;
2192
+ }
2193
+ children.sort((left, right) => left.name.localeCompare(right.name));
2194
+ for (const child of children) {
2195
+ await collectSnapshotEntry(
2196
+ (0, import_node_path4.join)(sourcePath, child.name),
2197
+ (0, import_node_path4.join)(relativePath, child.name),
2198
+ collected
2199
+ );
2200
+ }
2201
+ return;
2202
+ }
2203
+ if (entryStat.isFile()) {
2204
+ collected.set(relativePath, {
2205
+ kind: "file",
2206
+ hash: await hashSnapshotFile(sourcePath, relativePath),
2207
+ sourcePath
2208
+ });
2209
+ }
2210
+ }
2211
+ function serializeSnapshotManifestEntries(entries) {
2212
+ return Object.fromEntries(
2213
+ [...entries.entries()].sort(([leftPath], [rightPath]) => leftPath.localeCompare(rightPath)).map(([relativePath, entry]) => [
2214
+ relativePath,
2215
+ {
2216
+ kind: entry.kind,
2217
+ hash: entry.hash
2218
+ }
2219
+ ])
2220
+ );
2221
+ }
2222
+ function deserializeSnapshotManifestEntries(entries) {
2223
+ const manifestEntries = /* @__PURE__ */ new Map();
2224
+ for (const [relativePath, entry] of Object.entries(entries)) {
2225
+ if (!entry || entry.kind !== "directory" && entry.kind !== "file" || !(entry.hash === null || typeof entry.hash === "string")) {
2226
+ throw new Error(
2227
+ `Runtime profile metadata for "${relativePath}" is invalid.`
2228
+ );
2229
+ }
2230
+ manifestEntries.set(relativePath, {
2231
+ kind: entry.kind,
2232
+ hash: entry.hash
2233
+ });
2234
+ }
2235
+ return manifestEntries;
2236
+ }
2237
+ function resolveMergedSnapshotEntries(baseEntries, currentEntries, runtimeEntries) {
2238
+ const mergedEntries = /* @__PURE__ */ new Map();
2239
+ const relativePaths = /* @__PURE__ */ new Set([
2240
+ ...baseEntries.keys(),
2241
+ ...currentEntries.keys(),
2242
+ ...runtimeEntries.keys()
2243
+ ]);
2244
+ for (const relativePath of [...relativePaths].sort(compareSnapshotPaths)) {
2245
+ mergedEntries.set(
2246
+ relativePath,
2247
+ resolveMergedSnapshotEntrySelection(
2248
+ relativePath,
2249
+ baseEntries.get(relativePath) ?? null,
2250
+ currentEntries.get(relativePath) ?? null,
2251
+ runtimeEntries.get(relativePath) ?? null
2252
+ )
2253
+ );
2254
+ }
2255
+ return mergedEntries;
2256
+ }
2257
+ function resolveMergedSnapshotEntrySelection(relativePath, baseEntry, currentEntry, runtimeEntry) {
2258
+ if (snapshotEntriesEqual(runtimeEntry, baseEntry)) {
2259
+ return currentEntry ? "current" : null;
2260
+ }
2261
+ if (snapshotEntriesEqual(currentEntry, baseEntry)) {
2262
+ return runtimeEntry ? "runtime" : null;
2263
+ }
2264
+ if (!baseEntry) {
2265
+ if (!currentEntry) {
2266
+ return runtimeEntry ? "runtime" : null;
2267
+ }
2268
+ if (!runtimeEntry) {
2269
+ return "current";
2270
+ }
2271
+ if (snapshotEntriesEqual(currentEntry, runtimeEntry)) {
2272
+ return "current";
2273
+ }
2274
+ throw new Error(
2275
+ `Concurrent runtime updates changed "${relativePath}" differently; refusing to overwrite the persistent profile.`
2276
+ );
2277
+ }
2278
+ if (!currentEntry && !runtimeEntry) {
2279
+ return null;
2280
+ }
2281
+ if (snapshotEntriesEqual(currentEntry, runtimeEntry)) {
2282
+ return currentEntry ? "current" : null;
2283
+ }
2284
+ throw new Error(
2285
+ `Concurrent runtime updates changed "${relativePath}" differently; refusing to overwrite the persistent profile.`
2286
+ );
2287
+ }
2288
+ function snapshotEntriesEqual(left, right) {
2289
+ if (!left || !right) {
2290
+ return left === right;
2291
+ }
2292
+ return left.kind === right.kind && left.hash === right.hash;
2293
+ }
2294
+ async function materializeMergedPersistentProfileSnapshot(targetUserDataDir, currentEntries, runtimeEntries, mergedEntries) {
2295
+ const selectedEntries = [...mergedEntries.entries()].filter(([, selection]) => selection !== null).sort(
2296
+ ([leftPath], [rightPath]) => compareSnapshotPaths(leftPath, rightPath)
2297
+ );
2298
+ for (const [relativePath, selection] of selectedEntries) {
2299
+ const entry = (selection === "current" ? currentEntries.get(relativePath) : runtimeEntries.get(relativePath)) ?? null;
2300
+ if (!entry) {
2301
+ continue;
2302
+ }
2303
+ const targetPath = (0, import_node_path4.join)(targetUserDataDir, relativePath);
2304
+ if (entry.kind === "directory") {
2305
+ await (0, import_promises4.mkdir)(targetPath, { recursive: true });
2306
+ continue;
2307
+ }
2308
+ await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(targetPath), { recursive: true });
2309
+ await (0, import_promises4.copyFile)(entry.sourcePath, targetPath);
2310
+ }
2311
+ }
2312
+ function compareSnapshotPaths(left, right) {
2313
+ const leftDepth = left.split(import_node_path4.sep).length;
2314
+ const rightDepth = right.split(import_node_path4.sep).length;
2315
+ if (leftDepth !== rightDepth) {
2316
+ return leftDepth - rightDepth;
2317
+ }
2318
+ return left.localeCompare(right);
2319
+ }
2320
+ async function hashSnapshotFile(filePath, relativePath) {
2321
+ const normalizedJson = await readNormalizedSnapshotJson(filePath, relativePath);
2322
+ if (normalizedJson !== null) {
2323
+ return (0, import_node_crypto3.createHash)("sha256").update(JSON.stringify(normalizedJson)).digest("hex");
2324
+ }
2325
+ return await hashFile(filePath);
2326
+ }
2327
+ async function readNormalizedSnapshotJson(filePath, relativePath) {
2328
+ const normalizer = SNAPSHOT_JSON_NORMALIZERS.get(relativePath);
2329
+ if (!normalizer) {
2330
+ return null;
2331
+ }
2332
+ try {
2333
+ const parsed = JSON.parse(await (0, import_promises4.readFile)(filePath, "utf8"));
2334
+ return normalizer(parsed);
2335
+ } catch {
2336
+ return null;
2337
+ }
2338
+ }
2339
+ var SNAPSHOT_JSON_NORMALIZERS = /* @__PURE__ */ new Map([["Local State", normalizeLocalStateSnapshotJson]]);
2340
+ function normalizeLocalStateSnapshotJson(value) {
2341
+ if (!isJsonRecord(value)) {
2342
+ return value;
2343
+ }
2344
+ const { user_experience_metrics: _ignored, ...rest } = value;
2345
+ return rest;
2346
+ }
2347
+ function isJsonRecord(value) {
2348
+ return !!value && typeof value === "object" && !Array.isArray(value);
2349
+ }
2350
+ async function hashFile(filePath) {
2351
+ return new Promise((resolve, reject) => {
2352
+ const hash = (0, import_node_crypto3.createHash)("sha256");
2353
+ const stream = (0, import_node_fs3.createReadStream)(filePath);
2354
+ stream.on("data", (chunk) => {
2355
+ hash.update(chunk);
2356
+ });
2357
+ stream.on("error", reject);
2358
+ stream.on("end", () => {
2359
+ resolve(hash.digest("hex"));
2360
+ });
2361
+ });
2362
+ }
2363
+ async function ensurePersistentProfileMetadata(userDataDir, metadata) {
2364
+ if ((0, import_node_fs3.existsSync)((0, import_node_path4.join)(userDataDir, OPENSTEER_META_FILE))) {
2365
+ return;
2366
+ }
2367
+ await writePersistentProfileMetadata(userDataDir, metadata);
2368
+ }
2369
+ async function recoverPersistentProfileBackup(targetUserDataDir) {
2370
+ const backupDirPaths = await listPersistentProfileBackupDirs(targetUserDataDir);
2371
+ if (backupDirPaths.length === 0) {
2372
+ return;
2373
+ }
2374
+ if (!(0, import_node_fs3.existsSync)(targetUserDataDir)) {
2375
+ const [latestBackupDirPath, ...staleBackupDirPaths] = backupDirPaths;
2376
+ await (0, import_promises4.rename)(latestBackupDirPath, targetUserDataDir);
2377
+ await Promise.all(
2378
+ staleBackupDirPaths.map(
2379
+ (backupDirPath) => (0, import_promises4.rm)(backupDirPath, {
2380
+ recursive: true,
2381
+ force: true
2382
+ }).catch(() => void 0)
2383
+ )
2384
+ );
2385
+ return;
2386
+ }
2387
+ await Promise.all(
2388
+ backupDirPaths.map(
2389
+ (backupDirPath) => (0, import_promises4.rm)(backupDirPath, {
2390
+ recursive: true,
2391
+ force: true
2392
+ }).catch(() => void 0)
2393
+ )
2394
+ );
2395
+ }
2396
+ async function listPersistentProfileBackupDirs(targetUserDataDir) {
2397
+ const profilesDir = (0, import_node_path4.dirname)(targetUserDataDir);
2398
+ let entries;
2399
+ try {
2400
+ entries = await (0, import_promises4.readdir)(profilesDir, {
2401
+ encoding: "utf8",
2402
+ withFileTypes: true
2403
+ });
2404
+ } catch {
2405
+ return [];
2406
+ }
2407
+ const backupDirNamePrefix = buildPersistentProfileBackupDirNamePrefix(targetUserDataDir);
2408
+ return entries.filter(
2409
+ (entry) => entry.isDirectory() && entry.name.startsWith(backupDirNamePrefix)
2410
+ ).map((entry) => (0, import_node_path4.join)(profilesDir, entry.name)).sort((leftPath, rightPath) => rightPath.localeCompare(leftPath));
2411
+ }
2412
+ async function readPersistentProfileMetadata(userDataDir) {
2413
+ try {
2414
+ const raw = await (0, import_promises4.readFile)((0, import_node_path4.join)(userDataDir, OPENSTEER_META_FILE), "utf8");
2415
+ const parsed = JSON.parse(raw);
2416
+ if (typeof parsed.createdAt !== "string" || typeof parsed.profileDirectory !== "string" || typeof parsed.source !== "string") {
2417
+ return null;
2418
+ }
2419
+ return {
2420
+ createdAt: parsed.createdAt,
2421
+ profileDirectory: parsed.profileDirectory,
2422
+ source: parsed.source
2423
+ };
2424
+ } catch {
2425
+ return null;
2426
+ }
2427
+ }
2428
+ async function requirePersistentProfileMetadata(userDataDir) {
2429
+ const metadata = await readPersistentProfileMetadata(userDataDir);
2430
+ if (!metadata) {
2431
+ throw new Error(
2432
+ `Persistent profile metadata was not found for "${userDataDir}".`
2433
+ );
2434
+ }
2435
+ return metadata;
2436
+ }
2437
+ async function isHealthyPersistentProfile(userDataDir, expectedSourceUserDataDir, expectedProfileDirectory) {
2438
+ if (!(0, import_node_fs3.existsSync)(userDataDir) || !(0, import_node_fs3.existsSync)((0, import_node_path4.join)(userDataDir, expectedProfileDirectory))) {
2439
+ return false;
2440
+ }
2441
+ const metadata = await readPersistentProfileMetadata(userDataDir);
2442
+ return metadata?.source === expectedSourceUserDataDir && metadata.profileDirectory === expectedProfileDirectory;
2443
+ }
2444
+ function wasDirPublishedByAnotherProcess2(error, targetDirPath) {
2445
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
2446
+ return (0, import_node_fs3.existsSync)(targetDirPath) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
2447
+ }
2448
+ async function replaceProfileDirectory(targetUserDataDir, replacementUserDataDir) {
2449
+ if (!(0, import_node_fs3.existsSync)(targetUserDataDir)) {
2450
+ await (0, import_promises4.rename)(replacementUserDataDir, targetUserDataDir);
2451
+ return;
2452
+ }
2453
+ const backupUserDataDir = buildPersistentProfileBackupDirPath(targetUserDataDir);
2454
+ let targetMovedToBackup = false;
2455
+ let replacementPublished = false;
2456
+ try {
2457
+ await (0, import_promises4.rename)(targetUserDataDir, backupUserDataDir);
2458
+ targetMovedToBackup = true;
2459
+ await (0, import_promises4.rename)(replacementUserDataDir, targetUserDataDir);
2460
+ replacementPublished = true;
2461
+ } catch (error) {
2462
+ if (targetMovedToBackup && !(0, import_node_fs3.existsSync)(targetUserDataDir)) {
2463
+ await (0, import_promises4.rename)(backupUserDataDir, targetUserDataDir).catch(() => void 0);
2464
+ }
2465
+ throw error;
2466
+ } finally {
2467
+ if (replacementPublished && targetMovedToBackup && (0, import_node_fs3.existsSync)(backupUserDataDir)) {
2468
+ await (0, import_promises4.rm)(backupUserDataDir, {
2469
+ recursive: true,
2470
+ force: true
2471
+ }).catch(() => void 0);
2472
+ }
2473
+ }
2474
+ }
2475
+ async function withPersistentProfileWriteAccess(targetUserDataDir, action) {
2476
+ const releaseWriteLock = await acquirePersistentProfileWriteLock(
2477
+ targetUserDataDir
2478
+ );
2479
+ try {
2480
+ await waitForRuntimeProfileCreationsToDrain(targetUserDataDir);
2481
+ await waitForSharedRealBrowserSessionToDrain(targetUserDataDir);
2482
+ return await action();
2483
+ } finally {
2484
+ await releaseWriteLock();
2485
+ }
2486
+ }
2487
+ function buildPersistentProfileTempDirPrefix(targetUserDataDir) {
2488
+ return (0, import_node_path4.join)(
2489
+ (0, import_node_path4.dirname)(targetUserDataDir),
2490
+ `${buildPersistentProfileTempDirNamePrefix(targetUserDataDir)}${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-`
2491
+ );
2492
+ }
2493
+ function buildPersistentProfileTempDirNamePrefix(targetUserDataDir) {
2494
+ return `${(0, import_node_path4.basename)(targetUserDataDir)}-tmp-`;
2495
+ }
2496
+ function buildPersistentProfileBackupDirPath(targetUserDataDir) {
2497
+ return (0, import_node_path4.join)(
2498
+ (0, import_node_path4.dirname)(targetUserDataDir),
2499
+ `${buildPersistentProfileBackupDirNamePrefix(targetUserDataDir)}${Date.now()}-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto3.randomUUID)()}`
2500
+ );
2501
+ }
2502
+ function buildPersistentProfileBackupDirNamePrefix(targetUserDataDir) {
2503
+ return `${(0, import_node_path4.basename)(targetUserDataDir)}-backup-`;
2504
+ }
2505
+ function buildRuntimeProfileCreationRegistryDirPath(persistentUserDataDir) {
2506
+ return (0, import_node_path4.join)(
2507
+ (0, import_node_path4.dirname)(persistentUserDataDir),
2508
+ `${(0, import_node_path4.basename)(persistentUserDataDir)}.creating`
2509
+ );
2510
+ }
2511
+ function buildRuntimeProfileCreationRegistrationPath(persistentUserDataDir, runtimeUserDataDir) {
2512
+ const key = (0, import_node_crypto3.createHash)("sha256").update(runtimeUserDataDir).digest("hex").slice(0, 16);
2513
+ return (0, import_node_path4.join)(
2514
+ buildRuntimeProfileCreationRegistryDirPath(persistentUserDataDir),
2515
+ `${key}.json`
2516
+ );
2517
+ }
2518
+ function buildRuntimeProfileKey(sourceUserDataDir) {
2519
+ const hash = (0, import_node_crypto3.createHash)("sha256").update(sourceUserDataDir).digest("hex").slice(0, 16);
2520
+ return `${sanitizePathSegment((0, import_node_path4.basename)(sourceUserDataDir) || "profile")}-${hash}`;
2521
+ }
2522
+ function buildRuntimeProfileDirNamePrefix(sourceUserDataDir) {
2523
+ return `${buildRuntimeProfileKey(sourceUserDataDir)}-runtime-`;
2524
+ }
2525
+ function buildRuntimeProfileDirPrefix(runtimesRootDir, sourceUserDataDir) {
2526
+ return (0, import_node_path4.join)(
2527
+ runtimesRootDir,
2528
+ buildRuntimeProfileDirNamePrefix(sourceUserDataDir)
2529
+ );
2530
+ }
2531
+ async function reserveRuntimeProfileCreation(persistentUserDataDir, runtimeRootDir, profileDirectory) {
2532
+ while (true) {
2533
+ let runtimeUserDataDir = null;
2534
+ await withPersistentProfileControlLock(
2535
+ persistentUserDataDir,
2536
+ async () => {
2537
+ if (await isPersistentProfileWriteLocked(persistentUserDataDir)) {
2538
+ return;
2539
+ }
2540
+ if (await hasLiveSharedRealBrowserSession(persistentUserDataDir)) {
2541
+ return;
2542
+ }
2543
+ const createdRuntimeUserDataDir = await (0, import_promises4.mkdtemp)(
2544
+ buildRuntimeProfileDirPrefix(
2545
+ runtimeRootDir,
2546
+ persistentUserDataDir
2547
+ )
2548
+ );
2549
+ runtimeUserDataDir = createdRuntimeUserDataDir;
2550
+ const marker = {
2551
+ creator: CURRENT_PROCESS_OWNER,
2552
+ persistentUserDataDir,
2553
+ profileDirectory,
2554
+ runtimeUserDataDir: createdRuntimeUserDataDir
2555
+ };
2556
+ await writeRuntimeProfileCreationMarker(
2557
+ createdRuntimeUserDataDir,
2558
+ marker
2559
+ );
2560
+ await writeRuntimeProfileCreationRegistration(marker);
2561
+ }
2562
+ );
2563
+ if (runtimeUserDataDir) {
2564
+ return {
2565
+ persistentUserDataDir,
2566
+ userDataDir: runtimeUserDataDir
2567
+ };
2568
+ }
2569
+ await sleep4(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
2570
+ }
2571
+ }
2572
+ async function clearRuntimeProfileCreationState(runtimeUserDataDir, persistentUserDataDir) {
2573
+ await Promise.all([
2574
+ (0, import_promises4.rm)((0, import_node_path4.join)(runtimeUserDataDir, OPENSTEER_RUNTIME_CREATING_FILE), {
2575
+ force: true
2576
+ }).catch(() => void 0),
2577
+ (0, import_promises4.rm)(
2578
+ buildRuntimeProfileCreationRegistrationPath(
2579
+ persistentUserDataDir,
2580
+ runtimeUserDataDir
2581
+ ),
2582
+ {
2583
+ force: true
2584
+ }
2585
+ ).catch(() => void 0)
2586
+ ]);
2587
+ }
2588
+ async function writeRuntimeProfileCreationRegistration(marker) {
2589
+ const registryDirPath = buildRuntimeProfileCreationRegistryDirPath(
2590
+ marker.persistentUserDataDir
2591
+ );
2592
+ await (0, import_promises4.mkdir)(registryDirPath, { recursive: true });
2593
+ await (0, import_promises4.writeFile)(
2594
+ buildRuntimeProfileCreationRegistrationPath(
2595
+ marker.persistentUserDataDir,
2596
+ marker.runtimeUserDataDir
2597
+ ),
2598
+ JSON.stringify(marker, null, 2)
2599
+ );
2600
+ }
2601
+ async function listRuntimeProfileCreationRegistrations(persistentUserDataDir) {
2602
+ const registryDirPath = buildRuntimeProfileCreationRegistryDirPath(
2603
+ persistentUserDataDir
2604
+ );
2605
+ let entries;
2606
+ try {
2607
+ entries = await (0, import_promises4.readdir)(registryDirPath, {
2608
+ encoding: "utf8",
2609
+ withFileTypes: true
2610
+ });
2611
+ } catch {
2612
+ return [];
2613
+ }
2614
+ return await Promise.all(
2615
+ entries.filter((entry) => entry.isFile()).map(async (entry) => {
2616
+ const filePath = (0, import_node_path4.join)(registryDirPath, entry.name);
2617
+ return {
2618
+ filePath,
2619
+ marker: await readRuntimeProfileCreationRegistration(filePath)
2620
+ };
2621
+ })
2622
+ );
2623
+ }
2624
+ async function readRuntimeProfileCreationRegistration(filePath) {
2625
+ try {
2626
+ const raw = await (0, import_promises4.readFile)(filePath, "utf8");
2627
+ return parseRuntimeProfileCreationMarker(JSON.parse(raw));
2628
+ } catch {
2629
+ return null;
2630
+ }
2631
+ }
2632
+ async function cleanOrphanedRuntimeProfileDirs(rootDir, runtimeDirNamePrefix) {
2633
+ let entries;
2634
+ try {
2635
+ entries = await (0, import_promises4.readdir)(rootDir, {
2636
+ encoding: "utf8",
2637
+ withFileTypes: true
2638
+ });
2639
+ } catch {
2640
+ return;
2641
+ }
2642
+ const liveProcessCommandLines = await listProcessCommandLines();
2643
+ await Promise.all(
2644
+ entries.map(async (entry) => {
2645
+ if (!entry.isDirectory() || !entry.name.startsWith(runtimeDirNamePrefix)) {
2646
+ return;
2647
+ }
2648
+ const runtimeDirPath = (0, import_node_path4.join)(rootDir, entry.name);
2649
+ const creationMarker = await readRuntimeProfileCreationMarker(
2650
+ runtimeDirPath
2651
+ );
2652
+ if (await isRuntimeProfileDirInUse(
2653
+ runtimeDirPath,
2654
+ liveProcessCommandLines
2655
+ )) {
2656
+ return;
2657
+ }
2658
+ await (0, import_promises4.rm)(runtimeDirPath, {
2659
+ recursive: true,
2660
+ force: true
2661
+ }).catch(() => void 0);
2662
+ if (creationMarker) {
2663
+ await clearRuntimeProfileCreationState(
2664
+ runtimeDirPath,
2665
+ creationMarker.persistentUserDataDir
2666
+ );
2667
+ }
2668
+ })
2669
+ );
2670
+ }
2671
+ async function cleanOrphanedOwnedDirs(rootDir, ownedDirNamePrefix) {
2672
+ let entries;
2673
+ try {
2674
+ entries = await (0, import_promises4.readdir)(rootDir, {
2675
+ encoding: "utf8",
2676
+ withFileTypes: true
2677
+ });
2678
+ } catch {
2679
+ return;
2680
+ }
2681
+ await Promise.all(
2682
+ entries.map(async (entry) => {
2683
+ if (!entry.isDirectory() || !entry.name.startsWith(ownedDirNamePrefix)) {
2684
+ return;
2685
+ }
2686
+ if (await isOwnedDirByLiveProcess(entry.name, ownedDirNamePrefix)) {
2687
+ return;
2688
+ }
2689
+ await (0, import_promises4.rm)((0, import_node_path4.join)(rootDir, entry.name), {
2690
+ recursive: true,
2691
+ force: true
2692
+ }).catch(() => void 0);
2693
+ })
2694
+ );
2695
+ }
2696
+ async function isOwnedDirByLiveProcess(ownedDirName, ownedDirPrefix) {
2697
+ const owner = parseOwnedDirOwner(ownedDirName, ownedDirPrefix);
2698
+ return owner ? await getProcessLiveness(owner) !== "dead" : false;
2699
+ }
2700
+ async function isRuntimeProfileDirInUse(runtimeDirPath, liveProcessCommandLines) {
2701
+ const creationMarker = await readRuntimeProfileCreationMarker(runtimeDirPath);
2702
+ if (creationMarker && await getProcessLiveness(creationMarker.creator) !== "dead") {
2703
+ return true;
2704
+ }
2705
+ const metadata = await readRuntimeProfileMetadata(runtimeDirPath);
2706
+ if (metadata && await getProcessLiveness(metadata.creator) !== "dead") {
2707
+ return true;
2708
+ }
2709
+ return liveProcessCommandLines.some(
2710
+ (commandLine) => commandLineIncludesUserDataDir(commandLine, runtimeDirPath)
2711
+ );
2712
+ }
2713
+ async function claimRuntimeProfileForPersist(runtimeUserDataDir) {
2714
+ while (true) {
2715
+ await waitForRuntimeProfileProcessesToDrain(runtimeUserDataDir);
2716
+ const claimedRuntimeUserDataDir = buildClaimedRuntimeProfileDirPath(runtimeUserDataDir);
2717
+ try {
2718
+ await (0, import_promises4.rename)(runtimeUserDataDir, claimedRuntimeUserDataDir);
2719
+ } catch (error) {
2720
+ const code = getErrorCode3(error);
2721
+ if (code === "ENOENT") {
2722
+ throw new Error(
2723
+ `Runtime profile "${runtimeUserDataDir}" was not found.`
2724
+ );
2725
+ }
2726
+ if (code === "EACCES" || code === "EBUSY" || code === "EPERM") {
2727
+ await sleep4(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
2728
+ continue;
2729
+ }
2730
+ throw error;
2731
+ }
2732
+ if (!await hasLiveProcessUsingUserDataDir(runtimeUserDataDir)) {
2733
+ return claimedRuntimeUserDataDir;
2734
+ }
2735
+ await (0, import_promises4.rename)(
2736
+ claimedRuntimeUserDataDir,
2737
+ runtimeUserDataDir
2738
+ ).catch(() => void 0);
2739
+ await sleep4(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
2740
+ }
2741
+ }
2742
+ async function restoreClaimedRuntimeProfile(claimedRuntimeUserDataDir, runtimeUserDataDir) {
2743
+ if (!(0, import_node_fs3.existsSync)(claimedRuntimeUserDataDir)) {
2744
+ return;
2745
+ }
2746
+ if ((0, import_node_fs3.existsSync)(runtimeUserDataDir)) {
2747
+ throw new Error(
2748
+ `Runtime profile "${runtimeUserDataDir}" was recreated before the failed persist could restore it from "${claimedRuntimeUserDataDir}".`
2749
+ );
2750
+ }
2751
+ await (0, import_promises4.rename)(claimedRuntimeUserDataDir, runtimeUserDataDir);
2752
+ }
2753
+ function buildClaimedRuntimeProfileDirPath(runtimeUserDataDir) {
2754
+ return (0, import_node_path4.join)(
2755
+ (0, import_node_path4.dirname)(runtimeUserDataDir),
2756
+ `${(0, import_node_path4.basename)(runtimeUserDataDir)}-persisting-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto3.randomUUID)()}`
2757
+ );
2758
+ }
2759
+ async function waitForRuntimeProfileProcessesToDrain(runtimeUserDataDir) {
2760
+ while (await hasLiveProcessUsingUserDataDir(runtimeUserDataDir)) {
2761
+ await sleep4(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
2762
+ }
2763
+ }
2764
+ async function hasLiveProcessUsingUserDataDir(userDataDir) {
2765
+ const liveProcessCommandLines = await listProcessCommandLines();
2766
+ return liveProcessCommandLines.some(
2767
+ (commandLine) => commandLineIncludesUserDataDir(commandLine, userDataDir)
2768
+ );
2769
+ }
2770
+ async function hasActiveRuntimeProfileCreations(persistentUserDataDir) {
2771
+ const registrations = await listRuntimeProfileCreationRegistrations(
2772
+ persistentUserDataDir
2773
+ );
2774
+ let hasLiveCreation = false;
2775
+ for (const registration of registrations) {
2776
+ const marker = registration.marker;
2777
+ if (!marker || marker.persistentUserDataDir !== persistentUserDataDir) {
2778
+ await (0, import_promises4.rm)(registration.filePath, {
2779
+ force: true
2780
+ }).catch(() => void 0);
2781
+ continue;
2782
+ }
2783
+ const runtimeMarker = await readRuntimeProfileCreationMarker(
2784
+ marker.runtimeUserDataDir
2785
+ );
2786
+ if (!runtimeMarker || runtimeMarker.persistentUserDataDir !== persistentUserDataDir || runtimeMarker.runtimeUserDataDir !== marker.runtimeUserDataDir) {
2787
+ await clearRuntimeProfileCreationState(
2788
+ marker.runtimeUserDataDir,
2789
+ persistentUserDataDir
2790
+ );
2791
+ continue;
2792
+ }
2793
+ if (await getProcessLiveness(runtimeMarker.creator) === "dead") {
2794
+ await clearRuntimeProfileCreationState(
2795
+ marker.runtimeUserDataDir,
2796
+ persistentUserDataDir
2797
+ );
2798
+ await (0, import_promises4.rm)(marker.runtimeUserDataDir, {
2799
+ recursive: true,
2800
+ force: true
2801
+ }).catch(() => void 0);
2802
+ continue;
2803
+ }
2804
+ hasLiveCreation = true;
2805
+ }
2806
+ return hasLiveCreation;
2807
+ }
2808
+ async function waitForRuntimeProfileCreationsToDrain(persistentUserDataDir) {
2809
+ while (true) {
2810
+ if (!await hasActiveRuntimeProfileCreations(persistentUserDataDir)) {
2811
+ return;
2812
+ }
2813
+ await sleep4(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
2814
+ }
2815
+ }
2816
+ function parseRuntimeProfileCreationMarker(value) {
2817
+ if (!value || typeof value !== "object") {
2818
+ return null;
2819
+ }
2820
+ const parsed = value;
2821
+ const creator = parseProcessOwner(parsed.creator);
2822
+ const persistentUserDataDir = typeof parsed.persistentUserDataDir === "string" ? parsed.persistentUserDataDir : void 0;
2823
+ const profileDirectory = parsed.profileDirectory === null ? null : typeof parsed.profileDirectory === "string" ? parsed.profileDirectory : void 0;
2824
+ const runtimeUserDataDir = typeof parsed.runtimeUserDataDir === "string" ? parsed.runtimeUserDataDir : void 0;
2825
+ if (!creator || persistentUserDataDir === void 0 || profileDirectory === void 0 || runtimeUserDataDir === void 0) {
2826
+ return null;
2827
+ }
2828
+ return {
2829
+ creator,
2830
+ persistentUserDataDir,
2831
+ profileDirectory,
2832
+ runtimeUserDataDir
2833
+ };
2834
+ }
2835
+ function parseOwnedDirOwner(ownedDirName, ownedDirPrefix) {
2836
+ const remainder = ownedDirName.slice(ownedDirPrefix.length);
2837
+ const firstDashIndex = remainder.indexOf("-");
2838
+ const secondDashIndex = firstDashIndex === -1 ? -1 : remainder.indexOf("-", firstDashIndex + 1);
2839
+ if (firstDashIndex === -1 || secondDashIndex === -1) {
2840
+ return null;
2841
+ }
2842
+ const pid = Number.parseInt(remainder.slice(0, firstDashIndex), 10);
2843
+ const processStartedAtMs = Number.parseInt(
2844
+ remainder.slice(firstDashIndex + 1, secondDashIndex),
2845
+ 10
2846
+ );
2847
+ if (!Number.isInteger(pid) || pid <= 0) {
2848
+ return null;
2849
+ }
2850
+ if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
2851
+ return null;
2852
+ }
2853
+ return { pid, processStartedAtMs };
2854
+ }
2855
+ async function listProcessCommandLines() {
2856
+ if (process.platform === "win32") {
2857
+ return await listWindowsProcessCommandLines();
2858
+ }
2859
+ return await listPsProcessCommandLines();
2860
+ }
2861
+ async function listPsProcessCommandLines() {
2862
+ try {
2863
+ const { stdout } = await execFileAsync2(
2864
+ "ps",
2865
+ ["-axww", "-o", "command="],
2866
+ {
2867
+ env: PS_COMMAND_ENV2,
2868
+ maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES2
2869
+ }
2870
+ );
2871
+ return stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
2872
+ } catch {
2873
+ return [];
2874
+ }
2875
+ }
2876
+ async function listWindowsProcessCommandLines() {
2877
+ const script = [
2878
+ "$processes = Get-CimInstance Win32_Process | Select-Object CommandLine",
2879
+ "$processes | ConvertTo-Json -Compress"
2880
+ ].join("; ");
2881
+ try {
2882
+ const { stdout } = await execFileAsync2(
2883
+ "powershell.exe",
2884
+ ["-NoLogo", "-NoProfile", "-Command", script],
2885
+ {
2886
+ maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES2
2887
+ }
2888
+ );
2889
+ const parsed = JSON.parse(stdout);
2890
+ const records = Array.isArray(parsed) ? parsed : [parsed];
2891
+ return records.map((record) => record?.CommandLine?.trim() ?? "").filter((commandLine) => commandLine.length > 0);
2892
+ } catch {
2893
+ return [];
2894
+ }
2895
+ }
2896
+ function commandLineIncludesUserDataDir(commandLine, userDataDir) {
2897
+ const unquoted = `--user-data-dir=${userDataDir}`;
2898
+ const unquotedIndex = commandLine.indexOf(unquoted);
2899
+ if (unquotedIndex !== -1) {
2900
+ const after = commandLine[unquotedIndex + unquoted.length];
2901
+ if (after === void 0 || after === " " || after === " ") {
2902
+ return true;
2903
+ }
2904
+ }
2905
+ return [
2906
+ `--user-data-dir="${userDataDir}"`,
2907
+ `--user-data-dir='${userDataDir}'`
2908
+ ].some((candidate) => commandLine.includes(candidate));
2909
+ }
2910
+ function getErrorCode3(error) {
2911
+ return typeof error === "object" && error !== null && "code" in error && (typeof error.code === "string" || typeof error.code === "number") ? error.code : void 0;
2912
+ }
2913
+ async function sleep4(ms) {
2914
+ await new Promise((resolve) => setTimeout(resolve, ms));
2915
+ }
2916
+
2917
+ // src/browser/shared-real-browser-session.ts
2918
+ var import_node_crypto4 = require("crypto");
2919
+ var import_node_child_process3 = require("child_process");
2920
+ var import_promises5 = require("fs/promises");
2921
+ var import_node_net = require("net");
2922
+ var import_node_path5 = require("path");
2923
+ var import_playwright = require("playwright");
2924
+ var SHARED_SESSION_RETRY_DELAY_MS2 = 50;
2925
+ async function acquireSharedRealBrowserSession(options) {
2926
+ const reservation = await reserveSharedSessionClient(options);
2927
+ const sessionContext = await attachToSharedSession(reservation, options);
2928
+ let closed = false;
2929
+ return {
2930
+ browser: sessionContext.browser,
2931
+ context: sessionContext.context,
2932
+ page: sessionContext.page,
2933
+ close: async () => {
2934
+ if (closed) {
2935
+ return;
2936
+ }
2937
+ closed = true;
2938
+ await releaseSharedSessionClient(sessionContext);
2939
+ }
2940
+ };
2941
+ }
2942
+ function getOwnedRealBrowserProcessPolicy(platformName = process.platform) {
2943
+ if (platformName === "win32") {
2944
+ return {
2945
+ detached: false,
2946
+ killStrategy: "taskkill",
2947
+ shouldUnref: true
2948
+ };
2949
+ }
2950
+ if (platformName === "darwin") {
2951
+ return {
2952
+ detached: false,
2953
+ killStrategy: "process",
2954
+ shouldUnref: true
2955
+ };
2956
+ }
2957
+ return {
2958
+ detached: true,
2959
+ killStrategy: "process-group",
2960
+ shouldUnref: true
2961
+ };
2962
+ }
2963
+ async function reserveSharedSessionClient(options) {
2964
+ while (true) {
2965
+ const outcome = await withPersistentProfileControlLock(
2966
+ options.persistentProfile.userDataDir,
2967
+ async () => {
2968
+ if (await isPersistentProfileWriteLocked(
2969
+ options.persistentProfile.userDataDir
2970
+ )) {
2971
+ return { kind: "wait" };
2972
+ }
2973
+ if (await hasActiveRuntimeProfileCreations(
2974
+ options.persistentProfile.userDataDir
2975
+ )) {
2976
+ return { kind: "wait" };
2977
+ }
2978
+ return await withSharedSessionLock(
2979
+ options.persistentProfile.userDataDir,
2980
+ async () => {
2981
+ const state = await inspectSharedSessionState(options);
2982
+ if (state.kind === "wait") {
2983
+ return { kind: "wait" };
2984
+ }
2985
+ if (state.kind === "ready") {
2986
+ return {
2987
+ kind: "ready",
2988
+ reservation: await registerSharedSessionClient(
2989
+ options.persistentProfile.userDataDir,
2990
+ state.metadata
2991
+ )
2992
+ };
2993
+ }
2994
+ return {
2995
+ kind: "launch",
2996
+ reservation: await launchSharedSession(options)
2997
+ };
2998
+ }
2999
+ );
3000
+ }
3001
+ );
3002
+ if (outcome.kind === "wait") {
3003
+ await sleep5(SHARED_SESSION_RETRY_DELAY_MS2);
3004
+ continue;
3005
+ }
3006
+ if (outcome.kind === "ready") {
3007
+ return outcome.reservation;
3008
+ }
3009
+ try {
3010
+ await waitForSharedSessionReady(
3011
+ outcome.reservation.metadata,
3012
+ options.timeoutMs
3013
+ );
3014
+ } catch (error) {
3015
+ await cleanupFailedSharedSessionLaunch(outcome.reservation);
3016
+ throw error;
3017
+ }
3018
+ try {
3019
+ return await withSharedSessionLock(
3020
+ options.persistentProfile.userDataDir,
3021
+ async () => {
3022
+ const metadata = await readSharedSessionMetadata(
3023
+ options.persistentProfile.userDataDir
3024
+ );
3025
+ if (!metadata || metadata.sessionId !== outcome.reservation.metadata.sessionId || !processOwnersEqual(
3026
+ metadata.browserOwner,
3027
+ outcome.reservation.launchedBrowserOwner
3028
+ )) {
3029
+ throw new Error(
3030
+ "The shared real-browser session changed before launch finalized."
3031
+ );
3032
+ }
3033
+ const readyMetadata = {
3034
+ ...metadata,
3035
+ state: "ready"
3036
+ };
3037
+ await writeSharedSessionMetadata(
3038
+ options.persistentProfile.userDataDir,
3039
+ readyMetadata
3040
+ );
3041
+ return await registerSharedSessionClient(
3042
+ options.persistentProfile.userDataDir,
3043
+ readyMetadata
3044
+ );
3045
+ }
3046
+ );
3047
+ } catch (error) {
3048
+ await cleanupFailedSharedSessionLaunch(outcome.reservation);
3049
+ throw error;
3050
+ }
3051
+ }
3052
+ }
3053
+ async function attachToSharedSession(reservation, options) {
3054
+ let browser = null;
3055
+ let page = null;
3056
+ try {
3057
+ const browserWsUrl = await resolveCdpWebSocketUrl(
3058
+ buildSharedSessionDiscoveryUrl(reservation.metadata.debugPort),
3059
+ options.timeoutMs
3060
+ );
3061
+ browser = await import_playwright.chromium.connectOverCDP(browserWsUrl, {
3062
+ timeout: options.timeoutMs
3063
+ });
3064
+ const context = getPrimaryBrowserContext(browser);
3065
+ page = await getSharedSessionPage(context, reservation.reuseExistingPage);
3066
+ if (options.initialUrl) {
3067
+ await page.goto(options.initialUrl, {
3068
+ timeout: options.timeoutMs,
3069
+ waitUntil: "domcontentloaded"
3070
+ });
3071
+ }
3072
+ return {
3073
+ browser,
3074
+ clientId: reservation.client.clientId,
3075
+ context,
3076
+ page,
3077
+ persistentUserDataDir: reservation.metadata.persistentUserDataDir,
3078
+ sessionId: reservation.metadata.sessionId
3079
+ };
3080
+ } catch (error) {
3081
+ if (page) {
3082
+ await page.close().catch(() => void 0);
3083
+ }
3084
+ if (browser) {
3085
+ await browser.close().catch(() => void 0);
3086
+ }
3087
+ await cleanupFailedSharedSessionAttach({
3088
+ clientId: reservation.client.clientId,
3089
+ persistentUserDataDir: reservation.metadata.persistentUserDataDir,
3090
+ sessionId: reservation.metadata.sessionId
3091
+ });
3092
+ throw error;
3093
+ }
3094
+ }
3095
+ async function releaseSharedSessionClient(context) {
3096
+ const releasePlan = await prepareSharedSessionCloseIfIdle(
3097
+ context.persistentUserDataDir,
3098
+ context.clientId,
3099
+ context.sessionId
3100
+ );
3101
+ if (releasePlan.closeBrowser) {
3102
+ await closeSharedSessionBrowser(
3103
+ context.persistentUserDataDir,
3104
+ releasePlan,
3105
+ context.browser
3106
+ );
3107
+ return;
3108
+ }
3109
+ await context.page.close().catch(() => void 0);
3110
+ await context.browser.close().catch(() => void 0);
3111
+ }
3112
+ async function inspectSharedSessionState(options) {
3113
+ const persistentUserDataDir = options.persistentProfile.userDataDir;
3114
+ const liveClients = await listLiveSharedSessionClients(persistentUserDataDir);
3115
+ const metadata = await readSharedSessionMetadata(persistentUserDataDir);
3116
+ if (!metadata) {
3117
+ if (liveClients.length > 0) {
3118
+ throw new Error(
3119
+ `Shared real-browser session metadata for "${persistentUserDataDir}" is missing while clients are still attached.`
3120
+ );
3121
+ }
3122
+ await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
3123
+ force: true,
3124
+ recursive: true
3125
+ }).catch(() => void 0);
3126
+ return { kind: "missing" };
3127
+ }
3128
+ assertSharedSessionCompatibility(metadata, options);
3129
+ const browserState = await getProcessLiveness(metadata.browserOwner);
3130
+ if (browserState === "dead") {
3131
+ await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
3132
+ force: true,
3133
+ recursive: true
3134
+ }).catch(() => void 0);
3135
+ return { kind: "missing" };
3136
+ }
3137
+ if (metadata.state === "ready") {
3138
+ return {
3139
+ kind: "ready",
3140
+ metadata
3141
+ };
3142
+ }
3143
+ const stateOwnerState = await getProcessLiveness(metadata.stateOwner);
3144
+ if (stateOwnerState === "dead") {
3145
+ const recoveredMetadata = {
3146
+ ...metadata,
3147
+ state: "ready"
3148
+ };
3149
+ await writeSharedSessionMetadata(persistentUserDataDir, recoveredMetadata);
3150
+ return {
3151
+ kind: "ready",
3152
+ metadata: recoveredMetadata
3153
+ };
3154
+ }
3155
+ return { kind: "wait" };
3156
+ }
3157
+ async function launchSharedSession(options) {
3158
+ const persistentUserDataDir = options.persistentProfile.userDataDir;
3159
+ await clearPersistentProfileSingletons(persistentUserDataDir);
3160
+ const debugPort = await reserveDebugPort();
3161
+ const launchArgs = buildRealBrowserLaunchArgs({
3162
+ debugPort,
3163
+ headless: options.headless,
3164
+ profileDirectory: options.profileDirectory,
3165
+ userDataDir: persistentUserDataDir
3166
+ });
3167
+ const processPolicy = getOwnedRealBrowserProcessPolicy();
3168
+ const processHandle = (0, import_node_child_process3.spawn)(options.executablePath, launchArgs, {
3169
+ detached: processPolicy.detached,
3170
+ stdio: "ignore"
3171
+ });
3172
+ if (processPolicy.shouldUnref) {
3173
+ processHandle.unref();
3174
+ }
3175
+ try {
3176
+ const browserOwner = await waitForSpawnedProcessOwner(
3177
+ processHandle.pid,
3178
+ options.timeoutMs
3179
+ );
3180
+ const metadata = {
3181
+ browserOwner,
3182
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3183
+ debugPort,
3184
+ executablePath: options.executablePath,
3185
+ headless: options.headless,
3186
+ persistentUserDataDir,
3187
+ profileDirectory: options.profileDirectory,
3188
+ sessionId: (0, import_node_crypto4.randomUUID)(),
3189
+ state: "launching",
3190
+ stateOwner: CURRENT_PROCESS_OWNER
3191
+ };
3192
+ await writeSharedSessionMetadata(persistentUserDataDir, metadata);
3193
+ return {
3194
+ launchedBrowserOwner: browserOwner,
3195
+ metadata
3196
+ };
3197
+ } catch (error) {
3198
+ await killSpawnedBrowserProcess(processHandle);
3199
+ await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
3200
+ force: true,
3201
+ recursive: true
3202
+ }).catch(() => void 0);
3203
+ throw error;
3204
+ }
3205
+ }
3206
+ async function cleanupFailedSharedSessionLaunch(reservation) {
3207
+ const shouldPreserveLiveBrowser = await withSharedSessionLock(
3208
+ reservation.metadata.persistentUserDataDir,
3209
+ async () => {
3210
+ const metadata = await readSharedSessionMetadata(
3211
+ reservation.metadata.persistentUserDataDir
3212
+ );
3213
+ if (metadata && metadata.sessionId === reservation.metadata.sessionId && processOwnersEqual(
3214
+ metadata.browserOwner,
3215
+ reservation.launchedBrowserOwner
3216
+ )) {
3217
+ if (await getProcessLiveness(metadata.browserOwner) !== "dead") {
3218
+ const readyMetadata = {
3219
+ ...metadata,
3220
+ state: "ready"
3221
+ };
3222
+ await writeSharedSessionMetadata(
3223
+ reservation.metadata.persistentUserDataDir,
3224
+ readyMetadata
3225
+ );
3226
+ return true;
3227
+ }
3228
+ await (0, import_promises5.rm)(
3229
+ buildSharedSessionDirPath(
3230
+ reservation.metadata.persistentUserDataDir
3231
+ ),
3232
+ {
3233
+ force: true,
3234
+ recursive: true
3235
+ }
3236
+ ).catch(() => void 0);
3237
+ }
3238
+ return false;
3239
+ }
3240
+ );
3241
+ if (shouldPreserveLiveBrowser) {
3242
+ return;
3243
+ }
3244
+ await killOwnedBrowserProcess(reservation.launchedBrowserOwner);
3245
+ await waitForProcessToExit(reservation.launchedBrowserOwner, 2e3);
3246
+ }
3247
+ async function cleanupFailedSharedSessionAttach(options) {
3248
+ const closePlan = await prepareSharedSessionCloseIfIdle(
3249
+ options.persistentUserDataDir,
3250
+ options.clientId,
3251
+ options.sessionId
3252
+ );
3253
+ if (!closePlan.closeBrowser) {
3254
+ return;
3255
+ }
3256
+ await closeSharedSessionBrowser(options.persistentUserDataDir, closePlan);
3257
+ }
3258
+ async function waitForSharedSessionReady(metadata, timeoutMs) {
3259
+ await resolveCdpWebSocketUrl(
3260
+ buildSharedSessionDiscoveryUrl(metadata.debugPort),
3261
+ timeoutMs
3262
+ );
3263
+ }
3264
+ function buildRealBrowserLaunchArgs(options) {
3265
+ const args = [
3266
+ `--user-data-dir=${options.userDataDir}`,
3267
+ `--profile-directory=${options.profileDirectory}`,
3268
+ `--remote-debugging-port=${options.debugPort}`,
3269
+ "--disable-blink-features=AutomationControlled"
3270
+ ];
3271
+ if (options.headless) {
3272
+ args.push("--headless=new");
3273
+ }
3274
+ return args;
3275
+ }
3276
+ async function requestBrowserShutdown(browser) {
3277
+ let session = null;
3278
+ try {
3279
+ session = await browser.newBrowserCDPSession();
3280
+ await session.send("Browser.close");
3281
+ } catch {
3282
+ } finally {
3283
+ await session?.detach().catch(() => void 0);
3284
+ }
3285
+ }
3286
+ async function killOwnedBrowserProcess(owner) {
3287
+ if (await getProcessLiveness(owner) === "dead") {
3288
+ return;
3289
+ }
3290
+ await killOwnedBrowserProcessByPid(owner.pid);
3291
+ }
3292
+ async function killSpawnedBrowserProcess(processHandle) {
3293
+ const pid = processHandle.pid;
3294
+ if (!pid || processHandle.exitCode !== null) {
3295
+ return;
3296
+ }
3297
+ await killOwnedBrowserProcessByPid(pid);
3298
+ await waitForPidToExit(pid, 2e3);
3299
+ }
3300
+ async function killOwnedBrowserProcessByPid(pid) {
3301
+ const processPolicy = getOwnedRealBrowserProcessPolicy();
3302
+ if (processPolicy.killStrategy === "taskkill") {
3303
+ await new Promise((resolve) => {
3304
+ const killer = (0, import_node_child_process3.spawn)(
3305
+ "taskkill",
3306
+ ["/pid", String(pid), "/t", "/f"],
3307
+ {
3308
+ stdio: "ignore"
3309
+ }
3310
+ );
3311
+ killer.on("error", () => resolve());
3312
+ killer.on("exit", () => resolve());
3313
+ });
3314
+ return;
3315
+ }
3316
+ if (processPolicy.killStrategy === "process-group") {
3317
+ try {
3318
+ process.kill(-pid, "SIGKILL");
3319
+ return;
3320
+ } catch {
3321
+ }
3322
+ }
3323
+ try {
3324
+ process.kill(pid, "SIGKILL");
3325
+ } catch {
3326
+ }
3327
+ }
3328
+ async function waitForProcessToExit(owner, timeoutMs) {
3329
+ const deadline = Date.now() + timeoutMs;
3330
+ while (Date.now() < deadline) {
3331
+ if (await getProcessLiveness(owner) === "dead") {
3332
+ return;
3333
+ }
3334
+ await sleep5(50);
3335
+ }
3336
+ }
3337
+ async function waitForPidToExit(pid, timeoutMs) {
3338
+ const deadline = Date.now() + timeoutMs;
3339
+ while (Date.now() < deadline) {
3340
+ if (!isProcessRunning(pid)) {
3341
+ return;
3342
+ }
3343
+ await sleep5(50);
3344
+ }
3345
+ }
3346
+ async function waitForSpawnedProcessOwner(pid, timeoutMs) {
3347
+ if (!pid || pid <= 0) {
3348
+ throw new Error("Chrome did not expose a child process id.");
3349
+ }
3350
+ const deadline = Date.now() + timeoutMs;
3351
+ while (Date.now() < deadline) {
3352
+ const owner = await readProcessOwner(pid);
3353
+ if (owner) {
3354
+ return owner;
3355
+ }
3356
+ await sleep5(50);
3357
+ }
3358
+ throw new Error(
3359
+ `Chrome process ${pid} did not report a stable process start time.`
3360
+ );
3361
+ }
3362
+ async function withSharedSessionLock(persistentUserDataDir, action) {
3363
+ return await withDirLock(
3364
+ buildSharedSessionLockPath(persistentUserDataDir),
3365
+ action
3366
+ );
3367
+ }
3368
+ async function registerSharedSessionClient(persistentUserDataDir, metadata) {
3369
+ const liveClients = await listLiveSharedSessionClients(persistentUserDataDir);
3370
+ const client = buildSharedSessionClientRegistration();
3371
+ await (0, import_promises5.mkdir)(buildSharedSessionClientsDirPath(persistentUserDataDir), {
3372
+ recursive: true
3373
+ });
3374
+ await (0, import_promises5.writeFile)(
3375
+ buildSharedSessionClientPath(persistentUserDataDir, client.clientId),
3376
+ JSON.stringify(client, null, 2),
3377
+ {
3378
+ flag: "wx"
3379
+ }
3380
+ );
3381
+ return {
3382
+ client,
3383
+ metadata,
3384
+ reuseExistingPage: liveClients.length === 0
3385
+ };
3386
+ }
3387
+ async function removeSharedSessionClientRegistration(persistentUserDataDir, clientId) {
3388
+ await (0, import_promises5.rm)(buildSharedSessionClientPath(persistentUserDataDir, clientId), {
3389
+ force: true
3390
+ }).catch(() => void 0);
3391
+ }
3392
+ async function listLiveSharedSessionClients(persistentUserDataDir) {
3393
+ const clientsDirPath = buildSharedSessionClientsDirPath(persistentUserDataDir);
3394
+ let entries;
3395
+ try {
3396
+ entries = await (0, import_promises5.readdir)(clientsDirPath, {
3397
+ encoding: "utf8",
3398
+ withFileTypes: true
3399
+ });
3400
+ } catch {
3401
+ return [];
3402
+ }
3403
+ const liveClients = [];
3404
+ for (const entry of entries) {
3405
+ if (!entry.isFile()) {
3406
+ continue;
3407
+ }
3408
+ const filePath = (0, import_node_path5.join)(clientsDirPath, entry.name);
3409
+ const registration = await readSharedSessionClientRegistration(filePath);
3410
+ if (!registration) {
3411
+ await (0, import_promises5.rm)(filePath, { force: true }).catch(() => void 0);
3412
+ continue;
3413
+ }
3414
+ if (await getProcessLiveness(registration.owner) === "dead") {
3415
+ await (0, import_promises5.rm)(filePath, { force: true }).catch(() => void 0);
3416
+ continue;
3417
+ }
3418
+ liveClients.push(registration);
3419
+ }
3420
+ return liveClients;
3421
+ }
3422
+ async function readSharedSessionClientRegistration(filePath) {
3423
+ try {
3424
+ const raw = await (0, import_promises5.readFile)(filePath, "utf8");
3425
+ return parseSharedSessionClientRegistration(JSON.parse(raw));
3426
+ } catch {
3427
+ return null;
3428
+ }
3429
+ }
3430
+ function buildSharedSessionClientRegistration() {
3431
+ return {
3432
+ clientId: (0, import_node_crypto4.randomUUID)(),
3433
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3434
+ owner: CURRENT_PROCESS_OWNER
3435
+ };
3436
+ }
3437
+ function parseSharedSessionClientRegistration(value) {
3438
+ if (!value || typeof value !== "object") {
3439
+ return null;
3440
+ }
3441
+ const parsed = value;
3442
+ const owner = parseProcessOwner(parsed.owner);
3443
+ if (!owner || typeof parsed.clientId !== "string" || typeof parsed.createdAt !== "string") {
3444
+ return null;
3445
+ }
3446
+ return {
3447
+ clientId: parsed.clientId,
3448
+ createdAt: parsed.createdAt,
3449
+ owner
3450
+ };
3451
+ }
3452
+ function assertSharedSessionCompatibility(metadata, options) {
3453
+ if (metadata.executablePath !== options.executablePath) {
3454
+ throw new Error(
3455
+ `Chrome profile "${options.profileDirectory}" is already running with executable "${metadata.executablePath}", not "${options.executablePath}".`
3456
+ );
3457
+ }
3458
+ if (metadata.headless !== options.headless) {
3459
+ throw new Error(
3460
+ `Chrome profile "${options.profileDirectory}" is already running with headless=${metadata.headless}, not ${options.headless}.`
3461
+ );
3462
+ }
3463
+ }
3464
+ async function prepareSharedSessionCloseIfIdle(persistentUserDataDir, clientId, sessionId) {
3465
+ return await withSharedSessionLock(persistentUserDataDir, async () => {
3466
+ const metadata = await readSharedSessionMetadata(persistentUserDataDir);
3467
+ await removeSharedSessionClientRegistration(
3468
+ persistentUserDataDir,
3469
+ clientId
3470
+ );
3471
+ if (!metadata || metadata.sessionId !== sessionId) {
3472
+ return {
3473
+ closeBrowser: false,
3474
+ sessionId
3475
+ };
3476
+ }
3477
+ const liveClients = await listLiveSharedSessionClients(
3478
+ persistentUserDataDir
3479
+ );
3480
+ if (liveClients.length > 0) {
3481
+ return {
3482
+ closeBrowser: false,
3483
+ sessionId: metadata.sessionId
3484
+ };
3485
+ }
3486
+ const closingMetadata = {
3487
+ ...metadata,
3488
+ state: "closing",
3489
+ stateOwner: CURRENT_PROCESS_OWNER
3490
+ };
3491
+ await writeSharedSessionMetadata(
3492
+ persistentUserDataDir,
3493
+ closingMetadata
3494
+ );
3495
+ return {
3496
+ browserOwner: closingMetadata.browserOwner,
3497
+ closeBrowser: true,
3498
+ sessionId: closingMetadata.sessionId
3499
+ };
3500
+ });
3501
+ }
3502
+ async function closeSharedSessionBrowser(persistentUserDataDir, closePlan, browser) {
3503
+ if (browser) {
3504
+ await requestBrowserShutdown(browser);
3505
+ await waitForProcessToExit(closePlan.browserOwner, 1e3);
3506
+ }
3507
+ if (await getProcessLiveness(closePlan.browserOwner) !== "dead") {
3508
+ await killOwnedBrowserProcess(closePlan.browserOwner);
3509
+ await waitForProcessToExit(closePlan.browserOwner, 2e3);
3510
+ }
3511
+ await finalizeSharedSessionClose(persistentUserDataDir, closePlan.sessionId);
3512
+ }
3513
+ async function finalizeSharedSessionClose(persistentUserDataDir, sessionId) {
3514
+ await withSharedSessionLock(persistentUserDataDir, async () => {
3515
+ const metadata = await readSharedSessionMetadata(persistentUserDataDir);
3516
+ if (!metadata || metadata.sessionId !== sessionId) {
3517
+ return;
3518
+ }
3519
+ const liveClients = await listLiveSharedSessionClients(
3520
+ persistentUserDataDir
3521
+ );
3522
+ if (liveClients.length > 0) {
3523
+ const readyMetadata = {
3524
+ ...metadata,
3525
+ state: "ready"
3526
+ };
3527
+ await writeSharedSessionMetadata(
3528
+ persistentUserDataDir,
3529
+ readyMetadata
3530
+ );
3531
+ return;
3532
+ }
3533
+ if (await getProcessLiveness(metadata.browserOwner) !== "dead") {
3534
+ const readyMetadata = {
3535
+ ...metadata,
3536
+ state: "ready"
3537
+ };
3538
+ await writeSharedSessionMetadata(
3539
+ persistentUserDataDir,
3540
+ readyMetadata
3541
+ );
3542
+ return;
3543
+ }
3544
+ await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
3545
+ force: true,
3546
+ recursive: true
3547
+ }).catch(() => void 0);
3548
+ });
3549
+ }
3550
+ function getPrimaryBrowserContext(browser) {
3551
+ const contexts = browser.contexts();
3552
+ if (contexts.length === 0) {
3553
+ throw new Error(
3554
+ "Connection succeeded but no browser contexts were exposed."
3555
+ );
3556
+ }
3557
+ return contexts[0];
3558
+ }
3559
+ async function getSharedSessionPage(context, reuseExistingPage) {
3560
+ if (reuseExistingPage) {
3561
+ return await getExistingPageOrCreate(context);
3562
+ }
3563
+ return await context.newPage();
3564
+ }
3565
+ async function getExistingPageOrCreate(context) {
3566
+ const existingPage = context.pages()[0];
3567
+ if (existingPage) {
3568
+ return existingPage;
3569
+ }
3570
+ return await context.newPage();
3571
+ }
3572
+ function buildSharedSessionDiscoveryUrl(debugPort) {
3573
+ return `http://127.0.0.1:${debugPort}`;
3574
+ }
3575
+ async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
3576
+ if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
3577
+ return cdpUrl;
3578
+ }
3579
+ const versionUrl = normalizeDiscoveryUrl(cdpUrl);
3580
+ const deadline = Date.now() + timeoutMs;
3581
+ let lastError = "CDP discovery did not respond.";
3582
+ while (Date.now() < deadline) {
3583
+ const remaining = Math.max(deadline - Date.now(), 1e3);
3584
+ try {
3585
+ const response = await fetch(versionUrl, {
3586
+ signal: AbortSignal.timeout(Math.min(remaining, 5e3))
3587
+ });
3588
+ if (!response.ok) {
3589
+ lastError = `${response.status} ${response.statusText}`;
3590
+ } else {
3591
+ const payload = await response.json();
3592
+ const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
3593
+ if (wsUrl && wsUrl.trim()) {
3594
+ return wsUrl;
3595
+ }
3596
+ lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
1242
3597
  }
1243
- await (0, import_promises.rm)((0, import_node_path.join)(profilesDir, entry.name), {
1244
- recursive: true,
1245
- force: true
1246
- }).catch(() => void 0);
1247
- })
1248
- );
1249
- }
1250
- function isTempDirOwnedByLiveProcess(tempDirName, tempDirPrefix) {
1251
- const owner = parseTempDirOwner(tempDirName, tempDirPrefix);
1252
- if (!owner) {
1253
- return false;
1254
- }
1255
- if (owner.pid === process.pid && Math.abs(owner.processStartedAtMs - PROCESS_STARTED_AT_MS) <= PROCESS_START_TIME_TOLERANCE_MS) {
1256
- return true;
3598
+ } catch (error) {
3599
+ lastError = error instanceof Error ? error.message : "Unknown error";
3600
+ }
3601
+ await sleep5(100);
1257
3602
  }
1258
- return isProcessRunning(owner.pid);
3603
+ throw new Error(
3604
+ `Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
3605
+ );
1259
3606
  }
1260
- function parseTempDirOwner(tempDirName, tempDirPrefix) {
1261
- const remainder = tempDirName.slice(tempDirPrefix.length);
1262
- const firstDashIndex = remainder.indexOf("-");
1263
- const secondDashIndex = firstDashIndex === -1 ? -1 : remainder.indexOf("-", firstDashIndex + 1);
1264
- if (firstDashIndex === -1 || secondDashIndex === -1) {
1265
- return null;
3607
+ function normalizeDiscoveryUrl(cdpUrl) {
3608
+ let parsed;
3609
+ try {
3610
+ parsed = new URL(cdpUrl);
3611
+ } catch {
3612
+ throw new Error(
3613
+ `Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
3614
+ );
1266
3615
  }
1267
- const pid = Number.parseInt(remainder.slice(0, firstDashIndex), 10);
1268
- const processStartedAtMs = Number.parseInt(
1269
- remainder.slice(firstDashIndex + 1, secondDashIndex),
1270
- 10
1271
- );
1272
- if (!Number.isInteger(pid) || pid <= 0) {
1273
- return null;
3616
+ if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
3617
+ return parsed;
1274
3618
  }
1275
- if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
1276
- return null;
3619
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
3620
+ throw new Error(
3621
+ `Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
3622
+ );
1277
3623
  }
1278
- return { pid, processStartedAtMs };
3624
+ const normalized = new URL(parsed.toString());
3625
+ normalized.pathname = "/json/version";
3626
+ normalized.search = "";
3627
+ normalized.hash = "";
3628
+ return normalized;
1279
3629
  }
1280
- function isProcessRunning(pid) {
1281
- try {
1282
- process.kill(pid, 0);
1283
- return true;
1284
- } catch (error) {
1285
- const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
1286
- return code !== "ESRCH";
1287
- }
3630
+ async function reserveDebugPort() {
3631
+ return await new Promise((resolve, reject) => {
3632
+ const server = (0, import_node_net.createServer)();
3633
+ server.unref();
3634
+ server.on("error", reject);
3635
+ server.listen(0, "127.0.0.1", () => {
3636
+ const address = server.address();
3637
+ if (!address || typeof address === "string") {
3638
+ server.close();
3639
+ reject(new Error("Failed to reserve a local debug port."));
3640
+ return;
3641
+ }
3642
+ server.close((error) => {
3643
+ if (error) {
3644
+ reject(error);
3645
+ return;
3646
+ }
3647
+ resolve(address.port);
3648
+ });
3649
+ });
3650
+ });
3651
+ }
3652
+ async function sleep5(ms) {
3653
+ await new Promise((resolve) => setTimeout(resolve, ms));
1288
3654
  }
1289
3655
 
1290
3656
  // src/browser/pool.ts
1291
3657
  var BrowserPool = class {
1292
3658
  browser = null;
1293
- cdpProxy = null;
1294
- launchedProcess = null;
1295
- managedUserDataDir = null;
1296
- persistentProfile = false;
3659
+ activeSessionClose = null;
3660
+ closeInFlight = null;
1297
3661
  defaults;
1298
3662
  constructor(defaults = {}) {
1299
3663
  this.defaults = defaults;
1300
3664
  }
1301
3665
  async launch(options = {}) {
1302
- if (this.browser || this.cdpProxy || this.launchedProcess || this.managedUserDataDir) {
3666
+ if (this.browser || this.activeSessionClose) {
1303
3667
  await this.close();
1304
3668
  }
1305
3669
  const mode = options.mode ?? this.defaults.mode ?? "chromium";
@@ -1346,30 +3710,26 @@ var BrowserPool = class {
1346
3710
  return this.launchSandbox(options);
1347
3711
  }
1348
3712
  async close() {
1349
- const browser = this.browser;
1350
- const cdpProxy = this.cdpProxy;
1351
- const launchedProcess = this.launchedProcess;
1352
- const managedUserDataDir = this.managedUserDataDir;
1353
- const persistentProfile = this.persistentProfile;
1354
- this.browser = null;
1355
- this.cdpProxy = null;
1356
- this.launchedProcess = null;
1357
- this.managedUserDataDir = null;
1358
- this.persistentProfile = false;
3713
+ if (this.closeInFlight) {
3714
+ await this.closeInFlight;
3715
+ return;
3716
+ }
3717
+ const closeOperation = this.closeCurrent();
3718
+ this.closeInFlight = closeOperation;
1359
3719
  try {
1360
- if (browser) {
1361
- await browser.close().catch(() => void 0);
1362
- }
3720
+ await closeOperation;
3721
+ this.browser = null;
3722
+ this.activeSessionClose = null;
1363
3723
  } finally {
1364
- cdpProxy?.close();
1365
- await killProcessTree(launchedProcess);
1366
- if (managedUserDataDir && !persistentProfile) {
1367
- await (0, import_promises2.rm)(managedUserDataDir, {
1368
- recursive: true,
1369
- force: true
1370
- }).catch(() => void 0);
1371
- }
3724
+ this.closeInFlight = null;
3725
+ }
3726
+ }
3727
+ async closeCurrent() {
3728
+ if (this.activeSessionClose) {
3729
+ await this.activeSessionClose();
3730
+ return;
1372
3731
  }
3732
+ await this.browser?.close().catch(() => void 0);
1373
3733
  }
1374
3734
  async connectToRunning(cdpUrl, timeout) {
1375
3735
  let browser = null;
@@ -1384,11 +3744,14 @@ var BrowserPool = class {
1384
3744
  }
1385
3745
  cdpProxy = new CDPProxy(browserWsUrl, targetId);
1386
3746
  const proxyWsUrl = await cdpProxy.start();
1387
- browser = await import_playwright.chromium.connectOverCDP(proxyWsUrl, {
3747
+ browser = await import_playwright2.chromium.connectOverCDP(proxyWsUrl, {
1388
3748
  timeout: timeout ?? 3e4
1389
3749
  });
1390
3750
  this.browser = browser;
1391
- this.cdpProxy = cdpProxy;
3751
+ this.activeSessionClose = async () => {
3752
+ await browser?.close().catch(() => void 0);
3753
+ cdpProxy?.close();
3754
+ };
1392
3755
  const { context, page } = await pickBrowserContextAndPage(browser);
1393
3756
  return { browser, context, page, isExternal: true };
1394
3757
  } catch (error) {
@@ -1397,7 +3760,7 @@ var BrowserPool = class {
1397
3760
  }
1398
3761
  cdpProxy?.close();
1399
3762
  this.browser = null;
1400
- this.cdpProxy = null;
3763
+ this.activeSessionClose = null;
1401
3764
  throw error;
1402
3765
  }
1403
3766
  }
@@ -1417,55 +3780,29 @@ var BrowserPool = class {
1417
3780
  sourceUserDataDir,
1418
3781
  profileDirectory
1419
3782
  );
1420
- await clearPersistentProfileSingletons(persistentProfile.userDataDir);
1421
- const debugPort = await reserveDebugPort();
1422
- const headless = resolveLaunchHeadless(
1423
- "real",
1424
- options.headless,
1425
- this.defaults.headless
1426
- );
1427
- const launchArgs = buildRealBrowserLaunchArgs({
1428
- userDataDir: persistentProfile.userDataDir,
3783
+ const sharedSession = await acquireSharedRealBrowserSession({
3784
+ executablePath,
3785
+ headless: resolveLaunchHeadless(
3786
+ "real",
3787
+ options.headless,
3788
+ this.defaults.headless
3789
+ ),
3790
+ initialUrl: options.initialUrl,
3791
+ persistentProfile,
1429
3792
  profileDirectory,
1430
- debugPort,
1431
- headless
1432
- });
1433
- const processHandle = (0, import_node_child_process.spawn)(executablePath, launchArgs, {
1434
- detached: process.platform !== "win32",
1435
- stdio: "ignore"
3793
+ timeoutMs: options.timeout ?? 3e4
1436
3794
  });
1437
- processHandle.unref();
1438
- let browser = null;
1439
- try {
1440
- const wsUrl = await resolveCdpWebSocketUrl(
1441
- `http://127.0.0.1:${debugPort}`,
1442
- options.timeout ?? 3e4
1443
- );
1444
- browser = await import_playwright.chromium.connectOverCDP(wsUrl, {
1445
- timeout: options.timeout ?? 3e4
1446
- });
1447
- const { context, page } = await createOwnedBrowserContextAndPage(
1448
- browser
1449
- );
1450
- if (options.initialUrl) {
1451
- await page.goto(options.initialUrl, {
1452
- waitUntil: "domcontentloaded",
1453
- timeout: options.timeout ?? 3e4
1454
- });
1455
- }
1456
- this.browser = browser;
1457
- this.launchedProcess = processHandle;
1458
- this.managedUserDataDir = persistentProfile.userDataDir;
1459
- this.persistentProfile = true;
1460
- return { browser, context, page, isExternal: false };
1461
- } catch (error) {
1462
- await browser?.close().catch(() => void 0);
1463
- await killProcessTree(processHandle);
1464
- throw error;
1465
- }
3795
+ this.browser = sharedSession.browser;
3796
+ this.activeSessionClose = sharedSession.close;
3797
+ return {
3798
+ browser: sharedSession.browser,
3799
+ context: sharedSession.context,
3800
+ page: sharedSession.page,
3801
+ isExternal: false
3802
+ };
1466
3803
  }
1467
3804
  async launchSandbox(options) {
1468
- const browser = await import_playwright.chromium.launch({
3805
+ const browser = await import_playwright2.chromium.launch({
1469
3806
  headless: resolveLaunchHeadless(
1470
3807
  "chromium",
1471
3808
  options.headless,
@@ -1478,11 +3815,14 @@ var BrowserPool = class {
1478
3815
  const context = await browser.newContext(options.context || {});
1479
3816
  const page = await context.newPage();
1480
3817
  this.browser = browser;
3818
+ this.activeSessionClose = async () => {
3819
+ await browser.close().catch(() => void 0);
3820
+ };
1481
3821
  return { browser, context, page, isExternal: false };
1482
3822
  }
1483
3823
  };
1484
3824
  async function pickBrowserContextAndPage(browser) {
1485
- const context = getPrimaryBrowserContext(browser);
3825
+ const context = getPrimaryBrowserContext2(browser);
1486
3826
  const page = await getAttachedPageOrCreate(context);
1487
3827
  return { context, page };
1488
3828
  }
@@ -1495,11 +3835,6 @@ function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
1495
3835
  }
1496
3836
  return mode === "real";
1497
3837
  }
1498
- async function createOwnedBrowserContextAndPage(browser) {
1499
- const context = getPrimaryBrowserContext(browser);
1500
- const page = await getExistingPageOrCreate(context);
1501
- return { context, page };
1502
- }
1503
3838
  async function getAttachedPageOrCreate(context) {
1504
3839
  const pages = context.pages();
1505
3840
  const inspectablePage = pages.find(
@@ -1514,14 +3849,7 @@ async function getAttachedPageOrCreate(context) {
1514
3849
  }
1515
3850
  return await context.newPage();
1516
3851
  }
1517
- async function getExistingPageOrCreate(context) {
1518
- const existingPage = context.pages()[0];
1519
- if (existingPage) {
1520
- return existingPage;
1521
- }
1522
- return await context.newPage();
1523
- }
1524
- function getPrimaryBrowserContext(browser) {
3852
+ function getPrimaryBrowserContext2(browser) {
1525
3853
  const contexts = browser.contexts();
1526
3854
  if (contexts.length === 0) {
1527
3855
  throw new Error(
@@ -1540,125 +3868,6 @@ function safePageUrl(page) {
1540
3868
  return "";
1541
3869
  }
1542
3870
  }
1543
- function normalizeDiscoveryUrl(cdpUrl) {
1544
- let parsed;
1545
- try {
1546
- parsed = new URL(cdpUrl);
1547
- } catch {
1548
- throw new Error(
1549
- `Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
1550
- );
1551
- }
1552
- if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
1553
- return parsed;
1554
- }
1555
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
1556
- throw new Error(
1557
- `Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
1558
- );
1559
- }
1560
- const normalized = new URL(parsed.toString());
1561
- normalized.pathname = "/json/version";
1562
- normalized.search = "";
1563
- normalized.hash = "";
1564
- return normalized;
1565
- }
1566
- async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
1567
- if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
1568
- return cdpUrl;
1569
- }
1570
- const versionUrl = normalizeDiscoveryUrl(cdpUrl);
1571
- const deadline = Date.now() + timeoutMs;
1572
- let lastError = "CDP discovery did not respond.";
1573
- while (Date.now() < deadline) {
1574
- const remaining = Math.max(deadline - Date.now(), 1e3);
1575
- try {
1576
- const response = await fetch(versionUrl, {
1577
- signal: AbortSignal.timeout(Math.min(remaining, 5e3))
1578
- });
1579
- if (!response.ok) {
1580
- lastError = `${response.status} ${response.statusText}`;
1581
- } else {
1582
- const payload = await response.json();
1583
- const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
1584
- if (wsUrl && wsUrl.trim()) {
1585
- return wsUrl;
1586
- }
1587
- lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
1588
- }
1589
- } catch (error) {
1590
- lastError = error instanceof Error ? error.message : "Unknown error";
1591
- }
1592
- await sleep(100);
1593
- }
1594
- throw new Error(
1595
- `Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
1596
- );
1597
- }
1598
- async function reserveDebugPort() {
1599
- return await new Promise((resolve, reject) => {
1600
- const server = (0, import_node_net.createServer)();
1601
- server.unref();
1602
- server.on("error", reject);
1603
- server.listen(0, "127.0.0.1", () => {
1604
- const address = server.address();
1605
- if (!address || typeof address === "string") {
1606
- server.close();
1607
- reject(new Error("Failed to reserve a local debug port."));
1608
- return;
1609
- }
1610
- server.close((error) => {
1611
- if (error) {
1612
- reject(error);
1613
- return;
1614
- }
1615
- resolve(address.port);
1616
- });
1617
- });
1618
- });
1619
- }
1620
- function buildRealBrowserLaunchArgs(options) {
1621
- const args = [
1622
- `--user-data-dir=${options.userDataDir}`,
1623
- `--profile-directory=${options.profileDirectory}`,
1624
- `--remote-debugging-port=${options.debugPort}`,
1625
- "--disable-blink-features=AutomationControlled"
1626
- ];
1627
- if (options.headless) {
1628
- args.push("--headless=new");
1629
- }
1630
- return args;
1631
- }
1632
- async function killProcessTree(processHandle) {
1633
- if (!processHandle || processHandle.pid == null || processHandle.exitCode !== null) {
1634
- return;
1635
- }
1636
- if (process.platform === "win32") {
1637
- await new Promise((resolve) => {
1638
- const killer = (0, import_node_child_process.spawn)(
1639
- "taskkill",
1640
- ["/pid", String(processHandle.pid), "/t", "/f"],
1641
- {
1642
- stdio: "ignore"
1643
- }
1644
- );
1645
- killer.on("error", () => resolve());
1646
- killer.on("exit", () => resolve());
1647
- });
1648
- return;
1649
- }
1650
- try {
1651
- process.kill(-processHandle.pid, "SIGKILL");
1652
- } catch {
1653
- try {
1654
- processHandle.kill("SIGKILL");
1655
- } catch {
1656
- }
1657
- }
1658
- }
1659
- async function sleep(ms) {
1660
- await new Promise((resolve) => setTimeout(resolve, ms));
1661
- }
1662
3871
 
1663
3872
  // src/config.ts
1664
3873
  var import_fs2 = __toESM(require("fs"), 1);
@@ -1965,9 +4174,9 @@ function resolveNamespaceDir(rootDir, namespace) {
1965
4174
  const selectorsRoot = import_path2.default.resolve(rootDir, ".opensteer", "selectors");
1966
4175
  const normalizedNamespace = normalizeNamespace(namespace);
1967
4176
  const namespaceDir = import_path2.default.resolve(selectorsRoot, normalizedNamespace);
1968
- const relative = import_path2.default.relative(selectorsRoot, namespaceDir);
1969
- if (relative === "" || relative === ".") return namespaceDir;
1970
- if (relative.startsWith("..") || import_path2.default.isAbsolute(relative)) {
4177
+ const relative2 = import_path2.default.relative(selectorsRoot, namespaceDir);
4178
+ if (relative2 === "" || relative2 === ".") return namespaceDir;
4179
+ if (relative2.startsWith("..") || import_path2.default.isAbsolute(relative2)) {
1971
4180
  throw new Error(
1972
4181
  `Namespace "${namespace}" resolves outside selectors root.`
1973
4182
  );
@@ -2524,8 +4733,8 @@ function resolveNamespace(config, rootDir) {
2524
4733
  }
2525
4734
  const caller = getCallerFilePath();
2526
4735
  if (!caller) return normalizeNamespace("default");
2527
- const relative = import_path3.default.relative(rootDir, caller);
2528
- const cleaned = relative.replace(/\\/g, "/").replace(/\.(ts|tsx|js|mjs|cjs)$/, "");
4736
+ const relative2 = import_path3.default.relative(rootDir, caller);
4737
+ const cleaned = relative2.replace(/\\/g, "/").replace(/\.(ts|tsx|js|mjs|cjs)$/, "");
2529
4738
  return normalizeNamespace(cleaned || "default");
2530
4739
  }
2531
4740
  function getCallerFilePath() {
@@ -2930,7 +5139,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
2930
5139
  TRANSIENT_CONTEXT_RETRY_DELAY_MS,
2931
5140
  Math.max(0, deadline - Date.now())
2932
5141
  );
2933
- await sleep2(retryDelay);
5142
+ await sleep6(retryDelay);
2934
5143
  }
2935
5144
  }
2936
5145
  }
@@ -2963,7 +5172,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
2963
5172
  () => ({ kind: "resolved" }),
2964
5173
  (error) => ({ kind: "rejected", error })
2965
5174
  );
2966
- const timeoutPromise = sleep2(
5175
+ const timeoutPromise = sleep6(
2967
5176
  timeout + FRAME_EVALUATE_GRACE_MS
2968
5177
  ).then(() => ({ kind: "timeout" }));
2969
5178
  const result = await Promise.race([
@@ -3105,7 +5314,7 @@ function isIgnorableFrameError(error) {
3105
5314
  const message = error.message;
3106
5315
  return message.includes("Frame was detached") || message.includes("Target page, context or browser has been closed") || isTransientExecutionContextError(error) || message.includes("No frame for given id found");
3107
5316
  }
3108
- function sleep2(ms) {
5317
+ function sleep6(ms) {
3109
5318
  return new Promise((resolve) => {
3110
5319
  setTimeout(resolve, ms);
3111
5320
  });
@@ -7366,7 +9575,7 @@ async function closeTab(context, activePage, index) {
7366
9575
  }
7367
9576
 
7368
9577
  // src/actions/cookies.ts
7369
- var import_promises3 = require("fs/promises");
9578
+ var import_promises6 = require("fs/promises");
7370
9579
  async function getCookies(context, url) {
7371
9580
  return context.cookies(url ? [url] : void 0);
7372
9581
  }
@@ -7378,10 +9587,10 @@ async function clearCookies(context) {
7378
9587
  }
7379
9588
  async function exportCookies(context, filePath, url) {
7380
9589
  const cookies = await context.cookies(url ? [url] : void 0);
7381
- await (0, import_promises3.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
9590
+ await (0, import_promises6.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
7382
9591
  }
7383
9592
  async function importCookies(context, filePath) {
7384
- const raw = await (0, import_promises3.readFile)(filePath, "utf-8");
9593
+ const raw = await (0, import_promises6.readFile)(filePath, "utf-8");
7385
9594
  const cookies = JSON.parse(raw);
7386
9595
  await context.addCookies(cookies);
7387
9596
  }
@@ -7692,7 +9901,7 @@ var AdaptiveNetworkTracker = class {
7692
9901
  this.idleSince = 0;
7693
9902
  }
7694
9903
  const remaining = Math.max(1, options.deadline - now);
7695
- await sleep3(Math.min(NETWORK_POLL_MS, remaining));
9904
+ await sleep7(Math.min(NETWORK_POLL_MS, remaining));
7696
9905
  }
7697
9906
  }
7698
9907
  handleRequestStarted = (request) => {
@@ -7737,7 +9946,7 @@ var AdaptiveNetworkTracker = class {
7737
9946
  return false;
7738
9947
  }
7739
9948
  };
7740
- async function sleep3(ms) {
9949
+ async function sleep7(ms) {
7741
9950
  await new Promise((resolve) => {
7742
9951
  setTimeout(resolve, ms);
7743
9952
  });
@@ -9442,13 +11651,13 @@ function dedupeNewest(entries) {
9442
11651
  }
9443
11652
 
9444
11653
  // src/cloud/cdp-client.ts
9445
- var import_playwright2 = require("playwright");
11654
+ var import_playwright3 = require("playwright");
9446
11655
  var CloudCdpClient = class {
9447
11656
  async connect(args) {
9448
11657
  const endpoint = withTokenQuery(args.wsUrl, args.token);
9449
11658
  let browser;
9450
11659
  try {
9451
- browser = await import_playwright2.chromium.connectOverCDP(endpoint);
11660
+ browser = await import_playwright3.chromium.connectOverCDP(endpoint);
9452
11661
  } catch (error) {
9453
11662
  const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
9454
11663
  throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
@@ -11265,7 +13474,7 @@ async function executeAgentAction(page, action) {
11265
13474
  }
11266
13475
  case "wait": {
11267
13476
  const ms = numberOr(action.timeMs, action.time_ms, 1e3);
11268
- await sleep4(ms);
13477
+ await sleep8(ms);
11269
13478
  return;
11270
13479
  }
11271
13480
  case "goto": {
@@ -11430,7 +13639,7 @@ async function pressKeyCombo(page, combo) {
11430
13639
  }
11431
13640
  }
11432
13641
  }
11433
- function sleep4(ms) {
13642
+ function sleep8(ms) {
11434
13643
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
11435
13644
  }
11436
13645
 
@@ -11461,7 +13670,7 @@ var OpensteerCuaAgentHandler = class {
11461
13670
  if (isMutatingAgentAction(action)) {
11462
13671
  this.onMutatingAction?.(action);
11463
13672
  }
11464
- await sleep5(this.config.waitBetweenActionsMs);
13673
+ await sleep9(this.config.waitBetweenActionsMs);
11465
13674
  });
11466
13675
  try {
11467
13676
  const result = await this.client.execute({
@@ -11523,7 +13732,7 @@ var OpensteerCuaAgentHandler = class {
11523
13732
  await this.cursorController.preview({ x, y }, "agent");
11524
13733
  }
11525
13734
  };
11526
- function sleep5(ms) {
13735
+ function sleep9(ms) {
11527
13736
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
11528
13737
  }
11529
13738
 
@@ -11959,7 +14168,7 @@ var CursorController = class {
11959
14168
  for (const step of motion.points) {
11960
14169
  await this.renderer.move(step, this.style);
11961
14170
  if (motion.stepDelayMs > 0) {
11962
- await sleep6(motion.stepDelayMs);
14171
+ await sleep10(motion.stepDelayMs);
11963
14172
  }
11964
14173
  }
11965
14174
  if (shouldPulse(intent)) {
@@ -12117,7 +14326,7 @@ function clamp2(value, min, max) {
12117
14326
  function shouldPulse(intent) {
12118
14327
  return intent === "click" || intent === "dblclick" || intent === "rightclick" || intent === "agent";
12119
14328
  }
12120
- function sleep6(ms) {
14329
+ function sleep10(ms) {
12121
14330
  return new Promise((resolve) => setTimeout(resolve, ms));
12122
14331
  }
12123
14332
 
@@ -12591,15 +14800,22 @@ var Opensteer = class _Opensteer {
12591
14800
  }
12592
14801
  return;
12593
14802
  }
12594
- if (this.ownsBrowser) {
12595
- await this.pool.close();
12596
- }
12597
- this.browser = null;
12598
- this.pageRef = null;
12599
- this.contextRef = null;
12600
- this.ownsBrowser = false;
12601
- if (this.cursorController) {
12602
- await this.cursorController.dispose().catch(() => void 0);
14803
+ let closedOwnedBrowser = false;
14804
+ try {
14805
+ if (this.ownsBrowser) {
14806
+ await this.pool.close();
14807
+ closedOwnedBrowser = true;
14808
+ }
14809
+ } finally {
14810
+ this.browser = null;
14811
+ this.pageRef = null;
14812
+ this.contextRef = null;
14813
+ if (!this.ownsBrowser || closedOwnedBrowser) {
14814
+ this.ownsBrowser = false;
14815
+ }
14816
+ if (this.cursorController) {
14817
+ await this.cursorController.dispose().catch(() => void 0);
14818
+ }
12603
14819
  }
12604
14820
  }
12605
14821
  async syncLocalSelectorCacheToCloud() {
@@ -15053,7 +17269,7 @@ var CdpOverlayCursorRenderer = class {
15053
17269
  outlineColor: toProtocolRgba(pulseOutline)
15054
17270
  });
15055
17271
  });
15056
- await sleep7(PULSE_DELAY_MS);
17272
+ await sleep11(PULSE_DELAY_MS);
15057
17273
  await this.move(point, style);
15058
17274
  }
15059
17275
  async clear() {
@@ -15177,7 +17393,7 @@ function clampAlpha(value) {
15177
17393
  function roundPointValue(value) {
15178
17394
  return Math.round(value * 100) / 100;
15179
17395
  }
15180
- function sleep7(ms) {
17396
+ function sleep11(ms) {
15181
17397
  return new Promise((resolve) => setTimeout(resolve, ms));
15182
17398
  }
15183
17399
  // Annotate the CommonJS export names for ESM import in node:
@@ -15237,6 +17453,7 @@ function sleep7(ms) {
15237
17453
  createCuaClient,
15238
17454
  createEmptyRegistry,
15239
17455
  createExtractCallback,
17456
+ createIsolatedRuntimeProfile,
15240
17457
  createResolveCallback,
15241
17458
  createTab,
15242
17459
  detectChromePaths,
@@ -15252,8 +17469,10 @@ function sleep7(ms) {
15252
17469
  getElementValue,
15253
17470
  getModelProvider,
15254
17471
  getOrCreatePersistentProfile,
17472
+ getOwnedRealBrowserProcessPolicy,
15255
17473
  getPageHtml,
15256
17474
  getPageTitle,
17475
+ hasActiveRuntimeProfileCreations,
15257
17476
  importCookies,
15258
17477
  isCloudActionMethod,
15259
17478
  isCloudErrorCode,
@@ -15269,6 +17488,7 @@ function sleep7(ms) {
15269
17488
  performInput,
15270
17489
  performScroll,
15271
17490
  performSelect,
17491
+ persistIsolatedRuntimeProfile,
15272
17492
  planSnappyCursorMotion,
15273
17493
  prepareSnapshot,
15274
17494
  pressKey,