@ycodium-ai/agent-codex 0.2.2167 → 0.2.2510
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 +8 -1
- package/assets/ui.js +79 -0
- package/dist/index.js +504 -352
- package/locales/en.json +44 -0
- package/locales/zh-CN.json +44 -0
- package/package.json +34 -5
package/dist/index.js
CHANGED
|
@@ -11,6 +11,13 @@ var CODEX_CARD = {
|
|
|
11
11
|
commandName: "codex",
|
|
12
12
|
launchArgs: CODEX_DEFAULT_LAUNCH_ARGS,
|
|
13
13
|
homeEnvVar: "CODEX_HOME",
|
|
14
|
+
// 影子目录只分开凭据,会话文件仍写在共用 home 下(`resolveCodexHomeLayout`
|
|
15
|
+
// 的 authOverlay:auth.json 是真文件,sessions 是指回共用 home 的软链)。
|
|
16
|
+
homeLayout: {
|
|
17
|
+
accountConfigKey: "shadowHomePath",
|
|
18
|
+
credentialFile: "auth.json",
|
|
19
|
+
transcriptSubdir: "sessions"
|
|
20
|
+
},
|
|
14
21
|
environment: [],
|
|
15
22
|
install: { kind: "npm" },
|
|
16
23
|
signIn: [{ kind: "command", command: "codex login" }],
|
|
@@ -31,9 +38,6 @@ var CODEX_CARD = {
|
|
|
31
38
|
"5.3-spark": "gpt-5.3-codex-spark",
|
|
32
39
|
"gpt-5.3-spark": "gpt-5.3-codex-spark"
|
|
33
40
|
},
|
|
34
|
-
modelsDevProviderIds: ["openai"],
|
|
35
|
-
modelsDevSlugPrefixed: false,
|
|
36
|
-
modelSlugPrefix: "",
|
|
37
41
|
mcpTransports: ["stdio", "http"],
|
|
38
42
|
pluginManaged: true,
|
|
39
43
|
verifiedSkills: true,
|
|
@@ -51,16 +55,47 @@ var CODEX_CARD = {
|
|
|
51
55
|
},
|
|
52
56
|
credentialSources: [],
|
|
53
57
|
diagnostics: [],
|
|
58
|
+
// 设置表单照这份画:带 x-ycodium-form 的属性才进表单(enabled、customModels 等另有入口,不在此列),
|
|
59
|
+
// 顺序看 x-ycodium-form-order;文案键在本插件 locales/<locale>.json 里解析,宿主不兜底。
|
|
54
60
|
configSchema: {
|
|
55
61
|
type: "object",
|
|
62
|
+
"x-ycodium-form-order": ["binaryPath", "homePath", "shadowHomePath", "launchArgs"],
|
|
56
63
|
properties: {
|
|
57
64
|
authMode: { type: "string", enum: ["default", "chatgpt", "apiKey"] },
|
|
58
65
|
apiBaseUrl: { type: "string" },
|
|
59
66
|
enabled: { type: "boolean" },
|
|
60
|
-
binaryPath: {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
binaryPath: {
|
|
68
|
+
type: "string",
|
|
69
|
+
"x-ycodium-form": {
|
|
70
|
+
titleKey: "form.binaryPath.title",
|
|
71
|
+
descriptionKey: "form.binaryPath.description",
|
|
72
|
+
placeholderKey: "form.binaryPath.placeholder",
|
|
73
|
+
control: "executable-path"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
homePath: {
|
|
77
|
+
type: "string",
|
|
78
|
+
"x-ycodium-form": {
|
|
79
|
+
titleKey: "form.homePath.title",
|
|
80
|
+
descriptionKey: "form.homePath.description",
|
|
81
|
+
placeholderKey: "form.homePath.placeholder"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
shadowHomePath: {
|
|
85
|
+
type: "string",
|
|
86
|
+
"x-ycodium-form": {
|
|
87
|
+
titleKey: "form.shadowHomePath.title",
|
|
88
|
+
descriptionKey: "form.shadowHomePath.description",
|
|
89
|
+
placeholderKey: "form.shadowHomePath.placeholder"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
launchArgs: {
|
|
93
|
+
type: "string",
|
|
94
|
+
"x-ycodium-form": {
|
|
95
|
+
titleKey: "form.launchArgs.title",
|
|
96
|
+
descriptionKey: "form.launchArgs.description"
|
|
97
|
+
}
|
|
98
|
+
},
|
|
64
99
|
customModels: { type: "array" },
|
|
65
100
|
driver: { type: "string" },
|
|
66
101
|
config: { type: "object" },
|
|
@@ -74,12 +109,12 @@ var CODEX_CARD = {
|
|
|
74
109
|
nativePathSuffix: "/responses",
|
|
75
110
|
credentialEnvKey: "CODEX_API_KEY",
|
|
76
111
|
supportsNativeSubscription: true,
|
|
77
|
-
// codex
|
|
78
|
-
//
|
|
79
|
-
//
|
|
112
|
+
// codex 不认「地址一个变量」:地址写在 `-c model_providers.<id>.base_url` 上,
|
|
113
|
+
// 凭据只能经环境变量、变量名还得告诉它。这只手的「API URL and key」账户连接
|
|
114
|
+
// (`connection.ts`)就按这个形状拼;这一栏只是说明,宿主不读。
|
|
80
115
|
//
|
|
81
|
-
// `forced_login_method=api` 要写在最后:chatgpt
|
|
82
|
-
// `forced_login_method=chatgpt
|
|
116
|
+
// `forced_login_method=api` 要写在最后:chatgpt 实例自己先加了
|
|
117
|
+
// `forced_login_method=chatgpt`,不盖掉的话手仍走订阅登录,填的地址白填。
|
|
83
118
|
extraLaunchArgs: [
|
|
84
119
|
{ key: "model_provider", value: "$providerId" },
|
|
85
120
|
{ key: "model_providers.$providerId.name", value: "Ycodium API" },
|
|
@@ -92,13 +127,196 @@ var CODEX_CARD = {
|
|
|
92
127
|
}
|
|
93
128
|
};
|
|
94
129
|
|
|
95
|
-
// src/codexInstance.ts
|
|
96
|
-
import * as NodeProcess from "node:process";
|
|
97
|
-
import { ExecutorSpawnRefusedError } from "@ycodium-ai/plugin-api/executor";
|
|
98
|
-
|
|
99
130
|
// src/codexAccount.ts
|
|
100
131
|
import * as NodeFS from "node:fs";
|
|
132
|
+
import * as NodePath3 from "node:path";
|
|
133
|
+
|
|
134
|
+
// src/homeLayout.ts
|
|
135
|
+
import * as NodeFSP from "node:fs/promises";
|
|
136
|
+
import * as NodeOS2 from "node:os";
|
|
137
|
+
import * as NodePath2 from "node:path";
|
|
138
|
+
|
|
139
|
+
// src/paths.ts
|
|
140
|
+
import * as NodeOS from "node:os";
|
|
101
141
|
import * as NodePath from "node:path";
|
|
142
|
+
function expandHomePath(value) {
|
|
143
|
+
if (value === "~") return NodeOS.homedir();
|
|
144
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
145
|
+
return NodePath.join(NodeOS.homedir(), value.slice(2));
|
|
146
|
+
}
|
|
147
|
+
return value;
|
|
148
|
+
}
|
|
149
|
+
function nowIso() {
|
|
150
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/homeLayout.ts
|
|
154
|
+
var KNOWN_SHARED_DIRECTORIES = [
|
|
155
|
+
"sessions",
|
|
156
|
+
"archived_sessions",
|
|
157
|
+
"sqlite",
|
|
158
|
+
"shell_snapshots",
|
|
159
|
+
"worktrees",
|
|
160
|
+
"skills",
|
|
161
|
+
"plugins",
|
|
162
|
+
"cache",
|
|
163
|
+
"logs",
|
|
164
|
+
"mcp-oauth-locks"
|
|
165
|
+
];
|
|
166
|
+
var PRIVATE_ENTRY_NAMES = /* @__PURE__ */ new Set(["auth.json", "models_cache.json"]);
|
|
167
|
+
var SHADOW_LOCAL_ENTRY_NAMES = /* @__PURE__ */ new Set(["log", "memories", "tmp"]);
|
|
168
|
+
var REPLACEABLE_SHARED_RUNTIME_DIRECTORIES = /* @__PURE__ */ new Set(["mcp-oauth-locks"]);
|
|
169
|
+
function resolveCodexHomeDirectory(value) {
|
|
170
|
+
const expanded = value && value.trim().length > 0 ? expandHomePath(value) : NodePath2.join(NodeOS2.homedir(), ".codex");
|
|
171
|
+
return NodePath2.resolve(expanded);
|
|
172
|
+
}
|
|
173
|
+
function resolveCodexHomeLayout(input) {
|
|
174
|
+
const configuredHome = input.homePath.trim() || (input.environmentHome?.trim() ?? "");
|
|
175
|
+
const sharedHomePath = resolveCodexHomeDirectory(configuredHome || void 0);
|
|
176
|
+
const shadowHomePath = input.shadowHomePath.trim();
|
|
177
|
+
if (shadowHomePath.length === 0) {
|
|
178
|
+
return {
|
|
179
|
+
mode: "direct",
|
|
180
|
+
sharedHomePath,
|
|
181
|
+
effectiveHomePath: configuredHome.length > 0 ? sharedHomePath : void 0,
|
|
182
|
+
continuationKey: `codex:home:${sharedHomePath}`
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
mode: "authOverlay",
|
|
187
|
+
sharedHomePath,
|
|
188
|
+
effectiveHomePath: NodePath2.resolve(expandHomePath(shadowHomePath)),
|
|
189
|
+
continuationKey: `codex:home:${sharedHomePath}`
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
function codexContinuationKey(input) {
|
|
193
|
+
return resolveCodexHomeLayout({
|
|
194
|
+
homePath: input.homePath,
|
|
195
|
+
shadowHomePath: "",
|
|
196
|
+
environmentHome: input.instanceEnvironment["CODEX_HOME"] ?? input.inheritedEnvironment["CODEX_HOME"]
|
|
197
|
+
}).continuationKey;
|
|
198
|
+
}
|
|
199
|
+
async function readLinkState(linkPath) {
|
|
200
|
+
try {
|
|
201
|
+
const target = await NodeFSP.readlink(linkPath);
|
|
202
|
+
return { _tag: "Symlink", target };
|
|
203
|
+
} catch (error) {
|
|
204
|
+
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
205
|
+
if (code === "ENOENT") return { _tag: "Missing" };
|
|
206
|
+
if (code === "EINVAL") return { _tag: "NotSymlink" };
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
async function createEntryLink(target, link) {
|
|
211
|
+
const type = await NodeFSP.stat(target).then(
|
|
212
|
+
(info) => info.isDirectory() ? "junction" : "file",
|
|
213
|
+
() => "file"
|
|
214
|
+
);
|
|
215
|
+
await NodeFSP.symlink(target, link, type);
|
|
216
|
+
}
|
|
217
|
+
async function removePrivateSymlink(effectiveHomePath, entryName) {
|
|
218
|
+
const privatePath = NodePath2.join(effectiveHomePath, entryName);
|
|
219
|
+
const state = await readLinkState(privatePath);
|
|
220
|
+
if (state._tag === "Symlink") await NodeFSP.rm(privatePath, { force: true });
|
|
221
|
+
}
|
|
222
|
+
async function ensureSymlink(input) {
|
|
223
|
+
const target = NodePath2.join(input.sharedHomePath, input.entryName);
|
|
224
|
+
const link = NodePath2.join(input.effectiveHomePath, input.entryName);
|
|
225
|
+
const state = await readLinkState(link);
|
|
226
|
+
if (state._tag === "NotSymlink") {
|
|
227
|
+
if (!REPLACEABLE_SHARED_RUNTIME_DIRECTORIES.has(input.entryName)) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`Cannot create Codex shadow home entry '${input.entryName}' because '${link}' already exists and is not a symlink.`
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
await NodeFSP.rm(link, { recursive: true, force: true });
|
|
233
|
+
await createEntryLink(target, link);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (state._tag === "Missing") {
|
|
237
|
+
await createEntryLink(target, link);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const resolvedExisting = NodePath2.resolve(NodePath2.dirname(link), state.target);
|
|
241
|
+
if (resolvedExisting !== target) {
|
|
242
|
+
await NodeFSP.rm(link, { force: true });
|
|
243
|
+
await createEntryLink(target, link);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
async function makeDirectory(directoryPath) {
|
|
247
|
+
try {
|
|
248
|
+
await NodeFSP.mkdir(directoryPath, { recursive: true });
|
|
249
|
+
} catch (error) {
|
|
250
|
+
throw new Error(
|
|
251
|
+
`Codex shadow home filesystem operation 'makeDirectory' failed for '${directoryPath}': ${String(error)}`,
|
|
252
|
+
{ cause: error }
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async function materializeCodexShadowHome(layout, log) {
|
|
257
|
+
if (layout.mode !== "authOverlay") return;
|
|
258
|
+
const effectiveHomePath = layout.effectiveHomePath;
|
|
259
|
+
if (!effectiveHomePath) return;
|
|
260
|
+
if (layout.sharedHomePath === effectiveHomePath) {
|
|
261
|
+
throw new Error(
|
|
262
|
+
`Codex shadow home path '${effectiveHomePath}' must be different from the shared home path '${layout.sharedHomePath}'.`
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
await makeDirectory(layout.sharedHomePath);
|
|
266
|
+
await makeDirectory(effectiveHomePath);
|
|
267
|
+
await Promise.all(
|
|
268
|
+
KNOWN_SHARED_DIRECTORIES.map(
|
|
269
|
+
(directory) => makeDirectory(NodePath2.join(layout.sharedHomePath, directory))
|
|
270
|
+
)
|
|
271
|
+
);
|
|
272
|
+
let sharedEntryNames = [];
|
|
273
|
+
try {
|
|
274
|
+
sharedEntryNames = await NodeFSP.readdir(layout.sharedHomePath);
|
|
275
|
+
} catch (error) {
|
|
276
|
+
throw new Error(
|
|
277
|
+
`Codex shadow home filesystem operation 'readDirectory' failed for '${layout.sharedHomePath}': ${String(error)}`,
|
|
278
|
+
{ cause: error }
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
const entries = new Set(KNOWN_SHARED_DIRECTORIES);
|
|
282
|
+
for (const entryName of sharedEntryNames) {
|
|
283
|
+
if (!PRIVATE_ENTRY_NAMES.has(entryName) && !SHADOW_LOCAL_ENTRY_NAMES.has(entryName)) {
|
|
284
|
+
entries.add(entryName);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
for (const entryName of PRIVATE_ENTRY_NAMES) {
|
|
288
|
+
if (entryName === "auth.json") continue;
|
|
289
|
+
await removePrivateSymlink(effectiveHomePath, entryName);
|
|
290
|
+
}
|
|
291
|
+
for (const entryName of entries) {
|
|
292
|
+
if (PRIVATE_ENTRY_NAMES.has(entryName)) continue;
|
|
293
|
+
try {
|
|
294
|
+
await ensureSymlink({
|
|
295
|
+
sharedHomePath: layout.sharedHomePath,
|
|
296
|
+
effectiveHomePath,
|
|
297
|
+
entryName
|
|
298
|
+
});
|
|
299
|
+
} catch (error) {
|
|
300
|
+
log?.warn("Codex shadow home entry left unlinked.", {
|
|
301
|
+
entryName,
|
|
302
|
+
effectiveHomePath,
|
|
303
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
const authPath = NodePath2.join(effectiveHomePath, "auth.json");
|
|
308
|
+
const authState = await readLinkState(authPath);
|
|
309
|
+
if (authState._tag === "Symlink") {
|
|
310
|
+
throw new Error(
|
|
311
|
+
`Codex shadow home private entry 'auth.json' at '${authPath}' must be a real file, not a symlink.`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
function accountHomeFromLayout(layout) {
|
|
316
|
+
return layout.effectiveHomePath ?? layout.sharedHomePath;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// src/codexAccount.ts
|
|
102
320
|
var AUTH_FILE_NAME = "auth.json";
|
|
103
321
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
104
322
|
var readString = (record, key) => {
|
|
@@ -139,14 +357,16 @@ var signedOutProbe = (exists) => ({
|
|
|
139
357
|
directoryExists: exists,
|
|
140
358
|
email: void 0,
|
|
141
359
|
name: void 0,
|
|
142
|
-
expiresAt: void 0
|
|
360
|
+
expiresAt: void 0,
|
|
361
|
+
authType: void 0
|
|
143
362
|
});
|
|
144
363
|
var unreadableProbe = (exists) => ({
|
|
145
364
|
state: "unreadable",
|
|
146
365
|
directoryExists: exists,
|
|
147
366
|
email: void 0,
|
|
148
367
|
name: void 0,
|
|
149
|
-
expiresAt: void 0
|
|
368
|
+
expiresAt: void 0,
|
|
369
|
+
authType: void 0
|
|
150
370
|
});
|
|
151
371
|
var epochMillisToIso = (ms) => {
|
|
152
372
|
if (ms === void 0) return void 0;
|
|
@@ -166,7 +386,7 @@ function decodeJwtClaims(token) {
|
|
|
166
386
|
}
|
|
167
387
|
var readCodexAccount = (homePath) => {
|
|
168
388
|
const exists = directoryExists(homePath);
|
|
169
|
-
const raw = readCredentialFile(
|
|
389
|
+
const raw = readCredentialFile(NodePath3.join(homePath, AUTH_FILE_NAME));
|
|
170
390
|
if (raw === void 0) return signedOutProbe(exists);
|
|
171
391
|
const parsed = parseJsonRecord(raw);
|
|
172
392
|
if (parsed === void 0 || !("OPENAI_API_KEY" in parsed || "tokens" in parsed)) {
|
|
@@ -187,13 +407,33 @@ var readCodexAccount = (homePath) => {
|
|
|
187
407
|
directoryExists: exists,
|
|
188
408
|
email: claims === void 0 ? void 0 : readString(claims, "email"),
|
|
189
409
|
name: void 0,
|
|
190
|
-
expiresAt
|
|
410
|
+
expiresAt,
|
|
411
|
+
authType: accessToken !== void 0 ? "chatgpt" : "apiKey"
|
|
191
412
|
};
|
|
192
413
|
};
|
|
414
|
+
var probeCodexAccount = (accountPath) => {
|
|
415
|
+
const resolved = resolveCodexHomeDirectory(accountPath || process.env.CODEX_HOME);
|
|
416
|
+
const account = readCodexAccount(resolved);
|
|
417
|
+
return {
|
|
418
|
+
accountPath: resolved,
|
|
419
|
+
state: account.state,
|
|
420
|
+
...account.email === void 0 ? {} : { email: account.email },
|
|
421
|
+
...account.expiresAt === void 0 ? {} : { expiresAt: account.expiresAt },
|
|
422
|
+
...account.authType === void 0 ? {} : { authType: account.authType }
|
|
423
|
+
};
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
// src/codexInstance.ts
|
|
427
|
+
import * as NodeProcess from "node:process";
|
|
428
|
+
import { ExecutorSpawnRefusedError } from "@ycodium-ai/plugin-api/executor";
|
|
193
429
|
|
|
194
430
|
// src/config.ts
|
|
195
431
|
import { modelSelectionStringOption } from "@ycodium-ai/plugin-api/executor";
|
|
432
|
+
|
|
433
|
+
// src/apiKeyEnv.ts
|
|
196
434
|
var CODEX_INSTANCE_API_KEY_ENV = "YCODIUM_CODEX_API_KEY";
|
|
435
|
+
|
|
436
|
+
// src/config.ts
|
|
197
437
|
var CODEX_INSTANCE_MODEL_PROVIDER = "ycodium_api";
|
|
198
438
|
function isRecord2(value) {
|
|
199
439
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -351,12 +591,6 @@ function codexMcpLaunchConfig(servers) {
|
|
|
351
591
|
}
|
|
352
592
|
return { args, environment };
|
|
353
593
|
}
|
|
354
|
-
function codexOverrideArgs(overrides) {
|
|
355
|
-
return overrides.flatMap(({ key, value }) => ["-c", `${key}=${JSON.stringify(value)}`]);
|
|
356
|
-
}
|
|
357
|
-
function encodeCodexConfigOverrides(overrides) {
|
|
358
|
-
return overrides.map(({ key, value }) => `-c ${JSON.stringify(`${key}=${JSON.stringify(value)}`)}`).join(" ");
|
|
359
|
-
}
|
|
360
594
|
function modelIdFromSelection(selection) {
|
|
361
595
|
if (typeof selection !== "object" || selection === null || Array.isArray(selection)) {
|
|
362
596
|
return void 0;
|
|
@@ -366,7 +600,7 @@ function modelIdFromSelection(selection) {
|
|
|
366
600
|
}
|
|
367
601
|
|
|
368
602
|
// src/connection.ts
|
|
369
|
-
import { mergeLaunchEnv
|
|
603
|
+
import { mergeLaunchEnv } from "@ycodium-ai/plugin-api/executor";
|
|
370
604
|
function codexApiConnectionError(recipe, environment) {
|
|
371
605
|
if (recipe.authMode !== "apiKey") return null;
|
|
372
606
|
try {
|
|
@@ -406,16 +640,7 @@ function connectionArgv(recipe, layout) {
|
|
|
406
640
|
}
|
|
407
641
|
return args;
|
|
408
642
|
}
|
|
409
|
-
function codexSpawnArgs(recipe, layout
|
|
410
|
-
if (nativeSubscription) {
|
|
411
|
-
return [
|
|
412
|
-
...tokenizeLaunchArgs(recipe.launchArgs),
|
|
413
|
-
"-c",
|
|
414
|
-
`model_provider=${JSON.stringify("openai")}`,
|
|
415
|
-
"-c",
|
|
416
|
-
`forced_login_method=${JSON.stringify("chatgpt")}`
|
|
417
|
-
];
|
|
418
|
-
}
|
|
643
|
+
function codexSpawnArgs(recipe, layout) {
|
|
419
644
|
const needsOverrides = layout.mode === "authOverlay" || recipe.authMode !== "default";
|
|
420
645
|
if (!needsOverrides) return tokenizeLaunchArgs(recipe.launchArgs);
|
|
421
646
|
return connectionArgv(recipe, layout);
|
|
@@ -427,7 +652,6 @@ var CRUSHED_API_KEYS = {
|
|
|
427
652
|
function buildCodexSpawnEnv(input) {
|
|
428
653
|
const { HOME: _instanceHome, ...instanceTable } = input.recipe.environment;
|
|
429
654
|
return mergeLaunchEnv({
|
|
430
|
-
brainSlots: brainSlotEnvKeys(CODEX_CARD),
|
|
431
655
|
accountHome: input.layout.effectiveHomePath !== void 0 ? { CODEX_HOME: input.layout.effectiveHomePath } : {},
|
|
432
656
|
instanceTable,
|
|
433
657
|
handOverrides: input.crushApiKeys ? CRUSHED_API_KEYS : {},
|
|
@@ -436,7 +660,6 @@ function buildCodexSpawnEnv(input) {
|
|
|
436
660
|
}
|
|
437
661
|
function nativeLoginSpawnEnv(processEnv, isolatedHome) {
|
|
438
662
|
return mergeLaunchEnv({
|
|
439
|
-
brainSlots: brainSlotEnvKeys(CODEX_CARD),
|
|
440
663
|
inherited: processEnv,
|
|
441
664
|
handOverrides: { ...CRUSHED_API_KEYS, CODEX_HOME: isolatedHome }
|
|
442
665
|
});
|
|
@@ -445,191 +668,6 @@ function shouldCrushApiKeys(authMode) {
|
|
|
445
668
|
return authMode === "chatgpt";
|
|
446
669
|
}
|
|
447
670
|
|
|
448
|
-
// src/homeLayout.ts
|
|
449
|
-
import * as NodeFSP from "node:fs/promises";
|
|
450
|
-
import * as NodeOS2 from "node:os";
|
|
451
|
-
import * as NodePath3 from "node:path";
|
|
452
|
-
|
|
453
|
-
// src/paths.ts
|
|
454
|
-
import * as NodeOS from "node:os";
|
|
455
|
-
import * as NodePath2 from "node:path";
|
|
456
|
-
function expandHomePath(value) {
|
|
457
|
-
if (value === "~") return NodeOS.homedir();
|
|
458
|
-
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
459
|
-
return NodePath2.join(NodeOS.homedir(), value.slice(2));
|
|
460
|
-
}
|
|
461
|
-
return value;
|
|
462
|
-
}
|
|
463
|
-
function nowIso() {
|
|
464
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// src/homeLayout.ts
|
|
468
|
-
var KNOWN_SHARED_DIRECTORIES = [
|
|
469
|
-
"sessions",
|
|
470
|
-
"archived_sessions",
|
|
471
|
-
"sqlite",
|
|
472
|
-
"shell_snapshots",
|
|
473
|
-
"worktrees",
|
|
474
|
-
"skills",
|
|
475
|
-
"plugins",
|
|
476
|
-
"cache",
|
|
477
|
-
"logs",
|
|
478
|
-
"mcp-oauth-locks"
|
|
479
|
-
];
|
|
480
|
-
var PRIVATE_ENTRY_NAMES = /* @__PURE__ */ new Set(["auth.json", "models_cache.json"]);
|
|
481
|
-
var SHADOW_LOCAL_ENTRY_NAMES = /* @__PURE__ */ new Set(["log", "memories", "tmp"]);
|
|
482
|
-
var REPLACEABLE_SHARED_RUNTIME_DIRECTORIES = /* @__PURE__ */ new Set(["mcp-oauth-locks"]);
|
|
483
|
-
function resolveCodexHomeDirectory(value) {
|
|
484
|
-
const expanded = value && value.trim().length > 0 ? expandHomePath(value) : NodePath3.join(NodeOS2.homedir(), ".codex");
|
|
485
|
-
return NodePath3.resolve(expanded);
|
|
486
|
-
}
|
|
487
|
-
function resolveCodexHomeLayout(input) {
|
|
488
|
-
const configuredHome = input.homePath.trim() || (input.environmentHome?.trim() ?? "");
|
|
489
|
-
const sharedHomePath = resolveCodexHomeDirectory(configuredHome || void 0);
|
|
490
|
-
const shadowHomePath = input.shadowHomePath.trim();
|
|
491
|
-
if (shadowHomePath.length === 0) {
|
|
492
|
-
return {
|
|
493
|
-
mode: "direct",
|
|
494
|
-
sharedHomePath,
|
|
495
|
-
effectiveHomePath: configuredHome.length > 0 ? sharedHomePath : void 0,
|
|
496
|
-
continuationKey: `codex:home:${sharedHomePath}`
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
return {
|
|
500
|
-
mode: "authOverlay",
|
|
501
|
-
sharedHomePath,
|
|
502
|
-
effectiveHomePath: NodePath3.resolve(expandHomePath(shadowHomePath)),
|
|
503
|
-
continuationKey: `codex:home:${sharedHomePath}`
|
|
504
|
-
};
|
|
505
|
-
}
|
|
506
|
-
function codexContinuationKey(input) {
|
|
507
|
-
return resolveCodexHomeLayout({
|
|
508
|
-
homePath: input.homePath,
|
|
509
|
-
shadowHomePath: "",
|
|
510
|
-
environmentHome: input.instanceEnvironment["CODEX_HOME"] ?? input.inheritedEnvironment["CODEX_HOME"]
|
|
511
|
-
}).continuationKey;
|
|
512
|
-
}
|
|
513
|
-
async function readLinkState(linkPath) {
|
|
514
|
-
try {
|
|
515
|
-
const target = await NodeFSP.readlink(linkPath);
|
|
516
|
-
return { _tag: "Symlink", target };
|
|
517
|
-
} catch (error) {
|
|
518
|
-
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
519
|
-
if (code === "ENOENT") return { _tag: "Missing" };
|
|
520
|
-
if (code === "EINVAL") return { _tag: "NotSymlink" };
|
|
521
|
-
throw error;
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
async function createEntryLink(target, link) {
|
|
525
|
-
const type = await NodeFSP.stat(target).then(
|
|
526
|
-
(info) => info.isDirectory() ? "junction" : "file",
|
|
527
|
-
() => "file"
|
|
528
|
-
);
|
|
529
|
-
await NodeFSP.symlink(target, link, type);
|
|
530
|
-
}
|
|
531
|
-
async function removePrivateSymlink(effectiveHomePath, entryName) {
|
|
532
|
-
const privatePath = NodePath3.join(effectiveHomePath, entryName);
|
|
533
|
-
const state = await readLinkState(privatePath);
|
|
534
|
-
if (state._tag === "Symlink") await NodeFSP.rm(privatePath, { force: true });
|
|
535
|
-
}
|
|
536
|
-
async function ensureSymlink(input) {
|
|
537
|
-
const target = NodePath3.join(input.sharedHomePath, input.entryName);
|
|
538
|
-
const link = NodePath3.join(input.effectiveHomePath, input.entryName);
|
|
539
|
-
const state = await readLinkState(link);
|
|
540
|
-
if (state._tag === "NotSymlink") {
|
|
541
|
-
if (!REPLACEABLE_SHARED_RUNTIME_DIRECTORIES.has(input.entryName)) {
|
|
542
|
-
throw new Error(
|
|
543
|
-
`Cannot create Codex shadow home entry '${input.entryName}' because '${link}' already exists and is not a symlink.`
|
|
544
|
-
);
|
|
545
|
-
}
|
|
546
|
-
await NodeFSP.rm(link, { recursive: true, force: true });
|
|
547
|
-
await createEntryLink(target, link);
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
if (state._tag === "Missing") {
|
|
551
|
-
await createEntryLink(target, link);
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
const resolvedExisting = NodePath3.resolve(NodePath3.dirname(link), state.target);
|
|
555
|
-
if (resolvedExisting !== target) {
|
|
556
|
-
await NodeFSP.rm(link, { force: true });
|
|
557
|
-
await createEntryLink(target, link);
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
async function makeDirectory(directoryPath) {
|
|
561
|
-
try {
|
|
562
|
-
await NodeFSP.mkdir(directoryPath, { recursive: true });
|
|
563
|
-
} catch (error) {
|
|
564
|
-
throw new Error(
|
|
565
|
-
`Codex shadow home filesystem operation 'makeDirectory' failed for '${directoryPath}': ${String(error)}`,
|
|
566
|
-
{ cause: error }
|
|
567
|
-
);
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
async function materializeCodexShadowHome(layout, log) {
|
|
571
|
-
if (layout.mode !== "authOverlay") return;
|
|
572
|
-
const effectiveHomePath = layout.effectiveHomePath;
|
|
573
|
-
if (!effectiveHomePath) return;
|
|
574
|
-
if (layout.sharedHomePath === effectiveHomePath) {
|
|
575
|
-
throw new Error(
|
|
576
|
-
`Codex shadow home path '${effectiveHomePath}' must be different from the shared home path '${layout.sharedHomePath}'.`
|
|
577
|
-
);
|
|
578
|
-
}
|
|
579
|
-
await makeDirectory(layout.sharedHomePath);
|
|
580
|
-
await makeDirectory(effectiveHomePath);
|
|
581
|
-
await Promise.all(
|
|
582
|
-
KNOWN_SHARED_DIRECTORIES.map(
|
|
583
|
-
(directory) => makeDirectory(NodePath3.join(layout.sharedHomePath, directory))
|
|
584
|
-
)
|
|
585
|
-
);
|
|
586
|
-
let sharedEntryNames = [];
|
|
587
|
-
try {
|
|
588
|
-
sharedEntryNames = await NodeFSP.readdir(layout.sharedHomePath);
|
|
589
|
-
} catch (error) {
|
|
590
|
-
throw new Error(
|
|
591
|
-
`Codex shadow home filesystem operation 'readDirectory' failed for '${layout.sharedHomePath}': ${String(error)}`,
|
|
592
|
-
{ cause: error }
|
|
593
|
-
);
|
|
594
|
-
}
|
|
595
|
-
const entries = new Set(KNOWN_SHARED_DIRECTORIES);
|
|
596
|
-
for (const entryName of sharedEntryNames) {
|
|
597
|
-
if (!PRIVATE_ENTRY_NAMES.has(entryName) && !SHADOW_LOCAL_ENTRY_NAMES.has(entryName)) {
|
|
598
|
-
entries.add(entryName);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
for (const entryName of PRIVATE_ENTRY_NAMES) {
|
|
602
|
-
if (entryName === "auth.json") continue;
|
|
603
|
-
await removePrivateSymlink(effectiveHomePath, entryName);
|
|
604
|
-
}
|
|
605
|
-
for (const entryName of entries) {
|
|
606
|
-
if (PRIVATE_ENTRY_NAMES.has(entryName)) continue;
|
|
607
|
-
try {
|
|
608
|
-
await ensureSymlink({
|
|
609
|
-
sharedHomePath: layout.sharedHomePath,
|
|
610
|
-
effectiveHomePath,
|
|
611
|
-
entryName
|
|
612
|
-
});
|
|
613
|
-
} catch (error) {
|
|
614
|
-
log?.warn("Codex shadow home entry left unlinked.", {
|
|
615
|
-
entryName,
|
|
616
|
-
effectiveHomePath,
|
|
617
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
618
|
-
});
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
const authPath = NodePath3.join(effectiveHomePath, "auth.json");
|
|
622
|
-
const authState = await readLinkState(authPath);
|
|
623
|
-
if (authState._tag === "Symlink") {
|
|
624
|
-
throw new Error(
|
|
625
|
-
`Codex shadow home private entry 'auth.json' at '${authPath}' must be a real file, not a symlink.`
|
|
626
|
-
);
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
function accountHomeFromLayout(layout) {
|
|
630
|
-
return layout.effectiveHomePath ?? layout.sharedHomePath;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
671
|
// src/protocol.ts
|
|
634
672
|
var CODEX_INITIALIZE_PARAMS = {
|
|
635
673
|
clientInfo: { name: "ycodium", title: "Ycodium", version: "0" },
|
|
@@ -879,6 +917,28 @@ function jsonRpcErrorMessage(error) {
|
|
|
879
917
|
}
|
|
880
918
|
|
|
881
919
|
// src/rpc.ts
|
|
920
|
+
function createLineAssembler() {
|
|
921
|
+
const fragments = [];
|
|
922
|
+
return {
|
|
923
|
+
push: (text) => {
|
|
924
|
+
const lines = [];
|
|
925
|
+
let start = 0;
|
|
926
|
+
for (let newline = text.indexOf("\n"); newline !== -1; newline = text.indexOf("\n", start)) {
|
|
927
|
+
fragments.push(text.slice(start, newline));
|
|
928
|
+
lines.push(fragments.join(""));
|
|
929
|
+
fragments.length = 0;
|
|
930
|
+
start = newline + 1;
|
|
931
|
+
}
|
|
932
|
+
if (start < text.length) fragments.push(text.slice(start));
|
|
933
|
+
return lines;
|
|
934
|
+
},
|
|
935
|
+
end: () => {
|
|
936
|
+
const line = fragments.join("");
|
|
937
|
+
fragments.length = 0;
|
|
938
|
+
return line;
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
}
|
|
882
942
|
var STDERR_TAIL_LIMIT = 4e3;
|
|
883
943
|
var DEFAULT_REQUEST_TIMEOUT_MS = 3e4;
|
|
884
944
|
var ANSI_ESCAPE_CHAR = String.fromCharCode(27);
|
|
@@ -946,17 +1006,12 @@ async function createCodexRpcClient(handle, handlers) {
|
|
|
946
1006
|
}
|
|
947
1007
|
};
|
|
948
1008
|
const pumpStdout = (async () => {
|
|
949
|
-
|
|
1009
|
+
const lines = createLineAssembler();
|
|
950
1010
|
for await (const chunk of handle.stdout) {
|
|
951
|
-
|
|
952
|
-
let index = buffer.indexOf("\n");
|
|
953
|
-
while (index >= 0) {
|
|
954
|
-
dispatch(buffer.slice(0, index));
|
|
955
|
-
buffer = buffer.slice(index + 1);
|
|
956
|
-
index = buffer.indexOf("\n");
|
|
957
|
-
}
|
|
1011
|
+
for (const line of lines.push(decoder.decode(chunk, { stream: true }))) dispatch(line);
|
|
958
1012
|
}
|
|
959
|
-
|
|
1013
|
+
const last = lines.end();
|
|
1014
|
+
if (last.length > 0) dispatch(last);
|
|
960
1015
|
})().catch(() => void 0);
|
|
961
1016
|
const pumpStderr = (async () => {
|
|
962
1017
|
const errorDecoder = new TextDecoder();
|
|
@@ -1059,6 +1114,9 @@ async function createCodexRpcClient(handle, handlers) {
|
|
|
1059
1114
|
};
|
|
1060
1115
|
}
|
|
1061
1116
|
|
|
1117
|
+
// src/sessionRuntime.ts
|
|
1118
|
+
import * as NodeCrypto2 from "node:crypto";
|
|
1119
|
+
|
|
1062
1120
|
// src/approvalGrant.ts
|
|
1063
1121
|
function codexApprovalGrantKey(kind, identity) {
|
|
1064
1122
|
if (kind === "mcp-elicitation") return `codex:mcp:${identity || "unknown"}`;
|
|
@@ -1113,7 +1171,7 @@ You are running inside Ycodium. The \`ycodium\` MCP server is the product-native
|
|
|
1113
1171
|
|
|
1114
1172
|
For browser work, first call \`preview_status\`. If no automation-capable preview is attached, call \`preview_open\` before concluding that the browser is unavailable. Then use \`preview_navigate\`, \`preview_snapshot\`, and the focused interaction tools. Prefer snapshot-provided locators over coordinates.
|
|
1115
1173
|
|
|
1116
|
-
Do not switch to global browser skills, Chrome, Node REPL browser automation, standalone Playwright, or agent-browser merely because the preview is initially closed or a first call fails. Use an alternative browser system only when the Ycodium preview tools are absent, the user explicitly requests another browser, or \`preview_open\` returns an explicit unsupported/unavailable error. A failed Ycodium preview tool call should be inspected and retried with corrected arguments when the error is actionable.
|
|
1174
|
+
Do not switch to global browser skills, Chrome, Node REPL browser automation, standalone Playwright, or agent-browser merely because the preview is initially closed or a first call fails. Use an alternative browser system only when the Ycodium preview tools are absent, the user explicitly requests another browser, or \`preview_open\` returns an explicit unsupported/unavailable error. A failed Ycodium preview tool call should be inspected and retried with corrected arguments when the error is actionable. The exception is a tool result saying the user has not allowed agents to use a tab's browser profile: that is the user's decision, not a fault. Do not retry on that tab or work around it with another browser; follow the message (open a new tab, or ask the user).
|
|
1117
1175
|
`;
|
|
1118
1176
|
var YCODIUM_THREAD_TOOL_INSTRUCTIONS = `
|
|
1119
1177
|
|
|
@@ -1281,11 +1339,13 @@ function buildCodexDeveloperInstructions(interactionMode, runtime, browserToolsA
|
|
|
1281
1339
|
}
|
|
1282
1340
|
|
|
1283
1341
|
// src/events.ts
|
|
1342
|
+
import * as NodeCrypto from "node:crypto";
|
|
1284
1343
|
var CODEX_PROVIDER = "codex";
|
|
1344
|
+
var EVENT_ID_PREFIX = `codex-evt-${NodeCrypto.randomUUID()}`;
|
|
1285
1345
|
var eventSeq = 0;
|
|
1286
1346
|
function nextEventId() {
|
|
1287
1347
|
eventSeq += 1;
|
|
1288
|
-
return
|
|
1348
|
+
return `${EVENT_ID_PREFIX}-${eventSeq}`;
|
|
1289
1349
|
}
|
|
1290
1350
|
function trimText(value) {
|
|
1291
1351
|
if (typeof value !== "string") return void 0;
|
|
@@ -1416,7 +1476,7 @@ function turnCompletedControlFlow(params) {
|
|
|
1416
1476
|
const error = isRecord3(turn["error"]) ? trimText(turn["error"]["message"]) : void 0;
|
|
1417
1477
|
return error ? { state, errorMessage: error } : { state };
|
|
1418
1478
|
}
|
|
1419
|
-
function
|
|
1479
|
+
function fileChangeEntries(params) {
|
|
1420
1480
|
if (!isRecord3(params)) return [];
|
|
1421
1481
|
const item = isRecord3(params["item"]) ? params["item"] : params;
|
|
1422
1482
|
const changes = item["changes"] ?? item["fileChanges"] ?? params["fileChanges"];
|
|
@@ -1430,10 +1490,32 @@ function toApplyPatchFileChanges(params) {
|
|
|
1430
1490
|
if (!isRecord3(entry)) continue;
|
|
1431
1491
|
const path = asString(entry["path"]) ?? asString(entry["file"]);
|
|
1432
1492
|
if (!path) continue;
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1493
|
+
out.push({
|
|
1494
|
+
path,
|
|
1495
|
+
kind: (asString(entry["type"]) ?? asString(entry["kind"]) ?? "update").toLowerCase(),
|
|
1496
|
+
movePath: asString(entry["move_path"]) ?? asString(entry["movePath"]),
|
|
1497
|
+
diff: asString(entry["unified_diff"]) ?? asString(entry["unifiedDiff"]),
|
|
1498
|
+
content: asString(entry["content"])
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
return out;
|
|
1502
|
+
}
|
|
1503
|
+
var MAX_DESCRIBED_FILE_CHANGES = 20;
|
|
1504
|
+
function describeFileChanges(params) {
|
|
1505
|
+
const entries = fileChangeEntries(params).toSorted(
|
|
1506
|
+
(left, right) => left.path.localeCompare(right.path)
|
|
1507
|
+
);
|
|
1508
|
+
if (entries.length === 0) return void 0;
|
|
1509
|
+
const lines = entries.slice(0, MAX_DESCRIBED_FILE_CHANGES).map(
|
|
1510
|
+
(entry) => entry.movePath ? `${entry.kind} ${entry.path} -> ${entry.movePath}` : `${entry.kind} ${entry.path}`
|
|
1511
|
+
);
|
|
1512
|
+
const remaining = entries.length - lines.length;
|
|
1513
|
+
return remaining > 0 ? `${lines.join("\n")}
|
|
1514
|
+
+${remaining} more` : lines.join("\n");
|
|
1515
|
+
}
|
|
1516
|
+
function toApplyPatchFileChanges(params) {
|
|
1517
|
+
const out = [];
|
|
1518
|
+
for (const { path, kind, movePath, diff, content } of fileChangeEntries(params)) {
|
|
1437
1519
|
if (kind === "add") {
|
|
1438
1520
|
out.push({
|
|
1439
1521
|
path,
|
|
@@ -1689,8 +1771,10 @@ function describeFileChangeApproval(params) {
|
|
|
1689
1771
|
const reason = trimText(record["reason"]);
|
|
1690
1772
|
const grantRoot = asString(record["grantRoot"]);
|
|
1691
1773
|
const callId = asString(record["callId"]);
|
|
1774
|
+
const detail = reason ?? describeFileChanges(record) ?? trimText(grantRoot);
|
|
1692
1775
|
return {
|
|
1693
1776
|
...reason ? { reason } : {},
|
|
1777
|
+
...detail ? { detail } : {},
|
|
1694
1778
|
...grantRoot ? { grantRoot } : {},
|
|
1695
1779
|
...callId ? { callId } : {},
|
|
1696
1780
|
fileChanges: toApplyPatchFileChanges(record)
|
|
@@ -2320,7 +2404,7 @@ function elicitationOptions(params) {
|
|
|
2320
2404
|
];
|
|
2321
2405
|
}
|
|
2322
2406
|
function mapServerRequestToEvents(input) {
|
|
2323
|
-
const requestId =
|
|
2407
|
+
const requestId = input.requestId;
|
|
2324
2408
|
if (input.method === "item/tool/requestUserInput") {
|
|
2325
2409
|
const questions = toUserInputQuestions(input.params);
|
|
2326
2410
|
if (!questions) return [];
|
|
@@ -2351,7 +2435,7 @@ function mapServerRequestToEvents(input) {
|
|
|
2351
2435
|
payload["args"] = input.params;
|
|
2352
2436
|
} else if (input.method === "item/fileChange/requestApproval" || input.method.includes("applyPatch")) {
|
|
2353
2437
|
const described = describeFileChangeApproval(input.params);
|
|
2354
|
-
if (described.
|
|
2438
|
+
if (described.detail) payload["detail"] = described.detail;
|
|
2355
2439
|
if (described.fileChanges.length > 0) payload["fileChanges"] = described.fileChanges;
|
|
2356
2440
|
payload["args"] = {
|
|
2357
2441
|
...described.callId ? { callId: described.callId } : {},
|
|
@@ -2382,6 +2466,9 @@ function mapServerRequestToEvents(input) {
|
|
|
2382
2466
|
}
|
|
2383
2467
|
|
|
2384
2468
|
// src/sessionRuntime.ts
|
|
2469
|
+
function cancelWireFor(pending) {
|
|
2470
|
+
return pending.kind === "mcp-elicitation" ? toMcpElicitationResponse("cancel", pending.params) : { decision: "cancel" };
|
|
2471
|
+
}
|
|
2385
2472
|
var sleep = (ms) => new Promise((resolve2) => {
|
|
2386
2473
|
setTimeout(resolve2, ms);
|
|
2387
2474
|
});
|
|
@@ -2434,8 +2521,18 @@ var CodexSessionRuntime = class {
|
|
|
2434
2521
|
memoryThreadIds = /* @__PURE__ */ new Set();
|
|
2435
2522
|
appServerArgs;
|
|
2436
2523
|
host;
|
|
2524
|
+
attachments;
|
|
2437
2525
|
rpc;
|
|
2438
2526
|
emit;
|
|
2527
|
+
/**
|
|
2528
|
+
* 对外 requestId 的前缀,每个会话(即每个 app-server 进程)一份。JSON-RPC id 每个进程
|
|
2529
|
+
* 都从 0 数起,宿主按 requestId 记「已接受」,不加前缀的话重启后的新请求会撞上旧请求。
|
|
2530
|
+
*/
|
|
2531
|
+
#requestIdPrefix = `codex-${NodeCrypto2.randomUUID()}:`;
|
|
2532
|
+
/** JSON-RPC id → 对外 requestId。`serverRequest/resolved` 里的 id 可能是数字也可能是字符串。 */
|
|
2533
|
+
requestIdFor(jsonRpcId) {
|
|
2534
|
+
return `${this.#requestIdPrefix}${String(jsonRpcId)}`;
|
|
2535
|
+
}
|
|
2439
2536
|
/** 这一轮该用哪套三字段组合:只由这一轮的档决定(见 `codexNativeModeFor`)。 */
|
|
2440
2537
|
#threadConfig() {
|
|
2441
2538
|
return codexThreadConfigForNativeMode(codexNativeModeFor(this.runtimeMode, this.nativeModeId));
|
|
@@ -2455,7 +2552,8 @@ var CodexSessionRuntime = class {
|
|
|
2455
2552
|
error: error instanceof Error ? error.message : String(error)
|
|
2456
2553
|
});
|
|
2457
2554
|
}
|
|
2458
|
-
constructor(host, rpc, emit, input, cwd, appServerArgs, grants, declineRelay) {
|
|
2555
|
+
constructor(host, rpc, emit, input, cwd, appServerArgs, grants, declineRelay, attachments) {
|
|
2556
|
+
this.attachments = attachments;
|
|
2459
2557
|
this.host = host;
|
|
2460
2558
|
this.rpc = rpc;
|
|
2461
2559
|
this.emit = emit;
|
|
@@ -2514,7 +2612,7 @@ var CodexSessionRuntime = class {
|
|
|
2514
2612
|
if (kind === "drop") return;
|
|
2515
2613
|
if (isFatalStderr(line)) {
|
|
2516
2614
|
this.emit({
|
|
2517
|
-
eventId:
|
|
2615
|
+
eventId: nextEventId(),
|
|
2518
2616
|
provider: CODEX_DRIVER_KIND,
|
|
2519
2617
|
threadId: this.threadId,
|
|
2520
2618
|
createdAt: nowIso(),
|
|
@@ -2557,21 +2655,24 @@ var CodexSessionRuntime = class {
|
|
|
2557
2655
|
}
|
|
2558
2656
|
dispatchMapped(method, params) {
|
|
2559
2657
|
if (method === "serverRequest/resolved") {
|
|
2560
|
-
const
|
|
2561
|
-
if (
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2658
|
+
const rawId = isRecord3(params) ? params["requestId"] : void 0;
|
|
2659
|
+
if (typeof rawId !== "number" && typeof rawId !== "string") return;
|
|
2660
|
+
const resolvedId = this.requestIdFor(rawId);
|
|
2661
|
+
const pending = this.pendingApprovals.get(resolvedId);
|
|
2662
|
+
if (pending === void 0) return;
|
|
2663
|
+
pending.release();
|
|
2664
|
+
this.emit({
|
|
2665
|
+
eventId: nextEventId(),
|
|
2666
|
+
provider: CODEX_DRIVER_KIND,
|
|
2667
|
+
threadId: this.threadId,
|
|
2668
|
+
createdAt: nowIso(),
|
|
2669
|
+
type: "request.resolved",
|
|
2670
|
+
requestId: resolvedId,
|
|
2671
|
+
payload: {
|
|
2672
|
+
requestType: toRequestTypeFromKind(this.requestKinds.get(resolvedId)),
|
|
2673
|
+
resolution: params
|
|
2674
|
+
}
|
|
2675
|
+
});
|
|
2575
2676
|
return;
|
|
2576
2677
|
}
|
|
2577
2678
|
for (const event of mapNotificationToEvents({
|
|
@@ -2750,9 +2851,9 @@ var CodexSessionRuntime = class {
|
|
|
2750
2851
|
return;
|
|
2751
2852
|
}
|
|
2752
2853
|
if (method === "item/tool/requestUserInput") {
|
|
2753
|
-
const requestId =
|
|
2854
|
+
const requestId = this.requestIdFor(id);
|
|
2754
2855
|
const events = mapServerRequestToEvents({
|
|
2755
|
-
|
|
2856
|
+
requestId,
|
|
2756
2857
|
method,
|
|
2757
2858
|
params,
|
|
2758
2859
|
threadId: this.threadId,
|
|
@@ -2783,9 +2884,9 @@ var CodexSessionRuntime = class {
|
|
|
2783
2884
|
await this.rpc.respond(id, { decision });
|
|
2784
2885
|
return;
|
|
2785
2886
|
}
|
|
2786
|
-
const requestId =
|
|
2887
|
+
const requestId = this.requestIdFor(id);
|
|
2787
2888
|
const events = mapServerRequestToEvents({
|
|
2788
|
-
|
|
2889
|
+
requestId,
|
|
2789
2890
|
method,
|
|
2790
2891
|
params,
|
|
2791
2892
|
threadId: this.threadId,
|
|
@@ -2802,6 +2903,10 @@ var CodexSessionRuntime = class {
|
|
|
2802
2903
|
settle: (decision) => {
|
|
2803
2904
|
this.pendingApprovals.delete(requestId);
|
|
2804
2905
|
void this.rpc.respond(id, decision).catch((error) => this.#warnAnswerWriteFailed(method, id, error)).finally(resolve2);
|
|
2906
|
+
},
|
|
2907
|
+
release: () => {
|
|
2908
|
+
this.pendingApprovals.delete(requestId);
|
|
2909
|
+
resolve2();
|
|
2805
2910
|
}
|
|
2806
2911
|
});
|
|
2807
2912
|
for (const event of events) this.emit(event);
|
|
@@ -2809,7 +2914,7 @@ var CodexSessionRuntime = class {
|
|
|
2809
2914
|
}
|
|
2810
2915
|
async respondToRequest(requestId, decision, reason, persist) {
|
|
2811
2916
|
const pending = this.pendingApprovals.get(requestId);
|
|
2812
|
-
if (!pending) throw new Error(`
|
|
2917
|
+
if (!pending) throw new Error(`Unknown pending Codex approval request: ${requestId}`);
|
|
2813
2918
|
if (decision === "deny" && reason) this.declineRelay.queue(this.threadId, reason);
|
|
2814
2919
|
let wire = { decision: decision === "allow" ? "accept" : "decline" };
|
|
2815
2920
|
if (decision === "allow" && pending.kind === "mcp-elicitation") {
|
|
@@ -2825,7 +2930,7 @@ var CodexSessionRuntime = class {
|
|
|
2825
2930
|
pending.settle(wire);
|
|
2826
2931
|
const delivery = await ack.catch(() => "closed");
|
|
2827
2932
|
this.emit({
|
|
2828
|
-
eventId:
|
|
2933
|
+
eventId: nextEventId(),
|
|
2829
2934
|
provider: CODEX_DRIVER_KIND,
|
|
2830
2935
|
threadId: this.threadId,
|
|
2831
2936
|
createdAt: nowIso(),
|
|
@@ -2842,7 +2947,7 @@ var CodexSessionRuntime = class {
|
|
|
2842
2947
|
}
|
|
2843
2948
|
async respondToUserInput(requestId, answers) {
|
|
2844
2949
|
const pending = this.pendingUserInputs.get(requestId);
|
|
2845
|
-
if (!pending) throw new Error(`
|
|
2950
|
+
if (!pending) throw new Error(`Unknown pending Codex user input request: ${requestId}`);
|
|
2846
2951
|
const mapped = toCodexUserInputAnswers(answers);
|
|
2847
2952
|
if (mapped === void 0) {
|
|
2848
2953
|
throw new Error(`invalid user input answers for request ${requestId}`);
|
|
@@ -2851,7 +2956,7 @@ var CodexSessionRuntime = class {
|
|
|
2851
2956
|
pending.settle({ answers: mapped });
|
|
2852
2957
|
const delivery = await ack.catch(() => "closed");
|
|
2853
2958
|
this.emit({
|
|
2854
|
-
eventId:
|
|
2959
|
+
eventId: nextEventId(),
|
|
2855
2960
|
provider: CODEX_DRIVER_KIND,
|
|
2856
2961
|
threadId: this.threadId,
|
|
2857
2962
|
createdAt: nowIso(),
|
|
@@ -2903,7 +3008,7 @@ var CodexSessionRuntime = class {
|
|
|
2903
3008
|
}
|
|
2904
3009
|
async openThread(input) {
|
|
2905
3010
|
const resume = input.discardResumeCursor === true ? void 0 : parseCodexResumeCursor(input.resumeCursor);
|
|
2906
|
-
const requestedModel =
|
|
3011
|
+
const requestedModel = modelIdFromSelection(input.modelSelection);
|
|
2907
3012
|
const startParams = {
|
|
2908
3013
|
...this.threadConfigParams(),
|
|
2909
3014
|
...requestedModel ? { model: requestedModel } : {}
|
|
@@ -2939,7 +3044,7 @@ var CodexSessionRuntime = class {
|
|
|
2939
3044
|
...this.model ? { model: this.model } : {}
|
|
2940
3045
|
};
|
|
2941
3046
|
this.emit({
|
|
2942
|
-
eventId:
|
|
3047
|
+
eventId: nextEventId(),
|
|
2943
3048
|
provider: CODEX_DRIVER_KIND,
|
|
2944
3049
|
threadId: this.threadId,
|
|
2945
3050
|
createdAt: nowIso(),
|
|
@@ -2957,24 +3062,45 @@ var CodexSessionRuntime = class {
|
|
|
2957
3062
|
const effort = modelSelectionStringOption(input.modelSelection, "reasoningEffort") ?? DEFAULT_TEXT_GENERATION_REASONING_EFFORT;
|
|
2958
3063
|
return buildCodexDeveloperInstructions(mode, { model, reasoningEffort: effort }, tools, tools);
|
|
2959
3064
|
}
|
|
2960
|
-
turnInput(input) {
|
|
3065
|
+
async turnInput(input) {
|
|
3066
|
+
const images = await this.#imageInputs(input.attachments);
|
|
2961
3067
|
const items = [];
|
|
2962
3068
|
const declined = this.declineRelay.take(this.threadId);
|
|
2963
3069
|
const text = [declined, input.input ?? ""].filter((part) => part && part.length > 0).join("\n\n");
|
|
2964
3070
|
if (text.length > 0) items.push({ type: "text", text });
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
3071
|
+
items.push(...images);
|
|
3072
|
+
return items;
|
|
3073
|
+
}
|
|
3074
|
+
/** 宿主只给附件 id:字节经附件接口读回,拼成 data URL 交给 codex。读不到就报错,不悄悄丢图。 */
|
|
3075
|
+
async #imageInputs(attachments) {
|
|
3076
|
+
const items = [];
|
|
3077
|
+
for (const attachment of attachments ?? []) {
|
|
3078
|
+
if (!isRecord3(attachment) || attachment["type"] !== "image") continue;
|
|
3079
|
+
const id = asString(attachment["id"]);
|
|
3080
|
+
const mimeType = asString(attachment["mimeType"]);
|
|
3081
|
+
if (!id || !mimeType) {
|
|
3082
|
+
throw new Error("codex image attachment is missing its id or mime type");
|
|
2970
3083
|
}
|
|
3084
|
+
if (!this.attachments) {
|
|
3085
|
+
throw new Error(`codex cannot read image attachment ${id}: no attachment access`);
|
|
3086
|
+
}
|
|
3087
|
+
const bytes = await this.attachments.read(id);
|
|
3088
|
+
items.push({
|
|
3089
|
+
type: "image",
|
|
3090
|
+
url: `data:${mimeType};base64,${Buffer.from(bytes).toString("base64")}`
|
|
3091
|
+
});
|
|
2971
3092
|
}
|
|
2972
3093
|
return items;
|
|
2973
3094
|
}
|
|
2974
3095
|
async sendTurn(input) {
|
|
2975
3096
|
if (!this.nativeThreadId) throw new Error("codex session has no native thread");
|
|
2976
3097
|
if (hasConfiguredMcpServer(this.appServerArgs)) {
|
|
2977
|
-
await this.rpc.request("mcpServer/reload",
|
|
3098
|
+
await this.rpc.request("config/mcpServer/reload", void 0, 0).catch((error) => {
|
|
3099
|
+
this.host.log.warn("refreshing the codex MCP tool list before a turn failed", {
|
|
3100
|
+
threadId: this.threadId,
|
|
3101
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3102
|
+
});
|
|
3103
|
+
});
|
|
2978
3104
|
}
|
|
2979
3105
|
const sandboxPolicy = this.#turnSandboxPolicy();
|
|
2980
3106
|
const config = this.#threadConfig();
|
|
@@ -2983,7 +3109,7 @@ var CodexSessionRuntime = class {
|
|
|
2983
3109
|
const serviceTier = modelSelectionStringOption(input.modelSelection, "serviceTier");
|
|
2984
3110
|
const params = {
|
|
2985
3111
|
threadId: this.nativeThreadId,
|
|
2986
|
-
input: this.turnInput(input),
|
|
3112
|
+
input: await this.turnInput(input),
|
|
2987
3113
|
approvalPolicy: config.approvalPolicy,
|
|
2988
3114
|
approvalsReviewer: config.approvalsReviewer,
|
|
2989
3115
|
sandboxPolicy,
|
|
@@ -3043,24 +3169,29 @@ var CodexSessionRuntime = class {
|
|
|
3043
3169
|
);
|
|
3044
3170
|
if (!this.nativeThreadId) return;
|
|
3045
3171
|
const target = turnId ?? this.activeTurnId;
|
|
3046
|
-
if (
|
|
3047
|
-
|
|
3172
|
+
if (target) {
|
|
3173
|
+
await this.rpc.request("turn/interrupt", { threadId: this.nativeThreadId, turnId: target }, 0).catch(() => void 0);
|
|
3174
|
+
}
|
|
3175
|
+
this.cancelPendingApprovals();
|
|
3176
|
+
}
|
|
3177
|
+
/** 把还挂着的审批答成「取消」,逐条发 request.resolved。 */
|
|
3178
|
+
cancelPendingApprovals() {
|
|
3179
|
+
for (const [requestId, pending] of this.pendingApprovals) {
|
|
3180
|
+
pending.settle(cancelWireFor(pending));
|
|
3181
|
+
this.emit({
|
|
3182
|
+
eventId: nextEventId(),
|
|
3183
|
+
provider: CODEX_DRIVER_KIND,
|
|
3184
|
+
threadId: this.threadId,
|
|
3185
|
+
createdAt: nowIso(),
|
|
3186
|
+
type: "request.resolved",
|
|
3187
|
+
requestId,
|
|
3188
|
+
payload: { requestType: toRequestTypeFromKind(pending.kind), decision: "cancel" }
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3048
3191
|
}
|
|
3049
3192
|
async compactContext() {
|
|
3050
3193
|
if (!this.nativeThreadId) return;
|
|
3051
|
-
await this.rpc.request("thread/compact", { threadId: this.nativeThreadId }, 0)
|
|
3052
|
-
}
|
|
3053
|
-
async uploadFeedback(reason) {
|
|
3054
|
-
const result = await this.rpc.request(
|
|
3055
|
-
"feedback/upload",
|
|
3056
|
-
{
|
|
3057
|
-
threadId: this.nativeThreadId,
|
|
3058
|
-
...reason ? { reason } : {}
|
|
3059
|
-
},
|
|
3060
|
-
0
|
|
3061
|
-
);
|
|
3062
|
-
const id = (isRecord3(result) ? asString(result["feedbackId"]) : null) ?? `codex-feedback-${Date.now()}`;
|
|
3063
|
-
return { feedbackId: id };
|
|
3194
|
+
await this.rpc.request("thread/compact/start", { threadId: this.nativeThreadId }, 0);
|
|
3064
3195
|
}
|
|
3065
3196
|
async readThread() {
|
|
3066
3197
|
if (!this.nativeThreadId) return { threadId: this.threadId, turns: this.turns };
|
|
@@ -3115,7 +3246,7 @@ var CodexSessionRuntime = class {
|
|
|
3115
3246
|
}
|
|
3116
3247
|
this.closeCompleted = true;
|
|
3117
3248
|
this.emit({
|
|
3118
|
-
eventId:
|
|
3249
|
+
eventId: nextEventId(),
|
|
3119
3250
|
provider: CODEX_DRIVER_KIND,
|
|
3120
3251
|
threadId: this.threadId,
|
|
3121
3252
|
createdAt: nowIso(),
|
|
@@ -3470,10 +3601,6 @@ function serviceTierDescriptor(row) {
|
|
|
3470
3601
|
currentValue: defaultTier
|
|
3471
3602
|
};
|
|
3472
3603
|
}
|
|
3473
|
-
function isSupersededModel(row) {
|
|
3474
|
-
if (isRecord3(row["upgradeInfo"])) return true;
|
|
3475
|
-
return (asString(row["upgrade"]) ?? "").length > 0;
|
|
3476
|
-
}
|
|
3477
3604
|
function codexProbeModel(row) {
|
|
3478
3605
|
if (!isRecord3(row)) return null;
|
|
3479
3606
|
const slug = asString(row["model"]);
|
|
@@ -3486,7 +3613,6 @@ function codexProbeModel(row) {
|
|
|
3486
3613
|
slug,
|
|
3487
3614
|
...displayName ? { name: codexModelDisplayName(displayName) } : {},
|
|
3488
3615
|
...row["isDefault"] === true ? { isDefault: true } : {},
|
|
3489
|
-
...isSupersededModel(row) ? { isLegacy: true } : {},
|
|
3490
3616
|
...row["hidden"] === true ? { hidden: true } : {},
|
|
3491
3617
|
// 空表也给 `capabilities`:`{}` 与 `null` 对宿主是一回事(都不画那一格),
|
|
3492
3618
|
// 但给对象保住「这只手确实答过这一问」这层意思。
|
|
@@ -3519,6 +3645,47 @@ async function listCodexModels(rpc) {
|
|
|
3519
3645
|
return models;
|
|
3520
3646
|
}
|
|
3521
3647
|
|
|
3648
|
+
// src/skills.ts
|
|
3649
|
+
var SKILLS_LIST_TIMEOUT_MS = 15e3;
|
|
3650
|
+
function codexProbeSkill(raw) {
|
|
3651
|
+
if (!isRecord3(raw)) return null;
|
|
3652
|
+
const name = asString(raw["name"]);
|
|
3653
|
+
const path = asString(raw["path"]);
|
|
3654
|
+
const scope = asString(raw["scope"]);
|
|
3655
|
+
if (!name || !path || scope === "repo") return null;
|
|
3656
|
+
const face = isRecord3(raw["interface"]) ? raw["interface"] : void 0;
|
|
3657
|
+
const description = asString(raw["description"]);
|
|
3658
|
+
const displayName = asString(face?.["displayName"]);
|
|
3659
|
+
const shortDescription = asString(face?.["shortDescription"]) || asString(raw["shortDescription"]);
|
|
3660
|
+
return {
|
|
3661
|
+
name,
|
|
3662
|
+
path,
|
|
3663
|
+
enabled: raw["enabled"] !== false,
|
|
3664
|
+
...scope ? { scope } : {},
|
|
3665
|
+
...description ? { description } : {},
|
|
3666
|
+
...displayName ? { displayName } : {},
|
|
3667
|
+
...shortDescription ? { shortDescription } : {}
|
|
3668
|
+
};
|
|
3669
|
+
}
|
|
3670
|
+
async function listCodexSkills(rpc) {
|
|
3671
|
+
let response;
|
|
3672
|
+
try {
|
|
3673
|
+
response = await rpc.request("skills/list", {}, SKILLS_LIST_TIMEOUT_MS);
|
|
3674
|
+
} catch {
|
|
3675
|
+
return void 0;
|
|
3676
|
+
}
|
|
3677
|
+
if (!isRecord3(response) || !Array.isArray(response["data"])) return void 0;
|
|
3678
|
+
const skills = /* @__PURE__ */ new Map();
|
|
3679
|
+
for (const entry of response["data"]) {
|
|
3680
|
+
if (!isRecord3(entry) || !Array.isArray(entry["skills"])) continue;
|
|
3681
|
+
for (const raw of entry["skills"]) {
|
|
3682
|
+
const skill = codexProbeSkill(raw);
|
|
3683
|
+
if (skill !== null) skills.set(skill.path, skill);
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
return [...skills.values()].toSorted((left, right) => left.name.localeCompare(right.name));
|
|
3687
|
+
}
|
|
3688
|
+
|
|
3522
3689
|
// src/signIn.ts
|
|
3523
3690
|
var DEVICE_CODE_URL = /https?:\/\/\S+/;
|
|
3524
3691
|
var DEVICE_CODE_VALUE = /\b([A-Z0-9]{4,}(?:-[A-Z0-9]{4,})+)\b/;
|
|
@@ -3708,8 +3875,7 @@ import * as NodeFSP2 from "node:fs/promises";
|
|
|
3708
3875
|
import * as NodeOS3 from "node:os";
|
|
3709
3876
|
import * as NodePath5 from "node:path";
|
|
3710
3877
|
import {
|
|
3711
|
-
mergeLaunchEnv as mergeLaunchEnv2
|
|
3712
|
-
brainSlotEnvKeys as brainSlotEnvKeys2
|
|
3878
|
+
mergeLaunchEnv as mergeLaunchEnv2
|
|
3713
3879
|
} from "@ycodium-ai/plugin-api/executor";
|
|
3714
3880
|
var CODEX_TIMEOUT_MS = 18e4;
|
|
3715
3881
|
var EARLIER_CONTENT_TRUNCATION_MARKER = "[Earlier content truncated]\n\n";
|
|
@@ -3856,7 +4022,7 @@ async function collectStream(stream) {
|
|
|
3856
4022
|
return out;
|
|
3857
4023
|
}
|
|
3858
4024
|
function createCodexTextGeneration(input) {
|
|
3859
|
-
const run = async (operation, cwd, prompt, schema, modelSelection,
|
|
4025
|
+
const run = async (operation, cwd, prompt, schema, modelSelection, imagePaths, signal) => {
|
|
3860
4026
|
if (input.recipe.binaryPath.length === 0) {
|
|
3861
4027
|
throw new Error("Codex CLI is required but not available on PATH.");
|
|
3862
4028
|
}
|
|
@@ -3865,33 +4031,16 @@ function createCodexTextGeneration(input) {
|
|
|
3865
4031
|
const outputPath = NodePath5.join(tmp, "output.json");
|
|
3866
4032
|
await NodeFSP2.writeFile(schemaPath, JSON.stringify(schema));
|
|
3867
4033
|
await NodeFSP2.writeFile(outputPath, "");
|
|
3868
|
-
const launchRecord = isRecord3(launch) ? launch : {};
|
|
3869
|
-
const processEnv = isRecord3(launchRecord.processEnv) ? Object.fromEntries(
|
|
3870
|
-
Object.entries(launchRecord.processEnv).filter(
|
|
3871
|
-
(entry) => typeof entry[1] === "string"
|
|
3872
|
-
)
|
|
3873
|
-
) : {};
|
|
3874
|
-
const overlay = Array.isArray(launchRecord.launchArgOverrides) && launchRecord.launchArgOverrides.length > 0 ? encodeCodexConfigOverrides(
|
|
3875
|
-
launchRecord.launchArgOverrides.flatMap((entry) => {
|
|
3876
|
-
if (!isRecord3(entry) || typeof entry.key !== "string") return [];
|
|
3877
|
-
const value = entry.value;
|
|
3878
|
-
if (typeof value !== "string" && typeof value !== "boolean") return [];
|
|
3879
|
-
return [{ key: entry.key, value }];
|
|
3880
|
-
})
|
|
3881
|
-
) : "";
|
|
3882
|
-
const launchArgs = [input.recipe.launchArgs, overlay].filter((value) => value.length > 0).join(" ");
|
|
3883
4034
|
const reasoningEffort = modelSelectionStringOption(modelSelection, "reasoningEffort") ?? DEFAULT_TEXT_GENERATION_REASONING_EFFORT;
|
|
3884
4035
|
const serviceTier = modelSelectionStringOption(modelSelection, "serviceTier");
|
|
3885
|
-
const model =
|
|
4036
|
+
const model = modelIdFromSelection(modelSelection) ?? "gpt-5.6-luna";
|
|
3886
4037
|
const env = mergeLaunchEnv2({
|
|
3887
|
-
brainSlots: brainSlotEnvKeys2(CODEX_CARD),
|
|
3888
4038
|
accountHome: input.layout.effectiveHomePath ? { CODEX_HOME: input.layout.effectiveHomePath } : input.recipe.homePath.trim() ? { CODEX_HOME: expandHomePath(input.recipe.homePath) } : {},
|
|
3889
|
-
instanceTable: input.recipe.environment
|
|
3890
|
-
launch: processEnv
|
|
4039
|
+
instanceTable: input.recipe.environment
|
|
3891
4040
|
});
|
|
3892
4041
|
const args = [
|
|
3893
4042
|
"exec",
|
|
3894
|
-
...codexExecLaunchArgs(launchArgs),
|
|
4043
|
+
...codexExecLaunchArgs(input.recipe.launchArgs),
|
|
3895
4044
|
"--ephemeral",
|
|
3896
4045
|
"--skip-git-repo-check",
|
|
3897
4046
|
"-s",
|
|
@@ -3989,7 +4138,6 @@ function createCodexTextGeneration(input) {
|
|
|
3989
4138
|
prompt,
|
|
3990
4139
|
includeBranch ? COMMIT_WITH_BRANCH_SCHEMA : COMMIT_SCHEMA,
|
|
3991
4140
|
inputRecord.modelSelection,
|
|
3992
|
-
inputRecord.launch,
|
|
3993
4141
|
[],
|
|
3994
4142
|
signal
|
|
3995
4143
|
);
|
|
@@ -4040,7 +4188,6 @@ function createCodexTextGeneration(input) {
|
|
|
4040
4188
|
prompt,
|
|
4041
4189
|
PR_SCHEMA,
|
|
4042
4190
|
inputRecord.modelSelection,
|
|
4043
|
-
inputRecord.launch,
|
|
4044
4191
|
[],
|
|
4045
4192
|
signal
|
|
4046
4193
|
);
|
|
@@ -4080,7 +4227,6 @@ function createCodexTextGeneration(input) {
|
|
|
4080
4227
|
prompt,
|
|
4081
4228
|
BRANCH_SCHEMA,
|
|
4082
4229
|
inputRecord.modelSelection,
|
|
4083
|
-
inputRecord.launch,
|
|
4084
4230
|
absoluteImagePaths(attachments),
|
|
4085
4231
|
signal
|
|
4086
4232
|
);
|
|
@@ -4117,7 +4263,6 @@ ${extra}` : ""}`;
|
|
|
4117
4263
|
prompt,
|
|
4118
4264
|
TITLE_SCHEMA,
|
|
4119
4265
|
inputRecord.modelSelection,
|
|
4120
|
-
inputRecord.launch,
|
|
4121
4266
|
absoluteImagePaths(attachments),
|
|
4122
4267
|
signal
|
|
4123
4268
|
);
|
|
@@ -4146,7 +4291,6 @@ ${extra}` : ""}`;
|
|
|
4146
4291
|
prompt,
|
|
4147
4292
|
HANDOVER_SCHEMA,
|
|
4148
4293
|
inputRecord.modelSelection,
|
|
4149
|
-
inputRecord.launch,
|
|
4150
4294
|
[],
|
|
4151
4295
|
signal
|
|
4152
4296
|
);
|
|
@@ -4205,18 +4349,15 @@ function accountToAuth2(account) {
|
|
|
4205
4349
|
return { status: "unknown" };
|
|
4206
4350
|
}
|
|
4207
4351
|
}
|
|
4208
|
-
function layoutFor(recipe
|
|
4352
|
+
function layoutFor(recipe) {
|
|
4209
4353
|
const environmentHome = recipe.environment["CODEX_HOME"];
|
|
4210
|
-
if (nativeSubscription && launchHome?.trim()) {
|
|
4211
|
-
return resolveCodexHomeLayout({ homePath: launchHome.trim(), shadowHomePath: "" });
|
|
4212
|
-
}
|
|
4213
4354
|
return resolveCodexHomeLayout({
|
|
4214
|
-
homePath:
|
|
4355
|
+
homePath: recipe.homePath,
|
|
4215
4356
|
shadowHomePath: recipe.shadowHomePath,
|
|
4216
4357
|
...environmentHome === void 0 ? {} : { environmentHome }
|
|
4217
4358
|
});
|
|
4218
4359
|
}
|
|
4219
|
-
var createCodexInstance = (config, host) => {
|
|
4360
|
+
var createCodexInstance = (config, host, attachments) => {
|
|
4220
4361
|
const recipe = parseCodexInstanceConfig(config);
|
|
4221
4362
|
const events = new EventBus();
|
|
4222
4363
|
const threads = /* @__PURE__ */ new Map();
|
|
@@ -4256,10 +4397,11 @@ var createCodexInstance = (config, host) => {
|
|
|
4256
4397
|
await rpc.request("initialize", CODEX_INITIALIZE_PARAMS)
|
|
4257
4398
|
);
|
|
4258
4399
|
await rpc.notify("initialized");
|
|
4259
|
-
|
|
4400
|
+
const models = input.listModels ? await listCodexModels(rpc) : void 0;
|
|
4401
|
+
return { version, models, skills: await listCodexSkills(rpc) };
|
|
4260
4402
|
} catch (error) {
|
|
4261
4403
|
if (error instanceof ExecutorSpawnRefusedError) throw error;
|
|
4262
|
-
return { version, models: void 0 };
|
|
4404
|
+
return { version, models: void 0, skills: void 0 };
|
|
4263
4405
|
} finally {
|
|
4264
4406
|
await rpc?.close().catch(() => void 0);
|
|
4265
4407
|
await handle?.kill("SIGTERM").catch(() => void 0);
|
|
@@ -4286,7 +4428,9 @@ var createCodexInstance = (config, host) => {
|
|
|
4286
4428
|
};
|
|
4287
4429
|
}
|
|
4288
4430
|
const connectionError = codexApiConnectionError(recipe, recipe.environment);
|
|
4289
|
-
const { version, models } = await probeAppServer({
|
|
4431
|
+
const { version, models, skills } = await probeAppServer({
|
|
4432
|
+
listModels: connectionError === null
|
|
4433
|
+
});
|
|
4290
4434
|
if (connectionError) {
|
|
4291
4435
|
return {
|
|
4292
4436
|
installed: true,
|
|
@@ -4294,6 +4438,7 @@ var createCodexInstance = (config, host) => {
|
|
|
4294
4438
|
readiness: "error",
|
|
4295
4439
|
auth: probeAuth(),
|
|
4296
4440
|
detail: connectionError,
|
|
4441
|
+
...skills === void 0 ? {} : { skills },
|
|
4297
4442
|
...CODEX_NATIVE_MODE_REPORT
|
|
4298
4443
|
};
|
|
4299
4444
|
}
|
|
@@ -4303,11 +4448,14 @@ var createCodexInstance = (config, host) => {
|
|
|
4303
4448
|
readiness: models === void 0 ? "unchecked" : "ready",
|
|
4304
4449
|
auth: probeAuth(),
|
|
4305
4450
|
...models === void 0 ? {} : { models },
|
|
4451
|
+
...skills === void 0 ? {} : { skills },
|
|
4306
4452
|
// 宿主拿的是这一份。登录核验那份(`verifyCodexSignIn`)报了档、这里没报,
|
|
4307
4453
|
// 界面就一直画宿主四档——2026-09-20 真发生过,浏览器里才看得见。
|
|
4308
4454
|
...CODEX_NATIVE_MODE_REPORT
|
|
4309
4455
|
};
|
|
4310
4456
|
},
|
|
4457
|
+
// 有意不处理 input.readOnlyDirectories:codex 的沙箱只限制写入,读不受限,
|
|
4458
|
+
// 附件目录本来就能读,无需放行。
|
|
4311
4459
|
startSession: async (input, signal) => {
|
|
4312
4460
|
if (disposed) throw new Error("instance disposed");
|
|
4313
4461
|
if (signal?.aborted) throw new Error("aborted");
|
|
@@ -4321,22 +4469,17 @@ var createCodexInstance = (config, host) => {
|
|
|
4321
4469
|
await stopBooked(existing);
|
|
4322
4470
|
threads.delete(input.threadId);
|
|
4323
4471
|
}
|
|
4324
|
-
const
|
|
4325
|
-
const layout = layoutFor(recipe, input.launch.homePath, nativeSubscription);
|
|
4472
|
+
const layout = layoutFor(recipe);
|
|
4326
4473
|
await materializeCodexShadowHome(layout, host.log);
|
|
4327
4474
|
const mcp = codexMcpLaunchConfig(input.mcpServers ?? []);
|
|
4328
4475
|
const env = buildCodexSpawnEnv({
|
|
4329
4476
|
recipe,
|
|
4330
4477
|
layout,
|
|
4331
|
-
// MCP 那几个变量是宿主挑好的 server
|
|
4332
|
-
launchEnv:
|
|
4333
|
-
crushApiKeys:
|
|
4478
|
+
// MCP 那几个变量是宿主挑好的 server 带来的,铺在最终层:实例表盖不得。
|
|
4479
|
+
launchEnv: mcp.environment,
|
|
4480
|
+
crushApiKeys: shouldCrushApiKeys(recipe.authMode)
|
|
4334
4481
|
});
|
|
4335
|
-
const args = [
|
|
4336
|
-
...codexSpawnArgs(recipe, layout, nativeSubscription),
|
|
4337
|
-
...codexOverrideArgs(input.launch.launchArgOverrides),
|
|
4338
|
-
...mcp.args
|
|
4339
|
-
];
|
|
4482
|
+
const args = [...codexSpawnArgs(recipe, layout), ...mcp.args];
|
|
4340
4483
|
const handle = await host.process.spawn({
|
|
4341
4484
|
program: recipe.binaryPath,
|
|
4342
4485
|
args,
|
|
@@ -4371,7 +4514,17 @@ var createCodexInstance = (config, host) => {
|
|
|
4371
4514
|
}
|
|
4372
4515
|
}
|
|
4373
4516
|
});
|
|
4374
|
-
runtime = new CodexSessionRuntime(
|
|
4517
|
+
runtime = new CodexSessionRuntime(
|
|
4518
|
+
host,
|
|
4519
|
+
rpc,
|
|
4520
|
+
(event) => events.push(event),
|
|
4521
|
+
input,
|
|
4522
|
+
cwd,
|
|
4523
|
+
args,
|
|
4524
|
+
void 0,
|
|
4525
|
+
void 0,
|
|
4526
|
+
attachments
|
|
4527
|
+
);
|
|
4375
4528
|
try {
|
|
4376
4529
|
await rpc.request("initialize", CODEX_INITIALIZE_PARAMS);
|
|
4377
4530
|
await rpc.notify("initialized");
|
|
@@ -4439,10 +4592,6 @@ var createCodexInstance = (config, host) => {
|
|
|
4439
4592
|
const booked = requireThread(threadId);
|
|
4440
4593
|
return await booked.runtime.readThread();
|
|
4441
4594
|
},
|
|
4442
|
-
uploadFeedback: async (input) => {
|
|
4443
|
-
const booked = requireThread(input.threadId);
|
|
4444
|
-
return await booked.runtime.uploadFeedback(input.reason);
|
|
4445
|
-
},
|
|
4446
4595
|
events,
|
|
4447
4596
|
dispose: async () => {
|
|
4448
4597
|
if (disposed) return;
|
|
@@ -4628,13 +4777,15 @@ var createCodexInstance = (config, host) => {
|
|
|
4628
4777
|
};
|
|
4629
4778
|
|
|
4630
4779
|
// src/index.ts
|
|
4780
|
+
var capturedAttachments;
|
|
4631
4781
|
var executors = [
|
|
4632
4782
|
{
|
|
4633
4783
|
card: CODEX_CARD,
|
|
4634
4784
|
createInstance: async (config, host) => {
|
|
4635
4785
|
host.log.info("createInstance");
|
|
4636
|
-
return createCodexInstance(config, host);
|
|
4637
|
-
}
|
|
4786
|
+
return createCodexInstance(config, host, capturedAttachments);
|
|
4787
|
+
},
|
|
4788
|
+
probeAccount: async ({ accountPath }) => probeCodexAccount(accountPath)
|
|
4638
4789
|
}
|
|
4639
4790
|
];
|
|
4640
4791
|
var index_default = definePlugin({
|
|
@@ -4642,6 +4793,7 @@ var index_default = definePlugin({
|
|
|
4642
4793
|
apiVersion: 2,
|
|
4643
4794
|
executors,
|
|
4644
4795
|
activate(ctx) {
|
|
4796
|
+
capturedAttachments = ctx.attachments;
|
|
4645
4797
|
ctx.log.info("codex executor ready");
|
|
4646
4798
|
}
|
|
4647
4799
|
});
|