opensteer 0.6.12 → 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);
@@ -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_process2 = 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,669 +1816,247 @@ 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
- var import_node_child_process = require("child_process");
1824
1832
  var import_node_fs = require("fs");
1825
- var import_promises = require("fs/promises");
1826
- var import_node_os = require("os");
1833
+ var import_promises2 = require("fs/promises");
1827
1834
  var import_node_path = require("path");
1835
+
1836
+ // src/browser/process-owner.ts
1837
+ var import_node_child_process = require("child_process");
1838
+ var import_promises = require("fs/promises");
1828
1839
  var import_node_util = require("util");
1829
1840
  var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
1830
- var OPENSTEER_META_FILE = ".opensteer-meta.json";
1831
- var OPENSTEER_RUNTIME_META_FILE = ".opensteer-runtime.json";
1832
- var LOCK_OWNER_FILE = "owner.json";
1833
- var LOCK_RECLAIMER_DIR = "reclaimer";
1834
1841
  var PROCESS_STARTED_AT_MS = Math.floor(Date.now() - process.uptime() * 1e3);
1835
1842
  var PROCESS_START_TIME_TOLERANCE_MS = 1e3;
1836
- var PROFILE_LOCK_RETRY_DELAY_MS = 50;
1843
+ var PROCESS_LIST_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
1837
1844
  var PS_COMMAND_ENV = { ...process.env, LC_ALL: "C" };
1838
1845
  var LINUX_STAT_START_TIME_FIELD_INDEX = 19;
