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.
- package/README.md +81 -105
- package/README.zh_cn.md +82 -106
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/backend/acp/packaged-codex-acp.js +13 -0
- package/backend/acp-runtime.js +192 -14
- package/backend/agent-manager.js +176 -510
- package/backend/agent-runtime-binding.js +9 -55
- package/backend/chat-runtime.js +28 -0
- package/backend/codex-transcript.js +2 -1
- package/backend/config-manager.js +22 -14
- package/backend/control-api.js +3 -1
- package/backend/farming-app-cli.js +13 -0
- package/backend/farming-session-store.js +2 -1
- package/backend/npm-update-helper.js +65 -7
- package/backend/provider-adapters.js +6 -2
- package/backend/server.js +89 -104
- package/backend/storage-layout.js +0 -16
- package/backend/update-service.js +94 -5
- package/dist/acp/LICENSE.codex-acp +190 -0
- package/dist/acp/codex-acp-1.1.4.js +30718 -0
- package/dist/assets/App-y6p2yRar.js +206 -0
- package/dist/assets/{FileEditorMarkdownPreview-UlVd0O4D.js → FileEditorMarkdownPreview-R0jwwLME.js} +4 -4
- package/dist/assets/FileEditorPane-DNy3LAqS.js +2 -0
- package/dist/assets/{IconGlyphs-AU22gPDY.js → IconGlyphs-BsUfqpKN.js} +1 -1
- package/dist/assets/ProjectFilesSection-BhDEUVHA.js +12 -0
- package/dist/assets/{ReviewPage-CeDKDyZh.js → ReviewPage-ChtByBe3.js} +1 -1
- package/dist/assets/{abnfDiagram-VRR7QNED-BgawzDQT.js → abnfDiagram-VRR7QNED-nnglrXFi.js} +1 -1
- package/dist/assets/{architectureDiagram-ZJ3FMSHR-DUShwPg0.js → architectureDiagram-ZJ3FMSHR-BEXMaFfY.js} +1 -1
- package/dist/assets/{blockDiagram-677ZJIJ3-CeGZviXR.js → blockDiagram-677ZJIJ3-1mCezSBk.js} +1 -1
- package/dist/assets/{c4Diagram-LMCZKHZV-CLhYht6S.js → c4Diagram-LMCZKHZV-DhTiI6Uv.js} +1 -1
- package/dist/assets/channel-BVWNatUB.js +1 -0
- package/dist/assets/{chunk-32BRIVSS-LFYpT-Cu.js → chunk-32BRIVSS-CCdsKd73.js} +1 -1
- package/dist/assets/{chunk-52WLFC77-BH6jDw9J.js → chunk-52WLFC77-Gncjk4LF.js} +1 -1
- package/dist/assets/{chunk-C7G6YPKG-B_mIUlb7.js → chunk-C7G6YPKG-CiDVUP82.js} +1 -1
- package/dist/assets/{chunk-EX3LRPZG-C63hVuAJ.js → chunk-EX3LRPZG-z6gXP2_N.js} +1 -1
- package/dist/assets/{chunk-FWX5IMBZ-CboX-WF_.js → chunk-FWX5IMBZ-DMZBccQa.js} +2 -2
- package/dist/assets/{chunk-HOUHSVGY-BOgSd6Un.js → chunk-HOUHSVGY-D8tIeoPk.js} +1 -1
- package/dist/assets/{chunk-ICXQ74PX-C0K-pgZt.js → chunk-ICXQ74PX-b6oQ2Q17.js} +1 -1
- package/dist/assets/{chunk-MOJQB5TN-CVMfwN_7.js → chunk-MOJQB5TN-DAVH_p2a.js} +1 -1
- package/dist/assets/{chunk-OGEWGWER-DLT3pxyw.js → chunk-OGEWGWER-DxoiMCI-.js} +1 -1
- package/dist/assets/{chunk-PUDLZKDR-Cd079yIQ.js → chunk-PUDLZKDR-NsOWh7HM.js} +1 -1
- package/dist/assets/{chunk-Q4XR5HBZ-D0jHa6SO.js → chunk-Q4XR5HBZ-Di4H7L21.js} +1 -1
- package/dist/assets/{chunk-V7JOEXUC-CVDtR75H.js → chunk-V7JOEXUC-DttpZg9Y.js} +1 -1
- package/dist/assets/{chunk-VAUOI2AC-B-W2_9Ot.js → chunk-VAUOI2AC-UHNr_yhX.js} +1 -1
- package/dist/assets/{chunk-VR4S4FIN-FD3kWh32.js → chunk-VR4S4FIN-D-Fmtd4Z.js} +1 -1
- package/dist/assets/{chunk-WYO6CB5R-C2bGaS5r.js → chunk-WYO6CB5R-CUxMtfK2.js} +4 -4
- package/dist/assets/{chunk-ZGVPDNZ5-ymgts0zo.js → chunk-ZGVPDNZ5-CoV2QEl0.js} +1 -1
- package/dist/assets/classDiagram-OUVF2IWQ-Db0ib5iE.js +1 -0
- package/dist/assets/classDiagram-v2-EOCWNBFH-Bkf69XF9.js +1 -0
- package/dist/assets/code-dark-DKBGxoPX.css +1 -0
- package/dist/assets/{cynefinDiagram-TSTJHNR4-CwpobXIl.js → cynefinDiagram-TSTJHNR4-0xPI92Lg.js} +1 -1
- package/dist/assets/{dagre-VKFMJZFB-Dkhlfjna.js → dagre-VKFMJZFB-DfuMG4gU.js} +1 -1
- package/dist/assets/{diagram-FQU43EPY-hA8dicp6.js → diagram-FQU43EPY-CT00bLZ6.js} +1 -1
- package/dist/assets/{diagram-G47NLZAW-BTY6JTzO.js → diagram-G47NLZAW-Xbh7zXUx.js} +1 -1
- package/dist/assets/{diagram-NH7WQ7WH-xkaHHX9M.js → diagram-NH7WQ7WH-SRDVPfn7.js} +1 -1
- package/dist/assets/{diagram-OA4YK3LP-gk_fg9UU.js → diagram-OA4YK3LP-CQRli4Np.js} +1 -1
- package/dist/assets/{diagram-WEI45ONY-BvC8xd6T.js → diagram-WEI45ONY-DMf17WQi.js} +1 -1
- package/dist/assets/{ebnfDiagram-CCIWWBDH-CR7RaNat.js → ebnfDiagram-CCIWWBDH-DhpFkgJ5.js} +1 -1
- package/dist/assets/{erDiagram-Q63AITRT-_M-JFPbj.js → erDiagram-Q63AITRT-1IwxWmJo.js} +1 -1
- package/dist/assets/file-icons-DpbrD5ZP.js +1 -0
- package/dist/assets/flowDiagram-23GEKE2U-DYFo2xmB.js +1 -0
- package/dist/assets/{ganttDiagram-NO4QXBWP-Bl8P1yf_.js → ganttDiagram-NO4QXBWP-BNCdfyr1.js} +1 -1
- package/dist/assets/{gitGraphDiagram-IHSO6WYX-DoHa5ysz.js → gitGraphDiagram-IHSO6WYX-pbyWZuiX.js} +1 -1
- package/dist/assets/{index-CYZKdoqi.js → index-iKBvPjWS.js} +2 -2
- package/dist/assets/{infoDiagram-FWYZ7A6U-DSFcSuUs.js → infoDiagram-FWYZ7A6U-BcTOgDbe.js} +1 -1
- package/dist/assets/{ishikawaDiagram-FXEZZL3T-DdkqullI.js → ishikawaDiagram-FXEZZL3T-DCdvGKrn.js} +1 -1
- package/dist/assets/{journeyDiagram-5HDEW3XC-B4hRnQJr.js → journeyDiagram-5HDEW3XC-BZ-1q_IT.js} +1 -1
- package/dist/assets/{kanban-definition-HUTT4EX6-tm2FZB2E.js → kanban-definition-HUTT4EX6-BIgejCjj.js} +1 -1
- package/dist/assets/{line-CmsjK4kn.js → line-CfptFMmX.js} +1 -1
- package/dist/assets/main-CdkLOXYl.css +1 -0
- package/dist/assets/{mermaid.core-C7UT_EEL.js → mermaid.core-gSpX30pV.js} +3 -3
- package/dist/assets/{mindmap-definition-LN4V7U3C-DplfjGms.js → mindmap-definition-LN4V7U3C-BKBaI4LW.js} +1 -1
- package/dist/assets/{pegDiagram-2B236MQR-lu3SdkQs.js → pegDiagram-2B236MQR-sBvDY3VX.js} +1 -1
- package/dist/assets/{pieDiagram-ENE6RG2P-3fHbmDf9.js → pieDiagram-ENE6RG2P-DWlWcu08.js} +1 -1
- package/dist/assets/{quadrantDiagram-ABIIQ3AL-a5qoWz-X.js → quadrantDiagram-ABIIQ3AL-DZutUd3J.js} +1 -1
- package/dist/assets/{railroadDiagram-RFXS5EU6-D2TOuXW9.js → railroadDiagram-RFXS5EU6-DQb3lXGM.js} +1 -1
- package/dist/assets/{requirementDiagram-TGXJPOKE-LQBWpUvd.js → requirementDiagram-TGXJPOKE-DqFGXu2e.js} +1 -1
- package/dist/assets/{sankeyDiagram-HTMAVEWB-B1ohYoyK.js → sankeyDiagram-HTMAVEWB-d7ECahnO.js} +1 -1
- package/dist/assets/{sequenceDiagram-DBY2YBRQ-C3vM-hMq.js → sequenceDiagram-DBY2YBRQ-DdsbKqct.js} +1 -1
- package/dist/assets/{stateDiagram-2N3HPSRC-BvFhOryI.js → stateDiagram-2N3HPSRC-P6iSrgwR.js} +1 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-DIYSf_qh.js +1 -0
- package/dist/assets/{swimlanes-5IMT3BWC-DKF_b-2s.js → swimlanes-5IMT3BWC-BGnZJx1M.js} +1 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-BIkrlKrZ.js +8 -0
- package/dist/assets/{timeline-definition-FHXFAJF6-vAs11ux_.js → timeline-definition-FHXFAJF6-rA-r3aUR.js} +1 -1
- package/dist/assets/{vennDiagram-L72KCM5P-BybTX_9B.js → vennDiagram-L72KCM5P-DiVKUjA9.js} +1 -1
- package/dist/assets/{wardleyDiagram-EHGQE667-CicUmMBR.js → wardleyDiagram-EHGQE667-Cfxh7UOn.js} +1 -1
- package/dist/assets/workspace-editor-model-D0Im34S5.js +1 -0
- package/dist/assets/{workspace-editor-monaco-BICJESG0.js → workspace-editor-monaco-BIpqcWf1.js} +1 -1
- package/dist/assets/workspace-editor-monaco-DLlws5Ig.js +1 -0
- package/dist/assets/{workspace-view-state-DvYG_9PH.js → workspace-view-state-BpPINu84.js} +1 -1
- package/dist/assets/{xychartDiagram-FW5EYKEG-BhWDyCX-.js → xychartDiagram-FW5EYKEG--hmryQeL.js} +1 -1
- package/dist/crt-mermaid-renderer.js +313 -313
- package/dist/index.html +1 -1
- package/frontend/session-bridge.js +24 -2
- package/frontend/skins/crt/app.js +194 -82
- package/frontend/skins/crt/index.html +2 -11
- package/frontend/skins/crt/styles/effects.css +2 -34
- package/package.json +23 -9
- package/shared/browser-protocol.js +3 -3
- package/backend/app-server-api.js +0 -917
- package/backend/codex-app-server-home.js +0 -39
- package/backend/codex-app-server-runtime.js +0 -901
- package/dist/assets/App-xASd_Y0F.js +0 -206
- package/dist/assets/FileEditorPane-Dxx4rKOg.js +0 -2
- package/dist/assets/ProjectFilesSection-DE7Nb9Jk.js +0 -12
- package/dist/assets/channel-Uw90AO0b.js +0 -1
- package/dist/assets/classDiagram-OUVF2IWQ-DKW_dtku.js +0 -1
- package/dist/assets/classDiagram-v2-EOCWNBFH-C8Fi7-yv.js +0 -1
- package/dist/assets/code-dark-DPiuyeSp.css +0 -1
- package/dist/assets/code-mobile-3C2Rr-CN.css +0 -1
- package/dist/assets/file-icons-Bw2qd5iT.js +0 -1
- package/dist/assets/flowDiagram-23GEKE2U-DAiS5Y7G.js +0 -1
- package/dist/assets/main-Bqtb5kuz.css +0 -1
- package/dist/assets/stateDiagram-v2-6OUMAXLB-WMrz4PNo.js +0 -1
- package/dist/assets/swimlanesDiagram-G3AALYLV-BGswC9oC.js +0 -8
- package/dist/assets/workspace-editor-model-BQol4qbA.js +0 -1
- package/dist/assets/workspace-editor-monaco-DRJ7IaXk.js +0 -1
- package/scripts/sync-ghostty-vendor.js +0 -66
- /package/dist/assets/{core-D0LFJkDt.js → core-B9MwRjDE.js} +0 -0
package/backend/agent-manager.js
CHANGED
|
@@ -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
|
|
1617
|
-
const
|
|
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
|
|
1533
|
+
providerSessionId,
|
|
1652
1534
|
providerSessionKey: metadata.providerSessionKey || (
|
|
1653
|
-
providerSessionProvider &&
|
|
1654
|
-
? mainPageAgentSessionKey(providerSessionProvider,
|
|
1535
|
+
providerSessionProvider && providerSessionId
|
|
1536
|
+
? mainPageAgentSessionKey(providerSessionProvider, providerSessionId, metadata.providerHomeId || '')
|
|
1655
1537
|
: ''
|
|
1656
1538
|
),
|
|
1657
|
-
providerSessionTemporary: metadata.providerSessionTemporary === true || isTemporaryProviderSessionId(
|
|
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
|
-
//
|
|
1664
|
-
//
|
|
1665
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
const
|
|
2648
|
-
|
|
2649
|
-
|
|
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 =
|
|
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:
|
|
2716
|
-
? runtimeBindingFor('
|
|
2717
|
-
: (
|
|
2718
|
-
? runtimeBindingFor('
|
|
2719
|
-
|
|
2720
|
-
?
|
|
2721
|
-
|
|
2722
|
-
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
2962
|
+
async sendAcpSteerNow(agentId, message) {
|
|
3169
2963
|
const agent = this.agents.get(agentId);
|
|
3170
2964
|
if (!agent) throw new Error('Agent not found');
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
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
|
-
|
|
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 !
|
|
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 ||
|
|
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 (
|
|
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 (
|
|
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 =
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
if (currentMode === nextMode
|
|
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
|
-
|
|
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
|
|
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) && !
|
|
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
|
|
3919
|
-
if (!
|
|
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 (
|
|
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 =
|
|
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:
|
|
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:
|
|
3964
|
-
codexRuntimeMode: 'cli',
|
|
3623
|
+
acpStartFresh: startsFreshChatSession && nextRuntimeKind === 'acp',
|
|
3965
3624
|
codexApprovalMode: agent.launchPermissionMode || undefined,
|
|
3966
3625
|
jsonCliEvents: preserved.jsonCliEvents,
|
|
3967
|
-
runtimeSwitchVerifiedSessionId:
|
|
3968
|
-
...
|
|
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:
|
|
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),
|