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
@@ -14,7 +14,9 @@ const {
14
14
  allocateNativePtyControllerGeneration,
15
15
  positiveGeneration,
16
16
  } = require('./native-pty-controller-generation');
17
+ const { isTemporaryProviderSessionId } = require('./provider-session-id');
17
18
  const storageLayout = require('./storage-layout');
19
+ const { deserializeTerminalState } = require('./terminal-state-serialization');
18
20
 
19
21
  const DEFAULT_CONNECT_RETRIES = 300;
20
22
  const DEFAULT_CONNECT_RETRY_MS = 50;
@@ -463,6 +465,23 @@ class NativePtyHostClient extends EventEmitter {
463
465
  });
464
466
  }
465
467
 
468
+ async requirePreparedHostRotationResume(preparationToken, actual, primaryError) {
469
+ if (!preparationToken) return;
470
+ try {
471
+ await this.resumePreparedHostRotation(preparationToken);
472
+ } catch (resumeError) {
473
+ const recoveryError = new Error(
474
+ `Cannot confirm recovery of incompatible native PTY host (${actual}) after aborting rotation`
475
+ );
476
+ recoveryError.code = 'FARMING_NATIVE_HOST_ROTATION_RECOVERY_FAILED';
477
+ recoveryError.cause = new AggregateError(
478
+ [primaryError, resumeError].filter(Boolean),
479
+ 'Native PTY rotation failed and the prepared host could not be resumed'
480
+ );
481
+ throw recoveryError;
482
+ }
483
+ }
484
+
466
485
  async rotateMismatchedHost(hostInfo) {
467
486
  const expected = runtimeIdentityLabel(this.expectedRuntimeIdentity);
468
487
  const actual = runtimeIdentityLabel(hostInfo && hostInfo.runtimeIdentity);
@@ -513,7 +532,11 @@ class NativePtyHostClient extends EventEmitter {
513
532
  throw new Error('The native PTY host returned an invalid rotation checkpoint');
514
533
  }
515
534
  } catch (error) {
516
- await this.resumePreparedHostRotation(preparationToken).catch(() => {});
535
+ if (preparationToken) {
536
+ await this.requirePreparedHostRotationResume(preparationToken, actual, error);
537
+ } else {
538
+ await this.resumePreparedHostRotation('').catch(() => {});
539
+ }
517
540
  const recovered = await this.requestOnce('recoverSessions', {}, {
518
541
  ensureConnected: false,
519
542
  retryOnDisconnect: false,
@@ -532,6 +555,40 @@ class NativePtyHostClient extends EventEmitter {
532
555
  }
533
556
  }
534
557
 
558
+ let unresumableCodex;
559
+ try {
560
+ unresumableCodex = deserializeTerminalState(serializedTerminalState).find(entry => {
561
+ const metadata = entry.metadata || {};
562
+ const provider = String(metadata.providerSessionProvider || metadata.provider || '').trim();
563
+ return provider === 'codex'
564
+ && metadata.terminalInputReceived === true
565
+ && (
566
+ metadata.providerSessionTemporary === true
567
+ || isTemporaryProviderSessionId(metadata.providerSessionId)
568
+ );
569
+ });
570
+ } catch (error) {
571
+ await this.requirePreparedHostRotationResume(preparationToken, actual, error);
572
+ const mismatchError = new Error(
573
+ `Cannot rotate incompatible native PTY host (${actual}) with an invalid terminal checkpoint`
574
+ );
575
+ mismatchError.code = 'FARMING_NATIVE_HOST_RUNTIME_MISMATCH';
576
+ mismatchError.cause = error;
577
+ throw mismatchError;
578
+ }
579
+ if (unresumableCodex) {
580
+ const unresumableError = new Error(
581
+ `Codex session ${unresumableCodex.id} has user input but no exact resume id`
582
+ );
583
+ await this.requirePreparedHostRotationResume(preparationToken, actual, unresumableError);
584
+ const mismatchError = new Error(
585
+ `Cannot rotate incompatible native PTY host (${actual}) while Codex session ${unresumableCodex.id} has user input but no exact resume id`
586
+ );
587
+ mismatchError.code = 'FARMING_NATIVE_HOST_UNRESUMABLE_SESSION';
588
+ mismatchError.cause = unresumableError;
589
+ throw mismatchError;
590
+ }
591
+
535
592
  const socket = this.socket;
536
593
  if (socket) this.suppressedDisconnectSockets.add(socket);
537
594
  let shutdownUncertain = false;
@@ -546,7 +603,7 @@ class NativePtyHostClient extends EventEmitter {
546
603
  });
547
604
  } catch (error) {
548
605
  if (!isConnectRetryable(error)) {
549
- await this.resumePreparedHostRotation(preparationToken).catch(() => {});
606
+ await this.requirePreparedHostRotationResume(preparationToken, actual, error);
550
607
  const mismatchError = new Error(
551
608
  `Cannot rotate incompatible native PTY host (${actual}); stop the old host and restart Farming`
552
609
  );
@@ -64,12 +64,23 @@ function validatePayload(payload) {
64
64
  if (!/^[A-Za-z0-9@/._-]+$/.test(String(payload.packageName || ''))) throw new Error('Invalid npm package name');
65
65
  if (!/^[0-9A-Za-z.+-]+$/.test(String(payload.targetVersion || ''))) throw new Error('Invalid npm target version');
66
66
  if (!/^[0-9A-Za-z.+-]+$/.test(String(payload.previousVersion || ''))) throw new Error('Invalid npm previous version');
67
- for (const key of ['stateFile', 'logPath', 'cliPath', 'configDir']) {
67
+ for (const key of ['stateFile', 'logPath', 'cliPath', 'packageRoot', 'configDir']) {
68
68
  if (!path.isAbsolute(String(payload[key] || ''))) throw new Error(`Invalid npm update ${key}`);
69
69
  }
70
70
  if (payload.npmPrefix && !path.isAbsolute(String(payload.npmPrefix))) {
71
71
  throw new Error('Invalid npm update npmPrefix');
72
72
  }
73
+ if (payload.npmFallbackRegistryUrl) {
74
+ let registry;
75
+ try {
76
+ registry = new URL(String(payload.npmFallbackRegistryUrl));
77
+ } catch {
78
+ throw new Error('Invalid npm update registry');
79
+ }
80
+ if (!['http:', 'https:'].includes(registry.protocol)) {
81
+ throw new Error('Invalid npm update registry');
82
+ }
83
+ }
73
84
  return payload;
74
85
  }
75
86
 
@@ -109,15 +120,59 @@ function commandEnvironment() {
109
120
 
110
121
  async function installPackage(payload, version) {
111
122
  const packageSpec = `${payload.packageName}@${version}`;
112
- appendLog(payload.logPath, `Installing ${packageSpec}`);
123
+ return installPackageFromRegistry(payload, packageSpec);
124
+ }
125
+
126
+ function verifyInstalledVersion(payload, expectedVersion) {
127
+ const packageJsonPath = path.join(payload.packageRoot, 'package.json');
128
+ let metadata;
129
+ try {
130
+ metadata = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
131
+ } catch (error) {
132
+ throw new Error(`Installed ${payload.packageName} package metadata is unreadable: ${error.message || String(error)}`, { cause: error });
133
+ }
134
+ const actualVersion = String(metadata && metadata.version || '');
135
+ if (actualVersion !== expectedVersion) {
136
+ throw new Error(`Installed ${payload.packageName} version mismatch: expected ${expectedVersion}, found ${actualVersion || 'missing'}`);
137
+ }
138
+ }
139
+
140
+ function logSize(filePath) {
141
+ try {
142
+ return fs.statSync(filePath).size;
143
+ } catch {
144
+ return 0;
145
+ }
146
+ }
147
+
148
+ function logSince(filePath, offset) {
149
+ try {
150
+ return fs.readFileSync(filePath, 'utf8').slice(offset);
151
+ } catch {
152
+ return '';
153
+ }
154
+ }
155
+
156
+ async function installPackageFromRegistry(payload, packageSpec, registryUrl = '') {
157
+ appendLog(payload.logPath, `Installing ${packageSpec}${registryUrl ? ' from the update-status registry' : ''}`);
113
158
  const args = ['install', '--global'];
114
159
  if (payload.npmPrefix) args.push('--prefix', payload.npmPrefix);
160
+ if (registryUrl) args.push('--registry', registryUrl);
115
161
  args.push(packageSpec, '--no-audit', '--no-fund');
116
- await runCommand(payload.npmCommand || 'npm', args, {
117
- cwd: payload.configDir,
118
- env: commandEnvironment(),
119
- logPath: payload.logPath,
120
- });
162
+ const offset = logSize(payload.logPath);
163
+ try {
164
+ await runCommand(payload.npmCommand || 'npm', args, {
165
+ cwd: payload.configDir,
166
+ env: commandEnvironment(),
167
+ logPath: payload.logPath,
168
+ });
169
+ } catch (error) {
170
+ if (!registryUrl && payload.npmFallbackRegistryUrl && /(?:ETARGET|No matching version found)/.test(logSince(payload.logPath, offset))) {
171
+ appendLog(payload.logPath, `Configured npm registry has no ${packageSpec}; retrying from the update-status registry`);
172
+ return installPackageFromRegistry(payload, packageSpec, payload.npmFallbackRegistryUrl);
173
+ }
174
+ throw error;
175
+ }
121
176
  }
122
177
 
123
178
  async function startServer(payload, version = payload.targetVersion) {
@@ -134,6 +189,7 @@ async function runNpmUpdate(rawPayload) {
134
189
  try {
135
190
  writeJsonAtomic(payload.stateFile, stateFor(payload, 'installing'));
136
191
  await installPackage(payload, payload.targetVersion);
192
+ verifyInstalledVersion(payload, payload.targetVersion);
137
193
 
138
194
  writeJsonAtomic(payload.stateFile, stateFor(payload, 'restarting'));
139
195
  await stopProcess(Number(payload.serverPid));
@@ -151,6 +207,7 @@ async function runNpmUpdate(rawPayload) {
151
207
  try {
152
208
  writeJsonAtomic(payload.stateFile, stateFor(payload, 'rolling-back', { error: message }));
153
209
  await installPackage(payload, payload.previousVersion);
210
+ verifyInstalledVersion(payload, payload.previousVersion);
154
211
  await startServer(payload, payload.previousVersion);
155
212
  writeJsonAtomic(payload.stateFile, stateFor(payload, 'rolled-back', {
156
213
  version: payload.previousVersion,
@@ -196,5 +253,6 @@ module.exports = {
196
253
  runNpmUpdate,
197
254
  stopProcess,
198
255
  validatePayload,
256
+ verifyInstalledVersion,
199
257
  writeJsonAtomic,
200
258
  };
@@ -1,4 +1,5 @@
1
1
  const path = require('path');
2
+ const { chatCapabilitiesForProvider } = require('./chat-runtime');
2
3
  const {
3
4
  createProviderSessionId,
4
5
  createTemporaryProviderSessionId,
@@ -6,45 +7,73 @@ const {
6
7
  } = require('./provider-session-id');
7
8
 
8
9
  const CODEX_VALUE_OPTIONS = new Set([
9
- '-a', '-c', '-C', '-m', '-s', '--ask-for-approval', '--cd', '--config',
10
- '--config-profile', '--model', '--profile', '--sandbox',
10
+ '-a', '-c', '-C', '-m', '-p', '-s', '--ask-for-approval', '--cd', '--config',
11
+ '--config-profile', '--model', '--profile', '--sandbox', '--add-dir',
12
+ '--enable', '--disable', '--remote', '--remote-auth-token-env',
13
+ '--local-provider', '-i', '--image',
11
14
  ]);
15
+ const OPENCODE_VALUE_OPTIONS = new Set([
16
+ '-m', '-s', '--agent', '--hostname', '--log-level', '--mdns-domain',
17
+ '--model', '--port', '--prompt', '--replay-limit', '--session',
18
+ ]);
19
+ const CODEX_SUBCOMMANDS = new Set([
20
+ 'exec', 'e', 'review', 'login', 'logout', 'mcp', 'plugin', 'mcp-server',
21
+ 'app-server', 'remote-control', 'app', 'completion', 'update', 'doctor',
22
+ 'sandbox', 'debug', 'apply', 'a', 'resume', 'archive', 'delete', 'unarchive',
23
+ 'fork', 'cloud', 'exec-server', 'features', 'help',
24
+ ]);
25
+ const OPENCODE_SUBCOMMANDS = new Set([
26
+ 'completion', 'acp', 'mcp', 'attach', 'run', 'debug', 'providers', 'auth',
27
+ 'agent', 'upgrade', 'uninstall', 'serve', 'web', 'models', 'stats', 'export',
28
+ 'import', 'github', 'pr', 'session', 'plugin', 'plug', 'db',
29
+ ]);
30
+ const CODEX_SESSION_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
12
31
 
13
32
  function optionTakesValue(option, valueOptions) {
14
33
  return Boolean(option && !option.includes('=') && valueOptions.has(option));
15
34
  }
16
35
 
17
- function firstCodexSubcommand(args) {
36
+ function scanPositionals(args, valueOptions, options = {}) {
37
+ const positionals = [];
18
38
  for (let index = 0; index < args.length; index += 1) {
19
- const arg = args[index];
39
+ const arg = String(args[index] || '');
20
40
  if (!arg) continue;
41
+ if (arg === '--') {
42
+ for (let positionalIndex = index + 1; positionalIndex < args.length; positionalIndex += 1) {
43
+ const value = String(args[positionalIndex] || '');
44
+ if (value) positionals.push({ value, index: positionalIndex, afterDelimiter: true });
45
+ }
46
+ break;
47
+ }
21
48
  if (arg.startsWith('-')) {
22
- if (optionTakesValue(arg, CODEX_VALUE_OPTIONS)) index += 1;
49
+ if (
50
+ options.multiValueOptions?.has(arg)
51
+ && !arg.includes('=')
52
+ ) {
53
+ while (index + 1 < args.length && !String(args[index + 1] || '').startsWith('-')) index += 1;
54
+ continue;
55
+ }
56
+ if (optionTakesValue(arg, valueOptions)) index += 1;
23
57
  continue;
24
58
  }
25
- return { value: arg, index };
59
+ positionals.push({ value: arg, index, afterDelimiter: false });
26
60
  }
27
- return null;
61
+ return positionals;
28
62
  }
29
63
 
30
64
  function codexSessionIdAfterSubcommand(args, subcommandIndex) {
31
- const positional = [];
32
- for (let index = subcommandIndex + 1; index < args.length; index += 1) {
33
- const arg = args[index];
34
- if (!arg) continue;
35
- if (arg.startsWith('-')) {
36
- if (optionTakesValue(arg, CODEX_VALUE_OPTIONS)) index += 1;
37
- continue;
38
- }
39
- positional.push(arg);
40
- }
41
- const sessionId = positional.at(-1) || '';
42
- return isSafeProviderSessionId(sessionId) ? sessionId : '';
65
+ const sessionId = scanPositionals(
66
+ args.slice(subcommandIndex + 1),
67
+ CODEX_VALUE_OPTIONS,
68
+ { multiValueOptions: new Set(['-i', '--image']) },
69
+ )[0]?.value || '';
70
+ return CODEX_SESSION_ID_RE.test(sessionId) ? sessionId.toLowerCase() : '';
43
71
  }
44
72
 
45
73
  function argValue(args, names) {
46
74
  for (let index = 0; index < args.length; index += 1) {
47
75
  const arg = args[index];
76
+ if (arg === '--') break;
48
77
  for (const name of names) {
49
78
  if (arg === name) return args[index + 1] || '';
50
79
  if (arg.startsWith(`${name}=`)) return arg.slice(name.length + 1);
@@ -54,13 +83,26 @@ function argValue(args, names) {
54
83
  }
55
84
 
56
85
  function hasArg(args, names) {
57
- return args.some(arg => names.includes(arg) || names.some(name => arg.startsWith(`${name}=`)));
86
+ for (const arg of args) {
87
+ if (arg === '--') break;
88
+ if (names.includes(arg) || names.some(name => arg.startsWith(`${name}=`))) return true;
89
+ }
90
+ return false;
58
91
  }
59
92
 
60
- function codexSessionPlan(rawArgs) {
61
- const subcommand = firstCodexSubcommand(rawArgs);
93
+ function codexSessionPlan(rawArgs, _launchArgs) {
94
+ const positional = scanPositionals(rawArgs, CODEX_VALUE_OPTIONS, {
95
+ multiValueOptions: new Set(['-i', '--image']),
96
+ });
97
+ const subcommand = positional[0]
98
+ && positional[0].afterDelimiter !== true
99
+ && CODEX_SUBCOMMANDS.has(positional[0].value)
100
+ ? positional[0]
101
+ : null;
62
102
  if (subcommand?.value === 'resume') {
63
- const id = codexSessionIdAfterSubcommand(rawArgs, subcommand.index);
103
+ const id = hasArg(rawArgs.slice(subcommand.index + 1), ['--last'])
104
+ ? ''
105
+ : codexSessionIdAfterSubcommand(rawArgs, subcommand.index);
64
106
  if (id) return { id, temporary: false, source: 'resume' };
65
107
  }
66
108
  if (subcommand?.value === 'fork') {
@@ -68,11 +110,22 @@ function codexSessionPlan(rawArgs) {
68
110
  id: createTemporaryProviderSessionId(),
69
111
  temporary: true,
70
112
  source: 'codex-fork-temporary',
71
- forkedFromProviderSessionId: codexSessionIdAfterSubcommand(rawArgs, subcommand.index),
113
+ forkedFromProviderSessionId: hasArg(rawArgs.slice(subcommand.index + 1), ['--last'])
114
+ ? ''
115
+ : codexSessionIdAfterSubcommand(rawArgs, subcommand.index),
116
+ };
117
+ }
118
+ if (subcommand) return null;
119
+ if (hasArg(rawArgs, ['--remote'])) {
120
+ return {
121
+ error: 'A fresh Codex --remote Terminal cannot be correlated with a local resumable session id; resume an explicit remote session id instead',
72
122
  };
73
123
  }
74
- if (subcommand?.value) return null;
75
- return { id: createTemporaryProviderSessionId(), temporary: true, source: 'codex-temporary' };
124
+ return {
125
+ id: createTemporaryProviderSessionId(),
126
+ temporary: true,
127
+ source: 'codex-temporary',
128
+ };
76
129
  }
77
130
 
78
131
  function explicitSessionPlan(provider, rawArgs, launchArgs) {
@@ -102,17 +155,35 @@ function explicitSessionPlan(provider, rawArgs, launchArgs) {
102
155
  };
103
156
  }
104
157
 
105
- function openCodeSessionPlan(rawArgs) {
158
+ function openCodeSessionPlan(rawArgs, launchArgs) {
106
159
  const id = argValue(rawArgs, ['--session', '-s']);
107
- if (!id || !isSafeProviderSessionId(id)) return null;
108
- return hasArg(rawArgs, ['--fork'])
109
- ? {
110
- id: createTemporaryProviderSessionId(),
111
- temporary: true,
112
- source: 'opencode-fork-temporary',
113
- forkedFromProviderSessionId: id,
114
- }
115
- : { id, temporary: false, source: 'resume' };
160
+ if (id && isSafeProviderSessionId(id)) {
161
+ return hasArg(rawArgs, ['--fork'])
162
+ ? {
163
+ id: createTemporaryProviderSessionId(),
164
+ temporary: true,
165
+ source: 'opencode-fork-temporary',
166
+ forkedFromProviderSessionId: id,
167
+ }
168
+ : { id, temporary: false, source: 'resume' };
169
+ }
170
+ if (hasArg(rawArgs, ['--session', '-s', '--continue', '-c', '--fork'])) return null;
171
+ const positional = scanPositionals(rawArgs, OPENCODE_VALUE_OPTIONS, {
172
+ multiValueOptions: new Set(['--cors']),
173
+ });
174
+ if (
175
+ positional[0]
176
+ && positional[0].afterDelimiter !== true
177
+ && OPENCODE_SUBCOMMANDS.has(positional[0].value)
178
+ ) return null;
179
+ return {
180
+ id: '',
181
+ precreate: true,
182
+ temporary: false,
183
+ source: 'opencode-precreate',
184
+ identityWorkspace: positional[0]?.value || '',
185
+ resumeInsertIndex: launchArgs.length,
186
+ };
116
187
  }
117
188
 
118
189
  function codexAcpEnvironment(options = {}) {
@@ -145,11 +216,12 @@ const PROVIDER_ADAPTERS = Object.freeze([
145
216
  interruptInput: '\x1b',
146
217
  freshAcpSessionSources: ['codex-temporary'],
147
218
  commands: ['codex'],
148
- supportedRuntimes: ['terminal', 'acp', 'json', 'app-server'],
219
+ supportedRuntimes: ['terminal', 'acp', 'json'],
149
220
  planSession: codexSessionPlan,
221
+ terminalResumeArgs: (args, sessionId) => ['resume', sessionId, ...args],
150
222
  acp: { packageName: '@agentclientprotocol/codex-acp', version: '1.1.4' },
151
223
  prepareAcpEnvironment: codexAcpEnvironment,
152
- capabilities: { runtimeSwitch: true, terminalProfile: true, goals: true },
224
+ capabilities: { runtimeSwitch: true, terminalProfile: true, goals: false },
153
225
  },
154
226
  {
155
227
  id: 'claude',
@@ -174,6 +246,16 @@ const PROVIDER_ADAPTERS = Object.freeze([
174
246
  commands: ['opencode'],
175
247
  supportedRuntimes: ['terminal', 'acp', 'json'],
176
248
  planSession: openCodeSessionPlan,
249
+ terminalResumeArgs: (args, sessionId) => {
250
+ const delimiterIndex = args.indexOf('--');
251
+ const insertIndex = delimiterIndex >= 0 ? delimiterIndex : args.length;
252
+ return [
253
+ ...args.slice(0, insertIndex),
254
+ '--session',
255
+ sessionId,
256
+ ...args.slice(insertIndex),
257
+ ];
258
+ },
177
259
  acp: {
178
260
  version: 'native',
179
261
  launch: options => ({
@@ -225,6 +307,9 @@ function providerCapabilities(provider) {
225
307
  runtimeSwitch: adapter?.capabilities?.runtimeSwitch === true,
226
308
  terminalProfile: adapter?.capabilities?.terminalProfile === true,
227
309
  goals: adapter?.capabilities?.goals === true,
310
+ ...(adapter
311
+ ? chatCapabilitiesForProvider(provider)
312
+ : { chatRuntime: '', supportsChat: false, supportsSteer: false }),
228
313
  };
229
314
  }
230
315