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.
@@ -424,8 +424,8 @@ __export(profile_exports, {
424
424
  runOpensteerProfileCli: () => runOpensteerProfileCli
425
425
  });
426
426
  module.exports = __toCommonJS(profile_exports);
427
- var import_node_path4 = __toESM(require("path"), 1);
428
- var import_promises5 = require("readline/promises");
427
+ var import_node_path8 = __toESM(require("path"), 1);
428
+ var import_promises8 = require("readline/promises");
429
429
 
430
430
  // src/config.ts
431
431
  var import_fs = __toESM(require("fs"), 1);
@@ -732,9 +732,9 @@ function resolveNamespaceDir(rootDir, namespace) {
732
732
  const selectorsRoot = import_path.default.resolve(rootDir, ".opensteer", "selectors");
733
733
  const normalizedNamespace = normalizeNamespace(namespace);
734
734
  const namespaceDir = import_path.default.resolve(selectorsRoot, normalizedNamespace);
735
- const relative = import_path.default.relative(selectorsRoot, namespaceDir);
736
- if (relative === "" || relative === ".") return namespaceDir;
737
- if (relative.startsWith("..") || import_path.default.isAbsolute(relative)) {
735
+ const relative2 = import_path.default.relative(selectorsRoot, namespaceDir);
736
+ if (relative2 === "" || relative2 === ".") return namespaceDir;
737
+ if (relative2.startsWith("..") || import_path.default.isAbsolute(relative2)) {
738
738
  throw new Error(
739
739
  `Namespace "${namespace}" resolves outside selectors root.`
740
740
  );
@@ -1291,8 +1291,8 @@ function resolveNamespace(config, rootDir) {
1291
1291
  }
1292
1292
  const caller = getCallerFilePath();
1293
1293
  if (!caller) return normalizeNamespace("default");
1294
- const relative = import_path2.default.relative(rootDir, caller);
1295
- const cleaned = relative.replace(/\\/g, "/").replace(/\.(ts|tsx|js|mjs|cjs)$/, "");
1294
+ const relative2 = import_path2.default.relative(rootDir, caller);
1295
+ const cleaned = relative2.replace(/\\/g, "/").replace(/\.(ts|tsx|js|mjs|cjs)$/, "");
1296
1296
  return normalizeNamespace(cleaned || "default");
1297
1297
  }
1298
1298
  function getCallerFilePath() {
@@ -1323,10 +1323,7 @@ function getCallerFilePath() {
1323
1323
  var import_crypto = require("crypto");
1324
1324
 
1325
1325
  // src/browser/pool.ts
1326
- var import_node_child_process = require("child_process");
1327
- var import_promises2 = require("fs/promises");
1328
- var import_node_net = require("net");
1329
- var import_playwright = require("playwright");
1326
+ var import_playwright2 = require("playwright");
1330
1327
 
1331
1328
  // src/browser/cdp-proxy.ts
1332
1329
  var import_ws = __toESM(require("ws"), 1);
@@ -1819,281 +1816,1892 @@ function listLocalChromeProfiles(userDataDir = detectChromePaths().defaultUserDa
1819
1816
  }
1820
1817
 
1821
1818
  // src/browser/persistent-profile.ts
1819
+ var import_node_crypto3 = require("crypto");
1820
+ var import_node_child_process2 = require("child_process");
1821
+ var import_node_fs3 = require("fs");
1822
+ var import_promises4 = require("fs/promises");
1823
+ var import_node_os = require("os");
1824
+ var import_node_path4 = require("path");
1825
+ var import_node_util2 = require("util");
1826
+
1827
+ // src/browser/persistent-profile-coordination.ts
1828
+ var import_node_path2 = require("path");
1829
+
1830
+ // src/browser/dir-lock.ts
1822
1831
  var import_node_crypto = require("crypto");
1823
1832
  var import_node_fs = require("fs");
1824
- var import_promises = require("fs/promises");
1825
- var import_node_os = require("os");
1833
+ var import_promises2 = require("fs/promises");
1826
1834
  var import_node_path = require("path");
1827
- var OPENSTEER_META_FILE = ".opensteer-meta.json";
1835
+
1836
+ // src/browser/process-owner.ts
1837
+ var import_node_child_process = require("child_process");
1838
+ var import_promises = require("fs/promises");
1839
+ var import_node_util = require("util");
1840
+ var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
1828
1841
  var PROCESS_STARTED_AT_MS = Math.floor(Date.now() - process.uptime() * 1e3);
1829
1842
  var PROCESS_START_TIME_TOLERANCE_MS = 1e3;
1830
- var CHROME_SINGLETON_ENTRIES = /* @__PURE__ */ new Set([
1831
- "SingletonCookie",
1832
- "SingletonLock",
1833
- "SingletonSocket",
1834
- "DevToolsActivePort",
1835
- "lockfile"
1836
- ]);
1837
- var COPY_SKIP_ENTRIES = /* @__PURE__ */ new Set([
1838
- ...CHROME_SINGLETON_ENTRIES,
1839
- OPENSTEER_META_FILE
1840
- ]);
1841
- var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
1842
- "Crash Reports",
1843
- "Crashpad",
1844
- "BrowserMetrics",
1845
- "GrShaderCache",
1846
- "ShaderCache",
1847
- "GraphiteDawnCache",
1848
- "component_crx_cache",
1849
- "Crowd Deny",
1850
- "hyphen-data",
1851
- "OnDeviceHeadSuggestModel",
1852
- "OptimizationGuidePredictionModels",
1853
- "Segmentation Platform",
1854
- "SmartCardDeviceNames",
1855
- "WidevineCdm",
1856
- "pnacl"
1857
- ]);
1858
- async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory, profilesRootDir = defaultPersistentProfilesRootDir()) {
1859
- const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
1860
- const targetUserDataDir = (0, import_node_path.join)(
1861
- expandHome(profilesRootDir),
1862
- buildPersistentProfileKey(resolvedSourceUserDataDir, profileDirectory)
1863
- );
1864
- const sourceProfileDir = (0, import_node_path.join)(resolvedSourceUserDataDir, profileDirectory);
1865
- const metadata = buildPersistentProfileMetadata(
1866
- resolvedSourceUserDataDir,
1867
- profileDirectory
1868
- );
1869
- await (0, import_promises.mkdir)((0, import_node_path.dirname)(targetUserDataDir), { recursive: true });
1870
- await cleanOrphanedTempDirs(
1871
- (0, import_node_path.dirname)(targetUserDataDir),
1872
- (0, import_node_path.basename)(targetUserDataDir)
1873
- );
1874
- if (!(0, import_node_fs.existsSync)(sourceProfileDir)) {
1875
- throw new Error(
1876
- `Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
1877
- );
1843
+ var PROCESS_LIST_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
1844
+ var PS_COMMAND_ENV = { ...process.env, LC_ALL: "C" };
1845
+ var LINUX_STAT_START_TIME_FIELD_INDEX = 19;
1846
+ var CURRENT_PROCESS_OWNER = {
1847
+ pid: process.pid,
1848
+ processStartedAtMs: PROCESS_STARTED_AT_MS
1849
+ };
1850
+ var linuxClockTicksPerSecondPromise = null;
1851
+ function parseProcessOwner(value) {
1852
+ if (!value || typeof value !== "object") {
1853
+ return null;
1854
+ }
1855
+ const parsed = value;
1856
+ const pid = Number(parsed.pid);
1857
+ const processStartedAtMs = Number(parsed.processStartedAtMs);
1858
+ if (!Number.isInteger(pid) || pid <= 0) {
1859
+ return null;
1860
+ }
1861
+ if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
1862
+ return null;
1878
1863
  }
1879
- const created = await createPersistentProfileClone(
1880
- resolvedSourceUserDataDir,
1881
- sourceProfileDir,
1882
- targetUserDataDir,
1883
- profileDirectory,
1884
- metadata
1885
- );
1886
- await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
1887
1864
  return {
1888
- created,
1889
- userDataDir: targetUserDataDir
1865
+ pid,
1866
+ processStartedAtMs
1890
1867
  };
1891
1868
  }
1892
- async function clearPersistentProfileSingletons(userDataDir) {
1893
- await Promise.all(
1894
- [...CHROME_SINGLETON_ENTRIES].map(
1895
- (entry) => (0, import_promises.rm)((0, import_node_path.join)(userDataDir, entry), {
1896
- force: true,
1897
- recursive: true
1898
- }).catch(() => void 0)
1899
- )
1900
- );
1869
+ function processOwnersEqual(left, right) {
1870
+ if (!left || !right) {
1871
+ return left === right;
1872
+ }
1873
+ return left.pid === right.pid && left.processStartedAtMs === right.processStartedAtMs;
1901
1874
  }
1902
- function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
1903
- const hash = (0, import_node_crypto.createHash)("sha256").update(`${sourceUserDataDir}\0${profileDirectory}`).digest("hex").slice(0, 16);
1904
- const sourceLabel = sanitizePathSegment((0, import_node_path.basename)(sourceUserDataDir) || "user-data");
1905
- const profileLabel = sanitizePathSegment(profileDirectory || "Default");
1906
- return `${sourceLabel}-${profileLabel}-${hash}`;
1875
+ async function getProcessLiveness(owner) {
1876
+ if (owner.pid === process.pid && hasMatchingProcessStartTime(
1877
+ owner.processStartedAtMs,
1878
+ PROCESS_STARTED_AT_MS
1879
+ )) {
1880
+ return "live";
1881
+ }
1882
+ const startedAtMs = await readProcessStartedAtMs(owner.pid);
1883
+ if (typeof startedAtMs === "number") {
1884
+ return hasMatchingProcessStartTime(
1885
+ owner.processStartedAtMs,
1886
+ startedAtMs
1887
+ ) ? "live" : "dead";
1888
+ }
1889
+ return isProcessRunning(owner.pid) ? "unknown" : "dead";
1907
1890
  }
1908
- function defaultPersistentProfilesRootDir() {
1909
- return (0, import_node_path.join)((0, import_node_os.homedir)(), ".opensteer", "real-browser-profiles");
1891
+ function isProcessRunning(pid) {
1892
+ try {
1893
+ process.kill(pid, 0);
1894
+ return true;
1895
+ } catch (error) {
1896
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
1897
+ return code !== "ESRCH";
1898
+ }
1910
1899
  }
1911
- function sanitizePathSegment(value) {
1912
- const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-");
1913
- return sanitized.replace(/^-|-$/g, "") || "profile";
1900
+ async function readProcessOwner(pid) {
1901
+ const processStartedAtMs = await readProcessStartedAtMs(pid);
1902
+ if (processStartedAtMs === null) {
1903
+ return null;
1904
+ }
1905
+ return {
1906
+ pid,
1907
+ processStartedAtMs
1908
+ };
1914
1909
  }
1915
- function isProfileDirectory(userDataDir, entry) {
1916
- return (0, import_node_fs.existsSync)((0, import_node_path.join)(userDataDir, entry, "Preferences"));
1910
+ function hasMatchingProcessStartTime(expectedStartedAtMs, actualStartedAtMs) {
1911
+ return Math.abs(expectedStartedAtMs - actualStartedAtMs) <= PROCESS_START_TIME_TOLERANCE_MS;
1917
1912
  }
1918
- async function copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, targetProfileDirectory) {
1919
- let entries;
1913
+ async function readProcessStartedAtMs(pid) {
1914
+ if (pid <= 0) {
1915
+ return null;
1916
+ }
1917
+ if (process.platform === "linux") {
1918
+ return await readLinuxProcessStartedAtMs(pid);
1919
+ }
1920
+ if (process.platform === "win32") {
1921
+ return await readWindowsProcessStartedAtMs(pid);
1922
+ }
1923
+ return await readPsProcessStartedAtMs(pid);
1924
+ }
1925
+ async function readLinuxProcessStartedAtMs(pid) {
1926
+ let statRaw;
1920
1927
  try {
1921
- entries = await (0, import_promises.readdir)(sourceUserDataDir);
1928
+ statRaw = await (0, import_promises.readFile)(`/proc/${pid}/stat`, "utf8");
1922
1929
  } catch {
1923
- return;
1930
+ return null;
1924
1931
  }
1925
- const copyTasks = [];
1926
- for (const entry of entries) {
1927
- if (COPY_SKIP_ENTRIES.has(entry)) continue;
1928
- if (entry === targetProfileDirectory) continue;
1929
- const sourcePath = (0, import_node_path.join)(sourceUserDataDir, entry);
1930
- const targetPath = (0, import_node_path.join)(targetUserDataDir, entry);
1931
- if ((0, import_node_fs.existsSync)(targetPath)) continue;
1932
- let entryStat;
1933
- try {
1934
- entryStat = await (0, import_promises.stat)(sourcePath);
1935
- } catch {
1936
- continue;
1937
- }
1938
- if (entryStat.isFile()) {
1939
- copyTasks.push((0, import_promises.copyFile)(sourcePath, targetPath).catch(() => void 0));
1940
- } else if (entryStat.isDirectory()) {
1941
- if (isProfileDirectory(sourceUserDataDir, entry)) continue;
1942
- if (SKIPPED_ROOT_DIRECTORIES.has(entry)) continue;
1943
- copyTasks.push(
1944
- (0, import_promises.cp)(sourcePath, targetPath, { recursive: true }).catch(
1945
- () => void 0
1946
- )
1947
- );
1948
- }
1932
+ const startTicks = parseLinuxProcessStartTicks(statRaw);
1933
+ if (startTicks === null) {
1934
+ return null;
1949
1935
  }
1950
- await Promise.all(copyTasks);
1951
- }
1952
- async function writePersistentProfileMetadata(userDataDir, metadata) {
1953
- await (0, import_promises.writeFile)(
1954
- (0, import_node_path.join)(userDataDir, OPENSTEER_META_FILE),
1955
- JSON.stringify(metadata, null, 2)
1936
+ const [bootTimeMs, clockTicksPerSecond] = await Promise.all([
1937
+ readLinuxBootTimeMs(),
1938
+ readLinuxClockTicksPerSecond()
1939
+ ]);
1940
+ if (bootTimeMs === null || clockTicksPerSecond === null) {
1941
+ return null;
1942
+ }
1943
+ return Math.floor(
1944
+ bootTimeMs + startTicks * 1e3 / clockTicksPerSecond
1956
1945
  );
1957
1946
  }
1958
- function buildPersistentProfileMetadata(sourceUserDataDir, profileDirectory) {
1959
- return {
1960
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1961
- profileDirectory,
1962
- source: sourceUserDataDir
1963
- };
1947
+ function parseLinuxProcessStartTicks(statRaw) {
1948
+ const closingParenIndex = statRaw.lastIndexOf(")");
1949
+ if (closingParenIndex === -1) {
1950
+ return null;
1951
+ }
1952
+ const fields = statRaw.slice(closingParenIndex + 2).trim().split(/\s+/);
1953
+ const startTicks = Number(fields[LINUX_STAT_START_TIME_FIELD_INDEX]);
1954
+ return Number.isFinite(startTicks) && startTicks >= 0 ? startTicks : null;
1964
1955
  }
1965
- async function createPersistentProfileClone(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
1966
- if ((0, import_node_fs.existsSync)(targetUserDataDir)) {
1967
- return false;
1956
+ async function readLinuxBootTimeMs() {
1957
+ try {
1958
+ const statRaw = await (0, import_promises.readFile)("/proc/stat", "utf8");
1959
+ const bootTimeLine = statRaw.split("\n").find((line) => line.startsWith("btime "));
1960
+ if (!bootTimeLine) {
1961
+ return null;
1962
+ }
1963
+ const bootTimeSeconds = Number.parseInt(
1964
+ bootTimeLine.slice("btime ".length),
1965
+ 10
1966
+ );
1967
+ return Number.isFinite(bootTimeSeconds) ? bootTimeSeconds * 1e3 : null;
1968
+ } catch {
1969
+ return null;
1968
1970
  }
1969
- const tempUserDataDir = await (0, import_promises.mkdtemp)(
1970
- buildPersistentProfileTempDirPrefix(targetUserDataDir)
1971
- );
1972
- let published = false;
1971
+ }
1972
+ async function readLinuxClockTicksPerSecond() {
1973
+ if (!linuxClockTicksPerSecondPromise) {
1974
+ linuxClockTicksPerSecondPromise = execFileAsync("getconf", ["CLK_TCK"], {
1975
+ encoding: "utf8",
1976
+ maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
1977
+ }).then(({ stdout }) => {
1978
+ const value = Number.parseInt(stdout.trim(), 10);
1979
+ return Number.isFinite(value) && value > 0 ? value : null;
1980
+ }).catch(() => null);
1981
+ }
1982
+ return await linuxClockTicksPerSecondPromise;
1983
+ }
1984
+ async function readWindowsProcessStartedAtMs(pid) {
1973
1985
  try {
1974
- await (0, import_promises.cp)(sourceProfileDir, (0, import_node_path.join)(tempUserDataDir, profileDirectory), {
1975
- recursive: true
1976
- });
1977
- await copyRootLevelEntries(
1978
- sourceUserDataDir,
1979
- tempUserDataDir,
1980
- profileDirectory
1986
+ const { stdout } = await execFileAsync(
1987
+ "powershell.exe",
1988
+ [
1989
+ "-NoProfile",
1990
+ "-Command",
1991
+ `(Get-Process -Id ${pid}).StartTime.ToUniversalTime().ToString("o")`
1992
+ ],
1993
+ {
1994
+ encoding: "utf8",
1995
+ maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
1996
+ }
1981
1997
  );
1982
- await writePersistentProfileMetadata(tempUserDataDir, metadata);
1983
- try {
1984
- await (0, import_promises.rename)(tempUserDataDir, targetUserDataDir);
1985
- } catch (error) {
1986
- if (wasProfilePublishedByAnotherProcess(error, targetUserDataDir)) {
1987
- return false;
1998
+ const isoTimestamp = stdout.trim();
1999
+ if (!isoTimestamp) {
2000
+ return null;
2001
+ }
2002
+ const startedAtMs = Date.parse(isoTimestamp);
2003
+ return Number.isFinite(startedAtMs) ? startedAtMs : null;
2004
+ } catch {
2005
+ return null;
2006
+ }
2007
+ }
2008
+ async function readPsProcessStartedAtMs(pid) {
2009
+ try {
2010
+ const { stdout } = await execFileAsync(
2011
+ "ps",
2012
+ ["-o", "lstart=", "-p", String(pid)],
2013
+ {
2014
+ encoding: "utf8",
2015
+ env: PS_COMMAND_ENV,
2016
+ maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
1988
2017
  }
1989
- throw error;
2018
+ );
2019
+ const startedAt = stdout.trim();
2020
+ if (!startedAt) {
2021
+ return null;
1990
2022
  }
1991
- published = true;
1992
- return true;
2023
+ const startedAtMs = Date.parse(startedAt.replace(/\s+/g, " "));
2024
+ return Number.isFinite(startedAtMs) ? startedAtMs : null;
2025
+ } catch {
2026
+ return null;
2027
+ }
2028
+ }
2029
+
2030
+ // src/browser/dir-lock.ts
2031
+ var LOCK_OWNER_FILE = "owner.json";
2032
+ var LOCK_RECLAIMER_DIR = "reclaimer";
2033
+ var LOCK_RETRY_DELAY_MS = 50;
2034
+ async function withDirLock(lockDirPath, action) {
2035
+ const releaseLock = await acquireDirLock(lockDirPath);
2036
+ try {
2037
+ return await action();
1993
2038
  } finally {
1994
- if (!published) {
1995
- await (0, import_promises.rm)(tempUserDataDir, {
2039
+ await releaseLock();
2040
+ }
2041
+ }
2042
+ async function acquireDirLock(lockDirPath) {
2043
+ while (true) {
2044
+ const releaseLock = await tryAcquireDirLock(lockDirPath);
2045
+ if (releaseLock) {
2046
+ return releaseLock;
2047
+ }
2048
+ await sleep(LOCK_RETRY_DELAY_MS);
2049
+ }
2050
+ }
2051
+ async function tryAcquireDirLock(lockDirPath) {
2052
+ await (0, import_promises2.mkdir)((0, import_node_path.dirname)(lockDirPath), { recursive: true });
2053
+ while (true) {
2054
+ const tempLockDirPath = `${lockDirPath}-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto.randomUUID)()}`;
2055
+ try {
2056
+ await (0, import_promises2.mkdir)(tempLockDirPath);
2057
+ await writeLockOwner(tempLockDirPath, CURRENT_PROCESS_OWNER);
2058
+ try {
2059
+ await (0, import_promises2.rename)(tempLockDirPath, lockDirPath);
2060
+ break;
2061
+ } catch (error) {
2062
+ if (!wasDirPublishedByAnotherProcess(error, lockDirPath)) {
2063
+ throw error;
2064
+ }
2065
+ }
2066
+ } finally {
2067
+ await (0, import_promises2.rm)(tempLockDirPath, {
1996
2068
  recursive: true,
1997
2069
  force: true
1998
2070
  }).catch(() => void 0);
1999
2071
  }
2072
+ const owner = await readLockOwner(lockDirPath);
2073
+ if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
2074
+ continue;
2075
+ }
2076
+ return null;
2000
2077
  }
2078
+ return async () => {
2079
+ await (0, import_promises2.rm)(lockDirPath, {
2080
+ recursive: true,
2081
+ force: true
2082
+ }).catch(() => void 0);
2083
+ };
2001
2084
  }
2002
- async function ensurePersistentProfileMetadata(userDataDir, metadata) {
2003
- if ((0, import_node_fs.existsSync)((0, import_node_path.join)(userDataDir, OPENSTEER_META_FILE))) {
2004
- return;
2085
+ async function isDirLockHeld(lockDirPath) {
2086
+ if (!(0, import_node_fs.existsSync)(lockDirPath)) {
2087
+ return false;
2005
2088
  }
2006
- await writePersistentProfileMetadata(userDataDir, metadata);
2089
+ const owner = await readLockOwner(lockDirPath);
2090
+ if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
2091
+ return false;
2092
+ }
2093
+ return (0, import_node_fs.existsSync)(lockDirPath);
2007
2094
  }
2008
- function wasProfilePublishedByAnotherProcess(error, targetUserDataDir) {
2009
- const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
2010
- return (0, import_node_fs.existsSync)(targetUserDataDir) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
2095
+ function getErrorCode(error) {
2096
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
2011
2097
  }
2012
- function buildPersistentProfileTempDirPrefix(targetUserDataDir) {
2013
- return (0, import_node_path.join)(
2014
- (0, import_node_path.dirname)(targetUserDataDir),
2015
- `${(0, import_node_path.basename)(targetUserDataDir)}-tmp-${process.pid}-${PROCESS_STARTED_AT_MS}-`
2016
- );
2098
+ function wasDirPublishedByAnotherProcess(error, targetDirPath) {
2099
+ const code = getErrorCode(error);
2100
+ return (0, import_node_fs.existsSync)(targetDirPath) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
2017
2101
  }
2018
- async function cleanOrphanedTempDirs(profilesDir, targetBaseName) {
2019
- let entries;
2102
+ async function writeLockOwner(lockDirPath, owner) {
2103
+ await (0, import_promises2.writeFile)((0, import_node_path.join)(lockDirPath, LOCK_OWNER_FILE), JSON.stringify(owner));
2104
+ }
2105
+ async function readLockOwner(lockDirPath) {
2106
+ return await readLockParticipant((0, import_node_path.join)(lockDirPath, LOCK_OWNER_FILE));
2107
+ }
2108
+ async function readLockParticipant(filePath) {
2109
+ return (await readLockParticipantRecord(filePath)).owner;
2110
+ }
2111
+ async function readLockParticipantRecord(filePath) {
2020
2112
  try {
2021
- entries = await (0, import_promises.readdir)(profilesDir, {
2022
- encoding: "utf8",
2023
- withFileTypes: true
2024
- });
2025
- } catch {
2026
- return;
2113
+ const raw = await (0, import_promises2.readFile)(filePath, "utf8");
2114
+ const owner = parseProcessOwner(JSON.parse(raw));
2115
+ return {
2116
+ exists: true,
2117
+ owner
2118
+ };
2119
+ } catch (error) {
2120
+ return {
2121
+ exists: getErrorCode(error) !== "ENOENT",
2122
+ owner: null
2123
+ };
2027
2124
  }
2028
- const tempDirPrefix = `${targetBaseName}-tmp-`;
2029
- await Promise.all(
2030
- entries.map(async (entry) => {
2031
- if (!entry.isDirectory() || !entry.name.startsWith(tempDirPrefix)) {
2032
- return;
2033
- }
2034
- if (isTempDirOwnedByLiveProcess(entry.name, tempDirPrefix)) {
2035
- return;
2036
- }
2037
- await (0, import_promises.rm)((0, import_node_path.join)(profilesDir, entry.name), {
2038
- recursive: true,
2039
- force: true
2040
- }).catch(() => void 0);
2041
- })
2125
+ }
2126
+ async function readLockReclaimerRecord(lockDirPath) {
2127
+ return await readLockParticipantRecord(
2128
+ (0, import_node_path.join)(buildLockReclaimerDirPath(lockDirPath), LOCK_OWNER_FILE)
2042
2129
  );
2043
2130
  }
2044
- function isTempDirOwnedByLiveProcess(tempDirName, tempDirPrefix) {
2045
- const owner = parseTempDirOwner(tempDirName, tempDirPrefix);
2046
- if (!owner) {
2131
+ async function tryReclaimStaleLock(lockDirPath, expectedOwner) {
2132
+ if (!await tryAcquireLockReclaimer(lockDirPath)) {
2047
2133
  return false;
2048
2134
  }
2049
- if (owner.pid === process.pid && Math.abs(owner.processStartedAtMs - PROCESS_STARTED_AT_MS) <= PROCESS_START_TIME_TOLERANCE_MS) {
2050
- return true;
2135
+ let reclaimed = false;
2136
+ try {
2137
+ const owner = await readLockOwner(lockDirPath);
2138
+ if (!processOwnersEqual(owner, expectedOwner)) {
2139
+ return false;
2140
+ }
2141
+ if (owner && await getProcessLiveness(owner) !== "dead") {
2142
+ return false;
2143
+ }
2144
+ await (0, import_promises2.rm)(lockDirPath, {
2145
+ recursive: true,
2146
+ force: true
2147
+ }).catch(() => void 0);
2148
+ reclaimed = !(0, import_node_fs.existsSync)(lockDirPath);
2149
+ return reclaimed;
2150
+ } finally {
2151
+ if (!reclaimed) {
2152
+ await (0, import_promises2.rm)(buildLockReclaimerDirPath(lockDirPath), {
2153
+ recursive: true,
2154
+ force: true
2155
+ }).catch(() => void 0);
2156
+ }
2157
+ }
2158
+ }
2159
+ async function tryAcquireLockReclaimer(lockDirPath) {
2160
+ const reclaimerDirPath = buildLockReclaimerDirPath(lockDirPath);
2161
+ while (true) {
2162
+ const tempReclaimerDirPath = `${reclaimerDirPath}-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto.randomUUID)()}`;
2163
+ try {
2164
+ await (0, import_promises2.mkdir)(tempReclaimerDirPath);
2165
+ await writeLockOwner(tempReclaimerDirPath, CURRENT_PROCESS_OWNER);
2166
+ try {
2167
+ await (0, import_promises2.rename)(tempReclaimerDirPath, reclaimerDirPath);
2168
+ return true;
2169
+ } catch (error) {
2170
+ if (getErrorCode(error) === "ENOENT") {
2171
+ return false;
2172
+ }
2173
+ if (!wasDirPublishedByAnotherProcess(error, reclaimerDirPath)) {
2174
+ throw error;
2175
+ }
2176
+ }
2177
+ } catch (error) {
2178
+ const code = getErrorCode(error);
2179
+ if (code === "ENOENT") {
2180
+ return false;
2181
+ }
2182
+ throw error;
2183
+ } finally {
2184
+ await (0, import_promises2.rm)(tempReclaimerDirPath, {
2185
+ recursive: true,
2186
+ force: true
2187
+ }).catch(() => void 0);
2188
+ }
2189
+ const reclaimerRecord = await readLockReclaimerRecord(lockDirPath);
2190
+ if (!reclaimerRecord.exists || !reclaimerRecord.owner) {
2191
+ return false;
2192
+ }
2193
+ if (await getProcessLiveness(reclaimerRecord.owner) !== "dead") {
2194
+ return false;
2195
+ }
2196
+ await (0, import_promises2.rm)(reclaimerDirPath, {
2197
+ recursive: true,
2198
+ force: true
2199
+ }).catch(() => void 0);
2200
+ }
2201
+ }
2202
+ function buildLockReclaimerDirPath(lockDirPath) {
2203
+ return (0, import_node_path.join)(lockDirPath, LOCK_RECLAIMER_DIR);
2204
+ }
2205
+ async function sleep(ms) {
2206
+ await new Promise((resolve) => setTimeout(resolve, ms));
2207
+ }
2208
+
2209
+ // src/browser/persistent-profile-coordination.ts
2210
+ var PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS = 50;
2211
+ async function withPersistentProfileControlLock(targetUserDataDir, action) {
2212
+ return await withDirLock(
2213
+ buildPersistentProfileControlLockDirPath(targetUserDataDir),
2214
+ action
2215
+ );
2216
+ }
2217
+ async function acquirePersistentProfileWriteLock(targetUserDataDir) {
2218
+ const controlLockDirPath = buildPersistentProfileControlLockDirPath(targetUserDataDir);
2219
+ const writeLockDirPath = buildPersistentProfileWriteLockDirPath(
2220
+ targetUserDataDir
2221
+ );
2222
+ while (true) {
2223
+ let releaseWriteLock = null;
2224
+ const releaseControlLock = await acquireDirLock(controlLockDirPath);
2225
+ try {
2226
+ releaseWriteLock = await tryAcquireDirLock(writeLockDirPath);
2227
+ } finally {
2228
+ await releaseControlLock();
2229
+ }
2230
+ if (releaseWriteLock) {
2231
+ return releaseWriteLock;
2232
+ }
2233
+ await sleep2(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
2234
+ }
2235
+ }
2236
+ async function isPersistentProfileWriteLocked(targetUserDataDir) {
2237
+ return await isDirLockHeld(
2238
+ buildPersistentProfileWriteLockDirPath(targetUserDataDir)
2239
+ );
2240
+ }
2241
+ function buildPersistentProfileWriteLockDirPath(targetUserDataDir) {
2242
+ return (0, import_node_path2.join)((0, import_node_path2.dirname)(targetUserDataDir), `${(0, import_node_path2.basename)(targetUserDataDir)}.lock`);
2243
+ }
2244
+ function buildPersistentProfileControlLockDirPath(targetUserDataDir) {
2245
+ return (0, import_node_path2.join)(
2246
+ (0, import_node_path2.dirname)(targetUserDataDir),
2247
+ `${(0, import_node_path2.basename)(targetUserDataDir)}.control.lock`
2248
+ );
2249
+ }
2250
+ async function sleep2(ms) {
2251
+ await new Promise((resolve) => setTimeout(resolve, ms));
2252
+ }
2253
+
2254
+ // src/browser/shared-real-browser-session-state.ts
2255
+ var import_node_crypto2 = require("crypto");
2256
+ var import_node_fs2 = require("fs");
2257
+ var import_promises3 = require("fs/promises");
2258
+ var import_node_path3 = require("path");
2259
+ var SHARED_SESSION_METADATA_FILE = "session.json";
2260
+ var SHARED_SESSION_CLIENTS_DIR = "clients";
2261
+ var SHARED_SESSION_RETRY_DELAY_MS = 50;
2262
+ var SHARED_SESSION_METADATA_TEMP_FILE_PREFIX = `${SHARED_SESSION_METADATA_FILE}.`;
2263
+ var SHARED_SESSION_METADATA_TEMP_FILE_SUFFIX = ".tmp";
2264
+ function buildSharedSessionDirPath(persistentUserDataDir) {
2265
+ return (0, import_node_path3.join)(
2266
+ (0, import_node_path3.dirname)(persistentUserDataDir),
2267
+ `${(0, import_node_path3.basename)(persistentUserDataDir)}.session`
2268
+ );
2269
+ }
2270
+ function buildSharedSessionLockPath(persistentUserDataDir) {
2271
+ return `${buildSharedSessionDirPath(persistentUserDataDir)}.lock`;
2272
+ }
2273
+ function buildSharedSessionClientsDirPath(persistentUserDataDir) {
2274
+ return (0, import_node_path3.join)(
2275
+ buildSharedSessionDirPath(persistentUserDataDir),
2276
+ SHARED_SESSION_CLIENTS_DIR
2277
+ );
2278
+ }
2279
+ function buildSharedSessionClientPath(persistentUserDataDir, clientId) {
2280
+ return (0, import_node_path3.join)(
2281
+ buildSharedSessionClientsDirPath(persistentUserDataDir),
2282
+ `${clientId}.json`
2283
+ );
2284
+ }
2285
+ async function readSharedSessionMetadata(persistentUserDataDir) {
2286
+ return (await readSharedSessionMetadataRecord(persistentUserDataDir)).metadata;
2287
+ }
2288
+ async function writeSharedSessionMetadata(persistentUserDataDir, metadata) {
2289
+ const sessionDirPath = buildSharedSessionDirPath(persistentUserDataDir);
2290
+ const metadataPath = buildSharedSessionMetadataPath(persistentUserDataDir);
2291
+ const tempPath = buildSharedSessionMetadataTempPath(sessionDirPath);
2292
+ await (0, import_promises3.mkdir)(sessionDirPath, { recursive: true });
2293
+ try {
2294
+ await (0, import_promises3.writeFile)(tempPath, JSON.stringify(metadata, null, 2));
2295
+ await (0, import_promises3.rename)(tempPath, metadataPath);
2296
+ } finally {
2297
+ await (0, import_promises3.rm)(tempPath, { force: true }).catch(() => void 0);
2298
+ }
2299
+ }
2300
+ async function hasLiveSharedRealBrowserSession(persistentUserDataDir) {
2301
+ const sessionDirPath = buildSharedSessionDirPath(persistentUserDataDir);
2302
+ const metadataRecord = await readSharedSessionMetadataRecord(
2303
+ persistentUserDataDir
2304
+ );
2305
+ if (!metadataRecord.exists) {
2306
+ return await hasLiveSharedSessionPublisherOrClients(sessionDirPath);
2307
+ }
2308
+ if (!metadataRecord.metadata) {
2309
+ return true;
2310
+ }
2311
+ if (await getProcessLiveness(metadataRecord.metadata.browserOwner) === "dead") {
2312
+ await (0, import_promises3.rm)(sessionDirPath, {
2313
+ force: true,
2314
+ recursive: true
2315
+ }).catch(() => void 0);
2316
+ return false;
2317
+ }
2318
+ return true;
2319
+ }
2320
+ async function waitForSharedRealBrowserSessionToDrain(persistentUserDataDir) {
2321
+ while (true) {
2322
+ if (!await hasLiveSharedRealBrowserSession(persistentUserDataDir)) {
2323
+ return;
2324
+ }
2325
+ await sleep3(SHARED_SESSION_RETRY_DELAY_MS);
2326
+ }
2327
+ }
2328
+ async function readSharedSessionMetadataRecord(persistentUserDataDir) {
2329
+ try {
2330
+ const raw = await (0, import_promises3.readFile)(
2331
+ buildSharedSessionMetadataPath(persistentUserDataDir),
2332
+ "utf8"
2333
+ );
2334
+ return {
2335
+ exists: true,
2336
+ metadata: parseSharedSessionMetadata(JSON.parse(raw))
2337
+ };
2338
+ } catch (error) {
2339
+ return {
2340
+ exists: getErrorCode2(error) !== "ENOENT",
2341
+ metadata: null
2342
+ };
2343
+ }
2344
+ }
2345
+ async function hasLiveSharedSessionPublisherOrClients(sessionDirPath) {
2346
+ if (!(0, import_node_fs2.existsSync)(sessionDirPath)) {
2347
+ return false;
2348
+ }
2349
+ let entries;
2350
+ try {
2351
+ entries = await readDirNames(sessionDirPath);
2352
+ } catch (error) {
2353
+ return getErrorCode2(error) !== "ENOENT";
2354
+ }
2355
+ let hasUnknownEntries = false;
2356
+ for (const entry of entries) {
2357
+ if (entry === SHARED_SESSION_METADATA_FILE) {
2358
+ return true;
2359
+ }
2360
+ if (entry === SHARED_SESSION_CLIENTS_DIR) {
2361
+ if (await hasDirectoryEntries((0, import_node_path3.join)(sessionDirPath, entry))) {
2362
+ return true;
2363
+ }
2364
+ continue;
2365
+ }
2366
+ const owner = parseSharedSessionMetadataTempOwner(entry);
2367
+ if (!owner) {
2368
+ if (isSharedSessionMetadataTempFile(entry)) {
2369
+ continue;
2370
+ }
2371
+ hasUnknownEntries = true;
2372
+ continue;
2373
+ }
2374
+ if (await getProcessLiveness(owner) !== "dead") {
2375
+ return true;
2376
+ }
2377
+ }
2378
+ if (hasUnknownEntries) {
2379
+ return true;
2380
+ }
2381
+ await (0, import_promises3.rm)(sessionDirPath, {
2382
+ force: true,
2383
+ recursive: true
2384
+ }).catch(() => void 0);
2385
+ return false;
2386
+ }
2387
+ function buildSharedSessionMetadataPath(persistentUserDataDir) {
2388
+ return (0, import_node_path3.join)(
2389
+ buildSharedSessionDirPath(persistentUserDataDir),
2390
+ SHARED_SESSION_METADATA_FILE
2391
+ );
2392
+ }
2393
+ function buildSharedSessionMetadataTempPath(sessionDirPath) {
2394
+ return (0, import_node_path3.join)(
2395
+ sessionDirPath,
2396
+ [
2397
+ SHARED_SESSION_METADATA_FILE,
2398
+ CURRENT_PROCESS_OWNER.pid,
2399
+ CURRENT_PROCESS_OWNER.processStartedAtMs,
2400
+ (0, import_node_crypto2.randomUUID)(),
2401
+ "tmp"
2402
+ ].join(".")
2403
+ );
2404
+ }
2405
+ function parseSharedSessionMetadata(value) {
2406
+ if (!value || typeof value !== "object") {
2407
+ return null;
2408
+ }
2409
+ const parsed = value;
2410
+ const browserOwner = parseProcessOwner(parsed.browserOwner);
2411
+ const stateOwner = parseProcessOwner(parsed.stateOwner);
2412
+ const state = parsed.state === "launching" || parsed.state === "ready" || parsed.state === "closing" ? parsed.state : null;
2413
+ 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) {
2414
+ return null;
2415
+ }
2416
+ return {
2417
+ browserOwner,
2418
+ createdAt: parsed.createdAt,
2419
+ debugPort: parsed.debugPort,
2420
+ executablePath: parsed.executablePath,
2421
+ headless: parsed.headless,
2422
+ persistentUserDataDir: parsed.persistentUserDataDir,
2423
+ profileDirectory: parsed.profileDirectory,
2424
+ sessionId: parsed.sessionId,
2425
+ state,
2426
+ stateOwner
2427
+ };
2428
+ }
2429
+ function parseSharedSessionMetadataTempOwner(entryName) {
2430
+ if (!isSharedSessionMetadataTempFile(entryName)) {
2431
+ return null;
2432
+ }
2433
+ const segments = entryName.split(".");
2434
+ if (segments.length < 5) {
2435
+ return null;
2436
+ }
2437
+ return parseProcessOwner({
2438
+ pid: Number.parseInt(segments[2] ?? "", 10),
2439
+ processStartedAtMs: Number.parseInt(segments[3] ?? "", 10)
2440
+ });
2441
+ }
2442
+ function isSharedSessionMetadataTempFile(entryName) {
2443
+ return entryName.startsWith(SHARED_SESSION_METADATA_TEMP_FILE_PREFIX) && entryName.endsWith(SHARED_SESSION_METADATA_TEMP_FILE_SUFFIX);
2444
+ }
2445
+ function getErrorCode2(error) {
2446
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
2447
+ }
2448
+ async function hasDirectoryEntries(dirPath) {
2449
+ try {
2450
+ return (await readDirNames(dirPath)).length > 0;
2451
+ } catch (error) {
2452
+ return getErrorCode2(error) !== "ENOENT";
2453
+ }
2454
+ }
2455
+ async function readDirNames(dirPath) {
2456
+ return await (0, import_promises3.readdir)(dirPath, { encoding: "utf8" });
2457
+ }
2458
+ async function sleep3(ms) {
2459
+ await new Promise((resolve) => setTimeout(resolve, ms));
2460
+ }
2461
+
2462
+ // src/browser/persistent-profile.ts
2463
+ var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process2.execFile);
2464
+ var OPENSTEER_META_FILE = ".opensteer-meta.json";
2465
+ var OPENSTEER_RUNTIME_META_FILE = ".opensteer-runtime.json";
2466
+ var OPENSTEER_RUNTIME_CREATING_FILE = ".opensteer-runtime-creating.json";
2467
+ var PROCESS_LIST_MAX_BUFFER_BYTES2 = 16 * 1024 * 1024;
2468
+ var PS_COMMAND_ENV2 = { ...process.env, LC_ALL: "C" };
2469
+ var CHROME_SINGLETON_ENTRIES = /* @__PURE__ */ new Set([
2470
+ "SingletonCookie",
2471
+ "SingletonLock",
2472
+ "SingletonSocket",
2473
+ "DevToolsActivePort",
2474
+ "lockfile"
2475
+ ]);
2476
+ var COPY_SKIP_ENTRIES = /* @__PURE__ */ new Set([
2477
+ ...CHROME_SINGLETON_ENTRIES,
2478
+ OPENSTEER_META_FILE,
2479
+ OPENSTEER_RUNTIME_META_FILE,
2480
+ OPENSTEER_RUNTIME_CREATING_FILE
2481
+ ]);
2482
+ var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
2483
+ "Crash Reports",
2484
+ "Crashpad",
2485
+ "BrowserMetrics",
2486
+ "GrShaderCache",
2487
+ "ShaderCache",
2488
+ "GraphiteDawnCache",
2489
+ "component_crx_cache",
2490
+ "Crowd Deny",
2491
+ "hyphen-data",
2492
+ "OnDeviceHeadSuggestModel",
2493
+ "OptimizationGuidePredictionModels",
2494
+ "Segmentation Platform",
2495
+ "SmartCardDeviceNames",
2496
+ "WidevineCdm",
2497
+ "pnacl"
2498
+ ]);
2499
+ async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory, profilesRootDir = defaultPersistentProfilesRootDir()) {
2500
+ const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
2501
+ const targetUserDataDir = (0, import_node_path4.join)(
2502
+ expandHome(profilesRootDir),
2503
+ buildPersistentProfileKey(resolvedSourceUserDataDir, profileDirectory)
2504
+ );
2505
+ const sourceProfileDir = (0, import_node_path4.join)(resolvedSourceUserDataDir, profileDirectory);
2506
+ const metadata = buildPersistentProfileMetadata(
2507
+ resolvedSourceUserDataDir,
2508
+ profileDirectory
2509
+ );
2510
+ await (0, import_promises4.mkdir)((0, import_node_path4.dirname)(targetUserDataDir), { recursive: true });
2511
+ if (await isHealthyPersistentProfile(
2512
+ targetUserDataDir,
2513
+ resolvedSourceUserDataDir,
2514
+ profileDirectory
2515
+ ) && !await isPersistentProfileWriteLocked(targetUserDataDir)) {
2516
+ return {
2517
+ created: false,
2518
+ userDataDir: targetUserDataDir
2519
+ };
2520
+ }
2521
+ return await withPersistentProfileWriteAccess(targetUserDataDir, async () => {
2522
+ await recoverPersistentProfileBackup(targetUserDataDir);
2523
+ await cleanOrphanedOwnedDirs(
2524
+ (0, import_node_path4.dirname)(targetUserDataDir),
2525
+ buildPersistentProfileTempDirNamePrefix(targetUserDataDir)
2526
+ );
2527
+ if (!(0, import_node_fs3.existsSync)(sourceProfileDir)) {
2528
+ throw new Error(
2529
+ `Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
2530
+ );
2531
+ }
2532
+ const created = await createPersistentProfileClone(
2533
+ resolvedSourceUserDataDir,
2534
+ sourceProfileDir,
2535
+ targetUserDataDir,
2536
+ profileDirectory,
2537
+ metadata
2538
+ );
2539
+ await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
2540
+ return {
2541
+ created,
2542
+ userDataDir: targetUserDataDir
2543
+ };
2544
+ });
2545
+ }
2546
+ async function clearPersistentProfileSingletons(userDataDir) {
2547
+ await Promise.all(
2548
+ [...CHROME_SINGLETON_ENTRIES].map(
2549
+ (entry) => (0, import_promises4.rm)((0, import_node_path4.join)(userDataDir, entry), {
2550
+ force: true,
2551
+ recursive: true
2552
+ }).catch(() => void 0)
2553
+ )
2554
+ );
2555
+ }
2556
+ function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
2557
+ const hash = (0, import_node_crypto3.createHash)("sha256").update(`${sourceUserDataDir}\0${profileDirectory}`).digest("hex").slice(0, 16);
2558
+ const sourceLabel = sanitizePathSegment((0, import_node_path4.basename)(sourceUserDataDir) || "user-data");
2559
+ const profileLabel = sanitizePathSegment(profileDirectory || "Default");
2560
+ return `${sourceLabel}-${profileLabel}-${hash}`;
2561
+ }
2562
+ function defaultPersistentProfilesRootDir() {
2563
+ return (0, import_node_path4.join)((0, import_node_os.homedir)(), ".opensteer", "real-browser-profiles");
2564
+ }
2565
+ function sanitizePathSegment(value) {
2566
+ const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-");
2567
+ return sanitized.replace(/^-|-$/g, "") || "profile";
2568
+ }
2569
+ function isProfileDirectory(userDataDir, entry) {
2570
+ return (0, import_node_fs3.existsSync)((0, import_node_path4.join)(userDataDir, entry, "Preferences"));
2571
+ }
2572
+ async function copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, targetProfileDirectory) {
2573
+ let entries;
2574
+ try {
2575
+ entries = await (0, import_promises4.readdir)(sourceUserDataDir);
2576
+ } catch {
2577
+ return;
2578
+ }
2579
+ const copyTasks = [];
2580
+ for (const entry of entries) {
2581
+ if (COPY_SKIP_ENTRIES.has(entry)) continue;
2582
+ if (entry === targetProfileDirectory) continue;
2583
+ const sourcePath = (0, import_node_path4.join)(sourceUserDataDir, entry);
2584
+ const targetPath = (0, import_node_path4.join)(targetUserDataDir, entry);
2585
+ if ((0, import_node_fs3.existsSync)(targetPath)) continue;
2586
+ let entryStat;
2587
+ try {
2588
+ entryStat = await (0, import_promises4.stat)(sourcePath);
2589
+ } catch {
2590
+ continue;
2591
+ }
2592
+ if (entryStat.isFile()) {
2593
+ copyTasks.push((0, import_promises4.copyFile)(sourcePath, targetPath).catch(() => void 0));
2594
+ } else if (entryStat.isDirectory()) {
2595
+ if (isProfileDirectory(sourceUserDataDir, entry)) continue;
2596
+ if (SKIPPED_ROOT_DIRECTORIES.has(entry)) continue;
2597
+ copyTasks.push(
2598
+ (0, import_promises4.cp)(sourcePath, targetPath, { recursive: true }).catch(
2599
+ () => void 0
2600
+ )
2601
+ );
2602
+ }
2603
+ }
2604
+ await Promise.all(copyTasks);
2605
+ }
2606
+ async function writePersistentProfileMetadata(userDataDir, metadata) {
2607
+ await (0, import_promises4.writeFile)(
2608
+ (0, import_node_path4.join)(userDataDir, OPENSTEER_META_FILE),
2609
+ JSON.stringify(metadata, null, 2)
2610
+ );
2611
+ }
2612
+ function buildPersistentProfileMetadata(sourceUserDataDir, profileDirectory) {
2613
+ return {
2614
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2615
+ profileDirectory,
2616
+ source: sourceUserDataDir
2617
+ };
2618
+ }
2619
+ async function createPersistentProfileClone(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
2620
+ if ((0, import_node_fs3.existsSync)(targetUserDataDir)) {
2621
+ return false;
2622
+ }
2623
+ const tempUserDataDir = await (0, import_promises4.mkdtemp)(
2624
+ buildPersistentProfileTempDirPrefix(targetUserDataDir)
2625
+ );
2626
+ let published = false;
2627
+ try {
2628
+ await materializePersistentProfileSnapshot(
2629
+ sourceUserDataDir,
2630
+ sourceProfileDir,
2631
+ tempUserDataDir,
2632
+ profileDirectory,
2633
+ metadata
2634
+ );
2635
+ try {
2636
+ await (0, import_promises4.rename)(tempUserDataDir, targetUserDataDir);
2637
+ } catch (error) {
2638
+ if (wasDirPublishedByAnotherProcess2(error, targetUserDataDir)) {
2639
+ return false;
2640
+ }
2641
+ throw error;
2642
+ }
2643
+ published = true;
2644
+ return true;
2645
+ } finally {
2646
+ if (!published) {
2647
+ await (0, import_promises4.rm)(tempUserDataDir, {
2648
+ recursive: true,
2649
+ force: true
2650
+ }).catch(() => void 0);
2651
+ }
2652
+ }
2653
+ }
2654
+ async function materializePersistentProfileSnapshot(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
2655
+ if (!(0, import_node_fs3.existsSync)(sourceProfileDir)) {
2656
+ throw new Error(
2657
+ `Chrome profile "${profileDirectory}" was not found in "${sourceUserDataDir}".`
2658
+ );
2659
+ }
2660
+ await (0, import_promises4.cp)(sourceProfileDir, (0, import_node_path4.join)(targetUserDataDir, profileDirectory), {
2661
+ recursive: true
2662
+ });
2663
+ await copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, profileDirectory);
2664
+ await writePersistentProfileMetadata(targetUserDataDir, metadata);
2665
+ }
2666
+ async function readRuntimeProfileCreationMarker(userDataDir) {
2667
+ try {
2668
+ const raw = await (0, import_promises4.readFile)(
2669
+ (0, import_node_path4.join)(userDataDir, OPENSTEER_RUNTIME_CREATING_FILE),
2670
+ "utf8"
2671
+ );
2672
+ return parseRuntimeProfileCreationMarker(JSON.parse(raw));
2673
+ } catch {
2674
+ return null;
2675
+ }
2676
+ }
2677
+ async function ensurePersistentProfileMetadata(userDataDir, metadata) {
2678
+ if ((0, import_node_fs3.existsSync)((0, import_node_path4.join)(userDataDir, OPENSTEER_META_FILE))) {
2679
+ return;
2680
+ }
2681
+ await writePersistentProfileMetadata(userDataDir, metadata);
2682
+ }
2683
+ async function recoverPersistentProfileBackup(targetUserDataDir) {
2684
+ const backupDirPaths = await listPersistentProfileBackupDirs(targetUserDataDir);
2685
+ if (backupDirPaths.length === 0) {
2686
+ return;
2687
+ }
2688
+ if (!(0, import_node_fs3.existsSync)(targetUserDataDir)) {
2689
+ const [latestBackupDirPath, ...staleBackupDirPaths] = backupDirPaths;
2690
+ await (0, import_promises4.rename)(latestBackupDirPath, targetUserDataDir);
2691
+ await Promise.all(
2692
+ staleBackupDirPaths.map(
2693
+ (backupDirPath) => (0, import_promises4.rm)(backupDirPath, {
2694
+ recursive: true,
2695
+ force: true
2696
+ }).catch(() => void 0)
2697
+ )
2698
+ );
2699
+ return;
2700
+ }
2701
+ await Promise.all(
2702
+ backupDirPaths.map(
2703
+ (backupDirPath) => (0, import_promises4.rm)(backupDirPath, {
2704
+ recursive: true,
2705
+ force: true
2706
+ }).catch(() => void 0)
2707
+ )
2708
+ );
2709
+ }
2710
+ async function listPersistentProfileBackupDirs(targetUserDataDir) {
2711
+ const profilesDir = (0, import_node_path4.dirname)(targetUserDataDir);
2712
+ let entries;
2713
+ try {
2714
+ entries = await (0, import_promises4.readdir)(profilesDir, {
2715
+ encoding: "utf8",
2716
+ withFileTypes: true
2717
+ });
2718
+ } catch {
2719
+ return [];
2720
+ }
2721
+ const backupDirNamePrefix = buildPersistentProfileBackupDirNamePrefix(targetUserDataDir);
2722
+ return entries.filter(
2723
+ (entry) => entry.isDirectory() && entry.name.startsWith(backupDirNamePrefix)
2724
+ ).map((entry) => (0, import_node_path4.join)(profilesDir, entry.name)).sort((leftPath, rightPath) => rightPath.localeCompare(leftPath));
2725
+ }
2726
+ async function readPersistentProfileMetadata(userDataDir) {
2727
+ try {
2728
+ const raw = await (0, import_promises4.readFile)((0, import_node_path4.join)(userDataDir, OPENSTEER_META_FILE), "utf8");
2729
+ const parsed = JSON.parse(raw);
2730
+ if (typeof parsed.createdAt !== "string" || typeof parsed.profileDirectory !== "string" || typeof parsed.source !== "string") {
2731
+ return null;
2732
+ }
2733
+ return {
2734
+ createdAt: parsed.createdAt,
2735
+ profileDirectory: parsed.profileDirectory,
2736
+ source: parsed.source
2737
+ };
2738
+ } catch {
2739
+ return null;
2740
+ }
2741
+ }
2742
+ async function isHealthyPersistentProfile(userDataDir, expectedSourceUserDataDir, expectedProfileDirectory) {
2743
+ if (!(0, import_node_fs3.existsSync)(userDataDir) || !(0, import_node_fs3.existsSync)((0, import_node_path4.join)(userDataDir, expectedProfileDirectory))) {
2744
+ return false;
2745
+ }
2746
+ const metadata = await readPersistentProfileMetadata(userDataDir);
2747
+ return metadata?.source === expectedSourceUserDataDir && metadata.profileDirectory === expectedProfileDirectory;
2748
+ }
2749
+ function wasDirPublishedByAnotherProcess2(error, targetDirPath) {
2750
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
2751
+ return (0, import_node_fs3.existsSync)(targetDirPath) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
2752
+ }
2753
+ async function withPersistentProfileWriteAccess(targetUserDataDir, action) {
2754
+ const releaseWriteLock = await acquirePersistentProfileWriteLock(
2755
+ targetUserDataDir
2756
+ );
2757
+ try {
2758
+ await waitForRuntimeProfileCreationsToDrain(targetUserDataDir);
2759
+ await waitForSharedRealBrowserSessionToDrain(targetUserDataDir);
2760
+ return await action();
2761
+ } finally {
2762
+ await releaseWriteLock();
2763
+ }
2764
+ }
2765
+ function buildPersistentProfileTempDirPrefix(targetUserDataDir) {
2766
+ return (0, import_node_path4.join)(
2767
+ (0, import_node_path4.dirname)(targetUserDataDir),
2768
+ `${buildPersistentProfileTempDirNamePrefix(targetUserDataDir)}${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-`
2769
+ );
2770
+ }
2771
+ function buildPersistentProfileTempDirNamePrefix(targetUserDataDir) {
2772
+ return `${(0, import_node_path4.basename)(targetUserDataDir)}-tmp-`;
2773
+ }
2774
+ function buildPersistentProfileBackupDirNamePrefix(targetUserDataDir) {
2775
+ return `${(0, import_node_path4.basename)(targetUserDataDir)}-backup-`;
2776
+ }
2777
+ function buildRuntimeProfileCreationRegistryDirPath(persistentUserDataDir) {
2778
+ return (0, import_node_path4.join)(
2779
+ (0, import_node_path4.dirname)(persistentUserDataDir),
2780
+ `${(0, import_node_path4.basename)(persistentUserDataDir)}.creating`
2781
+ );
2782
+ }
2783
+ function buildRuntimeProfileCreationRegistrationPath(persistentUserDataDir, runtimeUserDataDir) {
2784
+ const key = (0, import_node_crypto3.createHash)("sha256").update(runtimeUserDataDir).digest("hex").slice(0, 16);
2785
+ return (0, import_node_path4.join)(
2786
+ buildRuntimeProfileCreationRegistryDirPath(persistentUserDataDir),
2787
+ `${key}.json`
2788
+ );
2789
+ }
2790
+ async function clearRuntimeProfileCreationState(runtimeUserDataDir, persistentUserDataDir) {
2791
+ await Promise.all([
2792
+ (0, import_promises4.rm)((0, import_node_path4.join)(runtimeUserDataDir, OPENSTEER_RUNTIME_CREATING_FILE), {
2793
+ force: true
2794
+ }).catch(() => void 0),
2795
+ (0, import_promises4.rm)(
2796
+ buildRuntimeProfileCreationRegistrationPath(
2797
+ persistentUserDataDir,
2798
+ runtimeUserDataDir
2799
+ ),
2800
+ {
2801
+ force: true
2802
+ }
2803
+ ).catch(() => void 0)
2804
+ ]);
2805
+ }
2806
+ async function listRuntimeProfileCreationRegistrations(persistentUserDataDir) {
2807
+ const registryDirPath = buildRuntimeProfileCreationRegistryDirPath(
2808
+ persistentUserDataDir
2809
+ );
2810
+ let entries;
2811
+ try {
2812
+ entries = await (0, import_promises4.readdir)(registryDirPath, {
2813
+ encoding: "utf8",
2814
+ withFileTypes: true
2815
+ });
2816
+ } catch {
2817
+ return [];
2818
+ }
2819
+ return await Promise.all(
2820
+ entries.filter((entry) => entry.isFile()).map(async (entry) => {
2821
+ const filePath = (0, import_node_path4.join)(registryDirPath, entry.name);
2822
+ return {
2823
+ filePath,
2824
+ marker: await readRuntimeProfileCreationRegistration(filePath)
2825
+ };
2826
+ })
2827
+ );
2828
+ }
2829
+ async function readRuntimeProfileCreationRegistration(filePath) {
2830
+ try {
2831
+ const raw = await (0, import_promises4.readFile)(filePath, "utf8");
2832
+ return parseRuntimeProfileCreationMarker(JSON.parse(raw));
2833
+ } catch {
2834
+ return null;
2835
+ }
2836
+ }
2837
+ async function cleanOrphanedOwnedDirs(rootDir, ownedDirNamePrefix) {
2838
+ let entries;
2839
+ try {
2840
+ entries = await (0, import_promises4.readdir)(rootDir, {
2841
+ encoding: "utf8",
2842
+ withFileTypes: true
2843
+ });
2844
+ } catch {
2845
+ return;
2846
+ }
2847
+ await Promise.all(
2848
+ entries.map(async (entry) => {
2849
+ if (!entry.isDirectory() || !entry.name.startsWith(ownedDirNamePrefix)) {
2850
+ return;
2851
+ }
2852
+ if (await isOwnedDirByLiveProcess(entry.name, ownedDirNamePrefix)) {
2853
+ return;
2854
+ }
2855
+ await (0, import_promises4.rm)((0, import_node_path4.join)(rootDir, entry.name), {
2856
+ recursive: true,
2857
+ force: true
2858
+ }).catch(() => void 0);
2859
+ })
2860
+ );
2861
+ }
2862
+ async function isOwnedDirByLiveProcess(ownedDirName, ownedDirPrefix) {
2863
+ const owner = parseOwnedDirOwner(ownedDirName, ownedDirPrefix);
2864
+ return owner ? await getProcessLiveness(owner) !== "dead" : false;
2865
+ }
2866
+ async function hasActiveRuntimeProfileCreations(persistentUserDataDir) {
2867
+ const registrations = await listRuntimeProfileCreationRegistrations(
2868
+ persistentUserDataDir
2869
+ );
2870
+ let hasLiveCreation = false;
2871
+ for (const registration of registrations) {
2872
+ const marker = registration.marker;
2873
+ if (!marker || marker.persistentUserDataDir !== persistentUserDataDir) {
2874
+ await (0, import_promises4.rm)(registration.filePath, {
2875
+ force: true
2876
+ }).catch(() => void 0);
2877
+ continue;
2878
+ }
2879
+ const runtimeMarker = await readRuntimeProfileCreationMarker(
2880
+ marker.runtimeUserDataDir
2881
+ );
2882
+ if (!runtimeMarker || runtimeMarker.persistentUserDataDir !== persistentUserDataDir || runtimeMarker.runtimeUserDataDir !== marker.runtimeUserDataDir) {
2883
+ await clearRuntimeProfileCreationState(
2884
+ marker.runtimeUserDataDir,
2885
+ persistentUserDataDir
2886
+ );
2887
+ continue;
2888
+ }
2889
+ if (await getProcessLiveness(runtimeMarker.creator) === "dead") {
2890
+ await clearRuntimeProfileCreationState(
2891
+ marker.runtimeUserDataDir,
2892
+ persistentUserDataDir
2893
+ );
2894
+ await (0, import_promises4.rm)(marker.runtimeUserDataDir, {
2895
+ recursive: true,
2896
+ force: true
2897
+ }).catch(() => void 0);
2898
+ continue;
2899
+ }
2900
+ hasLiveCreation = true;
2901
+ }
2902
+ return hasLiveCreation;
2903
+ }
2904
+ async function waitForRuntimeProfileCreationsToDrain(persistentUserDataDir) {
2905
+ while (true) {
2906
+ if (!await hasActiveRuntimeProfileCreations(persistentUserDataDir)) {
2907
+ return;
2908
+ }
2909
+ await sleep4(PERSISTENT_PROFILE_LOCK_RETRY_DELAY_MS);
2910
+ }
2911
+ }
2912
+ function parseRuntimeProfileCreationMarker(value) {
2913
+ if (!value || typeof value !== "object") {
2914
+ return null;
2915
+ }
2916
+ const parsed = value;
2917
+ const creator = parseProcessOwner(parsed.creator);
2918
+ const persistentUserDataDir = typeof parsed.persistentUserDataDir === "string" ? parsed.persistentUserDataDir : void 0;
2919
+ const profileDirectory = parsed.profileDirectory === null ? null : typeof parsed.profileDirectory === "string" ? parsed.profileDirectory : void 0;
2920
+ const runtimeUserDataDir = typeof parsed.runtimeUserDataDir === "string" ? parsed.runtimeUserDataDir : void 0;
2921
+ if (!creator || persistentUserDataDir === void 0 || profileDirectory === void 0 || runtimeUserDataDir === void 0) {
2922
+ return null;
2923
+ }
2924
+ return {
2925
+ creator,
2926
+ persistentUserDataDir,
2927
+ profileDirectory,
2928
+ runtimeUserDataDir
2929
+ };
2930
+ }
2931
+ function parseOwnedDirOwner(ownedDirName, ownedDirPrefix) {
2932
+ const remainder = ownedDirName.slice(ownedDirPrefix.length);
2933
+ const firstDashIndex = remainder.indexOf("-");
2934
+ const secondDashIndex = firstDashIndex === -1 ? -1 : remainder.indexOf("-", firstDashIndex + 1);
2935
+ if (firstDashIndex === -1 || secondDashIndex === -1) {
2936
+ return null;
2937
+ }
2938
+ const pid = Number.parseInt(remainder.slice(0, firstDashIndex), 10);
2939
+ const processStartedAtMs = Number.parseInt(
2940
+ remainder.slice(firstDashIndex + 1, secondDashIndex),
2941
+ 10
2942
+ );
2943
+ if (!Number.isInteger(pid) || pid <= 0) {
2944
+ return null;
2945
+ }
2946
+ if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
2947
+ return null;
2948
+ }
2949
+ return { pid, processStartedAtMs };
2950
+ }
2951
+ async function sleep4(ms) {
2952
+ await new Promise((resolve) => setTimeout(resolve, ms));
2953
+ }
2954
+
2955
+ // src/browser/shared-real-browser-session.ts
2956
+ var import_node_crypto4 = require("crypto");
2957
+ var import_node_child_process3 = require("child_process");
2958
+ var import_promises5 = require("fs/promises");
2959
+ var import_node_net = require("net");
2960
+ var import_node_path5 = require("path");
2961
+ var import_playwright = require("playwright");
2962
+ var SHARED_SESSION_RETRY_DELAY_MS2 = 50;
2963
+ async function acquireSharedRealBrowserSession(options) {
2964
+ const reservation = await reserveSharedSessionClient(options);
2965
+ const sessionContext = await attachToSharedSession(reservation, options);
2966
+ let closed = false;
2967
+ return {
2968
+ browser: sessionContext.browser,
2969
+ context: sessionContext.context,
2970
+ page: sessionContext.page,
2971
+ close: async () => {
2972
+ if (closed) {
2973
+ return;
2974
+ }
2975
+ closed = true;
2976
+ await releaseSharedSessionClient(sessionContext);
2977
+ }
2978
+ };
2979
+ }
2980
+ function getOwnedRealBrowserProcessPolicy(platformName = process.platform) {
2981
+ if (platformName === "win32") {
2982
+ return {
2983
+ detached: false,
2984
+ killStrategy: "taskkill",
2985
+ shouldUnref: true
2986
+ };
2987
+ }
2988
+ if (platformName === "darwin") {
2989
+ return {
2990
+ detached: false,
2991
+ killStrategy: "process",
2992
+ shouldUnref: true
2993
+ };
2994
+ }
2995
+ return {
2996
+ detached: true,
2997
+ killStrategy: "process-group",
2998
+ shouldUnref: true
2999
+ };
3000
+ }
3001
+ async function reserveSharedSessionClient(options) {
3002
+ while (true) {
3003
+ const outcome = await withPersistentProfileControlLock(
3004
+ options.persistentProfile.userDataDir,
3005
+ async () => {
3006
+ if (await isPersistentProfileWriteLocked(
3007
+ options.persistentProfile.userDataDir
3008
+ )) {
3009
+ return { kind: "wait" };
3010
+ }
3011
+ if (await hasActiveRuntimeProfileCreations(
3012
+ options.persistentProfile.userDataDir
3013
+ )) {
3014
+ return { kind: "wait" };
3015
+ }
3016
+ return await withSharedSessionLock(
3017
+ options.persistentProfile.userDataDir,
3018
+ async () => {
3019
+ const state = await inspectSharedSessionState(options);
3020
+ if (state.kind === "wait") {
3021
+ return { kind: "wait" };
3022
+ }
3023
+ if (state.kind === "ready") {
3024
+ return {
3025
+ kind: "ready",
3026
+ reservation: await registerSharedSessionClient(
3027
+ options.persistentProfile.userDataDir,
3028
+ state.metadata
3029
+ )
3030
+ };
3031
+ }
3032
+ return {
3033
+ kind: "launch",
3034
+ reservation: await launchSharedSession(options)
3035
+ };
3036
+ }
3037
+ );
3038
+ }
3039
+ );
3040
+ if (outcome.kind === "wait") {
3041
+ await sleep5(SHARED_SESSION_RETRY_DELAY_MS2);
3042
+ continue;
3043
+ }
3044
+ if (outcome.kind === "ready") {
3045
+ return outcome.reservation;
3046
+ }
3047
+ try {
3048
+ await waitForSharedSessionReady(
3049
+ outcome.reservation.metadata,
3050
+ options.timeoutMs
3051
+ );
3052
+ } catch (error) {
3053
+ await cleanupFailedSharedSessionLaunch(outcome.reservation);
3054
+ throw error;
3055
+ }
3056
+ try {
3057
+ return await withSharedSessionLock(
3058
+ options.persistentProfile.userDataDir,
3059
+ async () => {
3060
+ const metadata = await readSharedSessionMetadata(
3061
+ options.persistentProfile.userDataDir
3062
+ );
3063
+ if (!metadata || metadata.sessionId !== outcome.reservation.metadata.sessionId || !processOwnersEqual(
3064
+ metadata.browserOwner,
3065
+ outcome.reservation.launchedBrowserOwner
3066
+ )) {
3067
+ throw new Error(
3068
+ "The shared real-browser session changed before launch finalized."
3069
+ );
3070
+ }
3071
+ const readyMetadata = {
3072
+ ...metadata,
3073
+ state: "ready"
3074
+ };
3075
+ await writeSharedSessionMetadata(
3076
+ options.persistentProfile.userDataDir,
3077
+ readyMetadata
3078
+ );
3079
+ return await registerSharedSessionClient(
3080
+ options.persistentProfile.userDataDir,
3081
+ readyMetadata
3082
+ );
3083
+ }
3084
+ );
3085
+ } catch (error) {
3086
+ await cleanupFailedSharedSessionLaunch(outcome.reservation);
3087
+ throw error;
3088
+ }
3089
+ }
3090
+ }
3091
+ async function attachToSharedSession(reservation, options) {
3092
+ let browser = null;
3093
+ let page = null;
3094
+ try {
3095
+ const browserWsUrl = await resolveCdpWebSocketUrl(
3096
+ buildSharedSessionDiscoveryUrl(reservation.metadata.debugPort),
3097
+ options.timeoutMs
3098
+ );
3099
+ browser = await import_playwright.chromium.connectOverCDP(browserWsUrl, {
3100
+ timeout: options.timeoutMs
3101
+ });
3102
+ const context = getPrimaryBrowserContext(browser);
3103
+ page = await getSharedSessionPage(context, reservation.reuseExistingPage);
3104
+ if (options.initialUrl) {
3105
+ await page.goto(options.initialUrl, {
3106
+ timeout: options.timeoutMs,
3107
+ waitUntil: "domcontentloaded"
3108
+ });
3109
+ }
3110
+ return {
3111
+ browser,
3112
+ clientId: reservation.client.clientId,
3113
+ context,
3114
+ page,
3115
+ persistentUserDataDir: reservation.metadata.persistentUserDataDir,
3116
+ sessionId: reservation.metadata.sessionId
3117
+ };
3118
+ } catch (error) {
3119
+ if (page) {
3120
+ await page.close().catch(() => void 0);
3121
+ }
3122
+ if (browser) {
3123
+ await browser.close().catch(() => void 0);
3124
+ }
3125
+ await cleanupFailedSharedSessionAttach({
3126
+ clientId: reservation.client.clientId,
3127
+ persistentUserDataDir: reservation.metadata.persistentUserDataDir,
3128
+ sessionId: reservation.metadata.sessionId
3129
+ });
3130
+ throw error;
3131
+ }
3132
+ }
3133
+ async function releaseSharedSessionClient(context) {
3134
+ const releasePlan = await prepareSharedSessionCloseIfIdle(
3135
+ context.persistentUserDataDir,
3136
+ context.clientId,
3137
+ context.sessionId
3138
+ );
3139
+ if (releasePlan.closeBrowser) {
3140
+ await closeSharedSessionBrowser(
3141
+ context.persistentUserDataDir,
3142
+ releasePlan,
3143
+ context.browser
3144
+ );
3145
+ return;
3146
+ }
3147
+ await context.page.close().catch(() => void 0);
3148
+ await context.browser.close().catch(() => void 0);
3149
+ }
3150
+ async function inspectSharedSessionState(options) {
3151
+ const persistentUserDataDir = options.persistentProfile.userDataDir;
3152
+ const liveClients = await listLiveSharedSessionClients(persistentUserDataDir);
3153
+ const metadata = await readSharedSessionMetadata(persistentUserDataDir);
3154
+ if (!metadata) {
3155
+ if (liveClients.length > 0) {
3156
+ throw new Error(
3157
+ `Shared real-browser session metadata for "${persistentUserDataDir}" is missing while clients are still attached.`
3158
+ );
3159
+ }
3160
+ await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
3161
+ force: true,
3162
+ recursive: true
3163
+ }).catch(() => void 0);
3164
+ return { kind: "missing" };
3165
+ }
3166
+ assertSharedSessionCompatibility(metadata, options);
3167
+ const browserState = await getProcessLiveness(metadata.browserOwner);
3168
+ if (browserState === "dead") {
3169
+ await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
3170
+ force: true,
3171
+ recursive: true
3172
+ }).catch(() => void 0);
3173
+ return { kind: "missing" };
3174
+ }
3175
+ if (metadata.state === "ready") {
3176
+ return {
3177
+ kind: "ready",
3178
+ metadata
3179
+ };
3180
+ }
3181
+ const stateOwnerState = await getProcessLiveness(metadata.stateOwner);
3182
+ if (stateOwnerState === "dead") {
3183
+ const recoveredMetadata = {
3184
+ ...metadata,
3185
+ state: "ready"
3186
+ };
3187
+ await writeSharedSessionMetadata(persistentUserDataDir, recoveredMetadata);
3188
+ return {
3189
+ kind: "ready",
3190
+ metadata: recoveredMetadata
3191
+ };
3192
+ }
3193
+ return { kind: "wait" };
3194
+ }
3195
+ async function launchSharedSession(options) {
3196
+ const persistentUserDataDir = options.persistentProfile.userDataDir;
3197
+ await clearPersistentProfileSingletons(persistentUserDataDir);
3198
+ const debugPort = await reserveDebugPort();
3199
+ const launchArgs = buildRealBrowserLaunchArgs({
3200
+ debugPort,
3201
+ headless: options.headless,
3202
+ profileDirectory: options.profileDirectory,
3203
+ userDataDir: persistentUserDataDir
3204
+ });
3205
+ const processPolicy = getOwnedRealBrowserProcessPolicy();
3206
+ const processHandle = (0, import_node_child_process3.spawn)(options.executablePath, launchArgs, {
3207
+ detached: processPolicy.detached,
3208
+ stdio: "ignore"
3209
+ });
3210
+ if (processPolicy.shouldUnref) {
3211
+ processHandle.unref();
3212
+ }
3213
+ try {
3214
+ const browserOwner = await waitForSpawnedProcessOwner(
3215
+ processHandle.pid,
3216
+ options.timeoutMs
3217
+ );
3218
+ const metadata = {
3219
+ browserOwner,
3220
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3221
+ debugPort,
3222
+ executablePath: options.executablePath,
3223
+ headless: options.headless,
3224
+ persistentUserDataDir,
3225
+ profileDirectory: options.profileDirectory,
3226
+ sessionId: (0, import_node_crypto4.randomUUID)(),
3227
+ state: "launching",
3228
+ stateOwner: CURRENT_PROCESS_OWNER
3229
+ };
3230
+ await writeSharedSessionMetadata(persistentUserDataDir, metadata);
3231
+ return {
3232
+ launchedBrowserOwner: browserOwner,
3233
+ metadata
3234
+ };
3235
+ } catch (error) {
3236
+ await killSpawnedBrowserProcess(processHandle);
3237
+ await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
3238
+ force: true,
3239
+ recursive: true
3240
+ }).catch(() => void 0);
3241
+ throw error;
3242
+ }
3243
+ }
3244
+ async function cleanupFailedSharedSessionLaunch(reservation) {
3245
+ const shouldPreserveLiveBrowser = await withSharedSessionLock(
3246
+ reservation.metadata.persistentUserDataDir,
3247
+ async () => {
3248
+ const metadata = await readSharedSessionMetadata(
3249
+ reservation.metadata.persistentUserDataDir
3250
+ );
3251
+ if (metadata && metadata.sessionId === reservation.metadata.sessionId && processOwnersEqual(
3252
+ metadata.browserOwner,
3253
+ reservation.launchedBrowserOwner
3254
+ )) {
3255
+ if (await getProcessLiveness(metadata.browserOwner) !== "dead") {
3256
+ const readyMetadata = {
3257
+ ...metadata,
3258
+ state: "ready"
3259
+ };
3260
+ await writeSharedSessionMetadata(
3261
+ reservation.metadata.persistentUserDataDir,
3262
+ readyMetadata
3263
+ );
3264
+ return true;
3265
+ }
3266
+ await (0, import_promises5.rm)(
3267
+ buildSharedSessionDirPath(
3268
+ reservation.metadata.persistentUserDataDir
3269
+ ),
3270
+ {
3271
+ force: true,
3272
+ recursive: true
3273
+ }
3274
+ ).catch(() => void 0);
3275
+ }
3276
+ return false;
3277
+ }
3278
+ );
3279
+ if (shouldPreserveLiveBrowser) {
3280
+ return;
3281
+ }
3282
+ await killOwnedBrowserProcess(reservation.launchedBrowserOwner);
3283
+ await waitForProcessToExit(reservation.launchedBrowserOwner, 2e3);
3284
+ }
3285
+ async function cleanupFailedSharedSessionAttach(options) {
3286
+ const closePlan = await prepareSharedSessionCloseIfIdle(
3287
+ options.persistentUserDataDir,
3288
+ options.clientId,
3289
+ options.sessionId
3290
+ );
3291
+ if (!closePlan.closeBrowser) {
3292
+ return;
3293
+ }
3294
+ await closeSharedSessionBrowser(options.persistentUserDataDir, closePlan);
3295
+ }
3296
+ async function waitForSharedSessionReady(metadata, timeoutMs) {
3297
+ await resolveCdpWebSocketUrl(
3298
+ buildSharedSessionDiscoveryUrl(metadata.debugPort),
3299
+ timeoutMs
3300
+ );
3301
+ }
3302
+ function buildRealBrowserLaunchArgs(options) {
3303
+ const args = [
3304
+ `--user-data-dir=${options.userDataDir}`,
3305
+ `--profile-directory=${options.profileDirectory}`,
3306
+ `--remote-debugging-port=${options.debugPort}`,
3307
+ "--disable-blink-features=AutomationControlled"
3308
+ ];
3309
+ if (options.headless) {
3310
+ args.push("--headless=new");
3311
+ }
3312
+ return args;
3313
+ }
3314
+ async function requestBrowserShutdown(browser) {
3315
+ let session = null;
3316
+ try {
3317
+ session = await browser.newBrowserCDPSession();
3318
+ await session.send("Browser.close");
3319
+ } catch {
3320
+ } finally {
3321
+ await session?.detach().catch(() => void 0);
3322
+ }
3323
+ }
3324
+ async function killOwnedBrowserProcess(owner) {
3325
+ if (await getProcessLiveness(owner) === "dead") {
3326
+ return;
3327
+ }
3328
+ await killOwnedBrowserProcessByPid(owner.pid);
3329
+ }
3330
+ async function killSpawnedBrowserProcess(processHandle) {
3331
+ const pid = processHandle.pid;
3332
+ if (!pid || processHandle.exitCode !== null) {
3333
+ return;
3334
+ }
3335
+ await killOwnedBrowserProcessByPid(pid);
3336
+ await waitForPidToExit(pid, 2e3);
3337
+ }
3338
+ async function killOwnedBrowserProcessByPid(pid) {
3339
+ const processPolicy = getOwnedRealBrowserProcessPolicy();
3340
+ if (processPolicy.killStrategy === "taskkill") {
3341
+ await new Promise((resolve) => {
3342
+ const killer = (0, import_node_child_process3.spawn)(
3343
+ "taskkill",
3344
+ ["/pid", String(pid), "/t", "/f"],
3345
+ {
3346
+ stdio: "ignore"
3347
+ }
3348
+ );
3349
+ killer.on("error", () => resolve());
3350
+ killer.on("exit", () => resolve());
3351
+ });
3352
+ return;
3353
+ }
3354
+ if (processPolicy.killStrategy === "process-group") {
3355
+ try {
3356
+ process.kill(-pid, "SIGKILL");
3357
+ return;
3358
+ } catch {
3359
+ }
3360
+ }
3361
+ try {
3362
+ process.kill(pid, "SIGKILL");
3363
+ } catch {
3364
+ }
3365
+ }
3366
+ async function waitForProcessToExit(owner, timeoutMs) {
3367
+ const deadline = Date.now() + timeoutMs;
3368
+ while (Date.now() < deadline) {
3369
+ if (await getProcessLiveness(owner) === "dead") {
3370
+ return;
3371
+ }
3372
+ await sleep5(50);
3373
+ }
3374
+ }
3375
+ async function waitForPidToExit(pid, timeoutMs) {
3376
+ const deadline = Date.now() + timeoutMs;
3377
+ while (Date.now() < deadline) {
3378
+ if (!isProcessRunning(pid)) {
3379
+ return;
3380
+ }
3381
+ await sleep5(50);
3382
+ }
3383
+ }
3384
+ async function waitForSpawnedProcessOwner(pid, timeoutMs) {
3385
+ if (!pid || pid <= 0) {
3386
+ throw new Error("Chrome did not expose a child process id.");
3387
+ }
3388
+ const deadline = Date.now() + timeoutMs;
3389
+ while (Date.now() < deadline) {
3390
+ const owner = await readProcessOwner(pid);
3391
+ if (owner) {
3392
+ return owner;
3393
+ }
3394
+ await sleep5(50);
3395
+ }
3396
+ throw new Error(
3397
+ `Chrome process ${pid} did not report a stable process start time.`
3398
+ );
3399
+ }
3400
+ async function withSharedSessionLock(persistentUserDataDir, action) {
3401
+ return await withDirLock(
3402
+ buildSharedSessionLockPath(persistentUserDataDir),
3403
+ action
3404
+ );
3405
+ }
3406
+ async function registerSharedSessionClient(persistentUserDataDir, metadata) {
3407
+ const liveClients = await listLiveSharedSessionClients(persistentUserDataDir);
3408
+ const client = buildSharedSessionClientRegistration();
3409
+ await (0, import_promises5.mkdir)(buildSharedSessionClientsDirPath(persistentUserDataDir), {
3410
+ recursive: true
3411
+ });
3412
+ await (0, import_promises5.writeFile)(
3413
+ buildSharedSessionClientPath(persistentUserDataDir, client.clientId),
3414
+ JSON.stringify(client, null, 2),
3415
+ {
3416
+ flag: "wx"
3417
+ }
3418
+ );
3419
+ return {
3420
+ client,
3421
+ metadata,
3422
+ reuseExistingPage: liveClients.length === 0
3423
+ };
3424
+ }
3425
+ async function removeSharedSessionClientRegistration(persistentUserDataDir, clientId) {
3426
+ await (0, import_promises5.rm)(buildSharedSessionClientPath(persistentUserDataDir, clientId), {
3427
+ force: true
3428
+ }).catch(() => void 0);
3429
+ }
3430
+ async function listLiveSharedSessionClients(persistentUserDataDir) {
3431
+ const clientsDirPath = buildSharedSessionClientsDirPath(persistentUserDataDir);
3432
+ let entries;
3433
+ try {
3434
+ entries = await (0, import_promises5.readdir)(clientsDirPath, {
3435
+ encoding: "utf8",
3436
+ withFileTypes: true
3437
+ });
3438
+ } catch {
3439
+ return [];
3440
+ }
3441
+ const liveClients = [];
3442
+ for (const entry of entries) {
3443
+ if (!entry.isFile()) {
3444
+ continue;
3445
+ }
3446
+ const filePath = (0, import_node_path5.join)(clientsDirPath, entry.name);
3447
+ const registration = await readSharedSessionClientRegistration(filePath);
3448
+ if (!registration) {
3449
+ await (0, import_promises5.rm)(filePath, { force: true }).catch(() => void 0);
3450
+ continue;
3451
+ }
3452
+ if (await getProcessLiveness(registration.owner) === "dead") {
3453
+ await (0, import_promises5.rm)(filePath, { force: true }).catch(() => void 0);
3454
+ continue;
3455
+ }
3456
+ liveClients.push(registration);
3457
+ }
3458
+ return liveClients;
3459
+ }
3460
+ async function readSharedSessionClientRegistration(filePath) {
3461
+ try {
3462
+ const raw = await (0, import_promises5.readFile)(filePath, "utf8");
3463
+ return parseSharedSessionClientRegistration(JSON.parse(raw));
3464
+ } catch {
3465
+ return null;
3466
+ }
3467
+ }
3468
+ function buildSharedSessionClientRegistration() {
3469
+ return {
3470
+ clientId: (0, import_node_crypto4.randomUUID)(),
3471
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3472
+ owner: CURRENT_PROCESS_OWNER
3473
+ };
3474
+ }
3475
+ function parseSharedSessionClientRegistration(value) {
3476
+ if (!value || typeof value !== "object") {
3477
+ return null;
3478
+ }
3479
+ const parsed = value;
3480
+ const owner = parseProcessOwner(parsed.owner);
3481
+ if (!owner || typeof parsed.clientId !== "string" || typeof parsed.createdAt !== "string") {
3482
+ return null;
3483
+ }
3484
+ return {
3485
+ clientId: parsed.clientId,
3486
+ createdAt: parsed.createdAt,
3487
+ owner
3488
+ };
3489
+ }
3490
+ function assertSharedSessionCompatibility(metadata, options) {
3491
+ if (metadata.executablePath !== options.executablePath) {
3492
+ throw new Error(
3493
+ `Chrome profile "${options.profileDirectory}" is already running with executable "${metadata.executablePath}", not "${options.executablePath}".`
3494
+ );
3495
+ }
3496
+ if (metadata.headless !== options.headless) {
3497
+ throw new Error(
3498
+ `Chrome profile "${options.profileDirectory}" is already running with headless=${metadata.headless}, not ${options.headless}.`
3499
+ );
3500
+ }
3501
+ }
3502
+ async function prepareSharedSessionCloseIfIdle(persistentUserDataDir, clientId, sessionId) {
3503
+ return await withSharedSessionLock(persistentUserDataDir, async () => {
3504
+ const metadata = await readSharedSessionMetadata(persistentUserDataDir);
3505
+ await removeSharedSessionClientRegistration(
3506
+ persistentUserDataDir,
3507
+ clientId
3508
+ );
3509
+ if (!metadata || metadata.sessionId !== sessionId) {
3510
+ return {
3511
+ closeBrowser: false,
3512
+ sessionId
3513
+ };
3514
+ }
3515
+ const liveClients = await listLiveSharedSessionClients(
3516
+ persistentUserDataDir
3517
+ );
3518
+ if (liveClients.length > 0) {
3519
+ return {
3520
+ closeBrowser: false,
3521
+ sessionId: metadata.sessionId
3522
+ };
3523
+ }
3524
+ const closingMetadata = {
3525
+ ...metadata,
3526
+ state: "closing",
3527
+ stateOwner: CURRENT_PROCESS_OWNER
3528
+ };
3529
+ await writeSharedSessionMetadata(
3530
+ persistentUserDataDir,
3531
+ closingMetadata
3532
+ );
3533
+ return {
3534
+ browserOwner: closingMetadata.browserOwner,
3535
+ closeBrowser: true,
3536
+ sessionId: closingMetadata.sessionId
3537
+ };
3538
+ });
3539
+ }
3540
+ async function closeSharedSessionBrowser(persistentUserDataDir, closePlan, browser) {
3541
+ if (browser) {
3542
+ await requestBrowserShutdown(browser);
3543
+ await waitForProcessToExit(closePlan.browserOwner, 1e3);
3544
+ }
3545
+ if (await getProcessLiveness(closePlan.browserOwner) !== "dead") {
3546
+ await killOwnedBrowserProcess(closePlan.browserOwner);
3547
+ await waitForProcessToExit(closePlan.browserOwner, 2e3);
3548
+ }
3549
+ await finalizeSharedSessionClose(persistentUserDataDir, closePlan.sessionId);
3550
+ }
3551
+ async function finalizeSharedSessionClose(persistentUserDataDir, sessionId) {
3552
+ await withSharedSessionLock(persistentUserDataDir, async () => {
3553
+ const metadata = await readSharedSessionMetadata(persistentUserDataDir);
3554
+ if (!metadata || metadata.sessionId !== sessionId) {
3555
+ return;
3556
+ }
3557
+ const liveClients = await listLiveSharedSessionClients(
3558
+ persistentUserDataDir
3559
+ );
3560
+ if (liveClients.length > 0) {
3561
+ const readyMetadata = {
3562
+ ...metadata,
3563
+ state: "ready"
3564
+ };
3565
+ await writeSharedSessionMetadata(
3566
+ persistentUserDataDir,
3567
+ readyMetadata
3568
+ );
3569
+ return;
3570
+ }
3571
+ if (await getProcessLiveness(metadata.browserOwner) !== "dead") {
3572
+ const readyMetadata = {
3573
+ ...metadata,
3574
+ state: "ready"
3575
+ };
3576
+ await writeSharedSessionMetadata(
3577
+ persistentUserDataDir,
3578
+ readyMetadata
3579
+ );
3580
+ return;
3581
+ }
3582
+ await (0, import_promises5.rm)(buildSharedSessionDirPath(persistentUserDataDir), {
3583
+ force: true,
3584
+ recursive: true
3585
+ }).catch(() => void 0);
3586
+ });
3587
+ }
3588
+ function getPrimaryBrowserContext(browser) {
3589
+ const contexts = browser.contexts();
3590
+ if (contexts.length === 0) {
3591
+ throw new Error(
3592
+ "Connection succeeded but no browser contexts were exposed."
3593
+ );
3594
+ }
3595
+ return contexts[0];
3596
+ }
3597
+ async function getSharedSessionPage(context, reuseExistingPage) {
3598
+ if (reuseExistingPage) {
3599
+ return await getExistingPageOrCreate(context);
3600
+ }
3601
+ return await context.newPage();
3602
+ }
3603
+ async function getExistingPageOrCreate(context) {
3604
+ const existingPage = context.pages()[0];
3605
+ if (existingPage) {
3606
+ return existingPage;
3607
+ }
3608
+ return await context.newPage();
3609
+ }
3610
+ function buildSharedSessionDiscoveryUrl(debugPort) {
3611
+ return `http://127.0.0.1:${debugPort}`;
3612
+ }
3613
+ async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
3614
+ if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
3615
+ return cdpUrl;
3616
+ }
3617
+ const versionUrl = normalizeDiscoveryUrl(cdpUrl);
3618
+ const deadline = Date.now() + timeoutMs;
3619
+ let lastError = "CDP discovery did not respond.";
3620
+ while (Date.now() < deadline) {
3621
+ const remaining = Math.max(deadline - Date.now(), 1e3);
3622
+ try {
3623
+ const response = await fetch(versionUrl, {
3624
+ signal: AbortSignal.timeout(Math.min(remaining, 5e3))
3625
+ });
3626
+ if (!response.ok) {
3627
+ lastError = `${response.status} ${response.statusText}`;
3628
+ } else {
3629
+ const payload = await response.json();
3630
+ const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
3631
+ if (wsUrl && wsUrl.trim()) {
3632
+ return wsUrl;
3633
+ }
3634
+ lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
3635
+ }
3636
+ } catch (error) {
3637
+ lastError = error instanceof Error ? error.message : "Unknown error";
3638
+ }
3639
+ await sleep5(100);
2051
3640
  }
