opensteer 0.6.11 → 0.6.12

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.
@@ -25,24 +25,34 @@ import {
25
25
  } from "./chunk-3H5RRIMZ.js";
26
26
 
27
27
  // src/browser/persistent-profile.ts
28
- import { createHash } from "crypto";
29
- import { existsSync } from "fs";
28
+ import { createHash, randomUUID } from "crypto";
29
+ import { execFile } from "child_process";
30
+ import { createReadStream, existsSync } from "fs";
30
31
  import {
31
32
  cp,
32
33
  copyFile,
33
34
  mkdir,
34
35
  mkdtemp,
35
36
  readdir,
37
+ readFile,
36
38
  rename,
37
39
  rm,
38
40
  stat,
39
41
  writeFile
40
42
  } from "fs/promises";
41
- import { homedir } from "os";
42
- import { basename, dirname, join } from "path";
43
+ import { homedir, tmpdir } from "os";
44
+ import { basename, dirname, join, relative, sep } from "path";
45
+ import { promisify } from "util";
46
+ var execFileAsync = promisify(execFile);
43
47
  var OPENSTEER_META_FILE = ".opensteer-meta.json";
48
+ var OPENSTEER_RUNTIME_META_FILE = ".opensteer-runtime.json";
49
+ var LOCK_OWNER_FILE = "owner.json";
50
+ var LOCK_RECLAIMER_DIR = "reclaimer";
44
51
  var PROCESS_STARTED_AT_MS = Math.floor(Date.now() - process.uptime() * 1e3);
45
52
  var PROCESS_START_TIME_TOLERANCE_MS = 1e3;
53
+ var PROFILE_LOCK_RETRY_DELAY_MS = 50;
54
+ var PS_COMMAND_ENV = { ...process.env, LC_ALL: "C" };
55
+ var LINUX_STAT_START_TIME_FIELD_INDEX = 19;
46
56
  var CHROME_SINGLETON_ENTRIES = /* @__PURE__ */ new Set([
47
57
  "SingletonCookie",
48
58
  "SingletonLock",
@@ -52,7 +62,8 @@ var CHROME_SINGLETON_ENTRIES = /* @__PURE__ */ new Set([
52
62
  ]);
53
63
  var COPY_SKIP_ENTRIES = /* @__PURE__ */ new Set([
54
64
  ...CHROME_SINGLETON_ENTRIES,
55
- OPENSTEER_META_FILE
65
+ OPENSTEER_META_FILE,
66
+ OPENSTEER_RUNTIME_META_FILE
56
67
  ]);
57
68
  var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
58
69
  "Crash Reports",
@@ -71,6 +82,11 @@ var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
71
82
  "WidevineCdm",
72
83
  "pnacl"
73
84
  ]);
85
+ var CURRENT_PROCESS_LOCK_OWNER = {
86
+ pid: process.pid,
87
+ processStartedAtMs: PROCESS_STARTED_AT_MS
88
+ };
89
+ var linuxClockTicksPerSecondPromise = null;
74
90
  async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory, profilesRootDir = defaultPersistentProfilesRootDir()) {
75
91
  const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
76
92
  const targetUserDataDir = join(
@@ -83,27 +99,29 @@ async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory,
83
99
  profileDirectory
84
100
  );
85
101
  await mkdir(dirname(targetUserDataDir), { recursive: true });
86
- await cleanOrphanedTempDirs(
87
- dirname(targetUserDataDir),
88
- basename(targetUserDataDir)
89
- );
90
- if (!existsSync(sourceProfileDir)) {
91
- throw new Error(
92
- `Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
102
+ return await withPersistentProfileLock(targetUserDataDir, async () => {
103
+ await cleanOrphanedOwnedDirs(
104
+ dirname(targetUserDataDir),
105
+ buildPersistentProfileTempDirNamePrefix(targetUserDataDir)
93
106
  );
94
- }
95
- const created = await createPersistentProfileClone(
96
- resolvedSourceUserDataDir,
97
- sourceProfileDir,
98
- targetUserDataDir,
99
- profileDirectory,
100
- metadata
101
- );
102
- await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
103
- return {
104
- created,
105
- userDataDir: targetUserDataDir
106
- };
107
+ if (!existsSync(sourceProfileDir)) {
108
+ throw new Error(
109
+ `Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
110
+ );
111
+ }
112
+ const created = await createPersistentProfileClone(
113
+ resolvedSourceUserDataDir,
114
+ sourceProfileDir,
115
+ targetUserDataDir,
116
+ profileDirectory,
117
+ metadata
118
+ );
119
+ await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
120
+ return {
121
+ created,
122
+ userDataDir: targetUserDataDir
123
+ };
124
+ });
107
125
  }
108
126
  async function clearPersistentProfileSingletons(userDataDir) {
109
127
  await Promise.all(
@@ -115,6 +133,80 @@ async function clearPersistentProfileSingletons(userDataDir) {
115
133
  )
116
134
  );
117
135
  }
136
+ async function createIsolatedRuntimeProfile(sourceUserDataDir, runtimesRootDir = defaultRuntimeProfilesRootDir()) {
137
+ const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
138
+ const runtimeRootDir = expandHome(runtimesRootDir);
139
+ await mkdir(runtimeRootDir, { recursive: true });
140
+ return await withPersistentProfileLock(
141
+ resolvedSourceUserDataDir,
142
+ async () => {
143
+ const sourceMetadata = await readPersistentProfileMetadata(
144
+ resolvedSourceUserDataDir
145
+ );
146
+ await cleanOrphanedOwnedDirs(
147
+ runtimeRootDir,
148
+ buildRuntimeProfileDirNamePrefix(resolvedSourceUserDataDir)
149
+ );
150
+ const runtimeUserDataDir = await mkdtemp(
151
+ buildRuntimeProfileDirPrefix(
152
+ runtimeRootDir,
153
+ resolvedSourceUserDataDir
154
+ )
155
+ );
156
+ try {
157
+ await copyUserDataDirSnapshot(
158
+ resolvedSourceUserDataDir,
159
+ runtimeUserDataDir
160
+ );
161
+ await writeRuntimeProfileMetadata(
162
+ runtimeUserDataDir,
163
+ await buildRuntimeProfileMetadata(
164
+ runtimeUserDataDir,
165
+ sourceMetadata?.profileDirectory ?? null
166
+ )
167
+ );
168
+ return {
169
+ persistentUserDataDir: resolvedSourceUserDataDir,
170
+ userDataDir: runtimeUserDataDir
171
+ };
172
+ } catch (error) {
173
+ await rm(runtimeUserDataDir, {
174
+ recursive: true,
175
+ force: true
176
+ }).catch(() => void 0);
177
+ throw error;
178
+ }
179
+ }
180
+ );
181
+ }
182
+ async function persistIsolatedRuntimeProfile(runtimeUserDataDir, persistentUserDataDir) {
183
+ const resolvedRuntimeUserDataDir = expandHome(runtimeUserDataDir);
184
+ const resolvedPersistentUserDataDir = expandHome(persistentUserDataDir);
185
+ await withPersistentProfileLock(resolvedPersistentUserDataDir, async () => {
186
+ await mkdir(dirname(resolvedPersistentUserDataDir), { recursive: true });
187
+ await cleanOrphanedOwnedDirs(
188
+ dirname(resolvedPersistentUserDataDir),
189
+ buildPersistentProfileTempDirNamePrefix(resolvedPersistentUserDataDir)
190
+ );
191
+ const metadata = await requirePersistentProfileMetadata(
192
+ resolvedPersistentUserDataDir
193
+ );
194
+ const runtimeMetadata = await requireRuntimeProfileMetadata(
195
+ resolvedRuntimeUserDataDir,
196
+ metadata.profileDirectory
197
+ );
198
+ await mergePersistentProfileSnapshot(
199
+ resolvedRuntimeUserDataDir,
200
+ resolvedPersistentUserDataDir,
201
+ metadata,
202
+ runtimeMetadata
203
+ );
204
+ });
205
+ await rm(resolvedRuntimeUserDataDir, {
206
+ recursive: true,
207
+ force: true
208
+ });
209
+ }
118
210
  function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
