opensteer 0.9.6 → 0.9.7

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 (33) hide show
  1. package/dist/{chunk-BVRIPCWA.js → chunk-3OHKIPBD.js} +316 -465
  2. package/dist/chunk-3OHKIPBD.js.map +1 -0
  3. package/dist/{chunk-L4NF74KI.js → chunk-52UNH5UW.js} +5 -5
  4. package/dist/{chunk-L4NF74KI.js.map → chunk-52UNH5UW.js.map} +1 -1
  5. package/dist/{chunk-3XBQRZZC.js → chunk-PJXN7HED.js} +115 -14
  6. package/dist/chunk-PJXN7HED.js.map +1 -0
  7. package/dist/{chunk-3I3A5OLB.js → chunk-R33BXCMQ.js} +16 -7
  8. package/dist/chunk-R33BXCMQ.js.map +1 -0
  9. package/dist/{chunk-T5P2QGZ3.js → chunk-U4BUCIZ4.js} +153 -12
  10. package/dist/chunk-U4BUCIZ4.js.map +1 -0
  11. package/dist/cli/bin.cjs +663 -544
  12. package/dist/cli/bin.cjs.map +1 -1
  13. package/dist/cli/bin.js +66 -50
  14. package/dist/cli/bin.js.map +1 -1
  15. package/dist/index.cjs +584 -495
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.cts +26 -50
  18. package/dist/index.d.ts +26 -50
  19. package/dist/index.js +4 -4
  20. package/dist/local-view/public/assets/app.js +10 -1
  21. package/dist/local-view/serve-entry.cjs +687 -494
  22. package/dist/local-view/serve-entry.cjs.map +1 -1
  23. package/dist/local-view/serve-entry.js +2 -2
  24. package/dist/opensteer-CY2QUJEG.js +6 -0
  25. package/dist/{opensteer-UGA6YBRN.js.map → opensteer-CY2QUJEG.js.map} +1 -1
  26. package/dist/{session-control-U3L5H2ZI.js → session-control-FIP6ZJLH.js} +4 -4
  27. package/dist/{session-control-U3L5H2ZI.js.map → session-control-FIP6ZJLH.js.map} +1 -1
  28. package/package.json +7 -7
  29. package/dist/chunk-3I3A5OLB.js.map +0 -1
  30. package/dist/chunk-3XBQRZZC.js.map +0 -1
  31. package/dist/chunk-BVRIPCWA.js.map +0 -1
  32. package/dist/chunk-T5P2QGZ3.js.map +0 -1
  33. package/dist/opensteer-UGA6YBRN.js +0 -6
@@ -6,9 +6,9 @@ var crypto = require('crypto');
6
6
  var path12 = require('path');
7
7
  var url = require('url');
8
8
  var child_process = require('child_process');
9
- var util = require('util');
10
- var os = require('os');
11
9
  var fs = require('fs');
10
+ var os = require('os');
11
+ var util = require('util');
12
12
  var module$1 = require('module');
13
13
  var enginePlaywright = require('@opensteer/engine-playwright');
14
14
  var http = require('http');
@@ -203,388 +203,174 @@ function isAlreadyExistsError(error) {
203
203
  }
204
204
  async function withFilesystemLock(lockPath, task) {
205
205
  await ensureDirectory(path12__default.default.dirname(lockPath));
206
+ const ownerToken = crypto.randomUUID();
206
207
  let attempt = 0;
207
208
  while (true) {
208
209
  try {
209
210
  await promises.mkdir(lockPath);
211
+ const acquiredAt = Date.now();
212
+ await writeLockMetadata(lockPath, {
213
+ version: LOCK_METADATA_VERSION,
214
+ ownerToken,
215
+ pid: process.pid,
216
+ acquiredAt,
217
+ heartbeatAt: acquiredAt
218
+ });
210
219
  break;
211
220
  } catch (error) {
212
221
  if (!isAlreadyExistsError(error)) {
213
222
  throw error;
214
223
  }
224
+ if (await tryRecoverFilesystemLock(lockPath)) {
225
+ attempt = 0;
226
+ continue;
227
+ }
215
228
  const delayMs = LOCK_RETRY_DELAYS_MS[Math.min(attempt, LOCK_RETRY_DELAYS_MS.length - 1)];
216
229
  attempt += 1;
217
230
  await new Promise((resolve4) => setTimeout(resolve4, delayMs));
218
231
  }
219
232
  }
233
+ const heartbeatTimer = setInterval(() => {
234
+ void touchLockMetadata(lockPath, ownerToken);
235
+ }, LOCK_HEARTBEAT_INTERVAL_MS);
236
+ heartbeatTimer.unref?.();
220
237
  try {
221
238
  return await task();
222
239
  } finally {
223
- await promises.rm(lockPath, { recursive: true, force: true });
240
+ clearInterval(heartbeatTimer);
241
+ const metadata = await readLockMetadata(lockPath);
242
+ if (metadata?.ownerToken === ownerToken) {
243
+ await promises.rm(lockPath, { recursive: true, force: true });
244
+ }
224
245
  }
225
246
  }
