farming-code 2.2.13 → 2.2.16

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 (119) hide show
  1. package/README.md +81 -105
  2. package/README.zh_cn.md +82 -106
  3. package/THIRD_PARTY_NOTICES.md +1 -1
  4. package/backend/acp/packaged-codex-acp.js +13 -0
  5. package/backend/acp-runtime.js +192 -14
  6. package/backend/agent-manager.js +176 -510
  7. package/backend/agent-runtime-binding.js +9 -55
  8. package/backend/chat-runtime.js +28 -0
  9. package/backend/codex-transcript.js +2 -1
  10. package/backend/config-manager.js +22 -14
  11. package/backend/control-api.js +3 -1
  12. package/backend/farming-app-cli.js +13 -0
  13. package/backend/farming-session-store.js +2 -1
  14. package/backend/npm-update-helper.js +65 -7
  15. package/backend/provider-adapters.js +6 -2
  16. package/backend/server.js +89 -104
  17. package/backend/storage-layout.js +0 -16
  18. package/backend/update-service.js +94 -5
  19. package/dist/acp/LICENSE.codex-acp +190 -0
  20. package/dist/acp/codex-acp-1.1.4.js +30718 -0
  21. package/dist/assets/App-y6p2yRar.js +206 -0
  22. package/dist/assets/{FileEditorMarkdownPreview-UlVd0O4D.js → FileEditorMarkdownPreview-R0jwwLME.js} +4 -4
  23. package/dist/assets/FileEditorPane-DNy3LAqS.js +2 -0
  24. package/dist/assets/{IconGlyphs-AU22gPDY.js → IconGlyphs-BsUfqpKN.js} +1 -1
  25. package/dist/assets/ProjectFilesSection-BhDEUVHA.js +12 -0
  26. package/dist/assets/{ReviewPage-CeDKDyZh.js → ReviewPage-ChtByBe3.js} +1 -1
  27. package/dist/assets/{abnfDiagram-VRR7QNED-BgawzDQT.js → abnfDiagram-VRR7QNED-nnglrXFi.js} +1 -1
  28. package/dist/assets/{architectureDiagram-ZJ3FMSHR-DUShwPg0.js → architectureDiagram-ZJ3FMSHR-BEXMaFfY.js} +1 -1
  29. package/dist/assets/{blockDiagram-677ZJIJ3-CeGZviXR.js → blockDiagram-677ZJIJ3-1mCezSBk.js} +1 -1
  30. package/dist/assets/{c4Diagram-LMCZKHZV-CLhYht6S.js → c4Diagram-LMCZKHZV-DhTiI6Uv.js} +1 -1
  31. package/dist/assets/channel-BVWNatUB.js +1 -0
  32. package/dist/assets/{chunk-32BRIVSS-LFYpT-Cu.js → chunk-32BRIVSS-CCdsKd73.js} +1 -1
  33. package/dist/assets/{chunk-52WLFC77-BH6jDw9J.js → chunk-52WLFC77-Gncjk4LF.js} +1 -1
  34. package/dist/assets/{chunk-C7G6YPKG-B_mIUlb7.js → chunk-C7G6YPKG-CiDVUP82.js} +1 -1
  35. package/dist/assets/{chunk-EX3LRPZG-C63hVuAJ.js → chunk-EX3LRPZG-z6gXP2_N.js} +1 -1
  36. package/dist/assets/{chunk-FWX5IMBZ-CboX-WF_.js → chunk-FWX5IMBZ-DMZBccQa.js} +2 -2
  37. package/dist/assets/{chunk-HOUHSVGY-BOgSd6Un.js → chunk-HOUHSVGY-D8tIeoPk.js} +1 -1
  38. package/dist/assets/{chunk-ICXQ74PX-C0K-pgZt.js → chunk-ICXQ74PX-b6oQ2Q17.js} +1 -1
  39. package/dist/assets/{chunk-MOJQB5TN-CVMfwN_7.js → chunk-MOJQB5TN-DAVH_p2a.js} +1 -1
  40. package/dist/assets/{chunk-OGEWGWER-DLT3pxyw.js → chunk-OGEWGWER-DxoiMCI-.js} +1 -1
  41. package/dist/assets/{chunk-PUDLZKDR-Cd079yIQ.js → chunk-PUDLZKDR-NsOWh7HM.js} +1 -1
  42. package/dist/assets/{chunk-Q4XR5HBZ-D0jHa6SO.js → chunk-Q4XR5HBZ-Di4H7L21.js} +1 -1
  43. package/dist/assets/{chunk-V7JOEXUC-CVDtR75H.js → chunk-V7JOEXUC-DttpZg9Y.js} +1 -1
  44. package/dist/assets/{chunk-VAUOI2AC-B-W2_9Ot.js → chunk-VAUOI2AC-UHNr_yhX.js} +1 -1
  45. package/dist/assets/{chunk-VR4S4FIN-FD3kWh32.js → chunk-VR4S4FIN-D-Fmtd4Z.js} +1 -1
  46. package/dist/assets/{chunk-WYO6CB5R-C2bGaS5r.js → chunk-WYO6CB5R-CUxMtfK2.js} +4 -4
  47. package/dist/assets/{chunk-ZGVPDNZ5-ymgts0zo.js → chunk-ZGVPDNZ5-CoV2QEl0.js} +1 -1
  48. package/dist/assets/classDiagram-OUVF2IWQ-Db0ib5iE.js +1 -0
  49. package/dist/assets/classDiagram-v2-EOCWNBFH-Bkf69XF9.js +1 -0
  50. package/dist/assets/code-dark-DKBGxoPX.css +1 -0
  51. package/dist/assets/{cynefinDiagram-TSTJHNR4-CwpobXIl.js → cynefinDiagram-TSTJHNR4-0xPI92Lg.js} +1 -1
  52. package/dist/assets/{dagre-VKFMJZFB-Dkhlfjna.js → dagre-VKFMJZFB-DfuMG4gU.js} +1 -1
  53. package/dist/assets/{diagram-FQU43EPY-hA8dicp6.js → diagram-FQU43EPY-CT00bLZ6.js} +1 -1
  54. package/dist/assets/{diagram-G47NLZAW-BTY6JTzO.js → diagram-G47NLZAW-Xbh7zXUx.js} +1 -1
  55. package/dist/assets/{diagram-NH7WQ7WH-xkaHHX9M.js → diagram-NH7WQ7WH-SRDVPfn7.js} +1 -1
  56. package/dist/assets/{diagram-OA4YK3LP-gk_fg9UU.js → diagram-OA4YK3LP-CQRli4Np.js} +1 -1
  57. package/dist/assets/{diagram-WEI45ONY-BvC8xd6T.js → diagram-WEI45ONY-DMf17WQi.js} +1 -1
  58. package/dist/assets/{ebnfDiagram-CCIWWBDH-CR7RaNat.js → ebnfDiagram-CCIWWBDH-DhpFkgJ5.js} +1 -1
  59. package/dist/assets/{erDiagram-Q63AITRT-_M-JFPbj.js → erDiagram-Q63AITRT-1IwxWmJo.js} +1 -1
  60. package/dist/assets/file-icons-DpbrD5ZP.js +1 -0
  61. package/dist/assets/flowDiagram-23GEKE2U-DYFo2xmB.js +1 -0
  62. package/dist/assets/{ganttDiagram-NO4QXBWP-Bl8P1yf_.js → ganttDiagram-NO4QXBWP-BNCdfyr1.js} +1 -1
  63. package/dist/assets/{gitGraphDiagram-IHSO6WYX-DoHa5ysz.js → gitGraphDiagram-IHSO6WYX-pbyWZuiX.js} +1 -1
  64. package/dist/assets/{index-CYZKdoqi.js → index-iKBvPjWS.js} +2 -2
  65. package/dist/assets/{infoDiagram-FWYZ7A6U-DSFcSuUs.js → infoDiagram-FWYZ7A6U-BcTOgDbe.js} +1 -1
  66. package/dist/assets/{ishikawaDiagram-FXEZZL3T-DdkqullI.js → ishikawaDiagram-FXEZZL3T-DCdvGKrn.js} +1 -1
  67. package/dist/assets/{journeyDiagram-5HDEW3XC-B4hRnQJr.js → journeyDiagram-5HDEW3XC-BZ-1q_IT.js} +1 -1
  68. package/dist/assets/{kanban-definition-HUTT4EX6-tm2FZB2E.js → kanban-definition-HUTT4EX6-BIgejCjj.js} +1 -1
  69. package/dist/assets/{line-CmsjK4kn.js → line-CfptFMmX.js} +1 -1
  70. package/dist/assets/main-CdkLOXYl.css +1 -0
  71. package/dist/assets/{mermaid.core-C7UT_EEL.js → mermaid.core-gSpX30pV.js} +3 -3
  72. package/dist/assets/{mindmap-definition-LN4V7U3C-DplfjGms.js → mindmap-definition-LN4V7U3C-BKBaI4LW.js} +1 -1
  73. package/dist/assets/{pegDiagram-2B236MQR-lu3SdkQs.js → pegDiagram-2B236MQR-sBvDY3VX.js} +1 -1
  74. package/dist/assets/{pieDiagram-ENE6RG2P-3fHbmDf9.js → pieDiagram-ENE6RG2P-DWlWcu08.js} +1 -1
  75. package/dist/assets/{quadrantDiagram-ABIIQ3AL-a5qoWz-X.js → quadrantDiagram-ABIIQ3AL-DZutUd3J.js} +1 -1
  76. package/dist/assets/{railroadDiagram-RFXS5EU6-D2TOuXW9.js → railroadDiagram-RFXS5EU6-DQb3lXGM.js} +1 -1
  77. package/dist/assets/{requirementDiagram-TGXJPOKE-LQBWpUvd.js → requirementDiagram-TGXJPOKE-DqFGXu2e.js} +1 -1
  78. package/dist/assets/{sankeyDiagram-HTMAVEWB-B1ohYoyK.js → sankeyDiagram-HTMAVEWB-d7ECahnO.js} +1 -1
  79. package/dist/assets/{sequenceDiagram-DBY2YBRQ-C3vM-hMq.js → sequenceDiagram-DBY2YBRQ-DdsbKqct.js} +1 -1
  80. package/dist/assets/{stateDiagram-2N3HPSRC-BvFhOryI.js → stateDiagram-2N3HPSRC-P6iSrgwR.js} +1 -1
  81. package/dist/assets/stateDiagram-v2-6OUMAXLB-DIYSf_qh.js +1 -0
  82. package/dist/assets/{swimlanes-5IMT3BWC-DKF_b-2s.js → swimlanes-5IMT3BWC-BGnZJx1M.js} +1 -1
  83. package/dist/assets/swimlanesDiagram-G3AALYLV-BIkrlKrZ.js +8 -0
  84. package/dist/assets/{timeline-definition-FHXFAJF6-vAs11ux_.js → timeline-definition-FHXFAJF6-rA-r3aUR.js} +1 -1
  85. package/dist/assets/{vennDiagram-L72KCM5P-BybTX_9B.js → vennDiagram-L72KCM5P-DiVKUjA9.js} +1 -1
  86. package/dist/assets/{wardleyDiagram-EHGQE667-CicUmMBR.js → wardleyDiagram-EHGQE667-Cfxh7UOn.js} +1 -1
  87. package/dist/assets/workspace-editor-model-D0Im34S5.js +1 -0
  88. package/dist/assets/{workspace-editor-monaco-BICJESG0.js → workspace-editor-monaco-BIpqcWf1.js} +1 -1
  89. package/dist/assets/workspace-editor-monaco-DLlws5Ig.js +1 -0
  90. package/dist/assets/{workspace-view-state-DvYG_9PH.js → workspace-view-state-BpPINu84.js} +1 -1
  91. package/dist/assets/{xychartDiagram-FW5EYKEG-BhWDyCX-.js → xychartDiagram-FW5EYKEG--hmryQeL.js} +1 -1
  92. package/dist/crt-mermaid-renderer.js +313 -313
  93. package/dist/index.html +1 -1
  94. package/frontend/session-bridge.js +24 -2
  95. package/frontend/skins/crt/app.js +194 -82
  96. package/frontend/skins/crt/index.html +2 -11
  97. package/frontend/skins/crt/styles/effects.css +2 -34
  98. package/package.json +23 -9
  99. package/shared/browser-protocol.js +3 -3
  100. package/backend/app-server-api.js +0 -917
  101. package/backend/codex-app-server-home.js +0 -39
  102. package/backend/codex-app-server-runtime.js +0 -901
  103. package/dist/assets/App-xASd_Y0F.js +0 -206
  104. package/dist/assets/FileEditorPane-Dxx4rKOg.js +0 -2
  105. package/dist/assets/ProjectFilesSection-DE7Nb9Jk.js +0 -12
  106. package/dist/assets/channel-Uw90AO0b.js +0 -1
  107. package/dist/assets/classDiagram-OUVF2IWQ-DKW_dtku.js +0 -1
  108. package/dist/assets/classDiagram-v2-EOCWNBFH-C8Fi7-yv.js +0 -1
  109. package/dist/assets/code-dark-DPiuyeSp.css +0 -1
  110. package/dist/assets/code-mobile-3C2Rr-CN.css +0 -1
  111. package/dist/assets/file-icons-Bw2qd5iT.js +0 -1
  112. package/dist/assets/flowDiagram-23GEKE2U-DAiS5Y7G.js +0 -1
  113. package/dist/assets/main-Bqtb5kuz.css +0 -1
  114. package/dist/assets/stateDiagram-v2-6OUMAXLB-WMrz4PNo.js +0 -1
  115. package/dist/assets/swimlanesDiagram-G3AALYLV-BGswC9oC.js +0 -8
  116. package/dist/assets/workspace-editor-model-BQol4qbA.js +0 -1
  117. package/dist/assets/workspace-editor-monaco-DRJ7IaXk.js +0 -1
  118. package/scripts/sync-ghostty-vendor.js +0 -66
  119. /package/dist/assets/{core-D0LFJkDt.js → core-B9MwRjDE.js} +0 -0