1839
- var CHROME_SINGLETON_ENTRIES = /* @__PURE__ */ new Set([
1840
- "SingletonCookie",
1841
- "SingletonLock",
1842
- "SingletonSocket",
1843
- "DevToolsActivePort",
1844
- "lockfile"
1845
- ]);
1846
- var COPY_SKIP_ENTRIES = /* @__PURE__ */ new Set([
1847
- ...CHROME_SINGLETON_ENTRIES,
1848
- OPENSTEER_META_FILE,
1849
- OPENSTEER_RUNTIME_META_FILE
1850
- ]);
1851
- var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
1852
- "Crash Reports",
1853
- "Crashpad",
1854
- "BrowserMetrics",
1855
- "GrShaderCache",
1856
- "ShaderCache",
1857
- "GraphiteDawnCache",
1858
- "component_crx_cache",
1859
- "Crowd Deny",
1860
- "hyphen-data",
1861
- "OnDeviceHeadSuggestModel",
1862
- "OptimizationGuidePredictionModels",
1863
- "Segmentation Platform",
1864
- "SmartCardDeviceNames",
1865
- "WidevineCdm",
1866
- "pnacl"
1867
- ]);
1868
- var CURRENT_PROCESS_LOCK_OWNER = {
1846
+ var CURRENT_PROCESS_OWNER = {
1869
1847
  pid: process.pid,
1870
1848
  processStartedAtMs: PROCESS_STARTED_AT_MS
1871
1849
  };
1872
1850
  var linuxClockTicksPerSecondPromise = null;
1873
- async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory, profilesRootDir = defaultPersistentProfilesRootDir()) {
1874
- const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
1875
- const targetUserDataDir = (0, import_node_path.join)(
1876
- expandHome(profilesRootDir),
1877
- buildPersistentProfileKey(resolvedSourceUserDataDir, profileDirectory)
1878
- );
1879
- const sourceProfileDir = (0, import_node_path.join)(resolvedSourceUserDataDir, profileDirectory);
1880
- const metadata = buildPersistentProfileMetadata(
1881
- resolvedSourceUserDataDir,
1882
- profileDirectory
1883
- );
1884
- await (0, import_promises.mkdir)((0, import_node_path.dirname)(targetUserDataDir), { recursive: true });
1885
- return await withPersistentProfileLock(targetUserDataDir, async () => {
1886
- await cleanOrphanedOwnedDirs(
1887
- (0, import_node_path.dirname)(targetUserDataDir),
1888
- buildPersistentProfileTempDirNamePrefix(targetUserDataDir)
1889
- );
1890
- if (!(0, import_node_fs.existsSync)(sourceProfileDir)) {
1891
- throw new Error(
1892
- `Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
1893
- );
1894
- }
1895
- const created = await createPersistentProfileClone(
1896
- resolvedSourceUserDataDir,
1897
- sourceProfileDir,
1898
- targetUserDataDir,
1899
- profileDirectory,
1900
- metadata
1901
- );
1902
- await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
1903
- return {
1904
- created,
1905
- userDataDir: targetUserDataDir
1906
- };
1907
- });
1908
- }
1909
- async function createIsolatedRuntimeProfile(sourceUserDataDir, runtimesRootDir = defaultRuntimeProfilesRootDir()) {
1910
- const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
1911
- const runtimeRootDir = expandHome(runtimesRootDir);
1912
- await (0, import_promises.mkdir)(runtimeRootDir, { recursive: true });
1913
- return await withPersistentProfileLock(
1914
- resolvedSourceUserDataDir,
1915
- async () => {
1916
- const sourceMetadata = await readPersistentProfileMetadata(
1917
- resolvedSourceUserDataDir
1918
- );
1919
- await cleanOrphanedOwnedDirs(
1920
- runtimeRootDir,
1921
- buildRuntimeProfileDirNamePrefix(resolvedSourceUserDataDir)
1922
- );
1923
- const runtimeUserDataDir = await (0, import_promises.mkdtemp)(
1924
- buildRuntimeProfileDirPrefix(
1925
- runtimeRootDir,
1926
- resolvedSourceUserDataDir
1927
- )
1928
- );
1929
- try {
1930
- await copyUserDataDirSnapshot(
1931
- resolvedSourceUserDataDir,
1932
- runtimeUserDataDir
1933
- );
1934
- await writeRuntimeProfileMetadata(
1935
- runtimeUserDataDir,
1936
- await buildRuntimeProfileMetadata(
1937
- runtimeUserDataDir,
1938
- sourceMetadata?.profileDirectory ?? null
1939
- )
1940
- );
1941
- return {
1942
- persistentUserDataDir: resolvedSourceUserDataDir,
1943
- userDataDir: runtimeUserDataDir
1944
- };
1945
- } catch (error) {
1946
- await (0, import_promises.rm)(runtimeUserDataDir, {
1947
- recursive: true,
1948
- force: true
1949
- }).catch(() => void 0);
1950
- throw error;
1951
- }
1952
- }
1953
- );
1954
- }
1955
- async function persistIsolatedRuntimeProfile(runtimeUserDataDir, persistentUserDataDir) {
1956
- const resolvedRuntimeUserDataDir = expandHome(runtimeUserDataDir);
1957
- const resolvedPersistentUserDataDir = expandHome(persistentUserDataDir);
1958
- await withPersistentProfileLock(resolvedPersistentUserDataDir, async () => {
1959
- await (0, import_promises.mkdir)((0, import_node_path.dirname)(resolvedPersistentUserDataDir), { recursive: true });
1960
- await cleanOrphanedOwnedDirs(
1961
- (0, import_node_path.dirname)(resolvedPersistentUserDataDir),
1962
- buildPersistentProfileTempDirNamePrefix(resolvedPersistentUserDataDir)
1963
- );
1964
- const metadata = await requirePersistentProfileMetadata(
1965
- resolvedPersistentUserDataDir
1966
- );
1967
- const runtimeMetadata = await requireRuntimeProfileMetadata(
1968
- resolvedRuntimeUserDataDir,
1969
- metadata.profileDirectory
1970
- );
1971
- await mergePersistentProfileSnapshot(
1972
- resolvedRuntimeUserDataDir,
1973
- resolvedPersistentUserDataDir,
1974
- metadata,
1975
- runtimeMetadata
1976
- );
1977
- });
1978
- await (0, import_promises.rm)(resolvedRuntimeUserDataDir, {
1979
- recursive: true,
1980
- force: true
1981
- });
1982
- }
1983
- function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
1984
- const hash = (0, import_node_crypto.createHash)("sha256").update(`${sourceUserDataDir}\0${profileDirectory}`).digest("hex").slice(0, 16);
1985
- const sourceLabel = sanitizePathSegment((0, import_node_path.basename)(sourceUserDataDir) || "user-data");
1986
- const profileLabel = sanitizePathSegment(profileDirectory || "Default");
1987
- return `${sourceLabel}-${profileLabel}-${hash}`;
1988
- }
1989
- function defaultPersistentProfilesRootDir() {
1990
- return (0, import_node_path.join)((0, import_node_os.homedir)(), ".opensteer", "real-browser-profiles");
1991
- }
1992
- function defaultRuntimeProfilesRootDir() {
1993
- return (0, import_node_path.join)((0, import_node_os.tmpdir)(), "opensteer-real-browser-runtimes");
1994
- }
1995
- function sanitizePathSegment(value) {
1996
- const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-");
1997
- return sanitized.replace(/^-|-$/g, "") || "profile";
1998
- }
1999
- function isProfileDirectory(userDataDir, entry) {
2000
- return (0, import_node_fs.existsSync)((0, import_node_path.join)(userDataDir, entry, "Preferences"));
2001
- }
2002
- async function copyUserDataDirSnapshot(sourceUserDataDir, targetUserDataDir) {
2003
- await (0, import_promises.cp)(sourceUserDataDir, targetUserDataDir, {
2004
- recursive: true,
2005
- filter: (candidatePath) => shouldCopyRuntimeSnapshotEntry(sourceUserDataDir, candidatePath)
2006
- });
2007
- }
2008
- function shouldCopyRuntimeSnapshotEntry(userDataDir, candidatePath) {
2009
- const candidateRelativePath = (0, import_node_path.relative)(userDataDir, candidatePath);
2010
- if (!candidateRelativePath) {
2011
- return true;
2012
- }
2013
- const segments = candidateRelativePath.split(import_node_path.sep).filter(Boolean);
2014
- if (segments.length !== 1) {
2015
- return true;
1851
+ function parseProcessOwner(value) {
1852
+ if (!value || typeof value !== "object") {
1853
+ return null;
2016
1854
  }
2017
- return !COPY_SKIP_ENTRIES.has(segments[0]);
2018
- }
2019
- async function copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, targetProfileDirectory) {
2020
- let entries;
2021
- try {
2022
- entries = await (0, import_promises.readdir)(sourceUserDataDir);
2023
- } catch {
2024
- return;
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;
2025
1860
  }
2026
- const copyTasks = [];
2027
- for (const entry of entries) {
2028
- if (COPY_SKIP_ENTRIES.has(entry)) continue;
2029
- if (entry === targetProfileDirectory) continue;
2030
- const sourcePath = (0, import_node_path.join)(sourceUserDataDir, entry);
2031
- const targetPath = (0, import_node_path.join)(targetUserDataDir, entry);
2032
- if ((0, import_node_fs.existsSync)(targetPath)) continue;
2033
- let entryStat;
2034
- try {
2035
- entryStat = await (0, import_promises.stat)(sourcePath);
2036
- } catch {
2037
- continue;
2038
- }
2039
- if (entryStat.isFile()) {
2040
- copyTasks.push((0, import_promises.copyFile)(sourcePath, targetPath).catch(() => void 0));
2041
- } else if (entryStat.isDirectory()) {
2042
- if (isProfileDirectory(sourceUserDataDir, entry)) continue;
2043
- if (SKIPPED_ROOT_DIRECTORIES.has(entry)) continue;
2044
- copyTasks.push(
2045
- (0, import_promises.cp)(sourcePath, targetPath, { recursive: true }).catch(
2046
- () => void 0
2047
- )
2048
- );
2049
- }
1861
+ if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
1862
+ return null;
2050
1863
  }
2051
- await Promise.all(copyTasks);
2052
- }
2053
- async function writePersistentProfileMetadata(userDataDir, metadata) {
2054
- await (0, import_promises.writeFile)(
2055
- (0, import_node_path.join)(userDataDir, OPENSTEER_META_FILE),
2056
- JSON.stringify(metadata, null, 2)
2057
- );
2058
- }
2059
- function buildPersistentProfileMetadata(sourceUserDataDir, profileDirectory) {
2060
1864
  return {
2061
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2062
- profileDirectory,
2063
- source: sourceUserDataDir
1865
+ pid,
1866
+ processStartedAtMs
2064
1867
  };
2065
1868
  }
2066
- async function createPersistentProfileClone(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
2067
- if ((0, import_node_fs.existsSync)(targetUserDataDir)) {
2068
- return false;
2069
- }
2070
- const tempUserDataDir = await (0, import_promises.mkdtemp)(
2071
- buildPersistentProfileTempDirPrefix(targetUserDataDir)
2072
- );
2073
- let published = false;
2074
- try {
2075
- await materializePersistentProfileSnapshot(
2076
- sourceUserDataDir,
2077
- sourceProfileDir,
2078
- tempUserDataDir,
2079
- profileDirectory,
2080
- metadata
2081
- );
2082
- try {
2083
- await (0, import_promises.rename)(tempUserDataDir, targetUserDataDir);
2084
- } catch (error) {
2085
- if (wasDirPublishedByAnotherProcess(error, targetUserDataDir)) {
2086
- return false;
2087
- }
2088
- throw error;
2089
- }
2090
- published = true;
2091
- return true;
2092
- } finally {
2093
- if (!published) {
2094
- await (0, import_promises.rm)(tempUserDataDir, {
2095
- recursive: true,
2096
- force: true
2097
- }).catch(() => void 0);
2098
- }
1869
+ function processOwnersEqual(left, right) {
1870
+ if (!left || !right) {
1871
+ return left === right;
2099
1872
  }
1873
+ return left.pid === right.pid && left.processStartedAtMs === right.processStartedAtMs;
2100
1874
  }
2101
- async function materializePersistentProfileSnapshot(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
2102
- if (!(0, import_node_fs.existsSync)(sourceProfileDir)) {
2103
- throw new Error(
2104
- `Chrome profile "${profileDirectory}" was not found in "${sourceUserDataDir}".`
2105
- );
1875
+ async function getProcessLiveness(owner) {
1876
+ if (owner.pid === process.pid && hasMatchingProcessStartTime(
1877
+ owner.processStartedAtMs,
1878
+ PROCESS_STARTED_AT_MS
1879
+ )) {
1880
+ return "live";
2106
1881
  }
2107
- await (0, import_promises.cp)(sourceProfileDir, (0, import_node_path.join)(targetUserDataDir, profileDirectory), {
2108
- recursive: true
2109
- });
2110
- await copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, profileDirectory);
2111
- await writePersistentProfileMetadata(targetUserDataDir, metadata);
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";
2112
1890
  }
2113
- async function mergePersistentProfileSnapshot(runtimeUserDataDir, persistentUserDataDir, metadata, runtimeMetadata) {
2114
- const tempUserDataDir = await (0, import_promises.mkdtemp)(
2115
- buildPersistentProfileTempDirPrefix(persistentUserDataDir)
2116
- );
2117
- let published = false;
1891
+ function isProcessRunning(pid) {
2118
1892
  try {
2119
- const baseEntries = deserializeSnapshotManifestEntries(
2120
- runtimeMetadata.baseEntries
2121
- );
2122
- const currentEntries = await collectPersistentSnapshotEntries(
2123
- persistentUserDataDir,
2124
- metadata.profileDirectory
2125
- );
2126
- const runtimeEntries = await collectPersistentSnapshotEntries(
2127
- runtimeUserDataDir,
2128
- metadata.profileDirectory
2129
- );
2130
- const mergedEntries = resolveMergedSnapshotEntries(
2131
- baseEntries,
2132
- currentEntries,
2133
- runtimeEntries
2134
- );
2135
- await materializeMergedPersistentProfileSnapshot(
2136
- tempUserDataDir,
2137
- currentEntries,
2138
- runtimeEntries,
2139
- mergedEntries
2140
- );
2141
- await writePersistentProfileMetadata(tempUserDataDir, metadata);
2142
- await replaceProfileDirectory(persistentUserDataDir, tempUserDataDir);
2143
- published = true;
2144
- } finally {
2145
- if (!published) {
2146
- await (0, import_promises.rm)(tempUserDataDir, {
2147
- recursive: true,
2148
- force: true
2149
- }).catch(() => void 0);
2150
- }
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";
2151
1898
  }
2152
1899
  }
2153
- async function buildRuntimeProfileMetadata(runtimeUserDataDir, profileDirectory) {
2154
- if (!profileDirectory) {
2155
- return {
2156
- baseEntries: {},
2157
- profileDirectory: null
2158
- };
1900
+ async function readProcessOwner(pid) {
1901
+ const processStartedAtMs = await readProcessStartedAtMs(pid);
1902
+ if (processStartedAtMs === null) {
1903
+ return null;
2159
1904
  }
2160
- const baseEntries = await collectPersistentSnapshotEntries(
2161
- runtimeUserDataDir,
2162
- profileDirectory
2163
- );
2164
1905
  return {
2165
- baseEntries: serializeSnapshotManifestEntries(baseEntries),
2166
- profileDirectory
1906
+ pid,
1907
+ processStartedAtMs
2167
1908
  };
2168
1909
  }
2169
- async function writeRuntimeProfileMetadata(userDataDir, metadata) {
2170
- await (0, import_promises.writeFile)(
2171
- (0, import_node_path.join)(userDataDir, OPENSTEER_RUNTIME_META_FILE),
2172
- JSON.stringify(metadata, null, 2)
2173
- );
1910
+ function hasMatchingProcessStartTime(expectedStartedAtMs, actualStartedAtMs) {
1911
+ return Math.abs(expectedStartedAtMs - actualStartedAtMs) <= PROCESS_START_TIME_TOLERANCE_MS;
2174
1912
  }
2175
- async function readRuntimeProfileMetadata(userDataDir) {
2176
- try {
2177
- const raw = await (0, import_promises.readFile)(
2178
- (0, import_node_path.join)(userDataDir, OPENSTEER_RUNTIME_META_FILE),
2179
- "utf8"
2180
- );
2181
- const parsed = JSON.parse(raw);
2182
- const profileDirectory = parsed.profileDirectory === null ? null : typeof parsed.profileDirectory === "string" ? parsed.profileDirectory : void 0;
2183
- if (profileDirectory === void 0 || typeof parsed.baseEntries !== "object" || parsed.baseEntries === null || Array.isArray(parsed.baseEntries)) {
2184
- return null;
2185
- }
2186
- const baseEntries = deserializeSnapshotManifestEntries(
2187
- parsed.baseEntries
2188
- );
2189
- return {
2190
- baseEntries: Object.fromEntries(baseEntries),
2191
- profileDirectory
2192
- };
2193
- } catch {
1913
+ async function readProcessStartedAtMs(pid) {
1914
+ if (pid <= 0) {
2194
1915
  return null;
2195
1916
  }
2196
- }
2197
- async function requireRuntimeProfileMetadata(userDataDir, expectedProfileDirectory) {
2198
- const metadata = await readRuntimeProfileMetadata(userDataDir);
2199
- if (!metadata) {
2200
- throw new Error(
2201
- `Runtime profile metadata was not found for "${userDataDir}".`
2202
- );
2203
- }
2204
- if (metadata.profileDirectory !== expectedProfileDirectory) {
2205
- throw new Error(
2206
- `Runtime profile "${userDataDir}" was created for profile "${metadata.profileDirectory ?? "unknown"}", expected "${expectedProfileDirectory}".`
2207
- );
2208
- }
2209
- return metadata;
2210
- }
2211
- async function collectPersistentSnapshotEntries(userDataDir, profileDirectory) {
2212
- let rootEntries;
2213
- try {
2214
- rootEntries = await (0, import_promises.readdir)(userDataDir, {
2215
- encoding: "utf8",
2216
- withFileTypes: true
2217
- });
2218
- } catch {
2219
- return /* @__PURE__ */ new Map();
2220
- }
2221
- rootEntries.sort((left, right) => left.name.localeCompare(right.name));
2222
- const collected = /* @__PURE__ */ new Map();
2223
- for (const entry of rootEntries) {
2224
- if (!shouldIncludePersistentRootEntry(
2225
- userDataDir,
2226
- profileDirectory,
2227
- entry.name
2228
- )) {
2229
- continue;
2230
- }
2231
- await collectSnapshotEntry(
2232
- (0, import_node_path.join)(userDataDir, entry.name),
2233
- entry.name,
2234
- collected
2235
- );
2236
- }
2237
- return collected;
2238
- }
2239
- function shouldIncludePersistentRootEntry(userDataDir, profileDirectory, entry) {
2240
- if (entry === profileDirectory) {
2241
- return true;
2242
- }
2243
- if (COPY_SKIP_ENTRIES.has(entry)) {
2244
- return false;
1917
+ if (process.platform === "linux") {
1918
+ return await readLinuxProcessStartedAtMs(pid);
2245
1919
  }
2246
- if (SKIPPED_ROOT_DIRECTORIES.has(entry)) {
2247
- return false;
1920
+ if (process.platform === "win32") {
1921
+ return await readWindowsProcessStartedAtMs(pid);
2248
1922
  }
2249
- return !isProfileDirectory(userDataDir, entry);
1923
+ return await readPsProcessStartedAtMs(pid);
2250
1924
  }
2251
- async function collectSnapshotEntry(sourcePath, relativePath, collected) {
2252
- let entryStat;
1925
+ async function readLinuxProcessStartedAtMs(pid) {
1926
+ let statRaw;
2253
1927
  try {
2254
- entryStat = await (0, import_promises.stat)(sourcePath);
1928
+ statRaw = await (0, import_promises.readFile)(`/proc/${pid}/stat`, "utf8");
2255
1929
  } catch {
2256
- return;
2257
- }
2258
- if (entryStat.isDirectory()) {
2259
- collected.set(relativePath, {
2260
- kind: "directory",
2261
- hash: null,
2262
- sourcePath
2263
- });
2264
- let children;
2265
- try {
2266
- children = await (0, import_promises.readdir)(sourcePath, {
2267
- encoding: "utf8",
2268
- withFileTypes: true
2269
- });
2270
- } catch {
2271
- return;
2272
- }
2273
- children.sort((left, right) => left.name.localeCompare(right.name));
2274
- for (const child of children) {
2275
- await collectSnapshotEntry(
2276
- (0, import_node_path.join)(sourcePath, child.name),
2277
- (0, import_node_path.join)(relativePath, child.name),
2278
- collected
2279
- );
2280
- }
2281
- return;
2282
- }
2283
- if (entryStat.isFile()) {
2284
- collected.set(relativePath, {
2285
- kind: "file",
2286
- hash: await hashFile(sourcePath),
2287
- sourcePath
2288
- });
1930
+ return null;
2289
1931
  }
2290
- }
2291
- function serializeSnapshotManifestEntries(entries) {
2292
- return Object.fromEntries(
2293
- [...entries.entries()].sort(([leftPath], [rightPath]) => leftPath.localeCompare(rightPath)).map(([relativePath, entry]) => [
2294
- relativePath,
2295
- {
2296
- kind: entry.kind,
2297
- hash: entry.hash
2298
- }
2299
- ])
2300
- );
2301
- }
2302
- function deserializeSnapshotManifestEntries(entries) {
2303
- const manifestEntries = /* @__PURE__ */ new Map();
2304
- for (const [relativePath, entry] of Object.entries(entries)) {
2305
- if (!entry || entry.kind !== "directory" && entry.kind !== "file" || !(entry.hash === null || typeof entry.hash === "string")) {
2306
- throw new Error(
2307
- `Runtime profile metadata for "${relativePath}" is invalid.`
2308
- );
2309
- }
2310
- manifestEntries.set(relativePath, {
2311
- kind: entry.kind,
2312
- hash: entry.hash
2313
- });
1932
+ const startTicks = parseLinuxProcessStartTicks(statRaw);
1933
+ if (startTicks === null) {
1934
+ return null;
2314
1935
  }
2315
- return manifestEntries;
2316
- }
2317
- function resolveMergedSnapshotEntries(baseEntries, currentEntries, runtimeEntries) {
2318
- const mergedEntries = /* @__PURE__ */ new Map();
2319
- const relativePaths = /* @__PURE__ */ new Set([
2320
- ...baseEntries.keys(),
2321
- ...currentEntries.keys(),
2322
- ...runtimeEntries.keys()
1936
+ const [bootTimeMs, clockTicksPerSecond] = await Promise.all([
1937
+ readLinuxBootTimeMs(),
1938
+ readLinuxClockTicksPerSecond()
2323
1939
  ]);
2324
- for (const relativePath of [...relativePaths].sort(compareSnapshotPaths)) {
2325
- mergedEntries.set(
2326
- relativePath,
2327
- resolveMergedSnapshotEntrySelection(
2328
- relativePath,
2329
- baseEntries.get(relativePath) ?? null,
2330
- currentEntries.get(relativePath) ?? null,
2331
- runtimeEntries.get(relativePath) ?? null
2332
- )
2333
- );
2334
- }
2335
- return mergedEntries;
2336
- }
2337
- function resolveMergedSnapshotEntrySelection(relativePath, baseEntry, currentEntry, runtimeEntry) {
2338
- if (snapshotEntriesEqual(runtimeEntry, baseEntry)) {
2339
- return currentEntry ? "current" : null;
2340
- }
2341
- if (snapshotEntriesEqual(currentEntry, baseEntry)) {
2342
- return runtimeEntry ? "runtime" : null;
2343
- }
2344
- if (!baseEntry) {
2345
- if (!currentEntry) {
2346
- return runtimeEntry ? "runtime" : null;
2347
- }
2348
- if (!runtimeEntry) {
2349
- return "current";
2350
- }
2351
- if (snapshotEntriesEqual(currentEntry, runtimeEntry)) {
2352
- return "current";
2353
- }
2354
- throw new Error(
2355
- `Concurrent runtime updates changed "${relativePath}" differently; refusing to overwrite the persistent profile.`
2356
- );
2357
- }
2358
- if (!currentEntry && !runtimeEntry) {
1940
+ if (bootTimeMs === null || clockTicksPerSecond === null) {
2359
1941
  return null;
2360
1942
  }
2361
- if (snapshotEntriesEqual(currentEntry, runtimeEntry)) {
2362
- return currentEntry ? "current" : null;
2363
- }
2364
- throw new Error(
2365
- `Concurrent runtime updates changed "${relativePath}" differently; refusing to overwrite the persistent profile.`
1943
+ return Math.floor(
1944
+ bootTimeMs + startTicks * 1e3 / clockTicksPerSecond
2366
1945
  );
2367
1946
  }
2368
- function snapshotEntriesEqual(left, right) {
2369
- if (!left || !right) {
2370
- return left === right;
1947
+ function parseLinuxProcessStartTicks(statRaw) {
1948
+ const closingParenIndex = statRaw.lastIndexOf(")");
1949
+ if (closingParenIndex === -1) {
1950
+ return null;
2371
1951
  }
2372
- return left.kind === right.kind && left.hash === right.hash;
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;
2373
1955
  }
2374
- async function materializeMergedPersistentProfileSnapshot(targetUserDataDir, currentEntries, runtimeEntries, mergedEntries) {
2375
- const selectedEntries = [...mergedEntries.entries()].filter(([, selection]) => selection !== null).sort(
2376
- ([leftPath], [rightPath]) => compareSnapshotPaths(leftPath, rightPath)
2377
- );
2378
- for (const [relativePath, selection] of selectedEntries) {
2379
- const entry = (selection === "current" ? currentEntries.get(relativePath) : runtimeEntries.get(relativePath)) ?? null;
2380
- if (!entry) {
2381
- continue;
2382
- }
2383
- const targetPath = (0, import_node_path.join)(targetUserDataDir, relativePath);
2384
- if (entry.kind === "directory") {
2385
- await (0, import_promises.mkdir)(targetPath, { recursive: true });
2386
- continue;
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;
2387
1962
  }
2388
- await (0, import_promises.mkdir)((0, import_node_path.dirname)(targetPath), { recursive: true });
2389
- await (0, import_promises.copyFile)(entry.sourcePath, targetPath);
2390
- }
2391
- }
2392
- function compareSnapshotPaths(left, right) {
2393
- const leftDepth = left.split(import_node_path.sep).length;
2394
- const rightDepth = right.split(import_node_path.sep).length;
2395
- if (leftDepth !== rightDepth) {
2396
- return leftDepth - rightDepth;
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;
2397
1970
  }
2398
- return left.localeCompare(right);
2399
- }
2400
- async function hashFile(filePath) {
2401
- return new Promise((resolve, reject) => {
2402
- const hash = (0, import_node_crypto.createHash)("sha256");
2403
- const stream = (0, import_node_fs.createReadStream)(filePath);
2404
- stream.on("data", (chunk) => {
2405
- hash.update(chunk);
2406
- });
2407
- stream.on("error", reject);
2408
- stream.on("end", () => {
2409
- resolve(hash.digest("hex"));
2410
- });
2411
- });
2412
1971
  }
2413
- async function ensurePersistentProfileMetadata(userDataDir, metadata) {
2414
- if ((0, import_node_fs.existsSync)((0, import_node_path.join)(userDataDir, OPENSTEER_META_FILE))) {
2415
- return;
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);
2416
1981
  }
2417
- await writePersistentProfileMetadata(userDataDir, metadata);
1982
+ return await linuxClockTicksPerSecondPromise;
2418
1983
  }
2419
- async function readPersistentProfileMetadata(userDataDir) {
1984
+ async function readWindowsProcessStartedAtMs(pid) {
2420
1985
  try {
2421
- const raw = await (0, import_promises.readFile)((0, import_node_path.join)(userDataDir, OPENSTEER_META_FILE), "utf8");
2422
- const parsed = JSON.parse(raw);
2423
- if (typeof parsed.createdAt !== "string" || typeof parsed.profileDirectory !== "string" || typeof parsed.source !== "string") {
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
+ }
1997
+ );
1998
+ const isoTimestamp = stdout.trim();
1999
+ if (!isoTimestamp) {
2424
2000
  return null;
2425
2001
  }
2426
- return {
2427
- createdAt: parsed.createdAt,
2428
- profileDirectory: parsed.profileDirectory,
2429
- source: parsed.source
2430
- };
2002
+ const startedAtMs = Date.parse(isoTimestamp);
2003
+ return Number.isFinite(startedAtMs) ? startedAtMs : null;
2431
2004
  } catch {
2432
2005
  return null;
2433
2006
  }
2434
2007
  }
2435
- async function requirePersistentProfileMetadata(userDataDir) {
2436
- const metadata = await readPersistentProfileMetadata(userDataDir);
2437
- if (!metadata) {
2438
- throw new Error(
2439
- `Persistent profile metadata was not found for "${userDataDir}".`
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
2017
+ }
2440
2018
  );
2019
+ const startedAt = stdout.trim();
2020
+ if (!startedAt) {
2021
+ return null;
2022
+ }
2023
+ const startedAtMs = Date.parse(startedAt.replace(/\s+/g, " "));
2024
+ return Number.isFinite(startedAtMs) ? startedAtMs : null;
2025
+ } catch {
2026
+ return null;
2441
2027
  }
2442
- return metadata;
2443
- }
2444
- function wasDirPublishedByAnotherProcess(error, targetDirPath) {
2445
- const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
2446
- return (0, import_node_fs.existsSync)(targetDirPath) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
2447
2028
  }
2448
- async function replaceProfileDirectory(targetUserDataDir, replacementUserDataDir) {
2449
- if (!(0, import_node_fs.existsSync)(targetUserDataDir)) {
2450
- await (0, import_promises.rename)(replacementUserDataDir, targetUserDataDir);
2451
- return;
2452
- }
2453
- const backupUserDataDir = buildPersistentProfileBackupDirPath(targetUserDataDir);
2454
- let targetMovedToBackup = false;
2455
- let replacementPublished = false;
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);
2456
2036
  try {
2457
- await (0, import_promises.rename)(targetUserDataDir, backupUserDataDir);
2458
- targetMovedToBackup = true;
2459
- await (0, import_promises.rename)(replacementUserDataDir, targetUserDataDir);
2460
- replacementPublished = true;
2461
- } catch (error) {
2462
- if (targetMovedToBackup && !(0, import_node_fs.existsSync)(targetUserDataDir)) {
2463
- await (0, import_promises.rename)(backupUserDataDir, targetUserDataDir).catch(() => void 0);
2464
- }
2465
- throw error;
2037
+ return await action();
2466
2038
  } finally {
2467
- if (replacementPublished && targetMovedToBackup && (0, import_node_fs.existsSync)(backupUserDataDir)) {
2468
- await (0, import_promises.rm)(backupUserDataDir, {
2469
- recursive: true,
2470
- force: true
2471
- }).catch(() => void 0);
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;
2472
2047
  }
2048
+ await sleep(LOCK_RETRY_DELAY_MS);
2473
2049
  }
2474
2050
  }
2475
- async function withPersistentProfileLock(targetUserDataDir, action) {
2476
- const lockDirPath = buildPersistentProfileLockDirPath(targetUserDataDir);
2477
- await (0, import_promises.mkdir)((0, import_node_path.dirname)(lockDirPath), { recursive: true });
2051
+ async function tryAcquireDirLock(lockDirPath) {
2052
+ await (0, import_promises2.mkdir)((0, import_node_path.dirname)(lockDirPath), { recursive: true });
2478
2053
  while (true) {
2479
- const tempLockDirPath = `${lockDirPath}-${process.pid}-${PROCESS_STARTED_AT_MS}-${(0, import_node_crypto.randomUUID)()}`;
2054
+ const tempLockDirPath = `${lockDirPath}-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto.randomUUID)()}`;
2480
2055
  try {
2481
- await (0, import_promises.mkdir)(tempLockDirPath);
2482
- await writeLockOwner(tempLockDirPath, CURRENT_PROCESS_LOCK_OWNER);
2056
+ await (0, import_promises2.mkdir)(tempLockDirPath);
2057
+ await writeLockOwner(tempLockDirPath, CURRENT_PROCESS_OWNER);
2483
2058
  try {
2484
- await (0, import_promises.rename)(tempLockDirPath, lockDirPath);
2059
+ await (0, import_promises2.rename)(tempLockDirPath, lockDirPath);
2485
2060
  break;
2486
2061
  } catch (error) {
2487
2062
  if (!wasDirPublishedByAnotherProcess(error, lockDirPath)) {
@@ -2489,7 +2064,7 @@ async function withPersistentProfileLock(targetUserDataDir, action) {
2489
2064
  }
2490
2065
  }
2491
2066
  } finally {
2492
- await (0, import_promises.rm)(tempLockDirPath, {
2067
+ await (0, import_promises2.rm)(tempLockDirPath, {
2493
2068
  recursive: true,
2494
2069
  force: true
2495
2070
  }).catch(() => void 0);
@@ -2498,52 +2073,48 @@ async function withPersistentProfileLock(targetUserDataDir, action) {
2498
2073
  if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
2499
2074
  continue;
2500
2075
  }
2501
- await sleep(PROFILE_LOCK_RETRY_DELAY_MS);
2076
+ return null;
2502
2077
  }
2503
- try {
2504
- return await action();
2505
- } finally {
2506
- await (0, import_promises.rm)(lockDirPath, {
2078
+ return async () => {
2079
+ await (0, import_promises2.rm)(lockDirPath, {
2507
2080
  recursive: true,
2508
2081
  force: true
2509
2082
  }).catch(() => void 0);
2083
+ };
2084
+ }
2085
+ async function isDirLockHeld(lockDirPath) {
2086
+ if (!(0, import_node_fs.existsSync)(lockDirPath)) {
2087
+ return false;
2088
+ }
2089
+ const owner = await readLockOwner(lockDirPath);
2090
+ if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
2091
+ return false;
2510
2092
  }
2093
+ return (0, import_node_fs.existsSync)(lockDirPath);
2094
+ }
2095
+ function getErrorCode(error) {
2096
+ return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
2097
+ }
2098
+ function wasDirPublishedByAnotherProcess(error, targetDirPath) {
2099
+ const code = getErrorCode(error);
2100
+ return (0, import_node_fs.existsSync)(targetDirPath) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
2511
2101
  }
2512
2102
  async function writeLockOwner(lockDirPath, owner) {
2513
- await writeLockParticipant((0, import_node_path.join)(lockDirPath, LOCK_OWNER_FILE), owner);
2103
+ await (0, import_promises2.writeFile)((0, import_node_path.join)(lockDirPath, LOCK_OWNER_FILE), JSON.stringify(owner));
2514
2104
  }
2515
2105
  async function readLockOwner(lockDirPath) {
2516
2106
  return await readLockParticipant((0, import_node_path.join)(lockDirPath, LOCK_OWNER_FILE));
2517
2107
  }
2518
- async function writeLockParticipant(filePath, owner, options) {
2519
- await (0, import_promises.writeFile)(filePath, JSON.stringify(owner), options);
2520
- }
2521
2108
  async function readLockParticipant(filePath) {
2522
2109
  return (await readLockParticipantRecord(filePath)).owner;
2523
2110
  }
2524
- async function readLockReclaimerRecord(lockDirPath) {
2525
- return await readLockParticipantRecord(
2526
- (0, import_node_path.join)(buildLockReclaimerDirPath(lockDirPath), LOCK_OWNER_FILE)
2527
- );
2528
- }
2529
2111
  async function readLockParticipantRecord(filePath) {
2530
2112
  try {
2531
- const raw = await (0, import_promises.readFile)(filePath, "utf8");
2532
- const parsed = JSON.parse(raw);
2533
- const pid = Number(parsed.pid);
2534
- const processStartedAtMs = Number(parsed.processStartedAtMs);
2535
- if (!Number.isInteger(pid) || !Number.isInteger(processStartedAtMs)) {
2536
- return {
2537
- exists: true,
2538
- owner: null
2539
- };
2540
- }
2113
+ const raw = await (0, import_promises2.readFile)(filePath, "utf8");
2114
+ const owner = parseProcessOwner(JSON.parse(raw));
2541
2115
  return {
2542
2116
  exists: true,
2543
- owner: {
2544
- pid,
2545
- processStartedAtMs
2546
- }
2117
+ owner
2547
2118
  };
2548
2119
  } catch (error) {
2549
2120
  return {
@@ -2552,6 +2123,11 @@ async function readLockParticipantRecord(filePath) {
2552
2123
  };
2553
2124
  }
2554
2125
  }
2126
+ async function readLockReclaimerRecord(lockDirPath) {
2127
+ return await readLockParticipantRecord(
2128
+ (0, import_node_path.join)(buildLockReclaimerDirPath(lockDirPath), LOCK_OWNER_FILE)
2129
+ );
2130
+ }
2555
2131
  async function tryReclaimStaleLock(lockDirPath, expectedOwner) {
2556
2132
  if (!await tryAcquireLockReclaimer(lockDirPath)) {
2557
2133
  return false;
@@ -2559,13 +2135,13 @@ async function tryReclaimStaleLock(lockDirPath, expectedOwner) {
2559
2135
  let reclaimed = false;
2560
2136
  try {
2561
2137
  const owner = await readLockOwner(lockDirPath);
2562
- if (!lockOwnersEqual(owner, expectedOwner)) {
2138
+ if (!processOwnersEqual(owner, expectedOwner)) {
2563
2139
  return false;
2564
2140
  }
2565
2141
  if (owner && await getProcessLiveness(owner) !== "dead") {
2566
2142
  return false;
2567
2143
  }
2568
- await (0, import_promises.rm)(lockDirPath, {
2144
+ await (0, import_promises2.rm)(lockDirPath, {
2569
2145
  recursive: true,
2570
2146
  force: true
2571
2147
  }).catch(() => void 0);
@@ -2573,7 +2149,7 @@ async function tryReclaimStaleLock(lockDirPath, expectedOwner) {
2573
2149
  return reclaimed;
2574
2150
  } finally {
2575
2151
  if (!reclaimed) {
2576
- await (0, import_promises.rm)(buildLockReclaimerDirPath(lockDirPath), {
2152
+ await (0, import_promises2.rm)(buildLockReclaimerDirPath(lockDirPath), {
2577
2153
  recursive: true,
2578
2154
  force: true
2579
2155
  }).catch(() => void 0);
@@ -2583,12 +2159,12 @@ async function tryReclaimStaleLock(lockDirPath, expectedOwner) {
2583
2159
  async function tryAcquireLockReclaimer(lockDirPath) {
2584
2160
  const reclaimerDirPath = buildLockReclaimerDirPath(lockDirPath);
2585
2161
  while (true) {
2586
- const tempReclaimerDirPath = `${reclaimerDirPath}-${process.pid}-${PROCESS_STARTED_AT_MS}-${(0, import_node_crypto.randomUUID)()}`;
2162
+ const tempReclaimerDirPath = `${reclaimerDirPath}-${process.pid}-${CURRENT_PROCESS_OWNER.processStartedAtMs}-${(0, import_node_crypto.randomUUID)()}`;
2587
2163
  try {
2588
- await (0, import_promises.mkdir)(tempReclaimerDirPath);
2589
- await writeLockOwner(tempReclaimerDirPath, CURRENT_PROCESS_LOCK_OWNER);
2164
+ await (0, import_promises2.mkdir)(tempReclaimerDirPath);
2165
+ await writeLockOwner(tempReclaimerDirPath, CURRENT_PROCESS_OWNER);
2590
2166
  try {
2591
- await (0, import_promises.rename)(tempReclaimerDirPath, reclaimerDirPath);
2167
+ await (0, import_promises2.rename)(tempReclaimerDirPath, reclaimerDirPath);
2592
2168
  return true;
2593
2169
  } catch (error) {
2594
2170
  if (getErrorCode(error) === "ENOENT") {
@@ -2605,7 +2181,7 @@ async function tryAcquireLockReclaimer(lockDirPath) {
2605
2181
  }
2606
2182
  throw error;
2607
2183
  } finally {
2608
- await (0, import_promises.rm)(tempReclaimerDirPath, {
2184
+ await (0, import_promises2.rm)(tempReclaimerDirPath, {
2609
2185
  recursive: true,
2610
2186
  force: true
2611
2187
  }).catch(() => void 0);
@@ -2617,256 +2193,1515 @@ async function tryAcquireLockReclaimer(lockDirPath) {
2617
2193
  if (await getProcessLiveness(reclaimerRecord.owner) !== "dead") {
2618
2194
  return false;
2619
2195
  }
2620
- await (0, import_promises.rm)(reclaimerDirPath, {
2196
+ await (0, import_promises2.rm)(reclaimerDirPath, {
2621
2197
  recursive: true,
2622
2198
  force: true
2623
2199
  }).catch(() => void 0);
2624
2200
  }
2625
2201
  }
2626
- function lockOwnersEqual(left, right) {
2627
- if (!left || !right) {
2628
- return left === right;
2629
- }
2630
- return left.pid === right.pid && left.processStartedAtMs === right.processStartedAtMs;
2631
- }
2632
- async function getProcessLiveness(owner) {
2633
- if (owner.pid === process.pid && hasMatchingProcessStartTime(
2634
- owner.processStartedAtMs,
2635
- PROCESS_STARTED_AT_MS
2636
- )) {
2637
- return "live";
2638
- }
2639
- const startedAtMs = await readProcessStartedAtMs(owner.pid);
2640
- if (typeof startedAtMs === "number") {
2641
- return hasMatchingProcessStartTime(
2642
- owner.processStartedAtMs,
2643
- startedAtMs
2644
- ) ? "live" : "dead";
2645
- }
2646
- return isProcessRunning(owner.pid) ? "unknown" : "dead";
2202
+ function buildLockReclaimerDirPath(lockDirPath) {
2203
+ return (0, import_node_path.join)(lockDirPath, LOCK_RECLAIMER_DIR);
2647
2204
  }
2648
- function hasMatchingProcessStartTime(expectedStartedAtMs, actualStartedAtMs) {
2649
- return Math.abs(expectedStartedAtMs - actualStartedAtMs) <= PROCESS_START_TIME_TOLERANCE_MS;
2205
+ async function sleep(ms) {
2206
+ await new Promise((resolve) => setTimeout(resolve, ms));
2650
2207
  }
2651
- function buildPersistentProfileTempDirPrefix(targetUserDataDir) {
2652
- return (0, import_node_path.join)(
2653
- (0, import_node_path.dirname)(targetUserDataDir),
2654
- `${buildPersistentProfileTempDirNamePrefix(targetUserDataDir)}${process.pid}-${PROCESS_STARTED_AT_MS}-`
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
2655
2215
  );
2656
2216
  }
2657
- function buildPersistentProfileTempDirNamePrefix(targetUserDataDir) {
2658
- return `${(0, import_node_path.basename)(targetUserDataDir)}-tmp-`;
2659
- }
2660
- function buildPersistentProfileBackupDirPath(targetUserDataDir) {
2661
- return (0, import_node_path.join)(
2662
- (0, import_node_path.dirname)(targetUserDataDir),
2663
- `${buildPersistentProfileTempDirNamePrefix(targetUserDataDir)}${process.pid}-${PROCESS_STARTED_AT_MS}-backup-${Date.now()}`
2217
+ async function acquirePersistentProfileWriteLock(targetUserDataDir) {
2218
+ const controlLockDirPath = buildPersistentProfileControlLockDirPath(targetUserDataDir);
2219
+ const writeLockDirPath = buildPersistentProfileWriteLockDirPath(
2220
+ targetUserDataDir
2664
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
+ }
2665
2235
  }
2666
- function buildPersistentProfileLockDirPath(targetUserDataDir) {
2667
- return (0, import_node_path.join)((0, import_node_path.dirname)(targetUserDataDir), `${(0, import_node_path.basename)(targetUserDataDir)}.lock`);
2668
- }
2669
- function buildLockReclaimerDirPath(lockDirPath) {
2670
- return (0, import_node_path.join)(lockDirPath, LOCK_RECLAIMER_DIR);
2671
- }
2672
- function buildRuntimeProfileKey(sourceUserDataDir) {
2673
- const hash = (0, import_node_crypto.createHash)("sha256").update(sourceUserDataDir).digest("hex").slice(0, 16);
2674
- return `${sanitizePathSegment((0, import_node_path.basename)(sourceUserDataDir) || "profile")}-${hash}`;
2236
+ async function isPersistentProfileWriteLocked(targetUserDataDir) {
2237
+ return await isDirLockHeld(
2238
+ buildPersistentProfileWriteLockDirPath(targetUserDataDir)
2239
+ );
2675
2240
  }
2676
- function buildRuntimeProfileDirNamePrefix(sourceUserDataDir) {
2677
- return `${buildRuntimeProfileKey(sourceUserDataDir)}-runtime-`;
2241
+ function buildPersistentProfileWriteLockDirPath(targetUserDataDir) {
2242
+ return (0, import_node_path2.join)((0, import_node_path2.dirname)(targetUserDataDir), `${(0, import_node_path2.basename)(targetUserDataDir)}.lock`);
2678
2243
  }
2679
- function buildRuntimeProfileDirPrefix(runtimesRootDir, sourceUserDataDir) {
2680
- return (0, import_node_path.join)(
2681
- runtimesRootDir,
2682
- `${buildRuntimeProfileDirNamePrefix(sourceUserDataDir)}${process.pid}-${PROCESS_STARTED_AT_MS}-`
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`
2683
2248
  );
2684
2249
  }
2685
- async function cleanOrphanedOwnedDirs(rootDir, ownedDirNamePrefix) {
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) {
2686
2838
  let entries;
2687
2839
  try {
2688
- entries = await (0, import_promises.readdir)(rootDir, {
2840
+ entries = await (0, import_promises4.readdir)(rootDir, {
2689
2841
  encoding: "utf8",
2690
2842
  withFileTypes: true
2691
2843
  });
2692
2844
  } catch {
2693
2845
  return;
2694
2846
  }
2695
- await Promise.all(
2696
- entries.map(async (entry) => {
2697
- if (!entry.isDirectory() || !entry.name.startsWith(ownedDirNamePrefix)) {
2698
- return;
2699
- }
2700
- if (await isOwnedDirByLiveProcess(entry.name, ownedDirNamePrefix)) {
2701
- return;
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);
2702
3275
  }
2703
- await (0, import_promises.rm)((0, import_node_path.join)(rootDir, entry.name), {
2704
- recursive: true,
2705
- force: true
2706
- }).catch(() => void 0);
2707
- })
3276
+ return false;
3277
+ }
2708
3278
  );
3279
+ if (shouldPreserveLiveBrowser) {
3280
+ return;
3281
+ }
3282
+ await killOwnedBrowserProcess(reservation.launchedBrowserOwner);
3283
+ await waitForProcessToExit(reservation.launchedBrowserOwner, 2e3);
2709
3284
  }
2710
- async function isOwnedDirByLiveProcess(ownedDirName, ownedDirPrefix) {
2711
- const owner = parseOwnedDirOwner(ownedDirName, ownedDirPrefix);
2712
- return owner ? await getProcessLiveness(owner) !== "dead" : false;
2713
- }
2714
- function parseOwnedDirOwner(ownedDirName, ownedDirPrefix) {
2715
- const remainder = ownedDirName.slice(ownedDirPrefix.length);
2716
- const firstDashIndex = remainder.indexOf("-");
2717
- const secondDashIndex = firstDashIndex === -1 ? -1 : remainder.indexOf("-", firstDashIndex + 1);
2718
- if (firstDashIndex === -1 || secondDashIndex === -1) {
2719
- return null;
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;
2720
3293
  }
2721
- const pid = Number.parseInt(remainder.slice(0, firstDashIndex), 10);
2722
- const processStartedAtMs = Number.parseInt(
2723
- remainder.slice(firstDashIndex + 1, secondDashIndex),
2724
- 10
3294
+ await closeSharedSessionBrowser(options.persistentUserDataDir, closePlan);
3295
+ }
3296
+ async function waitForSharedSessionReady(metadata, timeoutMs) {
3297
+ await resolveCdpWebSocketUrl(
3298
+ buildSharedSessionDiscoveryUrl(metadata.debugPort),
3299
+ timeoutMs
2725
3300
  );
2726
- if (!Number.isInteger(pid) || pid <= 0) {
2727
- return null;
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");
2728
3311
  }
2729
- if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
2730
- return null;
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);
2731
3322
  }
2732
- return { pid, processStartedAtMs };
2733
3323
  }