226
- var LOCK_RETRY_DELAYS_MS;
227
- var init_filesystem = __esm({
228
- "../runtime-core/src/internal/filesystem.ts"() {
229
- init_json();
230
- LOCK_RETRY_DELAYS_MS = [1, 2, 5, 10, 20, 50];
231
- }
232
- });
233
-
234
- // src/internal/filesystem.ts
235
- var init_filesystem2 = __esm({
236
- "src/internal/filesystem.ts"() {
237
- init_filesystem();
247
+ async function tryRecoverFilesystemLock(lockPath) {
248
+ if (!await shouldRecoverFilesystemLock(lockPath)) {
249
+ return false;
238
250
  }
239
- });
240
- function resolveLiveSessionRecordPath(rootPath, provider) {
241
- return path12__default.default.join(rootPath, "live", provider === "local" ? "local.json" : "cloud.json");
251
+ await promises.rm(lockPath, { recursive: true, force: true });
252
+ return true;
242
253
  }
243
- async function readPersistedSessionRecord(rootPath, provider) {
244
- const sessionPath = resolveLiveSessionRecordPath(rootPath, provider);
245
- if (!await pathExists(sessionPath)) {
246
- return void 0;
254
+ async function shouldRecoverFilesystemLock(lockPath) {
255
+ const metadata = await readLockMetadata(lockPath);
256
+ if (metadata !== void 0) {
257
+ if (isProcessRunning(metadata.pid)) {
258
+ return false;
259
+ }
260
+ return Date.now() - metadata.heartbeatAt >= LOCK_ORPHAN_GRACE_MS;
247
261
  }
248
- const parsed = await readJsonFile(sessionPath);
249
- if (isPersistedLocalBrowserSessionRecord(parsed)) {
250
- return parsed;
262
+ const lockStat = await promises.stat(lockPath).catch(() => void 0);
263
+ if (lockStat === void 0) {
264
+ return false;
251
265
  }
252
- return void 0;
253
- }
254
- async function readPersistedLocalBrowserSessionRecord(rootPath) {
255
- const record = await readPersistedSessionRecord(rootPath, "local");
256
- return record?.provider === "local" ? record : void 0;
257
- }
258
- async function writePersistedSessionRecord(rootPath, record) {
259
- await writeJsonFileAtomic(resolveLiveSessionRecordPath(rootPath, record.provider), record);
260
- }
261
- async function clearPersistedSessionRecord(rootPath, provider) {
262
- await promises.rm(resolveLiveSessionRecordPath(rootPath, provider), { force: true });
266
+ return Date.now() - lockStat.mtimeMs >= LOCK_METADATALESS_STALE_MS;
263
267
  }
264
- function isPersistedLocalBrowserSessionRecord(value) {
265
- return value.layout === OPENSTEER_LIVE_SESSION_LAYOUT && value.version === OPENSTEER_LIVE_SESSION_VERSION && value.provider === "local" && (value.engine === "playwright" || value.engine === "abp") && typeof value.pid === "number" && Number.isFinite(value.pid) && typeof value.startedAt === "number" && Number.isFinite(value.startedAt) && typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt) && typeof value.userDataDir === "string" && value.userDataDir.length > 0;
266
- }
267
- var OPENSTEER_LIVE_SESSION_LAYOUT, OPENSTEER_LIVE_SESSION_VERSION;
268
- var init_live_session = __esm({
269
- "src/live-session.ts"() {
270
- init_filesystem2();
271
- OPENSTEER_LIVE_SESSION_LAYOUT = "opensteer-session";
272
- OPENSTEER_LIVE_SESSION_VERSION = 1;
273
- }
274
- });
275
- function parseProcessOwner(value) {
276
- if (!value || typeof value !== "object") {
277
- return null;
278
- }
279
- const parsed = value;
280
- const pid = Number(parsed.pid);
281
- const processStartedAtMs = Number(parsed.processStartedAtMs);
282
- if (!Number.isInteger(pid) || pid <= 0) {
283
- return null;
268
+ async function readLockMetadata(lockPath) {
269
+ const metadataPath = path12__default.default.join(lockPath, LOCK_METADATA_FILENAME);
270
+ if (!await pathExists(metadataPath)) {
271
+ return void 0;
284
272
  }
285
- if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
286
- return null;
273
+ try {
274
+ const parsed = await readJsonFile(metadataPath);
275
+ const pid = parsed.pid;
276
+ const acquiredAt = parsed.acquiredAt;
277
+ const heartbeatAt = parsed.heartbeatAt;
278
+ if (parsed.version !== LOCK_METADATA_VERSION || typeof parsed.ownerToken !== "string" || parsed.ownerToken.length === 0 || typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0 || typeof acquiredAt !== "number" || !Number.isFinite(acquiredAt) || typeof heartbeatAt !== "number" || !Number.isFinite(heartbeatAt)) {
279
+ return void 0;
280
+ }
281
+ return {
282
+ version: LOCK_METADATA_VERSION,
283
+ ownerToken: parsed.ownerToken,
284
+ pid,
285
+ acquiredAt,
286
+ heartbeatAt
287
+ };
288
+ } catch {
289
+ return void 0;
287
290
  }
288
- return {
289
- pid,
290
- processStartedAtMs
291
- };
292
291
  }
293
- function processOwnersEqual(left, right) {
294
- if (!left || !right) {
295
- return left === right;
292
+ async function writeLockMetadata(lockPath, metadata) {
293
+ try {
294
+ await writeJsonFileAtomic(path12__default.default.join(lockPath, LOCK_METADATA_FILENAME), metadata);
295
+ } catch (error) {
296
+ await promises.rm(lockPath, { recursive: true, force: true }).catch(() => void 0);
297
+ throw error;
296
298
  }
297
- return left.pid === right.pid && left.processStartedAtMs === right.processStartedAtMs;
298
299
  }
299
- async function getProcessLiveness(owner) {
300
- if (owner.pid === process.pid && hasMatchingProcessStartTime(owner.processStartedAtMs, PROCESS_STARTED_AT_MS)) {
301
- return "live";
302
- }
303
- const startedAtMs = await readProcessStartedAtMs(owner.pid);
304
- if (typeof startedAtMs === "number") {
305
- return hasMatchingProcessStartTime(owner.processStartedAtMs, startedAtMs) ? "live" : "dead";
300
+ async function touchLockMetadata(lockPath, ownerToken) {
301
+ const metadata = await readLockMetadata(lockPath);
302
+ if (metadata === void 0 || metadata.ownerToken !== ownerToken) {
303
+ return;
306
304
  }
307
- return isProcessRunning(owner.pid) ? "unknown" : "dead";
305
+ await writeJsonFileAtomic(path12__default.default.join(lockPath, LOCK_METADATA_FILENAME), {
306
+ ...metadata,
307
+ heartbeatAt: Date.now()
308
+ }).catch(() => void 0);
308
309
  }
309
310
  function isProcessRunning(pid) {
311
+ if (!Number.isInteger(pid) || pid <= 0) {
312
+ return false;
313
+ }
310
314
  try {
311
315
  process.kill(pid, 0);
312
316
  return true;
313
317
  } catch (error) {
314
- const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
315
- return code !== "ESRCH";
318
+ return error?.code === "EPERM";
316
319
  }
317
320
  }
318
- function hasMatchingProcessStartTime(expectedStartedAtMs, actualStartedAtMs) {
319
- return Math.abs(expectedStartedAtMs - actualStartedAtMs) <= PROCESS_START_TIME_TOLERANCE_MS;
320
- }
321
- async function readProcessStartedAtMs(pid) {
322
- if (pid <= 0) {
323
- return null;
324
- }
325
- if (process.platform === "linux") {
326
- return readLinuxProcessStartedAtMs(pid);
327
- }
328
- if (process.platform === "win32") {
329
- return readWindowsProcessStartedAtMs(pid);
321
+ var LOCK_RETRY_DELAYS_MS, LOCK_METADATA_FILENAME, LOCK_METADATA_VERSION, LOCK_HEARTBEAT_INTERVAL_MS, LOCK_ORPHAN_GRACE_MS, LOCK_METADATALESS_STALE_MS;
322
+ var init_filesystem = __esm({
323
+ "../runtime-core/src/internal/filesystem.ts"() {
324
+ init_json();
325
+ LOCK_RETRY_DELAYS_MS = [1, 2, 5, 10, 20, 50];
326
+ LOCK_METADATA_FILENAME = "owner.json";
327
+ LOCK_METADATA_VERSION = 1;
328
+ LOCK_HEARTBEAT_INTERVAL_MS = 1e3;
329
+ LOCK_ORPHAN_GRACE_MS = 2e3;
330
+ LOCK_METADATALESS_STALE_MS = 3e4;
330
331
  }
331
- return readPsProcessStartedAtMs(pid);
332
- }
333
- async function readLinuxProcessStartedAtMs(pid) {
334
- let statRaw;
335
- try {
336
- statRaw = await promises.readFile(`/proc/${String(pid)}/stat`, "utf8");
337
- } catch {
338
- return null;
332
+ });
333
+
334
+ // src/internal/filesystem.ts
335
+ var init_filesystem2 = __esm({
336
+ "src/internal/filesystem.ts"() {
337
+ init_filesystem();
339
338
  }
340
- const startTicks = parseLinuxProcessStartTicks(statRaw);
341
- if (startTicks === null) {
342
- return null;
339
+ });
340
+ function resolveBrandPlatformConfig(brand, platform = process.platform) {
341
+ if (platform === "darwin") {
342
+ return brand.darwin;
343
343
  }
344
- const [bootTimeMs, clockTicksPerSecond] = await Promise.all([
345
- readLinuxBootTimeMs(),
346
- readLinuxClockTicksPerSecond()
347
- ]);
348
- if (bootTimeMs === null || clockTicksPerSecond === null) {
349
- return null;
344
+ if (platform === "win32") {
345
+ return brand.win32;
350
346
  }
351
- return Math.floor(bootTimeMs + startTicks * 1e3 / clockTicksPerSecond);
352
- }
353
- function parseLinuxProcessStartTicks(statRaw) {
354
- const closingParenIndex = statRaw.lastIndexOf(")");
355
- if (closingParenIndex === -1) {
356
- return null;
347
+ if (platform === "linux") {
348
+ return brand.linux;
357
349
  }
358
- const fields = statRaw.slice(closingParenIndex + 2).trim().split(/\s+/);
359
- const startTicks = Number(fields[LINUX_STAT_START_TIME_FIELD_INDEX]);
360
- return Number.isFinite(startTicks) && startTicks >= 0 ? startTicks : null;
350
+ return void 0;
361
351
  }
362
- async function readLinuxBootTimeMs() {
363
- try {
364
- const statRaw = await promises.readFile("/proc/stat", "utf8");
365
- const bootTimeLine = statRaw.split("\n").find((line) => line.startsWith("btime "));
366
- if (!bootTimeLine) {
367
- return null;
352
+ function detectInstalledBrowserBrands() {
353
+ const installations = [];
354
+ for (const brand of BROWSER_BRANDS) {
355
+ const platformConfig = resolveBrandPlatformConfig(brand);
356
+ if (!platformConfig) {
357
+ continue;
368
358
  }
369
- const bootTimeSeconds = Number.parseInt(bootTimeLine.slice("btime ".length), 10);
370
- return Number.isFinite(bootTimeSeconds) ? bootTimeSeconds * 1e3 : null;
371
- } catch {
372
- return null;
373
- }
374
- }
375
- async function readLinuxClockTicksPerSecond() {
376
- if (!linuxClockTicksPerSecondPromise) {
377
- linuxClockTicksPerSecondPromise = execFileAsync("getconf", ["CLK_TCK"], {
378
- encoding: "utf8",
379
- maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
380
- }).then(({ stdout }) => {
381
- const value = Number.parseInt(stdout.trim(), 10);
382
- return Number.isFinite(value) && value > 0 ? value : null;
383
- }).catch(() => null);
384
- }
385
- return linuxClockTicksPerSecondPromise;
386
- }
387
- async function readWindowsProcessStartedAtMs(pid) {
388
- try {
389
- const { stdout } = await execFileAsync(
390
- "powershell.exe",
391
- [
392
- "-NoProfile",
393
- "-Command",
394
- `(Get-Process -Id ${String(pid)}).StartTime.ToUniversalTime().ToString("o")`
395
- ],
396
- {
397
- encoding: "utf8",
398
- maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
399
- }
359
+ const executablePath = firstExistingPath(
360
+ resolveExecutableCandidates(platformConfig.executableCandidates)
400
361
  );
401
- const isoTimestamp = stdout.trim();
402
- if (!isoTimestamp) {
403
- return null;
362
+ if (!executablePath) {
363
+ continue;
404
364
  }
405
- const startedAtMs = Date.parse(isoTimestamp);
406
- return Number.isFinite(startedAtMs) ? startedAtMs : null;
407
- } catch {
408
- return null;
409
- }
410
- }
411
- async function readPsProcessStartedAtMs(pid) {
412
- try {
413
- const { stdout } = await execFileAsync("ps", ["-o", "lstart=", "-p", String(pid)], {
414
- encoding: "utf8",
415
- env: PS_COMMAND_ENV,
416
- maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES
365
+ installations.push({
366
+ brand,
367
+ brandId: brand.id,
368
+ displayName: brand.displayName,
369
+ executablePath,
370
+ userDataDir: path12.resolve(expandHome(platformConfig.userDataDir))
417
371
  });
418
- const startedAt = stdout.trim();
419
- if (!startedAt) {
420
- return null;
421
- }
422
- const startedAtMs = Date.parse(startedAt.replace(/\s+/g, " "));
423
- return Number.isFinite(startedAtMs) ? startedAtMs : null;
424
- } catch {
425
- return null;
426
372
  }
427
- }
428
- 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;
429
- var init_process_owner = __esm({
430
- "src/local-browser/process-owner.ts"() {
431
- execFileAsync = util.promisify(child_process.execFile);
432
- PROCESS_STARTED_AT_MS = Math.floor(Date.now() - process.uptime() * 1e3);
433
- PROCESS_START_TIME_TOLERANCE_MS = 1e3;
434
- PROCESS_LIST_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
435
- PS_COMMAND_ENV = { ...process.env, LC_ALL: "C" };
436
- LINUX_STAT_START_TIME_FIELD_INDEX = 19;
437
- CURRENT_PROCESS_OWNER = {
438
- pid: process.pid,
439
- processStartedAtMs: PROCESS_STARTED_AT_MS
440
- };
441
- linuxClockTicksPerSecondPromise = null;
442
- }
443
- });
444
- function resolveOpensteerStateDir() {
445
- const explicit = process.env.OPENSTEER_HOME?.trim();
446
- if (explicit) {
447
- return path12__default.default.resolve(explicit);
448
- }
449
- if (process.platform === "win32") {
450
- return path12__default.default.join(
451
- process.env.LOCALAPPDATA ?? path12__default.default.join(os.homedir(), "AppData", "Local"),
452
- "Opensteer"
453
- );
454
- }
455
- if (process.platform === "darwin") {
456
- return path12__default.default.join(os.homedir(), "Library", "Application Support", "Opensteer");
457
- }
458
- return path12__default.default.join(
459
- process.env.XDG_STATE_HOME ?? path12__default.default.join(os.homedir(), ".local", "state"),
460
- "opensteer"
461
- );
462
- }
463
- function resolveLocalViewRootDir() {
464
- return path12__default.default.join(resolveOpensteerStateDir(), "local-view");
465
- }
466
- function resolveLocalViewPreferencesPath() {
467
- return path12__default.default.join(resolveLocalViewRootDir(), "preferences.json");
468
- }
469
- function resolveLocalViewServiceDir() {
470
- return path12__default.default.join(resolveLocalViewRootDir(), "service");
471
- }
472
- function resolveLocalViewSessionsDir() {
473
- return path12__default.default.join(resolveLocalViewRootDir(), "sessions");
474
- }
475
- function resolveLocalViewServiceLockDir() {
476
- return path12__default.default.join(resolveLocalViewServiceDir(), "startup.lock");
477
- }
478
- function resolveLocalViewServiceStatePath() {
479
- return path12__default.default.join(resolveLocalViewServiceDir(), "state.json");
480
- }
481
- var init_runtime_dir = __esm({
482
- "src/local-view/runtime-dir.ts"() {
483
- }
484
- });
485
- function buildLocalViewSessionId(input) {
486
- const hash = crypto.createHash("sha256").update(`${input.rootPath}
487
- ${String(input.pid)}
488
- ${String(input.startedAt)}`).digest("hex");
489
- return `local_${hash.slice(0, 24)}`;
490
- }
491
- function createLocalViewSessionManifest(input) {
492
- return {
493
- layout: OPENSTEER_LOCAL_VIEW_SESSION_LAYOUT,
494
- version: OPENSTEER_LOCAL_VIEW_SESSION_VERSION,
495
- sessionId: buildLocalViewSessionId({
496
- rootPath: input.rootPath,
497
- pid: input.live.pid,
498
- startedAt: input.live.startedAt
499
- }),
500
- rootPath: input.rootPath,
501
- ...input.workspace === void 0 ? {} : { workspace: input.workspace },
502
- engine: input.live.engine,
503
- ownership: input.ownership,
504
- pid: input.live.pid,
505
- startedAt: input.live.startedAt,
506
- updatedAt: Date.now()
507
- };
508
- }
509
- async function writeLocalViewSessionManifest(manifest) {
510
- await ensureDirectory(resolveLocalViewSessionsDir());
511
- await writeJsonFileAtomic(resolveLocalViewSessionManifestPath(manifest.sessionId), manifest);
512
- }
513
- async function deleteLocalViewSessionManifest(sessionId) {
514
- await promises.rm(resolveLocalViewSessionManifestPath(sessionId), { force: true }).catch(() => void 0);
515
- }
516
- async function readLocalViewSessionManifest(sessionId) {
517
- const manifestPath = resolveLocalViewSessionManifestPath(sessionId);
518
- if (!await pathExists(manifestPath)) {
519
- return void 0;
520
- }
521
- const parsed = await readJsonFile(manifestPath);
522
- return isPersistedLocalViewSessionManifest(parsed) ? parsed : void 0;
523
- }
524
- async function listLocalViewSessionManifests() {
525
- const directoryPath = resolveLocalViewSessionsDir();
526
- const fileNames = await listJsonFiles(directoryPath);
527
- const manifests = await Promise.all(
528
- fileNames.map(async (fileName) => {
529
- const parsed = await readJsonFile(
530
- path12__default.default.join(directoryPath, fileName)
531
- ).catch(() => void 0);
532
- return isPersistedLocalViewSessionManifest(parsed) ? parsed : void 0;
533
- })
534
- );
535
- return manifests.filter((manifest) => manifest !== void 0).sort(
536
- (left, right) => left.startedAt - right.startedAt || left.sessionId.localeCompare(right.sessionId)
537
- );
538
- }
539
- function resolveLocalViewSessionManifestPath(sessionId) {
540
- return path12__default.default.join(resolveLocalViewSessionsDir(), `${sessionId}.json`);
541
- }
542
- function isPersistedLocalViewSessionManifest(value) {
543
- 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);
544
- }
545
- var OPENSTEER_LOCAL_VIEW_SESSION_LAYOUT, OPENSTEER_LOCAL_VIEW_SESSION_VERSION;
546
- var init_session_manifest = __esm({
547
- "src/local-view/session-manifest.ts"() {
548
- init_filesystem2();
549
- init_runtime_dir();
550
- OPENSTEER_LOCAL_VIEW_SESSION_LAYOUT = "opensteer-local-view-session";
551
- OPENSTEER_LOCAL_VIEW_SESSION_VERSION = 1;
552
- }
553
- });
554
- function resolveBrandPlatformConfig(brand, platform = process.platform) {
555
- if (platform === "darwin") {
556
- return brand.darwin;
557
- }
558
- if (platform === "win32") {
559
- return brand.win32;
560
- }
561
- if (platform === "linux") {
562
- return brand.linux;
563
- }
564
- return void 0;
565
- }
566
- function detectInstalledBrowserBrands() {
567
- const installations = [];
568
- for (const brand of BROWSER_BRANDS) {
569
- const platformConfig = resolveBrandPlatformConfig(brand);
570
- if (!platformConfig) {
571
- continue;
572
- }
573
- const executablePath = firstExistingPath(
574
- resolveExecutableCandidates(platformConfig.executableCandidates)
575
- );
576
- if (!executablePath) {
577
- continue;
578
- }
579
- installations.push({
580
- brand,
581
- brandId: brand.id,
582
- displayName: brand.displayName,
583
- executablePath,
584
- userDataDir: path12.resolve(expandHome(platformConfig.userDataDir))
585
- });
586
- }
587
- return installations;
373
+ return installations;
588
374
  }
589
375
  function resolveExecutableCandidates(candidates) {
590
376
  return candidates.map((candidate) => candidate ? path12.resolve(expandHome(candidate)) : null);
@@ -1089,60 +875,417 @@ function normalizeProbeTarget(endpoint) {
1089
875
  throw new Error(`Invalid CDP endpoint "${endpoint}".`);
1090
876
  }
1091
877
  }
1092
- async function fetchJson(url, headers, timeoutMs) {
1093
- const response = await fetch(url, {
1094
- ...headers === void 0 ? {} : { headers },
1095
- signal: AbortSignal.timeout(timeoutMs)
1096
- }).catch(() => null);
1097
- if (!response?.ok) {
1098
- return null;
878
+ async function fetchJson(url, headers, timeoutMs) {
879
+ const response = await fetch(url, {
880
+ ...headers === void 0 ? {} : { headers },
881
+ signal: AbortSignal.timeout(timeoutMs)
882
+ }).catch(() => null);
883
+ if (!response?.ok) {
884
+ return null;
885
+ }
886
+ return await response.json();
887
+ }
888
+ async function isHttpEndpointReachable(url, timeoutMs) {
889
+ const response = await fetch(url, {
890
+ signal: AbortSignal.timeout(timeoutMs)
891
+ }).catch(() => null);
892
+ return response !== null;
893
+ }
894
+ function buildBrowserWebSocketUrl(httpUrl, webSocketPath) {
895
+ const protocol = httpUrl.protocol === "https:" ? "wss:" : "ws:";
896
+ return `${protocol}//${httpUrl.host}${normalizeWebSocketPath(webSocketPath)}`;
897
+ }
898
+ function normalizeWebSocketPath(path15) {
899
+ return path15.startsWith("/") ? path15 : `/${path15}`;
900
+ }
901
+ function rewriteBrowserWebSocketHost(browserWsUrl, requestedUrl) {
902
+ try {
903
+ const parsed = new URL(browserWsUrl);
904
+ parsed.protocol = requestedUrl.protocol === "https:" ? "wss:" : "ws:";
905
+ parsed.hostname = requestedUrl.hostname;
906
+ parsed.port = requestedUrl.port;
907
+ return parsed.toString();
908
+ } catch {
909
+ return browserWsUrl;
910
+ }
911
+ }
912
+ function readPort(url) {
913
+ const port = Number.parseInt(url.port, 10);
914
+ return Number.isInteger(port) && port > 0 ? port : void 0;
915
+ }
916
+ var DEFAULT_DISCOVERY_TIMEOUT_MS, DISCOVERY_FALLBACK_PORT, OpensteerAttachAmbiguousError;
917
+ var init_cdp_discovery = __esm({
918
+ "src/local-browser/cdp-discovery.ts"() {
919
+ init_chrome_discovery();
920
+ DEFAULT_DISCOVERY_TIMEOUT_MS = 2e3;
921
+ DISCOVERY_FALLBACK_PORT = 9222;
922
+ OpensteerAttachAmbiguousError = class extends Error {
923
+ constructor(candidates) {
924
+ super(
925
+ "Multiple running Chromium DevTools endpoints were discovered. Specify the desired endpoint explicitly."
926
+ );
927
+ this.candidates = candidates;
928
+ this.name = "OpensteerAttachAmbiguousError";
929
+ }
930
+ code = "attach-ambiguous";
931
+ };
932
+ }
933
+ });
934
+ function resolveLiveSessionRecordPath(rootPath, provider) {
935
+ return path12__default.default.join(rootPath, "live", provider === "local" ? "local.json" : "cloud.json");
936
+ }
937
+ async function readPersistedSessionRecord(rootPath, provider) {
938
+ const sessionPath = resolveLiveSessionRecordPath(rootPath, provider);
939
+ if (!await pathExists(sessionPath)) {
940
+ return void 0;
941
+ }
942
+ const parsed = await readJsonFile(sessionPath);
943
+ if (isPersistedLocalBrowserSessionRecord(parsed)) {
944
+ return parsed;
945
+ }
946
+ return void 0;
947
+ }
948
+ async function readPersistedLocalBrowserSessionRecord(rootPath) {
949
+ const record = await readPersistedSessionRecord(rootPath, "local");
950
+ return record?.provider === "local" ? record : void 0;
951
+ }
952
+ async function writePersistedSessionRecord(rootPath, record) {
953
+ await writeJsonFileAtomic(resolveLiveSessionRecordPath(rootPath, record.provider), record);
954
+ }
955
+ async function clearPersistedSessionRecord(rootPath, provider) {
956
+ await promises.rm(resolveLiveSessionRecordPath(rootPath, provider), { force: true });
957
+ }
958
+ function getPersistedLocalBrowserSessionOwnership(record) {
959
+ return record.ownership === "attached" ? "attached" : "owned";
960
+ }
961
+ async function isAttachedLocalBrowserSessionReachable(record) {
962
+ if (getPersistedLocalBrowserSessionOwnership(record) !== "attached") {
963
+ return false;
964
+ }
965
+ if (record.engine !== "playwright" || record.endpoint === void 0) {
966
+ return false;
967
+ }
968
+ try {
969
+ await inspectCdpEndpoint({
970
+ endpoint: record.endpoint,
971
+ timeoutMs: 1500
972
+ });
973
+ return true;
974
+ } catch {
975
+ return false;
976
+ }
977
+ }
978
+ function isPersistedLocalBrowserSessionRecord(value) {
979
+ return value.layout === OPENSTEER_LIVE_SESSION_LAYOUT && value.version === OPENSTEER_LIVE_SESSION_VERSION && value.provider === "local" && (value.engine === "playwright" || value.engine === "abp") && (value.ownership === void 0 || value.ownership === "owned" || value.ownership === "attached") && typeof value.pid === "number" && Number.isFinite(value.pid) && typeof value.startedAt === "number" && Number.isFinite(value.startedAt) && typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt) && typeof value.userDataDir === "string" && value.userDataDir.length > 0;
980
+ }
981
+ var OPENSTEER_LIVE_SESSION_LAYOUT, OPENSTEER_LIVE_SESSION_VERSION;
982
+ var init_live_session = __esm({
983
+ "src/live-session.ts"() {
984
+ init_filesystem2();
985
+ init_cdp_discovery();
986
+ OPENSTEER_LIVE_SESSION_LAYOUT = "opensteer-session";
987
+ OPENSTEER_LIVE_SESSION_VERSION = 1;
988
+ }
989
+ });
990
+ function parseProcessOwner(value) {
991
+ if (!value || typeof value !== "object") {
992
+ return null;
993
+ }
994
+ const parsed = value;
995
+ const pid = Number(parsed.pid);
996
+ const processStartedAtMs = Number(parsed.processStartedAtMs);
997
+ if (!Number.isInteger(pid) || pid <= 0) {
998
+ return null;
999
+ }
1000
+ if (!Number.isInteger(processStartedAtMs) || processStartedAtMs <= 0) {
1001
+ return null;
1002
+ }
1003
+ return {
1004
+ pid,
1005
+ processStartedAtMs
1006
+ };
1007
+ }
1008
+ function processOwnersEqual(left, right) {
1009
+ if (!left || !right) {
1010
+ return left === right;
1011
+ }
1012
+ return left.pid === right.pid && left.processStartedAtMs === right.processStartedAtMs;
1013
+ }
1014
+ async function getProcessLiveness(owner) {
1015
+ if (owner.pid === process.pid && hasMatchingProcessStartTime(owner.processStartedAtMs, PROCESS_STARTED_AT_MS)) {
1016
+ return "live";
1017
+ }
1018
+ const startedAtMs = await readProcessStartedAtMs(owner.pid);
1019
+ if (typeof startedAtMs === "number") {
1020
+ return hasMatchingProcessStartTime(owner.processStartedAtMs, startedAtMs) ? "live" : "dead";
1021
+ }
1022
+ return isProcessRunning2(owner.pid) ? "unknown" : "dead";
1023
+ }
1024
+ function isProcessRunning2(pid) {
1025
+ try {
1026
+ process.kill(pid, 0);
1027
+ return true;
1028
+ } catch (error) {
1029
+ const code = typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : void 0;
1030
+ return code !== "ESRCH";
1031
+ }
1032
+ }
1033
+ function hasMatchingProcessStartTime(expectedStartedAtMs, actualStartedAtMs) {
1034
+ return Math.abs(expectedStartedAtMs - actualStartedAtMs) <= PROCESS_START_TIME_TOLERANCE_MS;
1035
+ }
1036
+ async function readProcessStartedAtMs(pid) {
1037
+ if (pid <= 0) {
1038
+ return null;
1039
+ }
1040
+ if (process.platform === "linux") {
1041
+ return readLinuxProcessStartedAtMs(pid);
1042
+ }
1043
+ if (process.platform === "win32") {
1044
+ return readWindowsProcessStartedAtMs(pid);
1045
+ }
1046
+ return readPsProcessStartedAtMs(pid);
1047
+ }
1048
+ async function readLinuxProcessStartedAtMs(pid) {
1049
+ let statRaw;
1050
+ try {
1051
+ statRaw = await promises.readFile(`/proc/${String(pid)}/stat`, "utf8");
1052
+ } catch {
1053
+ return null;
1054
+ }
1055
+ const startTicks = parseLinuxProcessStartTicks(statRaw);
1056
+ if (startTicks === null) {
1057
+ return null;
1058
+ }
1059
+ const [bootTimeMs, clockTicksPerSecond] = await Promise.all([
1060
+ readLinuxBootTimeMs(),
1061
+ readLinuxClockTicksPerSecond()
1062
+ ]);
1063
+ if (bootTimeMs === null || clockTicksPerSecond === null) {
1064
+ return null;
1065
+ }
1066
+ return Math.floor(bootTimeMs + startTicks * 1e3 / clockTicksPerSecond);
1067
+ }
1068
+ function parseLinuxProcessStartTicks(statRaw) {
1069
+ const closingParenIndex = statRaw.lastIndexOf(")");
1070
+ if (closingParenIndex === -1) {
1071
+ return null;
1072
+ }
1073
+ const fields = statRaw.slice(closingParenIndex + 2).trim().split(/\s+/);
1074
+ const startTicks = Number(fields[LINUX_STAT_START_TIME_FIELD_INDEX]);
1075
+ return Number.isFinite(startTicks) && startTicks >= 0 ? startTicks : null;
1076
+ }
1077
+ async function readLinuxBootTimeMs() {
1078
+ try {
1079
+ const statRaw = await promises.readFile("/proc/stat", "utf8");
1080
+ const bootTimeLine = statRaw.split("\n").find((line) => line.startsWith("btime "));
1081
+ if (!bootTimeLine) {
1082
+ return null;
1083
+ }
1084
+ const bootTimeSeconds = Number.parseInt(bootTimeLine.slice("btime ".length), 10);
1085
+ return Number.isFinite(bootTimeSeconds) ? bootTimeSeconds * 1e3 : null;
1086
+ } catch {
1087
+ return null;
1088
+ }
1089
+ }
1090
+ async function readLinuxClockTicksPerSecond() {
1091
+ if (!linuxClockTicksPerSecondPromise) {
1092
+ linuxClockTicksPerSecondPromise = execFileAsync("getconf", ["CLK_TCK"], {
1093
+ encoding: "utf8",
1094
+ maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES2
1095
+ }).then(({ stdout }) => {
1096
+ const value = Number.parseInt(stdout.trim(), 10);
1097
+ return Number.isFinite(value) && value > 0 ? value : null;
1098
+ }).catch(() => null);
1099
+ }
1100
+ return linuxClockTicksPerSecondPromise;
1101
+ }
1102
+ async function readWindowsProcessStartedAtMs(pid) {
1103
+ try {
1104
+ const { stdout } = await execFileAsync(
1105
+ "powershell.exe",
1106
+ [
1107
+ "-NoProfile",
1108
+ "-Command",
1109
+ `(Get-Process -Id ${String(pid)}).StartTime.ToUniversalTime().ToString("o")`
1110
+ ],
1111
+ {
1112
+ encoding: "utf8",
1113
+ maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES2
1114
+ }
1115
+ );
1116
+ const isoTimestamp = stdout.trim();
1117
+ if (!isoTimestamp) {
1118
+ return null;
1119
+ }
1120
+ const startedAtMs = Date.parse(isoTimestamp);
1121
+ return Number.isFinite(startedAtMs) ? startedAtMs : null;
1122
+ } catch {
1123
+ return null;
1124
+ }
1125
+ }
1126
+ async function readPsProcessStartedAtMs(pid) {
1127
+ try {
1128
+ const { stdout } = await execFileAsync("ps", ["-o", "lstart=", "-p", String(pid)], {
1129
+ encoding: "utf8",
1130
+ env: PS_COMMAND_ENV2,
1131
+ maxBuffer: PROCESS_LIST_MAX_BUFFER_BYTES2
1132
+ });
1133
+ const startedAt = stdout.trim();
1134
+ if (!startedAt) {
1135
+ return null;
1136
+ }
1137
+ const startedAtMs = Date.parse(startedAt.replace(/\s+/g, " "));
1138
+ return Number.isFinite(startedAtMs) ? startedAtMs : null;
1139
+ } catch {
1140
+ return null;
1141
+ }
1142
+ }
1143
+ var execFileAsync, PROCESS_STARTED_AT_MS, PROCESS_START_TIME_TOLERANCE_MS, PROCESS_LIST_MAX_BUFFER_BYTES2, PS_COMMAND_ENV2, LINUX_STAT_START_TIME_FIELD_INDEX, CURRENT_PROCESS_OWNER, linuxClockTicksPerSecondPromise;
1144
+ var init_process_owner = __esm({
1145
+ "src/local-browser/process-owner.ts"() {
1146
+ execFileAsync = util.promisify(child_process.execFile);
1147
+ PROCESS_STARTED_AT_MS = Math.floor(Date.now() - process.uptime() * 1e3);
1148
+ PROCESS_START_TIME_TOLERANCE_MS = 1e3;
1149
+ PROCESS_LIST_MAX_BUFFER_BYTES2 = 16 * 1024 * 1024;
1150
+ PS_COMMAND_ENV2 = { ...process.env, LC_ALL: "C" };
1151
+ LINUX_STAT_START_TIME_FIELD_INDEX = 19;
1152
+ CURRENT_PROCESS_OWNER = {
1153
+ pid: process.pid,
1154
+ processStartedAtMs: PROCESS_STARTED_AT_MS
1155
+ };
1156
+ linuxClockTicksPerSecondPromise = null;
1157
+ }
1158
+ });
1159
+ function resolveOpensteerStateDir() {
1160
+ const explicit = process.env.OPENSTEER_HOME?.trim();
1161
+ if (explicit) {
1162
+ return path12__default.default.resolve(explicit);
1163
+ }
1164
+ if (process.platform === "win32") {
1165
+ return path12__default.default.join(
1166
+ process.env.LOCALAPPDATA ?? path12__default.default.join(os.homedir(), "AppData", "Local"),
1167
+ "Opensteer"
1168
+ );
1169
+ }
1170
+ if (process.platform === "darwin") {
1171
+ return path12__default.default.join(os.homedir(), "Library", "Application Support", "Opensteer");
1172
+ }
1173
+ return path12__default.default.join(
1174
+ process.env.XDG_STATE_HOME ?? path12__default.default.join(os.homedir(), ".local", "state"),
1175
+ "opensteer"
1176
+ );
1177
+ }
1178
+ function resolveLocalViewRootDir() {
1179
+ return path12__default.default.join(resolveOpensteerStateDir(), "local-view");
1180
+ }
1181
+ function resolveLocalViewPreferencesPath() {
1182
+ return path12__default.default.join(resolveLocalViewRootDir(), "preferences.json");
1183
+ }
1184
+ function resolveLocalViewServiceDir() {
1185
+ return path12__default.default.join(resolveLocalViewRootDir(), "service");
1186
+ }
1187
+ function resolveLocalViewSessionsDir() {
1188
+ return path12__default.default.join(resolveLocalViewRootDir(), "sessions");
1189
+ }
1190
+ function resolveLocalViewServiceLockDir() {
1191
+ return path12__default.default.join(resolveLocalViewServiceDir(), "startup.lock");
1192
+ }
1193
+ function resolveLocalViewServiceStatePath() {
1194
+ return path12__default.default.join(resolveLocalViewServiceDir(), "state.json");
1195
+ }
1196
+ var init_runtime_dir = __esm({
1197
+ "src/local-view/runtime-dir.ts"() {
1198
+ }
1199
+ });
1200
+ function buildLocalViewSessionId(input) {
1201
+ const ownership = input.ownership ?? "owned";
1202
+ const identity = ownership === "attached" ? input.endpoint ?? input.remoteDebuggingUrl ?? input.baseUrl ?? "attached" : `pid:${String(input.pid ?? 0)}`;
1203
+ const hash = crypto.createHash("sha256").update(`${input.rootPath}
1204
+ ${ownership}
1205
+ ${identity}
1206
+ ${String(input.startedAt)}`).digest("hex");
1207
+ return `local_${hash.slice(0, 24)}`;
1208
+ }
1209
+ function buildLocalViewSessionIdForRecord(input) {
1210
+ const ownership = getPersistedLocalBrowserSessionOwnership(input.live);
1211
+ if (ownership === "attached") {
1212
+ return buildLocalViewSessionId({
1213
+ rootPath: input.rootPath,
1214
+ ownership,
1215
+ startedAt: input.live.startedAt,
1216
+ ...input.live.endpoint === void 0 ? {} : { endpoint: input.live.endpoint },
1217
+ ...input.live.baseUrl === void 0 ? {} : { baseUrl: input.live.baseUrl },
1218
+ ...input.live.remoteDebuggingUrl === void 0 ? {} : { remoteDebuggingUrl: input.live.remoteDebuggingUrl }
1219
+ });
1099
1220
  }
1100
- return await response.json();
1221
+ return buildLocalViewSessionId({
1222
+ rootPath: input.rootPath,
1223
+ ownership,
1224
+ startedAt: input.live.startedAt,
1225
+ pid: input.live.pid
1226
+ });
1101
1227
  }
1102
- async function isHttpEndpointReachable(url, timeoutMs) {
1103
- const response = await fetch(url, {
1104
- signal: AbortSignal.timeout(timeoutMs)
1105
- }).catch(() => null);
1106
- return response !== null;
1228
+ function createLocalViewSessionManifest(input) {
1229
+ return {
1230
+ layout: OPENSTEER_LOCAL_VIEW_SESSION_LAYOUT,
1231
+ version: OPENSTEER_LOCAL_VIEW_SESSION_VERSION,
1232
+ sessionId: buildLocalViewSessionIdForRecord({
1233
+ rootPath: input.rootPath,
1234
+ live: input.live
1235
+ }),
1236
+ rootPath: input.rootPath,
1237
+ ...input.workspace === void 0 ? {} : { workspace: input.workspace },
1238
+ engine: input.live.engine,
1239
+ ownership: input.ownership,
1240
+ pid: input.live.pid,
1241
+ startedAt: input.live.startedAt,
1242
+ updatedAt: Date.now()
1243
+ };
1107
1244
  }
1108
- function buildBrowserWebSocketUrl(httpUrl, webSocketPath) {
1109
- const protocol = httpUrl.protocol === "https:" ? "wss:" : "ws:";
1110
- return `${protocol}//${httpUrl.host}${normalizeWebSocketPath(webSocketPath)}`;
1245
+ async function writeLocalViewSessionManifest(manifest) {
1246
+ await ensureDirectory(resolveLocalViewSessionsDir());
1247
+ await writeJsonFileAtomic(resolveLocalViewSessionManifestPath(manifest.sessionId), manifest);
1111
1248
  }
1112
- function normalizeWebSocketPath(path15) {
1113
- return path15.startsWith("/") ? path15 : `/${path15}`;
1249
+ async function deleteLocalViewSessionManifest(sessionId) {
1250
+ await promises.rm(resolveLocalViewSessionManifestPath(sessionId), { force: true }).catch(() => void 0);
1114
1251
  }
1115
- function rewriteBrowserWebSocketHost(browserWsUrl, requestedUrl) {
1116
- try {
1117
- const parsed = new URL(browserWsUrl);
1118
- parsed.protocol = requestedUrl.protocol === "https:" ? "wss:" : "ws:";
1119
- parsed.hostname = requestedUrl.hostname;
1120
- parsed.port = requestedUrl.port;
1121
- return parsed.toString();
1122
- } catch {
1123
- return browserWsUrl;
1252
+ async function readLocalViewSessionManifest(sessionId) {
1253
+ const manifestPath = resolveLocalViewSessionManifestPath(sessionId);
1254
+ if (!await pathExists(manifestPath)) {
1255
+ return void 0;
1124
1256
  }
1257
+ const parsed = await readJsonFile(manifestPath);
1258
+ return isPersistedLocalViewSessionManifest(parsed) ? parsed : void 0;
1125
1259
  }
1126
- function readPort(url) {
1127
- const port = Number.parseInt(url.port, 10);
1128
- return Number.isInteger(port) && port > 0 ? port : void 0;
1260
+ async function listLocalViewSessionManifests() {
1261
+ const directoryPath = resolveLocalViewSessionsDir();
1262
+ const fileNames = await listJsonFiles(directoryPath);
1263
+ const manifests = await Promise.all(
1264
+ fileNames.map(async (fileName) => {
1265
+ const parsed = await readJsonFile(
1266
+ path12__default.default.join(directoryPath, fileName)
1267
+ ).catch(() => void 0);
1268
+ return isPersistedLocalViewSessionManifest(parsed) ? parsed : void 0;
1269
+ })
1270
+ );
1271
+ return manifests.filter((manifest) => manifest !== void 0).sort(
1272
+ (left, right) => left.startedAt - right.startedAt || left.sessionId.localeCompare(right.sessionId)
1273
+ );
1129
1274
  }
1130
- var DEFAULT_DISCOVERY_TIMEOUT_MS, DISCOVERY_FALLBACK_PORT, OpensteerAttachAmbiguousError;
1131
- var init_cdp_discovery = __esm({
1132
- "src/local-browser/cdp-discovery.ts"() {
1133
- init_chrome_discovery();
1134
- DEFAULT_DISCOVERY_TIMEOUT_MS = 2e3;
1135
- DISCOVERY_FALLBACK_PORT = 9222;
1136
- OpensteerAttachAmbiguousError = class extends Error {
1137
- constructor(candidates) {
1138
- super(
1139
- "Multiple running Chromium DevTools endpoints were discovered. Specify the desired endpoint explicitly."
1140
- );
1141
- this.candidates = candidates;
1142
- this.name = "OpensteerAttachAmbiguousError";
1143
- }
1144
- code = "attach-ambiguous";
1145
- };
1275
+ function resolveLocalViewSessionManifestPath(sessionId) {
1276
+ return path12__default.default.join(resolveLocalViewSessionsDir(), `${sessionId}.json`);
1277
+ }
1278
+ function isPersistedLocalViewSessionManifest(value) {
1279
+ 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);
1280
+ }
1281
+ var OPENSTEER_LOCAL_VIEW_SESSION_LAYOUT, OPENSTEER_LOCAL_VIEW_SESSION_VERSION;
1282
+ var init_session_manifest = __esm({
1283
+ "src/local-view/session-manifest.ts"() {
1284
+ init_filesystem2();
1285
+ init_live_session();
1286
+ init_runtime_dir();
1287
+ OPENSTEER_LOCAL_VIEW_SESSION_LAYOUT = "opensteer-local-view-session";
1288
+ OPENSTEER_LOCAL_VIEW_SESSION_VERSION = 1;
1146
1289
  }
1147
1290
  });
1148
1291
  async function readLocalViewServiceState() {
@@ -2228,7 +2371,7 @@ var init_version = __esm({
2228
2371
  "../protocol/src/version.ts"() {
2229
2372
  init_json2();
2230
2373
  OPENSTEER_PROTOCOL_NAME = "opensteer";
2231
- OPENSTEER_PROTOCOL_COMPATIBILITY_REVISION = 2;
2374
+ OPENSTEER_PROTOCOL_COMPATIBILITY_REVISION = 3;
2232
2375
  OPENSTEER_PROTOCOL_VERSION = `0.${OPENSTEER_PROTOCOL_COMPATIBILITY_REVISION}.0`;
2233
2376
  OPENSTEER_PROTOCOL_MEDIA_TYPE = `application/vnd.${OPENSTEER_PROTOCOL_NAME}+json;version=${OPENSTEER_PROTOCOL_VERSION}`;
2234
2377
  OPENSTEER_PROTOCOL_REST_BASE_PATH = `/api/v${OPENSTEER_PROTOCOL_COMPATIBILITY_REVISION}`;
@@ -6252,14 +6395,14 @@ var init_interaction = __esm({
6252
6395
  function defineSemanticOperationSpec(spec) {
6253
6396
  return spec;
6254
6397
  }
6255
- var opensteerComputerAnnotationNames, opensteerExposedSemanticOperationNames, opensteerPackageRunnableSemanticOperationNames, snapshotModeSchema, viewportSchema, opensteerBrowserLaunchOptionsSchema, attachBrowserOptionsSchema, opensteerBrowserOptionsSchema, opensteerBrowserContextOptionsSchema, targetByElementSchema2, targetByPersistSchema2, targetBySelectorSchema2, opensteerTargetInputSchema, opensteerResolvedTargetSchema, opensteerActionResultSchema, opensteerSnapshotCounterSchema, opensteerSessionStateSchema, opensteerOpenInputSchema, opensteerPageListInputSchema, opensteerPageListOutputSchema, opensteerPageNewInputSchema, opensteerPageActivateInputSchema, opensteerPageCloseInputSchema, opensteerPageCloseOutputSchema, opensteerPageGotoInputSchema, opensteerPageEvaluateInputSchema, opensteerPageEvaluateOutputSchema, opensteerAddInitScriptInputSchema, opensteerAddInitScriptOutputSchema, opensteerCapturedScriptSchema, opensteerCaptureScriptsInputSchema, opensteerCaptureScriptsOutputSchema, opensteerPageSnapshotInputSchema, opensteerPageSnapshotOutputSchema, opensteerComputerMouseButtonSchema, opensteerComputerKeyModifierSchema, opensteerDomClickInputSchema, opensteerDomHoverInputSchema, opensteerDomInputInputSchema, opensteerDomScrollInputSchema, opensteerExtractTemplateSchema, opensteerDomExtractInputSchema, jsonValueSchema2, opensteerDomExtractOutputSchema, opensteerSessionCloseInputSchema, opensteerSessionCloseOutputSchema, opensteerComputerAnnotationSchema, opensteerComputerClickActionSchema, opensteerComputerMoveActionSchema, opensteerComputerScrollActionSchema, opensteerComputerTypeActionSchema, opensteerComputerKeyActionSchema, opensteerComputerDragActionSchema, opensteerComputerScreenshotActionSchema, opensteerComputerWaitActionSchema, opensteerComputerActionSchema, opensteerComputerScreenshotOptionsSchema, opensteerComputerExecuteInputSchema, opensteerComputerTracePointSchema, opensteerComputerTraceEnrichmentSchema, opensteerComputerExecuteTimingSchema, opensteerComputerDisplayScaleSchema, opensteerComputerExecuteOutputSchema, opensteerSemanticOperationSpecificationsBase, exposedSemanticOperationNameSet, opensteerSemanticOperationSpecificationsInternal, opensteerSemanticOperationSpecifications, semanticRestBasePath;
6398
+ var opensteerComputerAnnotationNames, opensteerExposedSemanticOperationNames, opensteerPackageRunnableSemanticOperationNames, snapshotModeSchema, viewportSchema, opensteerBrowserLaunchOptionsSchema, attachBrowserOptionsSchema, opensteerBrowserOptionsSchema, opensteerBrowserContextOptionsSchema, targetByElementSchema2, targetByPersistSchema2, targetBySelectorSchema2, opensteerTargetInputSchema, opensteerActionResultSchema, opensteerSnapshotCounterSchema, opensteerNavigationSummarySchema, opensteerOpenInputSchema, opensteerPageListInputSchema, opensteerPageListOutputSchema, opensteerPageNewInputSchema, opensteerPageActivateInputSchema, opensteerPageCloseInputSchema, opensteerPageCloseOutputSchema, opensteerPageNewOutputSchema, opensteerPageGotoInputSchema, opensteerPageEvaluateInputSchema, opensteerPageEvaluateOutputSchema, opensteerAddInitScriptInputSchema, opensteerAddInitScriptOutputSchema, opensteerCapturedScriptSchema, opensteerCaptureScriptsInputSchema, opensteerCaptureScriptsOutputSchema, opensteerPageSnapshotInputSchema, opensteerPageSnapshotOutputSchema, opensteerComputerMouseButtonSchema, opensteerComputerKeyModifierSchema, opensteerDomClickInputSchema, opensteerDomHoverInputSchema, opensteerDomInputInputSchema, opensteerDomScrollInputSchema, opensteerExtractTemplateSchema, opensteerDomExtractInputSchema, jsonValueSchema2, opensteerDomExtractOutputSchema, opensteerSessionCloseInputSchema, opensteerSessionCloseOutputSchema, opensteerComputerAnnotationSchema, opensteerComputerClickActionSchema, opensteerComputerMoveActionSchema, opensteerComputerScrollActionSchema, opensteerComputerTypeActionSchema, opensteerComputerKeyActionSchema, opensteerComputerDragActionSchema, opensteerComputerScreenshotActionSchema, opensteerComputerWaitActionSchema, opensteerComputerActionSchema, opensteerComputerScreenshotOptionsSchema, opensteerComputerExecuteInputSchema, opensteerScreenshotSummarySchema, opensteerComputerExecuteOutputSchema, opensteerSemanticOperationSpecificationsBase, exposedSemanticOperationNameSet, opensteerSemanticOperationSpecificationsInternal, opensteerSemanticOperationSpecifications, semanticRestBasePath;
6256
6399
  var init_semantic = __esm({
6257
6400
  "../protocol/src/semantic.ts"() {
6258
6401
  init_json2();
6402
+ init_binary_location();
6259
6403
  init_identity();
6260
6404
  init_geometry();
6261
6405
  init_metadata();
6262
- init_events();
6263
6406
  init_envelopes();
6264
6407
  init_snapshots();
6265
6408
  init_artifacts2();
@@ -6482,39 +6625,14 @@ var init_semantic = __esm({
6482
6625
  title: "OpensteerTargetInput"
6483
6626
  }
6484
6627
  );
6485
- opensteerResolvedTargetSchema = objectSchema(
6486
- {
6487
- pageRef: pageRefSchema,
6488
- frameRef: frameRefSchema,
6489
- documentRef: documentRefSchema,
6490
- documentEpoch: documentEpochSchema,
6491
- nodeRef: nodeRefSchema,
6492
- tagName: stringSchema(),
6493
- pathHint: stringSchema(),
6494
- persist: stringSchema(),
6495
- selectorUsed: stringSchema()
6496
- },
6497
- {
6498
- title: "OpensteerResolvedTarget",
6499
- required: [
6500
- "pageRef",
6501
- "frameRef",
6502
- "documentRef",
6503
- "documentEpoch",
6504
- "nodeRef",
6505
- "tagName",
6506
- "pathHint"
6507
- ]
6508
- }
6509
- );
6510
6628
  opensteerActionResultSchema = objectSchema(
6511
6629
  {
6512
- target: opensteerResolvedTargetSchema,
6513
- point: pointSchema
6630
+ tagName: stringSchema({ minLength: 1 }),
6631
+ persist: stringSchema({ minLength: 1 })
6514
6632
  },
6515
6633
  {
6516
6634
  title: "OpensteerActionResult",
6517
- required: ["target"]
6635
+ required: ["tagName"]
6518
6636
  }
6519
6637
  );
6520
6638
  opensteerSnapshotCounterSchema = objectSchema(
@@ -6560,16 +6678,14 @@ var init_semantic = __esm({
6560
6678
  ]
6561
6679
  }
6562
6680
  );
6563
- opensteerSessionStateSchema = objectSchema(
6681
+ opensteerNavigationSummarySchema = objectSchema(
6564
6682
  {
6565
- sessionRef: sessionRefSchema,
6566
- pageRef: pageRefSchema,
6567
6683
  url: stringSchema(),
6568
6684
  title: stringSchema()
6569
6685
  },
6570
6686
  {
6571
- title: "OpensteerSessionState",
6572
- required: ["sessionRef", "pageRef", "url", "title"]
6687
+ title: "OpensteerNavigationSummary",
6688
+ required: ["url", "title"]
6573
6689
  }
6574
6690
  );
6575
6691
  opensteerOpenInputSchema = objectSchema(
@@ -6637,6 +6753,17 @@ var init_semantic = __esm({
6637
6753
  required: ["closedPageRef", "pages"]
6638
6754
  }
6639
6755
  );
6756
+ opensteerPageNewOutputSchema = objectSchema(
6757
+ {
6758
+ pageRef: pageRefSchema,
6759
+ url: stringSchema(),
6760
+ title: stringSchema()
6761
+ },
6762
+ {
6763
+ title: "OpensteerPageNewOutput",
6764
+ required: ["pageRef", "url", "title"]
6765
+ }
6766
+ );
6640
6767
  opensteerPageGotoInputSchema = objectSchema(
6641
6768
  {
6642
6769
  url: stringSchema(),
@@ -7023,72 +7150,28 @@ var init_semantic = __esm({
7023
7150
  required: ["action"]
7024
7151
  }
7025
7152
  );
7026
- opensteerComputerTracePointSchema = objectSchema(
7027
- {
7028
- role: enumSchema(["point", "start", "end"]),
7029
- point: pointSchema,
7030
- hitTest: hitTestResultSchema,
7031
- target: opensteerResolvedTargetSchema
7032
- },
7033
- {
7034
- title: "OpensteerComputerTracePoint",
7035
- required: ["role", "point"]
7036
- }
7037
- );
7038
- opensteerComputerTraceEnrichmentSchema = objectSchema(
7039
- {
7040
- points: arraySchema(opensteerComputerTracePointSchema)
7041
- },
7042
- {
7043
- title: "OpensteerComputerTraceEnrichment",
7044
- required: ["points"]
7045
- }
7046
- );
7047
- opensteerComputerExecuteTimingSchema = objectSchema(
7153
+ opensteerScreenshotSummarySchema = objectSchema(
7048
7154
  {
7049
- actionMs: integerSchema({ minimum: 0 }),
7050
- waitMs: integerSchema({ minimum: 0 }),
7051
- totalMs: integerSchema({ minimum: 0 })
7052
- },
7053
- {
7054
- title: "OpensteerComputerExecuteTiming",
7055
- required: ["actionMs", "waitMs", "totalMs"]
7056
- }
7057
- );
7058
- opensteerComputerDisplayScaleSchema = objectSchema(
7059
- {
7060
- x: numberSchema({ exclusiveMinimum: 0 }),
7061
- y: numberSchema({ exclusiveMinimum: 0 })
7155
+ payload: externalBinaryLocationSchema,
7156
+ format: screenshotFormatSchema,
7157
+ size: sizeSchema,
7158
+ coordinateSpace: coordinateSpaceSchema,
7159
+ clip: rectSchema
7062
7160
  },
7063
7161
  {
7064
- title: "OpensteerComputerDisplayScale",
7065
- required: ["x", "y"]
7162
+ title: "OpensteerScreenshotSummary",
7163
+ required: ["payload", "format", "size", "coordinateSpace"]
7066
7164
  }
7067
7165
  );
7068
7166
  opensteerComputerExecuteOutputSchema = objectSchema(
7069
7167
  {
7070
- action: opensteerComputerActionSchema,
7071
- pageRef: pageRefSchema,
7072
- screenshot: screenshotArtifactSchema,
7073
- displayViewport: viewportMetricsSchema,
7074
- nativeViewport: viewportMetricsSchema,
7075
- displayScale: opensteerComputerDisplayScaleSchema,
7076
- events: arraySchema(opensteerEventSchema),
7077
- timing: opensteerComputerExecuteTimingSchema,
7078
- trace: opensteerComputerTraceEnrichmentSchema
7168
+ url: stringSchema(),
7169
+ title: stringSchema(),
7170
+ screenshot: opensteerScreenshotSummarySchema
7079
7171
  },
7080
7172
  {
7081
7173
  title: "OpensteerComputerExecuteOutput",
7082
- required: [
7083
- "action",
7084
- "pageRef",
7085
- "screenshot",
7086
- "displayViewport",
7087
- "nativeViewport",
7088
- "displayScale",
7089
- "events",
7090
- "timing"
7091
- ]
7174
+ required: ["url", "title", "screenshot"]
7092
7175
  }
7093
7176
  );
7094
7177
  opensteerSemanticOperationSpecificationsBase = [
@@ -7096,7 +7179,7 @@ var init_semantic = __esm({
7096
7179
  name: "session.open",
7097
7180
  description: "Open or resume the current Opensteer session and primary page.",
7098
7181
  inputSchema: opensteerOpenInputSchema,
7099
- outputSchema: opensteerSessionStateSchema,
7182
+ outputSchema: opensteerNavigationSummarySchema,
7100
7183
  requiredCapabilities: ["sessions.manage", "pages.manage"],
7101
7184
  resolveRequiredCapabilities: (input) => input.url === void 0 ? ["sessions.manage", "pages.manage"] : ["sessions.manage", "pages.manage", "pages.navigate"]
7102
7185
  }),
@@ -7111,7 +7194,7 @@ var init_semantic = __esm({
7111
7194
  name: "page.new",
7112
7195
  description: "Create and optionally navigate a new top-level page in the current session.",
7113
7196
  inputSchema: opensteerPageNewInputSchema,
7114
- outputSchema: opensteerSessionStateSchema,
7197
+ outputSchema: opensteerPageNewOutputSchema,
7115
7198
  requiredCapabilities: ["pages.manage"],
7116
7199
  resolveRequiredCapabilities: (input) => input.url === void 0 ? ["pages.manage"] : ["pages.manage", "pages.navigate"]
7117
7200
  }),
@@ -7119,7 +7202,7 @@ var init_semantic = __esm({
7119
7202
  name: "page.activate",
7120
7203
  description: "Activate an existing top-level page in the current session.",
7121
7204
  inputSchema: opensteerPageActivateInputSchema,
7122
- outputSchema: opensteerSessionStateSchema,
7205
+ outputSchema: opensteerNavigationSummarySchema,
7123
7206
  requiredCapabilities: ["pages.manage", "inspect.pages"]
7124
7207
  }),
7125
7208
  defineSemanticOperationSpec({
@@ -7133,7 +7216,7 @@ var init_semantic = __esm({
7133
7216
  name: "page.goto",
7134
7217
  description: "Navigate the current Opensteer page to a new URL.",
7135
7218
  inputSchema: opensteerPageGotoInputSchema,
7136
- outputSchema: opensteerSessionStateSchema,
7219
+ outputSchema: opensteerNavigationSummarySchema,
7137
7220
  requiredCapabilities: ["pages.navigate"]
7138
7221
  }),
7139
7222
  defineSemanticOperationSpec({
@@ -9800,6 +9883,7 @@ function toPersistedLocalBrowserSessionRecord(workspace, live) {
9800
9883
  version: 1,
9801
9884
  provider: "local",
9802
9885
  ...workspace === void 0 ? {} : { workspace },
9886
+ ownership: live.ownership,
9803
9887
  engine: live.engine,
9804
9888
  ...live.endpoint === void 0 ? {} : { endpoint: live.endpoint },
9805
9889
  ...live.baseUrl === void 0 ? {} : { baseUrl: live.baseUrl },
@@ -9815,6 +9899,7 @@ function toPersistedLocalBrowserSessionRecord(workspace, live) {
9815
9899
  function toWorkspaceLiveBrowserRecord(record) {
9816
9900
  return {
9817
9901
  mode: "persistent",
9902
+ ownership: getPersistedLocalBrowserSessionOwnership(record),
9818
9903
  engine: record.engine,
9819
9904
  ...record.endpoint === void 0 ? {} : { endpoint: record.endpoint },
9820
9905
  ...record.baseUrl === void 0 ? {} : { baseUrl: record.baseUrl },
@@ -9841,7 +9926,12 @@ function isAttachBrowserOptions(browser) {
9841
9926
  async function resolveAttachEndpoint(browser) {
9842
9927
  const endpoint = browser?.endpoint?.trim();
9843
9928
  if (endpoint && endpoint.length > 0) {
9844
- return endpoint;
9929
+ const inspected = await inspectCdpEndpoint({
9930
+ endpoint,
9931
+ ...browser?.headers === void 0 ? {} : { headers: browser.headers },
9932
+ timeoutMs: DEFAULT_TIMEOUT_MS
9933
+ });
9934
+ return inspected.endpoint;
9845
9935
  }
9846
9936
  const selection = await selectAttachBrowserCandidate({
9847
9937
  timeoutMs: DEFAULT_TIMEOUT_MS
@@ -10105,12 +10195,12 @@ async function waitForProcessExit(pid, timeoutMs) {
10105
10195
  }
10106
10196
  const deadline = Date.now() + timeoutMs;
10107
10197
  while (Date.now() < deadline) {
10108
- if (!isProcessRunning(pid)) {
10198
+ if (!isProcessRunning2(pid)) {
10109
10199
  return true;
10110
10200
  }
10111
10201
  await sleep2(50);
10112
10202
  }
10113
- return !isProcessRunning(pid);
10203
+ return !isProcessRunning2(pid);
10114
10204
  }
10115
10205
  function resolveAbpSessionDir(workspace) {
10116
10206
  return path12__default.default.join(workspace.livePath, "abp-session");
@@ -10267,7 +10357,7 @@ var init_browser_manager = __esm({
10267
10357
  }
10268
10358
  const liveRecord = await this.readLivePersistentBrowser(await this.ensureWorkspaceStore());
10269
10359
  return {
10270
- mode: this.mode,
10360
+ mode: liveRecord?.ownership === "attached" ? "attach" : this.mode,
10271
10361
  engine: liveRecord?.engine ?? this.engineName,
10272
10362
  ...this.workspace === void 0 ? {} : { workspace: this.workspace },
10273
10363
  live: liveRecord !== void 0
@@ -10395,6 +10485,7 @@ var init_browser_manager = __esm({
10395
10485
  });
10396
10486
  const liveRecord = {
10397
10487
  mode: "persistent",
10488
+ ownership: "owned",
10398
10489
  engine: "abp",
10399
10490
  baseUrl: launched.baseUrl,
10400
10491
  remoteDebuggingUrl: launched.remoteDebuggingUrl,
@@ -10481,11 +10572,78 @@ var init_browser_manager = __esm({
10481
10572
  }
10482
10573
  async createAttachEngine() {
10483
10574
  const endpoint = await resolveAttachEndpoint(this.browserOptions);
10484
- return this.createAttachedEngine({
10485
- endpoint,
10486
- ...this.browserOptions?.headers === void 0 ? {} : { headers: this.browserOptions.headers },
10487
- freshTab: this.browserOptions?.freshTab ?? true,
10488
- onDispose: async () => void 0
10575
+ if (this.workspace === void 0) {
10576
+ return this.createAttachedEngine({
10577
+ endpoint,
10578
+ ...this.browserOptions?.headers === void 0 ? {} : { headers: this.browserOptions.headers },
10579
+ freshTab: this.browserOptions?.freshTab ?? true,
10580
+ onDispose: async () => void 0
10581
+ });
10582
+ }
10583
+ const workspace = await this.ensureWorkspaceStore();
10584
+ return workspace.lock(async () => {
10585
+ const live = await this.readLivePersistentBrowser(workspace);
10586
+ if (live) {
10587
+ if (live.engine !== "playwright") {
10588
+ throw new Error(
10589
+ `workspace "${this.workspace}" already has a live ${live.engine} browser. Close it before attaching a Playwright browser.`
10590
+ );
10591
+ }
10592
+ if (live.ownership !== "attached") {
10593
+ throw new Error(
10594
+ `workspace "${this.workspace}" already has a live Opensteer-owned browser. Close it before attaching another browser.`
10595
+ );
10596
+ }
10597
+ if (live.endpoint === void 0) {
10598
+ throw new Error("workspace live browser record is missing a DevTools endpoint.");
10599
+ }
10600
+ if (live.endpoint !== endpoint) {
10601
+ throw new Error(
10602
+ `workspace "${this.workspace}" is already attached to a different browser endpoint. Close it before reattaching.`
10603
+ );
10604
+ }
10605
+ await bestEffortRegisterLocalViewSession({
10606
+ rootPath: workspace.rootPath,
10607
+ ...this.workspace === void 0 ? {} : { workspace: this.workspace },
10608
+ live: toPersistedLocalBrowserSessionRecord(this.workspace, live),
10609
+ ownership: "attached"
10610
+ });
10611
+ return this.createAttachedEngine({
10612
+ endpoint: live.endpoint,
10613
+ ...this.browserOptions?.headers === void 0 ? {} : { headers: this.browserOptions.headers },
10614
+ freshTab: this.browserOptions?.freshTab ?? true,
10615
+ onDispose: async () => void 0
10616
+ });
10617
+ }
10618
+ const liveRecord = {
10619
+ mode: "persistent",
10620
+ ownership: "attached",
10621
+ engine: "playwright",
10622
+ endpoint,
10623
+ pid: 0,
10624
+ startedAt: Date.now(),
10625
+ userDataDir: workspace.browserUserDataDir
10626
+ };
10627
+ await this.writeLivePersistentBrowser(workspace, liveRecord);
10628
+ const persistedLiveRecord = toPersistedLocalBrowserSessionRecord(this.workspace, liveRecord);
10629
+ await bestEffortRegisterLocalViewSession({
10630
+ rootPath: workspace.rootPath,
10631
+ ...this.workspace === void 0 ? {} : { workspace: this.workspace },
10632
+ live: persistedLiveRecord,
10633
+ ownership: "attached"
10634
+ });
10635
+ try {
10636
+ return await this.createAttachedEngine({
10637
+ endpoint,
10638
+ ...this.browserOptions?.headers === void 0 ? {} : { headers: this.browserOptions.headers },
10639
+ freshTab: this.browserOptions?.freshTab ?? true,
10640
+ onDispose: async () => void 0
10641
+ });
10642
+ } catch (error) {
10643
+ await this.unregisterLocalViewSessionForRecord(workspace.rootPath, persistedLiveRecord);
10644
+ await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
10645
+ throw error;
10646
+ }
10489
10647
  });
10490
10648
  }
10491
10649
  async createPersistentEngine() {
@@ -10505,7 +10663,7 @@ var init_browser_manager = __esm({
10505
10663
  rootPath: workspace.rootPath,
10506
10664
  ...this.workspace === void 0 ? {} : { workspace: this.workspace },
10507
10665
  live: toPersistedLocalBrowserSessionRecord(this.workspace, live),
10508
- ownership: "owned"
10666
+ ownership: live.ownership
10509
10667
  });
10510
10668
  return this.createAttachedEngine({
10511
10669
  endpoint: live.endpoint,
@@ -10521,6 +10679,7 @@ var init_browser_manager = __esm({
10521
10679
  });
10522
10680
  const liveRecord = {
10523
10681
  mode: "persistent",
10682
+ ownership: "owned",
10524
10683
  engine: "playwright",
10525
10684
  endpoint: launched.endpoint,
10526
10685
  pid: launched.pid,
@@ -10650,7 +10809,20 @@ var init_browser_manager = __esm({
10650
10809
  if (live === void 0) {
10651
10810
  return void 0;
10652
10811
  }
10653
- if (!isProcessRunning(live.pid)) {
10812
+ if (live.ownership === "attached") {
10813
+ const attachedRecord = toPersistedLocalBrowserSessionRecord(this.workspace, live);
10814
+ if (!await isAttachedLocalBrowserSessionReachable(attachedRecord)) {
10815
+ await this.unregisterLocalViewSessionForRecord(workspace.rootPath, attachedRecord);
10816
+ await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
10817
+ return void 0;
10818
+ }
10819
+ return live;
10820
+ }
10821
+ if (!isProcessRunning2(live.pid)) {
10822
+ await this.unregisterLocalViewSessionForRecord(
10823
+ workspace.rootPath,
10824
+ toPersistedLocalBrowserSessionRecord(this.workspace, live)
10825
+ );
10654
10826
  await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
10655
10827
  return void 0;
10656
10828
  }
@@ -10698,6 +10870,10 @@ var init_browser_manager = __esm({
10698
10870
  workspace.rootPath,
10699
10871
  toPersistedLocalBrowserSessionRecord(this.workspace, live)
10700
10872
  );
10873
+ if (live.ownership === "attached") {
10874
+ await clearPersistedSessionRecord(workspace.rootPath, "local").catch(() => void 0);
10875
+ return;
10876
+ }
10701
10877
  if (live.engine === "playwright") {
10702
10878
  if (live.endpoint !== void 0) {
10703
10879
  await requestBrowserClose(live.endpoint).catch(() => void 0);
@@ -10726,10 +10902,18 @@ var init_browser_manager = __esm({
10726
10902
  }
10727
10903
  async unregisterLocalViewSessionForRecord(rootPath, record) {
10728
10904
  await bestEffortUnregisterLocalViewSession(
10729
- buildLocalViewSessionId({
10905
+ getPersistedLocalBrowserSessionOwnership(record) === "attached" ? buildLocalViewSessionId({
10730
10906
  rootPath,
10731
- pid: record.pid,
10732
- startedAt: record.startedAt
10907
+ startedAt: record.startedAt,
10908
+ ownership: "attached",
10909
+ ...record.endpoint === void 0 ? {} : { endpoint: record.endpoint },
10910
+ ...record.baseUrl === void 0 ? {} : { baseUrl: record.baseUrl },
10911
+ ...record.remoteDebuggingUrl === void 0 ? {} : { remoteDebuggingUrl: record.remoteDebuggingUrl }
10912
+ }) : buildLocalViewSessionId({
10913
+ rootPath,
10914
+ startedAt: record.startedAt,
10915
+ ownership: "owned",
10916
+ pid: record.pid
10733
10917
  })
10734
10918
  );
10735
10919
  }
@@ -10833,13 +11017,13 @@ async function resolveSessionSummary(manifest) {
10833
11017
  return {
10834
11018
  sessionId: manifest.sessionId,
10835
11019
  label: manifest.workspace ?? (path12__default.default.basename(manifest.rootPath) || manifest.sessionId),
10836
- status: isProcessRunning(record.pid) ? "live" : "stale",
11020
+ status: getPersistedLocalBrowserSessionOwnership(record) === "attached" || isProcessRunning2(record.pid) ? "live" : "stale",
10837
11021
  ...manifest.workspace === void 0 ? {} : { workspace: manifest.workspace },
10838
11022
  rootPath: manifest.rootPath,
10839
11023
  engine: record.engine,
10840
11024
  ownership: manifest.ownership,
10841
- pid: record.pid,
10842
11025
  startedAt: record.startedAt,
11026
+ ...record.pid > 0 ? { pid: record.pid } : {},
10843
11027
  ...browserName === void 0 ? {} : { browserName }
10844
11028
  };
10845
11029
  }
@@ -10867,7 +11051,16 @@ async function readLiveRecord(manifest) {
10867
11051
  if (!record) {
10868
11052
  return void 0;
10869
11053
  }
10870
- if (record.pid !== manifest.pid || record.startedAt !== manifest.startedAt || !isProcessRunning(record.pid)) {
11054
+ if (buildLocalViewSessionIdForRecord({
11055
+ rootPath: manifest.rootPath,
11056
+ live: record
11057
+ }) !== manifest.sessionId || record.startedAt !== manifest.startedAt || record.engine !== manifest.engine || getPersistedLocalBrowserSessionOwnership(record) !== manifest.ownership) {
11058
+ return void 0;
11059
+ }
11060
+ if (getPersistedLocalBrowserSessionOwnership(record) === "attached") {
11061
+ return await isAttachedLocalBrowserSessionReachable(record) ? record : void 0;
11062
+ }
11063
+ if (record.pid !== manifest.pid || !isProcessRunning2(record.pid)) {
10871
11064
  return void 0;
10872
11065
  }
10873
11066
  return record;