@dotobokuri/fleet-console 1.23.0 → 1.24.0

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 (69) hide show
  1. package/AGENTS.md +41 -257
  2. package/README.md +8 -0
  3. package/dist/cli.d.ts +11 -1
  4. package/dist/cli.mjs +1738 -453
  5. package/dist/client/assets/{_baseUniq-BjOe5yYF.js → _baseUniq-yooYbTgN.js} +1 -1
  6. package/dist/client/assets/{arc-bFUuO3jf.js → arc-D4mme5Ts.js} +1 -1
  7. package/dist/client/assets/{architectureDiagram-Q4EWVU46-Ba7nimQq.js → architectureDiagram-Q4EWVU46-BAJOviEX.js} +1 -1
  8. package/dist/client/assets/{blockDiagram-DXYQGD6D-CQGf47hl.js → blockDiagram-DXYQGD6D-DuObpxQ3.js} +1 -1
  9. package/dist/client/assets/{c4Diagram-AHTNJAMY-DZF_cAK4.js → c4Diagram-AHTNJAMY-BOT-SD34.js} +1 -1
  10. package/dist/client/assets/channel-TZYyAq1v.js +1 -0
  11. package/dist/client/assets/{chunk-4BX2VUAB-DjUUHSQ5.js → chunk-4BX2VUAB-BF_aMTes.js} +1 -1
  12. package/dist/client/assets/{chunk-4TB4RGXK-Dvqti8bd.js → chunk-4TB4RGXK-M4brBcnD.js} +1 -1
  13. package/dist/client/assets/{chunk-55IACEB6-2BxiBvkp.js → chunk-55IACEB6-CrG-4b2Z.js} +1 -1
  14. package/dist/client/assets/{chunk-EDXVE4YY-CqCjol5O.js → chunk-EDXVE4YY-BxDSygH1.js} +1 -1
  15. package/dist/client/assets/{chunk-FMBD7UC4-eoCfIbly.js → chunk-FMBD7UC4-Bh7MEX7E.js} +1 -1
  16. package/dist/client/assets/{chunk-OYMX7WX6-CoC86V_1.js → chunk-OYMX7WX6-DBnBwxer.js} +1 -1
  17. package/dist/client/assets/{chunk-QZHKN3VN-CJQA5mlQ.js → chunk-QZHKN3VN-bjAogQwW.js} +1 -1
  18. package/dist/client/assets/{chunk-YZCP3GAM-C5C4kbLu.js → chunk-YZCP3GAM-l4FGlk_c.js} +1 -1
  19. package/dist/client/assets/classDiagram-6PBFFD2Q-Dh6hZUl4.js +1 -0
  20. package/dist/client/assets/classDiagram-v2-HSJHXN6E-Dh6hZUl4.js +1 -0
  21. package/dist/client/assets/clone-DgWGnVWH.js +1 -0
  22. package/dist/client/assets/{cose-bilkent-S5V4N54A-CahuunpL.js → cose-bilkent-S5V4N54A-Bm5QA4zo.js} +1 -1
  23. package/dist/client/assets/{dagre-KV5264BT-Cbv5kSi5.js → dagre-KV5264BT-PzXQN-5Q.js} +1 -1
  24. package/dist/client/assets/{diagram-5BDNPKRD-CNm40ZGB.js → diagram-5BDNPKRD-KhaAKPtX.js} +1 -1
  25. package/dist/client/assets/{diagram-G4DWMVQ6-DrlGNT3i.js → diagram-G4DWMVQ6-DBbjfiR8.js} +1 -1
  26. package/dist/client/assets/{diagram-MMDJMWI5-r8Y2oqmw.js → diagram-MMDJMWI5-QWro7PRX.js} +1 -1
  27. package/dist/client/assets/{diagram-TYMM5635-CMzEhXup.js → diagram-TYMM5635-B9LqWrbF.js} +1 -1
  28. package/dist/client/assets/{erDiagram-SMLLAGMA-UFjUUMPz.js → erDiagram-SMLLAGMA-b0VkMMN4.js} +1 -1
  29. package/dist/client/assets/{flowDiagram-DWJPFMVM-Dd1STmYc.js → flowDiagram-DWJPFMVM-edxBAbe6.js} +1 -1
  30. package/dist/client/assets/{ganttDiagram-T4ZO3ILL-CHHU0hVd.js → ganttDiagram-T4ZO3ILL-ks2Gg1_V.js} +1 -1
  31. package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-vj2HukAF.js → gitGraphDiagram-UUTBAWPF-DymUyYNl.js} +1 -1
  32. package/dist/client/assets/{graph-DhjfEbEb.js → graph-B-B_mqfh.js} +1 -1
  33. package/dist/client/assets/index-BgLzfg-v.css +1 -0
  34. package/dist/client/assets/index-pDnb-q6f.js +380 -0
  35. package/dist/client/assets/{infoDiagram-42DDH7IO-CC7AEgDm.js → infoDiagram-42DDH7IO-B5iLIYAc.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-DlRw2B9G.js → ishikawaDiagram-UXIWVN3A-DnztLLn7.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-VCZTEJTY-BbRXANKQ.js → journeyDiagram-VCZTEJTY-BKDRfH1Y.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-6JOO6SKY-DtvdCxGN.js → kanban-definition-6JOO6SKY-DPB3Phvr.js} +1 -1
  39. package/dist/client/assets/{layout-Dg5cvw5v.js → layout-BEeRhYTG.js} +1 -1
  40. package/dist/client/assets/{linear-BvVlQLqF.js → linear-QzmfWKSq.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-DzrV8sC-.js → mermaid.core-BVka5dgm.js} +4 -4
  42. package/dist/client/assets/{min-DNGQ1Ciz.js → min-CfXk9kSN.js} +1 -1
  43. package/dist/client/assets/{mindmap-definition-QFDTVHPH-DxJCNZbg.js → mindmap-definition-QFDTVHPH-tGrGhr6Z.js} +1 -1
  44. package/dist/client/assets/{pieDiagram-DEJITSTG-B75XHG-s.js → pieDiagram-DEJITSTG-ZRBqRiRK.js} +1 -1
  45. package/dist/client/assets/{quadrantDiagram-34T5L4WZ-CL4yoF0m.js → quadrantDiagram-34T5L4WZ-VCINZgct.js} +1 -1
  46. package/dist/client/assets/{requirementDiagram-MS252O5E-kGV4vcr1.js → requirementDiagram-MS252O5E-DFLzADh3.js} +1 -1
  47. package/dist/client/assets/{sankeyDiagram-XADWPNL6-DoA4u7wI.js → sankeyDiagram-XADWPNL6-BbouN8Ji.js} +1 -1
  48. package/dist/client/assets/{sequenceDiagram-FGHM5R23-ZpNDf1bS.js → sequenceDiagram-FGHM5R23-B58_LF0R.js} +1 -1
  49. package/dist/client/assets/{stateDiagram-FHFEXIEX-BzgKcVAG.js → stateDiagram-FHFEXIEX-pk-OHHuL.js} +1 -1
  50. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-xsN122JA.js +1 -0
  51. package/dist/client/assets/{timeline-definition-GMOUNBTQ-B_DkUDsD.js → timeline-definition-GMOUNBTQ-4jQdCvaj.js} +1 -1
  52. package/dist/client/assets/{vennDiagram-DHZGUBPP-MCUnnsKb.js → vennDiagram-DHZGUBPP-CUr1fH6N.js} +1 -1
  53. package/dist/client/assets/{wardley-RL74JXVD-D_JAMSxj.js → wardley-RL74JXVD-D0cfvvNA.js} +1 -1
  54. package/dist/client/assets/{wardleyDiagram-NUSXRM2D-BHCwZGLq.js → wardleyDiagram-NUSXRM2D-D0l_Rp_Z.js} +1 -1
  55. package/dist/client/assets/{xychartDiagram-5P7HB3ND-FbGwbD3V.js → xychartDiagram-5P7HB3ND-BdR8OEuJ.js} +1 -1
  56. package/dist/client/index.html +3 -3
  57. package/dist/desktop-protocol.d.ts +49 -0
  58. package/dist/desktop-protocol.mjs +99 -0
  59. package/dist/fleet-plugins/diff/routes.mjs +124 -225
  60. package/dist/fleet-plugins/skills/routes.mjs +218 -82
  61. package/dist/fleet-plugins/terminal/routes.mjs +190 -176
  62. package/package.json +7 -2
  63. package/dist/client/assets/channel-D2BahVp1.js +0 -1
  64. package/dist/client/assets/classDiagram-6PBFFD2Q-DsDAVLhS.js +0 -1
  65. package/dist/client/assets/classDiagram-v2-HSJHXN6E-DsDAVLhS.js +0 -1
  66. package/dist/client/assets/clone-CdZy2Xzf.js +0 -1
  67. package/dist/client/assets/index-Dq9EKm2G.css +0 -1
  68. package/dist/client/assets/index-DtzbOdd7.js +0 -380
  69. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-yRK5hzZ4.js +0 -1
package/dist/cli.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { spawn, execFileSync, execFile, execSync } from 'child_process';
3
- import * as fs11 from 'fs';
4
- import fs11__default, { realpathSync, accessSync, constants, existsSync, lstatSync } from 'fs';
5
- import * as path25 from 'path';
6
- import path25__default, { join, resolve, relative, sep, dirname } from 'path';
2
+ import { execFile, spawn, execFileSync, execSync } from 'child_process';
3
+ import * as fs14 from 'fs';
4
+ import fs14__default, { realpathSync, accessSync, constants, existsSync, lstatSync } from 'fs';
5
+ import * as path27 from 'path';
6
+ import path27__default, { join, resolve, relative, sep, dirname } from 'path';
7
7
  import process4 from 'process';
8
8
  import { fileURLToPath, pathToFileURL } from 'url';
9
9
  import * as os4 from 'os';
@@ -12,9 +12,11 @@ import crypto5, { createHash, randomUUID } from 'crypto';
12
12
  import { builtinModules, createRequire } from 'module';
13
13
  import http2 from 'http';
14
14
  import { EventEmitter } from 'events';
15
- import { stat, readFile, realpath, readdir, mkdir, mkdtemp, writeFile, appendFile, rm, rename, access } from 'fs/promises';
15
+ import { stat, mkdir, readdir, readFile, rm, realpath, mkdtemp, writeFile, appendFile, rename, access } from 'fs/promises';
16
16
  import { Writable, Readable } from 'stream';
17
+ import fontList from 'font-list';
17
18
  import net2 from 'net';
19
+ import { promisify } from 'util';
18
20
 
19
21
  var __defProp = Object.defineProperty;