2734
- function isProcessRunning(pid) {
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
+ }
2735
3361
  try {
2736
- process.kill(pid, 0);
2737
- return true;
2738
- } catch (error) {
2739
- const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
2740
- return code !== "ESRCH";
3362
+ process.kill(pid, "SIGKILL");
3363
+ } catch {
2741
3364
  }
2742
3365
  }
2743
- async function readProcessStartedAtMs(pid) {
2744
- if (pid <= 0) {
2745
- return null;
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);
2746
3373
  }
2747
- if (process.platform === "linux") {
2748
- return await readLinuxProcessStartedAtMs(pid);
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);
2749
3382
  }
2750
- if (process.platform === "win32") {
2751
- return await readWindowsProcessStartedAtMs(pid);
3383
+ }
3384
+ async function waitForSpawnedProcessOwner(pid, timeoutMs) {
3385
+ if (!pid || pid <= 0) {
3386
+ throw new Error("Chrome did not expose a child process id.");
2752
3387
  }
2753
- return await readPsProcessStartedAtMs(pid);
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
+ );
2754
3399
  }
2755
- async function readLinuxProcessStartedAtMs(pid) {
2756
- let statRaw;
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;
2757
3433
  try {
2758
- statRaw = await (0, import_promises.readFile)(`/proc/${pid}/stat`, "utf8");
2759
- } catch (error) {
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 {
2760
3465
  return null;
2761
3466
  }
2762
- const startTicks = parseLinuxProcessStartTicks(statRaw);
2763
- if (startTicks === null) {
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") {
2764
3477
  return null;
2765
3478
  }
2766
- const [bootTimeMs, clockTicksPerSecond] = await Promise.all([
2767
- readLinuxBootTimeMs(),
2768
- readLinuxClockTicksPerSecond()
2769
- ]);
2770
- if (bootTimeMs === null || clockTicksPerSecond === null) {
3479
+ const parsed = value;
3480
+ const owner = parseProcessOwner(parsed.owner);
3481
+ if (!owner || typeof parsed.clientId !== "string" || typeof parsed.createdAt !== "string") {
2771
3482
  return null;
2772
3483
  }
2773
- return Math.floor(
2774
- bootTimeMs + startTicks * 1e3 / clockTicksPerSecond
2775
- );
3484
+ return {
3485
+ clientId: parsed.clientId,
3486
+ createdAt: parsed.createdAt,
3487
+ owner
3488
+ };
2776
3489
  }
2777
- function parseLinuxProcessStartTicks(statRaw) {
2778
- const closingParenIndex = statRaw.lastIndexOf(")");
2779
- if (closingParenIndex === -1) {
2780
- return null;
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
+ );
2781
3500
  }
2782
- const fields = statRaw.slice(closingParenIndex + 2).trim().split(/\s+/);
2783
- const startTicks = Number(fields[LINUX_STAT_START_TIME_FIELD_INDEX]);
2784
- return Number.isFinite(startTicks) && startTicks >= 0 ? startTicks : null;
2785
3501
  }
2786
- async function readLinuxBootTimeMs() {
2787
- try {
2788
- const statRaw = await (0, import_promises.readFile)("/proc/stat", "utf8");
2789
- const bootTimeLine = statRaw.split("\n").find((line) => line.startsWith("btime "));
2790
- if (!bootTimeLine) {
2791
- return null;
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";
2792
3638
  }
2793
- const bootTimeSeconds = Number.parseInt(
2794
- bootTimeLine.slice("btime ".length),
2795
- 10
2796
- );
2797
- return Number.isFinite(bootTimeSeconds) ? bootTimeSeconds * 1e3 : null;
2798
- } catch {
2799
- return null;
3639
+ await sleep5(100);
2800
3640
  }
2801
- }
2802
- async function readLinuxClockTicksPerSecond() {
2803
- linuxClockTicksPerSecondPromise ??= execFileAsync(
2804
- "getconf",
2805
- ["CLK_TCK"]
2806
- ).then(
2807
- ({ stdout }) => {
2808
- const value = Number.parseInt(stdout.trim(), 10);
2809
- return Number.isFinite(value) && value > 0 ? value : null;
2810
- },
2811
- () => null
3641
+ throw new Error(
3642
+ `Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
2812
3643
  );
2813
- return await linuxClockTicksPerSecondPromise;
2814
3644
  }
2815
- async function readPsProcessStartedAtMs(pid) {
3645
+ function normalizeDiscoveryUrl(cdpUrl) {
3646
+ let parsed;
2816
3647
  try {
2817
- const { stdout } = await execFileAsync(
2818
- "ps",
2819
- ["-p", String(pid), "-o", "lstart="],
2820
- { env: PS_COMMAND_ENV }
3648
+ parsed = new URL(cdpUrl);
3649
+ } catch {
3650
+ throw new Error(
3651
+ `Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
2821
3652
  );
2822
- return parsePsStartedAtMs(stdout);
2823
- } catch (error) {
2824
- return null;
2825
3653
  }
2826
- }
2827
- function parsePsStartedAtMs(stdout) {
2828
- const raw = stdout.trim();
2829
- if (!raw) {
2830
- return null;
3654
+ if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
3655
+ return parsed;
2831
3656
  }
2832
- const startedAtMs = Date.parse(raw);
2833
- return Number.isNaN(startedAtMs) ? null : startedAtMs;
2834
- }
2835
- async function readWindowsProcessStartedAtMs(pid) {
2836
- const script = [
2837
- "$process = Get-Process -Id " + String(pid) + " -ErrorAction SilentlyContinue",
2838
- "if ($null -eq $process) { exit 3 }",
2839
- '$process.StartTime.ToUniversalTime().ToString("o")'
2840
- ].join("; ");
2841
- try {
2842
- const { stdout } = await execFileAsync(
2843
- "powershell.exe",
2844
- ["-NoLogo", "-NoProfile", "-Command", script]
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).`
2845
3660
  );
2846
- return parsePsStartedAtMs(stdout);
2847
- } catch (error) {
2848
- return null;
2849
3661
  }
3662
+ const normalized = new URL(parsed.toString());
3663
+ normalized.pathname = "/json/version";
3664
+ normalized.search = "";
3665
+ normalized.hash = "";
3666
+ return normalized;
2850
3667
  }
2851
- function getErrorCode(error) {
2852
- return typeof error === "object" && error !== null && "code" in error && (typeof error.code === "string" || typeof error.code === "number") ? error.code : void 0;
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
+ });
2853
3689
  }
2854
- async function sleep(ms) {
3690
+ async function sleep5(ms) {
2855
3691
  await new Promise((resolve) => setTimeout(resolve, ms));
2856
3692
  }
2857
3693
 
2858
3694
  // src/browser/pool.ts
2859
3695
  var BrowserPool = class {
2860
3696
  browser = null;
2861
- cdpProxy = null;
2862
- launchedProcess = null;
2863
- managedRuntimeProfile = null;
3697
+ activeSessionClose = null;
3698
+ closeInFlight = null;
2864
3699
  defaults;
2865
3700
  constructor(defaults = {}) {
2866
3701
  this.defaults = defaults;
2867
3702
  }
2868
3703
  async launch(options = {}) {
2869
- if (this.browser || this.cdpProxy || this.launchedProcess || this.managedRuntimeProfile) {
3704
+ if (this.browser || this.activeSessionClose) {
2870
3705
  await this.close();
2871
3706
  }
2872
3707
  const mode = options.mode ?? this.defaults.mode ?? "chromium";
@@ -2913,30 +3748,26 @@ var BrowserPool = class {
2913
3748
  return this.launchSandbox(options);
2914
3749
  }
2915
3750
  async close() {
2916
- const browser = this.browser;
2917
- const cdpProxy = this.cdpProxy;
2918
- const launchedProcess = this.launchedProcess;
2919
- const managedRuntimeProfile = this.managedRuntimeProfile;
2920
- this.browser = null;
2921
- this.cdpProxy = null;
2922
- this.launchedProcess = null;
3751
+ if (this.closeInFlight) {
3752
+ await this.closeInFlight;
3753
+ return;
3754
+ }
3755
+ const closeOperation = this.closeCurrent();
3756
+ this.closeInFlight = closeOperation;
2923
3757
  try {
2924
- if (browser) {
2925
- await browser.close().catch(() => void 0);
2926
- }
3758
+ await closeOperation;
3759
+ this.browser = null;
3760
+ this.activeSessionClose = null;
2927
3761
  } finally {
2928
- cdpProxy?.close();
2929
- await killProcessTree(launchedProcess);
2930
- if (managedRuntimeProfile) {
2931
- await persistIsolatedRuntimeProfile(
2932
- managedRuntimeProfile.userDataDir,
2933
- managedRuntimeProfile.persistentUserDataDir
2934
- );
2935
- if (this.managedRuntimeProfile === managedRuntimeProfile) {
2936
- this.managedRuntimeProfile = null;
2937
- }
2938
- }
3762
+ this.closeInFlight = null;
3763
+ }
3764
+ }
3765
+ async closeCurrent() {
3766
+ if (this.activeSessionClose) {
3767
+ await this.activeSessionClose();
3768
+ return;
2939
3769
  }
3770
+ await this.browser?.close().catch(() => void 0);
2940
3771
  }
2941
3772
  async connectToRunning(cdpUrl, timeout) {
2942
3773
  let browser = null;
@@ -2951,11 +3782,14 @@ var BrowserPool = class {
2951
3782
  }
2952
3783
  cdpProxy = new CDPProxy(browserWsUrl, targetId);
2953
3784
  const proxyWsUrl = await cdpProxy.start();
2954
- browser = await import_playwright.chromium.connectOverCDP(proxyWsUrl, {
3785
+ browser = await import_playwright2.chromium.connectOverCDP(proxyWsUrl, {
2955
3786
  timeout: timeout ?? 3e4
2956
3787
  });
2957
3788
  this.browser = browser;
2958
- this.cdpProxy = cdpProxy;
3789
+ this.activeSessionClose = async () => {
3790
+ await browser?.close().catch(() => void 0);
3791
+ cdpProxy?.close();
3792
+ };
2959
3793
  const { context, page } = await pickBrowserContextAndPage(browser);
2960
3794
  return { browser, context, page, isExternal: true };
2961
3795
  } catch (error) {
@@ -2964,7 +3798,7 @@ var BrowserPool = class {
2964
3798
  }
2965
3799
  cdpProxy?.close();
2966
3800
  this.browser = null;
2967
- this.cdpProxy = null;
3801
+ this.activeSessionClose = null;
2968
3802
  throw error;
2969
3803
  }
2970
3804
  }
@@ -2984,60 +3818,29 @@ var BrowserPool = class {
2984
3818
  sourceUserDataDir,
2985
3819
  profileDirectory
2986
3820
  );
2987
- const runtimeProfile = await createIsolatedRuntimeProfile(
2988
- persistentProfile.userDataDir
2989
- );
2990
- const debugPort = await reserveDebugPort();
2991
- const headless = resolveLaunchHeadless(
2992
- "real",
2993
- options.headless,
2994
- this.defaults.headless
2995
- );
2996
- const launchArgs = buildRealBrowserLaunchArgs({
2997
- userDataDir: runtimeProfile.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,
2998
3830
  profileDirectory,
2999
- debugPort,
3000
- headless
3001
- });
3002
- const processHandle = (0, import_node_child_process2.spawn)(executablePath, launchArgs, {
3003
- detached: process.platform !== "win32",
3004
- stdio: "ignore"
3831
+ timeoutMs: options.timeout ?? 3e4
3005
3832
  });
3006
- processHandle.unref();
3007
- let browser = null;
3008
- try {
3009
- const wsUrl = await resolveCdpWebSocketUrl(
3010
- `http://127.0.0.1:${debugPort}`,
3011
- options.timeout ?? 3e4
3012
- );
3013
- browser = await import_playwright.chromium.connectOverCDP(wsUrl, {
3014
- timeout: options.timeout ?? 3e4
3015
- });
3016
- const { context, page } = await createOwnedBrowserContextAndPage(
3017
- browser
3018
- );
3019
- if (options.initialUrl) {
3020
- await page.goto(options.initialUrl, {
3021
- waitUntil: "domcontentloaded",
3022
- timeout: options.timeout ?? 3e4
3023
- });
3024
- }
3025
- this.browser = browser;
3026
- this.launchedProcess = processHandle;
3027
- this.managedRuntimeProfile = runtimeProfile;
3028
- return { browser, context, page, isExternal: false };
3029
- } catch (error) {
3030
- await browser?.close().catch(() => void 0);
3031
- await killProcessTree(processHandle);
3032
- await (0, import_promises2.rm)(runtimeProfile.userDataDir, {
3033
- recursive: true,
3034
- force: true
3035
- }).catch(() => void 0);
3036
- throw error;
3037
- }
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
+ };
3038
3841
  }
