opensteer 0.9.0 → 0.9.1
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-4LP7QP2O.js +4336 -0
- package/dist/chunk-4LP7QP2O.js.map +1 -0
- package/dist/{chunk-656MQUSM.js → chunk-6PGXWW3X.js} +4787 -9519
- package/dist/chunk-6PGXWW3X.js.map +1 -0
- package/dist/chunk-BMPUL66S.js +1170 -0
- package/dist/chunk-BMPUL66S.js.map +1 -0
- package/dist/{chunk-OIKLSFXA.js → chunk-L4FWHBQJ.js} +4 -3
- package/dist/chunk-L4FWHBQJ.js.map +1 -0
- package/dist/chunk-Z53HNZ7Z.js +1800 -0
- package/dist/chunk-Z53HNZ7Z.js.map +1 -0
- package/dist/cli/bin.cjs +3050 -281
- package/dist/cli/bin.cjs.map +1 -1
- package/dist/cli/bin.js +124 -7
- package/dist/cli/bin.js.map +1 -1
- package/dist/index.cjs +918 -263
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -2
- package/dist/local-view/public/assets/app.css +770 -0
- package/dist/local-view/public/assets/app.js +2053 -0
- package/dist/local-view/public/index.html +235 -0
- package/dist/local-view/serve-entry.cjs +7436 -0
- package/dist/local-view/serve-entry.cjs.map +1 -0
- package/dist/local-view/serve-entry.d.cts +1 -0
- package/dist/local-view/serve-entry.d.ts +1 -0
- package/dist/local-view/serve-entry.js +23 -0
- package/dist/local-view/serve-entry.js.map +1 -0
- package/dist/opensteer-KZCRP425.js +6 -0
- package/dist/{opensteer-LKX3233A.js.map → opensteer-KZCRP425.js.map} +1 -1
- package/dist/session-control-VGBFOH3Y.js +39 -0
- package/dist/session-control-VGBFOH3Y.js.map +1 -0
- package/package.json +8 -8
- package/skills/README.md +3 -0
- package/skills/opensteer/SKILL.md +229 -48
- package/dist/chunk-656MQUSM.js.map +0 -1
- package/dist/chunk-OIKLSFXA.js.map +0 -1
- package/dist/opensteer-LKX3233A.js +0 -4
package/dist/cli/bin.cjs
CHANGED
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
var child_process = require('child_process');
|
|
5
5
|
var promises = require('fs/promises');
|
|
6
6
|
var util = require('util');
|
|
7
|
-
var
|
|
7
|
+
var path10 = require('path');
|
|
8
8
|
var fs = require('fs');
|
|
9
9
|
var os = require('os');
|
|
10
10
|
var crypto = require('crypto');
|
|
11
11
|
var url = require('url');
|
|
12
|
+
var module$1 = require('module');
|
|
12
13
|
var enginePlaywright = require('@opensteer/engine-playwright');
|
|
13
14
|
var zlib = require('zlib');
|
|
14
15
|
var cssSelect = require('css-select');
|
|
@@ -17,9 +18,10 @@ var cheerio = require('cheerio');
|
|
|
17
18
|
var prettier = require('prettier');
|
|
18
19
|
var vm = require('vm');
|
|
19
20
|
var async_hooks = require('async_hooks');
|
|
20
|
-
var
|
|
21
|
+
var WebSocket4 = require('ws');
|
|
21
22
|
var process4 = require('process');
|
|
22
|
-
var
|
|
23
|
+
var http = require('http');
|
|
24
|
+
var events = require('events');
|
|
23
25
|
|
|
24
26
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
25
27
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -42,13 +44,13 @@ function _interopNamespace(e) {
|
|
|
42
44
|
return Object.freeze(n);
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
var
|
|
47
|
+
var path10__default = /*#__PURE__*/_interopDefault(path10);
|
|
46
48
|
var os__default = /*#__PURE__*/_interopDefault(os);
|
|
47
49
|
var sharp__default = /*#__PURE__*/_interopDefault(sharp);
|
|
48
50
|
var cheerio__namespace = /*#__PURE__*/_interopNamespace(cheerio);
|
|
49
51
|
var prettier__namespace = /*#__PURE__*/_interopNamespace(prettier);
|
|
50
52
|
var vm__default = /*#__PURE__*/_interopDefault(vm);
|
|
51
|
-
var
|
|
53
|
+
var WebSocket4__namespace = /*#__PURE__*/_interopNamespace(WebSocket4);
|
|
52
54
|
var process4__default = /*#__PURE__*/_interopDefault(process4);
|
|
53
55
|
|
|
54
56
|
var __defProp = Object.defineProperty;
|
|
@@ -66,6 +68,40 @@ var __export = (target, all) => {
|
|
|
66
68
|
for (var name in all)
|
|
67
69
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
68
70
|
};
|
|
71
|
+
function parseProcessOwner(value) {
|
|
72
|
+
if (!value || typeof value !== "object") {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const parsed = value;
|
|
76
|
+
const pid = Number(parsed.pid);
|
|
77
|
+
const processStartedAtMs = Number(parsed.processStartedAtMs);
|
|
78
|
+
if (!Number.isInteger(pid) || pid <= 0) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
return {
|
|
85
|
+
pid,
|
|
86
|
+
processStartedAtMs
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function processOwnersEqual(left, right) {
|
|
90
|
+
if (!left || !right) {
|
|
91
|
+
return left === right;
|
|
92
|
+
}
|
|
93
|
+
return left.pid === right.pid && left.processStartedAtMs === right.processStartedAtMs;
|
|
94
|
+
}
|
|
95
|
+
async function getProcessLiveness(owner) {
|
|
96
|
+
if (owner.pid === process.pid && hasMatchingProcessStartTime(owner.processStartedAtMs, PROCESS_STARTED_AT_MS)) {
|
|
97
|
+
return "live";
|
|
98
|
+
}
|
|
99
|
+
const startedAtMs = await readProcessStartedAtMs(owner.pid);
|
|
100
|
+
if (typeof startedAtMs === "number") {
|
|
101
|
+
return hasMatchingProcessStartTime(owner.processStartedAtMs, startedAtMs) ? "live" : "dead";
|
|
102
|
+
}
|
|
103
|
+
return isProcessRunning(owner.pid) ? "unknown" : "dead";
|
|
104
|
+
}
|
|
69
105
|
function isProcessRunning(pid) {
|
|
70
106
|
try {
|
|
71
107
|
process.kill(pid, 0);
|
|
@@ -75,22 +111,136 @@ function isProcessRunning(pid) {
|
|
|
75
111
|
return code !== "ESRCH";
|
|
76
112
|
}
|
|
77
113
|
}
|
|
78
|
-
|
|
114
|
+
function hasMatchingProcessStartTime(expectedStartedAtMs, actualStartedAtMs) {
|
|
115
|
+
return Math.abs(expectedStartedAtMs - actualStartedAtMs) <= PROCESS_START_TIME_TOLERANCE_MS;
|
|
116
|
+
}
|
|
117
|
+
async function readProcessStartedAtMs(pid) {
|
|
118
|
+
if (pid <= 0) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
if (process.platform === "linux") {
|
|
122
|
+
return readLinuxProcessStartedAtMs(pid);
|
|
123
|
+
}
|
|
124
|
+
if (process.platform === "win32") {
|
|
125
|
+
return readWindowsProcessStartedAtMs(pid);
|
|
126
|
+
}
|
|
127
|
+
return readPsProcessStartedAtMs(pid);
|
|
128
|
+
}
|
|
129
|
+
async function readLinuxProcessStartedAtMs(pid) {
|
|
130
|
+
let statRaw;
|
|
131
|
+
try {
|
|
132
|
+
statRaw = await promises.readFile(`/proc/${String(pid)}/stat`, "utf8");
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const startTicks = parseLinuxProcessStartTicks(statRaw);
|
|
137
|
+
if (startTicks === null) {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const [bootTimeMs, clockTicksPerSecond] = await Promise.all([
|
|
141
|
+
readLinuxBootTimeMs(),
|
|
142
|
+
readLinuxClockTicksPerSecond()
|
|
143
|
+
]);
|
|
144
|
+
if (bootTimeMs === null || clockTicksPerSecond === null) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
return Math.floor(bootTimeMs + startTicks * 1e3 / clockTicksPerSecond);
|
|
148
|
+
}
|
|
149
|
+
function parseLinuxProcessStartTicks(statRaw) {
|
|
150
|
+
const closingParenIndex = statRaw.lastIndexOf(")");
|
|
151
|
+
if (closingParenIndex === -1) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
const fields = statRaw.slice(closingParenIndex + 2).trim().split(/\s+/);
|
|
155
|
+
const startTicks = Number(fields[LINUX_STAT_START_TIME_FIELD_INDEX]);
|
|
156
|
+
return Number.isFinite(startTicks) && startTicks >= 0 ? startTicks : null;
|
|
157
|
+
}
|
|
158
|
+
async function readLinuxBootTimeMs() {
|
|
159
|
+
try {
|
|
160
|
+
const statRaw = await promises.readFile("/proc/stat", "utf8");
|
|
161
|
+
const bootTimeLine = statRaw.split("\n").find((line) => line.startsWith("btime "));
|
|
162
|
+
if (!bootTimeLine) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
const bootTimeSeconds = Number.parseInt(bootTimeLine.slice("btime ".length), 10);
|
|
166
|
+
return Number.isFinite(bootTimeSeconds) ? bootTimeSeconds * 1e3 : null;
|
|
167
|
+
} catch {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async function readLinuxClockTicksPerSecond() {
|
|
172
|
+
if (!linuxClockTicksPerSecondPromise) {
|
|
173
|
+
linuxClockTicksPerSecondPromise = execFileAsync("getconf", ["CLK_TCK"], {
|
|
174
|
+
encoding: "utf8",
|
|
175
|
+
maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
|
|
176
|
+
}).then(({ stdout }) => {
|
|
177
|
+
const value = Number.parseInt(stdout.trim(), 10);
|
|
178
|
+
return Number.isFinite(value) && value > 0 ? value : null;
|
|
179
|
+
}).catch(() => null);
|
|
180
|
+
}
|
|
181
|
+
return linuxClockTicksPerSecondPromise;
|
|
182
|
+
}
|
|
183
|
+
async function readWindowsProcessStartedAtMs(pid) {
|
|
184
|
+
try {
|
|
185
|
+
const { stdout } = await execFileAsync(
|
|
186
|
+
"powershell.exe",
|
|
187
|
+
[
|
|
188
|
+
"-NoProfile",
|
|
189
|
+
"-Command",
|
|
190
|
+
`(Get-Process -Id ${String(pid)}).StartTime.ToUniversalTime().ToString("o")`
|
|
191
|
+
],
|
|
192
|
+
{
|
|
193
|
+
encoding: "utf8",
|
|
194
|
+
maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
const isoTimestamp = stdout.trim();
|
|
198
|
+
if (!isoTimestamp) {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
const startedAtMs = Date.parse(isoTimestamp);
|
|
202
|
+
return Number.isFinite(startedAtMs) ? startedAtMs : null;
|
|
203
|
+
} catch {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
async function readPsProcessStartedAtMs(pid) {
|
|
208
|
+
try {
|
|
209
|
+
const { stdout } = await execFileAsync("ps", ["-o", "lstart=", "-p", String(pid)], {
|
|
210
|
+
encoding: "utf8",
|
|
211
|
+
env: PS_COMMAND_ENV,
|
|
212
|
+
maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
|
|
213
|
+
});
|
|
214
|
+
const startedAt = stdout.trim();
|
|
215
|
+
if (!startedAt) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
const startedAtMs = Date.parse(startedAt.replace(/\s+/g, " "));
|
|
219
|
+
return Number.isFinite(startedAtMs) ? startedAtMs : null;
|
|
220
|
+
} catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
var execFileAsync, PROCESS_STARTED_AT_MS, PROCESS_START_TIME_TOLERANCE_MS, PROCESS_LIST_MAX_BUFFER_BYTES, PS_COMMAND_ENV, LINUX_STAT_START_TIME_FIELD_INDEX, CURRENT_PROCESS_OWNER, linuxClockTicksPerSecondPromise;
|
|
79
225
|
var init_process_owner = __esm({
|
|
80
226
|
"src/local-browser/process-owner.ts"() {
|
|
81
|
-
util.promisify(child_process.execFile);
|
|
227
|
+
execFileAsync = util.promisify(child_process.execFile);
|
|
82
228
|
PROCESS_STARTED_AT_MS = Math.floor(Date.now() - process.uptime() * 1e3);
|
|
83
|
-
|
|
84
|
-
|
|
229
|
+
PROCESS_START_TIME_TOLERANCE_MS = 1e3;
|
|
230
|
+
PROCESS_LIST_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
|
|
231
|
+
PS_COMMAND_ENV = { ...process.env, LC_ALL: "C" };
|
|
232
|
+
LINUX_STAT_START_TIME_FIELD_INDEX = 19;
|
|
233
|
+
CURRENT_PROCESS_OWNER = {
|
|
85
234
|
pid: process.pid,
|
|
86
235
|
processStartedAtMs: PROCESS_STARTED_AT_MS
|
|
87
|
-
}
|
|
236
|
+
};
|
|
237
|
+
linuxClockTicksPerSecondPromise = null;
|
|
88
238
|
}
|
|
89
239
|
});
|
|
90
240
|
async function clearChromeSingletonEntries(userDataDir) {
|
|
91
241
|
await Promise.all(
|
|
92
242
|
CHROME_SINGLETON_ARTIFACTS.map(
|
|
93
|
-
(entry) => promises.rm(
|
|
243
|
+
(entry) => promises.rm(path10.join(userDataDir, entry), {
|
|
94
244
|
recursive: true,
|
|
95
245
|
force: true
|
|
96
246
|
}).catch(() => void 0)
|
|
@@ -105,7 +255,7 @@ async function sanitizeChromeProfile(userDataDir) {
|
|
|
105
255
|
await Promise.all(profileDirs.map((dir) => sanitizeProfilePreferences(userDataDir, dir)));
|
|
106
256
|
}
|
|
107
257
|
async function sanitizeProfilePreferences(userDataDir, profileDir) {
|
|
108
|
-
const prefsPath =
|
|
258
|
+
const prefsPath = path10.join(userDataDir, profileDir, "Preferences");
|
|
109
259
|
try {
|
|
110
260
|
const raw = await promises.readFile(prefsPath, "utf8");
|
|
111
261
|
const prefs = JSON.parse(raw);
|
|
@@ -117,7 +267,7 @@ async function sanitizeProfilePreferences(userDataDir, profileDir) {
|
|
|
117
267
|
profile.exited_cleanly = true;
|
|
118
268
|
prefs.profile = profile;
|
|
119
269
|
await promises.writeFile(prefsPath, JSON.stringify(prefs), "utf8");
|
|
120
|
-
await promises.rm(
|
|
270
|
+
await promises.rm(path10.join(userDataDir, profileDir, "Secure Preferences"), { force: true }).catch(
|
|
121
271
|
() => void 0
|
|
122
272
|
);
|
|
123
273
|
} catch {
|
|
@@ -172,23 +322,23 @@ function detectInstalledBrowserBrands() {
|
|
|
172
322
|
brandId: brand2.id,
|
|
173
323
|
displayName: brand2.displayName,
|
|
174
324
|
executablePath,
|
|
175
|
-
userDataDir:
|
|
325
|
+
userDataDir: path10.resolve(expandHome(platformConfig.userDataDir))
|
|
176
326
|
});
|
|
177
327
|
}
|
|
178
328
|
return installations;
|
|
179
329
|
}
|
|
180
330
|
function resolveBrandUserDataDir(brand2, explicitDir) {
|
|
181
331
|
if (explicitDir !== void 0) {
|
|
182
|
-
return
|
|
332
|
+
return path10.resolve(expandHome(explicitDir));
|
|
183
333
|
}
|
|
184
334
|
const platformConfig = resolveBrandPlatformConfig(brand2);
|
|
185
335
|
if (!platformConfig) {
|
|
186
336
|
throw new Error(`${brand2.displayName} is not supported on ${process.platform}.`);
|
|
187
337
|
}
|
|
188
|
-
return
|
|
338
|
+
return path10.resolve(expandHome(platformConfig.userDataDir));
|
|
189
339
|
}
|
|
190
340
|
function resolveExecutableCandidates(candidates) {
|
|
191
|
-
return candidates.map((candidate) => candidate ?
|
|
341
|
+
return candidates.map((candidate) => candidate ? path10.resolve(expandHome(candidate)) : null);
|
|
192
342
|
}
|
|
193
343
|
var WINDOWS_PROGRAM_FILES, WINDOWS_PROGRAM_FILES_X86, BROWSER_BRANDS;
|
|
194
344
|
var init_browser_brands = __esm({
|
|
@@ -209,9 +359,9 @@ var init_browser_brands = __esm({
|
|
|
209
359
|
},
|
|
210
360
|
win32: {
|
|
211
361
|
executableCandidates: [
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
362
|
+
path10.join(WINDOWS_PROGRAM_FILES, "Google", "Chrome", "Application", "chrome.exe"),
|
|
363
|
+
path10.join(WINDOWS_PROGRAM_FILES_X86, "Google", "Chrome", "Application", "chrome.exe"),
|
|
364
|
+
path10.join("~", "AppData", "Local", "Google", "Chrome", "Application", "chrome.exe")
|
|
215
365
|
],
|
|
216
366
|
userDataDir: "~/AppData/Local/Google/Chrome/User Data",
|
|
217
367
|
processNames: ["/google/chrome/application/chrome.exe"]
|
|
@@ -241,7 +391,7 @@ var init_browser_brands = __esm({
|
|
|
241
391
|
},
|
|
242
392
|
win32: {
|
|
243
393
|
executableCandidates: [
|
|
244
|
-
|
|
394
|
+
path10.join("~", "AppData", "Local", "Google", "Chrome SxS", "Application", "chrome.exe")
|
|
245
395
|
],
|
|
246
396
|
userDataDir: "~/AppData/Local/Google/Chrome SxS/User Data",
|
|
247
397
|
processNames: ["/google/chrome sxs/application/chrome.exe"]
|
|
@@ -258,9 +408,9 @@ var init_browser_brands = __esm({
|
|
|
258
408
|
},
|
|
259
409
|
win32: {
|
|
260
410
|
executableCandidates: [
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
411
|
+
path10.join(WINDOWS_PROGRAM_FILES, "Chromium", "Application", "chrome.exe"),
|
|
412
|
+
path10.join(WINDOWS_PROGRAM_FILES_X86, "Chromium", "Application", "chrome.exe"),
|
|
413
|
+
path10.join("~", "AppData", "Local", "Chromium", "Application", "chrome.exe")
|
|
264
414
|
],
|
|
265
415
|
userDataDir: "~/AppData/Local/Chromium/User Data",
|
|
266
416
|
processNames: ["/chromium/application/chrome.exe"]
|
|
@@ -287,15 +437,15 @@ var init_browser_brands = __esm({
|
|
|
287
437
|
},
|
|
288
438
|
win32: {
|
|
289
439
|
executableCandidates: [
|
|
290
|
-
|
|
291
|
-
|
|
440
|
+
path10.join(WINDOWS_PROGRAM_FILES, "BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
|
|
441
|
+
path10.join(
|
|
292
442
|
WINDOWS_PROGRAM_FILES_X86,
|
|
293
443
|
"BraveSoftware",
|
|
294
444
|
"Brave-Browser",
|
|
295
445
|
"Application",
|
|
296
446
|
"brave.exe"
|
|
297
447
|
),
|
|
298
|
-
|
|
448
|
+
path10.join("~", "AppData", "Local", "BraveSoftware", "Brave-Browser", "Application", "brave.exe")
|
|
299
449
|
],
|
|
300
450
|
userDataDir: "~/AppData/Local/BraveSoftware/Brave-Browser/User Data",
|
|
301
451
|
processNames: ["/bravesoftware/brave-browser/application/brave.exe"]
|
|
@@ -321,9 +471,9 @@ var init_browser_brands = __esm({
|
|
|
321
471
|
},
|
|
322
472
|
win32: {
|
|
323
473
|
executableCandidates: [
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
474
|
+
path10.join(WINDOWS_PROGRAM_FILES, "Microsoft", "Edge", "Application", "msedge.exe"),
|
|
475
|
+
path10.join(WINDOWS_PROGRAM_FILES_X86, "Microsoft", "Edge", "Application", "msedge.exe"),
|
|
476
|
+
path10.join("~", "AppData", "Local", "Microsoft", "Edge", "Application", "msedge.exe")
|
|
327
477
|
],
|
|
328
478
|
userDataDir: "~/AppData/Local/Microsoft/Edge/User Data",
|
|
329
479
|
processNames: ["/microsoft/edge/application/msedge.exe"]
|
|
@@ -351,9 +501,9 @@ var init_browser_brands = __esm({
|
|
|
351
501
|
},
|
|
352
502
|
win32: {
|
|
353
503
|
executableCandidates: [
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
504
|
+
path10.join(WINDOWS_PROGRAM_FILES, "Vivaldi", "Application", "vivaldi.exe"),
|
|
505
|
+
path10.join(WINDOWS_PROGRAM_FILES_X86, "Vivaldi", "Application", "vivaldi.exe"),
|
|
506
|
+
path10.join("~", "AppData", "Local", "Vivaldi", "Application", "vivaldi.exe")
|
|
357
507
|
],
|
|
358
508
|
userDataDir: "~/AppData/Local/Vivaldi/User Data",
|
|
359
509
|
processNames: ["/vivaldi/application/vivaldi.exe"]
|
|
@@ -384,13 +534,13 @@ var init_browser_brands = __esm({
|
|
|
384
534
|
});
|
|
385
535
|
function expandHome(value) {
|
|
386
536
|
if (value === "~" || value.startsWith("~/")) {
|
|
387
|
-
return
|
|
537
|
+
return path10.join(os.homedir(), value.slice(1));
|
|
388
538
|
}
|
|
389
539
|
return value;
|
|
390
540
|
}
|
|
391
541
|
function resolveChromeExecutablePath(executablePath) {
|
|
392
542
|
if (executablePath !== void 0) {
|
|
393
|
-
const resolvedPath =
|
|
543
|
+
const resolvedPath = path10.resolve(expandHome(executablePath));
|
|
394
544
|
if (!fs.existsSync(resolvedPath)) {
|
|
395
545
|
throw new Error(`Chrome executable was not found at "${resolvedPath}".`);
|
|
396
546
|
}
|
|
@@ -414,37 +564,37 @@ function detectLocalChromeInstallations() {
|
|
|
414
564
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
415
565
|
"/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary"
|
|
416
566
|
]),
|
|
417
|
-
userDataDir:
|
|
567
|
+
userDataDir: path10.join(os.homedir(), "Library", "Application Support", "Google", "Chrome")
|
|
418
568
|
},
|
|
419
569
|
{
|
|
420
570
|
brand: "chromium",
|
|
421
571
|
executablePath: firstExistingPath(["/Applications/Chromium.app/Contents/MacOS/Chromium"]),
|
|
422
|
-
userDataDir:
|
|
572
|
+
userDataDir: path10.join(os.homedir(), "Library", "Application Support", "Chromium")
|
|
423
573
|
}
|
|
424
574
|
];
|
|
425
575
|
}
|
|
426
576
|
if (process.platform === "win32") {
|
|
427
577
|
const programFiles = process.env.PROGRAMFILES ?? "C:\\Program Files";
|
|
428
578
|
const programFilesX86 = process.env["PROGRAMFILES(X86)"] ?? "C:\\Program Files (x86)";
|
|
429
|
-
const localAppData = process.env.LOCALAPPDATA ??
|
|
579
|
+
const localAppData = process.env.LOCALAPPDATA ?? path10.join(os.homedir(), "AppData", "Local");
|
|
430
580
|
return [
|
|
431
581
|
{
|
|
432
582
|
brand: "chrome",
|
|
433
583
|
executablePath: firstExistingPath([
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
584
|
+
path10.join(programFiles, "Google", "Chrome", "Application", "chrome.exe"),
|
|
585
|
+
path10.join(programFilesX86, "Google", "Chrome", "Application", "chrome.exe"),
|
|
586
|
+
path10.join(localAppData, "Google", "Chrome", "Application", "chrome.exe")
|
|
437
587
|
]),
|
|
438
|
-
userDataDir:
|
|
588
|
+
userDataDir: path10.join(localAppData, "Google", "Chrome", "User Data")
|
|
439
589
|
},
|
|
440
590
|
{
|
|
441
591
|
brand: "chromium",
|
|
442
592
|
executablePath: firstExistingPath([
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
593
|
+
path10.join(programFiles, "Chromium", "Application", "chrome.exe"),
|
|
594
|
+
path10.join(programFilesX86, "Chromium", "Application", "chrome.exe"),
|
|
595
|
+
path10.join(localAppData, "Chromium", "Application", "chrome.exe")
|
|
446
596
|
]),
|
|
447
|
-
userDataDir:
|
|
597
|
+
userDataDir: path10.join(localAppData, "Chromium", "User Data")
|
|
448
598
|
}
|
|
449
599
|
];
|
|
450
600
|
}
|
|
@@ -457,7 +607,7 @@ function detectLocalChromeInstallations() {
|
|
|
457
607
|
resolveBinaryFromPath("google-chrome"),
|
|
458
608
|
resolveBinaryFromPath("google-chrome-stable")
|
|
459
609
|
]),
|
|
460
|
-
userDataDir:
|
|
610
|
+
userDataDir: path10.join(os.homedir(), ".config", "google-chrome")
|
|
461
611
|
},
|
|
462
612
|
{
|
|
463
613
|
brand: "chromium",
|
|
@@ -467,7 +617,7 @@ function detectLocalChromeInstallations() {
|
|
|
467
617
|
resolveBinaryFromPath("chromium"),
|
|
468
618
|
resolveBinaryFromPath("chromium-browser")
|
|
469
619
|
]),
|
|
470
|
-
userDataDir:
|
|
620
|
+
userDataDir: path10.join(os.homedir(), ".config", "chromium")
|
|
471
621
|
}
|
|
472
622
|
];
|
|
473
623
|
}
|
|
@@ -479,7 +629,7 @@ function detectLocalBrowserInstallations() {
|
|
|
479
629
|
}));
|
|
480
630
|
}
|
|
481
631
|
function readDevToolsActivePort(userDataDir) {
|
|
482
|
-
const devToolsPath =
|
|
632
|
+
const devToolsPath = path10.join(userDataDir, "DevToolsActivePort");
|
|
483
633
|
if (!fs.existsSync(devToolsPath)) {
|
|
484
634
|
return null;
|
|
485
635
|
}
|
|
@@ -705,8 +855,8 @@ function buildBrowserWebSocketUrl(httpUrl, webSocketPath) {
|
|
|
705
855
|
const protocol = httpUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
706
856
|
return `${protocol}//${httpUrl.host}${normalizeWebSocketPath(webSocketPath)}`;
|
|
707
857
|
}
|
|
708
|
-
function normalizeWebSocketPath(
|
|
709
|
-
return
|
|
858
|
+
function normalizeWebSocketPath(path24) {
|
|
859
|
+
return path24.startsWith("/") ? path24 : `/${path24}`;
|
|
710
860
|
}
|
|
711
861
|
function rewriteBrowserWebSocketHost(browserWsUrl, requestedUrl) {
|
|
712
862
|
try {
|
|
@@ -742,20 +892,20 @@ var init_cdp_discovery = __esm({
|
|
|
742
892
|
}
|
|
743
893
|
});
|
|
744
894
|
async function createBrowserProfileSnapshot(input) {
|
|
745
|
-
const sourceUserDataDir =
|
|
746
|
-
const targetUserDataDir =
|
|
895
|
+
const sourceUserDataDir = path10.resolve(expandHome(input.sourceUserDataDir));
|
|
896
|
+
const targetUserDataDir = path10.resolve(expandHome(input.targetUserDataDir));
|
|
747
897
|
const profileDirectory = input.profileDirectory?.trim();
|
|
748
898
|
const copyMode = input.copyMode;
|
|
749
899
|
await promises.mkdir(targetUserDataDir, { recursive: true });
|
|
750
900
|
await clearChromeSingletonEntries(targetUserDataDir);
|
|
751
901
|
if (profileDirectory) {
|
|
752
|
-
const sourceProfileDir =
|
|
902
|
+
const sourceProfileDir = path10.join(sourceUserDataDir, profileDirectory);
|
|
753
903
|
if (!fs.existsSync(sourceProfileDir)) {
|
|
754
904
|
throw new Error(
|
|
755
905
|
`Chrome profile "${profileDirectory}" was not found in "${sourceUserDataDir}".`
|
|
756
906
|
);
|
|
757
907
|
}
|
|
758
|
-
await promises.cp(sourceProfileDir,
|
|
908
|
+
await promises.cp(sourceProfileDir, path10.join(targetUserDataDir, profileDirectory), {
|
|
759
909
|
recursive: true,
|
|
760
910
|
filter: (candidate) => shouldCopyEntry({
|
|
761
911
|
candidatePath: candidate,
|
|
@@ -779,8 +929,8 @@ async function copyRootLevelEntries(input) {
|
|
|
779
929
|
if (CHROME_SINGLETON_ENTRIES.has(entry) || entry === input.selectedProfileDirectory) {
|
|
780
930
|
continue;
|
|
781
931
|
}
|
|
782
|
-
const sourcePath =
|
|
783
|
-
const targetPath =
|
|
932
|
+
const sourcePath = path10.join(input.sourceUserDataDir, entry);
|
|
933
|
+
const targetPath = path10.join(input.targetUserDataDir, entry);
|
|
784
934
|
const entryStat = await promises.stat(sourcePath).catch(() => null);
|
|
785
935
|
if (!entryStat) {
|
|
786
936
|
continue;
|
|
@@ -812,7 +962,7 @@ async function copyRootLevelEntries(input) {
|
|
|
812
962
|
}
|
|
813
963
|
}
|
|
814
964
|
function isProfileDirectory(userDataDir, entry) {
|
|
815
|
-
return fs.existsSync(
|
|
965
|
+
return fs.existsSync(path10.join(userDataDir, entry, "Preferences"));
|
|
816
966
|
}
|
|
817
967
|
function shouldCopyEntry(input) {
|
|
818
968
|
const entryName = input.candidatePath.split("/").at(-1)?.split("\\").at(-1) ?? input.candidatePath;
|
|
@@ -822,7 +972,7 @@ function shouldCopyEntry(input) {
|
|
|
822
972
|
if (input.copyMode !== "session") {
|
|
823
973
|
return true;
|
|
824
974
|
}
|
|
825
|
-
const relativePath =
|
|
975
|
+
const relativePath = path10.relative(input.rootPath, input.candidatePath);
|
|
826
976
|
if (relativePath.length === 0) {
|
|
827
977
|
return true;
|
|
828
978
|
}
|
|
@@ -1360,30 +1510,30 @@ function isPlainObject(value) {
|
|
|
1360
1510
|
const prototype = Object.getPrototypeOf(value);
|
|
1361
1511
|
return prototype === Object.prototype || prototype === null;
|
|
1362
1512
|
}
|
|
1363
|
-
function canonicalizeJsonValue(value,
|
|
1513
|
+
function canonicalizeJsonValue(value, path24) {
|
|
1364
1514
|
if (value === null || typeof value === "string" || typeof value === "boolean") {
|
|
1365
1515
|
return value;
|
|
1366
1516
|
}
|
|
1367
1517
|
if (typeof value === "number") {
|
|
1368
1518
|
if (!Number.isFinite(value)) {
|
|
1369
|
-
throw new TypeError(`${
|
|
1519
|
+
throw new TypeError(`${path24} must be a finite JSON number`);
|
|
1370
1520
|
}
|
|
1371
1521
|
return value;
|
|
1372
1522
|
}
|
|
1373
1523
|
if (Array.isArray(value)) {
|
|
1374
|
-
return value.map((entry, index) => canonicalizeJsonValue(entry, `${
|
|
1524
|
+
return value.map((entry, index) => canonicalizeJsonValue(entry, `${path24}[${index}]`));
|
|
1375
1525
|
}
|
|
1376
1526
|
if (!isPlainObject(value)) {
|
|
1377
|
-
throw new TypeError(`${
|
|
1527
|
+
throw new TypeError(`${path24} must be a plain JSON object`);
|
|
1378
1528
|
}
|
|
1379
1529
|
const sorted = Object.keys(value).sort((left, right) => left.localeCompare(right));
|
|
1380
1530
|
const result = {};
|
|
1381
1531
|
for (const key of sorted) {
|
|
1382
1532
|
const entry = value[key];
|
|
1383
1533
|
if (entry === void 0) {
|
|
1384
|
-
throw new TypeError(`${
|
|
1534
|
+
throw new TypeError(`${path24}.${key} must not be undefined`);
|
|
1385
1535
|
}
|
|
1386
|
-
result[key] = canonicalizeJsonValue(entry, `${
|
|
1536
|
+
result[key] = canonicalizeJsonValue(entry, `${path24}.${key}`);
|
|
1387
1537
|
}
|
|
1388
1538
|
return result;
|
|
1389
1539
|
}
|
|
@@ -1421,7 +1571,7 @@ function joinStoragePath(...segments) {
|
|
|
1421
1571
|
return segments.join("/");
|
|
1422
1572
|
}
|
|
1423
1573
|
function resolveStoragePath(rootPath, relativePath) {
|
|
1424
|
-
if (
|
|
1574
|
+
if (path10__default.default.isAbsolute(relativePath)) {
|
|
1425
1575
|
throw new TypeError(`storage path ${relativePath} must be relative`);
|
|
1426
1576
|
}
|
|
1427
1577
|
const segments = relativePath.split("/");
|
|
@@ -1433,7 +1583,7 @@ function resolveStoragePath(rootPath, relativePath) {
|
|
|
1433
1583
|
throw new TypeError(`storage path ${relativePath} must not contain path traversal`);
|
|
1434
1584
|
}
|
|
1435
1585
|
}
|
|
1436
|
-
return
|
|
1586
|
+
return path10__default.default.join(rootPath, ...segments);
|
|
1437
1587
|
}
|
|
1438
1588
|
async function ensureDirectory(directoryPath) {
|
|
1439
1589
|
await promises.mkdir(directoryPath, { recursive: true });
|
|
@@ -1453,7 +1603,7 @@ async function writeJsonFileAtomic(filePath, value) {
|
|
|
1453
1603
|
await writeTextFileAtomic(filePath, stableJsonString(value));
|
|
1454
1604
|
}
|
|
1455
1605
|
async function writeTextFileAtomic(filePath, value) {
|
|
1456
|
-
await ensureDirectory(
|
|
1606
|
+
await ensureDirectory(path10__default.default.dirname(filePath));
|
|
1457
1607
|
const temporaryPath = `${filePath}.${crypto.randomUUID()}.tmp`;
|
|
1458
1608
|
await promises.writeFile(temporaryPath, value, "utf8");
|
|
1459
1609
|
await promises.rename(temporaryPath, filePath);
|
|
@@ -1462,7 +1612,7 @@ async function writeJsonFileExclusive(filePath, value) {
|
|
|
1462
1612
|
await writeTextFileExclusive(filePath, stableJsonString(value));
|
|
1463
1613
|
}
|
|
1464
1614
|
async function writeTextFileExclusive(filePath, value) {
|
|
1465
|
-
await ensureDirectory(
|
|
1615
|
+
await ensureDirectory(path10__default.default.dirname(filePath));
|
|
1466
1616
|
const handle = await promises.open(filePath, "wx");
|
|
1467
1617
|
try {
|
|
1468
1618
|
await handle.writeFile(value, "utf8");
|
|
@@ -1471,7 +1621,7 @@ async function writeTextFileExclusive(filePath, value) {
|
|
|
1471
1621
|
}
|
|
1472
1622
|
}
|
|
1473
1623
|
async function writeBufferIfMissing(filePath, value) {
|
|
1474
|
-
await ensureDirectory(
|
|
1624
|
+
await ensureDirectory(path10__default.default.dirname(filePath));
|
|
1475
1625
|
try {
|
|
1476
1626
|
const handle = await promises.open(filePath, "wx");
|
|
1477
1627
|
try {
|
|
@@ -1505,7 +1655,7 @@ function isAlreadyExistsError(error) {
|
|
|
1505
1655
|
return error?.code === "EEXIST";
|
|
1506
1656
|
}
|
|
1507
1657
|
async function withFilesystemLock(lockPath, task) {
|
|
1508
|
-
await ensureDirectory(
|
|
1658
|
+
await ensureDirectory(path10__default.default.dirname(lockPath));
|
|
1509
1659
|
let attempt = 0;
|
|
1510
1660
|
while (true) {
|
|
1511
1661
|
try {
|
|
@@ -1594,8 +1744,8 @@ var init_artifacts = __esm({
|
|
|
1594
1744
|
FilesystemArtifactStore = class {
|
|
1595
1745
|
constructor(rootPath) {
|
|
1596
1746
|
this.rootPath = rootPath;
|
|
1597
|
-
this.manifestsDirectory =
|
|
1598
|
-
this.objectsDirectory =
|
|
1747
|
+
this.manifestsDirectory = path10__default.default.join(this.rootPath, "artifacts", "manifests");
|
|
1748
|
+
this.objectsDirectory = path10__default.default.join(this.rootPath, "artifacts", "objects", "sha256");
|
|
1599
1749
|
}
|
|
1600
1750
|
manifestsDirectory;
|
|
1601
1751
|
objectsDirectory;
|
|
@@ -1820,7 +1970,7 @@ var init_artifacts = __esm({
|
|
|
1820
1970
|
}
|
|
1821
1971
|
}
|
|
1822
1972
|
manifestPath(artifactId) {
|
|
1823
|
-
return
|
|
1973
|
+
return path10__default.default.join(this.manifestsDirectory, `${encodePathSegment(artifactId)}.json`);
|
|
1824
1974
|
}
|
|
1825
1975
|
};
|
|
1826
1976
|
}
|
|
@@ -1898,31 +2048,31 @@ var init_json2 = __esm({
|
|
|
1898
2048
|
});
|
|
1899
2049
|
|
|
1900
2050
|
// ../protocol/src/validation.ts
|
|
1901
|
-
function validateJsonSchema(schema, value,
|
|
1902
|
-
return validateSchemaNode(schema, value,
|
|
2051
|
+
function validateJsonSchema(schema, value, path24 = "$") {
|
|
2052
|
+
return validateSchemaNode(schema, value, path24);
|
|
1903
2053
|
}
|
|
1904
|
-
function validateSchemaNode(schema, value,
|
|
2054
|
+
function validateSchemaNode(schema, value, path24) {
|
|
1905
2055
|
const issues = [];
|
|
1906
2056
|
if ("const" in schema && !isJsonValueEqual(schema.const, value)) {
|
|
1907
2057
|
issues.push({
|
|
1908
|
-
path:
|
|
2058
|
+
path: path24,
|
|
1909
2059
|
message: `must equal ${JSON.stringify(schema.const)}`
|
|
1910
2060
|
});
|
|
1911
2061
|
return issues;
|
|
1912
2062
|
}
|
|
1913
2063
|
if (schema.enum !== void 0 && !schema.enum.some((candidate) => isJsonValueEqual(candidate, value))) {
|
|
1914
2064
|
issues.push({
|
|
1915
|
-
path:
|
|
2065
|
+
path: path24,
|
|
1916
2066
|
message: `must be one of ${schema.enum.map((candidate) => JSON.stringify(candidate)).join(", ")}`
|
|
1917
2067
|
});
|
|
1918
2068
|
return issues;
|
|
1919
2069
|
}
|
|
1920
2070
|
if (schema.oneOf !== void 0) {
|
|
1921
|
-
const branchIssues = schema.oneOf.map((member) => validateSchemaNode(member, value,
|
|
2071
|
+
const branchIssues = schema.oneOf.map((member) => validateSchemaNode(member, value, path24));
|
|
1922
2072
|
const validBranches = branchIssues.filter((current) => current.length === 0).length;
|
|
1923
2073
|
if (validBranches !== 1) {
|
|
1924
2074
|
issues.push({
|
|
1925
|
-
path:
|
|
2075
|
+
path: path24,
|
|
1926
2076
|
message: validBranches === 0 ? "must match exactly one supported shape" : "matches multiple supported shapes"
|
|
1927
2077
|
});
|
|
1928
2078
|
return issues;
|
|
@@ -1930,11 +2080,11 @@ function validateSchemaNode(schema, value, path18) {
|
|
|
1930
2080
|
}
|
|
1931
2081
|
if (schema.anyOf !== void 0) {
|
|
1932
2082
|
const hasMatch = schema.anyOf.some(
|
|
1933
|
-
(member) => validateSchemaNode(member, value,
|
|
2083
|
+
(member) => validateSchemaNode(member, value, path24).length === 0
|
|
1934
2084
|
);
|
|
1935
2085
|
if (!hasMatch) {
|
|
1936
2086
|
issues.push({
|
|
1937
|
-
path:
|
|
2087
|
+
path: path24,
|
|
1938
2088
|
message: "must match at least one supported shape"
|
|
1939
2089
|
});
|
|
1940
2090
|
return issues;
|
|
@@ -1942,7 +2092,7 @@ function validateSchemaNode(schema, value, path18) {
|
|
|
1942
2092
|
}
|
|
1943
2093
|
if (schema.allOf !== void 0) {
|
|
1944
2094
|
for (const member of schema.allOf) {
|
|
1945
|
-
issues.push(...validateSchemaNode(member, value,
|
|
2095
|
+
issues.push(...validateSchemaNode(member, value, path24));
|
|
1946
2096
|
}
|
|
1947
2097
|
if (issues.length > 0) {
|
|
1948
2098
|
return issues;
|
|
@@ -1950,7 +2100,7 @@ function validateSchemaNode(schema, value, path18) {
|
|
|
1950
2100
|
}
|
|
1951
2101
|
if (schema.type !== void 0 && !matchesSchemaType(schema.type, value)) {
|
|
1952
2102
|
issues.push({
|
|
1953
|
-
path:
|
|
2103
|
+
path: path24,
|
|
1954
2104
|
message: `must be ${describeSchemaType(schema.type)}`
|
|
1955
2105
|
});
|
|
1956
2106
|
return issues;
|
|
@@ -1958,19 +2108,19 @@ function validateSchemaNode(schema, value, path18) {
|
|
|
1958
2108
|
if (typeof value === "string") {
|
|
1959
2109
|
if (schema.minLength !== void 0 && value.length < schema.minLength) {
|
|
1960
2110
|
issues.push({
|
|
1961
|
-
path:
|
|
2111
|
+
path: path24,
|
|
1962
2112
|
message: `must have length >= ${String(schema.minLength)}`
|
|
1963
2113
|
});
|
|
1964
2114
|
}
|
|
1965
2115
|
if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
|
|
1966
2116
|
issues.push({
|
|
1967
|
-
path:
|
|
2117
|
+
path: path24,
|
|
1968
2118
|
message: `must have length <= ${String(schema.maxLength)}`
|
|
1969
2119
|
});
|
|
1970
2120
|
}
|
|
1971
2121
|
if (schema.pattern !== void 0 && !new RegExp(schema.pattern).test(value)) {
|
|
1972
2122
|
issues.push({
|
|
1973
|
-
path:
|
|
2123
|
+
path: path24,
|
|
1974
2124
|
message: `must match pattern ${schema.pattern}`
|
|
1975
2125
|
});
|
|
1976
2126
|
}
|
|
@@ -1979,25 +2129,25 @@ function validateSchemaNode(schema, value, path18) {
|
|
|
1979
2129
|
if (typeof value === "number") {
|
|
1980
2130
|
if (schema.minimum !== void 0 && value < schema.minimum) {
|
|
1981
2131
|
issues.push({
|
|
1982
|
-
path:
|
|
2132
|
+
path: path24,
|
|
1983
2133
|
message: `must be >= ${String(schema.minimum)}`
|
|
1984
2134
|
});
|
|
1985
2135
|
}
|
|
1986
2136
|
if (schema.maximum !== void 0 && value > schema.maximum) {
|
|
1987
2137
|
issues.push({
|
|
1988
|
-
path:
|
|
2138
|
+
path: path24,
|
|
1989
2139
|
message: `must be <= ${String(schema.maximum)}`
|
|
1990
2140
|
});
|
|
1991
2141
|
}
|
|
1992
2142
|
if (schema.exclusiveMinimum !== void 0 && value <= schema.exclusiveMinimum) {
|
|
1993
2143
|
issues.push({
|
|
1994
|
-
path:
|
|
2144
|
+
path: path24,
|
|
1995
2145
|
message: `must be > ${String(schema.exclusiveMinimum)}`
|
|
1996
2146
|
});
|
|
1997
2147
|
}
|
|
1998
2148
|
if (schema.exclusiveMaximum !== void 0 && value >= schema.exclusiveMaximum) {
|
|
1999
2149
|
issues.push({
|
|
2000
|
-
path:
|
|
2150
|
+
path: path24,
|
|
2001
2151
|
message: `must be < ${String(schema.exclusiveMaximum)}`
|
|
2002
2152
|
});
|
|
2003
2153
|
}
|
|
@@ -2006,13 +2156,13 @@ function validateSchemaNode(schema, value, path18) {
|
|
|
2006
2156
|
if (Array.isArray(value)) {
|
|
2007
2157
|
if (schema.minItems !== void 0 && value.length < schema.minItems) {
|
|
2008
2158
|
issues.push({
|
|
2009
|
-
path:
|
|
2159
|
+
path: path24,
|
|
2010
2160
|
message: `must have at least ${String(schema.minItems)} items`
|
|
2011
2161
|
});
|
|
2012
2162
|
}
|
|
2013
2163
|
if (schema.maxItems !== void 0 && value.length > schema.maxItems) {
|
|
2014
2164
|
issues.push({
|
|
2015
|
-
path:
|
|
2165
|
+
path: path24,
|
|
2016
2166
|
message: `must have at most ${String(schema.maxItems)} items`
|
|
2017
2167
|
});
|
|
2018
2168
|
}
|
|
@@ -2022,7 +2172,7 @@ function validateSchemaNode(schema, value, path18) {
|
|
|
2022
2172
|
const key = JSON.stringify(item);
|
|
2023
2173
|
if (seen.has(key)) {
|
|
2024
2174
|
issues.push({
|
|
2025
|
-
path:
|
|
2175
|
+
path: path24,
|
|
2026
2176
|
message: "must not contain duplicate items"
|
|
2027
2177
|
});
|
|
2028
2178
|
break;
|
|
@@ -2032,7 +2182,7 @@ function validateSchemaNode(schema, value, path18) {
|
|
|
2032
2182
|
}
|
|
2033
2183
|
if (schema.items !== void 0) {
|
|
2034
2184
|
for (let index = 0; index < value.length; index += 1) {
|
|
2035
|
-
issues.push(...validateSchemaNode(schema.items, value[index], `${
|
|
2185
|
+
issues.push(...validateSchemaNode(schema.items, value[index], `${path24}[${String(index)}]`));
|
|
2036
2186
|
}
|
|
2037
2187
|
}
|
|
2038
2188
|
return issues;
|
|
@@ -2042,7 +2192,7 @@ function validateSchemaNode(schema, value, path18) {
|
|
|
2042
2192
|
for (const requiredKey of schema.required ?? []) {
|
|
2043
2193
|
if (!(requiredKey in value)) {
|
|
2044
2194
|
issues.push({
|
|
2045
|
-
path: joinObjectPath(
|
|
2195
|
+
path: joinObjectPath(path24, requiredKey),
|
|
2046
2196
|
message: "is required"
|
|
2047
2197
|
});
|
|
2048
2198
|
}
|
|
@@ -2051,13 +2201,13 @@ function validateSchemaNode(schema, value, path18) {
|
|
|
2051
2201
|
const propertySchema = properties[key];
|
|
2052
2202
|
if (propertySchema !== void 0) {
|
|
2053
2203
|
issues.push(
|
|
2054
|
-
...validateSchemaNode(propertySchema, propertyValue, joinObjectPath(
|
|
2204
|
+
...validateSchemaNode(propertySchema, propertyValue, joinObjectPath(path24, key))
|
|
2055
2205
|
);
|
|
2056
2206
|
continue;
|
|
2057
2207
|
}
|
|
2058
2208
|
if (schema.additionalProperties === false) {
|
|
2059
2209
|
issues.push({
|
|
2060
|
-
path: joinObjectPath(
|
|
2210
|
+
path: joinObjectPath(path24, key),
|
|
2061
2211
|
message: "is not allowed"
|
|
2062
2212
|
});
|
|
2063
2213
|
continue;
|
|
@@ -2067,7 +2217,7 @@ function validateSchemaNode(schema, value, path18) {
|
|
|
2067
2217
|
...validateSchemaNode(
|
|
2068
2218
|
schema.additionalProperties,
|
|
2069
2219
|
propertyValue,
|
|
2070
|
-
joinObjectPath(
|
|
2220
|
+
joinObjectPath(path24, key)
|
|
2071
2221
|
)
|
|
2072
2222
|
);
|
|
2073
2223
|
}
|
|
@@ -2348,8 +2498,8 @@ function matchesNetworkRecordFilters(record, filters) {
|
|
|
2348
2498
|
}
|
|
2349
2499
|
}
|
|
2350
2500
|
if (filters.path !== void 0) {
|
|
2351
|
-
const
|
|
2352
|
-
if (!includesCaseInsensitive(
|
|
2501
|
+
const path24 = getParsedUrl().pathname;
|
|
2502
|
+
if (!includesCaseInsensitive(path24, filters.path)) {
|
|
2353
2503
|
return false;
|
|
2354
2504
|
}
|
|
2355
2505
|
}
|
|
@@ -5661,6 +5811,12 @@ var init_session_info = __esm({
|
|
|
5661
5811
|
}
|
|
5662
5812
|
});
|
|
5663
5813
|
|
|
5814
|
+
// ../protocol/src/local-view.ts
|
|
5815
|
+
var init_local_view = __esm({
|
|
5816
|
+
"../protocol/src/local-view.ts"() {
|
|
5817
|
+
}
|
|
5818
|
+
});
|
|
5819
|
+
|
|
5664
5820
|
// ../protocol/src/automation.ts
|
|
5665
5821
|
var init_automation = __esm({
|
|
5666
5822
|
"../protocol/src/automation.ts"() {
|
|
@@ -9678,6 +9834,7 @@ var init_src2 = __esm({
|
|
|
9678
9834
|
init_observability();
|
|
9679
9835
|
init_envelopes();
|
|
9680
9836
|
init_session_info();
|
|
9837
|
+
init_local_view();
|
|
9681
9838
|
init_automation();
|
|
9682
9839
|
init_operations();
|
|
9683
9840
|
init_rest();
|
|
@@ -9763,9 +9920,9 @@ var init_registry = __esm({
|
|
|
9763
9920
|
FilesystemRegistryStore = class {
|
|
9764
9921
|
constructor(rootPath, registryRelativePath) {
|
|
9765
9922
|
this.registryRelativePath = registryRelativePath;
|
|
9766
|
-
const basePath =
|
|
9767
|
-
this.recordsDirectory =
|
|
9768
|
-
this.indexesDirectory =
|
|
9923
|
+
const basePath = path10__default.default.join(rootPath, ...registryRelativePath);
|
|
9924
|
+
this.recordsDirectory = path10__default.default.join(basePath, "records");
|
|
9925
|
+
this.indexesDirectory = path10__default.default.join(basePath, "indexes", "by-key");
|
|
9769
9926
|
}
|
|
9770
9927
|
recordsDirectory;
|
|
9771
9928
|
indexesDirectory;
|
|
@@ -9834,7 +9991,7 @@ var init_registry = __esm({
|
|
|
9834
9991
|
async readRecordsFromDirectory() {
|
|
9835
9992
|
const files = await listJsonFiles(this.recordsDirectory);
|
|
9836
9993
|
const records = await Promise.all(
|
|
9837
|
-
files.map((fileName) => readJsonFile(
|
|
9994
|
+
files.map((fileName) => readJsonFile(path10__default.default.join(this.recordsDirectory, fileName)))
|
|
9838
9995
|
);
|
|
9839
9996
|
records.sort(compareByCreatedAtAndId);
|
|
9840
9997
|
return records;
|
|
@@ -9866,17 +10023,17 @@ var init_registry = __esm({
|
|
|
9866
10023
|
return record;
|
|
9867
10024
|
}
|
|
9868
10025
|
recordPath(id) {
|
|
9869
|
-
return
|
|
10026
|
+
return path10__default.default.join(this.recordsDirectory, `${encodePathSegment(id)}.json`);
|
|
9870
10027
|
}
|
|
9871
10028
|
indexPath(key, version) {
|
|
9872
|
-
return
|
|
10029
|
+
return path10__default.default.join(
|
|
9873
10030
|
this.indexesDirectory,
|
|
9874
10031
|
encodePathSegment(key),
|
|
9875
10032
|
`${encodePathSegment(version)}.json`
|
|
9876
10033
|
);
|
|
9877
10034
|
}
|
|
9878
10035
|
writeLockPath() {
|
|
9879
|
-
return
|
|
10036
|
+
return path10__default.default.join(path10__default.default.dirname(this.recordsDirectory), ".write.lock");
|
|
9880
10037
|
}
|
|
9881
10038
|
};
|
|
9882
10039
|
FilesystemDescriptorRegistry = class extends FilesystemRegistryStore {
|
|
@@ -10515,7 +10672,7 @@ var init_saved_store = __esm({
|
|
|
10515
10672
|
directoryInitialization;
|
|
10516
10673
|
databaseInitialization;
|
|
10517
10674
|
constructor(rootPath) {
|
|
10518
|
-
this.databasePath =
|
|
10675
|
+
this.databasePath = path10__default.default.join(rootPath, "registry", "saved-network.sqlite");
|
|
10519
10676
|
}
|
|
10520
10677
|
async initialize() {
|
|
10521
10678
|
await this.ensureDatabaseDirectory();
|
|
@@ -10755,7 +10912,7 @@ var init_saved_store = __esm({
|
|
|
10755
10912
|
}
|
|
10756
10913
|
}
|
|
10757
10914
|
async ensureDatabaseDirectory() {
|
|
10758
|
-
this.directoryInitialization ??= ensureDirectory(
|
|
10915
|
+
this.directoryInitialization ??= ensureDirectory(path10__default.default.dirname(this.databasePath)).catch(
|
|
10759
10916
|
(error) => {
|
|
10760
10917
|
this.directoryInitialization = void 0;
|
|
10761
10918
|
throw error;
|
|
@@ -11095,7 +11252,7 @@ var init_observations = __esm({
|
|
|
11095
11252
|
constructor(rootPath, artifacts) {
|
|
11096
11253
|
this.rootPath = rootPath;
|
|
11097
11254
|
this.artifacts = artifacts;
|
|
11098
|
-
this.sessionsDirectory =
|
|
11255
|
+
this.sessionsDirectory = path10__default.default.join(this.rootPath, "observations", "sessions");
|
|
11099
11256
|
}
|
|
11100
11257
|
sessionsDirectory;
|
|
11101
11258
|
redactors = /* @__PURE__ */ new Map();
|
|
@@ -11192,7 +11349,7 @@ var init_observations = __esm({
|
|
|
11192
11349
|
...raw.artifactIds === void 0 || raw.artifactIds.length === 0 ? {} : { artifactIds: [...raw.artifactIds] }
|
|
11193
11350
|
};
|
|
11194
11351
|
await writeJsonFileExclusive(
|
|
11195
|
-
|
|
11352
|
+
path10__default.default.join(this.sessionEventsDirectory(sessionId), eventFileName(sequence)),
|
|
11196
11353
|
event
|
|
11197
11354
|
);
|
|
11198
11355
|
updatedAt = Math.max(updatedAt, createdAt);
|
|
@@ -11250,7 +11407,7 @@ var init_observations = __esm({
|
|
|
11250
11407
|
}
|
|
11251
11408
|
const files = await listJsonFiles(directoryPath);
|
|
11252
11409
|
const events = await Promise.all(
|
|
11253
|
-
files.map((fileName) => readJsonFile(
|
|
11410
|
+
files.map((fileName) => readJsonFile(path10__default.default.join(directoryPath, fileName)))
|
|
11254
11411
|
);
|
|
11255
11412
|
const filtered = events.filter((event) => {
|
|
11256
11413
|
if (input.kind !== void 0 && event.kind !== input.kind) return false;
|
|
@@ -11277,7 +11434,7 @@ var init_observations = __esm({
|
|
|
11277
11434
|
const files = await listJsonFiles(directoryPath);
|
|
11278
11435
|
const artifacts = await Promise.all(
|
|
11279
11436
|
files.map(
|
|
11280
|
-
(fileName) => readJsonFile(
|
|
11437
|
+
(fileName) => readJsonFile(path10__default.default.join(directoryPath, fileName))
|
|
11281
11438
|
)
|
|
11282
11439
|
);
|
|
11283
11440
|
const filtered = artifacts.filter((artifact) => {
|
|
@@ -11340,25 +11497,25 @@ var init_observations = __esm({
|
|
|
11340
11497
|
)).filter((value) => value !== void 0);
|
|
11341
11498
|
}
|
|
11342
11499
|
sessionDirectory(sessionId) {
|
|
11343
|
-
return
|
|
11500
|
+
return path10__default.default.join(this.sessionsDirectory, encodePathSegment(sessionId));
|
|
11344
11501
|
}
|
|
11345
11502
|
sessionManifestPath(sessionId) {
|
|
11346
|
-
return
|
|
11503
|
+
return path10__default.default.join(this.sessionDirectory(sessionId), "session.json");
|
|
11347
11504
|
}
|
|
11348
11505
|
sessionEventsDirectory(sessionId) {
|
|
11349
|
-
return
|
|
11506
|
+
return path10__default.default.join(this.sessionDirectory(sessionId), "events");
|
|
11350
11507
|
}
|
|
11351
11508
|
sessionArtifactsDirectory(sessionId) {
|
|
11352
|
-
return
|
|
11509
|
+
return path10__default.default.join(this.sessionDirectory(sessionId), "artifacts");
|
|
11353
11510
|
}
|
|
11354
11511
|
sessionArtifactPath(sessionId, artifactId) {
|
|
11355
|
-
return
|
|
11512
|
+
return path10__default.default.join(
|
|
11356
11513
|
this.sessionArtifactsDirectory(sessionId),
|
|
11357
11514
|
`${encodePathSegment(artifactId)}.json`
|
|
11358
11515
|
);
|
|
11359
11516
|
}
|
|
11360
11517
|
sessionLockPath(sessionId) {
|
|
11361
|
-
return
|
|
11518
|
+
return path10__default.default.join(this.sessionDirectory(sessionId), ".lock");
|
|
11362
11519
|
}
|
|
11363
11520
|
async reconcileSessionManifest(sessionId) {
|
|
11364
11521
|
const session = await this.getSession(sessionId);
|
|
@@ -11386,14 +11543,14 @@ var init_observations = __esm({
|
|
|
11386
11543
|
Promise.all(
|
|
11387
11544
|
eventFiles.map(
|
|
11388
11545
|
(fileName) => readJsonFile(
|
|
11389
|
-
|
|
11546
|
+
path10__default.default.join(this.sessionEventsDirectory(sessionId), fileName)
|
|
11390
11547
|
)
|
|
11391
11548
|
)
|
|
11392
11549
|
),
|
|
11393
11550
|
Promise.all(
|
|
11394
11551
|
artifactFiles.map(
|
|
11395
11552
|
(fileName) => readJsonFile(
|
|
11396
|
-
|
|
11553
|
+
path10__default.default.join(this.sessionArtifactsDirectory(sessionId), fileName)
|
|
11397
11554
|
)
|
|
11398
11555
|
)
|
|
11399
11556
|
)
|
|
@@ -11440,7 +11597,7 @@ var init_traces2 = __esm({
|
|
|
11440
11597
|
constructor(rootPath, artifacts) {
|
|
11441
11598
|
this.rootPath = rootPath;
|
|
11442
11599
|
this.artifacts = artifacts;
|
|
11443
|
-
this.runsDirectory =
|
|
11600
|
+
this.runsDirectory = path10__default.default.join(this.rootPath, "traces", "runs");
|
|
11444
11601
|
}
|
|
11445
11602
|
runsDirectory;
|
|
11446
11603
|
async initialize() {
|
|
@@ -11517,7 +11674,7 @@ var init_traces2 = __esm({
|
|
|
11517
11674
|
...input.error === void 0 ? {} : { error: input.error }
|
|
11518
11675
|
};
|
|
11519
11676
|
await writeJsonFileExclusive(
|
|
11520
|
-
|
|
11677
|
+
path10__default.default.join(this.runEntriesDirectory(runId), sequenceFileName(sequence)),
|
|
11521
11678
|
entry
|
|
11522
11679
|
);
|
|
11523
11680
|
await writeJsonFileAtomic(this.runManifestPath(runId), {
|
|
@@ -11536,7 +11693,7 @@ var init_traces2 = __esm({
|
|
|
11536
11693
|
const files = await listJsonFiles(entriesDirectory);
|
|
11537
11694
|
return Promise.all(
|
|
11538
11695
|
files.map(
|
|
11539
|
-
(fileName) => readJsonFile(
|
|
11696
|
+
(fileName) => readJsonFile(path10__default.default.join(entriesDirectory, fileName))
|
|
11540
11697
|
)
|
|
11541
11698
|
);
|
|
11542
11699
|
}
|
|
@@ -11582,16 +11739,16 @@ var init_traces2 = __esm({
|
|
|
11582
11739
|
return { trace, artifacts };
|
|
11583
11740
|
}
|
|
11584
11741
|
runDirectory(runId) {
|
|
11585
|
-
return
|
|
11742
|
+
return path10__default.default.join(this.runsDirectory, encodeURIComponent(runId));
|
|
11586
11743
|
}
|
|
11587
11744
|
runEntriesDirectory(runId) {
|
|
11588
|
-
return
|
|
11745
|
+
return path10__default.default.join(this.runDirectory(runId), "entries");
|
|
11589
11746
|
}
|
|
11590
11747
|
runManifestPath(runId) {
|
|
11591
|
-
return
|
|
11748
|
+
return path10__default.default.join(this.runDirectory(runId), "manifest.json");
|
|
11592
11749
|
}
|
|
11593
11750
|
runWriteLockPath(runId) {
|
|
11594
|
-
return
|
|
11751
|
+
return path10__default.default.join(this.runDirectory(runId), ".append.lock");
|
|
11595
11752
|
}
|
|
11596
11753
|
};
|
|
11597
11754
|
}
|
|
@@ -11600,7 +11757,7 @@ function normalizeWorkspaceId(workspace) {
|
|
|
11600
11757
|
return encodePathSegment(workspace);
|
|
11601
11758
|
}
|
|
11602
11759
|
function resolveFilesystemWorkspacePath(input) {
|
|
11603
|
-
return
|
|
11760
|
+
return path10__default.default.join(
|
|
11604
11761
|
input.rootDir,
|
|
11605
11762
|
".opensteer",
|
|
11606
11763
|
"workspaces",
|
|
@@ -11609,18 +11766,18 @@ function resolveFilesystemWorkspacePath(input) {
|
|
|
11609
11766
|
}
|
|
11610
11767
|
async function createFilesystemOpensteerWorkspace(options) {
|
|
11611
11768
|
await ensureDirectory(options.rootPath);
|
|
11612
|
-
const manifestPath =
|
|
11613
|
-
const browserPath =
|
|
11614
|
-
const browserManifestPath =
|
|
11615
|
-
const browserUserDataDir =
|
|
11616
|
-
const livePath =
|
|
11617
|
-
const liveLocalPath =
|
|
11618
|
-
const liveCloudPath =
|
|
11619
|
-
const artifactsPath =
|
|
11620
|
-
const tracesPath =
|
|
11621
|
-
const observationsPath =
|
|
11622
|
-
const registryPath =
|
|
11623
|
-
const lockPath =
|
|
11769
|
+
const manifestPath = path10__default.default.join(options.rootPath, "workspace.json");
|
|
11770
|
+
const browserPath = path10__default.default.join(options.rootPath, "browser");
|
|
11771
|
+
const browserManifestPath = path10__default.default.join(browserPath, "manifest.json");
|
|
11772
|
+
const browserUserDataDir = path10__default.default.join(browserPath, "user-data");
|
|
11773
|
+
const livePath = path10__default.default.join(options.rootPath, "live");
|
|
11774
|
+
const liveLocalPath = path10__default.default.join(livePath, "local.json");
|
|
11775
|
+
const liveCloudPath = path10__default.default.join(livePath, "cloud.json");
|
|
11776
|
+
const artifactsPath = path10__default.default.join(options.rootPath, "artifacts");
|
|
11777
|
+
const tracesPath = path10__default.default.join(options.rootPath, "traces");
|
|
11778
|
+
const observationsPath = path10__default.default.join(options.rootPath, "observations");
|
|
11779
|
+
const registryPath = path10__default.default.join(options.rootPath, "registry");
|
|
11780
|
+
const lockPath = path10__default.default.join(options.rootPath, ".lock");
|
|
11624
11781
|
let manifest;
|
|
11625
11782
|
if (await pathExists(manifestPath)) {
|
|
11626
11783
|
manifest = await readJsonFile(manifestPath);
|
|
@@ -11760,7 +11917,7 @@ var init_filesystem2 = __esm({
|
|
|
11760
11917
|
}
|
|
11761
11918
|
});
|
|
11762
11919
|
function resolveLiveSessionRecordPath(rootPath, provider) {
|
|
11763
|
-
return
|
|
11920
|
+
return path10__default.default.join(rootPath, "live", provider === "local" ? "local.json" : "cloud.json");
|
|
11764
11921
|
}
|
|
11765
11922
|
async function readPersistedSessionRecord(rootPath, provider) {
|
|
11766
11923
|
const sessionPath = resolveLiveSessionRecordPath(rootPath, provider);
|
|
@@ -11804,6 +11961,585 @@ var init_live_session = __esm({
|
|
|
11804
11961
|
OPENSTEER_LIVE_SESSION_VERSION = 1;
|
|
11805
11962
|
}
|
|
11806
11963
|
});
|
|
11964
|
+
function resolveOpensteerStateDir() {
|
|
11965
|
+
const explicit = process.env.OPENSTEER_HOME?.trim();
|
|
11966
|
+
if (explicit) {
|
|
11967
|
+
return path10__default.default.resolve(explicit);
|
|
11968
|
+
}
|
|
11969
|
+
if (process.platform === "win32") {
|
|
11970
|
+
return path10__default.default.join(
|
|
11971
|
+
process.env.LOCALAPPDATA ?? path10__default.default.join(os.homedir(), "AppData", "Local"),
|
|
11972
|
+
"Opensteer"
|
|
11973
|
+
);
|
|
11974
|
+
}
|
|
11975
|
+
if (process.platform === "darwin") {
|
|
11976
|
+
return path10__default.default.join(os.homedir(), "Library", "Application Support", "Opensteer");
|
|
11977
|
+
}
|
|
11978
|
+
return path10__default.default.join(
|
|
11979
|
+
process.env.XDG_STATE_HOME ?? path10__default.default.join(os.homedir(), ".local", "state"),
|
|
11980
|
+
"opensteer"
|
|
11981
|
+
);
|
|
11982
|
+
}
|
|
11983
|
+
function resolveLocalViewRootDir() {
|
|
11984
|
+
return path10__default.default.join(resolveOpensteerStateDir(), "local-view");
|
|
11985
|
+
}
|
|
11986
|
+
function resolveLocalViewPreferencesPath() {
|
|
11987
|
+
return path10__default.default.join(resolveLocalViewRootDir(), "preferences.json");
|
|
11988
|
+
}
|
|
11989
|
+
function resolveLocalViewServiceDir() {
|
|
11990
|
+
return path10__default.default.join(resolveLocalViewRootDir(), "service");
|
|
11991
|
+
}
|
|
11992
|
+
function resolveLocalViewSessionsDir() {
|
|
11993
|
+
return path10__default.default.join(resolveLocalViewRootDir(), "sessions");
|
|
11994
|
+
}
|
|
11995
|
+
function resolveLocalViewServiceLockDir() {
|
|
11996
|
+
return path10__default.default.join(resolveLocalViewServiceDir(), "startup.lock");
|
|
11997
|
+
}
|
|
11998
|
+
function resolveLocalViewServiceStatePath() {
|
|
11999
|
+
return path10__default.default.join(resolveLocalViewServiceDir(), "state.json");
|
|
12000
|
+
}
|
|
12001
|
+
var init_runtime_dir = __esm({
|
|
12002
|
+
"src/local-view/runtime-dir.ts"() {
|
|
12003
|
+
}
|
|
12004
|
+
});
|
|
12005
|
+
|
|
12006
|
+
// src/local-view/preferences.ts
|
|
12007
|
+
async function resolveLocalViewMode() {
|
|
12008
|
+
const preferences = await readLocalViewPreferences();
|
|
12009
|
+
return preferences?.mode ?? "auto";
|
|
12010
|
+
}
|
|
12011
|
+
async function setLocalViewMode(mode) {
|
|
12012
|
+
return writeLocalViewPreferences(mode);
|
|
12013
|
+
}
|
|
12014
|
+
async function readLocalViewPreferences() {
|
|
12015
|
+
const preferencesPath = resolveLocalViewPreferencesPath();
|
|
12016
|
+
if (!await pathExists(preferencesPath)) {
|
|
12017
|
+
return void 0;
|
|
12018
|
+
}
|
|
12019
|
+
const parsed = await readJsonFile(preferencesPath);
|
|
12020
|
+
return isPersistedLocalViewPreferences(parsed) ? parsed : void 0;
|
|
12021
|
+
}
|
|
12022
|
+
async function writeLocalViewPreferences(mode) {
|
|
12023
|
+
const preferences = {
|
|
12024
|
+
layout: OPENSTEER_LOCAL_VIEW_PREFERENCES_LAYOUT,
|
|
12025
|
+
version: OPENSTEER_LOCAL_VIEW_PREFERENCES_VERSION,
|
|
12026
|
+
mode,
|
|
12027
|
+
updatedAt: Date.now()
|
|
12028
|
+
};
|
|
12029
|
+
await writeJsonFileAtomic(resolveLocalViewPreferencesPath(), preferences);
|
|
12030
|
+
return preferences;
|
|
12031
|
+
}
|
|
12032
|
+
function isPersistedLocalViewPreferences(value) {
|
|
12033
|
+
return value?.layout === OPENSTEER_LOCAL_VIEW_PREFERENCES_LAYOUT && value.version === OPENSTEER_LOCAL_VIEW_PREFERENCES_VERSION && (value.mode === "auto" || value.mode === "manual") && typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt);
|
|
12034
|
+
}
|
|
12035
|
+
var OPENSTEER_LOCAL_VIEW_PREFERENCES_LAYOUT, OPENSTEER_LOCAL_VIEW_PREFERENCES_VERSION;
|
|
12036
|
+
var init_preferences = __esm({
|
|
12037
|
+
"src/local-view/preferences.ts"() {
|
|
12038
|
+
init_filesystem2();
|
|
12039
|
+
init_runtime_dir();
|
|
12040
|
+
OPENSTEER_LOCAL_VIEW_PREFERENCES_LAYOUT = "opensteer-local-view-preferences";
|
|
12041
|
+
OPENSTEER_LOCAL_VIEW_PREFERENCES_VERSION = 1;
|
|
12042
|
+
}
|
|
12043
|
+
});
|
|
12044
|
+
async function acquireDirLock(lockDirPath) {
|
|
12045
|
+
while (true) {
|
|
12046
|
+
const releaseLock = await tryAcquireDirLock(lockDirPath);
|
|
12047
|
+
if (releaseLock) {
|
|
12048
|
+
return releaseLock;
|
|
12049
|
+
}
|
|
12050
|
+
await sleep(LOCK_RETRY_DELAY_MS);
|
|
12051
|
+
}
|
|
12052
|
+
}
|
|
12053
|
+
async function tryAcquireDirLock(lockDirPath) {
|
|
12054
|
+
await promises.mkdir(path10.dirname(lockDirPath), { recursive: true });
|
|
12055
|
+
while (true) {
|
|
12056
|
+
const tempLockDirPath = `${lockDirPath}-${String(process.pid)}-${String(CURRENT_PROCESS_OWNER.processStartedAtMs)}-${crypto.randomUUID()}`;
|
|
12057
|
+
try {
|
|
12058
|
+
await promises.mkdir(tempLockDirPath);
|
|
12059
|
+
await writeLockOwner(tempLockDirPath, CURRENT_PROCESS_OWNER);
|
|
12060
|
+
try {
|
|
12061
|
+
await promises.rename(tempLockDirPath, lockDirPath);
|
|
12062
|
+
break;
|
|
12063
|
+
} catch (error) {
|
|
12064
|
+
if (!wasDirPublishedByAnotherProcess(error, lockDirPath)) {
|
|
12065
|
+
throw error;
|
|
12066
|
+
}
|
|
12067
|
+
}
|
|
12068
|
+
} finally {
|
|
12069
|
+
await promises.rm(tempLockDirPath, {
|
|
12070
|
+
recursive: true,
|
|
12071
|
+
force: true
|
|
12072
|
+
}).catch(() => void 0);
|
|
12073
|
+
}
|
|
12074
|
+
const owner = await readLockOwner(lockDirPath);
|
|
12075
|
+
if ((!owner || await getProcessLiveness(owner) === "dead") && await tryReclaimStaleLock(lockDirPath, owner)) {
|
|
12076
|
+
continue;
|
|
12077
|
+
}
|
|
12078
|
+
return null;
|
|
12079
|
+
}
|
|
12080
|
+
return async () => {
|
|
12081
|
+
await promises.rm(lockDirPath, {
|
|
12082
|
+
recursive: true,
|
|
12083
|
+
force: true
|
|
12084
|
+
}).catch(() => void 0);
|
|
12085
|
+
};
|
|
12086
|
+
}
|
|
12087
|
+
function getErrorCode(error) {
|
|
12088
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
12089
|
+
}
|
|
12090
|
+
function wasDirPublishedByAnotherProcess(error, targetDirPath) {
|
|
12091
|
+
const code = getErrorCode(error);
|
|
12092
|
+
return fs.existsSync(targetDirPath) && (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM");
|
|
12093
|
+
}
|
|
12094
|
+
async function writeLockOwner(lockDirPath, owner) {
|
|
12095
|
+
await promises.writeFile(path10.join(lockDirPath, LOCK_OWNER_FILE), JSON.stringify(owner));
|
|
12096
|
+
}
|
|
12097
|
+
async function readLockOwner(lockDirPath) {
|
|
12098
|
+
return readLockParticipant(path10.join(lockDirPath, LOCK_OWNER_FILE));
|
|
12099
|
+
}
|
|
12100
|
+
async function readLockParticipant(filePath) {
|
|
12101
|
+
return (await readLockParticipantRecord(filePath)).owner;
|
|
12102
|
+
}
|
|
12103
|
+
async function readLockParticipantRecord(filePath) {
|
|
12104
|
+
try {
|
|
12105
|
+
const raw = await promises.readFile(filePath, "utf8");
|
|
12106
|
+
return {
|
|
12107
|
+
exists: true,
|
|
12108
|
+
owner: parseProcessOwner(JSON.parse(raw))
|
|
12109
|
+
};
|
|
12110
|
+
} catch (error) {
|
|
12111
|
+
return {
|
|
12112
|
+
exists: getErrorCode(error) !== "ENOENT",
|
|
12113
|
+
owner: null
|
|
12114
|
+
};
|
|
12115
|
+
}
|
|
12116
|
+
}
|
|
12117
|
+
async function readLockReclaimerRecord(lockDirPath) {
|
|
12118
|
+
return readLockParticipantRecord(path10.join(buildLockReclaimerDirPath(lockDirPath), LOCK_OWNER_FILE));
|
|
12119
|
+
}
|
|
12120
|
+
async function tryReclaimStaleLock(lockDirPath, expectedOwner) {
|
|
12121
|
+
if (!await tryAcquireLockReclaimer(lockDirPath)) {
|
|
12122
|
+
return false;
|
|
12123
|
+
}
|
|
12124
|
+
let reclaimed = false;
|
|
12125
|
+
try {
|
|
12126
|
+
const owner = await readLockOwner(lockDirPath);
|
|
12127
|
+
if (!processOwnersEqual(owner, expectedOwner)) {
|
|
12128
|
+
return false;
|
|
12129
|
+
}
|
|
12130
|
+
if (owner && await getProcessLiveness(owner) !== "dead") {
|
|
12131
|
+
return false;
|
|
12132
|
+
}
|
|
12133
|
+
await promises.rm(lockDirPath, {
|
|
12134
|
+
recursive: true,
|
|
12135
|
+
force: true
|
|
12136
|
+
}).catch(() => void 0);
|
|
12137
|
+
reclaimed = !fs.existsSync(lockDirPath);
|
|
12138
|
+
return reclaimed;
|
|
12139
|
+
} finally {
|
|
12140
|
+
if (!reclaimed) {
|
|
12141
|
+
await promises.rm(buildLockReclaimerDirPath(lockDirPath), {
|
|
12142
|
+
recursive: true,
|
|
12143
|
+
force: true
|
|
12144
|
+
}).catch(() => void 0);
|
|
12145
|
+
}
|
|
12146
|
+
}
|
|
12147
|
+
}
|
|
12148
|
+
async function tryAcquireLockReclaimer(lockDirPath) {
|
|
12149
|
+
const reclaimerDirPath = buildLockReclaimerDirPath(lockDirPath);
|
|
12150
|
+
while (true) {
|
|
12151
|
+
const tempReclaimerDirPath = `${reclaimerDirPath}-${String(process.pid)}-${String(CURRENT_PROCESS_OWNER.processStartedAtMs)}-${crypto.randomUUID()}`;
|
|
12152
|
+
try {
|
|
12153
|
+
await promises.mkdir(tempReclaimerDirPath);
|
|
12154
|
+
await writeLockOwner(tempReclaimerDirPath, CURRENT_PROCESS_OWNER);
|
|
12155
|
+
try {
|
|
12156
|
+
await promises.rename(tempReclaimerDirPath, reclaimerDirPath);
|
|
12157
|
+
return true;
|
|
12158
|
+
} catch (error) {
|
|
12159
|
+
if (getErrorCode(error) === "ENOENT") {
|
|
12160
|
+
return false;
|
|
12161
|
+
}
|
|
12162
|
+
if (!wasDirPublishedByAnotherProcess(error, reclaimerDirPath)) {
|
|
12163
|
+
throw error;
|
|
12164
|
+
}
|
|
12165
|
+
}
|
|
12166
|
+
} catch (error) {
|
|
12167
|
+
if (getErrorCode(error) === "ENOENT") {
|
|
12168
|
+
return false;
|
|
12169
|
+
}
|
|
12170
|
+
throw error;
|
|
12171
|
+
} finally {
|
|
12172
|
+
await promises.rm(tempReclaimerDirPath, {
|
|
12173
|
+
recursive: true,
|
|
12174
|
+
force: true
|
|
12175
|
+
}).catch(() => void 0);
|
|
12176
|
+
}
|
|
12177
|
+
const reclaimerRecord = await readLockReclaimerRecord(lockDirPath);
|
|
12178
|
+
if (!reclaimerRecord.exists || !reclaimerRecord.owner) {
|
|
12179
|
+
return false;
|
|
12180
|
+
}
|
|
12181
|
+
if (await getProcessLiveness(reclaimerRecord.owner) !== "dead") {
|
|
12182
|
+
return false;
|
|
12183
|
+
}
|
|
12184
|
+
await promises.rm(reclaimerDirPath, {
|
|
12185
|
+
recursive: true,
|
|
12186
|
+
force: true
|
|
12187
|
+
}).catch(() => void 0);
|
|
12188
|
+
}
|
|
12189
|
+
}
|
|
12190
|
+
function buildLockReclaimerDirPath(lockDirPath) {
|
|
12191
|
+
return path10.join(lockDirPath, LOCK_RECLAIMER_DIR);
|
|
12192
|
+
}
|
|
12193
|
+
async function sleep(ms) {
|
|
12194
|
+
await new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
12195
|
+
}
|
|
12196
|
+
var LOCK_OWNER_FILE, LOCK_RECLAIMER_DIR, LOCK_RETRY_DELAY_MS;
|
|
12197
|
+
var init_dir_lock = __esm({
|
|
12198
|
+
"src/local-browser/dir-lock.ts"() {
|
|
12199
|
+
init_process_owner();
|
|
12200
|
+
LOCK_OWNER_FILE = "owner.json";
|
|
12201
|
+
LOCK_RECLAIMER_DIR = "reclaimer";
|
|
12202
|
+
LOCK_RETRY_DELAY_MS = 50;
|
|
12203
|
+
}
|
|
12204
|
+
});
|
|
12205
|
+
async function readLocalViewServiceState() {
|
|
12206
|
+
const statePath = resolveLocalViewServiceStatePath();
|
|
12207
|
+
if (!await pathExists(statePath)) {
|
|
12208
|
+
return void 0;
|
|
12209
|
+
}
|
|
12210
|
+
const parsed = await readJsonFile(statePath);
|
|
12211
|
+
if (!isPersistedLocalViewServiceState(parsed)) {
|
|
12212
|
+
return void 0;
|
|
12213
|
+
}
|
|
12214
|
+
return parsed;
|
|
12215
|
+
}
|
|
12216
|
+
async function writeLocalViewServiceState(state) {
|
|
12217
|
+
await writeJsonFileAtomic(resolveLocalViewServiceStatePath(), state);
|
|
12218
|
+
}
|
|
12219
|
+
async function clearLocalViewServiceState(match = void 0) {
|
|
12220
|
+
if (match !== void 0) {
|
|
12221
|
+
const current = await readLocalViewServiceState();
|
|
12222
|
+
if (current === void 0 || current.pid !== match.pid || current.token !== match.token) {
|
|
12223
|
+
return;
|
|
12224
|
+
}
|
|
12225
|
+
}
|
|
12226
|
+
await promises.rm(resolveLocalViewServiceStatePath(), { force: true });
|
|
12227
|
+
}
|
|
12228
|
+
async function isLocalViewServiceStateLive(state) {
|
|
12229
|
+
return await getLocalViewServiceStateLiveness(state) !== "dead";
|
|
12230
|
+
}
|
|
12231
|
+
async function getLocalViewServiceStateLiveness(state) {
|
|
12232
|
+
if (state === void 0) {
|
|
12233
|
+
return "dead";
|
|
12234
|
+
}
|
|
12235
|
+
return getProcessLiveness({
|
|
12236
|
+
pid: state.pid,
|
|
12237
|
+
processStartedAtMs: state.processStartedAtMs
|
|
12238
|
+
});
|
|
12239
|
+
}
|
|
12240
|
+
function isPersistedLocalViewServiceState(value) {
|
|
12241
|
+
return value?.layout === OPENSTEER_LOCAL_VIEW_SERVICE_LAYOUT && value.version === OPENSTEER_LOCAL_VIEW_SERVICE_VERSION && typeof value.pid === "number" && Number.isFinite(value.pid) && typeof value.processStartedAtMs === "number" && Number.isFinite(value.processStartedAtMs) && typeof value.startedAt === "number" && Number.isFinite(value.startedAt) && typeof value.port === "number" && Number.isFinite(value.port) && typeof value.token === "string" && value.token.length > 0 && typeof value.url === "string" && value.url.length > 0;
|
|
12242
|
+
}
|
|
12243
|
+
var OPENSTEER_LOCAL_VIEW_SERVICE_LAYOUT, OPENSTEER_LOCAL_VIEW_SERVICE_VERSION;
|
|
12244
|
+
var init_service_state = __esm({
|
|
12245
|
+
"src/local-view/service-state.ts"() {
|
|
12246
|
+
init_filesystem2();
|
|
12247
|
+
init_process_owner();
|
|
12248
|
+
init_runtime_dir();
|
|
12249
|
+
OPENSTEER_LOCAL_VIEW_SERVICE_LAYOUT = "opensteer-local-view-service";
|
|
12250
|
+
OPENSTEER_LOCAL_VIEW_SERVICE_VERSION = 3;
|
|
12251
|
+
}
|
|
12252
|
+
});
|
|
12253
|
+
async function ensureLocalViewServiceRunning() {
|
|
12254
|
+
const current = await readReachableLocalViewServiceState();
|
|
12255
|
+
if (current !== void 0) {
|
|
12256
|
+
return current;
|
|
12257
|
+
}
|
|
12258
|
+
const releaseLock = await acquireDirLock(resolveLocalViewServiceLockDir());
|
|
12259
|
+
try {
|
|
12260
|
+
const lockedState = await readReachableLocalViewServiceState();
|
|
12261
|
+
if (lockedState !== void 0) {
|
|
12262
|
+
return lockedState;
|
|
12263
|
+
}
|
|
12264
|
+
await spawnLocalViewService();
|
|
12265
|
+
const started = await waitForLocalViewService();
|
|
12266
|
+
if (!started) {
|
|
12267
|
+
throw new Error("Timed out while starting the local view service.");
|
|
12268
|
+
}
|
|
12269
|
+
return started;
|
|
12270
|
+
} finally {
|
|
12271
|
+
await releaseLock();
|
|
12272
|
+
}
|
|
12273
|
+
}
|
|
12274
|
+
async function stopLocalViewService() {
|
|
12275
|
+
const state = await readLocalViewServiceState();
|
|
12276
|
+
if (state === void 0 || !await isLocalViewServiceStateLive(state)) {
|
|
12277
|
+
await clearLocalViewServiceState(
|
|
12278
|
+
state === void 0 ? void 0 : { pid: state.pid, token: state.token }
|
|
12279
|
+
);
|
|
12280
|
+
return false;
|
|
12281
|
+
}
|
|
12282
|
+
const liveState = state;
|
|
12283
|
+
const stopRequested = await requestLocalViewServiceStop(liveState).catch(() => false);
|
|
12284
|
+
if (!stopRequested && await getLocalViewServiceStateLiveness(liveState) !== "dead") {
|
|
12285
|
+
process.kill(liveState.pid);
|
|
12286
|
+
}
|
|
12287
|
+
await waitForLocalViewServiceStop(liveState);
|
|
12288
|
+
await clearLocalViewServiceState({ pid: liveState.pid, token: liveState.token });
|
|
12289
|
+
return true;
|
|
12290
|
+
}
|
|
12291
|
+
function buildLocalViewSessionUrl(args) {
|
|
12292
|
+
if (!args.sessionId) {
|
|
12293
|
+
return args.baseUrl;
|
|
12294
|
+
}
|
|
12295
|
+
return `${args.baseUrl}#session=${encodeURIComponent(args.sessionId)}`;
|
|
12296
|
+
}
|
|
12297
|
+
async function requestLocalViewServiceStop(state) {
|
|
12298
|
+
const response = await fetch(new URL("/api/service/stop", state.url), {
|
|
12299
|
+
method: "POST",
|
|
12300
|
+
headers: {
|
|
12301
|
+
"x-opensteer-local-token": state.token
|
|
12302
|
+
}
|
|
12303
|
+
});
|
|
12304
|
+
return response.ok;
|
|
12305
|
+
}
|
|
12306
|
+
async function waitForLocalViewServiceStop(state) {
|
|
12307
|
+
const deadline = Date.now() + LOCAL_VIEW_STOP_TIMEOUT_MS;
|
|
12308
|
+
while (Date.now() < deadline) {
|
|
12309
|
+
if (await getLocalViewServiceStateLiveness(state) === "dead") {
|
|
12310
|
+
return;
|
|
12311
|
+
}
|
|
12312
|
+
await delay(LOCAL_VIEW_STARTUP_POLL_MS);
|
|
12313
|
+
}
|
|
12314
|
+
throw new Error("Timed out while stopping the local view service.");
|
|
12315
|
+
}
|
|
12316
|
+
function spawnLocalViewService() {
|
|
12317
|
+
const command = resolveLocalViewSpawnCommand();
|
|
12318
|
+
const child = child_process.spawn(command.executable, command.args, {
|
|
12319
|
+
cwd: process.cwd(),
|
|
12320
|
+
env: {
|
|
12321
|
+
...process.env,
|
|
12322
|
+
...command.env ?? {},
|
|
12323
|
+
OPENSTEER_LOCAL_VIEW_BOOT_TOKEN: process.env.OPENSTEER_LOCAL_VIEW_BOOT_TOKEN ?? crypto.randomBytes(24).toString("hex")
|
|
12324
|
+
},
|
|
12325
|
+
detached: process.platform !== "win32",
|
|
12326
|
+
stdio: "ignore"
|
|
12327
|
+
});
|
|
12328
|
+
child.unref();
|
|
12329
|
+
}
|
|
12330
|
+
async function waitForLocalViewService() {
|
|
12331
|
+
const deadline = Date.now() + LOCAL_VIEW_STARTUP_TIMEOUT_MS;
|
|
12332
|
+
while (Date.now() < deadline) {
|
|
12333
|
+
const state = await readReachableLocalViewServiceState();
|
|
12334
|
+
if (state !== void 0) {
|
|
12335
|
+
return state;
|
|
12336
|
+
}
|
|
12337
|
+
await delay(LOCAL_VIEW_STARTUP_POLL_MS);
|
|
12338
|
+
}
|
|
12339
|
+
return void 0;
|
|
12340
|
+
}
|
|
12341
|
+
async function readReachableLocalViewServiceState() {
|
|
12342
|
+
const state = await readLocalViewServiceState();
|
|
12343
|
+
if (state === void 0 || !await isLocalViewServiceStateLive(state)) {
|
|
12344
|
+
return void 0;
|
|
12345
|
+
}
|
|
12346
|
+
return await isLocalViewServiceReachable(state.url, state.token) ? state : void 0;
|
|
12347
|
+
}
|
|
12348
|
+
async function isLocalViewServiceReachable(baseUrl, token) {
|
|
12349
|
+
try {
|
|
12350
|
+
const response = await fetch(new URL("/api/health", baseUrl), {
|
|
12351
|
+
headers: {
|
|
12352
|
+
"x-opensteer-local-token": token
|
|
12353
|
+
}
|
|
12354
|
+
});
|
|
12355
|
+
return response.ok;
|
|
12356
|
+
} catch {
|
|
12357
|
+
return false;
|
|
12358
|
+
}
|
|
12359
|
+
}
|
|
12360
|
+
function resolveLocalViewSpawnCommand() {
|
|
12361
|
+
const moduleDir = path10__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bin.cjs', document.baseURI).href))));
|
|
12362
|
+
const distServicePath = findExistingPath([
|
|
12363
|
+
path10__default.default.join(moduleDir, "local-view", "serve-entry.js"),
|
|
12364
|
+
path10__default.default.join(moduleDir, "serve-entry.js"),
|
|
12365
|
+
path10__default.default.join(moduleDir, "..", "local-view", "serve-entry.js")
|
|
12366
|
+
]);
|
|
12367
|
+
if (distServicePath) {
|
|
12368
|
+
return {
|
|
12369
|
+
executable: process.execPath,
|
|
12370
|
+
args: [distServicePath]
|
|
12371
|
+
};
|
|
12372
|
+
}
|
|
12373
|
+
const distCliPath = findExistingPath([
|
|
12374
|
+
path10__default.default.join(moduleDir, "cli", "bin.js"),
|
|
12375
|
+
path10__default.default.join(moduleDir, "..", "cli", "bin.js")
|
|
12376
|
+
]);
|
|
12377
|
+
if (distCliPath) {
|
|
12378
|
+
return {
|
|
12379
|
+
executable: process.execPath,
|
|
12380
|
+
args: [distCliPath, "view", "serve"]
|
|
12381
|
+
};
|
|
12382
|
+
}
|
|
12383
|
+
const srcServicePath = findExistingPath([
|
|
12384
|
+
path10__default.default.join(moduleDir, "serve-entry.ts"),
|
|
12385
|
+
path10__default.default.join(moduleDir, "..", "local-view", "serve-entry.ts"),
|
|
12386
|
+
path10__default.default.join(moduleDir, "..", "src", "local-view", "serve-entry.ts")
|
|
12387
|
+
]);
|
|
12388
|
+
if (srcServicePath) {
|
|
12389
|
+
const require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bin.cjs', document.baseURI).href)));
|
|
12390
|
+
const tsxLoaderPath = require2.resolve("tsx");
|
|
12391
|
+
const tsconfigPath = findNearestTsconfig(path10__default.default.resolve(moduleDir, "..", "..", ".."));
|
|
12392
|
+
return {
|
|
12393
|
+
executable: process.execPath,
|
|
12394
|
+
args: ["--import", tsxLoaderPath, srcServicePath],
|
|
12395
|
+
...tsconfigPath ? { env: { TSX_TSCONFIG_PATH: tsconfigPath } } : {}
|
|
12396
|
+
};
|
|
12397
|
+
}
|
|
12398
|
+
const srcCliPath = findExistingPath([
|
|
12399
|
+
path10__default.default.join(moduleDir, "..", "cli", "bin.ts"),
|
|
12400
|
+
path10__default.default.join(moduleDir, "..", "src", "cli", "bin.ts")
|
|
12401
|
+
]);
|
|
12402
|
+
if (srcCliPath) {
|
|
12403
|
+
const require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bin.cjs', document.baseURI).href)));
|
|
12404
|
+
const tsxLoaderPath = require2.resolve("tsx");
|
|
12405
|
+
const tsconfigPath = findNearestTsconfig(path10__default.default.resolve(moduleDir, "..", "..", ".."));
|
|
12406
|
+
return {
|
|
12407
|
+
executable: process.execPath,
|
|
12408
|
+
args: ["--import", tsxLoaderPath, srcCliPath, "view", "serve"],
|
|
12409
|
+
...tsconfigPath ? { env: { TSX_TSCONFIG_PATH: tsconfigPath } } : {}
|
|
12410
|
+
};
|
|
12411
|
+
}
|
|
12412
|
+
throw new Error(`Could not resolve the Opensteer CLI entrypoint from ${moduleDir}.`);
|
|
12413
|
+
}
|
|
12414
|
+
function findExistingPath(candidates) {
|
|
12415
|
+
return candidates.find((candidate) => fs.existsSync(candidate));
|
|
12416
|
+
}
|
|
12417
|
+
function findNearestTsconfig(startDir) {
|
|
12418
|
+
let currentDir = startDir;
|
|
12419
|
+
while (true) {
|
|
12420
|
+
const candidate = path10__default.default.join(currentDir, "tsconfig.json");
|
|
12421
|
+
if (fs.existsSync(candidate)) {
|
|
12422
|
+
return candidate;
|
|
12423
|
+
}
|
|
12424
|
+
const parentDir = path10__default.default.dirname(currentDir);
|
|
12425
|
+
if (parentDir === currentDir) {
|
|
12426
|
+
return void 0;
|
|
12427
|
+
}
|
|
12428
|
+
currentDir = parentDir;
|
|
12429
|
+
}
|
|
12430
|
+
}
|
|
12431
|
+
function delay(ms) {
|
|
12432
|
+
return new Promise((resolve4) => {
|
|
12433
|
+
setTimeout(resolve4, ms);
|
|
12434
|
+
});
|
|
12435
|
+
}
|
|
12436
|
+
var LOCAL_VIEW_STARTUP_TIMEOUT_MS, LOCAL_VIEW_STARTUP_POLL_MS, LOCAL_VIEW_STOP_TIMEOUT_MS;
|
|
12437
|
+
var init_service = __esm({
|
|
12438
|
+
"src/local-view/service.ts"() {
|
|
12439
|
+
init_dir_lock();
|
|
12440
|
+
init_service_state();
|
|
12441
|
+
init_runtime_dir();
|
|
12442
|
+
LOCAL_VIEW_STARTUP_TIMEOUT_MS = 1e4;
|
|
12443
|
+
LOCAL_VIEW_STARTUP_POLL_MS = 100;
|
|
12444
|
+
LOCAL_VIEW_STOP_TIMEOUT_MS = 1e4;
|
|
12445
|
+
}
|
|
12446
|
+
});
|
|
12447
|
+
function buildLocalViewSessionId(input) {
|
|
12448
|
+
const hash = crypto.createHash("sha256").update(`${input.rootPath}
|
|
12449
|
+
${String(input.pid)}
|
|
12450
|
+
${String(input.startedAt)}`).digest("hex");
|
|
12451
|
+
return `local_${hash.slice(0, 24)}`;
|
|
12452
|
+
}
|
|
12453
|
+
function createLocalViewSessionManifest(input) {
|
|
12454
|
+
return {
|
|
12455
|
+
layout: OPENSTEER_LOCAL_VIEW_SESSION_LAYOUT,
|
|
12456
|
+
version: OPENSTEER_LOCAL_VIEW_SESSION_VERSION,
|
|
12457
|
+
sessionId: buildLocalViewSessionId({
|
|
12458
|
+
rootPath: input.rootPath,
|
|
12459
|
+
pid: input.live.pid,
|
|
12460
|
+
startedAt: input.live.startedAt
|
|
12461
|
+
}),
|
|
12462
|
+
rootPath: input.rootPath,
|
|
12463
|
+
...input.workspace === void 0 ? {} : { workspace: input.workspace },
|
|
12464
|
+
engine: input.live.engine,
|
|
12465
|
+
ownership: input.ownership,
|
|
12466
|
+
pid: input.live.pid,
|
|
12467
|
+
startedAt: input.live.startedAt,
|
|
12468
|
+
updatedAt: Date.now()
|
|
12469
|
+
};
|
|
12470
|
+
}
|
|
12471
|
+
async function writeLocalViewSessionManifest(manifest) {
|
|
12472
|
+
await ensureDirectory(resolveLocalViewSessionsDir());
|
|
12473
|
+
await writeJsonFileAtomic(resolveLocalViewSessionManifestPath(manifest.sessionId), manifest);
|
|
12474
|
+
}
|
|
12475
|
+
async function deleteLocalViewSessionManifest(sessionId) {
|
|
12476
|
+
await promises.rm(resolveLocalViewSessionManifestPath(sessionId), { force: true }).catch(() => void 0);
|
|
12477
|
+
}
|
|
12478
|
+
async function readLocalViewSessionManifest(sessionId) {
|
|
12479
|
+
const manifestPath = resolveLocalViewSessionManifestPath(sessionId);
|
|
12480
|
+
if (!await pathExists(manifestPath)) {
|
|
12481
|
+
return void 0;
|
|
12482
|
+
}
|
|
12483
|
+
const parsed = await readJsonFile(manifestPath);
|
|
12484
|
+
return isPersistedLocalViewSessionManifest(parsed) ? parsed : void 0;
|
|
12485
|
+
}
|
|
12486
|
+
async function listLocalViewSessionManifests() {
|
|
12487
|
+
const directoryPath = resolveLocalViewSessionsDir();
|
|
12488
|
+
const fileNames = await listJsonFiles(directoryPath);
|
|
12489
|
+
const manifests = await Promise.all(
|
|
12490
|
+
fileNames.map(async (fileName) => {
|
|
12491
|
+
const parsed = await readJsonFile(
|
|
12492
|
+
path10__default.default.join(directoryPath, fileName)
|
|
12493
|
+
).catch(() => void 0);
|
|
12494
|
+
return isPersistedLocalViewSessionManifest(parsed) ? parsed : void 0;
|
|
12495
|
+
})
|
|
12496
|
+
);
|
|
12497
|
+
return manifests.filter((manifest) => manifest !== void 0).sort(
|
|
12498
|
+
(left, right) => left.startedAt - right.startedAt || left.sessionId.localeCompare(right.sessionId)
|
|
12499
|
+
);
|
|
12500
|
+
}
|
|
12501
|
+
function resolveLocalViewSessionManifestPath(sessionId) {
|
|
12502
|
+
return path10__default.default.join(resolveLocalViewSessionsDir(), `${sessionId}.json`);
|
|
12503
|
+
}
|
|
12504
|
+
function isPersistedLocalViewSessionManifest(value) {
|
|
12505
|
+
return value?.layout === OPENSTEER_LOCAL_VIEW_SESSION_LAYOUT && value.version === OPENSTEER_LOCAL_VIEW_SESSION_VERSION && typeof value.sessionId === "string" && value.sessionId.length > 0 && typeof value.rootPath === "string" && value.rootPath.length > 0 && (value.engine === "playwright" || value.engine === "abp") && (value.ownership === "owned" || value.ownership === "attached" || value.ownership === "managed") && typeof value.pid === "number" && Number.isFinite(value.pid) && typeof value.startedAt === "number" && Number.isFinite(value.startedAt) && typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt);
|
|
12506
|
+
}
|
|
12507
|
+
var OPENSTEER_LOCAL_VIEW_SESSION_LAYOUT, OPENSTEER_LOCAL_VIEW_SESSION_VERSION;
|
|
12508
|
+
var init_session_manifest = __esm({
|
|
12509
|
+
"src/local-view/session-manifest.ts"() {
|
|
12510
|
+
init_filesystem2();
|
|
12511
|
+
init_runtime_dir();
|
|
12512
|
+
OPENSTEER_LOCAL_VIEW_SESSION_LAYOUT = "opensteer-local-view-session";
|
|
12513
|
+
OPENSTEER_LOCAL_VIEW_SESSION_VERSION = 1;
|
|
12514
|
+
}
|
|
12515
|
+
});
|
|
12516
|
+
|
|
12517
|
+
// src/local-view/registration.ts
|
|
12518
|
+
async function bestEffortRegisterLocalViewSession(input) {
|
|
12519
|
+
try {
|
|
12520
|
+
const manifest = createLocalViewSessionManifest(input);
|
|
12521
|
+
await writeLocalViewSessionManifest(manifest);
|
|
12522
|
+
if (await resolveLocalViewMode() === "auto") {
|
|
12523
|
+
void ensureLocalViewServiceRunning().catch(() => void 0);
|
|
12524
|
+
}
|
|
12525
|
+
return manifest;
|
|
12526
|
+
} catch {
|
|
12527
|
+
return void 0;
|
|
12528
|
+
}
|
|
12529
|
+
}
|
|
12530
|
+
async function bestEffortUnregisterLocalViewSession(sessionId) {
|
|
12531
|
+
if (!sessionId) {
|
|
12532
|
+
return;
|
|
12533
|
+
}
|
|
12534
|
+
await deleteLocalViewSessionManifest(sessionId).catch(() => void 0);
|
|
12535
|
+
}
|
|
12536
|
+
var init_registration = __esm({
|
|
12537
|
+
"src/local-view/registration.ts"() {
|
|
12538
|
+
init_preferences();
|
|
12539
|
+
init_service();
|
|
12540
|
+
init_session_manifest();
|
|
12541
|
+
}
|
|
12542
|
+
});
|
|
11807
12543
|
|
|
11808
12544
|
// ../runtime-core/src/internal/engine-selection.ts
|
|
11809
12545
|
function resolveOpensteerEngineName(input = {}) {
|
|
@@ -12070,7 +12806,7 @@ async function waitForDevToolsEndpoint(input) {
|
|
|
12070
12806
|
if (exitCode !== null) {
|
|
12071
12807
|
throw new Error(formatChromeLaunchError(input.stderrLines));
|
|
12072
12808
|
}
|
|
12073
|
-
await
|
|
12809
|
+
await sleep2(DEVTOOLS_POLL_INTERVAL_MS);
|
|
12074
12810
|
}
|
|
12075
12811
|
throw new Error(formatChromeLaunchError(input.stderrLines));
|
|
12076
12812
|
}
|
|
@@ -12218,12 +12954,12 @@ async function waitForProcessExit(pid, timeoutMs) {
|
|
|
12218
12954
|
if (!isProcessRunning(pid)) {
|
|
12219
12955
|
return true;
|
|
12220
12956
|
}
|
|
12221
|
-
await
|
|
12957
|
+
await sleep2(50);
|
|
12222
12958
|
}
|
|
12223
12959
|
return !isProcessRunning(pid);
|
|
12224
12960
|
}
|
|
12225
12961
|
function resolveAbpSessionDir(workspace) {
|
|
12226
|
-
return
|
|
12962
|
+
return path10__default.default.join(workspace.livePath, "abp-session");
|
|
12227
12963
|
}
|
|
12228
12964
|
async function allocateEphemeralPort() {
|
|
12229
12965
|
const { allocatePort } = await loadAbpModule();
|
|
@@ -12280,7 +13016,7 @@ function resolveStealthProfile(input) {
|
|
|
12280
13016
|
function isStealthProfile(input) {
|
|
12281
13017
|
return input.id !== void 0 && input.platform !== void 0 && input.browserBrand !== void 0 && input.browserVersion !== void 0 && input.userAgent !== void 0 && input.viewport !== void 0 && input.screenResolution !== void 0 && input.devicePixelRatio !== void 0 && input.maxTouchPoints !== void 0 && input.webglVendor !== void 0 && input.webglRenderer !== void 0 && input.fonts !== void 0 && input.canvasNoiseSeed !== void 0 && input.audioNoiseSeed !== void 0 && input.locale !== void 0 && input.timezoneId !== void 0;
|
|
12282
13018
|
}
|
|
12283
|
-
async function
|
|
13019
|
+
async function sleep2(ms) {
|
|
12284
13020
|
await new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
12285
13021
|
}
|
|
12286
13022
|
var DEFAULT_TIMEOUT_MS, DEVTOOLS_POLL_INTERVAL_MS, TEMPORARY_WORKSPACE_PREFIX, BROWSER_CLOSE_TIMEOUT_MS, OpensteerBrowserManager;
|
|
@@ -12295,6 +13031,8 @@ var init_browser_manager = __esm({
|
|
|
12295
13031
|
init_stealth_profiles();
|
|
12296
13032
|
init_root2();
|
|
12297
13033
|
init_live_session();
|
|
13034
|
+
init_registration();
|
|
13035
|
+
init_session_manifest();
|
|
12298
13036
|
init_filesystem2();
|
|
12299
13037
|
init_engine_selection2();
|
|
12300
13038
|
DEFAULT_TIMEOUT_MS = 3e4;
|
|
@@ -12323,8 +13061,8 @@ var init_browser_manager = __esm({
|
|
|
12323
13061
|
...options.browser === void 0 ? {} : { browser: options.browser },
|
|
12324
13062
|
...this.contextOptions === void 0 ? {} : { context: this.contextOptions }
|
|
12325
13063
|
});
|
|
12326
|
-
this.rootPath = options.rootPath ?? (this.workspace === void 0 ?
|
|
12327
|
-
rootDir:
|
|
13064
|
+
this.rootPath = options.rootPath ?? (this.workspace === void 0 ? path10__default.default.join(os.tmpdir(), `${TEMPORARY_WORKSPACE_PREFIX}${crypto.randomUUID()}`) : resolveFilesystemWorkspacePath({
|
|
13065
|
+
rootDir: path10__default.default.resolve(options.rootDir ?? process.cwd()),
|
|
12328
13066
|
workspace: this.workspace
|
|
12329
13067
|
}));
|
|
12330
13068
|
this.cleanupRootOnDisconnect = this.workspace === void 0;
|
|
@@ -12385,7 +13123,7 @@ var init_browser_manager = __esm({
|
|
|
12385
13123
|
userDataDir: "browser/user-data",
|
|
12386
13124
|
bootstrap: {
|
|
12387
13125
|
kind: "cloneLocalProfile",
|
|
12388
|
-
sourceUserDataDir:
|
|
13126
|
+
sourceUserDataDir: path10__default.default.resolve(input.sourceUserDataDir),
|
|
12389
13127
|
...input.sourceProfileDirectory === void 0 ? {} : { sourceProfileDirectory: input.sourceProfileDirectory }
|
|
12390
13128
|
}
|
|
12391
13129
|
};
|
|
@@ -12458,6 +13196,12 @@ var init_browser_manager = __esm({
|
|
|
12458
13196
|
`workspace "${this.workspace}" already has a live ${live.engine} browser. Close it before reopening with engine "abp".`
|
|
12459
13197
|
);
|
|
12460
13198
|
}
|
|
13199
|
+
await bestEffortRegisterLocalViewSession({
|
|
13200
|
+
rootPath: workspace.rootPath,
|
|
13201
|
+
...this.workspace === void 0 ? {} : { workspace: this.workspace },
|
|
13202
|
+
live: toPersistedLocalBrowserSessionRecord(this.workspace, live),
|
|
13203
|
+
ownership: "owned"
|
|
13204
|
+
});
|
|
12461
13205
|
return this.createAdoptedAbpEngine(live);
|
|
12462
13206
|
}
|
|
12463
13207
|
await this.ensurePersistentBrowserManifest(workspace);
|
|
@@ -12490,9 +13234,17 @@ var init_browser_manager = __esm({
|
|
|
12490
13234
|
...launch?.browserExecutablePath === void 0 ? {} : { executablePath: launch.browserExecutablePath }
|
|
12491
13235
|
};
|
|
12492
13236
|
await this.writeLivePersistentBrowser(workspace, liveRecord);
|
|
13237
|
+
const persistedLiveRecord = toPersistedLocalBrowserSessionRecord(this.workspace, liveRecord);
|
|
13238
|
+
await bestEffortRegisterLocalViewSession({
|
|
13239
|
+
rootPath: workspace.rootPath,
|
|
13240
|
+
...this.workspace === void 0 ? {} : { workspace: this.workspace },
|
|
13241
|
+
live: persistedLiveRecord,
|
|
13242
|
+
ownership: "owned"
|
|
13243
|
+
});
|
|
12493
13244
|
try {
|
|
12494
13245
|
return await this.createAdoptedAbpEngine(liveRecord);
|
|
12495
13246
|
} catch (error) {
|
|
13247
|
+
await this.unregisterLocalViewSessionForRecord(workspace.rootPath, persistedLiveRecord);
|
|
12496
13248
|
await terminateProcess(launched.process.pid ?? 0).catch(() => void 0);
|
|
12497
13249
|
await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
|
|
12498
13250
|
throw error;
|
|
@@ -12512,23 +13264,45 @@ var init_browser_manager = __esm({
|
|
|
12512
13264
|
});
|
|
12513
13265
|
}
|
|
12514
13266
|
async createTemporaryEngine() {
|
|
12515
|
-
const userDataDir = await promises.mkdtemp(
|
|
13267
|
+
const userDataDir = await promises.mkdtemp(path10__default.default.join(os.tmpdir(), "opensteer-temporary-browser-"));
|
|
12516
13268
|
await clearChromeSingletonEntries(userDataDir);
|
|
12517
13269
|
const launched = await launchOwnedBrowser({
|
|
12518
13270
|
userDataDir,
|
|
12519
13271
|
...this.launchOptions === void 0 ? {} : { launch: this.launchOptions },
|
|
12520
13272
|
...this.contextOptions?.viewport === void 0 ? {} : { viewport: this.contextOptions.viewport }
|
|
12521
13273
|
});
|
|
13274
|
+
const temporaryLiveRecord = {
|
|
13275
|
+
layout: "opensteer-session",
|
|
13276
|
+
version: 1,
|
|
13277
|
+
provider: "local",
|
|
13278
|
+
engine: "playwright",
|
|
13279
|
+
endpoint: launched.endpoint,
|
|
13280
|
+
pid: launched.pid,
|
|
13281
|
+
startedAt: Date.now(),
|
|
13282
|
+
updatedAt: Date.now(),
|
|
13283
|
+
executablePath: launched.executablePath,
|
|
13284
|
+
userDataDir
|
|
13285
|
+
};
|
|
13286
|
+
await writePersistedSessionRecord(this.rootPath, temporaryLiveRecord);
|
|
13287
|
+
const localViewManifest = await bestEffortRegisterLocalViewSession({
|
|
13288
|
+
rootPath: this.rootPath,
|
|
13289
|
+
live: temporaryLiveRecord,
|
|
13290
|
+
ownership: "owned"
|
|
13291
|
+
});
|
|
12522
13292
|
try {
|
|
12523
13293
|
return await this.createAttachedEngine({
|
|
12524
13294
|
endpoint: launched.endpoint,
|
|
12525
13295
|
freshTab: false,
|
|
12526
13296
|
onDispose: async () => {
|
|
13297
|
+
await bestEffortUnregisterLocalViewSession(localViewManifest?.sessionId);
|
|
13298
|
+
await clearPersistedSessionRecord(this.rootPath, "local").catch(() => void 0);
|
|
12527
13299
|
await terminateProcess(launched.pid).catch(() => void 0);
|
|
12528
13300
|
await promises.rm(userDataDir, { recursive: true, force: true }).catch(() => void 0);
|
|
12529
13301
|
}
|
|
12530
13302
|
});
|
|
12531
13303
|
} catch (error) {
|
|
13304
|
+
await bestEffortUnregisterLocalViewSession(localViewManifest?.sessionId);
|
|
13305
|
+
await clearPersistedSessionRecord(this.rootPath, "local").catch(() => void 0);
|
|
12532
13306
|
await terminateProcess(launched.pid).catch(() => void 0);
|
|
12533
13307
|
await promises.rm(userDataDir, { recursive: true, force: true }).catch(() => void 0);
|
|
12534
13308
|
throw error;
|
|
@@ -12556,6 +13330,12 @@ var init_browser_manager = __esm({
|
|
|
12556
13330
|
if (live.endpoint === void 0) {
|
|
12557
13331
|
throw new Error("workspace live browser record is missing a DevTools endpoint.");
|
|
12558
13332
|
}
|
|
13333
|
+
await bestEffortRegisterLocalViewSession({
|
|
13334
|
+
rootPath: workspace.rootPath,
|
|
13335
|
+
...this.workspace === void 0 ? {} : { workspace: this.workspace },
|
|
13336
|
+
live: toPersistedLocalBrowserSessionRecord(this.workspace, live),
|
|
13337
|
+
ownership: "owned"
|
|
13338
|
+
});
|
|
12559
13339
|
return this.createAttachedEngine({
|
|
12560
13340
|
endpoint: live.endpoint,
|
|
12561
13341
|
freshTab: false,
|
|
@@ -12578,6 +13358,13 @@ var init_browser_manager = __esm({
|
|
|
12578
13358
|
userDataDir: workspace.browserUserDataDir
|
|
12579
13359
|
};
|
|
12580
13360
|
await this.writeLivePersistentBrowser(workspace, liveRecord);
|
|
13361
|
+
const persistedLiveRecord = toPersistedLocalBrowserSessionRecord(this.workspace, liveRecord);
|
|
13362
|
+
await bestEffortRegisterLocalViewSession({
|
|
13363
|
+
rootPath: workspace.rootPath,
|
|
13364
|
+
...this.workspace === void 0 ? {} : { workspace: this.workspace },
|
|
13365
|
+
live: persistedLiveRecord,
|
|
13366
|
+
ownership: "owned"
|
|
13367
|
+
});
|
|
12581
13368
|
try {
|
|
12582
13369
|
return await this.createAttachedEngine({
|
|
12583
13370
|
endpoint: launched.endpoint,
|
|
@@ -12585,6 +13372,7 @@ var init_browser_manager = __esm({
|
|
|
12585
13372
|
onDispose: async () => void 0
|
|
12586
13373
|
});
|
|
12587
13374
|
} catch (error) {
|
|
13375
|
+
await this.unregisterLocalViewSessionForRecord(workspace.rootPath, persistedLiveRecord);
|
|
12588
13376
|
await terminateProcess(launched.pid).catch(() => void 0);
|
|
12589
13377
|
await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
|
|
12590
13378
|
throw error;
|
|
@@ -12735,6 +13523,10 @@ var init_browser_manager = __esm({
|
|
|
12735
13523
|
await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
|
|
12736
13524
|
return;
|
|
12737
13525
|
}
|
|
13526
|
+
await this.unregisterLocalViewSessionForRecord(
|
|
13527
|
+
workspace.rootPath,
|
|
13528
|
+
toPersistedLocalBrowserSessionRecord(this.workspace, live)
|
|
13529
|
+
);
|
|
12738
13530
|
if (live.engine === "playwright") {
|
|
12739
13531
|
if (live.endpoint !== void 0) {
|
|
12740
13532
|
await requestBrowserClose(live.endpoint).catch(() => void 0);
|
|
@@ -12761,6 +13553,15 @@ var init_browser_manager = __esm({
|
|
|
12761
13553
|
throw new Error(`browser.${method}() requires a persistent workspace browser.`);
|
|
12762
13554
|
}
|
|
12763
13555
|
}
|
|
13556
|
+
async unregisterLocalViewSessionForRecord(rootPath, record) {
|
|
13557
|
+
await bestEffortUnregisterLocalViewSession(
|
|
13558
|
+
buildLocalViewSessionId({
|
|
13559
|
+
rootPath,
|
|
13560
|
+
pid: record.pid,
|
|
13561
|
+
startedAt: record.startedAt
|
|
13562
|
+
})
|
|
13563
|
+
);
|
|
13564
|
+
}
|
|
12764
13565
|
};
|
|
12765
13566
|
}
|
|
12766
13567
|
});
|
|
@@ -12813,17 +13614,17 @@ async function readBrowserCookies(input = {}) {
|
|
|
12813
13614
|
const brand2 = resolveRequestedBrand(input);
|
|
12814
13615
|
const userDataDir = resolveBrandUserDataDir(brand2, input.userDataDir);
|
|
12815
13616
|
const profileDirectory = input.profileDirectory ?? "Default";
|
|
12816
|
-
const cookiesPath =
|
|
13617
|
+
const cookiesPath = path10.join(userDataDir, profileDirectory, "Cookies");
|
|
12817
13618
|
if (!fs.existsSync(cookiesPath)) {
|
|
12818
13619
|
throw new Error(
|
|
12819
13620
|
`Cookies database not found at "${cookiesPath}". Verify the browser brand, user-data-dir, and profile-directory are correct.`
|
|
12820
13621
|
);
|
|
12821
13622
|
}
|
|
12822
|
-
const tempDir = await promises.mkdtemp(
|
|
13623
|
+
const tempDir = await promises.mkdtemp(path10.join(os.tmpdir(), "opensteer-cookies-"));
|
|
12823
13624
|
try {
|
|
12824
13625
|
await copyCookiesDatabase(cookiesPath, tempDir);
|
|
12825
13626
|
const decryptionKey = await resolveDecryptionKey(brand2.id, userDataDir);
|
|
12826
|
-
const rows = queryAllCookies(
|
|
13627
|
+
const rows = queryAllCookies(path10.join(tempDir, "Cookies"));
|
|
12827
13628
|
const cookies = decryptCookieRows(rows, decryptionKey);
|
|
12828
13629
|
return {
|
|
12829
13630
|
cookies,
|
|
@@ -12849,11 +13650,11 @@ function resolveRequestedBrand(input) {
|
|
|
12849
13650
|
return installed.brand;
|
|
12850
13651
|
}
|
|
12851
13652
|
async function copyCookiesDatabase(cookiesPath, destDir) {
|
|
12852
|
-
await promises.copyFile(cookiesPath,
|
|
13653
|
+
await promises.copyFile(cookiesPath, path10.join(destDir, "Cookies"));
|
|
12853
13654
|
for (const suffix of ["-wal", "-journal", "-shm"]) {
|
|
12854
13655
|
const src = cookiesPath + suffix;
|
|
12855
13656
|
if (fs.existsSync(src)) {
|
|
12856
|
-
await promises.copyFile(src,
|
|
13657
|
+
await promises.copyFile(src, path10.join(destDir, "Cookies" + suffix)).catch(() => void 0);
|
|
12857
13658
|
}
|
|
12858
13659
|
}
|
|
12859
13660
|
}
|
|
@@ -12921,7 +13722,7 @@ async function resolveKeychainPassword(brandId) {
|
|
|
12921
13722
|
}
|
|
12922
13723
|
}
|
|
12923
13724
|
async function resolveWindowsMasterKey(userDataDir) {
|
|
12924
|
-
const localStatePath =
|
|
13725
|
+
const localStatePath = path10.join(userDataDir, "Local State");
|
|
12925
13726
|
let localState;
|
|
12926
13727
|
try {
|
|
12927
13728
|
localState = JSON.parse(await promises.readFile(localStatePath, "utf8"));
|
|
@@ -13141,14 +13942,14 @@ function toPortableBrowserProfileCookieRecord(cookie) {
|
|
|
13141
13942
|
if (!name || !domain) {
|
|
13142
13943
|
return null;
|
|
13143
13944
|
}
|
|
13144
|
-
const
|
|
13945
|
+
const path24 = typeof cookie.path === "string" && cookie.path.trim().length > 0 ? cookie.path : "/";
|
|
13145
13946
|
const expiresAt = typeof cookie.expires === "number" && Number.isFinite(cookie.expires) && cookie.expires > 0 ? Math.floor(cookie.expires * 1e3) : null;
|
|
13146
13947
|
const sameSite = normalizeSameSite(cookie.sameSite);
|
|
13147
13948
|
return {
|
|
13148
13949
|
name,
|
|
13149
13950
|
value: cookie.value,
|
|
13150
13951
|
domain,
|
|
13151
|
-
path:
|
|
13952
|
+
path: path24,
|
|
13152
13953
|
secure: cookie.secure,
|
|
13153
13954
|
httpOnly: cookie.httpOnly,
|
|
13154
13955
|
...sameSite === void 0 ? {} : { sameSite },
|
|
@@ -13216,7 +14017,7 @@ async function waitForBrowserProfileImport(client, importId) {
|
|
|
13216
14017
|
if (current.status === "failed") {
|
|
13217
14018
|
throw new Error(current.error ?? "Browser profile sync failed.");
|
|
13218
14019
|
}
|
|
13219
|
-
await
|
|
14020
|
+
await sleep3(DEFAULT_POLL_INTERVAL_MS);
|
|
13220
14021
|
}
|
|
13221
14022
|
throw new Error(`Timed out waiting for browser profile sync "${importId}" to finish.`);
|
|
13222
14023
|
}
|
|
@@ -13225,7 +14026,7 @@ function normalizePlatform(platform) {
|
|
|
13225
14026
|
if (platform === "win32") return "windows";
|
|
13226
14027
|
return platform;
|
|
13227
14028
|
}
|
|
13228
|
-
async function
|
|
14029
|
+
async function sleep3(ms) {
|
|
13229
14030
|
await new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
13230
14031
|
}
|
|
13231
14032
|
var gzip, DEFAULT_POLL_INTERVAL_MS, DEFAULT_POLL_TIMEOUT_MS;
|
|
@@ -13247,7 +14048,7 @@ function createRequestSignal(options) {
|
|
|
13247
14048
|
}
|
|
13248
14049
|
return AbortSignal.any([options.signal, timeoutSignal]);
|
|
13249
14050
|
}
|
|
13250
|
-
function
|
|
14051
|
+
function delay2(ms) {
|
|
13251
14052
|
return new Promise((resolve4) => {
|
|
13252
14053
|
setTimeout(resolve4, ms);
|
|
13253
14054
|
});
|
|
@@ -13464,7 +14265,7 @@ var init_client = __esm({
|
|
|
13464
14265
|
`Unexpected cloud session status "${String(session.status)}" while waiting for close.`
|
|
13465
14266
|
);
|
|
13466
14267
|
}
|
|
13467
|
-
await
|
|
14268
|
+
await delay2(CLOUD_CLOSE_POLL_INTERVAL_MS);
|
|
13468
14269
|
}
|
|
13469
14270
|
throw new Error(`Timed out waiting for cloud session ${sessionId} to close.`);
|
|
13470
14271
|
}
|
|
@@ -13519,7 +14320,7 @@ var init_package = __esm({
|
|
|
13519
14320
|
"../runtime-core/package.json"() {
|
|
13520
14321
|
package_default2 = {
|
|
13521
14322
|
name: "@opensteer/runtime-core",
|
|
13522
|
-
version: "0.2.
|
|
14323
|
+
version: "0.2.1",
|
|
13523
14324
|
description: "Shared semantic runtime for Opensteer local and cloud execution.",
|
|
13524
14325
|
license: "MIT",
|
|
13525
14326
|
type: "module",
|
|
@@ -14345,9 +15146,9 @@ var init_match_selectors = __esm({
|
|
|
14345
15146
|
});
|
|
14346
15147
|
|
|
14347
15148
|
// ../runtime-core/src/runtimes/dom/extraction.ts
|
|
14348
|
-
function buildArrayFieldPathCandidates(
|
|
14349
|
-
const strict =
|
|
14350
|
-
const relaxedNodes = stripPositionClauses(
|
|
15149
|
+
function buildArrayFieldPathCandidates(path24) {
|
|
15150
|
+
const strict = path24.nodes.length ? buildPathCandidates(path24.nodes) : [];
|
|
15151
|
+
const relaxedNodes = stripPositionClauses(path24.nodes);
|
|
14351
15152
|
const relaxed = relaxedNodes.length ? buildPathCandidates(relaxedNodes) : [];
|
|
14352
15153
|
return dedupeSelectors([...strict, ...relaxed]);
|
|
14353
15154
|
}
|
|
@@ -14895,8 +15696,8 @@ function cloneStructuralElementAnchor(anchor) {
|
|
|
14895
15696
|
nodes: anchor.nodes.map(clonePathNode)
|
|
14896
15697
|
};
|
|
14897
15698
|
}
|
|
14898
|
-
function buildPathSelectorHint(
|
|
14899
|
-
const nodes =
|
|
15699
|
+
function buildPathSelectorHint(path24) {
|
|
15700
|
+
const nodes = path24?.nodes || [];
|
|
14900
15701
|
const last = nodes[nodes.length - 1];
|
|
14901
15702
|
if (!last) {
|
|
14902
15703
|
return "*";
|
|
@@ -14945,15 +15746,15 @@ function sanitizeStructuralElementAnchor(anchor) {
|
|
|
14945
15746
|
nodes: sanitizeNodes(anchor.nodes)
|
|
14946
15747
|
};
|
|
14947
15748
|
}
|
|
14948
|
-
function sanitizeReplayElementPath(
|
|
15749
|
+
function sanitizeReplayElementPath(path24) {
|
|
14949
15750
|
return {
|
|
14950
15751
|
resolution: "deterministic",
|
|
14951
|
-
context: sanitizeContext(
|
|
14952
|
-
nodes: sanitizeNodes(
|
|
15752
|
+
context: sanitizeContext(path24.context),
|
|
15753
|
+
nodes: sanitizeNodes(path24.nodes)
|
|
14953
15754
|
};
|
|
14954
15755
|
}
|
|
14955
|
-
function sanitizeElementPath(
|
|
14956
|
-
return sanitizeReplayElementPath(
|
|
15756
|
+
function sanitizeElementPath(path24) {
|
|
15757
|
+
return sanitizeReplayElementPath(path24);
|
|
14957
15758
|
}
|
|
14958
15759
|
function buildLocalStructuralElementAnchor(index, rawTargetNode) {
|
|
14959
15760
|
const targetNode = requireElementNode(index, rawTargetNode);
|
|
@@ -15076,8 +15877,8 @@ function buildTargetNotFoundMessage(domPath, diagnostics) {
|
|
|
15076
15877
|
}
|
|
15077
15878
|
return `${base} Target depth ${String(depth)}. Candidate counts: ${sample}.`;
|
|
15078
15879
|
}
|
|
15079
|
-
function buildArrayFieldCandidates(
|
|
15080
|
-
return buildArrayFieldPathCandidates(
|
|
15880
|
+
function buildArrayFieldCandidates(path24) {
|
|
15881
|
+
return buildArrayFieldPathCandidates(path24);
|
|
15081
15882
|
}
|
|
15082
15883
|
function firstDefinedAttribute(node, keys) {
|
|
15083
15884
|
for (const key of keys) {
|
|
@@ -16639,21 +17440,21 @@ var init_runtime = __esm({
|
|
|
16639
17440
|
return match;
|
|
16640
17441
|
}
|
|
16641
17442
|
async resolvePathTarget(session, pageRef, rawPath, source, persist, descriptor) {
|
|
16642
|
-
const
|
|
16643
|
-
const context = await this.resolvePathContext(session, pageRef,
|
|
16644
|
-
const target = resolveDomPathInScope(context.index,
|
|
17443
|
+
const path24 = sanitizeReplayElementPath(rawPath);
|
|
17444
|
+
const context = await this.resolvePathContext(session, pageRef, path24.context);
|
|
17445
|
+
const target = resolveDomPathInScope(context.index, path24.nodes, context.scope);
|
|
16645
17446
|
if (!target) {
|
|
16646
|
-
throwTargetNotFound(context.index,
|
|
17447
|
+
throwTargetNotFound(context.index, path24.nodes, context.scope);
|
|
16647
17448
|
}
|
|
16648
17449
|
if (target.node.nodeRef === void 0) {
|
|
16649
17450
|
throw new Error(
|
|
16650
|
-
`resolved path "${buildPathSelectorHint(
|
|
17451
|
+
`resolved path "${buildPathSelectorHint(path24)}" does not point to a live element`
|
|
16651
17452
|
);
|
|
16652
17453
|
}
|
|
16653
17454
|
const anchor = await this.buildAnchorFromSnapshotNode(session, context.snapshot, target.node);
|
|
16654
17455
|
return this.createResolvedTarget(source, context.snapshot, target.node, anchor, {
|
|
16655
17456
|
...persist === void 0 ? {} : { persist },
|
|
16656
|
-
replayPath:
|
|
17457
|
+
replayPath: path24,
|
|
16657
17458
|
...source === "path" || source === "descriptor" ? { selectorUsed: target.selector } : {},
|
|
16658
17459
|
...descriptor === void 0 ? {} : { descriptor }
|
|
16659
17460
|
});
|
|
@@ -16674,9 +17475,9 @@ var init_runtime = __esm({
|
|
|
16674
17475
|
});
|
|
16675
17476
|
}
|
|
16676
17477
|
async queryAllByElementPath(session, pageRef, rawPath) {
|
|
16677
|
-
const
|
|
16678
|
-
const context = await this.resolvePathContext(session, pageRef,
|
|
16679
|
-
return queryAllDomPathInScope(context.index,
|
|
17478
|
+
const path24 = sanitizeReplayElementPath(rawPath);
|
|
17479
|
+
const context = await this.resolvePathContext(session, pageRef, path24.context);
|
|
17480
|
+
return queryAllDomPathInScope(context.index, path24.nodes, context.scope).filter(
|
|
16680
17481
|
(node) => node.nodeRef !== void 0
|
|
16681
17482
|
).map((node) => this.createSnapshotTarget(context.snapshot, node));
|
|
16682
17483
|
}
|
|
@@ -16862,16 +17663,16 @@ var init_runtime = __esm({
|
|
|
16862
17663
|
const index = createSnapshotIndex(item.snapshot);
|
|
16863
17664
|
return this.resolveFirstArrayFieldTargetInNode(index, item.node, field.path);
|
|
16864
17665
|
}
|
|
16865
|
-
resolveFirstArrayFieldTargetInNode(index, rootNode,
|
|
16866
|
-
const normalizedPath = sanitizeElementPath(
|
|
17666
|
+
resolveFirstArrayFieldTargetInNode(index, rootNode, path24) {
|
|
17667
|
+
const normalizedPath = sanitizeElementPath(path24);
|
|
16867
17668
|
const selectors = buildArrayFieldCandidates(normalizedPath);
|
|
16868
17669
|
if (!selectors.length) {
|
|
16869
17670
|
return rootNode;
|
|
16870
17671
|
}
|
|
16871
17672
|
return resolveFirstWithinNodeBySelectors(index, rootNode, selectors);
|
|
16872
17673
|
}
|
|
16873
|
-
resolveUniqueArrayFieldTargetInNode(index, rootNode,
|
|
16874
|
-
const normalizedPath = sanitizeElementPath(
|
|
17674
|
+
resolveUniqueArrayFieldTargetInNode(index, rootNode, path24) {
|
|
17675
|
+
const normalizedPath = sanitizeElementPath(path24);
|
|
16875
17676
|
const selectors = buildArrayFieldCandidates(normalizedPath);
|
|
16876
17677
|
if (!selectors.length) {
|
|
16877
17678
|
return rootNode;
|
|
@@ -17703,11 +18504,11 @@ var init_history = __esm({
|
|
|
17703
18504
|
});
|
|
17704
18505
|
async function executeMatchedTlsTransportRequest(input) {
|
|
17705
18506
|
const binary = await resolveMatchedTlsBinary();
|
|
17706
|
-
const workingDirectory = await promises.mkdtemp(
|
|
17707
|
-
const headersPath =
|
|
17708
|
-
const bodyPath =
|
|
17709
|
-
const cookiesPath =
|
|
17710
|
-
const requestBodyPath =
|
|
18507
|
+
const workingDirectory = await promises.mkdtemp(path10__default.default.join(os.tmpdir(), "opensteer-matched-tls-"));
|
|
18508
|
+
const headersPath = path10__default.default.join(workingDirectory, "headers.txt");
|
|
18509
|
+
const bodyPath = path10__default.default.join(workingDirectory, "body.bin");
|
|
18510
|
+
const cookiesPath = path10__default.default.join(workingDirectory, "cookies.txt");
|
|
18511
|
+
const requestBodyPath = path10__default.default.join(workingDirectory, "request-body.bin");
|
|
17711
18512
|
try {
|
|
17712
18513
|
await promises.writeFile(cookiesPath, toNetscapeCookieJar(input.cookies ?? []), "utf8");
|
|
17713
18514
|
if (input.request.body !== void 0) {
|
|
@@ -17764,10 +18565,10 @@ async function executeMatchedTlsTransportRequest(input) {
|
|
|
17764
18565
|
}
|
|
17765
18566
|
}
|
|
17766
18567
|
async function resolveMatchedTlsBinary() {
|
|
17767
|
-
const pathEntries = (process.env.PATH ?? "").split(
|
|
18568
|
+
const pathEntries = (process.env.PATH ?? "").split(path10__default.default.delimiter).filter((entry) => entry.length > 0);
|
|
17768
18569
|
for (const directory of pathEntries) {
|
|
17769
18570
|
for (const name of MATCHED_TLS_BINARY_NAMES) {
|
|
17770
|
-
const candidate =
|
|
18571
|
+
const candidate = path10__default.default.join(directory, name);
|
|
17771
18572
|
if (await isExecutable(candidate)) {
|
|
17772
18573
|
return candidate;
|
|
17773
18574
|
}
|
|
@@ -17775,7 +18576,7 @@ async function resolveMatchedTlsBinary() {
|
|
|
17775
18576
|
const files = await readDirSafe(directory);
|
|
17776
18577
|
const discovered = files.find((file) => file.startsWith("curl_chrome"));
|
|
17777
18578
|
if (discovered !== void 0) {
|
|
17778
|
-
const candidate =
|
|
18579
|
+
const candidate = path10__default.default.join(directory, discovered);
|
|
17779
18580
|
if (await isExecutable(candidate)) {
|
|
17780
18581
|
return candidate;
|
|
17781
18582
|
}
|
|
@@ -17939,8 +18740,8 @@ function encodeDataPath(tokens) {
|
|
|
17939
18740
|
}
|
|
17940
18741
|
return out;
|
|
17941
18742
|
}
|
|
17942
|
-
function parseDataPath(
|
|
17943
|
-
const input =
|
|
18743
|
+
function parseDataPath(path24) {
|
|
18744
|
+
const input = path24.trim();
|
|
17944
18745
|
if (input.length === 0) {
|
|
17945
18746
|
return [];
|
|
17946
18747
|
}
|
|
@@ -17990,8 +18791,8 @@ function parseDataPath(path18) {
|
|
|
17990
18791
|
function inflateDataPathObject(flat) {
|
|
17991
18792
|
let root = {};
|
|
17992
18793
|
let initialized = false;
|
|
17993
|
-
for (const [
|
|
17994
|
-
const tokens = parseDataPath(
|
|
18794
|
+
for (const [path24, value] of Object.entries(flat)) {
|
|
18795
|
+
const tokens = parseDataPath(path24);
|
|
17995
18796
|
if (!tokens || tokens.length === 0) {
|
|
17996
18797
|
continue;
|
|
17997
18798
|
}
|
|
@@ -18300,8 +19101,8 @@ function buildVariantDescriptorFromCluster(descriptors) {
|
|
|
18300
19101
|
fields: mergedFields
|
|
18301
19102
|
};
|
|
18302
19103
|
}
|
|
18303
|
-
function minimizePathMatchClauses(
|
|
18304
|
-
const normalized = sanitizeElementPath(
|
|
19104
|
+
function minimizePathMatchClauses(path24, mode) {
|
|
19105
|
+
const normalized = sanitizeElementPath(path24);
|
|
18305
19106
|
const nodes = normalized.nodes.map((node, index) => {
|
|
18306
19107
|
const isLast = index === normalized.nodes.length - 1;
|
|
18307
19108
|
const attrs = node.attrs || {};
|
|
@@ -18405,8 +19206,8 @@ function seedMinimalAttrClause(attrs) {
|
|
|
18405
19206
|
}
|
|
18406
19207
|
return null;
|
|
18407
19208
|
}
|
|
18408
|
-
function relaxPathForSingleSample(
|
|
18409
|
-
const normalized = sanitizeElementPath(
|
|
19209
|
+
function relaxPathForSingleSample(path24, mode) {
|
|
19210
|
+
const normalized = sanitizeElementPath(path24);
|
|
18410
19211
|
const relaxedNodes = normalized.nodes.map((node, index) => {
|
|
18411
19212
|
const isLast = index === normalized.nodes.length - 1;
|
|
18412
19213
|
const attrs = normalizeAttrsForSingleSample(node.attrs || {});
|
|
@@ -18491,8 +19292,8 @@ function shouldKeepAttrForSingleSample(key) {
|
|
|
18491
19292
|
}
|
|
18492
19293
|
return true;
|
|
18493
19294
|
}
|
|
18494
|
-
function buildPathStructureKey(
|
|
18495
|
-
const normalized = sanitizeElementPath(
|
|
19295
|
+
function buildPathStructureKey(path24) {
|
|
19296
|
+
const normalized = sanitizeElementPath(path24);
|
|
18496
19297
|
return canonicalJsonString({
|
|
18497
19298
|
context: (normalized.context || []).map((hop) => ({
|
|
18498
19299
|
kind: hop.kind,
|
|
@@ -18619,30 +19420,30 @@ function buildArrayItemNode(fields) {
|
|
|
18619
19420
|
}
|
|
18620
19421
|
return node;
|
|
18621
19422
|
}
|
|
18622
|
-
function insertNodeAtPath(root,
|
|
18623
|
-
const tokens = parseDataPath(
|
|
19423
|
+
function insertNodeAtPath(root, path24, node) {
|
|
19424
|
+
const tokens = parseDataPath(path24);
|
|
18624
19425
|
if (!tokens || !tokens.length) {
|
|
18625
19426
|
throw new Error(
|
|
18626
|
-
`Invalid persisted extraction path "${
|
|
19427
|
+
`Invalid persisted extraction path "${path24}": expected a non-empty object path.`
|
|
18627
19428
|
);
|
|
18628
19429
|
}
|
|
18629
19430
|
if (tokens.some((token) => token.kind === "index")) {
|
|
18630
19431
|
throw new Error(
|
|
18631
|
-
`Invalid persisted extraction path "${
|
|
19432
|
+
`Invalid persisted extraction path "${path24}": nested array indices are not supported in cached descriptors.`
|
|
18632
19433
|
);
|
|
18633
19434
|
}
|
|
18634
19435
|
let current = root;
|
|
18635
19436
|
for (let index = 0; index < tokens.length; index += 1) {
|
|
18636
19437
|
const token = tokens[index];
|
|
18637
19438
|
if (!token || token.kind !== "prop") {
|
|
18638
|
-
throw new Error(`Invalid persisted extraction path "${
|
|
19439
|
+
throw new Error(`Invalid persisted extraction path "${path24}": expected object segment.`);
|
|
18639
19440
|
}
|
|
18640
19441
|
const isLast = index === tokens.length - 1;
|
|
18641
19442
|
if (isLast) {
|
|
18642
19443
|
const existing = current[token.key];
|
|
18643
19444
|
if (existing) {
|
|
18644
19445
|
throw new Error(
|
|
18645
|
-
`Conflicting persisted extraction path "${
|
|
19446
|
+
`Conflicting persisted extraction path "${path24}" detected while building descriptor tree.`
|
|
18646
19447
|
);
|
|
18647
19448
|
}
|
|
18648
19449
|
current[token.key] = node;
|
|
@@ -18657,7 +19458,7 @@ function insertNodeAtPath(root, path18, node) {
|
|
|
18657
19458
|
}
|
|
18658
19459
|
if (!isPersistedObjectNode(next)) {
|
|
18659
19460
|
throw new Error(
|
|
18660
|
-
`Conflicting persisted extraction path "${
|
|
19461
|
+
`Conflicting persisted extraction path "${path24}" detected at "${token.key}".`
|
|
18661
19462
|
);
|
|
18662
19463
|
}
|
|
18663
19464
|
current = next;
|
|
@@ -18692,7 +19493,7 @@ function buildItemRootForArrayIndex(entries) {
|
|
|
18692
19493
|
}
|
|
18693
19494
|
const paths = entries.map(
|
|
18694
19495
|
(entry) => isPersistablePathField(entry.source) ? sanitizeElementPath(entry.source.path) : null
|
|
18695
|
-
).filter((
|
|
19496
|
+
).filter((path24) => path24 !== null);
|
|
18696
19497
|
if (!paths.length) {
|
|
18697
19498
|
return null;
|
|
18698
19499
|
}
|
|
@@ -18713,7 +19514,7 @@ function getCommonPathPrefixLength(paths) {
|
|
|
18713
19514
|
if (!paths.length) {
|
|
18714
19515
|
return 0;
|
|
18715
19516
|
}
|
|
18716
|
-
const nodeChains = paths.map((
|
|
19517
|
+
const nodeChains = paths.map((path24) => path24.nodes);
|
|
18717
19518
|
const minLength = Math.min(...nodeChains.map((nodes) => nodes.length));
|
|
18718
19519
|
if (!Number.isFinite(minLength) || minLength <= 0) {
|
|
18719
19520
|
return 0;
|
|
@@ -18782,30 +19583,30 @@ function mergeElementPathsByMajority(paths) {
|
|
|
18782
19583
|
if (!paths.length) {
|
|
18783
19584
|
return null;
|
|
18784
19585
|
}
|
|
18785
|
-
const normalized = paths.map((
|
|
19586
|
+
const normalized = paths.map((path24) => sanitizeElementPath(path24));
|
|
18786
19587
|
const contextKey = pickModeString(
|
|
18787
|
-
normalized.map((
|
|
19588
|
+
normalized.map((path24) => canonicalJsonString(path24.context)),
|
|
18788
19589
|
1
|
|
18789
19590
|
);
|
|
18790
19591
|
if (!contextKey) {
|
|
18791
19592
|
return null;
|
|
18792
19593
|
}
|
|
18793
|
-
const sameContext = normalized.filter((
|
|
19594
|
+
const sameContext = normalized.filter((path24) => canonicalJsonString(path24.context) === contextKey);
|
|
18794
19595
|
if (!sameContext.length) {
|
|
18795
19596
|
return null;
|
|
18796
19597
|
}
|
|
18797
19598
|
const targetLength = pickModeNumber(
|
|
18798
|
-
sameContext.map((
|
|
19599
|
+
sameContext.map((path24) => path24.nodes.length),
|
|
18799
19600
|
1
|
|
18800
19601
|
) ?? sameContext[0]?.nodes.length ?? 0;
|
|
18801
|
-
const aligned = sameContext.filter((
|
|
19602
|
+
const aligned = sameContext.filter((path24) => path24.nodes.length === targetLength);
|
|
18802
19603
|
if (!aligned.length) {
|
|
18803
19604
|
return null;
|
|
18804
19605
|
}
|
|
18805
19606
|
const threshold = majorityThreshold(aligned.length);
|
|
18806
19607
|
const nodes = [];
|
|
18807
19608
|
for (let index = 0; index < targetLength; index += 1) {
|
|
18808
|
-
const nodesAtIndex = aligned.map((
|
|
19609
|
+
const nodesAtIndex = aligned.map((path24) => path24.nodes[index]).filter((node) => node !== void 0);
|
|
18809
19610
|
if (!nodesAtIndex.length) {
|
|
18810
19611
|
return null;
|
|
18811
19612
|
}
|
|
@@ -19051,8 +19852,8 @@ function clonePathContext(context) {
|
|
|
19051
19852
|
function clonePathNodes(nodes) {
|
|
19052
19853
|
return JSON.parse(JSON.stringify(nodes || []));
|
|
19053
19854
|
}
|
|
19054
|
-
function cloneElementPath2(
|
|
19055
|
-
return JSON.parse(JSON.stringify(
|
|
19855
|
+
function cloneElementPath2(path24) {
|
|
19856
|
+
return JSON.parse(JSON.stringify(path24));
|
|
19056
19857
|
}
|
|
19057
19858
|
function clonePersistedOpensteerExtractionNode(node) {
|
|
19058
19859
|
return JSON.parse(JSON.stringify(node));
|
|
@@ -19404,8 +20205,8 @@ function collectPersistedValueNodeRefs(node) {
|
|
|
19404
20205
|
return [
|
|
19405
20206
|
{
|
|
19406
20207
|
path: sanitizeElementPath(node.$path),
|
|
19407
|
-
replacePath: (
|
|
19408
|
-
node.$path = sanitizeElementPath(
|
|
20208
|
+
replacePath: (path24) => {
|
|
20209
|
+
node.$path = sanitizeElementPath(path24);
|
|
19409
20210
|
}
|
|
19410
20211
|
}
|
|
19411
20212
|
];
|
|
@@ -19419,13 +20220,13 @@ function collectPersistedValueNodeRefs(node) {
|
|
|
19419
20220
|
}
|
|
19420
20221
|
return refs;
|
|
19421
20222
|
}
|
|
19422
|
-
function hasPositionClause(
|
|
19423
|
-
return
|
|
20223
|
+
function hasPositionClause(path24) {
|
|
20224
|
+
return path24.nodes.some((node) => node.match.some((clause) => clause.kind === "position"));
|
|
19424
20225
|
}
|
|
19425
|
-
function stripPositionClauses2(
|
|
20226
|
+
function stripPositionClauses2(path24) {
|
|
19426
20227
|
return sanitizeElementPath({
|
|
19427
|
-
context:
|
|
19428
|
-
nodes:
|
|
20228
|
+
context: path24.context,
|
|
20229
|
+
nodes: path24.nodes.map((node) => ({
|
|
19429
20230
|
...node,
|
|
19430
20231
|
match: node.match.filter((clause) => clause.kind !== "position")
|
|
19431
20232
|
}))
|
|
@@ -19745,8 +20546,8 @@ function normalizeNonEmptyString2(name, value) {
|
|
|
19745
20546
|
function normalizeKey(value) {
|
|
19746
20547
|
return String(value ?? "").trim();
|
|
19747
20548
|
}
|
|
19748
|
-
function labelForPath(
|
|
19749
|
-
return
|
|
20549
|
+
function labelForPath(path24) {
|
|
20550
|
+
return path24.trim().length === 0 ? "$" : path24;
|
|
19750
20551
|
}
|
|
19751
20552
|
function sha256Hex3(value) {
|
|
19752
20553
|
return crypto.createHash("sha256").update(value).digest("hex");
|
|
@@ -22296,11 +23097,11 @@ var init_sandbox = __esm({
|
|
|
22296
23097
|
performanceNow() {
|
|
22297
23098
|
return this.mode === "manual" ? this.manualNow - this.startedAt : (globalThis.performance?.now() ?? 0) - this.performanceStartedAt;
|
|
22298
23099
|
}
|
|
22299
|
-
setTimeout(callback,
|
|
22300
|
-
return this.registerTimer(false, callback,
|
|
23100
|
+
setTimeout(callback, delay6 = 0, ...args) {
|
|
23101
|
+
return this.registerTimer(false, callback, delay6, args);
|
|
22301
23102
|
}
|
|
22302
|
-
setInterval(callback,
|
|
22303
|
-
return this.registerTimer(true, callback,
|
|
23103
|
+
setInterval(callback, delay6 = 0, ...args) {
|
|
23104
|
+
return this.registerTimer(true, callback, delay6, args);
|
|
22304
23105
|
}
|
|
22305
23106
|
clearTimeout(timerId) {
|
|
22306
23107
|
this.clearTimer(timerId);
|
|
@@ -22321,9 +23122,9 @@ var init_sandbox = __esm({
|
|
|
22321
23122
|
this.clearTimer(timerId);
|
|
22322
23123
|
}
|
|
22323
23124
|
}
|
|
22324
|
-
registerTimer(repeat, callback,
|
|
23125
|
+
registerTimer(repeat, callback, delay6, args) {
|
|
22325
23126
|
const timerId = this.nextTimerId++;
|
|
22326
|
-
const normalizedDelay = Math.max(0,
|
|
23127
|
+
const normalizedDelay = Math.max(0, delay6);
|
|
22327
23128
|
const record = {
|
|
22328
23129
|
callback,
|
|
22329
23130
|
args,
|
|
@@ -22422,7 +23223,7 @@ async function pollTask(apiKey, taskId, signal) {
|
|
|
22422
23223
|
const deadline = Date.now() + 12e4;
|
|
22423
23224
|
while (Date.now() < deadline) {
|
|
22424
23225
|
signal?.throwIfAborted?.();
|
|
22425
|
-
await
|
|
23226
|
+
await sleep4(3e3, signal);
|
|
22426
23227
|
const response = await fetch(CAPSOLVER_GET_TASK_RESULT_URL, {
|
|
22427
23228
|
method: "POST",
|
|
22428
23229
|
headers: {
|
|
@@ -22466,7 +23267,7 @@ function extractCaptchaToken(solution) {
|
|
|
22466
23267
|
function readString(value) {
|
|
22467
23268
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
22468
23269
|
}
|
|
22469
|
-
function
|
|
23270
|
+
function sleep4(ms, signal) {
|
|
22470
23271
|
return new Promise((resolve4, reject) => {
|
|
22471
23272
|
const timeout = setTimeout(resolve4, ms);
|
|
22472
23273
|
const abort = () => {
|
|
@@ -22525,7 +23326,7 @@ async function pollTask2(apiKey, taskId, signal) {
|
|
|
22525
23326
|
const deadline = Date.now() + 12e4;
|
|
22526
23327
|
while (Date.now() < deadline) {
|
|
22527
23328
|
signal?.throwIfAborted?.();
|
|
22528
|
-
await
|
|
23329
|
+
await sleep5(5e3, signal);
|
|
22529
23330
|
const response = await fetch(TWO_CAPTCHA_GET_TASK_RESULT_URL, {
|
|
22530
23331
|
method: "POST",
|
|
22531
23332
|
headers: {
|
|
@@ -22569,7 +23370,7 @@ function extractCaptchaToken2(solution) {
|
|
|
22569
23370
|
function readString2(value) {
|
|
22570
23371
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
22571
23372
|
}
|
|
22572
|
-
function
|
|
23373
|
+
function sleep5(ms, signal) {
|
|
22573
23374
|
return new Promise((resolve4, reject) => {
|
|
22574
23375
|
const timeout = setTimeout(resolve4, ms);
|
|
22575
23376
|
const abort = () => {
|
|
@@ -24061,7 +24862,7 @@ var init_runtime3 = __esm({
|
|
|
24061
24862
|
this.workspace = normalizeNamespace2(options.name);
|
|
24062
24863
|
this.workspaceName = options.workspaceName?.trim() === void 0 || options.workspaceName?.trim().length === 0 ? void 0 : options.workspaceName.trim();
|
|
24063
24864
|
this.root = options.workspace;
|
|
24064
|
-
this.rootPath = options.workspace?.rootPath ?? options.rootPath ??
|
|
24865
|
+
this.rootPath = options.workspace?.rootPath ?? options.rootPath ?? path10__default.default.resolve(process.cwd(), ".opensteer", "temporary", crypto.randomUUID());
|
|
24065
24866
|
this.injectedEngine = options.engine;
|
|
24066
24867
|
this.engineFactory = options.engineFactory;
|
|
24067
24868
|
this.policy = options.policy ?? defaultPolicy();
|
|
@@ -29077,7 +29878,7 @@ function comparePageIds(left, right) {
|
|
|
29077
29878
|
}
|
|
29078
29879
|
return left.localeCompare(right);
|
|
29079
29880
|
}
|
|
29080
|
-
function
|
|
29881
|
+
function delay3(ms) {
|
|
29081
29882
|
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
29082
29883
|
}
|
|
29083
29884
|
var FlowRecorderCollector;
|
|
@@ -29211,7 +30012,7 @@ var init_event_collector = __esm({
|
|
|
29211
30012
|
if (this.loopStopRequested) {
|
|
29212
30013
|
break;
|
|
29213
30014
|
}
|
|
29214
|
-
await
|
|
30015
|
+
await delay3(this.pollIntervalMs);
|
|
29215
30016
|
}
|
|
29216
30017
|
}
|
|
29217
30018
|
async readEvaluatedPages(listedPages) {
|
|
@@ -30179,7 +30980,7 @@ var init_automation_client = __esm({
|
|
|
30179
30980
|
}).catch(() => void 0);
|
|
30180
30981
|
}
|
|
30181
30982
|
async ensureConnected() {
|
|
30182
|
-
if (this.socket?.readyState ===
|
|
30983
|
+
if (this.socket?.readyState === WebSocket4__namespace.default.OPEN) {
|
|
30183
30984
|
return;
|
|
30184
30985
|
}
|
|
30185
30986
|
if (this.connectPromise) {
|
|
@@ -30200,7 +31001,7 @@ var init_automation_client = __esm({
|
|
|
30200
31001
|
}
|
|
30201
31002
|
const wsUrl = new URL(grant.url);
|
|
30202
31003
|
wsUrl.searchParams.set("token", grant.token);
|
|
30203
|
-
const socket = new
|
|
31004
|
+
const socket = new WebSocket4__namespace.default(wsUrl);
|
|
30204
31005
|
this.socket = socket;
|
|
30205
31006
|
socket.on("message", (data, isBinary) => {
|
|
30206
31007
|
if (isBinary) {
|
|
@@ -30345,7 +31146,7 @@ var init_automation_client = __esm({
|
|
|
30345
31146
|
return grant;
|
|
30346
31147
|
}
|
|
30347
31148
|
requireSocket() {
|
|
30348
|
-
if (!this.socket || this.socket.readyState !==
|
|
31149
|
+
if (!this.socket || this.socket.readyState !== WebSocket4__namespace.default.OPEN) {
|
|
30349
31150
|
throw new Error("cloud automation socket is not connected");
|
|
30350
31151
|
}
|
|
30351
31152
|
return this.socket;
|
|
@@ -30503,8 +31304,8 @@ var init_session_proxy = __esm({
|
|
|
30503
31304
|
this.workspace = options.workspace;
|
|
30504
31305
|
this.policy = options.policy ?? defaultPolicy();
|
|
30505
31306
|
this.observability = options.observability;
|
|
30506
|
-
this.rootPath = options.rootPath ?? (this.workspace === void 0 ?
|
|
30507
|
-
rootDir:
|
|
31307
|
+
this.rootPath = options.rootPath ?? (this.workspace === void 0 ? path10__default.default.join(os.tmpdir(), `${TEMPORARY_CLOUD_WORKSPACE_PREFIX}${crypto.randomUUID()}`) : resolveFilesystemWorkspacePath({
|
|
31308
|
+
rootDir: path10__default.default.resolve(options.rootDir ?? process.cwd()),
|
|
30508
31309
|
workspace: this.workspace
|
|
30509
31310
|
}));
|
|
30510
31311
|
this.cleanupRootOnClose = options.cleanupRootOnClose ?? this.workspace === void 0;
|
|
@@ -30956,8 +31757,8 @@ var init_runtime4 = __esm({
|
|
|
30956
31757
|
OpensteerRuntime = class extends OpensteerSessionRuntime {
|
|
30957
31758
|
constructor(options = {}) {
|
|
30958
31759
|
const publicWorkspace = normalizeWorkspace2(options.workspace);
|
|
30959
|
-
const rootPath = options.rootPath ?? (publicWorkspace === void 0 ?
|
|
30960
|
-
rootDir:
|
|
31760
|
+
const rootPath = options.rootPath ?? (publicWorkspace === void 0 ? path10__default.default.resolve(options.rootDir ?? process.cwd(), ".opensteer", "temporary", crypto.randomUUID()) : resolveFilesystemWorkspacePath({
|
|
31761
|
+
rootDir: path10__default.default.resolve(options.rootDir ?? process.cwd()),
|
|
30961
31762
|
workspace: publicWorkspace
|
|
30962
31763
|
}));
|
|
30963
31764
|
const cleanupRootOnClose = options.cleanupRootOnClose ?? publicWorkspace === void 0;
|
|
@@ -31044,7 +31845,7 @@ var init_runtime_resolution = __esm({
|
|
|
31044
31845
|
}
|
|
31045
31846
|
});
|
|
31046
31847
|
function resolveOpensteerEnvironment(cwd = process.cwd(), baseEnv = process.env) {
|
|
31047
|
-
const resolvedCwd =
|
|
31848
|
+
const resolvedCwd = path10__default.default.resolve(cwd);
|
|
31048
31849
|
const signature = buildEnvironmentSignature(baseEnv, isOpensteerEnvironmentKey);
|
|
31049
31850
|
const cached = opensteerEnvironmentCache.get(resolvedCwd);
|
|
31050
31851
|
if (cached && cached.signature === signature) {
|
|
@@ -31058,17 +31859,17 @@ function resolveOpensteerEnvironment(cwd = process.cwd(), baseEnv = process.env)
|
|
|
31058
31859
|
return { ...resolved };
|
|
31059
31860
|
}
|
|
31060
31861
|
function loadEnvironment(cwd = process.cwd()) {
|
|
31061
|
-
const resolved = resolveEnvironmentFiles(
|
|
31862
|
+
const resolved = resolveEnvironmentFiles(path10__default.default.resolve(cwd), process.env);
|
|
31062
31863
|
for (const [key, value] of Object.entries(resolved)) {
|
|
31063
31864
|
process.env[key] = value;
|
|
31064
31865
|
}
|
|
31065
31866
|
}
|
|
31066
31867
|
function collectDirectories(cwd) {
|
|
31067
31868
|
const directories = [];
|
|
31068
|
-
let current =
|
|
31869
|
+
let current = path10__default.default.resolve(cwd);
|
|
31069
31870
|
for (; ; ) {
|
|
31070
31871
|
directories.unshift(current);
|
|
31071
|
-
const parent =
|
|
31872
|
+
const parent = path10__default.default.dirname(current);
|
|
31072
31873
|
if (parent === current) {
|
|
31073
31874
|
return directories;
|
|
31074
31875
|
}
|
|
@@ -31111,7 +31912,7 @@ function resolveEnvironmentFiles(cwd, baseEnv, predicate) {
|
|
|
31111
31912
|
const directories = collectDirectories(cwd);
|
|
31112
31913
|
for (const directory of directories) {
|
|
31113
31914
|
for (const filename of ENV_FILENAMES) {
|
|
31114
|
-
const filePath =
|
|
31915
|
+
const filePath = path10__default.default.join(directory, filename);
|
|
31115
31916
|
if (!fs.existsSync(filePath)) {
|
|
31116
31917
|
continue;
|
|
31117
31918
|
}
|
|
@@ -31151,6 +31952,52 @@ var init_env = __esm({
|
|
|
31151
31952
|
}
|
|
31152
31953
|
});
|
|
31153
31954
|
|
|
31955
|
+
// src/local-view/session-control.ts
|
|
31956
|
+
var session_control_exports = {};
|
|
31957
|
+
__export(session_control_exports, {
|
|
31958
|
+
LocalViewSessionCloseError: () => LocalViewSessionCloseError,
|
|
31959
|
+
closeLocalViewSessionBrowser: () => closeLocalViewSessionBrowser
|
|
31960
|
+
});
|
|
31961
|
+
async function closeLocalViewSessionBrowser(sessionId) {
|
|
31962
|
+
const manifest = await readLocalViewSessionManifest(sessionId);
|
|
31963
|
+
if (!manifest) {
|
|
31964
|
+
throw new LocalViewSessionCloseError("Session not found.", 404);
|
|
31965
|
+
}
|
|
31966
|
+
if (manifest.ownership !== "owned") {
|
|
31967
|
+
throw new LocalViewSessionCloseError(
|
|
31968
|
+
"Only Opensteer-owned local browsers can be closed from the local view.",
|
|
31969
|
+
409
|
|
31970
|
+
);
|
|
31971
|
+
}
|
|
31972
|
+
const record = await readPersistedLocalBrowserSessionRecord(manifest.rootPath);
|
|
31973
|
+
if (!record || record.pid !== manifest.pid || record.startedAt !== manifest.startedAt || record.engine !== manifest.engine) {
|
|
31974
|
+
await deleteLocalViewSessionManifest(sessionId).catch(() => void 0);
|
|
31975
|
+
throw new LocalViewSessionCloseError("Session not found.", 404);
|
|
31976
|
+
}
|
|
31977
|
+
const manager = new OpensteerBrowserManager({
|
|
31978
|
+
rootPath: manifest.rootPath,
|
|
31979
|
+
...manifest.workspace === void 0 ? {} : { workspace: manifest.workspace },
|
|
31980
|
+
engineName: record.engine,
|
|
31981
|
+
browser: "persistent"
|
|
31982
|
+
});
|
|
31983
|
+
await manager.close();
|
|
31984
|
+
}
|
|
31985
|
+
var LocalViewSessionCloseError;
|
|
31986
|
+
var init_session_control = __esm({
|
|
31987
|
+
"src/local-view/session-control.ts"() {
|
|
31988
|
+
init_live_session();
|
|
31989
|
+
init_browser_manager();
|
|
31990
|
+
init_session_manifest();
|
|
31991
|
+
LocalViewSessionCloseError = class extends Error {
|
|
31992
|
+
constructor(message, statusCode) {
|
|
31993
|
+
super(message);
|
|
31994
|
+
this.statusCode = statusCode;
|
|
31995
|
+
this.name = "LocalViewSessionCloseError";
|
|
31996
|
+
}
|
|
31997
|
+
};
|
|
31998
|
+
}
|
|
31999
|
+
});
|
|
32000
|
+
|
|
31154
32001
|
// src/sdk/opensteer.ts
|
|
31155
32002
|
var opensteer_exports = {};
|
|
31156
32003
|
__export(opensteer_exports, {
|
|
@@ -31250,7 +32097,7 @@ function decodeBody(response) {
|
|
|
31250
32097
|
}
|
|
31251
32098
|
return Uint8Array.from(Buffer.from(response.body.data, "base64"));
|
|
31252
32099
|
}
|
|
31253
|
-
function
|
|
32100
|
+
function delay5(ms) {
|
|
31254
32101
|
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
31255
32102
|
}
|
|
31256
32103
|
var SessionCookieJar, Opensteer;
|
|
@@ -31432,7 +32279,7 @@ var init_opensteer = __esm({
|
|
|
31432
32279
|
if (Date.now() >= timeoutAt) {
|
|
31433
32280
|
throw new Error("waitForNetwork timed out");
|
|
31434
32281
|
}
|
|
31435
|
-
await
|
|
32282
|
+
await delay5(pollInterval);
|
|
31436
32283
|
}
|
|
31437
32284
|
}
|
|
31438
32285
|
async waitForResponse(input) {
|
|
@@ -31461,7 +32308,7 @@ var init_opensteer = __esm({
|
|
|
31461
32308
|
if (Date.now() >= timeoutAt) {
|
|
31462
32309
|
throw new Error("waitForPage timed out");
|
|
31463
32310
|
}
|
|
31464
|
-
await
|
|
32311
|
+
await delay5(pollIntervalMs);
|
|
31465
32312
|
}
|
|
31466
32313
|
}
|
|
31467
32314
|
async snapshot(mode = "action") {
|
|
@@ -31524,7 +32371,7 @@ var init_opensteer = __esm({
|
|
|
31524
32371
|
|
|
31525
32372
|
// package.json
|
|
31526
32373
|
var package_default = {
|
|
31527
|
-
version: "0.9.
|
|
32374
|
+
version: "0.9.1"};
|
|
31528
32375
|
|
|
31529
32376
|
// src/cli/bin.ts
|
|
31530
32377
|
init_browser_manager();
|
|
@@ -31547,6 +32394,10 @@ Session:
|
|
|
31547
32394
|
open <url> [--workspace <id>] [--headless] [--provider local|cloud]
|
|
31548
32395
|
close
|
|
31549
32396
|
status
|
|
32397
|
+
view [--workspace <id>] [--json]
|
|
32398
|
+
view stop [--json]
|
|
32399
|
+
view --auto [--json]
|
|
32400
|
+
view --no-auto [--json]
|
|
31550
32401
|
|
|
31551
32402
|
Navigation:
|
|
31552
32403
|
goto <url> [--capture-network <label>]
|
|
@@ -31671,6 +32522,9 @@ function resolveCommandLength(tokens) {
|
|
|
31671
32522
|
if (tokens[0] === "skills") {
|
|
31672
32523
|
return Math.min(tokens.length, 2);
|
|
31673
32524
|
}
|
|
32525
|
+
if (tokens[0] === "view") {
|
|
32526
|
+
return Math.min(tokens.length, 2);
|
|
32527
|
+
}
|
|
31674
32528
|
if (tokens[0] === "status" || tokens[0] === "record" || tokens[0] === "exec") {
|
|
31675
32529
|
return 1;
|
|
31676
32530
|
}
|
|
@@ -31702,6 +32556,8 @@ var CLI_OPTION_SPECS = {
|
|
|
31702
32556
|
copy: { kind: "boolean" },
|
|
31703
32557
|
all: { kind: "boolean" },
|
|
31704
32558
|
list: { kind: "boolean" },
|
|
32559
|
+
auto: { kind: "boolean" },
|
|
32560
|
+
"no-auto": { kind: "boolean" },
|
|
31705
32561
|
"attach-endpoint": { kind: "value" },
|
|
31706
32562
|
"attach-header": { kind: "value", multiple: true },
|
|
31707
32563
|
"fresh-tab": { kind: "boolean" },
|
|
@@ -31863,6 +32719,14 @@ function parseCommandLine(argv) {
|
|
|
31863
32719
|
const json = readOptionalBoolean(rawOptions, "json");
|
|
31864
32720
|
const agents = rawOptions.get("agent");
|
|
31865
32721
|
const skills = rawOptions.get("skill");
|
|
32722
|
+
const autoLocalView = readOptionalBoolean(rawOptions, "auto");
|
|
32723
|
+
const noAutoLocalView = readOptionalBoolean(rawOptions, "no-auto");
|
|
32724
|
+
if (autoLocalView === true && noAutoLocalView === true) {
|
|
32725
|
+
throw new Error('Options "--auto" and "--no-auto" cannot be combined.');
|
|
32726
|
+
}
|
|
32727
|
+
if (command[0] !== "view" && (autoLocalView !== void 0 || noAutoLocalView !== void 0)) {
|
|
32728
|
+
throw new Error('Options "--auto" and "--no-auto" are only supported with "view".');
|
|
32729
|
+
}
|
|
31866
32730
|
const global = readOptionalBoolean(rawOptions, "global");
|
|
31867
32731
|
const yes = readOptionalBoolean(rawOptions, "yes");
|
|
31868
32732
|
const copy = readOptionalBoolean(rawOptions, "copy");
|
|
@@ -31885,6 +32749,7 @@ function parseCommandLine(argv) {
|
|
|
31885
32749
|
...json === void 0 ? {} : { json },
|
|
31886
32750
|
...agents === void 0 ? {} : { agents },
|
|
31887
32751
|
...skills === void 0 ? {} : { skills },
|
|
32752
|
+
...autoLocalView === true ? { localViewMode: "auto" } : noAutoLocalView === true ? { localViewMode: "manual" } : {},
|
|
31888
32753
|
...global === void 0 ? {} : { global },
|
|
31889
32754
|
...yes === void 0 ? {} : { yes },
|
|
31890
32755
|
...copy === void 0 ? {} : { copy },
|
|
@@ -32111,7 +32976,7 @@ async function buildOperationInput(operation, parsed, runtime) {
|
|
|
32111
32976
|
const capture = readSingle(parsed.rawOptions, "capture");
|
|
32112
32977
|
const url = readSingle(parsed.rawOptions, "url");
|
|
32113
32978
|
const hostname = readSingle(parsed.rawOptions, "hostname");
|
|
32114
|
-
const
|
|
32979
|
+
const path24 = readSingle(parsed.rawOptions, "path");
|
|
32115
32980
|
const method = readSingle(parsed.rawOptions, "method");
|
|
32116
32981
|
const status = readOptionalNumber(parsed.rawOptions, "status");
|
|
32117
32982
|
const resourceType = readSingle(parsed.rawOptions, "type");
|
|
@@ -32123,7 +32988,7 @@ async function buildOperationInput(operation, parsed, runtime) {
|
|
|
32123
32988
|
...capture === void 0 ? {} : { capture },
|
|
32124
32989
|
...url === void 0 ? {} : { url },
|
|
32125
32990
|
...hostname === void 0 ? {} : { hostname },
|
|
32126
|
-
...
|
|
32991
|
+
...path24 === void 0 ? {} : { path: path24 },
|
|
32127
32992
|
...method === void 0 ? {} : { method },
|
|
32128
32993
|
...status === void 0 ? {} : { status },
|
|
32129
32994
|
...resourceType === void 0 ? {} : { resourceType },
|
|
@@ -33214,7 +34079,7 @@ async function runOpensteerRecordCommand(input) {
|
|
|
33214
34079
|
workspace: input.workspace,
|
|
33215
34080
|
startUrl: opened.url
|
|
33216
34081
|
});
|
|
33217
|
-
await promises.mkdir(
|
|
34082
|
+
await promises.mkdir(path10__default.default.dirname(outputPath), { recursive: true });
|
|
33218
34083
|
await promises.writeFile(outputPath, script, "utf8");
|
|
33219
34084
|
if (input.closeSession !== void 0) {
|
|
33220
34085
|
await input.closeSession();
|
|
@@ -33249,7 +34114,7 @@ async function runOpensteerCloudRecordCommand(input) {
|
|
|
33249
34114
|
workspace: input.workspace
|
|
33250
34115
|
});
|
|
33251
34116
|
const client = input.client ?? resolveCloud();
|
|
33252
|
-
const
|
|
34117
|
+
const sleep6 = input.sleep ?? delay4;
|
|
33253
34118
|
const openUrl = input.openUrl ?? openBrowserUrl;
|
|
33254
34119
|
let closed = false;
|
|
33255
34120
|
try {
|
|
@@ -33275,12 +34140,12 @@ async function runOpensteerCloudRecordCommand(input) {
|
|
|
33275
34140
|
client,
|
|
33276
34141
|
sessionId,
|
|
33277
34142
|
...input.pollIntervalMs === void 0 ? {} : { pollIntervalMs: input.pollIntervalMs },
|
|
33278
|
-
sleep:
|
|
34143
|
+
sleep: sleep6
|
|
33279
34144
|
});
|
|
33280
34145
|
if (completed.result === void 0) {
|
|
33281
34146
|
throw new Error("Cloud recording completed without a replay script.");
|
|
33282
34147
|
}
|
|
33283
|
-
await promises.mkdir(
|
|
34148
|
+
await promises.mkdir(path10__default.default.dirname(outputPath), { recursive: true });
|
|
33284
34149
|
await promises.writeFile(outputPath, completed.result.script, "utf8");
|
|
33285
34150
|
await runtime.close();
|
|
33286
34151
|
closed = true;
|
|
@@ -33298,9 +34163,9 @@ async function runOpensteerCloudRecordCommand(input) {
|
|
|
33298
34163
|
}
|
|
33299
34164
|
function resolveRecordOutputPath(input) {
|
|
33300
34165
|
if (input.outputPath !== void 0) {
|
|
33301
|
-
return
|
|
34166
|
+
return path10__default.default.resolve(input.rootDir, input.outputPath);
|
|
33302
34167
|
}
|
|
33303
|
-
return
|
|
34168
|
+
return path10__default.default.join(
|
|
33304
34169
|
resolveFilesystemWorkspacePath({
|
|
33305
34170
|
rootDir: input.rootDir,
|
|
33306
34171
|
workspace: input.workspace
|
|
@@ -33394,7 +34259,7 @@ function formatRecordedAction(action) {
|
|
|
33394
34259
|
return `[${time}] reload ${action.pageId}`;
|
|
33395
34260
|
}
|
|
33396
34261
|
}
|
|
33397
|
-
function
|
|
34262
|
+
function delay4(ms) {
|
|
33398
34263
|
return new Promise((resolve4) => {
|
|
33399
34264
|
setTimeout(resolve4, ms);
|
|
33400
34265
|
});
|
|
@@ -33433,25 +34298,40 @@ function createOpensteerSkillsInvocation(input) {
|
|
|
33433
34298
|
function resolveOpensteerSkillsCliPath() {
|
|
33434
34299
|
const require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bin.cjs', document.baseURI).href)));
|
|
33435
34300
|
const skillsPackagePath = require2.resolve("skills/package.json");
|
|
33436
|
-
const skillsPackageDir =
|
|
33437
|
-
const cliPath =
|
|
34301
|
+
const skillsPackageDir = path10__default.default.dirname(skillsPackagePath);
|
|
34302
|
+
const cliPath = path10__default.default.join(skillsPackageDir, "bin", "cli.mjs");
|
|
33438
34303
|
if (!fs.existsSync(cliPath)) {
|
|
33439
34304
|
throw new Error(`skills CLI entrypoint was not found at "${cliPath}".`);
|
|
33440
34305
|
}
|
|
33441
34306
|
return cliPath;
|
|
33442
34307
|
}
|
|
33443
34308
|
function resolveOpensteerLocalSkillSourcePath() {
|
|
33444
|
-
let ancestor =
|
|
34309
|
+
let ancestor = path10__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bin.cjs', document.baseURI).href))));
|
|
33445
34310
|
for (let index = 0; index < 6; index += 1) {
|
|
33446
|
-
const candidate =
|
|
33447
|
-
const skillManifest =
|
|
34311
|
+
const candidate = path10__default.default.join(ancestor, "skills");
|
|
34312
|
+
const skillManifest = path10__default.default.join(candidate, "opensteer", "SKILL.md");
|
|
33448
34313
|
if (fs.existsSync(skillManifest)) {
|
|
33449
34314
|
return candidate;
|
|
33450
34315
|
}
|
|
33451
|
-
ancestor =
|
|
34316
|
+
ancestor = path10__default.default.resolve(ancestor, "..");
|
|
33452
34317
|
}
|
|
33453
34318
|
throw new Error("Unable to find the packaged Opensteer skill source directory.");
|
|
33454
34319
|
}
|
|
34320
|
+
function resolveOpensteerRepoSkillSourcePath(startDir = process.cwd()) {
|
|
34321
|
+
let currentDir = path10__default.default.resolve(startDir);
|
|
34322
|
+
const filesystemRoot = path10__default.default.parse(currentDir).root;
|
|
34323
|
+
while (true) {
|
|
34324
|
+
const candidate = path10__default.default.join(currentDir, "skills");
|
|
34325
|
+
const skillManifest = path10__default.default.join(candidate, "opensteer", "SKILL.md");
|
|
34326
|
+
if (fs.existsSync(skillManifest)) {
|
|
34327
|
+
return candidate;
|
|
34328
|
+
}
|
|
34329
|
+
if (currentDir === filesystemRoot) {
|
|
34330
|
+
return void 0;
|
|
34331
|
+
}
|
|
34332
|
+
currentDir = path10__default.default.dirname(currentDir);
|
|
34333
|
+
}
|
|
34334
|
+
}
|
|
33455
34335
|
async function checkOpensteerGitHubReachable() {
|
|
33456
34336
|
try {
|
|
33457
34337
|
const response = await fetch(`https://github.com/${OPENSTEER_GITHUB_SOURCE}`, {
|
|
@@ -33467,13 +34347,14 @@ async function checkOpensteerGitHubReachable() {
|
|
|
33467
34347
|
async function runOpensteerSkillsInstaller(options = {}, overrideDeps = {}) {
|
|
33468
34348
|
const deps = {
|
|
33469
34349
|
resolveSkillsCliPath: resolveOpensteerSkillsCliPath,
|
|
34350
|
+
resolveRepoSkillSourcePath: resolveOpensteerRepoSkillSourcePath,
|
|
33470
34351
|
resolveLocalSkillSourcePath: resolveOpensteerLocalSkillSourcePath,
|
|
33471
34352
|
checkGitHubReachable: checkOpensteerGitHubReachable,
|
|
33472
34353
|
spawnInvocation: spawnOpensteerSkillsInvocation,
|
|
33473
34354
|
...overrideDeps
|
|
33474
34355
|
};
|
|
33475
|
-
const
|
|
33476
|
-
const skillSourcePath =
|
|
34356
|
+
const repoSkillSourcePath = deps.resolveRepoSkillSourcePath();
|
|
34357
|
+
const skillSourcePath = repoSkillSourcePath ?? (await deps.checkGitHubReachable() ? OPENSTEER_GITHUB_SOURCE : deps.resolveLocalSkillSourcePath());
|
|
33477
34358
|
const invocation = createOpensteerSkillsInvocation({
|
|
33478
34359
|
options,
|
|
33479
34360
|
skillsCliPath: deps.resolveSkillsCliPath(),
|
|
@@ -33595,7 +34476,7 @@ function describeLocalLane(record, current) {
|
|
|
33595
34476
|
summary: "none"
|
|
33596
34477
|
};
|
|
33597
34478
|
}
|
|
33598
|
-
const browser = record.executablePath ?
|
|
34479
|
+
const browser = record.executablePath ? path10__default.default.basename(record.executablePath).replace(/\.[A-Za-z0-9]+$/u, "") : void 0;
|
|
33599
34480
|
return {
|
|
33600
34481
|
provider: "local",
|
|
33601
34482
|
status: "active",
|
|
@@ -33683,6 +34564,1890 @@ async function runExecExpression(context, expression) {
|
|
|
33683
34564
|
return fn.call(context);
|
|
33684
34565
|
}
|
|
33685
34566
|
|
|
34567
|
+
// src/cli/view.ts
|
|
34568
|
+
init_live_session();
|
|
34569
|
+
init_process_owner();
|
|
34570
|
+
init_preferences();
|
|
34571
|
+
|
|
34572
|
+
// src/local-view/discovery.ts
|
|
34573
|
+
init_filesystem2();
|
|
34574
|
+
init_live_session();
|
|
34575
|
+
init_process_owner();
|
|
34576
|
+
init_session_manifest();
|
|
34577
|
+
|
|
34578
|
+
// src/local-view/resolve-browser-websocket.ts
|
|
34579
|
+
init_cdp_discovery();
|
|
34580
|
+
async function resolveBrowserWebSocketUrl(record) {
|
|
34581
|
+
if (record.engine === "playwright") {
|
|
34582
|
+
if (!record.endpoint) {
|
|
34583
|
+
throw new Error("Local Playwright session is missing a browser WebSocket endpoint.");
|
|
34584
|
+
}
|
|
34585
|
+
return record.endpoint;
|
|
34586
|
+
}
|
|
34587
|
+
if (!record.remoteDebuggingUrl) {
|
|
34588
|
+
throw new Error("Local ABP session is missing a remote debugging URL.");
|
|
34589
|
+
}
|
|
34590
|
+
const inspected = await inspectCdpEndpoint({
|
|
34591
|
+
endpoint: record.remoteDebuggingUrl,
|
|
34592
|
+
timeoutMs: 5e3
|
|
34593
|
+
});
|
|
34594
|
+
return inspected.endpoint;
|
|
34595
|
+
}
|
|
34596
|
+
|
|
34597
|
+
// src/local-view/discovery.ts
|
|
34598
|
+
async function listResolvedLocalViewSessions() {
|
|
34599
|
+
const manifests = await listLocalViewSessionManifests();
|
|
34600
|
+
const resolved = await Promise.all(manifests.map((manifest) => resolveSessionSummary(manifest)));
|
|
34601
|
+
return resolved.filter((session) => session !== void 0).sort(
|
|
34602
|
+
(left, right) => right.startedAt - left.startedAt || left.label.localeCompare(right.label)
|
|
34603
|
+
);
|
|
34604
|
+
}
|
|
34605
|
+
async function resolveLocalViewSession(sessionId) {
|
|
34606
|
+
const manifest = await readLocalViewSessionManifest(sessionId);
|
|
34607
|
+
if (!manifest) {
|
|
34608
|
+
return void 0;
|
|
34609
|
+
}
|
|
34610
|
+
return readResolvedLocalViewSession(manifest);
|
|
34611
|
+
}
|
|
34612
|
+
async function resolveSessionSummary(manifest) {
|
|
34613
|
+
const record = await readLiveRecord(manifest);
|
|
34614
|
+
if (!record) {
|
|
34615
|
+
await deleteLocalViewSessionManifest(manifest.sessionId);
|
|
34616
|
+
return void 0;
|
|
34617
|
+
}
|
|
34618
|
+
const browserName = record.executablePath ? path10__default.default.basename(record.executablePath).replace(/\.[A-Za-z0-9]+$/u, "") : void 0;
|
|
34619
|
+
return {
|
|
34620
|
+
sessionId: manifest.sessionId,
|
|
34621
|
+
label: manifest.workspace ?? (path10__default.default.basename(manifest.rootPath) || manifest.sessionId),
|
|
34622
|
+
status: isProcessRunning(record.pid) ? "live" : "stale",
|
|
34623
|
+
...manifest.workspace === void 0 ? {} : { workspace: manifest.workspace },
|
|
34624
|
+
rootPath: manifest.rootPath,
|
|
34625
|
+
engine: record.engine,
|
|
34626
|
+
ownership: manifest.ownership,
|
|
34627
|
+
pid: record.pid,
|
|
34628
|
+
startedAt: record.startedAt,
|
|
34629
|
+
...browserName === void 0 ? {} : { browserName }
|
|
34630
|
+
};
|
|
34631
|
+
}
|
|
34632
|
+
async function readResolvedLocalViewSession(manifest) {
|
|
34633
|
+
const record = await readLiveRecord(manifest);
|
|
34634
|
+
if (!record) {
|
|
34635
|
+
await deleteLocalViewSessionManifest(manifest.sessionId);
|
|
34636
|
+
return void 0;
|
|
34637
|
+
}
|
|
34638
|
+
const browserWebSocketUrl = await resolveBrowserWebSocketUrl(record).catch(() => void 0);
|
|
34639
|
+
if (!browserWebSocketUrl) {
|
|
34640
|
+
return void 0;
|
|
34641
|
+
}
|
|
34642
|
+
return {
|
|
34643
|
+
manifest,
|
|
34644
|
+
record,
|
|
34645
|
+
browserWebSocketUrl
|
|
34646
|
+
};
|
|
34647
|
+
}
|
|
34648
|
+
async function readLiveRecord(manifest) {
|
|
34649
|
+
if (!await pathExists(manifest.rootPath)) {
|
|
34650
|
+
return void 0;
|
|
34651
|
+
}
|
|
34652
|
+
const record = await readPersistedLocalBrowserSessionRecord(manifest.rootPath);
|
|
34653
|
+
if (!record) {
|
|
34654
|
+
return void 0;
|
|
34655
|
+
}
|
|
34656
|
+
if (record.pid !== manifest.pid || record.startedAt !== manifest.startedAt || !isProcessRunning(record.pid)) {
|
|
34657
|
+
return void 0;
|
|
34658
|
+
}
|
|
34659
|
+
return record;
|
|
34660
|
+
}
|
|
34661
|
+
|
|
34662
|
+
// src/local-view/runtime-state.ts
|
|
34663
|
+
var LocalViewRuntimeState = class {
|
|
34664
|
+
activationIntentBySessionId = /* @__PURE__ */ new Map();
|
|
34665
|
+
setPageActivationIntent(sessionId, targetId) {
|
|
34666
|
+
this.activationIntentBySessionId.set(sessionId, {
|
|
34667
|
+
targetId,
|
|
34668
|
+
ts: Date.now()
|
|
34669
|
+
});
|
|
34670
|
+
}
|
|
34671
|
+
getPageActivationIntent(sessionId) {
|
|
34672
|
+
return this.activationIntentBySessionId.get(sessionId);
|
|
34673
|
+
}
|
|
34674
|
+
clearPageActivationIntent(sessionId, targetId) {
|
|
34675
|
+
const current = this.activationIntentBySessionId.get(sessionId);
|
|
34676
|
+
if (!current) {
|
|
34677
|
+
return;
|
|
34678
|
+
}
|
|
34679
|
+
if (targetId !== void 0 && current.targetId !== targetId) {
|
|
34680
|
+
return;
|
|
34681
|
+
}
|
|
34682
|
+
this.activationIntentBySessionId.delete(sessionId);
|
|
34683
|
+
}
|
|
34684
|
+
};
|
|
34685
|
+
var LocalViewWebSocketServer = WebSocket4__namespace.WebSocketServer;
|
|
34686
|
+
|
|
34687
|
+
// src/local-view/cdp-proxy.ts
|
|
34688
|
+
var DEFAULT_MAX_PENDING_CLIENT_BUFFER_BYTES = 1e6;
|
|
34689
|
+
var DEFAULT_UPSTREAM_OPEN_TIMEOUT_MS = 1e4;
|
|
34690
|
+
var LocalViewCdpProxy = class {
|
|
34691
|
+
constructor(deps) {
|
|
34692
|
+
this.deps = deps;
|
|
34693
|
+
this.wss = new LocalViewWebSocketServer({ noServer: true });
|
|
34694
|
+
this.createUpstreamSocket = deps.createUpstreamSocket ?? ((url) => new WebSocket4__namespace.default(url));
|
|
34695
|
+
this.maxPendingClientBufferBytes = deps.maxPendingClientBufferBytes ?? DEFAULT_MAX_PENDING_CLIENT_BUFFER_BYTES;
|
|
34696
|
+
this.upstreamOpenTimeoutMs = deps.upstreamOpenTimeoutMs ?? DEFAULT_UPSTREAM_OPEN_TIMEOUT_MS;
|
|
34697
|
+
}
|
|
34698
|
+
wss;
|
|
34699
|
+
createUpstreamSocket;
|
|
34700
|
+
maxPendingClientBufferBytes;
|
|
34701
|
+
upstreamOpenTimeoutMs;
|
|
34702
|
+
handleUpgrade(req, socket, head) {
|
|
34703
|
+
const url = new URL(req.url || "/", "http://localhost");
|
|
34704
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
34705
|
+
const isCdpPath = parts.length === 3 && parts[0] === "ws" && parts[1] === "cdp";
|
|
34706
|
+
if (!isCdpPath) {
|
|
34707
|
+
socket.destroy();
|
|
34708
|
+
return;
|
|
34709
|
+
}
|
|
34710
|
+
const sessionId = parts[2];
|
|
34711
|
+
this.wss.handleUpgrade(req, socket, head, (clientSocket) => {
|
|
34712
|
+
void this.bindProxy(clientSocket, sessionId).catch(() => {
|
|
34713
|
+
safeCloseSocket(clientSocket);
|
|
34714
|
+
});
|
|
34715
|
+
});
|
|
34716
|
+
}
|
|
34717
|
+
close() {
|
|
34718
|
+
for (const client of this.wss.clients) {
|
|
34719
|
+
safeCloseSocket(client);
|
|
34720
|
+
}
|
|
34721
|
+
this.wss.close();
|
|
34722
|
+
}
|
|
34723
|
+
async bindProxy(clientSocket, sessionId) {
|
|
34724
|
+
const resolved = await resolveLocalViewSession(sessionId);
|
|
34725
|
+
if (!resolved) {
|
|
34726
|
+
safeCloseSocket(clientSocket);
|
|
34727
|
+
return;
|
|
34728
|
+
}
|
|
34729
|
+
const upstream = this.createUpstreamSocket(resolved.browserWebSocketUrl);
|
|
34730
|
+
const pendingCreateTargetCommandIds = /* @__PURE__ */ new Set();
|
|
34731
|
+
const pendingAttachTargetCommandTargetIds = /* @__PURE__ */ new Map();
|
|
34732
|
+
const targetIdByAttachedSessionId = /* @__PURE__ */ new Map();
|
|
34733
|
+
const pendingClientMessages = [];
|
|
34734
|
+
let pendingClientBufferBytes = 0;
|
|
34735
|
+
let closed = false;
|
|
34736
|
+
let upstreamOpenTimeout = null;
|
|
34737
|
+
const closeConnection = () => {
|
|
34738
|
+
if (closed) {
|
|
34739
|
+
return;
|
|
34740
|
+
}
|
|
34741
|
+
closed = true;
|
|
34742
|
+
if (upstreamOpenTimeout) {
|
|
34743
|
+
clearTimeout(upstreamOpenTimeout);
|
|
34744
|
+
upstreamOpenTimeout = null;
|
|
34745
|
+
}
|
|
34746
|
+
pendingClientMessages.length = 0;
|
|
34747
|
+
pendingClientBufferBytes = 0;
|
|
34748
|
+
safeCloseSocket(upstream);
|
|
34749
|
+
safeCloseSocket(clientSocket);
|
|
34750
|
+
};
|
|
34751
|
+
upstreamOpenTimeout = setTimeout(() => {
|
|
34752
|
+
if (upstream.readyState === WebSocket4__namespace.default.OPEN) {
|
|
34753
|
+
return;
|
|
34754
|
+
}
|
|
34755
|
+
closeConnection();
|
|
34756
|
+
}, this.upstreamOpenTimeoutMs);
|
|
34757
|
+
clientSocket.on("message", (data, isBinary) => {
|
|
34758
|
+
const outboundData = data;
|
|
34759
|
+
if (!isBinary) {
|
|
34760
|
+
const message = parseCdpProtocolMessage(data);
|
|
34761
|
+
if (message) {
|
|
34762
|
+
const activatedTargetId = readActivateTargetCommandTargetId(message);
|
|
34763
|
+
if (activatedTargetId) {
|
|
34764
|
+
this.deps.runtimeState.setPageActivationIntent(sessionId, activatedTargetId);
|
|
34765
|
+
}
|
|
34766
|
+
const createTargetCommandId = readCreateTargetCommandId(message);
|
|
34767
|
+
if (createTargetCommandId !== null) {
|
|
34768
|
+
pendingCreateTargetCommandIds.add(createTargetCommandId);
|
|
34769
|
+
}
|
|
34770
|
+
const attachTargetCommand = readAttachTargetCommand(message);
|
|
34771
|
+
if (attachTargetCommand) {
|
|
34772
|
+
pendingAttachTargetCommandTargetIds.set(
|
|
34773
|
+
attachTargetCommand.id,
|
|
34774
|
+
attachTargetCommand.targetId
|
|
34775
|
+
);
|
|
34776
|
+
}
|
|
34777
|
+
const interactionTargetId = readInteractionTargetId(message, targetIdByAttachedSessionId);
|
|
34778
|
+
if (interactionTargetId) {
|
|
34779
|
+
this.deps.runtimeState.setPageActivationIntent(sessionId, interactionTargetId);
|
|
34780
|
+
}
|
|
34781
|
+
}
|
|
34782
|
+
}
|
|
34783
|
+
if (upstream.readyState === WebSocket4__namespace.default.OPEN) {
|
|
34784
|
+
upstream.send(outboundData, { binary: isBinary });
|
|
34785
|
+
return;
|
|
34786
|
+
}
|
|
34787
|
+
if (upstream.readyState !== WebSocket4__namespace.default.CONNECTING) {
|
|
34788
|
+
closeConnection();
|
|
34789
|
+
return;
|
|
34790
|
+
}
|
|
34791
|
+
const sizeBytes = rawDataSizeBytes(outboundData);
|
|
34792
|
+
if (pendingClientBufferBytes + sizeBytes > this.maxPendingClientBufferBytes) {
|
|
34793
|
+
closeConnection();
|
|
34794
|
+
return;
|
|
34795
|
+
}
|
|
34796
|
+
pendingClientMessages.push({ data: outboundData, isBinary });
|
|
34797
|
+
pendingClientBufferBytes += sizeBytes;
|
|
34798
|
+
});
|
|
34799
|
+
upstream.on("open", () => {
|
|
34800
|
+
if (upstreamOpenTimeout) {
|
|
34801
|
+
clearTimeout(upstreamOpenTimeout);
|
|
34802
|
+
upstreamOpenTimeout = null;
|
|
34803
|
+
}
|
|
34804
|
+
for (const pendingMessage of pendingClientMessages.splice(0)) {
|
|
34805
|
+
upstream.send(pendingMessage.data, { binary: pendingMessage.isBinary });
|
|
34806
|
+
}
|
|
34807
|
+
pendingClientBufferBytes = 0;
|
|
34808
|
+
});
|
|
34809
|
+
upstream.on("message", (data, isBinary) => {
|
|
34810
|
+
if (!isBinary) {
|
|
34811
|
+
const message = parseCdpProtocolMessage(data);
|
|
34812
|
+
if (message) {
|
|
34813
|
+
const createdTargetId = readCreateTargetResultTargetId(
|
|
34814
|
+
message,
|
|
34815
|
+
pendingCreateTargetCommandIds
|
|
34816
|
+
);
|
|
34817
|
+
if (createdTargetId) {
|
|
34818
|
+
this.deps.runtimeState.setPageActivationIntent(sessionId, createdTargetId);
|
|
34819
|
+
}
|
|
34820
|
+
const attachedTarget = readAttachTargetResult(
|
|
34821
|
+
message,
|
|
34822
|
+
pendingAttachTargetCommandTargetIds
|
|
34823
|
+
);
|
|
34824
|
+
if (attachedTarget) {
|
|
34825
|
+
targetIdByAttachedSessionId.set(attachedTarget.sessionId, attachedTarget.targetId);
|
|
34826
|
+
}
|
|
34827
|
+
const detachedSessionId = readDetachedTargetSessionId(message);
|
|
34828
|
+
if (detachedSessionId) {
|
|
34829
|
+
targetIdByAttachedSessionId.delete(detachedSessionId);
|
|
34830
|
+
}
|
|
34831
|
+
}
|
|
34832
|
+
}
|
|
34833
|
+
if (clientSocket.readyState === WebSocket4__namespace.default.OPEN) {
|
|
34834
|
+
clientSocket.send(data, { binary: isBinary });
|
|
34835
|
+
}
|
|
34836
|
+
});
|
|
34837
|
+
clientSocket.on("close", closeConnection);
|
|
34838
|
+
clientSocket.on("error", closeConnection);
|
|
34839
|
+
upstream.on("close", closeConnection);
|
|
34840
|
+
upstream.on("error", closeConnection);
|
|
34841
|
+
}
|
|
34842
|
+
};
|
|
34843
|
+
function parseCdpProtocolMessage(data) {
|
|
34844
|
+
try {
|
|
34845
|
+
const parsed = JSON.parse(rawDataToString(data));
|
|
34846
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
34847
|
+
} catch {
|
|
34848
|
+
return null;
|
|
34849
|
+
}
|
|
34850
|
+
}
|
|
34851
|
+
function readCreateTargetCommandId(message) {
|
|
34852
|
+
return message.method === "Target.createTarget" && typeof message.id === "number" ? message.id : null;
|
|
34853
|
+
}
|
|
34854
|
+
function readCreateTargetResultTargetId(message, pendingCommandIds) {
|
|
34855
|
+
if (typeof message.id !== "number" || !pendingCommandIds.has(message.id)) {
|
|
34856
|
+
return null;
|
|
34857
|
+
}
|
|
34858
|
+
pendingCommandIds.delete(message.id);
|
|
34859
|
+
const targetId = message.result?.targetId;
|
|
34860
|
+
return typeof targetId === "string" && targetId.length > 0 ? targetId : null;
|
|
34861
|
+
}
|
|
34862
|
+
function readActivateTargetCommandTargetId(message) {
|
|
34863
|
+
const targetId = message.method === "Target.activateTarget" ? message.params?.targetId : void 0;
|
|
34864
|
+
return typeof targetId === "string" && targetId.length > 0 ? targetId : null;
|
|
34865
|
+
}
|
|
34866
|
+
function readAttachTargetCommand(message) {
|
|
34867
|
+
if (message.method !== "Target.attachToTarget" || typeof message.id !== "number") {
|
|
34868
|
+
return null;
|
|
34869
|
+
}
|
|
34870
|
+
const targetId = message.params?.targetId;
|
|
34871
|
+
if (typeof targetId !== "string" || targetId.length === 0) {
|
|
34872
|
+
return null;
|
|
34873
|
+
}
|
|
34874
|
+
return {
|
|
34875
|
+
id: message.id,
|
|
34876
|
+
targetId
|
|
34877
|
+
};
|
|
34878
|
+
}
|
|
34879
|
+
function readAttachTargetResult(message, pendingTargetIds) {
|
|
34880
|
+
if (typeof message.id !== "number") {
|
|
34881
|
+
return null;
|
|
34882
|
+
}
|
|
34883
|
+
const targetId = pendingTargetIds.get(message.id);
|
|
34884
|
+
if (!targetId) {
|
|
34885
|
+
return null;
|
|
34886
|
+
}
|
|
34887
|
+
pendingTargetIds.delete(message.id);
|
|
34888
|
+
const sessionId = message.result?.sessionId;
|
|
34889
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
34890
|
+
return null;
|
|
34891
|
+
}
|
|
34892
|
+
return {
|
|
34893
|
+
sessionId,
|
|
34894
|
+
targetId
|
|
34895
|
+
};
|
|
34896
|
+
}
|
|
34897
|
+
function readInteractionTargetId(message, targetIdByAttachedSessionId) {
|
|
34898
|
+
const sessionId = message.sessionId;
|
|
34899
|
+
if (typeof sessionId !== "string" || sessionId.length === 0) {
|
|
34900
|
+
return null;
|
|
34901
|
+
}
|
|
34902
|
+
if (!message.method || !message.method.startsWith("Input.") && !message.method.startsWith("Page.")) {
|
|
34903
|
+
return null;
|
|
34904
|
+
}
|
|
34905
|
+
return targetIdByAttachedSessionId.get(sessionId) ?? null;
|
|
34906
|
+
}
|
|
34907
|
+
function readDetachedTargetSessionId(message) {
|
|
34908
|
+
if (message.method !== "Target.detachedFromTarget") {
|
|
34909
|
+
return null;
|
|
34910
|
+
}
|
|
34911
|
+
const sessionId = message.params?.sessionId;
|
|
34912
|
+
return typeof sessionId === "string" && sessionId.length > 0 ? sessionId : null;
|
|
34913
|
+
}
|
|
34914
|
+
function rawDataToString(data) {
|
|
34915
|
+
if (typeof data === "string") {
|
|
34916
|
+
return data;
|
|
34917
|
+
}
|
|
34918
|
+
if (data instanceof ArrayBuffer) {
|
|
34919
|
+
return Buffer.from(data).toString("utf8");
|
|
34920
|
+
}
|
|
34921
|
+
if (Array.isArray(data)) {
|
|
34922
|
+
return Buffer.concat(data).toString("utf8");
|
|
34923
|
+
}
|
|
34924
|
+
return data.toString("utf8");
|
|
34925
|
+
}
|
|
34926
|
+
function rawDataSizeBytes(data) {
|
|
34927
|
+
if (typeof data === "string") {
|
|
34928
|
+
return Buffer.byteLength(data);
|
|
34929
|
+
}
|
|
34930
|
+
if (data instanceof ArrayBuffer) {
|
|
34931
|
+
return data.byteLength;
|
|
34932
|
+
}
|
|
34933
|
+
if (Array.isArray(data)) {
|
|
34934
|
+
return data.reduce((total, entry) => total + entry.byteLength, 0);
|
|
34935
|
+
}
|
|
34936
|
+
return data.byteLength;
|
|
34937
|
+
}
|
|
34938
|
+
function safeCloseSocket(socket) {
|
|
34939
|
+
try {
|
|
34940
|
+
socket.close();
|
|
34941
|
+
} catch {
|
|
34942
|
+
}
|
|
34943
|
+
}
|
|
34944
|
+
|
|
34945
|
+
// src/local-view/server.ts
|
|
34946
|
+
init_process_owner();
|
|
34947
|
+
init_service_state();
|
|
34948
|
+
init_service_state();
|
|
34949
|
+
|
|
34950
|
+
// src/local-view/tab-state-tracker.ts
|
|
34951
|
+
var ACTIVATION_INTENT_DISCOVERY_GRACE_MS = 2e3;
|
|
34952
|
+
var TabStateTracker = class {
|
|
34953
|
+
deps;
|
|
34954
|
+
timer = null;
|
|
34955
|
+
running = false;
|
|
34956
|
+
lastActivePage = null;
|
|
34957
|
+
lastTabsSignature = "";
|
|
34958
|
+
tickInFlight = false;
|
|
34959
|
+
metadataByPage = /* @__PURE__ */ new Map();
|
|
34960
|
+
targetIdByPage = /* @__PURE__ */ new WeakMap();
|
|
34961
|
+
pageCleanupByPage = /* @__PURE__ */ new Map();
|
|
34962
|
+
boundContextCleanup = null;
|
|
34963
|
+
constructor(deps) {
|
|
34964
|
+
this.deps = deps;
|
|
34965
|
+
}
|
|
34966
|
+
start() {
|
|
34967
|
+
if (this.running) {
|
|
34968
|
+
return;
|
|
34969
|
+
}
|
|
34970
|
+
this.running = true;
|
|
34971
|
+
this.bindContextEvents();
|
|
34972
|
+
void this.reconcile({
|
|
34973
|
+
includeFocus: true,
|
|
34974
|
+
refreshMetadata: true
|
|
34975
|
+
});
|
|
34976
|
+
}
|
|
34977
|
+
stop() {
|
|
34978
|
+
this.running = false;
|
|
34979
|
+
if (this.timer) {
|
|
34980
|
+
clearInterval(this.timer);
|
|
34981
|
+
this.timer = null;
|
|
34982
|
+
}
|
|
34983
|
+
this.boundContextCleanup?.();
|
|
34984
|
+
this.boundContextCleanup = null;
|
|
34985
|
+
for (const cleanup of this.pageCleanupByPage.values()) {
|
|
34986
|
+
cleanup();
|
|
34987
|
+
}
|
|
34988
|
+
this.pageCleanupByPage.clear();
|
|
34989
|
+
this.metadataByPage.clear();
|
|
34990
|
+
}
|
|
34991
|
+
bindContextEvents() {
|
|
34992
|
+
if (this.boundContextCleanup) {
|
|
34993
|
+
this.syncTrackedPages(this.deps.browserContext.pages());
|
|
34994
|
+
this.updatePolling(this.deps.browserContext.pages().length);
|
|
34995
|
+
return;
|
|
34996
|
+
}
|
|
34997
|
+
const onPage = (page) => {
|
|
34998
|
+
this.syncTrackedPages(this.deps.browserContext.pages());
|
|
34999
|
+
this.updatePolling(this.deps.browserContext.pages().length);
|
|
35000
|
+
this.attachPageListeners(page);
|
|
35001
|
+
void this.reconcile({
|
|
35002
|
+
includeFocus: true,
|
|
35003
|
+
refreshMetadata: true
|
|
35004
|
+
});
|
|
35005
|
+
};
|
|
35006
|
+
this.deps.browserContext.on("page", onPage);
|
|
35007
|
+
this.boundContextCleanup = () => {
|
|
35008
|
+
this.deps.browserContext.off("page", onPage);
|
|
35009
|
+
};
|
|
35010
|
+
this.syncTrackedPages(this.deps.browserContext.pages());
|
|
35011
|
+
this.updatePolling(this.deps.browserContext.pages().length);
|
|
35012
|
+
}
|
|
35013
|
+
syncTrackedPages(pages) {
|
|
35014
|
+
const nextPages = new Set(pages);
|
|
35015
|
+
for (const [page, cleanup] of this.pageCleanupByPage.entries()) {
|
|
35016
|
+
if (nextPages.has(page)) {
|
|
35017
|
+
continue;
|
|
35018
|
+
}
|
|
35019
|
+
cleanup();
|
|
35020
|
+
this.pageCleanupByPage.delete(page);
|
|
35021
|
+
this.metadataByPage.delete(page);
|
|
35022
|
+
}
|
|
35023
|
+
for (const page of pages) {
|
|
35024
|
+
this.attachPageListeners(page);
|
|
35025
|
+
}
|
|
35026
|
+
}
|
|
35027
|
+
attachPageListeners(page) {
|
|
35028
|
+
if (this.pageCleanupByPage.has(page)) {
|
|
35029
|
+
return;
|
|
35030
|
+
}
|
|
35031
|
+
const refreshMetadata = () => {
|
|
35032
|
+
void this.reconcile({
|
|
35033
|
+
includeFocus: false,
|
|
35034
|
+
refreshMetadata: true
|
|
35035
|
+
});
|
|
35036
|
+
};
|
|
35037
|
+
const handleClose = () => {
|
|
35038
|
+
this.pageCleanupByPage.get(page)?.();
|
|
35039
|
+
this.pageCleanupByPage.delete(page);
|
|
35040
|
+
this.metadataByPage.delete(page);
|
|
35041
|
+
void this.reconcile({
|
|
35042
|
+
includeFocus: true,
|
|
35043
|
+
refreshMetadata: true
|
|
35044
|
+
});
|
|
35045
|
+
};
|
|
35046
|
+
page.on("close", handleClose);
|
|
35047
|
+
page.on("domcontentloaded", refreshMetadata);
|
|
35048
|
+
page.on("load", refreshMetadata);
|
|
35049
|
+
page.on("framenavigated", refreshMetadata);
|
|
35050
|
+
this.pageCleanupByPage.set(page, () => {
|
|
35051
|
+
page.off("close", handleClose);
|
|
35052
|
+
page.off("domcontentloaded", refreshMetadata);
|
|
35053
|
+
page.off("load", refreshMetadata);
|
|
35054
|
+
page.off("framenavigated", refreshMetadata);
|
|
35055
|
+
});
|
|
35056
|
+
}
|
|
35057
|
+
updatePolling(pageCount) {
|
|
35058
|
+
const shouldPoll = this.running && pageCount > 0;
|
|
35059
|
+
if (!shouldPoll) {
|
|
35060
|
+
if (this.timer) {
|
|
35061
|
+
clearInterval(this.timer);
|
|
35062
|
+
this.timer = null;
|
|
35063
|
+
}
|
|
35064
|
+
return;
|
|
35065
|
+
}
|
|
35066
|
+
if (this.timer) {
|
|
35067
|
+
return;
|
|
35068
|
+
}
|
|
35069
|
+
this.timer = setInterval(() => {
|
|
35070
|
+
const trackedPageCount = this.deps.browserContext.pages().length;
|
|
35071
|
+
void this.reconcile({
|
|
35072
|
+
includeFocus: trackedPageCount > 1,
|
|
35073
|
+
refreshMetadata: true
|
|
35074
|
+
});
|
|
35075
|
+
}, this.deps.pollMs);
|
|
35076
|
+
}
|
|
35077
|
+
async reconcile(args) {
|
|
35078
|
+
if (!this.running || this.tickInFlight) {
|
|
35079
|
+
return;
|
|
35080
|
+
}
|
|
35081
|
+
this.tickInFlight = true;
|
|
35082
|
+
try {
|
|
35083
|
+
this.bindContextEvents();
|
|
35084
|
+
const pages = this.deps.browserContext.pages();
|
|
35085
|
+
this.syncTrackedPages(pages);
|
|
35086
|
+
this.updatePolling(pages.length);
|
|
35087
|
+
const preferredActivePage = this.lastActivePage ?? pages[0] ?? null;
|
|
35088
|
+
const pageStates = await Promise.all(
|
|
35089
|
+
pages.map(async (page, index) => {
|
|
35090
|
+
const metadata = await this.readPageMetadata(page, {
|
|
35091
|
+
refresh: args.refreshMetadata
|
|
35092
|
+
});
|
|
35093
|
+
const focusState = args.includeFocus ? await this.readFocusState(page) : {
|
|
35094
|
+
isVisible: page === preferredActivePage,
|
|
35095
|
+
hasFocus: page === preferredActivePage
|
|
35096
|
+
};
|
|
35097
|
+
return {
|
|
35098
|
+
page,
|
|
35099
|
+
index,
|
|
35100
|
+
targetId: metadata.targetId,
|
|
35101
|
+
url: page.url(),
|
|
35102
|
+
title: metadata.title,
|
|
35103
|
+
isVisible: focusState.isVisible,
|
|
35104
|
+
hasFocus: focusState.hasFocus
|
|
35105
|
+
};
|
|
35106
|
+
})
|
|
35107
|
+
);
|
|
35108
|
+
const activePage = this.pickActivePage(
|
|
35109
|
+
pageStates,
|
|
35110
|
+
this.lastActivePage,
|
|
35111
|
+
preferredActivePage,
|
|
35112
|
+
this.resolveIntentPage(pageStates)
|
|
35113
|
+
);
|
|
35114
|
+
if (activePage && activePage !== this.lastActivePage) {
|
|
35115
|
+
this.lastActivePage = activePage;
|
|
35116
|
+
this.deps.onActivePageChanged(activePage);
|
|
35117
|
+
}
|
|
35118
|
+
const tabs = pageStates.map((state) => ({
|
|
35119
|
+
index: state.index,
|
|
35120
|
+
...state.targetId === void 0 ? {} : { targetId: state.targetId },
|
|
35121
|
+
url: state.url,
|
|
35122
|
+
title: state.title,
|
|
35123
|
+
active: activePage ? state.page === activePage : false
|
|
35124
|
+
}));
|
|
35125
|
+
const activeTabIndex = tabs.findIndex((tab) => tab.active);
|
|
35126
|
+
const signature = JSON.stringify({
|
|
35127
|
+
activeTabIndex,
|
|
35128
|
+
tabs: tabs.map((tab) => ({
|
|
35129
|
+
index: tab.index,
|
|
35130
|
+
targetId: tab.targetId,
|
|
35131
|
+
url: tab.url,
|
|
35132
|
+
title: tab.title,
|
|
35133
|
+
active: tab.active
|
|
35134
|
+
}))
|
|
35135
|
+
});
|
|
35136
|
+
if (signature !== this.lastTabsSignature) {
|
|
35137
|
+
this.lastTabsSignature = signature;
|
|
35138
|
+
this.deps.onTabsChanged({
|
|
35139
|
+
tabs,
|
|
35140
|
+
activeTabIndex
|
|
35141
|
+
});
|
|
35142
|
+
}
|
|
35143
|
+
} finally {
|
|
35144
|
+
this.tickInFlight = false;
|
|
35145
|
+
}
|
|
35146
|
+
}
|
|
35147
|
+
async readPageMetadata(page, options) {
|
|
35148
|
+
const cached = this.metadataByPage.get(page);
|
|
35149
|
+
if (cached && !options.refresh) {
|
|
35150
|
+
return cached;
|
|
35151
|
+
}
|
|
35152
|
+
const [title, targetId] = await Promise.all([
|
|
35153
|
+
page.title().catch(() => cached?.title ?? ""),
|
|
35154
|
+
this.resolveTargetId(page).catch(() => cached?.targetId)
|
|
35155
|
+
]);
|
|
35156
|
+
const nextMetadata = {
|
|
35157
|
+
title,
|
|
35158
|
+
targetId: targetId ?? void 0
|
|
35159
|
+
};
|
|
35160
|
+
this.metadataByPage.set(page, nextMetadata);
|
|
35161
|
+
return nextMetadata;
|
|
35162
|
+
}
|
|
35163
|
+
async resolveTargetId(page) {
|
|
35164
|
+
const cached = this.targetIdByPage.get(page);
|
|
35165
|
+
if (cached) {
|
|
35166
|
+
return cached;
|
|
35167
|
+
}
|
|
35168
|
+
const cdp = await page.context().newCDPSession(page);
|
|
35169
|
+
try {
|
|
35170
|
+
const result = await cdp.send("Target.getTargetInfo");
|
|
35171
|
+
const targetId = result?.targetInfo?.targetId;
|
|
35172
|
+
if (typeof targetId === "string" && targetId.length > 0) {
|
|
35173
|
+
this.targetIdByPage.set(page, targetId);
|
|
35174
|
+
return targetId;
|
|
35175
|
+
}
|
|
35176
|
+
return null;
|
|
35177
|
+
} finally {
|
|
35178
|
+
await cdp.detach().catch(() => void 0);
|
|
35179
|
+
}
|
|
35180
|
+
}
|
|
35181
|
+
async readFocusState(page) {
|
|
35182
|
+
try {
|
|
35183
|
+
const result = await page.evaluate(() => ({
|
|
35184
|
+
visibilityState: globalThis.document?.visibilityState,
|
|
35185
|
+
hasFocus: globalThis.document?.hasFocus?.() ?? false
|
|
35186
|
+
}));
|
|
35187
|
+
return {
|
|
35188
|
+
isVisible: result.visibilityState === "visible",
|
|
35189
|
+
hasFocus: result.hasFocus === true
|
|
35190
|
+
};
|
|
35191
|
+
} catch {
|
|
35192
|
+
return {
|
|
35193
|
+
isVisible: false,
|
|
35194
|
+
hasFocus: false
|
|
35195
|
+
};
|
|
35196
|
+
}
|
|
35197
|
+
}
|
|
35198
|
+
pickActivePage(pageStates, lastActivePage, fallbackPage, intent) {
|
|
35199
|
+
if (intent) {
|
|
35200
|
+
return intent.page;
|
|
35201
|
+
}
|
|
35202
|
+
const focusedVisiblePages = pageStates.filter((state) => state.isVisible && state.hasFocus);
|
|
35203
|
+
if (focusedVisiblePages.length === 1) {
|
|
35204
|
+
return focusedVisiblePages[0]?.page ?? null;
|
|
35205
|
+
}
|
|
35206
|
+
const visiblePages = pageStates.filter((state) => state.isVisible);
|
|
35207
|
+
if (visiblePages.length === 1) {
|
|
35208
|
+
return visiblePages[0]?.page ?? null;
|
|
35209
|
+
}
|
|
35210
|
+
const lastActivePageState = lastActivePage ? pageStates.find((state) => state.page === lastActivePage) ?? null : null;
|
|
35211
|
+
if (lastActivePageState && (focusedVisiblePages.length > 1 && lastActivePageState.isVisible && lastActivePageState.hasFocus || visiblePages.length > 1 && lastActivePageState.isVisible || visiblePages.length === 0)) {
|
|
35212
|
+
return lastActivePage;
|
|
35213
|
+
}
|
|
35214
|
+
const fallbackPageState = fallbackPage ? pageStates.find((state) => state.page === fallbackPage) ?? null : null;
|
|
35215
|
+
if (fallbackPageState && (focusedVisiblePages.length > 1 && fallbackPageState.isVisible && fallbackPageState.hasFocus || visiblePages.length > 1 && fallbackPageState.isVisible)) {
|
|
35216
|
+
return fallbackPage;
|
|
35217
|
+
}
|
|
35218
|
+
if (focusedVisiblePages.length > 0) {
|
|
35219
|
+
return focusedVisiblePages[0]?.page ?? null;
|
|
35220
|
+
}
|
|
35221
|
+
if (visiblePages.length > 0) {
|
|
35222
|
+
return visiblePages[0]?.page ?? null;
|
|
35223
|
+
}
|
|
35224
|
+
if (lastActivePageState) {
|
|
35225
|
+
return lastActivePageState.page;
|
|
35226
|
+
}
|
|
35227
|
+
if (fallbackPageState) {
|
|
35228
|
+
return fallbackPageState.page;
|
|
35229
|
+
}
|
|
35230
|
+
return pageStates[0]?.page ?? null;
|
|
35231
|
+
}
|
|
35232
|
+
resolveIntentPage(pageStates) {
|
|
35233
|
+
const intent = this.deps.runtimeState.getPageActivationIntent(this.deps.sessionId);
|
|
35234
|
+
if (!intent) {
|
|
35235
|
+
return null;
|
|
35236
|
+
}
|
|
35237
|
+
const matched = pageStates.find((state) => state.targetId === intent.targetId);
|
|
35238
|
+
if (!matched) {
|
|
35239
|
+
if (Date.now() - intent.ts > ACTIVATION_INTENT_DISCOVERY_GRACE_MS) {
|
|
35240
|
+
this.deps.runtimeState.clearPageActivationIntent(this.deps.sessionId, intent.targetId);
|
|
35241
|
+
}
|
|
35242
|
+
return null;
|
|
35243
|
+
}
|
|
35244
|
+
this.deps.runtimeState.clearPageActivationIntent(this.deps.sessionId, intent.targetId);
|
|
35245
|
+
return { page: matched.page };
|
|
35246
|
+
}
|
|
35247
|
+
};
|
|
35248
|
+
|
|
35249
|
+
// src/local-view/view-stream-capture-policy.ts
|
|
35250
|
+
var MIN_CAPTURE_DIMENSION_PX = 100;
|
|
35251
|
+
var MAX_CAPTURE_DIMENSION_PX = 8192;
|
|
35252
|
+
var CAPTURE_BUCKET_PX = 64;
|
|
35253
|
+
function selectScreencastSize(args) {
|
|
35254
|
+
const viewport = normalizeViewport(args.viewport);
|
|
35255
|
+
if (!viewport) {
|
|
35256
|
+
return null;
|
|
35257
|
+
}
|
|
35258
|
+
let maxRequestedWidth = 0;
|
|
35259
|
+
let maxRequestedHeight = 0;
|
|
35260
|
+
for (const requestedSize of args.requestedSizes) {
|
|
35261
|
+
const normalized = normalizeRequestedSize(requestedSize);
|
|
35262
|
+
if (!normalized) {
|
|
35263
|
+
return null;
|
|
35264
|
+
}
|
|
35265
|
+
maxRequestedWidth = Math.max(maxRequestedWidth, normalized.width);
|
|
35266
|
+
maxRequestedHeight = Math.max(maxRequestedHeight, normalized.height);
|
|
35267
|
+
}
|
|
35268
|
+
if (maxRequestedWidth < MIN_CAPTURE_DIMENSION_PX || maxRequestedHeight < MIN_CAPTURE_DIMENSION_PX) {
|
|
35269
|
+
return null;
|
|
35270
|
+
}
|
|
35271
|
+
const desiredScale = Math.max(
|
|
35272
|
+
maxRequestedWidth / viewport.width,
|
|
35273
|
+
maxRequestedHeight / viewport.height
|
|
35274
|
+
);
|
|
35275
|
+
if (desiredScale >= 1) {
|
|
35276
|
+
return viewport;
|
|
35277
|
+
}
|
|
35278
|
+
const landscape = viewport.width >= viewport.height;
|
|
35279
|
+
const sourcePrimary = landscape ? viewport.width : viewport.height;
|
|
35280
|
+
const sourceSecondary = landscape ? viewport.height : viewport.width;
|
|
35281
|
+
const nextPrimary = bucketDimension(sourcePrimary * desiredScale);
|
|
35282
|
+
const nextSecondary = clampDimension(Math.round(nextPrimary / sourcePrimary * sourceSecondary));
|
|
35283
|
+
if (!nextSecondary) {
|
|
35284
|
+
return null;
|
|
35285
|
+
}
|
|
35286
|
+
return landscape ? { width: nextPrimary, height: nextSecondary } : { width: nextSecondary, height: nextPrimary };
|
|
35287
|
+
}
|
|
35288
|
+
function normalizeViewport(viewport) {
|
|
35289
|
+
const width = clampDimension(viewport.width);
|
|
35290
|
+
const height = clampDimension(viewport.height);
|
|
35291
|
+
return width && height ? { width, height } : null;
|
|
35292
|
+
}
|
|
35293
|
+
function normalizeRequestedSize(requestedSize) {
|
|
35294
|
+
const width = clampDimension(requestedSize.width);
|
|
35295
|
+
const height = clampDimension(requestedSize.height);
|
|
35296
|
+
return width && height ? { width, height } : null;
|
|
35297
|
+
}
|
|
35298
|
+
function clampDimension(value) {
|
|
35299
|
+
if (!Number.isFinite(value)) {
|
|
35300
|
+
return null;
|
|
35301
|
+
}
|
|
35302
|
+
const normalized = Math.floor(value);
|
|
35303
|
+
if (normalized < MIN_CAPTURE_DIMENSION_PX) {
|
|
35304
|
+
return null;
|
|
35305
|
+
}
|
|
35306
|
+
return Math.min(MAX_CAPTURE_DIMENSION_PX, normalized);
|
|
35307
|
+
}
|
|
35308
|
+
function bucketDimension(value) {
|
|
35309
|
+
const bucketed = Math.ceil(Math.max(MIN_CAPTURE_DIMENSION_PX, value) / CAPTURE_BUCKET_PX) * CAPTURE_BUCKET_PX;
|
|
35310
|
+
return Math.min(MAX_CAPTURE_DIMENSION_PX, bucketed);
|
|
35311
|
+
}
|
|
35312
|
+
function buildHelloMessage(args) {
|
|
35313
|
+
return {
|
|
35314
|
+
type: "hello",
|
|
35315
|
+
sessionId: args.sessionId,
|
|
35316
|
+
ts: Date.now(),
|
|
35317
|
+
mimeType: "image/jpeg",
|
|
35318
|
+
fps: args.fps,
|
|
35319
|
+
quality: args.quality,
|
|
35320
|
+
viewport: args.viewport
|
|
35321
|
+
};
|
|
35322
|
+
}
|
|
35323
|
+
function buildTabsMessage(args) {
|
|
35324
|
+
return {
|
|
35325
|
+
type: "tabs",
|
|
35326
|
+
sessionId: args.sessionId,
|
|
35327
|
+
ts: Date.now(),
|
|
35328
|
+
tabs: args.tabs,
|
|
35329
|
+
activeTabIndex: args.activeTabIndex
|
|
35330
|
+
};
|
|
35331
|
+
}
|
|
35332
|
+
function buildStatusMessage(args) {
|
|
35333
|
+
return {
|
|
35334
|
+
type: "status",
|
|
35335
|
+
sessionId: args.sessionId,
|
|
35336
|
+
ts: Date.now(),
|
|
35337
|
+
status: args.status
|
|
35338
|
+
};
|
|
35339
|
+
}
|
|
35340
|
+
function buildErrorMessage(args) {
|
|
35341
|
+
return {
|
|
35342
|
+
type: "error",
|
|
35343
|
+
sessionId: args.sessionId,
|
|
35344
|
+
ts: Date.now(),
|
|
35345
|
+
error: args.error
|
|
35346
|
+
};
|
|
35347
|
+
}
|
|
35348
|
+
function sendControlMessage(ws, message) {
|
|
35349
|
+
if (ws.readyState !== WebSocket4__namespace.default.OPEN) {
|
|
35350
|
+
return;
|
|
35351
|
+
}
|
|
35352
|
+
try {
|
|
35353
|
+
ws.send(JSON.stringify(message), { binary: false });
|
|
35354
|
+
} catch {
|
|
35355
|
+
}
|
|
35356
|
+
}
|
|
35357
|
+
function parseViewClientMessage(raw) {
|
|
35358
|
+
try {
|
|
35359
|
+
const parsed = JSON.parse(raw);
|
|
35360
|
+
if (parsed?.type !== "stream-config") {
|
|
35361
|
+
return null;
|
|
35362
|
+
}
|
|
35363
|
+
const renderWidth = normalizeRenderDimension(parsed.renderWidth);
|
|
35364
|
+
const renderHeight = normalizeRenderDimension(parsed.renderHeight);
|
|
35365
|
+
if (renderWidth === null || renderHeight === null) {
|
|
35366
|
+
return null;
|
|
35367
|
+
}
|
|
35368
|
+
return {
|
|
35369
|
+
type: "stream-config",
|
|
35370
|
+
renderWidth,
|
|
35371
|
+
renderHeight
|
|
35372
|
+
};
|
|
35373
|
+
} catch {
|
|
35374
|
+
return null;
|
|
35375
|
+
}
|
|
35376
|
+
}
|
|
35377
|
+
function normalizeRenderDimension(value) {
|
|
35378
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
35379
|
+
return null;
|
|
35380
|
+
}
|
|
35381
|
+
const normalized = Math.floor(value);
|
|
35382
|
+
if (normalized < 100) {
|
|
35383
|
+
return null;
|
|
35384
|
+
}
|
|
35385
|
+
return Math.min(8192, normalized);
|
|
35386
|
+
}
|
|
35387
|
+
|
|
35388
|
+
// src/local-view/view-stream.ts
|
|
35389
|
+
var INITIAL_FRAME_CAPTURE_ATTEMPTS = 3;
|
|
35390
|
+
var INITIAL_FRAME_CAPTURE_RETRY_DELAY_MS = 150;
|
|
35391
|
+
var TAB_STATE_POLL_MS = 1e3;
|
|
35392
|
+
var CLIENT_FRAME_FLUSH_RETRY_MS = 16;
|
|
35393
|
+
var LocalViewStreamHub = class {
|
|
35394
|
+
deps;
|
|
35395
|
+
producers = /* @__PURE__ */ new Map();
|
|
35396
|
+
constructor(deps) {
|
|
35397
|
+
this.deps = deps;
|
|
35398
|
+
}
|
|
35399
|
+
attachClient(sessionId, ws) {
|
|
35400
|
+
let producer = this.producers.get(sessionId);
|
|
35401
|
+
if (!producer) {
|
|
35402
|
+
producer = new SessionViewStreamProducer({
|
|
35403
|
+
sessionId,
|
|
35404
|
+
runtimeState: this.deps.runtimeState,
|
|
35405
|
+
maxFps: this.deps.maxFps,
|
|
35406
|
+
quality: this.deps.quality,
|
|
35407
|
+
maxClientBufferBytes: this.deps.maxClientBufferBytes,
|
|
35408
|
+
onDrained: () => {
|
|
35409
|
+
this.producers.delete(sessionId);
|
|
35410
|
+
}
|
|
35411
|
+
});
|
|
35412
|
+
this.producers.set(sessionId, producer);
|
|
35413
|
+
}
|
|
35414
|
+
producer.addClient(ws);
|
|
35415
|
+
}
|
|
35416
|
+
};
|
|
35417
|
+
var SessionViewStreamProducer = class {
|
|
35418
|
+
deps;
|
|
35419
|
+
clients = /* @__PURE__ */ new Set();
|
|
35420
|
+
clientStateBySocket = /* @__PURE__ */ new Map();
|
|
35421
|
+
frameIntervalMs;
|
|
35422
|
+
tracker = null;
|
|
35423
|
+
browser = null;
|
|
35424
|
+
browserDisconnectedHandler = null;
|
|
35425
|
+
context = null;
|
|
35426
|
+
cdpSession = null;
|
|
35427
|
+
screencastHandler = null;
|
|
35428
|
+
pageLifecycleCleanup = null;
|
|
35429
|
+
activePage = null;
|
|
35430
|
+
activeViewport = null;
|
|
35431
|
+
activeScreencastSizeKey = null;
|
|
35432
|
+
pendingFrameAckTimer = null;
|
|
35433
|
+
starting = null;
|
|
35434
|
+
started = false;
|
|
35435
|
+
rebinding = Promise.resolve();
|
|
35436
|
+
stopped = false;
|
|
35437
|
+
lastFrameSentAt = 0;
|
|
35438
|
+
lastFrameBuffer = null;
|
|
35439
|
+
lastTabsPayload = null;
|
|
35440
|
+
constructor(deps) {
|
|
35441
|
+
this.deps = deps;
|
|
35442
|
+
this.frameIntervalMs = Math.max(1, Math.floor(1e3 / Math.max(1, deps.maxFps)));
|
|
35443
|
+
}
|
|
35444
|
+
addClient(ws) {
|
|
35445
|
+
if (this.stopped) {
|
|
35446
|
+
ws.close(1011, "View stream is unavailable.");
|
|
35447
|
+
return;
|
|
35448
|
+
}
|
|
35449
|
+
this.clients.add(ws);
|
|
35450
|
+
this.clientStateBySocket.set(ws, {
|
|
35451
|
+
requestedRenderSize: null,
|
|
35452
|
+
frameSendInFlight: false,
|
|
35453
|
+
pendingFrameBuffer: null,
|
|
35454
|
+
pendingFlushTimer: null
|
|
35455
|
+
});
|
|
35456
|
+
if (this.activeViewport) {
|
|
35457
|
+
sendControlMessage(
|
|
35458
|
+
ws,
|
|
35459
|
+
buildHelloMessage({
|
|
35460
|
+
sessionId: this.deps.sessionId,
|
|
35461
|
+
fps: this.deps.maxFps,
|
|
35462
|
+
quality: this.deps.quality,
|
|
35463
|
+
viewport: this.activeViewport
|
|
35464
|
+
})
|
|
35465
|
+
);
|
|
35466
|
+
}
|
|
35467
|
+
if (this.lastTabsPayload) {
|
|
35468
|
+
sendControlMessage(
|
|
35469
|
+
ws,
|
|
35470
|
+
buildTabsMessage({
|
|
35471
|
+
sessionId: this.deps.sessionId,
|
|
35472
|
+
tabs: this.lastTabsPayload.tabs,
|
|
35473
|
+
activeTabIndex: this.lastTabsPayload.activeTabIndex
|
|
35474
|
+
})
|
|
35475
|
+
);
|
|
35476
|
+
}
|
|
35477
|
+
if (this.lastFrameBuffer) {
|
|
35478
|
+
const queued = this.enqueueFrameForClient(ws, this.lastFrameBuffer);
|
|
35479
|
+
if (!queued) {
|
|
35480
|
+
this.removeClient(ws);
|
|
35481
|
+
return;
|
|
35482
|
+
}
|
|
35483
|
+
}
|
|
35484
|
+
ws.on("close", () => {
|
|
35485
|
+
this.removeClient(ws);
|
|
35486
|
+
});
|
|
35487
|
+
ws.on("error", () => {
|
|
35488
|
+
this.removeClient(ws);
|
|
35489
|
+
});
|
|
35490
|
+
ws.on("message", (raw, isBinary) => {
|
|
35491
|
+
if (isBinary) {
|
|
35492
|
+
return;
|
|
35493
|
+
}
|
|
35494
|
+
const message = parseViewClientMessage(readTextFrame(raw));
|
|
35495
|
+
if (message?.type !== "stream-config") {
|
|
35496
|
+
return;
|
|
35497
|
+
}
|
|
35498
|
+
const nextSize = {
|
|
35499
|
+
width: message.renderWidth,
|
|
35500
|
+
height: message.renderHeight
|
|
35501
|
+
};
|
|
35502
|
+
const clientState = this.clientStateBySocket.get(ws);
|
|
35503
|
+
if (!clientState) {
|
|
35504
|
+
return;
|
|
35505
|
+
}
|
|
35506
|
+
const priorSize = clientState.requestedRenderSize;
|
|
35507
|
+
if (priorSize?.width === nextSize.width && priorSize?.height === nextSize.height) {
|
|
35508
|
+
return;
|
|
35509
|
+
}
|
|
35510
|
+
clientState.requestedRenderSize = nextSize;
|
|
35511
|
+
this.maybeRebindForStreamConfigChange();
|
|
35512
|
+
});
|
|
35513
|
+
void this.ensureStarted();
|
|
35514
|
+
}
|
|
35515
|
+
maybeRebindForStreamConfigChange() {
|
|
35516
|
+
if (!this.activePage || !this.started || this.stopped) {
|
|
35517
|
+
return;
|
|
35518
|
+
}
|
|
35519
|
+
const nextSizeKey = this.getRequestedScreencastSizeKey();
|
|
35520
|
+
if (nextSizeKey === this.activeScreencastSizeKey) {
|
|
35521
|
+
return;
|
|
35522
|
+
}
|
|
35523
|
+
void this.queueBindToPage(this.activePage, { force: true }).catch(() => void 0);
|
|
35524
|
+
}
|
|
35525
|
+
removeClient(ws) {
|
|
35526
|
+
this.clients.delete(ws);
|
|
35527
|
+
const clientState = this.clientStateBySocket.get(ws);
|
|
35528
|
+
if (clientState?.pendingFlushTimer) {
|
|
35529
|
+
clearTimeout(clientState.pendingFlushTimer);
|
|
35530
|
+
}
|
|
35531
|
+
this.clientStateBySocket.delete(ws);
|
|
35532
|
+
if (this.clients.size === 0) {
|
|
35533
|
+
void this.stop();
|
|
35534
|
+
return;
|
|
35535
|
+
}
|
|
35536
|
+
this.maybeRebindForStreamConfigChange();
|
|
35537
|
+
}
|
|
35538
|
+
async ensureStarted() {
|
|
35539
|
+
if (this.stopped || this.started) {
|
|
35540
|
+
return;
|
|
35541
|
+
}
|
|
35542
|
+
if (this.starting) {
|
|
35543
|
+
return this.starting;
|
|
35544
|
+
}
|
|
35545
|
+
this.starting = this.start().then(() => {
|
|
35546
|
+
if (!this.stopped) {
|
|
35547
|
+
this.started = true;
|
|
35548
|
+
}
|
|
35549
|
+
}).finally(() => {
|
|
35550
|
+
this.starting = null;
|
|
35551
|
+
});
|
|
35552
|
+
try {
|
|
35553
|
+
await this.starting;
|
|
35554
|
+
} catch {
|
|
35555
|
+
this.broadcastControl(
|
|
35556
|
+
buildErrorMessage({
|
|
35557
|
+
sessionId: this.deps.sessionId,
|
|
35558
|
+
error: "Failed to start live browser stream."
|
|
35559
|
+
})
|
|
35560
|
+
);
|
|
35561
|
+
this.closeAllClients(1011, "View stream failed");
|
|
35562
|
+
await this.stop();
|
|
35563
|
+
}
|
|
35564
|
+
}
|
|
35565
|
+
async start() {
|
|
35566
|
+
const session = await this.connectSession();
|
|
35567
|
+
this.broadcastControl(
|
|
35568
|
+
buildStatusMessage({
|
|
35569
|
+
sessionId: this.deps.sessionId,
|
|
35570
|
+
status: "starting"
|
|
35571
|
+
})
|
|
35572
|
+
);
|
|
35573
|
+
this.browser = session.browser;
|
|
35574
|
+
this.browserDisconnectedHandler = () => {
|
|
35575
|
+
if (this.stopped) {
|
|
35576
|
+
return;
|
|
35577
|
+
}
|
|
35578
|
+
this.browserDisconnectedHandler = null;
|
|
35579
|
+
this.broadcastControl(
|
|
35580
|
+
buildErrorMessage({
|
|
35581
|
+
sessionId: this.deps.sessionId,
|
|
35582
|
+
error: "Live browser stream disconnected."
|
|
35583
|
+
})
|
|
35584
|
+
);
|
|
35585
|
+
this.closeAllClients(1011, "View stream failed");
|
|
35586
|
+
void this.stop();
|
|
35587
|
+
};
|
|
35588
|
+
this.browser.once("disconnected", this.browserDisconnectedHandler);
|
|
35589
|
+
this.context = session.context;
|
|
35590
|
+
this.activePage = session.page;
|
|
35591
|
+
this.activeViewport = await readViewportForPage(session.page);
|
|
35592
|
+
if (this.stopped) {
|
|
35593
|
+
return;
|
|
35594
|
+
}
|
|
35595
|
+
if (this.activeViewport) {
|
|
35596
|
+
this.broadcastControl(
|
|
35597
|
+
buildHelloMessage({
|
|
35598
|
+
sessionId: this.deps.sessionId,
|
|
35599
|
+
fps: this.deps.maxFps,
|
|
35600
|
+
quality: this.deps.quality,
|
|
35601
|
+
viewport: this.activeViewport
|
|
35602
|
+
})
|
|
35603
|
+
);
|
|
35604
|
+
}
|
|
35605
|
+
this.tracker = new TabStateTracker({
|
|
35606
|
+
browserContext: session.context,
|
|
35607
|
+
sessionId: this.deps.sessionId,
|
|
35608
|
+
pollMs: TAB_STATE_POLL_MS,
|
|
35609
|
+
runtimeState: this.deps.runtimeState,
|
|
35610
|
+
onActivePageChanged: (page) => {
|
|
35611
|
+
this.activePage = page;
|
|
35612
|
+
void this.queueBindToPage(page).catch(() => void 0);
|
|
35613
|
+
},
|
|
35614
|
+
onTabsChanged: ({ tabs, activeTabIndex }) => {
|
|
35615
|
+
this.lastTabsPayload = { tabs, activeTabIndex };
|
|
35616
|
+
this.broadcastControl(
|
|
35617
|
+
buildTabsMessage({
|
|
35618
|
+
sessionId: this.deps.sessionId,
|
|
35619
|
+
tabs,
|
|
35620
|
+
activeTabIndex
|
|
35621
|
+
})
|
|
35622
|
+
);
|
|
35623
|
+
}
|
|
35624
|
+
});
|
|
35625
|
+
this.tracker.start();
|
|
35626
|
+
await this.queueBindToPage(session.page);
|
|
35627
|
+
if (this.stopped) {
|
|
35628
|
+
return;
|
|
35629
|
+
}
|
|
35630
|
+
this.broadcastControl(
|
|
35631
|
+
buildStatusMessage({
|
|
35632
|
+
sessionId: this.deps.sessionId,
|
|
35633
|
+
status: "live"
|
|
35634
|
+
})
|
|
35635
|
+
);
|
|
35636
|
+
}
|
|
35637
|
+
queueBindToPage(page, options = {}) {
|
|
35638
|
+
this.rebinding = this.rebinding.catch(() => void 0).then(() => this.bindToPage(page, options));
|
|
35639
|
+
return this.rebinding;
|
|
35640
|
+
}
|
|
35641
|
+
async bindToPage(page, options = {}) {
|
|
35642
|
+
if (this.stopped) {
|
|
35643
|
+
return;
|
|
35644
|
+
}
|
|
35645
|
+
const requestedSizeKey = this.getRequestedScreencastSizeKey();
|
|
35646
|
+
if (!options.force && this.activePage === page && this.cdpSession && this.activeScreencastSizeKey === requestedSizeKey) {
|
|
35647
|
+
return;
|
|
35648
|
+
}
|
|
35649
|
+
await this.stopScreencast();
|
|
35650
|
+
if (this.stopped) {
|
|
35651
|
+
return;
|
|
35652
|
+
}
|
|
35653
|
+
const context = this.context;
|
|
35654
|
+
if (!context) {
|
|
35655
|
+
throw new Error("Browser context is unavailable.");
|
|
35656
|
+
}
|
|
35657
|
+
const requestedSize = this.getRequestedScreencastSize();
|
|
35658
|
+
this.activePage = page;
|
|
35659
|
+
this.activeScreencastSizeKey = requestedSizeKey;
|
|
35660
|
+
this.activeViewport = await readViewportForPage(page);
|
|
35661
|
+
if (this.activeViewport) {
|
|
35662
|
+
this.broadcastControl(
|
|
35663
|
+
buildHelloMessage({
|
|
35664
|
+
sessionId: this.deps.sessionId,
|
|
35665
|
+
fps: this.deps.maxFps,
|
|
35666
|
+
quality: this.deps.quality,
|
|
35667
|
+
viewport: this.activeViewport
|
|
35668
|
+
})
|
|
35669
|
+
);
|
|
35670
|
+
}
|
|
35671
|
+
const cdpSession = await context.newCDPSession(page);
|
|
35672
|
+
if (this.stopped) {
|
|
35673
|
+
await cdpSession.detach().catch(() => void 0);
|
|
35674
|
+
return;
|
|
35675
|
+
}
|
|
35676
|
+
this.cdpSession = cdpSession;
|
|
35677
|
+
const onFrame = (event) => {
|
|
35678
|
+
void this.handleScreencastFrame(event);
|
|
35679
|
+
};
|
|
35680
|
+
this.screencastHandler = onFrame;
|
|
35681
|
+
cdpSession.on("Page.screencastFrame", onFrame);
|
|
35682
|
+
await cdpSession.send("Page.enable");
|
|
35683
|
+
await cdpSession.send("Page.startScreencast", {
|
|
35684
|
+
format: "jpeg",
|
|
35685
|
+
quality: this.deps.quality,
|
|
35686
|
+
everyNthFrame: 1,
|
|
35687
|
+
...requestedSize ? {
|
|
35688
|
+
maxWidth: requestedSize.width,
|
|
35689
|
+
maxHeight: requestedSize.height
|
|
35690
|
+
} : {}
|
|
35691
|
+
});
|
|
35692
|
+
this.bindPageLifecycleFrameRefresh(page, cdpSession);
|
|
35693
|
+
void this.seedInitialFrame(cdpSession).catch(() => void 0);
|
|
35694
|
+
}
|
|
35695
|
+
async connectSession() {
|
|
35696
|
+
const resolved = await resolveLocalViewSession(this.deps.sessionId);
|
|
35697
|
+
if (!resolved) {
|
|
35698
|
+
throw new Error(`Local view session ${this.deps.sessionId} is unavailable.`);
|
|
35699
|
+
}
|
|
35700
|
+
const browser = await connectPlaywrightChromiumBrowser2({
|
|
35701
|
+
url: resolved.browserWebSocketUrl
|
|
35702
|
+
});
|
|
35703
|
+
try {
|
|
35704
|
+
const context = browser.contexts()[0];
|
|
35705
|
+
if (!context) {
|
|
35706
|
+
throw new Error("Connected browser did not expose a Chromium browser context.");
|
|
35707
|
+
}
|
|
35708
|
+
const page = context.pages()[0] ?? await context.newPage();
|
|
35709
|
+
return {
|
|
35710
|
+
browser,
|
|
35711
|
+
context,
|
|
35712
|
+
page
|
|
35713
|
+
};
|
|
35714
|
+
} catch (error) {
|
|
35715
|
+
await disconnectPlaywrightChromiumBrowser2(browser).catch(() => void 0);
|
|
35716
|
+
throw error;
|
|
35717
|
+
}
|
|
35718
|
+
}
|
|
35719
|
+
async handleScreencastFrame(event) {
|
|
35720
|
+
const cdpSession = this.cdpSession;
|
|
35721
|
+
if (!cdpSession || this.stopped) {
|
|
35722
|
+
return;
|
|
35723
|
+
}
|
|
35724
|
+
const frameBuffer = Buffer.from(event.data, "base64");
|
|
35725
|
+
this.lastFrameBuffer = frameBuffer;
|
|
35726
|
+
const now = Date.now();
|
|
35727
|
+
const delayMs = Math.max(0, this.frameIntervalMs - (now - this.lastFrameSentAt));
|
|
35728
|
+
if (delayMs === 0) {
|
|
35729
|
+
this.flushScreencastFrame({
|
|
35730
|
+
cdpSession,
|
|
35731
|
+
sessionId: event.sessionId,
|
|
35732
|
+
frameBuffer
|
|
35733
|
+
});
|
|
35734
|
+
return;
|
|
35735
|
+
}
|
|
35736
|
+
if (this.pendingFrameAckTimer !== null) {
|
|
35737
|
+
return;
|
|
35738
|
+
}
|
|
35739
|
+
this.pendingFrameAckTimer = setTimeout(() => {
|
|
35740
|
+
this.pendingFrameAckTimer = null;
|
|
35741
|
+
if (this.stopped || this.cdpSession !== cdpSession) {
|
|
35742
|
+
return;
|
|
35743
|
+
}
|
|
35744
|
+
this.flushScreencastFrame({
|
|
35745
|
+
cdpSession,
|
|
35746
|
+
sessionId: event.sessionId,
|
|
35747
|
+
frameBuffer
|
|
35748
|
+
});
|
|
35749
|
+
}, delayMs);
|
|
35750
|
+
}
|
|
35751
|
+
flushScreencastFrame(args) {
|
|
35752
|
+
this.lastFrameSentAt = Date.now();
|
|
35753
|
+
this.broadcastFrame(args.frameBuffer);
|
|
35754
|
+
void args.cdpSession.send("Page.screencastFrameAck", { sessionId: args.sessionId }).catch(() => void 0);
|
|
35755
|
+
}
|
|
35756
|
+
broadcastFrame(frameBuffer) {
|
|
35757
|
+
for (const client of this.clients) {
|
|
35758
|
+
if (!this.enqueueFrameForClient(client, frameBuffer)) {
|
|
35759
|
+
this.removeClient(client);
|
|
35760
|
+
}
|
|
35761
|
+
}
|
|
35762
|
+
if (this.clients.size === 0) {
|
|
35763
|
+
void this.stop();
|
|
35764
|
+
}
|
|
35765
|
+
}
|
|
35766
|
+
enqueueFrameForClient(client, frameBuffer) {
|
|
35767
|
+
if (client.readyState !== WebSocket4__namespace.default.OPEN) {
|
|
35768
|
+
return false;
|
|
35769
|
+
}
|
|
35770
|
+
const clientState = this.clientStateBySocket.get(client);
|
|
35771
|
+
if (!clientState) {
|
|
35772
|
+
return false;
|
|
35773
|
+
}
|
|
35774
|
+
clientState.pendingFrameBuffer = frameBuffer;
|
|
35775
|
+
this.flushQueuedFrameToClient(client);
|
|
35776
|
+
return true;
|
|
35777
|
+
}
|
|
35778
|
+
flushQueuedFrameToClient(client) {
|
|
35779
|
+
if (client.readyState !== WebSocket4__namespace.default.OPEN) {
|
|
35780
|
+
this.removeClient(client);
|
|
35781
|
+
return;
|
|
35782
|
+
}
|
|
35783
|
+
const clientState = this.clientStateBySocket.get(client);
|
|
35784
|
+
if (!clientState || clientState.frameSendInFlight || !clientState.pendingFrameBuffer) {
|
|
35785
|
+
return;
|
|
35786
|
+
}
|
|
35787
|
+
if (clientState.pendingFlushTimer) {
|
|
35788
|
+
clearTimeout(clientState.pendingFlushTimer);
|
|
35789
|
+
clientState.pendingFlushTimer = null;
|
|
35790
|
+
}
|
|
35791
|
+
if (client.bufferedAmount > this.deps.maxClientBufferBytes) {
|
|
35792
|
+
clientState.pendingFlushTimer = setTimeout(() => {
|
|
35793
|
+
clientState.pendingFlushTimer = null;
|
|
35794
|
+
this.flushQueuedFrameToClient(client);
|
|
35795
|
+
}, CLIENT_FRAME_FLUSH_RETRY_MS);
|
|
35796
|
+
return;
|
|
35797
|
+
}
|
|
35798
|
+
const frameBuffer = clientState.pendingFrameBuffer;
|
|
35799
|
+
clientState.pendingFrameBuffer = null;
|
|
35800
|
+
clientState.frameSendInFlight = true;
|
|
35801
|
+
try {
|
|
35802
|
+
client.send(frameBuffer, { binary: true }, (error) => {
|
|
35803
|
+
const latestClientState = this.clientStateBySocket.get(client);
|
|
35804
|
+
if (latestClientState) {
|
|
35805
|
+
latestClientState.frameSendInFlight = false;
|
|
35806
|
+
}
|
|
35807
|
+
if (error) {
|
|
35808
|
+
this.removeClient(client);
|
|
35809
|
+
return;
|
|
35810
|
+
}
|
|
35811
|
+
this.flushQueuedFrameToClient(client);
|
|
35812
|
+
});
|
|
35813
|
+
} catch {
|
|
35814
|
+
clientState.frameSendInFlight = false;
|
|
35815
|
+
this.removeClient(client);
|
|
35816
|
+
}
|
|
35817
|
+
}
|
|
35818
|
+
broadcastControl(message) {
|
|
35819
|
+
for (const client of this.clients) {
|
|
35820
|
+
sendControlMessage(client, message);
|
|
35821
|
+
}
|
|
35822
|
+
}
|
|
35823
|
+
closeAllClients(code, reason) {
|
|
35824
|
+
for (const client of this.clients) {
|
|
35825
|
+
try {
|
|
35826
|
+
client.close(code, reason);
|
|
35827
|
+
} catch {
|
|
35828
|
+
}
|
|
35829
|
+
}
|
|
35830
|
+
this.clients.clear();
|
|
35831
|
+
for (const clientState of this.clientStateBySocket.values()) {
|
|
35832
|
+
if (clientState.pendingFlushTimer) {
|
|
35833
|
+
clearTimeout(clientState.pendingFlushTimer);
|
|
35834
|
+
}
|
|
35835
|
+
}
|
|
35836
|
+
this.clientStateBySocket.clear();
|
|
35837
|
+
}
|
|
35838
|
+
async stop() {
|
|
35839
|
+
if (this.stopped) {
|
|
35840
|
+
return;
|
|
35841
|
+
}
|
|
35842
|
+
this.stopped = true;
|
|
35843
|
+
this.started = false;
|
|
35844
|
+
if (this.tracker) {
|
|
35845
|
+
this.tracker.stop();
|
|
35846
|
+
this.tracker = null;
|
|
35847
|
+
}
|
|
35848
|
+
await this.rebinding.catch(() => void 0);
|
|
35849
|
+
await this.stopScreencast();
|
|
35850
|
+
const browser = this.browser;
|
|
35851
|
+
const browserDisconnectedHandler = this.browserDisconnectedHandler;
|
|
35852
|
+
this.browser = null;
|
|
35853
|
+
this.browserDisconnectedHandler = null;
|
|
35854
|
+
this.context = null;
|
|
35855
|
+
this.activePage = null;
|
|
35856
|
+
if (browser) {
|
|
35857
|
+
if (browserDisconnectedHandler) {
|
|
35858
|
+
browser.off("disconnected", browserDisconnectedHandler);
|
|
35859
|
+
}
|
|
35860
|
+
await disconnectPlaywrightChromiumBrowser2(browser).catch(() => void 0);
|
|
35861
|
+
}
|
|
35862
|
+
this.deps.onDrained();
|
|
35863
|
+
}
|
|
35864
|
+
async stopScreencast() {
|
|
35865
|
+
const cdpSession = this.cdpSession;
|
|
35866
|
+
const handler = this.screencastHandler;
|
|
35867
|
+
const pageLifecycleCleanup = this.pageLifecycleCleanup;
|
|
35868
|
+
this.cdpSession = null;
|
|
35869
|
+
this.screencastHandler = null;
|
|
35870
|
+
this.pageLifecycleCleanup = null;
|
|
35871
|
+
this.activeScreencastSizeKey = null;
|
|
35872
|
+
if (this.pendingFrameAckTimer !== null) {
|
|
35873
|
+
clearTimeout(this.pendingFrameAckTimer);
|
|
35874
|
+
this.pendingFrameAckTimer = null;
|
|
35875
|
+
}
|
|
35876
|
+
pageLifecycleCleanup?.();
|
|
35877
|
+
if (!cdpSession) {
|
|
35878
|
+
return;
|
|
35879
|
+
}
|
|
35880
|
+
if (handler) {
|
|
35881
|
+
cdpSession.off("Page.screencastFrame", handler);
|
|
35882
|
+
}
|
|
35883
|
+
await cdpSession.send("Page.stopScreencast").catch(() => void 0);
|
|
35884
|
+
await cdpSession.detach().catch(() => void 0);
|
|
35885
|
+
}
|
|
35886
|
+
bindPageLifecycleFrameRefresh(page, cdpSession) {
|
|
35887
|
+
this.pageLifecycleCleanup?.();
|
|
35888
|
+
const refresh = () => {
|
|
35889
|
+
void this.refreshPageFrame(page, cdpSession).catch(() => void 0);
|
|
35890
|
+
};
|
|
35891
|
+
page.on("domcontentloaded", refresh);
|
|
35892
|
+
page.on("load", refresh);
|
|
35893
|
+
page.on("framenavigated", refresh);
|
|
35894
|
+
this.pageLifecycleCleanup = () => {
|
|
35895
|
+
page.off("domcontentloaded", refresh);
|
|
35896
|
+
page.off("load", refresh);
|
|
35897
|
+
page.off("framenavigated", refresh);
|
|
35898
|
+
};
|
|
35899
|
+
}
|
|
35900
|
+
async refreshPageFrame(page, cdpSession) {
|
|
35901
|
+
if (this.stopped || this.cdpSession !== cdpSession || this.activePage !== page) {
|
|
35902
|
+
return;
|
|
35903
|
+
}
|
|
35904
|
+
const viewport = await readViewportForPage(page);
|
|
35905
|
+
if (viewport && this.cdpSession === cdpSession && this.activePage === page) {
|
|
35906
|
+
this.activeViewport = viewport;
|
|
35907
|
+
this.broadcastControl(
|
|
35908
|
+
buildHelloMessage({
|
|
35909
|
+
sessionId: this.deps.sessionId,
|
|
35910
|
+
fps: this.deps.maxFps,
|
|
35911
|
+
quality: this.deps.quality,
|
|
35912
|
+
viewport
|
|
35913
|
+
})
|
|
35914
|
+
);
|
|
35915
|
+
}
|
|
35916
|
+
if (this.stopped || this.cdpSession !== cdpSession || this.activePage !== page) {
|
|
35917
|
+
return;
|
|
35918
|
+
}
|
|
35919
|
+
await this.seedInitialFrame(cdpSession);
|
|
35920
|
+
}
|
|
35921
|
+
async seedInitialFrame(cdpSession) {
|
|
35922
|
+
let lastError = null;
|
|
35923
|
+
for (let attempt = 1; attempt <= INITIAL_FRAME_CAPTURE_ATTEMPTS; attempt += 1) {
|
|
35924
|
+
if (this.stopped || this.cdpSession !== cdpSession) {
|
|
35925
|
+
return;
|
|
35926
|
+
}
|
|
35927
|
+
try {
|
|
35928
|
+
const screenshotData = await this.captureCurrentFrame(cdpSession);
|
|
35929
|
+
if (this.stopped || this.cdpSession !== cdpSession) {
|
|
35930
|
+
return;
|
|
35931
|
+
}
|
|
35932
|
+
const frameBuffer = Buffer.from(screenshotData, "base64");
|
|
35933
|
+
this.lastFrameBuffer = frameBuffer;
|
|
35934
|
+
this.lastFrameSentAt = Date.now();
|
|
35935
|
+
this.broadcastFrame(frameBuffer);
|
|
35936
|
+
return;
|
|
35937
|
+
} catch (error) {
|
|
35938
|
+
lastError = error;
|
|
35939
|
+
}
|
|
35940
|
+
if (attempt < INITIAL_FRAME_CAPTURE_ATTEMPTS) {
|
|
35941
|
+
await new Promise((resolve4) => setTimeout(resolve4, INITIAL_FRAME_CAPTURE_RETRY_DELAY_MS));
|
|
35942
|
+
}
|
|
35943
|
+
}
|
|
35944
|
+
if (lastError instanceof Error) {
|
|
35945
|
+
throw lastError;
|
|
35946
|
+
}
|
|
35947
|
+
throw new Error("Failed to capture initial stream screenshot.");
|
|
35948
|
+
}
|
|
35949
|
+
async captureCurrentFrame(cdpSession) {
|
|
35950
|
+
const primaryParams = {
|
|
35951
|
+
format: "jpeg",
|
|
35952
|
+
quality: this.deps.quality,
|
|
35953
|
+
optimizeForSpeed: true
|
|
35954
|
+
};
|
|
35955
|
+
try {
|
|
35956
|
+
const result = await cdpSession.send("Page.captureScreenshot", primaryParams);
|
|
35957
|
+
if (result && typeof result.data === "string" && result.data.length > 0) {
|
|
35958
|
+
return result.data;
|
|
35959
|
+
}
|
|
35960
|
+
} catch {
|
|
35961
|
+
}
|
|
35962
|
+
const fallbackResult = await cdpSession.send("Page.captureScreenshot", {
|
|
35963
|
+
format: "jpeg",
|
|
35964
|
+
quality: this.deps.quality
|
|
35965
|
+
});
|
|
35966
|
+
if (!fallbackResult || typeof fallbackResult.data !== "string" || fallbackResult.data.length === 0) {
|
|
35967
|
+
throw new Error("Failed to capture initial stream screenshot.");
|
|
35968
|
+
}
|
|
35969
|
+
return fallbackResult.data;
|
|
35970
|
+
}
|
|
35971
|
+
getRequestedScreencastSize() {
|
|
35972
|
+
if (this.clients.size === 0 || !this.activeViewport) {
|
|
35973
|
+
return null;
|
|
35974
|
+
}
|
|
35975
|
+
const requestedSizes = [];
|
|
35976
|
+
for (const client of this.clients) {
|
|
35977
|
+
const requestedSize = this.clientStateBySocket.get(client)?.requestedRenderSize ?? null;
|
|
35978
|
+
if (!requestedSize) {
|
|
35979
|
+
return null;
|
|
35980
|
+
}
|
|
35981
|
+
requestedSizes.push(requestedSize);
|
|
35982
|
+
}
|
|
35983
|
+
return selectScreencastSize({
|
|
35984
|
+
viewport: this.activeViewport,
|
|
35985
|
+
requestedSizes
|
|
35986
|
+
});
|
|
35987
|
+
}
|
|
35988
|
+
getRequestedScreencastSizeKey() {
|
|
35989
|
+
const size = this.getRequestedScreencastSize();
|
|
35990
|
+
return size ? `${size.width}x${size.height}` : null;
|
|
35991
|
+
}
|
|
35992
|
+
};
|
|
35993
|
+
async function readViewportForPage(page) {
|
|
35994
|
+
const cdp = await page.context().newCDPSession(page);
|
|
35995
|
+
try {
|
|
35996
|
+
const result = await cdp.send("Page.getLayoutMetrics");
|
|
35997
|
+
const candidates = [
|
|
35998
|
+
result?.cssVisualViewport,
|
|
35999
|
+
result?.cssLayoutViewport,
|
|
36000
|
+
result?.visualViewport,
|
|
36001
|
+
result?.layoutViewport
|
|
36002
|
+
];
|
|
36003
|
+
for (const candidate of candidates) {
|
|
36004
|
+
const width = normalizeViewportDimension(candidate?.clientWidth);
|
|
36005
|
+
const height = normalizeViewportDimension(candidate?.clientHeight);
|
|
36006
|
+
if (width !== null && height !== null) {
|
|
36007
|
+
return { width, height };
|
|
36008
|
+
}
|
|
36009
|
+
}
|
|
36010
|
+
return null;
|
|
36011
|
+
} catch {
|
|
36012
|
+
const viewportSize = page.viewportSize();
|
|
36013
|
+
if (!viewportSize) {
|
|
36014
|
+
return null;
|
|
36015
|
+
}
|
|
36016
|
+
const width = normalizeViewportDimension(viewportSize.width);
|
|
36017
|
+
const height = normalizeViewportDimension(viewportSize.height);
|
|
36018
|
+
return width !== null && height !== null ? { width, height } : null;
|
|
36019
|
+
} finally {
|
|
36020
|
+
await cdp.detach().catch(() => void 0);
|
|
36021
|
+
}
|
|
36022
|
+
}
|
|
36023
|
+
function normalizeViewportDimension(value) {
|
|
36024
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
36025
|
+
return null;
|
|
36026
|
+
}
|
|
36027
|
+
const normalized = Math.floor(value);
|
|
36028
|
+
if (normalized < 100) {
|
|
36029
|
+
return null;
|
|
36030
|
+
}
|
|
36031
|
+
return Math.min(8192, normalized);
|
|
36032
|
+
}
|
|
36033
|
+
function readTextFrame(raw) {
|
|
36034
|
+
if (typeof raw === "string") {
|
|
36035
|
+
return raw;
|
|
36036
|
+
}
|
|
36037
|
+
if (raw instanceof ArrayBuffer) {
|
|
36038
|
+
return Buffer.from(raw).toString("utf8");
|
|
36039
|
+
}
|
|
36040
|
+
if (Array.isArray(raw)) {
|
|
36041
|
+
return Buffer.concat(raw).toString("utf8");
|
|
36042
|
+
}
|
|
36043
|
+
return raw.toString("utf8");
|
|
36044
|
+
}
|
|
36045
|
+
async function connectPlaywrightChromiumBrowser2(input) {
|
|
36046
|
+
const { connectPlaywrightChromiumBrowser: connect } = await import('@opensteer/engine-playwright');
|
|
36047
|
+
return connect(input);
|
|
36048
|
+
}
|
|
36049
|
+
async function disconnectPlaywrightChromiumBrowser2(browser) {
|
|
36050
|
+
const { disconnectPlaywrightChromiumBrowser: disconnect } = await import('@opensteer/engine-playwright');
|
|
36051
|
+
await disconnect(browser);
|
|
36052
|
+
}
|
|
36053
|
+
|
|
36054
|
+
// src/local-view/server.ts
|
|
36055
|
+
var DEFAULT_MAX_FPS = 12;
|
|
36056
|
+
var DEFAULT_QUALITY = 75;
|
|
36057
|
+
var DEFAULT_MAX_CLIENT_BUFFER_BYTES = 512 * 1024;
|
|
36058
|
+
var LOCAL_VIEW_ACCESS_EXPIRES_AT = Number.MAX_SAFE_INTEGER;
|
|
36059
|
+
async function startLocalViewServer(input = {}) {
|
|
36060
|
+
const token = input.token ?? crypto.randomBytes(24).toString("hex");
|
|
36061
|
+
const runtimeState = new LocalViewRuntimeState();
|
|
36062
|
+
const viewStreamHub = new LocalViewStreamHub({
|
|
36063
|
+
runtimeState,
|
|
36064
|
+
maxFps: DEFAULT_MAX_FPS,
|
|
36065
|
+
quality: DEFAULT_QUALITY,
|
|
36066
|
+
maxClientBufferBytes: DEFAULT_MAX_CLIENT_BUFFER_BYTES
|
|
36067
|
+
});
|
|
36068
|
+
const cdpProxy = new LocalViewCdpProxy({
|
|
36069
|
+
runtimeState
|
|
36070
|
+
});
|
|
36071
|
+
const httpServer = http.createServer((request, response) => {
|
|
36072
|
+
void handleHttpRequest({ request, response, token, shutdown: closeServer }).catch(() => {
|
|
36073
|
+
if (!response.headersSent && !response.writableEnded) {
|
|
36074
|
+
writeJson(response, 500, { error: "Internal server error." });
|
|
36075
|
+
return;
|
|
36076
|
+
}
|
|
36077
|
+
response.destroy();
|
|
36078
|
+
});
|
|
36079
|
+
});
|
|
36080
|
+
const viewWss = new LocalViewWebSocketServer({ noServer: true });
|
|
36081
|
+
viewWss.on("connection", (ws, request) => {
|
|
36082
|
+
const url2 = new URL(request.url ?? "/", "http://localhost");
|
|
36083
|
+
const parts = url2.pathname.split("/").filter(Boolean);
|
|
36084
|
+
const sessionId = parts[2];
|
|
36085
|
+
if (!sessionId) {
|
|
36086
|
+
ws.close(1008, "Session id is required.");
|
|
36087
|
+
return;
|
|
36088
|
+
}
|
|
36089
|
+
viewStreamHub.attachClient(sessionId, ws);
|
|
36090
|
+
});
|
|
36091
|
+
httpServer.on("upgrade", (request, socket, head) => {
|
|
36092
|
+
const url2 = new URL(request.url ?? "/", "http://localhost");
|
|
36093
|
+
const tokenParam = url2.searchParams.get("token");
|
|
36094
|
+
if (tokenParam !== token || !isAllowedOrigin(request.headers.origin)) {
|
|
36095
|
+
socket.destroy();
|
|
36096
|
+
return;
|
|
36097
|
+
}
|
|
36098
|
+
const parts = url2.pathname.split("/").filter(Boolean);
|
|
36099
|
+
if (parts[0] !== "ws" || parts.length !== 3) {
|
|
36100
|
+
socket.destroy();
|
|
36101
|
+
return;
|
|
36102
|
+
}
|
|
36103
|
+
if (parts[1] === "view") {
|
|
36104
|
+
viewWss.handleUpgrade(request, socket, head, (ws) => {
|
|
36105
|
+
viewWss.emit("connection", ws, request);
|
|
36106
|
+
});
|
|
36107
|
+
return;
|
|
36108
|
+
}
|
|
36109
|
+
if (parts[1] === "cdp") {
|
|
36110
|
+
cdpProxy.handleUpgrade(request, socket, head);
|
|
36111
|
+
return;
|
|
36112
|
+
}
|
|
36113
|
+
socket.destroy();
|
|
36114
|
+
});
|
|
36115
|
+
let closePromise;
|
|
36116
|
+
async function closeServer() {
|
|
36117
|
+
closePromise ??= (async () => {
|
|
36118
|
+
viewWss.clients.forEach((client) => {
|
|
36119
|
+
try {
|
|
36120
|
+
client.close();
|
|
36121
|
+
} catch {
|
|
36122
|
+
}
|
|
36123
|
+
});
|
|
36124
|
+
viewWss.close();
|
|
36125
|
+
cdpProxy.close();
|
|
36126
|
+
httpServer.close();
|
|
36127
|
+
await events.once(httpServer, "close");
|
|
36128
|
+
await clearLocalViewServiceState({ pid: process.pid, token });
|
|
36129
|
+
await input.onClosed?.();
|
|
36130
|
+
})();
|
|
36131
|
+
await closePromise;
|
|
36132
|
+
}
|
|
36133
|
+
httpServer.listen(input.port ?? 0, "127.0.0.1");
|
|
36134
|
+
await events.once(httpServer, "listening");
|
|
36135
|
+
const address = httpServer.address();
|
|
36136
|
+
if (!address || typeof address === "string") {
|
|
36137
|
+
throw new Error("Failed to resolve the local view server address.");
|
|
36138
|
+
}
|
|
36139
|
+
const url = `http://127.0.0.1:${String(address.port)}`;
|
|
36140
|
+
await writeLocalViewServiceState({
|
|
36141
|
+
layout: OPENSTEER_LOCAL_VIEW_SERVICE_LAYOUT,
|
|
36142
|
+
version: OPENSTEER_LOCAL_VIEW_SERVICE_VERSION,
|
|
36143
|
+
pid: process.pid,
|
|
36144
|
+
processStartedAtMs: CURRENT_PROCESS_OWNER.processStartedAtMs,
|
|
36145
|
+
startedAt: Date.now(),
|
|
36146
|
+
port: address.port,
|
|
36147
|
+
token,
|
|
36148
|
+
url
|
|
36149
|
+
});
|
|
36150
|
+
return {
|
|
36151
|
+
url,
|
|
36152
|
+
token,
|
|
36153
|
+
close: closeServer
|
|
36154
|
+
};
|
|
36155
|
+
}
|
|
36156
|
+
async function handleHttpRequest(args) {
|
|
36157
|
+
const url = new URL(args.request.url ?? "/", "http://localhost");
|
|
36158
|
+
if (url.pathname === "/api/health") {
|
|
36159
|
+
if (!isAuthorizedApiRequest(args.request, args.token)) {
|
|
36160
|
+
writeJson(args.response, 401, { error: "Unauthorized." });
|
|
36161
|
+
return;
|
|
36162
|
+
}
|
|
36163
|
+
writeJson(args.response, 200, { ok: true });
|
|
36164
|
+
return;
|
|
36165
|
+
}
|
|
36166
|
+
if (url.pathname === "/api/sessions") {
|
|
36167
|
+
if (!isAuthorizedApiRequest(args.request, args.token)) {
|
|
36168
|
+
writeJson(args.response, 401, { error: "Unauthorized." });
|
|
36169
|
+
return;
|
|
36170
|
+
}
|
|
36171
|
+
const sessions = await listResolvedLocalViewSessions();
|
|
36172
|
+
const payload = { sessions };
|
|
36173
|
+
writeJson(args.response, 200, payload);
|
|
36174
|
+
return;
|
|
36175
|
+
}
|
|
36176
|
+
if (url.pathname === "/api/service/stop") {
|
|
36177
|
+
if (!isAuthorizedApiRequest(args.request, args.token)) {
|
|
36178
|
+
writeJson(args.response, 401, { error: "Unauthorized." });
|
|
36179
|
+
return;
|
|
36180
|
+
}
|
|
36181
|
+
if (args.request.method !== "POST") {
|
|
36182
|
+
writeJson(args.response, 405, { error: "Method not allowed." });
|
|
36183
|
+
return;
|
|
36184
|
+
}
|
|
36185
|
+
args.response.once("finish", () => {
|
|
36186
|
+
void args.shutdown();
|
|
36187
|
+
});
|
|
36188
|
+
writeJson(args.response, 200, { stopped: true });
|
|
36189
|
+
return;
|
|
36190
|
+
}
|
|
36191
|
+
const accessMatch = url.pathname.match(/^\/api\/sessions\/([^/]+)\/access$/u);
|
|
36192
|
+
if (accessMatch) {
|
|
36193
|
+
if (!isAuthorizedApiRequest(args.request, args.token)) {
|
|
36194
|
+
writeJson(args.response, 401, { error: "Unauthorized." });
|
|
36195
|
+
return;
|
|
36196
|
+
}
|
|
36197
|
+
const sessionId = decodeURIComponent(accessMatch[1]);
|
|
36198
|
+
if (!await resolveLocalViewSession(sessionId)) {
|
|
36199
|
+
writeJson(args.response, 404, { error: "Session not found." });
|
|
36200
|
+
return;
|
|
36201
|
+
}
|
|
36202
|
+
const payload = {
|
|
36203
|
+
sessionId,
|
|
36204
|
+
expiresAt: LOCAL_VIEW_ACCESS_EXPIRES_AT,
|
|
36205
|
+
grants: {
|
|
36206
|
+
view: {
|
|
36207
|
+
kind: "view",
|
|
36208
|
+
transport: "ws",
|
|
36209
|
+
url: `${resolveWsBaseUrl(args.request)}/ws/view/${encodeURIComponent(sessionId)}`,
|
|
36210
|
+
token: args.token,
|
|
36211
|
+
expiresAt: LOCAL_VIEW_ACCESS_EXPIRES_AT
|
|
36212
|
+
},
|
|
36213
|
+
cdp: {
|
|
36214
|
+
kind: "cdp",
|
|
36215
|
+
transport: "ws",
|
|
36216
|
+
url: `${resolveWsBaseUrl(args.request)}/ws/cdp/${encodeURIComponent(sessionId)}`,
|
|
36217
|
+
token: args.token,
|
|
36218
|
+
expiresAt: LOCAL_VIEW_ACCESS_EXPIRES_AT
|
|
36219
|
+
}
|
|
36220
|
+
}
|
|
36221
|
+
};
|
|
36222
|
+
writeJson(args.response, 200, payload);
|
|
36223
|
+
return;
|
|
36224
|
+
}
|
|
36225
|
+
const closeMatch = url.pathname.match(/^\/api\/sessions\/([^/]+)\/close$/u);
|
|
36226
|
+
if (closeMatch) {
|
|
36227
|
+
if (!isAuthorizedApiRequest(args.request, args.token)) {
|
|
36228
|
+
writeJson(args.response, 401, { error: "Unauthorized." });
|
|
36229
|
+
return;
|
|
36230
|
+
}
|
|
36231
|
+
if (args.request.method !== "POST") {
|
|
36232
|
+
writeJson(args.response, 405, { error: "Method not allowed." });
|
|
36233
|
+
return;
|
|
36234
|
+
}
|
|
36235
|
+
const sessionId = decodeURIComponent(closeMatch[1]);
|
|
36236
|
+
const { closeLocalViewSessionBrowser: closeLocalViewSessionBrowser2, LocalViewSessionCloseError: LocalViewSessionCloseError2 } = await Promise.resolve().then(() => (init_session_control(), session_control_exports));
|
|
36237
|
+
try {
|
|
36238
|
+
await closeLocalViewSessionBrowser2(sessionId);
|
|
36239
|
+
} catch (error) {
|
|
36240
|
+
if (error instanceof LocalViewSessionCloseError2) {
|
|
36241
|
+
writeJson(args.response, error.statusCode, { error: error.message });
|
|
36242
|
+
return;
|
|
36243
|
+
}
|
|
36244
|
+
throw error;
|
|
36245
|
+
}
|
|
36246
|
+
const payload = {
|
|
36247
|
+
sessionId,
|
|
36248
|
+
closed: true
|
|
36249
|
+
};
|
|
36250
|
+
writeJson(args.response, 200, payload);
|
|
36251
|
+
return;
|
|
36252
|
+
}
|
|
36253
|
+
if (url.pathname === "/favicon.ico") {
|
|
36254
|
+
args.response.statusCode = 204;
|
|
36255
|
+
args.response.end();
|
|
36256
|
+
return;
|
|
36257
|
+
}
|
|
36258
|
+
if (url.pathname === "/" || url.pathname.startsWith("/assets/") || url.pathname.startsWith("/images/")) {
|
|
36259
|
+
await serveStaticAsset(args.response, url.pathname, args.token);
|
|
36260
|
+
return;
|
|
36261
|
+
}
|
|
36262
|
+
args.response.statusCode = 404;
|
|
36263
|
+
args.response.end("not found");
|
|
36264
|
+
}
|
|
36265
|
+
async function serveStaticAsset(response, pathname, token) {
|
|
36266
|
+
const publicDir = resolveLocalViewPublicDir();
|
|
36267
|
+
const relativePath = pathname === "/" ? "index.html" : pathname.slice(1);
|
|
36268
|
+
const assetPath = path10__default.default.resolve(publicDir, relativePath);
|
|
36269
|
+
const relativeAssetPath = path10__default.default.relative(publicDir, assetPath);
|
|
36270
|
+
if (relativeAssetPath.startsWith("..") || path10__default.default.isAbsolute(relativeAssetPath) || !fs.existsSync(assetPath)) {
|
|
36271
|
+
response.statusCode = 404;
|
|
36272
|
+
response.end("not found");
|
|
36273
|
+
return;
|
|
36274
|
+
}
|
|
36275
|
+
if (relativePath === "index.html") {
|
|
36276
|
+
const html = await promises.readFile(assetPath, "utf8");
|
|
36277
|
+
response.setHeader("content-type", "text/html; charset=utf-8");
|
|
36278
|
+
response.setHeader("cache-control", "no-store");
|
|
36279
|
+
response.end(
|
|
36280
|
+
html.replace(
|
|
36281
|
+
"__OPENSTEER_LOCAL_BOOTSTRAP_JSON__",
|
|
36282
|
+
JSON.stringify({
|
|
36283
|
+
apiBasePath: "/api",
|
|
36284
|
+
token
|
|
36285
|
+
})
|
|
36286
|
+
)
|
|
36287
|
+
);
|
|
36288
|
+
return;
|
|
36289
|
+
}
|
|
36290
|
+
response.setHeader("content-type", guessContentType(assetPath));
|
|
36291
|
+
response.setHeader("cache-control", "no-store");
|
|
36292
|
+
response.end(await promises.readFile(assetPath));
|
|
36293
|
+
}
|
|
36294
|
+
function resolveLocalViewPublicDir() {
|
|
36295
|
+
const moduleDir = path10__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('bin.cjs', document.baseURI).href))));
|
|
36296
|
+
const candidates = [
|
|
36297
|
+
path10__default.default.resolve(moduleDir, "local-view", "public"),
|
|
36298
|
+
path10__default.default.resolve(moduleDir, "public"),
|
|
36299
|
+
path10__default.default.resolve(moduleDir, "..", "local-view", "public")
|
|
36300
|
+
];
|
|
36301
|
+
for (const candidate of candidates) {
|
|
36302
|
+
if (fs.existsSync(candidate)) {
|
|
36303
|
+
return candidate;
|
|
36304
|
+
}
|
|
36305
|
+
}
|
|
36306
|
+
throw new Error(`Could not resolve local view public assets from ${moduleDir}.`);
|
|
36307
|
+
}
|
|
36308
|
+
function isAuthorizedApiRequest(request, token) {
|
|
36309
|
+
return request.headers["x-opensteer-local-token"] === token && isAllowedOrigin(request.headers.origin);
|
|
36310
|
+
}
|
|
36311
|
+
function isAllowedOrigin(origin) {
|
|
36312
|
+
if (origin === void 0) {
|
|
36313
|
+
return true;
|
|
36314
|
+
}
|
|
36315
|
+
try {
|
|
36316
|
+
const url = new URL(origin);
|
|
36317
|
+
const host = url.hostname;
|
|
36318
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]";
|
|
36319
|
+
} catch {
|
|
36320
|
+
return false;
|
|
36321
|
+
}
|
|
36322
|
+
}
|
|
36323
|
+
function resolveWsBaseUrl(request) {
|
|
36324
|
+
const host = request.headers.host ?? "127.0.0.1";
|
|
36325
|
+
return `ws://${host}`;
|
|
36326
|
+
}
|
|
36327
|
+
function writeJson(response, statusCode, value) {
|
|
36328
|
+
response.statusCode = statusCode;
|
|
36329
|
+
response.setHeader("content-type", "application/json; charset=utf-8");
|
|
36330
|
+
response.end(JSON.stringify(value));
|
|
36331
|
+
}
|
|
36332
|
+
function guessContentType(assetPath) {
|
|
36333
|
+
if (assetPath.endsWith(".css")) {
|
|
36334
|
+
return "text/css; charset=utf-8";
|
|
36335
|
+
}
|
|
36336
|
+
if (assetPath.endsWith(".js")) {
|
|
36337
|
+
return "application/javascript; charset=utf-8";
|
|
36338
|
+
}
|
|
36339
|
+
if (assetPath.endsWith(".svg")) {
|
|
36340
|
+
return "image/svg+xml";
|
|
36341
|
+
}
|
|
36342
|
+
if (assetPath.endsWith(".json")) {
|
|
36343
|
+
return "application/json; charset=utf-8";
|
|
36344
|
+
}
|
|
36345
|
+
if (assetPath.endsWith(".png")) {
|
|
36346
|
+
return "image/png";
|
|
36347
|
+
}
|
|
36348
|
+
if (assetPath.endsWith(".ico")) {
|
|
36349
|
+
return "image/x-icon";
|
|
36350
|
+
}
|
|
36351
|
+
return "application/octet-stream";
|
|
36352
|
+
}
|
|
36353
|
+
|
|
36354
|
+
// src/local-view/serve.ts
|
|
36355
|
+
async function runLocalViewService() {
|
|
36356
|
+
const server = await startLocalViewServer({
|
|
36357
|
+
token: process.env.OPENSTEER_LOCAL_VIEW_BOOT_TOKEN ?? crypto.randomBytes(24).toString("hex"),
|
|
36358
|
+
onClosed: () => {
|
|
36359
|
+
process.exit(0);
|
|
36360
|
+
}
|
|
36361
|
+
});
|
|
36362
|
+
const handleShutdownSignal = () => {
|
|
36363
|
+
void server.close();
|
|
36364
|
+
};
|
|
36365
|
+
process.once("SIGINT", handleShutdownSignal);
|
|
36366
|
+
process.once("SIGTERM", handleShutdownSignal);
|
|
36367
|
+
await new Promise(() => void 0);
|
|
36368
|
+
}
|
|
36369
|
+
|
|
36370
|
+
// src/cli/view.ts
|
|
36371
|
+
init_service();
|
|
36372
|
+
init_session_manifest();
|
|
36373
|
+
init_root2();
|
|
36374
|
+
async function handleViewCommand(parsed) {
|
|
36375
|
+
const subcommand = parsed.command[1];
|
|
36376
|
+
if (subcommand === "serve") {
|
|
36377
|
+
assertNoViewPreferenceFlag(parsed);
|
|
36378
|
+
await runLocalViewService();
|
|
36379
|
+
return;
|
|
36380
|
+
}
|
|
36381
|
+
if (subcommand === "stop") {
|
|
36382
|
+
assertNoViewPreferenceFlag(parsed);
|
|
36383
|
+
const stopped = await stopLocalViewService();
|
|
36384
|
+
writeViewOutput(parsed, { stopped });
|
|
36385
|
+
return;
|
|
36386
|
+
}
|
|
36387
|
+
if (subcommand !== void 0) {
|
|
36388
|
+
throw new Error(`Unknown view command: view ${subcommand}`);
|
|
36389
|
+
}
|
|
36390
|
+
if (parsed.options.localViewMode !== void 0) {
|
|
36391
|
+
const preference = await setLocalViewMode(parsed.options.localViewMode);
|
|
36392
|
+
writeViewOutput(parsed, { mode: preference.mode });
|
|
36393
|
+
return;
|
|
36394
|
+
}
|
|
36395
|
+
const service = await ensureLocalViewServiceRunning();
|
|
36396
|
+
const sessionId = parsed.options.workspace === void 0 ? void 0 : await resolveWorkspaceSessionId({
|
|
36397
|
+
rootDir: process.cwd(),
|
|
36398
|
+
workspace: parsed.options.workspace
|
|
36399
|
+
});
|
|
36400
|
+
const url = buildLocalViewSessionUrl({
|
|
36401
|
+
baseUrl: service.url,
|
|
36402
|
+
...sessionId === void 0 ? {} : { sessionId }
|
|
36403
|
+
});
|
|
36404
|
+
writeViewOutput(parsed, {
|
|
36405
|
+
url,
|
|
36406
|
+
...sessionId === void 0 ? {} : { sessionId }
|
|
36407
|
+
});
|
|
36408
|
+
}
|
|
36409
|
+
async function resolveWorkspaceSessionId(input) {
|
|
36410
|
+
const rootPath = resolveFilesystemWorkspacePath({
|
|
36411
|
+
rootDir: path10__default.default.resolve(input.rootDir),
|
|
36412
|
+
workspace: input.workspace
|
|
36413
|
+
});
|
|
36414
|
+
const live = await readPersistedLocalBrowserSessionRecord(rootPath);
|
|
36415
|
+
if (!live || !isProcessRunning(live.pid)) {
|
|
36416
|
+
return void 0;
|
|
36417
|
+
}
|
|
36418
|
+
return buildLocalViewSessionId({
|
|
36419
|
+
rootPath,
|
|
36420
|
+
pid: live.pid,
|
|
36421
|
+
startedAt: live.startedAt
|
|
36422
|
+
});
|
|
36423
|
+
}
|
|
36424
|
+
function assertNoViewPreferenceFlag(parsed) {
|
|
36425
|
+
if (parsed.options.localViewMode !== void 0) {
|
|
36426
|
+
throw new Error("View preference flags cannot be combined with this subcommand.");
|
|
36427
|
+
}
|
|
36428
|
+
}
|
|
36429
|
+
function writeViewOutput(parsed, value) {
|
|
36430
|
+
if (parsed.options.json === true) {
|
|
36431
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
36432
|
+
`);
|
|
36433
|
+
return;
|
|
36434
|
+
}
|
|
36435
|
+
if ("url" in value) {
|
|
36436
|
+
process.stdout.write(`${value.url}
|
|
36437
|
+
`);
|
|
36438
|
+
return;
|
|
36439
|
+
}
|
|
36440
|
+
if ("stopped" in value) {
|
|
36441
|
+
process.stdout.write(
|
|
36442
|
+
`${value.stopped ? "Local view service stopped." : "Local view service is not running."}
|
|
36443
|
+
`
|
|
36444
|
+
);
|
|
36445
|
+
return;
|
|
36446
|
+
}
|
|
36447
|
+
process.stdout.write(`Local view preference set to ${value.mode}.
|
|
36448
|
+
`);
|
|
36449
|
+
}
|
|
36450
|
+
|
|
33686
36451
|
// src/cli/bin.ts
|
|
33687
36452
|
var emitProcessWarning = process4__default.default.emitWarning.bind(process4__default.default);
|
|
33688
36453
|
process4__default.default.emitWarning = ((warning, ...args) => {
|
|
@@ -33733,6 +36498,10 @@ async function main() {
|
|
|
33733
36498
|
await handleRecordCommandEntry(parsed);
|
|
33734
36499
|
return;
|
|
33735
36500
|
}
|
|
36501
|
+
if (parsed.command[0] === "view") {
|
|
36502
|
+
await handleViewCommand(parsed);
|
|
36503
|
+
return;
|
|
36504
|
+
}
|
|
33736
36505
|
if (parsed.command[0] === "exec") {
|
|
33737
36506
|
await handleExecCommand(parsed);
|
|
33738
36507
|
return;
|