farming-code 2.2.8 → 2.2.12
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 +42 -28
- package/README.zh_cn.md +42 -28
- package/THIRD_PARTY_NOTICES.md +11 -2
- package/backend/acp-checkpoint-store.js +198 -0
- package/backend/acp-runtime.js +306 -83
- package/backend/acp-session-state.js +202 -6
- package/backend/acp-transcript.js +112 -0
- package/backend/agent-activity.js +6 -157
- package/backend/agent-manager.js +1592 -741
- package/backend/agent-provider-session.js +17 -242
- package/backend/agent-runtime-binding.js +219 -0
- package/backend/agent-session-history.js +66 -1
- package/backend/auth.js +79 -6
- package/backend/codex-models.js +81 -84
- package/backend/codex-session-archive.js +54 -0
- package/backend/codex-terminal-profile.js +500 -0
- package/backend/codex-transcript-sanitizer.js +12 -0
- package/backend/codex-transcript.js +230 -8
- package/backend/config-manager.js +35 -0
- package/backend/control-api.js +192 -17
- package/backend/executable-discovery.js +2 -0
- package/backend/farming-net-pass.js +285 -0
- package/backend/farming-net-registry.js +112 -0
- package/backend/farming-net-server.js +298 -0
- package/backend/farming-session-store.js +5 -13
- package/backend/git-worktree-info.js +181 -0
- package/backend/local-session-engine.js +411 -186
- package/backend/main-page-session.js +5 -2
- package/backend/native-pty-controller-generation.js +106 -0
- package/backend/native-pty-host-client.js +275 -7
- package/backend/native-pty-host-identity.js +86 -0
- package/backend/native-pty-host.js +813 -114
- package/backend/native-session-engine.js +100 -28
- package/backend/packaged-node-pty.js +22 -2
- package/backend/provider-adapters.js +253 -0
- package/backend/provider-session-service.js +241 -0
- package/backend/runtime-observation.js +81 -0
- package/backend/server.js +354 -84
- package/backend/session-engine-bridge.js +21 -2
- package/backend/session-engine-router.js +1 -1
- package/backend/session-engine.js +1 -1
- package/backend/session-stream-protocol.js +185 -0
- package/backend/storage-layout.js +55 -0
- package/backend/terminal-attach-checkpoint.js +74 -0
- package/backend/terminal-exit-quiescence.js +39 -0
- package/backend/terminal-reducer-flow-control.js +97 -0
- package/backend/terminal-screen-state.js +11 -2
- package/backend/terminal-screen-worker-pool.js +59 -6
- package/backend/terminal-screen-worker-thread.js +97 -57
- package/backend/terminal-screen-worker.js +133 -51
- package/backend/terminal-state-serialization.js +127 -0
- package/backend/terminal-status.js +23 -4
- package/backend/usage-monitor.js +176 -17
- package/backend/workspace-directory.js +232 -0
- package/backend/workspace-file-router.js +182 -76
- package/backend/workspace-file-service.js +319 -4
- package/backend/workspace-root-registry.js +164 -0
- package/dist/assets/App-DWsa7DXG.js +206 -0
- package/dist/assets/{FileEditorMarkdownPreview-elKWc8Im.js → FileEditorMarkdownPreview-UlVd0O4D.js} +92 -92
- package/dist/assets/FileEditorPane-Dxx4rKOg.js +2 -0
- package/dist/assets/IconGlyphs-AU22gPDY.js +1 -0
- package/dist/assets/ProjectFilesSection-DE7Nb9Jk.js +12 -0
- package/dist/assets/ReviewPage-CeDKDyZh.js +3 -0
- package/dist/assets/code-dark-DPiuyeSp.css +1 -0
- package/dist/assets/file-icons-Bw2qd5iT.js +1 -0
- package/dist/assets/{index-BrbljRqn.js → index-CUFPJoa5.js} +3 -3
- package/dist/assets/main-Bqtb5kuz.css +1 -0
- package/dist/assets/{monaco.contribution-CyLosfZI.js → monaco.contribution-BXz-lfFB.js} +2 -2
- package/dist/assets/{tsMode-DGZTlTj8.js → tsMode-B0oUTcXQ.js} +1 -1
- package/dist/assets/workspace-editor-model-BQol4qbA.js +1 -0
- package/dist/assets/workspace-editor-monaco-BICJESG0.js +4 -0
- package/dist/assets/workspace-editor-monaco-DRJ7IaXk.js +1 -0
- package/dist/assets/workspace-view-state-DvYG_9PH.js +7 -0
- package/dist/assets/workspace-working-copy-D8-s_Sgh.js +1 -0
- package/dist/farming-2/site.webmanifest +2 -0
- package/dist/index.html +1 -1
- package/frontend/farming-net/app.css +625 -0
- package/frontend/farming-net/app.js +268 -0
- package/frontend/farming-net/index.html +86 -0
- package/frontend/reading-anchor.js +198 -0
- package/frontend/session-bridge.js +12 -3
- package/frontend/session-modal-bridge.js +5 -12
- package/frontend/skins/crt/app.js +1978 -793
- package/frontend/skins/crt/index.html +313 -23
- package/frontend/skins/crt/styles/billing.css +294 -223
- package/frontend/skins/crt/styles/monochrome-green.css +7 -2
- package/frontend/terminal-replay.js +372 -0
- package/package.json +10 -3
- package/shared/browser-protocol.d.ts +5 -0
- package/shared/browser-protocol.js +130 -0
- package/dist/assets/App-8dYAM6ql.js +0 -124
- package/dist/assets/FileEditorPane-RWiFD2cq.js +0 -5
- package/dist/assets/IconGlyphs-DfL0EBnj.js +0 -1
- package/dist/assets/ProjectFilesSection-Q4PDsWmM.js +0 -12
- package/dist/assets/ReviewPage-BaXu1ZdX.js +0 -3
- package/dist/assets/code-dark-CDkOQAtK.css +0 -1
- package/dist/assets/file-icons-EFUGSSwf.js +0 -1
- package/dist/assets/main-D073SnW4.css +0 -1
- package/dist/assets/qoder-C9LmmOSf.svg +0 -1
- package/dist/assets/qoder-Cf9gl0Y5.svg +0 -1
- package/dist/assets/qoder-gHCinseV.svg +0 -1
- package/dist/assets/workspace-view-state-CTyDzk2D.js +0 -1
- package/dist/assets/zsh-CLpveKlF.svg +0 -1
- package/dist/assets/zsh-FxSpMPbz.svg +0 -1
- /package/dist/assets/{api-D1lyBYIQ.js → api-D8nyOEbz.js} +0 -0
- /package/dist/assets/{core-ZlAPicox.js → core-D0LFJkDt.js} +0 -0
- /package/dist/assets/{useWorkspaceMenuKeyboard-CneKAZUJ.js → useWorkspaceMenuKeyboard-Brws6Ar9.js} +0 -0
|
@@ -2,6 +2,7 @@ const SessionEngine = require('./session-engine');
|
|
|
2
2
|
const NativePtyHostClient = require('./native-pty-host-client');
|
|
3
3
|
const { normalizeShellSessionOptions } = require('./local-session-engine');
|
|
4
4
|
const { cleanupShellBusyIntegration } = require('./shell-busy-integration');
|
|
5
|
+
const { compareNativePtyRuntimeEpochs } = require('./native-pty-controller-generation');
|
|
5
6
|
|
|
6
7
|
function isRecoverableConnectError(error) {
|
|
7
8
|
const code = error && error.code;
|
|
@@ -13,6 +14,18 @@ function nativeSessionId(entry, fallback = '') {
|
|
|
13
14
|
return entry.sessionId || entry.agentId || entry.metadata?.agentId || fallback || '';
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
function recoveredRuntimeEpoch(entry) {
|
|
18
|
+
if (!entry || typeof entry !== 'object') return '';
|
|
19
|
+
if (typeof entry.runtimeEpoch === 'string' && entry.runtimeEpoch) return entry.runtimeEpoch;
|
|
20
|
+
return typeof entry.state?.runtimeEpoch === 'string' ? entry.state.runtimeEpoch : '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function shouldAdvanceRuntimeEpoch(currentEpoch, nextEpoch) {
|
|
24
|
+
if (!nextEpoch) return false;
|
|
25
|
+
if (!currentEpoch || currentEpoch === nextEpoch) return true;
|
|
26
|
+
return compareNativePtyRuntimeEpochs(nextEpoch, currentEpoch) === 1;
|
|
27
|
+
}
|
|
28
|
+
|
|
16
29
|
class NativeSessionEngine extends SessionEngine {
|
|
17
30
|
constructor(options = {}) {
|
|
18
31
|
super();
|
|
@@ -23,7 +36,10 @@ class NativeSessionEngine extends SessionEngine {
|
|
|
23
36
|
});
|
|
24
37
|
this.preserveHostOnDispose = options.preserveHostOnDispose === true;
|
|
25
38
|
this.activeSessionIds = new Set();
|
|
26
|
-
this.
|
|
39
|
+
this.activeSessionEpochs = new Map();
|
|
40
|
+
this.hostDisconnectGeneration = 0;
|
|
41
|
+
this.reconciledHostDisconnectGeneration = 0;
|
|
42
|
+
this.hostDisconnectReconcilePromise = null;
|
|
27
43
|
this.bindClientEvents();
|
|
28
44
|
}
|
|
29
45
|
|
|
@@ -31,6 +47,7 @@ class NativeSessionEngine extends SessionEngine {
|
|
|
31
47
|
[
|
|
32
48
|
'session-started',
|
|
33
49
|
'session-output',
|
|
50
|
+
'session-transition',
|
|
34
51
|
'session-sync',
|
|
35
52
|
'session-preview',
|
|
36
53
|
'session-title',
|
|
@@ -45,6 +62,7 @@ class NativeSessionEngine extends SessionEngine {
|
|
|
45
62
|
});
|
|
46
63
|
});
|
|
47
64
|
this.client.on('host-disconnect', () => {
|
|
65
|
+
this.hostDisconnectGeneration += 1;
|
|
48
66
|
this.reconcileHostDisconnect().catch(error => {
|
|
49
67
|
const message = error && error.message ? error.message : 'Native pty host disconnected';
|
|
50
68
|
this.failActiveSessions(message);
|
|
@@ -63,43 +81,68 @@ class NativeSessionEngine extends SessionEngine {
|
|
|
63
81
|
const sessionId = nativeSessionId(payload);
|
|
64
82
|
if (!sessionId) return;
|
|
65
83
|
if (eventName === 'session-started') {
|
|
66
|
-
|
|
84
|
+
const runtimeEpoch = typeof payload.runtimeEpoch === 'string' ? payload.runtimeEpoch : '';
|
|
85
|
+
const currentEpoch = this.activeSessionEpochs.get(sessionId) || '';
|
|
86
|
+
if (shouldAdvanceRuntimeEpoch(currentEpoch, runtimeEpoch)) {
|
|
87
|
+
this.activeSessionIds.add(sessionId);
|
|
88
|
+
this.activeSessionEpochs.set(sessionId, runtimeEpoch);
|
|
89
|
+
} else if (!currentEpoch && !runtimeEpoch) {
|
|
90
|
+
this.activeSessionIds.add(sessionId);
|
|
91
|
+
}
|
|
67
92
|
} else if (eventName === 'session-exited') {
|
|
93
|
+
const currentEpoch = this.activeSessionEpochs.get(sessionId) || '';
|
|
94
|
+
const exitedEpoch = typeof payload.runtimeEpoch === 'string' ? payload.runtimeEpoch : '';
|
|
95
|
+
if (currentEpoch ? exitedEpoch !== currentEpoch : Boolean(exitedEpoch)) return;
|
|
68
96
|
this.activeSessionIds.delete(sessionId);
|
|
97
|
+
this.activeSessionEpochs.delete(sessionId);
|
|
69
98
|
}
|
|
70
99
|
}
|
|
71
100
|
|
|
72
101
|
async reconcileHostDisconnect() {
|
|
73
|
-
if (this.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
try {
|
|
79
|
-
const recovered = await this.recoverSessions({ startHost: true });
|
|
80
|
-
const recoveredIds = new Set((recovered || [])
|
|
81
|
-
.map(entry => nativeSessionId(entry))
|
|
82
|
-
.filter(Boolean));
|
|
83
|
-
|
|
84
|
-
for (const sessionId of expectedSessionIds) {
|
|
85
|
-
if (recoveredIds.has(sessionId)) continue;
|
|
86
|
-
this.activeSessionIds.delete(sessionId);
|
|
87
|
-
this.emit('session-error', {
|
|
102
|
+
if (this.hostDisconnectReconcilePromise) return this.hostDisconnectReconcilePromise;
|
|
103
|
+
this.hostDisconnectReconcilePromise = (async () => {
|
|
104
|
+
while (this.reconciledHostDisconnectGeneration < this.hostDisconnectGeneration) {
|
|
105
|
+
const generation = this.hostDisconnectGeneration;
|
|
106
|
+
const expectedSessions = [...this.activeSessionIds].map(sessionId => ({
|
|
88
107
|
sessionId,
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
108
|
+
runtimeEpoch: this.activeSessionEpochs.get(sessionId) || '',
|
|
109
|
+
}));
|
|
110
|
+
if (expectedSessions.length > 0) {
|
|
111
|
+
const recovered = await this.recoverSessions({ startHost: true });
|
|
112
|
+
const recoveredIds = new Set((recovered || [])
|
|
113
|
+
.map(entry => nativeSessionId(entry))
|
|
114
|
+
.filter(Boolean));
|
|
115
|
+
|
|
116
|
+
for (const expected of expectedSessions) {
|
|
117
|
+
if (recoveredIds.has(expected.sessionId)) continue;
|
|
118
|
+
this.activeSessionIds.delete(expected.sessionId);
|
|
119
|
+
this.activeSessionEpochs.delete(expected.sessionId);
|
|
120
|
+
this.emit('session-error', {
|
|
121
|
+
sessionId: expected.sessionId,
|
|
122
|
+
runtimeEpoch: expected.runtimeEpoch,
|
|
123
|
+
error: 'Native pty host disconnected; terminal session is no longer recoverable',
|
|
124
|
+
fatal: true,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
this.reconciledHostDisconnectGeneration = generation;
|
|
92
129
|
}
|
|
130
|
+
})();
|
|
131
|
+
try {
|
|
132
|
+
await this.hostDisconnectReconcilePromise;
|
|
93
133
|
} finally {
|
|
94
|
-
this.
|
|
134
|
+
this.hostDisconnectReconcilePromise = null;
|
|
95
135
|
}
|
|
96
136
|
}
|
|
97
137
|
|
|
98
138
|
failActiveSessions(message) {
|
|
99
139
|
for (const sessionId of [...this.activeSessionIds]) {
|
|
100
140
|
this.activeSessionIds.delete(sessionId);
|
|
141
|
+
const runtimeEpoch = this.activeSessionEpochs.get(sessionId) || '';
|
|
142
|
+
this.activeSessionEpochs.delete(sessionId);
|
|
101
143
|
this.emit('session-error', {
|
|
102
144
|
sessionId,
|
|
145
|
+
runtimeEpoch,
|
|
103
146
|
error: message,
|
|
104
147
|
fatal: true,
|
|
105
148
|
});
|
|
@@ -128,25 +171,37 @@ class NativeSessionEngine extends SessionEngine {
|
|
|
128
171
|
return result;
|
|
129
172
|
}
|
|
130
173
|
|
|
131
|
-
async sendInput(sessionId, input) {
|
|
132
|
-
return this.client.request('sendInput', {
|
|
174
|
+
async sendInput(sessionId, input, options = {}) {
|
|
175
|
+
return this.client.request('sendInput', {
|
|
176
|
+
sessionId,
|
|
177
|
+
input,
|
|
178
|
+
expectedRuntimeEpoch: options.expectedRuntimeEpoch || '',
|
|
179
|
+
}, {
|
|
180
|
+
retryOnDisconnect: false,
|
|
181
|
+
});
|
|
133
182
|
}
|
|
134
183
|
|
|
135
|
-
async interruptSession(sessionId, input = '\x03') {
|
|
136
|
-
return this.sendInput(sessionId, input);
|
|
184
|
+
async interruptSession(sessionId, input = '\x03', options = {}) {
|
|
185
|
+
return this.sendInput(sessionId, input, options);
|
|
137
186
|
}
|
|
138
187
|
|
|
139
188
|
async resizeSession(sessionId, cols, rows) {
|
|
140
189
|
return this.client.request('resizeSession', { sessionId, cols, rows });
|
|
141
190
|
}
|
|
142
191
|
|
|
143
|
-
async clearBuffer(sessionId) {
|
|
144
|
-
return this.client.request('clearBuffer', {
|
|
192
|
+
async clearBuffer(sessionId, options = {}) {
|
|
193
|
+
return this.client.request('clearBuffer', {
|
|
194
|
+
sessionId,
|
|
195
|
+
expectedRuntimeEpoch: options.expectedRuntimeEpoch || '',
|
|
196
|
+
}, {
|
|
197
|
+
retryOnDisconnect: false,
|
|
198
|
+
});
|
|
145
199
|
}
|
|
146
200
|
|
|
147
201
|
async killSession(sessionId) {
|
|
148
202
|
const result = await this.client.request('killSession', { sessionId });
|
|
149
203
|
this.activeSessionIds.delete(sessionId);
|
|
204
|
+
this.activeSessionEpochs.delete(sessionId);
|
|
150
205
|
return result;
|
|
151
206
|
}
|
|
152
207
|
|
|
@@ -154,6 +209,10 @@ class NativeSessionEngine extends SessionEngine {
|
|
|
154
209
|
return this.client.request('getSessionState', { sessionId });
|
|
155
210
|
}
|
|
156
211
|
|
|
212
|
+
async getSessionAttachCheckpoint(sessionId) {
|
|
213
|
+
return this.client.request('getSessionAttachCheckpoint', { sessionId });
|
|
214
|
+
}
|
|
215
|
+
|
|
157
216
|
async getSessionPreview(sessionId) {
|
|
158
217
|
return this.client.request('getSessionPreview', { sessionId });
|
|
159
218
|
}
|
|
@@ -172,7 +231,13 @@ class NativeSessionEngine extends SessionEngine {
|
|
|
172
231
|
const recovered = await this.client.request('recoverSessions', {}, { startHost });
|
|
173
232
|
for (const entry of recovered || []) {
|
|
174
233
|
const sessionId = nativeSessionId(entry);
|
|
175
|
-
if (sessionId)
|
|
234
|
+
if (!sessionId) continue;
|
|
235
|
+
this.activeSessionIds.add(sessionId);
|
|
236
|
+
const runtimeEpoch = recoveredRuntimeEpoch(entry);
|
|
237
|
+
const currentEpoch = this.activeSessionEpochs.get(sessionId) || '';
|
|
238
|
+
if (shouldAdvanceRuntimeEpoch(currentEpoch, runtimeEpoch)) {
|
|
239
|
+
this.activeSessionEpochs.set(sessionId, runtimeEpoch);
|
|
240
|
+
}
|
|
176
241
|
}
|
|
177
242
|
return recovered;
|
|
178
243
|
} catch (error) {
|
|
@@ -185,11 +250,18 @@ class NativeSessionEngine extends SessionEngine {
|
|
|
185
250
|
return this.client.request('updateSessionMetadata', { sessionId, patch });
|
|
186
251
|
}
|
|
187
252
|
|
|
253
|
+
consumeRuntimeRotation() {
|
|
254
|
+
return typeof this.client.consumeRuntimeRotation === 'function'
|
|
255
|
+
? this.client.consumeRuntimeRotation()
|
|
256
|
+
: null;
|
|
257
|
+
}
|
|
258
|
+
|
|
188
259
|
dispose(options = {}) {
|
|
189
260
|
this.client.disconnect({
|
|
190
261
|
preserveHost: options.preserveHost === true || this.preserveHostOnDispose,
|
|
191
262
|
});
|
|
192
263
|
this.activeSessionIds.clear();
|
|
264
|
+
this.activeSessionEpochs.clear();
|
|
193
265
|
}
|
|
194
266
|
}
|
|
195
267
|
|
|
@@ -15,8 +15,27 @@ function packagedNodePtyTargetDir() {
|
|
|
15
15
|
function copyIfExists(source, target, mode) {
|
|
16
16
|
if (!fs.existsSync(source)) return false;
|
|
17
17
|
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
let unchanged = false;
|
|
19
|
+
try {
|
|
20
|
+
const sourceStat = fs.statSync(source);
|
|
21
|
+
const targetStat = fs.statSync(target);
|
|
22
|
+
unchanged = sourceStat.size === targetStat.size
|
|
23
|
+
&& fs.readFileSync(source).equals(fs.readFileSync(target));
|
|
24
|
+
} catch {
|
|
25
|
+
unchanged = false;
|
|
26
|
+
}
|
|
27
|
+
if (!unchanged) {
|
|
28
|
+
const temporary = `${target}.${process.pid}.${Math.random().toString(16).slice(2)}.tmp`;
|
|
29
|
+
try {
|
|
30
|
+
fs.copyFileSync(source, temporary, fs.constants.COPYFILE_EXCL);
|
|
31
|
+
if (mode !== undefined) fs.chmodSync(temporary, mode);
|
|
32
|
+
fs.renameSync(temporary, target);
|
|
33
|
+
} finally {
|
|
34
|
+
fs.rmSync(temporary, { force: true });
|
|
35
|
+
}
|
|
36
|
+
} else if (mode !== undefined) {
|
|
37
|
+
fs.chmodSync(target, mode);
|
|
38
|
+
}
|
|
20
39
|
return true;
|
|
21
40
|
}
|
|
22
41
|
|
|
@@ -75,5 +94,6 @@ function loadNodePty() {
|
|
|
75
94
|
|
|
76
95
|
module.exports = loadNodePty();
|
|
77
96
|
module.exports.copyPackagedSpawnHelper = copyPackagedSpawnHelper;
|
|
97
|
+
module.exports.copyIfExists = copyIfExists;
|
|
78
98
|
module.exports.preparePackagedNodePtyRuntime = preparePackagedNodePtyRuntime;
|
|
79
99
|
module.exports.isPackagedRuntime = isPackagedRuntime;
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const {
|
|
3
|
+
createProviderSessionId,
|
|
4
|
+
createTemporaryProviderSessionId,
|
|
5
|
+
isSafeProviderSessionId,
|
|
6
|
+
} = require('./provider-session-id');
|
|
7
|
+
|
|
8
|
+
const CODEX_VALUE_OPTIONS = new Set([
|
|
9
|
+
'-a', '-c', '-C', '-m', '-s', '--ask-for-approval', '--cd', '--config',
|
|
10
|
+
'--config-profile', '--model', '--profile', '--sandbox',
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
function optionTakesValue(option, valueOptions) {
|
|
14
|
+
return Boolean(option && !option.includes('=') && valueOptions.has(option));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function firstCodexSubcommand(args) {
|
|
18
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
19
|
+
const arg = args[index];
|
|
20
|
+
if (!arg) continue;
|
|
21
|
+
if (arg.startsWith('-')) {
|
|
22
|
+
if (optionTakesValue(arg, CODEX_VALUE_OPTIONS)) index += 1;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
return { value: arg, index };
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function codexSessionIdAfterSubcommand(args, subcommandIndex) {
|
|
31
|
+
const positional = [];
|
|
32
|
+
for (let index = subcommandIndex + 1; index < args.length; index += 1) {
|
|
33
|
+
const arg = args[index];
|
|
34
|
+
if (!arg) continue;
|
|
35
|
+
if (arg.startsWith('-')) {
|
|
36
|
+
if (optionTakesValue(arg, CODEX_VALUE_OPTIONS)) index += 1;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
positional.push(arg);
|
|
40
|
+
}
|
|
41
|
+
const sessionId = positional.at(-1) || '';
|
|
42
|
+
return isSafeProviderSessionId(sessionId) ? sessionId : '';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function argValue(args, names) {
|
|
46
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
47
|
+
const arg = args[index];
|
|
48
|
+
for (const name of names) {
|
|
49
|
+
if (arg === name) return args[index + 1] || '';
|
|
50
|
+
if (arg.startsWith(`${name}=`)) return arg.slice(name.length + 1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return '';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function hasArg(args, names) {
|
|
57
|
+
return args.some(arg => names.includes(arg) || names.some(name => arg.startsWith(`${name}=`)));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function codexSessionPlan(rawArgs) {
|
|
61
|
+
const subcommand = firstCodexSubcommand(rawArgs);
|
|
62
|
+
if (subcommand?.value === 'resume') {
|
|
63
|
+
const id = codexSessionIdAfterSubcommand(rawArgs, subcommand.index);
|
|
64
|
+
if (id) return { id, temporary: false, source: 'resume' };
|
|
65
|
+
}
|
|
66
|
+
if (subcommand?.value === 'fork') {
|
|
67
|
+
return {
|
|
68
|
+
id: createTemporaryProviderSessionId(),
|
|
69
|
+
temporary: true,
|
|
70
|
+
source: 'codex-fork-temporary',
|
|
71
|
+
forkedFromProviderSessionId: codexSessionIdAfterSubcommand(rawArgs, subcommand.index),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (subcommand?.value) return null;
|
|
75
|
+
return { id: createTemporaryProviderSessionId(), temporary: true, source: 'codex-temporary' };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function explicitSessionPlan(provider, rawArgs, launchArgs) {
|
|
79
|
+
const explicitSessionId = argValue(rawArgs, ['--session-id']);
|
|
80
|
+
const resumeSessionId = argValue(rawArgs, ['--resume']);
|
|
81
|
+
const isFork = hasArg(rawArgs, ['--fork-session']);
|
|
82
|
+
const isContinue = hasArg(rawArgs, provider === 'claude' ? ['--continue', '-c'] : ['--continue']);
|
|
83
|
+
if (explicitSessionId && isSafeProviderSessionId(explicitSessionId)) {
|
|
84
|
+
return {
|
|
85
|
+
id: explicitSessionId,
|
|
86
|
+
temporary: false,
|
|
87
|
+
source: 'launch-session-id',
|
|
88
|
+
forkedFromProviderSessionId: isFork && isSafeProviderSessionId(resumeSessionId) ? resumeSessionId : '',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (resumeSessionId && isSafeProviderSessionId(resumeSessionId) && !isFork) {
|
|
92
|
+
return { id: resumeSessionId, temporary: false, source: 'resume' };
|
|
93
|
+
}
|
|
94
|
+
if (isContinue) return null;
|
|
95
|
+
const id = createProviderSessionId();
|
|
96
|
+
return {
|
|
97
|
+
id,
|
|
98
|
+
temporary: false,
|
|
99
|
+
source: isFork ? `${provider}-fork-session-id` : `${provider}-session-id`,
|
|
100
|
+
forkedFromProviderSessionId: isFork && isSafeProviderSessionId(resumeSessionId) ? resumeSessionId : '',
|
|
101
|
+
args: ['--session-id', id, ...launchArgs],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function openCodeSessionPlan(rawArgs) {
|
|
106
|
+
const id = argValue(rawArgs, ['--session', '-s']);
|
|
107
|
+
if (!id || !isSafeProviderSessionId(id)) return null;
|
|
108
|
+
return hasArg(rawArgs, ['--fork'])
|
|
109
|
+
? {
|
|
110
|
+
id: createTemporaryProviderSessionId(),
|
|
111
|
+
temporary: true,
|
|
112
|
+
source: 'opencode-fork-temporary',
|
|
113
|
+
forkedFromProviderSessionId: id,
|
|
114
|
+
}
|
|
115
|
+
: { id, temporary: false, source: 'resume' };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function codexAcpEnvironment(options = {}) {
|
|
119
|
+
const env = { ...(options.env || process.env) };
|
|
120
|
+
if (options.executable && !env.CODEX_PATH) env.CODEX_PATH = options.executable;
|
|
121
|
+
let config = {};
|
|
122
|
+
if (env.CODEX_CONFIG) {
|
|
123
|
+
try {
|
|
124
|
+
const parsed = JSON.parse(env.CODEX_CONFIG);
|
|
125
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) config = parsed;
|
|
126
|
+
} catch {
|
|
127
|
+
// A selected Farming profile below replaces an invalid adapter config.
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (options.model && options.model !== 'config') config.model = options.model;
|
|
131
|
+
if (options.reasoningEffort && options.reasoningEffort !== 'config') config.model_reasoning_effort = options.reasoningEffort;
|
|
132
|
+
if (options.serviceTier && !['config', 'default'].includes(options.serviceTier)) config.service_tier = options.serviceTier;
|
|
133
|
+
if (Object.keys(config).length > 0) env.CODEX_CONFIG = JSON.stringify(config);
|
|
134
|
+
const initialMode = { ask: 'read-only', approve: 'agent', full: 'agent-full-access' }[options.approvalMode];
|
|
135
|
+
if (initialMode) env.INITIAL_AGENT_MODE = initialMode;
|
|
136
|
+
return env;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const PROVIDER_ADAPTERS = Object.freeze([
|
|
140
|
+
{
|
|
141
|
+
id: 'codex',
|
|
142
|
+
displayName: 'codex',
|
|
143
|
+
executable: 'codex',
|
|
144
|
+
homeEnvKey: 'CODEX_HOME',
|
|
145
|
+
interruptInput: '\x1b',
|
|
146
|
+
freshAcpSessionSources: ['codex-temporary'],
|
|
147
|
+
commands: ['codex'],
|
|
148
|
+
supportedRuntimes: ['terminal', 'acp', 'json', 'app-server'],
|
|
149
|
+
planSession: codexSessionPlan,
|
|
150
|
+
acp: { packageName: '@agentclientprotocol/codex-acp', version: '1.1.4' },
|
|
151
|
+
prepareAcpEnvironment: codexAcpEnvironment,
|
|
152
|
+
capabilities: { runtimeSwitch: true, terminalProfile: true, goals: true },
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: 'claude',
|
|
156
|
+
displayName: 'claude code',
|
|
157
|
+
executable: 'claude',
|
|
158
|
+
homeEnvKey: 'CLAUDE_CONFIG_DIR',
|
|
159
|
+
interruptInput: '\x1b',
|
|
160
|
+
freshAcpSessionSources: ['claude-session-id'],
|
|
161
|
+
commands: ['claude'],
|
|
162
|
+
supportedRuntimes: ['terminal', 'acp'],
|
|
163
|
+
planSession: (rawArgs, launchArgs) => explicitSessionPlan('claude', rawArgs, launchArgs),
|
|
164
|
+
acp: { packageName: '@agentclientprotocol/claude-agent-acp', version: '0.59.0' },
|
|
165
|
+
capabilities: { runtimeSwitch: true, terminalProfile: false, goals: false },
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
id: 'opencode',
|
|
169
|
+
displayName: 'opencode',
|
|
170
|
+
executable: 'opencode',
|
|
171
|
+
homeEnvKey: 'OPENCODE_CONFIG_DIR',
|
|
172
|
+
interruptInput: '\x03',
|
|
173
|
+
freshAcpSessionSources: [],
|
|
174
|
+
commands: ['opencode'],
|
|
175
|
+
supportedRuntimes: ['terminal', 'acp', 'json'],
|
|
176
|
+
planSession: openCodeSessionPlan,
|
|
177
|
+
acp: {
|
|
178
|
+
version: 'native',
|
|
179
|
+
launch: options => ({
|
|
180
|
+
command: options.executable || 'opencode',
|
|
181
|
+
args: ['acp', '--cwd', path.resolve(options.cwd || process.cwd())],
|
|
182
|
+
}),
|
|
183
|
+
},
|
|
184
|
+
capabilities: { runtimeSwitch: true, terminalProfile: false, goals: false },
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: 'qoder',
|
|
188
|
+
displayName: 'qoder',
|
|
189
|
+
executable: 'qodercli',
|
|
190
|
+
homeEnvKey: 'QODER_CONFIG_DIR',
|
|
191
|
+
interruptInput: '\x1b',
|
|
192
|
+
freshAcpSessionSources: ['qoder-session-id'],
|
|
193
|
+
commands: ['qoder', 'qodercli'],
|
|
194
|
+
supportedRuntimes: ['terminal', 'acp'],
|
|
195
|
+
planSession: (rawArgs, launchArgs) => explicitSessionPlan('qoder', rawArgs, launchArgs),
|
|
196
|
+
acp: {
|
|
197
|
+
version: 'native',
|
|
198
|
+
launch: options => ({ command: options.executable || 'qodercli', args: ['--acp'] }),
|
|
199
|
+
},
|
|
200
|
+
capabilities: { runtimeSwitch: true, terminalProfile: false, goals: false },
|
|
201
|
+
},
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
const ADAPTER_BY_ID = new Map(PROVIDER_ADAPTERS.map(adapter => [adapter.id, Object.freeze(adapter)]));
|
|
205
|
+
const ADAPTER_BY_COMMAND = new Map(PROVIDER_ADAPTERS.flatMap(adapter => (
|
|
206
|
+
adapter.commands.map(command => [command, adapter])
|
|
207
|
+
)));
|
|
208
|
+
|
|
209
|
+
function getProviderAdapter(provider) {
|
|
210
|
+
return ADAPTER_BY_ID.get(String(provider || '').trim().toLowerCase()) || null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function providerForProgram(program) {
|
|
214
|
+
return ADAPTER_BY_COMMAND.get(path.basename(String(program || '').trim()))?.id || '';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function listProviderAdapters() {
|
|
218
|
+
return [...PROVIDER_ADAPTERS];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function providerCapabilities(provider) {
|
|
222
|
+
const adapter = getProviderAdapter(provider);
|
|
223
|
+
return {
|
|
224
|
+
supportedRuntimes: adapter ? [...adapter.supportedRuntimes] : ['terminal'],
|
|
225
|
+
runtimeSwitch: adapter?.capabilities?.runtimeSwitch === true,
|
|
226
|
+
terminalProfile: adapter?.capabilities?.terminalProfile === true,
|
|
227
|
+
goals: adapter?.capabilities?.goals === true,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function providerSupportsRuntime(provider, runtime) {
|
|
232
|
+
return getProviderAdapter(provider)?.supportedRuntimes.includes(runtime) === true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function applyProviderHomeEnvironment(env, provider, homePath) {
|
|
236
|
+
const key = getProviderAdapter(provider)?.homeEnvKey;
|
|
237
|
+
if (key && homePath) env[key] = homePath;
|
|
238
|
+
return env;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function isFreshAcpSessionSource(provider, source) {
|
|
242
|
+
return getProviderAdapter(provider)?.freshAcpSessionSources.includes(source) === true;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
module.exports = {
|
|
246
|
+
getProviderAdapter,
|
|
247
|
+
applyProviderHomeEnvironment,
|
|
248
|
+
isFreshAcpSessionSource,
|
|
249
|
+
listProviderAdapters,
|
|
250
|
+
providerCapabilities,
|
|
251
|
+
providerForProgram,
|
|
252
|
+
providerSupportsRuntime,
|
|
253
|
+
};
|