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
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
1
2
|
const path = require('path');
|
|
2
3
|
const { findAgentSession } = require('./agent-session-history');
|
|
3
|
-
const {
|
|
4
|
-
|
|
4
|
+
const {
|
|
5
|
+
codexSessionDateKeys,
|
|
6
|
+
listCodexSessionIdentities,
|
|
7
|
+
} = require('./codex-session-history');
|
|
5
8
|
const { mainPageAgentSessionKey } = require('./main-page-session');
|
|
6
9
|
const { isTemporaryProviderSessionId } = require('./provider-session-id');
|
|
7
10
|
|
|
8
11
|
const CODEX_RESOLVE_COOLDOWN_MS = 1000;
|
|
9
|
-
const
|
|
12
|
+
const CODEX_MATCH_WINDOW_MS = 30 * 1000;
|
|
13
|
+
const CODEX_STARTUP_RETRY_DELAYS_MS = [500, 1000, 2000, 4000, 8000, 12000];
|
|
10
14
|
const TITLE_RESOLVE_COOLDOWN_MS = 30 * 1000;
|
|
11
15
|
|
|
12
16
|
function normalizePath(value) {
|
|
@@ -16,6 +20,16 @@ function normalizePath(value) {
|
|
|
16
20
|
return trimmed.replace(/[\\/]+$/, '');
|
|
17
21
|
}
|
|
18
22
|
|
|
23
|
+
function canonicalPath(value) {
|
|
24
|
+
const normalized = normalizePath(value);
|
|
25
|
+
if (!normalized) return '';
|
|
26
|
+
try {
|
|
27
|
+
return normalizePath(fs.realpathSync.native(normalized));
|
|
28
|
+
} catch {
|
|
29
|
+
return normalized;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
function timestampMs(value) {
|
|
20
34
|
const parsed = Date.parse(value || '');
|
|
21
35
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
@@ -26,10 +40,14 @@ class ProviderSessionService {
|
|
|
26
40
|
this.agents = options.agents || new Map();
|
|
27
41
|
this.getProviderHomes = options.getProviderHomes || (() => undefined);
|
|
28
42
|
this.commit = options.commit || (() => {});
|
|
29
|
-
this.
|
|
43
|
+
this.listCodexSessionIdentities = options.listCodexSessionIdentities || listCodexSessionIdentities;
|
|
30
44
|
this.findAgentSession = options.findAgentSession || findAgentSession;
|
|
31
|
-
this.
|
|
45
|
+
this.codexStartupRetryDelaysMs = Array.isArray(options.codexStartupRetryDelaysMs)
|
|
46
|
+
? options.codexStartupRetryDelaysMs
|
|
47
|
+
: CODEX_STARTUP_RETRY_DELAYS_MS;
|
|
32
48
|
this.resolutions = new Map();
|
|
49
|
+
this.codexIdentityScans = new Map();
|
|
50
|
+
this.codexStartupRetries = new Map();
|
|
33
51
|
}
|
|
34
52
|
|
|
35
53
|
activate(agentId) {
|
|
@@ -38,6 +56,7 @@ class ProviderSessionService {
|
|
|
38
56
|
|
|
39
57
|
if (agent.providerSessionProvider === 'codex' && agent.providerSessionTemporary === true) {
|
|
40
58
|
this.observe(agentId, { force: true });
|
|
59
|
+
this.scheduleTemporaryCodexStartupRetry(agentId);
|
|
41
60
|
return;
|
|
42
61
|
}
|
|
43
62
|
|
|
@@ -54,10 +73,71 @@ class ProviderSessionService {
|
|
|
54
73
|
stop(agentId) {
|
|
55
74
|
this.resolutions.delete(`codex:${agentId}`);
|
|
56
75
|
this.resolutions.delete(`title:${agentId}`);
|
|
76
|
+
const startupRetry = this.codexStartupRetries.get(agentId);
|
|
77
|
+
if (startupRetry?.timer) clearTimeout(startupRetry.timer);
|
|
78
|
+
this.codexStartupRetries.delete(agentId);
|
|
57
79
|
}
|
|
58
80
|
|
|
59
81
|
dispose() {
|
|
82
|
+
for (const startupRetry of this.codexStartupRetries.values()) {
|
|
83
|
+
if (startupRetry?.timer) clearTimeout(startupRetry.timer);
|
|
84
|
+
}
|
|
60
85
|
this.resolutions.clear();
|
|
86
|
+
this.codexIdentityScans.clear();
|
|
87
|
+
this.codexStartupRetries.clear();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
scheduleTemporaryCodexStartupRetry(agentId) {
|
|
91
|
+
if (this.codexStartupRetries.has(agentId)) return;
|
|
92
|
+
const agent = this.agents.get(agentId);
|
|
93
|
+
const retry = {
|
|
94
|
+
attempt: 0,
|
|
95
|
+
timer: null,
|
|
96
|
+
deadlineAt: (Number(agent?.startedAt) || Date.now()) + CODEX_MATCH_WINDOW_MS,
|
|
97
|
+
};
|
|
98
|
+
this.codexStartupRetries.set(agentId, retry);
|
|
99
|
+
|
|
100
|
+
const scheduleNext = () => {
|
|
101
|
+
if (this.codexStartupRetries.get(agentId) !== retry) return;
|
|
102
|
+
const agent = this.agents.get(agentId);
|
|
103
|
+
if (
|
|
104
|
+
!agent
|
|
105
|
+
|| agent.providerSessionProvider !== 'codex'
|
|
106
|
+
|| agent.providerSessionTemporary !== true
|
|
107
|
+
) {
|
|
108
|
+
this.stop(agentId);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const delayMs = Number(this.codexStartupRetryDelaysMs[retry.attempt]);
|
|
112
|
+
if (!Number.isFinite(delayMs) || delayMs < 0) {
|
|
113
|
+
this.codexStartupRetries.delete(agentId);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (Date.now() + delayMs > retry.deadlineAt) {
|
|
117
|
+
this.codexStartupRetries.delete(agentId);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
retry.attempt += 1;
|
|
121
|
+
retry.timer = setTimeout(async () => {
|
|
122
|
+
retry.timer = null;
|
|
123
|
+
if (
|
|
124
|
+
this.codexStartupRetries.get(agentId) !== retry
|
|
125
|
+
|| Date.now() > retry.deadlineAt
|
|
126
|
+
) {
|
|
127
|
+
if (this.codexStartupRetries.get(agentId) === retry) {
|
|
128
|
+
this.codexStartupRetries.delete(agentId);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const resolved = await this.resolveTemporaryCodex(agentId, { force: true });
|
|
133
|
+
if (this.codexStartupRetries.get(agentId) !== retry) return;
|
|
134
|
+
if (resolved) return;
|
|
135
|
+
scheduleNext();
|
|
136
|
+
}, delayMs);
|
|
137
|
+
retry.timer.unref?.();
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
scheduleNext();
|
|
61
141
|
}
|
|
62
142
|
|
|
63
143
|
bindConfirmed(agentId, provider, sessionId) {
|
|
@@ -97,6 +177,14 @@ class ProviderSessionService {
|
|
|
97
177
|
this.resolutions.delete(`codex:${agentId}`);
|
|
98
178
|
return Promise.resolve(false);
|
|
99
179
|
}
|
|
180
|
+
const startedAt = Number(agent.startedAt) || 0;
|
|
181
|
+
if (
|
|
182
|
+
options.force !== true
|
|
183
|
+
&& startedAt
|
|
184
|
+
&& Date.now() > startedAt + CODEX_MATCH_WINDOW_MS
|
|
185
|
+
) {
|
|
186
|
+
return Promise.resolve(false);
|
|
187
|
+
}
|
|
100
188
|
|
|
101
189
|
return this.runResolution(
|
|
102
190
|
'codex',
|
|
@@ -167,15 +255,30 @@ class ProviderSessionService {
|
|
|
167
255
|
}
|
|
168
256
|
|
|
169
257
|
async findTemporaryCodexSession(agent) {
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
258
|
+
const startedAt = Number(agent.startedAt) || 0;
|
|
259
|
+
const codexHome = agent.providerHomePath || '';
|
|
260
|
+
const scanKey = [
|
|
261
|
+
codexHome,
|
|
262
|
+
...codexSessionDateKeys(startedAt, CODEX_MATCH_WINDOW_MS),
|
|
263
|
+
].join('\0');
|
|
264
|
+
let scan = this.codexIdentityScans.get(scanKey);
|
|
265
|
+
if (!scan) {
|
|
266
|
+
scan = Promise.resolve(this.listCodexSessionIdentities({
|
|
267
|
+
codexHome: codexHome || undefined,
|
|
268
|
+
startedAt,
|
|
269
|
+
windowMs: CODEX_MATCH_WINDOW_MS,
|
|
270
|
+
})).finally(() => {
|
|
271
|
+
if (this.codexIdentityScans.get(scanKey) === scan) {
|
|
272
|
+
this.codexIdentityScans.delete(scanKey);
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
this.codexIdentityScans.set(scanKey, scan);
|
|
276
|
+
}
|
|
277
|
+
const sessions = await scan;
|
|
175
278
|
const workspace = normalizePath(
|
|
176
|
-
agent?.
|
|
279
|
+
agent?.projectWorkspace || agent?.cwd || agent?.gitWorktree?.workspace || ''
|
|
177
280
|
);
|
|
178
|
-
|
|
281
|
+
if (!workspace) return null;
|
|
179
282
|
const homeId = String(agent.providerHomeId || 'default').trim() || 'default';
|
|
180
283
|
const claimedSessionIds = new Set([...this.agents.values()]
|
|
181
284
|
.filter(candidate => candidate?.id !== agent.id
|
|
@@ -189,35 +292,36 @@ class ProviderSessionService {
|
|
|
189
292
|
if (!session?.id || claimedSessionIds.has(session.id)) return false;
|
|
190
293
|
const sessionWorkspace = normalizePath(session.workspace || session.cwd);
|
|
191
294
|
if (workspace && !sessionWorkspace) return false;
|
|
192
|
-
const sessionTime = timestampMs(session.createdAt
|
|
193
|
-
if (!sessionTime || !startedAt) return
|
|
194
|
-
return sessionTime
|
|
195
|
-
})
|
|
196
|
-
.sort((a, b) => {
|
|
197
|
-
const aTime = timestampMs(a.createdAt || a.updatedAt);
|
|
198
|
-
const bTime = timestampMs(b.createdAt || b.updatedAt);
|
|
199
|
-
const aDistance = startedAt && aTime ? Math.abs(aTime - startedAt) : Number.MAX_SAFE_INTEGER;
|
|
200
|
-
const bDistance = startedAt && bTime ? Math.abs(bTime - startedAt) : Number.MAX_SAFE_INTEGER;
|
|
201
|
-
if (aDistance !== bDistance) return aDistance - bDistance;
|
|
202
|
-
return bTime - aTime;
|
|
295
|
+
const sessionTime = timestampMs(session.createdAt);
|
|
296
|
+
if (!sessionTime || !startedAt) return false;
|
|
297
|
+
return Math.abs(sessionTime - startedAt) <= CODEX_MATCH_WINDOW_MS;
|
|
203
298
|
});
|
|
204
299
|
|
|
205
|
-
const exact = candidates.
|
|
206
|
-
|
|
300
|
+
const exact = candidates.filter(session => (
|
|
301
|
+
workspace === normalizePath(session.workspace || session.cwd)
|
|
207
302
|
));
|
|
208
|
-
if (exact) return exact;
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
303
|
+
if (exact.length === 1) return exact[0];
|
|
304
|
+
if (exact.length > 1) return null;
|
|
305
|
+
const canonicalWorkspace = canonicalPath(workspace);
|
|
306
|
+
const canonical = candidates.filter(session => (
|
|
307
|
+
canonicalWorkspace === canonicalPath(session.workspace || session.cwd)
|
|
308
|
+
));
|
|
309
|
+
if (canonical.length === 1) return canonical[0];
|
|
216
310
|
return null;
|
|
217
311
|
}
|
|
218
312
|
|
|
219
313
|
confirm(agentId, { provider, sessionId, source, title, workspace }) {
|
|
220
|
-
const
|
|
314
|
+
const current = this.agents.get(agentId);
|
|
315
|
+
const homeId = String(current?.providerHomeId || 'default').trim() || 'default';
|
|
316
|
+
const claimedByAnotherAgent = [...this.agents.values()].some(candidate => (
|
|
317
|
+
candidate?.id !== agentId
|
|
318
|
+
&& candidate?.providerSessionProvider === provider
|
|
319
|
+
&& candidate?.providerSessionId === sessionId
|
|
320
|
+
&& candidate?.providerSessionTemporary !== true
|
|
321
|
+
&& (String(candidate.providerHomeId || 'default').trim() || 'default') === homeId
|
|
322
|
+
));
|
|
323
|
+
if (claimedByAnotherAgent) return false;
|
|
324
|
+
const previousSessionId = current?.providerSessionId || '';
|
|
221
325
|
const agent = this.bindConfirmed(agentId, provider, sessionId);
|
|
222
326
|
if (!agent) return false;
|
|
223
327
|
const providerSessionTitle = String(title || '').trim().slice(0, 160);
|
package/backend/server.js
CHANGED
|
@@ -50,7 +50,6 @@ const { discoverSlashCommands } = require('./slash-command-discovery');
|
|
|
50
50
|
const { FarmingUpdateService } = require('./update-service');
|
|
51
51
|
const { isRestartBlockingAgent } = require('./agent-activity');
|
|
52
52
|
const { inputPartsFromMessage } = require('./input-parts');
|
|
53
|
-
const { AppServerApiBridge, createAppServerApiRouter } = require('./app-server-api');
|
|
54
53
|
const { cleanupTerminalRuntime } = require('./terminal-runtime-cleanup');
|
|
55
54
|
const { QrShareTicketStore, SHARE_TICKET_TTL_MS } = require('./qr-share-tickets');
|
|
56
55
|
const { ReviewStateStore } = require('./review-state-store');
|
|
@@ -123,8 +122,11 @@ const updateService = new FarmingUpdateService({
|
|
|
123
122
|
packagedRuntime: Boolean(process.pkg || process.env.FARMING_PACKAGED_RUNTIME === '1'),
|
|
124
123
|
getUpdateUrl: () => configManager.getSettings().updateUrl || '',
|
|
125
124
|
});
|
|
126
|
-
const
|
|
127
|
-
|
|
125
|
+
const usageMonitor = new UsageMonitor({
|
|
126
|
+
agentManager,
|
|
127
|
+
configDir: configManager.farmingDir,
|
|
128
|
+
getProviderHomes: configuredProviderHomes,
|
|
129
|
+
});
|
|
128
130
|
const codexContextWindowReader = new CodexContextWindowReader();
|
|
129
131
|
const usageSummaryCache = new AsyncCache(() => usageMonitor.getUsageSummary(), {
|
|
130
132
|
ttlMs: 15_000,
|
|
@@ -531,10 +533,6 @@ app.use(routePath(BASE_PATH, '/api/control'), createControlRouter(agentManager,
|
|
|
531
533
|
&& process.env.FARMING_E2E_FAKE_EXECUTABLES === '1',
|
|
532
534
|
}));
|
|
533
535
|
|
|
534
|
-
app.use(routePath(BASE_PATH, '/api/app-server'), createAppServerApiRouter({
|
|
535
|
-
bridge: appServerApiBridge,
|
|
536
|
-
}));
|
|
537
|
-
|
|
538
536
|
app.get([
|
|
539
537
|
BASE_PATH || '/',
|
|
540
538
|
`${BASE_PATH || ''}/`,
|
|
@@ -602,9 +600,21 @@ const IMAGE_ATTACHMENT_EXTENSIONS = {
|
|
|
602
600
|
'image/gif': 'gif',
|
|
603
601
|
'image/webp': 'webp',
|
|
604
602
|
};
|
|
603
|
+
const AUDIO_ATTACHMENT_EXTENSIONS = {
|
|
604
|
+
'audio/aac': 'aac',
|
|
605
|
+
'audio/flac': 'flac',
|
|
606
|
+
'audio/mp4': 'm4a',
|
|
607
|
+
'audio/mpeg': 'mp3',
|
|
608
|
+
'audio/ogg': 'ogg',
|
|
609
|
+
'audio/wav': 'wav',
|
|
610
|
+
'audio/wave': 'wav',
|
|
611
|
+
'audio/webm': 'webm',
|
|
612
|
+
'audio/x-wav': 'wav',
|
|
613
|
+
};
|
|
605
614
|
const IMAGE_ATTACHMENT_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
606
615
|
const IMAGE_ATTACHMENT_GC_INTERVAL_MS = 60 * 60 * 1000;
|
|
607
616
|
const IMAGE_ATTACHMENT_FILENAME_RE = /^pasted-image-\d+-[a-f0-9]{8}\.(?:png|jpg|gif|webp)$/;
|
|
617
|
+
const AUDIO_ATTACHMENT_FILENAME_RE = /^pasted-audio-\d+-[a-f0-9]{8}\.(?:aac|flac|m4a|mp3|ogg|wav|webm)$/;
|
|
608
618
|
let lastImageAttachmentGcAt = 0;
|
|
609
619
|
|
|
610
620
|
function imageAttachmentExtension(contentType) {
|
|
@@ -612,6 +622,11 @@ function imageAttachmentExtension(contentType) {
|
|
|
612
622
|
return IMAGE_ATTACHMENT_EXTENSIONS[normalized] || '';
|
|
613
623
|
}
|
|
614
624
|
|
|
625
|
+
function audioAttachmentExtension(contentType) {
|
|
626
|
+
const normalized = String(contentType || '').split(';')[0].trim().toLowerCase();
|
|
627
|
+
return AUDIO_ATTACHMENT_EXTENSIONS[normalized] || '';
|
|
628
|
+
}
|
|
629
|
+
|
|
615
630
|
function imageAttachmentsDir() {
|
|
616
631
|
return path.join(configManager.farmingDir, 'attachments');
|
|
617
632
|
}
|
|
@@ -634,7 +649,7 @@ async function cleanupExpiredImageAttachments(options = {}) {
|
|
|
634
649
|
|
|
635
650
|
const cutoff = now - IMAGE_ATTACHMENT_RETENTION_MS;
|
|
636
651
|
await Promise.all(entries.map(async (entry) => {
|
|
637
|
-
if (!entry.isFile() || !IMAGE_ATTACHMENT_FILENAME_RE.test(entry.name)) return;
|
|
652
|
+
if (!entry.isFile() || (!IMAGE_ATTACHMENT_FILENAME_RE.test(entry.name) && !AUDIO_ATTACHMENT_FILENAME_RE.test(entry.name))) return;
|
|
638
653
|
|
|
639
654
|
const filePath = path.join(attachmentsDir, entry.name);
|
|
640
655
|
try {
|
|
@@ -684,6 +699,30 @@ app.post(
|
|
|
684
699
|
}
|
|
685
700
|
);
|
|
686
701
|
|
|
702
|
+
app.post(
|
|
703
|
+
routePath(BASE_PATH, '/api/attachments/audio'),
|
|
704
|
+
express.raw({ type: 'audio/*', limit: '25mb' }),
|
|
705
|
+
(req, res) => {
|
|
706
|
+
const contentType = String(req.headers['content-type'] || '').split(';')[0].trim().toLowerCase();
|
|
707
|
+
const extension = audioAttachmentExtension(contentType);
|
|
708
|
+
if (!extension) {
|
|
709
|
+
res.status(415).json({ error: 'unsupported audio type' });
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (!Buffer.isBuffer(req.body) || req.body.length === 0) {
|
|
713
|
+
res.status(400).json({ error: 'empty audio attachment' });
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
const attachmentsDir = imageAttachmentsDir();
|
|
717
|
+
fs.mkdirSync(attachmentsDir, { recursive: true });
|
|
718
|
+
const filename = `pasted-audio-${Date.now()}-${crypto.randomBytes(4).toString('hex')}.${extension}`;
|
|
719
|
+
const filePath = path.join(attachmentsDir, filename);
|
|
720
|
+
fs.writeFileSync(filePath, req.body);
|
|
721
|
+
void cleanupExpiredImageAttachments();
|
|
722
|
+
res.status(201).json({ path: filePath, name: filename, type: contentType, size: req.body.length });
|
|
723
|
+
}
|
|
724
|
+
);
|
|
725
|
+
|
|
687
726
|
app.get(routePath(BASE_PATH, '/api/codex/models'), async (req, res) => {
|
|
688
727
|
try {
|
|
689
728
|
const catalog = await codexModelOptionsCache.get('catalog');
|
|
@@ -968,8 +1007,8 @@ app.get(routePath(BASE_PATH, '/api/agents/:agentId/session-view'), async (req, r
|
|
|
968
1007
|
});
|
|
969
1008
|
|
|
970
1009
|
app.get(routePath(BASE_PATH, '/api/agents/:agentId/codex-transcript'), async (req, res) => {
|
|
971
|
-
// Legacy JSONL transcript reader.
|
|
972
|
-
//
|
|
1010
|
+
// Legacy JSONL transcript reader. ACP Chat uses the dedicated ACP endpoint;
|
|
1011
|
+
// Terminal Agents stay in their terminal UI.
|
|
973
1012
|
const providerSession = agentManager.getAgentProviderSession(req.params.agentId);
|
|
974
1013
|
if (!providerSession) {
|
|
975
1014
|
res.status(404).json({ error: 'Agent not found' });
|
|
@@ -999,9 +1038,7 @@ app.get(routePath(BASE_PATH, '/api/agents/:agentId/codex-transcript'), async (re
|
|
|
999
1038
|
: DEFAULT_CODEX_TRANSCRIPT_MAX_TURNS;
|
|
1000
1039
|
const transcript = await readCodexTranscript(providerSession.sessionId, {
|
|
1001
1040
|
maxTurns,
|
|
1002
|
-
codexHome:
|
|
1003
|
-
? (providerSession.codexAppServerHomePath || providerSession.providerHomePath || '')
|
|
1004
|
-
: (providerSession.providerHomePath || ''),
|
|
1041
|
+
codexHome: providerSession.providerHomePath || '',
|
|
1005
1042
|
});
|
|
1006
1043
|
res.json({ transcript });
|
|
1007
1044
|
} catch (error) {
|
|
@@ -1010,20 +1047,6 @@ app.get(routePath(BASE_PATH, '/api/agents/:agentId/codex-transcript'), async (re
|
|
|
1010
1047
|
}
|
|
1011
1048
|
});
|
|
1012
1049
|
|
|
1013
|
-
app.get(routePath(BASE_PATH, '/api/agents/:agentId/codex-app-server-transcript'), async (req, res) => {
|
|
1014
|
-
try {
|
|
1015
|
-
const requestedMaxTurns = Number.parseInt(String(req.query.maxTurns || ''), 10);
|
|
1016
|
-
const maxTurns = Number.isFinite(requestedMaxTurns)
|
|
1017
|
-
? Math.min(MAX_CODEX_TRANSCRIPT_TURNS, Math.max(20, requestedMaxTurns))
|
|
1018
|
-
: DEFAULT_CODEX_TRANSCRIPT_MAX_TURNS;
|
|
1019
|
-
const transcript = agentManager.getCodexAppServerTranscript(req.params.agentId, { maxTurns });
|
|
1020
|
-
res.json({ transcript });
|
|
1021
|
-
} catch (error) {
|
|
1022
|
-
const message = error && error.message ? error.message : 'Failed to read Codex App Server transcript';
|
|
1023
|
-
res.status(/not using the Codex App Server runtime/i.test(message) ? 409 : 500).json({ error: message });
|
|
1024
|
-
}
|
|
1025
|
-
});
|
|
1026
|
-
|
|
1027
1050
|
app.get(routePath(BASE_PATH, '/api/agents/:agentId/json-cli-transcript'), async (req, res) => {
|
|
1028
1051
|
try {
|
|
1029
1052
|
const transcript = agentManager.getJsonCliTranscript(req.params.agentId, {
|
|
@@ -1191,6 +1214,15 @@ app.post(routePath(BASE_PATH, '/api/agents/:agentId/acp-session/authenticate'),
|
|
|
1191
1214
|
}
|
|
1192
1215
|
});
|
|
1193
1216
|
|
|
1217
|
+
app.post(routePath(BASE_PATH, '/api/agents/:agentId/acp-session/logout'), async (req, res) => {
|
|
1218
|
+
try {
|
|
1219
|
+
res.json(await agentManager.logoutAcpAgent(req.params.agentId));
|
|
1220
|
+
} catch (error) {
|
|
1221
|
+
const message = error && error.message ? error.message : 'Failed to log out ACP Agent';
|
|
1222
|
+
res.status(message === 'Agent not found' ? 404 : 409).json({ error: message });
|
|
1223
|
+
}
|
|
1224
|
+
});
|
|
1225
|
+
|
|
1194
1226
|
app.post(routePath(BASE_PATH, '/api/agents/:agentId/acp-session/fork'), express.json(), async (req, res) => {
|
|
1195
1227
|
try {
|
|
1196
1228
|
res.json(await agentManager.forkAcpSession(req.params.agentId, req.body || {}));
|
|
@@ -1243,41 +1275,6 @@ app.patch(routePath(BASE_PATH, '/api/agents/:agentId/acp-session'), express.json
|
|
|
1243
1275
|
}
|
|
1244
1276
|
});
|
|
1245
1277
|
|
|
1246
|
-
app.get(routePath(BASE_PATH, '/api/agents/:agentId/codex-goal'), async (req, res) => {
|
|
1247
|
-
try {
|
|
1248
|
-
const goal = await agentManager.getCodexAppServerGoal(req.params.agentId);
|
|
1249
|
-
res.json({ goal });
|
|
1250
|
-
} catch (error) {
|
|
1251
|
-
const message = error && error.message ? error.message : 'Failed to read Codex goal';
|
|
1252
|
-
res.status(message === 'Agent not found' ? 404 : 400).json({ error: message });
|
|
1253
|
-
}
|
|
1254
|
-
});
|
|
1255
|
-
|
|
1256
|
-
app.patch(routePath(BASE_PATH, '/api/agents/:agentId/codex-goal'), express.json(), async (req, res) => {
|
|
1257
|
-
try {
|
|
1258
|
-
const body = req.body || {};
|
|
1259
|
-
const goal = await agentManager.setCodexAppServerGoal(req.params.agentId, {
|
|
1260
|
-
objective: typeof body.objective === 'string' ? body.objective : undefined,
|
|
1261
|
-
status: typeof body.status === 'string' ? body.status : undefined,
|
|
1262
|
-
...(Object.prototype.hasOwnProperty.call(body, 'tokenBudget') ? { tokenBudget: body.tokenBudget } : {}),
|
|
1263
|
-
});
|
|
1264
|
-
res.json({ goal });
|
|
1265
|
-
} catch (error) {
|
|
1266
|
-
const message = error && error.message ? error.message : 'Failed to update Codex goal';
|
|
1267
|
-
res.status(message === 'Agent not found' ? 404 : 400).json({ error: message });
|
|
1268
|
-
}
|
|
1269
|
-
});
|
|
1270
|
-
|
|
1271
|
-
app.delete(routePath(BASE_PATH, '/api/agents/:agentId/codex-goal'), async (req, res) => {
|
|
1272
|
-
try {
|
|
1273
|
-
await agentManager.clearCodexAppServerGoal(req.params.agentId);
|
|
1274
|
-
res.json({ goal: null });
|
|
1275
|
-
} catch (error) {
|
|
1276
|
-
const message = error && error.message ? error.message : 'Failed to clear Codex goal';
|
|
1277
|
-
res.status(message === 'Agent not found' ? 404 : 400).json({ error: message });
|
|
1278
|
-
}
|
|
1279
|
-
});
|
|
1280
|
-
|
|
1281
1278
|
app.post(routePath(BASE_PATH, '/api/agents/:agentId/codex-terminal-profile'), express.json(), async (req, res) => {
|
|
1282
1279
|
const requestAbort = new globalThis.AbortController();
|
|
1283
1280
|
const abortRequest = () => requestAbort.abort(new Error('Terminal profile request was canceled'));
|
|
@@ -1710,7 +1707,7 @@ async function resumeAgentSessionById(provider, rawSessionId, options = {}) {
|
|
|
1710
1707
|
requiredCliVersion: normalizedProvider === 'codex' && session ? session.cliVersion : '',
|
|
1711
1708
|
projectWorkspace: session ? (session.workspace || session.cwd || '') : '',
|
|
1712
1709
|
source: shouldFork ? resumeSource.replace('-history:', '-history-fork:') : resumeSource,
|
|
1713
|
-
agentRuntimeMode:
|
|
1710
|
+
agentRuntimeMode: ['chat', 'acp'].includes(options.agentRuntimeMode) ? 'chat' : 'terminal',
|
|
1714
1711
|
acpHistoryMode: options.acpHistoryMode === 'resume' ? 'resume' : 'load',
|
|
1715
1712
|
providerHomeId: resolvedProviderHomeId,
|
|
1716
1713
|
providerHomePath: session ? (session.providerHomePath || '') : '',
|
|
@@ -1754,7 +1751,7 @@ async function startResumedAgentSession(req, res, provider, rawSessionId) {
|
|
|
1754
1751
|
allowUnarchiveArchived,
|
|
1755
1752
|
providerHomeId: req.body && typeof req.body.providerHomeId === 'string' ? req.body.providerHomeId : '',
|
|
1756
1753
|
customTitle: req.body && typeof req.body.customTitle === 'string' ? req.body.customTitle : '',
|
|
1757
|
-
agentRuntimeMode: req.body && req.body.agentRuntimeMode
|
|
1754
|
+
agentRuntimeMode: req.body && ['chat', 'acp'].includes(req.body.agentRuntimeMode) ? 'chat' : 'terminal',
|
|
1758
1755
|
acpHistoryMode: req.body && req.body.acpHistoryMode === 'resume' ? 'resume' : 'load',
|
|
1759
1756
|
});
|
|
1760
1757
|
|
|
@@ -2018,66 +2015,66 @@ async function sendInputMessage(ws, data) {
|
|
|
2018
2015
|
|
|
2019
2016
|
async function sendComposerInputMessage(ws, data) {
|
|
2020
2017
|
const targetAgentId = resolveInputTargetAgentId(ws, data);
|
|
2018
|
+
const requestId = typeof data.requestId === 'string' ? data.requestId : '';
|
|
2019
|
+
const responseAgentId = targetAgentId || (typeof data.agentId === 'string' ? data.agentId : '');
|
|
2020
|
+
const respond = (accepted, message = '') => {
|
|
2021
|
+
if (!requestId || !responseAgentId) return;
|
|
2022
|
+
ws.send(JSON.stringify({
|
|
2023
|
+
type: 'composer-input-result',
|
|
2024
|
+
requestId,
|
|
2025
|
+
agentId: responseAgentId,
|
|
2026
|
+
accepted,
|
|
2027
|
+
...(message ? { message } : {}),
|
|
2028
|
+
}));
|
|
2029
|
+
};
|
|
2021
2030
|
const message = typeof data.message === 'string' ? data.message : '';
|
|
2022
2031
|
const content = [];
|
|
2023
2032
|
if (message.trim()) content.push({ type: 'text', text: message });
|
|
2024
2033
|
const attachmentsRoot = path.resolve(imageAttachmentsDir());
|
|
2025
2034
|
const attachments = Array.isArray(data.attachments) ? data.attachments.slice(0, 8) : [];
|
|
2026
2035
|
for (const attachment of attachments) {
|
|
2027
|
-
if (attachment?.kind
|
|
2036
|
+
if (!['image', 'audio'].includes(attachment?.kind) || typeof attachment.path !== 'string') continue;
|
|
2028
2037
|
const filePath = path.resolve(attachment.path);
|
|
2029
2038
|
if (!filePath.startsWith(`${attachmentsRoot}${path.sep}`)) continue;
|
|
2030
|
-
const mimeType = typeof attachment.type === 'string' && /^image\/(?:png|jpe?g|gif|webp)$/i.test(attachment.type)
|
|
2039
|
+
const mimeType = typeof attachment.type === 'string' && attachment.kind === 'image' && /^image\/(?:png|jpe?g|gif|webp)$/i.test(attachment.type)
|
|
2031
2040
|
? attachment.type.toLowerCase()
|
|
2032
|
-
: ''
|
|
2041
|
+
: (typeof attachment.type === 'string' && attachment.kind === 'audio' && Object.hasOwn(AUDIO_ATTACHMENT_EXTENSIONS, attachment.type.toLowerCase())
|
|
2042
|
+
? attachment.type.toLowerCase()
|
|
2043
|
+
: '');
|
|
2033
2044
|
if (!mimeType) continue;
|
|
2034
2045
|
try {
|
|
2035
2046
|
const data = await fs.promises.readFile(filePath);
|
|
2036
2047
|
if (data.length === 0 || data.length > 12 * 1024 * 1024) continue;
|
|
2037
2048
|
content.push({
|
|
2038
|
-
type:
|
|
2049
|
+
type: attachment.kind,
|
|
2039
2050
|
data: data.toString('base64'),
|
|
2040
2051
|
mimeType,
|
|
2052
|
+
path: filePath,
|
|
2041
2053
|
uri: pathToFileURL(filePath).href,
|
|
2042
2054
|
});
|
|
2043
2055
|
} catch {
|
|
2044
2056
|
// The text fallback already explains failed or unavailable uploads.
|
|
2045
2057
|
}
|
|
2046
2058
|
}
|
|
2047
|
-
if (!targetAgentId || content.length === 0)
|
|
2059
|
+
if (!targetAgentId || content.length === 0) {
|
|
2060
|
+
respond(false, 'Composer message is empty');
|
|
2061
|
+
return;
|
|
2062
|
+
}
|
|
2048
2063
|
const targetAgent = agentManager.getState().agents.find(agent => agent.id === targetAgentId);
|
|
2049
2064
|
const structuredRuntime = targetAgent && runtimeKind(targetAgent) !== 'terminal';
|
|
2050
2065
|
if (!structuredRuntime) {
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
}));
|
|
2066
|
+
const error = 'Terminal Composer input requires the active terminal owner';
|
|
2067
|
+
if (requestId) respond(false, error);
|
|
2068
|
+
else ws.send(JSON.stringify({ type: 'error', message: error }));
|
|
2055
2069
|
return;
|
|
2056
2070
|
}
|
|
2057
2071
|
try {
|
|
2058
2072
|
await agentManager.sendComposerMessage(targetAgentId, content);
|
|
2073
|
+
respond(true);
|
|
2059
2074
|
} catch (error) {
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
}));
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2066
|
-
|
|
2067
|
-
async function respondToAppServerRequest(ws, data) {
|
|
2068
|
-
const targetAgentId = resolveInputTargetAgentId(ws, data);
|
|
2069
|
-
const requestId = typeof data.requestId === 'string' ? data.requestId : '';
|
|
2070
|
-
if (!targetAgentId || !requestId) return;
|
|
2071
|
-
try {
|
|
2072
|
-
agentManager.respondToCodexAppServerRequest(targetAgentId, requestId, data.result, {
|
|
2073
|
-
reject: data.reject === true,
|
|
2074
|
-
reason: typeof data.reason === 'string' ? data.reason : '',
|
|
2075
|
-
});
|
|
2076
|
-
} catch (error) {
|
|
2077
|
-
ws.send(JSON.stringify({
|
|
2078
|
-
type: 'error',
|
|
2079
|
-
message: error && error.message ? error.message : 'Failed to respond to Codex App Server request',
|
|
2080
|
-
}));
|
|
2075
|
+
const message = error && error.message ? error.message : 'Failed to send Composer message';
|
|
2076
|
+
if (requestId) respond(false, message);
|
|
2077
|
+
else ws.send(JSON.stringify({ type: 'error', message }));
|
|
2081
2078
|
}
|
|
2082
2079
|
}
|
|
2083
2080
|
|
|
@@ -2107,12 +2104,11 @@ function handleMessage(ws, data) {
|
|
|
2107
2104
|
workflowTemplate: typeof data.workflowTemplate === 'string' ? data.workflowTemplate : '',
|
|
2108
2105
|
customTitle: typeof data.customTitle === 'string' ? data.customTitle : '',
|
|
2109
2106
|
codexApprovalMode: typeof data.codexApprovalMode === 'string' ? data.codexApprovalMode : undefined,
|
|
2110
|
-
|
|
2111
|
-
? data.codexRuntimeMode
|
|
2112
|
-
: undefined,
|
|
2113
|
-
agentRuntimeMode: ['json', 'acp'].includes(data.agentRuntimeMode) ? data.agentRuntimeMode : 'terminal',
|
|
2107
|
+
agentRuntimeMode: ['json', 'acp', 'chat'].includes(data.agentRuntimeMode) ? data.agentRuntimeMode : 'terminal',
|
|
2114
2108
|
acpHistoryMode: data.acpHistoryMode === 'resume' ? 'resume' : 'load',
|
|
2115
2109
|
providerHomeId: typeof data.providerHomeId === 'string' ? data.providerHomeId : '',
|
|
2110
|
+
...(Array.isArray(data.additionalDirectories) ? { additionalDirectories: data.additionalDirectories } : {}),
|
|
2111
|
+
...(Array.isArray(data.mcpServers) ? { mcpServers: data.mcpServers } : {}),
|
|
2116
2112
|
...(data.dangerouslySkipPermissions === true ? { dangerouslySkipPermissions: true } : {}),
|
|
2117
2113
|
});
|
|
2118
2114
|
break;
|
|
@@ -2129,12 +2125,6 @@ function handleMessage(ws, data) {
|
|
|
2129
2125
|
}
|
|
2130
2126
|
break;
|
|
2131
2127
|
|
|
2132
|
-
case 'app-server-request-response':
|
|
2133
|
-
{
|
|
2134
|
-
void respondToAppServerRequest(ws, data);
|
|
2135
|
-
}
|
|
2136
|
-
break;
|
|
2137
|
-
|
|
2138
2128
|
case 'acp-permission-response':
|
|
2139
2129
|
try {
|
|
2140
2130
|
agentManager.respondToAcpPermission(
|
|
@@ -2717,7 +2707,6 @@ module.exports = {
|
|
|
2717
2707
|
server,
|
|
2718
2708
|
wss,
|
|
2719
2709
|
agentManager,
|
|
2720
|
-
appServerApiBridge,
|
|
2721
2710
|
workspaceFileService,
|
|
2722
2711
|
handleMessage,
|
|
2723
2712
|
resolveCliBinDir,
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const os = require('os');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const crypto = require('crypto');
|
|
4
3
|
|
|
5
4
|
function farmingConfigDir(env = process.env) {
|
|
6
5
|
return env.FARMING_CONFIG_DIR || path.join(env.HOME || os.homedir(), '.farming');
|
|
@@ -42,6 +41,10 @@ function reviewSessionsFile(configDir) {
|
|
|
42
41
|
return path.join(historyDir(configDir), 'review-sessions.json');
|
|
43
42
|
}
|
|
44
43
|
|
|
44
|
+
function usageHistoryCacheFile(configDir) {
|
|
45
|
+
return path.join(historyDir(configDir), 'usage-history-v2.sqlite3');
|
|
46
|
+
}
|
|
47
|
+
|
|
45
48
|
function sessionIndexFile(configDir) {
|
|
46
49
|
return path.join(sessionsDir(configDir), 'index.json');
|
|
47
50
|
}
|
|
@@ -110,23 +113,8 @@ function farmingNetTrustFile(configDir) {
|
|
|
110
113
|
return path.join(configDir, 'farming-net-trust.json');
|
|
111
114
|
}
|
|
112
115
|
|
|
113
|
-
function codexAppServerRuntimeDir(configDir) {
|
|
114
|
-
return path.join(configDir, 'c');
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function codexAppServerAgentHome(configDir, agentId) {
|
|
118
|
-
const safeAgentId = String(agentId || '').trim();
|
|
119
|
-
if (!/^agent-[A-Za-z0-9_-]+$/.test(safeAgentId)) {
|
|
120
|
-
throw new Error('Invalid Codex App Server agent id');
|
|
121
|
-
}
|
|
122
|
-
const homeId = crypto.createHash('sha256').update(safeAgentId).digest('hex').slice(0, 16);
|
|
123
|
-
return path.join(codexAppServerRuntimeDir(configDir), homeId);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
116
|
module.exports = {
|
|
127
117
|
acpCheckpointsDir,
|
|
128
|
-
codexAppServerAgentHome,
|
|
129
|
-
codexAppServerRuntimeDir,
|
|
130
118
|
farmingNetInstancesFile,
|
|
131
119
|
farmingNetServerLogFile,
|
|
132
120
|
farmingNetServerStateFile,
|
|
@@ -153,4 +141,5 @@ module.exports = {
|
|
|
153
141
|
themeSettingsFile,
|
|
154
142
|
updateLogFile,
|
|
155
143
|
updateStateFile,
|
|
144
|
+
usageHistoryCacheFile,
|
|
156
145
|
};
|