@dotobokuri/fleet-console 1.5.5 → 1.7.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 (68) hide show
  1. package/AGENTS.md +14 -11
  2. package/README.md +9 -4
  3. package/dist/cli-bin.mjs +1915 -689
  4. package/dist/cli.d.ts +10 -1
  5. package/dist/cli.mjs +1915 -689
  6. package/dist/client/assets/{_baseUniq-CpEjig2A.js → _baseUniq-DaRHKvkJ.js} +1 -1
  7. package/dist/client/assets/{arc-BmymS_Kz.js → arc-ByrjrNFZ.js} +1 -1
  8. package/dist/client/assets/{architectureDiagram-Q4EWVU46-D1d0KVz1.js → architectureDiagram-Q4EWVU46-yRDXYtYq.js} +1 -1
  9. package/dist/client/assets/{blockDiagram-DXYQGD6D-Cwuv0sNk.js → blockDiagram-DXYQGD6D-B3Ud4HmY.js} +1 -1
  10. package/dist/client/assets/{c4Diagram-AHTNJAMY-Bsk-GjC_.js → c4Diagram-AHTNJAMY-BWtT7uJ6.js} +1 -1
  11. package/dist/client/assets/channel-D1hUfR86.js +1 -0
  12. package/dist/client/assets/{chunk-4BX2VUAB-ZM4i9T4D.js → chunk-4BX2VUAB-Cdk_k5EX.js} +1 -1
  13. package/dist/client/assets/{chunk-4TB4RGXK-Sw5ODKRj.js → chunk-4TB4RGXK-CfeLiRAa.js} +1 -1
  14. package/dist/client/assets/{chunk-55IACEB6-DDDLwVg4.js → chunk-55IACEB6-B26kabv5.js} +1 -1
  15. package/dist/client/assets/{chunk-EDXVE4YY-BNk5Xk1Q.js → chunk-EDXVE4YY-CZ5hqsgg.js} +1 -1
  16. package/dist/client/assets/{chunk-FMBD7UC4-C_CC6grw.js → chunk-FMBD7UC4-CuCp-r0v.js} +1 -1
  17. package/dist/client/assets/{chunk-OYMX7WX6-DTbaYVUc.js → chunk-OYMX7WX6-Dy5T8Uzb.js} +1 -1
  18. package/dist/client/assets/{chunk-QZHKN3VN-i7z9jXPR.js → chunk-QZHKN3VN-PNRzwWoU.js} +1 -1
  19. package/dist/client/assets/{chunk-YZCP3GAM-cTSeZOtz.js → chunk-YZCP3GAM-BjJxIVvV.js} +1 -1
  20. package/dist/client/assets/classDiagram-6PBFFD2Q-B7ZtwAd2.js +1 -0
  21. package/dist/client/assets/classDiagram-v2-HSJHXN6E-B7ZtwAd2.js +1 -0
  22. package/dist/client/assets/clone-BggxG0Ng.js +1 -0
  23. package/dist/client/assets/{cose-bilkent-S5V4N54A-uVW4wTRq.js → cose-bilkent-S5V4N54A-CIL2KUnC.js} +1 -1
  24. package/dist/client/assets/{dagre-KV5264BT-CclQ97X6.js → dagre-KV5264BT-BoekxdN3.js} +1 -1
  25. package/dist/client/assets/{diagram-5BDNPKRD-eZaXK0Xj.js → diagram-5BDNPKRD-8Uqn3Uve.js} +1 -1
  26. package/dist/client/assets/{diagram-G4DWMVQ6-DIQ8qOFv.js → diagram-G4DWMVQ6-CWZp4Fw1.js} +1 -1
  27. package/dist/client/assets/{diagram-MMDJMWI5-B_EbeR8B.js → diagram-MMDJMWI5-Cxxmq5lR.js} +1 -1
  28. package/dist/client/assets/{diagram-TYMM5635-DI64qX-Y.js → diagram-TYMM5635-BDL3SVhp.js} +1 -1
  29. package/dist/client/assets/{erDiagram-SMLLAGMA-DLkwbOsk.js → erDiagram-SMLLAGMA-Bm_XDDZi.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-DWJPFMVM-BWvMzejD.js → flowDiagram-DWJPFMVM-z3JhyUpl.js} +1 -1
  31. package/dist/client/assets/fraunces-latin-ext-standard-italic-CGbN9UgK.woff2 +0 -0
  32. package/dist/client/assets/fraunces-latin-standard-italic-lSdLDfvT.woff2 +0 -0
  33. package/dist/client/assets/fraunces-vietnamese-standard-italic-DxWqP7Ku.woff2 +0 -0
  34. package/dist/client/assets/{ganttDiagram-T4ZO3ILL-B4qUxmRl.js → ganttDiagram-T4ZO3ILL-DVfDRTRP.js} +1 -1
  35. package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-CnvcLURP.js → gitGraphDiagram-UUTBAWPF-_gni8LDF.js} +1 -1
  36. package/dist/client/assets/{graph-N4oFFLXe.js → graph-Beq1lcyq.js} +1 -1
  37. package/dist/client/assets/index-3nhZQ9tb.css +1 -0
  38. package/dist/client/assets/index-DFffaVk9.js +734 -0
  39. package/dist/client/assets/{infoDiagram-42DDH7IO-3uxP8aX_.js → infoDiagram-42DDH7IO-B-TP33al.js} +1 -1
  40. package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-DQomHvSO.js → ishikawaDiagram-UXIWVN3A-1YDVINPA.js} +1 -1
  41. package/dist/client/assets/{journeyDiagram-VCZTEJTY-C_Ogwo77.js → journeyDiagram-VCZTEJTY-CZJuR6NC.js} +1 -1
  42. package/dist/client/assets/{kanban-definition-6JOO6SKY-CGOeur5D.js → kanban-definition-6JOO6SKY-_sSHU_-t.js} +1 -1
  43. package/dist/client/assets/{layout-DKlBkSB9.js → layout-UzmHJbGX.js} +1 -1
  44. package/dist/client/assets/{linear-2pQUtBdp.js → linear-BHitegAT.js} +1 -1
  45. package/dist/client/assets/{mermaid.core-BrseLPLb.js → mermaid.core-ptwBQDH8.js} +4 -4
  46. package/dist/client/assets/{min-OFtHLPyF.js → min-CBjwmJst.js} +1 -1
  47. package/dist/client/assets/{mindmap-definition-QFDTVHPH-DQk9T3wA.js → mindmap-definition-QFDTVHPH-C8NIvAO1.js} +1 -1
  48. package/dist/client/assets/{pieDiagram-DEJITSTG-Btu4aJWj.js → pieDiagram-DEJITSTG-paebhRXC.js} +1 -1
  49. package/dist/client/assets/{quadrantDiagram-34T5L4WZ-BHMDVip9.js → quadrantDiagram-34T5L4WZ-Bk0pN-T7.js} +1 -1
  50. package/dist/client/assets/{requirementDiagram-MS252O5E-CbXi80dX.js → requirementDiagram-MS252O5E-CkPyeOJB.js} +1 -1
  51. package/dist/client/assets/{sankeyDiagram-XADWPNL6-D5c7vUw4.js → sankeyDiagram-XADWPNL6-tJsRZl9C.js} +1 -1
  52. package/dist/client/assets/{sequenceDiagram-FGHM5R23-CGuRanG_.js → sequenceDiagram-FGHM5R23-D1V2Qnb3.js} +1 -1
  53. package/dist/client/assets/{stateDiagram-FHFEXIEX-CVD3CB69.js → stateDiagram-FHFEXIEX-DdEFxPzQ.js} +1 -1
  54. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-CpqK8Q3C.js +1 -0
  55. package/dist/client/assets/{timeline-definition-GMOUNBTQ-DMTeeglq.js → timeline-definition-GMOUNBTQ-D8sNtFqY.js} +1 -1
  56. package/dist/client/assets/{vennDiagram-DHZGUBPP-CQ6TwRhb.js → vennDiagram-DHZGUBPP-DoTEQk3E.js} +1 -1
  57. package/dist/client/assets/{wardley-RL74JXVD-DdgusU4o.js → wardley-RL74JXVD-BuEgLJ7M.js} +1 -1
  58. package/dist/client/assets/{wardleyDiagram-NUSXRM2D-BFsWqK49.js → wardleyDiagram-NUSXRM2D-B0Gl34Zh.js} +1 -1
  59. package/dist/client/assets/{xychartDiagram-5P7HB3ND-Hca3jUV6.js → xychartDiagram-5P7HB3ND-Bf4nAMbM.js} +1 -1
  60. package/dist/client/index.html +2 -2
  61. package/package.json +1 -1
  62. package/dist/client/assets/channel--qpYuR58.js +0 -1
  63. package/dist/client/assets/classDiagram-6PBFFD2Q-BAI-fNQ3.js +0 -1
  64. package/dist/client/assets/classDiagram-v2-HSJHXN6E-BAI-fNQ3.js +0 -1
  65. package/dist/client/assets/clone-CqNqlAzp.js +0 -1
  66. package/dist/client/assets/index-DfJLxHX8.js +0 -734
  67. package/dist/client/assets/index-uNbiLDxs.css +0 -1
  68. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-BevvDvg0.js +0 -1
package/dist/cli-bin.mjs CHANGED
@@ -4,14 +4,14 @@ import * as fs5 from 'fs';
4
4
  import fs5__default, { readFileSync, existsSync, mkdtempSync, writeFileSync, mkdirSync, rmSync, chmodSync, renameSync, readdirSync, constants, openSync, closeSync, lstatSync, unlinkSync, realpathSync, statSync, symlinkSync, fstatSync } from 'fs';
5
5
  import * as path93 from 'path';
6
6
  import path93__default, { join, resolve, relative, dirname } from 'path';
7
- import process4 from 'process';
7
+ import process6 from 'process';
8
8
  import { fileURLToPath, pathToFileURL } from 'url';
9
9
  import * as os22 from 'os';
10
10
  import os22__default from 'os';
11
11
  import { EventEmitter } from 'events';
12
12
  import { stat, mkdir, readFile, readdir, appendFile, writeFile, rename, access, rm } from 'fs/promises';
13
13
  import { Writable, Readable } from 'stream';
14
- import crypto3, { createHash, randomUUID, timingSafeEqual } from 'crypto';
14
+ import crypto5, { createHash, randomUUID, timingSafeEqual } from 'crypto';
15
15
  import http from 'http';
16
16
  import { createRequire } from 'module';
17
17
  import net2 from 'net';