@@ -1,4 +1,5 @@
1
1
  const EventEmitter = require('events');
2
+ const crypto = require('crypto');
2
3
  const fs = require('fs');
3
4
  const path = require('path');
4
5
  const { spawn } = require('child_process');
@@ -9,6 +10,7 @@ const { AcpCheckpointStore } = require('./acp-checkpoint-store');
9
10
  const { AcpSessionState } = require('./acp-session-state');
10
11
  const { readCodexHistoryImageData } = require('./codex-transcript');
11
12
  const { AcpClientFileSystem, AcpClientTerminalManager } = require('./acp/client-services');
13
+ const { PACKAGED_CODEX_ACP_ARG } = require('./acp/packaged-codex-acp');
12
14
  const { permissionSecurityWarnings } = require('./acp/permission-security');
13
15
  const { rejectPatch } = require('./acp/patch-decisions');
14
16
  const { getProviderAdapter, listProviderAdapters } = require('./provider-adapters');
@@ -25,6 +27,17 @@ const DEFAULT_HISTORY_REPLAY_MIN_WAIT_MS = 350;
25
27
  const DEFAULT_HISTORY_REPLAY_QUIET_MS = 150;
26
28
  const DEFAULT_HISTORY_REPLAY_MAX_WAIT_MS = 5_000;
