@dotobokuri/fleet-console 1.32.0 → 1.34.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 (65) hide show
  1. package/dist/cli.mjs +578 -451
  2. package/dist/client/assets/{_baseUniq-BcB5wmG1.js → _baseUniq-kpxXKqaz.js} +1 -1
  3. package/dist/client/assets/{arc-DdDM-o2K.js → arc-Cf0soLes.js} +1 -1
  4. package/dist/client/assets/{architectureDiagram-Q4EWVU46-CIF7NLBh.js → architectureDiagram-Q4EWVU46-DiRqbCQ7.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-DXYQGD6D-uTDIsi9C.js → blockDiagram-DXYQGD6D-DC25h3LU.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-AHTNJAMY-B-L5_peH.js → c4Diagram-AHTNJAMY-BJLnKoD4.js} +1 -1
  7. package/dist/client/assets/channel-Deg6stWj.js +1 -0
  8. package/dist/client/assets/{chunk-4BX2VUAB-R3NuBeMM.js → chunk-4BX2VUAB-DWUk4gCy.js} +1 -1
  9. package/dist/client/assets/{chunk-4TB4RGXK-GqEsM9Hl.js → chunk-4TB4RGXK-DW4Ee9nw.js} +1 -1
  10. package/dist/client/assets/{chunk-55IACEB6-D4a7-QMt.js → chunk-55IACEB6-8z_Na955.js} +1 -1
  11. package/dist/client/assets/{chunk-EDXVE4YY-CFnwIApt.js → chunk-EDXVE4YY-C3Z4nalr.js} +1 -1
  12. package/dist/client/assets/{chunk-FMBD7UC4-Bx6k_N10.js → chunk-FMBD7UC4-UIVDEAE7.js} +1 -1
  13. package/dist/client/assets/{chunk-OYMX7WX6-2QjCvEfI.js → chunk-OYMX7WX6-DpFWa4G9.js} +1 -1
  14. package/dist/client/assets/{chunk-QZHKN3VN-SRIQm3h2.js → chunk-QZHKN3VN-BJndYOsb.js} +1 -1
  15. package/dist/client/assets/{chunk-YZCP3GAM-D3j-Ehhn.js → chunk-YZCP3GAM-DJcSuWBd.js} +1 -1
  16. package/dist/client/assets/classDiagram-6PBFFD2Q-CpidLq53.js +1 -0
  17. package/dist/client/assets/classDiagram-v2-HSJHXN6E-CpidLq53.js +1 -0
  18. package/dist/client/assets/clone-CPyCexwP.js +1 -0
  19. package/dist/client/assets/{cose-bilkent-S5V4N54A-BDRpmFTS.js → cose-bilkent-S5V4N54A-C3m_quy1.js} +1 -1
  20. package/dist/client/assets/{dagre-KV5264BT-CNnBa0oO.js → dagre-KV5264BT-DzDFWezD.js} +1 -1
  21. package/dist/client/assets/{diagram-5BDNPKRD-DmLR5VLJ.js → diagram-5BDNPKRD-D3OFrOF_.js} +1 -1
  22. package/dist/client/assets/{diagram-G4DWMVQ6-D9-S4gAC.js → diagram-G4DWMVQ6-CgCqSaI7.js} +1 -1
  23. package/dist/client/assets/{diagram-MMDJMWI5-BgkkFSRH.js → diagram-MMDJMWI5-CnJzOV_w.js} +1 -1
  24. package/dist/client/assets/{diagram-TYMM5635-CRwM_tDY.js → diagram-TYMM5635-BrUz8pH4.js} +1 -1
  25. package/dist/client/assets/{erDiagram-SMLLAGMA-DU-pow1m.js → erDiagram-SMLLAGMA-C1IvCMgN.js} +1 -1
  26. package/dist/client/assets/{flowDiagram-DWJPFMVM-B91EFbjc.js → flowDiagram-DWJPFMVM-DCrfhs19.js} +1 -1
  27. package/dist/client/assets/{ganttDiagram-T4ZO3ILL-B-gUVWSs.js → ganttDiagram-T4ZO3ILL-ChQcD1Li.js} +1 -1
  28. package/dist/client/assets/{gitGraphDiagram-UUTBAWPF-TBkviHit.js → gitGraphDiagram-UUTBAWPF-C5aEYdTT.js} +1 -1
  29. package/dist/client/assets/{graph-Da2r3MMt.js → graph-CpGaCv1x.js} +1 -1
  30. package/dist/client/assets/index-DKrNZlfG.css +1 -0
  31. package/dist/client/assets/index-aW5PuGmI.js +452 -0
  32. package/dist/client/assets/{infoDiagram-42DDH7IO-CAdIgU9C.js → infoDiagram-42DDH7IO-BMhiz3U-.js} +1 -1
  33. package/dist/client/assets/{ishikawaDiagram-UXIWVN3A-DKMRplux.js → ishikawaDiagram-UXIWVN3A-5_f4rhFk.js} +1 -1
  34. package/dist/client/assets/{journeyDiagram-VCZTEJTY-CPR33z10.js → journeyDiagram-VCZTEJTY-BHP2UyTZ.js} +1 -1
  35. package/dist/client/assets/{kanban-definition-6JOO6SKY-CDsviLfH.js → kanban-definition-6JOO6SKY-edTTflKa.js} +1 -1
  36. package/dist/client/assets/{layout-CkN1-8C9.js → layout-B7z9q0n7.js} +1 -1
  37. package/dist/client/assets/{linear-BxLFCpuS.js → linear-BDNnEnhj.js} +1 -1
  38. package/dist/client/assets/{mermaid.core-DA-WJY50.js → mermaid.core-DDcMYg9W.js} +4 -4
  39. package/dist/client/assets/{min-BsISxNaF.js → min-D6WC647e.js} +1 -1
  40. package/dist/client/assets/{mindmap-definition-QFDTVHPH-B-IUTdhh.js → mindmap-definition-QFDTVHPH-lvrJFtla.js} +1 -1
  41. package/dist/client/assets/{pieDiagram-DEJITSTG-hwSroJDA.js → pieDiagram-DEJITSTG-D1h_ZTUL.js} +1 -1
  42. package/dist/client/assets/{quadrantDiagram-34T5L4WZ-DDt9AzF7.js → quadrantDiagram-34T5L4WZ-DSAYqOT0.js} +1 -1
  43. package/dist/client/assets/{requirementDiagram-MS252O5E-C5Xj9gqW.js → requirementDiagram-MS252O5E-BtNZUJPU.js} +1 -1
  44. package/dist/client/assets/{sankeyDiagram-XADWPNL6-ZwBDrthi.js → sankeyDiagram-XADWPNL6-Ci2wj876.js} +1 -1
  45. package/dist/client/assets/{sequenceDiagram-FGHM5R23-DmyMDdrv.js → sequenceDiagram-FGHM5R23-ES3fQi8m.js} +1 -1
  46. package/dist/client/assets/{stateDiagram-FHFEXIEX-VD5zvoai.js → stateDiagram-FHFEXIEX-BAJV8Z8I.js} +1 -1
  47. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-CiX1nm_r.js +1 -0
  48. package/dist/client/assets/{timeline-definition-GMOUNBTQ-DP8udMAZ.js → timeline-definition-GMOUNBTQ-Bl7xwJpF.js} +1 -1
  49. package/dist/client/assets/{vennDiagram-DHZGUBPP-CiBN7TbR.js → vennDiagram-DHZGUBPP-Csv3w_qu.js} +1 -1
  50. package/dist/client/assets/{wardley-RL74JXVD-BtqeTzop.js → wardley-RL74JXVD-CaXvusTS.js} +1 -1
  51. package/dist/client/assets/{wardleyDiagram-NUSXRM2D-DaRdj2RP.js → wardleyDiagram-NUSXRM2D-DfKsNCHq.js} +1 -1
  52. package/dist/client/assets/{xychartDiagram-5P7HB3ND-jlgNCyFB.js → xychartDiagram-5P7HB3ND-BmDHSI31.js} +1 -1
  53. package/dist/client/index.html +2 -2
  54. package/dist/fleet-plugins/file-explorer/routes.mjs +102 -5
  55. package/dist/fleet-plugins/repository/routes.mjs +106 -4
  56. package/dist/fleet-plugins/skills/routes.mjs +93 -42
  57. package/dist/fleet-plugins/terminal/routes.mjs +1003 -917
  58. package/package.json +1 -1
  59. package/dist/client/assets/channel-BamKwHPs.js +0 -1
  60. package/dist/client/assets/classDiagram-6PBFFD2Q-sEh9Glhk.js +0 -1
  61. package/dist/client/assets/classDiagram-v2-HSJHXN6E-sEh9Glhk.js +0 -1
  62. package/dist/client/assets/clone-iMU-qXwc.js +0 -1
  63. package/dist/client/assets/index-5-y-ZMI6.js +0 -451
  64. package/dist/client/assets/index-BBOlVlPk.css +0 -1
  65. package/dist/client/assets/stateDiagram-v2-QKLJ7IA2-CFTLlJ0p.js +0 -1
package/dist/cli.mjs CHANGED
@@ -903,16 +903,22 @@ function sanitizeGlobalOptionsData(value) {
903
903
  return { data: createEmptyGlobalOptionsData(), changed: true };
904
904
  }
905
905
  const kimiModel = sanitizeKimiModel(value.kimiModel);
906
+ const agentIdleDormantMinutes = sanitizeAgentIdleDormantMinutes(value.agentIdleDormantMinutes);
906
907
  const data = {
907
908
  version: GLOBAL_OPTIONS_VERSION,
908
909
  ...typeof value.enableMetaphor === "boolean" ? { enableMetaphor: value.enableMetaphor } : {},
909
- ...value.codexLaunchMode === "acp" || value.codexLaunchMode === "app-server" ? { codexLaunchMode: value.codexLaunchMode } : {},
910
- ...kimiModel ? { kimiModel } : {}
910
+ ...kimiModel ? { kimiModel } : {},
911
+ ...agentIdleDormantMinutes !== void 0 ? { agentIdleDormantMinutes } : {}
911
912
  };
912
- const allowedKeys = /* @__PURE__ */ new Set(["version", "enableMetaphor", "codexLaunchMode", "kimiModel"]);
913
- const changed = Object.keys(value).some((key) => !allowedKeys.has(key)) || "enableMetaphor" in value && typeof value.enableMetaphor !== "boolean" || "codexLaunchMode" in value && value.codexLaunchMode !== "acp" && value.codexLaunchMode !== "app-server" || "kimiModel" in value && kimiModel === void 0;
913
+ const allowedKeys = /* @__PURE__ */ new Set(["version", "enableMetaphor", "kimiModel", "agentIdleDormantMinutes"]);
914
+ const changed = Object.keys(value).some((key) => !allowedKeys.has(key)) || "enableMetaphor" in value && typeof value.enableMetaphor !== "boolean" || "kimiModel" in value && kimiModel === void 0 || "agentIdleDormantMinutes" in value && agentIdleDormantMinutes === void 0;
914
915
  return { data, changed };
915
916
  }