3039
3842
  async launchSandbox(options) {
3040
- const browser = await import_playwright.chromium.launch({
3843
+ const browser = await import_playwright2.chromium.launch({
3041
3844
  headless: resolveLaunchHeadless(
3042
3845
  "chromium",
3043
3846
  options.headless,
@@ -3050,11 +3853,14 @@ var BrowserPool = class {
3050
3853
  const context = await browser.newContext(options.context || {});
3051
3854
  const page = await context.newPage();
3052
3855
  this.browser = browser;
3856
+ this.activeSessionClose = async () => {
3857
+ await browser.close().catch(() => void 0);
3858
+ };
3053
3859
  return { browser, context, page, isExternal: false };
3054
3860
  }
3055
3861
  };
3056
3862
  async function pickBrowserContextAndPage(browser) {
3057
- const context = getPrimaryBrowserContext(browser);
3863
+ const context = getPrimaryBrowserContext2(browser);
3058
3864
  const page = await getAttachedPageOrCreate(context);
3059
3865
  return { context, page };
3060
3866
  }
@@ -3067,11 +3873,6 @@ function resolveLaunchHeadless(mode, requestedHeadless, defaultHeadless) {
3067
3873
  }
3068
3874
  return mode === "real";
3069
3875
  }
3070
- async function createOwnedBrowserContextAndPage(browser) {
3071
- const context = getPrimaryBrowserContext(browser);
3072
- const page = await getExistingPageOrCreate(context);
3073
- return { context, page };
3074
- }
3075
3876
  async function getAttachedPageOrCreate(context) {
3076
3877
  const pages = context.pages();
3077
3878
  const inspectablePage = pages.find(
@@ -3086,14 +3887,7 @@ async function getAttachedPageOrCreate(context) {
3086
3887
  }
3087
3888
  return await context.newPage();
3088
3889
  }
3089
- async function getExistingPageOrCreate(context) {
3090
- const existingPage = context.pages()[0];
3091
- if (existingPage) {
3092
- return existingPage;
3093
- }
3094
- return await context.newPage();
3095
- }
3096
- function getPrimaryBrowserContext(browser) {
3890
+ function getPrimaryBrowserContext2(browser) {
3097
3891
  const contexts = browser.contexts();
3098
3892
  if (contexts.length === 0) {
3099
3893
  throw new Error(
@@ -3112,125 +3906,6 @@ function safePageUrl(page) {
3112
3906
  return "";
3113
3907
  }
3114
3908
  }
3115
- function normalizeDiscoveryUrl(cdpUrl) {
3116
- let parsed;
3117
- try {
3118
- parsed = new URL(cdpUrl);
3119
- } catch {
3120
- throw new Error(
3121
- `Invalid CDP URL "${cdpUrl}". Use an http(s) or ws(s) endpoint.`
3122
- );
3123
- }
3124
- if (parsed.protocol === "ws:" || parsed.protocol === "wss:") {
3125
- return parsed;
3126
- }
3127
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
3128
- throw new Error(
3129
- `Unsupported CDP URL protocol "${parsed.protocol}". Use http(s) or ws(s).`
3130
- );
3131
- }
3132
- const normalized = new URL(parsed.toString());
3133
- normalized.pathname = "/json/version";
3134
- normalized.search = "";
3135
- normalized.hash = "";
3136
- return normalized;
3137
- }
3138
- async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
3139
- if (cdpUrl.startsWith("ws://") || cdpUrl.startsWith("wss://")) {
3140
- return cdpUrl;
3141
- }
3142
- const versionUrl = normalizeDiscoveryUrl(cdpUrl);
3143
- const deadline = Date.now() + timeoutMs;
3144
- let lastError = "CDP discovery did not respond.";
3145
- while (Date.now() < deadline) {
3146
- const remaining = Math.max(deadline - Date.now(), 1e3);
3147
- try {
3148
- const response = await fetch(versionUrl, {
3149
- signal: AbortSignal.timeout(Math.min(remaining, 5e3))
3150
- });
3151
- if (!response.ok) {
3152
- lastError = `${response.status} ${response.statusText}`;
3153
- } else {
3154
- const payload = await response.json();
3155
- const wsUrl = payload && typeof payload === "object" && !Array.isArray(payload) && typeof payload.webSocketDebuggerUrl === "string" ? payload.webSocketDebuggerUrl : null;
3156
- if (wsUrl && wsUrl.trim()) {
3157
- return wsUrl;
3158
- }
3159
- lastError = "CDP discovery response did not include webSocketDebuggerUrl.";
3160
- }
3161
- } catch (error) {
3162
- lastError = error instanceof Error ? error.message : "Unknown error";
3163
- }
3164
- await sleep2(100);
3165
- }
3166
- throw new Error(
3167
- `Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
3168
- );
3169
- }
3170
- async function reserveDebugPort() {
3171
- return await new Promise((resolve, reject) => {
3172
- const server = (0, import_node_net.createServer)();
3173
- server.unref();
3174
- server.on("error", reject);
3175
- server.listen(0, "127.0.0.1", () => {
3176
- const address = server.address();
3177
- if (!address || typeof address === "string") {
3178
- server.close();
3179
- reject(new Error("Failed to reserve a local debug port."));
3180
- return;
3181
- }
3182
- server.close((error) => {
3183
- if (error) {
3184
- reject(error);
3185
- return;
3186
- }
3187
- resolve(address.port);
3188
- });
3189
- });
3190
- });
3191
- }
3192
- function buildRealBrowserLaunchArgs(options) {
3193
- const args = [
3194
- `--user-data-dir=${options.userDataDir}`,
3195
- `--profile-directory=${options.profileDirectory}`,
3196
- `--remote-debugging-port=${options.debugPort}`,
3197
- "--disable-blink-features=AutomationControlled"
3198
- ];
3199
- if (options.headless) {
3200
- args.push("--headless=new");
3201
- }
3202
- return args;
3203
- }
3204
- async function killProcessTree(processHandle) {
3205
- if (!processHandle || processHandle.pid == null || processHandle.exitCode !== null) {
3206
- return;
3207
- }
3208
- if (process.platform === "win32") {
3209
- await new Promise((resolve) => {
3210
- const killer = (0, import_node_child_process2.spawn)(
3211
- "taskkill",
3212
- ["/pid", String(processHandle.pid), "/t", "/f"],
3213
- {
3214
- stdio: "ignore"
3215
- }
3216
- );
3217
- killer.on("error", () => resolve());
3218
- killer.on("exit", () => resolve());
3219
- });
3220
- return;
3221
- }
3222
- try {
3223
- process.kill(-processHandle.pid, "SIGKILL");
3224
- } catch {
3225
- try {
3226
- processHandle.kill("SIGKILL");
3227
- } catch {
3228
- }
3229
- }
3230
- }
3231
- async function sleep2(ms) {
3232
- await new Promise((resolve) => setTimeout(resolve, ms));
3233
- }
3234
3909
 
3235
3910
  // src/navigation.ts
3236
3911
  var DEFAULT_TIMEOUT = 3e4;
@@ -3610,7 +4285,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
3610
4285
  TRANSIENT_CONTEXT_RETRY_DELAY_MS,
3611
4286
  Math.max(0, deadline - Date.now())
3612
4287
  );
3613
- await sleep3(retryDelay);
4288
+ await sleep6(retryDelay);
3614
4289
  }
3615
4290
  }
3616
4291
  }
@@ -3643,7 +4318,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
3643
4318
  () => ({ kind: "resolved" }),
3644
4319
  (error) => ({ kind: "rejected", error })
3645
4320
  );
3646
- const timeoutPromise = sleep3(
4321
+ const timeoutPromise = sleep6(
3647
4322
  timeout + FRAME_EVALUATE_GRACE_MS
3648
4323
  ).then(() => ({ kind: "timeout" }));
3649
4324
  const result = await Promise.race([
@@ -3785,7 +4460,7 @@ function isIgnorableFrameError(error) {
3785
4460
  const message = error.message;
3786
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");
3787
4462
  }
3788
- function sleep3(ms) {
4463
+ function sleep6(ms) {
3789
4464
  return new Promise((resolve) => {
3790
4465
  setTimeout(resolve, ms);
3791
4466
  });
@@ -8036,7 +8711,7 @@ async function closeTab(context, activePage, index) {
8036
8711
  }
8037
8712
 
8038
8713
  // src/actions/cookies.ts
8039
- var import_promises3 = require("fs/promises");
8714
+ var import_promises6 = require("fs/promises");
8040
8715
  async function getCookies(context, url) {
8041
8716
  return context.cookies(url ? [url] : void 0);
8042
8717
  }
@@ -8048,10 +8723,10 @@ async function clearCookies(context) {
8048
8723
  }
8049
8724
  async function exportCookies(context, filePath, url) {
8050
8725
  const cookies = await context.cookies(url ? [url] : void 0);
8051
- 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");
8052
8727
  }
8053
8728
  async function importCookies(context, filePath) {
8054
- const raw = await (0, import_promises3.readFile)(filePath, "utf-8");
8729
+ const raw = await (0, import_promises6.readFile)(filePath, "utf-8");
8055
8730
  const cookies = JSON.parse(raw);
8056
8731
  await context.addCookies(cookies);
8057
8732
  }
@@ -8362,7 +9037,7 @@ var AdaptiveNetworkTracker = class {
8362
9037
  this.idleSince = 0;
8363
9038
  }
8364
9039
  const remaining = Math.max(1, options.deadline - now);
8365
- await sleep4(Math.min(NETWORK_POLL_MS, remaining));
9040
+ await sleep7(Math.min(NETWORK_POLL_MS, remaining));
8366
9041
  }
8367
9042
  }
8368
9043
  handleRequestStarted = (request) => {
@@ -8407,7 +9082,7 @@ var AdaptiveNetworkTracker = class {
8407
9082
  return false;
8408
9083
  }
8409
9084
  };
8410
- async function sleep4(ms) {
9085
+ async function sleep7(ms) {
8411
9086
  await new Promise((resolve) => {
8412
9087
  setTimeout(resolve, ms);
8413
9088
  });
@@ -10109,13 +10784,13 @@ function dedupeNewest(entries) {
10109
10784
  }
10110
10785
 
10111
10786
  // src/cloud/cdp-client.ts
10112
- var import_playwright2 = require("playwright");
10787
+ var import_playwright3 = require("playwright");
10113
10788
  var CloudCdpClient = class {
10114
10789
  async connect(args) {
10115
10790
  const endpoint = withTokenQuery(args.wsUrl, args.token);
10116
10791
  let browser;
10117
10792
  try {
10118
- browser = await import_playwright2.chromium.connectOverCDP(endpoint);
10793
+ browser = await import_playwright3.chromium.connectOverCDP(endpoint);
10119
10794
  } catch (error) {
10120
10795
  const message = error instanceof Error ? error.message : "Failed to connect to cloud CDP endpoint.";
10121
10796
  throw new OpensteerCloudError("CLOUD_TRANSPORT_ERROR", message);
@@ -11932,7 +12607,7 @@ async function executeAgentAction(page, action) {
11932
12607
  }
11933
12608
  case "wait": {
11934
12609
  const ms = numberOr(action.timeMs, action.time_ms, 1e3);
11935
- await sleep5(ms);
12610
+ await sleep8(ms);
11936
12611
  return;
11937
12612
  }
11938
12613
  case "goto": {
@@ -12097,7 +12772,7 @@ async function pressKeyCombo(page, combo) {
12097
12772
  }
12098
12773
  }
12099
12774
  }
12100
- function sleep5(ms) {
12775
+ function sleep8(ms) {
12101
12776
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
12102
12777
  }
12103
12778
 
@@ -12128,7 +12803,7 @@ var OpensteerCuaAgentHandler = class {
12128
12803
  if (isMutatingAgentAction(action)) {
12129
12804
  this.onMutatingAction?.(action);
12130
12805
  }
12131
- await sleep6(this.config.waitBetweenActionsMs);
12806
+ await sleep9(this.config.waitBetweenActionsMs);
12132
12807
  });
12133
12808
  try {
12134
12809
  const result = await this.client.execute({
@@ -12190,7 +12865,7 @@ var OpensteerCuaAgentHandler = class {
12190
12865
  await this.cursorController.preview({ x, y }, "agent");
12191
12866
  }
12192
12867
  };
12193
- function sleep6(ms) {
12868
+ function sleep9(ms) {
12194
12869
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
12195
12870
  }
12196
12871
 
@@ -12626,7 +13301,7 @@ var CursorController = class {
12626
13301
  for (const step of motion.points) {
12627
13302
  await this.renderer.move(step, this.style);
12628
13303
  if (motion.stepDelayMs > 0) {
12629
- await sleep7(motion.stepDelayMs);
13304
+ await sleep10(motion.stepDelayMs);
12630
13305
  }
12631
13306
  }
12632
13307
  if (shouldPulse(intent)) {
@@ -12784,7 +13459,7 @@ function clamp2(value, min, max) {
12784
13459
  function shouldPulse(intent) {
12785
13460
  return intent === "click" || intent === "dblclick" || intent === "rightclick" || intent === "agent";
12786
13461
  }
12787
- function sleep7(ms) {
13462
+ function sleep10(ms) {
12788
13463
  return new Promise((resolve) => setTimeout(resolve, ms));
12789
13464
  }
12790
13465
 
@@ -13258,15 +13933,19 @@ var Opensteer = class _Opensteer {
13258
13933
  }
13259
13934
  return;
13260
13935
  }
13936
+ let closedOwnedBrowser = false;
13261
13937
  try {
13262
13938
  if (this.ownsBrowser) {
13263
13939
  await this.pool.close();
13940
+ closedOwnedBrowser = true;
13264
13941
  }
13265
13942
  } finally {
13266
13943
  this.browser = null;
13267
13944
  this.pageRef = null;
13268
13945
  this.contextRef = null;
13269
- this.ownsBrowser = false;
13946
+ if (!this.ownsBrowser || closedOwnedBrowser) {
13947
+ this.ownsBrowser = false;
13948
+ }
13270
13949
  if (this.cursorController) {
13271
13950
  await this.cursorController.dispose().catch(() => void 0);
13272
13951
  }
@@ -15597,19 +16276,19 @@ function buildLocalRunId(namespace) {
15597
16276
  }
15598
16277
 
15599
16278
  // src/browser/chromium-profile.ts
15600
- var import_node_util2 = require("util");
15601
- var import_node_child_process4 = require("child_process");
15602
- var import_node_crypto2 = require("crypto");
15603
- var import_promises4 = require("fs/promises");
15604
- var import_node_fs2 = require("fs");
15605
- 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");
15606
16285
  var import_node_os2 = require("os");
15607
- var import_playwright3 = require("playwright");
16286
+ var import_playwright4 = require("playwright");
15608
16287
 
15609
16288
  // src/auth/keychain-store.ts
15610
- var import_node_child_process3 = require("child_process");
16289
+ var import_node_child_process4 = require("child_process");
15611
16290
  function commandExists(command) {
15612
- const result = (0, import_node_child_process3.spawnSync)(command, ["--help"], {
16291
+ const result = (0, import_node_child_process4.spawnSync)(command, ["--help"], {
15613
16292
  encoding: "utf8",
15614
16293
  stdio: "ignore"
15615
16294
  });
@@ -15648,7 +16327,7 @@ function createMacosSecurityStore() {
15648
16327
  return {
15649
16328
  backend: "macos-security",
15650
16329
  get(service, account) {
15651
- const result = (0, import_node_child_process3.spawnSync)(
16330
+ const result = (0, import_node_child_process4.spawnSync)(
15652
16331
  "security",
15653
16332
  ["find-generic-password", "-s", service, "-a", account, "-w"],
15654
16333
  { encoding: "utf8" }
@@ -15670,14 +16349,14 @@ function createMacosSecurityStore() {
15670
16349
  "-w",
15671
16350
  secret
15672
16351
  ];
15673
- const result = (0, import_node_child_process3.spawnSync)("security", args, { encoding: "utf8" });
16352
+ const result = (0, import_node_child_process4.spawnSync)("security", args, { encoding: "utf8" });
15674
16353
  if (commandFailed(result)) {
15675
16354
  throw buildCommandError("security", args, result);
15676
16355
  }
15677
16356
  },
15678
16357
  delete(service, account) {
15679
16358
  const args = ["delete-generic-password", "-s", service, "-a", account];
15680
- const result = (0, import_node_child_process3.spawnSync)("security", args, { encoding: "utf8" });
16359
+ const result = (0, import_node_child_process4.spawnSync)("security", args, { encoding: "utf8" });
15681
16360
  if (commandFailed(result)) {
15682
16361
  return;
15683
16362
  }
@@ -15688,7 +16367,7 @@ function createLinuxSecretToolStore() {
15688
16367
  return {
15689
16368
  backend: "linux-secret-tool",
15690
16369
  get(service, account) {
15691
- const result = (0, import_node_child_process3.spawnSync)(
16370
+ const result = (0, import_node_child_process4.spawnSync)(
15692
16371
  "secret-tool",
15693
16372
  ["lookup", "service", service, "account", account],
15694
16373
  {
@@ -15711,7 +16390,7 @@ function createLinuxSecretToolStore() {
15711
16390
  "account",
15712
16391
  account
15713
16392
  ];
15714
- const result = (0, import_node_child_process3.spawnSync)("secret-tool", args, {
16393
+ const result = (0, import_node_child_process4.spawnSync)("secret-tool", args, {
15715
16394
  encoding: "utf8",
15716
16395
  input: secret
15717
16396
  });
@@ -15721,7 +16400,7 @@ function createLinuxSecretToolStore() {
15721
16400
  },
15722
16401
  delete(service, account) {
15723
16402
  const args = ["clear", "service", service, "account", account];
15724
- (0, import_node_child_process3.spawnSync)("secret-tool", args, {
16403
+ (0, import_node_child_process4.spawnSync)("secret-tool", args, {
15725
16404
  encoding: "utf8"
15726
16405
  });
15727
16406
  }
@@ -15744,7 +16423,7 @@ function createKeychainStore() {
15744
16423
  }
15745
16424
 
15746
16425
  // src/browser/chromium-profile.ts
15747
- var execFileAsync2 = (0, import_node_util2.promisify)(import_node_child_process4.execFile);
16426
+ var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process5.execFile);
15748
16427
  var CHROMIUM_EPOCH_MICROS = 11644473600000000n;
15749
16428
  var AES_BLOCK_BYTES = 16;
15750
16429
  var MAC_KEY_ITERATIONS = 1003;
@@ -15803,20 +16482,20 @@ var CHROMIUM_BRANDS = [
15803
16482
  ];
15804
16483
  function directoryExists(filePath) {
15805
16484
  try {
15806
- return (0, import_node_fs2.statSync)(filePath).isDirectory();
16485
+ return (0, import_node_fs4.statSync)(filePath).isDirectory();
15807
16486
  } catch {
15808
16487
  return false;
15809
16488
  }
15810
16489
  }
15811
16490
  function fileExists(filePath) {
15812
16491
  try {
15813
- return (0, import_node_fs2.statSync)(filePath).isFile();
16492
+ return (0, import_node_fs4.statSync)(filePath).isFile();
15814
16493
  } catch {
15815
16494
  return false;
15816
16495
  }
15817
16496
  }
15818
16497
  function resolveCookieDbPath(profileDir) {
15819
- 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")];
15820
16499
  for (const candidate of candidates) {
15821
16500
  if (fileExists(candidate)) {
15822
16501
  return candidate;
@@ -15825,10 +16504,10 @@ function resolveCookieDbPath(profileDir) {
15825
16504
  return null;
15826
16505
  }
15827
16506
  async function selectProfileDirFromUserDataDir(userDataDir) {
15828
- const entries = await (0, import_promises4.readdir)(userDataDir, {
16507
+ const entries = await (0, import_promises7.readdir)(userDataDir, {
15829
16508
  withFileTypes: true
15830
16509
  }).catch(() => []);
15831
- 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));
15832
16511
  return candidates;
15833
16512
  }
15834
16513
  async function resolveChromiumProfileLocation(inputPath) {
@@ -15836,16 +16515,16 @@ async function resolveChromiumProfileLocation(inputPath) {
15836
16515
  if (!expandedPath) {
15837
16516
  throw new Error("Profile path cannot be empty.");
15838
16517
  }
15839
- if (fileExists(expandedPath) && (0, import_node_path2.basename)(expandedPath) === "Cookies") {
15840
- const directParent = (0, import_node_path2.dirname)(expandedPath);
15841
- const profileDir = (0, import_node_path2.basename)(directParent) === "Network" ? (0, import_node_path2.dirname)(directParent) : directParent;
15842
- 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);
15843
16522
  return {
15844
16523
  userDataDir,
15845
16524
  profileDir,
15846
- profileDirectory: (0, import_node_path2.basename)(profileDir),
16525
+ profileDirectory: (0, import_node_path6.basename)(profileDir),
15847
16526
  cookieDbPath: expandedPath,
15848
- 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
15849
16528
  };
15850
16529
  }
15851
16530
  if (fileExists(expandedPath)) {
@@ -15860,16 +16539,16 @@ async function resolveChromiumProfileLocation(inputPath) {
15860
16539
  }
15861
16540
  const directCookieDb = resolveCookieDbPath(expandedPath);
15862
16541
  if (directCookieDb) {
15863
- const userDataDir = (0, import_node_path2.dirname)(expandedPath);
16542
+ const userDataDir = (0, import_node_path6.dirname)(expandedPath);
15864
16543
  return {
15865
16544
  userDataDir,
15866
16545
  profileDir: expandedPath,
15867
- profileDirectory: (0, import_node_path2.basename)(expandedPath),
16546
+ profileDirectory: (0, import_node_path6.basename)(expandedPath),
15868
16547
  cookieDbPath: directCookieDb,
15869
- 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
15870
16549
  };
15871
16550
  }
15872
- const localStatePath = (0, import_node_path2.join)(expandedPath, "Local State");
16551
+ const localStatePath = (0, import_node_path6.join)(expandedPath, "Local State");
15873
16552
  if (!fileExists(localStatePath)) {
15874
16553
  throw new Error(
15875
16554
  `Unsupported profile source "${inputPath}". Pass a Chromium profile directory, user-data dir, or Cookies database path.`
@@ -15882,7 +16561,7 @@ async function resolveChromiumProfileLocation(inputPath) {
15882
16561
  );
15883
16562
  }
15884
16563
  if (profileDirs.length > 1) {
15885
- 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(", ");
15886
16565
  throw new Error(
15887
16566
  `"${inputPath}" contains multiple Chromium profiles (${candidates}). Pass a specific profile directory such as "${profileDirs[0]}".`
15888
16567
  );
@@ -15897,7 +16576,7 @@ async function resolveChromiumProfileLocation(inputPath) {
15897
16576
  return {
15898
16577
  userDataDir: expandedPath,
15899
16578
  profileDir: selectedProfileDir,
15900
- profileDirectory: (0, import_node_path2.basename)(selectedProfileDir),
16579
+ profileDirectory: (0, import_node_path6.basename)(selectedProfileDir),
15901
16580
  cookieDbPath,
15902
16581
  localStatePath
15903
16582
  };
@@ -15912,25 +16591,25 @@ function detectChromiumBrand(location) {
15912
16591
  return DEFAULT_CHROMIUM_BRAND;
15913
16592
  }
15914
16593
  async function createSqliteSnapshot(dbPath) {
15915
- const snapshotDir = await (0, import_promises4.mkdtemp)((0, import_node_path2.join)((0, import_node_os2.tmpdir)(), "opensteer-cookie-db-"));
15916
- const snapshotPath = (0, import_node_path2.join)(snapshotDir, "Cookies");
15917
- 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);
15918
16597
  for (const suffix of ["-wal", "-shm", "-journal"]) {
15919
16598
  const source = `${dbPath}${suffix}`;
15920
- if (!(0, import_node_fs2.existsSync)(source)) {
16599
+ if (!(0, import_node_fs4.existsSync)(source)) {
15921
16600
  continue;
15922
16601
  }
15923
- await (0, import_promises4.copyFile)(source, `${snapshotPath}${suffix}`);
16602
+ await (0, import_promises7.copyFile)(source, `${snapshotPath}${suffix}`);
15924
16603
  }
15925
16604
  return {
15926
16605
  snapshotPath,
15927
16606
  cleanup: async () => {
15928
- await (0, import_promises4.rm)(snapshotDir, { recursive: true, force: true });
16607
+ await (0, import_promises7.rm)(snapshotDir, { recursive: true, force: true });
15929
16608
  }
15930
16609
  };
15931
16610
  }
15932
16611
  async function querySqliteJson(dbPath, query) {
15933
- const result = await execFileAsync2("sqlite3", ["-json", dbPath, query], {
16612
+ const result = await execFileAsync3("sqlite3", ["-json", dbPath, query], {
15934
16613
  encoding: "utf8",
15935
16614
  maxBuffer: 64 * 1024 * 1024
15936
16615
  });
@@ -15971,7 +16650,7 @@ function stripDomainHashPrefix(buffer, hostKey) {
15971
16650
  if (buffer.length < 32) {
15972
16651
  return buffer;
15973
16652
  }
15974
- 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();
15975
16654
  if (buffer.subarray(0, 32).equals(domainHash)) {
15976
16655
  return buffer.subarray(32);
15977
16656
  }
@@ -15980,7 +16659,7 @@ function stripDomainHashPrefix(buffer, hostKey) {
15980
16659
  function decryptChromiumAes128CbcValue(encryptedValue, key, hostKey) {
15981
16660
  const ciphertext = encryptedValue.length > 3 && encryptedValue[0] === 118 && encryptedValue[1] === 49 && (encryptedValue[2] === 48 || encryptedValue[2] === 49) ? encryptedValue.subarray(3) : encryptedValue;
15982
16661
  const iv = Buffer.alloc(AES_BLOCK_BYTES, " ");
15983
- 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);
15984
16663
  const plaintext = Buffer.concat([
15985
16664
  decipher.update(ciphertext),
15986
16665
  decipher.final()
@@ -15993,7 +16672,7 @@ function decryptChromiumAes256GcmValue(encryptedValue, key) {
15993
16672
  const nonce = encryptedValue.subarray(3, 15);
15994
16673
  const ciphertext = encryptedValue.subarray(15, encryptedValue.length - 16);
15995
16674
  const authTag = encryptedValue.subarray(encryptedValue.length - 16);
15996
- 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);
15997
16676
  decipher.setAuthTag(authTag);
15998
16677
  return Buffer.concat([
15999
16678
  decipher.update(ciphertext),
@@ -16010,7 +16689,7 @@ async function dpapiUnprotect(buffer) {
16010
16689
  ")",
16011
16690
  "[Convert]::ToBase64String($plainBytes)"
16012
16691
  ].join("\n");
16013
- const { stdout } = await execFileAsync2(
16692
+ const { stdout } = await execFileAsync3(
16014
16693
  "powershell.exe",
16015
16694
  ["-NoProfile", "-NonInteractive", "-Command", script],
16016
16695
  {
@@ -16030,7 +16709,7 @@ async function buildChromiumDecryptor(location) {
16030
16709
  `Unable to read ${brand.macService} from macOS Keychain.`
16031
16710
  );
16032
16711
  }
16033
- 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");
16034
16713
  return async (row) => decryptChromiumAes128CbcValue(
16035
16714
  Buffer.from(row.encrypted_value || "", "hex"),
16036
16715
  key,
@@ -16041,7 +16720,7 @@ async function buildChromiumDecryptor(location) {
16041
16720
  const brand = detectChromiumBrand(location);
16042
16721
  const keychainStore = createKeychainStore();
16043
16722
  const password = keychainStore?.get(brand.macService, brand.macAccount) ?? brand.linuxApplications.map((application) => keychainStore?.get(application, application) ?? null).find(Boolean) ?? null;
16044
- const key = (0, import_node_crypto2.pbkdf2Sync)(
16723
+ const key = (0, import_node_crypto5.pbkdf2Sync)(
16045
16724
  password || "peanuts",
16046
16725
  KEY_SALT,
16047
16726
  LINUX_KEY_ITERATIONS,
@@ -16061,7 +16740,7 @@ async function buildChromiumDecryptor(location) {
16061
16740
  );
16062
16741
  }
16063
16742
  const localState = JSON.parse(
16064
- await (0, import_promises4.readFile)(location.localStatePath, "utf8")
16743
+ await (0, import_promises7.readFile)(location.localStatePath, "utf8")
16065
16744
  );
16066
16745
  const encryptedKeyBase64 = localState.os_crypt?.encrypted_key;
16067
16746
  if (!encryptedKeyBase64) {
@@ -16155,22 +16834,22 @@ async function loadCookiesFromSqlite(location) {
16155
16834
  }
16156
16835
  }
16157
16836
  async function loadCookiesFromBrowserSnapshot(location, options) {
16158
- const snapshotRootDir = await (0, import_promises4.mkdtemp)((0, import_node_path2.join)((0, import_node_os2.tmpdir)(), "opensteer-profile-"));
16159
- 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)(
16160
16839
  snapshotRootDir,
16161
- (0, import_node_path2.basename)(location.profileDir)
16840
+ (0, import_node_path6.basename)(location.profileDir)
16162
16841
  );
16163
16842
  let context = null;
16164
16843
  try {
16165
- await (0, import_promises4.cp)(location.profileDir, snapshotProfileDir, {
16844
+ await (0, import_promises7.cp)(location.profileDir, snapshotProfileDir, {
16166
16845
  recursive: true
16167
16846
  });
16168
16847
  if (location.localStatePath) {
16169
- 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"));
16170
16849
  }
16171
16850
  const brand = detectChromiumBrand(location);
16172
- const args = [`--profile-directory=${(0, import_node_path2.basename)(snapshotProfileDir)}`];
16173
- 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, {
16174
16853
  channel: brand.playwrightChannel,
16175
16854
  headless: options.headless ?? true,
16176
16855
  timeout: options.timeout ?? 12e4,
@@ -16179,7 +16858,7 @@ async function loadCookiesFromBrowserSnapshot(location, options) {
16179
16858
  return await context.cookies();
16180
16859
  } finally {
16181
16860
  await context?.close().catch(() => void 0);
16182
- await (0, import_promises4.rm)(snapshotRootDir, { recursive: true, force: true });
16861
+ await (0, import_promises7.rm)(snapshotRootDir, { recursive: true, force: true });
16183
16862
  }
16184
16863
  }
16185
16864
  function isMissingSqliteBinary(error) {
@@ -16422,10 +17101,10 @@ function toResolvedCloudCredential(source, credential) {
16422
17101
  }
16423
17102
 
16424
17103
  // src/auth/machine-credential-store.ts
16425
- var import_node_crypto3 = require("crypto");
16426
- var import_node_fs3 = __toESM(require("fs"), 1);
17104
+ var import_node_crypto6 = require("crypto");
17105
+ var import_node_fs5 = __toESM(require("fs"), 1);
16427
17106
  var import_node_os3 = __toESM(require("os"), 1);
16428
- var import_node_path3 = __toESM(require("path"), 1);
17107
+ var import_node_path7 = __toESM(require("path"), 1);
16429
17108
  var METADATA_VERSION = 2;
16430
17109
  var ACTIVE_TARGET_VERSION = 2;
16431
17110
  var KEYCHAIN_SERVICE = "com.opensteer.cli.cloud";
@@ -16440,7 +17119,7 @@ var MachineCredentialStore = class {
16440
17119
  const appName = options.appName || "opensteer";
16441
17120
  const env = options.env ?? process.env;
16442
17121
  const configDir = resolveConfigDir(appName, env);
16443
- this.authDir = import_node_path3.default.join(configDir, "auth");
17122
+ this.authDir = import_node_path7.default.join(configDir, "auth");
16444
17123
  this.warn = options.warn ?? (() => void 0);
16445
17124
  }
16446
17125
  readCloudCredential(target) {
@@ -16570,18 +17249,18 @@ function createMachineCredentialStore(options = {}) {
16570
17249
  }
16571
17250
  function resolveCredentialSlot(authDir, target) {
16572
17251
  const normalizedBaseUrl = normalizeCredentialUrl(target.baseUrl);
16573
- 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);
16574
17253
  return {
16575
17254
  keychainAccount: `${KEYCHAIN_ACCOUNT_PREFIX}${storageKey}`,
16576
- metadataPath: import_node_path3.default.join(authDir, `cli-login.${storageKey}.json`),
16577
- 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(
16578
17257
  authDir,
16579
17258
  `cli-login.${storageKey}.secret.json`
16580
17259
  )
16581
17260
  };
16582
17261
  }
16583
17262
  function resolveActiveTargetPath(authDir) {
16584
- return import_node_path3.default.join(authDir, ACTIVE_TARGET_FILE_NAME);
17263
+ return import_node_path7.default.join(authDir, ACTIVE_TARGET_FILE_NAME);
16585
17264
  }
16586
17265
  function matchesCredentialTarget(value, target) {
16587
17266
  return normalizeCredentialUrl(value.baseUrl) === normalizeCredentialUrl(target.baseUrl);
@@ -16600,36 +17279,36 @@ function normalizeCloudCredentialTarget(target) {
16600
17279
  }
16601
17280
  function resolveConfigDir(appName, env) {
16602
17281
  if (process.platform === "win32") {
16603
- const appData = env.APPDATA?.trim() || import_node_path3.default.join(import_node_os3.default.homedir(), "AppData", "Roaming");
16604
- 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);
16605
17284
  }
16606
17285
  if (process.platform === "darwin") {
16607
- return import_node_path3.default.join(
17286
+ return import_node_path7.default.join(
16608
17287
  import_node_os3.default.homedir(),
16609
17288
  "Library",
16610
17289
  "Application Support",
16611
17290
  appName
16612
17291
  );
16613
17292
  }
16614
- const xdgConfigHome = env.XDG_CONFIG_HOME?.trim() || import_node_path3.default.join(import_node_os3.default.homedir(), ".config");
16615
- 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);
16616
17295
  }
16617
17296
  function ensureDirectory(directoryPath) {
16618
- import_node_fs3.default.mkdirSync(directoryPath, { recursive: true, mode: 448 });
17297
+ import_node_fs5.default.mkdirSync(directoryPath, { recursive: true, mode: 448 });
16619
17298
  }
16620
17299
  function removeFileIfExists(filePath) {
16621
17300
  try {
16622
- import_node_fs3.default.rmSync(filePath, { force: true });
17301
+ import_node_fs5.default.rmSync(filePath, { force: true });
16623
17302
  } catch {
16624
17303
  return;
16625
17304
  }
16626
17305
  }
16627
17306
  function readMetadata(filePath) {
16628
- if (!import_node_fs3.default.existsSync(filePath)) {
17307
+ if (!import_node_fs5.default.existsSync(filePath)) {
16629
17308
  return null;
16630
17309
  }
16631
17310
  try {
16632
- const raw = import_node_fs3.default.readFileSync(filePath, "utf8");
17311
+ const raw = import_node_fs5.default.readFileSync(filePath, "utf8");
16633
17312
  const parsed = JSON.parse(raw);
16634
17313
  if (parsed.version !== METADATA_VERSION) return null;
16635
17314
  if (parsed.secretBackend !== "keychain" && parsed.secretBackend !== "file") {
@@ -16656,11 +17335,11 @@ function readMetadata(filePath) {
16656
17335
  }
16657
17336
  }
16658
17337
  function readActiveCloudTargetMetadata(filePath) {
16659
- if (!import_node_fs3.default.existsSync(filePath)) {
17338
+ if (!import_node_fs5.default.existsSync(filePath)) {
16660
17339
  return null;
16661
17340
  }
16662
17341
  try {
16663
- const raw = import_node_fs3.default.readFileSync(filePath, "utf8");
17342
+ const raw = import_node_fs5.default.readFileSync(filePath, "utf8");
16664
17343
  const parsed = JSON.parse(raw);
16665
17344
  if (parsed.version !== ACTIVE_TARGET_VERSION) {
16666
17345
  return null;
@@ -16690,22 +17369,22 @@ function parseSecretPayload(raw) {
16690
17369
  }
16691
17370
  }
16692
17371
  function readSecretFile(filePath) {
16693
- if (!import_node_fs3.default.existsSync(filePath)) {
17372
+ if (!import_node_fs5.default.existsSync(filePath)) {
16694
17373
  return null;
16695
17374
  }
16696
17375
  try {
16697
- return parseSecretPayload(import_node_fs3.default.readFileSync(filePath, "utf8"));
17376
+ return parseSecretPayload(import_node_fs5.default.readFileSync(filePath, "utf8"));
16698
17377
  } catch {
16699
17378
  return null;
16700
17379
  }
16701
17380
  }
16702
17381
  function writeJsonFile(filePath, value, options = {}) {
16703
- import_node_fs3.default.writeFileSync(filePath, JSON.stringify(value, null, 2), {
17382
+ import_node_fs5.default.writeFileSync(filePath, JSON.stringify(value, null, 2), {
16704
17383
  encoding: "utf8",
16705
17384
  mode: options.mode ?? 384
16706
17385
  });
16707
17386
  if (typeof options.mode === "number") {
16708
- import_node_fs3.default.chmodSync(filePath, options.mode);
17387
+ import_node_fs5.default.chmodSync(filePath, options.mode);
16709
17388
  }
16710
17389
  }
16711
17390
 
@@ -17039,7 +17718,7 @@ async function ensureCloudCredentialsForCommand(options) {
17039
17718
  const writeProgress = options.writeProgress ?? options.writeStdout ?? ((message) => process.stdout.write(message));
17040
17719
  const writeStderr = options.writeStderr ?? ((message) => process.stderr.write(message));
17041
17720
  const fetchFn = options.fetchFn ?? fetch;
17042
- const sleep8 = options.sleep ?? (async (ms) => {
17721
+ const sleep11 = options.sleep ?? (async (ms) => {
17043
17722
  await new Promise((resolve) => setTimeout(resolve, ms));
17044
17723
  });
17045
17724
  const now = options.now ?? Date.now;
@@ -17083,7 +17762,7 @@ async function ensureCloudCredentialsForCommand(options) {
17083
17762
  fetchFn,
17084
17763
  writeProgress,
17085
17764
  openExternalUrl,
17086
- sleep: sleep8,
17765
+ sleep: sleep11,
17087
17766
  now,
17088
17767
  openBrowser: true
17089
17768
  });
@@ -17495,7 +18174,7 @@ function createDefaultDeps() {
17495
18174
  loadLocalProfileCookies: (profileDir, options) => loadCookiesFromLocalProfileDir(profileDir, options),
17496
18175
  isInteractive: () => Boolean(process.stdin.isTTY && process.stdout.isTTY),
17497
18176
  confirm: async (message) => {
17498
- const rl = (0, import_promises5.createInterface)({
18177
+ const rl = (0, import_promises8.createInterface)({
17499
18178
  input: process.stdin,
17500
18179
  output: process.stderr
17501
18180
  });
@@ -17659,7 +18338,7 @@ async function resolveTargetProfileId(args, deps, client) {
17659
18338
  "Sync target is required in non-interactive mode. Use --to-profile-id <id> or --name <name>."
17660
18339
  );
17661
18340
  }
17662
- const defaultName = `Synced ${import_node_path4.default.basename(args.fromProfileDir)}`;
18341
+ const defaultName = `Synced ${import_node_path8.default.basename(args.fromProfileDir)}`;
17663
18342
  const shouldCreate = await deps.confirm(
17664
18343
  `No destination profile provided. Create a new cloud profile named "${defaultName}"?`
17665
18344
  );