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
@@ -1,17 +1,21 @@
1
1
  const EventEmitter = require('events');
2
+ const crypto = require('crypto');
2
3
  const fs = require('fs');
3
4
  const path = require('path');
4
- const { spawn } = require('child_process');
5
+ const { execFile, spawn } = require('child_process');
5
6
  const { Readable, Writable } = require('stream');
6
7
  const { createRequire } = require('module');
8
+ const { promisify } = require('util');
7
9
  const packageJson = require('../package.json');
8
10
  const { AcpCheckpointStore } = require('./acp-checkpoint-store');
9
11
  const { AcpSessionState } = require('./acp-session-state');
10
12
  const { readCodexHistoryImageData } = require('./codex-transcript');
11
13
  const { AcpClientFileSystem, AcpClientTerminalManager } = require('./acp/client-services');
14
+ const { PACKAGED_CODEX_ACP_ARG } = require('./acp/packaged-codex-acp');
12
15
  const { permissionSecurityWarnings } = require('./acp/permission-security');
13
16
  const { rejectPatch } = require('./acp/patch-decisions');
14
17
  const { getProviderAdapter, listProviderAdapters } = require('./provider-adapters');
18
+ const { isSafeProviderSessionId } = require('./provider-session-id');
15
19
 
16
20
  const ADAPTER_VERSIONS = Object.freeze(Object.fromEntries(
17
21
  listProviderAdapters().filter(adapter => adapter.acp).map(adapter => [adapter.id, adapter.acp.version]),
@@ -24,7 +28,22 @@ const DEFAULT_CANCEL_TIMEOUT_MS = 15_000;
24
28
  const DEFAULT_HISTORY_REPLAY_MIN_WAIT_MS = 350;
25
29
  const DEFAULT_HISTORY_REPLAY_QUIET_MS = 150;
26
30
  const DEFAULT_HISTORY_REPLAY_MAX_WAIT_MS = 5_000;
31
+ const IDENTITY_ADAPTER_GRACEFUL_EXIT_MS = 3_000;
32
+ const IDENTITY_ADAPTER_TERMINATE_MS = 1_000;
33
+ const IDENTITY_ADAPTER_KILL_MS = 1_000;
27
34
  const CODEX_SET_SESSION_MODEL_METHOD = 'session/set_model';
35
+ const CODEX_STEER_METHOD = '_codex/session/steer';
36
+ const CODEX_ACP_PACKAGE = '@agentclientprotocol/codex-acp';
37
+ const CODEX_ACP_VERSION = '1.1.4';
38
+ const CODEX_ACP_SHA256 = '39cbae01e336c2ca185d624358e03280d1f6fef6d73bbe42dd9eb77e2b1efb32';
39
+ const CODEX_ACP_VENDOR_ENTRY = path.join(
40
+ __dirname,
41
+ '..',
42
+ 'dist',
43
+ 'acp',
44
+ `codex-acp-${CODEX_ACP_VERSION}.js`,
45
+ );
46
+ const execFileAsync = promisify(execFile);
28
47
 
29
48
  let sdkPromise;
30
49
  const runtimeRequire = createRequire(__filename);
@@ -33,7 +52,24 @@ function loadAcpSdk() {
33
52
  return sdkPromise;
34
53
  }
35
54
 
55
+ function fileSha256(filePath) {
56
+ return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
57
+ }
58
+
59
+ function verifiedCodexAcpEntry(entry) {
60
+ const actualSha256 = fileSha256(entry);
61
+ if (actualSha256 !== CODEX_ACP_SHA256) {
62
+ throw new Error(
63
+ `Codex ACP runtime failed integrity verification: expected ${CODEX_ACP_SHA256}, found ${actualSha256}`,
64
+ );
65
+ }
66
+ return entry;
67
+ }
68
+
36
69
  function adapterEntry(packageName) {
70
+ if (packageName === CODEX_ACP_PACKAGE && fs.existsSync(CODEX_ACP_VENDOR_ENTRY)) {
71
+ return verifiedCodexAcpEntry(CODEX_ACP_VENDOR_ENTRY);
72
+ }
37
73
  let sdkDirectory;
38
74
  try {
39
75
  sdkDirectory = path.dirname(runtimeRequire.resolve('@agentclientprotocol/sdk'));
@@ -42,11 +78,15 @@ function adapterEntry(packageName) {
42
78
  }
43
79
  const entry = path.resolve(sdkDirectory, '..', '..', packageName.split('/').pop(), 'dist', 'index.js');
44
80
  if (!fs.existsSync(entry)) throw new Error(`ACP adapter is not installed: ${packageName}`);
81
+ if (packageName === CODEX_ACP_PACKAGE) return verifiedCodexAcpEntry(entry);
45
82
  return entry;
46
83
  }
47
84
 
48
- function nodeAdapterLaunch(entry, env = process.env) {
85
+ function nodeAdapterLaunch(entry, env = process.env, packagedArg = '') {
49
86
  const runtimeEnv = env && typeof env === 'object' ? env : process.env;
87
+ if (process.pkg && packagedArg) {
88
+ return { command: process.execPath, args: [packagedArg] };
89
+ }
50
90
  const nodeBin = runtimeEnv.FARMING_NODE_BIN || process.execPath;
51
91
  const ldPath = runtimeEnv.FARMING_NODE_LD || '';
52
92
  const libraryPath = runtimeEnv.FARMING_NODE_LIBRARY_PATH || '';
@@ -69,6 +109,7 @@ function resolveAcpLaunch(provider, options = {}) {
69
109
  const launch = nodeAdapterLaunch(
70
110
  adapterEntry(adapter.acp.packageName),
71
111
  options.runtimeEnv || process.env,
112
+ adapter.acp.packageName === CODEX_ACP_PACKAGE ? PACKAGED_CODEX_ACP_ARG : '',
72
113
  );
73
114
  return { ...launch, version: adapter.acp.version };
74
115
  }
@@ -202,6 +243,134 @@ function clone(value) {
202
243
  return value == null ? value : JSON.parse(JSON.stringify(value));
203
244
  }
204
245
 
246
+ function acpSessionRequestOptions(options = {}, cwd = process.cwd()) {
247
+ const root = path.resolve(cwd || process.cwd());
248
+ const additionalDirectories = Array.isArray(options.additionalDirectories)
249
+ ? [...new Set(options.additionalDirectories
250
+ .filter(directory => typeof directory === 'string' && directory.trim())
251
+ .map(directory => path.resolve(root, directory)))]
252
+ : [];
253
+ const mcpServers = Array.isArray(options.mcpServers)
254
+ ? clone(options.mcpServers.filter(server => server && typeof server === 'object' && !Array.isArray(server)))
255
+ : [];
256
+ return { cwd: root, additionalDirectories, mcpServers };
257
+ }
258
+
259
+ function promptContentForCapabilities(content, capabilities = {}) {
260
+ const promptCapabilities = capabilities?.promptCapabilities || {};
261
+ return (Array.isArray(content) ? content : []).flatMap(block => {
262
+ if (!block || typeof block !== 'object') return [];
263
+ if (!['image', 'audio'].includes(block.type)) return [clone(block)];
264
+ if (promptCapabilities[block.type] === true) return [clone(block)];
265
+
266
+ const label = block.type === 'audio' ? 'audio' : 'image';
267
+ const attachmentPath = typeof block.path === 'string' && block.path.trim()
268
+ ? path.resolve(block.path)
269
+ : '';
270
+ const name = attachmentPath ? path.basename(attachmentPath) : `attached ${label}`;
271
+ const detail = attachmentPath
272
+ ? `${label[0].toUpperCase()}${label.slice(1)} path: ${attachmentPath}`
273
+ : `[The ACP Agent does not accept native ${label} content]`;
274
+ return [{ type: 'text', text: `Attached ${label}: ${name}\n\n${detail}` }];
275
+ });
276
+ }
277
+
278
+ function supportsCodexSteer(capabilities = {}) {
279
+ const capability = capabilities?._meta?.codex?.steer;
280
+ return capability?.method === CODEX_STEER_METHOD
281
+ && Number.isFinite(Number(capability.version))
282
+ && Number(capability.version) >= 1;
283
+ }
284
+
285
+ function isCodexSteerUnavailableError(error) {
286
+ const text = [
287
+ error?.message,
288
+ error?.data?.details,
289
+ error?.cause?.message,
290
+ error?.cause?.data?.details,
291
+ ].filter(Boolean).join(' ').toLowerCase();
292
+ return text.includes('no active codex turn to steer')
293
+ || text.includes('no active turn to steer')
294
+ || /expected active turn id .* but found/.test(text)
295
+ || text.includes('cannot steer a review turn')
296
+ || text.includes('cannot steer a compact turn');
297
+ }
298
+
299
+ function childHasExited(child) {
300
+ return !child
301
+ || (child.exitCode !== null && child.exitCode !== undefined)
302
+ || (child.signalCode !== null && child.signalCode !== undefined);
303
+ }
304
+
305
+ function processGroupHasExited(binding) {
306
+ if (!binding?.ownsProcessGroup || !binding.child?.pid || process.platform === 'win32') {
307
+ return childHasExited(binding?.child);
308
+ }
309
+ try {
310
+ process.kill(-binding.child.pid, 0);
311
+ return false;
312
+ } catch (error) {
313
+ return error?.code === 'ESRCH';
314
+ }
315
+ }
316
+
317
+ async function waitForProcessTreeExit(binding, timeoutMs) {
318
+ const deadline = Date.now() + timeoutMs;
319
+ while (Date.now() <= deadline) {
320
+ if (processGroupHasExited(binding)) return true;
321
+ await new Promise(resolve => setTimeout(resolve, 25));
322
+ }
323
+ return processGroupHasExited(binding);
324
+ }
325
+
326
+ function signalProcessTree(binding, signal) {
327
+ if (!binding?.child || processGroupHasExited(binding)) return;
328
+ if (binding.ownsProcessGroup && binding.child.pid && process.platform !== 'win32') {
329
+ try {
330
+ process.kill(-binding.child.pid, signal);
331
+ return;
332
+ } catch (error) {
333
+ if (error?.code === 'ESRCH') return;
334
+ }
335
+ }
336
+ binding.child.kill(signal);
337
+ }
338
+
339
+ async function deleteProviderSessionIdentity(options = {}) {
340
+ const provider = String(options.provider || '').trim().toLowerCase();
341
+ const sessionId = String(options.sessionId || '').trim();
342
+ if (!isSafeProviderSessionId(sessionId)) {
343
+ throw new Error('Provider session rollback requires a safe exact session id');
344
+ }
345
+ let args;
346
+ if (provider === 'codex') args = ['delete', '--force', sessionId];
347
+ else if (provider === 'opencode') args = ['session', 'delete', sessionId];
348
+ else throw new Error(`${provider || 'Provider'} does not support identity rollback`);
349
+ await execFileAsync(options.executable || getProviderAdapter(provider)?.executable, args, {
350
+ cwd: options.cwd,
351
+ env: options.env,
352
+ timeout: 15_000,
353
+ maxBuffer: 1024 * 1024,
354
+ });
355
+ }
356
+
357
+ function attachProviderSessionIdentity(error, identity, rollbackError) {
358
+ const target = error instanceof Error ? error : new Error(String(error || 'Provider session identity failed'));
359
+ Object.defineProperty(target, 'providerSessionIdentity', {
360
+ value: identity,
361
+ enumerable: false,
362
+ configurable: true,
363
+ });
364
+ if (rollbackError) {
365
+ Object.defineProperty(target, 'providerSessionRollbackError', {
366
+ value: rollbackError,
367
+ enumerable: false,
368
+ configurable: true,
369
+ });
370
+ }
371
+ return target;
372
+ }
373
+
205
374
  class AcpRuntime extends EventEmitter {
206
375
  constructor(options = {}) {
207
376
  super();
@@ -216,6 +385,7 @@ class AcpRuntime extends EventEmitter {
216
385
  this.historyReplayMinWaitMs = options.historyReplayMinWaitMs ?? DEFAULT_HISTORY_REPLAY_MIN_WAIT_MS;
217
386
  this.historyReplayQuietMs = options.historyReplayQuietMs ?? DEFAULT_HISTORY_REPLAY_QUIET_MS;
218
387
  this.historyReplayMaxWaitMs = options.historyReplayMaxWaitMs ?? DEFAULT_HISTORY_REPLAY_MAX_WAIT_MS;
388
+ this.deleteProviderSessionIdentity = options.deleteProviderSessionIdentity || deleteProviderSessionIdentity;
219
389
  this.checkpointStore = options.checkpointStore
220
390
  || (options.configDir ? new AcpCheckpointStore(options.configDir, options.checkpointOptions) : null);
221
391
  this.bindings = new Map();
@@ -231,19 +401,24 @@ class AcpRuntime extends EventEmitter {
231
401
  if (this.bindings.has(agentId)) throw new Error('ACP Agent is already registered');
232
402
  const provider = String(options.provider || '').trim().toLowerCase();
233
403
  const launch = this.resolveLaunch(provider, options);
404
+ const cwd = path.resolve(options.cwd || process.cwd());
405
+ const sessionRequestOptions = acpSessionRequestOptions(options, cwd);
234
406
  const binding = {
235
407
  agentId,
236
408
  provider,
237
409
  providerHomeId: String(options.providerHomeId || 'default'),
238
- cwd: path.resolve(options.cwd || process.cwd()),
410
+ cwd,
411
+ sessionRequestOptions,
239
412
  env: (getProviderAdapter(provider)?.prepareAcpEnvironment || (value => value.env || process.env))(options),
240
413
  launch,
241
414
  restartOptions: { ...options, agentId, provider },
242
415
  approvalMode: options.approvalMode || 'approve',
416
+ ownsProcessGroup: options.identityOnly === true && process.platform !== 'win32',
243
417
  child: null,
244
418
  connection: null,
245
419
  initializeResponse: null,
246
420
  sessionId: '',
421
+ untrustedSessionId: '',
247
422
  state: 'connecting',
248
423
  error: '',
249
424
  stopReason: '',
@@ -257,6 +432,8 @@ class AcpRuntime extends EventEmitter {
257
432
  subagentStates: new Map(),
258
433
  interactionOrigins: new Map(),
259
434
  promptActive: false,
435
+ supportsSteer: false,
436
+ steerTail: null,
260
437
  historyReplayActive: false,
261
438
  sessionState: null,
262
439
  authTerminal: null,
@@ -273,6 +450,7 @@ class AcpRuntime extends EventEmitter {
273
450
  cwd: binding.cwd,
274
451
  env: binding.env,
275
452
  stdio: ['pipe', 'pipe', 'pipe'],
453
+ detached: binding.ownsProcessGroup,
276
454
  });
277
455
  binding.child = child;
278
456
  child.stderr.on('data', chunk => {
@@ -308,12 +486,14 @@ class AcpRuntime extends EventEmitter {
308
486
  if (binding.initializeResponse.protocolVersion !== sdk.PROTOCOL_VERSION) {
309
487
  throw new Error(`ACP protocol version mismatch: Agent selected ${binding.initializeResponse.protocolVersion}, Farming supports ${sdk.PROTOCOL_VERSION}`);
310
488
  }
489
+ binding.supportsSteer = binding.provider === 'codex'
490
+ && supportsCodexSteer(binding.initializeResponse.agentCapabilities);
311
491
 
312
492
  const requestedSessionId = String(options.sessionId || '').trim();
313
493
  const revisionBase = Number.isFinite(Number(options.revisionBase))
314
494
  ? Math.max(0, Math.floor(Number(options.revisionBase)))
315
495
  : 0;
316
- const sessionRequest = { sessionId: requestedSessionId, cwd: binding.cwd, mcpServers: [] };
496
+ const sessionRequest = { sessionId: requestedSessionId, ...binding.sessionRequestOptions };
317
497
  let sessionResponse;
318
498
  let historyMode = 'new';
319
499
  if (requestedSessionId) {
@@ -413,12 +593,16 @@ class AcpRuntime extends EventEmitter {
413
593
  }
414
594
  } else {
415
595
  sessionResponse = await withTimeout(
416
- connection.newSession({ cwd: binding.cwd, mcpServers: [] }),
596
+ connection.newSession(binding.sessionRequestOptions),
417
597
  this.sessionSetupTimeoutMs,
418
598
  'ACP session/new'
419
599
  );
420
- binding.sessionId = String(sessionResponse.sessionId || '');
421
- if (!binding.sessionId) throw new Error('ACP session/new did not return a session id');
600
+ const returnedSessionId = String(sessionResponse.sessionId || '').trim();
601
+ if (!isSafeProviderSessionId(returnedSessionId)) {
602
+ binding.untrustedSessionId = returnedSessionId;
603
+ throw new Error('ACP session/new returned an invalid resumable session id');
604
+ }
605
+ binding.sessionId = returnedSessionId;
422
606
  binding.sessionState = new AcpSessionState({ provider, sessionId: binding.sessionId, cwd: binding.cwd, maxUpdates: this.maxUpdates });
423
607
  }
424
608
  binding.modes = sessionResponse?.modes || null;
@@ -451,11 +635,109 @@ class AcpRuntime extends EventEmitter {
451
635
  } catch (error) {
452
636
  const runtimeError = new Error(acpErrorMessage(error), { cause: error });
453
637
  this.handleExit(binding, runtimeError);
454
- this.unregisterAgent(agentId);
638
+ if (options.identityOnly === true) {
639
+ const returnedIdentity = binding.sessionId || binding.untrustedSessionId;
640
+ const identity = returnedIdentity
641
+ ? {
642
+ provider,
643
+ executable: options.executable || getProviderAdapter(provider)?.executable,
644
+ env: binding.env,
645
+ cwd: binding.cwd,
646
+ sessionId: returnedIdentity,
647
+ producerStopped: false,
648
+ }
649
+ : null;
650
+ let producerStopped = false;
651
+ try {
652
+ await this.unregisterAgentAndWait(agentId);
653
+ producerStopped = true;
654
+ if (identity) identity.producerStopped = true;
655
+ } catch (cleanupError) {
656
+ Object.defineProperty(runtimeError, 'adapterCleanupError', {
657
+ value: cleanupError,
658
+ enumerable: false,
659
+ configurable: true,
660
+ });
661
+ if (identity) attachProviderSessionIdentity(runtimeError, identity);
662
+ }
663
+ if (identity && producerStopped && isSafeProviderSessionId(identity.sessionId)) {
664
+ try {
665
+ await this.deleteProviderSessionIdentity(identity);
666
+ } catch (rollbackError) {
667
+ attachProviderSessionIdentity(runtimeError, identity, rollbackError);
668
+ }
669
+ } else if (identity && producerStopped) {
670
+ attachProviderSessionIdentity(runtimeError, identity);
671
+ }
672
+ } else {
673
+ this.unregisterAgent(agentId);
674
+ }
455
675
  throw runtimeError;
456
676
  }
457
677
  }
458
678
 
679
+ async createSessionIdentity(options = {}) {
680
+ const agentId = `provider-session-${crypto.randomUUID()}`;
681
+ let prepared = null;
682
+ let result = null;
683
+ let failure = null;
684
+ let identity = null;
685
+ try {
686
+ prepared = await this.prepareAgent({
687
+ ...options,
688
+ agentId,
689
+ identityOnly: true,
690
+ sessionId: '',
691
+ });
692
+ const binding = this.requireBinding(agentId);
693
+ identity = {
694
+ provider: String(options.provider || '').trim().toLowerCase(),
695
+ executable: options.executable || getProviderAdapter(options.provider)?.executable,
696
+ env: binding.env,
697
+ cwd: binding.cwd,
698
+ sessionId: prepared.sessionId,
699
+ producerStopped: false,
700
+ };
701
+ result = {
702
+ ...prepared,
703
+ sessionRequestOptions: this.getSessionRequestOptions(agentId),
704
+ };
705
+ } catch (error) {
706
+ failure = error instanceof Error ? error : new Error(String(error || 'Provider session identity failed'));
707
+ }
708
+
709
+ if (prepared) {
710
+ let producerStopped = false;
711
+ try {
712
+ await this.unregisterAgentAndWait(agentId);
713
+ producerStopped = true;
714
+ if (identity) identity.producerStopped = true;
715
+ } catch (cleanupError) {
716
+ if (!failure) {
717
+ failure = cleanupError instanceof Error
718
+ ? cleanupError
719
+ : new Error(String(cleanupError || 'ACP identity adapter cleanup failed'));
720
+ } else {
721
+ Object.defineProperty(failure, 'adapterCleanupError', {
722
+ value: cleanupError,
723
+ enumerable: false,
724
+ configurable: true,
725
+ });
726
+ }
727
+ if (identity) failure = attachProviderSessionIdentity(failure, identity);
728
+ }
729
+ if (failure && identity?.sessionId && producerStopped) {
730
+ try {
731
+ await this.deleteProviderSessionIdentity(identity);
732
+ } catch (rollbackError) {
733
+ failure = attachProviderSessionIdentity(failure, identity, rollbackError);
734
+ }
735
+ }
736
+ }
737
+ if (failure) throw failure;
738
+ return result;
739
+ }
740
+
459
741
  checkpointIdentity(binding, sessionId = binding?.sessionId) {
460
742
  if (!binding || !sessionId) return null;
461
743
  return {
@@ -697,9 +979,11 @@ class AcpRuntime extends EventEmitter {
697
979
  }
698
980
 
699
981
  requestElicitation(binding, request) {
700
- const requestSessionId = String(request?.sessionId || binding.sessionId);
701
- const isPrimarySession = requestSessionId === binding.sessionId;
702
- if (!isPrimarySession && !binding.subagentStates.has(requestSessionId)) {
982
+ const hasSessionScope = typeof request?.sessionId === 'string' && request.sessionId.length > 0;
983
+ const requestSessionId = hasSessionScope ? String(request.sessionId) : '';
984
+ const isPrimarySession = hasSessionScope && requestSessionId === binding.sessionId;
985
+ const isRequestScoped = !hasSessionScope;
986
+ if (!isRequestScoped && !isPrimarySession && !binding.subagentStates.has(requestSessionId)) {
703
987
  throw new Error('ACP elicitation does not match an active session');
704
988
  }
705
989
  if (!['form', 'url'].includes(String(request?.mode || ''))) {
@@ -715,7 +999,7 @@ class AcpRuntime extends EventEmitter {
715
999
  ...cloned,
716
1000
  ...(protocolRequestId !== undefined ? { protocolRequestId } : {}),
717
1001
  requestId,
718
- origin: isPrimarySession ? 'agent' : 'subagent',
1002
+ origin: isRequestScoped ? 'request' : isPrimarySession ? 'agent' : 'subagent',
719
1003
  };
720
1004
  binding.interactionOrigins.set(requestId, binding.state);
721
1005
  binding.pendingElicitations.set(requestId, pending);
@@ -757,7 +1041,11 @@ class AcpRuntime extends EventEmitter {
757
1041
  async prompt(agentId, prompt) {
758
1042
  const binding = this.requireBinding(agentId);
759
1043
  if (!['idle', 'error'].includes(binding.state)) throw new Error(`ACP Agent is not ready (${binding.state})`);
760
- const content = Array.isArray(prompt) ? prompt : [{ type: 'text', text: String(prompt || '') }];
1044
+ const rawContent = Array.isArray(prompt) ? prompt : [{ type: 'text', text: String(prompt || '') }];
1045
+ const content = promptContentForCapabilities(
1046
+ rawContent,
1047
+ binding.initializeResponse?.agentCapabilities || {},
1048
+ );
761
1049
  await this.markCheckpointDirty(binding);
762
1050
  binding.sessionState.beginPrompt(content);
763
1051
  binding.promptActive = true;
@@ -795,6 +1083,54 @@ class AcpRuntime extends EventEmitter {
795
1083
  }
796
1084
  }
797
1085
 
1086
+ canSteer(agentId) {
1087
+ const binding = this.bindings.get(agentId);
1088
+ return binding?.provider === 'codex'
1089
+ && binding.supportsSteer === true
1090
+ && binding.promptActive === true
1091
+ && Boolean(binding.sessionId)
1092
+ && Boolean(binding.connection);
1093
+ }
1094
+
1095
+ async steer(agentId, prompt) {
1096
+ const binding = this.requireBinding(agentId);
1097
+ if (binding.provider !== 'codex' || binding.supportsSteer !== true) {
1098
+ throw new Error(`${binding.provider} ACP Agent does not support steer`);
1099
+ }
1100
+ const rawContent = Array.isArray(prompt) ? prompt : [{ type: 'text', text: String(prompt || '') }];
1101
+ const content = promptContentForCapabilities(
1102
+ rawContent,
1103
+ binding.initializeResponse?.agentCapabilities || {},
1104
+ );
1105
+ const clientMessageId = `farming-steer-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
1106
+ const operation = async () => {
1107
+ if (!binding.promptActive) throw new Error('No active Codex turn to steer');
1108
+ await this.markCheckpointDirty(binding);
1109
+ const response = await withTimeout(
1110
+ binding.connection.request(CODEX_STEER_METHOD, {
1111
+ sessionId: binding.sessionId,
1112
+ prompt: content,
1113
+ clientMessageId,
1114
+ }),
1115
+ this.requestTimeoutMs,
1116
+ 'Codex ACP steer',
1117
+ );
1118
+ return {
1119
+ sessionId: binding.sessionId,
1120
+ turnId: String(response?.turnId || ''),
1121
+ clientMessageId,
1122
+ };
1123
+ };
1124
+ const previous = binding.steerTail || Promise.resolve();
1125
+ const pending = previous.catch(() => {}).then(operation);
1126
+ binding.steerTail = pending;
1127
+ try {
1128
+ return await pending;
1129
+ } finally {
1130
+ if (binding.steerTail === pending) binding.steerTail = null;
1131
+ }
1132
+ }
1133
+
798
1134
  async cancel(agentId) {
799
1135
  const binding = this.requireBinding(agentId);
800
1136
  if (!binding.sessionId) return false;
@@ -871,10 +1207,18 @@ class AcpRuntime extends EventEmitter {
871
1207
  if (!capabilities?.list) throw new Error(`${binding.provider} ACP Agent does not support session/list`);
872
1208
  return withTimeout(binding.connection.listSessions({
873
1209
  ...(options.cwd ? { cwd: path.resolve(options.cwd) } : {}),
1210
+ ...(Array.isArray(options.additionalDirectories)
1211
+ ? { additionalDirectories: acpSessionRequestOptions(options, options.cwd || binding.cwd).additionalDirectories }
1212
+ : {}),
874
1213
  ...(options.cursor ? { cursor: String(options.cursor) } : {}),
875
1214
  }), this.requestTimeoutMs, 'ACP session/list');
876
1215
  }
877
1216
 
1217
+ getSessionRequestOptions(agentId) {
1218
+ const binding = this.requireBinding(agentId);
1219
+ return clone(binding.sessionRequestOptions);
1220
+ }
1221
+
878
1222
  async authenticate(agentId, methodId) {
879
1223
  const binding = this.requireBinding(agentId);
880
1224
  const method = binding.initializeResponse?.authMethods?.find(item => item.id === methodId);
@@ -896,6 +1240,20 @@ class AcpRuntime extends EventEmitter {
896
1240
  return { authenticated: true, methodId };
897
1241
  }
898
1242
 
1243
+ async logout(agentId) {
1244
+ const binding = this.requireBinding(agentId);
1245
+ if (binding.initializeResponse?.agentCapabilities?.auth?.logout == null) {
1246
+ throw new Error(`${binding.provider} ACP Agent does not support logout`);
1247
+ }
1248
+ await withTimeout(binding.connection.logout({}), this.requestTimeoutMs, 'ACP logout');
1249
+ binding.error = '';
1250
+ binding.stopReason = '';
1251
+ binding.updatedAt = new Date().toISOString();
1252
+ this.emitRuntime(binding);
1253
+ this.emitSession(binding);
1254
+ return { loggedOut: true };
1255
+ }
1256
+
899
1257
  terminalAuthenticationLaunch(binding, method) {
900
1258
  const legacy = method?._meta?.['terminal-auth'];
901
1259
  if (legacy && typeof legacy === 'object' && legacy.command) {
@@ -994,13 +1352,13 @@ class AcpRuntime extends EventEmitter {
994
1352
  const binding = this.requireBinding(agentId);
995
1353
  const capabilities = binding.initializeResponse?.agentCapabilities?.sessionCapabilities;
996
1354
  if (!capabilities?.fork) throw new Error(`${binding.provider} ACP Agent does not support session/fork`);
1355
+ const sessionOptions = acpSessionRequestOptions({
1356
+ additionalDirectories: options.additionalDirectories ?? binding.sessionRequestOptions.additionalDirectories,
1357
+ mcpServers: options.mcpServers ?? binding.sessionRequestOptions.mcpServers,
1358
+ }, options.cwd || binding.cwd);
997
1359
  return withTimeout(binding.connection.unstable_forkSession({
998
1360
  sessionId: options.sessionId || binding.sessionId,
999
- cwd: path.resolve(options.cwd || binding.cwd),
1000
- additionalDirectories: Array.isArray(options.additionalDirectories)
1001
- ? options.additionalDirectories.map(directory => path.resolve(directory))
1002
- : [],
1003
- mcpServers: [],
1361
+ ...sessionOptions,
1004
1362
  }), this.sessionSetupTimeoutMs, 'ACP session/fork');
1005
1363
  }
1006
1364
 
@@ -1186,6 +1544,7 @@ class AcpRuntime extends EventEmitter {
1186
1544
  error: binding.error,
1187
1545
  errorKind: binding.error ? acpErrorKind(binding.error) : '',
1188
1546
  stopReason: binding.stopReason,
1547
+ supportsSteer: binding.supportsSteer === true,
1189
1548
  protocolVersion: binding.initializeResponse?.protocolVersion || null,
1190
1549
  agentInfo: binding.initializeResponse?.agentInfo || null,
1191
1550
  capabilities: binding.initializeResponse?.agentCapabilities || {},
@@ -1376,6 +1735,7 @@ class AcpRuntime extends EventEmitter {
1376
1735
  error: binding.error,
1377
1736
  errorKind: binding.error ? acpErrorKind(binding.error) : '',
1378
1737
  stopReason: binding.stopReason,
1738
+ supportsSteer: binding.supportsSteer === true,
1379
1739
  pendingPermission: binding.pendingPermissions.values().next().value || null,
1380
1740
  pendingPermissions: [...binding.pendingPermissions.values()],
1381
1741
  pendingElicitation: binding.pendingElicitations.values().next().value || null,
@@ -1416,11 +1776,10 @@ class AcpRuntime extends EventEmitter {
1416
1776
  this.emitRuntime(binding);
1417
1777
  }
1418
1778
 
1419
- unregisterAgent(agentId) {
1420
- const binding = this.bindings.get(agentId);
1421
- if (!binding) return;
1779
+ detachAgentBinding(binding) {
1780
+ if (!binding || this.bindings.get(binding.agentId) !== binding) return false;
1422
1781
  if (binding.sessionState && !binding.promptActive) this.scheduleCheckpoint(binding, { exact: true });
1423
- this.bindings.delete(agentId);
1782
+ this.bindings.delete(binding.agentId);
1424
1783
  for (const resolve of binding.permissionResolvers.values()) resolve({ outcome: { outcome: 'cancelled' } });
1425
1784
  for (const resolve of binding.elicitationResolvers.values()) resolve({ action: 'cancel' });
1426
1785
  binding.permissionResolvers.clear();
@@ -1434,9 +1793,33 @@ class AcpRuntime extends EventEmitter {
1434
1793
  try {
1435
1794
  binding.connection?.close();
1436
1795
  } catch {
1437
- // The adapter process is terminated below even if transport cleanup raced its exit.
1796
+ // Process cleanup below remains authoritative if transport cleanup races.
1797
+ }
1798
+ if (binding.child?.stdin?.writable && !binding.child.stdin.destroyed) {
1799
+ try {
1800
+ binding.child.stdin.end();
1801
+ } catch {
1802
+ // Process cleanup below remains authoritative if stdin already closed.
1803
+ }
1438
1804
  }
1439
- if (binding.child && !binding.child.killed) binding.child.kill('SIGTERM');
1805
+ return true;
1806
+ }
1807
+
1808
+ unregisterAgent(agentId) {
1809
+ const binding = this.bindings.get(agentId);
1810
+ if (!binding || !this.detachAgentBinding(binding)) return;
1811
+ signalProcessTree(binding, 'SIGTERM');
1812
+ }
1813
+
1814
+ async unregisterAgentAndWait(agentId) {
1815
+ const binding = this.bindings.get(agentId);
1816
+ if (!binding || !this.detachAgentBinding(binding)) return;
1817
+ if (await waitForProcessTreeExit(binding, IDENTITY_ADAPTER_GRACEFUL_EXIT_MS)) return;
1818
+ signalProcessTree(binding, 'SIGTERM');
1819
+ if (await waitForProcessTreeExit(binding, IDENTITY_ADAPTER_TERMINATE_MS)) return;
1820
+ signalProcessTree(binding, 'SIGKILL');
1821
+ if (await waitForProcessTreeExit(binding, IDENTITY_ADAPTER_KILL_MS)) return;
1822
+ throw new Error(`ACP identity adapter process tree ${binding.child?.pid || ''} did not exit`);
1440
1823
  }
1441
1824
 
1442
1825
  async dispose() {
@@ -1452,8 +1835,14 @@ class AcpRuntime extends EventEmitter {
1452
1835
  module.exports = {
1453
1836
  AcpRuntime,
1454
1837
  ADAPTER_VERSIONS,
1838
+ acpSessionRequestOptions,
1455
1839
  acpErrorKind,
1456
1840
  autoPermissionResponse,
1457
1841
  codexAcpEnvironment,
1842
+ promptContentForCapabilities,
1458
1843
  resolveAcpLaunch,
1844
+ supportsCodexSteer,
1845
+ isCodexSteerUnavailableError,
1846
+ CODEX_STEER_METHOD,
1847
+ deleteProviderSessionIdentity,
1459
1848
  };