2052
- return isProcessRunning(owner.pid);
3641
+ throw new Error(
3642
+ `Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
3643
+ );
2053
3644
  }
2054
- function parseTempDirOwner(tempDirName, tempDirPrefix) {
2055
- const remainder = tempDirName.slice(tempDirPrefix.length);
2056
- const firstDashIndex = remainder.indexOf("-");
2057
- const secondDashIndex = firstDashIndex === -1 ? -1 : remainder.indexOf("-", firstDashIndex + 1);
2058
- if (firstDashIndex === -1 || secondDashIndex === -1) {
2059
- return null;
3645
+ function normalizeDiscoveryUrl(cdpUrl) {
3646
+ let parsed;
3647
+ try {
3648
+ parsed = new URL(cdpUrl);
3649
+ } catch {
3650
+ throw new Error(
3651
+ `Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
3652
+ );
2060
3653
  }
2061
- const pid = Number.parseInt(remainder.slice(0, firstDashIndex), 10);
2062
- const processStartedAtMs = Number.parseInt(
2063
- remainder.slice(firstDashIndex + 1, secondDashIndex),
2064
- 10
2065
- );
2066
- if (!Number.isInteger(pid) || pid <= 0) {
2067
- return null;
3654
+ if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
3655
+ return parsed;
2068
3656
  }