20
22
  var __export = (target, all) => {
@@ -37,7 +39,7 @@ function findBinaryPath(command2, env, options = {}) {
37
39
  return findOnPath(command2, pathValue, pathExts, platform);
38
40
  }
39
41
  function wrapWindowsShim(resolved, env, platform) {
40
- if (platform !== "win32" || !WINDOWS_SHIM_EXTENSIONS.has(path25__default.extname(resolved).toLowerCase())) {
42
+ if (platform !== "win32" || !WINDOWS_SHIM_EXTENSIONS.has(path27__default.extname(resolved).toLowerCase())) {
41
43
  return { bin: resolved, prefixArgs: [] };
42
44
  }
43
45
  rejectCmdExpansionSensitiveShim(resolved);
@@ -55,13 +57,13 @@ function forceWindowsArgQuoting(value) {
55
57
  return `${value} `;
56
58
  }
57
59
  function findOnPath(bin, pathValue, pathExts, platform) {
58
- const pathSeparator = platform === "win32" ? WINDOWS_PATH_SEPARATOR : path25__default.delimiter;
59
- if (hasPathSeparator(bin, platform) || path25__default.isAbsolute(bin)) {
60
+ const pathSeparator = platform === "win32" ? WINDOWS_PATH_SEPARATOR : path27__default.delimiter;
61
+ if (hasPathSeparator(bin, platform) || path27__default.isAbsolute(bin)) {
60
62
  return resolveWithExtensions(bin, pathExts, platform);
61
63
  }
62
64
  for (const entry of pathValue.split(pathSeparator)) {
63
65
  const searchDir = entry.length === 0 ? "." : entry;
64
- const candidate = resolveWithExtensions(path25__default.join(searchDir, bin), pathExts, platform);
66
+ const candidate = resolveWithExtensions(path27__default.join(searchDir, bin), pathExts, platform);
65
67
  if (candidate) {
66
68
  return candidate;
67
69
  }
@@ -69,7 +71,7 @@ function findOnPath(bin, pathValue, pathExts, platform) {
69
71
  return void 0;
70
72
  }
71
73
  function resolveWithExtensions(candidate, pathExts, platform) {
72
- if (platform !== "win32" || path25__default.extname(candidate).length > 0) {
74
+ if (platform !== "win32" || path27__default.extname(candidate).length > 0) {
73
75
  return isAccessibleExecutable(candidate, platform) ? candidate : void 0;
74
76
  }
75
77
  for (const ext of pathExts) {
@@ -304,7 +306,7 @@ function isC1Control(code) {
304
306
  var LOCK_DIR_MODE = 448;
305
307
  var LOCK_FILE_MODE = 384;
306
308
  function createConsoleLock(deps = {}) {
307
- const fsImpl = deps.fs ?? fs11__default;
309
+ const fsImpl = deps.fs ?? fs14__default;
308
310
  const now = deps.now ?? Date.now;
309
311
  const randomToken = deps.randomToken ?? (() => crypto5.randomBytes(32).toString("base64url"));
310
312
  const hostname5 = deps.hostname ?? (() => "127.0.0.1");
@@ -333,9 +335,10 @@ function createConsoleLock(deps = {}) {
333
335
  endpoint: input.endpoint,
334
336
  startedAt: now(),
335
337
  token: randomToken(),
336
- version: input.version
338
+ version: input.version,
339
+ ...input.owner ? { owner: input.owner } : {}
337
340
  };
338
- const fd = fsImpl.openSync(input.lockFile, fs11__default.constants.O_CREAT | fs11__default.constants.O_EXCL | fs11__default.constants.O_WRONLY, LOCK_FILE_MODE);
341
+ const fd = fsImpl.openSync(input.lockFile, fs14__default.constants.O_CREAT | fs14__default.constants.O_EXCL | fs14__default.constants.O_WRONLY, LOCK_FILE_MODE);
339
342
  try {
340
343
  fsImpl.writeFileSync(fd, `${JSON.stringify(payload, null, 2)}
341
344
  `, "utf8");
@@ -361,7 +364,7 @@ function createConsoleLock(deps = {}) {
361
364
  }
362
365
  function assertLockModes(lockFile) {
363
366
  if (typeof process.getuid !== "function") return;
364
- const dir = path25__default.dirname(lockFile);
367
+ const dir = path27__default.dirname(lockFile);
365
368
  const dirMode = fsImpl.statSync(dir).mode & 511;
366
369
  const fileMode = fsImpl.statSync(lockFile).mode & 511;
367
370
  if (dirMode !== LOCK_DIR_MODE) {
@@ -403,14 +406,14 @@ function createConsoleLock(deps = {}) {
403
406
  }
404
407
  var FLEET_DATA_DIR_NAME = ".fleet";
405
408
  function getFleetDataDir() {
406
- return path25.join(os4.homedir(), FLEET_DATA_DIR_NAME);
409
+ return path27.join(os4.homedir(), FLEET_DATA_DIR_NAME);
407
410
  }
408
411
  var SECURE_DIR_MODE = 448;
409
412
  var SECURE_FILE_MODE = 384;
410
- var NOFOLLOW_FLAG = fs11.constants.O_NOFOLLOW ?? 0;
413
+ var NOFOLLOW_FLAG = fs14.constants.O_NOFOLLOW ?? 0;
411
414
  function safeLstat(targetPath) {
412
415
  try {
413
- return fs11.lstatSync(targetPath);
416
+ return fs14.lstatSync(targetPath);
414
417
  } catch (error51) {
415
418
  if (error51.code === "ENOENT") {
416
419
  return null;
@@ -424,15 +427,15 @@ function ensureSafeDirectory(dirPath) {
424
427
  if (!stat5.isDirectory() || stat5.isSymbolicLink()) {
425
428
  throw new Error(`Unsafe Fleet directory: ${dirPath}`);
426
429
  }
427
- fs11.chmodSync(dirPath, SECURE_DIR_MODE);
430
+ fs14.chmodSync(dirPath, SECURE_DIR_MODE);
428
431
  return;
429
432
  }
430
- fs11.mkdirSync(dirPath, { recursive: true, mode: SECURE_DIR_MODE });
433
+ fs14.mkdirSync(dirPath, { recursive: true, mode: SECURE_DIR_MODE });
431
434
  const created = safeLstat(dirPath);
432
435
  if (!created?.isDirectory() || created.isSymbolicLink()) {
433
436
  throw new Error(`Unsafe Fleet directory: ${dirPath}`);
434
437
  }
435
- fs11.chmodSync(dirPath, SECURE_DIR_MODE);
438
+ fs14.chmodSync(dirPath, SECURE_DIR_MODE);
436
439
  }
437
440
 
438
441
  // ../../packages/core-infra/src/fs-store/atomic-write.ts
@@ -451,17 +454,17 @@ function writeAtomicSync(filePath, content, options = {}) {
451
454
  const tempPath = buildTempPath(filePath);
452
455
  let tempCreated = false;
453
456
  try {
454
- fs11.writeFileSync(tempPath, content, { encoding: "utf-8", flag: "wx", mode });
457
+ fs14.writeFileSync(tempPath, content, { encoding: "utf-8", flag: "wx", mode });
455
458
  tempCreated = true;
456
459
  if (doFsync) {
457
- const fd = fs11.openSync(tempPath, "r");
460
+ const fd = fs14.openSync(tempPath, "r");
458
461
  try {
459
462
  fsyncSyncBestEffort(fd);
460
463
  } finally {
461
- fs11.closeSync(fd);
464
+ fs14.closeSync(fd);
462
465
  }
463
466
  }
464
- fs11.renameSync(tempPath, filePath);
467
+ fs14.renameSync(tempPath, filePath);
465
468
  return;
466
469
  } catch (error51) {
467
470
  const code = error51.code;
@@ -471,7 +474,7 @@ function writeAtomicSync(filePath, content, options = {}) {
471
474
  }
472
475
  if (tempCreated) {
473
476
  try {
474
- fs11.unlinkSync(tempPath);
477
+ fs14.unlinkSync(tempPath);
475
478
  } catch {
476
479
  }
477
480
  }
@@ -483,7 +486,7 @@ function writeAtomicSync(filePath, content, options = {}) {
483
486
  function cleanupTempFiles(dir, prefix, minAgeMs, now) {
484
487
  let entries;
485
488
  try {
486
- entries = fs11.readdirSync(dir);
489
+ entries = fs14.readdirSync(dir);
487
490
  } catch {
488
491
  return;
489
492
  }
@@ -491,11 +494,11 @@ function cleanupTempFiles(dir, prefix, minAgeMs, now) {
491
494
  if (!entry.startsWith(prefix)) {
492
495
  continue;
493
496
  }
494
- const tempPath = path25.join(dir, entry);
497
+ const tempPath = path27.join(dir, entry);
495
498
  try {
496
- const stat5 = fs11.lstatSync(tempPath);
499
+ const stat5 = fs14.lstatSync(tempPath);
497
500
  if (stat5?.isFile() && !stat5.isSymbolicLink() && now - stat5.mtimeMs >= minAgeMs) {
498
- fs11.unlinkSync(tempPath);
501
+ fs14.unlinkSync(tempPath);
499
502
  }
500
503
  } catch {
501
504
  }
@@ -507,7 +510,7 @@ function isIgnoredFsyncError(error51) {
507
510
  }
508
511
  function fsyncSyncBestEffort(fd) {
509
512
  try {
510
- fs11.fsyncSync(fd);
513
+ fs14.fsyncSync(fd);
511
514
  } catch (error51) {
512
515
  if (!isIgnoredFsyncError(error51)) {
513
516
  throw error51;
@@ -527,7 +530,7 @@ function withDirectoryLock(deps, operation) {
527
530
  const startedAt = now();
528
531
  while (true) {
529
532
  try {
530
- fs11.mkdirSync(deps.lockDir, { mode: 448 });
533
+ fs14.mkdirSync(deps.lockDir, { mode: 448 });
531
534
  writeLockOwner(deps.lockDir, ownerFileName, now());
532
535
  try {
533
536
  return operation();
@@ -547,7 +550,7 @@ function withDirectoryLock(deps, operation) {
547
550
  }
548
551
  function releaseLock(lockDir) {
549
552
  try {
550
- fs11.rmSync(lockDir, { recursive: true, force: true });
553
+ fs14.rmSync(lockDir, { recursive: true, force: true });
551
554
  } catch {
552
555
  }
553
556
  }
@@ -557,14 +560,14 @@ function recoverStaleLock(lockDir, ownerFileName, now, staleLockMs) {
557
560
  if (!isRecoverableSnapshot(initialSnapshot, now, staleLockMs)) return;
558
561
  const quarantineDir = buildQuarantinePath(lockDir);
559
562
  try {
560
- fs11.renameSync(lockDir, quarantineDir);
563
+ fs14.renameSync(lockDir, quarantineDir);
561
564
  } catch {
562
565
  return;
563
566
  }
564
567
  try {
565
568
  const quarantinedSnapshot = readLockSnapshot(quarantineDir, ownerFileName);
566
569
  if (sameLockSnapshot(initialSnapshot, quarantinedSnapshot) && isRecoverableSnapshot(quarantinedSnapshot, now, staleLockMs)) {
567
- fs11.rmSync(quarantineDir, { recursive: true, force: true });
570
+ fs14.rmSync(quarantineDir, { recursive: true, force: true });
568
571
  } else {
569
572
  restoreQuarantinedLock(quarantineDir, lockDir);
570
573
  }
@@ -578,7 +581,7 @@ function recoverStaleLock(lockDir, ownerFileName, now, staleLockMs) {
578
581
  }
579
582
  }
580
583
  function readLockSnapshot(lockDir, ownerFileName) {
581
- const stat5 = fs11.lstatSync(lockDir);
584
+ const stat5 = fs14.lstatSync(lockDir);
582
585
  if (stat5.isSymbolicLink()) {
583
586
  return { owner: null, mtimeMs: 0 };
584
587
  }
@@ -597,18 +600,18 @@ function writeLockOwner(lockDir, ownerFileName, startedAt) {
597
600
  hostname: os4.hostname(),
598
601
  startedAt
599
602
  };
600
- const ownerPath = path25.join(lockDir, ownerFileName);
603
+ const ownerPath = path27.join(lockDir, ownerFileName);
601
604
  try {
602
- fs11.writeFileSync(ownerPath, JSON.stringify(owner), { encoding: "utf-8", mode: 384 });
605
+ fs14.writeFileSync(ownerPath, JSON.stringify(owner), { encoding: "utf-8", mode: 384 });
603
606
  } catch (error51) {
604
607
  releaseLock(lockDir);
605
608
  throw error51;
606
609
  }
607
610
  }
608
611
  function readLockOwner(lockDir, ownerFileName) {
609
- const ownerPath = path25.join(lockDir, ownerFileName);
612
+ const ownerPath = path27.join(lockDir, ownerFileName);
610
613
  try {
611
- return sanitizeLockOwner(JSON.parse(fs11.readFileSync(ownerPath, "utf-8")));
614
+ return sanitizeLockOwner(JSON.parse(fs14.readFileSync(ownerPath, "utf-8")));
612
615
  } catch (error51) {
613
616
  const code = error51.code;
614
617
  if (code === "ENOENT") return null;
@@ -630,7 +633,7 @@ function sameLockSnapshot(a, b) {
630
633
  }
631
634
  function restoreQuarantinedLock(quarantineDir, lockDir) {
632
635
  try {
633
- fs11.renameSync(quarantineDir, lockDir);
636
+ fs14.renameSync(quarantineDir, lockDir);
634
637
  } catch {
635
638
  }
636
639
  }
@@ -665,17 +668,17 @@ function sleepSync(ms) {
665
668
  function readAuthStoreFile(filePath) {
666
669
  let fd;
667
670
  try {
668
- fd = fs11.openSync(filePath, fs11.constants.O_RDONLY | NOFOLLOW_FLAG);
669
- if (!fs11.fstatSync(fd).isFile()) {
671
+ fd = fs14.openSync(filePath, fs14.constants.O_RDONLY | NOFOLLOW_FLAG);
672
+ if (!fs14.fstatSync(fd).isFile()) {
670
673
  return {};
671
674
  }
672
- return JSON.parse(fs11.readFileSync(fd, "utf-8"));
675
+ return JSON.parse(fs14.readFileSync(fd, "utf-8"));
673
676
  } catch {
674
677
  return {};
675
678
  } finally {
676
679
  if (fd !== void 0) {
677
680
  try {
678
- fs11.closeSync(fd);
681
+ fs14.closeSync(fd);
679
682
  } catch {
680
683
  }
681
684
  }
@@ -683,7 +686,7 @@ function readAuthStoreFile(filePath) {
683
686
  }
684
687
 
685
688
  // ../../packages/core-infra/src/auth/auth-storage.ts
686
- var DEFAULT_AUTH_PATH = path25.join(getFleetDataDir(), "auth.json");
689
+ var DEFAULT_AUTH_PATH = path27.join(getFleetDataDir(), "auth.json");
687
690
  var AUTH_LOCK_OWNER_FILE_NAME = "owner.json";
688
691
  var AUTH_LOCK_TIMEOUT_MS = 5e3;
689
692
  function createAuthService(deps = {}) {
@@ -694,7 +697,7 @@ function createAuthService(deps = {}) {
694
697
  async deleteApiKey(providerId) {
695
698
  let deleted = false;
696
699
  withAuthLock(authPath, lockDir, () => {
697
- if (!fs11.existsSync(authPath)) return;
700
+ if (!fs14.existsSync(authPath)) return;
698
701
  const data = readAuthStoreFile(authPath);
699
702
  if (!Object.prototype.hasOwnProperty.call(data, providerId)) return;
700
703
  delete data[providerId];
@@ -707,21 +710,21 @@ function createAuthService(deps = {}) {
707
710
  async getApiKey(providerId) {
708
711
  let fd;
709
712
  try {
710
- if (!fs11.existsSync(authPath)) {
713
+ if (!fs14.existsSync(authPath)) {
711
714
  return void 0;
712
715
  }
713
- fd = fs11.openSync(authPath, fs11.constants.O_RDONLY | NOFOLLOW_FLAG);
714
- if (!fs11.fstatSync(fd).isFile()) {
716
+ fd = fs14.openSync(authPath, fs14.constants.O_RDONLY | NOFOLLOW_FLAG);
717
+ if (!fs14.fstatSync(fd).isFile()) {
715
718
  return void 0;
716
719
  }
717
- const data = JSON.parse(fs11.readFileSync(fd, "utf-8"));
720
+ const data = JSON.parse(fs14.readFileSync(fd, "utf-8"));
718
721
  return typeof data[providerId]?.key === "string" ? data[providerId].key : void 0;
719
722
  } catch {
720
723
  return void 0;
721
724
  } finally {
722
725
  if (fd !== void 0) {
723
726
  try {
724
- fs11.closeSync(fd);
727
+ fs14.closeSync(fd);
725
728
  } catch {
726
729
  }
727
730
  }
@@ -729,7 +732,7 @@ function createAuthService(deps = {}) {
729
732
  },
730
733
  // [HIGH #2] 읽기 경로: fd 기반 O_RDONLY|O_NOFOLLOW 통일
731
734
  async listProviderIds() {
732
- if (!fs11.existsSync(authPath)) {
735
+ if (!fs14.existsSync(authPath)) {
733
736
  return [];
734
737
  }
735
738
  try {
@@ -739,10 +742,10 @@ function createAuthService(deps = {}) {
739
742
  }
740
743
  },
741
744
  async setApiKey(providerId, key) {
742
- const dir = path25.dirname(authPath);
745
+ const dir = path27.dirname(authPath);
743
746
  ensureSafeDirectory(dir);
744
747
  withAuthLock(authPath, lockDir, () => {
745
- const data = fs11.existsSync(authPath) ? readAuthStoreFile(authPath) : {};
748
+ const data = fs14.existsSync(authPath) ? readAuthStoreFile(authPath) : {};
746
749
  data[providerId] = {
747
750
  ...data[providerId] ?? {},
748
751
  key
@@ -775,7 +778,7 @@ function createDurableJsonStore(deps) {
775
778
  const sensitivity = deps.sensitivity;
776
779
  const mode = sensitivity === "sensitive" ? SECURE_FILE_MODE : 420;
777
780
  const minAgeMs = deps.tempCleanupMinAgeMs ?? DEFAULT_TEMP_FILE_MIN_AGE_MS;
778
- const dir = path25.dirname(deps.filePath);
781
+ const dir = path27.dirname(deps.filePath);
779
782
  return {
780
783
  path: deps.filePath,
781
784
  load() {
@@ -825,21 +828,21 @@ function createDurableJsonStore(deps) {
825
828
  function readJsonFile(filePath, sanitize) {
826
829
  let fd;
827
830
  try {
828
- const stat5 = fs11.lstatSync(filePath);
831
+ const stat5 = fs14.lstatSync(filePath);
829
832
  if (!stat5 || !stat5.isFile() || stat5.isSymbolicLink()) {
830
833
  return sanitize(void 0);
831
834
  }
832
- fd = fs11.openSync(filePath, fs11.constants.O_RDONLY | NOFOLLOW_FLAG);
833
- if (!fs11.fstatSync(fd).isFile()) {
835
+ fd = fs14.openSync(filePath, fs14.constants.O_RDONLY | NOFOLLOW_FLAG);
836
+ if (!fs14.fstatSync(fd).isFile()) {
834
837
  return sanitize(void 0);
835
838
  }
836
- return sanitize(JSON.parse(fs11.readFileSync(fd, "utf-8")));
839
+ return sanitize(JSON.parse(fs14.readFileSync(fd, "utf-8")));
837
840
  } catch {
838
841
  return sanitize(void 0);
839
842
  } finally {
840
843
  if (fd !== void 0) {
841
844
  try {
842
- fs11.closeSync(fd);
845
+ fs14.closeSync(fd);
843
846
  } catch {
844
847
  }
845
848
  }
@@ -849,7 +852,7 @@ function ensureSafeDirectoryIfSensitive(dir, sensitivity) {
849
852
  if (sensitivity === "sensitive") {
850
853
  ensureSafeDirectory(dir);
851
854
  } else {
852
- fs11.mkdirSync(dir, { recursive: true });
855
+ fs14.mkdirSync(dir, { recursive: true });
853
856
  }
854
857
  }
855
858
 
@@ -861,8 +864,8 @@ var LOCK_OWNER_FILE_NAME = "owner";
861
864
  var TEMP_FILE_PREFIX = `.tmp-${GLOBAL_OPTIONS_FILE_NAME}-`;
862
865
  function createGlobalOptionsStore(deps = {}) {
863
866
  const dataDir = deps.dataDir ?? getFleetDataDir();
864
- const optionsPath = path25.join(dataDir, GLOBAL_OPTIONS_FILE_NAME);
865
- const lockDir = path25.join(dataDir, LOCK_DIR_NAME);
867
+ const optionsPath = path27.join(dataDir, GLOBAL_OPTIONS_FILE_NAME);
868
+ const lockDir = path27.join(dataDir, LOCK_DIR_NAME);
866
869
  const store2 = createDurableJsonStore({
867
870
  filePath: optionsPath,
868
871
  lockDir,
@@ -895,10 +898,11 @@ function sanitizeGlobalOptionsData(value) {
895
898
  }
896
899
  const data = {
897
900
  version: GLOBAL_OPTIONS_VERSION,
898
- ...typeof value.enableMetaphor === "boolean" ? { enableMetaphor: value.enableMetaphor } : {}
901
+ ...typeof value.enableMetaphor === "boolean" ? { enableMetaphor: value.enableMetaphor } : {},
902
+ ...value.codexLaunchMode === "acp" || value.codexLaunchMode === "app-server" ? { codexLaunchMode: value.codexLaunchMode } : {}
899
903
  };
900
- const allowedKeys = /* @__PURE__ */ new Set(["version", "enableMetaphor"]);
901
- const changed = Object.keys(value).some((key) => !allowedKeys.has(key)) || "enableMetaphor" in value && typeof value.enableMetaphor !== "boolean";
904
+ const allowedKeys = /* @__PURE__ */ new Set(["version", "enableMetaphor", "codexLaunchMode"]);
905
+ const changed = Object.keys(value).some((key) => !allowedKeys.has(key)) || "enableMetaphor" in value && typeof value.enableMetaphor !== "boolean" || "codexLaunchMode" in value && value.codexLaunchMode !== "acp" && value.codexLaunchMode !== "app-server";
902
906
  return { data, changed };
903
907
  }
904
908
  function isRecord(value) {
@@ -930,16 +934,100 @@ function createInfraServices(deps = {}) {
930
934
  globalOptionsService
931
935
  };
932
936
  }
933
- function readFleetConsoleRelease() {
937
+ var DESKTOP_PROTOCOL_VERSION = 1;
938
+ var DESKTOP_RESOURCE_ROOT_ENV = "FLEET_CONSOLE_RESOURCE_ROOT";
939
+ var DESKTOP_OWNER_ID_ENV = "FLEET_CONSOLE_OWNER_ID";
940
+ var DESKTOP_OWNER_KIND_ENV = "FLEET_CONSOLE_OWNER_KIND";
941
+ var DESKTOP_PROTOCOL_VERSION_ENV = "FLEET_CONSOLE_PROTOCOL_VERSION";
942
+ var DESKTOP_RESOURCE_ROOT_MARKER = ".fleet-console-resource-root";
943
+ var DESKTOP_DEVELOPMENT_ENV = "FLEET_CONSOLE_DESKTOP_DEVELOPMENT";
944
+ var LOCK_DIR_NAME2 = "fleet-console";
945
+ var LOCK_FILE_NAME = "console.lock";
946
+ var CONSOLE_DATA_DIR_NAME = "console";
947
+ var CONSOLE_STATE_FILE_NAME = "state.json";
948
+ var CONSOLE_SETTINGS_FILE_NAME = "settings.json";
949
+ var CONSOLE_CAPTURES_DIR_NAME = "captures";
950
+ function readDesktopProtocolEnvironment(env = process.env, deps = {}) {
951
+ const resourceRoot = env[DESKTOP_RESOURCE_ROOT_ENV];
952
+ const ownerId = env[DESKTOP_OWNER_ID_ENV];
953
+ const ownerKind = env[DESKTOP_OWNER_KIND_ENV];
954
+ const protocolVersion = env[DESKTOP_PROTOCOL_VERSION_ENV];
955
+ const values = [resourceRoot, ownerId, ownerKind, protocolVersion];
956
+ if (values.every((value) => value === void 0)) return null;
957
+ if (values.some((value) => !value)) throw new Error("desktop_protocol_environment_incomplete");
958
+ if (ownerKind !== "desktop") throw new Error("desktop_owner_kind_invalid");
959
+ if (!isSafeOwnerId(ownerId)) throw new Error("desktop_owner_id_invalid");
960
+ if (protocolVersion !== String(DESKTOP_PROTOCOL_VERSION)) throw new Error("desktop_protocol_version_unsupported");
961
+ const resolvedRoot = isDesktopDevelopmentEnvironment(env) ? validateDesktopDevelopmentResourceRoot(resourceRoot) : validateDesktopResourceRoot(resourceRoot, deps.expectedPackageRoot);
962
+ return { owner: { kind: "desktop", id: ownerId, protocolVersion: DESKTOP_PROTOCOL_VERSION }, resourceRoot: resolvedRoot };
963
+ }
964
+ function isDesktopDevelopmentEnvironment(env = process.env) {
965
+ return env[DESKTOP_DEVELOPMENT_ENV] === "1";
966
+ }
967
+ function resolveCanonicalStableConsolePaths(input) {
968
+ const dir = input.consoleDirOverride ?? path27__default.join(input.tmpDir, `${LOCK_DIR_NAME2}-${input.uid}-stable`);
969
+ const dataDir = input.consoleDirOverride ?? path27__default.join(input.fleetDataDir, CONSOLE_DATA_DIR_NAME);
970
+ return {
971
+ dir,
972
+ lockFile: path27__default.join(dir, LOCK_FILE_NAME),
973
+ dataDir,
974
+ stateFile: path27__default.join(dataDir, CONSOLE_STATE_FILE_NAME),
975
+ settingsFile: path27__default.join(dataDir, CONSOLE_SETTINGS_FILE_NAME),
976
+ capturesDir: path27__default.join(dataDir, CONSOLE_CAPTURES_DIR_NAME)
977
+ };
978
+ }
979
+ function validateDesktopResourceRoot(root, expectedPackageRoot = resolveDesktopProtocolPackageRoot()) {
980
+ if (!path27__default.isAbsolute(root)) throw new Error("desktop_resource_root_not_absolute");
981
+ const resolvedRoot = fs14__default.realpathSync(root);
982
+ const expectedRoot = fs14__default.realpathSync(expectedPackageRoot);
983
+ if (resolvedRoot !== expectedRoot) throw new Error("desktop_resource_root_invalid");
984
+ const markerPath = path27__default.join(resolvedRoot, DESKTOP_RESOURCE_ROOT_MARKER);
985
+ const marker = fs14__default.readFileSync(markerPath, "utf8").trim();
986
+ if (marker !== String(DESKTOP_PROTOCOL_VERSION)) throw new Error("desktop_resource_root_marker_invalid");
987
+ return resolvedRoot;
988
+ }
989
+ function validateDesktopDevelopmentResourceRoot(root) {
990
+ if (!path27__default.isAbsolute(root)) throw new Error("desktop_resource_root_not_absolute");
991
+ let resolvedRoot;
992
+ try {
993
+ resolvedRoot = fs14__default.realpathSync(root);
994
+ } catch {
995
+ throw new Error("desktop_development_resource_root_invalid");
996
+ }
997
+ const expectedRoot = fs14__default.realpathSync(resolveDesktopProtocolPackageRoot());
998
+ if (resolvedRoot !== expectedRoot) throw new Error("desktop_development_resource_root_invalid");
999
+ return resolvedRoot;
1000
+ }
1001
+ function resolveDesktopProtocolPackageRoot() {
1002
+ const requireFromHere = createRequire(import.meta.url);
1003
+ for (const candidate of ["../package.json", "../../package.json"]) {
1004
+ try {
1005
+ return path27__default.dirname(requireFromHere.resolve(candidate));
1006
+ } catch (error51) {
1007
+ if (error51.code !== "MODULE_NOT_FOUND") throw error51;
1008
+ }
1009
+ }
1010
+ throw new Error("fleet_console_package_json_not_found");
1011
+ }
1012
+ function isSafeOwnerId(value) {
1013
+ return value.length > 0 && value.length <= 256 && /^[A-Za-z0-9._-]+$/.test(value);
1014
+ }
1015
+ function readFleetConsoleRelease(env = process.env) {
1016
+ const desktop = readDesktopProtocolEnvironment(env);
1017
+ if (desktop) return readReleaseAt(desktop.resourceRoot, isDesktopDevelopmentEnvironment(env) ? "local" : "desktop");
934
1018
  const requireFromHere = createRequire(import.meta.url);
935
1019
  const packageJsonPath = resolvePackageJsonPath(requireFromHere);
1020
+ const packageRoot = path27__default.dirname(packageJsonPath);
936
1021
  const pkg = requireFromHere(packageJsonPath);
937
- const packageRoot = path25__default.dirname(packageJsonPath);
938
- const version2 = pkg.version ?? "";
939
1022
  if (pkg.private === true) {
940
- return { channel: "local", version: version2, packageRoot };
1023
+ return readReleaseAt(packageRoot, "local", requireFromHere);
941
1024
  }
942
- return { channel: "stable", version: version2, packageRoot };
1025
+ return readReleaseAt(packageRoot, "stable", requireFromHere);
1026
+ }
1027
+ function readReleaseAt(packageRoot, channel, requireFromHere = createRequire(import.meta.url)) {
1028
+ const packageJsonPath = path27__default.join(packageRoot, "package.json");
1029
+ const pkg = requireFromHere(packageJsonPath);
1030
+ return { channel, version: pkg.version ?? "", packageRoot };
943
1031
  }
944
1032
  function resolvePackageJsonPath(requireFromHere) {
945
1033
  for (const candidate of ["../package.json", "../../package.json"]) {
@@ -953,25 +1041,23 @@ function resolvePackageJsonPath(requireFromHere) {
953
1041
  }
954
1042
 
955
1043
  // core/host/paths.ts
956
- var LOCK_DIR_NAME2 = "fleet-console";
957
1044
  var CONSOLE_RUNTIME_DIR_NAME = "console";
958
- var LOCK_FILE_NAME = "console.lock";
959
- var CONSOLE_DATA_DIR_NAME = "console";
960
- var CONSOLE_STATE_FILE_NAME = "state.json";
961
- var CONSOLE_SETTINGS_FILE_NAME = "settings.json";
962
- var CONSOLE_CAPTURES_DIR_NAME = "captures";
1045
+ var CONSOLE_DATA_DIR_NAME2 = "console";
1046
+ var CONSOLE_STATE_FILE_NAME2 = "state.json";
1047
+ var CONSOLE_SETTINGS_FILE_NAME2 = "settings.json";
1048
+ var CONSOLE_CAPTURES_DIR_NAME2 = "captures";
963
1049
  function createConsolePaths(deps = {}) {
964
1050
  const env = deps.env ?? process.env;
965
1051
  const base = env.FLEET_CONSOLE_DIR ?? defaultConsoleBaseDir(deps);
966
- return { dir: base, lockFile: path25__default.join(base, LOCK_FILE_NAME) };
1052
+ return { dir: base, lockFile: path27__default.join(base, "console.lock") };
967
1053
  }
968
1054
  function createConsoleDataPaths(deps = {}) {
969
1055
  const dir = defaultConsoleDataBaseDir(deps);
970
1056
  return {
971
1057
  dir,
972
- stateFile: path25__default.join(dir, CONSOLE_STATE_FILE_NAME),
973
- settingsFile: path25__default.join(dir, CONSOLE_SETTINGS_FILE_NAME),
974
- capturesDir: path25__default.join(dir, CONSOLE_CAPTURES_DIR_NAME)
1058
+ stateFile: path27__default.join(dir, CONSOLE_STATE_FILE_NAME2),
1059
+ settingsFile: path27__default.join(dir, CONSOLE_SETTINGS_FILE_NAME2),
1060
+ capturesDir: path27__default.join(dir, CONSOLE_CAPTURES_DIR_NAME2)
975
1061
  };
976
1062
  }
977
1063
  function defaultConsoleBaseDir(deps) {
@@ -982,11 +1068,15 @@ function defaultConsoleBaseDir(deps) {
982
1068
  return localConsoleDir(packageRoot);
983
1069
  }
984
1070
  const uid = deps.uid ?? (typeof process.getuid === "function" ? process.getuid() : 0);
985
- return path25__default.join(os4__default.tmpdir(), `${LOCK_DIR_NAME2}-${uid}-${channel}`);
1071
+ return resolveCanonicalStableConsolePaths({
1072
+ tmpDir: os4__default.tmpdir(),
1073
+ uid,
1074
+ fleetDataDir: getFleetDataDir()
1075
+ }).dir;
986
1076
  }
987
1077
  function defaultConsoleDataBaseDir(deps) {
988
1078
  if (deps.fleetDataDir !== void 0) {
989
- return path25__default.join(deps.fleetDataDir, CONSOLE_DATA_DIR_NAME);
1079
+ return path27__default.join(deps.fleetDataDir, CONSOLE_DATA_DIR_NAME2);
990
1080
  }
991
1081
  const env = deps.env ?? process.env;
992
1082
  if (env.FLEET_CONSOLE_DIR) {
@@ -998,10 +1088,14 @@ function defaultConsoleDataBaseDir(deps) {
998
1088
  const packageRoot = deps.packageRoot ?? (release ??= readFleetConsoleRelease()).packageRoot;
999
1089
  return localConsoleDir(packageRoot);
1000
1090
  }
1001
- return path25__default.join(getFleetDataDir(), CONSOLE_DATA_DIR_NAME);
1091
+ return resolveCanonicalStableConsolePaths({
1092
+ tmpDir: os4__default.tmpdir(),
1093
+ uid: typeof process.getuid === "function" ? process.getuid() : 0,
1094
+ fleetDataDir: getFleetDataDir()
1095
+ }).dataDir;
1002
1096
  }
1003
1097
  function localConsoleDir(packageRoot) {
1004
- return path25__default.join(path25__default.resolve(packageRoot, "..", ".."), ".fleet", CONSOLE_RUNTIME_DIR_NAME);
1098
+ return path27__default.join(path27__default.resolve(packageRoot, "..", ".."), ".fleet", CONSOLE_RUNTIME_DIR_NAME);
1005
1099
  }
1006
1100
 
1007
1101
  // ../../packages/fleet-carriers/src/dispatch/framework.ts
@@ -1837,10 +1931,10 @@ function mergeDefs(...defs) {
1837
1931
  function cloneDef(schema) {
1838
1932
  return mergeDefs(schema._zod.def);
1839
1933
  }
1840
- function getElementAtPath(obj, path46) {
1841
- if (!path46)
1934
+ function getElementAtPath(obj, path50) {
1935
+ if (!path50)
1842
1936
  return obj;
1843
- return path46.reduce((acc, key) => acc?.[key], obj);
1937
+ return path50.reduce((acc, key) => acc?.[key], obj);
1844
1938
  }
1845
1939
  function promiseAllObject(promisesObj) {
1846
1940
  const keys = Object.keys(promisesObj);
@@ -2249,11 +2343,11 @@ function explicitlyAborted(x, startIndex = 0) {
2249
2343
  }
2250
2344
  return false;
2251
2345
  }
2252
- function prefixIssues(path46, issues) {
2346
+ function prefixIssues(path50, issues) {
2253
2347
  return issues.map((iss) => {
2254
2348
  var _a3;
2255
2349
  (_a3 = iss).path ?? (_a3.path = []);
2256
- iss.path.unshift(path46);
2350
+ iss.path.unshift(path50);
2257
2351
  return iss;
2258
2352
  });
2259
2353
  }
@@ -2400,16 +2494,16 @@ function flattenError(error51, mapper = (issue2) => issue2.message) {
2400
2494
  }
2401
2495
  function formatError(error51, mapper = (issue2) => issue2.message) {
2402
2496
  const fieldErrors = { _errors: [] };
2403
- const processError = (error52, path46 = []) => {
2497
+ const processError = (error52, path50 = []) => {
2404
2498
  for (const issue2 of error52.issues) {
2405
2499
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2406
- issue2.errors.map((issues) => processError({ issues }, [...path46, ...issue2.path]));
2500
+ issue2.errors.map((issues) => processError({ issues }, [...path50, ...issue2.path]));
2407
2501
  } else if (issue2.code === "invalid_key") {
2408
- processError({ issues: issue2.issues }, [...path46, ...issue2.path]);
2502
+ processError({ issues: issue2.issues }, [...path50, ...issue2.path]);
2409
2503
  } else if (issue2.code === "invalid_element") {
2410
- processError({ issues: issue2.issues }, [...path46, ...issue2.path]);
2504
+ processError({ issues: issue2.issues }, [...path50, ...issue2.path]);
2411
2505
  } else {
2412
- const fullpath = [...path46, ...issue2.path];
2506
+ const fullpath = [...path50, ...issue2.path];
2413
2507
  if (fullpath.length === 0) {
2414
2508
  fieldErrors._errors.push(mapper(issue2));
2415
2509
  } else {
@@ -2436,17 +2530,17 @@ function formatError(error51, mapper = (issue2) => issue2.message) {
2436
2530
  }
2437
2531
  function treeifyError(error51, mapper = (issue2) => issue2.message) {
2438
2532
  const result = { errors: [] };
2439
- const processError = (error52, path46 = []) => {
2533
+ const processError = (error52, path50 = []) => {
2440
2534
  var _a3, _b;
2441
2535
  for (const issue2 of error52.issues) {
2442
2536
  if (issue2.code === "invalid_union" && issue2.errors.length) {
2443
- issue2.errors.map((issues) => processError({ issues }, [...path46, ...issue2.path]));
2537
+ issue2.errors.map((issues) => processError({ issues }, [...path50, ...issue2.path]));
2444
2538
  } else if (issue2.code === "invalid_key") {
2445
- processError({ issues: issue2.issues }, [...path46, ...issue2.path]);
2539
+ processError({ issues: issue2.issues }, [...path50, ...issue2.path]);
2446
2540
  } else if (issue2.code === "invalid_element") {
2447
- processError({ issues: issue2.issues }, [...path46, ...issue2.path]);
2541
+ processError({ issues: issue2.issues }, [...path50, ...issue2.path]);
2448
2542
  } else {
2449
- const fullpath = [...path46, ...issue2.path];
2543
+ const fullpath = [...path50, ...issue2.path];
2450
2544
  if (fullpath.length === 0) {
2451
2545
  result.errors.push(mapper(issue2));
2452
2546
  continue;
@@ -2478,8 +2572,8 @@ function treeifyError(error51, mapper = (issue2) => issue2.message) {
2478
2572
  }
2479
2573
  function toDotPath(_path) {
2480
2574
  const segs = [];
2481
- const path46 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2482
- for (const seg of path46) {
2575
+ const path50 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
2576
+ for (const seg of path50) {
2483
2577
  if (typeof seg === "number")
2484
2578
  segs.push(`[${seg}]`);
2485
2579
  else if (typeof seg === "symbol")
@@ -15143,13 +15237,13 @@ function resolveRef(ref, ctx) {
15143
15237
  if (!ref.startsWith("#")) {
15144
15238
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
15145
15239
  }
15146
- const path46 = ref.slice(1).split("/").filter(Boolean);
15147
- if (path46.length === 0) {
15240
+ const path50 = ref.slice(1).split("/").filter(Boolean);
15241
+ if (path50.length === 0) {
15148
15242
  return ctx.rootSchema;
15149
15243
  }
15150
15244
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
15151
- if (path46[0] === defsKey) {
15152
- const key = path46[1];
15245
+ if (path50[0] === defsKey) {
15246
+ const key = path50[1];
15153
15247
  if (!key || !ctx.defs[key]) {
15154
15248
  throw new Error(`Reference not found: ${ref}`);
15155
15249
  }
@@ -21995,12 +22089,12 @@ var CodexAppServerConnection = class extends BaseConnection {
21995
22089
  );
21996
22090
  }
21997
22091
  findRolloutPathForThreadId(threadId) {
21998
- const codexHome = this.codexHome ?? this.env.CODEX_HOME ?? path25.join(this.env.HOME ?? "", ".codex");
22092
+ const codexHome = this.codexHome ?? this.env.CODEX_HOME ?? path27.join(this.env.HOME ?? "", ".codex");
21999
22093
  if (!codexHome) {
22000
22094
  return null;
22001
22095
  }
22002
22096
  for (const rootName of ["sessions", "archived_sessions"]) {
22003
- const found = findRolloutPath(path25.join(codexHome, rootName), threadId);
22097
+ const found = findRolloutPath(path27.join(codexHome, rootName), threadId);
22004
22098
  if (found) {
22005
22099
  return found;
22006
22100
  }
@@ -22107,11 +22201,11 @@ function isMissingRolloutError(error51, threadId) {
22107
22201
  return error51 instanceof Error && error51.message.includes(`no rollout found for thread id ${threadId}`);
22108
22202
  }
22109
22203
  function findRolloutPath(root, threadId) {
22110
- if (!fs11.existsSync(root)) {
22204
+ if (!fs14.existsSync(root)) {
22111
22205
  return null;
22112
22206
  }
22113
- for (const entry of fs11.readdirSync(root, { withFileTypes: true })) {
22114
- const entryPath = path25.join(root, entry.name);
22207
+ for (const entry of fs14.readdirSync(root, { withFileTypes: true })) {
22208
+ const entryPath = path27.join(root, entry.name);
22115
22209
  if (entry.isDirectory()) {
22116
22210
  const found = findRolloutPath(entryPath, threadId);
22117
22211
  if (found) {
@@ -22154,9 +22248,10 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
22154
22248
  throw new Error("UnifiedCodexAgentClient\uB294 codex CLI\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4.");
22155
22249
  }
22156
22250
  this.validateModelEffort(options.model, options.effort);
22157
- {
22251
+ if (shouldUseCodexAcp(options)) {
22158
22252
  return this.connectAcp(options);
22159
22253
  }
22254
+ return this.connectAppServer(options);
22160
22255
  }
22161
22256
  async connectAppServer(options) {
22162
22257
  const backend = getBackendConfig("codex");
@@ -22794,6 +22889,11 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
22794
22889
  return patterns.some((pattern) => pattern.test(text));
22795
22890
  }
22796
22891
  };
22892
+ function shouldUseCodexAcp(options) {
22893
+ const value = options.env?.CODEX_USE_ACP ?? process.env.CODEX_USE_ACP;
22894
+ const normalized = value?.trim().toLowerCase();
22895
+ return normalized !== "false" && normalized !== "0";
22896
+ }
22797
22897
  var UnifiedCursorAgentClient = class extends EventEmitter {
22798
22898
  connection = null;
22799
22899
  sessionId = null;
@@ -24919,7 +25019,7 @@ function formatInstallCommand(packageNames, command2, versionOrChannel) {
24919
25019
  return `${command2} i -g ${packageNames.map((name) => `${name}@${versionOrChannel}`).join(" ")}`;
24920
25020
  }
24921
25021
  function isPathInside(deps, child, parent) {
24922
- const separator = deps.platform === "win32" ? "\\" : path25__default.sep;
25022
+ const separator = deps.platform === "win32" ? "\\" : path27__default.sep;
24923
25023
  return child === parent || child.startsWith(`${parent}${separator}`);
24924
25024
  }
24925
25025
  function normalizeExistingPath(deps, value) {
@@ -24934,10 +25034,10 @@ function normalizePath(deps, value) {
24934
25034
  return deps.platform === "win32" ? resolved.toLowerCase() : resolved;
24935
25035
  }
24936
25036
  function resolvePathForPlatform(platform, value) {
24937
- return platform === "win32" ? path25__default.win32.resolve(value) : path25__default.resolve(value);
25037
+ return platform === "win32" ? path27__default.win32.resolve(value) : path27__default.resolve(value);
24938
25038
  }
24939
25039
  function joinPathForPlatform(platform, ...parts) {
24940
- return platform === "win32" ? path25__default.win32.join(...parts) : path25__default.join(...parts);
25040
+ return platform === "win32" ? path27__default.win32.join(...parts) : path27__default.join(...parts);
24941
25041
  }
24942
25042
  function defaultExecFile(file2, args) {
24943
25043
  return execFileSync(file2, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
@@ -25046,7 +25146,7 @@ function readRawCarriersOrDefaultStore() {
25046
25146
  return readCarriers();
25047
25147
  }
25048
25148
  function getCarriersFilePath() {
25049
- return path25.join(resolveStoreDir(), FILENAME);
25149
+ return path27.join(resolveStoreDir(), FILENAME);
25050
25150
  }
25051
25151
  function updateCarriers(mutator) {
25052
25152
  withStoreLock(() => {
@@ -25060,7 +25160,7 @@ function updateCarriers(mutator) {
25060
25160
  function withStoreLock(operation) {
25061
25161
  const dir = resolveStoreDir();
25062
25162
  ensureSafeDirectory(dir);
25063
- const lockDir = path25.join(dir, LOCK_DIRNAME);
25163
+ const lockDir = path27.join(dir, LOCK_DIRNAME);
25064
25164
  return withDirectoryLock(
25065
25165
  { lockDir, ownerFileName: LOCK_OWNER_FILENAME },
25066
25166
  operation
@@ -25073,20 +25173,20 @@ function readCarriers() {
25073
25173
  return readCarriersFromDir(resolveStoreDir());
25074
25174
  }
25075
25175
  function readCarriersFromDir(dir) {
25076
- const filePath = path25.join(dir, FILENAME);
25176
+ const filePath = path27.join(dir, FILENAME);
25077
25177
  let fd;
25078
25178
  try {
25079
- const stat5 = fs11.lstatSync(filePath);
25179
+ const stat5 = fs14.lstatSync(filePath);
25080
25180
  if (!stat5.isFile() || stat5.isSymbolicLink()) return {};
25081
- fd = fs11.openSync(filePath, fs11.constants.O_RDONLY | NOFOLLOW_FLAG);
25082
- if (!fs11.fstatSync(fd).isFile()) return {};
25083
- return sanitizeFleetCarriers(JSON.parse(fs11.readFileSync(fd, "utf-8")));
25181
+ fd = fs14.openSync(filePath, fs14.constants.O_RDONLY | NOFOLLOW_FLAG);
25182
+ if (!fs14.fstatSync(fd).isFile()) return {};
25183
+ return sanitizeFleetCarriers(JSON.parse(fs14.readFileSync(fd, "utf-8")));
25084
25184
  } catch {
25085
25185
  return {};
25086
25186
  } finally {
25087
25187
  if (fd !== void 0) {
25088
25188
  try {
25089
- fs11.closeSync(fd);
25189
+ fs14.closeSync(fd);
25090
25190
  } catch {
25091
25191
  }
25092
25192
  }
@@ -25094,7 +25194,7 @@ function readCarriersFromDir(dir) {
25094
25194
  }
25095
25195
  function recordLastLocalWriteFingerprint(filePath, generation) {
25096
25196
  try {
25097
- const st = fs11.statSync(filePath);
25197
+ const st = fs14.statSync(filePath);
25098
25198
  runtimeState.lastLocalWriteFingerprint = { generation, mtimeMs: st.mtimeMs, size: st.size };
25099
25199
  } catch {
25100
25200
  runtimeState.lastLocalWriteFingerprint = { generation, mtimeMs: 0, size: 0 };
@@ -25102,7 +25202,7 @@ function recordLastLocalWriteFingerprint(filePath, generation) {
25102
25202
  }
25103
25203
  function writeCarriers(s) {
25104
25204
  const dir = resolveStoreDir();
25105
- const filePath = path25.join(dir, FILENAME);
25205
+ const filePath = path27.join(dir, FILENAME);
25106
25206
  const next = serializeFleetCarriers(s);
25107
25207
  writeAtomicSync(filePath, `${JSON.stringify(next, null, 2)}
25108
25208
  `, { fsync: false, mode: 420 });
@@ -25127,7 +25227,7 @@ function sanitizeFleetCarriers(value) {
25127
25227
  }
25128
25228
  function unlinkStaleSubagentModeFile(dir) {
25129
25229
  try {
25130
- fs11.unlinkSync(path25.join(dir, STALE_SUBAGENT_MODE_FILENAME));
25230
+ fs14.unlinkSync(path27.join(dir, STALE_SUBAGENT_MODE_FILENAME));
25131
25231
  } catch {
25132
25232
  }
25133
25233
  }
@@ -25656,7 +25756,7 @@ var CHRONICLE_DEFAULTS = {
25656
25756
  };
25657
25757
  var CARRIER_METADATA = {
25658
25758
  // ── Tier 1: Routing ──
25659
- title: "Captain \xB7 Chief Knowledge Officer",
25759
+ title: "Chief Knowledge Officer",
25660
25760
  summary: "Documentation steward across two surfaces: codebase markdown (AGENTS.md/README/CHANGELOG/inline docs) and Fleet Wiki entries proposed via the wiki patch queue.",
25661
25761
  category: "operations",
25662
25762
  whenToUse: [
@@ -25669,7 +25769,7 @@ var CARRIER_METADATA = {
25669
25769
  "before implementation and verification are complete",
25670
25770
  "code modification (\u2192genesis) or code review (\u2192sentinel)",
25671
25771
  "architectural judgment (\u2192nimitz) or release-scope planning decisions (\u2192kirov)",
25672
- "Fleet Wiki patch approval \u2014 Admiral retains the wiki_patch_queue approve/reject gate"
25772
+ "Fleet Wiki patch approval \u2014 host agent retains the wiki_patch_queue approve/reject gate"
25673
25773
  ],
25674
25774
  requestBlocks: [
25675
25775
  {
@@ -25705,18 +25805,18 @@ var CARRIER_METADATA = {
25705
25805
  ],
25706
25806
  // ── Tier 2: Composition ──
25707
25807
  permissions: [
25708
- "CRITICAL: Fleet Wiki entries are governed by the Fleet Wiki workspace AGENTS.md doctrine \u2014 propose via wiki_ingest only, and NEVER edit wiki entries or system-managed indexes/logs by hand. Admiral holds the wiki_patch_queue approve/reject gate.",
25808
+ "CRITICAL: Fleet Wiki entries are governed by the Fleet Wiki workspace AGENTS.md doctrine \u2014 propose via wiki_ingest only, and NEVER edit wiki entries or system-managed indexes/logs by hand. The host agent holds the wiki_patch_queue approve/reject gate.",
25709
25809
  "CRITICAL: Owns codebase markdown \u2014 AGENTS.md, README, CHANGELOG, and inline doc files outside the Fleet Wiki workspace \u2014 and may write them directly. NEVER modify source code logic (report issues instead).",
25710
- "MUST treat the Admiral's <doc_type>, <audience>, <scope>, and <change_scope> as binding contracts. NEVER redefine or expand documentation scope autonomously.",
25711
- "Owns expression details (tone, wording, paragraph flow within domain conventions) but MUST NOT silently change structural decisions specified by the Admiral (sections, items, target files).",
25810
+ "MUST treat the host agent's <doc_type>, <audience>, <scope>, and <change_scope> as binding contracts. NEVER redefine or expand documentation scope autonomously.",
25811
+ "Owns expression details (tone, wording, paragraph flow within domain conventions) but MUST NOT silently change structural decisions specified by the host agent (sections, items, target files).",
25712
25812
  "MUST NOT make go/no-go, release timing, or release-scope decisions (escalate to Kirov/Nimitz). May detect and document breaking changes only."
25713
25813
  ],
25714
25814
  outputFormat: `Deliver the documentation artifact directly.
25715
25815
  [Codebase Doc] Write the .md/AGENTS.md/README/CHANGELOG file(s) directly via filesystem tools.
25716
- [Fleet Wiki] Compose the entry body and enqueue via wiki_ingest. Report the patch_id; never edit wiki entries or system-managed files by hand. Admiral approves via wiki_patch_queue.
25816
+ [Fleet Wiki] Compose the entry body and enqueue via wiki_ingest. Report the patch_id; never edit wiki entries or system-managed files by hand. The host agent approves via wiki_patch_queue.
25717
25817
  After delivery, provide a brief completion report.
25718
25818
  [Required] always include:
25719
- **Workflow** \u2014 One of: 'Codebase Doc' or 'Fleet Wiki' (state which workflow this sortie used).
25819
+ **Workflow** \u2014 One of: 'Codebase Doc' or 'Fleet Wiki' (state which workflow this task used).
25720
25820
  **Documents written / Patches enqueued** \u2014 Each file created/modified (Codebase Doc) OR each patch_id enqueued (Fleet Wiki) with target path.
25721
25821
  **Cascade .md audit** \u2014 (Codebase Doc only) Each .md inspected: path, status (updated / already consistent / not applicable), 1-line summary if updated.
25722
25822
  [If applicable] omit if not relevant:
@@ -25725,10 +25825,10 @@ After delivery, provide a brief completion report.
25725
25825
  Keep the completion report concise \u2014 the documentation/patch itself is the primary deliverable.`,
25726
25826
  principles: [
25727
25827
  CARRIER_JOBS_SELF_CALL_HINT,
25728
- "Two workflows, one carrier \u2014 never mix the two surfaces. (A) Codebase Doc: direct filesystem edits to .md/AGENTS.md/README/CHANGELOG outside the Fleet Wiki workspace. (B) Fleet Wiki: orient \u2192 consult \u2192 lint \u2192 wiki_ingest to enqueue \u2192 Admiral approves via wiki_patch_queue. Decide the workflow from <doc_type>; if ambiguous, escalate before acting.",
25729
- "[Fleet Wiki] Read the Fleet Wiki workspace AGENTS.md before the first wiki operation in a sortie \u2014 it is the authoritative doctrine for boundaries, roles, gates, schema reference, and escalation. The active entry schema may evolve; always consult the workspace doctrine and current schema each sortie rather than relying on prior memory.",
25828
+ "Two workflows, one carrier \u2014 never mix the two surfaces. (A) Codebase Doc: direct filesystem edits to .md/AGENTS.md/README/CHANGELOG outside the Fleet Wiki workspace. (B) Fleet Wiki: orient \u2192 consult \u2192 lint \u2192 wiki_ingest to enqueue \u2192 host agent approves via wiki_patch_queue. Decide the workflow from <doc_type>; if ambiguous, escalate before acting.",
25829
+ "[Fleet Wiki] Read the Fleet Wiki workspace AGENTS.md before the first wiki operation in a task \u2014 it is the authoritative doctrine for boundaries, roles, gates, schema reference, and escalation. The active entry schema may evolve; always consult the workspace doctrine and current schema each task rather than relying on prior memory.",
25730
25830
  "[Fleet Wiki] Provide raw evidence (source) alongside every wiki_ingest call \u2014 synthesized entry body must cite raw substance without copying verbatim. The system writes raw, queue, and index automatically.",
25731
- "[Codebase Doc] Every sortie must include a cascade .md audit \u2014 identify all .md files within the change scope, verify they reflect current state, and cross-reference parent/child AGENTS.md to prevent doctrinal conflicts.",
25831
+ "[Codebase Doc] Every task must include a cascade .md audit \u2014 identify all .md files within the change scope, verify they reflect current state, and cross-reference parent/child AGENTS.md to prevent doctrinal conflicts.",
25732
25832
  "[Codebase Doc] CRITICAL: README.md files must ONLY be updated where they already exist \u2014 NEVER create new README.md files. If a directory lacks a README.md, leave it as-is and note the absence in the audit report.",
25733
25833
  "If additional documentation scope seems needed, MUST report it as a follow-up suggestion in the completion report. NEVER silently expand the audit/update scope.",
25734
25834
  "Change-impact documentation must be factual and observable \u2014 never recommend whether a change should ship, be reverted, or be delayed."
@@ -25750,8 +25850,8 @@ var GENESIS_DEFAULTS = {
25750
25850
  };
25751
25851
  var CARRIER_METADATA2 = {
25752
25852
  // ── Tier 1: Routing ──
25753
- title: "Captain \xB7 Chief Engineer",
25754
- summary: "Full-stack implementation workhorse \u2014 builds features, writes production-quality clean code, and maintains structural integrity throughout. As the Captain (\uD568\uC7A5) of this Carrier, Genesis takes direct command of build and implementation operations.",
25853
+ title: "Chief Engineer",
25854
+ summary: "Full-stack implementation workhorse \u2014 builds features, writes production-quality clean code, and maintains structural integrity throughout.",
25755
25855
  category: "operations",
25756
25856
  whenToUse: [
25757
25857
  "new features",
@@ -25778,14 +25878,14 @@ var CARRIER_METADATA2 = {
25778
25878
  // ── Tier 2: Composition ──
25779
25879
  permissions: [
25780
25880
  "Full access to the codebase \u2014 read, write, and execute commands.",
25781
- "Owns implementation details (internal helper structure, code organization, local naming) ONLY within the design boundaries set by the Admiral's instructions.",
25782
- "MUST NOT substitute autonomous design judgment for the Admiral's explicit design decisions \u2014 interface unification vs separation, type/function names, directory structure, public surface shape, and any choice the Admiral has specified are BINDING contracts, not suggestions.",
25881
+ "Owns implementation details (internal helper structure, code organization, local naming) ONLY within the design boundaries set by the host agent's instructions.",
25882
+ "MUST NOT substitute autonomous design judgment for the host agent's explicit design decisions \u2014 interface unification vs separation, type/function names, directory structure, public surface shape, and any choice the host agent has specified are BINDING contracts, not suggestions.",
25783
25883
  "MUST NOT silently re-plan, expand scope, invent alternative workflows, or shrink the assigned work beyond what the instructions specify.",
25784
25884
  "MUST NOT silently absorb Kirov's planning role or Nimitz's architecture arbitration role when those inputs are clearly missing."
25785
25885
  ],
25786
25886
  principles: [
25787
25887
  CARRIER_JOBS_SELF_CALL_HINT,
25788
- "MUST treat the Admiral's <objective>, <scope>, <constraints>, and <references> as binding design contracts. Specific design decisions stated in the instructions MUST be implemented as-instructed, not as 'cleaner' or 'better' substitutions.",
25888
+ "MUST treat the host agent's <objective>, <scope>, <constraints>, and <references> as binding design contracts. Specific design decisions stated in the instructions MUST be implemented as-instructed, not as 'cleaner' or 'better' substitutions.",
25789
25889
  "If an alternative design seems superior, MUST complete the assigned work AS-INSTRUCTED first, then report the alternative ONLY as a follow-up suggestion. NEVER substitute the alternative silently.",
25790
25890
  "On ambiguity or apparent conflict in the instructions, MUST report back and request clarification instead of choosing autonomously.",
25791
25891
  "Follow planning artifacts when provided \u2014 do not re-plan work that Kirov has already structured unless the input is clearly invalid.",
@@ -25819,8 +25919,8 @@ var KIROV_DEFAULTS = {
25819
25919
  };
25820
25920
  var CARRIER_METADATA3 = {
25821
25921
  // ── Tier 1: Routing ──
25822
- title: "Captain \xB7 Operational Planning Bridge",
25823
- summary: "Clarifies requirements, closes planning gaps, and writes executable .fleet/plans/*.md plan_files with ordered waves, ownership, dependencies, QA gates, acceptance criteria, documentation impacts, and escalation triggers.",
25922
+ title: "Operational Planning",
25923
+ summary: "Clarifies requirements, closes planning gaps, and writes one executable .fleet/plans/*.md plan_file SSoT with explicit parallel lanes, ownership, dependencies, QA gates, acceptance criteria, documentation impacts, and escalation triggers.",
25824
25924
  category: "planning",
25825
25925
  whenToUse: [
25826
25926
  "structured .fleet/plans/*.md plan_file requests, PRD decomposition, or Ohio-executable execution plans",
@@ -25828,14 +25928,14 @@ var CARRIER_METADATA3 = {
25828
25928
  "medium/large refactors, migrations, cross-module work, or materially ambiguous requirements where planning must close gaps before execution"
25829
25929
  ],
25830
25930
  whenNotToUse: [
25831
- "single-Carrier tasks with \u22643 dependent steps and clear acceptance criteria (\u2192Admiral plans directly)",
25931
+ "single-Carrier tasks with \u22643 dependent steps and clear acceptance criteria (\u2192host agent plans directly)",
25832
25932
  "direct code implementation, single-shot (\u2192genesis) or plan-driven (\u2192ohio)",
25833
- "final architecture arbitration (\u2192nimitz) or sorties needing reconnaissance first (\u2192vanguard/tempest)"
25933
+ "final architecture arbitration (\u2192nimitz) or dispatches needing reconnaissance first (\u2192vanguard/tempest)"
25834
25934
  ],
25835
25935
  requestBlocks: [
25836
25936
  { tag: "goal", hint: "What the user wants to build, fix, or achieve \u2014 specific feature, PRD, behavior, and any stated constraints.", required: true },
25837
25937
  { tag: "plan_file", hint: "If provided, exact repo-relative .fleet/plans/{name}.md path Kirov must create or update. Do not choose a different filename.", required: false },
25838
- { tag: "context", hint: "Relevant codebase context \u2014 files, modules, patterns, prior Admiral direction, or implementation realities the planner should respect.", required: false },
25938
+ { tag: "context", hint: "Relevant codebase context \u2014 files, modules, patterns, prior host-agent direction, or implementation realities the planner should respect.", required: false },
25839
25939
  { tag: "constraints", hint: "Business rules, tech stack requirements, scope boundaries, fixed decisions, or explicit exclusions the plan must respect.", required: false },
25840
25940
  { tag: "intent_type", hint: "If known: Refactoring | Build from Scratch | Mid-sized | Collaborative | Architecture Follow-through | Research-to-Plan.", required: false }
25841
25941
  ],
@@ -25843,27 +25943,31 @@ var CARRIER_METADATA3 = {
25843
25943
  // ── Tier 2: Composition ──
25844
25944
  permissions: [
25845
25945
  "CRITICAL: Write access strictly limited to .fleet/plans/*.md and .fleet/drafts/*.md. NEVER modify source code, configs, or any non-markdown file.",
25846
- "MUST honor exact provided .fleet/plans/*.md paths. Success means creating or updating an executable plan_file unless the Admiral explicitly requests draft-only work.",
25847
- "MUST return unresolved architecture choices, system-design trade-offs, and technical path decisions to the Admiral for direction instead of silently deciding them.",
25848
- "MUST report Blockers or Admiral Direction Needed instead of claiming completion when the plan file cannot be written or the schema cannot be filled."
25946
+ "MUST honor exact provided .fleet/plans/*.md paths. Keep one plan_file as the execution SSoT: Success means creating or updating that executable plan_file unless the host agent explicitly requests draft-only work. Do not create split plan files for parallel lanes.",
25947
+ "MUST return unresolved architecture choices, system-design trade-offs, and technical path decisions to the host agent for direction instead of silently deciding them.",
25948
+ "MUST report Blockers or Host Direction Needed instead of claiming completion when the plan file cannot be written or the schema cannot be filled."
25849
25949
  ],
25850
25950
  outputFormat: `After completing the plan, provide a structured plan summary.
25851
25951
  [Required] always include:
25852
25952
  **Plan file** \u2014 Exact generated or updated .fleet/plans/{name}.md path.
25853
- **Execution Waves** \u2014 Ordered waves with parallel markers (e.g., "W1 \u2192 W2 || W3 \u2192 W4") and critical dependencies.
25953
+ **Execution Topology** \u2014 Ordered waves, stable Wave/Lane IDs, parallel markers (e.g., "W1 \u2192 W2 || W3 \u2192 W4"), and critical dependencies.
25954
+ **Dispatch Manifest** \u2014 Each lane's exact write set, start condition, eligible concurrent lanes, integration gate, handoff, and rollback unit.
25854
25955
  **Scope: IN** \u2014 What is explicitly included in the plan.
25855
25956
  **Scope: OUT** \u2014 What is explicitly excluded.
25856
25957
  **Next step** \u2014 Run \`/start-work {name}\` to execute the plan.
25857
25958
  [If applicable] omit if not relevant:
25858
25959
  **Blockers** \u2014 Why no plan file was written or why the schema cannot be filled.
25859
- **Admiral Direction Needed** \u2014 Architecture, trade-off, or path choices needing confirmation.
25960
+ **Host Direction Needed** \u2014 Architecture, trade-off, or path choices needing confirmation.
25860
25961
  Keep the summary concise \u2014 bullets and short lines only. No narrative paragraphs.`,
25861
25962
  principles: [
25862
25963
  CARRIER_JOBS_SELF_CALL_HINT,
25863
25964
  "Clarify only to unlock planning \u2014 ask the minimum questions needed to produce a reliable execution plan.",
25864
25965
  "Pre-plan gap analysis is mandatory internal input, never a substitute final output. May launch background explore/librarian sub-agents for context gathering. Use incremental write protocol: Write() skeleton first, then Edit() in 2-4 task batches.",
25865
- 'The .fleet/plans/*.md file MUST contain this exact default Markdown template unless the Admiral provides a different template: # Objective, # File Ownership, # Waves, ## Wave N \u2014 <name>, - Target files/modules:, - Dependencies:, - Parallelizable:, - Implementation summary:, - Verification/static checks:, - Escalation triggers:, # QA Gates, # Acceptance Criteria, # Documentation Updates, # Final Review Loop. Required headings must not be renamed, reordered, or omitted; extra sections allowed only after them. Write headings into the plan_file itself, not just mentioned in the response. For tiny tasks, mark non-applicable fields "Not applicable" rather than deleting them.',
25866
- "Return unresolved architecture and deep trade-off decisions to the Admiral for direction.",
25966
+ 'The .fleet/plans/*.md file MUST contain this exact default Markdown template unless the host agent provides a different template: # Objective, # File Ownership, # Execution Topology, - Execution mode: Sequential | Parallel, - Shared mutable resources:, # Waves, ## Wave N \u2014 <name>, ### Lane WN-X \u2014 <name>, - Exact write set:, - Read dependencies:, - Dependency/start condition:, - Eligible concurrent lanes: (use "none" for serialized work), - Integration gate:, - Handoff:, - Rollback unit:, - Implementation summary:, - Verification/static checks:, - Escalation triggers:, # Dispatch Manifest, - Full-plan Ohio invocation (execution_scope omitted or all): allowed sequentially only when Execution mode is Sequential or Execution Topology is absent; for Parallel, dispatch exact Lane IDs only and never combine a full-plan invocation with lane jobs, - Lane WN-X \u2014 <name>: exact write set, read dependencies, dependency/start condition, eligible concurrent lanes, integration gate, handoff, and rollback unit summary for dispatch, # QA Gates, # Acceptance Criteria, # Documentation Updates, # Final Review Loop. Required headings must not be renamed, reordered, or omitted; extra sections allowed only after them. Write headings into the plan_file itself, not just mentioned in the response. For tiny tasks, mark non-applicable fields "Not applicable" rather than deleting them.',
25967
+ "Execution Topology is mandatory for every plan. It MUST declare Execution mode: Sequential | Parallel, shared mutable resources, ordered waves, and stable Wave/Lane IDs; a lane may be marked parallel only when its exact non-overlapping write set and read dependencies prove it is safe to run concurrently.",
25968
+ "Dispatch Manifest is mandatory for every plan. For each parallel lane, declare: stable Wave/Lane ID; exact non-overlapping write set; read dependencies; dependency/start condition; eligible concurrent lanes; integration gate; handoff; and rollback unit. It MUST also state that full-plan Ohio invocation (execution_scope omitted or all) is allowed sequentially only for Sequential or absent Execution Topology; Parallel requires exact Lane IDs, makes full-plan invocation unavailable as an alternative dispatch path, and never combines it with lane jobs. If disjoint lanes cannot be proven safe, mark the work sequential rather than calling it parallel.",
25969
+ "Under each lane's '- Implementation summary:' (or the wave's when a wave declares no lanes), enumerate the concrete implementation steps as a Markdown checkbox task list \u2014 one '- [ ]' line per step, roughly 3-7 per lane; use '- [x]' only for work already completed at planning time. These checkboxes are the machine-readable progress contract consumed by the execution carrier and the Console Plans surface \u2014 do not encode task state in any other notation (no emoji, no status words, no strikethrough).",
25970
+ "Return unresolved architecture and deep trade-off decisions to the host agent for direction.",
25867
25971
  "Optimize for direct execution from the resulting plan_file."
25868
25972
  ]
25869
25973
  };
@@ -25883,8 +25987,8 @@ var NIMITZ_DEFAULTS = {
25883
25987
  };
25884
25988
  var CARRIER_METADATA4 = {
25885
25989
  // ── Tier 1: Routing ──
25886
- title: "Captain \xB7 Strategic Command & Judgment",
25887
- summary: "Read-only strategic command \u2014 decides the technical path through doctrinal judgment, architecture decisions, deep analysis, and trade-off adjudication. As the Captain (\uD568\uC7A5) of this Carrier, Nimitz commands strategic technical judgment from the flagship bridge without entering the implementation path.",
25990
+ title: "Strategic Command & Judgment",
25991
+ summary: "Read-only strategic command \u2014 decides the technical path through doctrinal judgment, architecture decisions, deep analysis, and trade-off adjudication without entering the implementation path.",
25888
25992
  category: "strategy",
25889
25993
  whenToUse: [
25890
25994
  "architecture and design decisions",
@@ -25907,7 +26011,7 @@ var CARRIER_METADATA4 = {
25907
26011
  // ── Tier 2: Composition ──
25908
26012
  permissions: [
25909
26013
  "CRITICAL: Strictly read-only. NEVER delegate code modification or file editing to this carrier.",
25910
- "CRITICAL: NEVER sortie Nimitz without prior reconnaissance \u2014 if recon is needed, sortie vanguard/tempest FIRST. Hard prerequisite, not a suggestion.",
26014
+ "CRITICAL: NEVER dispatch Nimitz without prior reconnaissance \u2014 if recon is needed, dispatch vanguard/tempest FIRST. Hard prerequisite, not a suggestion.",
25911
26015
  "Full access to read the codebase and execute read-only commands for analysis.",
25912
26016
  "MUST NOT decompose work into task waves, delivery schedules, or implementation checklists \u2014 handoff belongs to Kirov."
25913
26017
  ],
@@ -25948,8 +26052,8 @@ var OHIO_DEFAULTS = {
25948
26052
  };
25949
26053
  var CARRIER_METADATA5 = {
25950
26054
  // ── Tier 1: Routing ──
25951
- title: "Captain \xB7 Multi-Wave Strike Execution",
25952
- summary: "Receives a Kirov-authored plan_file and executes it wave-by-wave to completion \u2014 silent, patient, sequential delivery of plan steps. As the Captain (\uD568\uC7A5) of this Carrier, Ohio commands multi-wave strike execution and is the sole carrier authorised to consume plan_file inputs.",
26055
+ title: "Multi-Wave Execution",
26056
+ summary: "Receives a Kirov-authored plan_file and executes it wave-by-wave to completion, or one manifest-declared lane when explicitly scoped. Ohio is the sole carrier authorised to consume plan_file inputs.",
25953
26057
  category: "planning",
25954
26058
  whenToUse: [
25955
26059
  "multi-wave builds driven by an explicit plan_file",
@@ -25958,13 +26062,14 @@ var CARRIER_METADATA5 = {
25958
26062
  "cross-module coordinated changes following a Kirov plan"
25959
26063
  ],
25960
26064
  whenNotToUse: [
25961
- "single-file edits or admiral-direct single-shot tasks (\u2192genesis)",
26065
+ "single-file edits or host-directed single-shot tasks (\u2192genesis)",
25962
26066
  "architecture decisions (\u2192nimitz)",
25963
26067
  "planning work itself (\u2192kirov)",
25964
26068
  "reconnaissance before planning (\u2192vanguard/tempest)"
25965
26069
  ],
25966
26070
  requestBlocks: [
25967
26071
  { tag: "plan_file", hint: "Required repo-relative path to a Markdown plan file under .fleet/plans/*.md only. Ohio reads this file and follows it as the authoritative execution plan.", required: true },
26072
+ { tag: "execution_scope", hint: "Optional: for legacy plans without Execution Topology or plans marked Execution mode: Sequential, omitted or `all` executes the full plan sequentially. For Execution mode: Parallel, provide one exact Wave/Lane ID declared by the Dispatch Manifest; omitted or `all` is rejected. Never combine a full-plan invocation with scoped-lane Ohio invocation(s).", required: false },
25968
26073
  { tag: "objective", hint: "Optional brief restatement of the overarching goal for context anchoring.", required: false },
25969
26074
  { tag: "scope", hint: "Optional explicit scope boundaries if narrower than the plan_file's full coverage.", required: false },
25970
26075
  { tag: "constraints", hint: "Optional hard constraints, deadlines, or compatibility requirements that override or supplement the plan.", required: false }
@@ -25974,7 +26079,9 @@ var CARRIER_METADATA5 = {
25974
26079
  permissions: [
25975
26080
  "Full access to the codebase \u2014 read, write, and execute commands.",
25976
26081
  "MUST consult plan_file as the authoritative execution contract \u2014 plan steps are not optional or negotiable.",
25977
- "MUST treat the Admiral's <objective>, <scope>, and <constraints> as binding ALONGSIDE the plan_file. Even if a step or constraint seems suboptimal, MUST NOT substitute autonomous design judgment.",
26082
+ "MUST read the plan's Execution Topology before resolving execution_scope. For legacy plans without Execution Topology or plans marked Execution mode: Sequential, omitted scope or `all` executes the full plan sequentially. For Execution mode: Parallel, require one exact Dispatch Manifest Wave/Lane ID and reject omitted or `all` scope rather than silently serializing available parallelism. A full-plan invocation (omitted or `all`) MUST NEVER be used alongside scoped-lane Ohio invocation(s).",
26083
+ "When executing a scoped lane, it MUST be exactly one Wave/Lane ID declared by the plan's Dispatch Manifest. A scoped Ohio may change only that lane's declared write set; it MUST NOT edit plan_file, execute unassigned lanes, or guess an ambiguous scope. Before execution, it MUST satisfy the lane's dependency/start condition and required predecessor integration gates; its own QA/integration gate occurs after execution and MUST be satisfied before Ohio reports the lane eligible to release downstream work.",
26084
+ "MUST treat the host agent's <objective>, <scope>, and <constraints> as binding ALONGSIDE the plan_file. Even if a step or constraint seems suboptimal, MUST NOT substitute autonomous design judgment.",
25978
26085
  "MUST NOT silently re-plan, skip steps, invent new workflow paths, or expand scope beyond what the plan_file specifies.",
25979
26086
  "On genuine blockers (ambiguous step, missing dependency, environmental failure), MUST report back and request re-direction instead of fabricating workarounds."
25980
26087
  ],
@@ -25982,13 +26089,17 @@ var CARRIER_METADATA5 = {
25982
26089
  CARRIER_JOBS_SELF_CALL_HINT,
25983
26090
  "Read plan_file as the binding execution contract \u2014 do not deviate, re-plan, or skip steps.",
25984
26091
  "Accept only repo-relative Markdown plan paths under .fleet/plans/*.md. If the path is missing, unreadable, outside .fleet/plans/, not repo-relative, or not a .md file, do not guess, do not silently re-plan, and do not invent a replacement workflow \u2014 report the problem back and ask for re-direction.",
26092
+ "Read Execution Topology before resolving execution_scope. For legacy plans without Execution Topology or Execution mode: Sequential, omitted scope or `all` retains full-plan sequential compatibility. For Execution mode: Parallel, require one exact manifest-declared Wave/Lane ID and reject omitted or `all` scope rather than silently serializing available parallelism. Never use a full-plan invocation alongside scoped-lane Ohio invocation(s).",
26093
+ "For a lane scope, change only that lane's declared write set. Never edit plan_file or execute another lane. Before execution, satisfy the lane's dependency/start condition and required predecessor integration gates; after execution, satisfy that lane's own QA/integration gate before reporting it eligible to release downstream work.",
25985
26094
  "Execute waves in the declared order; preserve QA checkpoints between waves and do not collapse them.",
25986
- "If a wave or constraint should be redesigned, MUST escalate to the Admiral via the completion report instead of silently altering the wave's intent.",
26095
+ "Progress write-back applies to full-plan scope only (execution_scope omitted or `all`): immediately after a wave's QA checkpoint passes, update the plan_file by flipping that wave's completed task checkboxes from '- [ ]' to '- [x]'. This is a state-marker edit only \u2014 never reword, reorder, add, or delete any other plan content while doing it. Flip only steps actually completed; if a completed step has no matching checkbox, leave the text untouched and record the mismatch under Deviations in the completion report. A scoped-lane Ohio never edits plan_file (lane jobs may run concurrently) \u2014 instead it lists the lane's completed task checkboxes in its completion report so the integrator flips them after the lane's integration gate.",
26096
+ "If a wave or constraint should be redesigned, MUST escalate to the host agent via the completion report instead of silently altering the wave's intent.",
25987
26097
  "Escalate genuine blockers (ambiguous step, missing dependency, environmental failure) instead of fabricating workarounds.",
25988
26098
  "Do not absorb planning, architecture, or QA roles \u2014 if the plan demands a decision Ohio cannot make, escalate to the appropriate carrier (Kirov/Nimitz/Sentinel)."
25989
26099
  ],
25990
26100
  outputFormat: `After completing the assigned wave(s), provide a structured wave-completion report.
25991
26101
  [Required] always include:
26102
+ **Execution scope** \u2014 \`all\` or the exact Wave/Lane ID executed from the plan's Dispatch Manifest.
25992
26103
  **Wave(s) executed** \u2014 Ordered list of wave/step IDs from the plan_file actually completed.
25993
26104
  **Changes** \u2014 Every file created/modified/deleted with a 1-line summary each.
25994
26105
  **QA results** \u2014 Outcome of each wave's QA checkpoint (pass/fail with detail).
@@ -26014,8 +26125,8 @@ var SENTINEL_DEFAULTS = {
26014
26125
  };
26015
26126
  var CARRIER_METADATA6 = {
26016
26127
  // ── Tier 1: Routing ──
26017
- title: "Captain \xB7 The Inquisitor / QA & Security Lead",
26018
- summary: "Bug hunter and security specialist \u2014 code review, defect detection, quality audits, vulnerability hunting, and penetration testing with ruthless precision. As the Captain (\uD568\uC7A5) of this Carrier, Sentinel commands the fleet's quality and security inspections.",
26128
+ title: "QA & Security Lead",
26129
+ summary: "Bug hunter and security specialist \u2014 code review, defect detection, quality audits, vulnerability hunting, and penetration testing with ruthless precision.",
26019
26130
  category: "strategy",
26020
26131
  whenToUse: [
26021
26132
  "code review, bug hunting, quality audits, test execution",
@@ -26071,8 +26182,8 @@ var TEMPEST_DEFAULTS = {
26071
26182
  };
26072
26183
  var CARRIER_METADATA7 = {
26073
26184
  // ── Tier 1: Routing ──
26074
- title: "Captain \xB7 External Intelligence Strike",
26075
- summary: "GitHub intelligence \u2014 external repo investigation via API and clone-based deep analysis. As the Captain (\uD568\uC7A5) of this Carrier, Tempest sprints across foreign waters to collect remote intelligence from upstream repositories and external codebases.",
26185
+ title: "External Intelligence",
26186
+ summary: "GitHub intelligence \u2014 external repo investigation via API and clone-based deep analysis of upstream repositories and unfamiliar codebases.",
26076
26187
  category: "operations",
26077
26188
  whenToUse: [
26078
26189
  "external GitHub repo investigation (library internals, upstream dependencies)",
@@ -26127,8 +26238,8 @@ var VANGUARD_DEFAULTS = {
26127
26238
  };
26128
26239
  var CARRIER_METADATA8 = {
26129
26240
  // ── Tier 1: Routing ──
26130
- title: "Captain \xB7 Scout Specialist",
26131
- summary: "Fast reconnaissance \u2014 codebase exploration, symbol tracing, web research. As the Captain (\uD568\uC7A5) of this Carrier, Vanguard leads forward scouting and fast intelligence gathering before heavier operations begin.",
26241
+ title: "Reconnaissance Specialist",
26242
+ summary: "Fast reconnaissance \u2014 codebase exploration, symbol tracing, web research, and external knowledge gathering.",
26132
26243
  category: "operations",
26133
26244
  whenToUse: [
26134
26245
  "codebase reconnaissance \u2014 exploration, multi-file scanning, symbol tracing",
@@ -31947,7 +32058,7 @@ function resolveDispatchCwd(rawCwd, fallbackCwd) {
31947
32058
  if (rawCwd === void 0) return { ok: true, cwd: fallbackCwd };
31948
32059
  const trimmed = rawCwd.trim();
31949
32060
  if (trimmed.length === 0) return { ok: true, cwd: fallbackCwd };
31950
- if (!path25__default.isAbsolute(trimmed)) {
32061
+ if (!path27__default.isAbsolute(trimmed)) {
31951
32062
  return {
31952
32063
  ok: false,
31953
32064
  error: `Invalid cwd "${rawCwd}": must be an absolute path. Provide an absolute directory or omit cwd to use the host session cwd.`
@@ -32422,10 +32533,103 @@ function requireCarrierConfig(registry3, carrierId) {
32422
32533
  if (!config2) throw new Error(`Carrier not found: ${carrierId}`);
32423
32534
  return config2;
32424
32535
  }
32425
-
32426
- // core/host/global-settings-routes.ts
32536
+ var PLUGIN_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
32537
+ var UI_FONT_SIZE_RANGE = { min: 12, max: 18};
32538
+ var DEFAULT_UI_FONT_SETTINGS = { source: "builtin", id: "manrope", size: 14 };
32539
+ var SETTINGS_VERSION = 1;
32540
+ var SETTINGS_LOCK_DIR_NAME = "settings.lock";
32541
+ var SETTINGS_LOCK_OWNER_FILE_NAME = "owner.json";
32542
+ var SETTINGS_TEMP_PREFIX = ".settings.";
32427
32543
  var MIN_CONSOLE_STATIC_PORT = 1024;
32428
32544
  var MAX_CONSOLE_STATIC_PORT = 65535;
32545
+ function createConsoleSettingsStore(deps = {}) {
32546
+ const paths = deps.paths ?? createConsoleDataPaths();
32547
+ const createStore = deps.createStore ?? createDurableJsonStore;
32548
+ return createStore({
32549
+ filePath: paths.settingsFile,
32550
+ lockDir: path27__default.join(paths.dir, SETTINGS_LOCK_DIR_NAME),
32551
+ lockOwnerFileName: SETTINGS_LOCK_OWNER_FILE_NAME,
32552
+ now: deps.now,
32553
+ sanitize: sanitizeConsoleSettingsData,
32554
+ sensitivity: "sensitive",
32555
+ tempCleanupPrefix: SETTINGS_TEMP_PREFIX
32556
+ });
32557
+ }
32558
+ function sanitizeConsoleSettingsData(value) {
32559
+ if (!isRecord4(value)) return emptyConsoleSettingsData();
32560
+ if (value.version !== SETTINGS_VERSION) return emptyConsoleSettingsData();
32561
+ const general = readConsoleGeneralSettings(value.general);
32562
+ const plugins = readConsolePluginSettings(value.plugins);
32563
+ return {
32564
+ version: SETTINGS_VERSION,
32565
+ general: general ?? {},
32566
+ plugins: plugins ?? {}
32567
+ };
32568
+ }
32569
+ function emptyConsoleSettingsData() {
32570
+ return { version: 1, general: {}, plugins: {} };
32571
+ }
32572
+ function sanitizeUiFontSettings(value) {
32573
+ if (isConsoleUiFontId(value)) return { source: "builtin", id: value, size: DEFAULT_UI_FONT_SETTINGS.size };
32574
+ if (!isRecord4(value) || !isValidUiFontSize(value.size)) return void 0;
32575
+ if (value.source === "builtin" && isConsoleUiFontId(value.id)) {
32576
+ return { source: "builtin", id: value.id, size: value.size };
32577
+ }
32578
+ if (value.source === "system" && typeof value.familyName === "string") {
32579
+ const familyName = sanitizeSystemFontFamily(value.familyName);
32580
+ return familyName ? { source: "system", familyName, size: value.size } : void 0;
32581
+ }
32582
+ return void 0;
32583
+ }
32584
+ function isUiFontSettings(value) {
32585
+ if (!isRecord4(value) || !isValidUiFontSize(value.size)) return false;
32586
+ if (value.source === "builtin") return isConsoleUiFontId(value.id);
32587
+ return value.source === "system" && typeof value.familyName === "string" && value.familyName.length > 0 && value.familyName === sanitizeSystemFontFamily(value.familyName);
32588
+ }
32589
+ function readConsoleGeneralSettings(value) {
32590
+ if (!isRecord4(value)) return null;
32591
+ const consolePortMode = value.consolePortMode === "dynamic" || value.consolePortMode === "static" ? value.consolePortMode : void 0;
32592
+ const consoleStaticPort = isValidConsoleStaticPort(value.consoleStaticPort) ? value.consoleStaticPort : void 0;
32593
+ const language = value.language === "auto" || value.language === "en" || value.language === "ko" ? value.language : void 0;
32594
+ const theme = value.theme === "instrument" || value.theme === "maritime" || value.theme === "carbon" ? value.theme : void 0;
32595
+ const uiFont = sanitizeUiFontSettings(value.uiFont);
32596
+ return {
32597
+ ...consolePortMode !== void 0 ? { consolePortMode } : {},
32598
+ ...consoleStaticPort !== void 0 ? { consoleStaticPort } : {},
32599
+ ...language !== void 0 ? { language } : {},
32600
+ ...theme !== void 0 ? { theme } : {},
32601
+ ...uiFont !== void 0 ? { uiFont } : {}
32602
+ };
32603
+ }
32604
+ function readConsolePluginSettings(value) {
32605
+ if (!isRecord4(value)) return null;
32606
+ const result = {};
32607
+ for (const [key, entry] of Object.entries(value)) {
32608
+ if (!PLUGIN_ID_PATTERN.test(key)) continue;
32609
+ if (!isRecord4(entry)) continue;
32610
+ result[key] = entry;
32611
+ }
32612
+ return result;
32613
+ }
32614
+ function isRecord4(value) {
32615
+ return typeof value === "object" && value !== null && !Array.isArray(value);
32616
+ }
32617
+ function isValidConsoleStaticPort(value) {
32618
+ return typeof value === "number" && Number.isInteger(value) && value >= MIN_CONSOLE_STATIC_PORT && value <= MAX_CONSOLE_STATIC_PORT;
32619
+ }
32620
+ function isConsoleUiFontId(value) {
32621
+ return value === "manrope" || value === "jetbrains-mono" || value === "source-code-pro";
32622
+ }
32623
+ function isValidUiFontSize(value) {
32624
+ return typeof value === "number" && Number.isInteger(value) && value >= UI_FONT_SIZE_RANGE.min && value <= UI_FONT_SIZE_RANGE.max;
32625
+ }
32626
+ function sanitizeSystemFontFamily(value) {
32627
+ return value.replace(/[\x00-\x1F\x7F]/g, "").trim().slice(0, 128);
32628
+ }
32629
+
32630
+ // core/host/global-settings-routes.ts
32631
+ var MIN_CONSOLE_STATIC_PORT2 = 1024;
32632
+ var MAX_CONSOLE_STATIC_PORT2 = 65535;
32429
32633
  var GLOBAL_SETTINGS_API_CATALOG = [
32430
32634
  {
32431
32635
  method: "GET",
@@ -32481,22 +32685,34 @@ async function mutateGlobalSettings(req, res, deps) {
32481
32685
  deps.writeJson(res, 400, { error: "invalid_console_port_mode" });
32482
32686
  return;
32483
32687
  }
32484
- if (body.consoleStaticPort !== void 0 && body.consoleStaticPort !== null && !isValidConsoleStaticPort(body.consoleStaticPort)) {
32688
+ if (body.consoleStaticPort !== void 0 && body.consoleStaticPort !== null && !isValidConsoleStaticPort2(body.consoleStaticPort)) {
32485
32689
  deps.writeJson(res, 400, { error: "invalid_console_static_port" });
32486
32690
  return;
32487
32691
  }
32488
- if (body.theme !== void 0 && body.theme !== "maritime" && body.theme !== "carbon") {
32692
+ if (body.language !== void 0 && body.language !== "auto" && body.language !== "en" && body.language !== "ko") {
32693
+ deps.writeJson(res, 400, { error: "invalid_language" });
32694
+ return;
32695
+ }
32696
+ if (body.theme !== void 0 && body.theme !== "instrument" && body.theme !== "maritime" && body.theme !== "carbon") {
32489
32697
  deps.writeJson(res, 400, { error: "invalid_theme" });
32490
32698
  return;
32491
32699
  }
32700
+ if (!isUiFontSettingsOrUndefined(body.uiFont)) {
32701
+ deps.writeJson(res, 400, { error: "invalid_ui_font" });
32702
+ return;
32703
+ }
32492
32704
  const updated = deps.consoleSettingsStore.update((current) => ({
32705
+ ...current,
32493
32706
  version: 1,
32494
32707
  general: {
32495
32708
  ...current.general,
32496
32709
  ...body.consolePortMode === "dynamic" || body.consolePortMode === "static" ? { consolePortMode: body.consolePortMode } : {},
32497
- ...isValidConsoleStaticPort(body.consoleStaticPort) ? { consoleStaticPort: body.consoleStaticPort } : {},
32498
- ...body.theme === "maritime" || body.theme === "carbon" ? { theme: body.theme } : {}
32499
- }
32710
+ ...isValidConsoleStaticPort2(body.consoleStaticPort) ? { consoleStaticPort: body.consoleStaticPort } : {},
32711
+ ...body.language === "auto" || body.language === "en" || body.language === "ko" ? { language: body.language } : {},
32712
+ ...body.theme === "instrument" || body.theme === "maritime" || body.theme === "carbon" ? { theme: body.theme } : {},
32713
+ ...isUiFontSettings(body.uiFont) ? { uiFont: body.uiFont } : {}
32714
+ },
32715
+ plugins: current.plugins
32500
32716
  }));
32501
32717
  const response = { state: toGlobalSettingsState(updated) };
32502
32718
  deps.writeJson(res, 200, response);
@@ -32506,77 +32722,21 @@ function toGlobalSettingsState(data) {
32506
32722
  return {
32507
32723
  consolePortMode: general.consolePortMode ?? "dynamic",
32508
32724
  consoleStaticPort: general.consoleStaticPort ?? null,
32509
- theme: general.theme ?? "maritime"
32725
+ language: general.language ?? "auto",
32726
+ theme: general.theme ?? "instrument",
32727
+ uiFont: general.uiFont ?? DEFAULT_UI_FONT_SETTINGS
32510
32728
  };
32511
32729
  }
32512
32730
  function isJsonRequest2(req) {
32513
32731
  const contentType = req.headers["content-type"];
32514
32732
  return typeof contentType === "string" && contentType.toLowerCase().split(";")[0]?.trim() === "application/json";
32515
32733
  }
32516
- function isValidConsoleStaticPort(value) {
32517
- return typeof value === "number" && Number.isInteger(value) && value >= MIN_CONSOLE_STATIC_PORT && value <= MAX_CONSOLE_STATIC_PORT;
32518
- }
32519
- var PLUGIN_ID_PATTERN = /^[a-z0-9][a-z0-9._-]*$/;
32520
- var SETTINGS_VERSION = 1;
32521
- var SETTINGS_LOCK_DIR_NAME = "settings.lock";
32522
- var SETTINGS_LOCK_OWNER_FILE_NAME = "owner.json";
32523
- var SETTINGS_TEMP_PREFIX = ".settings.";
32524
- var MIN_CONSOLE_STATIC_PORT2 = 1024;
32525
- var MAX_CONSOLE_STATIC_PORT2 = 65535;
32526
- function createConsoleSettingsStore(deps = {}) {
32527
- const paths = deps.paths ?? createConsoleDataPaths();
32528
- const createStore = deps.createStore ?? createDurableJsonStore;
32529
- return createStore({
32530
- filePath: paths.settingsFile,
32531
- lockDir: path25__default.join(paths.dir, SETTINGS_LOCK_DIR_NAME),
32532
- lockOwnerFileName: SETTINGS_LOCK_OWNER_FILE_NAME,
32533
- now: deps.now,
32534
- sanitize: sanitizeConsoleSettingsData,
32535
- sensitivity: "sensitive",
32536
- tempCleanupPrefix: SETTINGS_TEMP_PREFIX
32537
- });
32538
- }
32539
- function sanitizeConsoleSettingsData(value) {
32540
- if (!isRecord4(value)) return emptyConsoleSettingsData();
32541
- if (value.version !== SETTINGS_VERSION) return emptyConsoleSettingsData();
32542
- const general = readConsoleGeneralSettings(value.general);
32543
- const plugins = readConsolePluginSettings(value.plugins);
32544
- return {
32545
- version: SETTINGS_VERSION,
32546
- general: general ?? {},
32547
- plugins: plugins ?? {}
32548
- };
32549
- }
32550
- function emptyConsoleSettingsData() {
32551
- return { version: 1, general: {}, plugins: {} };
32552
- }
32553
- function readConsoleGeneralSettings(value) {
32554
- if (!isRecord4(value)) return null;
32555
- const consolePortMode = value.consolePortMode === "dynamic" || value.consolePortMode === "static" ? value.consolePortMode : void 0;
32556
- const consoleStaticPort = isValidConsoleStaticPort2(value.consoleStaticPort) ? value.consoleStaticPort : void 0;
32557
- const theme = value.theme === "maritime" || value.theme === "carbon" ? value.theme : void 0;
32558
- return {
32559
- ...consolePortMode !== void 0 ? { consolePortMode } : {},
32560
- ...consoleStaticPort !== void 0 ? { consoleStaticPort } : {},
32561
- ...theme !== void 0 ? { theme } : {}
32562
- };
32563
- }
32564
- function readConsolePluginSettings(value) {
32565
- if (!isRecord4(value)) return null;
32566
- const result = {};
32567
- for (const [key, entry] of Object.entries(value)) {
32568
- if (!PLUGIN_ID_PATTERN.test(key)) continue;
32569
- if (!isRecord4(entry)) continue;
32570
- result[key] = entry;
32571
- }
32572
- return result;
32573
- }
32574
- function isRecord4(value) {
32575
- return typeof value === "object" && value !== null && !Array.isArray(value);
32576
- }
32577
32734
  function isValidConsoleStaticPort2(value) {
32578
32735
  return typeof value === "number" && Number.isInteger(value) && value >= MIN_CONSOLE_STATIC_PORT2 && value <= MAX_CONSOLE_STATIC_PORT2;
32579
32736
  }
32737
+ function isUiFontSettingsOrUndefined(value) {
32738
+ return value === void 0 || isUiFontSettings(value);
32739
+ }
32580
32740
 
32581
32741
  // core/host/plugin-settings-routes.ts
32582
32742
  var PLUGIN_SETTINGS_PREFIX = "/api/v1/settings/plugins/";
@@ -32657,6 +32817,133 @@ function isJsonRequest3(req) {
32657
32817
  const contentType = req.headers["content-type"];
32658
32818
  return typeof contentType === "string" && contentType.toLowerCase().split(";")[0]?.trim() === "application/json";
32659
32819
  }
32820
+ var MAX_FAMILY_LENGTH = 128;
32821
+ var SUCCESS_TTL_MS = 5 * 60 * 1e3;
32822
+ var FAILURE_TTL_MS = 30 * 1e3;
32823
+ var CONTROL_CHARACTER_PATTERN = /[\x00-\x1F\x7F]/g;
32824
+ var NORMAL_FACE_MARKERS = ["normal", "regular", "roman", "book"];
32825
+ var TEXT_FAMILY_ALLOWLIST = /* @__PURE__ */ new Set([
32826
+ "arial",
32827
+ "arial nova",
32828
+ "avenir",
32829
+ "avenir next",
32830
+ "calibri",
32831
+ "candara",
32832
+ "helvetica",
32833
+ "helvetica neue",
32834
+ "inter",
32835
+ "manrope",
32836
+ "noto sans",
32837
+ "noto serif",
32838
+ "segoe ui",
32839
+ "sf pro text",
32840
+ "system ui",
32841
+ "times new roman",
32842
+ "verdana",
32843
+ "apple sd gothic neo",
32844
+ "hiragino sans",
32845
+ "hiragino kaku gothic pro",
32846
+ "malgun gothic",
32847
+ "meiryo",
32848
+ "microsoft yahei",
32849
+ "noto sans cjk",
32850
+ "noto serif cjk",
32851
+ "pingfang sc",
32852
+ "pingfang tc",
32853
+ "yu gothic"
32854
+ ]);
32855
+ var TEXT_FAMILY_MARKERS = ["sans", "serif", "text", "grotesk", "gothic", "roman", "book", "humanist"];
32856
+ var DENY_FAMILY_MARKERS = ["hidden", "vertical", "symbol", "icon", "emoji", "dingbat", "ornament", "music", "math", "display", "decorative"];
32857
+ var EMPTY_SYSTEM_FONTS_ERROR = new Error("system font enumeration returned no usable families");
32858
+ function createSystemFontsService(deps = {}) {
32859
+ const loadFonts = deps.loadFonts ?? (() => fontList.getFonts2({ disableQuoting: true }));
32860
+ const now = deps.now ?? Date.now;
32861
+ const successTtlMs = deps.successTtlMs ?? SUCCESS_TTL_MS;
32862
+ const failureTtlMs = deps.failureTtlMs ?? FAILURE_TTL_MS;
32863
+ let cached2 = null;
32864
+ let cachedFailure = null;
32865
+ let inFlight = null;
32866
+ const getFonts = () => {
32867
+ if (cached2 && now() - cached2.cachedAt < successTtlMs) return Promise.resolve(cached2.fonts);
32868
+ if (cachedFailure && now() - cachedFailure.cachedAt < failureTtlMs) return Promise.reject(cachedFailure.error);
32869
+ if (inFlight) return inFlight;
32870
+ inFlight = loadFonts().then((fonts) => {
32871
+ const normalized = normalizeSystemFonts(fonts);
32872
+ if (normalized.length === 0) throw EMPTY_SYSTEM_FONTS_ERROR;
32873
+ cached2 = { fonts: normalized, cachedAt: now() };
32874
+ cachedFailure = null;
32875
+ return normalized;
32876
+ }).catch((error51) => {
32877
+ cachedFailure = { error: error51, cachedAt: now() };
32878
+ throw error51;
32879
+ }).finally(() => {
32880
+ inFlight = null;
32881
+ });
32882
+ return inFlight;
32883
+ };
32884
+ return { getFonts };
32885
+ }
32886
+ function normalizeSystemFonts(fonts) {
32887
+ const groups = /* @__PURE__ */ new Map();
32888
+ for (const font of fonts) {
32889
+ if (!isFontInfo(font)) continue;
32890
+ const family = sanitizeFamilyName(font.familyName);
32891
+ if (!family) continue;
32892
+ const key = family.toLocaleLowerCase();
32893
+ const group = groups.get(key) ?? { family, faces: [] };
32894
+ group.faces.push(font);
32895
+ groups.set(key, group);
32896
+ }
32897
+ return [...groups.values()].map(toSystemFontRecord).sort((left, right) => left.family.localeCompare(right.family, void 0, { sensitivity: "base" }) || left.family.localeCompare(right.family));
32898
+ }
32899
+ function buildSystemFontsResponse(fonts) {
32900
+ return { version: 1, fonts };
32901
+ }
32902
+ function isFontInfo(value) {
32903
+ return typeof value === "object" && value !== null && typeof value.familyName === "string" && typeof value.monospace === "boolean" && typeof value.style === "string";
32904
+ }
32905
+ function sanitizeFamilyName(value) {
32906
+ return value.replace(CONTROL_CHARACTER_PATTERN, "").trim().slice(0, MAX_FAMILY_LENGTH);
32907
+ }
32908
+ function toSystemFontRecord(group) {
32909
+ const normalizedFamily = group.family.toLocaleLowerCase();
32910
+ const monospace = group.faces.length > 0 && group.faces.every((face) => face.monospace);
32911
+ const hasNormalNonMonospaceFace = group.faces.some((face) => !face.monospace && isNormalFace(face));
32912
+ const denied = DENY_FAMILY_MARKERS.some((marker) => normalizedFamily.includes(marker));
32913
+ const textFamily = TEXT_FAMILY_ALLOWLIST.has(normalizedFamily) || TEXT_FAMILY_MARKERS.some((marker) => normalizedFamily.includes(marker));
32914
+ return { family: group.family, monospace, uiSuitable: !denied && hasNormalNonMonospaceFace && textFamily };
32915
+ }
32916
+ function isNormalFace(face) {
32917
+ const style = face.style.toLocaleLowerCase();
32918
+ return NORMAL_FACE_MARKERS.some((marker) => style.includes(marker));
32919
+ }
32920
+
32921
+ // core/host/system-fonts-routes.ts
32922
+ var SYSTEM_FONTS_PATH = "/api/v1/settings/fonts/system";
32923
+ var SYSTEM_FONTS_API_CATALOG = [
32924
+ {
32925
+ method: "GET",
32926
+ path: SYSTEM_FONTS_PATH,
32927
+ summary: "List sanitized system font families for built-in settings.",
32928
+ category: "Settings",
32929
+ gate: "loopback"
32930
+ }
32931
+ ];
32932
+ function createSystemFontsRouter(deps) {
32933
+ return async function handleSystemFontsRoute(context) {
32934
+ if (context.pathname !== SYSTEM_FONTS_PATH) return false;
32935
+ if (context.req.method !== "GET") {
32936
+ deps.writeJson(context.res, 405, { error: "Method not allowed" });
32937
+ return true;
32938
+ }
32939
+ try {
32940
+ deps.writeJson(context.res, 200, buildSystemFontsResponse(await deps.systemFonts.getFonts()));
32941
+ } catch {
32942
+ deps.writeJson(context.res, 503, { error: "system_fonts_unavailable" });
32943
+ }
32944
+ return true;
32945
+ };
32946
+ }
32660
32947
 
32661
32948
  // core/host/api-catalog.ts
32662
32949
  var compareApiCatalogEntries = (left, right) => left.category.localeCompare(right.category) || left.path.localeCompare(right.path) || left.method.localeCompare(right.method);
@@ -32665,9 +32952,115 @@ function buildApiCatalog() {
32665
32952
  ...SERVER_API_CATALOG,
32666
32953
  ...CARRIER_SETTINGS_API_CATALOG,
32667
32954
  ...GLOBAL_SETTINGS_API_CATALOG,
32668
- ...PLUGIN_SETTINGS_API_CATALOG
32955
+ ...PLUGIN_SETTINGS_API_CATALOG,
32956
+ ...SYSTEM_FONTS_API_CATALOG
32669
32957
  ].slice().sort(compareApiCatalogEntries);
32670
32958
  }
32959
+ var TheaterPathContextError = class extends Error {
32960
+ code;
32961
+ constructor(code) {
32962
+ super(code);
32963
+ this.code = code;
32964
+ }
32965
+ };
32966
+ var DIRECTORY_ENTRY_CAP = 500;
32967
+ async function resolveTheaterPathContext(theaterRoot, relPath) {
32968
+ if (relPath !== null && !isSafeRelativePath(relPath)) throw new TheaterPathContextError("invalid_path");
32969
+ const nominalRoot = path27__default.resolve(theaterRoot);
32970
+ const nominalTarget = relPath === null ? nominalRoot : path27__default.resolve(nominalRoot, ...relPath.split("/"));
32971
+ if (!isWithinRoot(nominalTarget, nominalRoot)) throw new TheaterPathContextError("forbidden");
32972
+ let realRoot;
32973
+ let realPath;
32974
+ try {
32975
+ [realRoot, realPath] = await Promise.all([fs14__default.promises.realpath(nominalRoot), fs14__default.promises.realpath(nominalTarget)]);
32976
+ } catch (error51) {
32977
+ throw mapFsError(error51);
32978
+ }
32979
+ if (!isWithinRoot(realPath, realRoot)) throw new TheaterPathContextError("forbidden");
32980
+ let stat5;
32981
+ try {
32982
+ stat5 = await fs14__default.promises.stat(realPath);
32983
+ } catch (error51) {
32984
+ throw mapFsError(error51);
32985
+ }
32986
+ if (!stat5.isDirectory()) throw new TheaterPathContextError("invalid_path");
32987
+ const canonicalRel = path27__default.relative(realRoot, realPath).split(path27__default.sep).join("/");
32988
+ return { realRoot, realPath, relPath: canonicalRel === "" ? null : canonicalRel, label: path27__default.basename(realPath) || path27__default.basename(realRoot) || "Theater" };
32989
+ }
32990
+ async function listTheaterDirectories(theaterRoot, parentRelPath) {
32991
+ const parent = await resolveTheaterPathContext(theaterRoot, parentRelPath);
32992
+ let entries;
32993
+ try {
32994
+ entries = await fs14__default.promises.readdir(parent.realPath, { withFileTypes: true });
32995
+ } catch (error51) {
32996
+ throw mapFsError(error51);
32997
+ }
32998
+ const result = [];
32999
+ for (const entry of entries) {
33000
+ if (result.length >= DIRECTORY_ENTRY_CAP) break;
33001
+ const candidate = path27__default.join(parent.realPath, entry.name);
33002
+ try {
33003
+ const realCandidate = await fs14__default.promises.realpath(candidate);
33004
+ if (!isWithinRoot(realCandidate, parent.realRoot)) continue;
33005
+ const stat5 = await fs14__default.promises.stat(realCandidate);
33006
+ if (!stat5.isDirectory()) continue;
33007
+ const relPath = path27__default.relative(parent.realRoot, realCandidate).split(path27__default.sep).join("/");
33008
+ if (relPath) result.push({ relPath, label: path27__default.basename(realCandidate) });
33009
+ } catch {
33010
+ }
33011
+ }
33012
+ return result.sort((left, right) => left.label.localeCompare(right.label));
33013
+ }
33014
+ function isSafeRelativePath(value) {
33015
+ return value.length > 0 && value.length <= 4096 && !value.includes("\0") && !value.includes("\\") && !value.startsWith("/") && !/^[A-Za-z]:/.test(value) && !value.split("/").some((segment) => segment === "" || segment === "." || segment === "..");
33016
+ }
33017
+ function isWithinRoot(candidate, root) {
33018
+ const normalizedRoot = root.endsWith(path27__default.sep) ? root : `${root}${path27__default.sep}`;
33019
+ return candidate === root || candidate.startsWith(normalizedRoot);
33020
+ }
33021
+ function mapFsError(error51) {
33022
+ return error51.code === "ENOENT" ? new TheaterPathContextError("not_found") : new TheaterPathContextError("invalid_path");
33023
+ }
33024
+
33025
+ // core/host/codex/context-routes.ts
33026
+ function createCodexWorkspaceContextRouter(deps) {
33027
+ return async function handleCodexWorkspaceContextRoute({ req, res, pathname }) {
33028
+ const match = pathname.match(/^\/api\/v1\/theaters\/([^/]+)\/codex-workspace$/u);
33029
+ if (!match) return false;
33030
+ const theater = deps.getTheater(decodeURIComponent(match[1] ?? ""));
33031
+ if (!theater) {
33032
+ deps.writeJson(res, 400, { error: "invalid_theater" });
33033
+ return true;
33034
+ }
33035
+ if (req.method !== "POST") {
33036
+ deps.writeJson(res, 405, { error: "method_not_allowed" });
33037
+ return true;
33038
+ }
33039
+ if (!deps.isAuthorized(req)) {
33040
+ deps.writeJson(res, 401, { error: "unauthorized" });
33041
+ return true;
33042
+ }
33043
+ const body = await deps.readJsonBody(req);
33044
+ if (!isResolveWorkspaceBody(body)) {
33045
+ deps.writeJson(res, 400, { error: "invalid_request" });
33046
+ return true;
33047
+ }
33048
+ try {
33049
+ deps.writeJson(res, 200, await deps.resolveWorkspace(theater.id, theater.realpath, body.relPath));
33050
+ } catch (error51) {
33051
+ writePathError(res, deps, error51);
33052
+ }
33053
+ return true;
33054
+ };
33055
+ }
33056
+ function isResolveWorkspaceBody(value) {
33057
+ return typeof value === "object" && value !== null && Object.keys(value).length === 1 && (value.relPath === null || typeof value.relPath === "string");
33058
+ }
33059
+ function writePathError(res, deps, error51) {
33060
+ if (!(error51 instanceof TheaterPathContextError)) throw error51;
33061
+ const status = error51.code === "not_found" ? 404 : error51.code === "forbidden" ? 403 : 400;
33062
+ deps.writeJson(res, status, { error: error51.code });
33063
+ }
32671
33064
 
32672
33065
  // ../../packages/fleet-wiki/src/boundaries.ts
32673
33066
  var FLEET_WIKI_ENTRY_BEGIN = "<<<FLEET_WIKI_ENTRY_BEGIN";
@@ -33075,18 +33468,18 @@ Always read \`schema/wiki-schema.md\` directly before composing or revising entr
33075
33468
  `;
33076
33469
  async function ensureWorkspaceSchema(paths) {
33077
33470
  await mkdir(paths.schemaDir, { recursive: true });
33078
- await writeDefaultFileIfMissing(path25__default.join(paths.schemaDir, WORKSPACE_SCHEMA_AGENTS_FILENAME), DEFAULT_WORKSPACE_SCHEMA_AGENTS);
33079
- await writeDefaultFileIfMissing(path25__default.join(paths.schemaDir, WORKSPACE_SCHEMA_FILENAME), DEFAULT_WORKSPACE_WIKI_SCHEMA);
33471
+ await writeDefaultFileIfMissing(path27__default.join(paths.schemaDir, WORKSPACE_SCHEMA_AGENTS_FILENAME), DEFAULT_WORKSPACE_SCHEMA_AGENTS);
33472
+ await writeDefaultFileIfMissing(path27__default.join(paths.schemaDir, WORKSPACE_SCHEMA_FILENAME), DEFAULT_WORKSPACE_WIKI_SCHEMA);
33080
33473
  await writeDefaultFileIfMissing(buildTemplatePath(paths, "prd"), DEFAULT_TEMPLATE_PRD);
33081
33474
  return readWorkspaceSchemaSummary(paths);
33082
33475
  }
33083
33476
  async function ensureWorkspaceDoctrine(paths) {
33084
33477
  await mkdir(paths.root, { recursive: true });
33085
- await writeDefaultFileIfMissing(path25__default.join(paths.root, WORKSPACE_KNOWLEDGE_AGENTS_FILENAME), DEFAULT_WORKSPACE_KNOWLEDGE_AGENTS);
33478
+ await writeDefaultFileIfMissing(path27__default.join(paths.root, WORKSPACE_KNOWLEDGE_AGENTS_FILENAME), DEFAULT_WORKSPACE_KNOWLEDGE_AGENTS);
33086
33479
  }
33087
33480
  async function readWorkspaceSchemaSummary(paths) {
33088
- const agentsPath = path25__default.join(paths.schemaDir, WORKSPACE_SCHEMA_AGENTS_FILENAME);
33089
- const wikiSchemaPath = path25__default.join(paths.schemaDir, WORKSPACE_SCHEMA_FILENAME);
33481
+ const agentsPath = path27__default.join(paths.schemaDir, WORKSPACE_SCHEMA_AGENTS_FILENAME);
33482
+ const wikiSchemaPath = path27__default.join(paths.schemaDir, WORKSPACE_SCHEMA_FILENAME);
33090
33483
  const wikiSchemaContent = await tryReadFile(wikiSchemaPath);
33091
33484
  const templates = await scanTemplates(paths);
33092
33485
  if (wikiSchemaContent === null) {
@@ -33125,7 +33518,7 @@ async function scanTemplates(paths) {
33125
33518
  if (!entry.name.startsWith(WORKSPACE_TEMPLATE_PREFIX) || !entry.name.endsWith(WORKSPACE_TEMPLATE_SUFFIX)) continue;
33126
33519
  const id = entry.name.slice(WORKSPACE_TEMPLATE_PREFIX.length, -WORKSPACE_TEMPLATE_SUFFIX.length);
33127
33520
  if (!id) continue;
33128
- const filePath = path25__default.join(paths.schemaDir, entry.name);
33521
+ const filePath = path27__default.join(paths.schemaDir, entry.name);
33129
33522
  try {
33130
33523
  const parsed = parseTemplateMarkdown(await readFile(filePath, "utf8"));
33131
33524
  templates.push({
@@ -33159,7 +33552,7 @@ async function validateTemplateCompliance(paths, templateId, body) {
33159
33552
  }
33160
33553
  }
33161
33554
  function inferTemplateIdFromTarget(target, knownTemplateIds) {
33162
- const basename = path25__default.basename(target, ".md");
33555
+ const basename = path27__default.basename(target, ".md");
33163
33556
  const ids = knownTemplateIds ?? DEFAULT_TEMPLATE_IDS;
33164
33557
  const sorted = [...ids].sort((a, b) => b.length - a.length);
33165
33558
  for (const id of sorted) {
@@ -33188,7 +33581,7 @@ function extractSchemaSummary(content) {
33188
33581
  return lines.find((line) => line.length > 0 && !line.startsWith("#")) ?? "Workspace schema is present.";
33189
33582
  }
33190
33583
  function buildTemplatePath(paths, id) {
33191
- return path25__default.join(paths.schemaDir, `${WORKSPACE_TEMPLATE_PREFIX}${id}${WORKSPACE_TEMPLATE_SUFFIX}`);
33584
+ return path27__default.join(paths.schemaDir, `${WORKSPACE_TEMPLATE_PREFIX}${id}${WORKSPACE_TEMPLATE_SUFFIX}`);
33192
33585
  }
33193
33586
  function parseTemplateMarkdown(content) {
33194
33587
  const normalized = content.replace(/\r\n/g, "\n");
@@ -33215,16 +33608,16 @@ function isAlreadyExistsError(error51) {
33215
33608
 
33216
33609
  // ../../packages/fleet-wiki/src/paths.ts
33217
33610
  function resolveMemoryPaths(cwd) {
33218
- const root = path25__default.join(cwd, KNOWLEDGE_ROOT_DIRNAME);
33611
+ const root = path27__default.join(cwd, KNOWLEDGE_ROOT_DIRNAME);
33219
33612
  return {
33220
33613
  root,
33221
- rawDir: path25__default.join(root, RAW_DIRNAME),
33222
- wikiDir: path25__default.join(root, WIKI_DIRNAME),
33223
- schemaDir: path25__default.join(root, SCHEMA_DIRNAME),
33224
- queueDir: path25__default.join(root, QUEUE_DIRNAME),
33225
- archiveDir: path25__default.join(root, ARCHIVE_DIRNAME),
33226
- conflictsDir: path25__default.join(root, CONFLICTS_DIRNAME),
33227
- indexFile: path25__default.join(root, INDEX_FILENAME)
33614
+ rawDir: path27__default.join(root, RAW_DIRNAME),
33615
+ wikiDir: path27__default.join(root, WIKI_DIRNAME),
33616
+ schemaDir: path27__default.join(root, SCHEMA_DIRNAME),
33617
+ queueDir: path27__default.join(root, QUEUE_DIRNAME),
33618
+ archiveDir: path27__default.join(root, ARCHIVE_DIRNAME),
33619
+ conflictsDir: path27__default.join(root, CONFLICTS_DIRNAME),
33620
+ indexFile: path27__default.join(root, INDEX_FILENAME)
33228
33621
  };
33229
33622
  }
33230
33623
  async function ensureMemoryRoot(paths) {
@@ -33239,10 +33632,10 @@ async function ensureMemoryRoot(paths) {
33239
33632
  await ensureWorkspaceDoctrine(paths);
33240
33633
  }
33241
33634
  function getIndexMarkdownFile(paths) {
33242
- return path25__default.join(paths.wikiDir, INDEX_MD_FILENAME);
33635
+ return path27__default.join(paths.wikiDir, INDEX_MD_FILENAME);
33243
33636
  }
33244
33637
  function getLogFile(paths) {
33245
- return path25__default.join(paths.root, LOG_MD_FILENAME);
33638
+ return path27__default.join(paths.root, LOG_MD_FILENAME);
33246
33639
  }
33247
33640
 
33248
33641
  // ../../packages/fleet-wiki/src/log.ts
@@ -33308,12 +33701,12 @@ async function readWikiEntry(id, paths) {
33308
33701
  const index = await loadIndex(paths);
33309
33702
  const indexed = index[id];
33310
33703
  if (indexed) {
33311
- const resolved = path25__default.resolve(paths.root, indexed.path);
33312
- const rel = path25__default.relative(paths.wikiDir, resolved);
33313
- if (!rel || rel.startsWith("..") || path25__default.isAbsolute(rel)) return null;
33704
+ const resolved = path27__default.resolve(paths.root, indexed.path);
33705
+ const rel = path27__default.relative(paths.wikiDir, resolved);
33706
+ if (!rel || rel.startsWith("..") || path27__default.isAbsolute(rel)) return null;
33314
33707
  return readMarkdownFile(resolved);
33315
33708
  }
33316
- const fallbackPath = path25__default.join(paths.wikiDir, `${id}.md`);
33709
+ const fallbackPath = path27__default.join(paths.wikiDir, `${id}.md`);
33317
33710
  if (await pathExists(fallbackPath)) {
33318
33711
  return readMarkdownFile(fallbackPath);
33319
33712
  }
@@ -33324,20 +33717,20 @@ async function readWikiEntry(id, paths) {
33324
33717
  async function writeWikiEntryAtTarget(entry, target, paths) {
33325
33718
  await ensureMemoryRoot(paths);
33326
33719
  assertSafeEntryId(entry.id);
33327
- const absoluteTarget = path25__default.resolve(paths.root, target);
33328
- const relativeTarget = path25__default.relative(paths.wikiDir, absoluteTarget);
33329
- if (!relativeTarget || relativeTarget.startsWith("..") || path25__default.isAbsolute(relativeTarget)) {
33720
+ const absoluteTarget = path27__default.resolve(paths.root, target);
33721
+ const relativeTarget = path27__default.relative(paths.wikiDir, absoluteTarget);
33722
+ if (!relativeTarget || relativeTarget.startsWith("..") || path27__default.isAbsolute(relativeTarget)) {
33330
33723
  throw new Error(`[fleet-wiki] wiki target escapes wiki/: ${target}`);
33331
33724
  }
33332
33725
  if (!absoluteTarget.endsWith(".md")) {
33333
33726
  throw new Error(`[fleet-wiki] wiki target must end with .md: ${target}`);
33334
33727
  }
33335
- if (path25__default.basename(absoluteTarget, ".md") !== entry.id) {
33728
+ if (path27__default.basename(absoluteTarget, ".md") !== entry.id) {
33336
33729
  throw new Error("wiki patch body id must match target filename");
33337
33730
  }
33338
- await mkdir(path25__default.dirname(absoluteTarget), { recursive: true });
33731
+ await mkdir(path27__default.dirname(absoluteTarget), { recursive: true });
33339
33732
  await writeMarkdownAtomic(absoluteTarget, serializeWikiEntry(entry), paths);
33340
- return path25__default.join("wiki", relativeTarget).replaceAll(path25__default.sep, "/");
33733
+ return path27__default.join("wiki", relativeTarget).replaceAll(path27__default.sep, "/");
33341
33734
  }
33342
33735
  async function listWiki(paths) {
33343
33736
  const entries = await listWikiRecords(paths);
@@ -33560,7 +33953,7 @@ function parseWikiEntry(content) {
33560
33953
  async function listWikiRecords(paths) {
33561
33954
  return listMarkdownEntriesRecursive(paths.wikiDir, paths, (content, filePath) => ({
33562
33955
  entry: parseWikiEntry(content),
33563
- path: path25__default.relative(paths.root, filePath).replaceAll(path25__default.sep, "/")
33956
+ path: path27__default.relative(paths.root, filePath).replaceAll(path27__default.sep, "/")
33564
33957
  }));
33565
33958
  }
33566
33959
  async function listMarkdownEntriesRecursive(dirPath, paths, parser) {
@@ -33573,7 +33966,7 @@ async function listMarkdownEntriesRecursive(dirPath, paths, parser) {
33573
33966
  }
33574
33967
  const sortedEntries = entries.sort((left, right) => left.name.localeCompare(right.name));
33575
33968
  for (const entry of sortedEntries) {
33576
- const filePath = path25__default.join(dirPath, entry.name);
33969
+ const filePath = path27__default.join(dirPath, entry.name);
33577
33970
  if (entry.isDirectory()) {
33578
33971
  items.push(...await listMarkdownEntriesRecursive(filePath, paths, parser));
33579
33972
  continue;
@@ -33711,8 +34104,8 @@ async function writeJsonAtomic(filePath, value, paths) {
33711
34104
  }
33712
34105
  async function writeAtomic(filePath, content, paths) {
33713
34106
  await ensureMemoryRoot(paths);
33714
- const tempPath = path25__default.join(
33715
- path25__default.dirname(filePath),
34107
+ const tempPath = path27__default.join(
34108
+ path27__default.dirname(filePath),
33716
34109
  `.tmp-${process.pid}-${Date.now()}-${crypto5.randomUUID()}-${os4__default.hostname()}`
33717
34110
  );
33718
34111
  await writeFile(tempPath, content, "utf8");
@@ -33806,7 +34199,7 @@ function toRankedHit(entry, reason, detail, graphBoost, backlinksById) {
33806
34199
  score: basePriority * 100 + statusPriority * 10 + graphScore + (detail.tagBoost ?? 0),
33807
34200
  reason,
33808
34201
  excerpt,
33809
- path: path25__default.join("wiki", `${entry.id}.md`),
34202
+ path: path27__default.join("wiki", `${entry.id}.md`),
33810
34203
  tags: entry.tags,
33811
34204
  updated: entry.updated,
33812
34205
  version: entry.version,
@@ -33916,15 +34309,15 @@ var PATCH_SET_DIRNAME = "_sets";
33916
34309
  var PATCH_SET_META_FILENAME = PATCH_META_FILENAME;
33917
34310
  function getPatchSetDir(paths, patchSetId) {
33918
34311
  assertSafePatchSetId(patchSetId);
33919
- const patchSetDir = path25__default.join(paths.queueDir, PATCH_SET_DIRNAME, patchSetId);
33920
- const relative = path25__default.relative(path25__default.join(paths.queueDir, PATCH_SET_DIRNAME), patchSetDir);
33921
- if (!relative || relative.startsWith("..") || path25__default.isAbsolute(relative)) {
34312
+ const patchSetDir = path27__default.join(paths.queueDir, PATCH_SET_DIRNAME, patchSetId);
34313
+ const relative = path27__default.relative(path27__default.join(paths.queueDir, PATCH_SET_DIRNAME), patchSetDir);
34314
+ if (!relative || relative.startsWith("..") || path27__default.isAbsolute(relative)) {
33922
34315
  throw new Error(`[fleet-wiki] patch set id escapes queue/_sets: ${patchSetId}`);
33923
34316
  }
33924
34317
  return patchSetDir;
33925
34318
  }
33926
34319
  function getPatchSetMetaFile(paths, patchSetId) {
33927
- return path25__default.join(getPatchSetDir(paths, patchSetId), PATCH_SET_META_FILENAME);
34320
+ return path27__default.join(getPatchSetDir(paths, patchSetId), PATCH_SET_META_FILENAME);
33928
34321
  }
33929
34322
  async function readPatchSet(paths, patchSetId) {
33930
34323
  const patchSet = await readJsonFile2(getPatchSetMetaFile(paths, patchSetId));
@@ -33947,7 +34340,7 @@ async function createConflict(input, paths) {
33947
34340
  await ensureMemoryRoot(paths);
33948
34341
  const now = input.now ?? /* @__PURE__ */ new Date();
33949
34342
  const conflictId = buildConflictId(now, input.wikiId, input.reason, input.proposed);
33950
- const conflictDir = path25__default.join(paths.conflictsDir, conflictId);
34343
+ const conflictDir = path27__default.join(paths.conflictsDir, conflictId);
33951
34344
  await mkdir(conflictDir, { recursive: true });
33952
34345
  const meta3 = {
33953
34346
  id: conflictId,
@@ -33967,13 +34360,13 @@ async function createConflict(input, paths) {
33967
34360
  currentHash: input.currentHash,
33968
34361
  warnings: input.warnings
33969
34362
  };
33970
- await writeJsonFile(path25__default.join(conflictDir, CONFLICT_META_FILENAME), meta3, paths);
33971
- await writePatchFile(path25__default.join(conflictDir, CONFLICT_PROPOSED_FILENAME), input.proposed, paths);
34363
+ await writeJsonFile(path27__default.join(conflictDir, CONFLICT_META_FILENAME), meta3, paths);
34364
+ await writePatchFile(path27__default.join(conflictDir, CONFLICT_PROPOSED_FILENAME), input.proposed, paths);
33972
34365
  if (input.current !== void 0) {
33973
- await writePatchFile(path25__default.join(conflictDir, CONFLICT_CURRENT_FILENAME), input.current, paths);
34366
+ await writePatchFile(path27__default.join(conflictDir, CONFLICT_CURRENT_FILENAME), input.current, paths);
33974
34367
  }
33975
34368
  if (input.rawSource !== void 0) {
33976
- await writePatchFile(path25__default.join(conflictDir, CONFLICT_RAW_SOURCE_FILENAME), input.rawSource, paths);
34369
+ await writePatchFile(path27__default.join(conflictDir, CONFLICT_RAW_SOURCE_FILENAME), input.rawSource, paths);
33977
34370
  }
33978
34371
  return {
33979
34372
  meta: meta3,
@@ -34023,11 +34416,11 @@ async function validatePatch(patch, paths) {
34023
34416
  if (!target || !summary || !proposer || !created) throw new Error("patch frontmatter is incomplete");
34024
34417
  if (summary.length > 120) throw new Error("patch summary exceeds 120 chars");
34025
34418
  assertCanonicalPatchTarget(target);
34026
- const absoluteTarget = path25__default.resolve(paths.root, target);
34027
- if (!absoluteTarget.startsWith(`${paths.root}${path25__default.sep}`) && absoluteTarget !== paths.root) {
34419
+ const absoluteTarget = path27__default.resolve(paths.root, target);
34420
+ if (!absoluteTarget.startsWith(`${paths.root}${path27__default.sep}`) && absoluteTarget !== paths.root) {
34028
34421
  throw new Error("patch target escapes wiki root");
34029
34422
  }
34030
- if (!absoluteTarget.startsWith(`${paths.wikiDir}${path25__default.sep}`)) throw new Error("wiki patch must target wiki/");
34423
+ if (!absoluteTarget.startsWith(`${paths.wikiDir}${path27__default.sep}`)) throw new Error("wiki patch must target wiki/");
34031
34424
  assertNoSymlinkPathComponents(target, paths);
34032
34425
  if (op === "create_wiki" && await pathExists(absoluteTarget)) {
34033
34426
  throw new Error(
@@ -34051,7 +34444,7 @@ async function listQueue(paths) {
34051
34444
  for (const id of ids) {
34052
34445
  if (id === "_sets") continue;
34053
34446
  try {
34054
- const meta3 = await readJsonFile2(path25__default.join(paths.queueDir, id, PATCH_META_FILENAME));
34447
+ const meta3 = await readJsonFile2(path27__default.join(paths.queueDir, id, PATCH_META_FILENAME));
34055
34448
  results.push({ id, meta: meta3 });
34056
34449
  } catch {
34057
34450
  continue;
@@ -34076,9 +34469,9 @@ async function resolveQueueSelection(id, paths) {
34076
34469
  }
34077
34470
  async function showQueue(id, paths) {
34078
34471
  const selection = await resolveQueueSelection(id, paths);
34079
- const queueDir = path25__default.join(paths.queueDir, selection.id);
34080
- const patch = await parsePatch(await readPatchFile(path25__default.join(queueDir, PATCH_FILENAME)));
34081
- const meta3 = await readJsonFile2(path25__default.join(queueDir, PATCH_META_FILENAME));
34472
+ const queueDir = path27__default.join(paths.queueDir, selection.id);
34473
+ const patch = await parsePatch(await readPatchFile(path27__default.join(queueDir, PATCH_FILENAME)));
34474
+ const meta3 = await readJsonFile2(path27__default.join(queueDir, PATCH_META_FILENAME));
34082
34475
  return { patch, meta: meta3 };
34083
34476
  }
34084
34477
  async function approvePatch(id, paths) {
@@ -34101,8 +34494,8 @@ async function approvePatch(id, paths) {
34101
34494
  rawSourceRef: meta3.rawSourceRef,
34102
34495
  warnings: meta3.warnings
34103
34496
  });
34104
- const queueDir = path25__default.join(paths.queueDir, id);
34105
- await writeJsonFile(path25__default.join(queueDir, PATCH_META_FILENAME), {
34497
+ const queueDir = path27__default.join(paths.queueDir, id);
34498
+ await writeJsonFile(path27__default.join(queueDir, PATCH_META_FILENAME), {
34106
34499
  ...meta3,
34107
34500
  conflictId
34108
34501
  }, paths);
@@ -34155,7 +34548,7 @@ async function rejectPatch(id, reason, paths) {
34155
34548
  }
34156
34549
  async function normalizeWikiEntryPatch(entry, target, paths) {
34157
34550
  assertSafeEntryId(entry.id);
34158
- if (entry.id !== path25__default.basename(target, ".md")) {
34551
+ if (entry.id !== path27__default.basename(target, ".md")) {
34159
34552
  throw new Error("wiki patch body id must match target filename");
34160
34553
  }
34161
34554
  const inlineRawSourceRef = extractInlineRawSourceRef(entry.body);
@@ -34193,9 +34586,9 @@ function extractInlineRawSourceRef(body) {
34193
34586
  }
34194
34587
  async function validatePatchBase(patch, meta3, paths) {
34195
34588
  if (patch.frontmatter.op !== "update_wiki" || !meta3?.baseVersion && !meta3?.baseHash) return;
34196
- const wikiId = path25__default.basename(patch.frontmatter.target, ".md");
34589
+ const wikiId = path27__default.basename(patch.frontmatter.target, ".md");
34197
34590
  const currentEntry = await readWikiEntry(wikiId, paths);
34198
- const currentPath = path25__default.join(paths.root, patch.frontmatter.target);
34591
+ const currentPath = path27__default.join(paths.root, patch.frontmatter.target);
34199
34592
  const currentMarkdown = await pathExists(currentPath) ? await readPatchFile(currentPath) : void 0;
34200
34593
  if (meta3.baseVersion !== void 0 && currentEntry?.version !== meta3.baseVersion) {
34201
34594
  throw new Error(
@@ -34254,17 +34647,17 @@ function assertCanonicalPatchTarget(target) {
34254
34647
  if (target.endsWith("/")) {
34255
34648
  throw new Error("patch target must not end with a slash");
34256
34649
  }
34257
- if (target.startsWith("/") || path25__default.posix.isAbsolute(target) || path25__default.win32.isAbsolute(target)) {
34650
+ if (target.startsWith("/") || path27__default.posix.isAbsolute(target) || path27__default.win32.isAbsolute(target)) {
34258
34651
  throw new Error("patch target must be relative");
34259
34652
  }
34260
- if (path25__default.posix.normalize(target) !== target) {
34653
+ if (path27__default.posix.normalize(target) !== target) {
34261
34654
  throw new Error("patch target must not contain dot segments or redundant separators");
34262
34655
  }
34263
34656
  }
34264
34657
  function canonicalApprovalLockKey(paths, target) {
34265
34658
  const wikiRoot = realpathSync(paths.wikiDir);
34266
- const relativeTarget = path25__default.posix.relative("wiki", target);
34267
- const targetPath = path25__default.join(wikiRoot, ...relativeTarget.split("/"));
34659
+ const relativeTarget = path27__default.posix.relative("wiki", target);
34660
+ const targetPath = path27__default.join(wikiRoot, ...relativeTarget.split("/"));
34268
34661
  return `${wikiRoot}\0${targetPath}`.toLowerCase();
34269
34662
  }
34270
34663
  function canonicalPatchEditLockKey(paths, patchId) {
@@ -34280,7 +34673,7 @@ function assertNoSymlinkPathComponents(target, paths) {
34280
34673
  throw error51;
34281
34674
  }
34282
34675
  for (const part of parts) {
34283
- current = path25__default.join(current, part);
34676
+ current = path27__default.join(current, part);
34284
34677
  try {
34285
34678
  if (lstatSync(current).isSymbolicLink()) {
34286
34679
  throw new Error("patch target must not include symlink path components");
@@ -34295,8 +34688,8 @@ function assertSafeRawSourceRef(rawSourceRef, paths) {
34295
34688
  if (!rawSourceRef.startsWith("raw/")) {
34296
34689
  throw new Error("raw source provenance must point into raw/");
34297
34690
  }
34298
- const absoluteRef = path25__default.resolve(paths.root, rawSourceRef);
34299
- if (!absoluteRef.startsWith(`${paths.rawDir}${path25__default.sep}`)) {
34691
+ const absoluteRef = path27__default.resolve(paths.root, rawSourceRef);
34692
+ if (!absoluteRef.startsWith(`${paths.rawDir}${path27__default.sep}`)) {
34300
34693
  throw new Error("raw source provenance must point into raw/");
34301
34694
  }
34302
34695
  }
@@ -34312,14 +34705,14 @@ function classifyPatchConflict(error51) {
34312
34705
  }
34313
34706
  async function recordPatchConflict(patch, paths, reason, options) {
34314
34707
  const now = /* @__PURE__ */ new Date();
34315
- const targetPath = path25__default.join(paths.root, patch.frontmatter.target);
34708
+ const targetPath = path27__default.join(paths.root, patch.frontmatter.target);
34316
34709
  const current = await pathExists(targetPath) ? await readPatchFile(targetPath) : void 0;
34317
34710
  const currentEntry = current ? parseStoredWikiEntry(current) : void 0;
34318
34711
  const proposedEntry = parsePatchBodyEntry(patch.body);
34319
34712
  const record2 = await createConflict({
34320
34713
  reason,
34321
34714
  target: patch.frontmatter.target,
34322
- wikiId: proposedEntry?.id ?? path25__default.basename(patch.frontmatter.target, ".md"),
34715
+ wikiId: proposedEntry?.id ?? path27__default.basename(patch.frontmatter.target, ".md"),
34323
34716
  title: proposedEntry?.title,
34324
34717
  proposer: patch.frontmatter.proposer,
34325
34718
  rawSourceRef: options?.rawSourceRef,
@@ -34415,11 +34808,11 @@ function parseInlineArray(value) {
34415
34808
  return inner.split(",").map((item) => item.trim().replace(/^"(.*)"$/, "$1"));
34416
34809
  }
34417
34810
  async function archiveQueueEntry(id, paths, meta3) {
34418
- const fromDir = path25__default.join(paths.queueDir, id);
34419
- const toDir = path25__default.join(paths.archiveDir, id);
34811
+ const fromDir = path27__default.join(paths.queueDir, id);
34812
+ const toDir = path27__default.join(paths.archiveDir, id);
34420
34813
  await removePath(toDir);
34421
34814
  await movePath(fromDir, toDir);
34422
- await writeJsonFile(path25__default.join(toDir, PATCH_META_FILENAME), meta3, paths);
34815
+ await writeJsonFile(path27__default.join(toDir, PATCH_META_FILENAME), meta3, paths);
34423
34816
  }
34424
34817
  function buildQueueIdHelp(prefix, availableIds) {
34425
34818
  if (availableIds.length === 0) {
@@ -35141,15 +35534,15 @@ async function canonicalizeTheaterPath(cwd) {
35141
35534
  return canonicalizeTheaterPathSync(cwd);
35142
35535
  }
35143
35536
  function canonicalizeTheaterPathSync(cwd) {
35144
- const resolved = path25__default.resolve(cwd);
35537
+ const resolved = path27__default.resolve(cwd);
35145
35538
  try {
35146
- return fs11__default.realpathSync.native(resolved);
35539
+ return fs14__default.realpathSync.native(resolved);
35147
35540
  } catch {
35148
35541
  return resolved;
35149
35542
  }
35150
35543
  }
35151
35544
  function theaterLabel(cwd) {
35152
- return path25__default.basename(cwd) || cwd;
35545
+ return path27__default.basename(cwd) || cwd;
35153
35546
  }
35154
35547
 
35155
35548
  // core/host/codex/workspaces.ts
@@ -35157,7 +35550,7 @@ var WorkspaceRegistry = class {
35157
35550
  #items = /* @__PURE__ */ new Map();
35158
35551
  #mruId = null;
35159
35552
  async register(cwdInput, lastOpenedAt) {
35160
- const cwd = path25__default.resolve(cwdInput);
35553
+ const cwd = path27__default.resolve(cwdInput);
35161
35554
  const real = await canonicalizeTheaterPath(cwd);
35162
35555
  const paths = resolveMemoryPaths(real);
35163
35556
  if (!await directoryExists(paths.root)) {
@@ -35173,7 +35566,7 @@ var WorkspaceRegistry = class {
35173
35566
  id,
35174
35567
  cwd,
35175
35568
  realpath: real,
35176
- label: path25__default.basename(cwd),
35569
+ label: path27__default.basename(cwd),
35177
35570
  paths,
35178
35571
  registeredAt: existing?.registeredAt ?? now,
35179
35572
  // 복원 경로는 durable lastOpenedAt을 그대로 보존해 재시작 후에도 워크스페이스 최근성
@@ -35239,6 +35632,7 @@ var LOOPBACK_ACCESS_HOSTS = ["127.0.0.1", "::1"];
35239
35632
  var LOOPBACK_HOST = "127.0.0.1";
35240
35633
  function createCodexGateway(deps) {
35241
35634
  const workspaces = new WorkspaceRegistry();
35635
+ const workspaceOwners = /* @__PURE__ */ new Map();
35242
35636
  let accessSets = null;
35243
35637
  let initialWorkspace = null;
35244
35638
  let initialWorkspaceId = null;
@@ -35268,6 +35662,10 @@ function createCodexGateway(deps) {
35268
35662
  redirect(response, `${CODEX_BASE}${selected.location}`);
35269
35663
  return true;
35270
35664
  }
35665
+ if (selected.kind === "missing-workspace") {
35666
+ sendJson2(response, 404, { error: "workspace_not_found" });
35667
+ return true;
35668
+ }
35271
35669
  if (selected.kind === "no-workspace") {
35272
35670
  if (isJsonRequest4(request)) {
35273
35671
  sendJson2(response, 404, { error: "no_workspace_registered" });
@@ -35298,18 +35696,50 @@ function createCodexGateway(deps) {
35298
35696
  initialWorkspaceId = workspace.id;
35299
35697
  return workspace;
35300
35698
  }
35699
+ async function registerWorkspace(cwd, lastOpenedAt, ownerTheaterId) {
35700
+ const workspace = await workspaces.register(cwd, lastOpenedAt);
35701
+ if (ownerTheaterId) {
35702
+ const owners = workspaceOwners.get(workspace.id) ?? /* @__PURE__ */ new Set();
35703
+ owners.add(ownerTheaterId);
35704
+ workspaceOwners.set(workspace.id, owners);
35705
+ }
35706
+ return workspace;
35707
+ }
35708
+ async function resolveWorkspaceForPath(theaterId, theaterRoot, relPath) {
35709
+ const pathContext = await resolveTheaterPathContext(theaterRoot, relPath);
35710
+ try {
35711
+ const workspace = await registerWorkspace(pathContext.realPath, void 0, theaterId);
35712
+ return { hasWiki: true, id: workspace.id };
35713
+ } catch (error51) {
35714
+ if (error51 instanceof Error && error51.message === "knowledge_root_missing") {
35715
+ return { hasWiki: false, id: null };
35716
+ }
35717
+ throw error51;
35718
+ }
35719
+ }
35720
+ function unregisterWorkspace(id) {
35721
+ if (initialWorkspaceId === id) {
35722
+ initialWorkspace = null;
35723
+ initialWorkspaceId = null;
35724
+ }
35725
+ return workspaces.remove(id);
35726
+ }
35727
+ function unregisterTheaterWorkspaces(theaterId) {
35728
+ for (const [workspaceId, owners] of workspaceOwners) {
35729
+ owners.delete(theaterId);
35730
+ if (owners.size === 0) {
35731
+ workspaceOwners.delete(workspaceId);
35732
+ unregisterWorkspace(workspaceId);
35733
+ }
35734
+ }
35735
+ }
35301
35736
  return {
35302
35737
  getWorkspace: (id) => workspaces.get(id),
35303
35738
  handle,
35304
35739
  listWorkspaceRegistrations: () => workspaces.listRegistrations(),
35305
- registerWorkspace: (cwd, lastOpenedAt) => workspaces.register(cwd, lastOpenedAt),
35306
- unregisterWorkspace: (id) => {
35307
- if (initialWorkspaceId === id) {
35308
- initialWorkspace = null;
35309
- initialWorkspaceId = null;
35310
- }
35311
- return workspaces.remove(id);
35312
- }
35740
+ registerWorkspace,
35741
+ resolveWorkspaceForPath,
35742
+ unregisterTheaterWorkspaces
35313
35743
  };
35314
35744
  }
35315
35745
  function buildAllowedAccessSets(host, port, interfaces = os4__default.networkInterfaces()) {
@@ -35337,8 +35767,10 @@ function selectWorkspace(requestUrl, workspaces, cwd) {
35337
35767
  const prefixed = url2.pathname.match(/^\/w\/([^/]+)(\/.*)?$/);
35338
35768
  if (prefixed) {
35339
35769
  const workspace = workspaces.get(decodeURIComponent(prefixed[1] ?? ""));
35340
- if (!workspace) return { kind: "redirect", location: "/" };
35341
35770
  const suffix = prefixed[2] ?? "/";
35771
+ if (!workspace) {
35772
+ return suffix.startsWith("/api/") ? { kind: "missing-workspace" } : { kind: "redirect", location: "/" };
35773
+ }
35342
35774
  url2.pathname = suffix;
35343
35775
  return { kind: "workspace", workspace, rewrittenUrl: `${url2.pathname}${url2.search}` };
35344
35776
  }
@@ -35491,7 +35923,7 @@ function createConsoleDurableStateStore(deps = {}) {
35491
35923
  const createStore = deps.createStore ?? createDurableJsonStore;
35492
35924
  return createStore({
35493
35925
  filePath: paths.stateFile,
35494
- lockDir: path25__default.join(paths.dir, STATE_LOCK_DIR_NAME),
35926
+ lockDir: path27__default.join(paths.dir, STATE_LOCK_DIR_NAME),
35495
35927
  lockOwnerFileName: STATE_LOCK_OWNER_FILE_NAME,
35496
35928
  now: deps.now,
35497
35929
  sanitize: sanitizeDurableConsoleState,
@@ -35533,7 +35965,7 @@ function migrateV1Operation(value) {
35533
35965
  const cwd = readNonEmptyString(value.cwd);
35534
35966
  const createdAt = readFiniteNumber(value.createdAt);
35535
35967
  if (!sessionId || !theaterId || !cwd || createdAt === null) return null;
35536
- const title = readOptionalString(value.label) ?? (path25__default.basename(cwd) || cwd);
35968
+ const title = readOptionalString(value.label) ?? (path27__default.basename(cwd) || cwd);
35537
35969
  const payload = { cwd };
35538
35970
  const cliId = readOptionalString(value.cliId);
35539
35971
  const cliLabel = readOptionalString(value.cliLabel);
@@ -35580,7 +36012,11 @@ function sanitizeTheaterRegistration(value) {
35580
36012
  const registeredAt = readNonEmptyString(value.registeredAt);
35581
36013
  const lastOpenedAt = readNonEmptyString(value.lastOpenedAt);
35582
36014
  if (!id || !theaterPath || !realpath2 || !label || !registeredAt || !lastOpenedAt) return null;
35583
- return { id, path: theaterPath, realpath: realpath2, label, registeredAt, lastOpenedAt };
36015
+ const pathContext = typeof value.pathContext === "string" && isSafeDurableRelPath(value.pathContext) ? value.pathContext : null;
36016
+ return { id, path: theaterPath, realpath: realpath2, label, registeredAt, lastOpenedAt, pathContext };
36017
+ }
36018
+ function isSafeDurableRelPath(value) {
36019
+ return value.length > 0 && !value.includes("\0") && !value.startsWith("/") && !value.startsWith("\\") && !/^[A-Za-z]:/.test(value) && !value.split("/").some((part) => part === "" || part === "." || part === "..");
35584
36020
  }
35585
36021
  function sanitizeOperationNode(value) {
35586
36022
  if (!isRecord6(value)) return null;
@@ -36117,6 +36553,359 @@ function sanitizeReplacementNodes(nextNodes) {
36117
36553
  return result;
36118
36554
  }
36119
36555
 
36556
+ // core/host/plans/plan-parse.ts
36557
+ var TITLE_PATTERN = /^#\s+(.+)$/;
36558
+ var EXECUTION_MODE_PATTERN = /^-\s*Execution mode:\s*(sequential|parallel)\b/i;
36559
+ var WAVE_PATTERN = /^##\s+wave\s+(\d+)\b/i;
36560
+ var LANE_PATTERN = /^###\s+lane\s+(.+)$/i;
36561
+ var LANE_ID_PATTERN = /^(W\d+-[A-Za-z0-9]+)\b/i;
36562
+ var LANE_BOUNDARY_PATTERN = /^#{1,3}\s/;
36563
+ var SECTION_HEADING_PATTERN = /^#{1,2}\s/;
36564
+ var CHECKBOX_PATTERN = /^\s*-\s*\[( |x|X)\]/;
36565
+ var FENCE_PATTERN = /^\s*(`{3,}|~{3,})/;
36566
+ var CLOSING_FENCE_PATTERN = /^\s*(`{3,}|~{3,})\s*$/;
36567
+ var TEMPLATE_SECTION_TITLES = /* @__PURE__ */ new Set([
36568
+ "objective",
36569
+ "file ownership",
36570
+ "waves",
36571
+ "qa gates",
36572
+ "acceptance criteria",
36573
+ "documentation updates",
36574
+ "final review loop"
36575
+ ]);
36576
+ function parsePlan(content) {
36577
+ const lines = scanPlanLines(content.split(/\r?\n/));
36578
+ const title = findTitle(lines);
36579
+ const executionMode = findExecutionMode(lines);
36580
+ const waveStarts = findWaveStarts(lines);
36581
+ const waves = waveStarts.map(({ index, heading, lineIndex }, waveIndex) => {
36582
+ const nextWaveLineIndex = waveStarts[waveIndex + 1]?.lineIndex ?? lines.length;
36583
+ const endLineIndex = findSectionEnd(lines, lineIndex + 1, nextWaveLineIndex);
36584
+ const waveLines = lines.slice(lineIndex + 1, endLineIndex);
36585
+ const counts = countTasks(waveLines);
36586
+ return { index, heading, lanes: findLanes(waveLines), ...counts };
36587
+ });
36588
+ return { title, executionMode, waves, ...countTasks(lines) };
36589
+ }
36590
+ function scanPlanLines(lines) {
36591
+ let activeFence = null;
36592
+ return lines.map((text) => {
36593
+ if (activeFence !== null) {
36594
+ const closer = CLOSING_FENCE_PATTERN.exec(text)?.[1];
36595
+ if (closer !== void 0 && closer.startsWith(activeFence.char) && closer.length >= activeFence.length) {
36596
+ activeFence = null;
36597
+ }
36598
+ return { text, isFenced: true };
36599
+ }
36600
+ const marker = FENCE_PATTERN.exec(text)?.[1];
36601
+ if (marker !== void 0) {
36602
+ activeFence = { char: marker[0] ?? "`", length: marker.length };
36603
+ return { text, isFenced: true };
36604
+ }
36605
+ return { text, isFenced: false };
36606
+ });
36607
+ }
36608
+ function findTitle(lines) {
36609
+ for (const line of lines) {
36610
+ if (line.isFenced) continue;
36611
+ const match = TITLE_PATTERN.exec(line.text);
36612
+ if (!match?.[1]) continue;
36613
+ const heading = match[1].trim();
36614
+ if (TEMPLATE_SECTION_TITLES.has(heading.toLowerCase())) return null;
36615
+ return heading;
36616
+ }
36617
+ return null;
36618
+ }
36619
+ function findExecutionMode(lines) {
36620
+ for (const line of lines) {
36621
+ if (line.isFenced) continue;
36622
+ const match = EXECUTION_MODE_PATTERN.exec(line.text.trim());
36623
+ if (match?.[1]) return match[1].toLowerCase();
36624
+ }
36625
+ return null;
36626
+ }
36627
+ function findWaveStarts(lines) {
36628
+ const waves = [];
36629
+ for (const [lineIndex, line] of lines.entries()) {
36630
+ if (line.isFenced) continue;
36631
+ const match = WAVE_PATTERN.exec(line.text);
36632
+ if (!match?.[1]) continue;
36633
+ waves.push({
36634
+ index: Number(match[1]),
36635
+ heading: line.text.replace(/^##\s+/, ""),
36636
+ lineIndex
36637
+ });
36638
+ }
36639
+ return waves;
36640
+ }
36641
+ function findLanes(waveLines) {
36642
+ const laneStarts = [];
36643
+ for (const [lineIndex, line] of waveLines.entries()) {
36644
+ if (line.isFenced) continue;
36645
+ const match = LANE_PATTERN.exec(line.text);
36646
+ if (!match?.[1]) continue;
36647
+ const heading = match[1].trim();
36648
+ laneStarts.push({ heading, id: LANE_ID_PATTERN.exec(heading)?.[1]?.toUpperCase() ?? null, lineIndex });
36649
+ }
36650
+ return laneStarts.map(({ heading, id, lineIndex }, laneIndex) => {
36651
+ const nextLaneLineIndex = laneStarts[laneIndex + 1]?.lineIndex ?? waveLines.length;
36652
+ let endLineIndex = nextLaneLineIndex;
36653
+ for (let cursor = lineIndex + 1; cursor < nextLaneLineIndex; cursor++) {
36654
+ const line = waveLines[cursor];
36655
+ if (!line?.isFenced && LANE_BOUNDARY_PATTERN.test(line?.text ?? "")) {
36656
+ endLineIndex = cursor;
36657
+ break;
36658
+ }
36659
+ }
36660
+ return { id, heading, ...countTasks(waveLines.slice(lineIndex + 1, endLineIndex)) };
36661
+ });
36662
+ }
36663
+ function findSectionEnd(lines, startLineIndex, maxLineIndex) {
36664
+ for (let lineIndex = startLineIndex; lineIndex < maxLineIndex; lineIndex++) {
36665
+ const line = lines[lineIndex];
36666
+ if (!line?.isFenced && SECTION_HEADING_PATTERN.test(line?.text ?? "")) return lineIndex;
36667
+ }
36668
+ return maxLineIndex;
36669
+ }
36670
+ function countTasks(lines) {
36671
+ let tasksDone = 0;
36672
+ let tasksTotal = 0;
36673
+ for (const line of lines) {
36674
+ if (line.isFenced) continue;
36675
+ const match = CHECKBOX_PATTERN.exec(line.text);
36676
+ if (!match) continue;
36677
+ tasksTotal++;
36678
+ if (match[1] === "x" || match[1] === "X") tasksDone++;
36679
+ }
36680
+ return { tasksDone, tasksTotal };
36681
+ }
36682
+
36683
+ // core/host/plans/plan-store.ts
36684
+ var PLANS_DIRECTORY_SEGMENTS = [".fleet", "plans"];
36685
+ var MARKDOWN_EXTENSION = ".md";
36686
+ var PLAN_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]*\.md$/;
36687
+ var LIST_READ_CONCURRENCY = 16;
36688
+ var PLAN_READ_SIZE_CAP = 2 * 1024 * 1024;
36689
+ var PlanStoreError = class extends Error {
36690
+ code;
36691
+ constructor(code) {
36692
+ super(code);
36693
+ this.name = "PlanStoreError";
36694
+ this.code = code;
36695
+ }
36696
+ };
36697
+ async function listPlansForTheater(theaterPath) {
36698
+ const plansRoot = await resolvePlansRoot(theaterPath, true);
36699
+ if (plansRoot === null) return [];
36700
+ let entries;
36701
+ try {
36702
+ entries = await fs14__default.promises.readdir(plansRoot.realPlansPath, { withFileTypes: true });
36703
+ } catch (error51) {
36704
+ throw toPlanStoreError(error51);
36705
+ }
36706
+ const names = entries.filter((entry) => (entry.isFile() || entry.isSymbolicLink()) && entry.name.endsWith(MARKDOWN_EXTENSION) && isValidPlanName(entry.name)).map((entry) => entry.name);
36707
+ const plans = [];
36708
+ for (let startIndex = 0; startIndex < names.length; startIndex += LIST_READ_CONCURRENCY) {
36709
+ const chunk = names.slice(startIndex, startIndex + LIST_READ_CONCURRENCY);
36710
+ const results = await Promise.all(chunk.map((name) => toPlanListItem(plansRoot, name).catch(() => null)));
36711
+ plans.push(...results.filter((plan) => plan !== null));
36712
+ }
36713
+ return plans.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
36714
+ }
36715
+ function isValidPlanName(name) {
36716
+ return PLAN_NAME_PATTERN.test(name) && !name.includes("..");
36717
+ }
36718
+ async function readPlanForTheater(theaterPath, name) {
36719
+ const plansRoot = await resolvePlansRoot(theaterPath, false);
36720
+ if (plansRoot === null) throw new PlanStoreError("not_found");
36721
+ const file2 = await resolvePlanFile(plansRoot, name);
36722
+ if (file2.stat.size > PLAN_READ_SIZE_CAP) throw new PlanStoreError("too_large");
36723
+ let content;
36724
+ try {
36725
+ content = await fs14__default.promises.readFile(file2.realPath, "utf8");
36726
+ } catch (error51) {
36727
+ throw toPlanStoreError(error51);
36728
+ }
36729
+ const parsed = parsePlan(content);
36730
+ return {
36731
+ name,
36732
+ title: parsed.title ?? name,
36733
+ executionMode: parsed.executionMode,
36734
+ updatedAt: file2.stat.mtime.toISOString(),
36735
+ content,
36736
+ waves: parsed.waves,
36737
+ tasksDone: parsed.tasksDone,
36738
+ tasksTotal: parsed.tasksTotal
36739
+ };
36740
+ }
36741
+ async function toPlanListItem(plansRoot, name) {
36742
+ const file2 = await resolvePlanFile(plansRoot, name);
36743
+ if (file2.stat.size > PLAN_READ_SIZE_CAP) {
36744
+ return {
36745
+ name,
36746
+ title: name,
36747
+ executionMode: null,
36748
+ waveCount: 0,
36749
+ tasksDone: 0,
36750
+ tasksTotal: 0,
36751
+ updatedAt: file2.stat.mtime.toISOString(),
36752
+ sizeBytes: file2.stat.size
36753
+ };
36754
+ }
36755
+ const content = await fs14__default.promises.readFile(file2.realPath, "utf8").catch((error51) => {
36756
+ throw toPlanStoreError(error51);
36757
+ });
36758
+ const parsed = parsePlan(content);
36759
+ return {
36760
+ name,
36761
+ title: parsed.title ?? name,
36762
+ executionMode: parsed.executionMode,
36763
+ waveCount: parsed.waves.length,
36764
+ tasksDone: parsed.tasksDone,
36765
+ tasksTotal: parsed.tasksTotal,
36766
+ updatedAt: file2.stat.mtime.toISOString(),
36767
+ sizeBytes: file2.stat.size
36768
+ };
36769
+ }
36770
+ async function resolvePlansRoot(contextPath, allowMissing) {
36771
+ const nominalContextPath = path27__default.resolve(contextPath);
36772
+ const plansPath = path27__default.resolve(nominalContextPath, ...PLANS_DIRECTORY_SEGMENTS);
36773
+ if (!isWithinRoot2(plansPath, nominalContextPath)) throw new PlanStoreError("path_outside_theater");
36774
+ let realContextPath;
36775
+ let realPlansPath;
36776
+ try {
36777
+ [realContextPath, realPlansPath] = await Promise.all([
36778
+ fs14__default.promises.realpath(nominalContextPath),
36779
+ fs14__default.promises.realpath(plansPath)
36780
+ ]);
36781
+ } catch (error51) {
36782
+ if (allowMissing && isNotFoundError(error51)) return null;
36783
+ throw toPlanStoreError(error51);
36784
+ }
36785
+ if (!isWithinRoot2(realPlansPath, realContextPath)) throw new PlanStoreError("path_outside_theater");
36786
+ return { realContextPath, realPlansPath };
36787
+ }
36788
+ async function resolvePlanFile(plansRoot, name) {
36789
+ const candidatePath = path27__default.resolve(plansRoot.realPlansPath, name);
36790
+ if (!isWithinRoot2(candidatePath, plansRoot.realPlansPath)) throw new PlanStoreError("path_outside_theater");
36791
+ let realPath;
36792
+ try {
36793
+ realPath = await fs14__default.promises.realpath(candidatePath);
36794
+ } catch (error51) {
36795
+ throw toPlanStoreError(error51);
36796
+ }
36797
+ if (!isWithinRoot2(realPath, plansRoot.realPlansPath) || !isWithinRoot2(realPath, plansRoot.realContextPath)) {
36798
+ throw new PlanStoreError("path_outside_theater");
36799
+ }
36800
+ let stat5;
36801
+ try {
36802
+ stat5 = await fs14__default.promises.stat(realPath);
36803
+ } catch (error51) {
36804
+ throw toPlanStoreError(error51);
36805
+ }
36806
+ if (!stat5.isFile()) throw new PlanStoreError("not_found");
36807
+ return { realPath, stat: stat5 };
36808
+ }
36809
+ function isWithinRoot2(resolvedPath, rootPath) {
36810
+ const normalizedRoot = rootPath.endsWith(path27__default.sep) ? rootPath : rootPath + path27__default.sep;
36811
+ return resolvedPath === rootPath || resolvedPath.startsWith(normalizedRoot);
36812
+ }
36813
+ function isNotFoundError(error51) {
36814
+ return error51.code === "ENOENT";
36815
+ }
36816
+ function toPlanStoreError(error51) {
36817
+ if (error51 instanceof PlanStoreError) return error51;
36818
+ return new PlanStoreError("not_found");
36819
+ }
36820
+
36821
+ // core/host/plans/routes.ts
36822
+ function createPlansRouter(deps) {
36823
+ return async function handlePlansRoute(context) {
36824
+ const { req, res, pathname } = context;
36825
+ if (pathname === "/api/v1/plans/list") {
36826
+ await handlePlansList(req, res, deps);
36827
+ return true;
36828
+ }
36829
+ if (pathname === "/api/v1/plans/read") {
36830
+ await handlePlansRead(req, res, deps);
36831
+ return true;
36832
+ }
36833
+ return false;
36834
+ };
36835
+ }
36836
+ async function handlePlansList(req, res, deps) {
36837
+ if (req.method !== "POST") {
36838
+ deps.writeJson(res, 405, { error: "Method not allowed" });
36839
+ return;
36840
+ }
36841
+ if (!deps.isAuthorized(req)) {
36842
+ deps.writeJson(res, 401, { error: "unauthorized" });
36843
+ return;
36844
+ }
36845
+ const body = await deps.readJsonBody(req);
36846
+ if (!isPlainObject2(body) || typeof body.theaterId !== "string" || !isValidRelPath(body.relPath)) {
36847
+ deps.writeJson(res, 400, { error: "invalid_request" });
36848
+ return;
36849
+ }
36850
+ const theaterPath = deps.resolveTheaterPath(body.theaterId);
36851
+ if (!theaterPath) {
36852
+ deps.writeJson(res, 404, { error: "theater_not_found" });
36853
+ return;
36854
+ }
36855
+ try {
36856
+ const context = await resolveTheaterPathContext(theaterPath, body.relPath);
36857
+ deps.writeJson(res, 200, { plans: await listPlansForTheater(context.realPath) });
36858
+ } catch (error51) {
36859
+ writePlanStoreError(res, deps, error51);
36860
+ }
36861
+ }
36862
+ async function handlePlansRead(req, res, deps) {
36863
+ if (req.method !== "POST") {
36864
+ deps.writeJson(res, 405, { error: "Method not allowed" });
36865
+ return;
36866
+ }
36867
+ if (!deps.isAuthorized(req)) {
36868
+ deps.writeJson(res, 401, { error: "unauthorized" });
36869
+ return;
36870
+ }
36871
+ const body = await deps.readJsonBody(req);
36872
+ if (!isPlainObject2(body) || typeof body.theaterId !== "string" || !isValidRelPath(body.relPath)) {
36873
+ deps.writeJson(res, 400, { error: "invalid_request" });
36874
+ return;
36875
+ }
36876
+ if (typeof body.name !== "string" || !isValidPlanName(body.name)) {
36877
+ deps.writeJson(res, 400, { error: "invalid_name" });
36878
+ return;
36879
+ }
36880
+ const theaterPath = deps.resolveTheaterPath(body.theaterId);
36881
+ if (!theaterPath) {
36882
+ deps.writeJson(res, 404, { error: "theater_not_found" });
36883
+ return;
36884
+ }
36885
+ try {
36886
+ const context = await resolveTheaterPathContext(theaterPath, body.relPath);
36887
+ deps.writeJson(res, 200, await readPlanForTheater(context.realPath, body.name));
36888
+ } catch (error51) {
36889
+ writePlanStoreError(res, deps, error51);
36890
+ }
36891
+ }
36892
+ function isPlainObject2(value) {
36893
+ return typeof value === "object" && value !== null && !Array.isArray(value);
36894
+ }
36895
+ function isValidRelPath(value) {
36896
+ return value === null || typeof value === "string" && isSafeRelativePath(value);
36897
+ }
36898
+ function writePlanStoreError(res, deps, error51) {
36899
+ if (error51 instanceof TheaterPathContextError) {
36900
+ const httpStatus2 = error51.code === "forbidden" ? 403 : error51.code === "not_found" ? 404 : 400;
36901
+ deps.writeJson(res, httpStatus2, { error: error51.code });
36902
+ return;
36903
+ }
36904
+ if (!(error51 instanceof PlanStoreError)) throw error51;
36905
+ const httpStatus = error51.code === "path_outside_theater" ? 403 : error51.code === "too_large" ? 413 : 404;
36906
+ deps.writeJson(res, httpStatus, { error: error51.code });
36907
+ }
36908
+
36120
36909
  // sdk/version.ts
36121
36910
  var SDK_API_VERSION = 1;
36122
36911
 
@@ -36238,14 +37027,14 @@ function renderShim(definition) {
36238
37027
  ].join("\n");
36239
37028
  }
36240
37029
  function isSubpath(root, target) {
36241
- const relative = path25__default.relative(root, target);
36242
- return relative === "" || !relative.startsWith("..") && !path25__default.isAbsolute(relative);
37030
+ const relative = path27__default.relative(root, target);
37031
+ return relative === "" || !relative.startsWith("..") && !path27__default.isAbsolute(relative);
36243
37032
  }
36244
37033
  function discoverFleetPlugins(options = {}) {
36245
- const builtInSourceRoot = options.builtInSourceRoot ?? (options.cwd ? path25__default.resolve(options.cwd, "runtime/fleet-plugins") : null);
37034
+ const builtInSourceRoot = options.builtInSourceRoot ?? (options.cwd ? path27__default.resolve(options.cwd, "runtime/fleet-plugins") : null);
36246
37035
  const roots = [
36247
37036
  ...builtInSourceRoot ? [{ root: builtInSourceRoot, builtInDistRoot: options.builtInDistRoot ?? null, external: false }] : [],
36248
- { root: path25__default.join(options.homeDir ?? os4__default.homedir(), ".fleet", "plugins"), builtInDistRoot: null, external: true }
37037
+ { root: path27__default.join(options.homeDir ?? os4__default.homedir(), ".fleet", "plugins"), builtInDistRoot: null, external: true }
36249
37038
  ];
36250
37039
  const sourcePlugins = roots.flatMap((root) => discoverPluginRoot(root.root, root.builtInDistRoot, root.external));
36251
37040
  const sourcePluginIds = new Set(sourcePlugins.map((plugin) => plugin.manifest.id));
@@ -36266,14 +37055,14 @@ function parseFleetPluginManifest(value) {
36266
37055
  };
36267
37056
  }
36268
37057
  function discoverPluginRoot(root, builtInDistRoot, external) {
36269
- if (!fs11__default.existsSync(root)) return [];
36270
- const entries = fs11__default.readdirSync(root, { withFileTypes: true });
37058
+ if (!fs14__default.existsSync(root)) return [];
37059
+ const entries = fs14__default.readdirSync(root, { withFileTypes: true });
36271
37060
  const plugins = [];
36272
37061
  for (const entry of entries) {
36273
37062
  if (!entry.isDirectory() || entry.name === "shared") continue;
36274
- const pluginRoot = path25__default.join(root, entry.name);
36275
- const manifestPath = path25__default.join(pluginRoot, "plugin.json");
36276
- if (!fs11__default.existsSync(manifestPath)) continue;
37063
+ const pluginRoot = path27__default.join(root, entry.name);
37064
+ const manifestPath = path27__default.join(pluginRoot, "plugin.json");
37065
+ if (!fs14__default.existsSync(manifestPath)) continue;
36277
37066
  const manifest = readManifest(manifestPath);
36278
37067
  if (!manifest) continue;
36279
37068
  const clientEntry = resolveOptionalManifestEntry(pluginRoot, manifest.client, "client");
@@ -36285,14 +37074,14 @@ function discoverPluginRoot(root, builtInDistRoot, external) {
36285
37074
  return plugins;
36286
37075
  }
36287
37076
  function discoverDistPluginRoot(root) {
36288
- if (!fs11__default.existsSync(root)) return [];
36289
- const entries = fs11__default.readdirSync(root, { withFileTypes: true });
37077
+ if (!fs14__default.existsSync(root)) return [];
37078
+ const entries = fs14__default.readdirSync(root, { withFileTypes: true });
36290
37079
  const plugins = [];
36291
37080
  for (const entry of entries) {
36292
37081
  if (!entry.isDirectory() || entry.name === "shared") continue;
36293
- const pluginRoot = path25__default.join(root, entry.name);
36294
- const manifestPath = path25__default.join(pluginRoot, "plugin.json");
36295
- const manifest = fs11__default.existsSync(manifestPath) ? readManifest(manifestPath) : readDistPluginManifest(entry.name);
37082
+ const pluginRoot = path27__default.join(root, entry.name);
37083
+ const manifestPath = path27__default.join(pluginRoot, "plugin.json");
37084
+ const manifest = fs14__default.existsSync(manifestPath) ? readManifest(manifestPath) : readDistPluginManifest(entry.name);
36296
37085
  if (!manifest) continue;
36297
37086
  const clientEntry = resolveOptionalManifestEntry(pluginRoot, manifest.client, "client");
36298
37087
  if (clientEntry === false) continue;
@@ -36304,7 +37093,7 @@ function discoverDistPluginRoot(root) {
36304
37093
  }
36305
37094
  function readManifest(manifestPath) {
36306
37095
  try {
36307
- const manifest = parseFleetPluginManifest(JSON.parse(fs11__default.readFileSync(manifestPath, "utf8")));
37096
+ const manifest = parseFleetPluginManifest(JSON.parse(fs14__default.readFileSync(manifestPath, "utf8")));
36308
37097
  if (!manifest) console.warn(`[fleet-console] Plugin manifest skipped: invalid manifest at ${manifestPath}`);
36309
37098
  return manifest;
36310
37099
  } catch (error51) {
@@ -36318,11 +37107,11 @@ function resolveRoutesEntry(pluginRoot, manifest, builtInDistRoot) {
36318
37107
  if (manifestRoute2 === false) return false;
36319
37108
  if (manifestRoute2) return manifestRoute2;
36320
37109
  }
36321
- const manifestRoute = path25__default.join(pluginRoot, "routes.ts");
36322
- if (fs11__default.existsSync(manifestRoute)) return manifestRoute;
37110
+ const manifestRoute = path27__default.join(pluginRoot, "routes.ts");
37111
+ if (fs14__default.existsSync(manifestRoute)) return manifestRoute;
36323
37112
  if (!builtInDistRoot) return null;
36324
- const builtRoute = path25__default.join(builtInDistRoot, manifest.id, "routes.mjs");
36325
- return fs11__default.existsSync(builtRoute) ? builtRoute : null;
37113
+ const builtRoute = path27__default.join(builtInDistRoot, manifest.id, "routes.mjs");
37114
+ return fs14__default.existsSync(builtRoute) ? builtRoute : null;
36326
37115
  }
36327
37116
  function resolveDistRoutesEntry(pluginRoot, manifest) {
36328
37117
  const routesEntry = resolveOptionalManifestEntry(pluginRoot, manifest.routes ?? "routes.mjs", "routes");
@@ -36335,10 +37124,10 @@ function resolveOptionalManifestEntry(pluginRoot, entryPath, label) {
36335
37124
  console.warn(`[fleet-console] Plugin ${label} entry skipped: unsafe relative path ${entryPath}`);
36336
37125
  return false;
36337
37126
  }
36338
- const candidate = path25__default.resolve(pluginRoot, entryPath);
36339
- if (!fs11__default.existsSync(candidate)) return null;
36340
- const rootRealpath = fs11__default.realpathSync.native(pluginRoot);
36341
- const entryRealpath = fs11__default.realpathSync.native(candidate);
37127
+ const candidate = path27__default.resolve(pluginRoot, entryPath);
37128
+ if (!fs14__default.existsSync(candidate)) return null;
37129
+ const rootRealpath = fs14__default.realpathSync.native(pluginRoot);
37130
+ const entryRealpath = fs14__default.realpathSync.native(candidate);
36342
37131
  if (!isSubpath2(rootRealpath, entryRealpath)) {
36343
37132
  console.warn(`[fleet-console] Plugin ${label} entry skipped: path escapes plugin root ${entryPath}`);
36344
37133
  return false;
@@ -36353,12 +37142,12 @@ function readDistPluginManifest(id) {
36353
37142
  return pluginId ? { id: pluginId, routes: "routes.mjs" } : null;
36354
37143
  }
36355
37144
  function isSafeRelativeEntry(entryPath) {
36356
- if (path25__default.isAbsolute(entryPath)) return false;
37145
+ if (path27__default.isAbsolute(entryPath)) return false;
36357
37146
  return entryPath.split(/[\\/]+/u).every((segment) => segment !== "..");
36358
37147
  }
36359
37148
  function isSubpath2(root, candidate) {
36360
- const relative = path25__default.relative(root, candidate);
36361
- return relative === "" || !relative.startsWith("..") && !path25__default.isAbsolute(relative);
37149
+ const relative = path27__default.relative(root, candidate);
37150
+ return relative === "" || !relative.startsWith("..") && !path27__default.isAbsolute(relative);
36362
37151
  }
36363
37152
  function isRecord9(value) {
36364
37153
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -36376,17 +37165,22 @@ var PLUGIN_DEV_EXTERNALS = [
36376
37165
  "child_process",
36377
37166
  "node-pty",
36378
37167
  "ws",
36379
- "@fleet-console/sdk",
36380
- "@fleet-console/sdk/*",
36381
- "@dotobokuri/*",
36382
37168
  "@fleet-plugins/*"
36383
37169
  ];
36384
- var __dirname$1 = path25__default.dirname(fileURLToPath(import.meta.url));
37170
+ var PLUGIN_BUNDLE_DIR_PREFIX = "fleet-console-plugin-";
37171
+ var PLUGIN_BUNDLE_OWNER_FILE = ".fleet-console-plugin-owner.json";
37172
+ var PLUGIN_BUNDLE_PID_PATTERN = /^fleet-console-plugin-(\d+)-/u;
37173
+ var MAX_PLUGIN_BUNDLE_OWNER_PID = 2147483647;
37174
+ var __dirname$1 = path27__default.dirname(fileURLToPath(import.meta.url));
36385
37175
  function createFleetPluginHost(deps) {
36386
37176
  const plugins = filterDiscoveredPlugins(discoverFleetPlugins(deps));
36387
37177
  const sensitiveFieldsByPluginId = new Map(plugins.map((plugin) => [plugin.manifest.id, plugin.manifest.sensitiveFields ?? []]));
36388
- const importModule = deps.importModule ?? importPluginModule;
37178
+ const generatedBundleDirs = /* @__PURE__ */ new Set();
37179
+ const isProcessAlive2 = deps.isProcessAlive ?? isPluginBundleOwnerAlive;
37180
+ const removeBundleDir = deps.removeBundleDir ?? removePluginBundleDir;
37181
+ const importModule = deps.importModule ?? ((entry) => importPluginModule(entry, deps.bundleCacheDir, generatedBundleDirs));
36389
37182
  async function boot() {
37183
+ await removeStalePluginBundleDirs(deps.bundleCacheDir, isProcessAlive2, removeBundleDir);
36390
37184
  for (const plugin of plugins) {
36391
37185
  if (!plugin.routesEntry) continue;
36392
37186
  if (!plugin.external) {
@@ -36400,7 +37194,21 @@ function createFleetPluginHost(deps) {
36400
37194
  }
36401
37195
  }
36402
37196
  }
36403
- return { plugins, sensitiveFieldsByPluginId, boot };
37197
+ async function cleanup() {
37198
+ const dirs = [...generatedBundleDirs];
37199
+ const cleanupResults = await Promise.allSettled(dirs.map((dir) => removeBundleDir(dir)));
37200
+ for (const [index, result] of cleanupResults.entries()) {
37201
+ const dir = dirs[index];
37202
+ if (result.status === "fulfilled") {
37203
+ generatedBundleDirs.delete(dir);
37204
+ continue;
37205
+ }
37206
+ if (result.status === "rejected") {
37207
+ console.warn(`[fleet-console] Plugin bundle cache cleanup failed: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`);
37208
+ }
37209
+ }
37210
+ }
37211
+ return { plugins, sensitiveFieldsByPluginId, boot, cleanup };
36404
37212
  async function bootPluginRoutes(plugin) {
36405
37213
  const mod = await importModule(plugin.routesEntry);
36406
37214
  const register = resolveRegister(mod);
@@ -36434,7 +37242,7 @@ function filterDiscoveredPlugins(plugins) {
36434
37242
  }
36435
37243
  return accepted;
36436
37244
  }
36437
- async function importPluginModule(entry) {
37245
+ async function importPluginModule(entry, bundleCacheDir, generatedBundleDirs) {
36438
37246
  if (entry.endsWith(".ts")) {
36439
37247
  const { build } = await import('esbuild');
36440
37248
  const output = await build({
@@ -36445,49 +37253,101 @@ async function importPluginModule(entry) {
36445
37253
  target: "node20",
36446
37254
  write: false,
36447
37255
  sourcemap: false,
36448
- external: PLUGIN_DEV_EXTERNALS
37256
+ external: PLUGIN_DEV_EXTERNALS,
37257
+ nodePaths: [resolveConsolePackageNodeModules()].filter((value) => value !== null)
36449
37258
  });
36450
37259
  const bundled = output.outputFiles[0]?.text;
36451
37260
  if (!bundled) throw new Error("plugin_bundle_failed");
36452
- const bundledPath = await writeTemporaryPluginBundle(entry, bundled);
37261
+ const bundledPath = await writeTemporaryPluginBundle(entry, bundled, bundleCacheDir, generatedBundleDirs);
36453
37262
  return await import(pathToFileURL(bundledPath).href);
36454
37263
  }
36455
37264
  return await import(pathToFileURL(entry).href);
36456
37265
  }
36457
- async function writeTemporaryPluginBundle(entry, source) {
36458
- const cacheRoot = path25__default.join(resolvePluginBundleNodeModules(entry), ".cache");
37266
+ async function writeTemporaryPluginBundle(entry, source, bundleCacheDir, generatedBundleDirs) {
37267
+ const cacheRoot = bundleCacheDir ?? path27__default.join(resolvePluginBundleNodeModules(entry), ".cache");
36459
37268
  await mkdir(cacheRoot, { recursive: true });
36460
- const dir = await mkdtemp(path25__default.join(cacheRoot, "fleet-console-plugin-"));
36461
- const file2 = path25__default.join(dir, "routes.mjs");
37269
+ const dir = await mkdtemp(path27__default.join(cacheRoot, `${PLUGIN_BUNDLE_DIR_PREFIX}${process.pid}-`));
37270
+ if (bundleCacheDir) {
37271
+ generatedBundleDirs.add(dir);
37272
+ await writePluginBundleOwner(dir, process.pid);
37273
+ }
37274
+ const file2 = path27__default.join(dir, "routes.mjs");
36462
37275
  await writeFile(file2, source, "utf8");
36463
37276
  return file2;
36464
37277
  }
37278
+ async function removeStalePluginBundleDirs(bundleCacheDir, isProcessAlive2, removeBundleDir) {
37279
+ if (!bundleCacheDir) return;
37280
+ try {
37281
+ await mkdir(bundleCacheDir, { recursive: true });
37282
+ const entries = await readdir(bundleCacheDir, { withFileTypes: true });
37283
+ for (const entry of entries) {
37284
+ if (!entry.name.startsWith(PLUGIN_BUNDLE_DIR_PREFIX)) continue;
37285
+ const entryPath = path27__default.join(bundleCacheDir, entry.name);
37286
+ const ownerPid = await readPluginBundleOwnerPid(entryPath, entry.name);
37287
+ if (ownerPid !== null && isProcessAlive2(ownerPid)) continue;
37288
+ try {
37289
+ await removeBundleDir(entryPath);
37290
+ } catch (error51) {
37291
+ console.warn(`[fleet-console] Plugin bundle cache startup cleanup failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
37292
+ }
37293
+ }
37294
+ } catch (error51) {
37295
+ console.warn(`[fleet-console] Plugin bundle cache startup cleanup failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
37296
+ }
37297
+ }
37298
+ async function removePluginBundleDir(dir) {
37299
+ await rm(dir, { recursive: true, force: true });
37300
+ }
37301
+ async function writePluginBundleOwner(dir, pid) {
37302
+ await writeFile(path27__default.join(dir, PLUGIN_BUNDLE_OWNER_FILE), JSON.stringify({ pid }), "utf8");
37303
+ }
37304
+ async function readPluginBundleOwnerPid(dir, name) {
37305
+ try {
37306
+ const parsed = JSON.parse(await readFile(path27__default.join(dir, PLUGIN_BUNDLE_OWNER_FILE), "utf8"));
37307
+ if (isPluginBundleOwnerPid(parsed.pid)) return parsed.pid;
37308
+ } catch {
37309
+ }
37310
+ const pid = Number(PLUGIN_BUNDLE_PID_PATTERN.exec(name)?.[1]);
37311
+ return isPluginBundleOwnerPid(pid) ? pid : null;
37312
+ }
37313
+ function isPluginBundleOwnerAlive(pid) {
37314
+ if (!isPluginBundleOwnerPid(pid)) return false;
37315
+ try {
37316
+ process.kill(pid, 0);
37317
+ return true;
37318
+ } catch (error51) {
37319
+ return Boolean(error51 && typeof error51 === "object" && "code" in error51 && error51.code === "EPERM");
37320
+ }
37321
+ }
37322
+ function isPluginBundleOwnerPid(value) {
37323
+ return typeof value === "number" && Number.isInteger(value) && value > 0 && value <= MAX_PLUGIN_BUNDLE_OWNER_PID;
37324
+ }
36465
37325
  function resolvePluginBundleNodeModules(entry) {
36466
37326
  const packageNodeModules = resolveConsolePackageNodeModules();
36467
- let dir = path25__default.dirname(entry);
37327
+ let dir = path27__default.dirname(entry);
36468
37328
  let fallback = packageNodeModules;
36469
37329
  while (true) {
36470
- const workspaceConsoleNodeModules = path25__default.join(dir, "runtime", "fleet-console", "node_modules");
37330
+ const workspaceConsoleNodeModules = path27__default.join(dir, "runtime", "fleet-console", "node_modules");
36471
37331
  if (hasFleetConsoleSdk(workspaceConsoleNodeModules)) return workspaceConsoleNodeModules;
36472
- const candidate = path25__default.join(dir, "node_modules");
37332
+ const candidate = path27__default.join(dir, "node_modules");
36473
37333
  if (hasFleetConsoleSdk(candidate)) return candidate;
36474
37334
  if (fallback === null && existsSync(candidate)) fallback = candidate;
36475
- const parent = path25__default.dirname(dir);
36476
- if (parent === dir) return fallback ?? path25__default.join(path25__default.dirname(entry), "node_modules");
37335
+ const parent = path27__default.dirname(dir);
37336
+ if (parent === dir) return fallback ?? path27__default.join(path27__default.dirname(entry), "node_modules");
36477
37337
  dir = parent;
36478
37338
  }
36479
37339
  }
36480
37340
  function resolveConsolePackageNodeModules() {
36481
37341
  for (const candidate of [
36482
- path25__default.resolve(__dirname$1, "..", "node_modules"),
36483
- path25__default.resolve(__dirname$1, "..", "..", "..", "node_modules")
37342
+ path27__default.resolve(__dirname$1, "..", "node_modules"),
37343
+ path27__default.resolve(__dirname$1, "..", "..", "..", "node_modules")
36484
37344
  ]) {
36485
37345
  if (hasFleetConsoleSdk(candidate)) return candidate;
36486
37346
  }
36487
37347
  return null;
36488
37348
  }
36489
37349
  function hasFleetConsoleSdk(nodeModules) {
36490
- return existsSync(path25__default.join(nodeModules, "@fleet-console", "sdk"));
37350
+ return existsSync(path27__default.join(nodeModules, "@fleet-console", "sdk"));
36491
37351
  }
36492
37352
  function resolveRegister(mod) {
36493
37353
  if (mod.register) return mod.register;
@@ -36533,6 +37393,8 @@ function normalizePrefix(prefix) {
36533
37393
  var RELEASE_NOTE_HEADINGS = ["Added", "Changed", "Fixed", "Removed", "Breaking Changes"];
36534
37394
  var VERSION_HEADER_PATTERN = /^## \[([^\]]+)\](?: - ([0-9]{4}-[0-9]{2}-[0-9]{2}))?$/;
36535
37395
  var SECTION_HEADER_PATTERN = /^### (Added|Changed|Fixed|Removed|Breaking Changes)$/;
37396
+ var PRODUCT_HEADER_PATTERN = /^### fleet-(cli|console|desktop|plugin|core)$/;
37397
+ var PRODUCT_SECTION_HEADER_PATTERN = /^#### (Added|Changed|Fixed|Removed|Breaking Changes)$/;
36536
37398
  var BULLET_PATTERN = /^- (.+)$/;
36537
37399
  var PACKAGE_TAG_PATTERN = /^\[([^\]]+)\]/;
36538
37400
  function parseConsoleReleaseNotes(changelog) {
@@ -36553,24 +37415,55 @@ function parseConsoleReleaseNotes(changelog) {
36553
37415
  return notes;
36554
37416
  }
36555
37417
  function collectSections(lines) {
36556
- const sections = [];
37418
+ const legacySections = /* @__PURE__ */ new Map();
37419
+ const productSections = /* @__PURE__ */ new Map();
37420
+ const legacyHeadings = /* @__PURE__ */ new Set();
36557
37421
  let current = null;
37422
+ let isProductContext = false;
36558
37423
  for (const line of lines) {
36559
37424
  const sectionMatch = SECTION_HEADER_PATTERN.exec(line);
36560
37425
  if (sectionMatch !== null) {
36561
- current = { heading: sectionMatch[1], items: [] };
36562
- sections.push(current);
37426
+ const heading = sectionMatch[1];
37427
+ isProductContext = false;
37428
+ current = legacyHeadings.has(heading) ? null : getSection(legacySections, heading);
37429
+ legacyHeadings.add(heading);
37430
+ continue;
37431
+ }
37432
+ if (PRODUCT_HEADER_PATTERN.test(line)) {
37433
+ isProductContext = true;
37434
+ current = null;
36563
37435
  continue;
36564
37436
  }
36565
- if (current === null) continue;
36566
37437
  if (line.startsWith("### ")) {
37438
+ isProductContext = false;
37439
+ current = null;
37440
+ continue;
37441
+ }
37442
+ const productSectionMatch = PRODUCT_SECTION_HEADER_PATTERN.exec(line);
37443
+ if (productSectionMatch !== null) {
37444
+ current = isProductContext ? getSection(productSections, productSectionMatch[1]) : null;
37445
+ continue;
37446
+ }
37447
+ if (current === null) continue;
37448
+ if (line.startsWith("### ") || line.startsWith("#### ")) {
36567
37449
  current = null;
36568
37450
  continue;
36569
37451
  }
36570
37452
  const bulletMatch = BULLET_PATTERN.exec(line);
36571
37453
  if (bulletMatch !== null) current.items.push(parseReleaseNoteItem(bulletMatch[1] ?? ""));
36572
37454
  }
36573
- return RELEASE_NOTE_HEADINGS.map((heading) => sections.find((section2) => section2.heading === heading)).filter((section2) => Boolean(section2 && section2.items.length > 0));
37455
+ return RELEASE_NOTE_HEADINGS.map((heading) => combineSections(heading, legacySections.get(heading), productSections.get(heading))).filter((section2) => Boolean(section2 && section2.items.length > 0));
37456
+ }
37457
+ function combineSections(heading, legacySection, productSection) {
37458
+ if (legacySection === void 0 && productSection === void 0) return void 0;
37459
+ return { heading, items: [...legacySection?.items ?? [], ...productSection?.items ?? []] };
37460
+ }
37461
+ function getSection(sections, heading) {
37462
+ const existing = sections.get(heading);
37463
+ if (existing !== void 0) return existing;
37464
+ const section2 = { heading, items: [] };
37465
+ sections.set(heading, section2);
37466
+ return section2;
36574
37467
  }
36575
37468
  function parseReleaseNoteItem(rawText) {
36576
37469
  const packageTags = [];
@@ -36596,10 +37489,11 @@ var ConsoleReleaseNotesUnavailableError = class extends Error {
36596
37489
 
36597
37490
  // core/host/release-notes/service.ts
36598
37491
  var RAW_CHANGELOG_URL = "https://raw.githubusercontent.com/sbluemin/fleet-harness/main/CHANGELOG.md";
37492
+ var RAW_KOREAN_CHANGELOG_URL = "https://raw.githubusercontent.com/sbluemin/fleet-harness/main/CHANGELOG.ko.md";
36599
37493
  var SOURCE_REF = "main";
36600
37494
  var FETCH_TIMEOUT_MS = 3e3;
36601
37495
  var MAX_CHANGELOG_BYTES = 1024 * 1024;
36602
- var SUCCESS_TTL_MS = 60 * 60 * 1e3;
37496
+ var SUCCESS_TTL_MS2 = 60 * 60 * 1e3;
36603
37497
  var NEGATIVE_TTL_MS = 30 * 1e3;
36604
37498
  function createConsoleReleaseNotesService(deps = {}) {
36605
37499
  const fetchImpl = deps.fetchImpl ?? fetch;
@@ -36610,30 +37504,46 @@ function createConsoleReleaseNotesService(deps = {}) {
36610
37504
  let lastFailureAt = 0;
36611
37505
  let inFlight = null;
36612
37506
  let forceInFlight = null;
37507
+ let koreanLastSuccess = null;
37508
+ let koreanLastFailureAt = 0;
37509
+ let koreanInFlight = null;
37510
+ let koreanForceInFlight = null;
37511
+ let koreanRequestGeneration = 0;
36613
37512
  async function refresh(options = {}) {
37513
+ if (options.locale === "ko") return await refreshKoreanOverlay(options);
37514
+ return await refreshEnglish(options);
37515
+ }
37516
+ async function refreshEnglish(options) {
36614
37517
  const currentTime = now();
36615
37518
  if (options.force) {
36616
- if (forceInFlight) return forceInFlight;
37519
+ if (forceInFlight) return await forceInFlight;
36617
37520
  const pending2 = runFetch().finally(() => {
36618
37521
  if (forceInFlight === pending2) forceInFlight = null;
36619
37522
  });
36620
37523
  forceInFlight = pending2;
36621
- return pending2;
37524
+ return await pending2;
36622
37525
  }
36623
- if (lastSuccess && currentTime - lastSuccess.fetchedAt < SUCCESS_TTL_MS) return lastSuccess;
37526
+ if (lastSuccess && currentTime - lastSuccess.fetchedAt < SUCCESS_TTL_MS2) return lastSuccess;
36624
37527
  if (lastFailureAt > 0 && currentTime - lastFailureAt < NEGATIVE_TTL_MS) {
36625
37528
  if (lastSuccess) return { ...lastSuccess, stale: true };
36626
37529
  throw new ConsoleReleaseNotesUnavailableError("negative_cache");
36627
37530
  }
36628
- if (inFlight) return inFlight;
37531
+ if (inFlight) return await inFlight;
36629
37532
  const pending = runFetch().finally(() => {
36630
37533
  if (inFlight === pending) inFlight = null;
36631
37534
  });
36632
37535
  inFlight = pending;
36633
- return pending;
37536
+ return await pending;
37537
+ }
37538
+ async function refreshKoreanOverlay(options) {
37539
+ const english = refreshEnglish(options);
37540
+ const korean = refreshKorean(options).catch(() => null);
37541
+ const canonical = await english;
37542
+ const localized = await korean;
37543
+ return mergeKoreanOverlay(canonical, localized?.notes ?? null);
36634
37544
  }
36635
37545
  function runFetch() {
36636
- return fetchReleaseNotes().then((result) => {
37546
+ return fetchEnglishReleaseNotes().then((result) => {
36637
37547
  lastSuccess = result;
36638
37548
  lastFailureAt = 0;
36639
37549
  return result;
@@ -36644,29 +37554,93 @@ function createConsoleReleaseNotesService(deps = {}) {
36644
37554
  throw new ConsoleReleaseNotesUnavailableError("cold_unavailable");
36645
37555
  });
36646
37556
  }
36647
- async function fetchReleaseNotes() {
37557
+ async function fetchEnglishReleaseNotes() {
37558
+ return {
37559
+ notes: await fetchAndParse(RAW_CHANGELOG_URL),
37560
+ sourceRef: SOURCE_REF,
37561
+ fetchedAt: now(),
37562
+ stale: false
37563
+ };
37564
+ }
37565
+ async function refreshKorean(options) {
37566
+ const currentTime = now();
37567
+ if (options.force) {
37568
+ if (koreanForceInFlight) return await koreanForceInFlight;
37569
+ const pending2 = runKoreanFetch(++koreanRequestGeneration).finally(() => {
37570
+ if (koreanForceInFlight === pending2) koreanForceInFlight = null;
37571
+ });
37572
+ koreanForceInFlight = pending2;
37573
+ return await pending2;
37574
+ }
37575
+ if (koreanLastSuccess && currentTime - koreanLastSuccess.fetchedAt < SUCCESS_TTL_MS2) return koreanLastSuccess;
37576
+ if (koreanLastFailureAt > 0 && currentTime - koreanLastFailureAt < NEGATIVE_TTL_MS) return null;
37577
+ if (koreanInFlight) return await koreanInFlight;
37578
+ const pending = runKoreanFetch(++koreanRequestGeneration).finally(() => {
37579
+ if (koreanInFlight === pending) koreanInFlight = null;
37580
+ });
37581
+ koreanInFlight = pending;
37582
+ return await pending;
37583
+ }
37584
+ function runKoreanFetch(requestGeneration) {
37585
+ return fetchAndParse(RAW_KOREAN_CHANGELOG_URL).then((notes) => {
37586
+ const result = { notes, fetchedAt: now() };
37587
+ if (requestGeneration === koreanRequestGeneration) {
37588
+ koreanLastSuccess = result;
37589
+ koreanLastFailureAt = 0;
37590
+ }
37591
+ return result;
37592
+ }).catch(() => {
37593
+ if (requestGeneration === koreanRequestGeneration) koreanLastFailureAt = now();
37594
+ return null;
37595
+ });
37596
+ }
37597
+ async function fetchAndParse(url2) {
36648
37598
  const controller = new AbortController();
36649
37599
  const timer = setTimer(() => controller.abort(), FETCH_TIMEOUT_MS);
36650
37600
  timer.unref?.();
36651
37601
  try {
36652
- const response = await fetchImpl(RAW_CHANGELOG_URL, {
37602
+ const response = await fetchImpl(url2, {
36653
37603
  headers: { Accept: "text/plain; charset=utf-8" },
36654
37604
  signal: controller.signal
36655
37605
  });
36656
37606
  if (!response.ok) throw new ConsoleReleaseNotesUnavailableError("cold_unavailable");
36657
37607
  const text = await readTextWithByteLimit(response, controller);
36658
- return {
36659
- notes: parseConsoleReleaseNotes(text),
36660
- sourceRef: SOURCE_REF,
36661
- fetchedAt: now(),
36662
- stale: false
36663
- };
37608
+ return parseConsoleReleaseNotes(text).map((note) => ({ ...note, localizationFallback: false }));
36664
37609
  } finally {
36665
37610
  clearTimer(timer);
36666
37611
  }
36667
37612
  }
36668
37613
  return { refresh };
36669
37614
  }
37615
+ function mergeKoreanOverlay(canonical, koreanNotes) {
37616
+ if (koreanNotes === null) return { ...canonical, notes: canonical.notes.map(withFallback) };
37617
+ const occurrences = /* @__PURE__ */ new Map();
37618
+ for (const note of koreanNotes) {
37619
+ const queue = occurrences.get(note.version) ?? [];
37620
+ queue.push(note);
37621
+ occurrences.set(note.version, queue);
37622
+ }
37623
+ return {
37624
+ ...canonical,
37625
+ notes: canonical.notes.map((english) => {
37626
+ const korean = occurrences.get(english.version)?.shift();
37627
+ return korean && hasMatchingStructure(english, korean) ? { ...english, sections: korean.sections, localizationFallback: false } : withFallback(english);
37628
+ })
37629
+ };
37630
+ }
37631
+ function withFallback(note) {
37632
+ return { ...note, localizationFallback: true };
37633
+ }
37634
+ function hasMatchingStructure(english, korean) {
37635
+ return english.date === korean.date && english.sections.length === korean.sections.length && english.sections.every((section2, sectionIndex) => {
37636
+ const localizedSection = korean.sections[sectionIndex];
37637
+ return localizedSection?.heading === section2.heading && localizedSection.items.length === section2.items.length && section2.items.every((item, itemIndex) => sameTags(item.packageTags, localizedSection.items[itemIndex]?.packageTags));
37638
+ });
37639
+ }
37640
+ function sameTags(left, right) {
37641
+ if (right === void 0) return false;
37642
+ return left.length === right.length && left.every((tag, index) => tag === right[index]);
37643
+ }
36670
37644
  async function readTextWithByteLimit(response, controller) {
36671
37645
  const body = response.body;
36672
37646
  if (body === null) return await response.text();
@@ -36763,7 +37737,7 @@ var MIME_TYPES = {
36763
37737
  ".woff2": "font/woff2"
36764
37738
  };
36765
37739
  function createStaticConsoleHandler(packageRoot) {
36766
- const consoleRoot = path25__default.join(packageRoot, "dist", "client");
37740
+ const consoleRoot = path27__default.join(packageRoot, "dist", "client");
36767
37741
  return (req, res, pathname) => tryServeStaticConsole(req, res, pathname, consoleRoot);
36768
37742
  }
36769
37743
  function tryServeStaticConsole(req, res, pathname, consoleRoot) {
@@ -36779,15 +37753,15 @@ function tryServeStaticConsole(req, res, pathname, consoleRoot) {
36779
37753
  res.end(JSON.stringify({ error: "Not found" }));
36780
37754
  return true;
36781
37755
  }
36782
- const absolutePath = path25__default.resolve(consoleRoot, relativePath2);
37756
+ const absolutePath = path27__default.resolve(consoleRoot, relativePath2);
36783
37757
  if (!absolutePath.startsWith(consoleRoot)) {
36784
37758
  res.writeHead(404, withSecurityHeaders2({ "Content-Type": "application/json" }));
36785
37759
  res.end(JSON.stringify({ error: "Not found" }));
36786
37760
  return true;
36787
37761
  }
36788
37762
  try {
36789
- const data = fs11__default.readFileSync(absolutePath);
36790
- const contentType = MIME_TYPES[path25__default.extname(absolutePath)] ?? "application/octet-stream";
37763
+ const data = fs14__default.readFileSync(absolutePath);
37764
+ const contentType = MIME_TYPES[path27__default.extname(absolutePath)] ?? "application/octet-stream";
36791
37765
  res.writeHead(200, withSecurityHeaders2({ "Content-Type": contentType }));
36792
37766
  if (req.method === "HEAD") {
36793
37767
  res.end();
@@ -36807,7 +37781,7 @@ function tryServeStaticConsole(req, res, pathname, consoleRoot) {
36807
37781
  }
36808
37782
  function serveFallbackIndex(req, res, consoleRoot) {
36809
37783
  try {
36810
- const data = fs11__default.readFileSync(path25__default.join(consoleRoot, "index.html"));
37784
+ const data = fs14__default.readFileSync(path27__default.join(consoleRoot, "index.html"));
36811
37785
  res.writeHead(200, withSecurityHeaders2({ "Content-Type": MIME_TYPES[".html"] }));
36812
37786
  if (req.method === "HEAD") {
36813
37787
  res.end();
@@ -36833,7 +37807,7 @@ function resolveConsolePath(pathname) {
36833
37807
  }
36834
37808
  if (withoutPrefix === "") return "index.html";
36835
37809
  if (withoutPrefix.includes("\0")) return null;
36836
- if (!path25__default.extname(withoutPrefix)) return "index.html";
37810
+ if (!path27__default.extname(withoutPrefix)) return "index.html";
36837
37811
  return withoutPrefix;
36838
37812
  }
36839
37813
  var TheaterFolderListError = class extends Error {
@@ -36844,12 +37818,12 @@ var TheaterFolderListError = class extends Error {
36844
37818
  this.code = code;
36845
37819
  }
36846
37820
  };
36847
- var DIRECTORY_ENTRY_CAP = 500;
37821
+ var DIRECTORY_ENTRY_CAP2 = 500;
36848
37822
  var WINDOWS_DRIVE_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
36849
37823
  async function listTheaterFolders(requestedPath, deps = {}) {
36850
37824
  const platform = deps.platform ?? process.platform;
36851
- const stat5 = deps.stat ?? fs11__default.promises.stat;
36852
- const opendir = deps.opendir ?? fs11__default.promises.opendir;
37825
+ const stat5 = deps.stat ?? fs14__default.promises.stat;
37826
+ const opendir = deps.opendir ?? fs14__default.promises.opendir;
36853
37827
  const targetPath = normalizeListPath(requestedPath, platform, deps);
36854
37828
  const roots = await listRoots(platform, stat5);
36855
37829
  const targetStat = await statDirectory(targetPath, stat5);
@@ -36867,14 +37841,14 @@ async function listTheaterFolders(requestedPath, deps = {}) {
36867
37841
  }
36868
37842
  function normalizeFolderBrowserPath(value, platform = process.platform) {
36869
37843
  if (typeof value !== "string" || value.length === 0 || value.includes("\0")) throw new TheaterFolderListError("invalid_path");
36870
- if (isWindowsAmbiguousPath(value, platform) || !path25__default.isAbsolute(value)) throw new TheaterFolderListError("invalid_path");
36871
- return path25__default.resolve(value);
37844
+ if (isWindowsAmbiguousPath(value, platform) || !path27__default.isAbsolute(value)) throw new TheaterFolderListError("invalid_path");
37845
+ return path27__default.resolve(value);
36872
37846
  }
36873
37847
  function normalizeListPath(requestedPath, platform, deps) {
36874
37848
  if (requestedPath === null || requestedPath === void 0) {
36875
37849
  const home = deps.homedir?.() ?? os4__default.homedir();
36876
37850
  const start = home || deps.cwd?.() || process.cwd();
36877
- return path25__default.resolve(start);
37851
+ return path27__default.resolve(start);
36878
37852
  }
36879
37853
  return normalizeFolderBrowserPath(requestedPath, platform);
36880
37854
  }
@@ -36894,7 +37868,7 @@ async function statDirectory(targetPath, stat5) {
36894
37868
  try {
36895
37869
  return await stat5(targetPath);
36896
37870
  } catch (error51) {
36897
- throw mapFsError(error51);
37871
+ throw mapFsError2(error51);
36898
37872
  }
36899
37873
  }
36900
37874
  async function collectDirectoryEntries(targetPath, opendir, stat5, entries) {
@@ -36904,14 +37878,14 @@ async function collectDirectoryEntries(targetPath, opendir, stat5, entries) {
36904
37878
  while (dirent !== null) {
36905
37879
  const entry = await toTheaterFolderEntry(targetPath, dirent, stat5);
36906
37880
  if (entry !== null) {
36907
- if (entries.length >= DIRECTORY_ENTRY_CAP) return true;
37881
+ if (entries.length >= DIRECTORY_ENTRY_CAP2) return true;
36908
37882
  entries.push(entry);
36909
37883
  }
36910
37884
  dirent = await directory.read();
36911
37885
  }
36912
37886
  return false;
36913
37887
  } catch (error51) {
36914
- throw mapFsError(error51);
37888
+ throw mapFsError2(error51);
36915
37889
  } finally {
36916
37890
  await directory.close();
36917
37891
  }
@@ -36920,12 +37894,12 @@ async function openDirectory(targetPath, opendir) {
36920
37894
  try {
36921
37895
  return await opendir(targetPath);
36922
37896
  } catch (error51) {
36923
- throw mapFsError(error51);
37897
+ throw mapFsError2(error51);
36924
37898
  }
36925
37899
  }
36926
37900
  async function toTheaterFolderEntry(targetPath, dirent, stat5) {
36927
37901
  if (!dirent.isDirectory() && !dirent.isSymbolicLink()) return null;
36928
- const entryPath = path25__default.join(targetPath, dirent.name);
37902
+ const entryPath = path27__default.join(targetPath, dirent.name);
36929
37903
  if (dirent.isDirectory()) return { name: dirent.name, path: entryPath, kind: "dir", accessible: true };
36930
37904
  const accessible = await statSymlinkDirectory(entryPath, stat5);
36931
37905
  if (accessible === null) return null;
@@ -36938,17 +37912,17 @@ async function statSymlinkDirectory(entryPath, stat5) {
36938
37912
  return false;
36939
37913
  }
36940
37914
  }
36941
- function mapFsError(error51) {
37915
+ function mapFsError2(error51) {
36942
37916
  const code = error51.code;
36943
37917
  if (code === "EACCES" || code === "EPERM") return new TheaterFolderListError("forbidden");
36944
37918
  if (code === "ENOENT" || code === "ENOTDIR") return new TheaterFolderListError("not_found");
36945
37919
  return new TheaterFolderListError("invalid_path");
36946
37920
  }
36947
37921
  function parentPath(targetPath, platform) {
36948
- const parsed = path25__default.parse(targetPath);
36949
- const resolved = path25__default.resolve(targetPath);
36950
- if (resolved === path25__default.resolve(parsed.root)) return null;
36951
- const parent = path25__default.dirname(resolved);
37922
+ const parsed = path27__default.parse(targetPath);
37923
+ const resolved = path27__default.resolve(targetPath);
37924
+ if (resolved === path27__default.resolve(parsed.root)) return null;
37925
+ const parent = path27__default.dirname(resolved);
36952
37926
  if (platform === "win32" && parent === resolved) return null;
36953
37927
  return parent;
36954
37928
  }
@@ -36960,7 +37934,7 @@ var GRANT_ID_BYTES = 24;
36960
37934
  var DEFAULT_GRANT_TTL_MS = 6e4;
36961
37935
  function createFolderGrantStore(deps = {}) {
36962
37936
  const randomId = deps.randomId ?? (() => crypto5.randomBytes(GRANT_ID_BYTES).toString("base64url"));
36963
- const statSync2 = deps.statSync ?? fs11__default.statSync;
37937
+ const statSync2 = deps.statSync ?? fs14__default.statSync;
36964
37938
  const ttlMs = deps.ttlMs ?? DEFAULT_GRANT_TTL_MS;
36965
37939
  const now = deps.now ?? Date.now;
36966
37940
  const grants = /* @__PURE__ */ new Map();
@@ -36986,10 +37960,10 @@ function createFolderGrantStore(deps = {}) {
36986
37960
  }
36987
37961
  return { issue: issue2, consume };
36988
37962
  }
36989
- function validateAbsoluteDirectory(cwd, statSync2 = fs11__default.statSync) {
37963
+ function validateAbsoluteDirectory(cwd, statSync2 = fs14__default.statSync) {
36990
37964
  if (typeof cwd !== "string" || cwd.length === 0 || cwd.includes("\0")) throw new Error("invalid_folder");
36991
- if (isWindowsAmbiguousPath2(cwd) || !path25__default.isAbsolute(cwd)) throw new Error("invalid_folder");
36992
- const normalized = path25__default.resolve(cwd);
37965
+ if (isWindowsAmbiguousPath2(cwd) || !path27__default.isAbsolute(cwd)) throw new Error("invalid_folder");
37966
+ const normalized = path27__default.resolve(cwd);
36993
37967
  if (!statSync2(normalized).isDirectory()) throw new Error("invalid_folder");
36994
37968
  return normalized;
36995
37969
  }
@@ -36997,11 +37971,178 @@ function isWindowsAmbiguousPath2(value) {
36997
37971
  if (process.platform !== "win32") return false;
36998
37972
  return /^[a-zA-Z]:(?![\\/])/.test(value) || /^[\\/](?![\\/])/.test(value);
36999
37973
  }
37974
+ var execFileAsync = promisify(execFile);
37975
+ var GIT_TIMEOUT_MS = 3e3;
37976
+ var GIT_MAX_BUFFER = 1e6;
37977
+ async function listTheaterWorktrees(theaterRoot, deps = {}) {
37978
+ const exec = deps.execFile ?? ((file2, args, options) => execFileAsync(file2, args, options));
37979
+ const realpath2 = deps.realpath ?? fs14__default.promises.realpath;
37980
+ let stdout;
37981
+ try {
37982
+ ({ stdout } = await exec("git", ["worktree", "list", "--porcelain"], { cwd: theaterRoot, env: { ...process.env, LC_ALL: "C" }, shell: false, timeout: GIT_TIMEOUT_MS, maxBuffer: GIT_MAX_BUFFER }));
37983
+ } catch (error51) {
37984
+ if (isNotGitError(error51)) return { isGitRepo: false, worktrees: [] };
37985
+ throw error51;
37986
+ }
37987
+ const realRoot = await realpath2(theaterRoot);
37988
+ const records = parseGitWorktreePorcelain(stdout);
37989
+ const worktrees = [];
37990
+ for (const record2 of records) {
37991
+ try {
37992
+ const real = await realpath2(record2.worktree);
37993
+ if (!isWithinRoot3(real, realRoot) || real === realRoot) continue;
37994
+ worktrees.push({ relPath: path27__default.relative(realRoot, real).split(path27__default.sep).join("/"), branch: record2.branch, isCurrent: real === realRoot });
37995
+ } catch {
37996
+ }
37997
+ }
37998
+ return { isGitRepo: true, worktrees };
37999
+ }
38000
+ function parseGitWorktreePorcelain(value) {
38001
+ const records = [];
38002
+ for (const block of value.split("\n\n")) {
38003
+ const fields = block.split("\n");
38004
+ const worktree = fields.find((field) => field.startsWith("worktree "))?.slice("worktree ".length);
38005
+ if (!worktree) continue;
38006
+ const branchRef = fields.find((field) => field.startsWith("branch "))?.slice("branch ".length) ?? null;
38007
+ records.push({ worktree, branch: branchRef?.replace(/^refs\/heads\//, "") ?? null, detached: fields.includes("detached") });
38008
+ }
38009
+ return records;
38010
+ }
38011
+ function isWithinRoot3(candidate, root) {
38012
+ const normalizedRoot = root.endsWith(path27__default.sep) ? root : `${root}${path27__default.sep}`;
38013
+ return candidate === root || candidate.startsWith(normalizedRoot);
38014
+ }
38015
+ function isNotGitError(error51) {
38016
+ const value = error51;
38017
+ const text = `${typeof value.stderr === "string" ? value.stderr : ""}
38018
+ ${typeof value.message === "string" ? value.message : ""}`;
38019
+ return /not a git repository/i.test(text);
38020
+ }
38021
+
38022
+ // core/host/theater-path-context-routes.ts
38023
+ function createTheaterPathContextRouter(deps) {
38024
+ return async function handleTheaterPathContextRoute({ req, res, pathname }) {
38025
+ const match = pathname.match(/^\/api\/v1\/theaters\/([^/]+)\/path-context(?:\/(worktrees|directories))?$/u);
38026
+ if (!match) return false;
38027
+ const theater = deps.getTheater(decodeURIComponent(match[1] ?? ""));
38028
+ if (!theater) {
38029
+ deps.writeJson(res, 400, { error: "invalid_theater" });
38030
+ return true;
38031
+ }
38032
+ const action = match[2] ?? "context";
38033
+ if (action === "context") await handleContext(req, res, theater, deps);
38034
+ else if (action === "worktrees") await handleWorktrees(req, res, theater, deps);
38035
+ else await handleDirectories(req, res, theater, deps);
38036
+ return true;
38037
+ };
38038
+ }
38039
+ async function handleContext(req, res, theater, deps) {
38040
+ if (req.method === "GET") {
38041
+ await writeContext(res, theater, deps);
38042
+ return;
38043
+ }
38044
+ if (req.method !== "PUT") {
38045
+ deps.writeJson(res, 405, { error: "method_not_allowed" });
38046
+ return;
38047
+ }
38048
+ if (!deps.isAuthorized(req)) {
38049
+ deps.writeJson(res, 401, { error: "unauthorized" });
38050
+ return;
38051
+ }
38052
+ const body = await deps.readJsonBody(req);
38053
+ if (!isPlainObject3(body) || !(body.relPath === null || typeof body.relPath === "string") || Object.keys(body).length !== 1) {
38054
+ deps.writeJson(res, 400, { error: "invalid_request" });
38055
+ return;
38056
+ }
38057
+ try {
38058
+ const resolved = await resolveTheaterPathContext(theater.realpath, body.relPath);
38059
+ const updated = deps.setPathContext(theater.id, resolved.relPath);
38060
+ if (!updated) {
38061
+ deps.writeJson(res, 404, { error: "theater_not_found" });
38062
+ return;
38063
+ }
38064
+ deps.persist();
38065
+ deps.writeJson(res, 200, await toContextDto(updated, deps));
38066
+ } catch (error51) {
38067
+ writePathError2(res, deps, error51);
38068
+ }
38069
+ }
38070
+ async function handleWorktrees(req, res, theater, deps) {
38071
+ if (req.method !== "GET") {
38072
+ deps.writeJson(res, 405, { error: "method_not_allowed" });
38073
+ return;
38074
+ }
38075
+ try {
38076
+ deps.writeJson(res, 200, await listTheaterWorktrees(theater.realpath));
38077
+ } catch {
38078
+ deps.writeJson(res, 500, { error: "worktree_discovery_failed" });
38079
+ }
38080
+ }
38081
+ async function handleDirectories(req, res, theater, deps) {
38082
+ if (req.method !== "POST") {
38083
+ deps.writeJson(res, 405, { error: "method_not_allowed" });
38084
+ return;
38085
+ }
38086
+ if (!deps.isAuthorized(req)) {
38087
+ deps.writeJson(res, 401, { error: "unauthorized" });
38088
+ return;
38089
+ }
38090
+ const body = await deps.readJsonBody(req);
38091
+ if (!isPlainObject3(body) || !(body.relativePath === null || typeof body.relativePath === "string") || Object.keys(body).length !== 1) {
38092
+ deps.writeJson(res, 400, { error: "invalid_request" });
38093
+ return;
38094
+ }
38095
+ try {
38096
+ deps.writeJson(res, 200, { directories: await listTheaterDirectories(theater.realpath, body.relativePath) });
38097
+ } catch (error51) {
38098
+ writePathError2(res, deps, error51);
38099
+ }
38100
+ }
38101
+ async function writeContext(res, theater, deps) {
38102
+ try {
38103
+ deps.writeJson(res, 200, await toContextDto(theater, deps));
38104
+ } catch (error51) {
38105
+ if (error51 instanceof TheaterPathContextError && theater.pathContext !== null) {
38106
+ const healed = deps.setPathContext(theater.id, null);
38107
+ if (healed) {
38108
+ deps.persist();
38109
+ try {
38110
+ deps.writeJson(res, 200, await toContextDto(healed, deps));
38111
+ return;
38112
+ } catch (rootError) {
38113
+ writePathError2(res, deps, rootError);
38114
+ return;
38115
+ }
38116
+ }
38117
+ }
38118
+ writePathError2(res, deps, error51);
38119
+ }
38120
+ }
38121
+ async function toContextDto(theater, _deps) {
38122
+ const resolved = await resolveTheaterPathContext(theater.realpath, theater.pathContext);
38123
+ let kind = resolved.relPath === null ? "root" : "directory";
38124
+ if (resolved.relPath !== null) {
38125
+ try {
38126
+ const worktrees = await listTheaterWorktrees(theater.realpath);
38127
+ if (worktrees.worktrees.some((worktree) => worktree.relPath === resolved.relPath)) kind = "worktree";
38128
+ } catch {
38129
+ }
38130
+ }
38131
+ return { kind, relPath: resolved.relPath, label: resolved.relPath === null ? theater.label : resolved.label };
38132
+ }
38133
+ function writePathError2(res, deps, error51) {
38134
+ if (!(error51 instanceof TheaterPathContextError)) throw error51;
38135
+ const status = error51.code === "not_found" ? 404 : error51.code === "forbidden" ? 403 : 400;
38136
+ deps.writeJson(res, status, { error: error51.code });
38137
+ }
38138
+ function isPlainObject3(value) {
38139
+ return typeof value === "object" && value !== null && !Array.isArray(value);
38140
+ }
37000
38141
  var TheaterRegistry = class {
37001
38142
  #items = /* @__PURE__ */ new Map();
37002
38143
  #mruId = null;
37003
38144
  async register(cwd) {
37004
- const resolved = path25__default.resolve(cwd);
38145
+ const resolved = path27__default.resolve(cwd);
37005
38146
  const real = await canonicalizeTheaterPath(resolved);
37006
38147
  const id = workspaceHash(real);
37007
38148
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -37015,7 +38156,8 @@ var TheaterRegistry = class {
37015
38156
  realpath: real,
37016
38157
  label: theaterLabel(resolved),
37017
38158
  registeredAt: existing?.registeredAt ?? now,
37018
- lastOpenedAt: now
38159
+ lastOpenedAt: now,
38160
+ pathContext: existing?.pathContext ?? null
37019
38161
  };
37020
38162
  this.#items.set(id, item);
37021
38163
  this.#mruId = id;
@@ -37055,6 +38197,13 @@ var TheaterRegistry = class {
37055
38197
  if (this.#mruId === id) this.#mruId = this.list()[0]?.id ?? null;
37056
38198
  return removed;
37057
38199
  }
38200
+ setPathContext(id, pathContext) {
38201
+ const current = this.#items.get(id);
38202
+ if (!current) return null;
38203
+ const next = { ...current, pathContext };
38204
+ this.#items.set(id, next);
38205
+ return next;
38206
+ }
37058
38207
  };
37059
38208
  var PACKAGE_NAMES = ["@dotobokuri/fleet-cli", "@dotobokuri/fleet-console"];
37060
38209
  var WORKER_FILE_PREFIX = "fleet-console-update-";
@@ -37066,7 +38215,7 @@ function createConsoleUpdateApplyService(deps = {}) {
37066
38215
  const env = deps.env ?? process4.env;
37067
38216
  const execPath = deps.execPath ?? process4.execPath;
37068
38217
  const makeDir = deps.makeDir ?? ((dirPath, options) => {
37069
- fs11__default.mkdirSync(dirPath, options);
38218
+ fs14__default.mkdirSync(dirPath, options);
37070
38219
  });
37071
38220
  const now = deps.now ?? Date.now;
37072
38221
  const processPid = deps.processPid ?? process4.pid;
@@ -37075,15 +38224,15 @@ function createConsoleUpdateApplyService(deps = {}) {
37075
38224
  const spawnWorker = deps.spawnWorker ?? defaultSpawnWorker;
37076
38225
  const tmpDir = deps.tmpDir ?? os4__default.tmpdir();
37077
38226
  const writeFile5 = deps.writeFile ?? ((filePath, content, options) => {
37078
- fs11__default.writeFileSync(filePath, content, { mode: options.mode });
38227
+ fs14__default.writeFileSync(filePath, content, { mode: options.mode });
37079
38228
  });
37080
38229
  async function start(request) {
37081
38230
  const packageManager = await preflightInstall(request.currentPackageRoot);
37082
38231
  const stamp = `${now()}-${processPid}`;
37083
- const workerPath = path25__default.join(tmpDir, `${WORKER_FILE_PREFIX}${stamp}${WORKER_FILE_SUFFIX}`);
38232
+ const workerPath = path27__default.join(tmpDir, `${WORKER_FILE_PREFIX}${stamp}${WORKER_FILE_SUFFIX}`);
37084
38233
  makeDir(request.dataDir, { recursive: true, mode: 448 });
37085
- const statusFile = path25__default.join(request.dataDir, `${WORKER_FILE_PREFIX}${stamp}${STATUS_FILE_SUFFIX}`);
37086
- const logFile = path25__default.join(request.dataDir, `${WORKER_FILE_PREFIX}${stamp}${LOG_FILE_SUFFIX}`);
38234
+ const statusFile = path27__default.join(request.dataDir, `${WORKER_FILE_PREFIX}${stamp}${STATUS_FILE_SUFFIX}`);
38235
+ const logFile = path27__default.join(request.dataDir, `${WORKER_FILE_PREFIX}${stamp}${LOG_FILE_SUFFIX}`);
37087
38236
  const script = emitConsoleUpdateWorkerScript({
37088
38237
  currentEndpoint: request.currentEndpoint,
37089
38238
  currentPackageRoot: request.currentPackageRoot,
@@ -37426,12 +38575,12 @@ function defaultSpawnWorker(execPath, args, options) {
37426
38575
  }
37427
38576
  function resolveDefaultServerModulePath() {
37428
38577
  const builtPath = fileURLToPath(new URL("../dist/cli.mjs", import.meta.url));
37429
- if (fs11__default.existsSync(builtPath)) return builtPath;
38578
+ if (fs14__default.existsSync(builtPath)) return builtPath;
37430
38579
  const sourcePath = fileURLToPath(import.meta.url);
37431
38580
  if (sourcePath.endsWith(".ts")) {
37432
38581
  return sourcePath.replace(/update-apply\.ts$/, "cli.ts");
37433
38582
  }
37434
- return path25__default.join(path25__default.dirname(sourcePath), "cli.mjs");
38583
+ return path27__default.join(path27__default.dirname(sourcePath), "cli.mjs");
37435
38584
  }
37436
38585
 
37437
38586
  // core/host/update-check.ts
@@ -37485,7 +38634,7 @@ function createConsoleUpdateCheckService(deps = {}) {
37485
38634
  };
37486
38635
  const resolveUpdateStatus = async () => {
37487
38636
  const release = readRelease();
37488
- if (release.channel === "local") {
38637
+ if (release.channel === "local" || release.channel === "desktop") {
37489
38638
  return NO_UPDATE_STATUS;
37490
38639
  }
37491
38640
  const latestVersion = await fetchLatest(FLEET_CONSOLE_PACKAGE_NAME);
@@ -37580,6 +38729,41 @@ var SERVER_API_CATALOG = [
37580
38729
  category: "Observer",
37581
38730
  gate: "origin-write"
37582
38731
  },
38732
+ {
38733
+ method: "GET",
38734
+ path: "/api/v1/theaters/:theaterId/path-context",
38735
+ summary: "Get the selected Theater path context.",
38736
+ category: "Observer",
38737
+ gate: "loopback"
38738
+ },
38739
+ {
38740
+ method: "PUT",
38741
+ path: "/api/v1/theaters/:theaterId/path-context",
38742
+ summary: "Save the selected Theater path context.",
38743
+ category: "Observer",
38744
+ gate: "origin-write"
38745
+ },
38746
+ {
38747
+ method: "GET",
38748
+ path: "/api/v1/theaters/:theaterId/path-context/worktrees",
38749
+ summary: "List contained Git worktrees for a Theater.",
38750
+ category: "Observer",
38751
+ gate: "loopback"
38752
+ },
38753
+ {
38754
+ method: "POST",
38755
+ path: "/api/v1/theaters/:theaterId/path-context/directories",
38756
+ summary: "List contained directories for a Theater path context.",
38757
+ category: "Observer",
38758
+ gate: "origin-write"
38759
+ },
38760
+ {
38761
+ method: "POST",
38762
+ path: "/api/v1/theaters/:theaterId/codex-workspace",
38763
+ summary: "Resolve the Codex workspace for a Theater path context.",
38764
+ category: "Observer",
38765
+ gate: "origin-write"
38766
+ },
37583
38767
  {
37584
38768
  method: "POST",
37585
38769
  path: "/plugins/terminal/shell/ticket",
@@ -37615,6 +38799,20 @@ var SERVER_API_CATALOG = [
37615
38799
  category: "Observer",
37616
38800
  gate: "origin-write"
37617
38801
  },
38802
+ {
38803
+ method: "POST",
38804
+ path: "/api/v1/plans/list",
38805
+ summary: "Theater\uC758 \uC2E4\uD589 \uACC4\uD68D \uBAA9\uB85D\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4.",
38806
+ category: "Observer",
38807
+ gate: "origin-write"
38808
+ },
38809
+ {
38810
+ method: "POST",
38811
+ path: "/api/v1/plans/read",
38812
+ summary: "\uC2E4\uD589 \uACC4\uD68D \uBB38\uC11C\uB97C \uC870\uD68C\uD569\uB2C8\uB2E4.",
38813
+ category: "Observer",
38814
+ gate: "origin-write"
38815
+ },
37618
38816
  {
37619
38817
  method: "POST",
37620
38818
  path: "/api/v1/updates/apply",
@@ -37634,6 +38832,7 @@ function createConsoleServer(deps = {}) {
37634
38832
  const host = deps.host ?? DEFAULT_HOST;
37635
38833
  const port = deps.port ?? DEFAULT_PORT;
37636
38834
  const release = deps.release ?? readFleetConsoleRelease();
38835
+ const desktop = readDesktopProtocolEnvironment();
37637
38836
  const tryServeStaticConsole2 = createStaticConsoleHandler(release.packageRoot);
37638
38837
  const version2 = deps.version ?? release.version;
37639
38838
  const channel = release.channel;
@@ -37641,7 +38840,7 @@ function createConsoleServer(deps = {}) {
37641
38840
  registerDefaultCarriers(carrierRegistry);
37642
38841
  const lock = createConsoleLock({ hostname: () => host });
37643
38842
  const releaseNotes = deps.releaseNotes ?? createConsoleReleaseNotesService();
37644
- const updateCheck = deps.updateCheck ?? createConsoleUpdateCheckService();
38843
+ const updateCheck = deps.updateCheck ?? createConsoleUpdateCheckService({ readRelease: () => release });
37645
38844
  const updateApply = deps.updateApply ?? createConsoleUpdateApplyService();
37646
38845
  const theaters = new TheaterRegistry();
37647
38846
  const operations = createOperationStore();
@@ -37737,8 +38936,8 @@ function createConsoleServer(deps = {}) {
37737
38936
  },
37738
38937
  paths: {
37739
38938
  capturesDir: durablePaths.capturesDir,
37740
- pluginDataDir: (pluginId) => path25__default.join(durablePaths.dir, "plugins", pluginId),
37741
- resolveTheaterPath: (theaterId) => theaters.get(theaterId)?.path ?? null,
38939
+ pluginDataDir: (pluginId) => path27__default.join(durablePaths.dir, "plugins", pluginId),
38940
+ resolveTheaterPath: (theaterId) => theaters.get(theaterId)?.realpath ?? null,
37742
38941
  canonicalizeTheaterPath: canonicalizeTheaterPathSync,
37743
38942
  workspaceHash
37744
38943
  },
@@ -37762,8 +38961,10 @@ function createConsoleServer(deps = {}) {
37762
38961
  }
37763
38962
  }
37764
38963
  };
38964
+ process.env.FLEET_CONSOLE_PACKAGE_ROOT = release.packageRoot;
37765
38965
  const pluginHost = createFleetPluginHost({
37766
38966
  ...resolveBuiltInPluginDiscoveryRoots(release.packageRoot),
38967
+ bundleCacheDir: path27__default.join(durablePaths.dir, "plugin-cache"),
37767
38968
  routes: routeRegistry,
37768
38969
  upgrades: upgradeRegistry,
37769
38970
  host: pluginHostCapabilities
@@ -37829,6 +39030,31 @@ function createConsoleServer(deps = {}) {
37829
39030
  readJsonBody,
37830
39031
  writeJson
37831
39032
  });
39033
+ const systemFontsRouter = createSystemFontsRouter({
39034
+ systemFonts: deps.systemFonts ?? createSystemFontsService(),
39035
+ writeJson
39036
+ });
39037
+ const plansRouter = createPlansRouter({
39038
+ isAuthorized: isTerminalAuthorized,
39039
+ readJsonBody,
39040
+ resolveTheaterPath: (theaterId) => theaters.get(theaterId)?.realpath ?? null,
39041
+ writeJson
39042
+ });
39043
+ const theaterPathContextRouter = createTheaterPathContextRouter({
39044
+ getTheater: (theaterId) => theaters.get(theaterId),
39045
+ isAuthorized: isTerminalAuthorized,
39046
+ persist: persistDurableState,
39047
+ readJsonBody,
39048
+ setPathContext: (theaterId, relPath) => theaters.setPathContext(theaterId, relPath),
39049
+ writeJson
39050
+ });
39051
+ const codexWorkspaceContextRouter = createCodexWorkspaceContextRouter({
39052
+ getTheater: (theaterId) => theaters.get(theaterId),
39053
+ isAuthorized: isTerminalAuthorized,
39054
+ readJsonBody,
39055
+ resolveWorkspace: (theaterId, theaterRoot, relPath) => codex.resolveWorkspaceForPath(theaterId, theaterRoot, relPath),
39056
+ writeJson
39057
+ });
37832
39058
  const operationsRouter = createOperationsRouter({
37833
39059
  store: operations,
37834
39060
  isAuthorized: isTerminalAuthorized,
@@ -37857,6 +39083,10 @@ function createConsoleServer(deps = {}) {
37857
39083
  }
37858
39084
  });
37859
39085
  routeRegistry.register("/api/v1/operations", operationsRouter);
39086
+ routeRegistry.register("/api/v1/theaters", async (context) => {
39087
+ if (await theaterPathContextRouter(context)) return true;
39088
+ return codexWorkspaceContextRouter(context);
39089
+ });
37860
39090
  routeRegistry.register("/api/v1/settings", async (ctx) => {
37861
39091
  const { req, res, pathname } = ctx;
37862
39092
  if (pathname === "/api/v1/settings/api-catalog") {
@@ -37865,6 +39095,7 @@ function createConsoleServer(deps = {}) {
37865
39095
  }
37866
39096
  if (await carrierSettingsRouter(ctx)) return true;
37867
39097
  if (await pluginSettingsRouter(ctx)) return true;
39098
+ if (await systemFontsRouter(ctx)) return true;
37868
39099
  return globalSettingsRouter(ctx);
37869
39100
  });
37870
39101
  routeRegistry.register("/plugin-runtime", handlePluginRuntimeRoute);
@@ -37905,6 +39136,10 @@ function createConsoleServer(deps = {}) {
37905
39136
  runAsyncHandler(handleTheaterFolderGrants(req, res), res);
37906
39137
  return;
37907
39138
  }
39139
+ if (pathname === "/api/v1/plans/list" || pathname === "/api/v1/plans/read") {
39140
+ runAsyncBooleanHandler(plansRouter({ req, res, pathname }), res, () => false);
39141
+ return;
39142
+ }
37908
39143
  const theaterItemMatch = pathname.match(/^\/api\/v1\/theaters\/([^/]+)$/);
37909
39144
  if (theaterItemMatch) {
37910
39145
  runAsyncHandler(handleObserverTheaterItem(req, res, decodeURIComponent(theaterItemMatch[1] ?? "")), res);
@@ -37969,6 +39204,8 @@ function createConsoleServer(deps = {}) {
37969
39204
  endpoint: payload.endpoint,
37970
39205
  startedAt: payload.startedAt,
37971
39206
  version: payload.version,
39207
+ ...channel === "desktop" ? { channel } : {},
39208
+ ...payload.owner ? { owner: payload.owner } : {},
37972
39209
  workspaceCount: operations.list().length
37973
39210
  };
37974
39211
  writeJson(res, 200, body);
@@ -37986,7 +39223,7 @@ function createConsoleServer(deps = {}) {
37986
39223
  return;
37987
39224
  }
37988
39225
  const body = await readJsonBody(req);
37989
- if (!isPlainObject2(body) || body.path !== void 0 && body.path !== null && typeof body.path !== "string") {
39226
+ if (!isPlainObject4(body) || body.path !== void 0 && body.path !== null && typeof body.path !== "string") {
37990
39227
  writeJson(res, 400, { error: "invalid_path" });
37991
39228
  return;
37992
39229
  }
@@ -38011,7 +39248,7 @@ function createConsoleServer(deps = {}) {
38011
39248
  return;
38012
39249
  }
38013
39250
  const body = await readJsonBody(req);
38014
- if (!isPlainObject2(body) || typeof body.path !== "string") {
39251
+ if (!isPlainObject4(body) || typeof body.path !== "string") {
38015
39252
  writeJson(res, 400, { error: "invalid_folder" });
38016
39253
  return;
38017
39254
  }
@@ -38035,7 +39272,7 @@ function createConsoleServer(deps = {}) {
38035
39272
  return;
38036
39273
  }
38037
39274
  const body = await readJsonBody(req);
38038
- if (!isPlainObject2(body) || typeof body.folderGrantId !== "string") {
39275
+ if (!isPlainObject4(body) || typeof body.folderGrantId !== "string") {
38039
39276
  writeJson(res, 400, { error: "invalid_folder_grant" });
38040
39277
  return;
38041
39278
  }
@@ -38047,7 +39284,7 @@ function createConsoleServer(deps = {}) {
38047
39284
  const theater = await theaters.register(cwd);
38048
39285
  let hasWiki = false;
38049
39286
  try {
38050
- await codex.registerWorkspace(cwd);
39287
+ await codex.registerWorkspace(theater.realpath, void 0, theater.id);
38051
39288
  hasWiki = true;
38052
39289
  } catch (error51) {
38053
39290
  if (!(error51 instanceof Error && error51.message === "knowledge_root_missing")) {
@@ -38067,7 +39304,7 @@ function createConsoleServer(deps = {}) {
38067
39304
  return;
38068
39305
  }
38069
39306
  theaters.remove(theaterId);
38070
- codex.unregisterWorkspace(theaterId);
39307
+ codex.unregisterTheaterWorkspaces(theaterId);
38071
39308
  for (const operation of operations.listByTheater(theaterId)) {
38072
39309
  pluginHostCapabilities.events.publish(OPERATION_DELETED_EVENT_CHANNEL, {
38073
39310
  operationId: operation.id,
@@ -38108,8 +39345,10 @@ function createConsoleServer(deps = {}) {
38108
39345
  return;
38109
39346
  }
38110
39347
  try {
38111
- const force = readUrl(req).searchParams.get("force") === "true";
38112
- writeJson(res, 200, await releaseNotes.refresh({ force }));
39348
+ const searchParams = readUrl(req).searchParams;
39349
+ const force = searchParams.get("force") === "true";
39350
+ const locale = searchParams.get("locale") === "ko" ? "ko" : "en";
39351
+ writeJson(res, 200, await releaseNotes.refresh({ force, locale }));
38113
39352
  } catch (error51) {
38114
39353
  if (error51 instanceof ConsoleReleaseNotesUnavailableError) {
38115
39354
  writeJson(res, 503, { error: "release_notes_unavailable" });
@@ -38132,10 +39371,14 @@ function createConsoleServer(deps = {}) {
38132
39371
  writeJson(res, 400, { error: "invalid_update_apply_body" });
38133
39372
  return;
38134
39373
  }
38135
- if (body !== null && (!isPlainObject2(body) || hasForbiddenUpdateApplyBodyKeys(body))) {
39374
+ if (body !== null && (!isPlainObject4(body) || hasForbiddenUpdateApplyBodyKeys(body))) {
38136
39375
  writeJson(res, 400, { error: "invalid_update_apply_body" });
38137
39376
  return;
38138
39377
  }
39378
+ if (channel === "desktop") {
39379
+ writeJson(res, 403, { error: "desktop_update_managed" });
39380
+ return;
39381
+ }
38139
39382
  if (channel === "local") {
38140
39383
  writeJson(res, 403, { error: "local_channel" });
38141
39384
  return;
@@ -38244,6 +39487,7 @@ function createConsoleServer(deps = {}) {
38244
39487
  console.warn(`[fleet-console] Plugin cleanup failed: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`);
38245
39488
  }
38246
39489
  }
39490
+ await pluginHost.cleanup();
38247
39491
  pluginCleanupCallbacks.clear();
38248
39492
  pluginEventListeners.clear();
38249
39493
  currentLock?.release();
@@ -38255,6 +39499,8 @@ function createConsoleServer(deps = {}) {
38255
39499
  theaters.restore(state.theaters);
38256
39500
  operations.replace(state.operations);
38257
39501
  operations.replaceGroups(state.groups ?? []);
39502
+ const healed = await healRestoredPathContexts();
39503
+ if (healed) persistDurableState();
38258
39504
  } catch (error51) {
38259
39505
  console.warn(`[fleet-console] Durable state restore skipped: ${error51 instanceof Error ? error51.message : String(error51)}`);
38260
39506
  state = emptyDurableConsoleState();
@@ -38264,11 +39510,28 @@ function createConsoleServer(deps = {}) {
38264
39510
  }
38265
39511
  await restoreCodexWorkspaces();
38266
39512
  }
39513
+ async function healRestoredPathContexts() {
39514
+ let healed = false;
39515
+ for (const theater of theaters.list()) {
39516
+ if (theater.pathContext === null) continue;
39517
+ try {
39518
+ const resolved = await resolveTheaterPathContext(theater.realpath, theater.pathContext);
39519
+ if (resolved.relPath !== theater.pathContext) {
39520
+ theaters.setPathContext(theater.id, resolved.relPath);
39521
+ healed = true;
39522
+ }
39523
+ } catch {
39524
+ theaters.setPathContext(theater.id, null);
39525
+ healed = true;
39526
+ }
39527
+ }
39528
+ return healed;
39529
+ }
38267
39530
  async function restoreCodexWorkspaces() {
38268
39531
  const ordered = [...theaters.list()].sort((left, right) => left.lastOpenedAt.localeCompare(right.lastOpenedAt));
38269
39532
  for (const theater of ordered) {
38270
39533
  try {
38271
- await codex.registerWorkspace(theater.realpath, theater.lastOpenedAt);
39534
+ await codex.registerWorkspace(theater.realpath, theater.lastOpenedAt, theater.id);
38272
39535
  } catch (error51) {
38273
39536
  if (!(error51 instanceof Error && error51.message === "knowledge_root_missing")) {
38274
39537
  console.warn(`[fleet-console] Codex workspace restore skipped for Theater ${theater.id}: ${error51 instanceof Error ? error51.message : String(error51)}`);
@@ -38339,7 +39602,7 @@ function createConsoleServer(deps = {}) {
38339
39602
  server = result.srv;
38340
39603
  loopbackServer = result.localLoopbackServer;
38341
39604
  portState = result.portState;
38342
- lockHandle = lock.writeLock({ dir: lockPaths.dir, lockFile: lockPaths.lockFile, pid: process.pid, port: result.actualPort, endpoint: result.endpoint, version: version2 });
39605
+ lockHandle = lock.writeLock({ dir: lockPaths.dir, lockFile: lockPaths.lockFile, pid: process.pid, port: result.actualPort, endpoint: result.endpoint, version: version2, ...desktop ? { owner: desktop.owner } : {} });
38343
39606
  activeLockFile = lockPaths.lockFile;
38344
39607
  activeEndpoint = result.endpoint;
38345
39608
  } catch (error51) {
@@ -38373,6 +39636,14 @@ function createConsoleServer(deps = {}) {
38373
39636
  allowFallback: false
38374
39637
  };
38375
39638
  }
39639
+ if (channel === "local") {
39640
+ return {
39641
+ port: DEFAULT_PORT,
39642
+ requestedPort: null,
39643
+ portMode: "dynamic",
39644
+ allowFallback: false
39645
+ };
39646
+ }
38376
39647
  const options = consoleSettingsStore.load().general ?? {};
38377
39648
  if (options.consolePortMode === "static" && isValidConsoleStaticPort3(options.consoleStaticPort)) {
38378
39649
  return {
@@ -38439,11 +39710,11 @@ function createConsoleServer(deps = {}) {
38439
39710
  return returnedServer;
38440
39711
  }
38441
39712
  function resolveBuiltInPluginDiscoveryRoots(packageRoot) {
38442
- const packageRootRepo = path25__default.resolve(packageRoot, "..", "..");
38443
- const sourceRoot = path25__default.join(packageRootRepo, "runtime", "fleet-plugins");
39713
+ const packageRootRepo = path27__default.resolve(packageRoot, "..", "..");
39714
+ const sourceRoot = path27__default.join(packageRootRepo, "runtime", "fleet-plugins");
38444
39715
  return {
38445
- ...fs11__default.existsSync(sourceRoot) ? { builtInSourceRoot: sourceRoot } : {},
38446
- builtInDistRoot: path25__default.join(packageRoot, "dist", "fleet-plugins")
39716
+ ...fs14__default.existsSync(sourceRoot) ? { builtInSourceRoot: sourceRoot } : {},
39717
+ builtInDistRoot: path27__default.join(packageRoot, "dist", "fleet-plugins")
38447
39718
  };
38448
39719
  }
38449
39720
  function createHttpServer(handler, upgradeRegistry) {
@@ -38499,7 +39770,7 @@ function theaterFolderListStatus(error51) {
38499
39770
  if (error51.code === "not_found") return 404;
38500
39771
  return 400;
38501
39772
  }
38502
- function isPlainObject2(value) {
39773
+ function isPlainObject4(value) {
38503
39774
  return typeof value === "object" && value !== null && !Array.isArray(value);
38504
39775
  }
38505
39776
  function hasForbiddenUpdateApplyBodyKeys(body) {
@@ -38509,7 +39780,7 @@ function isValidConsoleStaticPort3(value) {
38509
39780
  return typeof value === "number" && Number.isInteger(value) && value >= MIN_CONSOLE_STATIC_PORT3 && value <= MAX_CONSOLE_STATIC_PORT3;
38510
39781
  }
38511
39782
  function sanitizeLaunchKind(value) {
38512
- if (!isPlainObject2(value) || typeof value.id !== "string" || typeof value.type !== "string" || typeof value.title !== "string") return null;
39783
+ if (!isPlainObject4(value) || typeof value.id !== "string" || typeof value.type !== "string" || typeof value.title !== "string") return null;
38513
39784
  return {
38514
39785
  id: value.id,
38515
39786
  type: value.type,
@@ -38588,7 +39859,7 @@ function isAllowedTerminalOrigin(req, expectedPort) {
38588
39859
  async function readPluginStorageJson(dataDir, pluginId, key) {
38589
39860
  const file2 = resolvePluginStorageFile(dataDir, pluginId, key);
38590
39861
  try {
38591
- return JSON.parse(await fs11__default.promises.readFile(file2, "utf8"));
39862
+ return JSON.parse(await fs14__default.promises.readFile(file2, "utf8"));
38592
39863
  } catch (error51) {
38593
39864
  if (isNodeError(error51) && error51.code === "ENOENT") return null;
38594
39865
  throw error51;
@@ -38596,20 +39867,20 @@ async function readPluginStorageJson(dataDir, pluginId, key) {
38596
39867
  }
38597
39868
  async function writePluginStorageJson(dataDir, pluginId, key, value) {
38598
39869
  const file2 = resolvePluginStorageFile(dataDir, pluginId, key);
38599
- await fs11__default.promises.mkdir(path25__default.dirname(file2), { recursive: true });
38600
- await fs11__default.promises.writeFile(file2, JSON.stringify(value), "utf8");
39870
+ await fs14__default.promises.mkdir(path27__default.dirname(file2), { recursive: true });
39871
+ await fs14__default.promises.writeFile(file2, JSON.stringify(value), "utf8");
38601
39872
  }
38602
39873
  function resolvePluginStorageFile(dataDir, pluginId, key) {
38603
39874
  if (!/^[a-z0-9][a-z0-9._-]*$/i.test(pluginId) || !/^[a-z0-9][a-z0-9._-]*$/i.test(key)) {
38604
39875
  throw new Error("invalid_plugin_storage_key");
38605
39876
  }
38606
- return path25__default.join(dataDir, "plugins", pluginId, `${key}.json`);
39877
+ return path27__default.join(dataDir, "plugins", pluginId, `${key}.json`);
38607
39878
  }
38608
39879
  function isNodeError(error51) {
38609
39880
  return error51 instanceof Error && "code" in error51;
38610
39881
  }
38611
39882
  function createConsoleStalePolicy(deps = {}) {
38612
- const fsImpl = deps.fs ?? fs11__default;
39883
+ const fsImpl = deps.fs ?? fs14__default;
38613
39884
  function isBuildStale(lock, buildFile) {
38614
39885
  try {
38615
39886
  return lock.startedAt < fsImpl.statSync(buildFile).mtimeMs;
@@ -38731,6 +40002,7 @@ function createConsoleDaemonLifecycle(deps = {}) {
38731
40002
  async function stop() {
38732
40003
  const payload = readTrustedLock();
38733
40004
  if (!payload) return;
40005
+ if (isLockProcessAlive(payload.pid)) assertCliCanControlDaemon(payload);
38734
40006
  try {
38735
40007
  process4.kill(payload.pid, "SIGTERM");
38736
40008
  } catch (err) {
@@ -38750,6 +40022,7 @@ function createConsoleDaemonLifecycle(deps = {}) {
38750
40022
  const probeResult = await health.probe(current);
38751
40023
  const isBuildStale = current ? stale.isBuildStale(current, serverModulePath) : false;
38752
40024
  if (probeResult.healthy && current) {
40025
+ if (current.owner?.kind === "desktop") return current.endpoint;
38753
40026
  if (!isBuildStale) return current.endpoint;
38754
40027
  if (typeof probeResult.health?.workspaceCount === "number" && probeResult.health.workspaceCount > 0) return current.endpoint;
38755
40028
  }
@@ -38802,11 +40075,12 @@ async function runConsoleStatus(deps = {}) {
38802
40075
  const consoleUrl = `${status.lock.endpoint}console/`;
38803
40076
  const workspaceCount = typeof status.health?.workspaceCount === "number" ? status.health.workspaceCount : 0;
38804
40077
  const staleNote = status.buildStale ? " \xB7 build stale (restart recommended)" : "";
40078
+ const ownerNote = status.lock.owner?.kind === "desktop" ? " \xB7 owned by desktop" : "";
38805
40079
  return [
38806
40080
  `Fleet Console server: running (pid ${status.lock.pid})`,
38807
40081
  ` endpoint ${status.lock.endpoint}`,
38808
40082
  ` console ${consoleUrl}`,
38809
- ` workspaces ${workspaceCount}${staleNote}`
40083
+ ` workspaces ${workspaceCount}${staleNote}${ownerNote}`
38810
40084
  ].join("\n");
38811
40085
  }
38812
40086
  async function runConsoleStop(deps = {}) {
@@ -38814,6 +40088,17 @@ async function runConsoleStop(deps = {}) {
38814
40088
  await lifecycle.stop();
38815
40089
  return "Fleet Console server stopped.";
38816
40090
  }
40091
+ function assertCliCanControlDaemon(payload) {
40092
+ if (payload.owner?.kind === "desktop") throw new Error("Fleet Console is owned by the desktop app; quit it from the native menu.");
40093
+ }
40094
+ function isLockProcessAlive(pid) {
40095
+ try {
40096
+ process4.kill(pid, 0);
40097
+ return true;
40098
+ } catch (err) {
40099
+ return err.code !== "ESRCH";
40100
+ }
40101
+ }
38817
40102
  async function runConsoleRestart(deps = {}) {
38818
40103
  const lifecycle = deps.lifecycle ?? createConsoleDaemonLifecycle();
38819
40104
  await lifecycle.stop();
@@ -38867,7 +40152,7 @@ async function main() {
38867
40152
  }
38868
40153
  function resolveDefaultServerModulePath2() {
38869
40154
  const builtPath = new URL("../dist/cli.mjs", import.meta.url).pathname;
38870
- if (fs11__default.existsSync(builtPath)) return builtPath;
40155
+ if (fs14__default.existsSync(builtPath)) return builtPath;
38871
40156
  return fileURLToPath(import.meta.url);
38872
40157
  }
38873
40158
  function readHookSessionId(env) {
@@ -38921,7 +40206,7 @@ function readStdinBestEffort() {
38921
40206
  });
38922
40207
  });
38923
40208
  }
38924
- var isDirectRun = process4.argv[1] && path25__default.resolve(process4.argv[1]) === fileURLToPath(import.meta.url);
40209
+ var isDirectRun = process4.argv[1] && path27__default.resolve(process4.argv[1]) === fileURLToPath(import.meta.url);
38925
40210
  if (isDirectRun) {
38926
40211
  await main().catch((error51) => {
38927
40212
  process4.stderr.write(`${error51 instanceof Error ? error51.message : String(error51)}
@@ -38930,4 +40215,4 @@ if (isDirectRun) {
38930
40215
  });
38931
40216
  }
38932
40217
 
38933
- export { buildConsoleHelpText, createConsoleDaemonLifecycle, main, openFleetConsole, parseConsoleCliMode, parseConsoleHookCommand, runConsoleRestart, runConsoleStatus, runConsoleStop };
40218
+ export { assertCliCanControlDaemon, buildConsoleHelpText, createConsoleDaemonLifecycle, isLockProcessAlive, main, openFleetConsole, parseConsoleCliMode, parseConsoleHookCommand, runConsoleRestart, runConsoleStatus, runConsoleStop };