@ycodium-ai/agent-codex 0.2.2167 → 0.2.2488
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 +2 -1
- package/dist/index.js +442 -337
- package/locales/en.json +23 -0
- package/locales/zh-CN.json +23 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -17,7 +17,8 @@ Facts:
|
|
|
17
17
|
- Manifest permission is `{ kind: "executor" }`.
|
|
18
18
|
- `runtimeModeSwitch` is `next-turn` (same table the host adapter reads).
|
|
19
19
|
- `sessionModelSwitch` is `in-session`.
|
|
20
|
-
- Native ChatGPT device-code login is supported
|
|
20
|
+
- Native ChatGPT device-code login is supported: `deviceCodeSignInArgs` runs
|
|
21
|
+
`codex login --device-auth`.
|
|
21
22
|
|
|
22
23
|
Develop with `vp run --filter @ycodium-ai/agent-codex typecheck`, `build`, and `test`.
|
|
23
24
|
|
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,10 +407,26 @@ 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"
|
|
412
|
+
};
|
|
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 }
|
|
191
423
|
};
|
|
192
424
|
};
|
|
193
425
|
|
|
426
|
+
// src/codexInstance.ts
|
|
427
|
+
import * as NodeProcess from "node:process";
|
|
428
|
+
import { ExecutorSpawnRefusedError } from "@ycodium-ai/plugin-api/executor";
|
|
429
|
+
|
|
194
430
|
// src/config.ts
|
|
195
431
|
import { modelSelectionStringOption } from "@ycodium-ai/plugin-api/executor";
|
|
196
432
|
var CODEX_INSTANCE_API_KEY_ENV = "YCODIUM_CODEX_API_KEY";
|
|
@@ -351,12 +587,6 @@ function codexMcpLaunchConfig(servers) {
|
|
|
351
587
|
}
|
|
352
588
|
return { args, environment };
|
|
353
589
|
}
|
|
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
590
|
function modelIdFromSelection(selection) {
|
|
361
591
|
if (typeof selection !== "object" || selection === null || Array.isArray(selection)) {
|
|
362
592
|
return void 0;
|
|
@@ -366,7 +596,7 @@ function modelIdFromSelection(selection) {
|
|
|
366
596
|
}
|
|
367
597
|
|
|
368
598
|
// src/connection.ts
|
|
369
|
-
import { mergeLaunchEnv
|
|
599
|
+
import { mergeLaunchEnv } from "@ycodium-ai/plugin-api/executor";
|
|
370
600
|
function codexApiConnectionError(recipe, environment) {
|
|
371
601
|
if (recipe.authMode !== "apiKey") return null;
|
|
372
602
|
try {
|
|
@@ -406,16 +636,7 @@ function connectionArgv(recipe, layout) {
|
|
|
406
636
|
}
|
|
407
637
|
return args;
|
|
408
638
|
}
|
|
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
|
-
}
|
|
639
|
+
function codexSpawnArgs(recipe, layout) {
|
|
419
640
|
const needsOverrides = layout.mode === "authOverlay" || recipe.authMode !== "default";
|
|
420
641
|
if (!needsOverrides) return tokenizeLaunchArgs(recipe.launchArgs);
|
|
421
642
|
return connectionArgv(recipe, layout);
|
|
@@ -427,7 +648,6 @@ var CRUSHED_API_KEYS = {
|
|
|
427
648
|
function buildCodexSpawnEnv(input) {
|
|
428
649
|
const { HOME: _instanceHome, ...instanceTable } = input.recipe.environment;
|
|
429
650
|
return mergeLaunchEnv({
|
|
430
|
-
brainSlots: brainSlotEnvKeys(CODEX_CARD),
|
|
431
651
|
accountHome: input.layout.effectiveHomePath !== void 0 ? { CODEX_HOME: input.layout.effectiveHomePath } : {},
|
|
432
652
|
instanceTable,
|
|
433
653
|
handOverrides: input.crushApiKeys ? CRUSHED_API_KEYS : {},
|
|
@@ -436,7 +656,6 @@ function buildCodexSpawnEnv(input) {
|
|
|
436
656
|
}
|
|
437
657
|
function nativeLoginSpawnEnv(processEnv, isolatedHome) {
|
|
438
658
|
return mergeLaunchEnv({
|
|
439
|
-
brainSlots: brainSlotEnvKeys(CODEX_CARD),
|
|
440
659
|
inherited: processEnv,
|
|
441
660
|
handOverrides: { ...CRUSHED_API_KEYS, CODEX_HOME: isolatedHome }
|
|
442
661
|
});
|
|
@@ -445,191 +664,6 @@ function shouldCrushApiKeys(authMode) {
|
|
|
445
664
|
return authMode === "chatgpt";
|
|
446
665
|
}
|
|
447
666
|
|
|
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
667
|
// src/protocol.ts
|
|
634
668
|
var CODEX_INITIALIZE_PARAMS = {
|
|
635
669
|
clientInfo: { name: "ycodium", title: "Ycodium", version: "0" },
|
|
@@ -1059,6 +1093,9 @@ async function createCodexRpcClient(handle, handlers) {
|
|
|
1059
1093
|
};
|
|
1060
1094
|
}
|
|
1061
1095
|
|
|
1096
|
+
// src/sessionRuntime.ts
|
|
1097
|
+
import * as NodeCrypto2 from "node:crypto";
|
|
1098
|
+
|
|
1062
1099
|
// src/approvalGrant.ts
|
|
1063
1100
|
function codexApprovalGrantKey(kind, identity) {
|
|
1064
1101
|
if (kind === "mcp-elicitation") return `codex:mcp:${identity || "unknown"}`;
|
|
@@ -1113,7 +1150,7 @@ You are running inside Ycodium. The \`ycodium\` MCP server is the product-native
|
|
|
1113
1150
|
|
|
1114
1151
|
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
1152
|
|
|
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.
|
|
1153
|
+
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
1154
|
`;
|
|
1118
1155
|
var YCODIUM_THREAD_TOOL_INSTRUCTIONS = `
|
|
1119
1156
|
|
|
@@ -1281,11 +1318,13 @@ function buildCodexDeveloperInstructions(interactionMode, runtime, browserToolsA
|
|
|
1281
1318
|
}
|
|
1282
1319
|
|
|
1283
1320
|
// src/events.ts
|
|
1321
|
+
import * as NodeCrypto from "node:crypto";
|
|
1284
1322
|
var CODEX_PROVIDER = "codex";
|
|
1323
|
+
var EVENT_ID_PREFIX = `codex-evt-${NodeCrypto.randomUUID()}`;
|
|
1285
1324
|
var eventSeq = 0;
|
|
1286
1325
|
function nextEventId() {
|
|
1287
1326
|
eventSeq += 1;
|
|
1288
|
-
return
|
|
1327
|
+
return `${EVENT_ID_PREFIX}-${eventSeq}`;
|
|
1289
1328
|
}
|
|
1290
1329
|
function trimText(value) {
|
|
1291
1330
|
if (typeof value !== "string") return void 0;
|
|
@@ -2320,7 +2359,7 @@ function elicitationOptions(params) {
|
|
|
2320
2359
|
];
|
|
2321
2360
|
}
|
|
2322
2361
|
function mapServerRequestToEvents(input) {
|
|
2323
|
-
const requestId =
|
|
2362
|
+
const requestId = input.requestId;
|
|
2324
2363
|
if (input.method === "item/tool/requestUserInput") {
|
|
2325
2364
|
const questions = toUserInputQuestions(input.params);
|
|
2326
2365
|
if (!questions) return [];
|
|
@@ -2382,6 +2421,9 @@ function mapServerRequestToEvents(input) {
|
|
|
2382
2421
|
}
|
|
2383
2422
|
|
|
2384
2423
|
// src/sessionRuntime.ts
|
|
2424
|
+
function cancelWireFor(pending) {
|
|
2425
|
+
return pending.kind === "mcp-elicitation" ? toMcpElicitationResponse("cancel", pending.params) : { decision: "cancel" };
|
|
2426
|
+
}
|
|
2385
2427
|
var sleep = (ms) => new Promise((resolve2) => {
|
|
2386
2428
|
setTimeout(resolve2, ms);
|
|
2387
2429
|
});
|
|
@@ -2434,8 +2476,18 @@ var CodexSessionRuntime = class {
|
|
|
2434
2476
|
memoryThreadIds = /* @__PURE__ */ new Set();
|
|
2435
2477
|
appServerArgs;
|
|
2436
2478
|
host;
|
|
2479
|
+
attachments;
|
|
2437
2480
|
rpc;
|
|
2438
2481
|
emit;
|
|
2482
|
+
/**
|
|
2483
|
+
* 对外 requestId 的前缀,每个会话(即每个 app-server 进程)一份。JSON-RPC id 每个进程
|
|
2484
|
+
* 都从 0 数起,宿主按 requestId 记「已接受」,不加前缀的话重启后的新请求会撞上旧请求。
|
|
2485
|
+
*/
|
|
2486
|
+
#requestIdPrefix = `codex-${NodeCrypto2.randomUUID()}:`;
|
|
2487
|
+
/** JSON-RPC id → 对外 requestId。`serverRequest/resolved` 里的 id 可能是数字也可能是字符串。 */
|
|
2488
|
+
requestIdFor(jsonRpcId) {
|
|
2489
|
+
return `${this.#requestIdPrefix}${String(jsonRpcId)}`;
|
|
2490
|
+
}
|
|
2439
2491
|
/** 这一轮该用哪套三字段组合:只由这一轮的档决定(见 `codexNativeModeFor`)。 */
|
|
2440
2492
|
#threadConfig() {
|
|
2441
2493
|
return codexThreadConfigForNativeMode(codexNativeModeFor(this.runtimeMode, this.nativeModeId));
|
|
@@ -2455,7 +2507,8 @@ var CodexSessionRuntime = class {
|
|
|
2455
2507
|
error: error instanceof Error ? error.message : String(error)
|
|
2456
2508
|
});
|
|
2457
2509
|
}
|
|
2458
|
-
constructor(host, rpc, emit, input, cwd, appServerArgs, grants, declineRelay) {
|
|
2510
|
+
constructor(host, rpc, emit, input, cwd, appServerArgs, grants, declineRelay, attachments) {
|
|
2511
|
+
this.attachments = attachments;
|
|
2459
2512
|
this.host = host;
|
|
2460
2513
|
this.rpc = rpc;
|
|
2461
2514
|
this.emit = emit;
|
|
@@ -2514,7 +2567,7 @@ var CodexSessionRuntime = class {
|
|
|
2514
2567
|
if (kind === "drop") return;
|
|
2515
2568
|
if (isFatalStderr(line)) {
|
|
2516
2569
|
this.emit({
|
|
2517
|
-
eventId:
|
|
2570
|
+
eventId: nextEventId(),
|
|
2518
2571
|
provider: CODEX_DRIVER_KIND,
|
|
2519
2572
|
threadId: this.threadId,
|
|
2520
2573
|
createdAt: nowIso(),
|
|
@@ -2557,21 +2610,24 @@ var CodexSessionRuntime = class {
|
|
|
2557
2610
|
}
|
|
2558
2611
|
dispatchMapped(method, params) {
|
|
2559
2612
|
if (method === "serverRequest/resolved") {
|
|
2560
|
-
const
|
|
2561
|
-
if (
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2613
|
+
const rawId = isRecord3(params) ? params["requestId"] : void 0;
|
|
2614
|
+
if (typeof rawId !== "number" && typeof rawId !== "string") return;
|
|
2615
|
+
const resolvedId = this.requestIdFor(rawId);
|
|
2616
|
+
const pending = this.pendingApprovals.get(resolvedId);
|
|
2617
|
+
if (pending === void 0) return;
|
|
2618
|
+
pending.release();
|
|
2619
|
+
this.emit({
|
|
2620
|
+
eventId: nextEventId(),
|
|
2621
|
+
provider: CODEX_DRIVER_KIND,
|
|
2622
|
+
threadId: this.threadId,
|
|
2623
|
+
createdAt: nowIso(),
|
|
2624
|
+
type: "request.resolved",
|
|
2625
|
+
requestId: resolvedId,
|
|
2626
|
+
payload: {
|
|
2627
|
+
requestType: toRequestTypeFromKind(this.requestKinds.get(resolvedId)),
|
|
2628
|
+
resolution: params
|
|
2629
|
+
}
|
|
2630
|
+
});
|
|
2575
2631
|
return;
|
|
2576
2632
|
}
|
|
2577
2633
|
for (const event of mapNotificationToEvents({
|
|
@@ -2750,9 +2806,9 @@ var CodexSessionRuntime = class {
|
|
|
2750
2806
|
return;
|
|
2751
2807
|
}
|
|
2752
2808
|
if (method === "item/tool/requestUserInput") {
|
|
2753
|
-
const requestId =
|
|
2809
|
+
const requestId = this.requestIdFor(id);
|
|
2754
2810
|
const events = mapServerRequestToEvents({
|
|
2755
|
-
|
|
2811
|
+
requestId,
|
|
2756
2812
|
method,
|
|
2757
2813
|
params,
|
|
2758
2814
|
threadId: this.threadId,
|
|
@@ -2783,9 +2839,9 @@ var CodexSessionRuntime = class {
|
|
|
2783
2839
|
await this.rpc.respond(id, { decision });
|
|
2784
2840
|
return;
|
|
2785
2841
|
}
|
|
2786
|
-
const requestId =
|
|
2842
|
+
const requestId = this.requestIdFor(id);
|
|
2787
2843
|
const events = mapServerRequestToEvents({
|
|
2788
|
-
|
|
2844
|
+
requestId,
|
|
2789
2845
|
method,
|
|
2790
2846
|
params,
|
|
2791
2847
|
threadId: this.threadId,
|
|
@@ -2802,6 +2858,10 @@ var CodexSessionRuntime = class {
|
|
|
2802
2858
|
settle: (decision) => {
|
|
2803
2859
|
this.pendingApprovals.delete(requestId);
|
|
2804
2860
|
void this.rpc.respond(id, decision).catch((error) => this.#warnAnswerWriteFailed(method, id, error)).finally(resolve2);
|
|
2861
|
+
},
|
|
2862
|
+
release: () => {
|
|
2863
|
+
this.pendingApprovals.delete(requestId);
|
|
2864
|
+
resolve2();
|
|
2805
2865
|
}
|
|
2806
2866
|
});
|
|
2807
2867
|
for (const event of events) this.emit(event);
|
|
@@ -2809,7 +2869,7 @@ var CodexSessionRuntime = class {
|
|
|
2809
2869
|
}
|
|
2810
2870
|
async respondToRequest(requestId, decision, reason, persist) {
|
|
2811
2871
|
const pending = this.pendingApprovals.get(requestId);
|
|
2812
|
-
if (!pending) throw new Error(`
|
|
2872
|
+
if (!pending) throw new Error(`Unknown pending Codex approval request: ${requestId}`);
|
|
2813
2873
|
if (decision === "deny" && reason) this.declineRelay.queue(this.threadId, reason);
|
|
2814
2874
|
let wire = { decision: decision === "allow" ? "accept" : "decline" };
|
|
2815
2875
|
if (decision === "allow" && pending.kind === "mcp-elicitation") {
|
|
@@ -2825,7 +2885,7 @@ var CodexSessionRuntime = class {
|
|
|
2825
2885
|
pending.settle(wire);
|
|
2826
2886
|
const delivery = await ack.catch(() => "closed");
|
|
2827
2887
|
this.emit({
|
|
2828
|
-
eventId:
|
|
2888
|
+
eventId: nextEventId(),
|
|
2829
2889
|
provider: CODEX_DRIVER_KIND,
|
|
2830
2890
|
threadId: this.threadId,
|
|
2831
2891
|
createdAt: nowIso(),
|
|
@@ -2842,7 +2902,7 @@ var CodexSessionRuntime = class {
|
|
|
2842
2902
|
}
|
|
2843
2903
|
async respondToUserInput(requestId, answers) {
|
|
2844
2904
|
const pending = this.pendingUserInputs.get(requestId);
|
|
2845
|
-
if (!pending) throw new Error(`
|
|
2905
|
+
if (!pending) throw new Error(`Unknown pending Codex user input request: ${requestId}`);
|
|
2846
2906
|
const mapped = toCodexUserInputAnswers(answers);
|
|
2847
2907
|
if (mapped === void 0) {
|
|
2848
2908
|
throw new Error(`invalid user input answers for request ${requestId}`);
|
|
@@ -2851,7 +2911,7 @@ var CodexSessionRuntime = class {
|
|
|
2851
2911
|
pending.settle({ answers: mapped });
|
|
2852
2912
|
const delivery = await ack.catch(() => "closed");
|
|
2853
2913
|
this.emit({
|
|
2854
|
-
eventId:
|
|
2914
|
+
eventId: nextEventId(),
|
|
2855
2915
|
provider: CODEX_DRIVER_KIND,
|
|
2856
2916
|
threadId: this.threadId,
|
|
2857
2917
|
createdAt: nowIso(),
|
|
@@ -2903,7 +2963,7 @@ var CodexSessionRuntime = class {
|
|
|
2903
2963
|
}
|
|
2904
2964
|
async openThread(input) {
|
|
2905
2965
|
const resume = input.discardResumeCursor === true ? void 0 : parseCodexResumeCursor(input.resumeCursor);
|
|
2906
|
-
const requestedModel =
|
|
2966
|
+
const requestedModel = modelIdFromSelection(input.modelSelection);
|
|
2907
2967
|
const startParams = {
|
|
2908
2968
|
...this.threadConfigParams(),
|
|
2909
2969
|
...requestedModel ? { model: requestedModel } : {}
|
|
@@ -2939,7 +2999,7 @@ var CodexSessionRuntime = class {
|
|
|
2939
2999
|
...this.model ? { model: this.model } : {}
|
|
2940
3000
|
};
|
|
2941
3001
|
this.emit({
|
|
2942
|
-
eventId:
|
|
3002
|
+
eventId: nextEventId(),
|
|
2943
3003
|
provider: CODEX_DRIVER_KIND,
|
|
2944
3004
|
threadId: this.threadId,
|
|
2945
3005
|
createdAt: nowIso(),
|
|
@@ -2957,24 +3017,45 @@ var CodexSessionRuntime = class {
|
|
|
2957
3017
|
const effort = modelSelectionStringOption(input.modelSelection, "reasoningEffort") ?? DEFAULT_TEXT_GENERATION_REASONING_EFFORT;
|
|
2958
3018
|
return buildCodexDeveloperInstructions(mode, { model, reasoningEffort: effort }, tools, tools);
|
|
2959
3019
|
}
|
|
2960
|
-
turnInput(input) {
|
|
3020
|
+
async turnInput(input) {
|
|
3021
|
+
const images = await this.#imageInputs(input.attachments);
|
|
2961
3022
|
const items = [];
|
|
2962
3023
|
const declined = this.declineRelay.take(this.threadId);
|
|
2963
3024
|
const text = [declined, input.input ?? ""].filter((part) => part && part.length > 0).join("\n\n");
|
|
2964
3025
|
if (text.length > 0) items.push({ type: "text", text });
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
3026
|
+
items.push(...images);
|
|
3027
|
+
return items;
|
|
3028
|
+
}
|
|
3029
|
+
/** 宿主只给附件 id:字节经附件接口读回,拼成 data URL 交给 codex。读不到就报错,不悄悄丢图。 */
|
|
3030
|
+
async #imageInputs(attachments) {
|
|
3031
|
+
const items = [];
|
|
3032
|
+
for (const attachment of attachments ?? []) {
|
|
3033
|
+
if (!isRecord3(attachment) || attachment["type"] !== "image") continue;
|
|
3034
|
+
const id = asString(attachment["id"]);
|
|
3035
|
+
const mimeType = asString(attachment["mimeType"]);
|
|
3036
|
+
if (!id || !mimeType) {
|
|
3037
|
+
throw new Error("codex image attachment is missing its id or mime type");
|
|
2970
3038
|
}
|
|
3039
|
+
if (!this.attachments) {
|
|
3040
|
+
throw new Error(`codex cannot read image attachment ${id}: no attachment access`);
|
|
3041
|
+
}
|
|
3042
|
+
const bytes = await this.attachments.read(id);
|
|
3043
|
+
items.push({
|
|
3044
|
+
type: "image",
|
|
3045
|
+
url: `data:${mimeType};base64,${Buffer.from(bytes).toString("base64")}`
|
|
3046
|
+
});
|
|
2971
3047
|
}
|
|
2972
3048
|
return items;
|
|
2973
3049
|
}
|
|
2974
3050
|
async sendTurn(input) {
|
|
2975
3051
|
if (!this.nativeThreadId) throw new Error("codex session has no native thread");
|
|
2976
3052
|
if (hasConfiguredMcpServer(this.appServerArgs)) {
|
|
2977
|
-
await this.rpc.request("mcpServer/reload",
|
|
3053
|
+
await this.rpc.request("config/mcpServer/reload", void 0, 0).catch((error) => {
|
|
3054
|
+
this.host.log.warn("refreshing the codex MCP tool list before a turn failed", {
|
|
3055
|
+
threadId: this.threadId,
|
|
3056
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3057
|
+
});
|
|
3058
|
+
});
|
|
2978
3059
|
}
|
|
2979
3060
|
const sandboxPolicy = this.#turnSandboxPolicy();
|
|
2980
3061
|
const config = this.#threadConfig();
|
|
@@ -2983,7 +3064,7 @@ var CodexSessionRuntime = class {
|
|
|
2983
3064
|
const serviceTier = modelSelectionStringOption(input.modelSelection, "serviceTier");
|
|
2984
3065
|
const params = {
|
|
2985
3066
|
threadId: this.nativeThreadId,
|
|
2986
|
-
input: this.turnInput(input),
|
|
3067
|
+
input: await this.turnInput(input),
|
|
2987
3068
|
approvalPolicy: config.approvalPolicy,
|
|
2988
3069
|
approvalsReviewer: config.approvalsReviewer,
|
|
2989
3070
|
sandboxPolicy,
|
|
@@ -3043,24 +3124,29 @@ var CodexSessionRuntime = class {
|
|
|
3043
3124
|
);
|
|
3044
3125
|
if (!this.nativeThreadId) return;
|
|
3045
3126
|
const target = turnId ?? this.activeTurnId;
|
|
3046
|
-
if (
|
|
3047
|
-
|
|
3127
|
+
if (target) {
|
|
3128
|
+
await this.rpc.request("turn/interrupt", { threadId: this.nativeThreadId, turnId: target }, 0).catch(() => void 0);
|
|
3129
|
+
}
|
|
3130
|
+
this.cancelPendingApprovals();
|
|
3131
|
+
}
|
|
3132
|
+
/** 把还挂着的审批答成「取消」,逐条发 request.resolved。 */
|
|
3133
|
+
cancelPendingApprovals() {
|
|
3134
|
+
for (const [requestId, pending] of this.pendingApprovals) {
|
|
3135
|
+
pending.settle(cancelWireFor(pending));
|
|
3136
|
+
this.emit({
|
|
3137
|
+
eventId: nextEventId(),
|
|
3138
|
+
provider: CODEX_DRIVER_KIND,
|
|
3139
|
+
threadId: this.threadId,
|
|
3140
|
+
createdAt: nowIso(),
|
|
3141
|
+
type: "request.resolved",
|
|
3142
|
+
requestId,
|
|
3143
|
+
payload: { requestType: toRequestTypeFromKind(pending.kind), decision: "cancel" }
|
|
3144
|
+
});
|
|
3145
|
+
}
|
|
3048
3146
|
}
|
|
3049
3147
|
async compactContext() {
|
|
3050
3148
|
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 };
|
|
3149
|
+
await this.rpc.request("thread/compact/start", { threadId: this.nativeThreadId }, 0);
|
|
3064
3150
|
}
|
|
3065
3151
|
async readThread() {
|
|
3066
3152
|
if (!this.nativeThreadId) return { threadId: this.threadId, turns: this.turns };
|
|
@@ -3115,7 +3201,7 @@ var CodexSessionRuntime = class {
|
|
|
3115
3201
|
}
|
|
3116
3202
|
this.closeCompleted = true;
|
|
3117
3203
|
this.emit({
|
|
3118
|
-
eventId:
|
|
3204
|
+
eventId: nextEventId(),
|
|
3119
3205
|
provider: CODEX_DRIVER_KIND,
|
|
3120
3206
|
threadId: this.threadId,
|
|
3121
3207
|
createdAt: nowIso(),
|
|
@@ -3470,10 +3556,6 @@ function serviceTierDescriptor(row) {
|
|
|
3470
3556
|
currentValue: defaultTier
|
|
3471
3557
|
};
|
|
3472
3558
|
}
|
|
3473
|
-
function isSupersededModel(row) {
|
|
3474
|
-
if (isRecord3(row["upgradeInfo"])) return true;
|
|
3475
|
-
return (asString(row["upgrade"]) ?? "").length > 0;
|
|
3476
|
-
}
|
|
3477
3559
|
function codexProbeModel(row) {
|
|
3478
3560
|
if (!isRecord3(row)) return null;
|
|
3479
3561
|
const slug = asString(row["model"]);
|
|
@@ -3486,7 +3568,6 @@ function codexProbeModel(row) {
|
|
|
3486
3568
|
slug,
|
|
3487
3569
|
...displayName ? { name: codexModelDisplayName(displayName) } : {},
|
|
3488
3570
|
...row["isDefault"] === true ? { isDefault: true } : {},
|
|
3489
|
-
...isSupersededModel(row) ? { isLegacy: true } : {},
|
|
3490
3571
|
...row["hidden"] === true ? { hidden: true } : {},
|
|
3491
3572
|
// 空表也给 `capabilities`:`{}` 与 `null` 对宿主是一回事(都不画那一格),
|
|
3492
3573
|
// 但给对象保住「这只手确实答过这一问」这层意思。
|
|
@@ -3519,6 +3600,47 @@ async function listCodexModels(rpc) {
|
|
|
3519
3600
|
return models;
|
|
3520
3601
|
}
|
|
3521
3602
|
|
|
3603
|
+
// src/skills.ts
|
|
3604
|
+
var SKILLS_LIST_TIMEOUT_MS = 15e3;
|
|
3605
|
+
function codexProbeSkill(raw) {
|
|
3606
|
+
if (!isRecord3(raw)) return null;
|
|
3607
|
+
const name = asString(raw["name"]);
|
|
3608
|
+
const path = asString(raw["path"]);
|
|
3609
|
+
const scope = asString(raw["scope"]);
|
|
3610
|
+
if (!name || !path || scope === "repo") return null;
|
|
3611
|
+
const face = isRecord3(raw["interface"]) ? raw["interface"] : void 0;
|
|
3612
|
+
const description = asString(raw["description"]);
|
|
3613
|
+
const displayName = asString(face?.["displayName"]);
|
|
3614
|
+
const shortDescription = asString(face?.["shortDescription"]) || asString(raw["shortDescription"]);
|
|
3615
|
+
return {
|
|
3616
|
+
name,
|
|
3617
|
+
path,
|
|
3618
|
+
enabled: raw["enabled"] !== false,
|
|
3619
|
+
...scope ? { scope } : {},
|
|
3620
|
+
...description ? { description } : {},
|
|
3621
|
+
...displayName ? { displayName } : {},
|
|
3622
|
+
...shortDescription ? { shortDescription } : {}
|
|
3623
|
+
};
|
|
3624
|
+
}
|
|
3625
|
+
async function listCodexSkills(rpc) {
|
|
3626
|
+
let response;
|
|
3627
|
+
try {
|
|
3628
|
+
response = await rpc.request("skills/list", {}, SKILLS_LIST_TIMEOUT_MS);
|
|
3629
|
+
} catch {
|
|
3630
|
+
return void 0;
|
|
3631
|
+
}
|
|
3632
|
+
if (!isRecord3(response) || !Array.isArray(response["data"])) return void 0;
|
|
3633
|
+
const skills = /* @__PURE__ */ new Map();
|
|
3634
|
+
for (const entry of response["data"]) {
|
|
3635
|
+
if (!isRecord3(entry) || !Array.isArray(entry["skills"])) continue;
|
|
3636
|
+
for (const raw of entry["skills"]) {
|
|
3637
|
+
const skill = codexProbeSkill(raw);
|
|
3638
|
+
if (skill !== null) skills.set(skill.path, skill);
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
return [...skills.values()].toSorted((left, right) => left.name.localeCompare(right.name));
|
|
3642
|
+
}
|
|
3643
|
+
|
|
3522
3644
|
// src/signIn.ts
|
|
3523
3645
|
var DEVICE_CODE_URL = /https?:\/\/\S+/;
|
|
3524
3646
|
var DEVICE_CODE_VALUE = /\b([A-Z0-9]{4,}(?:-[A-Z0-9]{4,})+)\b/;
|
|
@@ -3708,8 +3830,7 @@ import * as NodeFSP2 from "node:fs/promises";
|
|
|
3708
3830
|
import * as NodeOS3 from "node:os";
|
|
3709
3831
|
import * as NodePath5 from "node:path";
|
|
3710
3832
|
import {
|
|
3711
|
-
mergeLaunchEnv as mergeLaunchEnv2
|
|
3712
|
-
brainSlotEnvKeys as brainSlotEnvKeys2
|
|
3833
|
+
mergeLaunchEnv as mergeLaunchEnv2
|
|
3713
3834
|
} from "@ycodium-ai/plugin-api/executor";
|
|
3714
3835
|
var CODEX_TIMEOUT_MS = 18e4;
|
|
3715
3836
|
var EARLIER_CONTENT_TRUNCATION_MARKER = "[Earlier content truncated]\n\n";
|
|
@@ -3856,7 +3977,7 @@ async function collectStream(stream) {
|
|
|
3856
3977
|
return out;
|
|
3857
3978
|
}
|
|
3858
3979
|
function createCodexTextGeneration(input) {
|
|
3859
|
-
const run = async (operation, cwd, prompt, schema, modelSelection,
|
|
3980
|
+
const run = async (operation, cwd, prompt, schema, modelSelection, imagePaths, signal) => {
|
|
3860
3981
|
if (input.recipe.binaryPath.length === 0) {
|
|
3861
3982
|
throw new Error("Codex CLI is required but not available on PATH.");
|
|
3862
3983
|
}
|
|
@@ -3865,33 +3986,16 @@ function createCodexTextGeneration(input) {
|
|
|
3865
3986
|
const outputPath = NodePath5.join(tmp, "output.json");
|
|
3866
3987
|
await NodeFSP2.writeFile(schemaPath, JSON.stringify(schema));
|
|
3867
3988
|
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
3989
|
const reasoningEffort = modelSelectionStringOption(modelSelection, "reasoningEffort") ?? DEFAULT_TEXT_GENERATION_REASONING_EFFORT;
|
|
3884
3990
|
const serviceTier = modelSelectionStringOption(modelSelection, "serviceTier");
|
|
3885
|
-
const model =
|
|
3991
|
+
const model = modelIdFromSelection(modelSelection) ?? "gpt-5.6-luna";
|
|
3886
3992
|
const env = mergeLaunchEnv2({
|
|
3887
|
-
brainSlots: brainSlotEnvKeys2(CODEX_CARD),
|
|
3888
3993
|
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
|
|
3994
|
+
instanceTable: input.recipe.environment
|
|
3891
3995
|
});
|
|
3892
3996
|
const args = [
|
|
3893
3997
|
"exec",
|
|
3894
|
-
...codexExecLaunchArgs(launchArgs),
|
|
3998
|
+
...codexExecLaunchArgs(input.recipe.launchArgs),
|
|
3895
3999
|
"--ephemeral",
|
|
3896
4000
|
"--skip-git-repo-check",
|
|
3897
4001
|
"-s",
|
|
@@ -3989,7 +4093,6 @@ function createCodexTextGeneration(input) {
|
|
|
3989
4093
|
prompt,
|
|
3990
4094
|
includeBranch ? COMMIT_WITH_BRANCH_SCHEMA : COMMIT_SCHEMA,
|
|
3991
4095
|
inputRecord.modelSelection,
|
|
3992
|
-
inputRecord.launch,
|
|
3993
4096
|
[],
|
|
3994
4097
|
signal
|
|
3995
4098
|
);
|
|
@@ -4040,7 +4143,6 @@ function createCodexTextGeneration(input) {
|
|
|
4040
4143
|
prompt,
|
|
4041
4144
|
PR_SCHEMA,
|
|
4042
4145
|
inputRecord.modelSelection,
|
|
4043
|
-
inputRecord.launch,
|
|
4044
4146
|
[],
|
|
4045
4147
|
signal
|
|
4046
4148
|
);
|
|
@@ -4080,7 +4182,6 @@ function createCodexTextGeneration(input) {
|
|
|
4080
4182
|
prompt,
|
|
4081
4183
|
BRANCH_SCHEMA,
|
|
4082
4184
|
inputRecord.modelSelection,
|
|
4083
|
-
inputRecord.launch,
|
|
4084
4185
|
absoluteImagePaths(attachments),
|
|
4085
4186
|
signal
|
|
4086
4187
|
);
|
|
@@ -4117,7 +4218,6 @@ ${extra}` : ""}`;
|
|
|
4117
4218
|
prompt,
|
|
4118
4219
|
TITLE_SCHEMA,
|
|
4119
4220
|
inputRecord.modelSelection,
|
|
4120
|
-
inputRecord.launch,
|
|
4121
4221
|
absoluteImagePaths(attachments),
|
|
4122
4222
|
signal
|
|
4123
4223
|
);
|
|
@@ -4146,7 +4246,6 @@ ${extra}` : ""}`;
|
|
|
4146
4246
|
prompt,
|
|
4147
4247
|
HANDOVER_SCHEMA,
|
|
4148
4248
|
inputRecord.modelSelection,
|
|
4149
|
-
inputRecord.launch,
|
|
4150
4249
|
[],
|
|
4151
4250
|
signal
|
|
4152
4251
|
);
|
|
@@ -4205,18 +4304,15 @@ function accountToAuth2(account) {
|
|
|
4205
4304
|
return { status: "unknown" };
|
|
4206
4305
|
}
|
|
4207
4306
|
}
|
|
4208
|
-
function layoutFor(recipe
|
|
4307
|
+
function layoutFor(recipe) {
|
|
4209
4308
|
const environmentHome = recipe.environment["CODEX_HOME"];
|
|
4210
|
-
if (nativeSubscription && launchHome?.trim()) {
|
|
4211
|
-
return resolveCodexHomeLayout({ homePath: launchHome.trim(), shadowHomePath: "" });
|
|
4212
|
-
}
|
|
4213
4309
|
return resolveCodexHomeLayout({
|
|
4214
|
-
homePath:
|
|
4310
|
+
homePath: recipe.homePath,
|
|
4215
4311
|
shadowHomePath: recipe.shadowHomePath,
|
|
4216
4312
|
...environmentHome === void 0 ? {} : { environmentHome }
|
|
4217
4313
|
});
|
|
4218
4314
|
}
|
|
4219
|
-
var createCodexInstance = (config, host) => {
|
|
4315
|
+
var createCodexInstance = (config, host, attachments) => {
|
|
4220
4316
|
const recipe = parseCodexInstanceConfig(config);
|
|
4221
4317
|
const events = new EventBus();
|
|
4222
4318
|
const threads = /* @__PURE__ */ new Map();
|
|
@@ -4256,10 +4352,11 @@ var createCodexInstance = (config, host) => {
|
|
|
4256
4352
|
await rpc.request("initialize", CODEX_INITIALIZE_PARAMS)
|
|
4257
4353
|
);
|
|
4258
4354
|
await rpc.notify("initialized");
|
|
4259
|
-
|
|
4355
|
+
const models = input.listModels ? await listCodexModels(rpc) : void 0;
|
|
4356
|
+
return { version, models, skills: await listCodexSkills(rpc) };
|
|
4260
4357
|
} catch (error) {
|
|
4261
4358
|
if (error instanceof ExecutorSpawnRefusedError) throw error;
|
|
4262
|
-
return { version, models: void 0 };
|
|
4359
|
+
return { version, models: void 0, skills: void 0 };
|
|
4263
4360
|
} finally {
|
|
4264
4361
|
await rpc?.close().catch(() => void 0);
|
|
4265
4362
|
await handle?.kill("SIGTERM").catch(() => void 0);
|
|
@@ -4286,7 +4383,9 @@ var createCodexInstance = (config, host) => {
|
|
|
4286
4383
|
};
|
|
4287
4384
|
}
|
|
4288
4385
|
const connectionError = codexApiConnectionError(recipe, recipe.environment);
|
|
4289
|
-
const { version, models } = await probeAppServer({
|
|
4386
|
+
const { version, models, skills } = await probeAppServer({
|
|
4387
|
+
listModels: connectionError === null
|
|
4388
|
+
});
|
|
4290
4389
|
if (connectionError) {
|
|
4291
4390
|
return {
|
|
4292
4391
|
installed: true,
|
|
@@ -4294,6 +4393,7 @@ var createCodexInstance = (config, host) => {
|
|
|
4294
4393
|
readiness: "error",
|
|
4295
4394
|
auth: probeAuth(),
|
|
4296
4395
|
detail: connectionError,
|
|
4396
|
+
...skills === void 0 ? {} : { skills },
|
|
4297
4397
|
...CODEX_NATIVE_MODE_REPORT
|
|
4298
4398
|
};
|
|
4299
4399
|
}
|
|
@@ -4303,6 +4403,7 @@ var createCodexInstance = (config, host) => {
|
|
|
4303
4403
|
readiness: models === void 0 ? "unchecked" : "ready",
|
|
4304
4404
|
auth: probeAuth(),
|
|
4305
4405
|
...models === void 0 ? {} : { models },
|
|
4406
|
+
...skills === void 0 ? {} : { skills },
|
|
4306
4407
|
// 宿主拿的是这一份。登录核验那份(`verifyCodexSignIn`)报了档、这里没报,
|
|
4307
4408
|
// 界面就一直画宿主四档——2026-09-20 真发生过,浏览器里才看得见。
|
|
4308
4409
|
...CODEX_NATIVE_MODE_REPORT
|
|
@@ -4321,22 +4422,17 @@ var createCodexInstance = (config, host) => {
|
|
|
4321
4422
|
await stopBooked(existing);
|
|
4322
4423
|
threads.delete(input.threadId);
|
|
4323
4424
|
}
|
|
4324
|
-
const
|
|
4325
|
-
const layout = layoutFor(recipe, input.launch.homePath, nativeSubscription);
|
|
4425
|
+
const layout = layoutFor(recipe);
|
|
4326
4426
|
await materializeCodexShadowHome(layout, host.log);
|
|
4327
4427
|
const mcp = codexMcpLaunchConfig(input.mcpServers ?? []);
|
|
4328
4428
|
const env = buildCodexSpawnEnv({
|
|
4329
4429
|
recipe,
|
|
4330
4430
|
layout,
|
|
4331
|
-
// MCP 那几个变量是宿主挑好的 server
|
|
4332
|
-
launchEnv:
|
|
4333
|
-
crushApiKeys:
|
|
4431
|
+
// MCP 那几个变量是宿主挑好的 server 带来的,铺在最终层:实例表盖不得。
|
|
4432
|
+
launchEnv: mcp.environment,
|
|
4433
|
+
crushApiKeys: shouldCrushApiKeys(recipe.authMode)
|
|
4334
4434
|
});
|
|
4335
|
-
const args = [
|
|
4336
|
-
...codexSpawnArgs(recipe, layout, nativeSubscription),
|
|
4337
|
-
...codexOverrideArgs(input.launch.launchArgOverrides),
|
|
4338
|
-
...mcp.args
|
|
4339
|
-
];
|
|
4435
|
+
const args = [...codexSpawnArgs(recipe, layout), ...mcp.args];
|
|
4340
4436
|
const handle = await host.process.spawn({
|
|
4341
4437
|
program: recipe.binaryPath,
|
|
4342
4438
|
args,
|
|
@@ -4371,7 +4467,17 @@ var createCodexInstance = (config, host) => {
|
|
|
4371
4467
|
}
|
|
4372
4468
|
}
|
|
4373
4469
|
});
|
|
4374
|
-
runtime = new CodexSessionRuntime(
|
|
4470
|
+
runtime = new CodexSessionRuntime(
|
|
4471
|
+
host,
|
|
4472
|
+
rpc,
|
|
4473
|
+
(event) => events.push(event),
|
|
4474
|
+
input,
|
|
4475
|
+
cwd,
|
|
4476
|
+
args,
|
|
4477
|
+
void 0,
|
|
4478
|
+
void 0,
|
|
4479
|
+
attachments
|
|
4480
|
+
);
|
|
4375
4481
|
try {
|
|
4376
4482
|
await rpc.request("initialize", CODEX_INITIALIZE_PARAMS);
|
|
4377
4483
|
await rpc.notify("initialized");
|
|
@@ -4439,10 +4545,6 @@ var createCodexInstance = (config, host) => {
|
|
|
4439
4545
|
const booked = requireThread(threadId);
|
|
4440
4546
|
return await booked.runtime.readThread();
|
|
4441
4547
|
},
|
|
4442
|
-
uploadFeedback: async (input) => {
|
|
4443
|
-
const booked = requireThread(input.threadId);
|
|
4444
|
-
return await booked.runtime.uploadFeedback(input.reason);
|
|
4445
|
-
},
|
|
4446
4548
|
events,
|
|
4447
4549
|
dispose: async () => {
|
|
4448
4550
|
if (disposed) return;
|
|
@@ -4628,13 +4730,15 @@ var createCodexInstance = (config, host) => {
|
|
|
4628
4730
|
};
|
|
4629
4731
|
|
|
4630
4732
|
// src/index.ts
|
|
4733
|
+
var capturedAttachments;
|
|
4631
4734
|
var executors = [
|
|
4632
4735
|
{
|
|
4633
4736
|
card: CODEX_CARD,
|
|
4634
4737
|
createInstance: async (config, host) => {
|
|
4635
4738
|
host.log.info("createInstance");
|
|
4636
|
-
return createCodexInstance(config, host);
|
|
4637
|
-
}
|
|
4739
|
+
return createCodexInstance(config, host, capturedAttachments);
|
|
4740
|
+
},
|
|
4741
|
+
probeAccount: async ({ accountPath }) => probeCodexAccount(accountPath)
|
|
4638
4742
|
}
|
|
4639
4743
|
];
|
|
4640
4744
|
var index_default = definePlugin({
|
|
@@ -4642,6 +4746,7 @@ var index_default = definePlugin({
|
|
|
4642
4746
|
apiVersion: 2,
|
|
4643
4747
|
executors,
|
|
4644
4748
|
activate(ctx) {
|
|
4749
|
+
capturedAttachments = ctx.attachments;
|
|
4645
4750
|
ctx.log.info("codex executor ready");
|
|
4646
4751
|
}
|
|
4647
4752
|
});
|
package/locales/en.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"form": {
|
|
3
|
+
"binaryPath": {
|
|
4
|
+
"title": "Program path",
|
|
5
|
+
"description": "Path to the Codex program this provider runs.",
|
|
6
|
+
"placeholder": "codex"
|
|
7
|
+
},
|
|
8
|
+
"homePath": {
|
|
9
|
+
"title": "CODEX_HOME path",
|
|
10
|
+
"description": "Custom Codex home and config directory.",
|
|
11
|
+
"placeholder": "~/.codex"
|
|
12
|
+
},
|
|
13
|
+
"shadowHomePath": {
|
|
14
|
+
"title": "Shadow home path",
|
|
15
|
+
"description": "Account-specific Codex home. Keeps auth.json separate while sharing state from CODEX_HOME.",
|
|
16
|
+
"placeholder": "~/.codex-personal"
|
|
17
|
+
},
|
|
18
|
+
"launchArgs": {
|
|
19
|
+
"title": "Launch arguments",
|
|
20
|
+
"description": "Additional CLI arguments passed to codex app-server on session start."
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"form": {
|
|
3
|
+
"binaryPath": {
|
|
4
|
+
"title": "程序路径",
|
|
5
|
+
"description": "此 provider 运行的 Codex 程序的路径。",
|
|
6
|
+
"placeholder": "codex"
|
|
7
|
+
},
|
|
8
|
+
"homePath": {
|
|
9
|
+
"title": "CODEX_HOME 路径",
|
|
10
|
+
"description": "自定义的 Codex 主目录与配置目录。",
|
|
11
|
+
"placeholder": "~/.codex"
|
|
12
|
+
},
|
|
13
|
+
"shadowHomePath": {
|
|
14
|
+
"title": "影子目录路径",
|
|
15
|
+
"description": "账户专用的 Codex 目录。auth.json 单独保存,其余状态仍与 CODEX_HOME 共用。",
|
|
16
|
+
"placeholder": "~/.codex-personal"
|
|
17
|
+
},
|
|
18
|
+
"launchArgs": {
|
|
19
|
+
"title": "启动参数",
|
|
20
|
+
"description": "会话启动时传给 codex app-server 的额外命令行参数。"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ycodium-ai/agent-codex",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2488",
|
|
4
4
|
"description": "Codex executor: Codex CLI over the app-server JSON-RPC protocol.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ycodium",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"assets",
|
|
13
|
+
"locales",
|
|
13
14
|
"README.md"
|
|
14
15
|
],
|
|
15
16
|
"type": "module",
|
|
@@ -20,7 +21,7 @@
|
|
|
20
21
|
"@types/node": "24.12.4",
|
|
21
22
|
"esbuild": "0.28.1",
|
|
22
23
|
"vite-plus": "0.2.2",
|
|
23
|
-
"@ycodium-ai/plugin-api": "0.2.
|
|
24
|
+
"@ycodium-ai/plugin-api": "0.2.2488",
|
|
24
25
|
"ycodium-executor-testing": "0.0.0"
|
|
25
26
|
},
|
|
26
27
|
"ycodium": {
|