farming-code 2.2.14 → 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 +1 -1
- package/README.zh_cn.md +1 -1
- 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-BA-iH2Kg.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-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/README.md
CHANGED
|
@@ -119,7 +119,7 @@ farming logs
|
|
|
119
119
|
farming stop
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
The startup token is stored in `~/.farming/.session-token` and reused across restarts and upgrades. **Settings → Updates** can upgrade npm installations in place. GitHub Releases also provide standalone CLI and directory bundles; see [GitHub Releases](https://github.com/zhuwenzhuang/farming/releases) for current artifacts.
|
|
122
|
+
The startup token is stored in `~/.farming/.session-token` and reused across restarts and upgrades. **Settings → Updates** can upgrade npm installations in place. npm updates first use the machine's configured registry and retry the registry shown in Settings only when that registry lacks the selected version. Before installing, Farming verifies that npm will update the same package root that launched the server. GitHub Releases also provide standalone CLI and directory bundles; see [GitHub Releases](https://github.com/zhuwenzhuang/farming/releases) for current artifacts.
|
|
123
123
|
|
|
124
124
|
To run from source with the same port and base path:
|
|
125
125
|
|
package/README.zh_cn.md
CHANGED
|
@@ -119,7 +119,7 @@ farming logs
|
|
|
119
119
|
farming stop
|
|
120
120
|
```
|
|
121
121
|
|
|
122
|
-
启动 Token 存放在 `~/.farming/.session-token`,重启和升级会继续复用。**Settings → Updates** 可以原地升级 npm 安装。GitHub Releases 也提供独立 CLI 和目录 Bundle;当前产物见 [GitHub Releases](https://github.com/zhuwenzhuang/farming/releases)。
|
|
122
|
+
启动 Token 存放在 `~/.farming/.session-token`,重启和升级会继续复用。**Settings → Updates** 可以原地升级 npm 安装。npm 升级会先使用机器配置的 registry;只有该 registry 缺少所选版本时,才会回退到 Settings 展示版本所用的 registry。安装前 Farming 会校验 npm 将更新的包根目录与启动当前服务的包根目录一致。GitHub Releases 也提供独立 CLI 和目录 Bundle;当前产物见 [GitHub Releases](https://github.com/zhuwenzhuang/farming/releases)。
|
|
123
123
|
|
|
124
124
|
从源码运行,并使用相同的端口与 Base Path:
|
|
125
125
|
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -27,7 +27,7 @@ preserved in bundled `node_modules` when bundled dependencies are enabled.
|
|
|
27
27
|
| `ansi-to-html` | 0.7.2 | MIT | ANSI text rendering support |
|
|
28
28
|
| `chokidar` | 5.0.0 | MIT | Filesystem watching |
|
|
29
29
|
| `diff` | 9.0.0 | BSD-3-Clause | Character-level diff ranges for code review |
|
|
30
|
-
| `express` | 4.22.
|
|
30
|
+
| `express` | 4.22.2 | MIT | HTTP API server |
|
|
31
31
|
| `ghostty-web` | 0.4.0 | MIT | Optional/debug terminal renderer assets |
|
|
32
32
|
| `highlight.js` | 11.11.1 | BSD-3-Clause | Syntax highlighting |
|
|
33
33
|
| `katex` | 0.17.0 | MIT | Mathematical notation rendering |
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const PACKAGED_CODEX_ACP_ARG = '--farming-codex-acp';
|
|
2
|
+
|
|
3
|
+
function runPackagedCodexAcp() {
|
|
4
|
+
if (!process.pkg) {
|
|
5
|
+
throw new Error('The packaged Codex ACP entry is available only in a standalone Farming CLI');
|
|
6
|
+
}
|
|
7
|
+
require('../../dist/acp/codex-acp-1.1.4.js');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
PACKAGED_CODEX_ACP_ARG,
|
|
12
|
+
runPackagedCodexAcp,
|
|
13
|
+
};
|
package/backend/acp-runtime.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const EventEmitter = require('events');
|
|
2
|
+
const crypto = require('crypto');
|
|
2
3
|
const fs = require('fs');
|
|
3
4
|
const path = require('path');
|
|
4
5
|
const { spawn } = require('child_process');
|
|
@@ -9,6 +10,7 @@ const { AcpCheckpointStore } = require('./acp-checkpoint-store');
|
|
|
9
10
|
const { AcpSessionState } = require('./acp-session-state');
|
|
10
11
|
const { readCodexHistoryImageData } = require('./codex-transcript');
|
|
11
12
|
const { AcpClientFileSystem, AcpClientTerminalManager } = require('./acp/client-services');
|
|
13
|
+
const { PACKAGED_CODEX_ACP_ARG } = require('./acp/packaged-codex-acp');
|
|
12
14
|
const { permissionSecurityWarnings } = require('./acp/permission-security');
|
|
13
15
|
const { rejectPatch } = require('./acp/patch-decisions');
|
|
14
16
|
const { getProviderAdapter, listProviderAdapters } = require('./provider-adapters');
|
|
@@ -25,6 +27,17 @@ const DEFAULT_HISTORY_REPLAY_MIN_WAIT_MS = 350;
|
|
|
25
27
|
const DEFAULT_HISTORY_REPLAY_QUIET_MS = 150;
|
|
26
28
|
const DEFAULT_HISTORY_REPLAY_MAX_WAIT_MS = 5_000;
|
|
27
29
|
const CODEX_SET_SESSION_MODEL_METHOD = 'session/set_model';
|
|
30
|
+
const CODEX_STEER_METHOD = '_codex/session/steer';
|
|
31
|
+
const CODEX_ACP_PACKAGE = '@agentclientprotocol/codex-acp';
|
|
32
|
+
const CODEX_ACP_VERSION = '1.1.4';
|
|
33
|
+
const CODEX_ACP_SHA256 = '39cbae01e336c2ca185d624358e03280d1f6fef6d73bbe42dd9eb77e2b1efb32';
|
|
34
|
+
const CODEX_ACP_VENDOR_ENTRY = path.join(
|
|
35
|
+
__dirname,
|
|
36
|
+
'..',
|
|
37
|
+
'dist',
|
|
38
|
+
'acp',
|
|
39
|
+
`codex-acp-${CODEX_ACP_VERSION}.js`,
|
|
40
|
+
);
|
|
28
41
|
|
|
29
42
|
let sdkPromise;
|
|
30
43
|
const runtimeRequire = createRequire(__filename);
|
|
@@ -33,7 +46,24 @@ function loadAcpSdk() {
|
|
|
33
46
|
return sdkPromise;
|
|
34
47
|
}
|
|
35
48
|
|
|
49
|
+
function fileSha256(filePath) {
|
|
50
|
+
return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function verifiedCodexAcpEntry(entry) {
|
|
54
|
+
const actualSha256 = fileSha256(entry);
|
|
55
|
+
if (actualSha256 !== CODEX_ACP_SHA256) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Codex ACP runtime failed integrity verification: expected ${CODEX_ACP_SHA256}, found ${actualSha256}`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
return entry;
|
|
61
|
+
}
|
|
62
|
+
|
|
36
63
|
function adapterEntry(packageName) {
|
|
64
|
+
if (packageName === CODEX_ACP_PACKAGE && fs.existsSync(CODEX_ACP_VENDOR_ENTRY)) {
|
|
65
|
+
return verifiedCodexAcpEntry(CODEX_ACP_VENDOR_ENTRY);
|
|
66
|
+
}
|
|
37
67
|
let sdkDirectory;
|
|
38
68
|
try {
|
|
39
69
|
sdkDirectory = path.dirname(runtimeRequire.resolve('@agentclientprotocol/sdk'));
|
|
@@ -42,11 +72,15 @@ function adapterEntry(packageName) {
|
|
|
42
72
|
}
|
|
43
73
|
const entry = path.resolve(sdkDirectory, '..', '..', packageName.split('/').pop(), 'dist', 'index.js');
|
|
44
74
|
if (!fs.existsSync(entry)) throw new Error(`ACP adapter is not installed: ${packageName}`);
|
|
75
|
+
if (packageName === CODEX_ACP_PACKAGE) return verifiedCodexAcpEntry(entry);
|
|
45
76
|
return entry;
|
|
46
77
|
}
|
|
47
78
|
|
|
48
|
-
function nodeAdapterLaunch(entry, env = process.env) {
|
|
79
|
+
function nodeAdapterLaunch(entry, env = process.env, packagedArg = '') {
|
|
49
80
|
const runtimeEnv = env && typeof env === 'object' ? env : process.env;
|
|
81
|
+
if (process.pkg && packagedArg) {
|
|
82
|
+
return { command: process.execPath, args: [packagedArg] };
|
|
83
|
+
}
|
|
50
84
|
const nodeBin = runtimeEnv.FARMING_NODE_BIN || process.execPath;
|
|
51
85
|
const ldPath = runtimeEnv.FARMING_NODE_LD || '';
|
|
52
86
|
const libraryPath = runtimeEnv.FARMING_NODE_LIBRARY_PATH || '';
|
|
@@ -69,6 +103,7 @@ function resolveAcpLaunch(provider, options = {}) {
|
|
|
69
103
|
const launch = nodeAdapterLaunch(
|
|
70
104
|
adapterEntry(adapter.acp.packageName),
|
|
71
105
|
options.runtimeEnv || process.env,
|
|
106
|
+
adapter.acp.packageName === CODEX_ACP_PACKAGE ? PACKAGED_CODEX_ACP_ARG : '',
|
|
72
107
|
);
|
|
73
108
|
return { ...launch, version: adapter.acp.version };
|
|
74
109
|
}
|
|
@@ -202,6 +237,59 @@ function clone(value) {
|
|
|
202
237
|
return value == null ? value : JSON.parse(JSON.stringify(value));
|
|
203
238
|
}
|
|
204
239
|
|
|
240
|
+
function acpSessionRequestOptions(options = {}, cwd = process.cwd()) {
|
|
241
|
+
const root = path.resolve(cwd || process.cwd());
|
|
242
|
+
const additionalDirectories = Array.isArray(options.additionalDirectories)
|
|
243
|
+
? [...new Set(options.additionalDirectories
|
|
244
|
+
.filter(directory => typeof directory === 'string' && directory.trim())
|
|
245
|
+
.map(directory => path.resolve(root, directory)))]
|
|
246
|
+
: [];
|
|
247
|
+
const mcpServers = Array.isArray(options.mcpServers)
|
|
248
|
+
? clone(options.mcpServers.filter(server => server && typeof server === 'object' && !Array.isArray(server)))
|
|
249
|
+
: [];
|
|
250
|
+
return { cwd: root, additionalDirectories, mcpServers };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function promptContentForCapabilities(content, capabilities = {}) {
|
|
254
|
+
const promptCapabilities = capabilities?.promptCapabilities || {};
|
|
255
|
+
return (Array.isArray(content) ? content : []).flatMap(block => {
|
|
256
|
+
if (!block || typeof block !== 'object') return [];
|
|
257
|
+
if (!['image', 'audio'].includes(block.type)) return [clone(block)];
|
|
258
|
+
if (promptCapabilities[block.type] === true) return [clone(block)];
|
|
259
|
+
|
|
260
|
+
const label = block.type === 'audio' ? 'audio' : 'image';
|
|
261
|
+
const attachmentPath = typeof block.path === 'string' && block.path.trim()
|
|
262
|
+
? path.resolve(block.path)
|
|
263
|
+
: '';
|
|
264
|
+
const name = attachmentPath ? path.basename(attachmentPath) : `attached ${label}`;
|
|
265
|
+
const detail = attachmentPath
|
|
266
|
+
? `${label[0].toUpperCase()}${label.slice(1)} path: ${attachmentPath}`
|
|
267
|
+
: `[The ACP Agent does not accept native ${label} content]`;
|
|
268
|
+
return [{ type: 'text', text: `Attached ${label}: ${name}\n\n${detail}` }];
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function supportsCodexSteer(capabilities = {}) {
|
|
273
|
+
const capability = capabilities?._meta?.codex?.steer;
|
|
274
|
+
return capability?.method === CODEX_STEER_METHOD
|
|
275
|
+
&& Number.isFinite(Number(capability.version))
|
|
276
|
+
&& Number(capability.version) >= 1;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function isCodexSteerUnavailableError(error) {
|
|
280
|
+
const text = [
|
|
281
|
+
error?.message,
|
|
282
|
+
error?.data?.details,
|
|
283
|
+
error?.cause?.message,
|
|
284
|
+
error?.cause?.data?.details,
|
|
285
|
+
].filter(Boolean).join(' ').toLowerCase();
|
|
286
|
+
return text.includes('no active codex turn to steer')
|
|
287
|
+
|| text.includes('no active turn to steer')
|
|
288
|
+
|| /expected active turn id .* but found/.test(text)
|
|
289
|
+
|| text.includes('cannot steer a review turn')
|
|
290
|
+
|| text.includes('cannot steer a compact turn');
|
|
291
|
+
}
|
|
292
|
+
|
|
205
293
|
class AcpRuntime extends EventEmitter {
|
|
206
294
|
constructor(options = {}) {
|
|
207
295
|
super();
|
|
@@ -231,11 +319,14 @@ class AcpRuntime extends EventEmitter {
|
|
|
231
319
|
if (this.bindings.has(agentId)) throw new Error('ACP Agent is already registered');
|
|
232
320
|
const provider = String(options.provider || '').trim().toLowerCase();
|
|
233
321
|
const launch = this.resolveLaunch(provider, options);
|
|
322
|
+
const cwd = path.resolve(options.cwd || process.cwd());
|
|
323
|
+
const sessionRequestOptions = acpSessionRequestOptions(options, cwd);
|
|
234
324
|
const binding = {
|
|
235
325
|
agentId,
|
|
236
326
|
provider,
|
|
237
327
|
providerHomeId: String(options.providerHomeId || 'default'),
|
|
238
|
-
cwd
|
|
328
|
+
cwd,
|
|
329
|
+
sessionRequestOptions,
|
|
239
330
|
env: (getProviderAdapter(provider)?.prepareAcpEnvironment || (value => value.env || process.env))(options),
|
|
240
331
|
launch,
|
|
241
332
|
restartOptions: { ...options, agentId, provider },
|
|
@@ -257,6 +348,8 @@ class AcpRuntime extends EventEmitter {
|
|
|
257
348
|
subagentStates: new Map(),
|
|
258
349
|
interactionOrigins: new Map(),
|
|
259
350
|
promptActive: false,
|
|
351
|
+
supportsSteer: false,
|
|
352
|
+
steerTail: null,
|
|
260
353
|
historyReplayActive: false,
|
|
261
354
|
sessionState: null,
|
|
262
355
|
authTerminal: null,
|
|
@@ -308,12 +401,14 @@ class AcpRuntime extends EventEmitter {
|
|
|
308
401
|
if (binding.initializeResponse.protocolVersion !== sdk.PROTOCOL_VERSION) {
|
|
309
402
|
throw new Error(`ACP protocol version mismatch: Agent selected ${binding.initializeResponse.protocolVersion}, Farming supports ${sdk.PROTOCOL_VERSION}`);
|
|
310
403
|
}
|
|
404
|
+
binding.supportsSteer = binding.provider === 'codex'
|
|
405
|
+
&& supportsCodexSteer(binding.initializeResponse.agentCapabilities);
|
|
311
406
|
|
|
312
407
|
const requestedSessionId = String(options.sessionId || '').trim();
|
|
313
408
|
const revisionBase = Number.isFinite(Number(options.revisionBase))
|
|
314
409
|
? Math.max(0, Math.floor(Number(options.revisionBase)))
|
|
315
410
|
: 0;
|
|
316
|
-
const sessionRequest = { sessionId: requestedSessionId,
|
|
411
|
+
const sessionRequest = { sessionId: requestedSessionId, ...binding.sessionRequestOptions };
|
|
317
412
|
let sessionResponse;
|
|
318
413
|
let historyMode = 'new';
|
|
319
414
|
if (requestedSessionId) {
|
|
@@ -413,7 +508,7 @@ class AcpRuntime extends EventEmitter {
|
|
|
413
508
|
}
|
|
414
509
|
} else {
|
|
415
510
|
sessionResponse = await withTimeout(
|
|
416
|
-
connection.newSession(
|
|
511
|
+
connection.newSession(binding.sessionRequestOptions),
|
|
417
512
|
this.sessionSetupTimeoutMs,
|
|
418
513
|
'ACP session/new'
|
|
419
514
|
);
|
|
@@ -697,9 +792,11 @@ class AcpRuntime extends EventEmitter {
|
|
|
697
792
|
}
|
|
698
793
|
|
|
699
794
|
requestElicitation(binding, request) {
|
|
700
|
-
const
|
|
701
|
-
const
|
|
702
|
-
|
|
795
|
+
const hasSessionScope = typeof request?.sessionId === 'string' && request.sessionId.length > 0;
|
|
796
|
+
const requestSessionId = hasSessionScope ? String(request.sessionId) : '';
|
|
797
|
+
const isPrimarySession = hasSessionScope && requestSessionId === binding.sessionId;
|
|
798
|
+
const isRequestScoped = !hasSessionScope;
|
|
799
|
+
if (!isRequestScoped && !isPrimarySession && !binding.subagentStates.has(requestSessionId)) {
|
|
703
800
|
throw new Error('ACP elicitation does not match an active session');
|
|
704
801
|
}
|
|
705
802
|
if (!['form', 'url'].includes(String(request?.mode || ''))) {
|
|
@@ -715,7 +812,7 @@ class AcpRuntime extends EventEmitter {
|
|
|
715
812
|
...cloned,
|
|
716
813
|
...(protocolRequestId !== undefined ? { protocolRequestId } : {}),
|
|
717
814
|
requestId,
|
|
718
|
-
origin: isPrimarySession ? 'agent' : 'subagent',
|
|
815
|
+
origin: isRequestScoped ? 'request' : isPrimarySession ? 'agent' : 'subagent',
|
|
719
816
|
};
|
|
720
817
|
binding.interactionOrigins.set(requestId, binding.state);
|
|
721
818
|
binding.pendingElicitations.set(requestId, pending);
|
|
@@ -757,7 +854,11 @@ class AcpRuntime extends EventEmitter {
|
|
|
757
854
|
async prompt(agentId, prompt) {
|
|
758
855
|
const binding = this.requireBinding(agentId);
|
|
759
856
|
if (!['idle', 'error'].includes(binding.state)) throw new Error(`ACP Agent is not ready (${binding.state})`);
|
|
760
|
-
const
|
|
857
|
+
const rawContent = Array.isArray(prompt) ? prompt : [{ type: 'text', text: String(prompt || '') }];
|
|
858
|
+
const content = promptContentForCapabilities(
|
|
859
|
+
rawContent,
|
|
860
|
+
binding.initializeResponse?.agentCapabilities || {},
|
|
861
|
+
);
|
|
761
862
|
await this.markCheckpointDirty(binding);
|
|
762
863
|
binding.sessionState.beginPrompt(content);
|
|
763
864
|
binding.promptActive = true;
|
|
@@ -795,6 +896,54 @@ class AcpRuntime extends EventEmitter {
|
|
|
795
896
|
}
|
|
796
897
|
}
|
|
797
898
|
|
|
899
|
+
canSteer(agentId) {
|
|
900
|
+
const binding = this.bindings.get(agentId);
|
|
901
|
+
return binding?.provider === 'codex'
|
|
902
|
+
&& binding.supportsSteer === true
|
|
903
|
+
&& binding.promptActive === true
|
|
904
|
+
&& Boolean(binding.sessionId)
|
|
905
|
+
&& Boolean(binding.connection);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
async steer(agentId, prompt) {
|
|
909
|
+
const binding = this.requireBinding(agentId);
|
|
910
|
+
if (binding.provider !== 'codex' || binding.supportsSteer !== true) {
|
|
911
|
+
throw new Error(`${binding.provider} ACP Agent does not support steer`);
|
|
912
|
+
}
|
|
913
|
+
const rawContent = Array.isArray(prompt) ? prompt : [{ type: 'text', text: String(prompt || '') }];
|
|
914
|
+
const content = promptContentForCapabilities(
|
|
915
|
+
rawContent,
|
|
916
|
+
binding.initializeResponse?.agentCapabilities || {},
|
|
917
|
+
);
|
|
918
|
+
const clientMessageId = `farming-steer-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
919
|
+
const operation = async () => {
|
|
920
|
+
if (!binding.promptActive) throw new Error('No active Codex turn to steer');
|
|
921
|
+
await this.markCheckpointDirty(binding);
|
|
922
|
+
const response = await withTimeout(
|
|
923
|
+
binding.connection.request(CODEX_STEER_METHOD, {
|
|
924
|
+
sessionId: binding.sessionId,
|
|
925
|
+
prompt: content,
|
|
926
|
+
clientMessageId,
|
|
927
|
+
}),
|
|
928
|
+
this.requestTimeoutMs,
|
|
929
|
+
'Codex ACP steer',
|
|
930
|
+
);
|
|
931
|
+
return {
|
|
932
|
+
sessionId: binding.sessionId,
|
|
933
|
+
turnId: String(response?.turnId || ''),
|
|
934
|
+
clientMessageId,
|
|
935
|
+
};
|
|
936
|
+
};
|
|
937
|
+
const previous = binding.steerTail || Promise.resolve();
|
|
938
|
+
const pending = previous.catch(() => {}).then(operation);
|
|
939
|
+
binding.steerTail = pending;
|
|
940
|
+
try {
|
|
941
|
+
return await pending;
|
|
942
|
+
} finally {
|
|
943
|
+
if (binding.steerTail === pending) binding.steerTail = null;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
|
|
798
947
|
async cancel(agentId) {
|
|
799
948
|
const binding = this.requireBinding(agentId);
|
|
800
949
|
if (!binding.sessionId) return false;
|
|
@@ -871,10 +1020,18 @@ class AcpRuntime extends EventEmitter {
|
|
|
871
1020
|
if (!capabilities?.list) throw new Error(`${binding.provider} ACP Agent does not support session/list`);
|
|
872
1021
|
return withTimeout(binding.connection.listSessions({
|
|
873
1022
|
...(options.cwd ? { cwd: path.resolve(options.cwd) } : {}),
|
|
1023
|
+
...(Array.isArray(options.additionalDirectories)
|
|
1024
|
+
? { additionalDirectories: acpSessionRequestOptions(options, options.cwd || binding.cwd).additionalDirectories }
|
|
1025
|
+
: {}),
|
|
874
1026
|
...(options.cursor ? { cursor: String(options.cursor) } : {}),
|
|
875
1027
|
}), this.requestTimeoutMs, 'ACP session/list');
|
|
876
1028
|
}
|
|
877
1029
|
|
|
1030
|
+
getSessionRequestOptions(agentId) {
|
|
1031
|
+
const binding = this.requireBinding(agentId);
|
|
1032
|
+
return clone(binding.sessionRequestOptions);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
878
1035
|
async authenticate(agentId, methodId) {
|
|
879
1036
|
const binding = this.requireBinding(agentId);
|
|
880
1037
|
const method = binding.initializeResponse?.authMethods?.find(item => item.id === methodId);
|
|
@@ -896,6 +1053,20 @@ class AcpRuntime extends EventEmitter {
|
|
|
896
1053
|
return { authenticated: true, methodId };
|
|
897
1054
|
}
|
|
898
1055
|
|
|
1056
|
+
async logout(agentId) {
|
|
1057
|
+
const binding = this.requireBinding(agentId);
|
|
1058
|
+
if (binding.initializeResponse?.agentCapabilities?.auth?.logout == null) {
|
|
1059
|
+
throw new Error(`${binding.provider} ACP Agent does not support logout`);
|
|
1060
|
+
}
|
|
1061
|
+
await withTimeout(binding.connection.logout({}), this.requestTimeoutMs, 'ACP logout');
|
|
1062
|
+
binding.error = '';
|
|
1063
|
+
binding.stopReason = '';
|
|
1064
|
+
binding.updatedAt = new Date().toISOString();
|
|
1065
|
+
this.emitRuntime(binding);
|
|
1066
|
+
this.emitSession(binding);
|
|
1067
|
+
return { loggedOut: true };
|
|
1068
|
+
}
|
|
1069
|
+
|
|
899
1070
|
terminalAuthenticationLaunch(binding, method) {
|
|
900
1071
|
const legacy = method?._meta?.['terminal-auth'];
|
|
901
1072
|
if (legacy && typeof legacy === 'object' && legacy.command) {
|
|
@@ -994,13 +1165,13 @@ class AcpRuntime extends EventEmitter {
|
|
|
994
1165
|
const binding = this.requireBinding(agentId);
|
|
995
1166
|
const capabilities = binding.initializeResponse?.agentCapabilities?.sessionCapabilities;
|
|
996
1167
|
if (!capabilities?.fork) throw new Error(`${binding.provider} ACP Agent does not support session/fork`);
|
|
1168
|
+
const sessionOptions = acpSessionRequestOptions({
|
|
1169
|
+
additionalDirectories: options.additionalDirectories ?? binding.sessionRequestOptions.additionalDirectories,
|
|
1170
|
+
mcpServers: options.mcpServers ?? binding.sessionRequestOptions.mcpServers,
|
|
1171
|
+
}, options.cwd || binding.cwd);
|
|
997
1172
|
return withTimeout(binding.connection.unstable_forkSession({
|
|
998
1173
|
sessionId: options.sessionId || binding.sessionId,
|
|
999
|
-
|
|
1000
|
-
additionalDirectories: Array.isArray(options.additionalDirectories)
|
|
1001
|
-
? options.additionalDirectories.map(directory => path.resolve(directory))
|
|
1002
|
-
: [],
|
|
1003
|
-
mcpServers: [],
|
|
1174
|
+
...sessionOptions,
|
|
1004
1175
|
}), this.sessionSetupTimeoutMs, 'ACP session/fork');
|
|
1005
1176
|
}
|
|
1006
1177
|
|
|
@@ -1186,6 +1357,7 @@ class AcpRuntime extends EventEmitter {
|
|
|
1186
1357
|
error: binding.error,
|
|
1187
1358
|
errorKind: binding.error ? acpErrorKind(binding.error) : '',
|
|
1188
1359
|
stopReason: binding.stopReason,
|
|
1360
|
+
supportsSteer: binding.supportsSteer === true,
|
|
1189
1361
|
protocolVersion: binding.initializeResponse?.protocolVersion || null,
|
|
1190
1362
|
agentInfo: binding.initializeResponse?.agentInfo || null,
|
|
1191
1363
|
capabilities: binding.initializeResponse?.agentCapabilities || {},
|
|
@@ -1376,6 +1548,7 @@ class AcpRuntime extends EventEmitter {
|
|
|
1376
1548
|
error: binding.error,
|
|
1377
1549
|
errorKind: binding.error ? acpErrorKind(binding.error) : '',
|
|
1378
1550
|
stopReason: binding.stopReason,
|
|
1551
|
+
supportsSteer: binding.supportsSteer === true,
|
|
1379
1552
|
pendingPermission: binding.pendingPermissions.values().next().value || null,
|
|
1380
1553
|
pendingPermissions: [...binding.pendingPermissions.values()],
|
|
1381
1554
|
pendingElicitation: binding.pendingElicitations.values().next().value || null,
|
|
@@ -1452,8 +1625,13 @@ class AcpRuntime extends EventEmitter {
|
|
|
1452
1625
|
module.exports = {
|
|
1453
1626
|
AcpRuntime,
|
|
1454
1627
|
ADAPTER_VERSIONS,
|
|
1628
|
+
acpSessionRequestOptions,
|
|
1455
1629
|
acpErrorKind,
|
|
1456
1630
|
autoPermissionResponse,
|
|
1457
1631
|
codexAcpEnvironment,
|
|
1632
|
+
promptContentForCapabilities,
|
|
1458
1633
|
resolveAcpLaunch,
|
|
1634
|
+
supportsCodexSteer,
|
|
1635
|
+
isCodexSteerUnavailableError,
|
|
1636
|
+
CODEX_STEER_METHOD,
|
|
1459
1637
|
};
|