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
@@ -37,16 +37,15 @@ const {
37
37
  providerSupportsRuntime,
38
38
  } = require('./provider-adapters');
39
39
  const { deriveTerminalStatus } = require('./terminal-status');
40
- const { CodexAppServerRuntime, normalizeCodexRuntimeMode } = require('./codex-app-server-runtime');
41
40
  const { JsonCliRuntime } = require('./json-cli-runtime');
42
- const { AcpRuntime } = require('./acp-runtime');
41
+ const { AcpRuntime, isCodexSteerUnavailableError } = require('./acp-runtime');
42
+ const { chatRuntimeForProvider, isChatMode } = require('./chat-runtime');
43
43
  const { acpToolChanges, acpToolDetail, acpToolReviewChanges, acpTranscriptToolEntry } = require('./acp-transcript');
44
44
  const {
45
45
  applyCodexTerminalProfile,
46
46
  codexTerminalProfileFromOutput,
47
47
  codexTerminalProfileFromPreview,
48
48
  } = require('./codex-terminal-profile');
49
- const { ensureCodexAppServerHome } = require('./codex-app-server-home');
50
49
  const {
51
50
  ensureAgentOrders,
52
51
  finiteOrder,
@@ -216,18 +215,6 @@ function agentHomeProviderForProgram(command) {
216
215
  return providerForProgram(agentProgramName(command));
217
216
  }
218
217
 
219
- function hasUsableCodexAppServerHome(metadata) {
220
- const homePath = String(runtimeBindingOf(metadata, 'app-server')?.homePath || '').trim();
221
- return Boolean(homePath) && fs.existsSync(homePath);
222
- }
223
-
224
- function shouldRecoverAsCodexAppServer(metadata) {
225
- const provider = metadata?.providerSessionProvider || metadata?.provider || '';
226
- return provider === 'codex'
227
- && runtimeKind(metadata) === 'app-server'
228
- && hasUsableCodexAppServerHome(metadata);
229
- }
230
-
231
218
  function isJsonCliAgent(agent) {
232
219
  return runtimeKind(agent) === 'json';
233
220
  }
@@ -236,6 +223,19 @@ function isAcpAgent(agent) {
236
223
  return runtimeKind(agent) === 'acp';
237
224
  }
238
225
 
226
+ function normalizedComposerPrompt(message) {
227
+ const prompt = Array.isArray(message) ? message : [{ type: 'text', text: String(message || '') }];
228
+ const text = prompt
229
+ .filter(content => content?.type === 'text')
230
+ .map(content => String(content.text || ''))
231
+ .join('')
232
+ .trim();
233
+ if (prompt.length === 0 || (!text && !prompt.some(content => content?.type !== 'text'))) {
234
+ throw new Error('Composer message is empty');
235
+ }
236
+ return prompt;
237
+ }
238
+
239
239
  function isShellProgram(command) {
240
240
  return ['bash', 'zsh', 'sh', 'fish'].includes(agentProgramName(command).toLowerCase());
241
241
  }
@@ -264,7 +264,6 @@ function activeCodexTerminalProfile(agent, previewText) {
264
264
  || agentHomeProviderForProgram(agent.forkCommand || agent.command || '');
265
265
  if (
266
266
  provider !== 'codex'
267
- || isCodexAppServerAgent(agent)
268
267
  || isJsonCliAgent(agent)
269
268
  || isAcpAgent(agent)
270
269
  || runtimeKind(agent) !== 'terminal'
@@ -500,10 +499,6 @@ function isRunningAgentRuntimeStatus(status) {
500
499
  return String(status || '').toLowerCase() === 'running';
501
500
  }
502
501
 
503
- function isCodexAppServerAgent(agent) {
504
- return runtimeKind(agent) === 'app-server';
505
- }
506
-
507
502
  function isLiveEngineSessionState(sessionState) {
508
503
  return Boolean(sessionState && sessionState.status && sessionState.status !== 'exited');
509
504
  }
@@ -567,8 +562,11 @@ class AgentManager extends EventEmitter {
567
562
  this.permissionRestartInFlight = new Map();
568
563
  this.runtimeRestartInFlight = new Map();
569
564
  this.codexSessionUnarchiveInFlight = new Map();
565
+ // Standard ACP session inputs may contain MCP credentials. Keep the live
566
+ // copy outside browser-facing Agent records; crash recovery persists it
567
+ // only through the private Farming session store.
568
+ this.acpSessionOptionsByKey = new Map();
570
569
  this.permissionRestartSuppressedAgentIds = new Set();
571
- this.codexAppServerRuntime = options.codexAppServerRuntime || new CodexAppServerRuntime();
572
570
  this.jsonCliRuntime = options.jsonCliRuntime || new JsonCliRuntime();
573
571
  this.acpRuntime = options.acpRuntime || new AcpRuntime({
574
572
  ...(this.configManager?.farmingDir ? { configDir: this.configManager.farmingDir } : {}),
@@ -612,7 +610,6 @@ class AgentManager extends EventEmitter {
612
610
  this.lastZombieSweepAt = 0;
613
611
  this.startHeartbeat();
614
612
  this.bindEngineEvents();
615
- this.bindCodexAppServerRuntimeEvents();
616
613
  this.bindJsonCliRuntimeEvents();
617
614
  this.bindAcpRuntimeEvents();
618
615
  if (this.configManager && this.configManager.farmingDir) {
@@ -649,13 +646,14 @@ class AgentManager extends EventEmitter {
649
646
 
650
647
  bindAcpRuntimeEvents() {
651
648
  if (!this.acpRuntime || typeof this.acpRuntime.on !== 'function') return;
652
- this.acpRuntime.on('agent-runtime', ({ agentId, state, error, sessionId, stopReason, pendingPermission, pendingPermissions, pendingElicitation, pendingElicitations, activeElicitations, updatedAt }) => {
649
+ this.acpRuntime.on('agent-runtime', ({ agentId, state, error, sessionId, stopReason, supportsSteer, pendingPermission, pendingPermissions, pendingElicitation, pendingElicitations, activeElicitations, updatedAt }) => {
653
650
  const agent = this.agents.get(agentId);
654
651
  const runtime = runtimeBindingOf(agent, 'acp');
655
652
  if (!runtime) return;
656
653
  runtime.state = state || '';
657
654
  runtime.error = error || '';
658
655
  runtime.stopReason = stopReason || '';
656
+ runtime.supportsSteer = supportsSteer === true;
659
657
  runtime.pendingPermission = pendingPermission || null;
660
658
  runtime.pendingPermissions = Array.isArray(pendingPermissions) ? pendingPermissions : [];
661
659
  runtime.pendingElicitation = pendingElicitation || null;
@@ -679,46 +677,6 @@ class AgentManager extends EventEmitter {
679
677
  });
680
678
  }
681
679
 
682
- bindCodexAppServerRuntimeEvents() {
683
- if (!this.codexAppServerRuntime || typeof this.codexAppServerRuntime.on !== 'function') return;
684
- this.codexAppServerRuntime.on('agent-runtime', ({ agentId, ...patch }) => {
685
- const agent = this.agents.get(agentId);
686
- const runtime = runtimeBindingOf(agent, 'app-server');
687
- if (!runtime) return;
688
-
689
- if (typeof patch.threadId === 'string' && patch.threadId) {
690
- runtime.threadId = patch.threadId;
691
- this.providerSessionService.bindConfirmed(
692
- agentId,
693
- agent.providerSessionProvider || 'codex',
694
- patch.threadId
695
- );
696
- }
697
- if (typeof patch.turnId === 'string') runtime.turnId = patch.turnId;
698
- if (typeof patch.state === 'string' && patch.state) runtime.state = patch.state;
699
- if (typeof patch.error === 'string') runtime.error = patch.error;
700
- if (typeof patch.pendingRequestId === 'string') runtime.pendingRequestId = patch.pendingRequestId;
701
- if (typeof patch.pendingRequestMethod === 'string') runtime.pendingRequestMethod = patch.pendingRequestMethod;
702
- if (Object.prototype.hasOwnProperty.call(patch, 'pendingRequest')) {
703
- runtime.pendingRequest = patch.pendingRequest || null;
704
- }
705
- if (Object.prototype.hasOwnProperty.call(patch, 'notice')) {
706
- runtime.notice = patch.notice || null;
707
- }
708
- if (Object.prototype.hasOwnProperty.call(patch, 'goal')) {
709
- runtime.goal = patch.goal || null;
710
- }
711
- if (typeof patch.transcriptUpdatedAt === 'string') {
712
- runtime.transcriptUpdatedAt = patch.transcriptUpdatedAt;
713
- }
714
-
715
- if (patch.state === 'working' || patch.state === 'waiting-for-input') {
716
- this.lastActivity.set(agentId, Date.now());
717
- }
718
- this.emit('update');
719
- });
720
- }
721
-
722
680
  bindEngineEvents() {
723
681
  this.engineBridge.on('session-started', ({
724
682
  sessionId,
@@ -1055,12 +1013,6 @@ class AgentManager extends EventEmitter {
1055
1013
  clearPendingTerminalStartSyncCut(agent);
1056
1014
  if (this.permissionRestartSuppressedAgentIds.has(sessionId)) return;
1057
1015
 
1058
- // A fresh App Server thread deliberately has no CLI/PTy observer until
1059
- // its first turn has completed and become resumable. Native host
1060
- // cleanup can still report an old/missing session id while that gap is
1061
- // open; it is not an exit of the App Server Agent.
1062
- if (isCodexAppServerAgent(agent)) return;
1063
-
1064
1016
  if (stateProofAvailable === false) {
1065
1017
  this.providerSessionService.stop(sessionId);
1066
1018
  agent.status = 'dead';
@@ -1215,7 +1167,6 @@ class AgentManager extends EventEmitter {
1215
1167
  await this.restoreTerminalSessionsAfterRuntimeRotation(persistedRecords, runtimeRotations);
1216
1168
  }
1217
1169
 
1218
- await this.recoverCodexAppServerSessions();
1219
1170
  await this.recoverAcpSessions();
1220
1171
  }
1221
1172
 
@@ -1450,7 +1401,7 @@ class AgentManager extends EventEmitter {
1450
1401
  for (const record of records) {
1451
1402
  const agentId = String(record.runtimeAgentId || '').trim();
1452
1403
  const provider = String(record.providerSessionProvider || record.provider || '').trim();
1453
- const sessionId = String(record.providerSessionId || '').trim();
1404
+ const sessionId = String(record.providerSessionId || record.codexAppServerThreadId || '').trim();
1454
1405
  if (!agentId || !sessionId || !providerSupportsRuntime(provider, 'acp')) continue;
1455
1406
  const agent = this.agents.get(agentId);
1456
1407
  if (!agent) {
@@ -1469,7 +1420,7 @@ class AgentManager extends EventEmitter {
1469
1420
  for (const record of records) {
1470
1421
  const agentId = String(record.runtimeAgentId || '').trim();
1471
1422
  const provider = String(record.providerSessionProvider || record.provider || '').trim();
1472
- const sessionId = String(record.providerSessionId || '').trim();
1423
+ const sessionId = String(record.providerSessionId || record.codexAppServerThreadId || '').trim();
1473
1424
  const agent = this.agents.get(agentId);
1474
1425
  if (!agent || !sessionId || !providerSupportsRuntime(provider, 'acp')) continue;
1475
1426
  try {
@@ -1495,8 +1446,23 @@ class AgentManager extends EventEmitter {
1495
1446
  model: 'config',
1496
1447
  reasoningEffort: 'config',
1497
1448
  serviceTier: 'config',
1449
+ additionalDirectories: Array.isArray(record.acpAdditionalDirectories) ? record.acpAdditionalDirectories : [],
1450
+ mcpServers: Array.isArray(record.acpMcpServers) ? record.acpMcpServers : [],
1498
1451
  });
1499
1452
  agent.providerSessionId = prepared.sessionId;
1453
+ agent.providerSessionKey = mainPageAgentSessionKey(
1454
+ provider,
1455
+ prepared.sessionId,
1456
+ agent.providerHomeId || record.providerHomeId || 'default'
1457
+ );
1458
+ this.acpSessionOptionsByKey.set(agent.providerSessionKey, {
1459
+ additionalDirectories: Array.isArray(record.acpAdditionalDirectories)
1460
+ ? [...record.acpAdditionalDirectories]
1461
+ : [],
1462
+ mcpServers: Array.isArray(record.acpMcpServers)
1463
+ ? JSON.parse(JSON.stringify(record.acpMcpServers))
1464
+ : [],
1465
+ });
1500
1466
  agent.providerSessionTemporary = false;
1501
1467
  agent.providerSessionSource = `acp-${prepared.historyMode}`;
1502
1468
  const runtime = replaceRuntimeBinding(agent, 'acp', runtimeBindingOf(agent, 'acp'));
@@ -1520,82 +1486,6 @@ class AgentManager extends EventEmitter {
1520
1486
  this.emit('update');
1521
1487
  }
1522
1488
 
1523
- async recoverCodexAppServerSessions() {
1524
- if (!this.codexAppServerRuntime || !this.configManager || typeof this.configManager.listAgentSessionRecords !== 'function') return;
1525
- const records = this.configManager.listAgentSessionRecords();
1526
- for (const record of records) {
1527
- if (!record || record.archived === true || runtimeKind(record) !== 'app-server') continue;
1528
- const agentId = String(record.runtimeAgentId || '').trim();
1529
- const threadId = String(record.codexAppServerThreadId || record.providerSessionId || '').trim();
1530
- const home = String(record.codexAppServerHomePath || '').trim();
1531
- if (!agentId || !threadId || !home || !fs.existsSync(home)) {
1532
- if (record.provider === 'codex' || record.providerSessionProvider === 'codex') {
1533
- let agent = this.agents.get(agentId);
1534
- if (!agent && agentId) {
1535
- agent = this.recoveredAgentRecord(agentId, record.engine || 'native', record, { status: 'running' });
1536
- agent.persistentSessionId = record.id || '';
1537
- }
1538
- if (agent) {
1539
- ensureAgentOrders(agent, Array.from(this.agents.values()));
1540
- replaceRuntimeBinding(agent, 'terminal');
1541
- this.ensurePersistentAgentSession(agent);
1542
- this.updateEngineProviderSessionMetadata(agent);
1543
- }
1544
- }
1545
- continue;
1546
- }
1547
- let agent = this.agents.get(agentId);
1548
- if (!agent) {
1549
- agent = this.recoveredAgentRecord(agentId, record.engine || 'native', record, { status: 'running' });
1550
- ensureAgentOrders(agent, Array.from(this.agents.values()));
1551
- agent.persistentSessionId = record.id || '';
1552
- agent.engineStarted = false;
1553
- this.agents.set(agentId, agent);
1554
- void this.refreshAgentWorktree(agentId);
1555
- this.lastActivity.set(agentId, Date.now());
1556
- }
1557
- try {
1558
- // Remove a CLI observer recovered from pre-split metadata before the
1559
- // structured runtime reconnects to this App Server Agent.
1560
- await this.killRecoveredEngineSession({ engineName: agent.engineName }, record, agentId);
1561
- const executable = resolveCompatibleCodexExecutable('').path || resolveAgentExecutable('codex') || 'codex';
1562
- const binding = await this.codexAppServerRuntime.reattachAgent({
1563
- agentId,
1564
- threadId,
1565
- codexHome: home,
1566
- executable,
1567
- env: this.buildAgentEnv(agentId, agent),
1568
- cwd: agent.cwd,
1569
- workspaceRoot: effectiveAgentWorkspaceRoot(agent),
1570
- approvalMode: agent.launchPermissionMode || 'approve',
1571
- });
1572
- const runtime = runtimeBindingOf(agent, 'app-server');
1573
- runtime.threadId = binding.threadId;
1574
- runtime.state = 'idle';
1575
- runtime.error = '';
1576
- // App Server owns this runtime. It deliberately has no CLI/PTy
1577
- // observer, so recovery cannot create a second reader or writer.
1578
- agent.status = 'running';
1579
- agent.engineStatus = 'running';
1580
- agent.engineStarted = false;
1581
- runtime.observerDeferred = false;
1582
- agent.exitedAt = null;
1583
- this.providerSessionService.activate(agentId);
1584
- this.ensurePersistentAgentSession(agent);
1585
- } catch (error) {
1586
- const runtime = runtimeBindingOf(agent, 'app-server');
1587
- runtime.error = `Codex App Server recovery failed: ${error && (error.message || error)}`;
1588
- runtime.state = 'error';
1589
- agent.status = 'stopped';
1590
- agent.engineStatus = 'stopped';
1591
- agent.engineStarted = false;
1592
- agent.exitedAt = Date.now();
1593
- this.ensurePersistentAgentSession(agent);
1594
- }
1595
- }
1596
- this.emit('update');
1597
- }
1598
-
1599
1489
  async killRecoveredEngineSession(entry, metadata, agentId) {
1600
1490
  if (!this.engineBridge || typeof this.engineBridge.killSession !== 'function') return;
1601
1491
  const engineName = entry.engineName || metadata.engineName || 'native';
@@ -1613,16 +1503,8 @@ class AgentManager extends EventEmitter {
1613
1503
  recoveredAgentRecord(agentId, engineName, metadata, state) {
1614
1504
  const wantsMain = metadata.wantsMain === true;
1615
1505
  const providerSessionProvider = metadata.providerSessionProvider || metadata.provider || '';
1616
- const recoverCodexAppServer = shouldRecoverAsCodexAppServer(metadata);
1617
- const persistedRuntimeKind = runtimeKind(metadata);
1618
- const recoveredRuntimeKind = recoverCodexAppServer
1619
- ? 'app-server'
1620
- : (persistedRuntimeKind === 'app-server' ? 'terminal' : persistedRuntimeKind);
1621
- const runtimeBinding = runtimeBindingFor(recoveredRuntimeKind, {
1622
- ...metadata,
1623
- codexAppServerThreadId: metadata.codexAppServerThreadId || metadata.providerSessionId || '',
1624
- codexCliObserverDeferred: false,
1625
- });
1506
+ const providerSessionId = metadata.providerSessionId || metadata.codexAppServerThreadId || '';
1507
+ const runtimeBinding = runtimeBindingFor(runtimeKind(metadata), metadata);
1626
1508
  return {
1627
1509
  id: agentId,
1628
1510
  command: metadata.forkCommand || metadata.command || '',
@@ -1648,23 +1530,21 @@ class AgentManager extends EventEmitter {
1648
1530
  providerSessionProvider,
1649
1531
  providerHomeId: metadata.providerHomeId || '',
1650
1532
  providerHomePath: metadata.providerHomePath || '',
1651
- providerSessionId: metadata.providerSessionId || '',
1533
+ providerSessionId,
1652
1534
  providerSessionKey: metadata.providerSessionKey || (
1653
- providerSessionProvider && metadata.providerSessionId
1654
- ? mainPageAgentSessionKey(providerSessionProvider, metadata.providerSessionId, metadata.providerHomeId || '')
1535
+ providerSessionProvider && providerSessionId
1536
+ ? mainPageAgentSessionKey(providerSessionProvider, providerSessionId, metadata.providerHomeId || '')
1655
1537
  : ''
1656
1538
  ),
1657
- providerSessionTemporary: metadata.providerSessionTemporary === true || isTemporaryProviderSessionId(metadata.providerSessionId),
1539
+ providerSessionTemporary: metadata.providerSessionTemporary === true || isTemporaryProviderSessionId(providerSessionId),
1658
1540
  providerSessionSource: metadata.providerSessionSource || '',
1659
1541
  providerSessionResolvedAt: metadata.providerSessionResolvedAt || null,
1660
1542
  providerSessionTitle: metadata.providerSessionTitle || '',
1661
1543
  providerSessionWorkspace: metadata.providerSessionWorkspace || '',
1662
1544
  terminalInputReceived: metadata.terminalInputReceived === true,
1663
- // Older persisted sessions predate App Server mode. Also, a Codex
1664
- // App Server record without its isolated runtime home is not actually
1665
- // attachable; recover it as terminal-owned CLI instead of leaving the
1666
- // UI in a split state where the pane is terminal but Composer sends to
1667
- // App Server.
1545
+ // Legacy App Server records normalize to ACP at the runtime-binding
1546
+ // boundary. Codex thread ids are valid ACP session ids, so the existing
1547
+ // conversation remains recoverable without restarting App Server.
1668
1548
  runtimeBinding,
1669
1549
  forkedFromProviderSessionId: metadata.forkedFromProviderSessionId || '',
1670
1550
  restartedFromAgentId: metadata.restartedFromAgentId || '',
@@ -1735,7 +1615,6 @@ class AgentManager extends EventEmitter {
1735
1615
 
1736
1616
  shouldDeferMissingEngineSession(agent) {
1737
1617
  if (!agent || !isRunningAgentRuntimeStatus(agent.status)) return false;
1738
- if (isCodexAppServerAgent(agent)) return true;
1739
1618
  if (agent.engineStarted === false) return true;
1740
1619
  const startedAt = Number(agent.startedAt);
1741
1620
  return Number.isFinite(startedAt) && Date.now() - startedAt < MISSING_ENGINE_SESSION_STARTUP_GRACE_MS;
@@ -1767,7 +1646,16 @@ class AgentManager extends EventEmitter {
1767
1646
  if (!agent || !this.configManager || typeof this.configManager.ensureAgentSessionRecord !== 'function') {
1768
1647
  return '';
1769
1648
  }
1770
- const persistentSessionId = this.configManager.ensureAgentSessionRecord(agent, patch);
1649
+ const sessionOptions = agent.providerSessionKey
1650
+ ? this.acpSessionOptionsByKey.get(agent.providerSessionKey)
1651
+ : null;
1652
+ const persistentSessionId = this.configManager.ensureAgentSessionRecord(agent, {
1653
+ ...(sessionOptions ? {
1654
+ acpAdditionalDirectories: [...sessionOptions.additionalDirectories],
1655
+ acpMcpServers: JSON.parse(JSON.stringify(sessionOptions.mcpServers)),
1656
+ } : {}),
1657
+ ...patch,
1658
+ });
1771
1659
  if (persistentSessionId && !agent.persistentSessionId) {
1772
1660
  agent.persistentSessionId = persistentSessionId;
1773
1661
  }
@@ -2180,11 +2068,10 @@ class AgentManager extends EventEmitter {
2180
2068
  }
2181
2069
  if (agent.providerHomePath) {
2182
2070
  const provider = agent.providerSessionProvider || agentHomeProviderForProgram(agent.forkCommand || agent.command);
2183
- const appServerHome = runtimeBindingOf(agent, 'app-server')?.homePath || '';
2184
2071
  applyProviderHomeEnvironment(
2185
2072
  env,
2186
2073
  provider,
2187
- provider === 'codex' ? (appServerHome || agent.providerHomePath) : agent.providerHomePath
2074
+ agent.providerHomePath
2188
2075
  );
2189
2076
  }
2190
2077
 
@@ -2306,9 +2193,7 @@ class AgentManager extends EventEmitter {
2306
2193
  this.resizeDrains.clear();
2307
2194
  this.inputQueues.clear();
2308
2195
  this.codexTerminalProfileQueues.clear();
2309
- if (this.codexAppServerRuntime && typeof this.codexAppServerRuntime.dispose === 'function') {
2310
- this.codexAppServerRuntime.dispose();
2311
- }
2196
+ this.acpSessionOptionsByKey.clear();
2312
2197
  if (this.jsonCliRuntime) {
2313
2198
  for (const agentId of this.agents.keys()) this.jsonCliRuntime.unregisterAgent(agentId);
2314
2199
  }
@@ -2629,56 +2514,35 @@ class AgentManager extends EventEmitter {
2629
2514
  resolvedProviderHomeId = defaultCodexHome.id || 'default';
2630
2515
  }
2631
2516
  }
2632
- const requestedAgentRuntimeMode = ['json', 'acp'].includes(options.agentRuntimeMode)
2517
+ const requestedAgentRuntimeMode = ['json', 'acp', 'chat'].includes(options.agentRuntimeMode)
2633
2518
  ? options.agentRuntimeMode
2634
2519
  : 'terminal';
2635
2520
  // A fresh structured runtime does not need a provider CLI resume id yet:
2636
2521
  // ACP/JSON creates the provider session and writes the resulting id back
2637
2522
  // after connecting. OpenCode is the important case here because its
2638
2523
  // terminal CLI does not accept a pre-generated id for a fresh session.
2639
- const structuredRuntimeProvider = ['json', 'acp'].includes(requestedAgentRuntimeMode)
2524
+ const structuredRuntimeProvider = ['json', 'acp', 'chat'].includes(requestedAgentRuntimeMode)
2640
2525
  ? homeProvider
2641
2526
  : providerSessionPlan.provider;
2642
- const requestedCodexRuntimeMode = options.codexRuntimeMode === 'app-server' || options.codexRuntimeMode === 'cli'
2643
- ? options.codexRuntimeMode
2644
- : (this.configManager && typeof this.configManager.getCodexRuntimeMode === 'function'
2645
- ? this.configManager.getCodexRuntimeMode()
2646
- : 'cli');
2647
- const useCodexAppServer = providerSessionPlan.provider === 'codex'
2648
- && this.codexAppServerRuntime
2649
- && options.agentRuntimeMode !== 'acp'
2650
- // The deterministic browser/server fixtures are terminal-only shims,
2651
- // not an implementation of the Codex App Server protocol. App Server
2652
- // behavior has its own mock runtime test; keep these legacy PTY tests
2653
- // on the path they are designed to exercise.
2654
- && process.env.FARMING_E2E_FAKE_EXECUTABLES !== '1'
2655
- && normalizeCodexRuntimeMode(requestedCodexRuntimeMode) === 'app-server';
2527
+ // `chat` is the only browser-facing structured-runtime request. Treat the
2528
+ // retired Codex ACP launch request as that intent too, so an old client
2529
+ // cannot create a second Codex Chat implementation.
2530
+ const requestedChatRuntime = isChatMode(requestedAgentRuntimeMode)
2531
+ || (requestedAgentRuntimeMode === 'acp' && homeProvider === 'codex');
2532
+ const resolvedChatRuntime = requestedChatRuntime
2533
+ ? chatRuntimeForProvider(homeProvider)
2534
+ : (requestedAgentRuntimeMode === 'acp' ? 'acp' : '');
2656
2535
  const useJsonCli = requestedAgentRuntimeMode === 'json'
2657
2536
  && providerSupportsRuntime(structuredRuntimeProvider, 'json')
2658
- && !useCodexAppServer
2659
2537
  && process.env.FARMING_E2E_FAKE_EXECUTABLES !== '1';
2660
- const useAcp = requestedAgentRuntimeMode === 'acp'
2538
+ const useAcp = resolvedChatRuntime === 'acp'
2661
2539
  && providerSupportsRuntime(structuredRuntimeProvider, 'acp')
2662
- && !useCodexAppServer
2663
2540
  && (
2664
2541
  process.env.FARMING_E2E_FAKE_EXECUTABLES !== '1'
2665
2542
  || process.env.FARMING_E2E_FAKE_ACP_AGENT === '1'
2666
2543
  );
2667
2544
  const acpGeneratedFreshSession = useAcp
2668
2545
  && isFreshAcpSessionSource(structuredRuntimeProvider, providerSessionPlan.source);
2669
- let codexAppServerHomePath = '';
2670
- if (useCodexAppServer) {
2671
- try {
2672
- codexAppServerHomePath = ensureCodexAppServerHome({
2673
- configDir: this.configManager && this.configManager.farmingDir,
2674
- agentId,
2675
- sourceHome: providerHomePath || path.join(os.homedir(), '.codex'),
2676
- });
2677
- } catch (error) {
2678
- if (callback) callback(null, error.message);
2679
- return null;
2680
- }
2681
- }
2682
2546
  const agentRecord = {
2683
2547
  id: agentId,
2684
2548
  command: launch.program,
@@ -2712,16 +2576,14 @@ class AgentManager extends EventEmitter {
2712
2576
  providerSessionTitle: typeof options.providerSessionTitle === 'string' ? options.providerSessionTitle.trim().slice(0, 160) : '',
2713
2577
  providerSessionWorkspace: '',
2714
2578
  terminalInputReceived: false,
2715
- runtimeBinding: useCodexAppServer
2716
- ? runtimeBindingFor('app-server', { state: 'connecting', homePath: codexAppServerHomePath })
2717
- : (useAcp
2718
- ? runtimeBindingFor('acp', { state: 'connecting' })
2719
- : (useJsonCli
2720
- ? runtimeBindingFor('json', {
2721
- state: 'idle',
2722
- events: Array.isArray(options.jsonCliEvents) ? options.jsonCliEvents : [],
2723
- })
2724
- : runtimeBindingFor('terminal'))),
2579
+ runtimeBinding: useAcp
2580
+ ? runtimeBindingFor('acp', { state: 'connecting' })
2581
+ : (useJsonCli
2582
+ ? runtimeBindingFor('json', {
2583
+ state: 'idle',
2584
+ events: Array.isArray(options.jsonCliEvents) ? options.jsonCliEvents : [],
2585
+ })
2586
+ : runtimeBindingFor('terminal')),
2725
2587
  forkedFromProviderSessionId: providerSessionPlan.forkedFromProviderSessionId || '',
2726
2588
  restartedFromAgentId: typeof options.restartedFromAgentId === 'string' ? options.restartedFromAgentId : '',
2727
2589
  restartedFromAgentIds: Array.isArray(options.restartedFromAgentIds)
@@ -2777,50 +2639,6 @@ class AgentManager extends EventEmitter {
2777
2639
  this.emit('update');
2778
2640
 
2779
2641
  try {
2780
- if (useCodexAppServer) {
2781
- try {
2782
- const appServer = runtimeBindingOf(agentRecord, 'app-server');
2783
- const prepared = await this.codexAppServerRuntime.prepareAgent({
2784
- agentId,
2785
- codexHome: appServer.homePath,
2786
- executable: spawnProgram,
2787
- env: this.buildAgentEnv(agentId, agentRecord),
2788
- cwd: workspace,
2789
- workspaceRoot: projectWorkspace || workspace,
2790
- resumeThreadId: agentRecord.providerSessionTemporary ? '' : agentRecord.providerSessionId,
2791
- temporaryThreadId: agentRecord.providerSessionTemporary ? agentRecord.providerSessionId : '',
2792
- approvalMode: agentRecord.launchPermissionMode || (
2793
- this.configManager && this.configManager.getCodexApprovalMode
2794
- ? this.configManager.getCodexApprovalMode()
2795
- : 'approve'
2796
- ),
2797
- model: codexModel,
2798
- reasoningEffort: codexReasoningEffort,
2799
- serviceTier: codexServiceTier,
2800
- developerInstructions: wantsMain ? renderMainAgentBootstrap() : '',
2801
- });
2802
- agentRecord.providerSessionId = prepared.threadId;
2803
- agentRecord.providerSessionKey = mainPageAgentSessionKey(
2804
- 'codex',
2805
- prepared.threadId,
2806
- agentRecord.providerHomeId || 'default'
2807
- );
2808
- agentRecord.providerSessionTemporary = false;
2809
- agentRecord.providerSessionSource = prepared.resumed ? 'app-server-resume' : 'app-server-start';
2810
- agentRecord.providerSessionResolvedAt = Date.now();
2811
- appServer.state = 'idle';
2812
- appServer.endpoint = prepared.endpoint;
2813
- appServer.threadId = prepared.threadId;
2814
- appServer.error = '';
2815
- // App Server mode has no embedded CLI observer. Its chat and
2816
- // lifecycle both come from the structured App Server connection.
2817
- appServer.observerDeferred = false;
2818
- } catch (error) {
2819
- const reason = error && error.message ? error.message : String(error);
2820
- throw new Error(`Codex App Server failed to start: ${reason}`, { cause: error });
2821
- }
2822
- }
2823
-
2824
2642
  if (useJsonCli) {
2825
2643
  const jsonRuntime = runtimeBindingOf(agentRecord, 'json');
2826
2644
  this.jsonCliRuntime.registerAgent({
@@ -2838,6 +2656,22 @@ class AgentManager extends EventEmitter {
2838
2656
 
2839
2657
  if (useAcp) {
2840
2658
  const acpRuntime = runtimeBindingOf(agentRecord, 'acp');
2659
+ const sessionOptionsKey = agentRecord.providerSessionId && !agentRecord.providerSessionTemporary
2660
+ ? mainPageAgentSessionKey(
2661
+ structuredRuntimeProvider,
2662
+ agentRecord.providerSessionId,
2663
+ agentRecord.providerHomeId || 'default'
2664
+ )
2665
+ : '';
2666
+ const rememberedSessionOptions = sessionOptionsKey
2667
+ ? this.acpSessionOptionsByKey.get(sessionOptionsKey) || {}
2668
+ : {};
2669
+ const additionalDirectories = Array.isArray(options.additionalDirectories)
2670
+ ? options.additionalDirectories
2671
+ : rememberedSessionOptions.additionalDirectories || [];
2672
+ const mcpServers = Array.isArray(options.mcpServers)
2673
+ ? options.mcpServers
2674
+ : rememberedSessionOptions.mcpServers || [];
2841
2675
  const prepared = await this.acpRuntime.prepareAgent({
2842
2676
  agentId,
2843
2677
  provider: structuredRuntimeProvider,
@@ -2855,6 +2689,8 @@ class AgentManager extends EventEmitter {
2855
2689
  model: codexModel,
2856
2690
  reasoningEffort: codexReasoningEffort,
2857
2691
  serviceTier: codexServiceTier,
2692
+ additionalDirectories,
2693
+ mcpServers,
2858
2694
  });
2859
2695
  agentRecord.providerSessionId = prepared.sessionId;
2860
2696
  agentRecord.providerSessionKey = mainPageAgentSessionKey(
@@ -2865,12 +2701,23 @@ class AgentManager extends EventEmitter {
2865
2701
  agentRecord.providerSessionTemporary = false;
2866
2702
  agentRecord.providerSessionSource = `acp-${prepared.historyMode}`;
2867
2703
  agentRecord.providerSessionResolvedAt = Date.now();
2704
+ let normalizedSessionOptions = { additionalDirectories, mcpServers };
2705
+ try {
2706
+ normalizedSessionOptions = this.acpRuntime.getSessionRequestOptions(agentId);
2707
+ } catch {
2708
+ // prepareAgent already validated the request; retain the caller copy
2709
+ // only for custom runtimes that do not expose the normalized scope.
2710
+ }
2711
+ this.acpSessionOptionsByKey.set(agentRecord.providerSessionKey, {
2712
+ additionalDirectories: [...normalizedSessionOptions.additionalDirectories],
2713
+ mcpServers: JSON.parse(JSON.stringify(normalizedSessionOptions.mcpServers)),
2714
+ });
2868
2715
  acpRuntime.state = 'idle';
2869
2716
  acpRuntime.error = '';
2870
2717
  }
2871
2718
 
2872
2719
  agentRecord.persistentSessionId = this.ensurePersistentAgentSession(agentRecord);
2873
- if (!useCodexAppServer && !useJsonCli && !useAcp) {
2720
+ if (!useJsonCli && !useAcp) {
2874
2721
  const engineLaunch = {
2875
2722
  command: spawnProgram,
2876
2723
  args,
@@ -2919,9 +2766,6 @@ class AgentManager extends EventEmitter {
2919
2766
  this.agentUsageRateCache.delete(agentId);
2920
2767
  this.lastResizeByAgent.delete(agentId);
2921
2768
  this.providerSessionService.stop(agentId);
2922
- if (this.codexAppServerRuntime && typeof this.codexAppServerRuntime.unregisterAgent === 'function') {
2923
- this.codexAppServerRuntime.unregisterAgent(agentId);
2924
- }
2925
2769
  if (this.acpRuntime) this.acpRuntime.unregisterAgent(agentId);
2926
2770
 
2927
2771
  if (this.mainAgentId === agentId) {
@@ -2980,41 +2824,14 @@ class AgentManager extends EventEmitter {
2980
2824
  return this.enqueueInputOperation(agentId, () => this.sendInputNow(agentId, input, options));
2981
2825
  }
2982
2826
 
2983
- codexAppServerOptionsForAgent(agent, message = '') {
2984
- const codexResolution = resolveCompatibleCodexExecutable(agent.requiredCliVersion || '');
2985
- if (!codexResolution.compatible) {
2986
- throw new Error(codexResolution.error || 'Codex CLI is not compatible with App Server mode');
2987
- }
2988
-
2989
- const runtime = runtimeBindingOf(agent, 'app-server');
2990
- if (!runtime) throw new Error('Agent is not using the Codex App Server runtime');
2991
- return {
2992
- agentId: agent.id,
2993
- codexHome: runtime.homePath,
2994
- executable: codexResolution.path || resolveAgentExecutable(agent.command) || agent.command,
2995
- env: this.buildAgentEnv(agent.id, agent),
2996
- cwd: agent.cwd,
2997
- workspaceRoot: effectiveAgentWorkspaceRoot(agent),
2998
- threadId: runtime.threadId || agent.providerSessionId || '',
2999
- approvalMode: agent.launchPermissionMode || (
3000
- this.configManager && this.configManager.getCodexApprovalMode
3001
- ? this.configManager.getCodexApprovalMode()
3002
- : 'approve'
3003
- ),
3004
- model: this.configManager && this.configManager.getCodexModel
3005
- ? this.configManager.getCodexModel()
3006
- : 'gpt-5.5',
3007
- reasoningEffort: this.configManager && this.configManager.getCodexReasoningEffort
3008
- ? this.configManager.getCodexReasoningEffort()
3009
- : 'xhigh',
3010
- serviceTier: this.configManager && this.configManager.getCodexServiceTier
3011
- ? this.configManager.getCodexServiceTier()
3012
- : 'default',
3013
- message,
3014
- };
3015
- }
3016
-
3017
2827
  async sendComposerMessage(agentId, message) {
2828
+ if (this.acpRuntime.canSteer?.(agentId)) {
2829
+ try {
2830
+ return await this.sendAcpSteerNow(agentId, message);
2831
+ } catch (error) {
2832
+ if (!isCodexSteerUnavailableError(error)) throw error;
2833
+ }
2834
+ }
3018
2835
  return this.enqueueInputOperation(agentId, () => this.sendComposerMessageNow(agentId, message));
3019
2836
  }
3020
2837
 
@@ -3045,7 +2862,6 @@ class AgentManager extends EventEmitter {
3045
2862
  if (!agent) throw new Error('Agent not found');
3046
2863
  if (
3047
2864
  agentProgramName(agent.command).toLowerCase() !== 'codex'
3048
- || isCodexAppServerAgent(agent)
3049
2865
  || isJsonCliAgent(agent)
3050
2866
  || isAcpAgent(agent)
3051
2867
  || runtimeKind(agent) !== 'terminal'
@@ -3113,34 +2929,12 @@ class AgentManager extends EventEmitter {
3113
2929
  async sendComposerMessageNow(agentId, message) {
3114
2930
  const agent = this.agents.get(agentId);
3115
2931
  if (!agent) throw new Error('Agent not found');
3116
- const prompt = Array.isArray(message) ? message : [{ type: 'text', text: String(message || '') }];
2932
+ const prompt = normalizedComposerPrompt(message);
3117
2933
  const text = prompt
3118
2934
  .filter(content => content?.type === 'text')
3119
2935
  .map(content => String(content.text || ''))
3120
2936
  .join('')
3121
2937
  .trim();
3122
- if (prompt.length === 0 || (!text && !prompt.some(content => content?.type !== 'text'))) {
3123
- throw new Error('Composer message is empty');
3124
- }
3125
-
3126
- const appServer = runtimeBindingOf(agent, 'app-server');
3127
- if (
3128
- agent.providerSessionProvider === 'codex'
3129
- && appServer
3130
- && this.codexAppServerRuntime
3131
- && typeof this.codexAppServerRuntime.submitComposerMessage === 'function'
3132
- ) {
3133
- const result = await this.codexAppServerRuntime.submitComposerMessage(
3134
- this.codexAppServerOptionsForAgent(agent, text)
3135
- );
3136
- appServer.threadId = result.threadId;
3137
- appServer.turnId = result.turnId;
3138
- appServer.state = 'working';
3139
- appServer.error = '';
3140
- this.lastActivity.set(agentId, Date.now());
3141
- this.emit('update');
3142
- return result;
3143
- }
3144
2938
 
3145
2939
  if (isJsonCliAgent(agent)) {
3146
2940
  const result = await this.jsonCliRuntime.submitComposerMessage(agentId, text, {
@@ -3165,62 +2959,14 @@ class AgentManager extends EventEmitter {
3165
2959
  return { kind: 'terminal' };
3166
2960
  }
3167
2961
 
3168
- respondToCodexAppServerRequest(agentId, requestId, result, options = {}) {
2962
+ async sendAcpSteerNow(agentId, message) {
3169
2963
  const agent = this.agents.get(agentId);
3170
2964
  if (!agent) throw new Error('Agent not found');
3171
- const runtime = runtimeBindingOf(agent, 'app-server');
3172
- if (agent.providerSessionProvider !== 'codex' || !runtime) {
3173
- throw new Error('This Agent is not using the Codex App Server runtime');
3174
- }
3175
- const resolvedRequestId = String(requestId || '');
3176
- if (!resolvedRequestId || resolvedRequestId !== runtime.pendingRequestId) {
3177
- throw new Error('Codex App Server request is no longer pending');
3178
- }
3179
- if (!this.codexAppServerRuntime) throw new Error('Codex App Server runtime is unavailable');
3180
-
3181
- const response = options.reject === true
3182
- ? this.codexAppServerRuntime.rejectAgentServerRequest(
3183
- agentId,
3184
- resolvedRequestId,
3185
- { code: -32000, message: String(options.reason || 'Rejected by Farming') }
3186
- )
3187
- : this.codexAppServerRuntime.resolveAgentServerRequest(agentId, resolvedRequestId, result);
3188
- runtime.pendingRequestId = '';
3189
- runtime.pendingRequestMethod = '';
3190
- runtime.pendingRequest = null;
3191
- runtime.state = runtime.turnId ? 'working' : 'idle';
3192
- this.emit('update');
3193
- return response;
3194
- }
3195
-
3196
- assertCodexAppServerGoalAgent(agentId) {
3197
- const agent = this.agents.get(agentId);
3198
- if (!agent) throw new Error('Agent not found');
3199
- const runtime = runtimeBindingOf(agent, 'app-server');
3200
- if (agent.providerSessionProvider !== 'codex' || !runtime) {
3201
- throw new Error('This Agent is not using the Codex App Server runtime');
3202
- }
3203
- if (!this.codexAppServerRuntime) throw new Error('Codex App Server runtime is unavailable');
3204
- if (!(runtime.threadId || agent.providerSessionId)) {
3205
- throw new Error('Codex App Server thread id is not available yet');
3206
- }
3207
- return agent;
3208
- }
3209
-
3210
- async getCodexAppServerGoal(agentId) {
3211
- const agent = this.assertCodexAppServerGoalAgent(agentId);
3212
- const goal = await this.codexAppServerRuntime.getAgentGoal(this.codexAppServerOptionsForAgent(agent));
3213
- runtimeBindingOf(agent, 'app-server').goal = goal || null;
3214
- this.emit('update');
3215
- return goal || null;
3216
- }
3217
-
3218
- getCodexAppServerTranscript(agentId, options = {}) {
3219
- const agent = this.assertCodexAppServerGoalAgent(agentId);
3220
- if (!this.codexAppServerRuntime || typeof this.codexAppServerRuntime.getAgentTranscript !== 'function') {
3221
- throw new Error('Codex App Server transcript is unavailable');
3222
- }
3223
- return this.codexAppServerRuntime.getAgentTranscript(agent.id, options);
2965
+ if (!isAcpAgent(agent)) throw new Error('Agent is not using ACP Chat');
2966
+ const prompt = normalizedComposerPrompt(message);
2967
+ const result = await this.acpRuntime.steer(agentId, prompt);
2968
+ this.ensurePersistentAgentSession(agent);
2969
+ return { kind: 'acp', steered: true, ...result };
3224
2970
  }
3225
2971
 
3226
2972
  getJsonCliTranscript(agentId, options = {}) {
@@ -3337,6 +3083,11 @@ class AgentManager extends EventEmitter {
3337
3083
  return this.acpRuntime.authenticate(agentId, methodId);
3338
3084
  }
3339
3085
 
3086
+ logoutAcpAgent(agentId) {
3087
+ this.getAcpSession(agentId);
3088
+ return this.acpRuntime.logout(agentId);
3089
+ }
3090
+
3340
3091
  forkAcpSession(agentId, options = {}) {
3341
3092
  this.getAcpSession(agentId);
3342
3093
  return this.acpRuntime.forkSession(agentId, options);
@@ -3367,44 +3118,10 @@ class AgentManager extends EventEmitter {
3367
3118
  return this.acpRuntime.setSessionConfigOptions(agentId, changes);
3368
3119
  }
3369
3120
 
3370
- async setCodexAppServerGoal(agentId, patch = {}) {
3371
- const agent = this.assertCodexAppServerGoalAgent(agentId);
3372
- const objective = typeof patch.objective === 'string' ? patch.objective.trim().slice(0, 4000) : undefined;
3373
- const status = typeof patch.status === 'string' ? patch.status : undefined;
3374
- let tokenBudget;
3375
- if (Object.prototype.hasOwnProperty.call(patch, 'tokenBudget')) {
3376
- tokenBudget = Number.isFinite(Number(patch.tokenBudget)) && Number(patch.tokenBudget) > 0
3377
- ? Math.floor(Number(patch.tokenBudget))
3378
- : null;
3379
- }
3380
- if (objective !== undefined && !objective) {
3381
- throw new Error('Codex goal objective is required');
3382
- }
3383
- const goal = await this.codexAppServerRuntime.setAgentGoal({
3384
- ...this.codexAppServerOptionsForAgent(agent),
3385
- ...(objective !== undefined ? { objective } : {}),
3386
- ...(status !== undefined ? { status } : {}),
3387
- ...(Object.prototype.hasOwnProperty.call(patch, 'tokenBudget') ? { tokenBudget } : {}),
3388
- });
3389
- runtimeBindingOf(agent, 'app-server').goal = goal || null;
3390
- this.emit('update');
3391
- return goal || null;
3392
- }
3393
-
3394
- async clearCodexAppServerGoal(agentId) {
3395
- const agent = this.assertCodexAppServerGoalAgent(agentId);
3396
- await this.codexAppServerRuntime.clearAgentGoal(this.codexAppServerOptionsForAgent(agent));
3397
- runtimeBindingOf(agent, 'app-server').goal = null;
3398
- this.emit('update');
3399
- return null;
3400
- }
3401
-
3402
3121
  async sendInputNow(agentId, input, { markUserInput = true, expectedRuntimeEpoch = '' } = {}) {
3403
3122
  const agent = this.agents.get(agentId);
3404
3123
  if (!agent) return;
3405
- // The observer PTY is optional telemetry for App Server Agents. Its
3406
- // disappearance must never redefine the App Server Agent as dead.
3407
- if (isCodexAppServerAgent(agent) || isJsonCliAgent(agent) || isAcpAgent(agent)) return;
3124
+ if (isJsonCliAgent(agent) || isAcpAgent(agent)) return;
3408
3125
  if (expectedRuntimeEpoch && agent.runtimeEpoch !== expectedRuntimeEpoch) {
3409
3126
  return { status: 'input-rejected', reason: 'runtime-epoch-mismatch' };
3410
3127
  }
@@ -3437,8 +3154,6 @@ class AgentManager extends EventEmitter {
3437
3154
  markAgentSessionDead(agentId, error) {
3438
3155
  const agent = this.agents.get(agentId);
3439
3156
  if (!agent || agent.status === 'dead') return;
3440
- if (isCodexAppServerAgent(agent)) return;
3441
-
3442
3157
  const message = error && error.message ? error.message : String(error || 'Session not available');
3443
3158
  agent.status = 'dead';
3444
3159
  agent.engineStatus = 'dead';
@@ -3452,31 +3167,7 @@ class AgentManager extends EventEmitter {
3452
3167
  async interruptAgent(agentId, options = {}) {
3453
3168
  const agent = this.agents.get(agentId);
3454
3169
  if (!agent) return;
3455
- const appServer = runtimeBindingOf(agent, 'app-server');
3456
-
3457
3170
  try {
3458
- if (
3459
- agent.providerSessionProvider === 'codex'
3460
- && appServer
3461
- && this.codexAppServerRuntime
3462
- && typeof this.codexAppServerRuntime.interruptAgent === 'function'
3463
- ) {
3464
- try {
3465
- const interrupted = await this.codexAppServerRuntime.interruptAgent(agentId);
3466
- if (interrupted) {
3467
- appServer.state = 'interrupting';
3468
- this.emit('update');
3469
- return;
3470
- }
3471
- appServer.error = 'Codex App Server interrupt is not available';
3472
- this.emit('update');
3473
- return;
3474
- } catch (error) {
3475
- appServer.error = `Codex App Server interrupt failed: ${error && (error.message || error)}`;
3476
- this.emit('update');
3477
- return;
3478
- }
3479
- }
3480
3171
  if (isJsonCliAgent(agent)) {
3481
3172
  this.jsonCliRuntime.interruptAgent(agentId);
3482
3173
  return;
@@ -3505,12 +3196,12 @@ class AgentManager extends EventEmitter {
3505
3196
  agentSupportsTerminalInput(agentId) {
3506
3197
  const agent = this.agents.get(agentId);
3507
3198
  if (!agent) return false;
3508
- return !isCodexAppServerAgent(agent) && !isJsonCliAgent(agent) && !isAcpAgent(agent);
3199
+ return !isJsonCliAgent(agent) && !isAcpAgent(agent);
3509
3200
  }
3510
3201
 
3511
3202
  async getAgentSessionAttachCheckpoint(agentId) {
3512
3203
  const agent = this.agents.get(agentId);
3513
- if (!agent || isCodexAppServerAgent(agent) || isAcpAgent(agent) || isJsonCliAgent(agent)) {
3204
+ if (!agent || isAcpAgent(agent) || isJsonCliAgent(agent)) {
3514
3205
  return null;
3515
3206
  }
3516
3207
  try {
@@ -3541,7 +3232,7 @@ class AgentManager extends EventEmitter {
3541
3232
  async resizeAgentSession(agentId, cols, rows) {
3542
3233
  const agent = this.agents.get(agentId);
3543
3234
  if (!agent) return { status: 'resize-rejected', reason: 'session-unavailable', resized: false };
3544
- if (isCodexAppServerAgent(agent) || isAcpAgent(agent) || isJsonCliAgent(agent)) {
3235
+ if (isAcpAgent(agent) || isJsonCliAgent(agent)) {
3545
3236
  return { status: 'resize-rejected', reason: 'unsupported-session', resized: false };
3546
3237
  }
3547
3238
 
@@ -3579,7 +3270,7 @@ class AgentManager extends EventEmitter {
3579
3270
  async clearAgentSessionBuffer(agentId, options = {}) {
3580
3271
  const agent = this.agents.get(agentId);
3581
3272
  if (!agent) return { cleared: false };
3582
- if (isCodexAppServerAgent(agent) || isAcpAgent(agent) || isJsonCliAgent(agent)) return { cleared: false };
3273
+ if (isAcpAgent(agent) || isJsonCliAgent(agent)) return { cleared: false };
3583
3274
  if (options.expectedRuntimeEpoch && agent.runtimeEpoch !== options.expectedRuntimeEpoch) {
3584
3275
  return { cleared: false, reason: 'runtime-epoch-mismatch' };
3585
3276
  }
@@ -3780,52 +3471,9 @@ class AgentManager extends EventEmitter {
3780
3471
  async syncCodexTerminalPermissionMode(agentId, mode) {
3781
3472
  const agent = this.agents.get(agentId);
3782
3473
  if (!agent) return { error: 'Agent not found' };
3783
-
3784
- if (
3785
- agent.providerSessionProvider === 'codex'
3786
- && runtimeKind(agent) === 'app-server'
3787
- ) {
3788
- return this.updateCodexAppServerPermissionMode(agentId, mode);
3789
- }
3790
-
3791
3474
  return this.restartAgentWithPermissionMode(agentId, mode);
3792
3475
  }
3793
3476
 
3794
- async updateCodexAppServerPermissionMode(agentId, mode) {
3795
- const agent = this.agents.get(agentId);
3796
- if (!agent) return { error: 'Agent not found' };
3797
- const runtime = runtimeBindingOf(agent, 'app-server');
3798
- if (agent.providerSessionProvider !== 'codex' || !runtime) {
3799
- return { error: 'Agent is not using the Codex App Server runtime' };
3800
- }
3801
-
3802
- const nextMode = ['ask', 'approve', 'full', 'custom'].includes(mode) ? mode : '';
3803
- if (!nextMode) return { error: 'Unsupported Codex permission mode' };
3804
- if (!this.codexAppServerRuntime || typeof this.codexAppServerRuntime.updateAgentPermissionMode !== 'function') {
3805
- return { error: 'Codex App Server permission updates are unavailable' };
3806
- }
3807
-
3808
- try {
3809
- await this.codexAppServerRuntime.updateAgentPermissionMode({
3810
- ...this.codexAppServerOptionsForAgent(agent),
3811
- approvalMode: nextMode,
3812
- });
3813
- } catch (error) {
3814
- return { error: error && error.message ? error.message : 'Failed to update Codex App Server permissions' };
3815
- }
3816
-
3817
- agent.launchPermissionMode = nextMode;
3818
- runtime.error = '';
3819
- this.updateEngineProviderSessionMetadata(agent);
3820
- this.ensurePersistentAgentSession(agent, { launchPermissionMode: nextMode });
3821
- this.emit('update');
3822
- return {
3823
- agentId,
3824
- updated: true,
3825
- launchPermissionMode: nextMode,
3826
- };
3827
- }
3828
-
3829
3477
  async restartAgentWithPermissionMode(agentId, mode) {
3830
3478
  const inFlight = this.permissionRestartInFlight.get(agentId);
3831
3479
  if (inFlight) {
@@ -3868,15 +3516,16 @@ class AgentManager extends EventEmitter {
3868
3516
  async performAgentRuntimeModeRestart(agentId, mode) {
3869
3517
  const agent = this.agents.get(agentId);
3870
3518
  if (!agent) return { error: 'Agent not found' };
3871
- const nextMode = ['terminal', 'json', 'acp'].includes(mode) ? mode : '';
3872
- if (!nextMode) return { error: 'Unsupported Agent runtime mode' };
3873
3519
  const provider = agent.providerSessionProvider || '';
3520
+ const requestedMode = ['terminal', 'json', 'acp', 'chat'].includes(mode) ? mode : '';
3521
+ const nextMode = requestedMode === 'acp' && provider === 'codex' ? 'chat' : requestedMode;
3522
+ if (!nextMode) return { error: 'Unsupported Agent runtime mode' };
3874
3523
  const currentKind = runtimeKind(agent);
3875
- const currentMode = ['acp', 'json'].includes(currentKind) ? currentKind : 'terminal';
3876
- const leavesCodexAppServer = provider === 'codex'
3877
- && currentKind === 'app-server'
3878
- && nextMode === 'terminal';
3879
- if (currentMode === nextMode && !leavesCodexAppServer) {
3524
+ const currentMode = currentKind === 'acp'
3525
+ ? 'chat'
3526
+ : (currentKind === 'json' ? 'json' : 'terminal');
3527
+ const nextRuntimeKind = nextMode === 'chat' ? chatRuntimeForProvider(provider) : nextMode;
3528
+ if (currentMode === nextMode) {
3880
3529
  return { agentId, agentRuntimeMode: nextMode };
3881
3530
  }
3882
3531
  const turnActive = currentKind === 'acp'
@@ -3885,18 +3534,21 @@ class AgentManager extends EventEmitter {
3885
3534
  if (turnActive) {
3886
3535
  return { error: 'Interrupt the active Agent turn before switching Chat and Terminal.' };
3887
3536
  }
3888
- if (!providerSupportsRuntime(provider, nextMode)) {
3537
+ const supportsNextMode = nextMode === 'chat'
3538
+ ? providerCapabilities(provider).supportsChat === true
3539
+ : providerSupportsRuntime(provider, nextMode);
3540
+ if (!supportsNextMode) {
3889
3541
  return { error: `Agent does not support the ${nextMode.toUpperCase()} runtime` };
3890
3542
  }
3891
3543
  const sessionId = String(agent.providerSessionId || '').trim();
3892
- const canStartFreshAcpSession = nextMode === 'acp'
3544
+ const canStartFreshChatSession = nextMode === 'chat'
3893
3545
  && currentMode === 'terminal'
3894
3546
  && agent.terminalInputReceived !== true
3895
3547
  && (
3896
3548
  agent.providerSessionTemporary === true
3897
3549
  || isFreshAcpSessionSource(provider, agent.providerSessionSource || '')
3898
3550
  );
3899
- if (!isSafeProviderSessionId(sessionId) && !canStartFreshAcpSession) {
3551
+ if (!isSafeProviderSessionId(sessionId) && !canStartFreshChatSession) {
3900
3552
  return { error: 'Runtime switching requires a resumable provider session. Send the first message and try again.' };
3901
3553
  }
3902
3554
  // A live ACP binding is the authoritative owner of a newly-created
@@ -3915,15 +3567,15 @@ class AgentManager extends EventEmitter {
3915
3567
  }
3916
3568
  }
3917
3569
  const previouslyVerifiedSession = String(agent.runtimeSwitchVerifiedSessionId || '') === sessionId;
3918
- let startsFreshAcpSession = canStartFreshAcpSession && !isSafeProviderSessionId(sessionId);
3919
- if (!startsFreshAcpSession && !liveAcpSession && !previouslyVerifiedSession) {
3570
+ let startsFreshChatSession = canStartFreshChatSession && !isSafeProviderSessionId(sessionId);
3571
+ if (!startsFreshChatSession && !liveAcpSession && !previouslyVerifiedSession) {
3920
3572
  const providerSession = await this.findRuntimeSwitchSession(agent);
3921
3573
  if (!providerSession) {
3922
- if (canStartFreshAcpSession) startsFreshAcpSession = true;
3574
+ if (canStartFreshChatSession) startsFreshChatSession = true;
3923
3575
  else return { error: 'The saved Agent session is no longer available in the selected Agent Home.' };
3924
3576
  }
3925
3577
  }
3926
- const command = startsFreshAcpSession
3578
+ const command = startsFreshChatSession
3927
3579
  ? (agent.forkCommand || agent.command)
3928
3580
  : buildAgentSessionResumeCommand(provider, sessionId, {
3929
3581
  cwd: effectiveAgentWorkspaceRoot(agent),
@@ -3940,12 +3592,20 @@ class AgentManager extends EventEmitter {
3940
3592
  ? this.jsonCliRuntime.getEvents(agentId)
3941
3593
  : (Array.isArray(agent.runtimeResumeState?.jsonEvents) ? agent.runtimeResumeState.jsonEvents : []),
3942
3594
  };
3595
+ let acpSessionOptions = {};
3596
+ if (currentKind === 'acp' && this.acpRuntime?.getSessionRequestOptions) {
3597
+ try {
3598
+ acpSessionOptions = this.acpRuntime.getSessionRequestOptions(agentId);
3599
+ } catch {
3600
+ acpSessionOptions = {};
3601
+ }
3602
+ }
3943
3603
  const restartOptions = {
3944
3604
  wantsMain: agent.wantsMain === true,
3945
3605
  task: agent.task || agent.providerSessionTitle || '',
3946
3606
  workflowTemplate: agent.workflowTemplate || '',
3947
3607
  projectWorkspace: effectiveAgentWorkspaceRoot(agent),
3948
- source: startsFreshAcpSession
3608
+ source: startsFreshChatSession
3949
3609
  ? 'ui-runtime-switch-fresh'
3950
3610
  : resumedAgentSource(provider, sessionId, agent.providerHomeId || ''),
3951
3611
  providerHomeId: agent.providerHomeId || '',
@@ -3960,12 +3620,12 @@ class AgentManager extends EventEmitter {
3960
3620
  projectOrder: preserved.projectOrder,
3961
3621
  pinnedOrder: preserved.pinnedOrder,
3962
3622
  agentRuntimeMode: nextMode,
3963
- acpStartFresh: startsFreshAcpSession,
3964
- codexRuntimeMode: 'cli',
3623
+ acpStartFresh: startsFreshChatSession && nextRuntimeKind === 'acp',
3965
3624
  codexApprovalMode: agent.launchPermissionMode || undefined,
3966
3625
  jsonCliEvents: preserved.jsonCliEvents,
3967
- runtimeSwitchVerifiedSessionId: startsFreshAcpSession ? '' : sessionId,
3968
- ...(provider === 'codex' && !startsFreshAcpSession
3626
+ runtimeSwitchVerifiedSessionId: startsFreshChatSession ? '' : sessionId,
3627
+ ...acpSessionOptions,
3628
+ ...(provider === 'codex' && !startsFreshChatSession
3969
3629
  ? preserveCodexSessionProfileOptions()
3970
3630
  : {}),
3971
3631
  };
@@ -3973,7 +3633,6 @@ class AgentManager extends EventEmitter {
3973
3633
  const originalOptions = {
3974
3634
  ...restartOptions,
3975
3635
  agentRuntimeMode: originalMode,
3976
- codexRuntimeMode: currentKind === 'app-server' ? 'app-server' : 'cli',
3977
3636
  acpStartFresh: false,
3978
3637
  };
3979
3638
  const startReplacement = options => new Promise(resolve => {
@@ -4103,6 +3762,14 @@ class AgentManager extends EventEmitter {
4103
3762
  return { error: 'Failed to build provider resume command' };
4104
3763
  }
4105
3764
 
3765
+ let acpSessionOptions = {};
3766
+ if (isAcpAgent(agent) && this.acpRuntime?.getSessionRequestOptions) {
3767
+ try {
3768
+ acpSessionOptions = this.acpRuntime.getSessionRequestOptions(agentId);
3769
+ } catch {
3770
+ acpSessionOptions = {};
3771
+ }
3772
+ }
4106
3773
  const restartOptions = {
4107
3774
  wantsMain: agent.wantsMain === true,
4108
3775
  task: agent.task || agent.providerSessionTitle || '',
@@ -4125,6 +3792,7 @@ class AgentManager extends EventEmitter {
4125
3792
  ])),
4126
3793
  projectOrder: finiteOrder(agent.projectOrder),
4127
3794
  pinnedOrder: finiteOrder(agent.pinnedOrder),
3795
+ ...acpSessionOptions,
4128
3796
  ...(provider === 'codex' ? { codexApprovalMode: nextMode } : { claudePermissionMode: nextMode }),
4129
3797
  ...(provider === 'codex' && hasResumableSession
4130
3798
  ? preserveCodexSessionProfileOptions()
@@ -4670,9 +4338,6 @@ class AgentManager extends EventEmitter {
4670
4338
  this.agentUsageRateCache.delete(agentId);
4671
4339
  this.lastResizeByAgent.delete(agentId);
4672
4340
  this.providerSessionService.stop(agentId);
4673
- if (this.codexAppServerRuntime && typeof this.codexAppServerRuntime.unregisterAgent === 'function') {
4674
- this.codexAppServerRuntime.unregisterAgent(agentId);
4675
- }
4676
4341
  if (this.jsonCliRuntime) this.jsonCliRuntime.unregisterAgent(agentId);
4677
4342
  if (this.acpRuntime) this.acpRuntime.unregisterAgent(agentId);
4678
4343
 
@@ -4729,13 +4394,11 @@ class AgentManager extends EventEmitter {
4729
4394
  getAgentProviderSession(agentId) {
4730
4395
  const agent = this.agents.get(agentId);
4731
4396
  if (!agent) return null;
4732
- const appServer = runtimeBindingOf(agent, 'app-server');
4733
4397
  return {
4734
4398
  provider: agent.providerSessionProvider || '',
4735
4399
  sessionId: agent.providerSessionId || '',
4736
4400
  providerHomeId: agent.providerHomeId || '',
4737
4401
  providerHomePath: agent.providerHomePath || '',
4738
- codexAppServerHomePath: appServer?.homePath || '',
4739
4402
  runtimeBinding: publicRuntimeBinding(agent),
4740
4403
  temporary: agent.providerSessionTemporary === true,
4741
4404
  title: agent.providerSessionTitle || '',
@@ -5023,7 +4686,10 @@ class AgentManager extends EventEmitter {
5023
4686
  providerSessionResolvedAt: agent.providerSessionResolvedAt || null,
5024
4687
  providerSessionTitle: agent.providerSessionTitle || '',
5025
4688
  providerSessionWorkspace: agent.providerSessionWorkspace || '',
5026
- providerCapabilities: providerCapabilities(agent.providerSessionProvider),
4689
+ providerCapabilities: {
4690
+ ...providerCapabilities(agent.providerSessionProvider),
4691
+ supportsSteer: runtimeBindingOf(agent, 'acp')?.supportsSteer === true,
4692
+ },
5027
4693
  terminalInputReceived: agent.terminalInputReceived === true,
5028
4694
  runtimeBinding: publicRuntimeBinding(agent),
5029
4695
  runtimeObservation: deriveRuntimeObservation(agent),