farming-code 2.2.7 → 2.2.11
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 +110 -199
- package/README.zh_cn.md +106 -237
- 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 +1496 -737
- 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 +45 -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 +30 -0
- package/backend/control-api.js +192 -17
- 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 +294 -86
- 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 +81 -4
- package/backend/workspace-directory.js +152 -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-CHYpgECZ.js +208 -0
- package/dist/assets/{FileEditorMarkdownPreview-elKWc8Im.js → FileEditorMarkdownPreview--Cri80nQ.js} +92 -92
- package/dist/assets/FileEditorPane-DGJlEKdf.js +2 -0
- package/dist/assets/IconGlyphs-Cc14sepw.js +1 -0
- package/dist/assets/ProjectFilesSection-Cz1wG4NT.js +12 -0
- package/dist/assets/{ReviewPage-BaXu1ZdX.js → ReviewPage-LonRc6Fz.js} +1 -1
- package/dist/assets/code-dark-B1pAtO_P.css +1 -0
- package/dist/assets/file-icons-Bw2qd5iT.js +1 -0
- package/dist/assets/{index-B8SHldPE.js → index-miO2r9IZ.js} +3 -3
- package/dist/assets/main-DNe0jPw-.css +1 -0
- package/dist/assets/workspace-editor-model-BQol4qbA.js +1 -0
- package/dist/assets/workspace-editor-monaco-5gFbeDPF.js +1 -0
- package/dist/assets/workspace-editor-monaco-xPv8JzHH.js +4 -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/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 +1976 -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-iA2BU9P6.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/code-dark-CDkOQAtK.css +0 -1
- package/dist/assets/file-icons-EFUGSSwf.js +0 -1
- package/dist/assets/main-DpYuBonn.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
package/backend/auth.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const crypto = require('crypto');
|
|
2
2
|
const fs = require('fs');
|
|
3
|
+
const { FarmingNetPassVerifier, PASS_QUERY_PARAM } = require('./farming-net-pass');
|
|
3
4
|
const { createPoeticToken, generatePoeticToken, getPoeticTokenEntropyBits } = require('./haiku-token');
|
|
4
5
|
const storageLayout = require('./storage-layout');
|
|
5
6
|
|
|
@@ -24,6 +25,21 @@ function decodeCookieToken(token) {
|
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
function normalizeCookieName(value) {
|
|
29
|
+
const cookieName = String(value || '').trim();
|
|
30
|
+
return /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(cookieName) ? cookieName : 'farming_token';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function normalizeCookiePath(value) {
|
|
34
|
+
const cookiePath = String(value || '/').trim();
|
|
35
|
+
if (!cookiePath.startsWith('/') || /[;\r\n]/.test(cookiePath)) return '/';
|
|
36
|
+
return cookiePath;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function escapeRegExp(value) {
|
|
40
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
function readExistingTokenFile(tokenFile) {
|
|
28
44
|
try {
|
|
29
45
|
const token = fs.readFileSync(tokenFile, 'utf8').trim();
|
|
@@ -35,12 +51,17 @@ function readExistingTokenFile(tokenFile) {
|
|
|
35
51
|
|
|
36
52
|
class TokenAuth {
|
|
37
53
|
constructor(options = {}) {
|
|
38
|
-
|
|
54
|
+
const authEnv = options.env || process.env;
|
|
55
|
+
this.disabled = options.disabled === true || isTruthyEnv(authEnv.FARMING_DISABLE_AUTH);
|
|
39
56
|
this.basePath = normalizeBasePath(options.basePath || '/');
|
|
40
57
|
this.authStatusPath = this.basePath ? `${this.basePath}/api/auth/status` : '/api/auth/status';
|
|
58
|
+
this.cookieName = normalizeCookieName(options.cookieName);
|
|
59
|
+
this.cookiePath = normalizeCookiePath(options.cookiePath);
|
|
60
|
+
this.redirectQueryToken = options.redirectQueryToken === true;
|
|
41
61
|
this.tokenFile = '';
|
|
42
62
|
this.token = '';
|
|
43
63
|
this.tokenInfo = null;
|
|
64
|
+
this.farmingNetPassVerifier = null;
|
|
44
65
|
|
|
45
66
|
if (this.disabled) {
|
|
46
67
|
return;
|
|
@@ -51,7 +72,15 @@ class TokenAuth {
|
|
|
51
72
|
fs.mkdirSync(farmingDir, { recursive: true });
|
|
52
73
|
}
|
|
53
74
|
this.tokenFile = storageLayout.sessionTokenFile(farmingDir);
|
|
54
|
-
|
|
75
|
+
if (options.farmingNetPassVerifier !== false) {
|
|
76
|
+
this.farmingNetPassVerifier = options.farmingNetPassVerifier || new FarmingNetPassVerifier({
|
|
77
|
+
trustFile: options.farmingNetTrustFile || storageLayout.farmingNetTrustFile(farmingDir),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const configuredTokenSource = Object.prototype.hasOwnProperty.call(options, 'token')
|
|
81
|
+
? options.token
|
|
82
|
+
: authEnv.FARMING_TOKEN;
|
|
83
|
+
const configuredToken = String(configuredTokenSource || '').trim();
|
|
55
84
|
const existingToken = configuredToken ? '' : readExistingTokenFile(this.tokenFile);
|
|
56
85
|
if (configuredToken) {
|
|
57
86
|
this.token = configuredToken;
|
|
@@ -70,7 +99,7 @@ class TokenAuth {
|
|
|
70
99
|
entropyBits: 0,
|
|
71
100
|
};
|
|
72
101
|
} else {
|
|
73
|
-
this.tokenInfo = createPoeticToken({ locale: options.tokenLocale, env:
|
|
102
|
+
this.tokenInfo = createPoeticToken({ locale: options.tokenLocale, env: authEnv, timeZone: options.timeZone });
|
|
74
103
|
this.token = this.tokenInfo.token;
|
|
75
104
|
}
|
|
76
105
|
this.saveTokenFile();
|
|
@@ -97,6 +126,10 @@ class TokenAuth {
|
|
|
97
126
|
return this.tokenInfo;
|
|
98
127
|
}
|
|
99
128
|
|
|
129
|
+
getCookieName() {
|
|
130
|
+
return this.cookieName;
|
|
131
|
+
}
|
|
132
|
+
|
|
100
133
|
verify(token) {
|
|
101
134
|
if (this.disabled) return true;
|
|
102
135
|
if (!token || !this.token) return false;
|
|
@@ -115,13 +148,32 @@ class TokenAuth {
|
|
|
115
148
|
const queryToken = url.searchParams.get('token');
|
|
116
149
|
if (queryToken) return queryToken;
|
|
117
150
|
|
|
151
|
+
return this.extractCookieToken(req);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
extractCookieToken(req) {
|
|
118
155
|
const cookies = req.headers.cookie || '';
|
|
119
|
-
const match = cookies.match(
|
|
156
|
+
const match = cookies.match(new RegExp(`(?:^|;\\s*)${escapeRegExp(this.cookieName)}=([^;]+)`));
|
|
120
157
|
if (match) return decodeCookieToken(match[1]);
|
|
121
158
|
|
|
122
159
|
return null;
|
|
123
160
|
}
|
|
124
161
|
|
|
162
|
+
setAuthenticatedCookie(res) {
|
|
163
|
+
res.setHeader('Set-Cookie',
|
|
164
|
+
`${this.cookieName}=${encodeCookieToken(this.token)}; Path=${this.cookiePath}; HttpOnly; SameSite=Lax`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
redirectWithoutQueryParameter(res, url, parameter) {
|
|
168
|
+
url.searchParams.delete(parameter);
|
|
169
|
+
const search = url.searchParams.toString();
|
|
170
|
+
res.writeHead(302, {
|
|
171
|
+
'Cache-Control': 'no-store',
|
|
172
|
+
Location: `${url.pathname}${search ? `?${search}` : ''}`,
|
|
173
|
+
});
|
|
174
|
+
res.end();
|
|
175
|
+
}
|
|
176
|
+
|
|
125
177
|
middleware() {
|
|
126
178
|
return (req, res, next) => {
|
|
127
179
|
if (this.disabled) return next();
|
|
@@ -133,13 +185,34 @@ class TokenAuth {
|
|
|
133
185
|
return next();
|
|
134
186
|
}
|
|
135
187
|
|
|
188
|
+
const method = String(req.method || 'GET').toUpperCase();
|
|
189
|
+
if (['GET', 'HEAD'].includes(method) && url.searchParams.has(PASS_QUERY_PARAM)) {
|
|
190
|
+
const pass = url.searchParams.get(PASS_QUERY_PARAM);
|
|
191
|
+
const passResult = this.farmingNetPassVerifier
|
|
192
|
+
? this.farmingNetPassVerifier.verify(pass)
|
|
193
|
+
: { valid: false };
|
|
194
|
+
if (passResult.valid) {
|
|
195
|
+
this.setAuthenticatedCookie(res);
|
|
196
|
+
this.redirectWithoutQueryParameter(res, url, PASS_QUERY_PARAM);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
const cookieToken = this.extractCookieToken(req);
|
|
200
|
+
if (cookieToken && this.verify(cookieToken)) {
|
|
201
|
+
this.redirectWithoutQueryParameter(res, url, PASS_QUERY_PARAM);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
136
206
|
const token = this.extractToken(req);
|
|
137
207
|
|
|
138
208
|
// URL has token query param -> validate, set cookie, redirect
|
|
139
209
|
if (url.searchParams.has('token')) {
|
|
140
210
|
if (token && this.verify(token)) {
|
|
141
|
-
|
|
142
|
-
|
|
211
|
+
this.setAuthenticatedCookie(res);
|
|
212
|
+
if (this.redirectQueryToken && ['GET', 'HEAD'].includes(method)) {
|
|
213
|
+
this.redirectWithoutQueryParameter(res, url, 'token');
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
143
216
|
return next();
|
|
144
217
|
}
|
|
145
218
|
}
|
package/backend/codex-models.js
CHANGED
|
@@ -1,69 +1,24 @@
|
|
|
1
1
|
const { execFile } = require('child_process');
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
{ effort: 'xhigh', description: 'Extra high reasoning depth for complex problems' },
|
|
14
|
-
],
|
|
15
|
-
service_tiers: [
|
|
16
|
-
{ id: 'priority', name: 'Fast', description: '1.5x speed, increased usage' },
|
|
17
|
-
],
|
|
18
|
-
visibility: 'list',
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
slug: 'gpt-5.4',
|
|
22
|
-
display_name: 'GPT-5.4',
|
|
23
|
-
description: 'Strong model for everyday coding.',
|
|
24
|
-
default_reasoning_level: 'medium',
|
|
25
|
-
supported_reasoning_levels: [
|
|
26
|
-
{ effort: 'low', description: 'Fast responses with lighter reasoning' },
|
|
27
|
-
{ effort: 'medium', description: 'Balances speed and reasoning depth for everyday tasks' },
|
|
28
|
-
{ effort: 'high', description: 'Greater reasoning depth for complex problems' },
|
|
29
|
-
{ effort: 'xhigh', description: 'Extra high reasoning depth for complex problems' },
|
|
30
|
-
],
|
|
31
|
-
service_tiers: [
|
|
32
|
-
{ id: 'priority', name: 'Fast', description: '1.5x speed, increased usage' },
|
|
33
|
-
],
|
|
34
|
-
visibility: 'list',
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
slug: 'gpt-5.4-mini',
|
|
38
|
-
display_name: 'GPT-5.4 Mini',
|
|
39
|
-
description: 'Small, fast, and cost-efficient model for simpler coding tasks.',
|
|
40
|
-
default_reasoning_level: 'medium',
|
|
41
|
-
supported_reasoning_levels: [
|
|
42
|
-
{ effort: 'low', description: 'Fast responses with lighter reasoning' },
|
|
43
|
-
{ effort: 'medium', description: 'Balances speed and reasoning depth for everyday tasks' },
|
|
44
|
-
{ effort: 'high', description: 'Greater reasoning depth for complex problems' },
|
|
45
|
-
],
|
|
46
|
-
visibility: 'list',
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
slug: 'gpt-5.3-codex-spark',
|
|
50
|
-
display_name: 'GPT-5.3 Codex Spark',
|
|
51
|
-
description: 'Ultra-fast coding model.',
|
|
52
|
-
default_reasoning_level: 'medium',
|
|
53
|
-
supported_reasoning_levels: [
|
|
54
|
-
{ effort: 'low', description: 'Fast responses with lighter reasoning' },
|
|
55
|
-
{ effort: 'medium', description: 'Balances speed and reasoning depth for everyday tasks' },
|
|
56
|
-
],
|
|
57
|
-
visibility: 'list',
|
|
58
|
-
},
|
|
59
|
-
];
|
|
3
|
+
const DEFAULT_CODEX_MODELS_TIMEOUT_MS = 15_000;
|
|
4
|
+
|
|
5
|
+
class CodexModelCatalogError extends Error {
|
|
6
|
+
constructor(code, message, cause = null) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'CodexModelCatalogError';
|
|
9
|
+
this.code = code;
|
|
10
|
+
if (cause) this.cause = cause;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
60
13
|
|
|
61
14
|
const EFFORT_LABELS = {
|
|
15
|
+
minimal: 'Minimal',
|
|
62
16
|
low: 'Low',
|
|
63
17
|
medium: 'Medium',
|
|
64
18
|
high: 'High',
|
|
65
19
|
xhigh: 'Extra High',
|
|
66
20
|
max: 'Max',
|
|
21
|
+
ultra: 'Ultra',
|
|
67
22
|
};
|
|
68
23
|
|
|
69
24
|
const DEFAULT_SERVICE_TIER = {
|
|
@@ -182,46 +137,88 @@ function buildModelOptions(models, source = 'codex') {
|
|
|
182
137
|
|
|
183
138
|
function listCodexModelOptions(options = {}) {
|
|
184
139
|
const codexBin = options.codexBin || process.env.FARMING_CODEX_BIN || 'codex';
|
|
185
|
-
const timeout = options.timeout
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
models: buildModelOptions(FALLBACK_MODELS, 'fallback'),
|
|
190
|
-
catalog: buildModelCatalog(FALLBACK_MODELS, 'fallback'),
|
|
191
|
-
source: 'fallback',
|
|
192
|
-
});
|
|
140
|
+
const timeout = Number.isFinite(options.timeout)
|
|
141
|
+
? Math.max(1, options.timeout)
|
|
142
|
+
: DEFAULT_CODEX_MODELS_TIMEOUT_MS;
|
|
143
|
+
const runExecFile = options.execFile || execFile;
|
|
193
144
|
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
194
146
|
try {
|
|
195
|
-
|
|
147
|
+
runExecFile(codexBin, ['debug', 'models'], {
|
|
196
148
|
timeout,
|
|
197
149
|
maxBuffer: 20 * 1024 * 1024,
|
|
198
|
-
}, (error, stdout) => {
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
});
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
} catch {
|
|
212
|
-
// fall through to fallback catalog
|
|
150
|
+
}, (error, stdout, stderr) => {
|
|
151
|
+
if (error) {
|
|
152
|
+
const timedOut = error.code === 'ETIMEDOUT' || error.killed === true;
|
|
153
|
+
if (timedOut) {
|
|
154
|
+
reject(new CodexModelCatalogError(
|
|
155
|
+
'CODEX_MODELS_TIMEOUT',
|
|
156
|
+
`Codex model catalog timed out after ${timeout}ms`,
|
|
157
|
+
error
|
|
158
|
+
));
|
|
159
|
+
return;
|
|
213
160
|
}
|
|
161
|
+
|
|
162
|
+
const detail = String(stderr || error.message || '').trim().split(/\r?\n/, 1)[0];
|
|
163
|
+
reject(new CodexModelCatalogError(
|
|
164
|
+
'CODEX_MODELS_COMMAND_FAILED',
|
|
165
|
+
detail
|
|
166
|
+
? `Codex model catalog command failed: ${detail}`
|
|
167
|
+
: 'Codex model catalog command failed',
|
|
168
|
+
error
|
|
169
|
+
));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!String(stdout || '').trim()) {
|
|
174
|
+
reject(new CodexModelCatalogError(
|
|
175
|
+
'CODEX_MODELS_EMPTY_OUTPUT',
|
|
176
|
+
'Codex model catalog command returned no output'
|
|
177
|
+
));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let models;
|
|
182
|
+
try {
|
|
183
|
+
models = catalogModelsFromJson(stdout);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
reject(new CodexModelCatalogError(
|
|
186
|
+
'CODEX_MODELS_INVALID_JSON',
|
|
187
|
+
'Codex model catalog returned invalid JSON',
|
|
188
|
+
error
|
|
189
|
+
));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const modelOptions = buildModelOptions(models, 'codex');
|
|
194
|
+
const catalog = buildModelCatalog(models, 'codex');
|
|
195
|
+
if (modelOptions.length === 0 || catalog.length === 0) {
|
|
196
|
+
reject(new CodexModelCatalogError(
|
|
197
|
+
'CODEX_MODELS_EMPTY_CATALOG',
|
|
198
|
+
'Codex model catalog did not contain any visible models'
|
|
199
|
+
));
|
|
200
|
+
return;
|
|
214
201
|
}
|
|
215
202
|
|
|
216
|
-
|
|
203
|
+
resolve({
|
|
204
|
+
models: modelOptions,
|
|
205
|
+
catalog,
|
|
206
|
+
source: 'codex',
|
|
207
|
+
});
|
|
217
208
|
});
|
|
218
|
-
} catch {
|
|
219
|
-
|
|
209
|
+
} catch (error) {
|
|
210
|
+
reject(new CodexModelCatalogError(
|
|
211
|
+
'CODEX_MODELS_COMMAND_FAILED',
|
|
212
|
+
`Failed to start Codex model catalog command: ${error.message || error}`,
|
|
213
|
+
error
|
|
214
|
+
));
|
|
220
215
|
}
|
|
221
216
|
});
|
|
222
217
|
}
|
|
223
218
|
|
|
224
219
|
module.exports = {
|
|
220
|
+
CodexModelCatalogError,
|
|
221
|
+
DEFAULT_CODEX_MODELS_TIMEOUT_MS,
|
|
225
222
|
buildModelCatalog,
|
|
226
223
|
buildModelOptions,
|
|
227
224
|
catalogModelsFromJson,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const { execFile } = require('child_process');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const { promisify } = require('util');
|
|
4
|
+
const { resolveCompatibleCodexExecutable } = require('./executable-discovery');
|
|
5
|
+
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
|
|
8
|
+
async function unarchiveCodexSession(sessionId, session = {}, options = {}) {
|
|
9
|
+
const resolveExecutable = options.resolveCompatibleCodexExecutable || resolveCompatibleCodexExecutable;
|
|
10
|
+
const runExecFile = options.execFileAsync || execFileAsync;
|
|
11
|
+
const processEnv = options.processEnv || process.env;
|
|
12
|
+
const codexResolution = resolveExecutable(session.cliVersion || '', processEnv.PATH || '');
|
|
13
|
+
if (!codexResolution.compatible) {
|
|
14
|
+
return {
|
|
15
|
+
error: codexResolution.error || 'Codex CLI is not compatible with this session',
|
|
16
|
+
status: 400,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
await runExecFile(codexResolution.path || 'codex', ['unarchive', sessionId], {
|
|
22
|
+
cwd: session.cwd || session.workspace || os.homedir(),
|
|
23
|
+
env: session.providerHomePath
|
|
24
|
+
? { ...processEnv, CODEX_HOME: session.providerHomePath }
|
|
25
|
+
: processEnv,
|
|
26
|
+
timeout: 30_000,
|
|
27
|
+
maxBuffer: 1024 * 1024,
|
|
28
|
+
});
|
|
29
|
+
return { unarchived: true };
|
|
30
|
+
} catch (error) {
|
|
31
|
+
const message = [
|
|
32
|
+
error && error.stdout ? String(error.stdout).trim() : '',
|
|
33
|
+
error && error.stderr ? String(error.stderr).trim() : '',
|
|
34
|
+
error && error.message ? String(error.message).trim() : '',
|
|
35
|
+
].filter(Boolean).join('\n') || 'failed to unarchive Codex session';
|
|
36
|
+
return {
|
|
37
|
+
error: message,
|
|
38
|
+
status: 409,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
unarchiveCodexSession,
|
|
45
|
+
};
|