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.
Files changed (38) hide show
  1. package/dist/chunk-4LP7QP2O.js +4336 -0
  2. package/dist/chunk-4LP7QP2O.js.map +1 -0
  3. package/dist/{chunk-656MQUSM.js → chunk-6PGXWW3X.js} +4787 -9519
  4. package/dist/chunk-6PGXWW3X.js.map +1 -0
  5. package/dist/chunk-BMPUL66S.js +1170 -0
  6. package/dist/chunk-BMPUL66S.js.map +1 -0
  7. package/dist/{chunk-OIKLSFXA.js → chunk-L4FWHBQJ.js} +4 -3
  8. package/dist/chunk-L4FWHBQJ.js.map +1 -0
  9. package/dist/chunk-Z53HNZ7Z.js +1800 -0
  10. package/dist/chunk-Z53HNZ7Z.js.map +1 -0
  11. package/dist/cli/bin.cjs +3050 -281
  12. package/dist/cli/bin.cjs.map +1 -1
  13. package/dist/cli/bin.js +124 -7
  14. package/dist/cli/bin.js.map +1 -1
  15. package/dist/index.cjs +918 -263
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +1 -0
  18. package/dist/index.d.ts +1 -0
  19. package/dist/index.js +4 -2
  20. package/dist/local-view/public/assets/app.css +770 -0
  21. package/dist/local-view/public/assets/app.js +2053 -0
  22. package/dist/local-view/public/index.html +235 -0
  23. package/dist/local-view/serve-entry.cjs +7436 -0
  24. package/dist/local-view/serve-entry.cjs.map +1 -0
  25. package/dist/local-view/serve-entry.d.cts +1 -0
  26. package/dist/local-view/serve-entry.d.ts +1 -0
  27. package/dist/local-view/serve-entry.js +23 -0
  28. package/dist/local-view/serve-entry.js.map +1 -0
  29. package/dist/opensteer-KZCRP425.js +6 -0
  30. package/dist/{opensteer-LKX3233A.js.map → opensteer-KZCRP425.js.map} +1 -1
  31. package/dist/session-control-VGBFOH3Y.js +39 -0
  32. package/dist/session-control-VGBFOH3Y.js.map +1 -0
  33. package/package.json +8 -8
  34. package/skills/README.md +3 -0
  35. package/skills/opensteer/SKILL.md +229 -48
  36. package/dist/chunk-656MQUSM.js.map +0 -1
  37. package/dist/chunk-OIKLSFXA.js.map +0 -1
  38. 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 path7 = require('path');
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 WebSocket2 = require('ws');
21
+ var WebSocket4 = require('ws');
21
22
  var process4 = require('process');