@@ -969,10 +969,10 @@ function mergeDefs(...defs) {
969
969
  function cloneDef(schema) {
970
970
  return mergeDefs(schema._zod.def);
971
971
  }
972
- function getElementAtPath(obj, path30) {
973
- if (!path30)
972
+ function getElementAtPath(obj, path35) {
973
+ if (!path35)
974
974
  return obj;
975
- return path30.reduce((acc, key) => acc?.[key], obj);
975
+ return path35.reduce((acc, key) => acc?.[key], obj);
976
976
  }
977
977
  function promiseAllObject(promisesObj) {
978
978
  const keys = Object.keys(promisesObj);
@@ -1381,11 +1381,11 @@ function explicitlyAborted(x, startIndex = 0) {
1381
1381
  }
1382
1382
  return false;
1383
1383
  }
1384
- function prefixIssues(path30, issues) {
1384
+ function prefixIssues(path35, issues) {
1385
1385
  return issues.map((iss) => {
1386
1386
  var _a3;
1387
1387
  (_a3 = iss).path ?? (_a3.path = []);
1388
- iss.path.unshift(path30);
1388
+ iss.path.unshift(path35);
1389
1389
  return iss;
1390
1390
  });
1391
1391
  }
@@ -1532,16 +1532,16 @@ function flattenError(error51, mapper = (issue3) => issue3.message) {
1532
1532
  }
1533
1533
  function formatError(error51, mapper = (issue3) => issue3.message) {
1534
1534
  const fieldErrors = { _errors: [] };
1535
- const processError = (error52, path30 = []) => {
1535
+ const processError = (error52, path35 = []) => {
1536
1536
  for (const issue3 of error52.issues) {
1537
1537
  if (issue3.code === "invalid_union" && issue3.errors.length) {
1538
- issue3.errors.map((issues) => processError({ issues }, [...path30, ...issue3.path]));
1538
+ issue3.errors.map((issues) => processError({ issues }, [...path35, ...issue3.path]));
1539
1539
  } else if (issue3.code === "invalid_key") {
1540
- processError({ issues: issue3.issues }, [...path30, ...issue3.path]);
1540
+ processError({ issues: issue3.issues }, [...path35, ...issue3.path]);
1541
1541
  } else if (issue3.code === "invalid_element") {
1542
- processError({ issues: issue3.issues }, [...path30, ...issue3.path]);
1542
+ processError({ issues: issue3.issues }, [...path35, ...issue3.path]);
1543
1543
  } else {
1544
- const fullpath = [...path30, ...issue3.path];
1544
+ const fullpath = [...path35, ...issue3.path];
1545
1545
  if (fullpath.length === 0) {
1546
1546
  fieldErrors._errors.push(mapper(issue3));
1547
1547
  } else {
@@ -1568,17 +1568,17 @@ function formatError(error51, mapper = (issue3) => issue3.message) {
1568
1568
  }
1569
1569
  function treeifyError(error51, mapper = (issue3) => issue3.message) {
1570
1570
  const result = { errors: [] };
1571
- const processError = (error52, path30 = []) => {
1571
+ const processError = (error52, path35 = []) => {
1572
1572
  var _a3, _b;
1573
1573
  for (const issue3 of error52.issues) {
1574
1574
  if (issue3.code === "invalid_union" && issue3.errors.length) {
1575
- issue3.errors.map((issues) => processError({ issues }, [...path30, ...issue3.path]));
1575
+ issue3.errors.map((issues) => processError({ issues }, [...path35, ...issue3.path]));
1576
1576
  } else if (issue3.code === "invalid_key") {
1577
- processError({ issues: issue3.issues }, [...path30, ...issue3.path]);
1577
+ processError({ issues: issue3.issues }, [...path35, ...issue3.path]);
1578
1578
  } else if (issue3.code === "invalid_element") {
1579
- processError({ issues: issue3.issues }, [...path30, ...issue3.path]);
1579
+ processError({ issues: issue3.issues }, [...path35, ...issue3.path]);
1580
1580
  } else {
1581
- const fullpath = [...path30, ...issue3.path];
1581
+ const fullpath = [...path35, ...issue3.path];
1582
1582
  if (fullpath.length === 0) {
1583
1583
  result.errors.push(mapper(issue3));
1584
1584
  continue;
@@ -1610,8 +1610,8 @@ function treeifyError(error51, mapper = (issue3) => issue3.message) {
1610
1610
  }
1611
1611
  function toDotPath(_path) {
1612
1612
  const segs = [];
1613
- const path30 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1614
- for (const seg of path30) {
1613
+ const path35 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1614
+ for (const seg of path35) {
1615
1615
  if (typeof seg === "number")
1616
1616
  segs.push(`[${seg}]`);
1617
1617
  else if (typeof seg === "symbol")
@@ -14275,13 +14275,13 @@ function resolveRef(ref, ctx) {
14275
14275
  if (!ref.startsWith("#")) {
14276
14276
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
14277
14277
  }
14278
- const path30 = ref.slice(1).split("/").filter(Boolean);
14279
- if (path30.length === 0) {
14278
+ const path35 = ref.slice(1).split("/").filter(Boolean);
14279
+ if (path35.length === 0) {
14280
14280
  return ctx.rootSchema;
14281
14281
  }
14282
14282
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
14283
- if (path30[0] === defsKey) {
14284
- const key = path30[1];
14283
+ if (path35[0] === defsKey) {
14284
+ const key = path35[1];
14285
14285
  if (!key || !ctx.defs[key]) {
14286
14286
  throw new Error(`Reference not found: ${ref}`);
14287
14287
  }
@@ -17046,7 +17046,9 @@ function killProcess(child, forceTimeoutMs = 3e3) {
17046
17046
  try {
17047
17047
  execSync(`taskkill /PID ${child.pid} /T /F`, {
17048
17048
  stdio: "pipe",
17049
- timeout: 5e3
17049
+ timeout: 5e3,
17050
+ // 콘솔 없는 호스트에서 taskkill 호출 시 새 콘솔 창이 깜빡이는 것을 방지한다.
17051
+ windowsHide: true
17050
17052
  });
17051
17053
  } catch {
17052
17054
  child.kill("SIGKILL");
@@ -17120,7 +17122,12 @@ var BaseConnection = class extends EventEmitter {
17120
17122
  cwd: this.cwd,
17121
17123
  stdio: ["pipe", "pipe", "pipe"],
17122
17124
  env: this.env,
17123
- windowsVerbatimArguments: true
17125
+ windowsVerbatimArguments: true,
17126
+ // 콘솔이 없는 호스트(예: fleet-console 백엔드)에서 cmd.exe를 spawn하면
17127
+ // Windows가 새 콘솔 창을 할당해 깜빡인다. stdio는 모두 pipe라 가시 콘솔이
17128
+ // 불필요하므로 CREATE_NO_WINDOW로 창 생성을 억제한다. (fleet-cli처럼 콘솔이
17129
+ // 이미 있는 경우에도 무해하다.)
17130
+ windowsHide: true
17124
17131
  }) : spawn(this.command, this.args, {
17125
17132
  cwd: this.cwd,
17126
17133
  stdio: ["pipe", "pipe", "pipe"],
@@ -17876,7 +17883,9 @@ function resolveNpxPath(env) {
17876
17883
  encoding: "utf-8",
17877
17884
  stdio: "pipe",
17878
17885
  timeout: 5e3,
17879
- env
17886
+ env,
17887
+ // 콘솔 없는 호스트에서 `where npx` 실행 시 콘솔 창이 깜빡이는 것을 방지한다.
17888
+ windowsHide: true
17880
17889
  }).trim();
17881
17890
  const candidates = result.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
17882
17891
  if (windows) {
@@ -17913,7 +17922,7 @@ function inferBinName(packageName) {
17913
17922
  // ../../packages/core-unified-agent/models.json
17914
17923
  var models_default = {
17915
17924
  version: 1,
17916
- updatedAt: "2026-06-13T00:00:00Z",
17925
+ updatedAt: "2026-06-16T00:00:00Z",
17917
17926
  providers: {
17918
17927
  claude: {
17919
17928
  name: "Claude Code with Anthropic",
@@ -17923,7 +17932,9 @@ var models_default = {
17923
17932
  { modelId: "sonnet", name: "Claude Sonnet", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
17924
17933
  { modelId: "opus", name: "Claude Opus", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
17925
17934
  { modelId: "opus[1m]", name: "Claude Opus [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "xhigh" } },
17926
- { modelId: "claude-opus-4-6[1m]", name: "Claude Opus 4.6 [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } }
17935
+ { modelId: "claude-opus-4-6[1m]", name: "Claude Opus 4.6 [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
17936
+ { modelId: "claude-opus-4-7[1m]", name: "Claude Opus 4.7 [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } },
17937
+ { modelId: "claude-opus-4-8[1m]", name: "Claude Opus 4.8 [1M]", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "high" } }
17927
17938
  ]
17928
17939
  },
17929
17940
  "claude-zai": {
@@ -21074,7 +21085,7 @@ function specToMcpTool(spec) {
21074
21085
  }
21075
21086
  function installExecutorToolCallRouter(runtime, sessionToken, ctx) {
21076
21087
  runtime.server.setOnToolCallArrived(sessionToken, (toolName, args) => {
21077
- const toolCallId = crypto3.randomUUID();
21088
+ const toolCallId = crypto5.randomUUID();
21078
21089
  void runtime.registry.invoke(toolName, args, { cwd: ctx.cwd, sessionLabel: ctx.sessionLabel, toolCallId, signal: ctx.signal }).then((result) => runtime.server.resolveNextToolCall(sessionToken, toolCallId, result)).catch((err) => {
21079
21090
  runtime.server.resolveNextToolCall(sessionToken, toolCallId, {
21080
21091
  content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }],
@@ -21465,7 +21476,7 @@ function createInProcessMcpServer(deps = {}) {
21465
21476
  if (activeStopPromise) await activeStopPromise;
21466
21477
  if (activeServer && activeServerUrl) return activeServerUrl;
21467
21478
  if (activeStartPromise) return activeStartPromise;
21468
- activeOpaquePath = `/${crypto3.randomUUID()}`;
21479
+ activeOpaquePath = `/${crypto5.randomUUID()}`;
21469
21480
  activeStartPromise = new Promise((resolve, reject) => {
21470
21481
  const srv = http.createServer(handleRequest);
21471
21482
  srv.timeout = MCP_SERVER_TIMEOUT_MS;
@@ -21722,7 +21733,7 @@ function createExecutorMcpSession(deps, toolTimeoutSeconds, request) {
21722
21733
  throw new Error("Executor session cwd is required");
21723
21734
  }
21724
21735
  assertNonEmptyExecutorTools(request.serverName, request.specs);
21725
- const token = crypto3.randomUUID();
21736
+ const token = crypto5.randomUUID();
21726
21737
  registerExecutorSessionTools(runtime.runtime, token, [...request.specs]);
21727
21738
  installExecutorToolCallRouter(runtime.runtime, token, {
21728
21739
  cwd,
@@ -21768,7 +21779,7 @@ function issueSessionToken(deps, sessionTokensByLabel, request) {
21768
21779
  for (const { name, runtime } of deps.runtimes) {
21769
21780
  const tools = runtime.registry.getAllAgentTools();
21770
21781
  assertNonEmptyExecutorTools(name, tools);
21771
- const token = crypto3.randomUUID();
21782
+ const token = crypto5.randomUUID();
21772
21783
  registerExecutorSessionTools(runtime, token, tools);
21773
21784
  installExecutorToolCallRouter(runtime, token, { cwd, sessionLabel: label, signal: request.signal });
21774
21785
  tokens.push({ name, token });
@@ -23134,7 +23145,8 @@ function sleepSync(ms) {
23134
23145
  var DEFAULT_TEMP_FILE_MIN_AGE_MS = 6e4;
23135
23146
  function createDurableJsonStore(deps) {
23136
23147
  const now = deps.now ?? (() => Date.now());
23137
- const mode = SECURE_FILE_MODE ;
23148
+ const sensitivity = deps.sensitivity;
23149
+ const mode = sensitivity === "sensitive" ? SECURE_FILE_MODE : 420;
23138
23150
  const minAgeMs = deps.tempCleanupMinAgeMs ?? DEFAULT_TEMP_FILE_MIN_AGE_MS;
23139
23151
  const dir = path93.dirname(deps.filePath);
23140
23152
  return {
@@ -23162,10 +23174,10 @@ function createDurableJsonStore(deps) {
23162
23174
  };
23163
23175
  function withLock(operation) {
23164
23176
  if (!deps.lockDir) {
23165
- ensureSafeDirectoryIfSensitive(dir);
23177
+ ensureSafeDirectoryIfSensitive(dir, sensitivity);
23166
23178
  return operation();
23167
23179
  }
23168
- ensureSafeDirectoryIfSensitive(dir);
23180
+ ensureSafeDirectoryIfSensitive(dir, sensitivity);
23169
23181
  return withDirectoryLock(
23170
23182
  {
23171
23183
  lockDir: deps.lockDir,
@@ -23207,8 +23219,10 @@ function readJsonFile(filePath, sanitize) {
23207
23219
  }
23208
23220
  }
23209
23221
  function ensureSafeDirectoryIfSensitive(dir, sensitivity) {
23210
- {
23222
+ if (sensitivity === "sensitive") {
23211
23223
  ensureSafeDirectory(dir);
23224
+ } else {
23225
+ fs5.mkdirSync(dir, { recursive: true });
23212
23226
  }
23213
23227
  }
23214
23228
 
@@ -31414,7 +31428,7 @@ var LOCK_FILE_MODE = 384;
31414
31428
  function createConsoleLock(deps = {}) {
31415
31429
  const fsImpl = deps.fs ?? fs5__default;
31416
31430
  const now = deps.now ?? Date.now;
31417
- const randomToken = deps.randomToken ?? (() => crypto3.randomBytes(32).toString("base64url"));
31431
+ const randomToken = deps.randomToken ?? (() => crypto5.randomBytes(32).toString("base64url"));
31418
31432
  const hostname5 = deps.hostname ?? (() => "127.0.0.1");
31419
31433
  function ensureLockDir(dir) {
31420
31434
  fsImpl.mkdirSync(dir, { recursive: true, mode: LOCK_DIR_MODE });
@@ -31509,6 +31523,389 @@ function createConsoleLock(deps = {}) {
31509
31523
  }
31510
31524
  return { ensureLockDir, readLock, writeLock, removeLock, assertLockModes, assertTrustedLock };
31511
31525
  }
31526
+ var FLEET_DATA_DIR_NAME = ".fleet";
31527
+ function getFleetDataDir() {
31528
+ return path93.join(os22.homedir(), FLEET_DATA_DIR_NAME);
31529
+ }
31530
+ function readAuthStoreFile(filePath) {
31531
+ let fd;
31532
+ try {
31533
+ fd = fs5.openSync(filePath, fs5.constants.O_RDONLY | NOFOLLOW_FLAG);
31534
+ if (!fs5.fstatSync(fd).isFile()) {
31535
+ return {};
31536
+ }
31537
+ return JSON.parse(fs5.readFileSync(fd, "utf-8"));
31538
+ } catch {
31539
+ return {};
31540
+ } finally {
31541
+ if (fd !== void 0) {
31542
+ try {
31543
+ fs5.closeSync(fd);
31544
+ } catch {
31545
+ }
31546
+ }
31547
+ }
31548
+ }
31549
+ var DEFAULT_AUTH_PATH = path93.join(getFleetDataDir(), "auth.json");
31550
+ var AUTH_LOCK_OWNER_FILE_NAME = "owner.json";
31551
+ var AUTH_LOCK_TIMEOUT_MS = 5e3;
31552
+ function createAuthService(deps = {}) {
31553
+ const authPath = deps.authPath ?? DEFAULT_AUTH_PATH;
31554
+ const lockDir = `${authPath}.lock`;
31555
+ return {
31556
+ // [HIGH #1] TOCTOU 수정: existsSync/read/check/delete/write 전체를 락 블록 안으로 이동
31557
+ async deleteApiKey(providerId) {
31558
+ let deleted = false;
31559
+ withAuthLock(authPath, lockDir, () => {
31560
+ if (!fs5.existsSync(authPath)) return;
31561
+ const data = readAuthStoreFile(authPath);
31562
+ if (!Object.prototype.hasOwnProperty.call(data, providerId)) return;
31563
+ delete data[providerId];
31564
+ writeAuthData(authPath, data);
31565
+ deleted = true;
31566
+ });
31567
+ return deleted;
31568
+ },
31569
+ // [HIGH #2] 읽기 경로: fd 기반 O_RDONLY|O_NOFOLLOW+fstatSync isFile
31570
+ async getApiKey(providerId) {
31571
+ let fd;
31572
+ try {
31573
+ if (!fs5.existsSync(authPath)) {
31574
+ return void 0;
31575
+ }
31576
+ fd = fs5.openSync(authPath, fs5.constants.O_RDONLY | NOFOLLOW_FLAG);
31577
+ if (!fs5.fstatSync(fd).isFile()) {
31578
+ return void 0;
31579
+ }
31580
+ const data = JSON.parse(fs5.readFileSync(fd, "utf-8"));
31581
+ return typeof data[providerId]?.key === "string" ? data[providerId].key : void 0;
31582
+ } catch {
31583
+ return void 0;
31584
+ } finally {
31585
+ if (fd !== void 0) {
31586
+ try {
31587
+ fs5.closeSync(fd);
31588
+ } catch {
31589
+ }
31590
+ }
31591
+ }
31592
+ },
31593
+ // [HIGH #2] 읽기 경로: fd 기반 O_RDONLY|O_NOFOLLOW 통일
31594
+ async listProviderIds() {
31595
+ if (!fs5.existsSync(authPath)) {
31596
+ return [];
31597
+ }
31598
+ try {
31599
+ return Object.keys(readAuthStoreFile(authPath)).sort();
31600
+ } catch {
31601
+ return [];
31602
+ }
31603
+ },
31604
+ async setApiKey(providerId, key) {
31605
+ const dir = path93.dirname(authPath);
31606
+ ensureSafeDirectory(dir);
31607
+ withAuthLock(authPath, lockDir, () => {
31608
+ const data = fs5.existsSync(authPath) ? readAuthStoreFile(authPath) : {};
31609
+ data[providerId] = {
31610
+ ...data[providerId] ?? {},
31611
+ key
31612
+ };
31613
+ writeAuthData(authPath, data);
31614
+ });
31615
+ }
31616
+ };
31617
+ }
31618
+ function withAuthLock(authPath, lockDir, operation) {
31619
+ return withDirectoryLock(
31620
+ {
31621
+ lockDir,
31622
+ ownerFileName: AUTH_LOCK_OWNER_FILE_NAME,
31623
+ timeoutMs: AUTH_LOCK_TIMEOUT_MS
31624
+ },
31625
+ operation
31626
+ );
31627
+ }
31628
+ function writeAuthData(authPath, data) {
31629
+ writeAtomicSync(authPath, `${JSON.stringify(data, null, 2)}
31630
+ `, {
31631
+ mode: SECURE_FILE_MODE,
31632
+ fsync: true
31633
+ });
31634
+ }
31635
+ function formatMissingAuthKeyMessage(input) {
31636
+ const cliHint = input.cli ? `cli '${input.cli}'` : "selected CLI";
31637
+ return `Auth token not found for ${cliHint} (providerId: '${input.providerId}'). Run \`fleet auth login\` to register one.`;
31638
+ }
31639
+ function formatAuthValidationFailureMessage(input) {
31640
+ const detail = input.detail ? ` Detail: ${input.detail}` : "";
31641
+ if (input.status === "unauthorized") {
31642
+ return `Auth token was rejected (providerId: '${input.providerId}'). Check the token and try again.${detail}`;
31643
+ }
31644
+ if (input.status === "forbidden") {
31645
+ return `Auth token is not allowed for this provider (providerId: '${input.providerId}'). Check the token permissions.${detail}`;
31646
+ }
31647
+ if (input.status === "timeout") {
31648
+ return `Auth token validation timed out (providerId: '${input.providerId}'). Check the connection and try again.${detail}`;
31649
+ }
31650
+ if (input.status === "network") {
31651
+ return `Auth token validation failed due to a network error (providerId: '${input.providerId}'). Check the connection and try again.${detail}`;
31652
+ }
31653
+ if (input.status === "server") {
31654
+ return `Auth token validation failed because the provider returned an error (providerId: '${input.providerId}'). Try again later.${detail}`;
31655
+ }
31656
+ return `Auth token validation failed (providerId: '${input.providerId}'). Check the token and try again.${detail}`;
31657
+ }
31658
+ var LEGACY_AUTH_PATH = path93.join(getFleetDataDir(), "agent", "auth.json");
31659
+ var CURRENT_AUTH_PATH = path93.join(getFleetDataDir(), "auth.json");
31660
+ function mergeAuthStoresNoOverwrite(legacy, current) {
31661
+ const data = { ...current };
31662
+ const migratedProviderIds = [];
31663
+ const skippedProviderIds = [];
31664
+ for (const [providerId, entry] of Object.entries(legacy)) {
31665
+ if (Object.prototype.hasOwnProperty.call(data, providerId)) {
31666
+ skippedProviderIds.push(providerId);
31667
+ continue;
31668
+ }
31669
+ data[providerId] = entry;
31670
+ migratedProviderIds.push(providerId);
31671
+ }
31672
+ return {
31673
+ data,
31674
+ migratedProviderIds,
31675
+ skippedProviderIds
31676
+ };
31677
+ }
31678
+ async function migrateLegacyAuthStore(options = {}) {
31679
+ const legacyPath = options.legacyPath ?? LEGACY_AUTH_PATH;
31680
+ const currentPath = options.currentPath ?? CURRENT_AUTH_PATH;
31681
+ if (!fs5.existsSync(legacyPath)) {
31682
+ return createMigrationResult({
31683
+ legacyPath,
31684
+ currentPath,
31685
+ status: "legacy-missing",
31686
+ migratedProviderIds: [],
31687
+ skippedProviderIds: [],
31688
+ shouldPrintNotice: false
31689
+ });
31690
+ }
31691
+ const legacy = readAuthStoreFile(legacyPath);
31692
+ const current = fs5.existsSync(currentPath) ? readAuthStoreFile(currentPath) : {};
31693
+ const merged = mergeAuthStoresNoOverwrite(legacy, current);
31694
+ if (merged.migratedProviderIds.length > 0) {
31695
+ const dir = path93.dirname(currentPath);
31696
+ ensureSafeDirectory(dir);
31697
+ writeAtomicSync(currentPath, `${JSON.stringify(merged.data, null, 2)}
31698
+ `, {
31699
+ mode: SECURE_FILE_MODE,
31700
+ fsync: true
31701
+ });
31702
+ }
31703
+ return createMigrationResult({
31704
+ legacyPath,
31705
+ currentPath,
31706
+ status: merged.migratedProviderIds.length > 0 ? "migrated" : "unchanged",
31707
+ migratedProviderIds: merged.migratedProviderIds,
31708
+ skippedProviderIds: merged.skippedProviderIds,
31709
+ shouldPrintNotice: options.notify !== false && merged.migratedProviderIds.length > 0
31710
+ });
31711
+ }
31712
+ function createMigrationResult(input) {
31713
+ return {
31714
+ legacyPath: input.legacyPath,
31715
+ currentPath: input.currentPath,
31716
+ migratedCount: input.migratedProviderIds.length,
31717
+ skippedCount: input.skippedProviderIds.length,
31718
+ migratedProviderIds: input.migratedProviderIds,
31719
+ skippedProviderIds: input.skippedProviderIds,
31720
+ shouldPrintNotice: input.shouldPrintNotice,
31721
+ status: input.status
31722
+ };
31723
+ }
31724
+ var DEFAULT_AUTH_VALIDATION_TIMEOUT_MS = 5e3;
31725
+ async function validateAnthropicCompatibleApiKey(request) {
31726
+ const controller = new AbortController();
31727
+ const timeout = setTimeout(() => {
31728
+ controller.abort();
31729
+ }, request.timeoutMs ?? DEFAULT_AUTH_VALIDATION_TIMEOUT_MS);
31730
+ try {
31731
+ const response = await fetch(buildMessagesUrl(request.baseUrl), {
31732
+ method: "POST",
31733
+ headers: {
31734
+ "anthropic-version": "2023-06-01",
31735
+ "content-type": "application/json",
31736
+ "x-api-key": request.apiKey
31737
+ },
31738
+ body: JSON.stringify({
31739
+ model: "claude-3-5-haiku-20241022",
31740
+ max_tokens: 1,
31741
+ messages: [
31742
+ {
31743
+ role: "user",
31744
+ content: "ping"
31745
+ }
31746
+ ]
31747
+ }),
31748
+ signal: controller.signal
31749
+ });
31750
+ if (response.ok) {
31751
+ return { providerId: request.providerId, status: "success" };
31752
+ }
31753
+ if (response.status === 401) {
31754
+ return { providerId: request.providerId, status: "unauthorized" };
31755
+ }
31756
+ if (response.status === 403) {
31757
+ return { providerId: request.providerId, status: "forbidden" };
31758
+ }
31759
+ if (response.status >= 500) {
31760
+ return {
31761
+ providerId: request.providerId,
31762
+ status: "server",
31763
+ detail: `HTTP ${response.status}`
31764
+ };
31765
+ }
31766
+ return {
31767
+ providerId: request.providerId,
31768
+ status: "unknown",
31769
+ detail: `HTTP ${response.status}`
31770
+ };
31771
+ } catch (error51) {
31772
+ if (isAbortError(error51)) {
31773
+ return { providerId: request.providerId, status: "timeout" };
31774
+ }
31775
+ return {
31776
+ providerId: request.providerId,
31777
+ status: "network",
31778
+ detail: error51 instanceof Error ? error51.message : String(error51)
31779
+ };
31780
+ } finally {
31781
+ clearTimeout(timeout);
31782
+ }
31783
+ }
31784
+ function isAuthValidationSuccess(result) {
31785
+ return result.status === "success";
31786
+ }
31787
+ function createAuthValidationError(result) {
31788
+ return new Error(formatAuthValidationFailureMessage(result));
31789
+ }
31790
+ function buildMessagesUrl(baseUrl) {
31791
+ return `${baseUrl.replace(/\/+$/, "")}/v1/messages`;
31792
+ }
31793
+ function isAbortError(error51) {
31794
+ return error51 instanceof DOMException && error51.name === "AbortError";
31795
+ }
31796
+ var CLAUDE_ZAI_PROVIDER_ID = "Claude Code with Z.AI GLM";
31797
+ var CLAUDE_KIMI_PROVIDER_ID = "Claude Code with Moonshot Kimi";
31798
+ var AUTH_PROVIDER_DEFINITIONS = {
31799
+ "claude-zai": {
31800
+ providerId: CLAUDE_ZAI_PROVIDER_ID,
31801
+ baseUrl: CLI_BACKENDS["claude-zai"].defaultEnv.ANTHROPIC_BASE_URL,
31802
+ env: CLI_BACKENDS["claude-zai"].defaultEnv
31803
+ },
31804
+ "claude-kimi": {
31805
+ providerId: CLAUDE_KIMI_PROVIDER_ID,
31806
+ baseUrl: CLI_BACKENDS["claude-kimi"].defaultEnv.ANTHROPIC_BASE_URL,
31807
+ env: CLI_BACKENDS["claude-kimi"].defaultEnv
31808
+ }
31809
+ };
31810
+ async function resolveAuthEnv(cli, deps) {
31811
+ const provider = AUTH_PROVIDER_DEFINITIONS[cli];
31812
+ if (!provider) return {};
31813
+ await migrateLegacyAuthStore();
31814
+ const auth = deps?.authService ?? createAuthService({ authPath: DEFAULT_AUTH_PATH });
31815
+ const token = await auth.getApiKey(provider.providerId);
31816
+ if (!token) {
31817
+ throw new Error(formatMissingAuthKeyMessage({ cli, providerId: provider.providerId }));
31818
+ }
31819
+ const validation = await validateAnthropicCompatibleApiKey({
31820
+ providerId: provider.providerId,
31821
+ apiKey: token,
31822
+ baseUrl: provider.baseUrl
31823
+ });
31824
+ if (!isAuthValidationSuccess(validation)) {
31825
+ const failure = {
31826
+ providerId: validation.providerId,
31827
+ status: validation.status,
31828
+ detail: validation.detail
31829
+ };
31830
+ throw createAuthValidationError(failure);
31831
+ }
31832
+ return { ...provider.env, ANTHROPIC_AUTH_TOKEN: token };
31833
+ }
31834
+ var GLOBAL_OPTIONS_VERSION = 1;
31835
+ var GLOBAL_OPTIONS_FILE_NAME = "settings.json";
31836
+ var LOCK_DIR_NAME = "settings.json.lock";
31837
+ var LOCK_OWNER_FILE_NAME = "owner";
31838
+ var TEMP_FILE_PREFIX = `.tmp-${GLOBAL_OPTIONS_FILE_NAME}-`;
31839
+ function createGlobalOptionsStore(deps = {}) {
31840
+ const dataDir = deps.dataDir ?? getFleetDataDir();
31841
+ const optionsPath = path93.join(dataDir, GLOBAL_OPTIONS_FILE_NAME);
31842
+ const lockDir = path93.join(dataDir, LOCK_DIR_NAME);
31843
+ const store2 = createDurableJsonStore({
31844
+ filePath: optionsPath,
31845
+ lockDir,
31846
+ lockOwnerFileName: LOCK_OWNER_FILE_NAME,
31847
+ sanitize: (value) => sanitizeGlobalOptionsData(value).data,
31848
+ sensitivity: "sensitive",
31849
+ timeoutMs: deps.timeoutMs,
31850
+ staleLockMs: deps.staleLockMs,
31851
+ tempCleanupPrefix: TEMP_FILE_PREFIX,
31852
+ now: deps.now
31853
+ });
31854
+ return {
31855
+ path: optionsPath,
31856
+ load: () => store2.load(),
31857
+ save: (data) => store2.save(sanitizeGlobalOptionsData(data).data),
31858
+ update: (mutate) => store2.update((current) => sanitizeGlobalOptionsData(mutate(current)).data)
31859
+ };
31860
+ }
31861
+ function createEmptyGlobalOptionsData() {
31862
+ return {
31863
+ version: GLOBAL_OPTIONS_VERSION
31864
+ };
31865
+ }
31866
+ function sanitizeGlobalOptionsData(value) {
31867
+ if (!isRecord3(value)) {
31868
+ return { data: createEmptyGlobalOptionsData(), changed: true };
31869
+ }
31870
+ if (value.version !== GLOBAL_OPTIONS_VERSION) {
31871
+ return { data: createEmptyGlobalOptionsData(), changed: true };
31872
+ }
31873
+ const data = {
31874
+ version: GLOBAL_OPTIONS_VERSION,
31875
+ ...typeof value.replaceSystemPrompt === "boolean" ? { replaceSystemPrompt: value.replaceSystemPrompt } : {},
31876
+ ...typeof value.enableMetaphor === "boolean" ? { enableMetaphor: value.enableMetaphor } : {}
31877
+ };
31878
+ const allowedKeys = /* @__PURE__ */ new Set(["version", "replaceSystemPrompt", "enableMetaphor"]);
31879
+ const changed = Object.keys(value).some((key) => !allowedKeys.has(key)) || "replaceSystemPrompt" in value && typeof value.replaceSystemPrompt !== "boolean" || "enableMetaphor" in value && typeof value.enableMetaphor !== "boolean";
31880
+ return { data, changed };
31881
+ }
31882
+ function isRecord3(value) {
31883
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31884
+ }
31885
+ function createGlobalOptionsService(deps = {}) {
31886
+ const store2 = deps.store ?? createGlobalOptionsStore({ dataDir: deps.dataDir });
31887
+ return {
31888
+ load: () => store2.load(),
31889
+ save: (data) => {
31890
+ store2.save(data);
31891
+ return store2.load();
31892
+ },
31893
+ update: (mutate) => updateGlobalOptions(store2, mutate)
31894
+ };
31895
+ }
31896
+ function updateGlobalOptions(store2, mutate) {
31897
+ return store2.update(mutate);
31898
+ }
31899
+
31900
+ // ../../packages/fleet-infra/dist/index.js
31901
+ function createInfraServices(deps = {}) {
31902
+ const globalOptionsService = createGlobalOptionsService();
31903
+ const authService = createAuthService({ authPath: deps.authPath ?? DEFAULT_AUTH_PATH });
31904
+ return {
31905
+ authService,
31906
+ globalOptionsService
31907
+ };
31908
+ }
31512
31909
  function readFleetConsoleRelease() {
31513
31910
  const requireFromHere = createRequire(import.meta.url);
31514
31911
  const packageJsonPath = requireFromHere.resolve("../package.json");
@@ -31522,17 +31919,34 @@ function readFleetConsoleRelease() {
31522
31919
  }
31523
31920
 
31524
31921
  // src/paths.ts
31525
- var LOCK_DIR_NAME = "fleet-console";
31922
+ var LOCK_DIR_NAME2 = "fleet-console";
31923
+ var CONSOLE_RUNTIME_DIR_NAME = "console";
31526
31924
  var LOCK_FILE_NAME = "console.lock";
31925
+ var CONSOLE_DATA_DIR_NAME = "console";
31926
+ var CONSOLE_STATE_FILE_NAME = "state.json";
31927
+ var CONSOLE_CAPTURES_DIR_NAME = "captures";
31527
31928
  function createConsolePaths(deps = {}) {
31528
31929
  const env = deps.env ?? process.env;
31529
31930
  const base = env.FLEET_CONSOLE_DIR ?? defaultConsoleBaseDir(deps);
31530
31931
  return { dir: base, lockFile: path93__default.join(base, LOCK_FILE_NAME) };
31531
31932
  }
31933
+ function createConsoleDataPaths(deps = {}) {
31934
+ const dir = path93__default.join(deps.fleetDataDir ?? getFleetDataDir(), CONSOLE_DATA_DIR_NAME);
31935
+ return {
31936
+ dir,
31937
+ stateFile: path93__default.join(dir, CONSOLE_STATE_FILE_NAME),
31938
+ capturesDir: path93__default.join(dir, CONSOLE_CAPTURES_DIR_NAME)
31939
+ };
31940
+ }
31532
31941
  function defaultConsoleBaseDir(deps) {
31942
+ let release;
31943
+ const channel = deps.channel ?? (release = readFleetConsoleRelease()).channel;
31944
+ if (channel === "local") {
31945
+ const packageRoot = deps.packageRoot ?? (release ??= readFleetConsoleRelease()).packageRoot;
31946
+ return path93__default.join(path93__default.resolve(packageRoot, "..", ".."), ".fleet", CONSOLE_RUNTIME_DIR_NAME);
31947
+ }
31533
31948
  const uid = deps.uid ?? (typeof process.getuid === "function" ? process.getuid() : 0);
31534
- const channel = deps.channel ?? readFleetConsoleRelease().channel;
31535
- return path93__default.join(os22__default.tmpdir(), `${LOCK_DIR_NAME}-${uid}-${channel}`);
31949
+ return path93__default.join(os22__default.tmpdir(), `${LOCK_DIR_NAME2}-${uid}-${channel}`);
31536
31950
  }
31537
31951
  var FLEET_PROTOCOL_GATE_PROMPT = String.raw`# Protocol Gate
31538
31952
 
@@ -31925,6 +32339,8 @@ function createClaudeFamilyCliDefinition(options) {
31925
32339
  lineTerminator: "\r",
31926
32340
  multilineStrategy: "paste-mode"
31927
32341
  },
32342
+ // Claude Code 계열은 세션 이름 변경 슬래시 명령 `/rename`을 지원한다.
32343
+ renameCommand: "/rename",
31928
32344
  terminalName: "xterm-256color"
31929
32345
  };
31930
32346
  }
@@ -31948,7 +32364,7 @@ var codexCli = {
31948
32364
  async createProfile(options) {
31949
32365
  const { bin, prefixArgs } = resolveBinary("codex", "CODEX_BIN", options.env);
31950
32366
  return {
31951
- args: [...prefixArgs, "--no-alt-screen", ...buildModelArgs2(options.model)],
32367
+ args: [...prefixArgs, ...buildResumeArgs(options.resumeSessionId), "--no-alt-screen", ...buildModelArgs2(options.model)],
31952
32368
  bin,
31953
32369
  binPrefixArgs: prefixArgs,
31954
32370
  cwd: options.cwd,
@@ -31960,10 +32376,15 @@ var codexCli = {
31960
32376
  lineTerminator: "\r",
31961
32377
  multilineStrategy: "paste-mode"
31962
32378
  },
32379
+ // Codex CLI도 세션 이름 변경 슬래시 명령 `/rename`을 지원한다.
32380
+ renameCommand: "/rename",
31963
32381
  terminalName: "xterm-256color"
31964
32382
  };
31965
32383
  }
31966
32384
  };
32385
+ function buildResumeArgs(resumeSessionId) {
32386
+ return resumeSessionId === void 0 ? [] : ["resume", resumeSessionId];
32387
+ }
31967
32388
  function buildModelArgs2(model) {
31968
32389
  return model === void 0 ? [] : ["--model", model];
31969
32390
  }
@@ -31980,7 +32401,8 @@ async function resolveAgentCliProfile(env, cwd, options = {}) {
31980
32401
  authService: options.authService,
31981
32402
  cwd,
31982
32403
  env,
31983
- model: options.model
32404
+ model: options.model,
32405
+ resumeSessionId: options.resumeSessionId
31984
32406
  });
31985
32407
  }
31986
32408
  function resolveAgentCliId(env, options = {}) {
@@ -32024,6 +32446,20 @@ var AGENT_CLI_INJECTION_CAPABILITIES = {
32024
32446
  function getAgentCliInjectionCapability(cliId) {
32025
32447
  return AGENT_CLI_INJECTION_CAPABILITIES[cliId];
32026
32448
  }
32449
+ function createSessionCaptureHookExec(deps) {
32450
+ const args = deps.tsxLoader ? [
32451
+ "--import",
32452
+ pathToFileURL(deps.tsxLoader).href,
32453
+ deps.entryPath,
32454
+ "hook",
32455
+ "capture-session",
32456
+ deps.provider
32457
+ ] : [deps.entryPath, "hook", "capture-session", deps.provider];
32458
+ return {
32459
+ command: deps.execPath ?? process6.execPath,
32460
+ args
32461
+ };
32462
+ }
32027
32463
  function buildHostCarrierModelDefaults2(config2) {
32028
32464
  const cliType = resolveAgentCliType2(config2.id, config2.defaultCliType);
32029
32465
  const cliDefaults = buildCarrierModelDefaults(config2, cliType);
@@ -32053,6 +32489,7 @@ function buildClaudeNativeSubagentPlan2(registry3) {
32053
32489
  function buildClaudeNativeArgs(context) {
32054
32490
  const systemPromptArg = context.replaceSystemPrompt ? "--system-prompt-file" : "--append-system-prompt-file";
32055
32491
  return [
32492
+ ...buildResumeArgs2(context.resumeSessionId),
32056
32493
  systemPromptArg,
32057
32494
  requireSystemPromptFile(context),
32058
32495
  ...context.pluginRoots.flatMap((pluginRoot) => [
@@ -32063,6 +32500,9 @@ function buildClaudeNativeArgs(context) {
32063
32500
  "--dangerously-skip-permissions"
32064
32501
  ];
32065
32502
  }
32503
+ function buildResumeArgs2(resumeSessionId) {
32504
+ return resumeSessionId === void 0 ? [] : ["--resume", resumeSessionId];
32505
+ }
32066
32506
  function buildClaudeMcpConfig(servers) {
32067
32507
  return JSON.stringify({
32068
32508
  mcpServers: Object.fromEntries(
@@ -32120,20 +32560,32 @@ function escapeTomlMultilineString(value) {
32120
32560
  });
32121
32561
  return result;
32122
32562
  }
32563
+ function buildPosixShellCommand(values) {
32564
+ return values.map(posixShellQuote).join(" ");
32565
+ }
32566
+ function posixShellQuote(value) {
32567
+ return `'${value.replaceAll("'", "'\\''")}'`;
32568
+ }
32123
32569
  var CODEX_TOOL_TIMEOUT_SEC = 1800;
32124
32570
  function buildCodexNativeArgs(context) {
32125
32571
  const profileName = requireCodexProfileName(context);
32126
32572
  const args = [
32573
+ ...buildResumeArgs3(context.resumeSessionId),
32127
32574
  "--enable",
32128
32575
  "plugins",
32129
32576
  "--enable",
32130
32577
  "child_agents_md",
32578
+ "--enable",
32579
+ "hooks",
32131
32580
  "--profile",
32132
32581
  profileName,
32133
32582
  "-c",
32134
32583
  'approval_policy="never"',
32135
32584
  "-c",
32136
- 'sandbox_mode="danger-full-access"'
32585
+ 'sandbox_mode="danger-full-access"',
32586
+ // hook 신뢰 프롬프트("Hooks need review")를 fleet 관리 세션에서 건너뛴다. bypass_hook_trust는
32587
+ // config 키가 아니라 전용 CLI 플래그이므로 -c override가 아닌 플래그로 전달해야 한다.
32588
+ "--dangerously-bypass-hook-trust"
32137
32589
  ];
32138
32590
  for (const server of context.mcpServers) {
32139
32591
  const prefix = `mcp_servers.${server.name}`;
@@ -32149,6 +32601,9 @@ function buildCodexNativeArgs(context) {
32149
32601
  }
32150
32602
  return args;
32151
32603
  }
32604
+ function buildResumeArgs3(resumeSessionId) {
32605
+ return resumeSessionId === void 0 ? [] : ["resume", resumeSessionId];
32606
+ }
32152
32607
  function requireCodexProfileName(context) {
32153
32608
  if (context.codexProfileName) return context.codexProfileName;
32154
32609
  throw new Error("Codex profile name is required for native injection");
@@ -32359,20 +32814,38 @@ function claudeHooks(options) {
32359
32814
  if (!hookExec) {
32360
32815
  throw new Error("Fleet Claude session hook command is required");
32361
32816
  }
32817
+ const userPromptSubmitExecs = [options.captureSessionHookExec, options.turnStartHookExec].filter((exec) => exec !== void 0);
32818
+ const stopExecs = [options.turnEndHookExec].filter((exec) => exec !== void 0);
32362
32819
  return {
32363
32820
  hooks: {
32364
32821
  SessionStart: [{
32365
- hooks: [{
32366
- // exec 형식: command는 직접 spawn되는 실행 파일, args는 셸 토크나이징 없이 그대로 전달된다.
32367
- // Windows cmd/powershell의 따옴표 규칙과 무관하게 동작하며 공백 포함 경로도 안전하다.
32368
- args: [...hookExec.args],
32369
- command: hookExec.command,
32370
- type: "command"
32822
+ hooks: [claudeCommandHook(hookExec)]
32823
+ }],
32824
+ ...userPromptSubmitExecs.length > 0 ? {
32825
+ UserPromptSubmit: [{
32826
+ hooks: userPromptSubmitExecs.map(claudeCommandHook)
32827
+ }]
32828
+ } : {},
32829
+ ...stopExecs.length > 0 ? {
32830
+ Stop: [{
32831
+ hooks: stopExecs.map(claudeCommandHook)
32371
32832
  }]
32372
- }]
32833
+ } : {}
32373
32834
  }
32374
32835
  };
32375
32836
  }
32837
+ function claudeCommandHook(hookExec) {
32838
+ if (!hookExec) {
32839
+ throw new Error("Fleet Claude session hook command is required");
32840
+ }
32841
+ return {
32842
+ // exec 형식: command는 직접 spawn되는 실행 파일, args는 셸 토크나이징 없이 그대로 전달된다.
32843
+ // Windows cmd/powershell의 따옴표 규칙과 무관하게 동작하며 공백 포함 경로도 안전하다.
32844
+ args: [...hookExec.args],
32845
+ command: hookExec.command,
32846
+ type: "command"
32847
+ };
32848
+ }
32376
32849
  function claudeAgentFile(subagent) {
32377
32850
  const frontmatter = [
32378
32851
  "---",
@@ -32882,7 +33355,7 @@ function homeMarketplaceTarget(fleetRoot) {
32882
33355
  }
32883
33356
  function projectMarketplaceTarget(cwd) {
32884
33357
  const projectFleetRoot = resolveProjectFleetRoot(cwd);
32885
- const hash2 = crypto3.createHash("sha256").update(path93__default.resolve(cwd, ".fleet")).digest("hex").slice(0, 12);
33358
+ const hash2 = crypto5.createHash("sha256").update(path93__default.resolve(cwd, ".fleet")).digest("hex").slice(0, 12);
32886
33359
  return {
32887
33360
  flat: true,
32888
33361
  name: `${FLEET_PROJECT_MARKETPLACE_NAME_PREFIX}-${hash2}`,
@@ -32908,7 +33381,6 @@ function renderPluginRoot(pluginRoot, bundle, options, fleetRoot) {
32908
33381
  const stagedPluginRoot = path93__default.join(stageParent, path93__default.basename(pluginRoot));
32909
33382
  try {
32910
33383
  ensurePrivateDir(stagedPluginRoot, stagedPluginRoot);
32911
- writePrivateJson(path93__default.join(stagedPluginRoot, ".codex-plugin", "plugin.json"), codexManifest(bundle), stagedPluginRoot);
32912
33384
  writePrivateJson(path93__default.join(stagedPluginRoot, ".claude-plugin", "plugin.json"), claudeManifest(bundle), stagedPluginRoot);
32913
33385
  switch (bundle.source) {
32914
33386
  case "asset":
@@ -32923,6 +33395,7 @@ function renderPluginRoot(pluginRoot, bundle, options, fleetRoot) {
32923
33395
  renderGlobalPluginRoot(stagedPluginRoot, fleetRoot);
32924
33396
  break;
32925
33397
  }
33398
+ writePrivateJson(path93__default.join(stagedPluginRoot, ".codex-plugin", "plugin.json"), codexManifest(bundle, stagedPluginRoot), stagedPluginRoot);
32926
33399
  removePrivatePath(pluginRoot, parentRoot);
32927
33400
  renameSync(stagedPluginRoot, pluginRoot);
32928
33401
  } finally {
@@ -32951,7 +33424,7 @@ function pruneMarketplaceRoot(target) {
32951
33424
  }
32952
33425
  }
32953
33426
  function buildContentHash(target) {
32954
- const hash2 = crypto3.createHash("sha256");
33427
+ const hash2 = crypto5.createHash("sha256");
32955
33428
  for (const filePath of listRenderableFiles(target)) {
32956
33429
  const relativePath2 = path93__default.relative(target.root, filePath);
32957
33430
  hash2.update(relativePath2);
@@ -33054,14 +33527,36 @@ function marketplacePluginPath(target, bundle) {
33054
33527
  if (target.flat) return `./${FLAT_PLUGIN_DIR_NAME}`;
33055
33528
  return `./${PLUGIN_BUNDLES_DIR_NAME}/${bundle.directoryName}`;
33056
33529
  }
33057
- function codexManifest(bundle) {
33530
+ function codexManifest(bundle, pluginRoot) {
33058
33531
  return {
33059
33532
  name: bundle.name,
33060
- version: "0.0.0",
33533
+ version: codexManifestVersion(bundle, pluginRoot),
33061
33534
  description: bundle.description,
33062
33535
  skills: "./skills/"
33063
33536
  };
33064
33537
  }
33538
+ function codexManifestVersion(bundle, pluginRoot) {
33539
+ const hash2 = crypto5.createHash("sha256");
33540
+ hash2.update(bundle.name);
33541
+ hash2.update("\0");
33542
+ hash2.update(bundle.description);
33543
+ hash2.update("\0");
33544
+ for (const filePath of listCodexEffectivePluginFiles(pluginRoot)) {
33545
+ const relativePath2 = path93__default.relative(pluginRoot, filePath);
33546
+ hash2.update(relativePath2);
33547
+ hash2.update("\0");
33548
+ hash2.update(readFileSync(filePath));
33549
+ hash2.update("\0");
33550
+ }
33551
+ return `0.0.0+${hash2.digest("hex").slice(0, 12)}`;
33552
+ }
33553
+ function listCodexEffectivePluginFiles(pluginRoot) {
33554
+ const skillsPath = path93__default.join(pluginRoot, "skills");
33555
+ if (!existsSync(skillsPath)) return [];
33556
+ const files = [];
33557
+ collectRenderableFiles(pluginRoot, skillsPath, files, /* @__PURE__ */ new Set());
33558
+ return files.sort();
33559
+ }
33065
33560
  function claudeManifest(bundle) {
33066
33561
  return {
33067
33562
  name: bundle.name,
@@ -33081,7 +33576,7 @@ async function injectAgentCliProfile(profile, options) {
33081
33576
  const injectTone = options.enableMetaphor ?? false;
33082
33577
  const endpoint = await options.dedicatedMcpSession.getEndpoint();
33083
33578
  const startupDefinitions = buildStartupNativeDefinitions(profile.id, options.carrierRuntime);
33084
- const tokenLabel = options.mcpSessionLabel ?? `agent:${profile.id}:${crypto3.randomUUID()}`;
33579
+ const tokenLabel = options.mcpSessionLabel ?? `agent:${profile.id}:${crypto5.randomUUID()}`;
33085
33580
  const tokens = await options.dedicatedMcpSession.issueSessionToken({ cwd: profile.cwd, label: tokenLabel });
33086
33581
  const mcpServers = buildAgentCliMcpServerConfigs(endpoint.servers, tokens);
33087
33582
  const doctrine = options.buildSystemPrompt(injectTone);
@@ -33095,11 +33590,18 @@ async function injectAgentCliProfile(profile, options) {
33095
33590
  cwd: profile.cwd,
33096
33591
  dataDir: options.dataDir,
33097
33592
  rootDir: options.pluginRootDir,
33593
+ captureSessionHookExec: options.captureSessionHookExec,
33594
+ turnStartHookExec: options.turnStartHookExec,
33595
+ turnEndHookExec: options.turnEndHookExec,
33098
33596
  hookExec: startupDefinitions.host === "claude" ? requireHookExec(options.hookExec) : void 0,
33099
33597
  withMarketplaceLock: options.withMarketplaceLock
33100
33598
  });
33101
33599
  const codexPluginKeys = plugin.codexRegistrations.map((registration) => `${registration.pluginName}@${registration.marketplaceName}`);
33102
- const codexProfile = profile.id === "codex" ? writeCodexFleetProfile(profile.env, doctrine, codexPluginKeys, (cleanup2) => tempCleanups.push(cleanup2)) : void 0;
33600
+ const codexProfile = profile.id === "codex" ? writeCodexFleetProfile(profile.env, doctrine, codexPluginKeys, {
33601
+ captureSessionHookExec: options.captureSessionHookExec,
33602
+ turnStartHookExec: options.turnStartHookExec,
33603
+ turnEndHookExec: options.turnEndHookExec
33604
+ }, (cleanup2) => tempCleanups.push(cleanup2)) : void 0;
33103
33605
  const launchWarnings = [];
33104
33606
  for (const registration of plugin.codexRegistrations) {
33105
33607
  const registrationWarning = await ensureCodexPluginRegistered(registration, {
@@ -33126,13 +33628,14 @@ async function injectAgentCliProfile(profile, options) {
33126
33628
  pluginRoot: plugin.pluginRoot,
33127
33629
  pluginRoots: plugin.pluginRoots,
33128
33630
  codexProfileName: codexProfile?.profileName,
33129
- replaceSystemPrompt: options.replaceSystemPrompt ?? true,
33631
+ replaceSystemPrompt: options.replaceSystemPrompt ?? false,
33632
+ resumeSessionId: options.resumeSessionId,
33130
33633
  systemPromptFile
33131
33634
  };
33132
33635
  const injectedArgs = buildAgentCliArgs(capability.builderId, context);
33133
33636
  return {
33134
33637
  ...profile,
33135
- args: [...profile.args, ...injectedArgs],
33638
+ args: mergeAgentCliArgs(profile, capability.builderId, context, injectedArgs),
33136
33639
  cleanup,
33137
33640
  launchWarnings: [...profile.launchWarnings ?? [], ...launchWarnings]
33138
33641
  };
@@ -33174,11 +33677,11 @@ function writeSystemPromptFile(cliId, systemPrompt, onCleanup) {
33174
33677
  chmodBestEffort2(filePath, SYSTEM_PROMPT_FILE_MODE);
33175
33678
  return filePath;
33176
33679
  }
33177
- function writeCodexFleetProfile(env, doctrine, pluginKeys, onCleanup) {
33680
+ function writeCodexFleetProfile(env, doctrine, pluginKeys, hookExecs, onCleanup) {
33178
33681
  const codexHome = env.CODEX_HOME ?? path93__default.join(env.HOME ?? os22__default.homedir(), ".codex");
33179
33682
  mkdirSync(codexHome, { recursive: true });
33180
33683
  pruneStaleCodexFleetProfiles(codexHome);
33181
- const profileName = `fleet-${crypto3.randomUUID()}`;
33684
+ const profileName = `fleet-${crypto5.randomUUID()}`;
33182
33685
  const profilePath = path93__default.join(codexHome, `${profileName}.config.toml`);
33183
33686
  onCleanup(() => rmBestEffort(profilePath));
33184
33687
  writeFileNoFollow2(profilePath, [
@@ -33193,11 +33696,33 @@ function writeCodexFleetProfile(env, doctrine, pluginKeys, onCleanup) {
33193
33696
  `[plugins."${escapeTomlBasicString2(pluginKey)}"]`,
33194
33697
  "enabled = true",
33195
33698
  ""
33196
- ])
33699
+ ]),
33700
+ ...codexHooksConfig(hookExecs)
33197
33701
  ].join("\n"));
33198
33702
  chmodBestEffort2(profilePath, SYSTEM_PROMPT_FILE_MODE);
33199
33703
  return { profileName, profilePath };
33200
33704
  }
33705
+ function codexHooksConfig(hookExecs) {
33706
+ const userPromptSubmitExecs = [hookExecs.captureSessionHookExec, hookExecs.turnStartHookExec].filter((exec) => exec !== void 0);
33707
+ const stopExecs = [hookExecs.turnEndHookExec].filter((exec) => exec !== void 0);
33708
+ if (userPromptSubmitExecs.length === 0 && stopExecs.length === 0) return [];
33709
+ const lines = ["[hooks]"];
33710
+ if (userPromptSubmitExecs.length > 0) {
33711
+ lines.push(`UserPromptSubmit = ${codexHookHandlersInline(userPromptSubmitExecs)}`);
33712
+ }
33713
+ if (stopExecs.length > 0) {
33714
+ lines.push(`Stop = ${codexHookHandlersInline(stopExecs)}`);
33715
+ }
33716
+ lines.push("");
33717
+ return lines;
33718
+ }
33719
+ function codexHookHandlersInline(execs) {
33720
+ const handlers = execs.map((exec) => {
33721
+ const command2 = buildPosixShellCommand([exec.command, ...exec.args]);
33722
+ return `{ type = "command", command = "${escapeTomlBasicString2(command2)}" }`;
33723
+ }).join(", ");
33724
+ return `[{ hooks = [${handlers}] }]`;
33725
+ }
33201
33726
  function writeFileNoFollow2(filePath, content) {
33202
33727
  const flags = constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW;
33203
33728
  const fd = openSync(filePath, flags, SYSTEM_PROMPT_FILE_MODE);
@@ -33270,6 +33795,24 @@ function buildAgentCliArgs(builderId, context) {
33270
33795
  return buildCodexNativeArgs(context);
33271
33796
  }
33272
33797
  }
33798
+ function mergeAgentCliArgs(profile, builderId, context, injectedArgs) {
33799
+ if (builderId !== "codex-native" || context.resumeSessionId === void 0) {
33800
+ return [...profile.args, ...injectedArgs];
33801
+ }
33802
+ const resumeSessionId = context.resumeSessionId;
33803
+ const prefixLength = profile.binPrefixArgs?.length ?? 0;
33804
+ const codexResumeArgs = ["resume", resumeSessionId];
33805
+ const injectedTail = injectedArgs.slice(codexResumeArgs.length);
33806
+ return [
33807
+ ...profile.args.slice(0, prefixLength),
33808
+ ...codexResumeArgs,
33809
+ ...profile.args.slice(prefixLength).filter((arg, index, args) => !isCodexResumeArgAt(args, index, resumeSessionId)),
33810
+ ...injectedTail
33811
+ ];
33812
+ }
33813
+ function isCodexResumeArgAt(args, index, resumeSessionId) {
33814
+ return args[index] === "resume" && args[index + 1] === resumeSessionId || args[index] === resumeSessionId && args[index - 1] === "resume";
33815
+ }
33273
33816
  function createOnceCleanup(cleanup) {
33274
33817
  let cleaned = false;
33275
33818
  return () => {
@@ -33453,389 +33996,6 @@ function applyMessagePolicy(text, policy) {
33453
33996
  submit: policy.lineTerminator
33454
33997
  };
33455
33998
  }
33456
- var FLEET_DATA_DIR_NAME = ".fleet";
33457
- function getFleetDataDir() {
33458
- return path93.join(os22.homedir(), FLEET_DATA_DIR_NAME);
33459
- }
33460
- function readAuthStoreFile(filePath) {
33461
- let fd;
33462
- try {
33463
- fd = fs5.openSync(filePath, fs5.constants.O_RDONLY | NOFOLLOW_FLAG);
33464
- if (!fs5.fstatSync(fd).isFile()) {
33465
- return {};
33466
- }
33467
- return JSON.parse(fs5.readFileSync(fd, "utf-8"));
33468
- } catch {
33469
- return {};
33470
- } finally {
33471
- if (fd !== void 0) {
33472
- try {
33473
- fs5.closeSync(fd);
33474
- } catch {
33475
- }
33476
- }
33477
- }
33478
- }
33479
- var DEFAULT_AUTH_PATH = path93.join(getFleetDataDir(), "auth.json");
33480
- var AUTH_LOCK_OWNER_FILE_NAME = "owner.json";
33481
- var AUTH_LOCK_TIMEOUT_MS = 5e3;
33482
- function createAuthService(deps = {}) {
33483
- const authPath = deps.authPath ?? DEFAULT_AUTH_PATH;
33484
- const lockDir = `${authPath}.lock`;
33485
- return {
33486
- // [HIGH #1] TOCTOU 수정: existsSync/read/check/delete/write 전체를 락 블록 안으로 이동
33487
- async deleteApiKey(providerId) {
33488
- let deleted = false;
33489
- withAuthLock(authPath, lockDir, () => {
33490
- if (!fs5.existsSync(authPath)) return;
33491
- const data = readAuthStoreFile(authPath);
33492
- if (!Object.prototype.hasOwnProperty.call(data, providerId)) return;
33493
- delete data[providerId];
33494
- writeAuthData(authPath, data);
33495
- deleted = true;
33496
- });
33497
- return deleted;
33498
- },
33499
- // [HIGH #2] 읽기 경로: fd 기반 O_RDONLY|O_NOFOLLOW+fstatSync isFile
33500
- async getApiKey(providerId) {
33501
- let fd;
33502
- try {
33503
- if (!fs5.existsSync(authPath)) {
33504
- return void 0;
33505
- }
33506
- fd = fs5.openSync(authPath, fs5.constants.O_RDONLY | NOFOLLOW_FLAG);
33507
- if (!fs5.fstatSync(fd).isFile()) {
33508
- return void 0;
33509
- }
33510
- const data = JSON.parse(fs5.readFileSync(fd, "utf-8"));
33511
- return typeof data[providerId]?.key === "string" ? data[providerId].key : void 0;
33512
- } catch {
33513
- return void 0;
33514
- } finally {
33515
- if (fd !== void 0) {
33516
- try {
33517
- fs5.closeSync(fd);
33518
- } catch {
33519
- }
33520
- }
33521
- }
33522
- },
33523
- // [HIGH #2] 읽기 경로: fd 기반 O_RDONLY|O_NOFOLLOW 통일
33524
- async listProviderIds() {
33525
- if (!fs5.existsSync(authPath)) {
33526
- return [];
33527
- }
33528
- try {
33529
- return Object.keys(readAuthStoreFile(authPath)).sort();
33530
- } catch {
33531
- return [];
33532
- }
33533
- },
33534
- async setApiKey(providerId, key) {
33535
- const dir = path93.dirname(authPath);
33536
- ensureSafeDirectory(dir);
33537
- withAuthLock(authPath, lockDir, () => {
33538
- const data = fs5.existsSync(authPath) ? readAuthStoreFile(authPath) : {};
33539
- data[providerId] = {
33540
- ...data[providerId] ?? {},
33541
- key
33542
- };
33543
- writeAuthData(authPath, data);
33544
- });
33545
- }
33546
- };
33547
- }
33548
- function withAuthLock(authPath, lockDir, operation) {
33549
- return withDirectoryLock(
33550
- {
33551
- lockDir,
33552
- ownerFileName: AUTH_LOCK_OWNER_FILE_NAME,
33553
- timeoutMs: AUTH_LOCK_TIMEOUT_MS
33554
- },
33555
- operation
33556
- );
33557
- }
33558
- function writeAuthData(authPath, data) {
33559
- writeAtomicSync(authPath, `${JSON.stringify(data, null, 2)}
33560
- `, {
33561
- mode: SECURE_FILE_MODE,
33562
- fsync: true
33563
- });
33564
- }
33565
- function formatMissingAuthKeyMessage(input) {
33566
- const cliHint = input.cli ? `cli '${input.cli}'` : "selected CLI";
33567
- return `Auth token not found for ${cliHint} (providerId: '${input.providerId}'). Run \`fleet auth login\` to register one.`;
33568
- }
33569
- function formatAuthValidationFailureMessage(input) {
33570
- const detail = input.detail ? ` Detail: ${input.detail}` : "";
33571
- if (input.status === "unauthorized") {
33572
- return `Auth token was rejected (providerId: '${input.providerId}'). Check the token and try again.${detail}`;
33573
- }
33574
- if (input.status === "forbidden") {
33575
- return `Auth token is not allowed for this provider (providerId: '${input.providerId}'). Check the token permissions.${detail}`;
33576
- }
33577
- if (input.status === "timeout") {
33578
- return `Auth token validation timed out (providerId: '${input.providerId}'). Check the connection and try again.${detail}`;
33579
- }
33580
- if (input.status === "network") {
33581
- return `Auth token validation failed due to a network error (providerId: '${input.providerId}'). Check the connection and try again.${detail}`;
33582
- }
33583
- if (input.status === "server") {
33584
- return `Auth token validation failed because the provider returned an error (providerId: '${input.providerId}'). Try again later.${detail}`;
33585
- }
33586
- return `Auth token validation failed (providerId: '${input.providerId}'). Check the token and try again.${detail}`;
33587
- }
33588
- var LEGACY_AUTH_PATH = path93.join(getFleetDataDir(), "agent", "auth.json");
33589
- var CURRENT_AUTH_PATH = path93.join(getFleetDataDir(), "auth.json");
33590
- function mergeAuthStoresNoOverwrite(legacy, current) {
33591
- const data = { ...current };
33592
- const migratedProviderIds = [];
33593
- const skippedProviderIds = [];
33594
- for (const [providerId, entry] of Object.entries(legacy)) {
33595
- if (Object.prototype.hasOwnProperty.call(data, providerId)) {
33596
- skippedProviderIds.push(providerId);
33597
- continue;
33598
- }
33599
- data[providerId] = entry;
33600
- migratedProviderIds.push(providerId);
33601
- }
33602
- return {
33603
- data,
33604
- migratedProviderIds,
33605
- skippedProviderIds
33606
- };
33607
- }
33608
- async function migrateLegacyAuthStore(options = {}) {
33609
- const legacyPath = options.legacyPath ?? LEGACY_AUTH_PATH;
33610
- const currentPath = options.currentPath ?? CURRENT_AUTH_PATH;
33611
- if (!fs5.existsSync(legacyPath)) {
33612
- return createMigrationResult({
33613
- legacyPath,
33614
- currentPath,
33615
- status: "legacy-missing",
33616
- migratedProviderIds: [],
33617
- skippedProviderIds: [],
33618
- shouldPrintNotice: false
33619
- });
33620
- }
33621
- const legacy = readAuthStoreFile(legacyPath);
33622
- const current = fs5.existsSync(currentPath) ? readAuthStoreFile(currentPath) : {};
33623
- const merged = mergeAuthStoresNoOverwrite(legacy, current);
33624
- if (merged.migratedProviderIds.length > 0) {
33625
- const dir = path93.dirname(currentPath);
33626
- ensureSafeDirectory(dir);
33627
- writeAtomicSync(currentPath, `${JSON.stringify(merged.data, null, 2)}
33628
- `, {
33629
- mode: SECURE_FILE_MODE,
33630
- fsync: true
33631
- });
33632
- }
33633
- return createMigrationResult({
33634
- legacyPath,
33635
- currentPath,
33636
- status: merged.migratedProviderIds.length > 0 ? "migrated" : "unchanged",
33637
- migratedProviderIds: merged.migratedProviderIds,
33638
- skippedProviderIds: merged.skippedProviderIds,
33639
- shouldPrintNotice: options.notify !== false && merged.migratedProviderIds.length > 0
33640
- });
33641
- }
33642
- function createMigrationResult(input) {
33643
- return {
33644
- legacyPath: input.legacyPath,
33645
- currentPath: input.currentPath,
33646
- migratedCount: input.migratedProviderIds.length,
33647
- skippedCount: input.skippedProviderIds.length,
33648
- migratedProviderIds: input.migratedProviderIds,
33649
- skippedProviderIds: input.skippedProviderIds,
33650
- shouldPrintNotice: input.shouldPrintNotice,
33651
- status: input.status
33652
- };
33653
- }
33654
- var DEFAULT_AUTH_VALIDATION_TIMEOUT_MS = 5e3;
33655
- async function validateAnthropicCompatibleApiKey(request) {
33656
- const controller = new AbortController();
33657
- const timeout = setTimeout(() => {
33658
- controller.abort();
33659
- }, request.timeoutMs ?? DEFAULT_AUTH_VALIDATION_TIMEOUT_MS);
33660
- try {
33661
- const response = await fetch(buildMessagesUrl(request.baseUrl), {
33662
- method: "POST",
33663
- headers: {
33664
- "anthropic-version": "2023-06-01",
33665
- "content-type": "application/json",
33666
- "x-api-key": request.apiKey
33667
- },
33668
- body: JSON.stringify({
33669
- model: "claude-3-5-haiku-20241022",
33670
- max_tokens: 1,
33671
- messages: [
33672
- {
33673
- role: "user",
33674
- content: "ping"
33675
- }
33676
- ]
33677
- }),
33678
- signal: controller.signal
33679
- });
33680
- if (response.ok) {
33681
- return { providerId: request.providerId, status: "success" };
33682
- }
33683
- if (response.status === 401) {
33684
- return { providerId: request.providerId, status: "unauthorized" };
33685
- }
33686
- if (response.status === 403) {
33687
- return { providerId: request.providerId, status: "forbidden" };
33688
- }
33689
- if (response.status >= 500) {
33690
- return {
33691
- providerId: request.providerId,
33692
- status: "server",
33693
- detail: `HTTP ${response.status}`
33694
- };
33695
- }
33696
- return {
33697
- providerId: request.providerId,
33698
- status: "unknown",
33699
- detail: `HTTP ${response.status}`
33700
- };
33701
- } catch (error51) {
33702
- if (isAbortError(error51)) {
33703
- return { providerId: request.providerId, status: "timeout" };
33704
- }
33705
- return {
33706
- providerId: request.providerId,
33707
- status: "network",
33708
- detail: error51 instanceof Error ? error51.message : String(error51)
33709
- };
33710
- } finally {
33711
- clearTimeout(timeout);
33712
- }
33713
- }
33714
- function isAuthValidationSuccess(result) {
33715
- return result.status === "success";
33716
- }
33717
- function createAuthValidationError(result) {
33718
- return new Error(formatAuthValidationFailureMessage(result));
33719
- }
33720
- function buildMessagesUrl(baseUrl) {
33721
- return `${baseUrl.replace(/\/+$/, "")}/v1/messages`;
33722
- }
33723
- function isAbortError(error51) {
33724
- return error51 instanceof DOMException && error51.name === "AbortError";
33725
- }
33726
- var CLAUDE_ZAI_PROVIDER_ID = "Claude Code with Z.AI GLM";
33727
- var CLAUDE_KIMI_PROVIDER_ID = "Claude Code with Moonshot Kimi";
33728
- var AUTH_PROVIDER_DEFINITIONS = {
33729
- "claude-zai": {
33730
- providerId: CLAUDE_ZAI_PROVIDER_ID,
33731
- baseUrl: CLI_BACKENDS["claude-zai"].defaultEnv.ANTHROPIC_BASE_URL,
33732
- env: CLI_BACKENDS["claude-zai"].defaultEnv
33733
- },
33734
- "claude-kimi": {
33735
- providerId: CLAUDE_KIMI_PROVIDER_ID,
33736
- baseUrl: CLI_BACKENDS["claude-kimi"].defaultEnv.ANTHROPIC_BASE_URL,
33737
- env: CLI_BACKENDS["claude-kimi"].defaultEnv
33738
- }
33739
- };
33740
- async function resolveAuthEnv(cli, deps) {
33741
- const provider = AUTH_PROVIDER_DEFINITIONS[cli];
33742
- if (!provider) return {};
33743
- await migrateLegacyAuthStore();
33744
- const auth = deps?.authService ?? createAuthService({ authPath: DEFAULT_AUTH_PATH });
33745
- const token = await auth.getApiKey(provider.providerId);
33746
- if (!token) {
33747
- throw new Error(formatMissingAuthKeyMessage({ cli, providerId: provider.providerId }));
33748
- }
33749
- const validation = await validateAnthropicCompatibleApiKey({
33750
- providerId: provider.providerId,
33751
- apiKey: token,
33752
- baseUrl: provider.baseUrl
33753
- });
33754
- if (!isAuthValidationSuccess(validation)) {
33755
- const failure = {
33756
- providerId: validation.providerId,
33757
- status: validation.status,
33758
- detail: validation.detail
33759
- };
33760
- throw createAuthValidationError(failure);
33761
- }
33762
- return { ...provider.env, ANTHROPIC_AUTH_TOKEN: token };
33763
- }
33764
- var GLOBAL_OPTIONS_VERSION = 1;
33765
- var GLOBAL_OPTIONS_FILE_NAME = "settings.json";
33766
- var LOCK_DIR_NAME2 = "settings.json.lock";
33767
- var LOCK_OWNER_FILE_NAME = "owner";
33768
- var TEMP_FILE_PREFIX = `.tmp-${GLOBAL_OPTIONS_FILE_NAME}-`;
33769
- function createGlobalOptionsStore(deps = {}) {
33770
- const dataDir = deps.dataDir ?? getFleetDataDir();
33771
- const optionsPath = path93.join(dataDir, GLOBAL_OPTIONS_FILE_NAME);
33772
- const lockDir = path93.join(dataDir, LOCK_DIR_NAME2);
33773
- const store2 = createDurableJsonStore({
33774
- filePath: optionsPath,
33775
- lockDir,
33776
- lockOwnerFileName: LOCK_OWNER_FILE_NAME,
33777
- sanitize: (value) => sanitizeGlobalOptionsData(value).data,
33778
- sensitivity: "sensitive",
33779
- timeoutMs: deps.timeoutMs,
33780
- staleLockMs: deps.staleLockMs,
33781
- tempCleanupPrefix: TEMP_FILE_PREFIX,
33782
- now: deps.now
33783
- });
33784
- return {
33785
- path: optionsPath,
33786
- load: () => store2.load(),
33787
- save: (data) => store2.save(sanitizeGlobalOptionsData(data).data),
33788
- update: (mutate) => store2.update((current) => sanitizeGlobalOptionsData(mutate(current)).data)
33789
- };
33790
- }
33791
- function createEmptyGlobalOptionsData() {
33792
- return {
33793
- version: GLOBAL_OPTIONS_VERSION
33794
- };
33795
- }
33796
- function sanitizeGlobalOptionsData(value) {
33797
- if (!isRecord3(value)) {
33798
- return { data: createEmptyGlobalOptionsData(), changed: true };
33799
- }
33800
- if (value.version !== GLOBAL_OPTIONS_VERSION) {
33801
- return { data: createEmptyGlobalOptionsData(), changed: true };
33802
- }
33803
- const data = {
33804
- version: GLOBAL_OPTIONS_VERSION,
33805
- ...typeof value.replaceSystemPrompt === "boolean" ? { replaceSystemPrompt: value.replaceSystemPrompt } : {},
33806
- ...typeof value.enableMetaphor === "boolean" ? { enableMetaphor: value.enableMetaphor } : {}
33807
- };
33808
- const allowedKeys = /* @__PURE__ */ new Set(["version", "replaceSystemPrompt", "enableMetaphor"]);
33809
- const changed = Object.keys(value).some((key) => !allowedKeys.has(key)) || "replaceSystemPrompt" in value && typeof value.replaceSystemPrompt !== "boolean" || "enableMetaphor" in value && typeof value.enableMetaphor !== "boolean";
33810
- return { data, changed };
33811
- }
33812
- function isRecord3(value) {
33813
- return typeof value === "object" && value !== null && !Array.isArray(value);
33814
- }
33815
- function createGlobalOptionsService(deps = {}) {
33816
- const store2 = deps.store ?? createGlobalOptionsStore({ dataDir: deps.dataDir });
33817
- return {
33818
- load: () => store2.load(),
33819
- save: (data) => {
33820
- store2.save(data);
33821
- return store2.load();
33822
- },
33823
- update: (mutate) => updateGlobalOptions(store2, mutate)
33824
- };
33825
- }
33826
- function updateGlobalOptions(store2, mutate) {
33827
- return store2.update(mutate);
33828
- }
33829
-
33830
- // ../../packages/fleet-infra/dist/index.js
33831
- function createInfraServices(deps = {}) {
33832
- const globalOptionsService = createGlobalOptionsService();
33833
- const authService = createAuthService({ authPath: deps.authPath ?? DEFAULT_AUTH_PATH });
33834
- return {
33835
- authService,
33836
- globalOptionsService
33837
- };
33838
- }
33839
33999
  var FLEET_WIKI_ENTRY_BEGIN = "<<<FLEET_WIKI_ENTRY_BEGIN";
33840
34000
  var FLEET_WIKI_ENTRY_END = "<<<FLEET_WIKI_ENTRY_END>>>";
33841
34001
  var FLEET_WIKI_RAW_SOURCE_BEGIN = "<<<FLEET_WIKI_RAW_SOURCE_BEGIN";
@@ -34862,7 +35022,7 @@ function stripLeadingFrontmatter(body) {
34862
35022
  return stripped ? next : body;
34863
35023
  }
34864
35024
  function computeContentHash(content) {
34865
- return crypto3.createHash("sha256").update(content, "utf8").digest("hex").slice(0, 8);
35025
+ return crypto5.createHash("sha256").update(content, "utf8").digest("hex").slice(0, 8);
34866
35026
  }
34867
35027
  function assertWithinRawDir(absolutePath, paths) {
34868
35028
  const relative = path93__default.relative(paths.rawDir, absolutePath);
@@ -35075,7 +35235,7 @@ async function writeAtomic(filePath, content, paths) {
35075
35235
  await ensureMemoryRoot(paths);
35076
35236
  const tempPath = path93__default.join(
35077
35237
  path93__default.dirname(filePath),
35078
- `.tmp-${process.pid}-${Date.now()}-${crypto3.randomUUID()}-${os22__default.hostname()}`
35238
+ `.tmp-${process.pid}-${Date.now()}-${crypto5.randomUUID()}-${os22__default.hostname()}`
35079
35239
  );
35080
35240
  await writeFile(tempPath, content, "utf8");
35081
35241
  await rename(tempPath, filePath);
@@ -37612,7 +37772,7 @@ function parseOptionalIsoDate(value) {
37612
37772
  return Number.isFinite(timestamp) ? timestamp : null;
37613
37773
  }
37614
37774
  function hashBodyForComparison(body) {
37615
- return crypto3.createHash("sha256").update(body.replace(/\s+/g, " ").trim(), "utf8").digest("hex").slice(0, 8);
37775
+ return crypto5.createHash("sha256").update(body.replace(/\s+/g, " ").trim(), "utf8").digest("hex").slice(0, 8);
37616
37776
  }
37617
37777
  function hasConflictingStatuses(entries) {
37618
37778
  const statuses = new Set(entries.map((entry) => entry.status).filter((status) => status !== "unknown"));
@@ -39795,6 +39955,420 @@ function buildWikiToolSpec(config2, usage) {
39795
39955
  };
39796
39956
  }
39797
39957
 
39958
+ // src/carrier-settings-routes.ts
39959
+ var SUBAGENT_CLI_TYPES = /* @__PURE__ */ new Set(["claude", "claude-zai", "claude-kimi"]);
39960
+ var TASKFORCE_MIN_BACKENDS = 2;
39961
+ function createCarrierSettingsRouter(deps) {
39962
+ const controller = createStatusOverlayController(deps.registry);
39963
+ return async function handleCarrierSettingsRoute(context) {
39964
+ const { req, res, pathname } = context;
39965
+ if (pathname === "/carrier-settings/state") {
39966
+ if (req.method !== "GET") {
39967
+ deps.writeJson(res, 405, { error: "Method not allowed" });
39968
+ return true;
39969
+ }
39970
+ deps.writeJson(res, 200, buildCarrierSettingsState(deps.registry));
39971
+ return true;
39972
+ }
39973
+ if (pathname === "/carrier-settings/options") {
39974
+ if (req.method !== "GET") {
39975
+ deps.writeJson(res, 405, { error: "Method not allowed" });
39976
+ return true;
39977
+ }
39978
+ deps.writeJson(res, 200, buildCarrierSettingsOptions());
39979
+ return true;
39980
+ }
39981
+ const mutation = parseCarrierMutation(pathname);
39982
+ if (!mutation) return false;
39983
+ await handleCarrierMutation(req, res, deps, controller, mutation);
39984
+ return true;
39985
+ };
39986
+ }
39987
+ function buildCarrierSettingsState(registry3) {
39988
+ const defaultsByCarrier = buildDefaultsByCarrier(registry3);
39989
+ const snapshot = readCarriersSnapshot(defaultsByCarrier);
39990
+ return {
39991
+ generation: snapshot.generation,
39992
+ carriers: getRegisteredOrder(registry3).map((carrierId) => {
39993
+ const config2 = requireCarrierConfig(registry3, carrierId);
39994
+ const resolved = snapshot.carriers[carrierId] ?? fallbackResolvedState(config2);
39995
+ return toCarrierSettingsCarrier(registry3, config2, resolved);
39996
+ })
39997
+ };
39998
+ }
39999
+ function buildCarrierSettingsOptions() {
40000
+ return {
40001
+ cliTypes: getCliTypes().map(toCliOption),
40002
+ taskForceConstraints: { minBackends: TASKFORCE_MIN_BACKENDS }
40003
+ };
40004
+ }
40005
+ async function handleCarrierMutation(req, res, deps, controller, mutation) {
40006
+ const method = req.method ?? "GET";
40007
+ const expectedMethod = mutation.kind === "display-name" ? "PATCH" : mutation.kind === "taskforce-backend" || mutation.kind === "taskforce-all" ? method : "PUT";
40008
+ if (!isExpectedCarrierMethod(mutation, method, expectedMethod)) {
40009
+ deps.writeJson(res, 405, { error: "Method not allowed" });
40010
+ return;
40011
+ }
40012
+ if (!deps.isAuthorized(req)) {
40013
+ deps.writeJson(res, 401, { error: "unauthorized" });
40014
+ return;
40015
+ }
40016
+ if (!isJsonRequest(req)) {
40017
+ deps.writeJson(res, 415, { error: "unsupported_media_type" });
40018
+ return;
40019
+ }
40020
+ if (!getRegisteredCarrierConfig(deps.registry, mutation.carrierId)) {
40021
+ deps.writeJson(res, 404, { error: "carrier_not_found" });
40022
+ return;
40023
+ }
40024
+ if (mutation.kind === "taskforce-backend" && !isCliType(mutation.cliType)) {
40025
+ deps.writeJson(res, 400, { error: "invalid_cli_type" });
40026
+ return;
40027
+ }
40028
+ try {
40029
+ if (mutation.kind === "cli") {
40030
+ const body2 = await readRequiredJsonBody(req, res, deps);
40031
+ if (!body2.ok) return;
40032
+ await mutateCarrierCli(res, deps, controller, mutation.carrierId, body2.body);
40033
+ return;
40034
+ }
40035
+ if (mutation.kind === "model") {
40036
+ const body2 = await readRequiredJsonBody(req, res, deps);
40037
+ if (!body2.ok) return;
40038
+ await mutateCarrierModel(res, deps, mutation.carrierId, body2.body);
40039
+ return;
40040
+ }
40041
+ if (mutation.kind === "display-name") {
40042
+ const body2 = await readRequiredJsonBody(req, res, deps);
40043
+ if (!body2.ok) return;
40044
+ mutateDisplayName(res, deps, mutation.carrierId, body2.body);
40045
+ return;
40046
+ }
40047
+ if (mutation.kind === "agent-mode") {
40048
+ const body2 = await readRequiredJsonBody(req, res, deps);
40049
+ if (!body2.ok) return;
40050
+ mutateAgentMode(res, deps, mutation.carrierId, body2.body);
40051
+ return;
40052
+ }
40053
+ if (mutation.kind === "taskforce-backend") {
40054
+ if (method === "DELETE") {
40055
+ const body3 = await readRequiredJsonBody(req, res, deps);
40056
+ if (!body3.ok) return;
40057
+ resetTaskForceModelSelection(mutation.carrierId, mutation.cliType);
40058
+ refreshTaskForceConfiguredCarriers(deps.registry);
40059
+ writeMutationState(res, deps);
40060
+ return;
40061
+ }
40062
+ const body2 = await readRequiredJsonBody(req, res, deps);
40063
+ if (!body2.ok) return;
40064
+ mutateTaskForceBackend(res, deps, mutation.carrierId, mutation.cliType, body2.body);
40065
+ return;
40066
+ }
40067
+ const body = await readRequiredJsonBody(req, res, deps);
40068
+ if (!body.ok) return;
40069
+ resetCarrierTaskForceConfig(mutation.carrierId);
40070
+ refreshTaskForceConfiguredCarriers(deps.registry);
40071
+ writeMutationState(res, deps);
40072
+ } catch (error51) {
40073
+ deps.writeJson(res, 400, { error: error51 instanceof Error ? error51.message : "invalid_request" });
40074
+ }
40075
+ }
40076
+ async function mutateCarrierCli(res, deps, controller, carrierId, body) {
40077
+ const cliType = readCliType(body.cliType);
40078
+ if (!cliType) {
40079
+ deps.writeJson(res, 400, { error: "invalid_cli_type" });
40080
+ return;
40081
+ }
40082
+ await controller.changeCliType(carrierId, cliType);
40083
+ if (!SUBAGENT_CLI_TYPES.has(cliType)) {
40084
+ const resolved = readCarriersSnapshot(buildDefaultsByCarrier(deps.registry)).carriers[carrierId];
40085
+ if (resolved?.agentMode === "subagent") {
40086
+ const config2 = requireCarrierConfig(deps.registry, carrierId);
40087
+ updateCarrierAgentModeAtomically(carrierId, "cli", config2.defaultAgentMode ?? "cli");
40088
+ }
40089
+ }
40090
+ writeMutationState(res, deps);
40091
+ }
40092
+ async function mutateCarrierModel(res, deps, carrierId, body) {
40093
+ const config2 = requireCarrierConfig(deps.registry, carrierId);
40094
+ const cliType = resolveAgentCliType2(carrierId, config2.defaultCliType);
40095
+ const selection = readSelection(cliType, body);
40096
+ if (!selection) {
40097
+ deps.writeJson(res, 400, { error: "invalid_model_selection" });
40098
+ return;
40099
+ }
40100
+ await updateAgentCliSelection(carrierId, cliType, selection);
40101
+ notifyStatusUpdate(deps.registry);
40102
+ writeMutationState(res, deps);
40103
+ }
40104
+ function mutateDisplayName(res, deps, carrierId, body) {
40105
+ const displayName = normalizeCarrierDisplayNameInput(body.displayName);
40106
+ if (displayName === null) {
40107
+ deps.writeJson(res, 400, { error: "invalid_display_name" });
40108
+ return;
40109
+ }
40110
+ updateCarrierDisplayName(carrierId, displayName, getCarrierSourceDisplayName(deps.registry, carrierId));
40111
+ notifyStatusUpdate(deps.registry);
40112
+ writeMutationState(res, deps);
40113
+ }
40114
+ function mutateAgentMode(res, deps, carrierId, body) {
40115
+ const config2 = requireCarrierConfig(deps.registry, carrierId);
40116
+ const agentMode = body.agentMode;
40117
+ if (agentMode !== "cli" && agentMode !== "subagent") {
40118
+ deps.writeJson(res, 400, { error: "invalid_agent_mode" });
40119
+ return;
40120
+ }
40121
+ const cliType = resolveAgentCliType2(carrierId, config2.defaultCliType);
40122
+ if (agentMode === "subagent" && !SUBAGENT_CLI_TYPES.has(cliType)) {
40123
+ deps.writeJson(res, 400, { error: "subagent_unsupported" });
40124
+ return;
40125
+ }
40126
+ updateCarrierAgentModeAtomically(carrierId, agentMode, config2.defaultAgentMode ?? "cli");
40127
+ if (agentMode === "subagent") refreshTaskForceConfiguredCarriers(deps.registry);
40128
+ notifyStatusUpdate(deps.registry);
40129
+ writeMutationState(res, deps);
40130
+ }
40131
+ function mutateTaskForceBackend(res, deps, carrierId, cliType, body) {
40132
+ const selection = readSelection(cliType, body);
40133
+ if (!selection) {
40134
+ deps.writeJson(res, 400, { error: "invalid_model_selection" });
40135
+ return;
40136
+ }
40137
+ updateTaskForceBackendAtomically(carrierId, cliType, selection);
40138
+ refreshTaskForceConfiguredCarriers(deps.registry);
40139
+ notifyStatusUpdate(deps.registry);
40140
+ writeMutationState(res, deps);
40141
+ }
40142
+ function writeMutationState(res, deps) {
40143
+ const response = { state: buildCarrierSettingsState(deps.registry) };
40144
+ deps.writeJson(res, 200, response);
40145
+ }
40146
+ async function readRequiredJsonBody(req, res, deps) {
40147
+ const body = await deps.readJsonBody(req);
40148
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
40149
+ deps.writeJson(res, 400, { error: "invalid_json" });
40150
+ return { ok: false };
40151
+ }
40152
+ return { ok: true, body };
40153
+ }
40154
+ function buildDefaultsByCarrier(registry3) {
40155
+ return Object.fromEntries(
40156
+ getRegisteredOrder(registry3).map((carrierId) => {
40157
+ const config2 = requireCarrierConfig(registry3, carrierId);
40158
+ return [carrierId, {
40159
+ cliType: config2.defaultCliType,
40160
+ ...config2.defaultAgentMode ? { defaultAgentMode: config2.defaultAgentMode } : {},
40161
+ ...buildCarrierModelDefaults(config2, config2.defaultCliType)
40162
+ }];
40163
+ })
40164
+ );
40165
+ }
40166
+ function toCarrierSettingsCarrier(registry3, config2, resolved) {
40167
+ const cliType = resolved.agentCliType ?? config2.defaultCliType;
40168
+ const selection = resolved.agentCli[cliType] ?? readDefaultSelection(cliType);
40169
+ const taskForceBackends = toTaskForceBackends(resolved.taskforce);
40170
+ return {
40171
+ carrierId: config2.id,
40172
+ displayName: resolved.displayName ?? getCarrierSourceDisplayName(registry3, config2.id),
40173
+ sourceDisplayName: getCarrierSourceDisplayName(registry3, config2.id),
40174
+ role: config2.carrierMetadata?.title ?? config2.displayName,
40175
+ roleDescription: config2.carrierMetadata?.summary ?? "",
40176
+ ...config2.carrierMetadata?.category ? { category: config2.carrierMetadata.category } : {},
40177
+ slot: config2.slot,
40178
+ cliType,
40179
+ defaultCliType: config2.defaultCliType,
40180
+ model: selection.model,
40181
+ ...selection.effort ? { effort: selection.effort } : {},
40182
+ agentMode: resolved.agentMode,
40183
+ subagentMode: resolved.agentMode === "subagent",
40184
+ taskForceBackendCount: taskForceBackends.length,
40185
+ taskforce: { backends: taskForceBackends }
40186
+ };
40187
+ }
40188
+ function toTaskForceBackends(taskforce) {
40189
+ return TASKFORCE_CLI_TYPES.map((cliType) => {
40190
+ const selection = taskforce[cliType];
40191
+ if (!selection) return null;
40192
+ return {
40193
+ cliType,
40194
+ model: selection.model,
40195
+ ...selection.effort ? { effort: selection.effort } : {}
40196
+ };
40197
+ }).filter((backend) => backend !== null);
40198
+ }
40199
+ function toCliOption(cliType) {
40200
+ const provider = getProviderModels(cliType);
40201
+ return {
40202
+ id: cliType,
40203
+ displayName: CLI_DISPLAY_NAMES[cliType] ?? provider.name,
40204
+ supportsSubagent: SUBAGENT_CLI_TYPES.has(cliType),
40205
+ models: provider.models.map((model) => {
40206
+ const effort = getEffort(cliType, model.modelId);
40207
+ return {
40208
+ modelId: model.modelId,
40209
+ name: model.name,
40210
+ ...effort.supported ? { effort: { levels: effort.levels, default: effort.default } } : {}
40211
+ };
40212
+ }),
40213
+ defaultModel: provider.defaultModel ?? provider.models[0]?.modelId
40214
+ };
40215
+ }
40216
+ function createStatusOverlayController(registry3) {
40217
+ return new StatusOverlayController({
40218
+ getEntries: () => [],
40219
+ getRegisteredOrder: () => getRegisteredOrder(registry3),
40220
+ getCarrierConfig: (carrierId) => getRegisteredCarrierConfig(registry3, carrierId),
40221
+ getResolvedCliType: (carrierId) => {
40222
+ const config2 = getRegisteredCarrierConfig(registry3, carrierId);
40223
+ return config2 ? resolveAgentCliType2(carrierId, config2.defaultCliType) : void 0;
40224
+ },
40225
+ getCurrentModelSelection: (carrierId) => {
40226
+ return readCurrentModelSelection(registry3, carrierId);
40227
+ },
40228
+ getAvailableModels: (cliType) => {
40229
+ const provider = getProviderModels(cliType);
40230
+ return {
40231
+ defaultModel: provider.defaultModel,
40232
+ models: provider.models.map((model) => ({
40233
+ modelId: model.modelId,
40234
+ name: model.name,
40235
+ effort: getEffort(cliType, model.modelId)
40236
+ }))
40237
+ };
40238
+ },
40239
+ getEffort: (cliType, modelId) => getEffort(cliType, modelId),
40240
+ getAgentCliSelection: (carrierId, cliType) => getAgentCliSelection(carrierId, cliType),
40241
+ saveAgentCliSelection,
40242
+ updateCarrierCliType: (carrierId, cliType) => updateCarrierCliType(registry3, carrierId),
40243
+ applyAgentCliTypeSelectionUpdate,
40244
+ refreshAgentPanel: () => void 0,
40245
+ syncModelConfig: () => void 0,
40246
+ notifyStatusUpdate: () => notifyStatusUpdate(registry3)
40247
+ });
40248
+ }
40249
+ function updateCarrierAgentModeAtomically(carrierId, agentMode, defaultAgentMode) {
40250
+ if (agentMode === "cli") {
40251
+ setCarrierAgentMode(carrierId, false, defaultAgentMode);
40252
+ return;
40253
+ }
40254
+ updateCarriers((states) => {
40255
+ const carriers = { ...states.carriers ?? {} };
40256
+ const current = carriers[carrierId] ?? {};
40257
+ const next = { ...current };
40258
+ next.agentMode = "subagent";
40259
+ delete next.taskforce;
40260
+ carriers[carrierId] = next;
40261
+ states.carriers = carriers;
40262
+ });
40263
+ }
40264
+ function updateTaskForceBackendAtomically(carrierId, cliType, selection) {
40265
+ updateCarriers((states) => {
40266
+ const carriers = { ...states.carriers ?? {} };
40267
+ const current = carriers[carrierId] ?? {};
40268
+ carriers[carrierId] = {
40269
+ ...current,
40270
+ agentMode: "cli",
40271
+ taskforce: {
40272
+ ...current.taskforce ?? {},
40273
+ [cliType]: selection
40274
+ }
40275
+ };
40276
+ states.carriers = carriers;
40277
+ });
40278
+ }
40279
+ function refreshTaskForceConfiguredCarriers(registry3) {
40280
+ setTaskForceConfiguredCarriers(registry3, getConfiguredTaskForceCarrierIds(getRegisteredOrder(registry3)));
40281
+ notifyStatusUpdate(registry3);
40282
+ }
40283
+ function readSelection(cliType, body) {
40284
+ if (typeof body.model !== "string") return null;
40285
+ const provider = getProviderModels(cliType);
40286
+ if (!provider.models.some((model) => model.modelId === body.model)) return null;
40287
+ const effort = getEffort(cliType, body.model);
40288
+ if (!effort.supported) {
40289
+ return body.effort === void 0 ? { model: body.model } : null;
40290
+ }
40291
+ if (typeof body.effort !== "string" || !effort.levels.includes(body.effort)) return null;
40292
+ return { model: body.model, effort: body.effort };
40293
+ }
40294
+ function readDefaultSelection(cliType) {
40295
+ const provider = getProviderModels(cliType);
40296
+ const model = provider.defaultModel;
40297
+ const effort = getEffort(cliType, model);
40298
+ return {
40299
+ model,
40300
+ ...effort.supported ? { effort: effort.default } : {}
40301
+ };
40302
+ }
40303
+ function readCurrentModelSelection(registry3, carrierId) {
40304
+ const config2 = getRegisteredCarrierConfig(registry3, carrierId);
40305
+ if (!config2) return void 0;
40306
+ const defaults = {
40307
+ [carrierId]: {
40308
+ cliType: config2.defaultCliType,
40309
+ ...config2.defaultAgentMode ? { defaultAgentMode: config2.defaultAgentMode } : {},
40310
+ ...buildCarrierModelDefaults(config2, config2.defaultCliType)
40311
+ }
40312
+ };
40313
+ const resolved = readCarriersSnapshot(defaults).carriers[carrierId] ?? fallbackResolvedState(config2);
40314
+ const cliType = resolved.agentCliType ?? config2.defaultCliType;
40315
+ return resolved.agentCli[cliType] ?? readDefaultSelection(cliType);
40316
+ }
40317
+ function fallbackResolvedState(config2) {
40318
+ return {
40319
+ agentMode: config2.defaultAgentMode ?? "cli",
40320
+ agentCliType: config2.defaultCliType,
40321
+ agentCli: { [config2.defaultCliType]: readDefaultSelection(config2.defaultCliType) },
40322
+ taskforce: {}
40323
+ };
40324
+ }
40325
+ function parseCarrierMutation(pathname) {
40326
+ const parts = pathname.split("/").filter(Boolean);
40327
+ if (parts[0] !== "carrier-settings" || parts[1] !== "carriers" || !parts[2]) return null;
40328
+ const carrierId = safeDecodeURIComponent(parts[2]);
40329
+ if (!carrierId) return null;
40330
+ if (parts.length === 4 && parts[3] === "cli") return { kind: "cli", carrierId };
40331
+ if (parts.length === 4 && parts[3] === "model") return { kind: "model", carrierId };
40332
+ if (parts.length === 4 && parts[3] === "display-name") return { kind: "display-name", carrierId };
40333
+ if (parts.length === 4 && parts[3] === "agent-mode") return { kind: "agent-mode", carrierId };
40334
+ if (parts.length === 4 && parts[3] === "taskforce") return { kind: "taskforce-all", carrierId };
40335
+ if (parts.length === 5 && parts[3] === "taskforce") {
40336
+ const cliType = safeDecodeURIComponent(parts[4] ?? "");
40337
+ return cliType ? { kind: "taskforce-backend", carrierId, cliType } : null;
40338
+ }
40339
+ return null;
40340
+ }
40341
+ function safeDecodeURIComponent(value) {
40342
+ try {
40343
+ return decodeURIComponent(value);
40344
+ } catch {
40345
+ return null;
40346
+ }
40347
+ }
40348
+ function isExpectedCarrierMethod(mutation, method, expectedMethod) {
40349
+ if (mutation.kind === "taskforce-backend") return method === "PUT" || method === "DELETE";
40350
+ if (mutation.kind === "taskforce-all") return method === "DELETE";
40351
+ return method === expectedMethod;
40352
+ }
40353
+ function isJsonRequest(req) {
40354
+ const contentType = req.headers["content-type"];
40355
+ return typeof contentType === "string" && contentType.toLowerCase().split(";")[0]?.trim() === "application/json";
40356
+ }
40357
+ function readCliType(value) {
40358
+ return typeof value === "string" && isCliType(value) ? value : null;
40359
+ }
40360
+ function isCliType(value) {
40361
+ return Object.prototype.hasOwnProperty.call(CLI_BACKENDS, value);
40362
+ }
40363
+ function getCliTypes() {
40364
+ return Object.keys(CLI_BACKENDS);
40365
+ }
40366
+ function requireCarrierConfig(registry3, carrierId) {
40367
+ const config2 = getRegisteredCarrierConfig(registry3, carrierId);
40368
+ if (!config2) throw new Error(`Carrier not found: ${carrierId}`);
40369
+ return config2;
40370
+ }
40371
+
39798
40372
  // src/codex/security-headers.ts
39799
40373
  var CONTENT_SECURITY_POLICY = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'none'";
39800
40374
  var SECURITY_HEADERS = {
@@ -39822,7 +40396,7 @@ function safeEqual(left, right) {
39822
40396
  return timingSafeEqual(leftBytes, rightBytes);
39823
40397
  }
39824
40398
  function workspaceHash(canonicalCwd) {
39825
- return crypto3.createHash("sha256").update(canonicalCwd).digest("hex").slice(0, 12);
40399
+ return crypto5.createHash("sha256").update(canonicalCwd).digest("hex").slice(0, 12);
39826
40400
  }
39827
40401
  async function canonicalizeTheaterPath(cwd) {
39828
40402
  return canonicalizeTheaterPathSync(cwd);
@@ -40702,7 +41276,7 @@ function createCodexGateway(deps) {
40702
41276
  return true;
40703
41277
  }
40704
41278
  if (selected.kind === "no-workspace") {
40705
- if (isJsonRequest(request)) {
41279
+ if (isJsonRequest2(request)) {
40706
41280
  sendJson2(response, 404, { error: "no_workspace_registered" });
40707
41281
  return true;
40708
41282
  }
@@ -40814,7 +41388,7 @@ function legacyWorkspacePath(url2) {
40814
41388
  }
40815
41389
  return null;
40816
41390
  }
40817
- function isJsonRequest(request) {
41391
+ function isJsonRequest2(request) {
40818
41392
  const accept = Array.isArray(request.headers.accept) ? request.headers.accept.join(",") : request.headers.accept ?? "";
40819
41393
  const requestedWith = Array.isArray(request.headers["x-requested-with"]) ? request.headers["x-requested-with"].join(",") : request.headers["x-requested-with"] ?? "";
40820
41394
  return accept.includes("application/json") || requestedWith.toLowerCase() === "xmlhttprequest";
@@ -40889,6 +41463,155 @@ function readRawHeaderValues(rawHeaders, name) {
40889
41463
  function stripIpv6Brackets2(host) {
40890
41464
  return host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
40891
41465
  }
41466
+ var STATE_VERSION = 1;
41467
+ var STATE_LOCK_DIR_NAME = "state.lock";
41468
+ var STATE_LOCK_OWNER_FILE_NAME = "owner.json";
41469
+ var STATE_TEMP_PREFIX = ".state.";
41470
+ function createConsoleDurableStateStore(deps = {}) {
41471
+ const paths = deps.paths ?? createConsoleDataPaths();
41472
+ const createStore = deps.createStore ?? createDurableJsonStore;
41473
+ return createStore({
41474
+ filePath: paths.stateFile,
41475
+ lockDir: path93__default.join(paths.dir, STATE_LOCK_DIR_NAME),
41476
+ lockOwnerFileName: STATE_LOCK_OWNER_FILE_NAME,
41477
+ now: deps.now,
41478
+ sanitize: sanitizeDurableConsoleState,
41479
+ sensitivity: "sensitive",
41480
+ tempCleanupPrefix: STATE_TEMP_PREFIX
41481
+ });
41482
+ }
41483
+ function sanitizeDurableConsoleState(value) {
41484
+ if (!isRecord4(value) || value.version !== STATE_VERSION) return emptyDurableConsoleState();
41485
+ return {
41486
+ version: STATE_VERSION,
41487
+ theaters: readTheaterRegistrations(value.theaters),
41488
+ operations: readDurableOperations(value.operations)
41489
+ };
41490
+ }
41491
+ function emptyDurableConsoleState() {
41492
+ return { version: STATE_VERSION, theaters: [], operations: [] };
41493
+ }
41494
+ function readProviderSessionCapture(fleetSessionId, deps = {}) {
41495
+ if (!isSafeCaptureId(fleetSessionId)) return null;
41496
+ const capturesDir = deps.capturesDir ?? createConsoleDataPaths().capturesDir;
41497
+ const filePath = path93__default.join(capturesDir, `${fleetSessionId}.json`);
41498
+ try {
41499
+ const parsed = JSON.parse(fs5__default.readFileSync(filePath, "utf8"));
41500
+ return sanitizeProviderSession(parsed);
41501
+ } catch {
41502
+ return null;
41503
+ }
41504
+ }
41505
+ function mergeProviderSessionCaptures(state, deps = {}) {
41506
+ let changed = false;
41507
+ const operations = state.operations.map((operation) => {
41508
+ if (operation.providerSession) return operation;
41509
+ const providerSession = readProviderSessionCapture(operation.sessionId, deps);
41510
+ if (!providerSession) return operation;
41511
+ changed = true;
41512
+ return { ...operation, providerSession };
41513
+ });
41514
+ return changed ? { ...state, operations } : state;
41515
+ }
41516
+ function unlinkProviderSessionCapture(fleetSessionId, deps = {}) {
41517
+ if (!isSafeCaptureId(fleetSessionId)) return false;
41518
+ const capturesDir = deps.capturesDir ?? createConsoleDataPaths().capturesDir;
41519
+ try {
41520
+ fs5__default.unlinkSync(path93__default.join(capturesDir, `${fleetSessionId}.json`));
41521
+ return true;
41522
+ } catch {
41523
+ return false;
41524
+ }
41525
+ }
41526
+ function cleanupProviderSessionCaptures(state, deps = {}) {
41527
+ for (const operation of state.operations) {
41528
+ if (operation.providerSession) unlinkProviderSessionCapture(operation.sessionId, deps);
41529
+ }
41530
+ }
41531
+ function readTheaterRegistrations(value) {
41532
+ if (!Array.isArray(value)) return [];
41533
+ const registrations = [];
41534
+ for (const item of value) {
41535
+ const registration = sanitizeTheaterRegistration(item);
41536
+ if (registration) registrations.push(registration);
41537
+ }
41538
+ return registrations;
41539
+ }
41540
+ function readDurableOperations(value) {
41541
+ if (!Array.isArray(value)) return [];
41542
+ const operations = [];
41543
+ for (const item of value) {
41544
+ const operation = sanitizeDurableOperation(item);
41545
+ if (operation) operations.push(operation);
41546
+ }
41547
+ return operations;
41548
+ }
41549
+ function sanitizeTheaterRegistration(value) {
41550
+ if (!isRecord4(value)) return null;
41551
+ const id = readNonEmptyString(value.id);
41552
+ const theaterPath = readNonEmptyString(value.path);
41553
+ const realpath = readNonEmptyString(value.realpath);
41554
+ const label = readNonEmptyString(value.label);
41555
+ const registeredAt = readNonEmptyString(value.registeredAt);
41556
+ const lastOpenedAt = readNonEmptyString(value.lastOpenedAt);
41557
+ if (!id || !theaterPath || !realpath || !label || !registeredAt || !lastOpenedAt) return null;
41558
+ return { id, path: theaterPath, realpath, label, registeredAt, lastOpenedAt };
41559
+ }
41560
+ function sanitizeDurableOperation(value) {
41561
+ if (!isRecord4(value)) return null;
41562
+ const sessionId = readNonEmptyString(value.sessionId);
41563
+ const theaterId = readNonEmptyString(value.theaterId);
41564
+ const cwd = readNonEmptyString(value.cwd);
41565
+ const cwdLabel = readNonEmptyString(value.cwdLabel);
41566
+ const sequence = readPositiveInteger(value.sequence);
41567
+ const createdAt = readFiniteNumber(value.createdAt);
41568
+ if (!sessionId || !theaterId || !cwd || !cwdLabel || sequence === null || createdAt === null) return null;
41569
+ const providerSession = sanitizeProviderSession(value.providerSession);
41570
+ return {
41571
+ sessionId,
41572
+ theaterId,
41573
+ cwd,
41574
+ cwdLabel,
41575
+ sequence,
41576
+ ...readOptionalString(value.label) ? { label: readOptionalString(value.label) } : {},
41577
+ ...readOptionalString(value.cliId) ? { cliId: readOptionalString(value.cliId) } : {},
41578
+ ...readOptionalString(value.cliLabel) ? { cliLabel: readOptionalString(value.cliLabel) } : {},
41579
+ createdAt,
41580
+ ...providerSession ? { providerSession } : {}
41581
+ };
41582
+ }
41583
+ function sanitizeProviderSession(value) {
41584
+ if (!isRecord4(value)) return null;
41585
+ const provider = value.provider === "claude" || value.provider === "codex" ? value.provider : null;
41586
+ const sessionId = readNonEmptyString(value.sessionId);
41587
+ const capturedAt = readNonEmptyString(value.capturedAt);
41588
+ if (!provider || !sessionId || !capturedAt) return null;
41589
+ return {
41590
+ provider,
41591
+ sessionId,
41592
+ ...readOptionalString(value.transcriptPath) ? { transcriptPath: readOptionalString(value.transcriptPath) } : {},
41593
+ ...readOptionalString(value.source) ? { source: readOptionalString(value.source) } : {},
41594
+ capturedAt
41595
+ };
41596
+ }
41597
+ function isRecord4(value) {
41598
+ return typeof value === "object" && value !== null && !Array.isArray(value);
41599
+ }
41600
+ function isSafeCaptureId(value) {
41601
+ return value.length > 0 && path93__default.basename(value) === value && !value.includes(path93__default.sep) && !value.includes(path93__default.posix.sep);
41602
+ }
41603
+ function readNonEmptyString(value) {
41604
+ return typeof value === "string" && value.length > 0 ? value : null;
41605
+ }
41606
+ function readOptionalString(value) {
41607
+ return typeof value === "string" && value.length > 0 ? value : void 0;
41608
+ }
41609
+ function readPositiveInteger(value) {
41610
+ return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : null;
41611
+ }
41612
+ function readFiniteNumber(value) {
41613
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
41614
+ }
40892
41615
 
40893
41616
  // src/observability-routes.ts
40894
41617
  function writeObserverEvents(req, res, workspace, store2) {
@@ -41099,15 +41822,77 @@ function createConsoleObservabilityStore(deps = {}) {
41099
41822
  terminalSessionsById.set(state.sessionId, state);
41100
41823
  return toTerminalSessionInfo(state);
41101
41824
  }
41825
+ function injectDormantOperation(operation) {
41826
+ const currentSequence = terminalSequenceByTheater.get(operation.theaterId) ?? 0;
41827
+ terminalSequenceByTheater.set(operation.theaterId, Math.max(currentSequence, operation.sequence));
41828
+ const state = {
41829
+ sessionId: operation.sessionId,
41830
+ cwd: operation.cwd,
41831
+ canonicalCwd: canonicalizeTheaterPathSync(operation.cwd),
41832
+ cwdLabel: operation.cwdLabel,
41833
+ sequence: operation.sequence,
41834
+ label: operation.label,
41835
+ cliId: operation.cliId,
41836
+ cliLabel: operation.cliLabel,
41837
+ createdAt: operation.createdAt,
41838
+ theaterId: operation.theaterId,
41839
+ terminalSessionId: operation.sessionId,
41840
+ status: "dormant",
41841
+ providerSession: operation.providerSession
41842
+ };
41843
+ terminalSessionsById.set(state.sessionId, state);
41844
+ return toTerminalSessionInfo(state);
41845
+ }
41102
41846
  function listTerminalSessions() {
41103
41847
  return Array.from(terminalSessionsById.values()).map(toTerminalSessionInfo).sort((a, b) => b.createdAt - a.createdAt);
41104
41848
  }
41849
+ function listDurableOperations() {
41850
+ return Array.from(terminalSessionsById.values()).map((session) => ({
41851
+ sessionId: session.sessionId,
41852
+ theaterId: session.theaterId,
41853
+ cwd: session.cwd,
41854
+ cwdLabel: session.cwdLabel,
41855
+ sequence: session.sequence,
41856
+ ...session.label ? { label: session.label } : {},
41857
+ ...session.cliId ? { cliId: session.cliId } : {},
41858
+ ...session.cliLabel ? { cliLabel: session.cliLabel } : {},
41859
+ createdAt: session.createdAt,
41860
+ ...session.providerSession ? { providerSession: session.providerSession } : {}
41861
+ }));
41862
+ }
41863
+ function getDurableOperation(sessionId) {
41864
+ return listDurableOperations().find((operation) => operation.sessionId === sessionId) ?? null;
41865
+ }
41866
+ function updateTerminalSessionProviderSession(sessionId, providerSession) {
41867
+ const session = terminalSessionsById.get(sessionId);
41868
+ if (!session) return null;
41869
+ session.providerSession = providerSession;
41870
+ return toTerminalSessionInfo(session);
41871
+ }
41105
41872
  function updateTerminalSessionStatus(sessionId, status) {
41106
41873
  const session = terminalSessionsById.get(sessionId);
41107
41874
  if (!session) return null;
41108
41875
  session.status = status;
41109
41876
  return toTerminalSessionInfo(session);
41110
41877
  }
41878
+ function setTerminalSessionTurnState(sessionId, turnState) {
41879
+ const session = terminalSessionsById.get(sessionId);
41880
+ if (!session) return null;
41881
+ session.turnState = turnState;
41882
+ return toTerminalSessionInfo(session);
41883
+ }
41884
+ function transitionTerminalSessionToDormant(sessionId, providerSession) {
41885
+ const session = terminalSessionsById.get(sessionId);
41886
+ if (!session) return null;
41887
+ const workspace = workspacesByCliRunId.get(sessionId);
41888
+ if (workspace?.terminalSessionId === sessionId) {
41889
+ removeWorkspaceIndexes(workspace);
41890
+ workspacesByCliRunId.delete(sessionId);
41891
+ }
41892
+ session.status = "dormant";
41893
+ session.providerSession = providerSession;
41894
+ return toTerminalSessionInfo(session);
41895
+ }
41111
41896
  function renameTerminalSession(sessionId, rawLabel) {
41112
41897
  const session = terminalSessionsById.get(sessionId);
41113
41898
  if (!session) return null;
@@ -41147,18 +41932,24 @@ function createConsoleObservabilityStore(deps = {}) {
41147
41932
  clear,
41148
41933
  getLaunchCwd,
41149
41934
  getTruncation,
41935
+ getDurableOperation,
41150
41936
  getWorkspace,
41151
41937
  listEvents,
41152
41938
  listJobs,
41939
+ listDurableOperations,
41153
41940
  listTerminalSessions,
41154
41941
  listWorkspaces,
41155
41942
  appendTerminalRuntimeEvent,
41156
41943
  createPendingTerminalSession,
41944
+ injectDormantOperation,
41157
41945
  notifySessionUpdated,
41158
41946
  renameTerminalSession,
41159
41947
  subscribe,
41160
41948
  subscribeAll,
41949
+ updateTerminalSessionProviderSession,
41161
41950
  updateTerminalSessionStatus,
41951
+ setTerminalSessionTurnState,
41952
+ transitionTerminalSessionToDormant,
41162
41953
  removeTerminalSession,
41163
41954
  registerTerminalRuntimeSession,
41164
41955
  workspaceCount: () => listWorkspaces().length
@@ -41226,11 +42017,13 @@ function toTerminalSessionInfo(state) {
41226
42017
  cliId: state.cliId,
41227
42018
  cliLabel: state.cliLabel,
41228
42019
  status: state.status,
42020
+ turnState: state.turnState ?? "none",
41229
42021
  createdAt: state.createdAt,
41230
42022
  theaterId: state.theaterId,
41231
42023
  registrationId: state.registrationId,
41232
42024
  cliRunId: state.cliRunId,
41233
- tenantId: state.cliRunId
42025
+ tenantId: state.cliRunId,
42026
+ resumeAvailable: state.providerSession !== void 0
41234
42027
  };
41235
42028
  }
41236
42029
  function pruneTenantJobs(state) {
@@ -41441,10 +42234,130 @@ function resolveConsolePath(pathname) {
41441
42234
  if (!path93__default.extname(withoutPrefix)) return "index.html";
41442
42235
  return withoutPrefix;
41443
42236
  }
42237
+ var TerminalFolderListError = class extends Error {
42238
+ code;
42239
+ constructor(code) {
42240
+ super(code);
42241
+ this.name = "TerminalFolderListError";
42242
+ this.code = code;
42243
+ }
42244
+ };
42245
+ var DIRECTORY_ENTRY_CAP = 500;
42246
+ var WINDOWS_DRIVE_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
42247
+ async function listTerminalFolders(requestedPath, deps = {}) {
42248
+ const platform = deps.platform ?? process.platform;
42249
+ const stat5 = deps.stat ?? fs5__default.promises.stat;
42250
+ const opendir = deps.opendir ?? fs5__default.promises.opendir;
42251
+ const targetPath = normalizeListPath(requestedPath, platform, deps);
42252
+ const roots = await listRoots(platform, stat5);
42253
+ const targetStat = await statDirectory(targetPath, stat5);
42254
+ if (!targetStat.isDirectory()) throw new TerminalFolderListError("invalid_path");
42255
+ const entries = [];
42256
+ const truncated = await collectDirectoryEntries(targetPath, opendir, stat5, entries);
42257
+ entries.sort((a, b) => a.name.localeCompare(b.name));
42258
+ return {
42259
+ path: targetPath,
42260
+ parentPath: parentPath(targetPath, platform),
42261
+ roots,
42262
+ entries,
42263
+ ...truncated ? { truncated: true } : {}
42264
+ };
42265
+ }
42266
+ function normalizeFolderBrowserPath(value, platform = process.platform) {
42267
+ if (typeof value !== "string" || value.length === 0 || value.includes("\0")) throw new TerminalFolderListError("invalid_path");
42268
+ if (isWindowsAmbiguousPath(value, platform) || !path93__default.isAbsolute(value)) throw new TerminalFolderListError("invalid_path");
42269
+ return path93__default.resolve(value);
42270
+ }
42271
+ function normalizeListPath(requestedPath, platform, deps) {
42272
+ if (requestedPath === null || requestedPath === void 0) {
42273
+ const home = deps.homedir?.() ?? os22__default.homedir();
42274
+ const start = home || deps.cwd?.() || process.cwd();
42275
+ return path93__default.resolve(start);
42276
+ }
42277
+ return normalizeFolderBrowserPath(requestedPath, platform);
42278
+ }
42279
+ async function listRoots(platform, stat5) {
42280
+ if (platform !== "win32") return ["/"];
42281
+ const roots = [];
42282
+ await Promise.all(WINDOWS_DRIVE_LETTERS.map(async (letter) => {
42283
+ const root = `${letter}:\\`;
42284
+ try {
42285
+ if ((await stat5(root)).isDirectory()) roots.push(root);
42286
+ } catch {
42287
+ }
42288
+ }));
42289
+ return roots.sort();
42290
+ }
42291
+ async function statDirectory(targetPath, stat5) {
42292
+ try {
42293
+ return await stat5(targetPath);
42294
+ } catch (error51) {
42295
+ throw mapFsError(error51);
42296
+ }
42297
+ }
42298
+ async function collectDirectoryEntries(targetPath, opendir, stat5, entries) {
42299
+ const directory = await openDirectory(targetPath, opendir);
42300
+ try {
42301
+ let dirent = await directory.read();
42302
+ while (dirent !== null) {
42303
+ const entry = await toTerminalFolderEntry(targetPath, dirent, stat5);
42304
+ if (entry !== null) {
42305
+ if (entries.length >= DIRECTORY_ENTRY_CAP) return true;
42306
+ entries.push(entry);
42307
+ }
42308
+ dirent = await directory.read();
42309
+ }
42310
+ return false;
42311
+ } catch (error51) {
42312
+ throw mapFsError(error51);
42313
+ } finally {
42314
+ await directory.close();
42315
+ }
42316
+ }
42317
+ async function openDirectory(targetPath, opendir) {
42318
+ try {
42319
+ return await opendir(targetPath);
42320
+ } catch (error51) {
42321
+ throw mapFsError(error51);
42322
+ }
42323
+ }
42324
+ async function toTerminalFolderEntry(targetPath, dirent, stat5) {
42325
+ if (!dirent.isDirectory() && !dirent.isSymbolicLink()) return null;
42326
+ const entryPath = path93__default.join(targetPath, dirent.name);
42327
+ if (dirent.isDirectory()) return { name: dirent.name, path: entryPath, kind: "dir", accessible: true };
42328
+ const accessible = await statSymlinkDirectory(entryPath, stat5);
42329
+ if (accessible === null) return null;
42330
+ return { name: dirent.name, path: entryPath, kind: "dir", accessible };
42331
+ }
42332
+ async function statSymlinkDirectory(entryPath, stat5) {
42333
+ try {
42334
+ return (await stat5(entryPath)).isDirectory() ? true : null;
42335
+ } catch {
42336
+ return false;
42337
+ }
42338
+ }
42339
+ function mapFsError(error51) {
42340
+ const code = error51.code;
42341
+ if (code === "EACCES" || code === "EPERM") return new TerminalFolderListError("forbidden");
42342
+ if (code === "ENOENT" || code === "ENOTDIR") return new TerminalFolderListError("not_found");
42343
+ return new TerminalFolderListError("invalid_path");
42344
+ }
42345
+ function parentPath(targetPath, platform) {
42346
+ const parsed = path93__default.parse(targetPath);
42347
+ const resolved = path93__default.resolve(targetPath);
42348
+ if (resolved === path93__default.resolve(parsed.root)) return null;
42349
+ const parent = path93__default.dirname(resolved);
42350
+ if (platform === "win32" && parent === resolved) return null;
42351
+ return parent;
42352
+ }
42353
+ function isWindowsAmbiguousPath(value, platform) {
42354
+ if (platform !== "win32") return false;
42355
+ return /^[a-zA-Z]:(?![\\/])/.test(value) || /^[\\/](?![\\/])/.test(value);
42356
+ }
41444
42357
  var GRANT_ID_BYTES = 24;
41445
42358
  var DEFAULT_GRANT_TTL_MS = 6e4;
41446
42359
  function createFolderGrantStore(deps = {}) {
41447
- const randomId = deps.randomId ?? (() => crypto3.randomBytes(GRANT_ID_BYTES).toString("base64url"));
42360
+ const randomId = deps.randomId ?? (() => crypto5.randomBytes(GRANT_ID_BYTES).toString("base64url"));
41448
42361
  const statSync5 = deps.statSync ?? fs5__default.statSync;
41449
42362
  const ttlMs = deps.ttlMs ?? DEFAULT_GRANT_TTL_MS;
41450
42363
  const now = deps.now ?? Date.now;
@@ -41472,167 +42385,15 @@ function createFolderGrantStore(deps = {}) {
41472
42385
  return { issue: issue3, consume };
41473
42386
  }
41474
42387
  function validateAbsoluteDirectory(cwd, statSync5 = fs5__default.statSync) {
41475
- if (!path93__default.isAbsolute(cwd)) throw new Error("invalid_folder");
42388
+ if (typeof cwd !== "string" || cwd.length === 0 || cwd.includes("\0")) throw new Error("invalid_folder");
42389
+ if (isWindowsAmbiguousPath2(cwd) || !path93__default.isAbsolute(cwd)) throw new Error("invalid_folder");
41476
42390
  const normalized = path93__default.resolve(cwd);
41477
42391
  if (!statSync5(normalized).isDirectory()) throw new Error("invalid_folder");
41478
42392
  return normalized;
41479
42393
  }
41480
-
41481
- // src/terminal/folder-picker.ts
41482
- var execFileAsync = promisify(execFile);
41483
- var DIALOG_TIMEOUT_MS = 3e4;
41484
- var POWERSHELL_FOLDER_PICKER_CSHARP = `
41485
- using System;
41486
- using System.Runtime.InteropServices;
41487
- namespace FleetPicker {
41488
- [ComImport, Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
41489
- public interface IShellItem {
41490
- void BindToHandler();
41491
- void GetParent();
41492
- [PreserveSig] int GetDisplayName(int sigdn, out IntPtr ppszName);
41493
- void GetAttributes();
41494
- void Compare();
41495
- }
41496
- [ComImport, Guid("d57c7288-d4ad-4768-be02-9d969532d960"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
41497
- public interface IFileOpenDialog {
41498
- [PreserveSig] int Show(IntPtr parent);
41499
- void SetFileTypes(); void SetFileTypeIndex(); void GetFileTypeIndex();
41500
- void Advise(); void Unadvise();
41501
- [PreserveSig] int SetOptions(uint fos);
41502
- [PreserveSig] int GetOptions(out uint pfos);
41503
- void SetDefaultFolder();
41504
- void SetFolder(IShellItem psi);
41505
- void GetFolder(); void GetCurrentSelection();
41506
- void SetFileName(); void GetFileName(); void SetTitle(); void SetOkButtonLabel(); void SetFileNameLabel();
41507
- [PreserveSig] int GetResult(out IShellItem ppsi);
41508
- void AddPlace(IShellItem psi, int fdap);
41509
- void SetDefaultExtension(); void Close(); void SetClientGuid(); void ClearClientData(); void SetFilter();
41510
- void GetResults(); void GetSelectedItems();
41511
- }
41512
- [ComImport, Guid("DC1C5A9C-E88A-4DDE-A5A1-60F82A20AEF7")] public class FileOpenDialog { }
41513
- public static class Picker {
41514
- [DllImport("shell32.dll", CharSet=CharSet.Unicode, PreserveSig=false)]
41515
- static extern void SHCreateItemFromParsingName(string pszPath, IntPtr pbc, ref Guid riid, [MarshalAs(UnmanagedType.Interface)] out IShellItem ppv);
41516
- static IShellItem ItemFromPath(string path) {
41517
- Guid iid = new Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe");
41518
- IShellItem it; SHCreateItemFromParsingName(path, IntPtr.Zero, ref iid, out it); return it;
41519
- }
41520
- public static string Pick(string initialPath) {
41521
- var dlg = (IFileOpenDialog)(new FileOpenDialog());
41522
- uint opts; dlg.GetOptions(out opts);
41523
- dlg.SetOptions(opts | 0x20); // FOS_PICKFOLDERS
41524
- if (!string.IsNullOrEmpty(initialPath)) {
41525
- // \uC2DC\uC791 \uD3F4\uB354\uB97C WSL \uC704\uCE58\uB85C \uC9C0\uC815\uD558\uACE0 \uC88C\uCE21\uC5D0 \uACE0\uC815(FDAP_TOP). \uC2E4\uD328\uD574\uB3C4 \uAE30\uBCF8 \uC704\uCE58\uB85C \uC9C4\uD589\uD55C\uB2E4.
41526
- try { IShellItem s = ItemFromPath(initialPath); dlg.AddPlace(s, 1); dlg.SetFolder(s); } catch { }
41527
- }
41528
- if (dlg.Show(IntPtr.Zero) != 0) return null; // \uCDE8\uC18C/\uC2E4\uD328 \uC2DC null
41529
- IShellItem item; dlg.GetResult(out item);
41530
- IntPtr ptr; if (item.GetDisplayName(unchecked((int)0x80058000), out ptr) != 0) return null; // SIGDN_FILESYSPATH; \uBE44 \uD30C\uC77C\uC2DC\uC2A4\uD15C \uC120\uD0DD \uAC00\uB4DC
41531
- string path = Marshal.PtrToStringAuto(ptr);
41532
- Marshal.FreeCoTaskMem(ptr);
41533
- return path;
41534
- }
41535
- }
41536
- }`;
41537
- function createNativeFolderPicker(deps = {}) {
41538
- const platform = deps.platform ?? process.platform;
41539
- const runCommand = deps.runCommand ?? runNativeCommand;
41540
- const statSync5 = deps.statSync ?? fs5__default.statSync;
41541
- const readProcVersion = deps.readProcVersion ?? defaultReadProcVersion;
41542
- const env = deps.env ?? process.env;
41543
- return async () => {
41544
- const isWsl = platform === "linux" && detectWsl(readProcVersion, env);
41545
- const wslStartPath = isWsl ? wslStartFolderUnc(env) : "";
41546
- const commands = buildPickerCommands(platform, isWsl, wslStartPath);
41547
- if (commands.length === 0) return { kind: "error", error: "unsupported_platform" };
41548
- for (const command2 of commands) {
41549
- const result = await runPickerCommand(runCommand, command2);
41550
- if ("kind" in result && result.kind === "unavailable") continue;
41551
- if ("kind" in result && result.kind === "cancelled") return { kind: "cancelled" };
41552
- if ("kind" in result && result.kind === "timeout") return { kind: "error", error: "dialog_timeout" };
41553
- if ("kind" in result) continue;
41554
- let resolvedPath = result.stdout.trim();
41555
- if (command2.postProcess === "wslpath") {
41556
- try {
41557
- const wslResult = await runCommand("wslpath", ["-u", resolvedPath]);
41558
- resolvedPath = wslResult.stdout.trim();
41559
- } catch {
41560
- return { kind: "error", error: "invalid_folder" };
41561
- }
41562
- }
41563
- try {
41564
- return { kind: "selected", cwd: validateAbsoluteDirectory(resolvedPath, statSync5) };
41565
- } catch {
41566
- return { kind: "error", error: "invalid_folder" };
41567
- }
41568
- }
41569
- return { kind: "error", error: "dialog_unavailable" };
41570
- };
41571
- }
41572
- function defaultReadProcVersion() {
41573
- try {
41574
- return fs5__default.readFileSync("/proc/version", "utf8");
41575
- } catch {
41576
- return "";
41577
- }
41578
- }
41579
- function detectWsl(readProcVersion, env) {
41580
- if (env.WSL_INTEROP !== void 0 || env.WSL_DISTRO_NAME !== void 0) return true;
41581
- return /microsoft|wsl/i.test(readProcVersion());
41582
- }
41583
- function buildPickerCommands(platform, isWsl = false, wslStartPath = "") {
41584
- if (platform === "darwin") {
41585
- return [{ bin: "osascript", args: ["-e", 'POSIX path of (choose folder with prompt "Choose a Fleet workspace")'] }];
41586
- }
41587
- if (platform === "linux") {
41588
- const commands = [];
41589
- if (isWsl) {
41590
- commands.push({ bin: "powershell.exe", args: buildPowerShellPickerArgs(wslStartPath), postProcess: "wslpath" });
41591
- }
41592
- commands.push(
41593
- { bin: "zenity", args: ["--file-selection", "--directory", "--title=Choose a Fleet workspace"] },
41594
- { bin: "kdialog", args: ["--getexistingdirectory", ".", "Choose a Fleet workspace"] }
41595
- );
41596
- return commands;
41597
- }
41598
- if (platform === "win32") {
41599
- return [{ bin: "powershell.exe", args: buildPowerShellPickerArgs("") }];
41600
- }
41601
- return [];
41602
- }
41603
- function buildPowerShellPickerArgs(initialWindowsPath) {
41604
- const escaped = initialWindowsPath.replace(/'/g, "''");
41605
- const script = [
41606
- "[Console]::OutputEncoding=[Text.Encoding]::UTF8",
41607
- `Add-Type -TypeDefinition '${POWERSHELL_FOLDER_PICKER_CSHARP}'`,
41608
- `$selection = [FleetPicker.Picker]::Pick('${escaped}')`,
41609
- "if ([string]::IsNullOrEmpty($selection)) { exit 1 }",
41610
- "[Console]::Out.Write($selection)"
41611
- ].join("\n");
41612
- return ["-NoProfile", "-Sta", "-Command", script];
41613
- }
41614
- function wslStartFolderUnc(env) {
41615
- const distro = env.WSL_DISTRO_NAME;
41616
- if (!distro) return "";
41617
- const home = env.HOME;
41618
- if (home && home.startsWith("/") && !home.startsWith("/mnt/")) {
41619
- return `\\\\wsl.localhost\\${distro}${home.replace(/\//g, "\\")}`;
41620
- }
41621
- return `\\\\wsl.localhost\\${distro}`;
41622
- }
41623
- async function runPickerCommand(runCommand, command2) {
41624
- try {
41625
- return await runCommand(command2.bin, command2.args);
41626
- } catch (error51) {
41627
- const err = error51;
41628
- if (err.code === "ENOENT") return { kind: "unavailable" };
41629
- if (err.code === "ETIMEDOUT") return { kind: "timeout" };
41630
- return { kind: "cancelled" };
41631
- }
41632
- }
41633
- async function runNativeCommand(bin, args) {
41634
- const result = await execFileAsync(bin, [...args], { timeout: DIALOG_TIMEOUT_MS, windowsHide: true });
41635
- return { stdout: result.stdout, stderr: result.stderr };
42394
+ function isWindowsAmbiguousPath2(value) {
42395
+ if (process.platform !== "win32") return false;
42396
+ return /^[a-zA-Z]:(?![\\/])/.test(value) || /^[\\/](?![\\/])/.test(value);
41636
42397
  }
41637
42398
  var JAVASCRIPT_ENTRY_EXTENSIONS = /* @__PURE__ */ new Set([".cjs", ".js", ".mjs"]);
41638
42399
  var TYPESCRIPT_ENTRY_EXTENSIONS = /* @__PURE__ */ new Set([".cts", ".mts", ".ts", ".tsx"]);
@@ -41641,29 +42402,43 @@ function buildConsoleHookCommand(entry) {
41641
42402
  if (entry === void 0) {
41642
42403
  throw new Error("Fleet Console session hook command requires the current console entry path");
41643
42404
  }
42405
+ return buildConsoleCliHookExec(entry, ["hook", "subagents-context"]);
42406
+ }
42407
+ function buildConsoleTurnHookCommand(entry, phase) {
42408
+ return buildConsoleCliHookExec(entry, ["hook", phase === "start" ? "turn-start" : "turn-end"]);
42409
+ }
42410
+ function buildConsoleCaptureHookCommand(entry, cliId) {
41644
42411
  const extension = path93__default.extname(entry.entryPath);
41645
42412
  if (JAVASCRIPT_ENTRY_EXTENSIONS.has(extension)) {
41646
- return {
41647
- command: entry.execPath,
41648
- args: [entry.entryPath, "hook", "subagents-context"]
41649
- };
42413
+ return createSessionCaptureHookExec({
42414
+ entryPath: entry.entryPath,
42415
+ execPath: entry.execPath,
42416
+ provider: toCaptureProvider(cliId)
42417
+ });
41650
42418
  }
41651
42419
  if (TYPESCRIPT_ENTRY_EXTENSIONS.has(extension)) {
41652
42420
  if (!entry.tsxLoaderPath) {
41653
- throw new Error("Fleet Console session hook command for TypeScript entries requires a tsx loader path");
42421
+ throw new Error("Fleet Console capture session hook requires a tsx loader path");
41654
42422
  }
41655
- return {
41656
- command: entry.execPath,
41657
- args: ["--import", pathToFileURL(entry.tsxLoaderPath).href, entry.entryPath, "hook", "subagents-context"]
41658
- };
42423
+ return createSessionCaptureHookExec({
42424
+ entryPath: entry.entryPath,
42425
+ execPath: entry.execPath,
42426
+ provider: toCaptureProvider(cliId),
42427
+ tsxLoader: entry.tsxLoaderPath
42428
+ });
41659
42429
  }
41660
42430
  throw new Error(`Unsupported Fleet Console session hook entry extension: ${extension}`);
41661
42431
  }
42432
+ function toCaptureProvider(cliId) {
42433
+ return cliId === "codex" ? "codex" : "claude";
42434
+ }
41662
42435
  function runCodexCommand(command2) {
41663
42436
  const result = spawnSync(command2.bin, command2.args, {
41664
42437
  cwd: command2.cwd,
41665
42438
  encoding: "utf8",
41666
- env: command2.env
42439
+ env: command2.env,
42440
+ // 콘솔 없는 fleet-console 백엔드에서 codex 등록 명령 실행 시 콘솔 창이 깜빡이는 것을 방지한다.
42441
+ windowsHide: true
41667
42442
  });
41668
42443
  return {
41669
42444
  status: result.status,
@@ -41676,6 +42451,25 @@ function withConsoleMarketplaceLock(target, fn) {
41676
42451
  mkdirSync(path93__default.dirname(lockDir), { recursive: true });
41677
42452
  return withDirectoryLock({ lockDir }, fn);
41678
42453
  }
42454
+ function buildConsoleCliHookExec(entry, trailingArgs) {
42455
+ const extension = path93__default.extname(entry.entryPath);
42456
+ if (JAVASCRIPT_ENTRY_EXTENSIONS.has(extension)) {
42457
+ return {
42458
+ command: entry.execPath,
42459
+ args: [entry.entryPath, ...trailingArgs]
42460
+ };
42461
+ }
42462
+ if (TYPESCRIPT_ENTRY_EXTENSIONS.has(extension)) {
42463
+ if (!entry.tsxLoaderPath) {
42464
+ throw new Error("Fleet Console session hook command for TypeScript entries requires a tsx loader path");
42465
+ }
42466
+ return {
42467
+ command: entry.execPath,
42468
+ args: ["--import", pathToFileURL(entry.tsxLoaderPath).href, entry.entryPath, ...trailingArgs]
42469
+ };
42470
+ }
42471
+ throw new Error(`Unsupported Fleet Console session hook entry extension: ${extension}`);
42472
+ }
41679
42473
 
41680
42474
  // src/terminal/launch.ts
41681
42475
  var DEFAULT_TERMINAL_CWD_FALLBACK = os22__default.homedir;
@@ -41684,12 +42478,12 @@ var CONSOLE_ENTRY_PATH = fileURLToPath(import.meta.url);
41684
42478
  var HOOK_ENTRY_EXTENSIONS = /* @__PURE__ */ new Set([".cjs", ".cts", ".js", ".mjs", ".mts", ".ts", ".tsx"]);
41685
42479
  var require2 = createRequire(import.meta.url);
41686
42480
  function createDefaultTerminalLaunchResolver(deps = {}) {
41687
- const baseCwd = deps.cwd ?? process4.cwd();
41688
- const env = deps.env ?? process4.env;
41689
- const execPath = deps.execPath ?? process4.execPath;
42481
+ const baseCwd = deps.cwd ?? process6.cwd();
42482
+ const env = deps.env ?? process6.env;
42483
+ const execPath = deps.execPath ?? process6.execPath;
41690
42484
  const homedir3 = deps.homedir ?? DEFAULT_TERMINAL_CWD_FALLBACK;
41691
- const platform = deps.platform ?? process4.platform;
41692
- const entryPath = resolveHookEntryPath(deps.entryPath ?? process4.argv[1]);
42485
+ const platform = deps.platform ?? process6.platform;
42486
+ const entryPath = resolveHookEntryPath(deps.entryPath ?? process6.argv[1]);
41693
42487
  const tsxLoaderPath = deps.tsxLoaderPath ?? resolveOptionalPackage("tsx");
41694
42488
  const dataDir = deps.dataDir ?? getFleetDataDir();
41695
42489
  const infraServices = deps.infraServices ?? createInfraServices();
@@ -41729,6 +42523,7 @@ function createDefaultTerminalLaunchResolver(deps = {}) {
41729
42523
  onRuntimeSessionStart: deps.onRuntimeSessionStart,
41730
42524
  resolveProfile,
41731
42525
  cliId: context?.cliId,
42526
+ resumeSessionId: context?.resumeSessionId,
41732
42527
  sessionId
41733
42528
  });
41734
42529
  };
@@ -41749,7 +42544,7 @@ function hasHookEntryExtension(entryPath) {
41749
42544
  }
41750
42545
  function startTerminalShell(launch, size) {
41751
42546
  const { spawn: spawnPty } = require2("node-pty");
41752
- const useConptyDll = resolveUseConptyDll(process4.platform, process4.env);
42547
+ const useConptyDll = resolveUseConptyDll(process6.platform, process6.env);
41753
42548
  return spawnPty(launch.bin, [...launch.args], {
41754
42549
  cols: size.cols,
41755
42550
  rows: size.rows,
@@ -41774,7 +42569,8 @@ async function createAgentCliLaunchSpec(options) {
41774
42569
  const profile = await options.resolveProfile(options.env, options.cwd, {
41775
42570
  authEnvResolver: options.authEnvResolver,
41776
42571
  authService: options.infraServices.authService,
41777
- cliId: options.cliId
42572
+ cliId: options.cliId,
42573
+ resumeSessionId: options.resumeSessionId
41778
42574
  });
41779
42575
  const injectedProfile = await options.injectProfile(profile, {
41780
42576
  buildSystemPrompt: (injectTone) => createSystemPromptBuilder({ carrierRuntime: agentRuntime.carrierRuntime }).build(injectTone),
@@ -41783,9 +42579,13 @@ async function createAgentCliLaunchSpec(options) {
41783
42579
  dataDir: options.dataDir,
41784
42580
  dedicatedMcpSession: agentRuntime.dedicatedMcpSession,
41785
42581
  enableMetaphor: false,
42582
+ captureSessionHookExec: buildConsoleCaptureHookCommand(options.hookEntry, profile.id),
42583
+ turnStartHookExec: buildConsoleTurnHookCommand(options.hookEntry, "start"),
42584
+ turnEndHookExec: buildConsoleTurnHookCommand(options.hookEntry, "end"),
41786
42585
  hookExec: buildConsoleHookCommand(options.hookEntry),
41787
42586
  onCleanup: (cleanup) => cleanupStack.push(cleanup),
41788
- replaceSystemPrompt: true,
42587
+ replaceSystemPrompt: false,
42588
+ resumeSessionId: options.resumeSessionId,
41789
42589
  withMarketplaceLock: withConsoleMarketplaceLock,
41790
42590
  mcpSessionLabel: options.sessionId
41791
42591
  });
@@ -41819,6 +42619,7 @@ function toLaunchSpec(profile, cleanup) {
41819
42619
  cwd: profile.cwd,
41820
42620
  env: { ...profile.env },
41821
42621
  messagePolicy: profile.messagePolicy,
42622
+ renameCommand: profile.renameCommand,
41822
42623
  terminalName: profile.terminalName
41823
42624
  };
41824
42625
  }
@@ -41881,6 +42682,8 @@ var DEFAULT_COLS = 80;
41881
42682
  var DEFAULT_ROWS = 24;
41882
42683
  var DEFAULT_SCROLLBACK_LIMIT = 512;
41883
42684
  var WS_OPEN_STATE = 1;
42685
+ var THEATER_SHELL_SESSION_PREFIX = "shell:";
42686
+ var THEATER_SHELL_DETACH_GRACE_MS = 4e3;
41884
42687
  function createTerminalSessionManager(deps) {
41885
42688
  const startShell = deps.startShell ?? startTerminalShell;
41886
42689
  const scrollbackLimit = deps.scrollbackLimit ?? DEFAULT_SCROLLBACK_LIMIT;
@@ -41891,6 +42694,7 @@ function createTerminalSessionManager(deps) {
41891
42694
  }
41892
42695
  async function attach(socket, context) {
41893
42696
  const session = await getOrCreateSession(context);
42697
+ clearGraceTimer(session);
41894
42698
  if (session.activeSocket && session.activeSocket !== socket) {
41895
42699
  session.activeSocket.close(4e3, "terminal_replaced");
41896
42700
  }
@@ -41912,6 +42716,9 @@ function createTerminalSessionManager(deps) {
41912
42716
  function getSessionMessagePolicy(sessionId) {
41913
42717
  return sessions.get(sessionId)?.messagePolicy;
41914
42718
  }
42719
+ function getSessionRenameCommand(sessionId) {
42720
+ return sessions.get(sessionId)?.renameCommand;
42721
+ }
41915
42722
  function writeToSession(sessionId, data) {
41916
42723
  const session = sessions.get(sessionId);
41917
42724
  if (!session || typeof session.pty.write !== "function") return false;
@@ -41949,7 +42756,7 @@ function createTerminalSessionManager(deps) {
41949
42756
  }
41950
42757
  }
41951
42758
  async function launchSession(context) {
41952
- const launch = await deps.launch(context.cwd, { sessionId: context.sessionId, kind: context.kind, cliId: context.cliId });
42759
+ const launch = await deps.launch(context.cwd, { sessionId: context.sessionId, kind: context.kind, cliId: context.cliId, resumeSessionId: context.resumeSessionId });
41953
42760
  let pty;
41954
42761
  try {
41955
42762
  pty = startShell(launch, { cols: DEFAULT_COLS, rows: DEFAULT_ROWS });
@@ -41964,9 +42771,11 @@ function createTerminalSessionManager(deps) {
41964
42771
  scrollback: [],
41965
42772
  cleanup: launch.cleanup,
41966
42773
  messagePolicy: launch.messagePolicy,
42774
+ renameCommand: launch.renameCommand,
41967
42775
  activeSocket: null,
41968
42776
  cols: DEFAULT_COLS,
41969
- rows: DEFAULT_ROWS
42777
+ rows: DEFAULT_ROWS,
42778
+ graceTimer: null
41970
42779
  };
41971
42780
  const dataDisposable = pty.onData((data) => handlePtyData(session, data));
41972
42781
  const exitDisposable = pty.onExit(() => removeSession(session));
@@ -42004,6 +42813,13 @@ function createTerminalSessionManager(deps) {
42004
42813
  function detachSocket(session, socket) {
42005
42814
  if (session.activeSocket !== socket) return;
42006
42815
  session.activeSocket = null;
42816
+ if (isTheaterShell(session.id)) {
42817
+ clearGraceTimer(session);
42818
+ session.graceTimer = setTimeout(() => {
42819
+ session.graceTimer = null;
42820
+ if (session.activeSocket === null) removeSession(session);
42821
+ }, THEATER_SHELL_DETACH_GRACE_MS);
42822
+ }
42007
42823
  }
42008
42824
  function replayScrollback(session, socket) {
42009
42825
  for (const chunk of session.scrollback) {
@@ -42019,6 +42835,7 @@ function createTerminalSessionManager(deps) {
42019
42835
  }
42020
42836
  async function killSession(session, options = {}) {
42021
42837
  const killPty = options.killPty ?? true;
42838
+ clearGraceTimer(session);
42022
42839
  session.activeSocket?.close(4001, "terminal_closed");
42023
42840
  session.activeSocket = null;
42024
42841
  for (const disposable of session.disposables) disposable.dispose();
@@ -42028,7 +42845,7 @@ function createTerminalSessionManager(deps) {
42028
42845
  await runLaunchCleanup(session.cleanup);
42029
42846
  }
42030
42847
  }
42031
- return { canAttach, createSession, attach, getSessionMessagePolicy, terminate, stop, writeToSession };
42848
+ return { canAttach, createSession, attach, getSessionMessagePolicy, getSessionRenameCommand, terminate, stop, writeToSession };
42032
42849
  }
42033
42850
  async function runLaunchCleanup(cleanup) {
42034
42851
  try {
@@ -42042,6 +42859,14 @@ function killPtyBestEffort(pty) {
42042
42859
  } catch {
42043
42860
  }
42044
42861
  }
42862
+ function isTheaterShell(sessionId) {
42863
+ return sessionId.startsWith(THEATER_SHELL_SESSION_PREFIX);
42864
+ }
42865
+ function clearGraceTimer(session) {
42866
+ if (session.graceTimer === null) return;
42867
+ clearTimeout(session.graceTimer);
42868
+ session.graceTimer = null;
42869
+ }
42045
42870
  function toBuffer(data) {
42046
42871
  if (Buffer.isBuffer(data)) return data;
42047
42872
  if (Array.isArray(data)) return Buffer.concat(data);
@@ -42058,7 +42883,7 @@ var DEFAULT_TICKET_TTL_MS = 1e4;
42058
42883
  function createTerminalTicketRegistry(deps = {}) {
42059
42884
  const ttlMs = deps.ttlMs ?? DEFAULT_TICKET_TTL_MS;
42060
42885
  const now = deps.now ?? Date.now;
42061
- const randomTicket = deps.randomTicket ?? (() => crypto3.randomBytes(32).toString("base64url"));
42886
+ const randomTicket = deps.randomTicket ?? (() => crypto5.randomBytes(32).toString("base64url"));
42062
42887
  const tickets = /* @__PURE__ */ new Map();
42063
42888
  function issue3(context) {
42064
42889
  prune();
@@ -42085,7 +42910,7 @@ function createTerminalTicketRegistry(deps = {}) {
42085
42910
  }
42086
42911
  return { ttlMs, issue: issue3, consume, prune };
42087
42912
  }
42088
- var execFileAsync2 = promisify(execFile);
42913
+ var execFileAsync = promisify(execFile);
42089
42914
  var GIT_STATUS_TIMEOUT_MS = 4e3;
42090
42915
  var GIT_STATUS_MAX_BUFFER = 1024 * 1024;
42091
42916
  var GIT_HASH_TIMEOUT_MS = 4e3;
@@ -42094,11 +42919,13 @@ function createWorkspaceChangeScanner() {
42094
42919
  return {
42095
42920
  async snapshot(cwd) {
42096
42921
  try {
42097
- const { stdout } = await execFileAsync2("git", ["status", "--porcelain=v1", "-z", "--untracked-files=all"], {
42922
+ const { stdout } = await execFileAsync("git", ["status", "--porcelain=v1", "-z", "--untracked-files=all"], {
42098
42923
  cwd,
42099
42924
  encoding: "utf8",
42100
42925
  maxBuffer: GIT_STATUS_MAX_BUFFER,
42101
- timeout: GIT_STATUS_TIMEOUT_MS
42926
+ timeout: GIT_STATUS_TIMEOUT_MS,
42927
+ // 콘솔 없는 fleet-console 백엔드에서 git.exe 실행 시 콘솔 창이 깜빡이는 것을 방지한다.
42928
+ windowsHide: true
42102
42929
  });
42103
42930
  const entries = parseGitStatusPorcelainZ(stdout);
42104
42931
  if (!entries) return null;
@@ -42158,7 +42985,9 @@ function execGitHashObject(cwd, paths) {
42158
42985
  cwd,
42159
42986
  encoding: "utf8",
42160
42987
  maxBuffer: GIT_HASH_MAX_BUFFER,
42161
- timeout: GIT_HASH_TIMEOUT_MS
42988
+ timeout: GIT_HASH_TIMEOUT_MS,
42989
+ // 콘솔 없는 fleet-console 백엔드에서 git.exe 실행 시 콘솔 창이 깜빡이는 것을 방지한다.
42990
+ windowsHide: true
42162
42991
  },
42163
42992
  (error51, stdout) => {
42164
42993
  if (error51) {
@@ -42246,6 +43075,26 @@ var TheaterRegistry = class {
42246
43075
  this.#mruId = id;
42247
43076
  return item;
42248
43077
  }
43078
+ load(items) {
43079
+ this.restore(items);
43080
+ }
43081
+ restore(items) {
43082
+ const restored = /* @__PURE__ */ new Map();
43083
+ let mruId = null;
43084
+ for (const item of items) {
43085
+ const existing = restored.get(item.id);
43086
+ if (existing && existing.realpath !== item.realpath) {
43087
+ throw new Error("theater_id_collision");
43088
+ }
43089
+ restored.set(item.id, item);
43090
+ if (!mruId || item.lastOpenedAt.localeCompare(restored.get(mruId)?.lastOpenedAt ?? "") > 0) {
43091
+ mruId = item.id;
43092
+ }
43093
+ }
43094
+ this.#items.clear();
43095
+ for (const [id, item] of restored) this.#items.set(id, item);
43096
+ this.#mruId = mruId;
43097
+ }
42249
43098
  get(id) {
42250
43099
  return this.#items.get(id) ?? null;
42251
43100
  }
@@ -42255,6 +43104,11 @@ var TheaterRegistry = class {
42255
43104
  list() {
42256
43105
  return [...this.#items.values()].sort((left, right) => right.lastOpenedAt.localeCompare(left.lastOpenedAt));
42257
43106
  }
43107
+ remove(id) {
43108
+ const removed = this.#items.delete(id);
43109
+ if (this.#mruId === id) this.#mruId = this.list()[0]?.id ?? null;
43110
+ return removed;
43111
+ }
42258
43112
  };
42259
43113
 
42260
43114
  // src/update-check.ts
@@ -42314,6 +43168,7 @@ function createConsoleUpdateCheckService(deps = {}) {
42314
43168
  var DEFAULT_HOST2 = "127.0.0.1";
42315
43169
  var DEFAULT_PORT2 = 0;
42316
43170
  var SHELL_TERMINAL_SESSION_ID = "shell";
43171
+ var THEATER_SHELL_SESSION_PREFIX2 = "shell:";
42317
43172
  var SERVER_TIMEOUT_MS = 30 * 60 * 1e3;
42318
43173
  var MAX_BODY_BYTES = 1024 * 1024;
42319
43174
  function createConsoleServer(deps = {}) {
@@ -42331,6 +43186,9 @@ function createConsoleServer(deps = {}) {
42331
43186
  const folderGrants = createFolderGrantStore();
42332
43187
  const infraServices = createInfraServices();
42333
43188
  const dataDir = deps.dataDir ?? getFleetDataDir();
43189
+ initStore(dataDir);
43190
+ const durablePaths = createConsoleDataPaths({ fleetDataDir: dataDir });
43191
+ const durableStateStore = createConsoleDurableStateStore({ paths: durablePaths });
42334
43192
  const authEnvResolver = (cli) => resolveAuthEnv(cli, { authService: infraServices.authService });
42335
43193
  const ownsAgentRuntime = deps.agentRuntime === void 0;
42336
43194
  const agentRuntime = deps.agentRuntime ?? createFleetAgentRuntimeLifecycle({
@@ -42355,7 +43213,6 @@ function createConsoleServer(deps = {}) {
42355
43213
  getPort: () => lockHandle?.payload.port ?? port,
42356
43214
  getAdminToken: () => lockHandle?.payload.token ?? null
42357
43215
  });
42358
- const pickTerminalFolder = deps.terminalPickFolder ?? createNativeFolderPicker();
42359
43216
  const pendingRuntimeSessions = /* @__PURE__ */ new Map();
42360
43217
  const terminalSessions = createTerminalSessionManager({
42361
43218
  launch: deps.terminalLaunch ?? createDefaultTerminalLaunchResolver({
@@ -42369,10 +43226,18 @@ function createConsoleServer(deps = {}) {
42369
43226
  }),
42370
43227
  startShell: deps.terminalStartShell,
42371
43228
  maxSessions: deps.maxTerminalSessions,
42372
- // PTY가 종료되면 콘솔 세션 목록에서도 제거해 잔존/재실행을 막는다.
42373
43229
  onSessionExit: (sessionId) => {
42374
43230
  pendingRuntimeSessions.delete(sessionId);
42375
- observability.removeTerminalSession(sessionId);
43231
+ const operation = observability.getDurableOperation(sessionId);
43232
+ const providerSession = operation?.providerSession ?? readProviderSessionCapture(sessionId, { capturesDir: durablePaths.capturesDir });
43233
+ if (providerSession) {
43234
+ observability.updateTerminalSessionProviderSession(sessionId, providerSession);
43235
+ const dormant = observability.transitionTerminalSessionToDormant(sessionId, providerSession);
43236
+ if (dormant) observability.notifySessionUpdated(dormant);
43237
+ } else {
43238
+ observability.removeTerminalSession(sessionId);
43239
+ }
43240
+ persistDurableState();
42376
43241
  }
42377
43242
  });
42378
43243
  const unsubscribeCarrierReminderRouter = createCarrierResultReminderRouter({
@@ -42404,6 +43269,12 @@ function createConsoleServer(deps = {}) {
42404
43269
  let activeEndpoint = null;
42405
43270
  let agentRuntimeStopped = false;
42406
43271
  let consoleResourcesDisposed = false;
43272
+ const carrierSettingsRouter = createCarrierSettingsRouter({
43273
+ registry: carrierRegistry,
43274
+ isAuthorized: isTerminalAuthorized,
43275
+ readJsonBody,
43276
+ writeJson
43277
+ });
42407
43278
  function handleRequest(req, res) {
42408
43279
  const pathname = getPathname(req);
42409
43280
  if (pathname === "/console/codex" || pathname.startsWith("/console/codex/")) {
@@ -42423,14 +43294,28 @@ function createConsoleServer(deps = {}) {
42423
43294
  runAsyncHandler(handleTerminalTicket(req, res), res);
42424
43295
  return;
42425
43296
  }
42426
- if (pathname === "/terminal/folders/pick") {
42427
- runAsyncHandler(handleTerminalFolderPick(req, res), res);
43297
+ if (pathname === "/terminal/folders/list") {
43298
+ runAsyncHandler(handleTerminalFoldersList(req, res), res);
43299
+ return;
43300
+ }
43301
+ if (pathname === "/terminal/folders/grants") {
43302
+ runAsyncHandler(handleTerminalFolderGrants(req, res), res);
42428
43303
  return;
42429
43304
  }
42430
43305
  if (pathname === "/terminal/sessions") {
42431
43306
  runAsyncHandler(handleTerminalSessions(req, res), res);
42432
43307
  return;
42433
43308
  }
43309
+ const terminalSessionResumeMatch = pathname.match(/^\/terminal\/sessions\/([^/]+)\/resume$/);
43310
+ if (terminalSessionResumeMatch) {
43311
+ runAsyncHandler(handleTerminalSessionResume(req, res, decodeURIComponent(terminalSessionResumeMatch[1] ?? "")), res);
43312
+ return;
43313
+ }
43314
+ const terminalSessionTurnMatch = pathname.match(/^\/terminal\/sessions\/([^/]+)\/turn$/);
43315
+ if (terminalSessionTurnMatch) {
43316
+ runAsyncHandler(handleTerminalSessionTurn(req, res, decodeURIComponent(terminalSessionTurnMatch[1] ?? "")), res);
43317
+ return;
43318
+ }
42434
43319
  const terminalSessionItemMatch = pathname.match(/^\/terminal\/sessions\/([^/]+)$/);
42435
43320
  if (terminalSessionItemMatch) {
42436
43321
  runAsyncHandler(handleTerminalSessionItem(req, res, decodeURIComponent(terminalSessionItemMatch[1] ?? "")), res);
@@ -42440,6 +43325,11 @@ function createConsoleServer(deps = {}) {
42440
43325
  runAsyncHandler(handleObserverTheaters(req, res), res);
42441
43326
  return;
42442
43327
  }
43328
+ const theaterItemMatch = pathname.match(/^\/observer\/theaters\/([^/]+)$/);
43329
+ if (theaterItemMatch) {
43330
+ runAsyncHandler(handleObserverTheaterItem(req, res, decodeURIComponent(theaterItemMatch[1] ?? "")), res);
43331
+ return;
43332
+ }
42443
43333
  const theaterSessionMatch = pathname.match(/^\/observer\/theaters\/([^/]+)\/sessions$/);
42444
43334
  if (theaterSessionMatch) {
42445
43335
  runAsyncHandler(handleObserverTheaterSessions(req, res, decodeURIComponent(theaterSessionMatch[1] ?? "")), res);
@@ -42453,6 +43343,10 @@ function createConsoleServer(deps = {}) {
42453
43343
  handleObserverCarriers(req, res);
42454
43344
  return;
42455
43345
  }
43346
+ if (pathname === "/carrier-settings" || pathname.startsWith("/carrier-settings/")) {
43347
+ runAsyncBooleanHandler(carrierSettingsRouter({ req, res, pathname }), res);
43348
+ return;
43349
+ }
42456
43350
  if (pathname === "/observer/tenants") {
42457
43351
  handleObserverWorkspaces(req, res);
42458
43352
  return;
@@ -42488,6 +43382,30 @@ function createConsoleServer(deps = {}) {
42488
43382
  };
42489
43383
  writeJson(res, 200, body);
42490
43384
  }
43385
+ async function handleTerminalSessionTurn(req, res, sessionId) {
43386
+ if (req.method !== "POST") {
43387
+ writeJson(res, 405, { error: "Method not allowed" });
43388
+ return;
43389
+ }
43390
+ const token = lockHandle?.payload.token;
43391
+ if (!token || req.headers.authorization !== `Bearer ${token}`) {
43392
+ writeJson(res, 401, { error: "Unauthorized" });
43393
+ return;
43394
+ }
43395
+ const body = await readJsonBody(req);
43396
+ const turnState = body?.phase === "start" ? "running" : body?.phase === "end" ? "ended" : null;
43397
+ if (turnState === null) {
43398
+ writeJson(res, 400, { error: "invalid_phase" });
43399
+ return;
43400
+ }
43401
+ const updated = observability.setTerminalSessionTurnState(sessionId, turnState);
43402
+ if (!updated) {
43403
+ writeJson(res, 404, { error: "terminal_session_not_found" });
43404
+ return;
43405
+ }
43406
+ observability.notifySessionUpdated(updated);
43407
+ writeJson(res, 200, { ok: true });
43408
+ }
42491
43409
  async function handleTerminalTicket(req, res) {
42492
43410
  if (req.method !== "POST") {
42493
43411
  writeJson(res, 405, { error: "Method not allowed" });
@@ -42500,16 +43418,29 @@ function createConsoleServer(deps = {}) {
42500
43418
  const body = await readJsonBody(req);
42501
43419
  const kind = body?.kind === "shell" ? "shell" : "fleet";
42502
43420
  const requestedSessionId = body?.sessionId;
43421
+ const requestedTheaterId = typeof body?.theaterId === "string" ? body.theaterId : void 0;
42503
43422
  let sessionId;
43423
+ let cwd;
42504
43424
  if (kind === "shell") {
42505
- sessionId = SHELL_TERMINAL_SESSION_ID;
43425
+ if (typeof requestedSessionId === "string" && requestedSessionId.startsWith(THEATER_SHELL_SESSION_PREFIX2) && requestedTheaterId) {
43426
+ const theater = theaters.get(requestedTheaterId);
43427
+ if (!theater) {
43428
+ writeJson(res, 404, { error: "theater_not_found" });
43429
+ return;
43430
+ }
43431
+ sessionId = requestedSessionId;
43432
+ cwd = theater.path;
43433
+ } else {
43434
+ sessionId = SHELL_TERMINAL_SESSION_ID;
43435
+ cwd = "";
43436
+ }
42506
43437
  } else if (typeof requestedSessionId === "string" && requestedSessionId.length > 0) {
42507
43438
  sessionId = requestedSessionId;
43439
+ cwd = observability.getLaunchCwd(sessionId);
42508
43440
  } else {
42509
43441
  writeJson(res, 400, { error: "terminal_session_not_found" });
42510
43442
  return;
42511
43443
  }
42512
- const cwd = kind === "shell" ? "" : observability.getLaunchCwd(sessionId);
42513
43444
  if (cwd === null) {
42514
43445
  writeJson(res, 404, { error: "terminal_session_not_found" });
42515
43446
  return;
@@ -42524,7 +43455,7 @@ function createConsoleServer(deps = {}) {
42524
43455
  kind
42525
43456
  }));
42526
43457
  }
42527
- async function handleTerminalFolderPick(req, res) {
43458
+ async function handleTerminalFoldersList(req, res) {
42528
43459
  if (req.method !== "POST") {
42529
43460
  writeJson(res, 405, { error: "Method not allowed" });
42530
43461
  return;
@@ -42533,16 +43464,41 @@ function createConsoleServer(deps = {}) {
42533
43464
  writeJson(res, 401, { error: "unauthorized" });
42534
43465
  return;
42535
43466
  }
42536
- const result = await pickTerminalFolder();
42537
- if (result.kind === "cancelled") {
42538
- writeJson(res, 200, { cancelled: true });
43467
+ const body = await readJsonBody(req);
43468
+ if (!isPlainObject2(body) || body.path !== void 0 && body.path !== null && typeof body.path !== "string") {
43469
+ writeJson(res, 400, { error: "invalid_path" });
43470
+ return;
43471
+ }
43472
+ try {
43473
+ const payload = await listTerminalFolders(body.path === void 0 ? null : body.path);
43474
+ writeJson(res, 200, payload);
43475
+ } catch (error51) {
43476
+ if (error51 instanceof TerminalFolderListError) {
43477
+ writeJson(res, terminalFolderListStatus(error51), { error: error51.code });
43478
+ return;
43479
+ }
43480
+ throw error51;
43481
+ }
43482
+ }
43483
+ async function handleTerminalFolderGrants(req, res) {
43484
+ if (req.method !== "POST") {
43485
+ writeJson(res, 405, { error: "Method not allowed" });
43486
+ return;
43487
+ }
43488
+ if (!isTerminalAuthorized(req)) {
43489
+ writeJson(res, 401, { error: "unauthorized" });
42539
43490
  return;
42540
43491
  }
42541
- if (result.kind === "error") {
42542
- writeJson(res, result.error === "invalid_folder" ? 400 : 503, { error: result.error });
43492
+ const body = await readJsonBody(req);
43493
+ if (!isPlainObject2(body) || typeof body.path !== "string") {
43494
+ writeJson(res, 400, { error: "invalid_folder" });
42543
43495
  return;
42544
43496
  }
42545
- writeJson(res, 200, { folderGrantId: folderGrants.issue(result.cwd) });
43497
+ try {
43498
+ writeJson(res, 200, { folderGrantId: folderGrants.issue(body.path) });
43499
+ } catch {
43500
+ writeJson(res, 400, { error: "invalid_folder" });
43501
+ }
42546
43502
  }
42547
43503
  async function handleTerminalSessions(req, res) {
42548
43504
  if (!isTerminalAuthorized(req)) {
@@ -42571,6 +43527,17 @@ function createConsoleServer(deps = {}) {
42571
43527
  }
42572
43528
  await createTerminalSessionForCwd(cwd, res, cliId);
42573
43529
  }
43530
+ function injectRenameCommand(sessionId, label) {
43531
+ if (!label) return;
43532
+ const renameCommand = terminalSessions.getSessionRenameCommand(sessionId);
43533
+ if (!renameCommand) return;
43534
+ const safeLabel = sanitizeCarrierResultReminder(label.replace(/[\r\n\t]+/g, " ")).trim();
43535
+ if (safeLabel.length === 0) return;
43536
+ const policy = terminalSessions.getSessionMessagePolicy(sessionId) ?? {};
43537
+ for (const chunk of formatCarrierResultReminderMessage(policy, `${renameCommand} ${safeLabel}`)) {
43538
+ terminalSessions.writeToSession(sessionId, chunk);
43539
+ }
43540
+ }
42574
43541
  async function handleTerminalSessionItem(req, res, sessionId) {
42575
43542
  if (req.method !== "DELETE" && req.method !== "PATCH") {
42576
43543
  writeJson(res, 405, { error: "Method not allowed" });
@@ -42592,13 +43559,62 @@ function createConsoleServer(deps = {}) {
42592
43559
  return;
42593
43560
  }
42594
43561
  observability.notifySessionUpdated(updated);
43562
+ injectRenameCommand(sessionId, updated.label);
43563
+ persistDurableState();
42595
43564
  writeJson(res, 200, updated);
42596
43565
  return;
42597
43566
  }
42598
- terminalSessions.terminate(sessionId);
42599
- observability.removeTerminalSession(sessionId);
43567
+ forgetTerminalSession(sessionId);
42600
43568
  writeJson(res, 200, { ok: true });
42601
43569
  }
43570
+ async function handleTerminalSessionResume(req, res, sessionId) {
43571
+ if (req.method !== "POST") {
43572
+ writeJson(res, 405, { error: "Method not allowed" });
43573
+ return;
43574
+ }
43575
+ if (!isTerminalAuthorized(req)) {
43576
+ writeJson(res, 401, { error: "unauthorized" });
43577
+ return;
43578
+ }
43579
+ const dormantSession = observability.listTerminalSessions().find((session) => session.sessionId === sessionId && session.status === "dormant");
43580
+ const operation = dormantSession ? observability.getDurableOperation(sessionId) : null;
43581
+ if (!operation) {
43582
+ writeJson(res, 404, { error: "session_not_found" });
43583
+ return;
43584
+ }
43585
+ const cliId = readDurableAgentCliId(operation);
43586
+ if (!cliId) {
43587
+ writeJson(res, 409, { error: "resume_unavailable" });
43588
+ return;
43589
+ }
43590
+ const providerSession = operation.providerSession ?? readProviderSessionCapture(sessionId, { capturesDir: durablePaths.capturesDir });
43591
+ if (!providerSession) {
43592
+ writeJson(res, 409, { error: "resume_unavailable" });
43593
+ return;
43594
+ }
43595
+ observability.updateTerminalSessionProviderSession(sessionId, providerSession);
43596
+ const starting = observability.updateTerminalSessionStatus(sessionId, "starting");
43597
+ if (starting) observability.notifySessionUpdated(starting);
43598
+ try {
43599
+ await terminalSessions.createSession({ sessionId, cwd: operation.cwd, cliId, resumeSessionId: providerSession.sessionId });
43600
+ const runtimeSession = pendingRuntimeSessions.get(sessionId);
43601
+ pendingRuntimeSessions.delete(sessionId);
43602
+ const resumed = runtimeSession ? observability.registerTerminalRuntimeSession(runtimeSession) ?? starting : observability.updateTerminalSessionStatus(sessionId, "terminal-only") ?? starting;
43603
+ if (!resumed) {
43604
+ writeJson(res, 404, { error: "session_not_found" });
43605
+ return;
43606
+ }
43607
+ observability.notifySessionUpdated(resumed);
43608
+ persistDurableState();
43609
+ writeJson(res, 200, resumed);
43610
+ } catch {
43611
+ pendingRuntimeSessions.delete(sessionId);
43612
+ const reverted = observability.updateTerminalSessionStatus(sessionId, "dormant");
43613
+ if (reverted) observability.notifySessionUpdated(reverted);
43614
+ persistDurableState();
43615
+ writeJson(res, 503, { error: "terminal_unavailable" });
43616
+ }
43617
+ }
42602
43618
  async function handleObserverTheaters(req, res) {
42603
43619
  if (req.method === "GET") {
42604
43620
  writeJson(res, 200, { theaters: listTheaterInfos(), agentClis: getAgentCliMetadata() });
@@ -42612,27 +43628,44 @@ function createConsoleServer(deps = {}) {
42612
43628
  writeJson(res, 401, { error: "unauthorized" });
42613
43629
  return;
42614
43630
  }
42615
- const result = await pickTerminalFolder();
42616
- if (result.kind === "cancelled") {
42617
- writeJson(res, 200, { cancelled: true });
43631
+ const body = await readJsonBody(req);
43632
+ if (!isPlainObject2(body) || typeof body.folderGrantId !== "string") {
43633
+ writeJson(res, 400, { error: "invalid_folder_grant" });
42618
43634
  return;
42619
43635
  }
42620
- if (result.kind === "error") {
42621
- writeJson(res, result.error === "invalid_folder" ? 400 : 503, { error: result.error });
43636
+ const cwd = folderGrants.consume(body.folderGrantId);
43637
+ if (!cwd) {
43638
+ writeJson(res, 400, { error: "invalid_folder_grant" });
42622
43639
  return;
42623
43640
  }
42624
- const theater = await theaters.register(result.cwd);
43641
+ const theater = await theaters.register(cwd);
42625
43642
  let hasWiki = false;
42626
43643
  try {
42627
- await codex.registerWorkspace(result.cwd);
43644
+ await codex.registerWorkspace(cwd);
42628
43645
  hasWiki = true;
42629
43646
  } catch (error51) {
42630
43647
  if (!(error51 instanceof Error && error51.message === "knowledge_root_missing")) {
42631
43648
  console.warn(`[fleet-console] Codex workspace registration skipped for Theater ${theater.id}: ${error51 instanceof Error ? error51.message : String(error51)}`);
42632
43649
  }
42633
43650
  }
43651
+ persistDurableState();
42634
43652
  writeJson(res, 200, toTheaterInfo(theater, hasWiki));
42635
43653
  }
43654
+ async function handleObserverTheaterItem(req, res, theaterId) {
43655
+ if (req.method !== "DELETE") {
43656
+ writeJson(res, 405, { error: "Method not allowed" });
43657
+ return;
43658
+ }
43659
+ if (!isTerminalAuthorized(req)) {
43660
+ writeJson(res, 401, { error: "unauthorized" });
43661
+ return;
43662
+ }
43663
+ const operations = observability.listDurableOperations().filter((operation) => operation.theaterId === theaterId);
43664
+ theaters.remove(theaterId);
43665
+ for (const operation of operations) forgetTerminalSession(operation.sessionId, { persist: false });
43666
+ persistDurableState();
43667
+ writeJson(res, 200, { ok: true });
43668
+ }
42636
43669
  async function handleObserverTheaterSessions(req, res, theaterId) {
42637
43670
  if (req.method !== "POST") {
42638
43671
  writeJson(res, 405, { error: "Method not allowed" });
@@ -42653,7 +43686,7 @@ function createConsoleServer(deps = {}) {
42653
43686
  await createTerminalSessionForCwd(theater.path, res, cliId);
42654
43687
  }
42655
43688
  async function createTerminalSessionForCwd(cwd, res, cliId) {
42656
- const sessionId = crypto3.randomUUID();
43689
+ const sessionId = crypto5.randomUUID();
42657
43690
  const session = observability.createPendingTerminalSession({ sessionId, cwd, cliId });
42658
43691
  try {
42659
43692
  await terminalSessions.createSession({ sessionId, cwd, cliId });
@@ -42661,6 +43694,7 @@ function createConsoleServer(deps = {}) {
42661
43694
  pendingRuntimeSessions.delete(sessionId);
42662
43695
  const created = runtimeSession ? observability.registerTerminalRuntimeSession(runtimeSession) ?? session : observability.updateTerminalSessionStatus(sessionId, "terminal-only") ?? session;
42663
43696
  observability.notifySessionUpdated(created);
43697
+ persistDurableState();
42664
43698
  writeJson(res, 200, created);
42665
43699
  } catch (error51) {
42666
43700
  pendingRuntimeSessions.delete(sessionId);
@@ -42734,6 +43768,13 @@ function createConsoleServer(deps = {}) {
42734
43768
  function listTheaterInfos() {
42735
43769
  return theaters.list().map((theater) => toTheaterInfo(theater, codex.getWorkspace(theater.id) !== null));
42736
43770
  }
43771
+ function forgetTerminalSession(sessionId, options = {}) {
43772
+ terminalSessions.terminate(sessionId);
43773
+ pendingRuntimeSessions.delete(sessionId);
43774
+ observability.removeTerminalSession(sessionId);
43775
+ unlinkProviderSessionCapture(sessionId, { capturesDir: durablePaths.capturesDir });
43776
+ if (options.persist !== false) persistDurableState();
43777
+ }
42737
43778
  function toTheaterInfo(theater, hasWiki) {
42738
43779
  return {
42739
43780
  id: theater.id,
@@ -42796,12 +43837,69 @@ function createConsoleServer(deps = {}) {
42796
43837
  terminalUpgrade.close();
42797
43838
  currentLock?.release();
42798
43839
  }
43840
+ function rehydrateDurableState() {
43841
+ let state;
43842
+ try {
43843
+ state = durableStateStore.load();
43844
+ theaters.restore(state.theaters);
43845
+ } catch (error51) {
43846
+ console.warn(`[fleet-console] Durable state restore skipped: ${error51 instanceof Error ? error51.message : String(error51)}`);
43847
+ state = emptyDurableConsoleState();
43848
+ theaters.restore([]);
43849
+ }
43850
+ const merged = mergeProviderSessionCaptures(state, { capturesDir: durablePaths.capturesDir });
43851
+ syncProviderSessionsToObservability(merged);
43852
+ const restorable = {
43853
+ ...merged,
43854
+ operations: merged.operations.filter((operation) => operation.providerSession)
43855
+ };
43856
+ if (merged !== state || restorable.operations.length !== merged.operations.length) {
43857
+ try {
43858
+ durableStateStore.save(restorable);
43859
+ cleanupProviderSessionCaptures(restorable, { capturesDir: durablePaths.capturesDir });
43860
+ } catch (error51) {
43861
+ console.warn(`[fleet-console] Durable state capture merge was not persisted: ${error51 instanceof Error ? error51.message : String(error51)}`);
43862
+ }
43863
+ } else {
43864
+ cleanupProviderSessionCaptures(restorable, { capturesDir: durablePaths.capturesDir });
43865
+ }
43866
+ for (const operation of restorable.operations) {
43867
+ if (theaters.get(operation.theaterId)) observability.injectDormantOperation(operation);
43868
+ }
43869
+ }
43870
+ function persistDurableState() {
43871
+ try {
43872
+ const state = mergeProviderSessionCaptures({
43873
+ version: 1,
43874
+ theaters: theaters.list(),
43875
+ operations: observability.listDurableOperations()
43876
+ }, { capturesDir: durablePaths.capturesDir });
43877
+ syncProviderSessionsToObservability(state);
43878
+ const operationsWithProviderSession = state.operations.filter((operation) => operation.providerSession);
43879
+ durableStateStore.save({
43880
+ version: state.version,
43881
+ theaters: state.theaters,
43882
+ // 대화 없는 빈 Operation 드롭은 rehydrate에서 capture 머지 뒤에만 수행한다.
43883
+ // create 시점의 메타데이터를 먼저 남겨야 이후 도착한 capture 파일이 재시작 때 병합될 수 있다.
43884
+ operations: state.operations
43885
+ });
43886
+ cleanupProviderSessionCaptures({ ...state, operations: operationsWithProviderSession }, { capturesDir: durablePaths.capturesDir });
43887
+ } catch (error51) {
43888
+ console.warn(`[fleet-console] Durable state save failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
43889
+ }
43890
+ }
43891
+ function syncProviderSessionsToObservability(state) {
43892
+ for (const operation of state.operations) {
43893
+ if (operation.providerSession) observability.updateTerminalSessionProviderSession(operation.sessionId, operation.providerSession);
43894
+ }
43895
+ }
42799
43896
  return {
42800
43897
  host,
42801
43898
  port,
42802
43899
  async start(lockPaths) {
42803
43900
  if (server && lockHandle) return lockHandle.payload.endpoint;
42804
43901
  try {
43902
+ rehydrateDurableState();
42805
43903
  await new Promise((resolve, reject) => {
42806
43904
  const srv = createHttpServer(handleRequest, terminalUpgrade);
42807
43905
  srv.once("error", reject);
@@ -42884,6 +43982,14 @@ function resolveCarrierEventOrigin(event, jobOriginById) {
42884
43982
  function queueOriginCleanup(jobId, jobOriginById) {
42885
43983
  queueMicrotask(() => jobOriginById.delete(jobId));
42886
43984
  }
43985
+ function readDurableAgentCliId(operation) {
43986
+ if (!operation.cliId) return null;
43987
+ try {
43988
+ return parseAgentCliId(operation.cliId) ?? null;
43989
+ } catch {
43990
+ return null;
43991
+ }
43992
+ }
42887
43993
  function createHttpServer(handler, terminalUpgrade) {
42888
43994
  const srv = http.createServer(handler);
42889
43995
  srv.timeout = SERVER_TIMEOUT_MS;
@@ -42943,6 +44049,14 @@ function readOptionalAgentCliId(value, res) {
42943
44049
  return false;
42944
44050
  }
42945
44051
  }
44052
+ function terminalFolderListStatus(error51) {
44053
+ if (error51.code === "forbidden") return 403;
44054
+ if (error51.code === "not_found") return 404;
44055
+ return 400;
44056
+ }
44057
+ function isPlainObject2(value) {
44058
+ return typeof value === "object" && value !== null && !Array.isArray(value);
44059
+ }
42946
44060
  async function readJsonBody(req) {
42947
44061
  const chunks = [];
42948
44062
  let total = 0;
@@ -43001,6 +44115,78 @@ function isAllowedTerminalOrigin(req, expectedPort) {
43001
44115
  if (origin === void 0) return true;
43002
44116
  return origin === `http://127.0.0.1:${expectedPort}`;
43003
44117
  }
44118
+ var CAPTURE_TEMP_PREFIX = ".capture.";
44119
+ async function runCaptureSessionHook(provider, env = process6.env) {
44120
+ return captureSession({
44121
+ diagnostics: process6.stderr,
44122
+ env,
44123
+ input: await readStdin(),
44124
+ provider
44125
+ });
44126
+ }
44127
+ function captureSession(options) {
44128
+ try {
44129
+ return captureSessionStrict(options);
44130
+ } catch (error51) {
44131
+ options.diagnostics?.write(`[fleet-console] capture-session skipped: ${error51 instanceof Error ? error51.message : String(error51)}
44132
+ `);
44133
+ return null;
44134
+ }
44135
+ }
44136
+ function captureSessionStrict(options) {
44137
+ const provider = parseProvider(options.provider);
44138
+ const fleetSessionId = readFleetSessionId(options.env ?? process6.env);
44139
+ const input = parseHookInput(options.input ?? "");
44140
+ const providerSession = toProviderSession(provider, input, options.now ?? (() => /* @__PURE__ */ new Date()));
44141
+ const capturesDir = (options.paths ?? createConsoleDataPaths()).capturesDir;
44142
+ const finalPath = path93__default.join(capturesDir, `${fleetSessionId}.json`);
44143
+ const tempPath = path93__default.join(capturesDir, `${CAPTURE_TEMP_PREFIX}${fleetSessionId}.${process6.pid}.${Date.now()}.tmp`);
44144
+ fs5__default.mkdirSync(capturesDir, { recursive: true, mode: 448 });
44145
+ fs5__default.writeFileSync(tempPath, `${JSON.stringify(providerSession, null, 2)}
44146
+ `, { mode: 384 });
44147
+ fs5__default.renameSync(tempPath, finalPath);
44148
+ return { path: finalPath, providerSession };
44149
+ }
44150
+ function parseProvider(value) {
44151
+ if (value === "claude" || value === "codex") return value;
44152
+ throw new Error("invalid_provider");
44153
+ }
44154
+ function readFleetSessionId(env) {
44155
+ const value = env.FLEET_CONSOLE_SESSION_ID;
44156
+ if (!value) throw new Error("missing_fleet_session_id");
44157
+ if (path93__default.basename(value) !== value || value.includes(path93__default.sep) || value.includes(path93__default.posix.sep)) {
44158
+ throw new Error("invalid_fleet_session_id");
44159
+ }
44160
+ return value;
44161
+ }
44162
+ function parseHookInput(input) {
44163
+ try {
44164
+ const parsed = JSON.parse(input);
44165
+ if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) return parsed;
44166
+ } catch {
44167
+ }
44168
+ throw new Error("invalid_hook_input");
44169
+ }
44170
+ function toProviderSession(provider, input, now) {
44171
+ if (typeof input.session_id !== "string" || input.session_id.length === 0) throw new Error("missing_provider_session_id");
44172
+ return {
44173
+ provider,
44174
+ sessionId: input.session_id,
44175
+ ...typeof input.transcript_path === "string" && input.transcript_path.length > 0 ? { transcriptPath: input.transcript_path } : {},
44176
+ ...typeof input.source === "string" && input.source.length > 0 ? { source: input.source } : {},
44177
+ capturedAt: now().toISOString()
44178
+ };
44179
+ }
44180
+ function readStdin() {
44181
+ return new Promise((resolve, reject) => {
44182
+ const chunks = [];
44183
+ process6.stdin.on("data", (chunk) => {
44184
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
44185
+ });
44186
+ process6.stdin.on("error", reject);
44187
+ process6.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
44188
+ });
44189
+ }
43004
44190
  function createConsoleStalePolicy(deps = {}) {
43005
44191
  const fsImpl = deps.fs ?? fs5__default;
43006
44192
  function isBuildStale(lock, buildFile) {
@@ -43013,12 +44199,42 @@ function createConsoleStalePolicy(deps = {}) {
43013
44199
  }
43014
44200
  return { isBuildStale };
43015
44201
  }
44202
+ var TURN_POST_TIMEOUT_MS = 1500;
44203
+ async function runTurnStateHook(phase, env = process6.env, options = {}) {
44204
+ try {
44205
+ await postTurnState(phase, env, options);
44206
+ } catch {
44207
+ }
44208
+ }
44209
+ async function postTurnState(phase, env, options) {
44210
+ const sessionId = env.FLEET_CONSOLE_SESSION_ID;
44211
+ if (!sessionId) return;
44212
+ const paths = createConsolePaths({ env });
44213
+ const lock = createConsoleLock().readLock(paths.lockFile);
44214
+ if (!lock) return;
44215
+ const fetchImpl = options.fetchImpl ?? fetch;
44216
+ const controller = new AbortController();
44217
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? TURN_POST_TIMEOUT_MS);
44218
+ try {
44219
+ await fetchImpl(`${lock.endpoint}terminal/sessions/${encodeURIComponent(sessionId)}/turn`, {
44220
+ method: "POST",
44221
+ headers: {
44222
+ authorization: `Bearer ${lock.token}`,
44223
+ "content-type": "application/json"
44224
+ },
44225
+ body: JSON.stringify({ phase }),
44226
+ signal: controller.signal
44227
+ });
44228
+ } finally {
44229
+ clearTimeout(timer);
44230
+ }
44231
+ }
43016
44232
 
43017
44233
  // src/cli.ts
43018
44234
  var FIXED_HOST = "127.0.0.1";
43019
44235
  var HELP_BANNER_INDENT = " ";
43020
44236
  var DEFAULT_HELP_RELEASE = "local";
43021
- var CONSOLE_HOOK_COMMANDS = /* @__PURE__ */ new Set(["subagents-context"]);
44237
+ var CONSOLE_HOOK_COMMANDS = /* @__PURE__ */ new Set(["capture-session", "subagents-context", "turn-start", "turn-end"]);
43022
44238
  function parseConsoleCliMode(argv) {
43023
44239
  if (argv.length === 0) return "start";
43024
44240
  const [first, ...rest] = argv;
@@ -43045,10 +44261,14 @@ Run 'fleet console --help' for usage.`);
43045
44261
  }
43046
44262
  function parseConsoleHookCommand(argv) {
43047
44263
  const [commandName, ...rest] = argv;
43048
- if (!commandName || !CONSOLE_HOOK_COMMANDS.has(commandName) || rest.length > 0) {
44264
+ if (!commandName || !CONSOLE_HOOK_COMMANDS.has(commandName)) {
43049
44265
  throw new Error("Unknown fleet-console hook command");
43050
44266
  }
43051
- return "subagents-context";
44267
+ if (commandName === "subagents-context" && rest.length === 0) return { command: "subagents-context" };
44268
+ if (commandName === "turn-start" && rest.length === 0) return { command: "turn-start" };
44269
+ if (commandName === "turn-end" && rest.length === 0) return { command: "turn-end" };
44270
+ if (commandName === "capture-session" && rest.length === 1 && rest[0]) return { command: "capture-session", provider: rest[0] };
44271
+ throw new Error("Unknown fleet-console hook command");
43052
44272
  }
43053
44273
  function buildConsoleHelpText(options = {}) {
43054
44274
  const colorEnabled = resolveColorEnabled(options);
@@ -43110,8 +44330,8 @@ function buildWikiHelpText(options = {}) {
43110
44330
  return colorEnabled ? text : stripAnsi(text);
43111
44331
  }
43112
44332
  function createConsoleDaemonLifecycle(deps = {}) {
43113
- const env = deps.env ?? process4.env;
43114
- const execPath = deps.execPath ?? process4.execPath;
44333
+ const env = deps.env ?? process6.env;
44334
+ const execPath = deps.execPath ?? process6.execPath;
43115
44335
  const serverModulePath = deps.serverModulePath ?? resolveDefaultServerModulePath();
43116
44336
  const spawnDetached = deps.spawnDetached ?? ((bin, args, options) => {
43117
44337
  spawn(bin, [...args], options).unref();
@@ -43125,10 +44345,10 @@ function createConsoleDaemonLifecycle(deps = {}) {
43125
44345
  const server = createConsoleServer();
43126
44346
  await server.start(paths);
43127
44347
  await new Promise((resolve) => {
43128
- process4.once("SIGTERM", () => {
44348
+ process6.once("SIGTERM", () => {
43129
44349
  void server.stop().finally(resolve);
43130
44350
  });
43131
- process4.once("SIGINT", () => {
44351
+ process6.once("SIGINT", () => {
43132
44352
  void server.stop().finally(resolve);
43133
44353
  });
43134
44354
  });
@@ -43142,14 +44362,14 @@ function createConsoleDaemonLifecycle(deps = {}) {
43142
44362
  const payload = readTrustedLock();
43143
44363
  if (!payload) return;
43144
44364
  try {
43145
- process4.kill(payload.pid, "SIGTERM");
44365
+ process6.kill(payload.pid, "SIGTERM");
43146
44366
  } catch (err) {
43147
44367
  if (err.code !== "ESRCH") throw err;
43148
44368
  }
43149
44369
  await sleep(200);
43150
44370
  try {
43151
- process4.kill(payload.pid, 0);
43152
- process4.kill(payload.pid, "SIGKILL");
44371
+ process6.kill(payload.pid, 0);
44372
+ process6.kill(payload.pid, "SIGKILL");
43153
44373
  } catch (err) {
43154
44374
  if (err.code !== "ESRCH") throw err;
43155
44375
  }
@@ -43256,49 +44476,55 @@ async function runConsoleRestart(deps = {}) {
43256
44476
  return openFleetConsole({ lifecycle, openBrowser: deps.openBrowser });
43257
44477
  }
43258
44478
  async function main() {
43259
- if (process4.argv[2] === "serve") {
44479
+ if (process6.argv[2] === "serve") {
43260
44480
  await createConsoleDaemonLifecycle().runServer();
43261
44481
  return;
43262
44482
  }
43263
- if (process4.argv[2] === "hook") {
43264
- parseConsoleHookCommand(process4.argv.slice(3));
43265
- {
43266
- process4.stdout.write(`${runSubagentsContextHook(process4.env)}
44483
+ if (process6.argv[2] === "hook") {
44484
+ const hookCommand = parseConsoleHookCommand(process6.argv.slice(3));
44485
+ if (hookCommand.command === "subagents-context") {
44486
+ process6.stdout.write(`${runSubagentsContextHook(process6.env)}
43267
44487
  `);
43268
44488
  return;
43269
44489
  }
44490
+ if (hookCommand.command === "turn-start" || hookCommand.command === "turn-end") {
44491
+ await runTurnStateHook(hookCommand.command === "turn-start" ? "start" : "end", process6.env);
44492
+ return;
44493
+ }
44494
+ await runCaptureSessionHook(hookCommand.provider, process6.env);
44495
+ return;
43270
44496
  }
43271
- if (process4.argv[2] === "codex") {
43272
- await mainWiki(process4.argv.slice(3));
44497
+ if (process6.argv[2] === "codex") {
44498
+ await mainWiki(process6.argv.slice(3));
43273
44499
  return;
43274
44500
  }
43275
- const mode = parseConsoleCliMode(process4.argv.slice(2));
44501
+ const mode = parseConsoleCliMode(process6.argv.slice(2));
43276
44502
  if (mode === "help") {
43277
- process4.stdout.write(`${buildConsoleHelpText({ env: process4.env, isTTY: process4.stdout.isTTY })}
44503
+ process6.stdout.write(`${buildConsoleHelpText({ env: process6.env, isTTY: process6.stdout.isTTY })}
43278
44504
  `);
43279
44505
  return;
43280
44506
  }
43281
44507
  if (mode === "status") {
43282
- process4.stdout.write(`${await runConsoleStatus()}
44508
+ process6.stdout.write(`${await runConsoleStatus()}
43283
44509
  `);
43284
44510
  return;
43285
44511
  }
43286
44512
  if (mode === "stop") {
43287
- process4.stdout.write(`${await runConsoleStop()}
44513
+ process6.stdout.write(`${await runConsoleStop()}
43288
44514
  `);
43289
44515
  return;
43290
44516
  }
43291
44517
  if (mode === "restart") {
43292
44518
  await runConsoleRestart();
43293
- process4.stdout.write("Fleet Console restarted.\n");
44519
+ process6.stdout.write("Fleet Console restarted.\n");
43294
44520
  return;
43295
44521
  }
43296
44522
  await openFleetConsole();
43297
- process4.stdout.write("Fleet Console opened.\n");
44523
+ process6.stdout.write("Fleet Console opened.\n");
43298
44524
  }
43299
- async function mainWiki(argv = process4.argv.slice(2)) {
44525
+ async function mainWiki(argv = process6.argv.slice(2)) {
43300
44526
  if (argv.includes("--help") || argv.includes("-h")) {
43301
- process4.stdout.write(`${buildWikiHelpText({ env: process4.env, isTTY: process4.stdout.isTTY })}
44527
+ process6.stdout.write(`${buildWikiHelpText({ env: process6.env, isTTY: process6.stdout.isTTY })}
43302
44528
  `);
43303
44529
  return;
43304
44530
  }
@@ -43310,19 +44536,19 @@ async function mainWiki(argv = process4.argv.slice(2)) {
43310
44536
  if (unsupported.length > 0) {
43311
44537
  throw new Error(`Unknown fleet wiki option: ${unsupported[0]}`);
43312
44538
  }
43313
- await openFleetWikiWorkspace({ cwd: process4.cwd() });
44539
+ await openFleetWikiWorkspace({ cwd: process6.cwd() });
43314
44540
  }
43315
44541
  function resolveDefaultServerModulePath() {
43316
44542
  const builtPath = new URL("../dist/cli.mjs", import.meta.url).pathname;
43317
44543
  if (fs5__default.existsSync(builtPath)) return builtPath;
43318
44544
  return fileURLToPath(import.meta.url);
43319
44545
  }
43320
- var isDirectRun = process4.argv[1] && path93__default.resolve(process4.argv[1]) === fileURLToPath(import.meta.url);
44546
+ var isDirectRun = process6.argv[1] && path93__default.resolve(process6.argv[1]) === fileURLToPath(import.meta.url);
43321
44547
  if (isDirectRun) {
43322
44548
  await main().catch((error51) => {
43323
- process4.stderr.write(`${error51 instanceof Error ? error51.message : String(error51)}
44549
+ process6.stderr.write(`${error51 instanceof Error ? error51.message : String(error51)}
43324
44550
  `);
43325
- process4.exitCode = 1;
44551
+ process6.exitCode = 1;
43326
44552
  });
43327
44553
  }
43328
44554