2069
- if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
2070
- return null;
3657
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
3658
+ throw new Error(
3659
+ `Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
3660
+ );
2071
3661
  }
2072
- return { pid, processStartedAtMs };
3662
+ const normalized = new URL(parsed.toString());
3663
+ normalized.pathname = "/json/version";
3664
+ normalized.search = "";
3665
+ normalized.hash = "";
3666
+ return normalized;
2073
3667
  }
2074
- function isProcessRunning(pid) {
2075
- try {
2076
- process.kill(pid, 0);
2077
- return true;
2078
- } catch (error) {
2079
- const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
2080
- return code !== "ESRCH";
2081
- }
3668
+ async function reserveDebugPort() {
3669
+ return await new Promise((resolve, reject) => {
3670
+ const server = (0, import_node_net.createServer)();
3671
+ server.unref();
3672
+ server.on("error", reject);
3673
+ server.listen(0, "127.0.0.1", () => {
3674
+ const address = server.address();
3675
+ if (!address || typeof address === "string") {
3676
+ server.close();
3677
+ reject(new Error("Failed to reserve a local debug port."));
3678
+ return;
3679
+ }
3680
+ server.close((error) => {
3681
+ if (error) {
3682
+ reject(error);
3683
+ return;
3684
+ }
3685
+ resolve(address.port);
3686
+ });
3687
+ });
3688
+ });
3689
+ }
3690
+ async function sleep5(ms) {
3691
+ await new Promise((resolve) => setTimeout(resolve, ms));
2082
3692
  }
2083
3693
 
2084
3694
  // src/browser/pool.ts
2085
3695
  var BrowserPool = class {
2086
3696
  browser = null;
2087
- cdpProxy = null;
2088
- launchedProcess = null;
2089
- managedUserDataDir = null;
2090
- persistentProfile = false;
3697
+ activeSessionClose = null;
3698
+ closeInFlight = null;
2091
3699
  defaults;
2092
3700
  constructor(defaults = {}) {
2093
3701
  this.defaults = defaults;
2094
3702
  }
2095
3703
  async launch(options = {}) {
2096
- if (this.browser || this.cdpProxy || this.launchedProcess || this.managedUserDataDir) {
3704
+ if (this.browser || this.activeSessionClose) {
2097
3705
  await this.close();
2098
3706
  }
2099
3707
  const mode = options.mode ?? this.defaults.mode ?? "chromium";
@@ -2140,30 +3748,26 @@ var BrowserPool = class {
2140
3748
  return this.launchSandbox(options);
2141
3749
  }
2142
3750
  async close() {
2143
- const browser = this.browser;
2144
- const cdpProxy = this.cdpProxy;
2145
- const launchedProcess = this.launchedProcess;
2146
- const managedUserDataDir = this.managedUserDataDir;
2147
- const persistentProfile = this.persistentProfile;
2148
- this.browser = null;
2149
- this.cdpProxy = null;
2150
- this.launchedProcess = null;
2151
- this.managedUserDataDir = null;
2152
- this.persistentProfile = false;
3751
+ if (this.closeInFlight) {
3752
+ await this.closeInFlight;
3753
+ return;
3754
+ }
3755
+ const closeOperation = this.closeCurrent();
3756
+ this.closeInFlight = closeOperation;
2153
3757
  try {
2154
- if (browser) {
2155
- await browser.close().catch(() => void 0);
2156
- }
3758
+ await closeOperation;
3759
+ this.browser = null;
3760
+ this.activeSessionClose = null;
2157
3761
  } finally {
2158
- cdpProxy?.close();
2159
- await killProcessTree(launchedProcess);
2160
- if (managedUserDataDir && !persistentProfile) {
2161
- await (0, import_promises2.rm)(managedUserDataDir, {
2162
- recursive: true,
2163
- force: true
2164
- }).catch(() => void 0);
2165
- }
3762
+ this.closeInFlight = null;
3763
+ }
3764
+ }
3765
+ async closeCurrent() {
3766
+ if (this.activeSessionClose) {
3767
+ await this.activeSessionClose();
3768
+ return;
2166
3769
  }
3770
+ await this.browser?.close().catch(() => void 0);
2167
3771
  }
2168
3772
  async connectToRunning(cdpUrl, timeout) {
2169
3773
  let browser = null;
@@ -2178,11 +3782,14 @@ var BrowserPool = class {
2178
3782
  }
2179
3783
  cdpProxy = new CDPProxy(browserWsUrl, targetId);
2180
3784
  const proxyWsUrl = await cdpProxy.start();
2181
- browser = await import_playwright.chromium.connectOverCDP(proxyWsUrl, {
3785
+ browser = await import_playwright2.chromium.connectOverCDP(proxyWsUrl, {
2182
3786
  timeout: timeout ?? 3e4
2183
3787
  });
2184
3788
  this.browser = browser;
2185
- this.cdpProxy = cdpProxy;
3789
+ this.activeSessionClose = async () => {
3790
+ await browser?.close().catch(() => void 0);
3791
+ cdpProxy?.close();
3792
+ };
2186
3793
  const { context, page } = await pickBrowserContextAndPage(browser);
2187
3794
  return { browser, context, page, isExternal: true };
2188
3795
  } catch (error) {
@@ -2191,7 +3798,7 @@ var BrowserPool = class {
2191
3798
  }
2192
3799
  cdpProxy?.close();
2193
3800
  this.browser = null;
2194
- this.cdpProxy = null;
3801
+ this.activeSessionClose = null;
2195
3802
  throw error;
2196
3803
  }
2197
3804
  }
@@ -2211,55 +3818,29 @@ var BrowserPool = class {
2211
3818
  sourceUserDataDir,
2212
3819
  profileDirectory
2213
3820
  );
2214
- await clearPersistentProfileSingletons(persistentProfile.userDataDir);
2215
- const debugPort = await reserveDebugPort();
2216
- const headless = resolveLaunchHeadless(
2217
- "real",
2218
- options.headless,
2219
- this.defaults.headless
2220
- );
2221
- const launchArgs = buildRealBrowserLaunchArgs({
2222
- userDataDir: persistentProfile.userDataDir,
3821
+ const sharedSession = await acquireSharedRealBrowserSession({
3822
+ executablePath,
3823
+ headless: resolveLaunchHeadless(
3824
+ "real",
3825
+ options.headless,
3826
+ this.defaults.headless
3827
+ ),
3828
+ initialUrl: options.initialUrl,
3829
+ persistentProfile,
2223
3830
  profileDirectory,
2224
- debugPort,
2225
- headless
2226
- });
2227
- const processHandle = (0, import_node_child_process.spawn)(executablePath, launchArgs, {
2228
- detached: process.platform !== "win32",
2229
- stdio: "ignore"
3831
+ timeoutMs: options.timeout ?? 3e4
2230
3832
  });
2231
- processHandle.unref();
2232
- let browser = null;
2233
- try {
2234
- const wsUrl = await resolveCdpWebSocketUrl(
2235
- `http://127.0.0.1:${debugPort}`,
2236
- options.timeout ?? 3e4
2237
- );
2238
- browser = await import_playwright.chromium.connectOverCDP(wsUrl, {
2239
- timeout: options.timeout ?? 3e4
2240
- });
2241
- const { context, page } = await createOwnedBrowserContextAndPage(
2242
- browser
2243
- );
2244
- if (options.initialUrl) {
2245
- await page.goto(options.initialUrl, {
2246
- waitUntil: "domcontentloaded",
2247
- timeout: options.timeout ?? 3e4
2248
- });
2249
- }
2250
- this.browser = browser;
2251
- this.launchedProcess = processHandle;
2252
- this.managedUserDataDir = persistentProfile.userDataDir;
2253
- this.persistentProfile = true;
2254
- return { browser, context, page, isExternal: false };
2255
- } catch (error) {
2256
- await browser?.close().catch(() => void 0);
2257
- await killProcessTree(processHandle);
2258
- throw error;
2259
- }
3833
+ this.browser = sharedSession.browser;
3834
+ this.activeSessionClose = sharedSession.close;
3835
+ return {
3836
+ browser: sharedSession.browser,
3837
+ context: sharedSession.context,
3838
+ page: sharedSession.page,
3839
+ isExternal: false
3840
+ };
2260
3841
  }