22
- var module$1 = require('module');
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 path7__default = /*#__PURE__*/_interopDefault(path7);
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 WebSocket2__default = /*#__PURE__*/_interopDefault(WebSocket2);
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
- var PROCESS_STARTED_AT_MS;
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
- ({ ...process.env, LC_ALL: "C" });
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(path7.join(userDataDir, entry), {
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 = path7.join(userDataDir, profileDir, "Preferences");
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(path7.join(userDataDir, profileDir, "Secure Preferences"), { force: true }).catch(
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: path7.resolve(expandHome(platformConfig.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 path7.resolve(expandHome(explicitDir));
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 path7.resolve(expandHome(platformConfig.userDataDir));
338
+ return path10.resolve(expandHome(platformConfig.userDataDir));
189
339
  }
190
340
  function resolveExecutableCandidates(candidates) {
191
- return candidates.map((candidate) => candidate ? path7.resolve(expandHome(candidate)) : null);
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
- path7.join(WINDOWS_PROGRAM_FILES, "Google", "Chrome", "Application", "chrome.exe"),
213
- path7.join(WINDOWS_PROGRAM_FILES_X86, "Google", "Chrome", "Application", "chrome.exe"),
214
- path7.join("~", "AppData", "Local", "Google", "Chrome", "Application", "chrome.exe")
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
- path7.join("~", "AppData", "Local", "Google", "Chrome SxS", "Application", "chrome.exe")
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
- path7.join(WINDOWS_PROGRAM_FILES, "Chromium", "Application", "chrome.exe"),
262
- path7.join(WINDOWS_PROGRAM_FILES_X86, "Chromium", "Application", "chrome.exe"),
263
- path7.join("~", "AppData", "Local", "Chromium", "Application", "chrome.exe")
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
- path7.join(WINDOWS_PROGRAM_FILES, "BraveSoftware", "Brave-Browser", "Application", "brave.exe"),
291
- path7.join(
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
- path7.join("~", "AppData", "Local", "BraveSoftware", "Brave-Browser", "Application", "brave.exe")
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
- path7.join(WINDOWS_PROGRAM_FILES, "Microsoft", "Edge", "Application", "msedge.exe"),
325
- path7.join(WINDOWS_PROGRAM_FILES_X86, "Microsoft", "Edge", "Application", "msedge.exe"),
326
- path7.join("~", "AppData", "Local", "Microsoft", "Edge", "Application", "msedge.exe")
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
- path7.join(WINDOWS_PROGRAM_FILES, "Vivaldi", "Application", "vivaldi.exe"),
355
- path7.join(WINDOWS_PROGRAM_FILES_X86, "Vivaldi", "Application", "vivaldi.exe"),
356
- path7.join("~", "AppData", "Local", "Vivaldi", "Application", "vivaldi.exe")
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 path7.join(os.homedir(), value.slice(1));
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 = path7.resolve(expandHome(executablePath));
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: path7.join(os.homedir(), "Library", "Application Support", "Google", "Chrome")
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: path7.join(os.homedir(), "Library", "Application Support", "Chromium")
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 ?? path7.join(os.homedir(), "AppData", "Local");
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
- path7.join(programFiles, "Google", "Chrome", "Application", "chrome.exe"),
435
- path7.join(programFilesX86, "Google", "Chrome", "Application", "chrome.exe"),
436
- path7.join(localAppData, "Google", "Chrome", "Application", "chrome.exe")
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: path7.join(localAppData, "Google", "Chrome", "User Data")
588
+ userDataDir: path10.join(localAppData, "Google", "Chrome", "User Data")
439
589
  },
440
590
  {
441
591
  brand: "chromium",
442
592
  executablePath: firstExistingPath([
443
- path7.join(programFiles, "Chromium", "Application", "chrome.exe"),
444
- path7.join(programFilesX86, "Chromium", "Application", "chrome.exe"),
445
- path7.join(localAppData, "Chromium", "Application", "chrome.exe")
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: path7.join(localAppData, "Chromium", "User Data")
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: path7.join(os.homedir(), ".config", "google-chrome")
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: path7.join(os.homedir(), ".config", "chromium")
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 = path7.join(userDataDir, "DevToolsActivePort");
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(path18) {
709
- return path18.startsWith("/") ? path18 : `/${path18}`;
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 = path7.resolve(expandHome(input.sourceUserDataDir));
746
- const targetUserDataDir = path7.resolve(expandHome(input.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 = path7.join(sourceUserDataDir, profileDirectory);
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, path7.join(targetUserDataDir, profileDirectory), {
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 = path7.join(input.sourceUserDataDir, entry);
783
- const targetPath = path7.join(input.targetUserDataDir, entry);
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(path7.join(userDataDir, entry, "Preferences"));
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 = path7.relative(input.rootPath, input.candidatePath);
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, path18) {
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(`${path18} must be a finite JSON number`);
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, `${path18}[${index}]`));
1524
+ return value.map((entry, index) => canonicalizeJsonValue(entry, `${path24}[${index}]`));
1375
1525
  }
1376
1526
  if (!isPlainObject(value)) {
1377
- throw new TypeError(`${path18} must be a plain JSON object`);
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(`${path18}.${key} must not be undefined`);
1534
+ throw new TypeError(`${path24}.${key} must not be undefined`);
1385
1535
  }
1386
- result[key] = canonicalizeJsonValue(entry, `${path18}.${key}`);
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 (path7__default.default.isAbsolute(relativePath)) {
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 path7__default.default.join(rootPath, ...segments);
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(path7__default.default.dirname(filePath));
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(path7__default.default.dirname(filePath));
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(path7__default.default.dirname(filePath));
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(path7__default.default.dirname(lockPath));
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 = path7__default.default.join(this.rootPath, "artifacts", "manifests");
1598
- this.objectsDirectory = path7__default.default.join(this.rootPath, "artifacts", "objects", "sha256");
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 path7__default.default.join(this.manifestsDirectory, `${encodePathSegment(artifactId)}.json`);
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, path18 = "$") {
1902
- return validateSchemaNode(schema, value, path18);
2051
+ function validateJsonSchema(schema, value, path24 = "$") {
2052
+ return validateSchemaNode(schema, value, path24);
1903
2053
  }
1904
- function validateSchemaNode(schema, value, path18) {
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: path18,
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: path18,
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, path18));
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: path18,
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, path18).length === 0
2083
+ (member) => validateSchemaNode(member, value, path24).length === 0
1934
2084
  );
1935
2085
  if (!hasMatch) {
1936
2086
  issues.push({
1937
- path: path18,
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, path18));
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: path18,
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: path18,
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: path18,
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: path18,
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: path18,
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: path18,
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: path18,
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: path18,
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: path18,
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: path18,
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: path18,
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], `${path18}[${String(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(path18, requiredKey),
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(path18, key))
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(path18, key),
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(path18, key)
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 path18 = getParsedUrl().pathname;
2352
- if (!includesCaseInsensitive(path18, filters.path)) {
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 = path7__default.default.join(rootPath, ...registryRelativePath);
9767
- this.recordsDirectory = path7__default.default.join(basePath, "records");
9768
- this.indexesDirectory = path7__default.default.join(basePath, "indexes", "by-key");
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(path7__default.default.join(this.recordsDirectory, fileName)))
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 path7__default.default.join(this.recordsDirectory, `${encodePathSegment(id)}.json`);
10026
+ return path10__default.default.join(this.recordsDirectory, `${encodePathSegment(id)}.json`);
9870
10027
  }
9871
10028
  indexPath(key, version) {
9872
- return path7__default.default.join(
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 path7__default.default.join(path7__default.default.dirname(this.recordsDirectory), ".write.lock");
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 = path7__default.default.join(rootPath, "registry", "saved-network.sqlite");
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(path7__default.default.dirname(this.databasePath)).catch(
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 = path7__default.default.join(this.rootPath, "observations", "sessions");
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
- path7__default.default.join(this.sessionEventsDirectory(sessionId), eventFileName(sequence)),
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(path7__default.default.join(directoryPath, fileName)))
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(path7__default.default.join(directoryPath, fileName))
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 path7__default.default.join(this.sessionsDirectory, encodePathSegment(sessionId));
11500
+ return path10__default.default.join(this.sessionsDirectory, encodePathSegment(sessionId));
11344
11501
  }
11345
11502
  sessionManifestPath(sessionId) {
11346
- return path7__default.default.join(this.sessionDirectory(sessionId), "session.json");
11503
+ return path10__default.default.join(this.sessionDirectory(sessionId), "session.json");
11347
11504
  }
11348
11505
  sessionEventsDirectory(sessionId) {
11349
- return path7__default.default.join(this.sessionDirectory(sessionId), "events");
11506
+ return path10__default.default.join(this.sessionDirectory(sessionId), "events");
11350
11507
  }
11351
11508
  sessionArtifactsDirectory(sessionId) {
11352
- return path7__default.default.join(this.sessionDirectory(sessionId), "artifacts");
11509
+ return path10__default.default.join(this.sessionDirectory(sessionId), "artifacts");
11353
11510
  }
11354
11511
  sessionArtifactPath(sessionId, artifactId) {
11355
- return path7__default.default.join(
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 path7__default.default.join(this.sessionDirectory(sessionId), ".lock");
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
- path7__default.default.join(this.sessionEventsDirectory(sessionId), fileName)
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
- path7__default.default.join(this.sessionArtifactsDirectory(sessionId), fileName)
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 = path7__default.default.join(this.rootPath, "traces", "runs");
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
- path7__default.default.join(this.runEntriesDirectory(runId), sequenceFileName(sequence)),
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(path7__default.default.join(entriesDirectory, fileName))
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 path7__default.default.join(this.runsDirectory, encodeURIComponent(runId));
11742
+ return path10__default.default.join(this.runsDirectory, encodeURIComponent(runId));
11586
11743
  }
11587
11744
  runEntriesDirectory(runId) {
11588
- return path7__default.default.join(this.runDirectory(runId), "entries");
11745
+ return path10__default.default.join(this.runDirectory(runId), "entries");
11589
11746
  }
11590
11747
  runManifestPath(runId) {
11591
- return path7__default.default.join(this.runDirectory(runId), "manifest.json");
11748
+ return path10__default.default.join(this.runDirectory(runId), "manifest.json");
11592
11749
  }
11593
11750
  runWriteLockPath(runId) {
11594
- return path7__default.default.join(this.runDirectory(runId), ".append.lock");
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 path7__default.default.join(
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 = path7__default.default.join(options.rootPath, "workspace.json");
11613
- const browserPath = path7__default.default.join(options.rootPath, "browser");
11614
- const browserManifestPath = path7__default.default.join(browserPath, "manifest.json");
11615
- const browserUserDataDir = path7__default.default.join(browserPath, "user-data");
11616
- const livePath = path7__default.default.join(options.rootPath, "live");
11617
- const liveLocalPath = path7__default.default.join(livePath, "local.json");
11618
- const liveCloudPath = path7__default.default.join(livePath, "cloud.json");
11619
- const artifactsPath = path7__default.default.join(options.rootPath, "artifacts");
11620
- const tracesPath = path7__default.default.join(options.rootPath, "traces");
11621
- const observationsPath = path7__default.default.join(options.rootPath, "observations");
11622
- const registryPath = path7__default.default.join(options.rootPath, "registry");
11623
- const lockPath = path7__default.default.join(options.rootPath, ".lock");
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 path7__default.default.join(rootPath, "live", provider === "local" ? "local.json" : "cloud.json");
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 sleep(DEVTOOLS_POLL_INTERVAL_MS);
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 sleep(50);
12957
+ await sleep2(50);
12222
12958
  }
12223
12959
  return !isProcessRunning(pid);
12224
12960
  }
12225
12961
  function resolveAbpSessionDir(workspace) {
12226
- return path7__default.default.join(workspace.livePath, "abp-session");
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 sleep(ms) {
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 ? path7__default.default.join(os.tmpdir(), `${TEMPORARY_WORKSPACE_PREFIX}${crypto.randomUUID()}`) : resolveFilesystemWorkspacePath({
12327
- rootDir: path7__default.default.resolve(options.rootDir ?? process.cwd()),
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: path7__default.default.resolve(input.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(path7__default.default.join(os.tmpdir(), "opensteer-temporary-browser-"));
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 = path7.join(userDataDir, profileDirectory, "Cookies");
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(path7.join(os.tmpdir(), "opensteer-cookies-"));
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(path7.join(tempDir, "Cookies"));
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, path7.join(destDir, "Cookies"));
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, path7.join(destDir, "Cookies" + suffix)).catch(() => void 0);
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 = path7.join(userDataDir, "Local State");
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 path18 = typeof cookie.path === "string" && cookie.path.trim().length > 0 ? cookie.path : "/";
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: path18,
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 sleep2(DEFAULT_POLL_INTERVAL_MS);
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 sleep2(ms) {
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 delay(ms) {
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 delay(CLOUD_CLOSE_POLL_INTERVAL_MS);
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.0",
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(path18) {
14349
- const strict = path18.nodes.length ? buildPathCandidates(path18.nodes) : [];
14350
- const relaxedNodes = stripPositionClauses(path18.nodes);
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(path18) {
14899
- const nodes = path18?.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(path18) {
15749
+ function sanitizeReplayElementPath(path24) {
14949
15750
  return {
14950
15751
  resolution: "deterministic",
14951
- context: sanitizeContext(path18.context),
14952
- nodes: sanitizeNodes(path18.nodes)
15752
+ context: sanitizeContext(path24.context),
15753
+ nodes: sanitizeNodes(path24.nodes)
14953
15754
  };
14954
15755
  }
14955
- function sanitizeElementPath(path18) {
14956
- return sanitizeReplayElementPath(path18);
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(path18) {
15080
- return buildArrayFieldPathCandidates(path18);
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 path18 = sanitizeReplayElementPath(rawPath);
16643
- const context = await this.resolvePathContext(session, pageRef, path18.context);
16644
- const target = resolveDomPathInScope(context.index, path18.nodes, context.scope);
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, path18.nodes, context.scope);
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(path18)}" does not point to a live element`
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: path18,
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 path18 = sanitizeReplayElementPath(rawPath);
16678
- const context = await this.resolvePathContext(session, pageRef, path18.context);
16679
- return queryAllDomPathInScope(context.index, path18.nodes, context.scope).filter(
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, path18) {
16866
- const normalizedPath = sanitizeElementPath(path18);
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, path18) {
16874
- const normalizedPath = sanitizeElementPath(path18);
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(path7__default.default.join(os.tmpdir(), "opensteer-matched-tls-"));
17707
- const headersPath = path7__default.default.join(workingDirectory, "headers.txt");
17708
- const bodyPath = path7__default.default.join(workingDirectory, "body.bin");
17709
- const cookiesPath = path7__default.default.join(workingDirectory, "cookies.txt");
17710
- const requestBodyPath = path7__default.default.join(workingDirectory, "request-body.bin");
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(path7__default.default.delimiter).filter((entry) => entry.length > 0);
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 = path7__default.default.join(directory, name);
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 = path7__default.default.join(directory, discovered);
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(path18) {
17943
- const input = path18.trim();
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 [path18, value] of Object.entries(flat)) {
17994
- const tokens = parseDataPath(path18);
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(path18, mode) {
18304
- const normalized = sanitizeElementPath(path18);
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(path18, mode) {
18409
- const normalized = sanitizeElementPath(path18);
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(path18) {
18495
- const normalized = sanitizeElementPath(path18);
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, path18, node) {
18623
- const tokens = parseDataPath(path18);
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 "${path18}": expected a non-empty object 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 "${path18}": nested array indices are not supported in cached descriptors.`
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 "${path18}": expected object segment.`);
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 "${path18}" detected while building descriptor tree.`
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 "${path18}" detected at "${token.key}".`
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((path18) => path18 !== null);
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((path18) => path18.nodes);
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((path18) => sanitizeElementPath(path18));
19586
+ const normalized = paths.map((path24) => sanitizeElementPath(path24));
18786
19587
  const contextKey = pickModeString(
18787
- normalized.map((path18) => canonicalJsonString(path18.context)),
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((path18) => canonicalJsonString(path18.context) === contextKey);
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((path18) => path18.nodes.length),
19599
+ sameContext.map((path24) => path24.nodes.length),
18799
19600
  1
18800
19601
  ) ?? sameContext[0]?.nodes.length ?? 0;
18801
- const aligned = sameContext.filter((path18) => path18.nodes.length === targetLength);
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((path18) => path18.nodes[index]).filter((node) => node !== void 0);
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(path18) {
19055
- return JSON.parse(JSON.stringify(path18));
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: (path18) => {
19408
- node.$path = sanitizeElementPath(path18);
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(path18) {
19423
- return path18.nodes.some((node) => node.match.some((clause) => clause.kind === "position"));
20223
+ function hasPositionClause(path24) {
20224
+ return path24.nodes.some((node) => node.match.some((clause) => clause.kind === "position"));
19424
20225
  }
19425
- function stripPositionClauses2(path18) {
20226
+ function stripPositionClauses2(path24) {
19426
20227
  return sanitizeElementPath({
19427
- context: path18.context,
19428
- nodes: path18.nodes.map((node) => ({
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(path18) {
19749
- return path18.trim().length === 0 ? "$" : path18;
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, delay5 = 0, ...args) {
22300
- return this.registerTimer(false, callback, delay5, args);
23100
+ setTimeout(callback, delay6 = 0, ...args) {
23101
+ return this.registerTimer(false, callback, delay6, args);
22301
23102
  }
22302
- setInterval(callback, delay5 = 0, ...args) {
22303
- return this.registerTimer(true, callback, delay5, args);
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, delay5, args) {
23125
+ registerTimer(repeat, callback, delay6, args) {
22325
23126
  const timerId = this.nextTimerId++;
22326
- const normalizedDelay = Math.max(0, delay5);
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 sleep3(3e3, signal);
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 sleep3(ms, signal) {
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 sleep4(5e3, signal);
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 sleep4(ms, signal) {
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 ?? path7__default.default.resolve(process.cwd(), ".opensteer", "temporary", crypto.randomUUID());
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 delay2(ms) {
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 delay2(this.pollIntervalMs);
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 === WebSocket2__default.default.OPEN) {
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 WebSocket2__default.default(wsUrl);
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 !== WebSocket2__default.default.OPEN) {
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 ? path7__default.default.join(os.tmpdir(), `${TEMPORARY_CLOUD_WORKSPACE_PREFIX}${crypto.randomUUID()}`) : resolveFilesystemWorkspacePath({
30507
- rootDir: path7__default.default.resolve(options.rootDir ?? process.cwd()),
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 ? path7__default.default.resolve(options.rootDir ?? process.cwd(), ".opensteer", "temporary", crypto.randomUUID()) : resolveFilesystemWorkspacePath({
30960
- rootDir: path7__default.default.resolve(options.rootDir ?? process.cwd()),
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 = path7__default.default.resolve(cwd);
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(path7__default.default.resolve(cwd), process.env);
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 = path7__default.default.resolve(cwd);
31869
+ let current = path10__default.default.resolve(cwd);
31069
31870
  for (; ; ) {
31070
31871
  directories.unshift(current);
31071
- const parent = path7__default.default.dirname(current);
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 = path7__default.default.join(directory, filename);
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 delay4(ms) {
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 delay4(pollInterval);
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 delay4(pollIntervalMs);
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.0"};
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 path18 = readSingle(parsed.rawOptions, "path");
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
- ...path18 === void 0 ? {} : { path: path18 },
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(path7__default.default.dirname(outputPath), { recursive: true });
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 sleep5 = input.sleep ?? delay3;
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: sleep5
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(path7__default.default.dirname(outputPath), { recursive: true });
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 path7__default.default.resolve(input.rootDir, input.outputPath);
34166
+ return path10__default.default.resolve(input.rootDir, input.outputPath);
33302
34167
  }
33303
- return path7__default.default.join(
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 delay3(ms) {
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 = path7__default.default.dirname(skillsPackagePath);
33437
- const cliPath = path7__default.default.join(skillsPackageDir, "bin", "cli.mjs");
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 = path7__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))));
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 = path7__default.default.join(ancestor, "skills");
33447
- const skillManifest = path7__default.default.join(candidate, "opensteer", "SKILL.md");
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 = path7__default.default.resolve(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 useGitHub = await deps.checkGitHubReachable();
33476
- const skillSourcePath = useGitHub ? OPENSTEER_GITHUB_SOURCE : deps.resolveLocalSkillSourcePath();
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 ? path7__default.default.basename(record.executablePath).replace(/\.[A-Za-z0-9]+$/u, "") : void 0;
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;