119
211
  const hash = createHash("sha256").update(`${sourceUserDataDir}\0${profileDirectory}`).digest("hex").slice(0, 16);
120
212
  const sourceLabel = sanitizePathSegment(basename(sourceUserDataDir) || "user-data");
@@ -124,6 +216,9 @@ function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
124
216
  function defaultPersistentProfilesRootDir() {
125
217
  return join(homedir(), ".opensteer", "real-browser-profiles");
126
218
  }
219
+ function defaultRuntimeProfilesRootDir() {
220
+ return join(tmpdir(), "opensteer-real-browser-runtimes");
221
+ }
127
222
  function sanitizePathSegment(value) {
128
223
  const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-");
129
224
  return sanitized.replace(/^-|-$/g, "") || "profile";
@@ -131,6 +226,23 @@ function sanitizePathSegment(value) {
131
226
  function isProfileDirectory(userDataDir, entry) {
132
227
  return existsSync(join(userDataDir, entry, "Preferences"));
133
228
  }
229
+ async function copyUserDataDirSnapshot(sourceUserDataDir, targetUserDataDir) {
230
+ await cp(sourceUserDataDir, targetUserDataDir, {
231
+ recursive: true,
232
+ filter: (candidatePath) => shouldCopyRuntimeSnapshotEntry(sourceUserDataDir, candidatePath)
233
+ });
234
+ }
235
+ function shouldCopyRuntimeSnapshotEntry(userDataDir, candidatePath) {
236
+ const candidateRelativePath = relative(userDataDir, candidatePath);
237
+ if (!candidateRelativePath) {
238
+ return true;
239
+ }
240
+ const segments = candidateRelativePath.split(sep).filter(Boolean);
241
+ if (segments.length !== 1) {
242
+ return true;
243
+ }
244
+ return !COPY_SKIP_ENTRIES.has(segments[0]);
245
+ }
134
246
  async function copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, targetProfileDirectory) {
135
247
  let entries;
136
248
  try {
@@ -187,19 +299,17 @@ async function createPersistentProfileClone(sourceUserDataDir, sourceProfileDir,
187
299
  );
188
300
  let published = false;
189
301
  try {
190
- await cp(sourceProfileDir, join(tempUserDataDir, profileDirectory), {
191
- recursive: true
192
- });
193
- await copyRootLevelEntries(
302
+ await materializePersistentProfileSnapshot(
194
303
  sourceUserDataDir,
304
+ sourceProfileDir,
195
305
  tempUserDataDir,
196
- profileDirectory
306
+ profileDirectory,
307
+ metadata
197
308
  );
198
- await writePersistentProfileMetadata(tempUserDataDir, metadata);
199
309
  try {
200
310
  await rename(tempUserDataDir, targetUserDataDir);
201
311
  } catch (error) {
202
- if (wasProfilePublishedByAnotherProcess(error, targetUserDataDir)) {
312
+ if (wasDirPublishedByAnotherProcess(error, targetUserDataDir)) {
203
313
  return false;
204
314
  }
205
315
  throw error;
@@ -215,60 +325,621 @@ async function createPersistentProfileClone(sourceUserDataDir, sourceProfileDir,
215
325
  }
216
326
  }
217
327
  }
328
+ async function materializePersistentProfileSnapshot(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
329
+ if (!existsSync(sourceProfileDir)) {
330
+ throw new Error(
331
+ `Chrome profile "${profileDirectory}" was not found in "${sourceUserDataDir}".`
332
+ );
333
+ }
334
+ await cp(sourceProfileDir, join(targetUserDataDir, profileDirectory), {
335
+ recursive: true
336
+ });
337
+ await copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, profileDirectory);
338
+ await writePersistentProfileMetadata(targetUserDataDir, metadata);
339
+ }
340
+ async function mergePersistentProfileSnapshot(runtimeUserDataDir, persistentUserDataDir, metadata, runtimeMetadata) {
341
+ const tempUserDataDir = await mkdtemp(
342
+ buildPersistentProfileTempDirPrefix(persistentUserDataDir)
343
+ );
344
+ let published = false;
345
+ try {
346
+ const baseEntries = deserializeSnapshotManifestEntries(
347
+ runtimeMetadata.baseEntries
348
+ );
349
+ const currentEntries = await collectPersistentSnapshotEntries(
350
+ persistentUserDataDir,
351
+ metadata.profileDirectory
352
+ );
353
+ const runtimeEntries = await collectPersistentSnapshotEntries(
354
+ runtimeUserDataDir,
355
+ metadata.profileDirectory
356
+ );
357
+ const mergedEntries = resolveMergedSnapshotEntries(
358
+ baseEntries,
359
+ currentEntries,
360
+ runtimeEntries
361
+ );
362
+ await materializeMergedPersistentProfileSnapshot(
363
+ tempUserDataDir,
364
+ currentEntries,
365
+ runtimeEntries,
366
+ mergedEntries
367
+ );
368
+ await writePersistentProfileMetadata(tempUserDataDir, metadata);
369
+ await replaceProfileDirectory(persistentUserDataDir, tempUserDataDir);
370
+ published = true;
371
+ } finally {
372
+ if (!published) {
373
+ await rm(tempUserDataDir, {
374
+ recursive: true,
375
+ force: true
376
+ }).catch(() => void 0);
377
+ }
378
+ }
379
+ }
380
+ async function buildRuntimeProfileMetadata(runtimeUserDataDir, profileDirectory) {
381
+ if (!profileDirectory) {
382
+ return {
383
+ baseEntries: {},
384
+ profileDirectory: null
385
+ };
386
+ }
387
+ const baseEntries = await collectPersistentSnapshotEntries(
388
+ runtimeUserDataDir,
389
+ profileDirectory
390
+ );
391
+ return {
392
+ baseEntries: serializeSnapshotManifestEntries(baseEntries),
393
+ profileDirectory
394
+ };
395
+ }
396
+ async function writeRuntimeProfileMetadata(userDataDir, metadata) {
397
+ await writeFile(
398
+ join(userDataDir, OPENSTEER_RUNTIME_META_FILE),
399
+ JSON.stringify(metadata, null, 2)
400
+ );
401
+ }
402
+ async function readRuntimeProfileMetadata(userDataDir) {
403
+ try {
404
+ const raw = await readFile(
405
+ join(userDataDir, OPENSTEER_RUNTIME_META_FILE),
406
+ "utf8"
407
+ );
408
+ const parsed = JSON.parse(raw);
409
+ const profileDirectory = parsed.profileDirectory === null ? null : typeof parsed.profileDirectory === "string" ? parsed.profileDirectory : void 0;
410
+ if (profileDirectory === void 0 || typeof parsed.baseEntries !== "object" || parsed.baseEntries === null || Array.isArray(parsed.baseEntries)) {
411
+ return null;
412
+ }
413
+ const baseEntries = deserializeSnapshotManifestEntries(
414
+ parsed.baseEntries
415
+ );
416
+ return {
417
+ baseEntries: Object.fromEntries(baseEntries),
418
+ profileDirectory
419
+ };
420
+ } catch {
421
+ return null;
422
+ }
423
+ }
424
+ async function requireRuntimeProfileMetadata(userDataDir, expectedProfileDirectory) {
425
+ const metadata = await readRuntimeProfileMetadata(userDataDir);
426
+ if (!metadata) {
427
+ throw new Error(
428
+ `Runtime profile metadata was not found for "${userDataDir}".`
429
+ );
430
+ }
431
+ if (metadata.profileDirectory !== expectedProfileDirectory) {
432
+ throw new Error(
433
+ `Runtime profile "${userDataDir}" was created for profile "${metadata.profileDirectory ?? "unknown"}", expected "${expectedProfileDirectory}".`
434
+ );
435
+ }
436
+ return metadata;
437
+ }
438
+ async function collectPersistentSnapshotEntries(userDataDir, profileDirectory) {
439
+ let rootEntries;
440
+ try {
441
+ rootEntries = await readdir(userDataDir, {
442
+ encoding: "utf8",
443
+ withFileTypes: true
444
+ });
445
+ } catch {
446
+ return /* @__PURE__ */ new Map();
447
+ }
448
+ rootEntries.sort((left, right) => left.name.localeCompare(right.name));
449
+ const collected = /* @__PURE__ */ new Map();
450
+ for (const entry of rootEntries) {
451
+ if (!shouldIncludePersistentRootEntry(
452
+ userDataDir,
453
+ profileDirectory,
454
+ entry.name
455
+ )) {
456
+ continue;
457
+ }
458
+ await collectSnapshotEntry(
459
+ join(userDataDir, entry.name),
460
+ entry.name,
461
+ collected
462
+ );
463
+ }
464
+ return collected;
465
+ }
466
+ function shouldIncludePersistentRootEntry(userDataDir, profileDirectory, entry) {
467
+ if (entry === profileDirectory) {
468
+ return true;
469
+ }
470
+ if (COPY_SKIP_ENTRIES.has(entry)) {
471
+ return false;
472
+ }
473
+ if (SKIPPED_ROOT_DIRECTORIES.has(entry)) {
474
+ return false;
475
+ }
476
+ return !isProfileDirectory(userDataDir, entry);
477
+ }
478
+ async function collectSnapshotEntry(sourcePath, relativePath, collected) {
479
+ let entryStat;
480
+ try {
481
+ entryStat = await stat(sourcePath);
482
+ } catch {
483
+ return;
484
+ }
485
+ if (entryStat.isDirectory()) {
486
+ collected.set(relativePath, {
487
+ kind: "directory",
488
+ hash: null,
489
+ sourcePath
490
+ });
491
+ let children;
492
+ try {
493
+ children = await readdir(sourcePath, {
494
+ encoding: "utf8",
495
+ withFileTypes: true
496
+ });
497
+ } catch {
498
+ return;
499
+ }
500
+ children.sort((left, right) => left.name.localeCompare(right.name));
501
+ for (const child of children) {
502
+ await collectSnapshotEntry(
503
+ join(sourcePath, child.name),
504
+ join(relativePath, child.name),
505
+ collected
506
+ );
507
+ }
508
+ return;
509
+ }
510
+ if (entryStat.isFile()) {
511
+ collected.set(relativePath, {
512
+ kind: "file",
513
+ hash: await hashFile(sourcePath),
514
+ sourcePath
515
+ });
516
+ }
517
+ }
518
+ function serializeSnapshotManifestEntries(entries) {
519
+ return Object.fromEntries(
520
+ [...entries.entries()].sort(([leftPath], [rightPath]) => leftPath.localeCompare(rightPath)).map(([relativePath, entry]) => [
521
+ relativePath,
522
+ {
523
+ kind: entry.kind,
524
+ hash: entry.hash
525
+ }
526
+ ])
527
+ );
528
+ }
529
+ function deserializeSnapshotManifestEntries(entries) {
530
+ const manifestEntries = /* @__PURE__ */ new Map();
531
+ for (const [relativePath, entry] of Object.entries(entries)) {
532
+ if (!entry || entry.kind !== "directory" && entry.kind !== "file" || !(entry.hash === null || typeof entry.hash === "string")) {
533
+ throw new Error(
534
+ `Runtime profile metadata for "${relativePath}" is invalid.`
535
+ );
536
+ }
537
+ manifestEntries.set(relativePath, {
538
+ kind: entry.kind,
539
+ hash: entry.hash
540
+ });
541
+ }
542
+ return manifestEntries;
543
+ }
544
+ function resolveMergedSnapshotEntries(baseEntries, currentEntries, runtimeEntries) {
545
+ const mergedEntries = /* @__PURE__ */ new Map();
546
+ const relativePaths = /* @__PURE__ */ new Set([
547
+ ...baseEntries.keys(),
548
+ ...currentEntries.keys(),
549
+ ...runtimeEntries.keys()
550
+ ]);
551
+ for (const relativePath of [...relativePaths].sort(compareSnapshotPaths)) {
552
+ mergedEntries.set(
553
+ relativePath,
554
+ resolveMergedSnapshotEntrySelection(
555
+ relativePath,
556
+ baseEntries.get(relativePath) ?? null,
557
+ currentEntries.get(relativePath) ?? null,
558
+ runtimeEntries.get(relativePath) ?? null
559
+ )
560
+ );
561
+ }
562
+ return mergedEntries;
563
+ }
564
+ function resolveMergedSnapshotEntrySelection(relativePath, baseEntry, currentEntry, runtimeEntry) {
565
+ if (snapshotEntriesEqual(runtimeEntry, baseEntry)) {
566
+ return currentEntry ? "current" : null;
567
+ }
568
+ if (snapshotEntriesEqual(currentEntry, baseEntry)) {
569
+ return runtimeEntry ? "runtime" : null;
570
+ }
571
+ if (!baseEntry) {
572
+ if (!currentEntry) {
573
+ return runtimeEntry ? "runtime" : null;
574
+ }
575
+ if (!runtimeEntry) {
576
+ return "current";
577
+ }
578
+ if (snapshotEntriesEqual(currentEntry, runtimeEntry)) {
579
+ return "current";
580
+ }
581
+ throw new Error(
582
+ `Concurrent runtime updates changed "${relativePath}" differently; refusing to overwrite the persistent profile.`
583
+ );
584
+ }
585
+ if (!currentEntry && !runtimeEntry) {
586
+ return null;
587
+ }
588
+ if (snapshotEntriesEqual(currentEntry, runtimeEntry)) {
589
+ return currentEntry ? "current" : null;
590
+ }
591
+ throw new Error(
592
+ `Concurrent runtime updates changed "${relativePath}" differently; refusing to overwrite the persistent profile.`
593
+ );
594
+ }
595
+ function snapshotEntriesEqual(left, right) {
596
+ if (!left || !right) {
597
+ return left === right;
598
+ }
599
+ return left.kind === right.kind && left.hash === right.hash;
600
+ }
601
+ async function materializeMergedPersistentProfileSnapshot(targetUserDataDir, currentEntries, runtimeEntries, mergedEntries) {
602
+ const selectedEntries = [...mergedEntries.entries()].filter(([, selection]) => selection !== null).sort(
603
+ ([leftPath], [rightPath]) => compareSnapshotPaths(leftPath, rightPath)
604
+ );
605
+ for (const [relativePath, selection] of selectedEntries) {
606
+ const entry = (selection === "current" ? currentEntries.get(relativePath) : runtimeEntries.get(relativePath)) ?? null;
607
+ if (!entry) {
608
+ continue;
609
+ }
610
+ const targetPath = join(targetUserDataDir, relativePath);
611
+ if (entry.kind === "directory") {
612
+ await mkdir(targetPath, { recursive: true });
613
+ continue;
614
+ }
615
+ await mkdir(dirname(targetPath), { recursive: true });
616
+ await copyFile(entry.sourcePath, targetPath);
617
+ }
618
+ }
619
+ function compareSnapshotPaths(left, right) {
620
+ const leftDepth = left.split(sep).length;
621
+ const rightDepth = right.split(sep).length;
622
+ if (leftDepth !== rightDepth) {
623
+ return leftDepth - rightDepth;
624
+ }
625
+ return left.localeCompare(right);
626
+ }
627
+ async function hashFile(filePath) {
628
+ return new Promise((resolve, reject) => {
629
+ const hash = createHash("sha256");
630
+ const stream = createReadStream(filePath);
631
+ stream.on("data", (chunk) => {
632
+ hash.update(chunk);
633
+ });
634
+ stream.on("error", reject);
635
+ stream.on("end", () => {
636
+ resolve(hash.digest("hex"));
637
+ });
638
+ });
639
+ }
218
640
  async function ensurePersistentProfileMetadata(userDataDir, metadata) {
219
641
  if (existsSync(join(userDataDir, OPENSTEER_META_FILE))) {
220
642
  return;
221
643
  }
222
644
  await writePersistentProfileMetadata(userDataDir, metadata);
223
645
  }
224
- function wasProfilePublishedByAnotherProcess(error, targetUserDataDir) {
646
+ async function readPersistentProfileMetadata(userDataDir) {
647
+ try {
648
+ const raw = await readFile(join(userDataDir, OPENSTEER_META_FILE), "utf8");
649
+ const parsed = JSON.parse(raw);
650
+ if (typeof parsed.createdAt !== "string" || typeof parsed.profileDirectory !== "string" || typeof parsed.source !== "string") {
651
+ return null;
652
+ }
653
+ return {
654
+ createdAt: parsed.createdAt,
655
+ profileDirectory: parsed.profileDirectory,
656
+ source: parsed.source
657
+ };
658
+ } catch {
659
+ return null;
660
+ }
661
+ }
662
+ async function requirePersistentProfileMetadata(userDataDir) {
663
+ const metadata = await readPersistentProfileMetadata(userDataDir);
664
+ if (!metadata) {
665
+ throw new Error(
666
+ `Persistent profile metadata was not found for "${userDataDir}".`
667
+ );
668
+ }
669
+ return metadata;
670
+ }
671
+ function wasDirPublishedByAnotherProcess(error, targetDirPath) {
225
672
  const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
226
- return existsSync(targetUserDataDir) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
673
+ return existsSync(targetDirPath) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
674
+ }
675
+ async function replaceProfileDirectory(targetUserDataDir, replacementUserDataDir) {
676
+ if (!existsSync(targetUserDataDir)) {
677
+ await rename(replacementUserDataDir, targetUserDataDir);
678
+ return;
679
+ }
680
+ const backupUserDataDir = buildPersistentProfileBackupDirPath(targetUserDataDir);
681
+ let targetMovedToBackup = false;
682
+ let replacementPublished = false;
683
+ try {
684
+ await rename(targetUserDataDir, backupUserDataDir);
685
+ targetMovedToBackup = true;
686
+ await rename(replacementUserDataDir, targetUserDataDir);
687
+ replacementPublished = true;
688
+ } catch (error) {
689
+ if (targetMovedToBackup && !existsSync(targetUserDataDir)) {
690
+ await rename(backupUserDataDir, targetUserDataDir).catch(() => void 0);
691
+ }
692
+ throw error;
693
+ } finally {
694
+ if (replacementPublished && targetMovedToBackup && existsSync(backupUserDataDir)) {
695
+ await rm(backupUserDataDir, {
696
+ recursive: true,
697
+ force: true
698
+ }).catch(() => void 0);
699
+ }
700
+ }
701
+ }
702
+ async function withPersistentProfileLock(targetUserDataDir, action) {
703
+ const lockDirPath = buildPersistentProfileLockDirPath(targetUserDataDir);
704
+ await mkdir(dirname(lockDirPath), { recursive: true });
705
+ while (true) {
706
+ const tempLockDirPath = `${lockDirPath}-${process.pid}-${PROCESS_STARTED_AT_MS}-${randomUUID()}`;
707
+ try {
708
+ await mkdir(tempLockDirPath);
709
+ await writeLockOwner(tempLockDirPath, CURRENT_PROCESS_LOCK_OWNER);
710
+ try {
711
+ await rename(tempLockDirPath, lockDirPath);
712
+ break;
713
+ } catch (error) {
714
+ if (!wasDirPublishedByAnotherProcess(error, lockDirPath)) {
715
+ throw error;
716
+ }
717
+ }
718
+ } finally {
719
+ await rm(tempLockDirPath, {
720
+ recursive: true,
721
+ force: true
722
+ }).catch(() => void 0);
723
+ }
724
+ const owner = await readLockOwner(lockDirPath);
725
+ if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
726
+ continue;
727
+ }
728
+ await sleep(PROFILE_LOCK_RETRY_DELAY_MS);
729
+ }
730
+ try {
731
+ return await action();
732
+ } finally {
733
+ await rm(lockDirPath, {
734
+ recursive: true,
735
+ force: true
736
+ }).catch(() => void 0);
737
+ }
738
+ }
739
+ async function writeLockOwner(lockDirPath, owner) {
740
+ await writeLockParticipant(join(lockDirPath, LOCK_OWNER_FILE), owner);
741
+ }
742
+ async function readLockOwner(lockDirPath) {
743
+ return await readLockParticipant(join(lockDirPath, LOCK_OWNER_FILE));
744
+ }
745
+ async function writeLockParticipant(filePath, owner, options) {
746
+ await writeFile(filePath, JSON.stringify(owner), options);
747
+ }
748
+ async function readLockParticipant(filePath) {
749
+ return (await readLockParticipantRecord(filePath)).owner;
750
+ }
751
+ async function readLockReclaimerRecord(lockDirPath) {
752
+ return await readLockParticipantRecord(
753
+ join(buildLockReclaimerDirPath(lockDirPath), LOCK_OWNER_FILE)
754
+ );
755
+ }
756
+ async function readLockParticipantRecord(filePath) {
757
+ try {
758
+ const raw = await readFile(filePath, "utf8");
759
+ const parsed = JSON.parse(raw);
760
+ const pid = Number(parsed.pid);
761
+ const processStartedAtMs = Number(parsed.processStartedAtMs);
762
+ if (!Number.isInteger(pid) || !Number.isInteger(processStartedAtMs)) {
763
+ return {
764
+ exists: true,
765
+ owner: null
766
+ };
767
+ }
768
+ return {
769
+ exists: true,
770
+ owner: {
771
+ pid,
772
+ processStartedAtMs
773
+ }
774
+ };
775
+ } catch (error) {
776
+ return {
777
+ exists: getErrorCode(error) !== "ENOENT",
778
+ owner: null
779
+ };
780
+ }
781
+ }
782
+ async function tryReclaimStaleLock(lockDirPath, expectedOwner) {
783
+ if (!await tryAcquireLockReclaimer(lockDirPath)) {
784
+ return false;
785
+ }
786
+ let reclaimed = false;
787
+ try {
788
+ const owner = await readLockOwner(lockDirPath);
789
+ if (!lockOwnersEqual(owner, expectedOwner)) {
790
+ return false;
791
+ }
792
+ if (owner && await getProcessLiveness(owner) !== "dead") {
793
+ return false;
794
+ }
795
+ await rm(lockDirPath, {
796
+ recursive: true,
797
+ force: true
798
+ }).catch(() => void 0);
799
+ reclaimed = !existsSync(lockDirPath);
800
+ return reclaimed;
801
+ } finally {
802
+ if (!reclaimed) {
803
+ await rm(buildLockReclaimerDirPath(lockDirPath), {
804
+ recursive: true,
805
+ force: true
806
+ }).catch(() => void 0);
807
+ }
808
+ }
809
+ }
810
+ async function tryAcquireLockReclaimer(lockDirPath) {
811
+ const reclaimerDirPath = buildLockReclaimerDirPath(lockDirPath);
812
+ while (true) {
813
+ const tempReclaimerDirPath = `${reclaimerDirPath}-${process.pid}-${PROCESS_STARTED_AT_MS}-${randomUUID()}`;
814
+ try {
815
+ await mkdir(tempReclaimerDirPath);
816
+ await writeLockOwner(tempReclaimerDirPath, CURRENT_PROCESS_LOCK_OWNER);
817
+ try {
818
+ await rename(tempReclaimerDirPath, reclaimerDirPath);
819
+ return true;
820
+ } catch (error) {
821
+ if (getErrorCode(error) === "ENOENT") {
822
+ return false;
823
+ }
824
+ if (!wasDirPublishedByAnotherProcess(error, reclaimerDirPath)) {
825
+ throw error;
826
+ }
827
+ }
828
+ } catch (error) {
829
+ const code = getErrorCode(error);
830
+ if (code === "ENOENT") {
831
+ return false;
832
+ }
833
+ throw error;
834
+ } finally {
835
+ await rm(tempReclaimerDirPath, {
836
+ recursive: true,
837
+ force: true
838
+ }).catch(() => void 0);
839
+ }
840
+ const reclaimerRecord = await readLockReclaimerRecord(lockDirPath);
841
+ if (!reclaimerRecord.exists || !reclaimerRecord.owner) {
842
+ return false;
843
+ }
844
+ if (await getProcessLiveness(reclaimerRecord.owner) !== "dead") {
845
+ return false;
846
+ }
847
+ await rm(reclaimerDirPath, {
848
+ recursive: true,
849
+ force: true
850
+ }).catch(() => void 0);
851
+ }
852
+ }
853
+ function lockOwnersEqual(left, right) {
854
+ if (!left || !right) {
855
+ return left === right;
856
+ }
857
+ return left.pid === right.pid && left.processStartedAtMs === right.processStartedAtMs;
858
+ }
859
+ async function getProcessLiveness(owner) {
860
+ if (owner.pid === process.pid && hasMatchingProcessStartTime(
861
+ owner.processStartedAtMs,
862
+ PROCESS_STARTED_AT_MS
863
+ )) {
864
+ return "live";
865
+ }
866
+ const startedAtMs = await readProcessStartedAtMs(owner.pid);
867
+ if (typeof startedAtMs === "number") {
868
+ return hasMatchingProcessStartTime(
869
+ owner.processStartedAtMs,
870
+ startedAtMs
871
+ ) ? "live" : "dead";
872
+ }
873
+ return isProcessRunning(owner.pid) ? "unknown" : "dead";
874
+ }
875
+ function hasMatchingProcessStartTime(expectedStartedAtMs, actualStartedAtMs) {
876
+ return Math.abs(expectedStartedAtMs - actualStartedAtMs) <= PROCESS_START_TIME_TOLERANCE_MS;
227
877
  }
228
878
  function buildPersistentProfileTempDirPrefix(targetUserDataDir) {
229
879
  return join(
230
880
  dirname(targetUserDataDir),
231
- `${basename(targetUserDataDir)}-tmp-${process.pid}-${PROCESS_STARTED_AT_MS}-`
881
+ `${buildPersistentProfileTempDirNamePrefix(targetUserDataDir)}${process.pid}-${PROCESS_STARTED_AT_MS}-`
882
+ );
883
+ }
884
+ function buildPersistentProfileTempDirNamePrefix(targetUserDataDir) {
885
+ return `${basename(targetUserDataDir)}-tmp-`;
886
+ }
887
+ function buildPersistentProfileBackupDirPath(targetUserDataDir) {
888
+ return join(
889
+ dirname(targetUserDataDir),
890
+ `${buildPersistentProfileTempDirNamePrefix(targetUserDataDir)}${process.pid}-${PROCESS_STARTED_AT_MS}-backup-${Date.now()}`
891
+ );
892
+ }
893
+ function buildPersistentProfileLockDirPath(targetUserDataDir) {
894
+ return join(dirname(targetUserDataDir), `${basename(targetUserDataDir)}.lock`);
895
+ }
896
+ function buildLockReclaimerDirPath(lockDirPath) {
897
+ return join(lockDirPath, LOCK_RECLAIMER_DIR);
898
+ }
899
+ function buildRuntimeProfileKey(sourceUserDataDir) {
900
+ const hash = createHash("sha256").update(sourceUserDataDir).digest("hex").slice(0, 16);
901
+ return `${sanitizePathSegment(basename(sourceUserDataDir) || "profile")}-${hash}`;
902
+ }
903
+ function buildRuntimeProfileDirNamePrefix(sourceUserDataDir) {
904
+ return `${buildRuntimeProfileKey(sourceUserDataDir)}-runtime-`;
905
+ }
906
+ function buildRuntimeProfileDirPrefix(runtimesRootDir, sourceUserDataDir) {
907
+ return join(
908
+ runtimesRootDir,
909
+ `${buildRuntimeProfileDirNamePrefix(sourceUserDataDir)}${process.pid}-${PROCESS_STARTED_AT_MS}-`
232
910
  );
233
911
  }
234
- async function cleanOrphanedTempDirs(profilesDir, targetBaseName) {
912
+ async function cleanOrphanedOwnedDirs(rootDir, ownedDirNamePrefix) {
235
913
  let entries;
236
914
  try {
237
- entries = await readdir(profilesDir, {
915
+ entries = await readdir(rootDir, {
238
916
  encoding: "utf8",
239
917
  withFileTypes: true
240
918
  });
241
919
  } catch {
242
920
  return;
243
921
  }
244
- const tempDirPrefix = `${targetBaseName}-tmp-`;
245
922
  await Promise.all(
246
923
  entries.map(async (entry) => {
247
- if (!entry.isDirectory() || !entry.name.startsWith(tempDirPrefix)) {
924
+ if (!entry.isDirectory() || !entry.name.startsWith(ownedDirNamePrefix)) {
248
925
  return;
249
926
  }
250
- if (isTempDirOwnedByLiveProcess(entry.name, tempDirPrefix)) {
927
+ if (await isOwnedDirByLiveProcess(entry.name, ownedDirNamePrefix)) {
251
928
  return;
252
929
  }
253
- await rm(join(profilesDir, entry.name), {
930
+ await rm(join(rootDir, entry.name), {
254
931
  recursive: true,
255
932
  force: true
256
933
  }).catch(() => void 0);
257
934
  })
258
935
  );
259
936
  }
260
- function isTempDirOwnedByLiveProcess(tempDirName, tempDirPrefix) {
261
- const owner = parseTempDirOwner(tempDirName, tempDirPrefix);
262
- if (!owner) {
263
- return false;
264
- }
265
- if (owner.pid === process.pid && Math.abs(owner.processStartedAtMs - PROCESS_STARTED_AT_MS) <= PROCESS_START_TIME_TOLERANCE_MS) {
266
- return true;
267
- }
268
- return isProcessRunning(owner.pid);
937
+ async function isOwnedDirByLiveProcess(ownedDirName, ownedDirPrefix) {
938
+ const owner = parseOwnedDirOwner(ownedDirName, ownedDirPrefix);
939
+ return owner ? await getProcessLiveness(owner) !== "dead" : false;
269
940
  }
270
- function parseTempDirOwner(tempDirName, tempDirPrefix) {
271
- const remainder = tempDirName.slice(tempDirPrefix.length);
941
+ function parseOwnedDirOwner(ownedDirName, ownedDirPrefix) {
942
+ const remainder = ownedDirName.slice(ownedDirPrefix.length);
272
943
  const firstDashIndex = remainder.indexOf("-");
273
944
  const secondDashIndex = firstDashIndex === -1 ? -1 : remainder.indexOf("-", firstDashIndex + 1);
274
945
  if (firstDashIndex === -1 || secondDashIndex === -1) {
@@ -296,6 +967,120 @@ function isProcessRunning(pid) {
296
967
  return code !== "ESRCH";
297
968
  }
298
969
  }
970
+ async function readProcessStartedAtMs(pid) {
971
+ if (pid <= 0) {
972
+ return null;
973
+ }
974
+ if (process.platform === "linux") {
975
+ return await readLinuxProcessStartedAtMs(pid);
976
+ }
977
+ if (process.platform === "win32") {
978
+ return await readWindowsProcessStartedAtMs(pid);
979
+ }
980
+ return await readPsProcessStartedAtMs(pid);
981
+ }
982
+ async function readLinuxProcessStartedAtMs(pid) {
983
+ let statRaw;
984
+ try {
985
+ statRaw = await readFile(`/proc/${pid}/stat`, "utf8");
986
+ } catch (error) {
987
+ return null;
988
+ }
989
+ const startTicks = parseLinuxProcessStartTicks(statRaw);
990
+ if (startTicks === null) {
991
+ return null;
992
+ }
993
+ const [bootTimeMs, clockTicksPerSecond] = await Promise.all([
994
+ readLinuxBootTimeMs(),
995
+ readLinuxClockTicksPerSecond()
996
+ ]);
997
+ if (bootTimeMs === null || clockTicksPerSecond === null) {
998
+ return null;
999
+ }
1000
+ return Math.floor(
1001
+ bootTimeMs + startTicks * 1e3 / clockTicksPerSecond
1002
+ );
1003
+ }
1004
+ function parseLinuxProcessStartTicks(statRaw) {
1005
+ const closingParenIndex = statRaw.lastIndexOf(")");
1006
+ if (closingParenIndex === -1) {
1007
+ return null;
1008
+ }
1009
+ const fields = statRaw.slice(closingParenIndex + 2).trim().split(/\s+/);
1010
+ const startTicks = Number(fields[LINUX_STAT_START_TIME_FIELD_INDEX]);
1011
+ return Number.isFinite(startTicks) && startTicks >= 0 ? startTicks : null;
1012
+ }
1013
+ async function readLinuxBootTimeMs() {
1014
+ try {
1015
+ const statRaw = await readFile("/proc/stat", "utf8");
1016
+ const bootTimeLine = statRaw.split("\n").find((line) => line.startsWith("btime "));
1017
+ if (!bootTimeLine) {
1018
+ return null;
1019
+ }
1020
+ const bootTimeSeconds = Number.parseInt(
1021
+ bootTimeLine.slice("btime ".length),
1022
+ 10
1023
+ );
1024
+ return Number.isFinite(bootTimeSeconds) ? bootTimeSeconds * 1e3 : null;
1025
+ } catch {
1026
+ return null;
1027
+ }
1028
+ }
1029
+ async function readLinuxClockTicksPerSecond() {
1030
+ linuxClockTicksPerSecondPromise ??= execFileAsync(
1031
+ "getconf",
1032
+ ["CLK_TCK"]
1033
+ ).then(
1034
+ ({ stdout }) => {
1035
+ const value = Number.parseInt(stdout.trim(), 10);
1036
+ return Number.isFinite(value) && value > 0 ? value : null;
1037
+ },
1038
+ () => null
1039
+ );
1040
+ return await linuxClockTicksPerSecondPromise;
1041
+ }
1042
+ async function readPsProcessStartedAtMs(pid) {
1043
+ try {
1044
+ const { stdout } = await execFileAsync(
1045
+ "ps",
1046
+ ["-p", String(pid), "-o", "lstart="],
1047
+ { env: PS_COMMAND_ENV }
1048
+ );
1049
+ return parsePsStartedAtMs(stdout);
1050
+ } catch (error) {
1051
+ return null;
1052
+ }
1053
+ }
1054
+ function parsePsStartedAtMs(stdout) {
1055
+ const raw = stdout.trim();
1056
+ if (!raw) {
1057
+ return null;
1058
+ }
1059
+ const startedAtMs = Date.parse(raw);
1060
+ return Number.isNaN(startedAtMs) ? null : startedAtMs;
1061
+ }
1062
+ async function readWindowsProcessStartedAtMs(pid) {
1063
+ const script = [
1064
+ "$process = Get-Process -Id " + String(pid) + " -ErrorAction SilentlyContinue",
1065
+ "if ($null -eq $process) { exit 3 }",
1066
+ '$process.StartTime.ToUniversalTime().ToString("o")'
1067
+ ].join("; ");
1068
+ try {
1069
+ const { stdout } = await execFileAsync(
1070
+ "powershell.exe",
1071
+ ["-NoLogo", "-NoProfile", "-Command", script]
1072
+ );
1073
+ return parsePsStartedAtMs(stdout);
1074
+ } catch (error) {
1075
+ return null;
1076
+ }
1077
+ }
1078
+ function getErrorCode(error) {
1079
+ return typeof error === "object" && error !== null && "code" in error && (typeof error.code === "string" || typeof error.code === "number") ? error.code : void 0;
1080
+ }
1081
+ async function sleep(ms) {
1082
+ await new Promise((resolve) => setTimeout(resolve, ms));
1083
+ }
299
1084
 
300
1085
  // src/browser/pool.ts
301
1086
  import { spawn } from "child_process";
@@ -724,14 +1509,13 @@ var BrowserPool = class {
724
1509
  browser = null;
725
1510
  cdpProxy = null;
726
1511
  launchedProcess = null;
727
- managedUserDataDir = null;
728
- persistentProfile = false;
1512
+ managedRuntimeProfile = null;
729
1513
  defaults;
730
1514
  constructor(defaults = {}) {
731
1515
  this.defaults = defaults;
732
1516
  }
733
1517
  async launch(options = {}) {
734
- if (this.browser || this.cdpProxy || this.launchedProcess || this.managedUserDataDir) {
1518
+ if (this.browser || this.cdpProxy || this.launchedProcess || this.managedRuntimeProfile) {
735
1519
  await this.close();
736
1520
  }
737
1521
  const mode = options.mode ?? this.defaults.mode ?? "chromium";
@@ -781,13 +1565,10 @@ var BrowserPool = class {
781
1565
  const browser = this.browser;
782
1566
  const cdpProxy = this.cdpProxy;
783
1567
  const launchedProcess = this.launchedProcess;
784
- const managedUserDataDir = this.managedUserDataDir;
785
- const persistentProfile = this.persistentProfile;
1568
+ const managedRuntimeProfile = this.managedRuntimeProfile;
786
1569
  this.browser = null;
787
1570
  this.cdpProxy = null;
788
1571
  this.launchedProcess = null;
789
- this.managedUserDataDir = null;
790
- this.persistentProfile = false;
791
1572
  try {
792
1573
  if (browser) {
793
1574
  await browser.close().catch(() => void 0);
@@ -795,11 +1576,14 @@ var BrowserPool = class {
795
1576
  } finally {
796
1577
  cdpProxy?.close();
797
1578
  await killProcessTree(launchedProcess);
798
- if (managedUserDataDir && !persistentProfile) {
799
- await rm2(managedUserDataDir, {
800
- recursive: true,
801
- force: true
802
- }).catch(() => void 0);
1579
+ if (managedRuntimeProfile) {
1580
+ await persistIsolatedRuntimeProfile(
1581
+ managedRuntimeProfile.userDataDir,
1582
+ managedRuntimeProfile.persistentUserDataDir
1583
+ );
1584
+ if (this.managedRuntimeProfile === managedRuntimeProfile) {
1585
+ this.managedRuntimeProfile = null;
1586
+ }
803
1587
  }
804
1588
  }
805
1589
  }
@@ -849,7 +1633,9 @@ var BrowserPool = class {
849
1633
  sourceUserDataDir,
850
1634
  profileDirectory
851
1635
  );
852
- await clearPersistentProfileSingletons(persistentProfile.userDataDir);
1636
+ const runtimeProfile = await createIsolatedRuntimeProfile(
1637
+ persistentProfile.userDataDir
1638
+ );
853
1639
  const debugPort = await reserveDebugPort();
854
1640
  const headless = resolveLaunchHeadless(
855
1641
  "real",
@@ -857,7 +1643,7 @@ var BrowserPool = class {
857
1643
  this.defaults.headless
858
1644
  );
859
1645
  const launchArgs = buildRealBrowserLaunchArgs({
860
- userDataDir: persistentProfile.userDataDir,
1646
+ userDataDir: runtimeProfile.userDataDir,
861
1647
  profileDirectory,
862
1648
  debugPort,
863
1649
  headless
@@ -887,12 +1673,15 @@ var BrowserPool = class {
887
1673
  }
888
1674
  this.browser = browser;
889
1675
  this.launchedProcess = processHandle;
890
- this.managedUserDataDir = persistentProfile.userDataDir;
891
- this.persistentProfile = true;
1676
+ this.managedRuntimeProfile = runtimeProfile;
892
1677
  return { browser, context, page, isExternal: false };
893
1678
  } catch (error) {
894
1679
  await browser?.close().catch(() => void 0);
895
1680
  await killProcessTree(processHandle);
1681
+ await rm2(runtimeProfile.userDataDir, {
1682
+ recursive: true,
1683
+ force: true
1684
+ }).catch(() => void 0);
896
1685
  throw error;
897
1686
  }
898
1687
  }
@@ -1021,7 +1810,7 @@ async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
1021
1810
  } catch (error) {
1022
1811
  lastError = error instanceof Error ? error.message : "Unknown error";
1023
1812
  }
1024
- await sleep(100);
1813
+ await sleep2(100);
1025
1814
  }
1026
1815
  throw new Error(
1027
1816
  `Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
@@ -1088,7 +1877,7 @@ async function killProcessTree(processHandle) {
1088
1877
  }
1089
1878
  }
1090
1879
  }
1091
- async function sleep(ms) {
1880
+ async function sleep2(ms) {
1092
1881
  await new Promise((resolve) => setTimeout(resolve, ms));
1093
1882
  }
1094
1883
 
@@ -1470,7 +2259,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
1470
2259
  TRANSIENT_CONTEXT_RETRY_DELAY_MS,
1471
2260
  Math.max(0, deadline - Date.now())
1472
2261
  );
1473
- await sleep2(retryDelay);
2262
+ await sleep3(retryDelay);
1474
2263
  }
1475
2264
  }
1476
2265
  }
@@ -1503,7 +2292,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
1503
2292
  () => ({ kind: "resolved" }),
1504
2293
  (error) => ({ kind: "rejected", error })
1505
2294
  );
1506
- const timeoutPromise = sleep2(
2295
+ const timeoutPromise = sleep3(
1507
2296
  timeout + FRAME_EVALUATE_GRACE_MS
1508
2297
  ).then(() => ({ kind: "timeout" }));
1509
2298
  const result = await Promise.race([
@@ -1645,7 +2434,7 @@ function isIgnorableFrameError(error) {
1645
2434
  const message = error.message;
1646
2435
  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");
1647
2436
  }
1648
- function sleep2(ms) {
2437
+ function sleep3(ms) {
1649
2438
  return new Promise((resolve) => {
1650
2439
  setTimeout(resolve, ms);
1651
2440
  });
@@ -5897,7 +6686,7 @@ async function closeTab(context, activePage, index) {
5897
6686
  }
5898
6687
 
5899
6688
  // src/actions/cookies.ts
5900
- import { readFile, writeFile as writeFile2 } from "fs/promises";
6689
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
5901
6690
  async function getCookies(context, url) {
5902
6691
  return context.cookies(url ? [url] : void 0);
5903
6692
  }
@@ -5912,7 +6701,7 @@ async function exportCookies(context, filePath, url) {
5912
6701
  await writeFile2(filePath, JSON.stringify(cookies, null, 2), "utf-8");
5913
6702
  }
5914
6703
  async function importCookies(context, filePath) {
5915
- const raw = await readFile(filePath, "utf-8");
6704
+ const raw = await readFile2(filePath, "utf-8");
5916
6705
  const cookies = JSON.parse(raw);
5917
6706
  await context.addCookies(cookies);
5918
6707
  }
@@ -7740,7 +8529,7 @@ async function executeAgentAction(page, action) {
7740
8529
  }
7741
8530
  case "wait": {
7742
8531
  const ms = numberOr(action.timeMs, action.time_ms, 1e3);
7743
- await sleep3(ms);
8532
+ await sleep4(ms);
7744
8533
  return;
7745
8534
  }
7746
8535
  case "goto": {
@@ -7905,7 +8694,7 @@ async function pressKeyCombo(page, combo) {
7905
8694
  }
7906
8695
  }
7907
8696
  }
7908
- function sleep3(ms) {
8697
+ function sleep4(ms) {
7909
8698
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
7910
8699
  }
7911
8700
 
@@ -7936,7 +8725,7 @@ var OpensteerCuaAgentHandler = class {
7936
8725
  if (isMutatingAgentAction(action)) {
7937
8726
  this.onMutatingAction?.(action);
7938
8727
  }
7939
- await sleep4(this.config.waitBetweenActionsMs);
8728
+ await sleep5(this.config.waitBetweenActionsMs);
7940
8729
  });
7941
8730
  try {
7942
8731
  const result = await this.client.execute({
@@ -7998,7 +8787,7 @@ var OpensteerCuaAgentHandler = class {
7998
8787
  await this.cursorController.preview({ x, y }, "agent");
7999
8788
  }
8000
8789
  };
8001
- function sleep4(ms) {
8790
+ function sleep5(ms) {
8002
8791
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
8003
8792
  }
8004
8793
 
@@ -8434,7 +9223,7 @@ var CursorController = class {
8434
9223
  for (const step of motion.points) {
8435
9224
  await this.renderer.move(step, this.style);
8436
9225
  if (motion.stepDelayMs > 0) {
8437
- await sleep5(motion.stepDelayMs);
9226
+ await sleep6(motion.stepDelayMs);
8438
9227
  }
8439
9228
  }
8440
9229
  if (shouldPulse(intent)) {
@@ -8592,12 +9381,12 @@ function clamp2(value, min, max) {
8592
9381
  function shouldPulse(intent) {
8593
9382
  return intent === "click" || intent === "dblclick" || intent === "rightclick" || intent === "agent";
8594
9383
  }
8595
- function sleep5(ms) {
9384
+ function sleep6(ms) {
8596
9385
  return new Promise((resolve) => setTimeout(resolve, ms));
8597
9386
  }
8598
9387
 
8599
9388
  // src/opensteer.ts
8600
- import { createHash as createHash2, randomUUID } from "crypto";
9389
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
8601
9390
 
8602
9391
  // src/action-wait.ts
8603
9392
  var ROBUST_PROFILE = {
@@ -8792,7 +9581,7 @@ var AdaptiveNetworkTracker = class {
8792
9581
  this.idleSince = 0;
8793
9582
  }
8794
9583
  const remaining = Math.max(1, options.deadline - now);
8795
- await sleep6(Math.min(NETWORK_POLL_MS, remaining));
9584
+ await sleep7(Math.min(NETWORK_POLL_MS, remaining));
8796
9585
  }
8797
9586
  }
8798
9587
  handleRequestStarted = (request) => {
@@ -8837,7 +9626,7 @@ var AdaptiveNetworkTracker = class {
8837
9626
  return false;
8838
9627
  }
8839
9628
  };
8840
- async function sleep6(ms) {
9629
+ async function sleep7(ms) {
8841
9630
  await new Promise((resolve) => {
8842
9631
  setTimeout(resolve, ms);
8843
9632
  });
@@ -10617,15 +11406,18 @@ var Opensteer = class _Opensteer {
10617
11406
  }
10618
11407
  return;
10619
11408
  }
10620
- if (this.ownsBrowser) {
10621
- await this.pool.close();
10622
- }
10623
- this.browser = null;
10624
- this.pageRef = null;
10625
- this.contextRef = null;
10626
- this.ownsBrowser = false;
10627
- if (this.cursorController) {
10628
- await this.cursorController.dispose().catch(() => void 0);
11409
+ try {
11410
+ if (this.ownsBrowser) {
11411
+ await this.pool.close();
11412
+ }
11413
+ } finally {
11414
+ this.browser = null;
11415
+ this.pageRef = null;
11416
+ this.contextRef = null;
11417
+ this.ownsBrowser = false;
11418
+ if (this.cursorController) {
11419
+ await this.cursorController.dispose().catch(() => void 0);
11420
+ }
10629
11421
  }
10630
11422
  }
10631
11423
  async syncLocalSelectorCacheToCloud() {
@@ -12949,12 +13741,14 @@ function normalizeCloudBrowserProfilePreference(value, source) {
12949
13741
  }
12950
13742
  function buildLocalRunId(namespace) {
12951
13743
  const normalized = namespace.trim() || "default";
12952
- return `${normalized}-${Date.now().toString(36)}-${randomUUID().slice(0, 8)}`;
13744
+ return `${normalized}-${Date.now().toString(36)}-${randomUUID2().slice(0, 8)}`;
12953
13745
  }
12954
13746
 
12955
13747
  export {
12956
13748
  getOrCreatePersistentProfile,
12957
13749
  clearPersistentProfileSingletons,
13750
+ createIsolatedRuntimeProfile,
13751
+ persistIsolatedRuntimeProfile,
12958
13752
  BrowserPool,
12959
13753
  waitForVisualStability,
12960
13754
  createEmptyRegistry,