27
29
  const CODEX_SET_SESSION_MODEL_METHOD = 'session/set_model';
30
+ const CODEX_STEER_METHOD = '_codex/session/steer';
31
+ const CODEX_ACP_PACKAGE = '@agentclientprotocol/codex-acp';
32
+ const CODEX_ACP_VERSION = '1.1.4';
33
+ const CODEX_ACP_SHA256 = '39cbae01e336c2ca185d624358e03280d1f6fef6d73bbe42dd9eb77e2b1efb32';
34
+ const CODEX_ACP_VENDOR_ENTRY = path.join(
35
+ __dirname,
36
+ '..',
37
+ 'dist',
38
+ 'acp',
39
+ `codex-acp-${CODEX_ACP_VERSION}.js`,
40
+ );
28
41
 
29
42
  let sdkPromise;
30
43
  const runtimeRequire = createRequire(__filename);
@@ -33,7 +46,24 @@ function loadAcpSdk() {
33
46
  return sdkPromise;
34
47
  }
35
48
 
49
+ function fileSha256(filePath) {
50
+ return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
51
+ }
52
+
53
+ function verifiedCodexAcpEntry(entry) {
54
+ const actualSha256 = fileSha256(entry);
55
+ if (actualSha256 !== CODEX_ACP_SHA256) {
56
+ throw new Error(
57
+ `Codex ACP runtime failed integrity verification: expected ${CODEX_ACP_SHA256}, found ${actualSha256}`,
58
+ );
59
+ }
60
+ return entry;
61
+ }
62
+
36
63
  function adapterEntry(packageName) {
64
+ if (packageName === CODEX_ACP_PACKAGE && fs.existsSync(CODEX_ACP_VENDOR_ENTRY)) {
65
+ return verifiedCodexAcpEntry(CODEX_ACP_VENDOR_ENTRY);
66
+ }
37
67
  let sdkDirectory;
38
68
  try {
39
69
  sdkDirectory = path.dirname(runtimeRequire.resolve('@agentclientprotocol/sdk'));
@@ -42,11 +72,15 @@ function adapterEntry(packageName) {
42
72
  }
43
73
  const entry = path.resolve(sdkDirectory, '..', '..', packageName.split('/').pop(), 'dist', 'index.js');
44
74
  if (!fs.existsSync(entry)) throw new Error(`ACP adapter is not installed: ${packageName}`);
75
+ if (packageName === CODEX_ACP_PACKAGE) return verifiedCodexAcpEntry(entry);
45
76
  return entry;
46
77
  }
47
78
 
48
- function nodeAdapterLaunch(entry, env = process.env) {
79
+ function nodeAdapterLaunch(entry, env = process.env, packagedArg = '') {
49
80
  const runtimeEnv = env && typeof env === 'object' ? env : process.env;
81
+ if (process.pkg && packagedArg) {
82
+ return { command: process.execPath, args: [packagedArg] };
83
+ }
50
84
  const nodeBin = runtimeEnv.FARMING_NODE_BIN || process.execPath;
51
85
  const ldPath = runtimeEnv.FARMING_NODE_LD || '';
52
86
  const libraryPath = runtimeEnv.FARMING_NODE_LIBRARY_PATH || '';
@@ -69,6 +103,7 @@ function resolveAcpLaunch(provider, options = {}) {
69
103
  const launch = nodeAdapterLaunch(
70
104
  adapterEntry(adapter.acp.packageName),
71
105
  options.runtimeEnv || process.env,
106
+ adapter.acp.packageName === CODEX_ACP_PACKAGE ? PACKAGED_CODEX_ACP_ARG : '',
72
107
  );
73
108
  return { ...launch, version: adapter.acp.version };
74
109
  }
@@ -202,6 +237,59 @@ function clone(value) {
202
237
  return value == null ? value : JSON.parse(JSON.stringify(value));
203
238
  }
204
239
 
240
+ function acpSessionRequestOptions(options = {}, cwd = process.cwd()) {
241
+ const root = path.resolve(cwd || process.cwd());
242
+ const additionalDirectories = Array.isArray(options.additionalDirectories)
243
+ ? [...new Set(options.additionalDirectories
244
+ .filter(directory => typeof directory === 'string' && directory.trim())
245
+ .map(directory => path.resolve(root, directory)))]
246
+ : [];
247
+ const mcpServers = Array.isArray(options.mcpServers)
248
+ ? clone(options.mcpServers.filter(server => server && typeof server === 'object' && !Array.isArray(server)))
249
+ : [];
250
+ return { cwd: root, additionalDirectories, mcpServers };
251
+ }
252
+
253
+ function promptContentForCapabilities(content, capabilities = {}) {
254
+ const promptCapabilities = capabilities?.promptCapabilities || {};
255
+ return (Array.isArray(content) ? content : []).flatMap(block => {
256
+ if (!block || typeof block !== 'object') return [];
257
+ if (!['image', 'audio'].includes(block.type)) return [clone(block)];
258
+ if (promptCapabilities[block.type] === true) return [clone(block)];
259
+
260
+ const label = block.type === 'audio' ? 'audio' : 'image';
261
+ const attachmentPath = typeof block.path === 'string' && block.path.trim()
262
+ ? path.resolve(block.path)
263
+ : '';
264
+ const name = attachmentPath ? path.basename(attachmentPath) : `attached ${label}`;
265
+ const detail = attachmentPath
266
+ ? `${label[0].toUpperCase()}${label.slice(1)} path: ${attachmentPath}`
267
+ : `[The ACP Agent does not accept native ${label} content]`;
268
+ return [{ type: 'text', text: `Attached ${label}: ${name}\n\n${detail}` }];
269
+ });
270
+ }
271
+
272
+ function supportsCodexSteer(capabilities = {}) {
273
+ const capability = capabilities?._meta?.codex?.steer;
274
+ return capability?.method === CODEX_STEER_METHOD
275
+ && Number.isFinite(Number(capability.version))
276
+ && Number(capability.version) >= 1;
277
+ }
278
+
279
+ function isCodexSteerUnavailableError(error) {
280
+ const text = [
281
+ error?.message,
282
+ error?.data?.details,
283
+ error?.cause?.message,
284
+ error?.cause?.data?.details,
285
+ ].filter(Boolean).join(' ').toLowerCase();
286
+ return text.includes('no active codex turn to steer')
287
+ || text.includes('no active turn to steer')
288
+ || /expected active turn id .* but found/.test(text)
289
+ || text.includes('cannot steer a review turn')
290
+ || text.includes('cannot steer a compact turn');
291
+ }
292
+
205
293
  class AcpRuntime extends EventEmitter {
206
294
  constructor(options = {}) {
207
295
  super();
@@ -231,11 +319,14 @@ class AcpRuntime extends EventEmitter {
231
319
  if (this.bindings.has(agentId)) throw new Error('ACP Agent is already registered');
232
320
  const provider = String(options.provider || '').trim().toLowerCase();
233
321
  const launch = this.resolveLaunch(provider, options);
322
+ const cwd = path.resolve(options.cwd || process.cwd());
323
+ const sessionRequestOptions = acpSessionRequestOptions(options, cwd);
234
324
  const binding = {
235
325
  agentId,
236
326
  provider,
237
327
  providerHomeId: String(options.providerHomeId || 'default'),
238
- cwd: path.resolve(options.cwd || process.cwd()),
328
+ cwd,
329
+ sessionRequestOptions,
239
330
  env: (getProviderAdapter(provider)?.prepareAcpEnvironment || (value => value.env || process.env))(options),
240
331
  launch,
241
332
  restartOptions: { ...options, agentId, provider },
@@ -257,6 +348,8 @@ class AcpRuntime extends EventEmitter {
257
348
  subagentStates: new Map(),
258
349
  interactionOrigins: new Map(),
259
350
  promptActive: false,
351
+ supportsSteer: false,
352
+ steerTail: null,
260
353
  historyReplayActive: false,
261
354
  sessionState: null,
262
355
  authTerminal: null,
@@ -308,12 +401,14 @@ class AcpRuntime extends EventEmitter {
308
401
  if (binding.initializeResponse.protocolVersion !== sdk.PROTOCOL_VERSION) {
309
402
  throw new Error(`ACP protocol version mismatch: Agent selected ${binding.initializeResponse.protocolVersion}, Farming supports ${sdk.PROTOCOL_VERSION}`);
310
403
  }
404
+ binding.supportsSteer = binding.provider === 'codex'
405
+ && supportsCodexSteer(binding.initializeResponse.agentCapabilities);
311
406
 
312
407
  const requestedSessionId = String(options.sessionId || '').trim();
313
408
  const revisionBase = Number.isFinite(Number(options.revisionBase))
314
409
  ? Math.max(0, Math.floor(Number(options.revisionBase)))
315
410
  : 0;
316
- const sessionRequest = { sessionId: requestedSessionId, cwd: binding.cwd, mcpServers: [] };
411
+ const sessionRequest = { sessionId: requestedSessionId, ...binding.sessionRequestOptions };
317
412
  let sessionResponse;
318
413
  let historyMode = 'new';
319
414
  if (requestedSessionId) {
@@ -413,7 +508,7 @@ class AcpRuntime extends EventEmitter {
413
508
  }
414
509
  } else {
415
510
  sessionResponse = await withTimeout(
416
- connection.newSession({ cwd: binding.cwd, mcpServers: [] }),
511
+ connection.newSession(binding.sessionRequestOptions),
417
512
  this.sessionSetupTimeoutMs,
418
513
  'ACP session/new'
419
514
  );
@@ -697,9 +792,11 @@ class AcpRuntime extends EventEmitter {
697
792
  }
698
793
 
699
794
  requestElicitation(binding, request) {
700
- const requestSessionId = String(request?.sessionId || binding.sessionId);
701
- const isPrimarySession = requestSessionId === binding.sessionId;
702
- if (!isPrimarySession && !binding.subagentStates.has(requestSessionId)) {
795
+ const hasSessionScope = typeof request?.sessionId === 'string' && request.sessionId.length > 0;
796
+ const requestSessionId = hasSessionScope ? String(request.sessionId) : '';
797
+ const isPrimarySession = hasSessionScope && requestSessionId === binding.sessionId;
798
+ const isRequestScoped = !hasSessionScope;
799
+ if (!isRequestScoped && !isPrimarySession && !binding.subagentStates.has(requestSessionId)) {
703
800
  throw new Error('ACP elicitation does not match an active session');
704
801
  }
705
802
  if (!['form', 'url'].includes(String(request?.mode || ''))) {
@@ -715,7 +812,7 @@ class AcpRuntime extends EventEmitter {
715
812
  ...cloned,
716
813
  ...(protocolRequestId !== undefined ? { protocolRequestId } : {}),
717
814
  requestId,
718
- origin: isPrimarySession ? 'agent' : 'subagent',
815
+ origin: isRequestScoped ? 'request' : isPrimarySession ? 'agent' : 'subagent',
719
816
  };
720
817
  binding.interactionOrigins.set(requestId, binding.state);
721
818
  binding.pendingElicitations.set(requestId, pending);
@@ -757,7 +854,11 @@ class AcpRuntime extends EventEmitter {
757
854
  async prompt(agentId, prompt) {
758
855
  const binding = this.requireBinding(agentId);
759
856
  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 || '') }];
857
+ const rawContent = Array.isArray(prompt) ? prompt : [{ type: 'text', text: String(prompt || '') }];
858
+ const content = promptContentForCapabilities(
859
+ rawContent,
860
+ binding.initializeResponse?.agentCapabilities || {},
861
+ );
761
862
  await this.markCheckpointDirty(binding);
762
863
  binding.sessionState.beginPrompt(content);
763
864
  binding.promptActive = true;
@@ -795,6 +896,54 @@ class AcpRuntime extends EventEmitter {
795
896
  }
796
897
  }
797
898
 
899
+ canSteer(agentId) {
900
+ const binding = this.bindings.get(agentId);
901
+ return binding?.provider === 'codex'
902
+ && binding.supportsSteer === true
903
+ && binding.promptActive === true
904
+ && Boolean(binding.sessionId)
905
+ && Boolean(binding.connection);
906
+ }
907
+
908
+ async steer(agentId, prompt) {
909
+ const binding = this.requireBinding(agentId);
910
+ if (binding.provider !== 'codex' || binding.supportsSteer !== true) {
911
+ throw new Error(`${binding.provider} ACP Agent does not support steer`);
912
+ }
913
+ const rawContent = Array.isArray(prompt) ? prompt : [{ type: 'text', text: String(prompt || '') }];
914
+ const content = promptContentForCapabilities(
915
+ rawContent,
916
+ binding.initializeResponse?.agentCapabilities || {},
917
+ );
918
+ const clientMessageId = `farming-steer-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
919
+ const operation = async () => {
920
+ if (!binding.promptActive) throw new Error('No active Codex turn to steer');
921
+ await this.markCheckpointDirty(binding);
922
+ const response = await withTimeout(
923
+ binding.connection.request(CODEX_STEER_METHOD, {
924
+ sessionId: binding.sessionId,
925
+ prompt: content,
926
+ clientMessageId,
927
+ }),
928
+ this.requestTimeoutMs,
929
+ 'Codex ACP steer',
930
+ );
931
+ return {
932
+ sessionId: binding.sessionId,
933
+ turnId: String(response?.turnId || ''),
934
+ clientMessageId,
935
+ };
936
+ };
937
+ const previous = binding.steerTail || Promise.resolve();
938
+ const pending = previous.catch(() => {}).then(operation);
939
+ binding.steerTail = pending;
940
+ try {
941
+ return await pending;
942
+ } finally {
943
+ if (binding.steerTail === pending) binding.steerTail = null;
944
+ }
945
+ }
946
+
798
947
  async cancel(agentId) {
799
948
  const binding = this.requireBinding(agentId);
800
949
  if (!binding.sessionId) return false;
@@ -871,10 +1020,18 @@ class AcpRuntime extends EventEmitter {
871
1020
  if (!capabilities?.list) throw new Error(`${binding.provider} ACP Agent does not support session/list`);
872
1021
  return withTimeout(binding.connection.listSessions({
873
1022
  ...(options.cwd ? { cwd: path.resolve(options.cwd) } : {}),
1023
+ ...(Array.isArray(options.additionalDirectories)
1024
+ ? { additionalDirectories: acpSessionRequestOptions(options, options.cwd || binding.cwd).additionalDirectories }
1025
+ : {}),
874
1026
  ...(options.cursor ? { cursor: String(options.cursor) } : {}),
875
1027
  }), this.requestTimeoutMs, 'ACP session/list');
876
1028
  }
877
1029
 
1030
+ getSessionRequestOptions(agentId) {
1031
+ const binding = this.requireBinding(agentId);
1032
+ return clone(binding.sessionRequestOptions);
1033
+ }
1034
+
878
1035
  async authenticate(agentId, methodId) {
879
1036
  const binding = this.requireBinding(agentId);
880
1037
  const method = binding.initializeResponse?.authMethods?.find(item => item.id === methodId);
@@ -896,6 +1053,20 @@ class AcpRuntime extends EventEmitter {
896
1053
  return { authenticated: true, methodId };
897
1054
  }
898
1055
 
1056
+ async logout(agentId) {
1057
+ const binding = this.requireBinding(agentId);
1058
+ if (binding.initializeResponse?.agentCapabilities?.auth?.logout == null) {
1059
+ throw new Error(`${binding.provider} ACP Agent does not support logout`);
1060
+ }
1061
+ await withTimeout(binding.connection.logout({}), this.requestTimeoutMs, 'ACP logout');
1062
+ binding.error = '';
1063
+ binding.stopReason = '';
1064
+ binding.updatedAt = new Date().toISOString();
1065
+ this.emitRuntime(binding);
1066
+ this.emitSession(binding);
1067
+ return { loggedOut: true };
1068
+ }
1069
+
899
1070
  terminalAuthenticationLaunch(binding, method) {
900
1071
  const legacy = method?._meta?.['terminal-auth'];
901
1072
  if (legacy && typeof legacy === 'object' && legacy.command) {
@@ -994,13 +1165,13 @@ class AcpRuntime extends EventEmitter {
994
1165
  const binding = this.requireBinding(agentId);
995
1166
  const capabilities = binding.initializeResponse?.agentCapabilities?.sessionCapabilities;
996
1167
  if (!capabilities?.fork) throw new Error(`${binding.provider} ACP Agent does not support session/fork`);
1168
+ const sessionOptions = acpSessionRequestOptions({
1169
+ additionalDirectories: options.additionalDirectories ?? binding.sessionRequestOptions.additionalDirectories,
1170
+ mcpServers: options.mcpServers ?? binding.sessionRequestOptions.mcpServers,
1171
+ }, options.cwd || binding.cwd);
997
1172
  return withTimeout(binding.connection.unstable_forkSession({
998
1173
  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: [],
1174
+ ...sessionOptions,
1004
1175
  }), this.sessionSetupTimeoutMs, 'ACP session/fork');
1005
1176
  }
1006
1177
 
@@ -1186,6 +1357,7 @@ class AcpRuntime extends EventEmitter {
1186
1357
  error: binding.error,
1187
1358
  errorKind: binding.error ? acpErrorKind(binding.error) : '',
1188
1359
  stopReason: binding.stopReason,
1360
+ supportsSteer: binding.supportsSteer === true,
1189
1361
  protocolVersion: binding.initializeResponse?.protocolVersion || null,
1190
1362
  agentInfo: binding.initializeResponse?.agentInfo || null,
1191
1363
  capabilities: binding.initializeResponse?.agentCapabilities || {},
@@ -1376,6 +1548,7 @@ class AcpRuntime extends EventEmitter {
1376
1548
  error: binding.error,
1377
1549
  errorKind: binding.error ? acpErrorKind(binding.error) : '',
1378
1550
  stopReason: binding.stopReason,
1551
+ supportsSteer: binding.supportsSteer === true,
1379
1552
  pendingPermission: binding.pendingPermissions.values().next().value || null,
1380
1553
  pendingPermissions: [...binding.pendingPermissions.values()],
1381
1554
  pendingElicitation: binding.pendingElicitations.values().next().value || null,
@@ -1452,8 +1625,13 @@ class AcpRuntime extends EventEmitter {
1452
1625
  module.exports = {
1453
1626
  AcpRuntime,
1454
1627
  ADAPTER_VERSIONS,
1628
+ acpSessionRequestOptions,
1455
1629
  acpErrorKind,
1456
1630
  autoPermissionResponse,
1457
1631
  codexAcpEnvironment,
1632
+ promptContentForCapabilities,
1458
1633
  resolveAcpLaunch,
1634
+ supportsCodexSteer,
1635
+ isCodexSteerUnavailableError,
1636
+ CODEX_STEER_METHOD,
1459
1637
  };