2261
3842
  async launchSandbox(options) {
2262
- const browser = await import_playwright.chromium.launch({
3843
+ const browser = await import_playwright2.chromium.launch({
2263
3844
  headless: resolveLaunchHeadless(
2264
3845
  "chromium",
2265
3846
  options.headless,
@@ -2272,11 +3853,14 @@ var BrowserPool = class {
2272
3853
  const context = await browser.newContext(options.context || {});
2273
3854
  const page = await context.newPage();
2274
3855
  this.browser = browser;
3856
+ this.activeSessionClose = async () => {
3857
+ await browser.close().catch(() => void 0);
3858
+ };
2275
3859
  return { browser, context, page, isExternal: false };
2276
3860
  }
2277
3861
  };
2278
3862
  async function pickBrowserContextAndPage(browser) {
2279
- const context = getPrimaryBrowserContext(browser);
3863
+ const context = getPrimaryBrowserContext2(browser);
2280
3864
  const page = await getAttachedPageOrCreate(context);
2281
3865
  return { context, page };
2282
3866
  }
@@ -2289,11 +3873,6 @@ function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
2289
3873
  }
2290
3874
  return mode === "real";
2291
3875
  }
2292
- async function createOwnedBrowserContextAndPage(browser) {
2293
- const context = getPrimaryBrowserContext(browser);
2294
- const page = await getExistingPageOrCreate(context);
2295
- return { context, page };
2296
- }
2297
3876
  async function getAttachedPageOrCreate(context) {
2298
3877
  const pages = context.pages();
2299
3878
  const inspectablePage = pages.find(
@@ -2308,14 +3887,7 @@ async function getAttachedPageOrCreate(context) {
2308
3887
  }
2309
3888
  return await context.newPage();
2310
3889
  }
2311
- async function getExistingPageOrCreate(context) {
2312
- const existingPage = context.pages()[0];
2313
- if (existingPage) {
2314
- return existingPage;
2315
- }
2316
- return await context.newPage();
2317
- }
2318
- function getPrimaryBrowserContext(browser) {
3890
+ function getPrimaryBrowserContext2(browser) {
2319
3891
  const contexts = browser.contexts();
2320
3892
  if (contexts.length === 0) {
2321
3893
  throw new Error(
@@ -2334,125 +3906,6 @@ function safePageUrl(page) {
2334
3906
  return "";
2335
3907
  }
2336
3908
  }
2337
- function normalizeDiscoveryUrl(cdpUrl) {
2338
- let parsed;
2339
- try {
2340
- parsed = new URL(cdpUrl);
2341
- } catch {
2342
- throw new Error(
2343
- `Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
2344
- );
2345
- }
2346
- if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
2347
- return parsed;
2348
- }
2349
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
2350
- throw new Error(
2351
- `Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
2352
- );
2353
- }
2354
- const normalized = new URL(parsed.toString());
2355
- normalized.pathname = "/json/version";
2356
- normalized.search = "";
2357
- normalized.hash = "";
2358
- return normalized;
2359
- }
2360
- async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
2361
- if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
2362
- return cdpUrl;
2363
- }
2364
- const versionUrl = normalizeDiscoveryUrl(cdpUrl);
2365
- const deadline = Date.now() + timeoutMs;
2366
- let lastError = "CDP discovery did not respond.";
2367
- while (Date.now() < deadline) {
2368
- const remaining = Math.max(deadline - Date.now(), 1e3);
2369
- try {
2370
- const response = await fetch(versionUrl, {
2371
- signal: AbortSignal.timeout(Math.min(remaining, 5e3))
2372
- });
2373
- if (!response.ok) {
2374
- lastError = `${response.status} ${response.statusText}`;
2375
- } else {
2376
- const payload = await response.json();
2377
- const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
2378
- if (wsUrl && wsUrl.trim()) {
2379
- return wsUrl;
2380
- }
2381
- lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
2382
- }
2383
- } catch (error) {
2384
- lastError = error instanceof Error ? error.message : "Unknown error";
2385
- }
2386
- await sleep(100);
2387
- }
2388
- throw new Error(
2389
- `Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
2390
- );
2391
- }
2392
- async function reserveDebugPort() {
2393
- return await new Promise((resolve, reject) => {
2394
- const server = (0, import_node_net.createServer)();
2395
- server.unref();
2396
- server.on("error", reject);
2397
- server.listen(0, "127.0.0.1", () => {
2398
- const address = server.address();
2399
- if (!address || typeof address === "string") {
2400
- server.close();
2401
- reject(new Error("Failed to reserve a local debug port."));
2402
- return;
2403
- }
2404
- server.close((error) => {
2405
- if (error) {
2406
- reject(error);
2407
- return;
2408
- }
2409
- resolve(address.port);
2410
- });
2411
- });
2412
- });
2413
- }
2414
- function buildRealBrowserLaunchArgs(options) {
2415
- const args = [
2416
- `--user-data-dir=${options.userDataDir}`,
2417
- `--profile-directory=${options.profileDirectory}`,
2418
- `--remote-debugging-port=${options.debugPort}`,
2419
- "--disable-blink-features=AutomationControlled"
2420
- ];
2421
- if (options.headless) {
2422
- args.push("--headless=new");
2423
- }
2424
- return args;
2425
- }
2426
- async function killProcessTree(processHandle) {
2427
- if (!processHandle || processHandle.pid == null || processHandle.exitCode !== null) {
2428
- return;
2429
- }
2430
- if (process.platform === "win32") {
2431
- await new Promise((resolve) => {
2432
- const killer = (0, import_node_child_process.spawn)(
2433
- "taskkill",
2434
- ["/pid", String(processHandle.pid), "/t", "/f"],
2435
- {
2436
- stdio: "ignore"
2437
- }
2438
- );
2439
- killer.on("error", () => resolve());
2440
- killer.on("exit", () => resolve());
2441
- });
2442
- return;
2443
- }
2444
- try {
2445
- process.kill(-processHandle.pid, "SIGKILL");
2446
- } catch {
2447
- try {
2448
- processHandle.kill("SIGKILL");
2449
- } catch {
2450
- }
2451
- }
2452
- }
2453
- async function sleep(ms) {
2454
- await new Promise((resolve) => setTimeout(resolve, ms));
2455
- }
2456
3909
 
2457
3910
  // src/navigation.ts
2458
3911
  var DEFAULT_TIMEOUT = 3e4;
@@ -2832,7 +4285,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
2832
4285
  TRANSIENT_CONTEXT_RETRY_DELAY_MS,
2833
4286
  Math.max(0, deadline - Date.now())
2834
4287
  );
2835
- await sleep2(retryDelay);
4288
+ await sleep6(retryDelay);
2836
4289
  }
2837
4290
  }
2838
4291
  }
@@ -2865,7 +4318,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
2865
4318
  () => ({ kind: "resolved" }),
2866
4319
  (error) => ({ kind: "rejected", error })
2867
4320
  );
2868
- const timeoutPromise = sleep2(
4321
+ const timeoutPromise = sleep6(
2869
4322
  timeout + FRAME_EVALUATE_GRACE_MS
2870
4323
  ).then(() => ({ kind: "timeout" }));
2871
4324
  const result = await Promise.race([
@@ -3007,7 +4460,7 @@ function isIgnorableFrameError(error) {
3007
4460
  const message = error.message;
3008
4461
  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");
3009
4462
  }
3010
- function sleep2(ms) {
4463
+ function sleep6(ms) {
3011
4464
  return new Promise((resolve) => {
3012
4465
  setTimeout(resolve, ms);
3013
4466
  });
@@ -7258,7 +8711,7 @@ async function closeTab(context, activePage, index) {
7258
8711
  }
7259
8712
 
7260
8713
  // src/actions/cookies.ts
7261
- var import_promises3 = require("fs/promises");
8714
+ var import_promises6 = require("fs/promises");
7262
8715
  async function getCookies(context, url) {
7263
8716
  return context.cookies(url ? [url] : void 0);
7264
8717
  }
@@ -7270,10 +8723,10 @@ async function clearCookies(context) {
7270
8723
  }
7271
8724
  async function exportCookies(context, filePath, url) {
7272
8725
  const cookies = await context.cookies(url ? [url] : void 0);
7273
- await (0, import_promises3.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
8726
+ await (0, import_promises6.writeFile)(filePath, JSON.stringify(cookies, null, 2), "utf-8");
7274
8727
  }
7275
8728
  async function importCookies(context, filePath) {
7276
- const raw = await (0, import_promises3.readFile)(filePath, "utf-8");
8729
+ const raw = await (0, import_promises6.readFile)(filePath, "utf-8");
7277
8730
  const cookies = JSON.parse(raw);
7278
8731
  await context.addCookies(cookies);
7279
8732
  }
@@ -7584,7 +9037,7 @@ var AdaptiveNetworkTracker = class {
7584
9037
  this.idleSince = 0;
7585
9038
  }
7586
9039
  const remaining = Math.max(1, options.deadline - now);
7587
- await sleep3(Math.min(NETWORK_POLL_MS, remaining));
9040
+ await sleep7(Math.min(NETWORK_POLL_MS, remaining));
7588
9041
  }
7589
9042
  }
7590
9043
  handleRequestStarted = (request) => {
@@ -7629,7 +9082,7 @@ var AdaptiveNetworkTracker = class {
7629
9082
  return false;
7630
9083
  }
7631
9084
  };
7632
- async function sleep3(ms) {
9085
+ async function sleep7(ms) {
7633
9086
  await new Promise((resolve) => {
7634
9087
  setTimeout(resolve, ms);
7635
9088
  });
@@ -9331,13 +10784,13 @@ function dedupeNewest(entries) {
9331
10784
  }
9332
10785
 
9333
10786
  // src/cloud/cdp-client.ts
9334
- var import_playwright2 = require("playwright");
10787
+ var import_playwright3 = require("playwright");
9335
10788
  var CloudCdpClient = class {
9336
10789
  async connect(args) {
9337
10790
  const endpoint = withTokenQuery(args.wsUrl, args.token);
9338
10791
  let browser;
9339
10792
  try {
9340
- browser = await import_playwright2.chromium.connectOverCDP(endpoint);
10793
+ browser = await import_playwright3.chromium.connectOverCDP(endpoint);
9341
10794
  } catch (error) {
9342
10795
  const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
9343
10796
  throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
@@ -11154,7 +12607,7 @@ async function executeAgentAction(page, action) {
11154
12607
  }
11155
12608
  case "wait": {
11156
12609
  const ms = numberOr(action.timeMs, action.time_ms, 1e3);
11157
- await sleep4(ms);
12610
+ await sleep8(ms);
11158
12611
  return;
11159
12612
  }
11160
12613
  case "goto": {
@@ -11319,7 +12772,7 @@ async function pressKeyCombo(page, combo) {
11319
12772
  }
11320
12773
  }
11321
12774
  }
11322
- function sleep4(ms) {
12775
+ function sleep8(ms) {
11323
12776
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
11324
12777
  }
11325
12778
 
@@ -11350,7 +12803,7 @@ var OpensteerCuaAgentHandler = class {
11350
12803
  if (isMutatingAgentAction(action)) {
11351
12804
  this.onMutatingAction?.(action);
11352
12805
  }
11353
- await sleep5(this.config.waitBetweenActionsMs);
12806
+ await sleep9(this.config.waitBetweenActionsMs);
11354
12807
  });
11355
12808
  try {
11356
12809
  const result = await this.client.execute({
@@ -11412,7 +12865,7 @@ var OpensteerCuaAgentHandler = class {
11412
12865
  await this.cursorController.preview({ x, y }, "agent");
11413
12866
  }
11414
12867
  };
11415
- function sleep5(ms) {
12868
+ function sleep9(ms) {
11416
12869
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
11417
12870
  }
11418
12871
 
@@ -11848,7 +13301,7 @@ var CursorController = class {
11848
13301
  for (const step of motion.points) {
11849
13302
  await this.renderer.move(step, this.style);
11850
13303
  if (motion.stepDelayMs > 0) {
11851
- await sleep6(motion.stepDelayMs);
13304
+ await sleep10(motion.stepDelayMs);
11852
13305
  }
11853
13306
  }
11854
13307
  if (shouldPulse(intent)) {
@@ -12006,7 +13459,7 @@ function clamp2(value, min, max) {
12006
13459
  function shouldPulse(intent) {
12007
13460
  return intent === "click" || intent === "dblclick" || intent === "rightclick" || intent === "agent";
12008
13461
  }
12009
- function sleep6(ms) {
13462
+ function sleep10(ms) {
12010
13463
  return new Promise((resolve) => setTimeout(resolve, ms));
12011
13464
  }
12012
13465
 
@@ -12480,15 +13933,22 @@ var Opensteer = class _Opensteer {
12480
13933
  }
12481
13934
  return;
12482
13935
  }
12483
- if (this.ownsBrowser) {
12484
- await this.pool.close();
12485
- }
12486
- this.browser = null;
12487
- this.pageRef = null;
12488
- this.contextRef = null;
12489
- this.ownsBrowser = false;
12490
- if (this.cursorController) {
12491
- await this.cursorController.dispose().catch(() => void 0);
13936
+ let closedOwnedBrowser = false;
13937
+ try {
13938
+ if (this.ownsBrowser) {
13939
+ await this.pool.close();
13940
+ closedOwnedBrowser = true;
13941
+ }
13942
+ } finally {
13943
+ this.browser = null;
13944
+ this.pageRef = null;
13945
+ this.contextRef = null;
13946
+ if (!this.ownsBrowser || closedOwnedBrowser) {
13947
+ this.ownsBrowser = false;
13948
+ }
13949
+ if (this.cursorController) {
13950
+ await this.cursorController.dispose().catch(() => void 0);
13951
+ }
12492
13952
  }
12493
13953
  }
12494
13954
  async syncLocalSelectorCacheToCloud() {
@@ -14816,19 +16276,19 @@ function buildLocalRunId(namespace) {
14816
16276
  }
14817
16277
 
14818
16278
  // src/browser/chromium-profile.ts
14819
- var import_node_util = require("util");
14820
- var import_node_child_process3 = require("child_process");
14821
- var import_node_crypto2 = require("crypto");
14822
- var import_promises4 = require("fs/promises");
14823
- var import_node_fs2 = require("fs");
14824
- var import_node_path2 = require("path");
16279
+ var import_node_util3 = require("util");
16280
+ var import_node_child_process5 = require("child_process");
16281
+ var import_node_crypto5 = require("crypto");
16282
+ var import_promises7 = require("fs/promises");
16283
+ var import_node_fs4 = require("fs");
16284
+ var import_node_path6 = require("path");
14825
16285
  var import_node_os2 = require("os");
14826
- var import_playwright3 = require("playwright");
16286
+ var import_playwright4 = require("playwright");
14827
16287
 
14828
16288
  // src/auth/keychain-store.ts
14829
- var import_node_child_process2 = require("child_process");
16289
+ var import_node_child_process4 = require("child_process");
14830
16290
  function commandExists(command) {
14831
- const result = (0, import_node_child_process2.spawnSync)(command, ["--help"], {
16291
+ const result = (0, import_node_child_process4.spawnSync)(command, ["--help"], {
14832
16292
  encoding: "utf8",
14833
16293
  stdio: "ignore"
14834
16294
  });
@@ -14867,7 +16327,7 @@ function createMacosSecurityStore() {
14867
16327
  return {
14868
16328
  backend: "macos-security",
14869
16329
  get(service, account) {
14870
- const result = (0, import_node_child_process2.spawnSync)(
16330
+ const result = (0, import_node_child_process4.spawnSync)(
14871
16331
  "security",
14872
16332
  ["find-generic-password", "-s", service, "-a", account, "-w"],
14873
16333
  { encoding: "utf8" }
@@ -14889,14 +16349,14 @@ function createMacosSecurityStore() {
14889
16349
  "-w",
14890
16350
  secret
14891
16351
  ];
14892
- const result = (0, import_node_child_process2.spawnSync)("security", args, { encoding: "utf8" });
16352
+ const result = (0, import_node_child_process4.spawnSync)("security", args, { encoding: "utf8" });
14893
16353
  if (commandFailed(result)) {
14894
16354
  throw buildCommandError("security", args, result);
14895
16355
  }
14896
16356
  },
14897
16357
  delete(service, account) {
14898
16358
  const args = ["delete-generic-password", "-s", service, "-a", account];
14899
- const result = (0, import_node_child_process2.spawnSync)("security", args, { encoding: "utf8" });
16359
+ const result = (0, import_node_child_process4.spawnSync)("security", args, { encoding: "utf8" });
14900
16360
  if (commandFailed(result)) {
14901
16361
  return;
14902
16362
  }
@@ -14907,7 +16367,7 @@ function createLinuxSecretToolStore() {
14907
16367
  return {
14908
16368
  backend: "linux-secret-tool",
14909
16369
  get(service, account) {
14910
- const result = (0, import_node_child_process2.spawnSync)(
16370
+ const result = (0, import_node_child_process4.spawnSync)(
14911
16371
  "secret-tool",
14912
16372
  ["lookup", "service", service, "account", account],
14913
16373
  {
@@ -14930,7 +16390,7 @@ function createLinuxSecretToolStore() {
14930
16390
  "account",
14931
16391
  account
14932
16392
  ];
14933
- const result = (0, import_node_child_process2.spawnSync)("secret-tool", args, {
16393
+ const result = (0, import_node_child_process4.spawnSync)("secret-tool", args, {
14934
16394
  encoding: "utf8",
14935
16395
  input: secret
14936
16396
  });
@@ -14940,7 +16400,7 @@ function createLinuxSecretToolStore() {
14940
16400
  },
14941
16401
  delete(service, account) {
14942
16402
  const args = ["clear", "service", service, "account", account];
14943
- (0, import_node_child_process2.spawnSync)("secret-tool", args, {
16403
+ (0, import_node_child_process4.spawnSync)("secret-tool", args, {
14944
16404
  encoding: "utf8"
14945
16405
  });
14946
16406
  }
@@ -14963,7 +16423,7 @@ function createKeychainStore() {
14963
16423
  }
14964
16424
 
14965
16425
  // src/browser/chromium-profile.ts
14966
- var execFileAsync = (0, import_node_util.promisify)(import_node_child_process3.execFile);
16426
+ var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process5.execFile);
14967
16427
  var CHROMIUM_EPOCH_MICROS = 11644473600000000n;
14968
16428
  var AES_BLOCK_BYTES = 16;
14969
16429
  var MAC_KEY_ITERATIONS = 1003;
@@ -15022,20 +16482,20 @@ var CHROMIUM_BRANDS = [
15022
16482
  ];
15023
16483
  function directoryExists(filePath) {
15024
16484
  try {
15025
- return (0, import_node_fs2.statSync)(filePath).isDirectory();
16485
+ return (0, import_node_fs4.statSync)(filePath).isDirectory();
15026
16486
  } catch {
15027
16487
  return false;
15028
16488
  }
15029
16489
  }
15030
16490
  function fileExists(filePath) {
15031
16491
  try {
15032
- return (0, import_node_fs2.statSync)(filePath).isFile();
16492
+ return (0, import_node_fs4.statSync)(filePath).isFile();
15033
16493
  } catch {
15034
16494
  return false;
15035
16495
  }
15036
16496
  }
15037
16497
  function resolveCookieDbPath(profileDir) {
15038
- const candidates = [(0, import_node_path2.join)(profileDir, "Network", "Cookies"), (0, import_node_path2.join)(profileDir, "Cookies")];
16498
+ const candidates = [(0, import_node_path6.join)(profileDir, "Network", "Cookies"), (0, import_node_path6.join)(profileDir, "Cookies")];
15039
16499
  for (const candidate of candidates) {
15040
16500
  if (fileExists(candidate)) {
15041
16501
  return candidate;
@@ -15044,10 +16504,10 @@ function resolveCookieDbPath(profileDir) {
15044
16504
  return null;
15045
16505
  }
15046
16506
  async function selectProfileDirFromUserDataDir(userDataDir) {
15047
- const entries = await (0, import_promises4.readdir)(userDataDir, {
16507
+ const entries = await (0, import_promises7.readdir)(userDataDir, {
15048
16508
  withFileTypes: true
15049
16509
  }).catch(() => []);
15050
- const candidates = entries.filter((entry) => entry.isDirectory()).map((entry) => (0, import_node_path2.join)(userDataDir, entry.name)).filter((entryPath) => resolveCookieDbPath(entryPath));
16510
+ const candidates = entries.filter((entry) => entry.isDirectory()).map((entry) => (0, import_node_path6.join)(userDataDir, entry.name)).filter((entryPath) => resolveCookieDbPath(entryPath));
15051
16511
  return candidates;
15052
16512
  }
15053
16513
  async function resolveChromiumProfileLocation(inputPath) {
@@ -15055,16 +16515,16 @@ async function resolveChromiumProfileLocation(inputPath) {
15055
16515
  if (!expandedPath) {
15056
16516
  throw new Error("Profile path cannot be empty.");
15057
16517
  }
15058
- if (fileExists(expandedPath) && (0, import_node_path2.basename)(expandedPath) === "Cookies") {
15059
- const directParent = (0, import_node_path2.dirname)(expandedPath);
15060
- const profileDir = (0, import_node_path2.basename)(directParent) === "Network" ? (0, import_node_path2.dirname)(directParent) : directParent;
15061
- const userDataDir = (0, import_node_path2.dirname)(profileDir);
16518
+ if (fileExists(expandedPath) && (0, import_node_path6.basename)(expandedPath) === "Cookies") {
16519
+ const directParent = (0, import_node_path6.dirname)(expandedPath);
16520
+ const profileDir = (0, import_node_path6.basename)(directParent) === "Network" ? (0, import_node_path6.dirname)(directParent) : directParent;
16521
+ const userDataDir = (0, import_node_path6.dirname)(profileDir);
15062
16522
  return {
15063
16523
  userDataDir,
15064
16524
  profileDir,
15065
- profileDirectory: (0, import_node_path2.basename)(profileDir),
16525
+ profileDirectory: (0, import_node_path6.basename)(profileDir),
15066
16526
  cookieDbPath: expandedPath,
15067
- localStatePath: fileExists((0, import_node_path2.join)(userDataDir, "Local State")) ? (0, import_node_path2.join)(userDataDir, "Local State") : null
16527
+ localStatePath: fileExists((0, import_node_path6.join)(userDataDir, "Local State")) ? (0, import_node_path6.join)(userDataDir, "Local State") : null
15068
16528
  };
15069
16529
  }
15070
16530
  if (fileExists(expandedPath)) {
@@ -15079,16 +16539,16 @@ async function resolveChromiumProfileLocation(inputPath) {
15079
16539
  }
15080
16540
  const directCookieDb = resolveCookieDbPath(expandedPath);
15081
16541
  if (directCookieDb) {
15082
- const userDataDir = (0, import_node_path2.dirname)(expandedPath);
16542
+ const userDataDir = (0, import_node_path6.dirname)(expandedPath);
15083
16543
  return {
15084
16544
  userDataDir,
15085
16545
  profileDir: expandedPath,
15086
- profileDirectory: (0, import_node_path2.basename)(expandedPath),
16546
+ profileDirectory: (0, import_node_path6.basename)(expandedPath),
15087
16547
  cookieDbPath: directCookieDb,
15088
- localStatePath: fileExists((0, import_node_path2.join)(userDataDir, "Local State")) ? (0, import_node_path2.join)(userDataDir, "Local State") : null
16548
+ localStatePath: fileExists((0, import_node_path6.join)(userDataDir, "Local State")) ? (0, import_node_path6.join)(userDataDir, "Local State") : null
15089
16549
  };
15090
16550
  }
15091
- const localStatePath = (0, import_node_path2.join)(expandedPath, "Local State");
16551
+ const localStatePath = (0, import_node_path6.join)(expandedPath, "Local State");
15092
16552
  if (!fileExists(localStatePath)) {
15093
16553
  throw new Error(
15094
16554
  `Unsupported profile source "${inputPath}". Pass a Chromium profile directory, user-data dir, or Cookies database path.`
@@ -15101,7 +16561,7 @@ async function resolveChromiumProfileLocation(inputPath) {
15101
16561
  );
15102
16562
  }
15103
16563
  if (profileDirs.length > 1) {
15104
- const candidates = profileDirs.map((entry) => (0, import_node_path2.basename)(entry)).join(", ");
16564
+ const candidates = profileDirs.map((entry) => (0, import_node_path6.basename)(entry)).join(", ");
15105
16565
  throw new Error(
15106
16566
  `"${inputPath}" contains multiple Chromium profiles (${candidates}). Pass a specific profile directory such as "${profileDirs[0]}".`
15107
16567
  );
@@ -15116,7 +16576,7 @@ async function resolveChromiumProfileLocation(inputPath) {
15116
16576
  return {
15117
16577
  userDataDir: expandedPath,
15118
16578
  profileDir: selectedProfileDir,
15119
- profileDirectory: (0, import_node_path2.basename)(selectedProfileDir),
16579
+ profileDirectory: (0, import_node_path6.basename)(selectedProfileDir),
15120
16580
  cookieDbPath,
15121
16581
  localStatePath
15122
16582
  };
@@ -15131,25 +16591,25 @@ function detectChromiumBrand(location) {
15131
16591
  return DEFAULT_CHROMIUM_BRAND;
15132
16592
  }
15133
16593
  async function createSqliteSnapshot(dbPath) {
15134
- const snapshotDir = await (0, import_promises4.mkdtemp)((0, import_node_path2.join)((0, import_node_os2.tmpdir)(), "opensteer-cookie-db-"));
15135
- const snapshotPath = (0, import_node_path2.join)(snapshotDir, "Cookies");
15136
- await (0, import_promises4.copyFile)(dbPath, snapshotPath);
16594
+ const snapshotDir = await (0, import_promises7.mkdtemp)((0, import_node_path6.join)((0, import_node_os2.tmpdir)(), "opensteer-cookie-db-"));
16595
+ const snapshotPath = (0, import_node_path6.join)(snapshotDir, "Cookies");
16596
+ await (0, import_promises7.copyFile)(dbPath, snapshotPath);
15137
16597
  for (const suffix of ["-wal", "-shm", "-journal"]) {
15138
16598
  const source = `${dbPath}${suffix}`;
15139
- if (!(0, import_node_fs2.existsSync)(source)) {
16599
+ if (!(0, import_node_fs4.existsSync)(source)) {
15140
16600
  continue;
15141
16601
  }
15142
- await (0, import_promises4.copyFile)(source, `${snapshotPath}${suffix}`);
16602
+ await (0, import_promises7.copyFile)(source, `${snapshotPath}${suffix}`);
15143
16603
  }
15144
16604
  return {
15145
16605
  snapshotPath,
15146
16606
  cleanup: async () => {
15147
- await (0, import_promises4.rm)(snapshotDir, { recursive: true, force: true });
16607
+ await (0, import_promises7.rm)(snapshotDir, { recursive: true, force: true });
15148
16608
  }
15149
16609
  };
15150
16610
  }
15151
16611
  async function querySqliteJson(dbPath, query) {
15152
- const result = await execFileAsync("sqlite3", ["-json", dbPath, query], {
16612
+ const result = await execFileAsync3("sqlite3", ["-json", dbPath, query], {
15153
16613
  encoding: "utf8",
15154
16614
  maxBuffer: 64 * 1024 * 1024
15155
16615
  });
@@ -15190,7 +16650,7 @@ function stripDomainHashPrefix(buffer, hostKey) {
15190
16650
  if (buffer.length < 32) {
15191
16651
  return buffer;
15192
16652
  }
15193
- const domainHash = (0, import_node_crypto2.createHash)("sha256").update(hostKey, "utf8").digest();
16653
+ const domainHash = (0, import_node_crypto5.createHash)("sha256").update(hostKey, "utf8").digest();
15194
16654
  if (buffer.subarray(0, 32).equals(domainHash)) {
15195
16655
  return buffer.subarray(32);
15196
16656
  }
@@ -15199,7 +16659,7 @@ function stripDomainHashPrefix(buffer, hostKey) {
15199
16659
  function decryptChromiumAes128CbcValue(encryptedValue, key, hostKey) {
15200
16660
  const ciphertext = encryptedValue.length > 3 && encryptedValue[0] === 118 && encryptedValue[1] === 49 && (encryptedValue[2] === 48 || encryptedValue[2] === 49) ? encryptedValue.subarray(3) : encryptedValue;
15201
16661
  const iv = Buffer.alloc(AES_BLOCK_BYTES, " ");
15202
- const decipher = (0, import_node_crypto2.createDecipheriv)("aes-128-cbc", key, iv);
16662
+ const decipher = (0, import_node_crypto5.createDecipheriv)("aes-128-cbc", key, iv);
15203
16663
  const plaintext = Buffer.concat([
15204
16664
  decipher.update(ciphertext),
15205
16665
  decipher.final()
@@ -15212,7 +16672,7 @@ function decryptChromiumAes256GcmValue(encryptedValue, key) {
15212
16672
  const nonce = encryptedValue.subarray(3, 15);
15213
16673
  const ciphertext = encryptedValue.subarray(15, encryptedValue.length - 16);
15214
16674
  const authTag = encryptedValue.subarray(encryptedValue.length - 16);
15215
- const decipher = (0, import_node_crypto2.createDecipheriv)("aes-256-gcm", key, nonce);
16675
+ const decipher = (0, import_node_crypto5.createDecipheriv)("aes-256-gcm", key, nonce);
15216
16676
  decipher.setAuthTag(authTag);
15217
16677
  return Buffer.concat([
15218
16678
  decipher.update(ciphertext),
@@ -15229,7 +16689,7 @@ async function dpapiUnprotect(buffer) {
15229
16689
  ")",
15230
16690
  "[Convert]::ToBase64String($plainBytes)"
15231
16691
  ].join("\n");
15232
- const { stdout } = await execFileAsync(
16692
+ const { stdout } = await execFileAsync3(
15233
16693
  "powershell.exe",
15234
16694
  ["-NoProfile", "-NonInteractive", "-Command", script],
15235
16695
  {
@@ -15249,7 +16709,7 @@ async function buildChromiumDecryptor(location) {
15249
16709
  `Unable to read ${brand.macService} from macOS Keychain.`
15250
16710
  );
15251
16711
  }
15252
- const key = (0, import_node_crypto2.pbkdf2Sync)(password, KEY_SALT, MAC_KEY_ITERATIONS, KEY_LENGTH, "sha1");
16712
+ const key = (0, import_node_crypto5.pbkdf2Sync)(password, KEY_SALT, MAC_KEY_ITERATIONS, KEY_LENGTH, "sha1");
15253
16713
  return async (row) => decryptChromiumAes128CbcValue(
15254
16714
  Buffer.from(row.encrypted_value || "", "hex"),
15255
16715
  key,
@@ -15260,7 +16720,7 @@ async function buildChromiumDecryptor(location) {
15260
16720
  const brand = detectChromiumBrand(location);
15261
16721
  const keychainStore = createKeychainStore();
15262
16722
  const password = keychainStore?.get(brand.macService, brand.macAccount) ?? brand.linuxApplications.map((application) => keychainStore?.get(application, application) ?? null).find(Boolean) ?? null;
15263
- const key = (0, import_node_crypto2.pbkdf2Sync)(
16723
+ const key = (0, import_node_crypto5.pbkdf2Sync)(
15264
16724
  password || "peanuts",
15265
16725
  KEY_SALT,
15266
16726
  LINUX_KEY_ITERATIONS,
@@ -15280,7 +16740,7 @@ async function buildChromiumDecryptor(location) {
15280
16740
  );
15281
16741
  }
15282
16742
  const localState = JSON.parse(
15283
- await (0, import_promises4.readFile)(location.localStatePath, "utf8")
16743
+ await (0, import_promises7.readFile)(location.localStatePath, "utf8")
15284
16744
  );
15285
16745
  const encryptedKeyBase64 = localState.os_crypt?.encrypted_key;
15286
16746
  if (!encryptedKeyBase64) {
@@ -15374,22 +16834,22 @@ async function loadCookiesFromSqlite(location) {
15374
16834
  }
15375
16835
  }
15376
16836
  async function loadCookiesFromBrowserSnapshot(location, options) {
15377
- const snapshotRootDir = await (0, import_promises4.mkdtemp)((0, import_node_path2.join)((0, import_node_os2.tmpdir)(), "opensteer-profile-"));
15378
- const snapshotProfileDir = (0, import_node_path2.join)(
16837
+ const snapshotRootDir = await (0, import_promises7.mkdtemp)((0, import_node_path6.join)((0, import_node_os2.tmpdir)(), "opensteer-profile-"));
16838
+ const snapshotProfileDir = (0, import_node_path6.join)(
15379
16839
  snapshotRootDir,
15380
- (0, import_node_path2.basename)(location.profileDir)
16840
+ (0, import_node_path6.basename)(location.profileDir)
15381
16841
  );
15382
16842
  let context = null;
15383
16843
  try {
15384
- await (0, import_promises4.cp)(location.profileDir, snapshotProfileDir, {
16844
+ await (0, import_promises7.cp)(location.profileDir, snapshotProfileDir, {
15385
16845
  recursive: true
15386
16846
  });
15387
16847
  if (location.localStatePath) {
15388
- await (0, import_promises4.copyFile)(location.localStatePath, (0, import_node_path2.join)(snapshotRootDir, "Local State"));
16848
+ await (0, import_promises7.copyFile)(location.localStatePath, (0, import_node_path6.join)(snapshotRootDir, "Local State"));
15389
16849
  }
15390
16850
  const brand = detectChromiumBrand(location);
15391
- const args = [`--profile-directory=${(0, import_node_path2.basename)(snapshotProfileDir)}`];
15392
- context = await import_playwright3.chromium.launchPersistentContext(snapshotRootDir, {
16851
+ const args = [`--profile-directory=${(0, import_node_path6.basename)(snapshotProfileDir)}`];
16852
+ context = await import_playwright4.chromium.launchPersistentContext(snapshotRootDir, {
15393
16853
  channel: brand.playwrightChannel,
15394
16854
  headless: options.headless ?? true,
15395
16855
  timeout: options.timeout ?? 12e4,
@@ -15398,7 +16858,7 @@ async function loadCookiesFromBrowserSnapshot(location, options) {
15398
16858
  return await context.cookies();
15399
16859
  } finally {
15400
16860
  await context?.close().catch(() => void 0);
15401
- await (0, import_promises4.rm)(snapshotRootDir, { recursive: true, force: true });
16861
+ await (0, import_promises7.rm)(snapshotRootDir, { recursive: true, force: true });
15402
16862
  }
15403
16863
  }
15404
16864
  function isMissingSqliteBinary(error) {
@@ -15641,10 +17101,10 @@ function toResolvedCloudCredential(source, credential) {
15641
17101
  }
15642
17102
 
15643
17103
  // src/auth/machine-credential-store.ts
15644
- var import_node_crypto3 = require("crypto");
15645
- var import_node_fs3 = __toESM(require("fs"), 1);
17104
+ var import_node_crypto6 = require("crypto");
17105
+ var import_node_fs5 = __toESM(require("fs"), 1);
15646
17106
  var import_node_os3 = __toESM(require("os"), 1);
15647
- var import_node_path3 = __toESM(require("path"), 1);
17107
+ var import_node_path7 = __toESM(require("path"), 1);
15648
17108
  var METADATA_VERSION = 2;
15649
17109
  var ACTIVE_TARGET_VERSION = 2;
15650
17110
  var KEYCHAIN_SERVICE = "com.opensteer.cli.cloud";
@@ -15659,7 +17119,7 @@ var MachineCredentialStore = class {
15659
17119
  const appName = options.appName || "opensteer";
15660
17120
  const env = options.env ?? process.env;
15661
17121
  const configDir = resolveConfigDir(appName, env);
15662
- this.authDir = import_node_path3.default.join(configDir, "auth");
17122
+ this.authDir = import_node_path7.default.join(configDir, "auth");
15663
17123
  this.warn = options.warn ?? (() => void 0);
15664
17124
  }
15665
17125
  readCloudCredential(target) {
@@ -15789,18 +17249,18 @@ function createMachineCredentialStore(options = {}) {
15789
17249
  }
15790
17250
  function resolveCredentialSlot(authDir, target) {
15791
17251
  const normalizedBaseUrl = normalizeCredentialUrl(target.baseUrl);
15792
- const storageKey = (0, import_node_crypto3.createHash)("sha256").update(normalizedBaseUrl).digest("hex").slice(0, 24);
17252
+ const storageKey = (0, import_node_crypto6.createHash)("sha256").update(normalizedBaseUrl).digest("hex").slice(0, 24);
15793
17253
  return {
15794
17254
  keychainAccount: `${KEYCHAIN_ACCOUNT_PREFIX}${storageKey}`,
15795
- metadataPath: import_node_path3.default.join(authDir, `cli-login.${storageKey}.json`),
15796
- fallbackSecretPath: import_node_path3.default.join(
17255
+ metadataPath: import_node_path7.default.join(authDir, `cli-login.${storageKey}.json`),
17256
+ fallbackSecretPath: import_node_path7.default.join(
15797
17257
  authDir,
15798
17258
  `cli-login.${storageKey}.secret.json`
15799
17259
  )
15800
17260
  };
15801
17261
  }
15802
17262
  function resolveActiveTargetPath(authDir) {
15803
- return import_node_path3.default.join(authDir, ACTIVE_TARGET_FILE_NAME);
17263
+ return import_node_path7.default.join(authDir, ACTIVE_TARGET_FILE_NAME);
15804
17264
  }
15805
17265
  function matchesCredentialTarget(value, target) {
15806
17266
  return normalizeCredentialUrl(value.baseUrl) === normalizeCredentialUrl(target.baseUrl);
@@ -15819,36 +17279,36 @@ function normalizeCloudCredentialTarget(target) {
15819
17279
  }
15820
17280
  function resolveConfigDir(appName, env) {
15821
17281
  if (process.platform === "win32") {
15822
- const appData = env.APPDATA?.trim() || import_node_path3.default.join(import_node_os3.default.homedir(), "AppData", "Roaming");
15823
- return import_node_path3.default.join(appData, appName);
17282
+ const appData = env.APPDATA?.trim() || import_node_path7.default.join(import_node_os3.default.homedir(), "AppData", "Roaming");
17283
+ return import_node_path7.default.join(appData, appName);
15824
17284
  }
15825
17285
  if (process.platform === "darwin") {
15826
- return import_node_path3.default.join(
17286
+ return import_node_path7.default.join(
15827
17287
  import_node_os3.default.homedir(),
15828
17288
  "Library",
15829
17289
  "Application Support",
15830
17290
  appName
15831
17291
  );
15832
17292
  }
15833
- const xdgConfigHome = env.XDG_CONFIG_HOME?.trim() || import_node_path3.default.join(import_node_os3.default.homedir(), ".config");
15834
- return import_node_path3.default.join(xdgConfigHome, appName);
17293
+ const xdgConfigHome = env.XDG_CONFIG_HOME?.trim() || import_node_path7.default.join(import_node_os3.default.homedir(), ".config");
17294
+ return import_node_path7.default.join(xdgConfigHome, appName);
15835
17295
  }
15836
17296
  function ensureDirectory(directoryPath) {
15837
- import_node_fs3.default.mkdirSync(directoryPath, { recursive: true, mode: 448 });
17297
+ import_node_fs5.default.mkdirSync(directoryPath, { recursive: true, mode: 448 });
15838
17298
  }
15839
17299
  function removeFileIfExists(filePath) {
15840
17300
  try {
15841
- import_node_fs3.default.rmSync(filePath, { force: true });
17301
+ import_node_fs5.default.rmSync(filePath, { force: true });
15842
17302
  } catch {
15843
17303
  return;
15844
17304
  }
15845
17305
  }
15846
17306
  function readMetadata(filePath) {
15847
- if (!import_node_fs3.default.existsSync(filePath)) {
17307
+ if (!import_node_fs5.default.existsSync(filePath)) {
15848
17308
  return null;
15849
17309
  }
15850
17310
  try {
15851
- const raw = import_node_fs3.default.readFileSync(filePath, "utf8");
17311
+ const raw = import_node_fs5.default.readFileSync(filePath, "utf8");
15852
17312
  const parsed = JSON.parse(raw);
15853
17313
  if (parsed.version !== METADATA_VERSION) return null;
15854
17314
  if (parsed.secretBackend !== "keychain" && parsed.secretBackend !== "file") {
@@ -15875,11 +17335,11 @@ function readMetadata(filePath) {
15875
17335
  }
15876
17336
  }
15877
17337
  function readActiveCloudTargetMetadata(filePath) {
15878
- if (!import_node_fs3.default.existsSync(filePath)) {
17338
+ if (!import_node_fs5.default.existsSync(filePath)) {
15879
17339
  return null;
15880
17340
  }
15881
17341
  try {
15882
- const raw = import_node_fs3.default.readFileSync(filePath, "utf8");
17342
+ const raw = import_node_fs5.default.readFileSync(filePath, "utf8");
15883
17343
  const parsed = JSON.parse(raw);
15884
17344
  if (parsed.version !== ACTIVE_TARGET_VERSION) {
15885
17345
  return null;
@@ -15909,22 +17369,22 @@ function parseSecretPayload(raw) {
15909
17369
  }
15910
17370
  }
15911
17371
  function readSecretFile(filePath) {
15912
- if (!import_node_fs3.default.existsSync(filePath)) {
17372
+ if (!import_node_fs5.default.existsSync(filePath)) {
15913
17373
  return null;
15914
17374
  }
15915
17375
  try {
15916
- return parseSecretPayload(import_node_fs3.default.readFileSync(filePath, "utf8"));
17376
+ return parseSecretPayload(import_node_fs5.default.readFileSync(filePath, "utf8"));
15917
17377
  } catch {
15918
17378
  return null;
15919
17379
  }
15920
17380
  }
15921
17381
  function writeJsonFile(filePath, value, options = {}) {
15922
- import_node_fs3.default.writeFileSync(filePath, JSON.stringify(value, null, 2), {
17382
+ import_node_fs5.default.writeFileSync(filePath, JSON.stringify(value, null, 2), {
15923
17383
  encoding: "utf8",
15924
17384
  mode: options.mode ?? 384
15925
17385
  });
15926
17386
  if (typeof options.mode === "number") {
15927
- import_node_fs3.default.chmodSync(filePath, options.mode);
17387
+ import_node_fs5.default.chmodSync(filePath, options.mode);
15928
17388
  }
15929
17389
  }
15930
17390
 
@@ -16258,7 +17718,7 @@ async function ensureCloudCredentialsForCommand(options) {
16258
17718
  const writeProgress = options.writeProgress ?? options.writeStdout ?? ((message) => process.stdout.write(message));
16259
17719
  const writeStderr = options.writeStderr ?? ((message) => process.stderr.write(message));
16260
17720
  const fetchFn = options.fetchFn ?? fetch;
16261
- const sleep7 = options.sleep ?? (async (ms) => {
17721
+ const sleep11 = options.sleep ?? (async (ms) => {
16262
17722
  await new Promise((resolve) => setTimeout(resolve, ms));
16263
17723
  });
16264
17724
  const now = options.now ?? Date.now;
@@ -16302,7 +17762,7 @@ async function ensureCloudCredentialsForCommand(options) {
16302
17762
  fetchFn,
16303
17763
  writeProgress,
16304
17764
  openExternalUrl,
16305
- sleep: sleep7,
17765
+ sleep: sleep11,
16306
17766
  now,
16307
17767
  openBrowser: true
16308
17768
  });
@@ -16714,7 +18174,7 @@ function createDefaultDeps() {
16714
18174
  loadLocalProfileCookies: (profileDir, options) => loadCookiesFromLocalProfileDir(profileDir, options),
16715
18175
  isInteractive: () => Boolean(process.stdin.isTTY && process.stdout.isTTY),
16716
18176
  confirm: async (message) => {
16717
- const rl = (0, import_promises5.createInterface)({
18177
+ const rl = (0, import_promises8.createInterface)({
16718
18178
  input: process.stdin,
16719
18179
  output: process.stderr
16720
18180
  });
@@ -16878,7 +18338,7 @@ async function resolveTargetProfileId(args, deps, client) {
16878
18338
  "Sync target is required in non-interactive mode. Use --to-profile-id <id> or --name <name>."
16879
18339
  );
16880
18340
  }
16881
- const defaultName = `Synced ${import_node_path4.default.basename(args.fromProfileDir)}`;
18341
+ const defaultName = `Synced ${import_node_path8.default.basename(args.fromProfileDir)}`;
16882
18342
  const shouldCreate = await deps.confirm(
16883
18343
  `No destination profile provided. Create a new cloud profile named "${defaultName}"?`
16884
18344
  );