opensteer 0.6.9 → 0.6.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-GTT3A3RU.js → chunk-2ES46WCO.js} +1 -1
- package/dist/{chunk-I66RNYIW.js → chunk-RC4IPQDZ.js} +963 -151
- package/dist/{chunk-K7DQUSZG.js → chunk-U724TBY6.js} +1 -1
- package/dist/{chunk-A6LF44E3.js → chunk-ZRCFF546.js} +1 -1
- package/dist/cli/auth.cjs +1 -1
- package/dist/cli/auth.js +2 -2
- package/dist/cli/profile.cjs +972 -173
- package/dist/cli/profile.js +4 -4
- package/dist/cli/server.cjs +957 -158
- package/dist/cli/server.js +2 -2
- package/dist/index.cjs +966 -153
- package/dist/index.d.cts +8 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.js +7 -3
- package/package.json +1 -1
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
resolveNamespaceDir,
|
|
15
15
|
selectCloudCredential,
|
|
16
16
|
withTokenQuery
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-2ES46WCO.js";
|
|
18
18
|
import {
|
|
19
19
|
detectChromePaths,
|
|
20
20
|
expandHome,
|
|
@@ -25,24 +25,34 @@ import {
|
|
|
25
25
|
} from "./chunk-3H5RRIMZ.js";
|
|
26
26
|
|
|
27
27
|
// src/browser/persistent-profile.ts
|
|
28
|
-
import { createHash } from "crypto";
|
|
29
|
-
import {
|
|
28
|
+
import { createHash, randomUUID } from "crypto";
|
|
29
|
+
import { execFile } from "child_process";
|
|
30
|
+
import { createReadStream, existsSync } from "fs";
|
|
30
31
|
import {
|
|
31
32
|
cp,
|
|
32
33
|
copyFile,
|
|
33
34
|
mkdir,
|
|
34
35
|
mkdtemp,
|
|
35
36
|
readdir,
|
|
37
|
+
readFile,
|
|
36
38
|
rename,
|
|
37
39
|
rm,
|
|
38
40
|
stat,
|
|
39
41
|
writeFile
|
|
40
42
|
} from "fs/promises";
|
|
41
|
-
import { homedir } from "os";
|
|
42
|
-
import { basename, dirname, join } from "path";
|
|
43
|
+
import { homedir, tmpdir } from "os";
|
|
44
|
+
import { basename, dirname, join, relative, sep } from "path";
|
|
45
|
+
import { promisify } from "util";
|
|
46
|
+
var execFileAsync = promisify(execFile);
|
|
43
47
|
var OPENSTEER_META_FILE = ".opensteer-meta.json";
|
|
48
|
+
var OPENSTEER_RUNTIME_META_FILE = ".opensteer-runtime.json";
|
|
49
|
+
var LOCK_OWNER_FILE = "owner.json";
|
|
50
|
+
var LOCK_RECLAIMER_DIR = "reclaimer";
|
|
44
51
|
var PROCESS_STARTED_AT_MS = Math.floor(Date.now() - process.uptime() * 1e3);
|
|
45
52
|
var PROCESS_START_TIME_TOLERANCE_MS = 1e3;
|
|
53
|
+
var PROFILE_LOCK_RETRY_DELAY_MS = 50;
|
|
54
|
+
var PS_COMMAND_ENV = { ...process.env, LC_ALL: "C" };
|
|
55
|
+
var LINUX_STAT_START_TIME_FIELD_INDEX = 19;
|
|
46
56
|
var CHROME_SINGLETON_ENTRIES = /* @__PURE__ */ new Set([
|
|
47
57
|
"SingletonCookie",
|
|
48
58
|
"SingletonLock",
|
|
@@ -52,7 +62,8 @@ var CHROME_SINGLETON_ENTRIES = /* @__PURE__ */ new Set([
|
|
|
52
62
|
]);
|
|
53
63
|
var COPY_SKIP_ENTRIES = /* @__PURE__ */ new Set([
|
|
54
64
|
...CHROME_SINGLETON_ENTRIES,
|
|
55
|
-
OPENSTEER_META_FILE
|
|
65
|
+
OPENSTEER_META_FILE,
|
|
66
|
+
OPENSTEER_RUNTIME_META_FILE
|
|
56
67
|
]);
|
|
57
68
|
var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
58
69
|
"Crash Reports",
|
|
@@ -71,6 +82,11 @@ var SKIPPED_ROOT_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
|
71
82
|
"WidevineCdm",
|
|
72
83
|
"pnacl"
|
|
73
84
|
]);
|
|
85
|
+
var CURRENT_PROCESS_LOCK_OWNER = {
|
|
86
|
+
pid: process.pid,
|
|
87
|
+
processStartedAtMs: PROCESS_STARTED_AT_MS
|
|
88
|
+
};
|
|
89
|
+
var linuxClockTicksPerSecondPromise = null;
|
|
74
90
|
async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory, profilesRootDir = defaultPersistentProfilesRootDir()) {
|
|
75
91
|
const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
|
|
76
92
|
const targetUserDataDir = join(
|
|
@@ -83,27 +99,29 @@ async function getOrCreatePersistentProfile(sourceUserDataDir, profileDirectory,
|
|
|
83
99
|
profileDirectory
|
|
84
100
|
);
|
|
85
101
|
await mkdir(dirname(targetUserDataDir), { recursive: true });
|
|
86
|
-
await
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (!existsSync(sourceProfileDir)) {
|
|
91
|
-
throw new Error(
|
|
92
|
-
`Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
|
|
102
|
+
return await withPersistentProfileLock(targetUserDataDir, async () => {
|
|
103
|
+
await cleanOrphanedOwnedDirs(
|
|
104
|
+
dirname(targetUserDataDir),
|
|
105
|
+
buildPersistentProfileTempDirNamePrefix(targetUserDataDir)
|
|
93
106
|
);
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
if (!existsSync(sourceProfileDir)) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Chrome profile "${profileDirectory}" was not found in "${resolvedSourceUserDataDir}".`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const created = await createPersistentProfileClone(
|
|
113
|
+
resolvedSourceUserDataDir,
|
|
114
|
+
sourceProfileDir,
|
|
115
|
+
targetUserDataDir,
|
|
116
|
+
profileDirectory,
|
|
117
|
+
metadata
|
|
118
|
+
);
|
|
119
|
+
await ensurePersistentProfileMetadata(targetUserDataDir, metadata);
|
|
120
|
+
return {
|
|
121
|
+
created,
|
|
122
|
+
userDataDir: targetUserDataDir
|
|
123
|
+
};
|
|
124
|
+
});
|
|
107
125
|
}
|
|
108
126
|
async function clearPersistentProfileSingletons(userDataDir) {
|
|
109
127
|
await Promise.all(
|
|
@@ -115,6 +133,80 @@ async function clearPersistentProfileSingletons(userDataDir) {
|
|
|
115
133
|
)
|
|
116
134
|
);
|
|
117
135
|
}
|
|
136
|
+
async function createIsolatedRuntimeProfile(sourceUserDataDir, runtimesRootDir = defaultRuntimeProfilesRootDir()) {
|
|
137
|
+
const resolvedSourceUserDataDir = expandHome(sourceUserDataDir);
|
|
138
|
+
const runtimeRootDir = expandHome(runtimesRootDir);
|
|
139
|
+
await mkdir(runtimeRootDir, { recursive: true });
|
|
140
|
+
return await withPersistentProfileLock(
|
|
141
|
+
resolvedSourceUserDataDir,
|
|
142
|
+
async () => {
|
|
143
|
+
const sourceMetadata = await readPersistentProfileMetadata(
|
|
144
|
+
resolvedSourceUserDataDir
|
|
145
|
+
);
|
|
146
|
+
await cleanOrphanedOwnedDirs(
|
|
147
|
+
runtimeRootDir,
|
|
148
|
+
buildRuntimeProfileDirNamePrefix(resolvedSourceUserDataDir)
|
|
149
|
+
);
|
|
150
|
+
const runtimeUserDataDir = await mkdtemp(
|
|
151
|
+
buildRuntimeProfileDirPrefix(
|
|
152
|
+
runtimeRootDir,
|
|
153
|
+
resolvedSourceUserDataDir
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
try {
|
|
157
|
+
await copyUserDataDirSnapshot(
|
|
158
|
+
resolvedSourceUserDataDir,
|
|
159
|
+
runtimeUserDataDir
|
|
160
|
+
);
|
|
161
|
+
await writeRuntimeProfileMetadata(
|
|
162
|
+
runtimeUserDataDir,
|
|
163
|
+
await buildRuntimeProfileMetadata(
|
|
164
|
+
runtimeUserDataDir,
|
|
165
|
+
sourceMetadata?.profileDirectory ?? null
|
|
166
|
+
)
|
|
167
|
+
);
|
|
168
|
+
return {
|
|
169
|
+
persistentUserDataDir: resolvedSourceUserDataDir,
|
|
170
|
+
userDataDir: runtimeUserDataDir
|
|
171
|
+
};
|
|
172
|
+
} catch (error) {
|
|
173
|
+
await rm(runtimeUserDataDir, {
|
|
174
|
+
recursive: true,
|
|
175
|
+
force: true
|
|
176
|
+
}).catch(() => void 0);
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
async function persistIsolatedRuntimeProfile(runtimeUserDataDir, persistentUserDataDir) {
|
|
183
|
+
const resolvedRuntimeUserDataDir = expandHome(runtimeUserDataDir);
|
|
184
|
+
const resolvedPersistentUserDataDir = expandHome(persistentUserDataDir);
|
|
185
|
+
await withPersistentProfileLock(resolvedPersistentUserDataDir, async () => {
|
|
186
|
+
await mkdir(dirname(resolvedPersistentUserDataDir), { recursive: true });
|
|
187
|
+
await cleanOrphanedOwnedDirs(
|
|
188
|
+
dirname(resolvedPersistentUserDataDir),
|
|
189
|
+
buildPersistentProfileTempDirNamePrefix(resolvedPersistentUserDataDir)
|
|
190
|
+
);
|
|
191
|
+
const metadata = await requirePersistentProfileMetadata(
|
|
192
|
+
resolvedPersistentUserDataDir
|
|
193
|
+
);
|
|
194
|
+
const runtimeMetadata = await requireRuntimeProfileMetadata(
|
|
195
|
+
resolvedRuntimeUserDataDir,
|
|
196
|
+
metadata.profileDirectory
|
|
197
|
+
);
|
|
198
|
+
await mergePersistentProfileSnapshot(
|
|
199
|
+
resolvedRuntimeUserDataDir,
|
|
200
|
+
resolvedPersistentUserDataDir,
|
|
201
|
+
metadata,
|
|
202
|
+
runtimeMetadata
|
|
203
|
+
);
|
|
204
|
+
});
|
|
205
|
+
await rm(resolvedRuntimeUserDataDir, {
|
|
206
|
+
recursive: true,
|
|
207
|
+
force: true
|
|
208
|
+
});
|
|
209
|
+
}
|
|
118
210
|
function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
|
|
119
211
|
const hash = createHash("sha256").update(`${sourceUserDataDir}\0${profileDirectory}`).digest("hex").slice(0, 16);
|
|
120
212
|
const sourceLabel = sanitizePathSegment(basename(sourceUserDataDir) || "user-data");
|
|
@@ -124,6 +216,9 @@ function buildPersistentProfileKey(sourceUserDataDir, profileDirectory) {
|
|
|
124
216
|
function defaultPersistentProfilesRootDir() {
|
|
125
217
|
return join(homedir(), ".opensteer", "real-browser-profiles");
|
|
126
218
|
}
|
|
219
|
+
function defaultRuntimeProfilesRootDir() {
|
|
220
|
+
return join(tmpdir(), "opensteer-real-browser-runtimes");
|
|
221
|
+
}
|
|
127
222
|
function sanitizePathSegment(value) {
|
|
128
223
|
const sanitized = value.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/-+/g, "-");
|
|
129
224
|
return sanitized.replace(/^-|-$/g, "") || "profile";
|
|
@@ -131,6 +226,23 @@ function sanitizePathSegment(value) {
|
|
|
131
226
|
function isProfileDirectory(userDataDir, entry) {
|
|
132
227
|
return existsSync(join(userDataDir, entry, "Preferences"));
|
|
133
228
|
}
|
|
229
|
+
async function copyUserDataDirSnapshot(sourceUserDataDir, targetUserDataDir) {
|
|
230
|
+
await cp(sourceUserDataDir, targetUserDataDir, {
|
|
231
|
+
recursive: true,
|
|
232
|
+
filter: (candidatePath) => shouldCopyRuntimeSnapshotEntry(sourceUserDataDir, candidatePath)
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
function shouldCopyRuntimeSnapshotEntry(userDataDir, candidatePath) {
|
|
236
|
+
const candidateRelativePath = relative(userDataDir, candidatePath);
|
|
237
|
+
if (!candidateRelativePath) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
const segments = candidateRelativePath.split(sep).filter(Boolean);
|
|
241
|
+
if (segments.length !== 1) {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
return !COPY_SKIP_ENTRIES.has(segments[0]);
|
|
245
|
+
}
|
|
134
246
|
async function copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, targetProfileDirectory) {
|
|
135
247
|
let entries;
|
|
136
248
|
try {
|
|
@@ -187,19 +299,17 @@ async function createPersistentProfileClone(sourceUserDataDir, sourceProfileDir,
|
|
|
187
299
|
);
|
|
188
300
|
let published = false;
|
|
189
301
|
try {
|
|
190
|
-
await
|
|
191
|
-
recursive: true
|
|
192
|
-
});
|
|
193
|
-
await copyRootLevelEntries(
|
|
302
|
+
await materializePersistentProfileSnapshot(
|
|
194
303
|
sourceUserDataDir,
|
|
304
|
+
sourceProfileDir,
|
|
195
305
|
tempUserDataDir,
|
|
196
|
-
profileDirectory
|
|
306
|
+
profileDirectory,
|
|
307
|
+
metadata
|
|
197
308
|
);
|
|
198
|
-
await writePersistentProfileMetadata(tempUserDataDir, metadata);
|
|
199
309
|
try {
|
|
200
310
|
await rename(tempUserDataDir, targetUserDataDir);
|
|
201
311
|
} catch (error) {
|
|
202
|
-
if (
|
|
312
|
+
if (wasDirPublishedByAnotherProcess(error, targetUserDataDir)) {
|
|
203
313
|
return false;
|
|
204
314
|
}
|
|
205
315
|
throw error;
|
|
@@ -215,60 +325,621 @@ async function createPersistentProfileClone(sourceUserDataDir, sourceProfileDir,
|
|
|
215
325
|
}
|
|
216
326
|
}
|
|
217
327
|
}
|
|
328
|
+
async function materializePersistentProfileSnapshot(sourceUserDataDir, sourceProfileDir, targetUserDataDir, profileDirectory, metadata) {
|
|
329
|
+
if (!existsSync(sourceProfileDir)) {
|
|
330
|
+
throw new Error(
|
|
331
|
+
`Chrome profile "${profileDirectory}" was not found in "${sourceUserDataDir}".`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
await cp(sourceProfileDir, join(targetUserDataDir, profileDirectory), {
|
|
335
|
+
recursive: true
|
|
336
|
+
});
|
|
337
|
+
await copyRootLevelEntries(sourceUserDataDir, targetUserDataDir, profileDirectory);
|
|
338
|
+
await writePersistentProfileMetadata(targetUserDataDir, metadata);
|
|
339
|
+
}
|
|
340
|
+
async function mergePersistentProfileSnapshot(runtimeUserDataDir, persistentUserDataDir, metadata, runtimeMetadata) {
|
|
341
|
+
const tempUserDataDir = await mkdtemp(
|
|
342
|
+
buildPersistentProfileTempDirPrefix(persistentUserDataDir)
|
|
343
|
+
);
|
|
344
|
+
let published = false;
|
|
345
|
+
try {
|
|
346
|
+
const baseEntries = deserializeSnapshotManifestEntries(
|
|
347
|
+
runtimeMetadata.baseEntries
|
|
348
|
+
);
|
|
349
|
+
const currentEntries = await collectPersistentSnapshotEntries(
|
|
350
|
+
persistentUserDataDir,
|
|
351
|
+
metadata.profileDirectory
|
|
352
|
+
);
|
|
353
|
+
const runtimeEntries = await collectPersistentSnapshotEntries(
|
|
354
|
+
runtimeUserDataDir,
|
|
355
|
+
metadata.profileDirectory
|
|
356
|
+
);
|
|
357
|
+
const mergedEntries = resolveMergedSnapshotEntries(
|
|
358
|
+
baseEntries,
|
|
359
|
+
currentEntries,
|
|
360
|
+
runtimeEntries
|
|
361
|
+
);
|
|
362
|
+
await materializeMergedPersistentProfileSnapshot(
|
|
363
|
+
tempUserDataDir,
|
|
364
|
+
currentEntries,
|
|
365
|
+
runtimeEntries,
|
|
366
|
+
mergedEntries
|
|
367
|
+
);
|
|
368
|
+
await writePersistentProfileMetadata(tempUserDataDir, metadata);
|
|
369
|
+
await replaceProfileDirectory(persistentUserDataDir, tempUserDataDir);
|
|
370
|
+
published = true;
|
|
371
|
+
} finally {
|
|
372
|
+
if (!published) {
|
|
373
|
+
await rm(tempUserDataDir, {
|
|
374
|
+
recursive: true,
|
|
375
|
+
force: true
|
|
376
|
+
}).catch(() => void 0);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
async function buildRuntimeProfileMetadata(runtimeUserDataDir, profileDirectory) {
|
|
381
|
+
if (!profileDirectory) {
|
|
382
|
+
return {
|
|
383
|
+
baseEntries: {},
|
|
384
|
+
profileDirectory: null
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
const baseEntries = await collectPersistentSnapshotEntries(
|
|
388
|
+
runtimeUserDataDir,
|
|
389
|
+
profileDirectory
|
|
390
|
+
);
|
|
391
|
+
return {
|
|
392
|
+
baseEntries: serializeSnapshotManifestEntries(baseEntries),
|
|
393
|
+
profileDirectory
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
async function writeRuntimeProfileMetadata(userDataDir, metadata) {
|
|
397
|
+
await writeFile(
|
|
398
|
+
join(userDataDir, OPENSTEER_RUNTIME_META_FILE),
|
|
399
|
+
JSON.stringify(metadata, null, 2)
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
async function readRuntimeProfileMetadata(userDataDir) {
|
|
403
|
+
try {
|
|
404
|
+
const raw = await readFile(
|
|
405
|
+
join(userDataDir, OPENSTEER_RUNTIME_META_FILE),
|
|
406
|
+
"utf8"
|
|
407
|
+
);
|
|
408
|
+
const parsed = JSON.parse(raw);
|
|
409
|
+
const profileDirectory = parsed.profileDirectory === null ? null : typeof parsed.profileDirectory === "string" ? parsed.profileDirectory : void 0;
|
|
410
|
+
if (profileDirectory === void 0 || typeof parsed.baseEntries !== "object" || parsed.baseEntries === null || Array.isArray(parsed.baseEntries)) {
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
const baseEntries = deserializeSnapshotManifestEntries(
|
|
414
|
+
parsed.baseEntries
|
|
415
|
+
);
|
|
416
|
+
return {
|
|
417
|
+
baseEntries: Object.fromEntries(baseEntries),
|
|
418
|
+
profileDirectory
|
|
419
|
+
};
|
|
420
|
+
} catch {
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async function requireRuntimeProfileMetadata(userDataDir, expectedProfileDirectory) {
|
|
425
|
+
const metadata = await readRuntimeProfileMetadata(userDataDir);
|
|
426
|
+
if (!metadata) {
|
|
427
|
+
throw new Error(
|
|
428
|
+
`Runtime profile metadata was not found for "${userDataDir}".`
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
if (metadata.profileDirectory !== expectedProfileDirectory) {
|
|
432
|
+
throw new Error(
|
|
433
|
+
`Runtime profile "${userDataDir}" was created for profile "${metadata.profileDirectory ?? "unknown"}", expected "${expectedProfileDirectory}".`
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
return metadata;
|
|
437
|
+
}
|
|
438
|
+
async function collectPersistentSnapshotEntries(userDataDir, profileDirectory) {
|
|
439
|
+
let rootEntries;
|
|
440
|
+
try {
|
|
441
|
+
rootEntries = await readdir(userDataDir, {
|
|
442
|
+
encoding: "utf8",
|
|
443
|
+
withFileTypes: true
|
|
444
|
+
});
|
|
445
|
+
} catch {
|
|
446
|
+
return /* @__PURE__ */ new Map();
|
|
447
|
+
}
|
|
448
|
+
rootEntries.sort((left, right) => left.name.localeCompare(right.name));
|
|
449
|
+
const collected = /* @__PURE__ */ new Map();
|
|
450
|
+
for (const entry of rootEntries) {
|
|
451
|
+
if (!shouldIncludePersistentRootEntry(
|
|
452
|
+
userDataDir,
|
|
453
|
+
profileDirectory,
|
|
454
|
+
entry.name
|
|
455
|
+
)) {
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
await collectSnapshotEntry(
|
|
459
|
+
join(userDataDir, entry.name),
|
|
460
|
+
entry.name,
|
|
461
|
+
collected
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
return collected;
|
|
465
|
+
}
|
|
466
|
+
function shouldIncludePersistentRootEntry(userDataDir, profileDirectory, entry) {
|
|
467
|
+
if (entry === profileDirectory) {
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
if (COPY_SKIP_ENTRIES.has(entry)) {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
if (SKIPPED_ROOT_DIRECTORIES.has(entry)) {
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
return !isProfileDirectory(userDataDir, entry);
|
|
477
|
+
}
|
|
478
|
+
async function collectSnapshotEntry(sourcePath, relativePath, collected) {
|
|
479
|
+
let entryStat;
|
|
480
|
+
try {
|
|
481
|
+
entryStat = await stat(sourcePath);
|
|
482
|
+
} catch {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
if (entryStat.isDirectory()) {
|
|
486
|
+
collected.set(relativePath, {
|
|
487
|
+
kind: "directory",
|
|
488
|
+
hash: null,
|
|
489
|
+
sourcePath
|
|
490
|
+
});
|
|
491
|
+
let children;
|
|
492
|
+
try {
|
|
493
|
+
children = await readdir(sourcePath, {
|
|
494
|
+
encoding: "utf8",
|
|
495
|
+
withFileTypes: true
|
|
496
|
+
});
|
|
497
|
+
} catch {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
children.sort((left, right) => left.name.localeCompare(right.name));
|
|
501
|
+
for (const child of children) {
|
|
502
|
+
await collectSnapshotEntry(
|
|
503
|
+
join(sourcePath, child.name),
|
|
504
|
+
join(relativePath, child.name),
|
|
505
|
+
collected
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (entryStat.isFile()) {
|
|
511
|
+
collected.set(relativePath, {
|
|
512
|
+
kind: "file",
|
|
513
|
+
hash: await hashFile(sourcePath),
|
|
514
|
+
sourcePath
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function serializeSnapshotManifestEntries(entries) {
|
|
519
|
+
return Object.fromEntries(
|
|
520
|
+
[...entries.entries()].sort(([leftPath], [rightPath]) => leftPath.localeCompare(rightPath)).map(([relativePath, entry]) => [
|
|
521
|
+
relativePath,
|
|
522
|
+
{
|
|
523
|
+
kind: entry.kind,
|
|
524
|
+
hash: entry.hash
|
|
525
|
+
}
|
|
526
|
+
])
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
function deserializeSnapshotManifestEntries(entries) {
|
|
530
|
+
const manifestEntries = /* @__PURE__ */ new Map();
|
|
531
|
+
for (const [relativePath, entry] of Object.entries(entries)) {
|
|
532
|
+
if (!entry || entry.kind !== "directory" && entry.kind !== "file" || !(entry.hash === null || typeof entry.hash === "string")) {
|
|
533
|
+
throw new Error(
|
|
534
|
+
`Runtime profile metadata for "${relativePath}" is invalid.`
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
manifestEntries.set(relativePath, {
|
|
538
|
+
kind: entry.kind,
|
|
539
|
+
hash: entry.hash
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
return manifestEntries;
|
|
543
|
+
}
|
|
544
|
+
function resolveMergedSnapshotEntries(baseEntries, currentEntries, runtimeEntries) {
|
|
545
|
+
const mergedEntries = /* @__PURE__ */ new Map();
|
|
546
|
+
const relativePaths = /* @__PURE__ */ new Set([
|
|
547
|
+
...baseEntries.keys(),
|
|
548
|
+
...currentEntries.keys(),
|
|
549
|
+
...runtimeEntries.keys()
|
|
550
|
+
]);
|
|
551
|
+
for (const relativePath of [...relativePaths].sort(compareSnapshotPaths)) {
|
|
552
|
+
mergedEntries.set(
|
|
553
|
+
relativePath,
|
|
554
|
+
resolveMergedSnapshotEntrySelection(
|
|
555
|
+
relativePath,
|
|
556
|
+
baseEntries.get(relativePath) ?? null,
|
|
557
|
+
currentEntries.get(relativePath) ?? null,
|
|
558
|
+
runtimeEntries.get(relativePath) ?? null
|
|
559
|
+
)
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
return mergedEntries;
|
|
563
|
+
}
|
|
564
|
+
function resolveMergedSnapshotEntrySelection(relativePath, baseEntry, currentEntry, runtimeEntry) {
|
|
565
|
+
if (snapshotEntriesEqual(runtimeEntry, baseEntry)) {
|
|
566
|
+
return currentEntry ? "current" : null;
|
|
567
|
+
}
|
|
568
|
+
if (snapshotEntriesEqual(currentEntry, baseEntry)) {
|
|
569
|
+
return runtimeEntry ? "runtime" : null;
|
|
570
|
+
}
|
|
571
|
+
if (!baseEntry) {
|
|
572
|
+
if (!currentEntry) {
|
|
573
|
+
return runtimeEntry ? "runtime" : null;
|
|
574
|
+
}
|
|
575
|
+
if (!runtimeEntry) {
|
|
576
|
+
return "current";
|
|
577
|
+
}
|
|
578
|
+
if (snapshotEntriesEqual(currentEntry, runtimeEntry)) {
|
|
579
|
+
return "current";
|
|
580
|
+
}
|
|
581
|
+
throw new Error(
|
|
582
|
+
`Concurrent runtime updates changed "${relativePath}" differently; refusing to overwrite the persistent profile.`
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
if (!currentEntry && !runtimeEntry) {
|
|
586
|
+
return null;
|
|
587
|
+
}
|
|
588
|
+
if (snapshotEntriesEqual(currentEntry, runtimeEntry)) {
|
|
589
|
+
return currentEntry ? "current" : null;
|
|
590
|
+
}
|
|
591
|
+
throw new Error(
|
|
592
|
+
`Concurrent runtime updates changed "${relativePath}" differently; refusing to overwrite the persistent profile.`
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
function snapshotEntriesEqual(left, right) {
|
|
596
|
+
if (!left || !right) {
|
|
597
|
+
return left === right;
|
|
598
|
+
}
|
|
599
|
+
return left.kind === right.kind && left.hash === right.hash;
|
|
600
|
+
}
|
|
601
|
+
async function materializeMergedPersistentProfileSnapshot(targetUserDataDir, currentEntries, runtimeEntries, mergedEntries) {
|
|
602
|
+
const selectedEntries = [...mergedEntries.entries()].filter(([, selection]) => selection !== null).sort(
|
|
603
|
+
([leftPath], [rightPath]) => compareSnapshotPaths(leftPath, rightPath)
|
|
604
|
+
);
|
|
605
|
+
for (const [relativePath, selection] of selectedEntries) {
|
|
606
|
+
const entry = (selection === "current" ? currentEntries.get(relativePath) : runtimeEntries.get(relativePath)) ?? null;
|
|
607
|
+
if (!entry) {
|
|
608
|
+
continue;
|
|
609
|
+
}
|
|
610
|
+
const targetPath = join(targetUserDataDir, relativePath);
|
|
611
|
+
if (entry.kind === "directory") {
|
|
612
|
+
await mkdir(targetPath, { recursive: true });
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
await mkdir(dirname(targetPath), { recursive: true });
|
|
616
|
+
await copyFile(entry.sourcePath, targetPath);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
function compareSnapshotPaths(left, right) {
|
|
620
|
+
const leftDepth = left.split(sep).length;
|
|
621
|
+
const rightDepth = right.split(sep).length;
|
|
622
|
+
if (leftDepth !== rightDepth) {
|
|
623
|
+
return leftDepth - rightDepth;
|
|
624
|
+
}
|
|
625
|
+
return left.localeCompare(right);
|
|
626
|
+
}
|
|
627
|
+
async function hashFile(filePath) {
|
|
628
|
+
return new Promise((resolve, reject) => {
|
|
629
|
+
const hash = createHash("sha256");
|
|
630
|
+
const stream = createReadStream(filePath);
|
|
631
|
+
stream.on("data", (chunk) => {
|
|
632
|
+
hash.update(chunk);
|
|
633
|
+
});
|
|
634
|
+
stream.on("error", reject);
|
|
635
|
+
stream.on("end", () => {
|
|
636
|
+
resolve(hash.digest("hex"));
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
}
|
|
218
640
|
async function ensurePersistentProfileMetadata(userDataDir, metadata) {
|
|
219
641
|
if (existsSync(join(userDataDir, OPENSTEER_META_FILE))) {
|
|
220
642
|
return;
|
|
221
643
|
}
|
|
222
644
|
await writePersistentProfileMetadata(userDataDir, metadata);
|
|
223
645
|
}
|
|
224
|
-
function
|
|
646
|
+
async function readPersistentProfileMetadata(userDataDir) {
|
|
647
|
+
try {
|
|
648
|
+
const raw = await readFile(join(userDataDir, OPENSTEER_META_FILE), "utf8");
|
|
649
|
+
const parsed = JSON.parse(raw);
|
|
650
|
+
if (typeof parsed.createdAt !== "string" || typeof parsed.profileDirectory !== "string" || typeof parsed.source !== "string") {
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
return {
|
|
654
|
+
createdAt: parsed.createdAt,
|
|
655
|
+
profileDirectory: parsed.profileDirectory,
|
|
656
|
+
source: parsed.source
|
|
657
|
+
};
|
|
658
|
+
} catch {
|
|
659
|
+
return null;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
async function requirePersistentProfileMetadata(userDataDir) {
|
|
663
|
+
const metadata = await readPersistentProfileMetadata(userDataDir);
|
|
664
|
+
if (!metadata) {
|
|
665
|
+
throw new Error(
|
|
666
|
+
`Persistent profile metadata was not found for "${userDataDir}".`
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
return metadata;
|
|
670
|
+
}
|
|
671
|
+
function wasDirPublishedByAnotherProcess(error, targetDirPath) {
|
|
225
672
|
const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
226
|
-
return existsSync(
|
|
673
|
+
return existsSync(targetDirPath) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
|
|
674
|
+
}
|
|
675
|
+
async function replaceProfileDirectory(targetUserDataDir, replacementUserDataDir) {
|
|
676
|
+
if (!existsSync(targetUserDataDir)) {
|
|
677
|
+
await rename(replacementUserDataDir, targetUserDataDir);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
const backupUserDataDir = buildPersistentProfileBackupDirPath(targetUserDataDir);
|
|
681
|
+
let targetMovedToBackup = false;
|
|
682
|
+
let replacementPublished = false;
|
|
683
|
+
try {
|
|
684
|
+
await rename(targetUserDataDir, backupUserDataDir);
|
|
685
|
+
targetMovedToBackup = true;
|
|
686
|
+
await rename(replacementUserDataDir, targetUserDataDir);
|
|
687
|
+
replacementPublished = true;
|
|
688
|
+
} catch (error) {
|
|
689
|
+
if (targetMovedToBackup && !existsSync(targetUserDataDir)) {
|
|
690
|
+
await rename(backupUserDataDir, targetUserDataDir).catch(() => void 0);
|
|
691
|
+
}
|
|
692
|
+
throw error;
|
|
693
|
+
} finally {
|
|
694
|
+
if (replacementPublished && targetMovedToBackup && existsSync(backupUserDataDir)) {
|
|
695
|
+
await rm(backupUserDataDir, {
|
|
696
|
+
recursive: true,
|
|
697
|
+
force: true
|
|
698
|
+
}).catch(() => void 0);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
async function withPersistentProfileLock(targetUserDataDir, action) {
|
|
703
|
+
const lockDirPath = buildPersistentProfileLockDirPath(targetUserDataDir);
|
|
704
|
+
await mkdir(dirname(lockDirPath), { recursive: true });
|
|
705
|
+
while (true) {
|
|
706
|
+
const tempLockDirPath = `${lockDirPath}-${process.pid}-${PROCESS_STARTED_AT_MS}-${randomUUID()}`;
|
|
707
|
+
try {
|
|
708
|
+
await mkdir(tempLockDirPath);
|
|
709
|
+
await writeLockOwner(tempLockDirPath, CURRENT_PROCESS_LOCK_OWNER);
|
|
710
|
+
try {
|
|
711
|
+
await rename(tempLockDirPath, lockDirPath);
|
|
712
|
+
break;
|
|
713
|
+
} catch (error) {
|
|
714
|
+
if (!wasDirPublishedByAnotherProcess(error, lockDirPath)) {
|
|
715
|
+
throw error;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
} finally {
|
|
719
|
+
await rm(tempLockDirPath, {
|
|
720
|
+
recursive: true,
|
|
721
|
+
force: true
|
|
722
|
+
}).catch(() => void 0);
|
|
723
|
+
}
|
|
724
|
+
const owner = await readLockOwner(lockDirPath);
|
|
725
|
+
if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
await sleep(PROFILE_LOCK_RETRY_DELAY_MS);
|
|
729
|
+
}
|
|
730
|
+
try {
|
|
731
|
+
return await action();
|
|
732
|
+
} finally {
|
|
733
|
+
await rm(lockDirPath, {
|
|
734
|
+
recursive: true,
|
|
735
|
+
force: true
|
|
736
|
+
}).catch(() => void 0);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
async function writeLockOwner(lockDirPath, owner) {
|
|
740
|
+
await writeLockParticipant(join(lockDirPath, LOCK_OWNER_FILE), owner);
|
|
741
|
+
}
|
|
742
|
+
async function readLockOwner(lockDirPath) {
|
|
743
|
+
return await readLockParticipant(join(lockDirPath, LOCK_OWNER_FILE));
|
|
744
|
+
}
|
|
745
|
+
async function writeLockParticipant(filePath, owner, options) {
|
|
746
|
+
await writeFile(filePath, JSON.stringify(owner), options);
|
|
747
|
+
}
|
|
748
|
+
async function readLockParticipant(filePath) {
|
|
749
|
+
return (await readLockParticipantRecord(filePath)).owner;
|
|
750
|
+
}
|
|
751
|
+
async function readLockReclaimerRecord(lockDirPath) {
|
|
752
|
+
return await readLockParticipantRecord(
|
|
753
|
+
join(buildLockReclaimerDirPath(lockDirPath), LOCK_OWNER_FILE)
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
async function readLockParticipantRecord(filePath) {
|
|
757
|
+
try {
|
|
758
|
+
const raw = await readFile(filePath, "utf8");
|
|
759
|
+
const parsed = JSON.parse(raw);
|
|
760
|
+
const pid = Number(parsed.pid);
|
|
761
|
+
const processStartedAtMs = Number(parsed.processStartedAtMs);
|
|
762
|
+
if (!Number.isInteger(pid) || !Number.isInteger(processStartedAtMs)) {
|
|
763
|
+
return {
|
|
764
|
+
exists: true,
|
|
765
|
+
owner: null
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
return {
|
|
769
|
+
exists: true,
|
|
770
|
+
owner: {
|
|
771
|
+
pid,
|
|
772
|
+
processStartedAtMs
|
|
773
|
+
}
|
|
774
|
+
};
|
|
775
|
+
} catch (error) {
|
|
776
|
+
return {
|
|
777
|
+
exists: getErrorCode(error) !== "ENOENT",
|
|
778
|
+
owner: null
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
async function tryReclaimStaleLock(lockDirPath, expectedOwner) {
|
|
783
|
+
if (!await tryAcquireLockReclaimer(lockDirPath)) {
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
let reclaimed = false;
|
|
787
|
+
try {
|
|
788
|
+
const owner = await readLockOwner(lockDirPath);
|
|
789
|
+
if (!lockOwnersEqual(owner, expectedOwner)) {
|
|
790
|
+
return false;
|
|
791
|
+
}
|
|
792
|
+
if (owner && await getProcessLiveness(owner) !== "dead") {
|
|
793
|
+
return false;
|
|
794
|
+
}
|
|
795
|
+
await rm(lockDirPath, {
|
|
796
|
+
recursive: true,
|
|
797
|
+
force: true
|
|
798
|
+
}).catch(() => void 0);
|
|
799
|
+
reclaimed = !existsSync(lockDirPath);
|
|
800
|
+
return reclaimed;
|
|
801
|
+
} finally {
|
|
802
|
+
if (!reclaimed) {
|
|
803
|
+
await rm(buildLockReclaimerDirPath(lockDirPath), {
|
|
804
|
+
recursive: true,
|
|
805
|
+
force: true
|
|
806
|
+
}).catch(() => void 0);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
async function tryAcquireLockReclaimer(lockDirPath) {
|
|
811
|
+
const reclaimerDirPath = buildLockReclaimerDirPath(lockDirPath);
|
|
812
|
+
while (true) {
|
|
813
|
+
const tempReclaimerDirPath = `${reclaimerDirPath}-${process.pid}-${PROCESS_STARTED_AT_MS}-${randomUUID()}`;
|
|
814
|
+
try {
|
|
815
|
+
await mkdir(tempReclaimerDirPath);
|
|
816
|
+
await writeLockOwner(tempReclaimerDirPath, CURRENT_PROCESS_LOCK_OWNER);
|
|
817
|
+
try {
|
|
818
|
+
await rename(tempReclaimerDirPath, reclaimerDirPath);
|
|
819
|
+
return true;
|
|
820
|
+
} catch (error) {
|
|
821
|
+
if (getErrorCode(error) === "ENOENT") {
|
|
822
|
+
return false;
|
|
823
|
+
}
|
|
824
|
+
if (!wasDirPublishedByAnotherProcess(error, reclaimerDirPath)) {
|
|
825
|
+
throw error;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
} catch (error) {
|
|
829
|
+
const code = getErrorCode(error);
|
|
830
|
+
if (code === "ENOENT") {
|
|
831
|
+
return false;
|
|
832
|
+
}
|
|
833
|
+
throw error;
|
|
834
|
+
} finally {
|
|
835
|
+
await rm(tempReclaimerDirPath, {
|
|
836
|
+
recursive: true,
|
|
837
|
+
force: true
|
|
838
|
+
}).catch(() => void 0);
|
|
839
|
+
}
|
|
840
|
+
const reclaimerRecord = await readLockReclaimerRecord(lockDirPath);
|
|
841
|
+
if (!reclaimerRecord.exists || !reclaimerRecord.owner) {
|
|
842
|
+
return false;
|
|
843
|
+
}
|
|
844
|
+
if (await getProcessLiveness(reclaimerRecord.owner) !== "dead") {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
await rm(reclaimerDirPath, {
|
|
848
|
+
recursive: true,
|
|
849
|
+
force: true
|
|
850
|
+
}).catch(() => void 0);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
function lockOwnersEqual(left, right) {
|
|
854
|
+
if (!left || !right) {
|
|
855
|
+
return left === right;
|
|
856
|
+
}
|
|
857
|
+
return left.pid === right.pid && left.processStartedAtMs === right.processStartedAtMs;
|
|
858
|
+
}
|
|
859
|
+
async function getProcessLiveness(owner) {
|
|
860
|
+
if (owner.pid === process.pid && hasMatchingProcessStartTime(
|
|
861
|
+
owner.processStartedAtMs,
|
|
862
|
+
PROCESS_STARTED_AT_MS
|
|
863
|
+
)) {
|
|
864
|
+
return "live";
|
|
865
|
+
}
|
|
866
|
+
const startedAtMs = await readProcessStartedAtMs(owner.pid);
|
|
867
|
+
if (typeof startedAtMs === "number") {
|
|
868
|
+
return hasMatchingProcessStartTime(
|
|
869
|
+
owner.processStartedAtMs,
|
|
870
|
+
startedAtMs
|
|
871
|
+
) ? "live" : "dead";
|
|
872
|
+
}
|
|
873
|
+
return isProcessRunning(owner.pid) ? "unknown" : "dead";
|
|
874
|
+
}
|
|
875
|
+
function hasMatchingProcessStartTime(expectedStartedAtMs, actualStartedAtMs) {
|
|
876
|
+
return Math.abs(expectedStartedAtMs - actualStartedAtMs) <= PROCESS_START_TIME_TOLERANCE_MS;
|
|
227
877
|
}
|
|
228
878
|
function buildPersistentProfileTempDirPrefix(targetUserDataDir) {
|
|
229
879
|
return join(
|
|
230
880
|
dirname(targetUserDataDir),
|
|
231
|
-
`${
|
|
881
|
+
`${buildPersistentProfileTempDirNamePrefix(targetUserDataDir)}${process.pid}-${PROCESS_STARTED_AT_MS}-`
|
|
232
882
|
);
|
|
233
883
|
}
|
|
234
|
-
|
|
884
|
+
function buildPersistentProfileTempDirNamePrefix(targetUserDataDir) {
|
|
885
|
+
return `${basename(targetUserDataDir)}-tmp-`;
|
|
886
|
+
}
|
|
887
|
+
function buildPersistentProfileBackupDirPath(targetUserDataDir) {
|
|
888
|
+
return join(
|
|
889
|
+
dirname(targetUserDataDir),
|
|
890
|
+
`${buildPersistentProfileTempDirNamePrefix(targetUserDataDir)}${process.pid}-${PROCESS_STARTED_AT_MS}-backup-${Date.now()}`
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
function buildPersistentProfileLockDirPath(targetUserDataDir) {
|
|
894
|
+
return join(dirname(targetUserDataDir), `${basename(targetUserDataDir)}.lock`);
|
|
895
|
+
}
|
|
896
|
+
function buildLockReclaimerDirPath(lockDirPath) {
|
|
897
|
+
return join(lockDirPath, LOCK_RECLAIMER_DIR);
|
|
898
|
+
}
|
|
899
|
+
function buildRuntimeProfileKey(sourceUserDataDir) {
|
|
900
|
+
const hash = createHash("sha256").update(sourceUserDataDir).digest("hex").slice(0, 16);
|
|
901
|
+
return `${sanitizePathSegment(basename(sourceUserDataDir) || "profile")}-${hash}`;
|
|
902
|
+
}
|
|
903
|
+
function buildRuntimeProfileDirNamePrefix(sourceUserDataDir) {
|
|
904
|
+
return `${buildRuntimeProfileKey(sourceUserDataDir)}-runtime-`;
|
|
905
|
+
}
|
|
906
|
+
function buildRuntimeProfileDirPrefix(runtimesRootDir, sourceUserDataDir) {
|
|
907
|
+
return join(
|
|
908
|
+
runtimesRootDir,
|
|
909
|
+
`${buildRuntimeProfileDirNamePrefix(sourceUserDataDir)}${process.pid}-${PROCESS_STARTED_AT_MS}-`
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
async function cleanOrphanedOwnedDirs(rootDir, ownedDirNamePrefix) {
|
|
235
913
|
let entries;
|
|
236
914
|
try {
|
|
237
|
-
entries = await readdir(
|
|
915
|
+
entries = await readdir(rootDir, {
|
|
238
916
|
encoding: "utf8",
|
|
239
917
|
withFileTypes: true
|
|
240
918
|
});
|
|
241
919
|
} catch {
|
|
242
920
|
return;
|
|
243
921
|
}
|
|
244
|
-
const tempDirPrefix = `${targetBaseName}-tmp-`;
|
|
245
922
|
await Promise.all(
|
|
246
923
|
entries.map(async (entry) => {
|
|
247
|
-
if (!entry.isDirectory() || !entry.name.startsWith(
|
|
924
|
+
if (!entry.isDirectory() || !entry.name.startsWith(ownedDirNamePrefix)) {
|
|
248
925
|
return;
|
|
249
926
|
}
|
|
250
|
-
if (
|
|
927
|
+
if (await isOwnedDirByLiveProcess(entry.name, ownedDirNamePrefix)) {
|
|
251
928
|
return;
|
|
252
929
|
}
|
|
253
|
-
await rm(join(
|
|
930
|
+
await rm(join(rootDir, entry.name), {
|
|
254
931
|
recursive: true,
|
|
255
932
|
force: true
|
|
256
933
|
}).catch(() => void 0);
|
|
257
934
|
})
|
|
258
935
|
);
|
|
259
936
|
}
|
|
260
|
-
function
|
|
261
|
-
const owner =
|
|
262
|
-
|
|
263
|
-
return false;
|
|
264
|
-
}
|
|
265
|
-
if (owner.pid === process.pid && Math.abs(owner.processStartedAtMs - PROCESS_STARTED_AT_MS) <= PROCESS_START_TIME_TOLERANCE_MS) {
|
|
266
|
-
return true;
|
|
267
|
-
}
|
|
268
|
-
return isProcessRunning(owner.pid);
|
|
937
|
+
async function isOwnedDirByLiveProcess(ownedDirName, ownedDirPrefix) {
|
|
938
|
+
const owner = parseOwnedDirOwner(ownedDirName, ownedDirPrefix);
|
|
939
|
+
return owner ? await getProcessLiveness(owner) !== "dead" : false;
|
|
269
940
|
}
|
|
270
|
-
function
|
|
271
|
-
const remainder =
|
|
941
|
+
function parseOwnedDirOwner(ownedDirName, ownedDirPrefix) {
|
|
942
|
+
const remainder = ownedDirName.slice(ownedDirPrefix.length);
|
|
272
943
|
const firstDashIndex = remainder.indexOf("-");
|
|
273
944
|
const secondDashIndex = firstDashIndex === -1 ? -1 : remainder.indexOf("-", firstDashIndex + 1);
|
|
274
945
|
if (firstDashIndex === -1 || secondDashIndex === -1) {
|
|
@@ -296,6 +967,120 @@ function isProcessRunning(pid) {
|
|
|
296
967
|
return code !== "ESRCH";
|
|
297
968
|
}
|
|
298
969
|
}
|
|
970
|
+
async function readProcessStartedAtMs(pid) {
|
|
971
|
+
if (pid <= 0) {
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
if (process.platform === "linux") {
|
|
975
|
+
return await readLinuxProcessStartedAtMs(pid);
|
|
976
|
+
}
|
|
977
|
+
if (process.platform === "win32") {
|
|
978
|
+
return await readWindowsProcessStartedAtMs(pid);
|
|
979
|
+
}
|
|
980
|
+
return await readPsProcessStartedAtMs(pid);
|
|
981
|
+
}
|
|
982
|
+
async function readLinuxProcessStartedAtMs(pid) {
|
|
983
|
+
let statRaw;
|
|
984
|
+
try {
|
|
985
|
+
statRaw = await readFile(`/proc/${pid}/stat`, "utf8");
|
|
986
|
+
} catch (error) {
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
const startTicks = parseLinuxProcessStartTicks(statRaw);
|
|
990
|
+
if (startTicks === null) {
|
|
991
|
+
return null;
|
|
992
|
+
}
|
|
993
|
+
const [bootTimeMs, clockTicksPerSecond] = await Promise.all([
|
|
994
|
+
readLinuxBootTimeMs(),
|
|
995
|
+
readLinuxClockTicksPerSecond()
|
|
996
|
+
]);
|
|
997
|
+
if (bootTimeMs === null || clockTicksPerSecond === null) {
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
1000
|
+
return Math.floor(
|
|
1001
|
+
bootTimeMs + startTicks * 1e3 / clockTicksPerSecond
|
|
1002
|
+
);
|
|
1003
|
+
}
|
|
1004
|
+
function parseLinuxProcessStartTicks(statRaw) {
|
|
1005
|
+
const closingParenIndex = statRaw.lastIndexOf(")");
|
|
1006
|
+
if (closingParenIndex === -1) {
|
|
1007
|
+
return null;
|
|
1008
|
+
}
|
|
1009
|
+
const fields = statRaw.slice(closingParenIndex + 2).trim().split(/\s+/);
|
|
1010
|
+
const startTicks = Number(fields[LINUX_STAT_START_TIME_FIELD_INDEX]);
|
|
1011
|
+
return Number.isFinite(startTicks) && startTicks >= 0 ? startTicks : null;
|
|
1012
|
+
}
|
|
1013
|
+
async function readLinuxBootTimeMs() {
|
|
1014
|
+
try {
|
|
1015
|
+
const statRaw = await readFile("/proc/stat", "utf8");
|
|
1016
|
+
const bootTimeLine = statRaw.split("\n").find((line) => line.startsWith("btime "));
|
|
1017
|
+
if (!bootTimeLine) {
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
const bootTimeSeconds = Number.parseInt(
|
|
1021
|
+
bootTimeLine.slice("btime ".length),
|
|
1022
|
+
10
|
|
1023
|
+
);
|
|
1024
|
+
return Number.isFinite(bootTimeSeconds) ? bootTimeSeconds * 1e3 : null;
|
|
1025
|
+
} catch {
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
async function readLinuxClockTicksPerSecond() {
|
|
1030
|
+
linuxClockTicksPerSecondPromise ??= execFileAsync(
|
|
1031
|
+
"getconf",
|
|
1032
|
+
["CLK_TCK"]
|
|
1033
|
+
).then(
|
|
1034
|
+
({ stdout }) => {
|
|
1035
|
+
const value = Number.parseInt(stdout.trim(), 10);
|
|
1036
|
+
return Number.isFinite(value) && value > 0 ? value : null;
|
|
1037
|
+
},
|
|
1038
|
+
() => null
|
|
1039
|
+
);
|
|
1040
|
+
return await linuxClockTicksPerSecondPromise;
|
|
1041
|
+
}
|
|
1042
|
+
async function readPsProcessStartedAtMs(pid) {
|
|
1043
|
+
try {
|
|
1044
|
+
const { stdout } = await execFileAsync(
|
|
1045
|
+
"ps",
|
|
1046
|
+
["-p", String(pid), "-o", "lstart="],
|
|
1047
|
+
{ env: PS_COMMAND_ENV }
|
|
1048
|
+
);
|
|
1049
|
+
return parsePsStartedAtMs(stdout);
|
|
1050
|
+
} catch (error) {
|
|
1051
|
+
return null;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
function parsePsStartedAtMs(stdout) {
|
|
1055
|
+
const raw = stdout.trim();
|
|
1056
|
+
if (!raw) {
|
|
1057
|
+
return null;
|
|
1058
|
+
}
|
|
1059
|
+
const startedAtMs = Date.parse(raw);
|
|
1060
|
+
return Number.isNaN(startedAtMs) ? null : startedAtMs;
|
|
1061
|
+
}
|
|
1062
|
+
async function readWindowsProcessStartedAtMs(pid) {
|
|
1063
|
+
const script = [
|
|
1064
|
+
"$process = Get-Process -Id " + String(pid) + " -ErrorAction SilentlyContinue",
|
|
1065
|
+
"if ($null -eq $process) { exit 3 }",
|
|
1066
|
+
'$process.StartTime.ToUniversalTime().ToString("o")'
|
|
1067
|
+
].join("; ");
|
|
1068
|
+
try {
|
|
1069
|
+
const { stdout } = await execFileAsync(
|
|
1070
|
+
"powershell.exe",
|
|
1071
|
+
["-NoLogo", "-NoProfile", "-Command", script]
|
|
1072
|
+
);
|
|
1073
|
+
return parsePsStartedAtMs(stdout);
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
return null;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
function getErrorCode(error) {
|
|
1079
|
+
return typeof error === "object" && error !== null && "code" in error && (typeof error.code === "string" || typeof error.code === "number") ? error.code : void 0;
|
|
1080
|
+
}
|
|
1081
|
+
async function sleep(ms) {
|
|
1082
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1083
|
+
}
|
|
299
1084
|
|
|
300
1085
|
// src/browser/pool.ts
|
|
301
1086
|
import { spawn } from "child_process";
|
|
@@ -724,14 +1509,13 @@ var BrowserPool = class {
|
|
|
724
1509
|
browser = null;
|
|
725
1510
|
cdpProxy = null;
|
|
726
1511
|
launchedProcess = null;
|
|
727
|
-
|
|
728
|
-
persistentProfile = false;
|
|
1512
|
+
managedRuntimeProfile = null;
|
|
729
1513
|
defaults;
|
|
730
1514
|
constructor(defaults = {}) {
|
|
731
1515
|
this.defaults = defaults;
|
|
732
1516
|
}
|
|
733
1517
|
async launch(options = {}) {
|
|
734
|
-
if (this.browser || this.cdpProxy || this.launchedProcess || this.
|
|
1518
|
+
if (this.browser || this.cdpProxy || this.launchedProcess || this.managedRuntimeProfile) {
|
|
735
1519
|
await this.close();
|
|
736
1520
|
}
|
|
737
1521
|
const mode = options.mode ?? this.defaults.mode ?? "chromium";
|
|
@@ -781,13 +1565,10 @@ var BrowserPool = class {
|
|
|
781
1565
|
const browser = this.browser;
|
|
782
1566
|
const cdpProxy = this.cdpProxy;
|
|
783
1567
|
const launchedProcess = this.launchedProcess;
|
|
784
|
-
const
|
|
785
|
-
const persistentProfile = this.persistentProfile;
|
|
1568
|
+
const managedRuntimeProfile = this.managedRuntimeProfile;
|
|
786
1569
|
this.browser = null;
|
|
787
1570
|
this.cdpProxy = null;
|
|
788
1571
|
this.launchedProcess = null;
|
|
789
|
-
this.managedUserDataDir = null;
|
|
790
|
-
this.persistentProfile = false;
|
|
791
1572
|
try {
|
|
792
1573
|
if (browser) {
|
|
793
1574
|
await browser.close().catch(() => void 0);
|
|
@@ -795,11 +1576,14 @@ var BrowserPool = class {
|
|
|
795
1576
|
} finally {
|
|
796
1577
|
cdpProxy?.close();
|
|
797
1578
|
await killProcessTree(launchedProcess);
|
|
798
|
-
if (
|
|
799
|
-
await
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
1579
|
+
if (managedRuntimeProfile) {
|
|
1580
|
+
await persistIsolatedRuntimeProfile(
|
|
1581
|
+
managedRuntimeProfile.userDataDir,
|
|
1582
|
+
managedRuntimeProfile.persistentUserDataDir
|
|
1583
|
+
);
|
|
1584
|
+
if (this.managedRuntimeProfile === managedRuntimeProfile) {
|
|
1585
|
+
this.managedRuntimeProfile = null;
|
|
1586
|
+
}
|
|
803
1587
|
}
|
|
804
1588
|
}
|
|
805
1589
|
}
|
|
@@ -849,7 +1633,9 @@ var BrowserPool = class {
|
|
|
849
1633
|
sourceUserDataDir,
|
|
850
1634
|
profileDirectory
|
|
851
1635
|
);
|
|
852
|
-
await
|
|
1636
|
+
const runtimeProfile = await createIsolatedRuntimeProfile(
|
|
1637
|
+
persistentProfile.userDataDir
|
|
1638
|
+
);
|
|
853
1639
|
const debugPort = await reserveDebugPort();
|
|
854
1640
|
const headless = resolveLaunchHeadless(
|
|
855
1641
|
"real",
|
|
@@ -857,7 +1643,7 @@ var BrowserPool = class {
|
|
|
857
1643
|
this.defaults.headless
|
|
858
1644
|
);
|
|
859
1645
|
const launchArgs = buildRealBrowserLaunchArgs({
|
|
860
|
-
userDataDir:
|
|
1646
|
+
userDataDir: runtimeProfile.userDataDir,
|
|
861
1647
|
profileDirectory,
|
|
862
1648
|
debugPort,
|
|
863
1649
|
headless
|
|
@@ -887,12 +1673,15 @@ var BrowserPool = class {
|
|
|
887
1673
|
}
|
|
888
1674
|
this.browser = browser;
|
|
889
1675
|
this.launchedProcess = processHandle;
|
|
890
|
-
this.
|
|
891
|
-
this.persistentProfile = true;
|
|
1676
|
+
this.managedRuntimeProfile = runtimeProfile;
|
|
892
1677
|
return { browser, context, page, isExternal: false };
|
|
893
1678
|
} catch (error) {
|
|
894
1679
|
await browser?.close().catch(() => void 0);
|
|
895
1680
|
await killProcessTree(processHandle);
|
|
1681
|
+
await rm2(runtimeProfile.userDataDir, {
|
|
1682
|
+
recursive: true,
|
|
1683
|
+
force: true
|
|
1684
|
+
}).catch(() => void 0);
|
|
896
1685
|
throw error;
|
|
897
1686
|
}
|
|
898
1687
|
}
|
|
@@ -1021,7 +1810,7 @@ async function resolveCdpWebSocketUrl(cdpUrl, timeoutMs) {
|
|
|
1021
1810
|
} catch (error) {
|
|
1022
1811
|
lastError = error instanceof Error ? error.message : "Unknown error";
|
|
1023
1812
|
}
|
|
1024
|
-
await
|
|
1813
|
+
await sleep2(100);
|
|
1025
1814
|
}
|
|
1026
1815
|
throw new Error(
|
|
1027
1816
|
`Failed to resolve a CDP websocket URL from ${versionUrl.toString()}: ${lastError}`
|
|
@@ -1088,7 +1877,7 @@ async function killProcessTree(processHandle) {
|
|
|
1088
1877
|
}
|
|
1089
1878
|
}
|
|
1090
1879
|
}
|
|
1091
|
-
async function
|
|
1880
|
+
async function sleep2(ms) {
|
|
1092
1881
|
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
1093
1882
|
}
|
|
1094
1883
|
|
|
@@ -1470,7 +2259,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
1470
2259
|
TRANSIENT_CONTEXT_RETRY_DELAY_MS,
|
|
1471
2260
|
Math.max(0, deadline - Date.now())
|
|
1472
2261
|
);
|
|
1473
|
-
await
|
|
2262
|
+
await sleep3(retryDelay);
|
|
1474
2263
|
}
|
|
1475
2264
|
}
|
|
1476
2265
|
}
|
|
@@ -1503,7 +2292,7 @@ var StealthCdpRuntime = class _StealthCdpRuntime {
|
|
|
1503
2292
|
() => ({ kind: "resolved" }),
|
|
1504
2293
|
(error) => ({ kind: "rejected", error })
|
|
1505
2294
|
);
|
|
1506
|
-
const timeoutPromise =
|
|
2295
|
+
const timeoutPromise = sleep3(
|
|
1507
2296
|
timeout + FRAME_EVALUATE_GRACE_MS
|
|
1508
2297
|
).then(() => ({ kind: "timeout" }));
|
|
1509
2298
|
const result = await Promise.race([
|
|
@@ -1645,7 +2434,7 @@ function isIgnorableFrameError(error) {
|
|
|
1645
2434
|
const message = error.message;
|
|
1646
2435
|
return message.includes("Frame was detached") || message.includes("Target page, context or browser has been closed") || isTransientExecutionContextError(error) || message.includes("No frame for given id found");
|
|
1647
2436
|
}
|
|
1648
|
-
function
|
|
2437
|
+
function sleep3(ms) {
|
|
1649
2438
|
return new Promise((resolve) => {
|
|
1650
2439
|
setTimeout(resolve, ms);
|
|
1651
2440
|
});
|
|
@@ -3077,6 +3866,58 @@ async function serializeFrameRecursive(frame, baseContext, frameKey) {
|
|
|
3077
3866
|
};
|
|
3078
3867
|
}
|
|
3079
3868
|
|
|
3869
|
+
// src/html/interactive-patterns.ts
|
|
3870
|
+
var NATIVE_INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
|
|
3871
|
+
"a",
|
|
3872
|
+
"button",
|
|
3873
|
+
"input",
|
|
3874
|
+
"select",
|
|
3875
|
+
"textarea"
|
|
3876
|
+
]);
|
|
3877
|
+
var INTERACTIVE_SELECTOR_PARTS = [
|
|
3878
|
+
"a[href]",
|
|
3879
|
+
"button",
|
|
3880
|
+
"input",
|
|
3881
|
+
"textarea",
|
|
3882
|
+
"select",
|
|
3883
|
+
'[role="button"]',
|
|
3884
|
+
'[role="link"]',
|
|
3885
|
+
'[role="menuitem"]',
|
|
3886
|
+
'[role="option"]',
|
|
3887
|
+
'[role="radio"]',
|
|
3888
|
+
'[role="checkbox"]',
|
|
3889
|
+
'[role="tab"]',
|
|
3890
|
+
'[contenteditable="true"]',
|
|
3891
|
+
"[onclick]",
|
|
3892
|
+
"[onmousedown]",
|
|
3893
|
+
"[onmouseup]"
|
|
3894
|
+
];
|
|
3895
|
+
var INTERACTIVE_SELECTOR = INTERACTIVE_SELECTOR_PARTS.join(",");
|
|
3896
|
+
var INTERACTIVE_ROLE_TOKENS = [
|
|
3897
|
+
"button",
|
|
3898
|
+
"link",
|
|
3899
|
+
"menuitem",
|
|
3900
|
+
"option",
|
|
3901
|
+
"radio",
|
|
3902
|
+
"checkbox",
|
|
3903
|
+
"tab",
|
|
3904
|
+
"textbox",
|
|
3905
|
+
"combobox",
|
|
3906
|
+
"slider",
|
|
3907
|
+
"spinbutton",
|
|
3908
|
+
"search",
|
|
3909
|
+
"searchbox"
|
|
3910
|
+
];
|
|
3911
|
+
var INTERACTIVE_ROLE_SET = new Set(
|
|
3912
|
+
INTERACTIVE_ROLE_TOKENS
|
|
3913
|
+
);
|
|
3914
|
+
var NON_NEGATIVE_TAB_INDEX_MIN = 0;
|
|
3915
|
+
function hasNonNegativeTabIndex(value) {
|
|
3916
|
+
if (value == null) return false;
|
|
3917
|
+
const parsed = Number.parseInt(value, 10);
|
|
3918
|
+
return Number.isFinite(parsed) && parsed >= NON_NEGATIVE_TAB_INDEX_MIN;
|
|
3919
|
+
}
|
|
3920
|
+
|
|
3080
3921
|
// src/html/interactivity.ts
|
|
3081
3922
|
var OPENSTEER_INTERACTIVE_ATTR = "data-opensteer-interactive";
|
|
3082
3923
|
var OPENSTEER_HIDDEN_ATTR = "data-opensteer-hidden";
|
|
@@ -3091,42 +3932,12 @@ async function markInteractiveElements(page, {
|
|
|
3091
3932
|
markAttribute: markAttribute2,
|
|
3092
3933
|
skipIfAlreadyMarked: skipIfAlreadyMarked2,
|
|
3093
3934
|
hiddenAttr,
|
|
3094
|
-
scrollableAttr
|
|
3935
|
+
scrollableAttr,
|
|
3936
|
+
interactiveSelector,
|
|
3937
|
+
interactiveRoles,
|
|
3938
|
+
nonNegativeTabIndexMin
|
|
3095
3939
|
}) => {
|
|
3096
|
-
const
|
|
3097
|
-
"a[href]",
|
|
3098
|
-
"button",
|
|
3099
|
-
"input",
|
|
3100
|
-
"textarea",
|
|
3101
|
-
"select",
|
|
3102
|
-
'[role="button"]',
|
|
3103
|
-
'[role="link"]',
|
|
3104
|
-
'[role="menuitem"]',
|
|
3105
|
-
'[role="option"]',
|
|
3106
|
-
'[role="radio"]',
|
|
3107
|
-
'[role="checkbox"]',
|
|
3108
|
-
'[role="tab"]',
|
|
3109
|
-
'[contenteditable="true"]',
|
|
3110
|
-
"[onclick]",
|
|
3111
|
-
"[onmousedown]",
|
|
3112
|
-
"[onmouseup]",
|
|
3113
|
-
"[tabindex]"
|
|
3114
|
-
].join(",");
|
|
3115
|
-
const interactiveRoles = /* @__PURE__ */ new Set([
|
|
3116
|
-
"button",
|
|
3117
|
-
"link",
|
|
3118
|
-
"menuitem",
|
|
3119
|
-
"option",
|
|
3120
|
-
"radio",
|
|
3121
|
-
"checkbox",
|
|
3122
|
-
"tab",
|
|
3123
|
-
"textbox",
|
|
3124
|
-
"combobox",
|
|
3125
|
-
"slider",
|
|
3126
|
-
"spinbutton",
|
|
3127
|
-
"search",
|
|
3128
|
-
"searchbox"
|
|
3129
|
-
]);
|
|
3940
|
+
const interactiveRolesSet = new Set(interactiveRoles);
|
|
3130
3941
|
function isExplicitlyHidden(el, style) {
|
|
3131
3942
|
if (el.hasAttribute("hidden")) {
|
|
3132
3943
|
return true;
|
|
@@ -3168,6 +3979,12 @@ async function markInteractiveElements(page, {
|
|
|
3168
3979
|
}
|
|
3169
3980
|
return !hasVisibleOutOfFlowChild(el);
|
|
3170
3981
|
}
|
|
3982
|
+
function hasInteractiveTabIndex(el) {
|
|
3983
|
+
const value = el.getAttribute("tabindex");
|
|
3984
|
+
if (value == null) return false;
|
|
3985
|
+
const parsed = Number.parseInt(value, 10);
|
|
3986
|
+
return Number.isFinite(parsed) && parsed >= nonNegativeTabIndexMin;
|
|
3987
|
+
}
|
|
3171
3988
|
const roots = [document];
|
|
3172
3989
|
while (roots.length) {
|
|
3173
3990
|
const root = roots.pop();
|
|
@@ -3193,11 +4010,13 @@ async function markInteractiveElements(page, {
|
|
|
3193
4010
|
let interactive = false;
|
|
3194
4011
|
if (el.matches(interactiveSelector)) {
|
|
3195
4012
|
interactive = true;
|
|
4013
|
+
} else if (hasInteractiveTabIndex(el)) {
|
|
4014
|
+
interactive = true;
|
|
3196
4015
|
} else if (style.cursor === "pointer") {
|
|
3197
4016
|
interactive = true;
|
|
3198
4017
|
} else {
|
|
3199
4018
|
const role = (el.getAttribute("role") || "").toLowerCase();
|
|
3200
|
-
if (
|
|
4019
|
+
if (interactiveRolesSet.has(role)) {
|
|
3201
4020
|
interactive = true;
|
|
3202
4021
|
}
|
|
3203
4022
|
}
|
|
@@ -3242,7 +4061,10 @@ async function markInteractiveElements(page, {
|
|
|
3242
4061
|
markAttribute,
|
|
3243
4062
|
skipIfAlreadyMarked,
|
|
3244
4063
|
hiddenAttr: OPENSTEER_HIDDEN_ATTR,
|
|
3245
|
-
scrollableAttr: OPENSTEER_SCROLLABLE_ATTR
|
|
4064
|
+
scrollableAttr: OPENSTEER_SCROLLABLE_ATTR,
|
|
4065
|
+
interactiveSelector: INTERACTIVE_SELECTOR,
|
|
4066
|
+
interactiveRoles: INTERACTIVE_ROLE_TOKENS,
|
|
4067
|
+
nonNegativeTabIndexMin: NON_NEGATIVE_TAB_INDEX_MIN
|
|
3246
4068
|
}
|
|
3247
4069
|
);
|
|
3248
4070
|
};
|
|
@@ -3329,7 +4151,7 @@ function isClickable($, el, context) {
|
|
|
3329
4151
|
}
|
|
3330
4152
|
const tag = (el[0]?.tagName || "").toLowerCase();
|
|
3331
4153
|
if (!tag || ROOT_TAGS.has(tag)) return false;
|
|
3332
|
-
if (
|
|
4154
|
+
if (NATIVE_INTERACTIVE_TAGS.has(tag)) {
|
|
3333
4155
|
if (tag === "input") {
|
|
3334
4156
|
const inputType = String(el.attr("type") || "").toLowerCase();
|
|
3335
4157
|
if (inputType === "hidden") return false;
|
|
@@ -3343,26 +4165,11 @@ function isClickable($, el, context) {
|
|
|
3343
4165
|
if (attrs["data-action"] !== void 0) return true;
|
|
3344
4166
|
if (attrs["data-click"] !== void 0) return true;
|
|
3345
4167
|
if (attrs["data-toggle"] !== void 0) return true;
|
|
3346
|
-
if (attrs.tabindex
|
|
3347
|
-
|
|
3348
|
-
if (!Number.isNaN(tabIndex) && tabIndex >= 0) return true;
|
|
4168
|
+
if (hasNonNegativeTabIndex(attrs.tabindex)) {
|
|
4169
|
+
return true;
|
|
3349
4170
|
}
|
|
3350
4171
|
const role = String(attrs.role || "").toLowerCase();
|
|
3351
|
-
if (
|
|
3352
|
-
"button",
|
|
3353
|
-
"link",
|
|
3354
|
-
"menuitem",
|
|
3355
|
-
"option",
|
|
3356
|
-
"radio",
|
|
3357
|
-
"checkbox",
|
|
3358
|
-
"tab",
|
|
3359
|
-
"textbox",
|
|
3360
|
-
"combobox",
|
|
3361
|
-
"slider",
|
|
3362
|
-
"spinbutton",
|
|
3363
|
-
"search",
|
|
3364
|
-
"searchbox"
|
|
3365
|
-
].includes(role)) {
|
|
4172
|
+
if (INTERACTIVE_ROLE_SET.has(role)) {
|
|
3366
4173
|
return true;
|
|
3367
4174
|
}
|
|
3368
4175
|
const className = String(attrs.class || "").toLowerCase();
|
|
@@ -5879,7 +6686,7 @@ async function closeTab(context, activePage, index) {
|
|
|
5879
6686
|
}
|
|
5880
6687
|
|
|
5881
6688
|
// src/actions/cookies.ts
|
|
5882
|
-
import { readFile, writeFile as writeFile2 } from "fs/promises";
|
|
6689
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
5883
6690
|
async function getCookies(context, url) {
|
|
5884
6691
|
return context.cookies(url ? [url] : void 0);
|
|
5885
6692
|
}
|
|
@@ -5894,7 +6701,7 @@ async function exportCookies(context, filePath, url) {
|
|
|
5894
6701
|
await writeFile2(filePath, JSON.stringify(cookies, null, 2), "utf-8");
|
|
5895
6702
|
}
|
|
5896
6703
|
async function importCookies(context, filePath) {
|
|
5897
|
-
const raw = await
|
|
6704
|
+
const raw = await readFile2(filePath, "utf-8");
|
|
5898
6705
|
const cookies = JSON.parse(raw);
|
|
5899
6706
|
await context.addCookies(cookies);
|
|
5900
6707
|
}
|
|
@@ -7722,7 +8529,7 @@ async function executeAgentAction(page, action) {
|
|
|
7722
8529
|
}
|
|
7723
8530
|
case "wait": {
|
|
7724
8531
|
const ms = numberOr(action.timeMs, action.time_ms, 1e3);
|
|
7725
|
-
await
|
|
8532
|
+
await sleep4(ms);
|
|
7726
8533
|
return;
|
|
7727
8534
|
}
|
|
7728
8535
|
case "goto": {
|
|
@@ -7887,7 +8694,7 @@ async function pressKeyCombo(page, combo) {
|
|
|
7887
8694
|
}
|
|
7888
8695
|
}
|
|
7889
8696
|
}
|
|
7890
|
-
function
|
|
8697
|
+
function sleep4(ms) {
|
|
7891
8698
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
7892
8699
|
}
|
|
7893
8700
|
|
|
@@ -7918,7 +8725,7 @@ var OpensteerCuaAgentHandler = class {
|
|
|
7918
8725
|
if (isMutatingAgentAction(action)) {
|
|
7919
8726
|
this.onMutatingAction?.(action);
|
|
7920
8727
|
}
|
|
7921
|
-
await
|
|
8728
|
+
await sleep5(this.config.waitBetweenActionsMs);
|
|
7922
8729
|
});
|
|
7923
8730
|
try {
|
|
7924
8731
|
const result = await this.client.execute({
|
|
@@ -7980,7 +8787,7 @@ var OpensteerCuaAgentHandler = class {
|
|
|
7980
8787
|
await this.cursorController.preview({ x, y }, "agent");
|
|
7981
8788
|
}
|
|
7982
8789
|
};
|
|
7983
|
-
function
|
|
8790
|
+
function sleep5(ms) {
|
|
7984
8791
|
return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
|
|
7985
8792
|
}
|
|
7986
8793
|
|
|
@@ -8416,7 +9223,7 @@ var CursorController = class {
|
|
|
8416
9223
|
for (const step of motion.points) {
|
|
8417
9224
|
await this.renderer.move(step, this.style);
|
|
8418
9225
|
if (motion.stepDelayMs > 0) {
|
|
8419
|
-
await
|
|
9226
|
+
await sleep6(motion.stepDelayMs);
|
|
8420
9227
|
}
|
|
8421
9228
|
}
|
|
8422
9229
|
if (shouldPulse(intent)) {
|
|
@@ -8574,12 +9381,12 @@ function clamp2(value, min, max) {
|
|
|
8574
9381
|
function shouldPulse(intent) {
|
|
8575
9382
|
return intent === "click" || intent === "dblclick" || intent === "rightclick" || intent === "agent";
|
|
8576
9383
|
}
|
|
8577
|
-
function
|
|
9384
|
+
function sleep6(ms) {
|
|
8578
9385
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
8579
9386
|
}
|
|
8580
9387
|
|
|
8581
9388
|
// src/opensteer.ts
|
|
8582
|
-
import { createHash as createHash2, randomUUID } from "crypto";
|
|
9389
|
+
import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
|
|
8583
9390
|
|
|
8584
9391
|
// src/action-wait.ts
|
|
8585
9392
|
var ROBUST_PROFILE = {
|
|
@@ -8774,7 +9581,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
8774
9581
|
this.idleSince = 0;
|
|
8775
9582
|
}
|
|
8776
9583
|
const remaining = Math.max(1, options.deadline - now);
|
|
8777
|
-
await
|
|
9584
|
+
await sleep7(Math.min(NETWORK_POLL_MS, remaining));
|
|
8778
9585
|
}
|
|
8779
9586
|
}
|
|
8780
9587
|
handleRequestStarted = (request) => {
|
|
@@ -8819,7 +9626,7 @@ var AdaptiveNetworkTracker = class {
|
|
|
8819
9626
|
return false;
|
|
8820
9627
|
}
|
|
8821
9628
|
};
|
|
8822
|
-
async function
|
|
9629
|
+
async function sleep7(ms) {
|
|
8823
9630
|
await new Promise((resolve) => {
|
|
8824
9631
|
setTimeout(resolve, ms);
|
|
8825
9632
|
});
|
|
@@ -10599,15 +11406,18 @@ var Opensteer = class _Opensteer {
|
|
|
10599
11406
|
}
|
|
10600
11407
|
return;
|
|
10601
11408
|
}
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
10606
|
-
|
|
10607
|
-
|
|
10608
|
-
|
|
10609
|
-
|
|
10610
|
-
|
|
11409
|
+
try {
|
|
11410
|
+
if (this.ownsBrowser) {
|
|
11411
|
+
await this.pool.close();
|
|
11412
|
+
}
|
|
11413
|
+
} finally {
|
|
11414
|
+
this.browser = null;
|
|
11415
|
+
this.pageRef = null;
|
|
11416
|
+
this.contextRef = null;
|
|
11417
|
+
this.ownsBrowser = false;
|
|
11418
|
+
if (this.cursorController) {
|
|
11419
|
+
await this.cursorController.dispose().catch(() => void 0);
|
|
11420
|
+
}
|
|
10611
11421
|
}
|
|
10612
11422
|
}
|
|
10613
11423
|
async syncLocalSelectorCacheToCloud() {
|
|
@@ -12931,12 +13741,14 @@ function normalizeCloudBrowserProfilePreference(value, source) {
|
|
|
12931
13741
|
}
|
|
12932
13742
|
function buildLocalRunId(namespace) {
|
|
12933
13743
|
const normalized = namespace.trim() || "default";
|
|
12934
|
-
return `${normalized}-${Date.now().toString(36)}-${
|
|
13744
|
+
return `${normalized}-${Date.now().toString(36)}-${randomUUID2().slice(0, 8)}`;
|
|
12935
13745
|
}
|
|
12936
13746
|
|
|
12937
13747
|
export {
|
|
12938
13748
|
getOrCreatePersistentProfile,
|
|
12939
13749
|
clearPersistentProfileSingletons,
|
|
13750
|
+
createIsolatedRuntimeProfile,
|
|
13751
|
+
persistIsolatedRuntimeProfile,
|
|
12940
13752
|
BrowserPool,
|
|
12941
13753
|
waitForVisualStability,
|
|
12942
13754
|
createEmptyRegistry,
|