farming-code 2.2.14 → 2.2.17

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 (136) hide show
  1. package/README.md +3 -3
  2. package/README.zh_cn.md +3 -3
  3. package/THIRD_PARTY_NOTICES.md +17 -1
  4. package/backend/acp/packaged-codex-acp.js +13 -0
  5. package/backend/acp-runtime.js +413 -24
  6. package/backend/agent-manager.js +572 -557
  7. package/backend/agent-provider-session.js +15 -1
  8. package/backend/agent-runtime-binding.js +9 -55
  9. package/backend/chat-runtime.js +28 -0
  10. package/backend/codex-rollout-follower.js +83 -0
  11. package/backend/codex-session-archive.js +12 -1
  12. package/backend/codex-session-history.js +94 -0
  13. package/backend/codex-transcript.js +2 -1
  14. package/backend/config-manager.js +25 -17
  15. package/backend/control-api.js +3 -1
  16. package/backend/farming-app-cli.js +21 -0
  17. package/backend/farming-session-store.js +2 -1
  18. package/backend/native-pty-host-client.js +59 -2
  19. package/backend/npm-update-helper.js +65 -7
  20. package/backend/provider-adapters.js +123 -38
  21. package/backend/provider-session-service.js +138 -34
  22. package/backend/server.js +94 -105
  23. package/backend/storage-layout.js +5 -16
  24. package/backend/update-service.js +94 -5
  25. package/backend/usage-history-client.js +305 -0
  26. package/backend/usage-history-scanner.generated.js +2250 -0
  27. package/backend/usage-history-scanner.ts +2718 -0
  28. package/backend/usage-history-smoke.js +64 -0
  29. package/backend/usage-history-worker.js +34 -0
  30. package/backend/usage-monitor.js +188 -588
  31. package/backend/vendor/usage-parsers/CC_STATISTICS_LICENSE +21 -0
  32. package/backend/vendor/usage-parsers/CODEXBAR_LICENSE +21 -0
  33. package/backend/vendor/usage-parsers/VENDOR.json +45 -0
  34. package/dist/acp/LICENSE.codex-acp +190 -0
  35. package/dist/acp/codex-acp-1.1.4.js +30718 -0
  36. package/dist/assets/App-6yWdwzj8.js +210 -0
  37. package/dist/assets/{FileEditorMarkdownPreview-UlVd0O4D.js → FileEditorMarkdownPreview-R0jwwLME.js} +4 -4
  38. package/dist/assets/FileEditorPane-DNy3LAqS.js +2 -0
  39. package/dist/assets/{IconGlyphs-AU22gPDY.js → IconGlyphs-BsUfqpKN.js} +1 -1
  40. package/dist/assets/ProjectFilesSection-BhDEUVHA.js +12 -0
  41. package/dist/assets/{ReviewPage-CeDKDyZh.js → ReviewPage-ChtByBe3.js} +1 -1
  42. package/dist/assets/{abnfDiagram-VRR7QNED-BgawzDQT.js → abnfDiagram-VRR7QNED-nnglrXFi.js} +1 -1
  43. package/dist/assets/{architectureDiagram-ZJ3FMSHR-DUShwPg0.js → architectureDiagram-ZJ3FMSHR-BEXMaFfY.js} +1 -1
  44. package/dist/assets/{blockDiagram-677ZJIJ3-CeGZviXR.js → blockDiagram-677ZJIJ3-1mCezSBk.js} +1 -1
  45. package/dist/assets/{c4Diagram-LMCZKHZV-CLhYht6S.js → c4Diagram-LMCZKHZV-DhTiI6Uv.js} +1 -1
  46. package/dist/assets/channel-BVWNatUB.js +1 -0
  47. package/dist/assets/{chunk-32BRIVSS-LFYpT-Cu.js → chunk-32BRIVSS-CCdsKd73.js} +1 -1
  48. package/dist/assets/{chunk-52WLFC77-BH6jDw9J.js → chunk-52WLFC77-Gncjk4LF.js} +1 -1
  49. package/dist/assets/{chunk-C7G6YPKG-B_mIUlb7.js → chunk-C7G6YPKG-CiDVUP82.js} +1 -1
  50. package/dist/assets/{chunk-EX3LRPZG-C63hVuAJ.js → chunk-EX3LRPZG-z6gXP2_N.js} +1 -1
  51. package/dist/assets/{chunk-FWX5IMBZ-CboX-WF_.js → chunk-FWX5IMBZ-DMZBccQa.js} +2 -2
  52. package/dist/assets/{chunk-HOUHSVGY-BOgSd6Un.js → chunk-HOUHSVGY-D8tIeoPk.js} +1 -1
  53. package/dist/assets/{chunk-ICXQ74PX-C0K-pgZt.js → chunk-ICXQ74PX-b6oQ2Q17.js} +1 -1
  54. package/dist/assets/{chunk-MOJQB5TN-CVMfwN_7.js → chunk-MOJQB5TN-DAVH_p2a.js} +1 -1
  55. package/dist/assets/{chunk-OGEWGWER-DLT3pxyw.js → chunk-OGEWGWER-DxoiMCI-.js} +1 -1
  56. package/dist/assets/{chunk-PUDLZKDR-Cd079yIQ.js → chunk-PUDLZKDR-NsOWh7HM.js} +1 -1
  57. package/dist/assets/{chunk-Q4XR5HBZ-D0jHa6SO.js → chunk-Q4XR5HBZ-Di4H7L21.js} +1 -1
  58. package/dist/assets/{chunk-V7JOEXUC-CVDtR75H.js → chunk-V7JOEXUC-DttpZg9Y.js} +1 -1
  59. package/dist/assets/{chunk-VAUOI2AC-B-W2_9Ot.js → chunk-VAUOI2AC-UHNr_yhX.js} +1 -1
  60. package/dist/assets/{chunk-VR4S4FIN-FD3kWh32.js → chunk-VR4S4FIN-D-Fmtd4Z.js} +1 -1
  61. package/dist/assets/{chunk-WYO6CB5R-C2bGaS5r.js → chunk-WYO6CB5R-CUxMtfK2.js} +4 -4
  62. package/dist/assets/{chunk-ZGVPDNZ5-ymgts0zo.js → chunk-ZGVPDNZ5-CoV2QEl0.js} +1 -1
  63. package/dist/assets/classDiagram-OUVF2IWQ-Db0ib5iE.js +1 -0
  64. package/dist/assets/classDiagram-v2-EOCWNBFH-Bkf69XF9.js +1 -0
  65. package/dist/assets/code-dark-5esj0bRu.css +1 -0
  66. package/dist/assets/{cynefinDiagram-TSTJHNR4-CwpobXIl.js → cynefinDiagram-TSTJHNR4-0xPI92Lg.js} +1 -1
  67. package/dist/assets/{dagre-VKFMJZFB-Dkhlfjna.js → dagre-VKFMJZFB-DfuMG4gU.js} +1 -1
  68. package/dist/assets/{diagram-FQU43EPY-hA8dicp6.js → diagram-FQU43EPY-CT00bLZ6.js} +1 -1
  69. package/dist/assets/{diagram-G47NLZAW-BTY6JTzO.js → diagram-G47NLZAW-Xbh7zXUx.js} +1 -1
  70. package/dist/assets/{diagram-NH7WQ7WH-xkaHHX9M.js → diagram-NH7WQ7WH-SRDVPfn7.js} +1 -1
  71. package/dist/assets/{diagram-OA4YK3LP-gk_fg9UU.js → diagram-OA4YK3LP-CQRli4Np.js} +1 -1
  72. package/dist/assets/{diagram-WEI45ONY-BvC8xd6T.js → diagram-WEI45ONY-DMf17WQi.js} +1 -1
  73. package/dist/assets/{ebnfDiagram-CCIWWBDH-CR7RaNat.js → ebnfDiagram-CCIWWBDH-DhpFkgJ5.js} +1 -1
  74. package/dist/assets/{erDiagram-Q63AITRT-_M-JFPbj.js → erDiagram-Q63AITRT-1IwxWmJo.js} +1 -1
  75. package/dist/assets/file-icons-DpbrD5ZP.js +1 -0
  76. package/dist/assets/flowDiagram-23GEKE2U-DYFo2xmB.js +1 -0
  77. package/dist/assets/{ganttDiagram-NO4QXBWP-Bl8P1yf_.js → ganttDiagram-NO4QXBWP-BNCdfyr1.js} +1 -1
  78. package/dist/assets/{gitGraphDiagram-IHSO6WYX-DoHa5ysz.js → gitGraphDiagram-IHSO6WYX-pbyWZuiX.js} +1 -1
  79. package/dist/assets/{index-BA-iH2Kg.js → index-C8ZFKye1.js} +2 -2
  80. package/dist/assets/{infoDiagram-FWYZ7A6U-DSFcSuUs.js → infoDiagram-FWYZ7A6U-BcTOgDbe.js} +1 -1
  81. package/dist/assets/{ishikawaDiagram-FXEZZL3T-DdkqullI.js → ishikawaDiagram-FXEZZL3T-DCdvGKrn.js} +1 -1
  82. package/dist/assets/{journeyDiagram-5HDEW3XC-B4hRnQJr.js → journeyDiagram-5HDEW3XC-BZ-1q_IT.js} +1 -1
  83. package/dist/assets/{kanban-definition-HUTT4EX6-tm2FZB2E.js → kanban-definition-HUTT4EX6-BIgejCjj.js} +1 -1
  84. package/dist/assets/{line-CmsjK4kn.js → line-CfptFMmX.js} +1 -1
  85. package/dist/assets/main-DSO3i_Gs.css +1 -0
  86. package/dist/assets/{mermaid.core-C7UT_EEL.js → mermaid.core-gSpX30pV.js} +3 -3
  87. package/dist/assets/{mindmap-definition-LN4V7U3C-DplfjGms.js → mindmap-definition-LN4V7U3C-BKBaI4LW.js} +1 -1
  88. package/dist/assets/{pegDiagram-2B236MQR-lu3SdkQs.js → pegDiagram-2B236MQR-sBvDY3VX.js} +1 -1
  89. package/dist/assets/{pieDiagram-ENE6RG2P-3fHbmDf9.js → pieDiagram-ENE6RG2P-DWlWcu08.js} +1 -1
  90. package/dist/assets/{quadrantDiagram-ABIIQ3AL-a5qoWz-X.js → quadrantDiagram-ABIIQ3AL-DZutUd3J.js} +1 -1
  91. package/dist/assets/{railroadDiagram-RFXS5EU6-D2TOuXW9.js → railroadDiagram-RFXS5EU6-DQb3lXGM.js} +1 -1
  92. package/dist/assets/{requirementDiagram-TGXJPOKE-LQBWpUvd.js → requirementDiagram-TGXJPOKE-DqFGXu2e.js} +1 -1
  93. package/dist/assets/{sankeyDiagram-HTMAVEWB-B1ohYoyK.js → sankeyDiagram-HTMAVEWB-d7ECahnO.js} +1 -1
  94. package/dist/assets/{sequenceDiagram-DBY2YBRQ-C3vM-hMq.js → sequenceDiagram-DBY2YBRQ-DdsbKqct.js} +1 -1
  95. package/dist/assets/{stateDiagram-2N3HPSRC-BvFhOryI.js → stateDiagram-2N3HPSRC-P6iSrgwR.js} +1 -1
  96. package/dist/assets/stateDiagram-v2-6OUMAXLB-DIYSf_qh.js +1 -0
  97. package/dist/assets/{swimlanes-5IMT3BWC-DKF_b-2s.js → swimlanes-5IMT3BWC-BGnZJx1M.js} +1 -1
  98. package/dist/assets/swimlanesDiagram-G3AALYLV-BIkrlKrZ.js +8 -0
  99. package/dist/assets/{timeline-definition-FHXFAJF6-vAs11ux_.js → timeline-definition-FHXFAJF6-rA-r3aUR.js} +1 -1
  100. package/dist/assets/{vennDiagram-L72KCM5P-BybTX_9B.js → vennDiagram-L72KCM5P-DiVKUjA9.js} +1 -1
  101. package/dist/assets/{wardleyDiagram-EHGQE667-CicUmMBR.js → wardleyDiagram-EHGQE667-Cfxh7UOn.js} +1 -1
  102. package/dist/assets/workspace-editor-model-D0Im34S5.js +1 -0
  103. package/dist/assets/{workspace-editor-monaco-BICJESG0.js → workspace-editor-monaco-BIpqcWf1.js} +1 -1
  104. package/dist/assets/workspace-editor-monaco-DLlws5Ig.js +1 -0
  105. package/dist/assets/{workspace-view-state-DvYG_9PH.js → workspace-view-state-BpPINu84.js} +1 -1
  106. package/dist/assets/{xychartDiagram-FW5EYKEG-BhWDyCX-.js → xychartDiagram-FW5EYKEG--hmryQeL.js} +1 -1
  107. package/dist/crt-mermaid-renderer.js +313 -313
  108. package/dist/index.html +1 -1
  109. package/frontend/session-bridge.js +24 -2
  110. package/frontend/skins/crt/app.js +203 -85
  111. package/frontend/skins/crt/index.html +2 -11
  112. package/frontend/skins/crt/styles/effects.css +2 -34
  113. package/package.json +30 -12
  114. package/shared/backend-connection-status.d.ts +25 -0
  115. package/shared/backend-connection-status.js +40 -0
  116. package/shared/browser-protocol.js +3 -3
  117. package/backend/app-server-api.js +0 -917
  118. package/backend/codex-app-server-home.js +0 -39
  119. package/backend/codex-app-server-runtime.js +0 -901
  120. package/dist/assets/App-Bumi772v.js +0 -206
  121. package/dist/assets/FileEditorPane-Dxx4rKOg.js +0 -2
  122. package/dist/assets/ProjectFilesSection-DE7Nb9Jk.js +0 -12
  123. package/dist/assets/channel-Uw90AO0b.js +0 -1
  124. package/dist/assets/classDiagram-OUVF2IWQ-DKW_dtku.js +0 -1
  125. package/dist/assets/classDiagram-v2-EOCWNBFH-C8Fi7-yv.js +0 -1
  126. package/dist/assets/code-dark-DPiuyeSp.css +0 -1
  127. package/dist/assets/code-mobile-3C2Rr-CN.css +0 -1
  128. package/dist/assets/file-icons-Bw2qd5iT.js +0 -1
  129. package/dist/assets/flowDiagram-23GEKE2U-DAiS5Y7G.js +0 -1
  130. package/dist/assets/main-Bqtb5kuz.css +0 -1
  131. package/dist/assets/stateDiagram-v2-6OUMAXLB-WMrz4PNo.js +0 -1
  132. package/dist/assets/swimlanesDiagram-G3AALYLV-BGswC9oC.js +0 -8
  133. package/dist/assets/workspace-editor-model-BQol4qbA.js +0 -1
  134. package/dist/assets/workspace-editor-monaco-DRJ7IaXk.js +0 -1
  135. package/scripts/sync-ghostty-vendor.js +0 -66
  136. /package/dist/assets/{core-D0LFJkDt.js → core-B9MwRjDE.js} +0 -0
