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