917
+ function sanitizeAgentIdleDormantMinutes(value) {
918
+ if (value === null) return null;
919
+ if (typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value > 0) return value;
920
+ return void 0;
921
+ }
916
922
  function sanitizeKimiModel(value) {
917
923
  if (!isRecord(value)) {
918
924
  return void 0;
@@ -1679,55 +1685,55 @@ authority: Admiral of the Navy (\uB300\uC6D0\uC218)
1679
1685
 
1680
1686
  # Fleet Wiki Workspace Doctrine
1681
1687
 
1682
- This directory is the **Fleet Wiki** \u2014 a workspace-local markdown knowledge base. All entries here are governed by a deterministic patch queue with mandatory Admiral approval. **Direct filesystem edits are forbidden for any agent or carrier.**
1688
+ This directory is the **Fleet Wiki** \u2014 a workspace-local markdown knowledge base. All entries here are governed by a deterministic patch queue with mandatory host approval. **Direct filesystem edits are forbidden for any agent or carrier.**
1683
1689
 
1684
1690
  The active entry schema is defined in \`schema/wiki-schema.md\` and may be revised by the Admiral of the Navy (\uB300\uC6D0\uC218) at any time. Always consult the current schema rather than assuming a fixed format.
1685
1691
 
1686
1692
  ## 1. Hard Boundaries (CRITICAL)
1687
1693
 
1688
- The following prohibitions are **absolute** for every carrier (including Chronicle) and any sub-agent operating in this directory:
1694
+ The following prohibitions are **absolute** for every carrier and any sub-agent operating in this directory:
1689
1695
 
1690
- - **NEVER** edit any file under \`wiki/\` directly via filesystem tools (Read/Write/Edit). Entry creation and new staged revisions must go through \`wiki_ingest\`; already-pending queue proposal revisions may use \`wiki_patch_edit\`.
1696
+ - **NEVER** edit any file under \`wiki/\` directly via filesystem tools (Read/Write/Edit). Entry creation and new staged revisions must go through host-only \`wiki_ingest\`; already-pending queue proposal revisions may use host-only \`wiki_patch_edit\`.
1691
1697
  - **NEVER** edit \`index.json\`, \`wiki/index.md\`, or \`log.md\` by hand. These files are **system-managed** and rebuilt automatically when patches are approved.
1692
1698
  - **NEVER** edit files under \`raw/\` after creation \u2014 raw sources are immutable evidence and \`wiki_ingest\` writes them automatically.
1693
1699
  - **NEVER** touch \`queue/\`, \`archive/\`, or \`conflicts/\` \u2014 these are workflow-internal stores managed by the wiki tooling.
1694
- - \`schema/\` is the **only** directory in this workspace where direct edits are permitted, and only the Admiral of the Navy (\uB300\uC6D0\uC218) authorizes schema changes.
1700
+ - **NEVER** invoke mutation, staging, lint, schema, or approval Wiki tools from a carrier executor. Those tools are **host-only**; runtime ACLs are authoritative.
1701
+ - **NEVER** approve or reject patches from a carrier executor. Approval authority is **host-only** through \`wiki_patch_queue\`.
1695
1702
 
1696
1703
  ## 2. Roles and Gates
1697
1704
 
1698
1705
  | Role | Capability | Gate |
1699
1706
  |------|-----------|------|
1700
- | **Carriers** (Chronicle for entry proposals; any carrier for read-only consult) | Propose: \`wiki_ingest\` \xB7 Revise pending: \`wiki_patch_edit\` \xB7 Orient: \`wiki_orient\` \xB7 Lookup: \`wiki_briefing\` / \`wiki_read\` / \`wiki_resolve\` \xB7 Lint: \`wiki_drydock\` \xB7 Query: \`wiki_query\` \xB7 Chronicle schema lookup: \`wiki_schema_list\` / \`wiki_schema_read\` | Cannot approve patches |
1701
- | **Admiral (Host PI)** | All carrier capabilities + \`wiki_schema_create\` (new custom templates) + \`wiki_patch_queue\` (approve / reject) | Sole approval authority |
1707
+ | **Carriers** | Read-only consult: \`wiki_orient\` \xB7 \`wiki_briefing\` \xB7 \`wiki_read\` \xB7 \`wiki_resolve\` | Cannot mutate, stage, lint, inspect/create schema, or approve patches |
1708
+ | **Host (Admiral PI)** | Read, compose, stage (\`wiki_ingest\`), revise pending (\`wiki_patch_edit\`), lint (\`wiki_drydock\`), query (\`wiki_query\`), compile (\`wiki_compile_source\`), schema (\`wiki_schema_list\` / \`wiki_schema_read\` / \`wiki_schema_create\`), approve/reject (\`wiki_patch_queue\`) | Sole mutation, schema, and approval authority |
1702
1709
 
1703
- Sub-agents **propose**; the Admiral **commits**. Wiki entry writes reach disk only after \`wiki_patch_queue approve\` is invoked by the Admiral. New custom schema templates are created directly through the Admiral's create-only \`wiki_schema_create\` tool.
1710
+ The host performs every Fleet Wiki operation directly. Carriers may consult read-only tools for context but must not stage entries, revise patches, lint, create schema templates, or approve queue items.
1704
1711
 
1705
1712
  ## 3. Standard Workflow
1706
1713
 
1707
1714
  ### 3.1 Read / Lookup
1708
1715
 
1709
- \`wiki_orient\` \u2192 \`wiki_briefing\` \u2192 \`wiki_read\` \u2192 \`wiki_resolve\`. Read-only; no approval needed.
1716
+ \`wiki_orient\` \u2192 \`wiki_briefing\` \u2192 \`wiki_read\` \u2192 \`wiki_resolve\`. Read-only; globally shared with carriers; no approval needed.
1710
1717
 
1711
- ### 3.2 Propose a New Entry
1718
+ ### 3.2 Stage a New Entry (Host)
1712
1719
 
1713
1720
  1. **Orient** \u2014 \`wiki_orient\` to confirm workspace state and locate the active schema.
1714
1721
  2. **Compose** \u2014 Draft the entry body per the active workspace schema (see \`schema/wiki-schema.md\`). Synthesize raw sources, never copy verbatim.
1715
1722
  3. **Ingest** \u2014 \`wiki_ingest\` with \`id\`, \`title\`, \`tags\`, \`body\`, and \`source\` (raw evidence). System auto-writes raw, stages a patch under \`queue/\`, and returns a \`patch_id\`.
1716
1723
  4. **Lint** \u2014 \`wiki_drydock\` to verify schema compliance and link integrity.
1717
- 5. **Hand off** \u2014 Report the \`patch_id\` to the Admiral. Do **not** invoke \`wiki_patch_queue approve\`.
1718
- 6. **Admiral approves** \u2014 \`wiki_patch_queue approve\` writes the entry to \`wiki/\`, updates indexes, and appends to \`log.md\`. No mutation is performed by hand.
1724
+ 5. **Approve** \u2014 \`wiki_patch_queue approve\` writes the entry to \`wiki/\`, updates indexes, and appends to \`log.md\`. No filesystem mutation is performed by hand.
1719
1725
 
1720
- ### 3.3 Update an Existing Entry
1726
+ ### 3.3 Update an Existing Entry (Host)
1721
1727
 
1722
1728
  Same as 3.2 but \`wiki_ingest\` runs in \`update\` mode with \`base_version\` for stale-base detection.
1723
1729
 
1724
- ### 3.4 Revise an Already-Pending Patch
1730
+ ### 3.4 Revise an Already-Pending Patch (Host)
1725
1731
 
1726
- Use \`wiki_patch_edit\` with a pending \`patch_id\` for small exact body replacements or metadata corrections before Admiral approval. Do not create another ingest patch for a one-line correction to an existing pending proposal.
1732
+ Use \`wiki_patch_edit\` with a pending \`patch_id\` for small exact body replacements or metadata corrections before approval. Do not create another ingest patch for a one-line correction to an existing pending proposal.
1727
1733
 
1728
1734
  ## 4. Schema Reference
1729
1735
 
1730
- The authoritative source is **\`schema/wiki-schema.md\`**. The Admiral of the Navy (\uB300\uC6D0\uC218) may revise the schema at any time, and \`wiki_drydock\` enforces the current version.
1736
+ The authoritative source is **\`schema/wiki-schema.md\`**. The Admiral of the Navy (\uB300\uC6D0\uC218) may revise the schema at any time through host-only schema tools, and \`wiki_drydock\` enforces the current version.
1731
1737
 
1732
1738
  \`schema/AGENTS.md\` defines the maintainer role for the schema directory itself.
1733
1739
 
@@ -1742,7 +1748,7 @@ Always read \`schema/wiki-schema.md\` directly before composing or revising entr
1742
1748
  ## 6. Escalation
1743
1749
 
1744
1750
  - Schema disputes \u2192 Admiral.
1745
- - Approval requests \u2192 Admiral with reported \`patch_id\`.
1751
+ - Approval or rejection decisions \u2192 host via \`wiki_patch_queue\`.
1746
1752
  - Tool failures / stale state \u2192 re-run \`wiki_drydock\`, then escalate to Admiral if unresolved.
1747
1753
  `;
1748
1754
  async function ensureWorkspaceSchema(paths) {
@@ -1911,9 +1917,10 @@ async function writeDefaultFileIfMissing(filePath, content) {
1911
1917
  try {
1912
1918
  await writeFile(filePath, content, { encoding: "utf8", flag: "wx" });
1913
1919
  } catch (error51) {
1914
- if (!isAlreadyExistsError(error51)) {
1915
- throw error51;
1920
+ if (isAlreadyExistsError(error51) || isEisdirError(error51) || isEloopError(error51)) {
1921
+ return;
1916
1922
  }
1923
+ throw error51;
1917
1924
  }
1918
1925
  }
1919
1926
  async function tryReadSchemaFile(paths, filePath) {
@@ -2023,6 +2030,12 @@ function parseRequiredSections(content) {
2023
2030
  }
2024
2031
  return sections;
2025
2032
  }
2033
+ function isEloopError(error51) {
2034
+ return typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === "ELOOP";
2035
+ }
2036
+ function isEisdirError(error51) {
2037
+ return typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === "EISDIR";
2038
+ }
2026
2039
  function isAlreadyExistsError(error51) {
2027
2040
  return typeof error51 === "object" && error51 !== null && "code" in error51 && error51.code === "EEXIST";
2028
2041
  }
@@ -31767,35 +31780,23 @@ var AcpConnection = class extends BaseConnection {
31767
31780
  }
31768
31781
  case "tool_call": {
31769
31782
  const { sessionUpdate: _tc, ...toolCallData } = update;
31770
- const rawTcTitle = toolCallData.title ?? "";
31771
- const cleanTcTitle = rawTcTitle.replace(/\s*\(\s*[\d.,\s-]*\b(?:NaN|undefined)\b[\d.,\s-]*\)/g, "");
31772
- if (cleanTcTitle !== rawTcTitle) {
31773
- console.debug("[AcpConnection] sanitized tool_call payload", { rawTitle: rawTcTitle, cleanTitle: cleanTcTitle });
31774
- }
31775
- const titleTcOverride = "title" in toolCallData ? { title: cleanTcTitle } : {};
31776
31783
  this.emit(
31777
31784
  "toolCall",
31778
- cleanTcTitle,
31785
+ toolCallData.title ?? "",
31779
31786
  toolCallData.status ?? "",
31780
31787
  sessionId,
31781
- { ...toolCallData, ...titleTcOverride }
31788
+ toolCallData
31782
31789
  );
31783
31790
  break;
31784
31791
  }
31785
31792
  case "tool_call_update": {
31786
31793
  const { sessionUpdate: _tcu, ...toolCallUpdateData } = update;
31787
- const rawTitle = toolCallUpdateData.title ?? "";
31788
- const cleanTitle = rawTitle.replace(/\s*\(\s*[\d.,\s-]*\b(?:NaN|undefined)\b[\d.,\s-]*\)/g, "");
31789
- if (cleanTitle !== rawTitle) {
31790
- console.debug("[AcpConnection] sanitized tool_call_update payload", { rawTitle, cleanTitle });
31791
- }
31792
- const titleOverride = "title" in toolCallUpdateData ? { title: cleanTitle } : {};
31793
31794
  this.emit(
31794
31795
  "toolCallUpdate",
31795
- cleanTitle,
31796
+ toolCallUpdateData.title ?? "",
31796
31797
  toolCallUpdateData.status ?? "",
31797
31798
  sessionId,
31798
- { ...toolCallUpdateData, ...titleOverride }
31799
+ toolCallUpdateData
31799
31800
  );
31800
31801
  break;
31801
31802
  }
@@ -31960,7 +31961,7 @@ function inferBinName(packageName) {
31960
31961
  // ../../packages/core-unified-agent/models.json
31961
31962
  var models_default = {
31962
31963
  version: 1,
31963
- updatedAt: "2026-07-21T00:00:00Z",
31964
+ updatedAt: "2026-07-25T00:00:00Z",
31964
31965
  providers: {
31965
31966
  claude: {
31966
31967
  name: "Claude Code",
@@ -31996,11 +31997,13 @@ var models_default = {
31996
31997
  defaultModel: "gpt-5.6-sol",
31997
31998
  models: [
31998
31999
  { modelId: "gpt-5.6-sol", name: "GPT-5.6-Sol", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "low" } },
32000
+ { modelId: "gpt-5.6-sol-fast", name: "GPT-5.6-Sol Fast", providerModelId: "gpt-5.6-sol", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "low" } },
31999
32001
  { modelId: "gpt-5.6-terra", name: "GPT-5.6-Terra", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "medium" } },
32002
+ { modelId: "gpt-5.6-terra-fast", name: "GPT-5.6-Terra Fast", providerModelId: "gpt-5.6-terra", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max", "ultra"], default: "medium" } },
32000
32003
  { modelId: "gpt-5.6-luna", name: "GPT-5.6-Luna", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
32004
+ { modelId: "gpt-5.6-luna-fast", name: "GPT-5.6-Luna Fast", providerModelId: "gpt-5.6-luna", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh", "max"], default: "medium" } },
32001
32005
  { modelId: "gpt-5.5", name: "GPT-5.5", effort: { supported: true, levels: ["low", "medium", "high", "xhigh"], default: "high" } },
32002
- { modelId: "gpt-5.4", name: "GPT-5.4", effort: { supported: true, levels: ["low", "medium", "high", "xhigh"], default: "high" } },
32003
- { modelId: "gpt-5.4-mini", name: "GPT-5.4 Mini", effort: { supported: true, levels: ["low", "medium", "high", "xhigh"], default: "high" } }
32006
+ { modelId: "gpt-5.5-fast", name: "GPT-5.5 Fast", providerModelId: "gpt-5.5", serviceTier: "priority", effort: { supported: true, levels: ["low", "medium", "high", "xhigh"], default: "high" } }
32004
32007
  ]
32005
32008
  },
32006
32009
  "opencode-go": {
@@ -32086,6 +32089,10 @@ var ModelEntrySchema = external_exports.object({
32086
32089
  description: external_exports.string().optional(),
32087
32090
  /** spawn 시 실제 CLI 모델 ID를 조립해야 하는 경우의 템플릿 */
32088
32091
  spawnModelTemplate: external_exports.string().optional(),
32092
+ /** 카탈로그 ID와 실제 provider 모델 ID가 다른 경우의 원본 모델 ID */
32093
+ providerModelId: external_exports.string().optional(),
32094
+ /** provider가 모델과 별도로 받는 서비스 티어 */
32095
+ serviceTier: external_exports.string().optional(),
32089
32096
  /** 모델의 컨텍스트 윈도우 크기 (토큰) */
32090
32097
  contextWindow: external_exports.number().int().positive().optional(),
32091
32098
  /** 모델별 effort 설정 */
@@ -32108,6 +32115,14 @@ var ModelEntrySchema = external_exports.object({
32108
32115
  path: ["spawnModelTemplate"]
32109
32116
  });
32110
32117
  }
32118
+ if (ctx.value.serviceTier && !ctx.value.providerModelId) {
32119
+ ctx.issues.push({
32120
+ code: "custom",
32121
+ input: ctx.value.serviceTier,
32122
+ message: "serviceTier\uB97C \uC9C0\uC815\uD55C \uBAA8\uB378\uC740 providerModelId\uB3C4 \uC9C0\uC815\uD574\uC57C \uD569\uB2C8\uB2E4",
32123
+ path: ["serviceTier"]
32124
+ });
32125
+ }
32111
32126
  });
32112
32127
  var ModelsMappingSchema = external_exports.record(external_exports.string(), external_exports.string());
32113
32128
  var ProviderSchema = external_exports.object({
@@ -32257,8 +32272,6 @@ var CLI_BACKENDS = {
32257
32272
  cliCommand: "codex",
32258
32273
  protocol: "codex-app-server",
32259
32274
  authRequired: true,
32260
- npxPackage: "@agentclientprotocol/codex-acp@1.1.2",
32261
- acpArgs: [],
32262
32275
  appServerArgs: ["app-server", "--listen", "stdio://"],
32263
32276
  modes: [
32264
32277
  { id: "default", label: "Plan" },
@@ -32268,7 +32281,7 @@ var CLI_BACKENDS = {
32268
32281
  supportsSessionClose: true,
32269
32282
  supportsSessionLoad: true,
32270
32283
  requiresModelAtSpawn: false,
32271
- usesNpxBridge: true,
32284
+ usesNpxBridge: false,
32272
32285
  defaultMaxTokens: 1e5
32273
32286
  },
32274
32287
  "opencode-go": {
@@ -32359,22 +32372,6 @@ function getYoloModeId(cli) {
32359
32372
  function getAllBackendConfigs() {
32360
32373
  return Object.values(CLI_BACKENDS);
32361
32374
  }
32362
- function buildCodexConfigEnv(configOverrides, baseConfigJson) {
32363
- const map2 = parseBaseConfigMap(baseConfigJson);
32364
- for (const override of configOverrides ?? []) {
32365
- const eqIndex = override.indexOf("=");
32366
- if (eqIndex < 0) {
32367
- continue;
32368
- }
32369
- const key = override.slice(0, eqIndex);
32370
- const rawValue = override.slice(eqIndex + 1);
32371
- assignNestedKey(map2, key, parseOverrideValue(rawValue));
32372
- }
32373
- if (Object.keys(map2).length === 0) {
32374
- return void 0;
32375
- }
32376
- return JSON.stringify(map2);
32377
- }
32378
32375
  function buildConfigOverrideArgs(overrides) {
32379
32376
  return overrides.flatMap((override) => ["-c", override]);
32380
32377
  }
@@ -32401,45 +32398,6 @@ function mcpServerConfigsToAcp(servers) {
32401
32398
  headers: server.headers ?? []
32402
32399
  }));
32403
32400
  }
32404
- function parseBaseConfigMap(baseConfigJson) {
32405
- if (!baseConfigJson) {
32406
- return {};
32407
- }
32408
- try {
32409
- const parsed = JSON.parse(baseConfigJson);
32410
- if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
32411
- return parsed;
32412
- }
32413
- } catch {
32414
- }
32415
- return {};
32416
- }
32417
- function parseOverrideValue(raw) {
32418
- try {
32419
- return JSON.parse(raw);
32420
- } catch {
32421
- if (raw.length >= 2 && raw.startsWith('"') && raw.endsWith('"')) {
32422
- return raw.slice(1, -1);
32423
- }
32424
- return raw;
32425
- }
32426
- }
32427
- function assignNestedKey(target, dottedKey, value) {
32428
- const segments = dottedKey.split(".");
32429
- let cursor = target;
32430
- for (let i = 0; i < segments.length - 1; i += 1) {
32431
- const segment = segments[i];
32432
- const existing = cursor[segment];
32433
- if (typeof existing === "object" && existing !== null && !Array.isArray(existing)) {
32434
- cursor = existing;
32435
- } else {
32436
- const nested = {};
32437
- cursor[segment] = nested;
32438
- cursor = nested;
32439
- }
32440
- }
32441
- cursor[segments[segments.length - 1]] = value;
32442
- }
32443
32401
 
32444
32402
  // ../../packages/core-unified-agent/src/detector/CliDetector.ts
32445
32403
  var CLI_DETECT_LIST = Object.entries(CLI_BACKENDS).map(([id, backend]) => ({
@@ -32995,6 +32953,7 @@ var CODEX_METHODS = {
32995
32953
  THREAD_RESUME: "thread/resume",
32996
32954
  THREAD_READ: "thread/read",
32997
32955
  THREAD_ARCHIVE: "thread/archive",
32956
+ THREAD_UNARCHIVE: "thread/unarchive",
32998
32957
  TURN_START: "turn/start",
32999
32958
  TURN_INTERRUPT: "turn/interrupt",
33000
32959
  TURN_STEER: "turn/steer"
@@ -33023,6 +32982,7 @@ var CODEX_MCP_READY_STATUS = "ready";
33023
32982
  var CODEX_MCP_FAILED_STATUSES = /* @__PURE__ */ new Set(["failed", "error"]);
33024
32983
  var DEFAULT_MCP_STARTUP_TIMEOUT = 6e4;
33025
32984
  var STDERR_TAIL_LIMIT = 20;
32985
+ var SESSION_TEARDOWN_TIMEOUT_MS = 5e3;
33026
32986
  var CodexAppServerConnection = class extends BaseConnection {
33027
32987
  clientInfo;
33028
32988
  autoApprove;
@@ -33035,6 +32995,7 @@ var CodexAppServerConnection = class extends BaseConnection {
33035
32995
  pendingMcpReadyWaiters = /* @__PURE__ */ new Set();
33036
32996
  stdoutBuffer = "";
33037
32997
  pendingModel = null;
32998
+ pendingServiceTier = void 0;
33038
32999
  pendingEffort = null;
33039
33000
  codexHome = null;
33040
33001
  isDisconnecting = false;
@@ -33059,6 +33020,9 @@ var CodexAppServerConnection = class extends BaseConnection {
33059
33020
  setPendingModel(model) {
33060
33021
  this.pendingModel = model;
33061
33022
  }
33023
+ setPendingServiceTier(serviceTier) {
33024
+ this.pendingServiceTier = serviceTier;
33025
+ }
33062
33026
  setPendingEffort(effort) {
33063
33027
  this.pendingEffort = effort;
33064
33028
  }
@@ -33101,6 +33065,7 @@ var CodexAppServerConnection = class extends BaseConnection {
33101
33065
  cwd: options?.cwd ?? this.cwd,
33102
33066
  developerInstructions: options?.developerInstructions ?? null,
33103
33067
  model: options?.model ?? null,
33068
+ serviceTier: options?.serviceTier ?? null,
33104
33069
  approvalPolicy: options?.approvalPolicy ?? null,
33105
33070
  sandbox: options?.sandbox ?? null,
33106
33071
  config: options?.config ?? null
@@ -33111,10 +33076,11 @@ var CodexAppServerConnection = class extends BaseConnection {
33111
33076
  this.setState("ready");
33112
33077
  return response;
33113
33078
  } catch (error51) {
33079
+ const diagnosticError = this.withStderrDiagnostics(error51, "Codex initialize");
33114
33080
  this.setState("error");
33115
33081
  this.isDisconnecting = true;
33116
33082
  await this.disconnect();
33117
- throw error51;
33083
+ throw diagnosticError;
33118
33084
  }
33119
33085
  }
33120
33086
  async loadSession(threadId, options) {
@@ -33122,11 +33088,37 @@ var CodexAppServerConnection = class extends BaseConnection {
33122
33088
  try {
33123
33089
  response = await this.sendThreadResumeRequest(threadId, options);
33124
33090
  } catch (error51) {
33125
- const rolloutPath = this.findRolloutPathForThreadId(threadId);
33126
- if (!rolloutPath || !isMissingRolloutError(error51, threadId)) {
33127
- throw error51;
33091
+ if (isArchivedThreadError(error51, threadId)) {
33092
+ try {
33093
+ await this.sendRequest(
33094
+ CODEX_METHODS.THREAD_UNARCHIVE,
33095
+ { threadId }
33096
+ );
33097
+ } catch (unarchiveError) {
33098
+ const message = unarchiveError instanceof Error ? unarchiveError.message : String(unarchiveError);
33099
+ throw new Error(
33100
+ `thread/unarchive\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4(\uC6D0\uC778: archived \uC0C1\uD0DC\uB85C resume \uAC70\uBD80\uB428): ${message}`,
33101
+ { cause: unarchiveError }
33102
+ );
33103
+ }
33104
+ try {
33105
+ response = await this.sendThreadResumeRequest(threadId, options);
33106
+ } catch (resumeError) {
33107
+ await this.sendRequest(
33108
+ CODEX_METHODS.THREAD_ARCHIVE,
33109
+ { threadId },
33110
+ SESSION_TEARDOWN_TIMEOUT_MS
33111
+ ).catch(() => {
33112
+ });
33113
+ throw resumeError;
33114
+ }
33115
+ } else {
33116
+ const rolloutPath = this.findRolloutPathForThreadId(threadId);
33117
+ if (!rolloutPath || !isMissingRolloutError(error51, threadId)) {
33118
+ throw error51;
33119
+ }
33120
+ response = await this.sendThreadResumeRequest(threadId, options, rolloutPath);
33128
33121
  }
33129
- response = await this.sendThreadResumeRequest(threadId, options, rolloutPath);
33130
33122
  }
33131
33123
  this.threadId = response.thread.id;
33132
33124
  this.turnId = null;
@@ -33188,16 +33180,19 @@ var CodexAppServerConnection = class extends BaseConnection {
33188
33180
  this.on("exit", onExit);
33189
33181
  });
33190
33182
  try {
33183
+ const serviceTier = options?.serviceTier !== void 0 ? options.serviceTier : this.pendingServiceTier;
33191
33184
  const response = await this.sendRequest(
33192
33185
  CODEX_METHODS.TURN_START,
33193
33186
  {
33194
33187
  threadId: sessionId,
33195
33188
  input,
33196
33189
  model: options?.model ?? this.pendingModel ?? null,
33190
+ ...serviceTier !== void 0 ? { serviceTier } : {},
33197
33191
  effort: options?.effort ?? this.pendingEffort ?? null
33198
33192
  }
33199
33193
  );
33200
33194
  this.pendingModel = null;
33195
+ this.pendingServiceTier = void 0;
33201
33196
  this.pendingEffort = null;
33202
33197
  this.turnId = response.turn.id;
33203
33198
  await turnCompleted;
@@ -33224,14 +33219,16 @@ var CodexAppServerConnection = class extends BaseConnection {
33224
33219
  if (threadId && activeTurnId) {
33225
33220
  await this.sendRequest(
33226
33221
  CODEX_METHODS.TURN_INTERRUPT,
33227
- { threadId, turnId: activeTurnId }
33222
+ { threadId, turnId: activeTurnId },
33223
+ SESSION_TEARDOWN_TIMEOUT_MS
33228
33224
  ).catch(() => {
33229
33225
  });
33230
33226
  }
33231
33227
  if (threadId) {
33232
33228
  await this.sendRequest(
33233
33229
  CODEX_METHODS.THREAD_ARCHIVE,
33234
- { threadId }
33230
+ { threadId },
33231
+ SESSION_TEARDOWN_TIMEOUT_MS
33235
33232
  ).catch(() => {
33236
33233
  });
33237
33234
  }
@@ -33249,6 +33246,7 @@ var CodexAppServerConnection = class extends BaseConnection {
33249
33246
  cwd: options?.cwd ?? this.cwd,
33250
33247
  developerInstructions: options?.developerInstructions ?? null,
33251
33248
  model: options?.model ?? null,
33249
+ serviceTier: options?.serviceTier ?? null,
33252
33250
  approvalPolicy: options?.approvalPolicy ?? null,
33253
33251
  sandbox: options?.sandbox ?? null,
33254
33252
  config: options?.config ?? null
@@ -33266,6 +33264,9 @@ var CodexAppServerConnection = class extends BaseConnection {
33266
33264
  this.rejectPendingRequests(new Error("Codex \uC5F0\uACB0\uC774 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
33267
33265
  this.rejectPendingMcpReadyWaiters(new Error("Codex \uC5F0\uACB0\uC774 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
33268
33266
  this.stdoutBuffer = "";
33267
+ this.pendingModel = null;
33268
+ this.pendingServiceTier = void 0;
33269
+ this.pendingEffort = null;
33269
33270
  this.codexHome = null;
33270
33271
  try {
33271
33272
  await super.disconnect();
@@ -33714,6 +33715,7 @@ var CodexAppServerConnection = class extends BaseConnection {
33714
33715
  path: rolloutPath ?? null,
33715
33716
  cwd: options?.cwd ?? this.cwd,
33716
33717
  model: options?.model ?? null,
33718
+ serviceTier: options?.serviceTier ?? null,
33717
33719
  approvalPolicy: options?.approvalPolicy ?? null,
33718
33720
  sandbox: options?.sandbox ?? null,
33719
33721
  developerInstructions: options?.developerInstructions ?? null,
@@ -33833,6 +33835,9 @@ var CodexAppServerConnection = class extends BaseConnection {
33833
33835
  function isMissingRolloutError(error51, threadId) {
33834
33836
  return error51 instanceof Error && error51.message.includes(`no rollout found for thread id ${threadId}`);
33835
33837
  }
33838
+ function isArchivedThreadError(error51, threadId) {
33839
+ return error51 instanceof Error && error51.message.includes(`session ${threadId} is archived`);
33840
+ }
33836
33841
  function findRolloutPath(root, threadId) {
33837
33842
  if (!fs10.existsSync(root)) {
33838
33843
  return null;
@@ -33854,7 +33859,7 @@ function findRolloutPath(root, threadId) {
33854
33859
  }
33855
33860
 
33856
33861
  // ../../packages/core-unified-agent/src/client/UnifiedCodexAgentClient.ts
33857
- var CODEX_TURN_LEVEL_CONFIG_KEYS = /* @__PURE__ */ new Set(["effort", "model"]);
33862
+ var CODEX_TURN_LEVEL_CONFIG_KEYS = /* @__PURE__ */ new Set(["effort"]);
33858
33863
  var CODEX_THREAD_POLICY_CONFIG_KEYS = /* @__PURE__ */ new Set(["approvalPolicy", "sandbox"]);
33859
33864
  var UnifiedCodexAgentClient = class extends EventEmitter {
33860
33865
  connection = null;
@@ -33863,6 +33868,8 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
33863
33868
  currentSystemPrompt = null;
33864
33869
  firstPromptPending = null;
33865
33870
  pendingOverrides = null;
33871
+ currentModelSelection = {};
33872
+ archiveSessionOnDisconnect = false;
33866
33873
  detector = new CliDetector();
33867
33874
  on(event, listener) {
33868
33875
  return super.on(event, listener);
@@ -33882,9 +33889,6 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
33882
33889
  throw new Error("UnifiedCodexAgentClient\uB294 codex CLI\uB9CC \uC9C0\uC6D0\uD569\uB2C8\uB2E4.");
33883
33890
  }
33884
33891
  this.validateModelEffort(options.model, options.effort);
33885
- if (shouldUseCodexAcp(options)) {
33886
- return this.connectAcp(options);
33887
- }
33888
33892
  return this.connectAppServer(options);
33889
33893
  }
33890
33894
  async connectAppServer(options) {
@@ -33893,6 +33897,7 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
33893
33897
  const command2 = options.cliPath ?? backend.cliCommand;
33894
33898
  const baseArgs = backend.appServerArgs ?? ["app-server", "--listen", "stdio://"];
33895
33899
  const modeMapping = this.resolveMode(options.yoloMode === false ? "default" : "yolo");
33900
+ const modelSelection = this.resolveModelSelection(options.model);
33896
33901
  const args = [
33897
33902
  ...baseArgs,
33898
33903
  ...this.buildStartupConfigArgs(options.configOverrides, options.mcpServers, modeMapping)
@@ -33916,13 +33921,14 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
33916
33921
  if (options.sessionId) {
33917
33922
  await connection.connect({
33918
33923
  skipThreadStart: true,
33919
- model: options.model
33924
+ model: modelSelection.providerModelId,
33925
+ serviceTier: modelSelection.serviceTier
33920
33926
  });
33921
33927
  await connection.loadSession(
33922
33928
  options.sessionId,
33923
33929
  this.buildCodexThreadDefaultsForResume(
33924
33930
  options.cwd,
33925
- options.model,
33931
+ modelSelection,
33926
33932
  null,
33927
33933
  modeMapping
33928
33934
  )
@@ -33930,7 +33936,8 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
33930
33936
  } else {
33931
33937
  await connection.connect({
33932
33938
  developerInstructions: void 0,
33933
- model: options.model,
33939
+ model: modelSelection.providerModelId,
33940
+ serviceTier: modelSelection.serviceTier,
33934
33941
  approvalPolicy: modeMapping.approvalPolicy,
33935
33942
  sandbox: modeMapping.sandbox
33936
33943
  });
@@ -33944,6 +33951,8 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
33944
33951
  this.sessionCwd = options.cwd;
33945
33952
  this.currentSystemPrompt = options.systemPrompt ?? null;
33946
33953
  this.firstPromptPending = options.sessionId ? null : this.currentSystemPrompt;
33954
+ this.currentModelSelection = modelSelection;
33955
+ this.archiveSessionOnDisconnect = options.archiveSessionOnDisconnect === true;
33947
33956
  this.pendingOverrides = {
33948
33957
  turnConfig: options.effort ? { effort: options.effort } : {},
33949
33958
  threadConfig: {
@@ -33957,71 +33966,17 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
33957
33966
  session: { sessionId }
33958
33967
  };
33959
33968
  }
33960
- async connectAcp(options) {
33961
- const cleanEnv = cleanEnvironment(process.env, options.env);
33962
- const spawnConfig = createSpawnConfig("codex", options);
33963
- const codexConfigEnv = buildCodexConfigEnv(
33964
- options.configOverrides,
33965
- cleanEnv.CODEX_CONFIG
33966
- );
33967
- const connection = new AcpConnection({
33968
- command: spawnConfig.command,
33969
- args: spawnConfig.args,
33970
- cliType: "codex",
33971
- cwd: options.cwd,
33972
- env: { ...cleanEnv, ...codexConfigEnv ? { CODEX_CONFIG: codexConfigEnv } : {} },
33973
- requestTimeout: options.timeout ?? 6e5,
33974
- initTimeout: options.timeout ?? 6e4,
33975
- promptIdleTimeout: options.promptIdleTimeout ?? 6e5,
33976
- clientInfo: options.clientInfo,
33977
- autoApprove: options.autoApprove,
33978
- fsAccess: options.fsAccess
33979
- });
33980
- this.connection = connection;
33981
- this.setupEventForwarding();
33982
- const recentLogs = [];
33983
- const collectLog = (message) => {
33984
- recentLogs.push(message);
33985
- if (recentLogs.length > 30) {
33986
- recentLogs.shift();
33987
- }
33988
- };
33989
- connection.on("log", collectLog);
33990
- let session;
33991
- try {
33992
- session = await connection.connect(
33993
- options.cwd,
33994
- options.sessionId,
33995
- this.resolveAcpMcpServers(options.mcpServers)
33996
- );
33997
- } catch (error51) {
33998
- const connectionError = this.buildConnectionError(error51, recentLogs);
33999
- await this.cleanupFailedConnection();
34000
- throw connectionError;
34001
- } finally {
34002
- connection.off("log", collectLog);
34003
- }
34004
- await this.finalizeAcpConnect(options, session);
34005
- return {
34006
- cli: "codex",
34007
- protocol: "acp",
34008
- session
34009
- };
34010
- }
34011
33969
  async disconnect() {
34012
33970
  if (!this.connection) {
34013
33971
  this.clearSessionState();
34014
33972
  return;
34015
33973
  }
34016
- const conn = this.connection;
34017
- if (this.sessionId && conn instanceof AcpConnection && conn.canResetSession) {
34018
- try {
34019
- await conn.endSession(this.sessionId);
34020
- } catch {
34021
- }
33974
+ if (this.archiveSessionOnDisconnect && this.sessionId) {
33975
+ await this.connection.endSession().catch(() => {
33976
+ });
34022
33977
  }
34023
- await conn.disconnect();
34024
- conn.removeAllListeners();
33978
+ await this.connection.disconnect();
33979
+ this.connection.removeAllListeners();
34025
33980
  this.connection = null;
34026
33981
  this.clearSessionState();
34027
33982
  }
@@ -34029,14 +33984,7 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
34029
33984
  if (!this.connection) {
34030
33985
  throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
34031
33986
  }
34032
- if (this.connection instanceof AcpConnection) {
34033
- if (!this.sessionId) {
34034
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
34035
- }
34036
- await this.connection.endSession(this.sessionId);
34037
- } else {
34038
- await this.connection.endSession();
34039
- }
33987
+ await this.connection.endSession();
34040
33988
  this.sessionId = null;
34041
33989
  this.sessionCwd = null;
34042
33990
  }
@@ -34051,7 +33999,7 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
34051
33999
  }
34052
34000
  return {
34053
34001
  cli: "codex",
34054
- protocol: this.connection instanceof AcpConnection ? "acp" : "codex-app-server",
34002
+ protocol: "codex-app-server",
34055
34003
  sessionId: this.sessionId,
34056
34004
  state: this.connection.connectionState
34057
34005
  };
@@ -34063,12 +34011,6 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
34063
34011
  if (!this.connection) {
34064
34012
  throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
34065
34013
  }
34066
- if (this.connection instanceof AcpConnection) {
34067
- if (!this.sessionId) {
34068
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
34069
- }
34070
- return this.sendPromptWithPendingSystemPrompt(content);
34071
- }
34072
34014
  this.applyPendingOverrides();
34073
34015
  const input = typeof content === "string" ? [{ type: "text", text: content, text_elements: [] }] : content.map((block) => "text" in block ? { type: "text", text: block.text, text_elements: [] } : { type: "text", text: JSON.stringify(block), text_elements: [] });
34074
34016
  const systemPrompt = this.firstPromptPending;
@@ -34079,53 +34021,20 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
34079
34021
  }
34080
34022
  return { stopReason: "end_turn" };
34081
34023
  }
34082
- async sendPromptWithPendingSystemPrompt(content) {
34083
- if (!(this.connection instanceof AcpConnection) || !this.sessionId) {
34084
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
34085
- }
34086
- const systemPrompt = this.firstPromptPending;
34087
- if (!systemPrompt) {
34088
- return this.connection.sendPrompt(this.sessionId, content);
34089
- }
34090
- const userBlocks = typeof content === "string" ? [{ type: "text", text: content }] : content;
34091
- const response = await this.connection.sendPrompt(this.sessionId, [
34092
- { type: "text", text: systemPrompt },
34093
- ...userBlocks
34094
- ]);
34095
- this.firstPromptPending = null;
34096
- return response;
34097
- }
34098
34024
  async cancelPrompt() {
34099
34025
  if (!this.connection) {
34100
34026
  throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
34101
34027
  }
34102
- if (this.connection instanceof AcpConnection) {
34103
- if (!this.sessionId) {
34104
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
34105
- }
34106
- await this.connection.cancelSession(this.sessionId);
34107
- return;
34108
- }
34109
34028
  await this.connection.cancelPrompt();
34110
34029
  }
34111
34030
  async setModel(model) {
34112
- if (this.connection instanceof AcpConnection && this.sessionId) {
34113
- await this.connection.setModel(this.sessionId, model);
34114
- return;
34115
- }
34116
- this.ensurePendingOverrides().model = model;
34031
+ const selection = this.resolveModelSelection(model);
34032
+ this.currentModelSelection = selection;
34033
+ this.ensurePendingOverrides().model = selection;
34117
34034
  }
34118
34035
  async setConfigOption(configId, value) {
34119
- if (this.connection instanceof AcpConnection && this.sessionId) {
34120
- if (configId === "model") {
34121
- await this.connection.setModel(this.sessionId, value);
34122
- return;
34123
- }
34124
- await this.connection.setConfigOption(
34125
- this.sessionId,
34126
- configId === "effort" ? "reasoning_effort" : configId,
34127
- value
34128
- );
34036
+ if (configId === "model") {
34037
+ await this.setModel(value);
34129
34038
  return;
34130
34039
  }
34131
34040
  const pending = this.ensurePendingOverrides();
@@ -34137,10 +34046,6 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
34137
34046
  this.emitTyped("log", `[codex] config '${configId}' will apply on next thread/start or thread/resume`);
34138
34047
  }
34139
34048
  async setMode(mode) {
34140
- if (this.connection instanceof AcpConnection && this.sessionId) {
34141
- await this.connection.setMode(this.sessionId, this.resolveAcpMode(mode));
34142
- return;
34143
- }
34144
34049
  const resolved = this.resolveMode(mode);
34145
34050
  const pending = this.ensurePendingOverrides();
34146
34051
  pending.threadConfig.approvalPolicy = resolved.approvalPolicy;
@@ -34163,17 +34068,6 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
34163
34068
  if (!this.connection) {
34164
34069
  throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
34165
34070
  }
34166
- if (this.connection instanceof AcpConnection) {
34167
- await this.connection.loadSession({
34168
- sessionId,
34169
- cwd: this.sessionCwd ?? process.cwd(),
34170
- mcpServers: this.resolveAcpMcpServers(mcpServers)
34171
- });
34172
- this.sessionId = sessionId;
34173
- this.currentSystemPrompt = null;
34174
- this.firstPromptPending = null;
34175
- return;
34176
- }
34177
34071
  if (mcpServers?.length) {
34178
34072
  this.emitTyped(
34179
34073
  "log",
@@ -34185,7 +34079,7 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
34185
34079
  sessionId,
34186
34080
  this.buildCodexThreadDefaultsForResume(
34187
34081
  targetCwd,
34188
- void 0,
34082
+ this.currentModelSelection,
34189
34083
  null,
34190
34084
  this.pendingOverrides?.threadConfig ?? {}
34191
34085
  )
@@ -34199,24 +34093,6 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
34199
34093
  throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
34200
34094
  }
34201
34095
  const targetCwd = cwd ?? this.sessionCwd ?? process.cwd();
34202
- if (this.connection instanceof AcpConnection) {
34203
- if (!this.sessionId) {
34204
- throw new Error("\uC5F0\uACB0\uB418\uC5B4 \uC788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4");
34205
- }
34206
- if (!this.connection.canResetSession) {
34207
- throw new Error("[codex] \uC138\uC158 \uB9AC\uC14B\uC744 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. disconnect() \uD6C4 \uC7AC\uC5F0\uACB0\uD558\uC138\uC694.");
34208
- }
34209
- await this.connection.endSession(this.sessionId);
34210
- const session = await this.connection.reconnectSession(targetCwd);
34211
- this.sessionId = session.sessionId;
34212
- this.sessionCwd = targetCwd;
34213
- this.firstPromptPending = this.currentSystemPrompt;
34214
- return {
34215
- cli: "codex",
34216
- protocol: "acp",
34217
- session
34218
- };
34219
- }
34220
34096
  const result = await this.connection.resetSession(
34221
34097
  this.buildCodexThreadDefaultsForReset(targetCwd)
34222
34098
  );
@@ -34232,33 +34108,14 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
34232
34108
  protocol: "codex-app-server"
34233
34109
  };
34234
34110
  }
34235
- resolveAcpMcpServers(servers) {
34236
- return servers?.length ? mcpServerConfigsToAcp(servers) : [];
34237
- }
34238
- async finalizeAcpConnect(options, session) {
34239
- this.sessionId = session.sessionId;
34240
- this.sessionCwd = options.cwd;
34241
- this.currentSystemPrompt = options.systemPrompt ?? null;
34242
- this.firstPromptPending = options.sessionId ? null : this.currentSystemPrompt;
34243
- this.pendingOverrides = {
34244
- turnConfig: {},
34245
- threadConfig: {}
34246
- };
34247
- const mode = options.yoloMode === false ? "default" : "yolo";
34248
- await this.setMode(mode);
34249
- if (options.model) {
34250
- await this.setModel(options.model);
34251
- }
34252
- if (options.effort) {
34253
- await this.setConfigOption("effort", options.effort);
34254
- }
34255
- }
34256
34111
  clearSessionState() {
34257
34112
  this.sessionId = null;
34258
34113
  this.sessionCwd = null;
34259
34114
  this.currentSystemPrompt = null;
34260
34115
  this.firstPromptPending = null;
34261
34116
  this.pendingOverrides = null;
34117
+ this.currentModelSelection = {};
34118
+ this.archiveSessionOnDisconnect = false;
34262
34119
  }
34263
34120
  validateModelEffort(modelId, effort) {
34264
34121
  if (!effort) {
@@ -34281,69 +34138,7 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
34281
34138
  }
34282
34139
  setupEventForwarding() {
34283
34140
  if (!this.connection) return;
34284
- const connection = this.connection;
34285
- if (connection instanceof AcpConnection) {
34286
- this.setupAcpEventForwarding(connection);
34287
- return;
34288
- }
34289
- this.setupAppServerEventForwarding(connection);
34290
- }
34291
- setupAcpEventForwarding(connection) {
34292
- connection.on("stateChange", (state) => {
34293
- this.emitTyped("stateChange", state);
34294
- });
34295
- connection.on("userMessageChunk", (text, sessionId) => {
34296
- this.emitTyped("userMessageChunk", text, sessionId);
34297
- });
34298
- connection.on("messageChunk", (text, sessionId) => {
34299
- this.emitTyped("messageChunk", text, sessionId);
34300
- });
34301
- connection.on("thoughtChunk", (text, sessionId) => {
34302
- this.emitTyped("thoughtChunk", text, sessionId);
34303
- });
34304
- connection.on("toolCall", (title, status, sessionId, data) => {
34305
- this.emitTyped("toolCall", title, status, sessionId, data);
34306
- });
34307
- connection.on("toolCallUpdate", (title, status, sessionId, data) => {
34308
- this.emitTyped("toolCallUpdate", title, status, sessionId, data);
34309
- });
34310
- connection.on("plan", (plan, sessionId) => {
34311
- this.emitTyped("plan", plan, sessionId);
34312
- });
34313
- connection.on("availableCommandsUpdate", (commands, sessionId) => {
34314
- this.emitTyped("availableCommandsUpdate", commands, sessionId);
34315
- });
34316
- connection.on("promptComplete", (sessionId) => {
34317
- this.emitTyped("promptComplete", sessionId);
34318
- });
34319
- connection.on("permissionRequest", (params, resolve3) => {
34320
- this.emitTyped(
34321
- "permissionRequest",
34322
- params,
34323
- resolve3
34324
- );
34325
- });
34326
- connection.on("sessionUpdate", (update) => {
34327
- this.emitTyped("sessionUpdate", update);
34328
- });
34329
- connection.on("fileRead", (params, resolve3) => {
34330
- this.emitTyped("fileRead", params, resolve3);
34331
- });
34332
- connection.on("fileWrite", (params, resolve3) => {
34333
- this.emitTyped("fileWrite", params, resolve3);
34334
- });
34335
- connection.on("error", (err) => {
34336
- this.emitTyped("error", err);
34337
- });
34338
- connection.on("exit", (code, signal) => {
34339
- this.emitTyped("exit", code, signal);
34340
- });
34341
- connection.on("log", (msg) => {
34342
- this.emitTyped("log", msg);
34343
- });
34344
- connection.on("logEntry", (entry) => {
34345
- this.emitTyped("logEntry", entry);
34346
- });
34141
+ this.setupAppServerEventForwarding(this.connection);
34347
34142
  }
34348
34143
  setupAppServerEventForwarding(connection) {
34349
34144
  connection.on("stateChange", (state) => {
@@ -34406,15 +34201,15 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
34406
34201
  if (!this.pendingOverrides || !this.connection) {
34407
34202
  return;
34408
34203
  }
34409
- if (this.connection instanceof AcpConnection) {
34410
- return;
34411
- }
34412
34204
  if (this.pendingOverrides.model) {
34413
- this.connection.setPendingModel(this.pendingOverrides.model);
34205
+ this.connection.setPendingModel(this.pendingOverrides.model.providerModelId ?? null);
34206
+ this.connection.setPendingServiceTier(this.pendingOverrides.model.serviceTier ?? null);
34414
34207
  this.pendingOverrides.model = void 0;
34415
34208
  }
34416
34209
  if (this.pendingOverrides.turnConfig.model) {
34417
- this.connection.setPendingModel(this.pendingOverrides.turnConfig.model);
34210
+ const selection = this.resolveModelSelection(this.pendingOverrides.turnConfig.model);
34211
+ this.connection.setPendingModel(selection.providerModelId ?? null);
34212
+ this.connection.setPendingServiceTier(selection.serviceTier ?? null);
34418
34213
  delete this.pendingOverrides.turnConfig.model;
34419
34214
  }
34420
34215
  if (this.pendingOverrides.turnConfig.effort) {
@@ -34429,19 +34224,22 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
34429
34224
  const config2 = configEntries.length > 0 ? Object.fromEntries(configEntries) : void 0;
34430
34225
  return {
34431
34226
  cwd,
34227
+ model: this.currentModelSelection.providerModelId,
34228
+ serviceTier: this.currentModelSelection.serviceTier,
34432
34229
  approvalPolicy,
34433
34230
  sandbox,
34434
34231
  developerInstructions: void 0,
34435
34232
  config: config2
34436
34233
  };
34437
34234
  }
34438
- buildCodexThreadDefaultsForResume(cwd, model, systemPrompt, threadConfig) {
34235
+ buildCodexThreadDefaultsForResume(cwd, modelSelection, systemPrompt, threadConfig) {
34439
34236
  const { approvalPolicy, sandbox } = threadConfig;
34440
34237
  const configEntries = Object.entries(threadConfig).filter(([key]) => !CODEX_THREAD_POLICY_CONFIG_KEYS.has(key));
34441
34238
  const config2 = configEntries.length > 0 ? Object.fromEntries(configEntries) : void 0;
34442
34239
  return {
34443
34240
  cwd,
34444
- ...model ? { model } : {},
34241
+ ...modelSelection.providerModelId ? { model: modelSelection.providerModelId } : {},
34242
+ ...modelSelection.serviceTier ? { serviceTier: modelSelection.serviceTier } : {},
34445
34243
  approvalPolicy,
34446
34244
  sandbox,
34447
34245
  developerInstructions: systemPrompt ?? void 0,
@@ -34458,16 +34256,6 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
34458
34256
  return { approvalPolicy: "on-request", sandbox: "read-only" };
34459
34257
  }
34460
34258
  }
34461
- resolveAcpMode(modeId) {
34462
- switch (modeId) {
34463
- case "autoEdit":
34464
- return "agent";
34465
- case "yolo":
34466
- return "agent-full-access";
34467
- default:
34468
- return "read-only";
34469
- }
34470
- }
34471
34259
  buildStartupConfigArgs(overrides, servers, modeMapping) {
34472
34260
  const configArgs = [
34473
34261
  ...modeMapping ? [
@@ -34497,6 +34285,10 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
34497
34285
  return;
34498
34286
  }
34499
34287
  try {
34288
+ if (this.archiveSessionOnDisconnect && this.sessionId) {
34289
+ await this.connection.endSession().catch(() => {
34290
+ });
34291
+ }
34500
34292
  await this.connection.disconnect();
34501
34293
  } catch {
34502
34294
  }
@@ -34504,65 +34296,17 @@ var UnifiedCodexAgentClient = class extends EventEmitter {
34504
34296
  this.connection = null;
34505
34297
  this.clearSessionState();
34506
34298
  }
34507
- buildConnectionError(error51, recentLogs) {
34508
- if (getBackendConfig("codex").authRequired && this.isAuthenticationError(error51, recentLogs)) {
34509
- return new Error(
34510
- "[codex] \uC778\uC99D\uC774 \uD544\uC694\uD558\uAC70\uB098 \uC778\uC99D\uC774 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uBA3C\uC800 \uD574\uB2F9 CLI\uC5D0\uC11C \uB85C\uADF8\uC778/\uC778\uC99D\uC744 \uC644\uB8CC\uD55C \uB4A4 \uB2E4\uC2DC \uC2DC\uB3C4\uD574\uC8FC\uC138\uC694."
34511
- );
34512
- }
34513
- if (error51 instanceof Error) {
34514
- return error51;
34515
- }
34516
- if (typeof error51 === "object" && error51 !== null) {
34517
- const obj = error51;
34518
- if (typeof obj.message === "string") {
34519
- const code = typeof obj.code === "number" ? ` (code: ${obj.code})` : "";
34520
- const data = obj.data ? ` \u2014 ${JSON.stringify(obj.data)}` : "";
34521
- return new Error(`${obj.message}${code}${data}`);
34522
- }
34523
- return new Error(JSON.stringify(error51));
34524
- }
34525
- return new Error(String(error51));
34526
- }
34527
- isAuthenticationError(error51, recentLogs) {
34528
- const authPatterns = [
34529
- /auth_required/i,
34530
- /authentication required/i,
34531
- /not authenticated/i,
34532
- /please login/i,
34533
- /please log in/i,
34534
- /sign in/i,
34535
- /reauth/i,
34536
- /unauthorized/i,
34537
- /invalid api key/i
34538
- ];
34539
- if (this.matchAnyPattern(this.extractErrorText(error51), authPatterns)) {
34540
- return true;
34541
- }
34542
- return recentLogs.some((log) => this.matchAnyPattern(log, authPatterns));
34543
- }
34544
- extractErrorText(error51) {
34545
- if (error51 instanceof Error) {
34546
- const code = error51.code;
34547
- if (code === -32e3) {
34548
- return `auth_required ${error51.message}`;
34549
- }
34550
- return error51.message;
34551
- }
34552
- if (typeof error51 === "string") {
34553
- return error51;
34299
+ resolveModelSelection(modelId) {
34300
+ if (!modelId) {
34301
+ return {};
34554
34302
  }
34555
- return String(error51);
34556
- }
34557
- matchAnyPattern(text, patterns) {
34558
- return patterns.some((pattern) => pattern.test(text));
34303
+ const model = getProviderModels("codex").models.find((entry) => entry.modelId === modelId);
34304
+ return {
34305
+ providerModelId: model?.providerModelId ?? modelId,
34306
+ serviceTier: model?.serviceTier
34307
+ };
34559
34308
  }
34560
34309
  };
34561
- function shouldUseCodexAcp(options) {
34562
- const value = options.env?.CODEX_USE_ACP ?? process.env.CODEX_USE_ACP;
34563
- const normalized = value?.trim().toLowerCase();
34564
- return normalized !== void 0 && normalized !== "false" && normalized !== "0";
34565
- }
34566
34310
  var UnifiedCursorAgentClient = class extends EventEmitter {
34567
34311
  connection = null;
34568
34312
  sessionId = null;
@@ -38079,9 +37823,243 @@ function stripIpv6Brackets2(host) {
38079
37823
 
38080
37824
  // core/host/operations/types.ts
38081
37825
  var MAX_GROUP_NAME_LENGTH = 64;
37826
+ var DELETION_GRACE_MS = 8e3;
38082
37827
 
38083
- // core/host/durable-state.ts
38084
- var STATE_VERSION = 2;
37828
+ // core/host/deferred-deletion.ts
37829
+ var DeferredDeletionError = class extends Error {
37830
+ status;
37831
+ constructor(status, message) {
37832
+ super(message);
37833
+ this.name = "DeferredDeletionError";
37834
+ this.status = status;
37835
+ }
37836
+ };
37837
+ var OPERATION_DELETED_EVENT_CHANNEL = "operation:deleted";
37838
+ var OPERATION_RESTORED_EVENT_CHANNEL = "operation:restored";
37839
+ var OPERATION_PURGED_EVENT_CHANNEL = "operation:purged";
37840
+ var PURGE_RETRY_MS = 1e3;
37841
+ function createDeferredDeletionCoordinator(deps) {
37842
+ const now = deps.now ?? Date.now;
37843
+ const randomId = deps.randomId ?? crypto2.randomUUID;
37844
+ const setTimer = deps.setTimer ?? setTimeout;
37845
+ const clearTimer = deps.clearTimer ?? clearTimeout;
37846
+ let tombstones = [];
37847
+ let timer = null;
37848
+ function list() {
37849
+ return tombstones;
37850
+ }
37851
+ function load(nextTombstones) {
37852
+ tombstones = [...nextTombstones];
37853
+ schedule();
37854
+ }
37855
+ function deleteOperation(operationId) {
37856
+ sweepExpired();
37857
+ const existing = tombstones.find((item) => item.kind === "operation" && item.targetId === operationId);
37858
+ if (existing) return toReceipt(existing);
37859
+ const operation = deps.operations.get(operationId);
37860
+ if (!operation) return null;
37861
+ const previousOperations = deps.operations.list();
37862
+ const deletedAt = now();
37863
+ const tombstone = {
37864
+ deletionId: randomId(),
37865
+ targetId: operation.id,
37866
+ deletedAt,
37867
+ expiresAt: deletedAt + DELETION_GRACE_MS,
37868
+ kind: "operation",
37869
+ operation
37870
+ };
37871
+ const nextTombstones = [...tombstones, tombstone];
37872
+ deps.operations.delete(operationId);
37873
+ try {
37874
+ deps.save(nextTombstones);
37875
+ } catch (error51) {
37876
+ deps.operations.replace(previousOperations);
37877
+ throw error51;
37878
+ }
37879
+ tombstones = nextTombstones;
37880
+ publishOperation(OPERATION_DELETED_EVENT_CHANNEL, operation);
37881
+ schedule();
37882
+ return toReceipt(tombstone);
37883
+ }
37884
+ function deleteTheater(theaterId) {
37885
+ sweepExpired();
37886
+ const existing = tombstones.find((item) => item.kind === "theater" && item.targetId === theaterId);
37887
+ if (existing) return toReceipt(existing);
37888
+ const theater = deps.theaters.get(theaterId);
37889
+ if (!theater) return null;
37890
+ const previousTheaters = deps.theaters.list();
37891
+ const previousOperations = deps.operations.list();
37892
+ const previousGroups = deps.operations.listAllGroups();
37893
+ const deletedOperations = deps.operations.listByTheater(theaterId);
37894
+ const deletedGroups = deps.operations.listGroups(theaterId);
37895
+ const deletedAt = now();
37896
+ const tombstone = {
37897
+ deletionId: randomId(),
37898
+ targetId: theater.id,
37899
+ deletedAt,
37900
+ expiresAt: deletedAt + DELETION_GRACE_MS,
37901
+ kind: "theater",
37902
+ theater,
37903
+ operations: deletedOperations,
37904
+ groups: deletedGroups
37905
+ };
37906
+ const nextTombstones = [...tombstones, tombstone];
37907
+ deps.theaters.remove(theaterId);
37908
+ deps.operations.deleteByTheater(theaterId);
37909
+ try {
37910
+ deps.save(nextTombstones);
37911
+ } catch (error51) {
37912
+ deps.theaters.restore(previousTheaters);
37913
+ deps.operations.replace(previousOperations);
37914
+ deps.operations.replaceGroups(previousGroups);
37915
+ throw error51;
37916
+ }
37917
+ tombstones = nextTombstones;
37918
+ deps.unregisterTheaterWorkspaces(theaterId);
37919
+ for (const operation of deletedOperations) publishOperation(OPERATION_DELETED_EVENT_CHANNEL, operation);
37920
+ schedule();
37921
+ return toReceipt(tombstone);
37922
+ }
37923
+ async function restore(deletionId) {
37924
+ const restoredAt = now();
37925
+ const tombstone = tombstones.find((item) => item.deletionId === deletionId);
37926
+ if (!tombstone || tombstone.expiresAt <= restoredAt) {
37927
+ if (tombstone) sweepExpired();
37928
+ throw new DeferredDeletionError(404, "deletion_not_found");
37929
+ }
37930
+ if (tombstone.kind === "operation") {
37931
+ if (deps.operations.get(tombstone.targetId) || !deps.theaters.get(tombstone.operation.theaterId)) {
37932
+ throw new DeferredDeletionError(409, "restore_conflict");
37933
+ }
37934
+ const previousOperations2 = deps.operations.list();
37935
+ const nextTombstones2 = tombstones.filter((item) => item.deletionId !== deletionId);
37936
+ deps.operations.replace([...previousOperations2, tombstone.operation]);
37937
+ try {
37938
+ deps.save(nextTombstones2);
37939
+ } catch (error51) {
37940
+ deps.operations.replace(previousOperations2);
37941
+ throw error51;
37942
+ }
37943
+ tombstones = nextTombstones2;
37944
+ publishOperation(OPERATION_RESTORED_EVENT_CHANNEL, tombstone.operation);
37945
+ schedule();
37946
+ return { ok: true, kind: tombstone.kind, targetId: tombstone.targetId };
37947
+ }
37948
+ await deps.validateTheaterRestore(tombstone.theater);
37949
+ if (deps.theaters.get(tombstone.targetId) || tombstone.operations.some((operation) => deps.operations.get(operation.id)) || hasGroupConflict(tombstone.groups, deps.operations.listAllGroups())) {
37950
+ throw new DeferredDeletionError(409, "restore_conflict");
37951
+ }
37952
+ const previousTheaters = deps.theaters.list();
37953
+ const previousOperations = deps.operations.list();
37954
+ const previousGroups = deps.operations.listAllGroups();
37955
+ const nextTombstones = tombstones.filter((item) => item.deletionId !== deletionId);
37956
+ deps.theaters.restore([...previousTheaters, tombstone.theater]);
37957
+ deps.operations.replace([...previousOperations, ...tombstone.operations]);
37958
+ deps.operations.replaceGroups([...previousGroups, ...tombstone.groups]);
37959
+ try {
37960
+ deps.save(nextTombstones);
37961
+ } catch (error51) {
37962
+ deps.theaters.restore(previousTheaters);
37963
+ deps.operations.replace(previousOperations);
37964
+ deps.operations.replaceGroups(previousGroups);
37965
+ throw error51;
37966
+ }
37967
+ tombstones = nextTombstones;
37968
+ await deps.registerTheaterWorkspace(tombstone.theater);
37969
+ for (const operation of tombstone.operations) publishOperation(OPERATION_RESTORED_EVENT_CHANNEL, operation);
37970
+ schedule();
37971
+ return { ok: true, kind: tombstone.kind, targetId: tombstone.targetId };
37972
+ }
37973
+ function sweepExpired() {
37974
+ const cutoff = now();
37975
+ const expired = tombstones.filter((item) => item.expiresAt <= cutoff);
37976
+ if (expired.length === 0) {
37977
+ schedule();
37978
+ return;
37979
+ }
37980
+ const expiredIds = new Set(expired.map((item) => item.deletionId));
37981
+ const nextTombstones = tombstones.filter((item) => !expiredIds.has(item.deletionId));
37982
+ try {
37983
+ deps.save(nextTombstones);
37984
+ } catch (error51) {
37985
+ schedule(PURGE_RETRY_MS);
37986
+ throw error51;
37987
+ }
37988
+ tombstones = nextTombstones;
37989
+ for (const tombstone of expired) {
37990
+ if (tombstone.kind === "operation") {
37991
+ publishOperation(OPERATION_PURGED_EVENT_CHANNEL, tombstone.operation);
37992
+ } else {
37993
+ for (const operation of tombstone.operations) publishOperation(OPERATION_PURGED_EVENT_CHANNEL, operation);
37994
+ }
37995
+ }
37996
+ schedule();
37997
+ }
37998
+ function hasPendingOperation(operationId) {
37999
+ sweepExpired();
38000
+ return tombstones.some((item) => item.kind === "operation" ? item.targetId === operationId : item.operations.some((operation) => operation.id === operationId));
38001
+ }
38002
+ function hasPendingTheater(theaterId) {
38003
+ sweepExpired();
38004
+ return tombstones.some((item) => item.kind === "theater" && item.targetId === theaterId);
38005
+ }
38006
+ function schedule(forcedDelay) {
38007
+ if (timer) clearTimer(timer);
38008
+ timer = null;
38009
+ if (forcedDelay !== void 0) {
38010
+ timer = setTimer(runScheduledSweep, forcedDelay);
38011
+ timer.unref?.();
38012
+ return;
38013
+ }
38014
+ const nearest = tombstones.reduce((minimum, item) => minimum === null ? item.expiresAt : Math.min(minimum, item.expiresAt), null);
38015
+ if (nearest === null) return;
38016
+ timer = setTimer(runScheduledSweep, Math.max(0, nearest - now()));
38017
+ timer.unref?.();
38018
+ }
38019
+ function runScheduledSweep() {
38020
+ timer = null;
38021
+ try {
38022
+ sweepExpired();
38023
+ } catch {
38024
+ }
38025
+ }
38026
+ function dispose() {
38027
+ if (timer) clearTimer(timer);
38028
+ timer = null;
38029
+ }
38030
+ function publishOperation(channel, operation) {
38031
+ deps.publish(channel, {
38032
+ operationId: operation.id,
38033
+ pluginId: operation.pluginId,
38034
+ type: operation.type,
38035
+ ...channel === OPERATION_RESTORED_EVENT_CHANNEL ? { operation } : {}
38036
+ });
38037
+ }
38038
+ return {
38039
+ list,
38040
+ load,
38041
+ deleteOperation,
38042
+ deleteTheater,
38043
+ restore,
38044
+ sweepExpired,
38045
+ hasPendingOperation,
38046
+ hasPendingTheater,
38047
+ dispose
38048
+ };
38049
+ }
38050
+ function toReceipt(tombstone) {
38051
+ return {
38052
+ deletionId: tombstone.deletionId,
38053
+ kind: tombstone.kind,
38054
+ targetId: tombstone.targetId,
38055
+ expiresAt: tombstone.expiresAt
38056
+ };
38057
+ }
38058
+ function hasGroupConflict(groups, existing) {
38059
+ const existingIds = new Set(existing.map((group) => group.id));
38060
+ return groups.some((group) => existingIds.has(group.id));
38061
+ }
38062
+ var STATE_VERSION = 3;
38085
38063
  var STATE_LOCK_DIR_NAME = "state.lock";
38086
38064
  var STATE_LOCK_OWNER_FILE_NAME = "owner.json";
38087
38065
  var STATE_TEMP_PREFIX = ".state.";
@@ -38128,25 +38106,37 @@ function sanitizeDurableConsoleState(value) {
38128
38106
  version: STATE_VERSION,
38129
38107
  theaters: readTheaterRegistrations(upgraded.theaters),
38130
38108
  operations: readOperations(upgraded.operations),
38131
- groups: readOperationGroups(upgraded.groups)
38109
+ groups: readOperationGroups(upgraded.groups),
38110
+ deletionTombstones: readDeletionTombstones(upgraded.deletionTombstones)
38132
38111
  };
38133
38112
  }
38134
38113
  function emptyDurableConsoleState() {
38135
- return { version: STATE_VERSION, theaters: [], operations: [], groups: [] };
38114
+ return { version: STATE_VERSION, theaters: [], operations: [], groups: [], deletionTombstones: [] };
38136
38115
  }
38137
38116
  function migrateToCurrentVersion(value) {
38138
- if (value.version === 1) return migrateV1ToV2(value);
38139
- return value;
38117
+ let current = value;
38118
+ if (current.version === 1) current = migrateV1ToV2(current);
38119
+ if (current.version === 2) current = migrateV2ToV3(current);
38120
+ return current;
38140
38121
  }
38141
38122
  function migrateV1ToV2(v1) {
38142
38123
  const operations = Array.isArray(v1.operations) ? v1.operations.map(migrateV1Operation) : [];
38143
38124
  return {
38144
- version: STATE_VERSION,
38125
+ version: 2,
38145
38126
  theaters: v1.theaters ?? [],
38146
38127
  operations,
38147
38128
  groups: []
38148
38129
  };
38149
38130
  }
38131
+ function migrateV2ToV3(v2) {
38132
+ return {
38133
+ version: STATE_VERSION,
38134
+ theaters: v2.theaters ?? [],
38135
+ operations: v2.operations ?? [],
38136
+ groups: v2.groups ?? [],
38137
+ deletionTombstones: []
38138
+ };
38139
+ }
38150
38140
  function migrateV1Operation(value) {
38151
38141
  if (!isRecord5(value)) return null;
38152
38142
  const sessionId = readNonEmptyString(value.sessionId);
@@ -38310,6 +38300,37 @@ function sanitizeOperationGroup(value) {
38310
38300
  if (!VALID_GROUP_COLOR_KEYS.has(color)) return null;
38311
38301
  return { id, theaterId, name: rawName, color, order, createdAt };
38312
38302
  }
38303
+ function readDeletionTombstones(value) {
38304
+ if (!Array.isArray(value)) return [];
38305
+ const tombstones = [];
38306
+ for (const item of value) {
38307
+ const tombstone = sanitizeDeletionTombstone(item);
38308
+ if (tombstone) tombstones.push(tombstone);
38309
+ }
38310
+ return tombstones;
38311
+ }
38312
+ function sanitizeDeletionTombstone(value) {
38313
+ if (!isRecord5(value)) return null;
38314
+ const deletionId = readNonEmptyString(value.deletionId);
38315
+ const targetId = readNonEmptyString(value.targetId);
38316
+ const deletedAt = readFiniteNumber(value.deletedAt);
38317
+ const expiresAt = readFiniteNumber(value.expiresAt);
38318
+ if (!deletionId || !targetId || deletedAt === null || expiresAt === null) return null;
38319
+ if (value.kind === "operation") {
38320
+ const operation = sanitizeOperationNode(value.operation);
38321
+ if (!operation || operation.id !== targetId) return null;
38322
+ return { deletionId, targetId, deletedAt, expiresAt, kind: "operation", operation };
38323
+ }
38324
+ if (value.kind === "theater") {
38325
+ const theater = sanitizeTheaterRegistration(value.theater);
38326
+ if (!theater || theater.id !== targetId || !Array.isArray(value.operations) || !Array.isArray(value.groups)) return null;
38327
+ const operations = value.operations.map(sanitizeOperationNode).filter((operation) => operation !== null);
38328
+ const groups = value.groups.map(sanitizeOperationGroup).filter((group) => group !== null);
38329
+ if (operations.length !== value.operations.length || groups.length !== value.groups.length || operations.some((operation) => operation.theaterId !== targetId) || groups.some((group) => group.theaterId !== targetId)) return null;
38330
+ return { deletionId, targetId, deletedAt, expiresAt, kind: "theater", theater, operations, groups };
38331
+ }
38332
+ return null;
38333
+ }
38313
38334
 
38314
38335
  // core/host/operations/sanitize.ts
38315
38336
  var FIXED_SENSITIVE_OPERATION_FIELDS = /* @__PURE__ */ new Set([
@@ -38325,11 +38346,20 @@ var FIXED_SENSITIVE_OPERATION_FIELDS = /* @__PURE__ */ new Set([
38325
38346
  ]);
38326
38347
  function createSanitizedOpDto(node, options = {}) {
38327
38348
  const sensitiveFields = /* @__PURE__ */ new Set([...FIXED_SENSITIVE_OPERATION_FIELDS, ...options.sensitiveFields ?? []]);
38349
+ const payload = sanitizeRecord(node.payload, sensitiveFields);
38350
+ delete payload.resumeAvailable;
38351
+ if (isResumableProviderSession(node.payload?.providerSession)) payload.resumeAvailable = true;
38328
38352
  return {
38329
38353
  ...node,
38330
- payload: sanitizeRecord(node.payload, sensitiveFields)
38354
+ payload
38331
38355
  };
38332
38356
  }
38357
+ function isResumableProviderSession(value) {
38358
+ if (!isRecord6(value)) return false;
38359
+ const provider = value.provider;
38360
+ const sessionId = value.sessionId;
38361
+ return typeof provider === "string" && provider.length > 0 && typeof sessionId === "string" && sessionId.length > 0;
38362
+ }
38333
38363
  function sanitizeRecord(value, sensitiveFields) {
38334
38364
  const output = {};
38335
38365
  for (const [key, item] of Object.entries(value)) {
@@ -38411,6 +38441,10 @@ async function handleCollection(req, res, deps) {
38411
38441
  deps.writeJson(res, 400, { error: "invalid_operation_accent" });
38412
38442
  return;
38413
38443
  }
38444
+ if (typeof body2.id === "string" && deps.isPendingDeletion?.(body2.id)) {
38445
+ deps.writeJson(res, 409, { error: "pending_deletion" });
38446
+ return;
38447
+ }
38414
38448
  try {
38415
38449
  const node = deps.store.create({
38416
38450
  id: typeof body2.id === "string" ? body2.id : void 0,
@@ -38443,8 +38477,8 @@ async function handleItem(req, res, id, deps) {
38443
38477
  return;
38444
38478
  }
38445
38479
  if (req.method === "DELETE") {
38446
- const deleted = deps.deleteOperation(id);
38447
- deps.writeJson(res, deleted ? 200 : 404, deleted ? { ok: true } : { error: "operation_not_found" });
38480
+ const deletion = deps.deleteOperation(id);
38481
+ deps.writeJson(res, 200, { ok: true, deletion });
38448
38482
  return;
38449
38483
  }
38450
38484
  const body2 = await deps.readJsonBody(req);
@@ -39035,6 +39069,10 @@ function createPlansRouter(deps) {
39035
39069
  await handlePlansRead(req, res, deps);
39036
39070
  return true;
39037
39071
  }
39072
+ if (pathname === "/api/v1/plans/search") {
39073
+ await handlePlansSearch(req, res, deps);
39074
+ return true;
39075
+ }
39038
39076
  if (pathname === "/api/v1/plans/events") {
39039
39077
  await handlePlansEvents(req, res, deps);
39040
39078
  return true;
@@ -39042,6 +39080,38 @@ function createPlansRouter(deps) {
39042
39080
  return false;
39043
39081
  };
39044
39082
  }
39083
+ async function handlePlansSearch(req, res, deps) {
39084
+ if (req.method !== "POST") {
39085
+ deps.writeJson(res, 405, { error: "Method not allowed" });
39086
+ return;
39087
+ }
39088
+ if (!deps.isAuthorized(req)) {
39089
+ deps.writeJson(res, 401, { error: "unauthorized" });
39090
+ return;
39091
+ }
39092
+ const body2 = await deps.readJsonBody(req);
39093
+ if (!isPlainObject2(body2) || typeof body2.theaterId !== "string" || typeof body2.query !== "string" || body2.query.trim() === "" || !Number.isInteger(body2.limit) || body2.limit < 1 || body2.limit > 8 || "relPath" in body2) {
39094
+ deps.writeJson(res, 400, { error: "invalid_request" });
39095
+ return;
39096
+ }
39097
+ const theaterPath = deps.resolveTheaterPath(body2.theaterId);
39098
+ if (!theaterPath) {
39099
+ deps.writeJson(res, 404, { error: "theater_not_found" });
39100
+ return;
39101
+ }
39102
+ try {
39103
+ const context = await resolveTheaterRoot(theaterPath);
39104
+ const tokens = body2.query.trim().toLocaleLowerCase().split(/\s+/).filter(Boolean);
39105
+ const plans = (await listPlansForWorkspace(deps.dataDir, context.realRoot)).filter((plan) => {
39106
+ const text = `${plan.name}
39107
+ ${plan.title}`.toLocaleLowerCase();
39108
+ return tokens.every((token) => text.includes(token));
39109
+ }).slice(0, body2.limit).map((plan) => ({ name: plan.name, title: plan.title }));
39110
+ deps.writeJson(res, 200, { plans });
39111
+ } catch (error51) {
39112
+ writePlanStoreError(res, deps, error51);
39113
+ }
39114
+ }
39045
39115
  async function handlePlansEvents(req, res, deps) {
39046
39116
  if (req.method !== "GET") {
39047
39117
  deps.writeJson(res, 405, { error: "Method not allowed" });
@@ -40857,7 +40927,6 @@ var SERVER_TIMEOUT_MS = 30 * 60 * 1e3;
40857
40927
  var MAX_BODY_BYTES = 1024 * 1024;
40858
40928
  var UPDATE_APPLY_FORBIDDEN_BODY_KEYS = /* @__PURE__ */ new Set(["channel", "package", "packageName", "packageVersion", "packages", "targetVersion", "version"]);
40859
40929
  var OPERATION_RENAMED_EVENT_CHANNEL = "operation:renamed";
40860
- var OPERATION_DELETED_EVENT_CHANNEL = "operation:deleted";
40861
40930
  var PAIRING_IDENTITY_PATH = "/api/v1/pairing-identity";
40862
40931
  var PAIRING_IDENTITY = { product: "fleet-console", schemaVersion: 1, pairingProtocolVersion: 1 };
40863
40932
  var SERVER_API_CATALOG = [
@@ -40917,6 +40986,13 @@ var SERVER_API_CATALOG = [
40917
40986
  category: "Observer",
40918
40987
  gate: "origin-write"
40919
40988
  },
40989
+ {
40990
+ method: "POST",
40991
+ path: "/api/v1/deletions/:deletionId/restore",
40992
+ summary: "\uC720\uC608 \uC911\uC778 Operation \uB610\uB294 Theater \uC0AD\uC81C\uB97C \uBCF5\uAD6C\uD569\uB2C8\uB2E4.",
40993
+ category: "Observer",
40994
+ gate: "origin-write"
40995
+ },
40920
40996
  {
40921
40997
  method: "POST",
40922
40998
  path: "/api/v1/theaters/:theaterId/codex-workspace",
@@ -41044,16 +41120,29 @@ function createConsoleServer(deps = {}) {
41044
41120
  function publishPluginEvent(channel2, payload) {
41045
41121
  for (const listener of pluginEventListeners.get(channel2) ?? []) listener(payload);
41046
41122
  }
41047
- function deleteOperation(operationId) {
41048
- const operation = operations.get(operationId);
41049
- if (!operation || !operations.delete(operationId)) return false;
41050
- persistDurableState();
41051
- publishPluginEvent(OPERATION_DELETED_EVENT_CHANNEL, {
41052
- operationId: operation.id,
41053
- pluginId: operation.pluginId,
41054
- type: operation.type
41055
- });
41056
- return true;
41123
+ const deletionCoordinator = createDeferredDeletionCoordinator({
41124
+ operations,
41125
+ theaters,
41126
+ save: saveDurableState,
41127
+ publish: publishPluginEvent,
41128
+ unregisterTheaterWorkspaces: (theaterId) => codex.unregisterTheaterWorkspaces(theaterId),
41129
+ validateTheaterRestore: async (theater) => {
41130
+ try {
41131
+ const restoredRealpath = await fs10__default.promises.realpath(theater.path);
41132
+ const stat4 = await fs10__default.promises.stat(restoredRealpath);
41133
+ if (!stat4.isDirectory() || restoredRealpath !== theater.realpath || workspaceHash(restoredRealpath) !== theater.id) {
41134
+ throw new Error("restore_conflict");
41135
+ }
41136
+ } catch {
41137
+ throw new DeferredDeletionError(409, "restore_parent_missing");
41138
+ }
41139
+ },
41140
+ registerTheaterWorkspace: async (theater) => {
41141
+ await codex.registerWorkspace(theater.realpath, theater.lastOpenedAt, theater.id);
41142
+ }
41143
+ });
41144
+ function deleteOperationForPlugin(operationId) {
41145
+ return deletionCoordinator.deleteOperation(operationId) !== null;
41057
41146
  }
41058
41147
  let desktopFullscreen = false;
41059
41148
  let unsubscribeUpdateCheckChanges = updateCheck.onChange?.(() => {
@@ -41064,6 +41153,7 @@ function createConsoleServer(deps = {}) {
41064
41153
  list: () => operations.list(),
41065
41154
  get: (id) => operations.get(id),
41066
41155
  create: (input) => {
41156
+ if (input.id && deletionCoordinator.hasPendingOperation(input.id)) throw new Error("pending_deletion");
41067
41157
  const operation = operations.create(input);
41068
41158
  persistDurableState();
41069
41159
  return operation;
@@ -41079,7 +41169,7 @@ function createConsoleServer(deps = {}) {
41079
41169
  }
41080
41170
  return operation;
41081
41171
  },
41082
- delete: deleteOperation,
41172
+ delete: deleteOperationForPlugin,
41083
41173
  registerOperationType: (type) => {
41084
41174
  pluginOperationTypes.add(type);
41085
41175
  return () => {
@@ -41293,7 +41383,8 @@ function createConsoleServer(deps = {}) {
41293
41383
  readJsonBody,
41294
41384
  writeJson,
41295
41385
  persist: persistDurableState,
41296
- deleteOperation,
41386
+ deleteOperation: (operationId) => deletionCoordinator.deleteOperation(operationId),
41387
+ isPendingDeletion: (operationId) => deletionCoordinator.hasPendingOperation(operationId),
41297
41388
  getPluginSensitiveFields: (pluginId) => [
41298
41389
  ...pluginHost.sensitiveFieldsByPluginId.get(pluginId) ?? [],
41299
41390
  ...pluginPayloadSanitizers.get(pluginId) ?? []
@@ -41383,6 +41474,11 @@ function createConsoleServer(deps = {}) {
41383
41474
  runAsyncBooleanHandler(plansRouter({ req, res, pathname }), res, () => false);
41384
41475
  return;
41385
41476
  }
41477
+ const restoreMatch = pathname.match(/^\/api\/v1\/deletions\/([^/]+)\/restore$/);
41478
+ if (restoreMatch) {
41479
+ runAsyncHandler(handleDeferredDeletionRestore(req, res, decodeURIComponent(restoreMatch[1] ?? "")), res);
41480
+ return;
41481
+ }
41386
41482
  const theaterItemMatch = pathname.match(/^\/api\/v1\/theaters\/([^/]+)$/);
41387
41483
  if (theaterItemMatch) {
41388
41484
  runAsyncHandler(handleObserverTheaterItem(req, res, decodeURIComponent(theaterItemMatch[1] ?? "")), res);
@@ -41530,6 +41626,11 @@ function createConsoleServer(deps = {}) {
41530
41626
  writeJson(res, 400, { error: "invalid_folder_grant" });
41531
41627
  return;
41532
41628
  }
41629
+ const canonicalCwd = canonicalizeTheaterPathSync(cwd);
41630
+ if (deletionCoordinator.hasPendingTheater(workspaceHash(canonicalCwd))) {
41631
+ writeJson(res, 409, { error: "pending_deletion" });
41632
+ return;
41633
+ }
41533
41634
  const theater = await theaters.register(cwd);
41534
41635
  await codex.registerWorkspace(theater.realpath, void 0, theater.id);
41535
41636
  persistDurableState();
@@ -41559,12 +41660,27 @@ function createConsoleServer(deps = {}) {
41559
41660
  writeJson(res, 200, toTheaterInfo(theater, true));
41560
41661
  return;
41561
41662
  }
41562
- theaters.remove(theaterId);
41563
- codex.unregisterTheaterWorkspaces(theaterId);
41564
- for (const operation of operations.listByTheater(theaterId)) deleteOperation(operation.id);
41565
- operations.deleteGroupsByTheater(theaterId);
41566
- persistDurableState();
41567
- writeJson(res, 200, { ok: true });
41663
+ const deletion = deletionCoordinator.deleteTheater(theaterId);
41664
+ writeJson(res, 200, { ok: true, deletion });
41665
+ }
41666
+ async function handleDeferredDeletionRestore(req, res, deletionId) {
41667
+ if (req.method !== "POST") {
41668
+ writeJson(res, 405, { error: "Method not allowed" });
41669
+ return;
41670
+ }
41671
+ if (!isTerminalAuthorized(req)) {
41672
+ writeJson(res, 401, { error: "unauthorized" });
41673
+ return;
41674
+ }
41675
+ try {
41676
+ writeJson(res, 200, await deletionCoordinator.restore(deletionId));
41677
+ } catch (error51) {
41678
+ if (error51 instanceof DeferredDeletionError) {
41679
+ writeJson(res, error51.status, { error: error51.message });
41680
+ return;
41681
+ }
41682
+ throw error51;
41683
+ }
41568
41684
  }
41569
41685
  function handleStatus(req, res) {
41570
41686
  const theaterId = readUrl(req).searchParams.get("theaterId");
@@ -41781,6 +41897,12 @@ function createConsoleServer(deps = {}) {
41781
41897
  operations.replace([]);
41782
41898
  operations.replaceGroups([]);
41783
41899
  }
41900
+ deletionCoordinator.load(state.deletionTombstones ?? []);
41901
+ try {
41902
+ deletionCoordinator.sweepExpired();
41903
+ } catch (error51) {
41904
+ console.warn(`[fleet-console] Expired deletion sweep deferred: ${error51 instanceof Error ? error51.message : String(error51)}`);
41905
+ }
41784
41906
  await restoreCodexWorkspaces();
41785
41907
  }
41786
41908
  async function restoreCodexWorkspaces() {
@@ -41822,16 +41944,20 @@ function createConsoleServer(deps = {}) {
41822
41944
  }
41823
41945
  function persistDurableState() {
41824
41946
  try {
41825
- durableStateStore.save({
41826
- version: 2,
41827
- theaters: theaters.list(),
41828
- operations: operations.list(),
41829
- groups: operations.listAllGroups()
41830
- });
41947
+ saveDurableState(deletionCoordinator.list());
41831
41948
  } catch (error51) {
41832
41949
  console.warn(`[fleet-console] Durable state save failed: ${error51 instanceof Error ? error51.message : String(error51)}`);
41833
41950
  }
41834
41951
  }
41952
+ function saveDurableState(deletionTombstones) {
41953
+ durableStateStore.save({
41954
+ version: STATE_VERSION,
41955
+ theaters: theaters.list(),
41956
+ operations: operations.list(),
41957
+ groups: operations.listAllGroups(),
41958
+ deletionTombstones
41959
+ });
41960
+ }
41835
41961
  async function stopServer() {
41836
41962
  const current = server;
41837
41963
  const currentLoopback = loopbackServer;
@@ -41841,6 +41967,7 @@ function createConsoleServer(deps = {}) {
41841
41967
  lockHandle = null;
41842
41968
  activeLockFile = null;
41843
41969
  activeEndpoint = null;
41970
+ deletionCoordinator.dispose();
41844
41971
  try {
41845
41972
  await Promise.all([
41846
41973
  closeHttpServer(current),