@@ -16,9 +16,13 @@ function emptyPlan(args) {
16
16
  return {
17
17
  provider: '',
18
18
  id: '',
19
+ precreate: false,
19
20
  temporary: false,
20
21
  source: '',
21
22
  forkedFromProviderSessionId: '',
23
+ identityWorkspace: '',
24
+ resumeInsertIndex: null,
25
+ error: '',
22
26
  args,
23
27
  };
24
28
  }
@@ -44,13 +48,23 @@ function buildAgentProviderSessionPlan({ command, program, args, source } = {})
44
48
  }
45
49
 
46
50
  const plan = adapter.planSession(rawParts.slice(1), launchArgs);
47
- if (!plan?.id) return emptyPlan(launchArgs);
51
+ if (plan?.error) {
52
+ return {
53
+ ...emptyPlan(launchArgs),
54
+ provider,
55
+ error: String(plan.error),
56
+ };
57
+ }
58
+ if (!plan || (!plan.id && plan.precreate !== true)) return emptyPlan(launchArgs);
48
59
  return {
49
60
  provider,
50
61
  id: plan.id,
62
+ precreate: plan.precreate === true,
51
63
  temporary: plan.temporary === true,
52
64
  source: plan.source || '',
53
65
  forkedFromProviderSessionId: plan.forkedFromProviderSessionId || '',
66
+ identityWorkspace: plan.identityWorkspace || '',
67
+ resumeInsertIndex: Number.isInteger(plan.resumeInsertIndex) ? plan.resumeInsertIndex : null,
54
68
  args: Array.isArray(plan.args) ? plan.args : launchArgs,
55
69
  };
56
70
  }
@@ -1,4 +1,4 @@
1
- const RUNTIME_KINDS = new Set(['terminal', 'acp', 'json', 'app-server']);
1
+ const RUNTIME_KINDS = new Set(['terminal', 'acp', 'json']);
2
2
 
3
3
  function terminalBinding() {
4
4
  return { kind: 'terminal' };
@@ -10,6 +10,7 @@ function acpBinding(source = {}) {
10
10
  state: source.state || source.acpState || '',
11
11
  error: source.error || source.acpError || '',
12
12
  stopReason: source.stopReason || source.acpStopReason || '',
13
+ supportsSteer: source.supportsSteer === true,
13
14
  pendingPermission: source.pendingPermission || source.acpPendingPermission || null,
14
15
  pendingPermissions: source.pendingPermissions || source.acpPendingPermissions || [],
15
16
  pendingElicitation: source.pendingElicitation || source.acpPendingElicitation || null,
@@ -30,38 +31,24 @@ function jsonBinding(source = {}) {
30
31
  };
31
32
  }
32
33
 
33
- function appServerBinding(source = {}) {
34
- return {
35
- kind: 'app-server',
36
- state: source.state || source.codexAppServerState || '',
37
- endpoint: source.endpoint || source.codexAppServerEndpoint || '',
38
- threadId: source.threadId || source.codexAppServerThreadId || '',
39
- turnId: source.turnId || source.codexAppServerTurnId || '',
40
- error: source.error || source.codexAppServerError || '',
41
- pendingRequestId: source.pendingRequestId || source.codexAppServerPendingRequestId || '',
42
- pendingRequestMethod: source.pendingRequestMethod || source.codexAppServerPendingRequestMethod || '',
43
- pendingRequest: source.pendingRequest || source.codexAppServerPendingRequest || null,
44
- notice: source.notice || source.codexAppServerNotice || null,
45
- goal: source.goal || source.codexAppServerGoal || null,
46
- observerDeferred: source.observerDeferred === true || source.codexCliObserverDeferred === true,
47
- homePath: source.homePath || source.codexAppServerHomePath || '',
48
- transcriptUpdatedAt: source.transcriptUpdatedAt || source.codexAppServerTranscriptUpdatedAt || '',
49
- };
50
- }
51
-
52
34
  function runtimeKind(agent) {
53
35
  if (RUNTIME_KINDS.has(agent?.runtimeBinding?.kind)) return agent.runtimeBinding.kind;
36
+ // App Server was an experimental Codex runtime. Persisted records migrate to
37
+ // ACP because codex-acp uses the same Codex thread id as its session id.
38
+ if (agent?.runtimeBinding?.kind === 'app-server' || agent?.codexRuntimeMode === 'app-server') return 'acp';
54
39
  if (agent?.agentRuntimeMode === 'acp') return 'acp';
55
40
  if (agent?.agentRuntimeMode === 'json') return 'json';
56
- return agent?.codexRuntimeMode === 'app-server' ? 'app-server' : 'terminal';
41
+ return 'terminal';
57
42
  }
58
43
 
59
44
  function bindingFromLegacy(agent) {
60
45
  if (RUNTIME_KINDS.has(agent?.runtimeBinding?.kind)) return agent.runtimeBinding;
46
+ if (agent?.runtimeBinding?.kind === 'app-server' || agent?.codexRuntimeMode === 'app-server') {
47
+ return acpBinding({ state: 'connecting' });
48
+ }
61
49
  switch (runtimeKind(agent)) {
62
50
  case 'acp': return acpBinding(agent);
63
51
  case 'json': return jsonBinding(agent);
64
- case 'app-server': return appServerBinding(agent);
65
52
  default: return terminalBinding();
66
53
  }
67
54
  }
@@ -70,7 +57,6 @@ function runtimeBindingFor(kind, source = {}) {
70
57
  switch (kind) {
71
58
  case 'acp': return acpBinding(source);
72
59
  case 'json': return jsonBinding(source);
73
- case 'app-server': return appServerBinding(source);
74
60
  default: return terminalBinding();
75
61
  }
76
62
  }
@@ -133,22 +119,6 @@ function publicRuntimeBinding(agent) {
133
119
  transcriptUpdatedAt: binding.transcriptUpdatedAt,
134
120
  };
135
121
  }
136
- if (binding.kind === 'app-server') {
137
- return {
138
- kind: 'app-server',
139
- state: binding.state,
140
- endpoint: binding.endpoint,
141
- threadId: binding.threadId,
142
- turnId: binding.turnId,
143
- error: binding.error,
144
- pendingRequestId: binding.pendingRequestId,
145
- pendingRequestMethod: binding.pendingRequestMethod,
146
- pendingRequest: binding.pendingRequest,
147
- notice: binding.notice,
148
- goal: binding.goal,
149
- observerDeferred: binding.observerDeferred,
150
- };
151
- }
152
122
  return { ...binding };
153
123
  }
154
124
 
@@ -162,7 +132,6 @@ function runtimeState(agent) {
162
132
  function legacyRuntimeMetadata(agent) {
163
133
  const binding = bindingFromLegacy(agent);
164
134
  const metadata = {
165
- codexRuntimeMode: binding.kind === 'app-server' ? 'app-server' : 'cli',
166
135
  agentRuntimeMode: ['acp', 'json'].includes(binding.kind) ? binding.kind : 'terminal',
167
136
  };
168
137
  if (binding.kind === 'acp') {
@@ -188,21 +157,6 @@ function legacyRuntimeMetadata(agent) {
188
157
  jsonCliTranscriptUpdatedAt: binding.transcriptUpdatedAt,
189
158
  };
190
159
  }
191
- if (binding.kind === 'app-server') {
192
- return {
193
- ...metadata,
194
- codexAppServerHomePath: binding.homePath,
195
- codexAppServerState: binding.state,
196
- codexAppServerEndpoint: binding.endpoint,
197
- codexAppServerThreadId: binding.threadId,
198
- codexAppServerTurnId: binding.turnId,
199
- codexAppServerError: binding.error,
200
- codexAppServerPendingRequestId: binding.pendingRequestId,
201
- codexAppServerPendingRequestMethod: binding.pendingRequestMethod,
202
- codexAppServerPendingRequest: binding.pendingRequest,
203
- codexCliObserverDeferred: binding.observerDeferred,
204
- };
205
- }
206
160
  return metadata;
207
161
  }
208
162
 
@@ -0,0 +1,28 @@
1
+ // Chat is a product-level intent. ACP is the single structured Chat runtime for
2
+ // every supported coding agent, including Codex.
3
+ const CHAT_MODE = 'chat';
4
+
5
+ function chatRuntimeForProvider() {
6
+ return 'acp';
7
+ }
8
+
9
+ function isChatMode(mode) {
10
+ return mode === CHAT_MODE;
11
+ }
12
+
13
+ function chatCapabilitiesForProvider(provider) {
14
+ const runtime = chatRuntimeForProvider(provider);
15
+ return {
16
+ chatRuntime: runtime,
17
+ supportsChat: true,
18
+ // ACP does not currently define a turn-versioned steer operation.
19
+ supportsSteer: false,
20
+ };
21
+ }
22
+
23
+ module.exports = {
24
+ CHAT_MODE,
25
+ chatRuntimeForProvider,
26
+ chatCapabilitiesForProvider,
27
+ isChatMode,
28
+ };
@@ -59,6 +59,88 @@ function findCodexRolloutFile(sessionId, options = {}) {
59
59
  return '';
60
60
  }
61
61
 
62
+ function recentSessionDirectories(root, now = new Date()) {
63
+ const directories = [];
64
+ for (const offsetDays of [-1, 0, 1]) {
65
+ const date = new Date(now.getTime() + offsetDays * 24 * 60 * 60 * 1000);
66
+ directories.push(
67
+ path.join(
68
+ root,
69
+ String(date.getFullYear()),
70
+ String(date.getMonth() + 1).padStart(2, '0'),
71
+ String(date.getDate()).padStart(2, '0'),
72
+ ),
73
+ path.join(
74
+ root,
75
+ String(date.getUTCFullYear()),
76
+ String(date.getUTCMonth() + 1).padStart(2, '0'),
77
+ String(date.getUTCDate()).padStart(2, '0'),
78
+ ),
79
+ );
80
+ }
81
+ return [...new Set(directories)];
82
+ }
83
+
84
+ async function findCodexRolloutFileAsync(sessionId, options = {}) {
85
+ const normalizedSessionId = String(sessionId || '').trim();
86
+ if (!normalizedSessionId) return '';
87
+ const codexHome = normalizePathValue(options.codexHome || path.join(os.homedir(), '.codex'));
88
+ const maxEntries = Number.isFinite(options.maxEntries)
89
+ ? Math.max(1, Math.floor(options.maxEntries))
90
+ : 100_000;
91
+ const deadline = Number.isFinite(options.deadline)
92
+ ? Number(options.deadline)
93
+ : Date.now() + 5_000;
94
+ const sessionsRoot = path.join(codexHome, 'sessions');
95
+ const archivedRoot = path.join(codexHome, 'archived_sessions');
96
+ const preferredDirectories = [
97
+ ...recentSessionDirectories(sessionsRoot),
98
+ ...(options.recentOnly === true ? [] : [archivedRoot]),
99
+ ];
100
+ const visited = new Set();
101
+ let inspectedEntries = 0;
102
+
103
+ async function inspectDirectory(directory, descend) {
104
+ if (Date.now() > deadline || inspectedEntries >= maxEntries || visited.has(directory)) return '';
105
+ visited.add(directory);
106
+ let entries;
107
+ try {
108
+ entries = await fsp.readdir(directory, { withFileTypes: true });
109
+ } catch {
110
+ return '';
111
+ }
112
+ const subdirectories = [];
113
+ for (const entry of entries) {
114
+ inspectedEntries += 1;
115
+ if (Date.now() > deadline || inspectedEntries > maxEntries) return '';
116
+ const fullPath = path.join(directory, entry.name);
117
+ if (entry.isFile() && entry.name.endsWith('.jsonl') && sessionIdFromFilePath(fullPath) === normalizedSessionId) {
118
+ return fullPath;
119
+ }
120
+ if (descend && entry.isDirectory()) subdirectories.push(fullPath);
121
+ }
122
+ return subdirectories;
123
+ }
124
+
125
+ for (const directory of preferredDirectories) {
126
+ const found = await inspectDirectory(directory, false);
127
+ if (typeof found === 'string' && found) return found;
128
+ }
129
+ if (options.recentOnly === true) return '';
130
+
131
+ const stack = [sessionsRoot, archivedRoot];
132
+ while (stack.length > 0 && Date.now() <= deadline && inspectedEntries < maxEntries) {
133
+ const directory = stack.pop();
134
+ const result = await inspectDirectory(directory, true);
135
+ if (typeof result === 'string') {
136
+ if (result) return result;
137
+ continue;
138
+ }
139
+ stack.push(...result);
140
+ }
141
+ return '';
142
+ }
143
+
62
144
  function normalizePreviewText(value, maxLength = 400) {
63
145
  return String(value || '')
64
146
  .replace(/\r\n/g, '\n')
@@ -239,5 +321,6 @@ class CodexRolloutFollower extends EventEmitter {
239
321
  module.exports = {
240
322
  CodexRolloutFollower,
241
323
  findCodexRolloutFile,
324
+ findCodexRolloutFileAsync,
242
325
  formatCodexRolloutEvent,
243
326
  };
@@ -1,4 +1,5 @@
1
1
  const { execFile } = require('child_process');
2
+ const fs = require('fs');
2
3
  const os = require('os');
3
4
  const { promisify } = require('util');
4
5
  const { resolveCompatibleCodexExecutable } = require('./executable-discovery');
@@ -9,6 +10,13 @@ async function runCodexSessionArchiveCommand(action, sessionId, session = {}, op
9
10
  const resolveExecutable = options.resolveCompatibleCodexExecutable || resolveCompatibleCodexExecutable;
10
11
  const runExecFile = options.execFileAsync || execFileAsync;
11
12
  const processEnv = options.processEnv || process.env;
13
+ const directoryExists = options.directoryExists || ((directory) => {
14
+ try {
15
+ return fs.statSync(directory).isDirectory();
16
+ } catch {
17
+ return false;
18
+ }
19
+ });
12
20
  const codexResolution = resolveExecutable(session.cliVersion || '', processEnv.PATH || '');
13
21
  if (!codexResolution.compatible) {
14
22
  return {
@@ -18,8 +26,11 @@ async function runCodexSessionArchiveCommand(action, sessionId, session = {}, op
18
26
  }
19
27
 
20
28
  try {
29
+ const sessionCwd = session.cwd || session.workspace || '';
21
30
  await runExecFile(codexResolution.path || 'codex', [action, sessionId], {
22
- cwd: session.cwd || session.workspace || os.homedir(),
31
+ cwd: sessionCwd && directoryExists(sessionCwd)
32
+ ? sessionCwd
33
+ : (processEnv.HOME || os.homedir()),
23
34
  env: session.providerHomePath
24
35
  ? { ...processEnv, CODEX_HOME: session.providerHomePath }
25
36
  : processEnv,
@@ -412,6 +412,98 @@ async function readSessionMetadata(filePath, maxLines = 80) {
412
412
  return metadata.id ? metadata : null;
413
413
  }
414
414
 
415
+ function codexSessionDateKeys(startedAt, windowMs) {
416
+ const center = Number(startedAt);
417
+ const radius = Math.max(0, Number(windowMs) || 0);
418
+ if (!Number.isFinite(center) || center <= 0) return [];
419
+ const cursor = new Date(center - radius);
420
+ cursor.setHours(0, 0, 0, 0);
421
+ const lastDay = new Date(center + radius);
422
+ lastDay.setHours(0, 0, 0, 0);
423
+ const keys = [];
424
+ while (cursor <= lastDay) {
425
+ keys.push([
426
+ cursor.getFullYear(),
427
+ String(cursor.getMonth() + 1).padStart(2, '0'),
428
+ String(cursor.getDate()).padStart(2, '0'),
429
+ ].join('-'));
430
+ cursor.setDate(cursor.getDate() + 1);
431
+ }
432
+ return keys;
433
+ }
434
+
435
+ async function readCodexSessionIdentity(filePath) {
436
+ let handle;
437
+ try {
438
+ handle = await fs.promises.open(filePath, 'r');
439
+ const chunk = Buffer.allocUnsafe(64 * 1024);
440
+ const chunks = [];
441
+ let bytesReadTotal = 0;
442
+ let lineEnd = -1;
443
+ while (bytesReadTotal < 1024 * 1024 && lineEnd < 0) {
444
+ const { bytesRead } = await handle.read(chunk, 0, chunk.length, bytesReadTotal);
445
+ if (bytesRead <= 0) break;
446
+ const current = Buffer.from(chunk.subarray(0, bytesRead));
447
+ chunks.push(current);
448
+ const newlineIndex = current.indexOf(0x0a);
449
+ if (newlineIndex >= 0) lineEnd = bytesReadTotal + newlineIndex;
450
+ bytesReadTotal += bytesRead;
451
+ }
452
+ const header = Buffer.concat(chunks);
453
+ const firstLine = header.subarray(0, lineEnd >= 0 ? lineEnd : header.length).toString('utf8').replace(/\r$/, '');
454
+ const fileSessionId = sessionIdFromFilePath(filePath);
455
+ const event = JSON.parse(firstLine);
456
+ if (event?.type !== 'session_meta') return null;
457
+ const payload = event.payload && typeof event.payload === 'object' ? event.payload : {};
458
+ const id = typeof payload.id === 'string' ? payload.id.trim() : '';
459
+ if (!id || (fileSessionId && id !== fileSessionId)) return null;
460
+ const cwd = typeof payload.cwd === 'string' ? normalizePathValue(payload.cwd) : '';
461
+ return {
462
+ id,
463
+ createdAt: typeof event.timestamp === 'string' ? event.timestamp : '',
464
+ cwd,
465
+ workspace: cwd,
466
+ };
467
+ } catch {
468
+ return null;
469
+ } finally {
470
+ await handle?.close().catch(() => {});
471
+ }
472
+ }
473
+
474
+ async function listCodexSessionIdentities(options = {}) {
475
+ const codexHome = options.codexHome || path.join(os.homedir(), '.codex');
476
+ const startedAt = Number(options.startedAt);
477
+ const windowMs = Math.max(0, Number(options.windowMs) || 0);
478
+ if (!Number.isFinite(startedAt) || startedAt <= 0) return [];
479
+
480
+ const files = [];
481
+ for (const dateKey of codexSessionDateKeys(startedAt, windowMs)) {
482
+ const [year, month, day] = dateKey.split('-');
483
+ const directory = path.join(codexHome, 'sessions', year, month, day);
484
+ let entries;
485
+ try {
486
+ entries = fs.readdirSync(directory, { withFileTypes: true });
487
+ } catch {
488
+ continue;
489
+ }
490
+ for (const entry of entries) {
491
+ if (!entry.isFile() || !entry.name.endsWith('.jsonl')) continue;
492
+ files.push(path.join(directory, entry.name));
493
+ }
494
+ }
495
+
496
+ const identities = [];
497
+ const batchSize = 32;
498
+ for (let offset = 0; offset < files.length; offset += batchSize) {
499
+ const batch = await Promise.all(
500
+ files.slice(offset, offset + batchSize).map(readCodexSessionIdentity)
501
+ );
502
+ identities.push(...batch.filter(Boolean));
503
+ }
504
+ return identities;
505
+ }
506
+
415
507
  function getGlobalState(codexHome) {
416
508
  const state = readJsonFile(path.join(codexHome, '.codex-global-state.json'), {});
417
509
  const atom = state['electron-persisted-atom-state'] || {};
@@ -540,8 +632,10 @@ async function listCodexSessions(options = {}) {
540
632
  }
541
633
 
542
634
  module.exports = {
635
+ codexSessionDateKeys,
543
636
  hasTemporaryWorkspaceReference,
544
637
  isTemporaryWorkspace,
638
+ listCodexSessionIdentities,
545
639
  listCodexSessions,
546
640
  readSessionIndex,
547
641
  readSessionMetadata,
@@ -12,7 +12,8 @@ const {
12
12
  } = require('./codex-transcript-sanitizer');
13
13
 
14
14
  // @deprecated `readCodexTranscript` below is the legacy JSONL file reader.
15
- // App Server uses the generic event-to-turn projection exported here instead.
15
+ // The generic event-to-turn projection remains for JSON compatibility feeds;
16
+ // live structured Chat is reduced from ACP session updates instead.
16
17
  const DEFAULT_MAX_READ_BYTES = 32 * 1024 * 1024;
17
18
  const DEFAULT_MAX_TURNS = 240;
18
19
  const USER_MESSAGE_BEGIN = '## My request for Codex:';
@@ -67,6 +67,7 @@ const MAX_SEARCH_TIMEOUT_MS = 180000;
67
67
  const DEFAULT_CRT_TERMINAL_FONT_SIZE = 15;
68
68
  const MIN_CRT_TERMINAL_FONT_SIZE = 10;
69
69
  const MAX_CRT_TERMINAL_FONT_SIZE = 20;
70
+ const MAX_INSTANCE_NAME_LENGTH = 80;
70
71
 
71
72
  const PERSISTED_SETTING_KEYS = new Set([
72
73
  'workspace',
@@ -75,6 +76,7 @@ const PERSISTED_SETTING_KEYS = new Set([
75
76
  'projectWorkspaces',
76
77
  'pinnedProjectWorkspaces',
77
78
  'projectNames',
79
+ 'instanceName',
78
80
  'theme',
79
81
  'appearance',
80
82
  'language',
@@ -88,7 +90,6 @@ const PERSISTED_SETTING_KEYS = new Set([
88
90
  'agentHomes',
89
91
  'updateUrl',
90
92
  'searchTimeoutMs',
91
- 'codexRuntimeMode',
92
93
  'codexApprovalMode',
93
94
  'codexModel',
94
95
  'codexReasoningEffort',
@@ -183,6 +184,19 @@ class ConfigManager {
183
184
  return this.farmingDir;
184
185
  }
185
186
 
187
+ normalizeInstanceName(value) {
188
+ if (typeof value !== 'string') return '';
189
+ return value
190
+ .replace(/[\x00-\x1f\x7f]/g, ' ')
191
+ .replace(/\s+/g, ' ')
192
+ .trim()
193
+ .slice(0, MAX_INSTANCE_NAME_LENGTH);
194
+ }
195
+
196
+ getInstanceName() {
197
+ return this.settings?.instanceName || this.normalizeInstanceName(os.hostname()) || 'Farming';
198
+ }
199
+
186
200
  normalizeWorkspaceHistory(history) {
187
201
  const entries = Array.isArray(history) ? history : [];
188
202
  const result = [];
@@ -290,8 +304,9 @@ class ConfigManager {
290
304
  projectWorkspaces: [],
291
305
  pinnedProjectWorkspaces: [],
292
306
  projectNames: {},
307
+ instanceName: '',
293
308
  theme: 'terminal',
294
- appearance: 'light',
309
+ appearance: 'system',
295
310
  language: 'en',
296
311
  heartbeatInterval: 1000,
297
312
  dangerouslySkipAgentPermissionsByDefault: false,
@@ -306,7 +321,6 @@ class ConfigManager {
306
321
  agentHomes: cloneAgentHomes(DEFAULT_AGENT_HOMES),
307
322
  updateUrl: DEFAULT_UPDATE_URL,
308
323
  searchTimeoutMs: DEFAULT_SEARCH_TIMEOUT_MS,
309
- codexRuntimeMode: 'cli',
310
324
  codexApprovalMode: 'approve',
311
325
  codexModel: 'gpt-5.5',
312
326
  codexReasoningEffort: 'xhigh',
@@ -326,8 +340,9 @@ class ConfigManager {
326
340
  projectWorkspaces: [],
327
341
  pinnedProjectWorkspaces: [],
328
342
  projectNames: {},
343
+ instanceName: '',
329
344
  theme: 'terminal',
330
- appearance: 'light',
345
+ appearance: 'system',
331
346
  language: 'en',
332
347
  heartbeatInterval: 1000,
333
348
  dangerouslySkipAgentPermissionsByDefault: false,
@@ -342,7 +357,6 @@ class ConfigManager {
342
357
  agentHomes: cloneAgentHomes(DEFAULT_AGENT_HOMES),
343
358
  updateUrl: DEFAULT_UPDATE_URL,
344
359
  searchTimeoutMs: DEFAULT_SEARCH_TIMEOUT_MS,
345
- codexRuntimeMode: 'cli',
346
360
  codexApprovalMode: 'approve',
347
361
  codexModel: 'gpt-5.5',
348
362
  codexReasoningEffort: 'xhigh',
@@ -376,13 +390,14 @@ class ConfigManager {
376
390
  this.settings.projectWorkspaces = this.normalizeProjectWorkspaces(this.settings.projectWorkspaces);
377
391
  this.settings.pinnedProjectWorkspaces = this.normalizeProjectWorkspaces(this.settings.pinnedProjectWorkspaces);
378
392
  this.settings.projectNames = this.normalizeProjectNames(this.settings.projectNames);
393
+ this.settings.instanceName = this.normalizeInstanceName(this.settings.instanceName);
379
394
  this.settings.agentHomes = this.normalizeAgentHomes(this.settings.agentHomes);
380
395
  if (this.settings.updateUrl === LEGACY_DEFAULT_UPDATE_URL || this.settings.updateUrl === API_DEFAULT_UPDATE_URL) {
381
396
  this.settings.updateUrl = DEFAULT_UPDATE_URL;
382
397
  }
383
398
  this.settings.updateUrl = this.normalizeUpdateUrl(this.settings.updateUrl);
384
399
  this.settings.searchTimeoutMs = this.normalizeSearchTimeoutMs(this.settings.searchTimeoutMs);
385
- this.settings.codexRuntimeMode = this.normalizeCodexRuntimeMode(this.settings.codexRuntimeMode);
400
+ delete this.settings.codexRuntimeMode;
386
401
  const legacyMainPageSessionKeys = this.normalizeMainPageSessionKeys(this.settings.mainPageSessionKeys);
387
402
  delete this.settings.mainPageSessionKeys;
388
403
  this.sessionStore.init({ legacyMainPageSessionKeys });
@@ -483,7 +498,7 @@ class ConfigManager {
483
498
  }
484
499
 
485
500
  normalizeAppearance(appearance) {
486
- return ['light', 'dark'].includes(appearance) ? appearance : 'light';
501
+ return ['system', 'light', 'dark'].includes(appearance) ? appearance : 'system';
487
502
  }
488
503
 
489
504
  normalizeLanguage(language) {
@@ -517,10 +532,6 @@ class ConfigManager {
517
532
  return ['ask', 'approve', 'full', 'custom'].includes(mode) ? mode : 'approve';
518
533
  }
519
534
 
520
- normalizeCodexRuntimeMode(mode) {
521
- return mode === 'app-server' ? 'app-server' : 'cli';
522
- }
523
-
524
535
  normalizeCodexModelPreset(preset) {
525
536
  if (preset === 'config') return preset;
526
537
  if (typeof preset !== 'string') return 'gpt-5.5:xhigh';
@@ -676,11 +687,6 @@ class ConfigManager {
676
687
  return this.getAgentLaunchProfile('codex').approvalMode;
677
688
  }
678
689
 
679
- getCodexRuntimeMode() {
680
- if (!this.settings) return 'cli';
681
- return this.normalizeCodexRuntimeMode(this.settings.codexRuntimeMode);
682
- }
683
-
684
690
  getCodexModelPreset() {
685
691
  if (!this.settings) return 'gpt-5.5:xhigh';
686
692
  return this.getAgentLaunchProfile('codex').modelPreset;
@@ -739,6 +745,7 @@ class ConfigManager {
739
745
  getSettings() {
740
746
  return {
741
747
  ...this.settings,
748
+ instanceName: this.getInstanceName(),
742
749
  workspace: this.farmingDir,
743
750
  mainPageSessionKeys: this.getMainPageSessionKeys(),
744
751
  taskHistory: this.getTaskHistory(),
@@ -819,10 +826,11 @@ class ConfigManager {
819
826
  this.settings.projectWorkspaces = this.normalizeProjectWorkspaces(this.settings.projectWorkspaces);
820
827
  this.settings.pinnedProjectWorkspaces = this.normalizeProjectWorkspaces(this.settings.pinnedProjectWorkspaces);
821
828
  this.settings.projectNames = this.normalizeProjectNames(this.settings.projectNames);
829
+ this.settings.instanceName = this.normalizeInstanceName(this.settings.instanceName);
822
830
  this.settings.agentHomes = this.normalizeAgentHomes(this.settings.agentHomes);
823
831
  this.settings.updateUrl = this.normalizeUpdateUrl(this.settings.updateUrl);
824
832
  this.settings.searchTimeoutMs = this.normalizeSearchTimeoutMs(this.settings.searchTimeoutMs);
825
- this.settings.codexRuntimeMode = this.normalizeCodexRuntimeMode(this.settings.codexRuntimeMode);
833
+ delete this.settings.codexRuntimeMode;
826
834
  delete this.settings.mainPageSessionKeys;
827
835
  delete this.settings.taskHistory;
828
836
  if (incomingMainPageSessionKeys !== undefined) {
@@ -219,9 +219,11 @@ function createControlRouter(agentManager, options = {}) {
219
219
  parentAgentId: typeof body.parentAgentId === 'string' ? body.parentAgentId : '',
220
220
  task,
221
221
  source: 'control-cli',
222
- agentRuntimeMode: ['json', 'acp'].includes(body.agentRuntimeMode) ? body.agentRuntimeMode : 'terminal',
222
+ agentRuntimeMode: ['json', 'acp', 'chat'].includes(body.agentRuntimeMode) ? body.agentRuntimeMode : 'terminal',
223
223
  acpHistoryMode: body.acpHistoryMode === 'resume' ? 'resume' : 'load',
224
224
  providerSessionTitle: typeof body.providerSessionTitle === 'string' ? body.providerSessionTitle : '',
225
+ ...(Array.isArray(body.additionalDirectories) ? { additionalDirectories: body.additionalDirectories } : {}),
226
+ ...(Array.isArray(body.mcpServers) ? { mcpServers: body.mcpServers } : {}),
225
227
  dangerouslySkipPermissions: body.dangerouslySkipPermissions === true,
226
228
  });
227
229
  });
@@ -7,11 +7,13 @@ const path = require('path');
7
7
  const { URLSearchParams } = require('url');
8
8
  const { execFileSync, spawn } = require('child_process');
9
9
  const { run: runControlCli } = require('./farming-cli');
10
+ const { PACKAGED_CODEX_ACP_ARG, runPackagedCodexAcp } = require('./acp/packaged-codex-acp');
10
11
  const storageLayout = require('./storage-layout');
11
12
 
12
13
  const SERVER_MODE_ARG = '--farming-server';
13
14
  const SERVER_MODE_ENV = 'FARMING_RUN_SERVER';
14
15
  const NATIVE_PTY_HOST_ARG = '--native-pty-host';
16
+ const USAGE_HISTORY_SMOKE_ARG = '--farming-usage-history-smoke';
15
17
  const DEFAULT_PORT = '6694';
16
18
  const DEFAULT_BASE_PATH = '/farming';
17
19
  const DEFAULT_SERVER_START_TIMEOUT_MS = 30_000;
@@ -85,6 +87,13 @@ function appendNodeOption(existing, option) {
85
87
  function buildServerEnv(overrides = {}, baseEnv = process.env) {
86
88
  const env = { ...baseEnv, ...overrides };
87
89
  delete env.PKG_EXECPATH;
90
+ if (!process.pkg && !env.FARMING_MANAGED_PACKAGE_ROOT) {
91
+ try {
92
+ env.FARMING_MANAGED_PACKAGE_ROOT = fs.realpathSync(path.join(__dirname, '..'));
93
+ } catch {
94
+ // npm update verification will remain unavailable when the package root cannot be proven.
95
+ }
96
+ }
88
97
  const cliBinDir = process.pkg ? path.dirname(process.execPath) : path.join(__dirname, '..', 'bin');
89
98
  env.PORT = String(env.FARMING_PORT || env.PORT || DEFAULT_PORT);
90
99
  env.FARMING_BASE_PATH = env.FARMING_BASE_PATH || DEFAULT_BASE_PATH;
@@ -774,6 +783,18 @@ async function run(argv = process.argv.slice(2)) {
774
783
  return 0;
775
784
  }
776
785
 
786
+ if (argv[0] === PACKAGED_CODEX_ACP_ARG) {
787
+ runPackagedCodexAcp();
788
+ return 0;
789
+ }
790
+
791
+ if (argv[0] === USAGE_HISTORY_SMOKE_ARG) {
792
+ const { runUsageHistorySmoke } = require('./usage-history-smoke');
793
+ const result = await runUsageHistorySmoke();
794
+ console.log(JSON.stringify(result));
795
+ return 0;
796
+ }
797
+
777
798
  if (argv[0] === NATIVE_PTY_HOST_ARG || process.env.FARMING_RUN_NATIVE_PTY_HOST === '1') {
778
799
  runNativePtyHostInCurrentProcess();
779
800
  return 0;
@@ -25,8 +25,9 @@ function safeSessionFileName(id) {
25
25
  function atomicWriteJson(file, value) {
26
26
  fs.mkdirSync(path.dirname(file), { recursive: true });
27
27
  const tmpFile = `${file}.${process.pid}.${Date.now()}.tmp`;
28
- fs.writeFileSync(tmpFile, JSON.stringify(value, null, 2));
28
+ fs.writeFileSync(tmpFile, JSON.stringify(value, null, 2), { mode: 0o600 });
29
29
  fs.renameSync(tmpFile, file);
30
+ fs.chmodSync(file, 0o600);
30
31
  }
31
32
 
32
33
  function parseProviderSessionKey(key) {