@ycodium-ai/agent-codex 0.2.2163 → 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 +458 -349
- package/locales/en.json +23 -0
- package/locales/zh-CN.json +23 -0
- package/package.json +4 -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,
|
|
@@ -45,20 +49,53 @@ var CODEX_CARD = {
|
|
|
45
49
|
runtimeModeSwitch: "next-turn",
|
|
46
50
|
// 三字段组合在 openThread 时定,切档要重开会话(与 runtimeModeSwitch 同为 next-turn)。
|
|
47
51
|
nativeModeSwitch: true,
|
|
48
|
-
contextCompaction: "native"
|
|
52
|
+
contextCompaction: "native",
|
|
53
|
+
// 子代理经 collab 通知报成 task.*(events.ts 的 mapCollabAgentEvent)。
|
|
54
|
+
reportsSpawnedAgents: true
|
|
49
55
|
},
|
|
50
56
|
credentialSources: [],
|
|
51
57
|
diagnostics: [],
|
|
58
|
+
// 设置表单照这份画:带 x-ycodium-form 的属性才进表单(enabled、customModels 等另有入口,不在此列),
|
|
59
|
+
// 顺序看 x-ycodium-form-order;文案键在本插件 locales/<locale>.json 里解析,宿主不兜底。
|
|
52
60
|
configSchema: {
|
|
53
61
|
type: "object",
|
|
62
|
+
"x-ycodium-form-order": ["binaryPath", "homePath", "shadowHomePath", "launchArgs"],
|
|
54
63
|
properties: {
|
|
55
64
|
authMode: { type: "string", enum: ["default", "chatgpt", "apiKey"] },
|
|
56
65
|
apiBaseUrl: { type: "string" },
|
|
57
66
|
enabled: { type: "boolean" },
|
|
58
|
-
binaryPath: {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
+
},
|
|
62
99
|
customModels: { type: "array" },
|
|
63
100
|
driver: { type: "string" },
|
|
64
101
|
config: { type: "object" },
|
|
@@ -72,12 +109,12 @@ var CODEX_CARD = {
|
|
|
72
109
|
nativePathSuffix: "/responses",
|
|
73
110
|
credentialEnvKey: "CODEX_API_KEY",
|
|
74
111
|
supportsNativeSubscription: true,
|
|
75
|
-
// codex
|
|
76
|
-
//
|
|
77
|
-
//
|
|
112
|
+
// codex 不认「地址一个变量」:地址写在 `-c model_providers.<id>.base_url` 上,
|
|
113
|
+
// 凭据只能经环境变量、变量名还得告诉它。这只手的「API URL and key」账户连接
|
|
114
|
+
// (`connection.ts`)就按这个形状拼;这一栏只是说明,宿主不读。
|
|
78
115
|
//
|
|
79
|
-
// `forced_login_method=api` 要写在最后:chatgpt
|
|
80
|
-
// `forced_login_method=chatgpt
|
|
116
|
+
// `forced_login_method=api` 要写在最后:chatgpt 实例自己先加了
|
|
117
|
+
// `forced_login_method=chatgpt`,不盖掉的话手仍走订阅登录,填的地址白填。
|
|
81
118
|
extraLaunchArgs: [
|
|
82
119
|
{ key: "model_provider", value: "$providerId" },
|
|
83
120
|
{ key: "model_providers.$providerId.name", value: "Ycodium API" },
|
|
@@ -90,13 +127,196 @@ var CODEX_CARD = {
|
|
|
90
127
|
}
|
|
91
128
|
};
|
|
92
129
|
|
|
93
|
-
// src/codexInstance.ts
|
|
94
|
-
import * as NodeProcess from "node:process";
|
|
95
|
-
import { ExecutorSpawnRefusedError } from "@ycodium-ai/plugin-api/executor";
|
|
96
|
-
|
|
97
130
|
// src/codexAccount.ts
|
|
98
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";
|
|
99
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
|
|
100
320
|
var AUTH_FILE_NAME = "auth.json";
|
|
101
321
|
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
102
322
|
var readString = (record, key) => {
|
|
@@ -137,14 +357,16 @@ var signedOutProbe = (exists) => ({
|
|
|
137
357
|
directoryExists: exists,
|
|
138
358
|
email: void 0,
|
|
139
359
|
name: void 0,
|
|
140
|
-
expiresAt: void 0
|
|
360
|
+
expiresAt: void 0,
|
|
361
|
+
authType: void 0
|
|
141
362
|
});
|
|
142
363
|
var unreadableProbe = (exists) => ({
|
|
143
364
|
state: "unreadable",
|
|
144
365
|
directoryExists: exists,
|
|
145
366
|
email: void 0,
|
|
146
367
|
name: void 0,
|
|
147
|
-
expiresAt: void 0
|
|
368
|
+
expiresAt: void 0,
|
|
369
|
+
authType: void 0
|
|
148
370
|
});
|
|
149
371
|
var epochMillisToIso = (ms) => {
|
|
150
372
|
if (ms === void 0) return void 0;
|
|
@@ -164,7 +386,7 @@ function decodeJwtClaims(token) {
|
|
|
164
386
|
}
|
|
165
387
|
var readCodexAccount = (homePath) => {
|
|
166
388
|
const exists = directoryExists(homePath);
|
|
167
|
-
const raw = readCredentialFile(
|
|
389
|
+
const raw = readCredentialFile(NodePath3.join(homePath, AUTH_FILE_NAME));
|
|
168
390
|
if (raw === void 0) return signedOutProbe(exists);
|
|
169
391
|
const parsed = parseJsonRecord(raw);
|
|
170
392
|
if (parsed === void 0 || !("OPENAI_API_KEY" in parsed || "tokens" in parsed)) {
|
|
@@ -185,10 +407,26 @@ var readCodexAccount = (homePath) => {
|
|
|
185
407
|
directoryExists: exists,
|
|
186
408
|
email: claims === void 0 ? void 0 : readString(claims, "email"),
|
|
187
409
|
name: void 0,
|
|
188
|
-
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 }
|
|
189
423
|
};
|
|
190
424
|
};
|
|
191
425
|
|
|
426
|
+
// src/codexInstance.ts
|
|
427
|
+
import * as NodeProcess from "node:process";
|
|
428
|
+
import { ExecutorSpawnRefusedError } from "@ycodium-ai/plugin-api/executor";
|
|
429
|
+
|
|
192
430
|
// src/config.ts
|
|
193
431
|
import { modelSelectionStringOption } from "@ycodium-ai/plugin-api/executor";
|
|
194
432
|
var CODEX_INSTANCE_API_KEY_ENV = "YCODIUM_CODEX_API_KEY";
|
|
@@ -349,12 +587,6 @@ function codexMcpLaunchConfig(servers) {
|
|
|
349
587
|
}
|
|
350
588
|
return { args, environment };
|
|
351
589
|
}
|
|
352
|
-
function codexOverrideArgs(overrides) {
|
|
353
|
-
return overrides.flatMap(({ key, value }) => ["-c", `${key}=${JSON.stringify(value)}`]);
|
|
354
|
-
}
|
|
355
|
-
function encodeCodexConfigOverrides(overrides) {
|
|
356
|
-
return overrides.map(({ key, value }) => `-c ${JSON.stringify(`${key}=${JSON.stringify(value)}`)}`).join(" ");
|
|
357
|
-
}
|
|
358
590
|
function modelIdFromSelection(selection) {
|
|
359
591
|
if (typeof selection !== "object" || selection === null || Array.isArray(selection)) {
|
|
360
592
|
return void 0;
|
|
@@ -364,7 +596,7 @@ function modelIdFromSelection(selection) {
|
|
|
364
596
|
}
|
|
365
597
|
|
|
366
598
|
// src/connection.ts
|
|
367
|
-
import { mergeLaunchEnv
|
|
599
|
+
import { mergeLaunchEnv } from "@ycodium-ai/plugin-api/executor";
|
|
368
600
|
function codexApiConnectionError(recipe, environment) {
|
|
369
601
|
if (recipe.authMode !== "apiKey") return null;
|
|
370
602
|
try {
|
|
@@ -404,16 +636,7 @@ function connectionArgv(recipe, layout) {
|
|
|
404
636
|
}
|
|
405
637
|
return args;
|
|
406
638
|
}
|
|
407
|
-
function codexSpawnArgs(recipe, layout
|
|
408
|
-
if (nativeSubscription) {
|
|
409
|
-
return [
|
|
410
|
-
...tokenizeLaunchArgs(recipe.launchArgs),
|
|
411
|
-
"-c",
|
|
412
|
-
`model_provider=${JSON.stringify("openai")}`,
|
|
413
|
-
"-c",
|
|
414
|
-
`forced_login_method=${JSON.stringify("chatgpt")}`
|
|
415
|
-
];
|
|
416
|
-
}
|
|
639
|
+
function codexSpawnArgs(recipe, layout) {
|
|
417
640
|
const needsOverrides = layout.mode === "authOverlay" || recipe.authMode !== "default";
|
|
418
641
|
if (!needsOverrides) return tokenizeLaunchArgs(recipe.launchArgs);
|
|
419
642
|
return connectionArgv(recipe, layout);
|
|
@@ -425,7 +648,6 @@ var CRUSHED_API_KEYS = {
|
|
|
425
648
|
function buildCodexSpawnEnv(input) {
|
|
426
649
|
const { HOME: _instanceHome, ...instanceTable } = input.recipe.environment;
|
|
427
650
|
return mergeLaunchEnv({
|
|
428
|
-
brainSlots: brainSlotEnvKeys(CODEX_CARD),
|
|
429
651
|
accountHome: input.layout.effectiveHomePath !== void 0 ? { CODEX_HOME: input.layout.effectiveHomePath } : {},
|
|
430
652
|
instanceTable,
|
|
431
653
|
handOverrides: input.crushApiKeys ? CRUSHED_API_KEYS : {},
|
|
@@ -434,7 +656,6 @@ function buildCodexSpawnEnv(input) {
|
|
|
434
656
|
}
|
|
435
657
|
function nativeLoginSpawnEnv(processEnv, isolatedHome) {
|
|
436
658
|
return mergeLaunchEnv({
|
|
437
|
-
brainSlots: brainSlotEnvKeys(CODEX_CARD),
|
|
438
659
|
inherited: processEnv,
|
|
439
660
|
handOverrides: { ...CRUSHED_API_KEYS, CODEX_HOME: isolatedHome }
|
|
440
661
|
});
|
|
@@ -443,191 +664,6 @@ function shouldCrushApiKeys(authMode) {
|
|
|
443
664
|
return authMode === "chatgpt";
|
|
444
665
|
}
|
|
445
666
|
|
|
446
|
-
// src/homeLayout.ts
|
|
447
|
-
import * as NodeFSP from "node:fs/promises";
|
|
448
|
-
import * as NodeOS2 from "node:os";
|
|
449
|
-
import * as NodePath3 from "node:path";
|
|
450
|
-
|
|
451
|
-
// src/paths.ts
|
|
452
|
-
import * as NodeOS from "node:os";
|
|
453
|
-
import * as NodePath2 from "node:path";
|
|
454
|
-
function expandHomePath(value) {
|
|
455
|
-
if (value === "~") return NodeOS.homedir();
|
|
456
|
-
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
457
|
-
return NodePath2.join(NodeOS.homedir(), value.slice(2));
|
|
458
|
-
}
|
|
459
|
-
return value;
|
|
460
|
-
}
|
|
461
|
-
function nowIso() {
|
|
462
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// src/homeLayout.ts
|
|
466
|
-
var KNOWN_SHARED_DIRECTORIES = [
|
|
467
|
-
"sessions",
|
|
468
|
-
"archived_sessions",
|
|
469
|
-
"sqlite",
|
|
470
|
-
"shell_snapshots",
|
|
471
|
-
"worktrees",
|
|
472
|
-
"skills",
|
|
473
|
-
"plugins",
|
|
474
|
-
"cache",
|
|
475
|
-
"logs",
|
|
476
|
-
"mcp-oauth-locks"
|
|
477
|
-
];
|
|
478
|
-
var PRIVATE_ENTRY_NAMES = /* @__PURE__ */ new Set(["auth.json", "models_cache.json"]);
|
|
479
|
-
var SHADOW_LOCAL_ENTRY_NAMES = /* @__PURE__ */ new Set(["log", "memories", "tmp"]);
|
|
480
|
-
var REPLACEABLE_SHARED_RUNTIME_DIRECTORIES = /* @__PURE__ */ new Set(["mcp-oauth-locks"]);
|
|
481
|
-
function resolveCodexHomeDirectory(value) {
|
|
482
|
-
const expanded = value && value.trim().length > 0 ? expandHomePath(value) : NodePath3.join(NodeOS2.homedir(), ".codex");
|
|
483
|
-
return NodePath3.resolve(expanded);
|
|
484
|
-
}
|
|
485
|
-
function resolveCodexHomeLayout(input) {
|
|
486
|
-
const configuredHome = input.homePath.trim() || (input.environmentHome?.trim() ?? "");
|
|
487
|
-
const sharedHomePath = resolveCodexHomeDirectory(configuredHome || void 0);
|
|
488
|
-
const shadowHomePath = input.shadowHomePath.trim();
|
|
489
|
-
if (shadowHomePath.length === 0) {
|
|
490
|
-
return {
|
|
491
|
-
mode: "direct",
|
|
492
|
-
sharedHomePath,
|
|
493
|
-
effectiveHomePath: configuredHome.length > 0 ? sharedHomePath : void 0,
|
|
494
|
-
continuationKey: `codex:home:${sharedHomePath}`
|
|
495
|
-
};
|
|
496
|
-
}
|
|
497
|
-
return {
|
|
498
|
-
mode: "authOverlay",
|
|
499
|
-
sharedHomePath,
|
|
500
|
-
effectiveHomePath: NodePath3.resolve(expandHomePath(shadowHomePath)),
|
|
501
|
-
continuationKey: `codex:home:${sharedHomePath}`
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
function codexContinuationKey(input) {
|
|
505
|
-
return resolveCodexHomeLayout({
|
|
506
|
-
homePath: input.homePath,
|
|
507
|
-
shadowHomePath: "",
|
|
508
|
-
environmentHome: input.instanceEnvironment["CODEX_HOME"] ?? input.inheritedEnvironment["CODEX_HOME"]
|
|
509
|
-
}).continuationKey;
|
|
510
|
-
}
|
|
511
|
-
async function readLinkState(linkPath) {
|
|
512
|
-
try {
|
|
513
|
-
const target = await NodeFSP.readlink(linkPath);
|
|
514
|
-
return { _tag: "Symlink", target };
|
|
515
|
-
} catch (error) {
|
|
516
|
-
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
517
|
-
if (code === "ENOENT") return { _tag: "Missing" };
|
|
518
|
-
if (code === "EINVAL") return { _tag: "NotSymlink" };
|
|
519
|
-
throw error;
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
async function createEntryLink(target, link) {
|
|
523
|
-
const type = await NodeFSP.stat(target).then(
|
|
524
|
-
(info) => info.isDirectory() ? "junction" : "file",
|
|
525
|
-
() => "file"
|
|
526
|
-
);
|
|
527
|
-
await NodeFSP.symlink(target, link, type);
|
|
528
|
-
}
|
|
529
|
-
async function removePrivateSymlink(effectiveHomePath, entryName) {
|
|
530
|
-
const privatePath = NodePath3.join(effectiveHomePath, entryName);
|
|
531
|
-
const state = await readLinkState(privatePath);
|
|
532
|
-
if (state._tag === "Symlink") await NodeFSP.rm(privatePath, { force: true });
|
|
533
|
-
}
|
|
534
|
-
async function ensureSymlink(input) {
|
|
535
|
-
const target = NodePath3.join(input.sharedHomePath, input.entryName);
|
|
536
|
-
const link = NodePath3.join(input.effectiveHomePath, input.entryName);
|
|
537
|
-
const state = await readLinkState(link);
|
|
538
|
-
if (state._tag === "NotSymlink") {
|
|
539
|
-
if (!REPLACEABLE_SHARED_RUNTIME_DIRECTORIES.has(input.entryName)) {
|
|
540
|
-
throw new Error(
|
|
541
|
-
`Cannot create Codex shadow home entry '${input.entryName}' because '${link}' already exists and is not a symlink.`
|
|
542
|
-
);
|
|
543
|
-
}
|
|
544
|
-
await NodeFSP.rm(link, { recursive: true, force: true });
|
|
545
|
-
await createEntryLink(target, link);
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
if (state._tag === "Missing") {
|
|
549
|
-
await createEntryLink(target, link);
|
|
550
|
-
return;
|
|
551
|
-
}
|
|
552
|
-
const resolvedExisting = NodePath3.resolve(NodePath3.dirname(link), state.target);
|
|
553
|
-
if (resolvedExisting !== target) {
|
|
554
|
-
await NodeFSP.rm(link, { force: true });
|
|
555
|
-
await createEntryLink(target, link);
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
async function makeDirectory(directoryPath) {
|
|
559
|
-
try {
|
|
560
|
-
await NodeFSP.mkdir(directoryPath, { recursive: true });
|
|
561
|
-
} catch (error) {
|
|
562
|
-
throw new Error(
|
|
563
|
-
`Codex shadow home filesystem operation 'makeDirectory' failed for '${directoryPath}': ${String(error)}`,
|
|
564
|
-
{ cause: error }
|
|
565
|
-
);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
async function materializeCodexShadowHome(layout, log) {
|
|
569
|
-
if (layout.mode !== "authOverlay") return;
|
|
570
|
-
const effectiveHomePath = layout.effectiveHomePath;
|
|
571
|
-
if (!effectiveHomePath) return;
|
|
572
|
-
if (layout.sharedHomePath === effectiveHomePath) {
|
|
573
|
-
throw new Error(
|
|
574
|
-
`Codex shadow home path '${effectiveHomePath}' must be different from the shared home path '${layout.sharedHomePath}'.`
|
|
575
|
-
);
|
|
576
|
-
}
|
|
577
|
-
await makeDirectory(layout.sharedHomePath);
|
|
578
|
-
await makeDirectory(effectiveHomePath);
|
|
579
|
-
await Promise.all(
|
|
580
|
-
KNOWN_SHARED_DIRECTORIES.map(
|
|
581
|
-
(directory) => makeDirectory(NodePath3.join(layout.sharedHomePath, directory))
|
|
582
|
-
)
|
|
583
|
-
);
|
|
584
|
-
let sharedEntryNames = [];
|
|
585
|
-
try {
|
|
586
|
-
sharedEntryNames = await NodeFSP.readdir(layout.sharedHomePath);
|
|
587
|
-
} catch (error) {
|
|
588
|
-
throw new Error(
|
|
589
|
-
`Codex shadow home filesystem operation 'readDirectory' failed for '${layout.sharedHomePath}': ${String(error)}`,
|
|
590
|
-
{ cause: error }
|
|
591
|
-
);
|
|
592
|
-
}
|
|
593
|
-
const entries = new Set(KNOWN_SHARED_DIRECTORIES);
|
|
594
|
-
for (const entryName of sharedEntryNames) {
|
|
595
|
-
if (!PRIVATE_ENTRY_NAMES.has(entryName) && !SHADOW_LOCAL_ENTRY_NAMES.has(entryName)) {
|
|
596
|
-
entries.add(entryName);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
for (const entryName of PRIVATE_ENTRY_NAMES) {
|
|
600
|
-
if (entryName === "auth.json") continue;
|
|
601
|
-
await removePrivateSymlink(effectiveHomePath, entryName);
|
|
602
|
-
}
|
|
603
|
-
for (const entryName of entries) {
|
|
604
|
-
if (PRIVATE_ENTRY_NAMES.has(entryName)) continue;
|
|
605
|
-
try {
|
|
606
|
-
await ensureSymlink({
|
|
607
|
-
sharedHomePath: layout.sharedHomePath,
|
|
608
|
-
effectiveHomePath,
|
|
609
|
-
entryName
|
|
610
|
-
});
|
|
611
|
-
} catch (error) {
|
|
612
|
-
log?.warn("Codex shadow home entry left unlinked.", {
|
|
613
|
-
entryName,
|
|
614
|
-
effectiveHomePath,
|
|
615
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
const authPath = NodePath3.join(effectiveHomePath, "auth.json");
|
|
620
|
-
const authState = await readLinkState(authPath);
|
|
621
|
-
if (authState._tag === "Symlink") {
|
|
622
|
-
throw new Error(
|
|
623
|
-
`Codex shadow home private entry 'auth.json' at '${authPath}' must be a real file, not a symlink.`
|
|
624
|
-
);
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
function accountHomeFromLayout(layout) {
|
|
628
|
-
return layout.effectiveHomePath ?? layout.sharedHomePath;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
667
|
// src/protocol.ts
|
|
632
668
|
var CODEX_INITIALIZE_PARAMS = {
|
|
633
669
|
clientInfo: { name: "ycodium", title: "Ycodium", version: "0" },
|
|
@@ -1057,6 +1093,9 @@ async function createCodexRpcClient(handle, handlers) {
|
|
|
1057
1093
|
};
|
|
1058
1094
|
}
|
|
1059
1095
|
|
|
1096
|
+
// src/sessionRuntime.ts
|
|
1097
|
+
import * as NodeCrypto2 from "node:crypto";
|
|
1098
|
+
|
|
1060
1099
|
// src/approvalGrant.ts
|
|
1061
1100
|
function codexApprovalGrantKey(kind, identity) {
|
|
1062
1101
|
if (kind === "mcp-elicitation") return `codex:mcp:${identity || "unknown"}`;
|
|
@@ -1111,7 +1150,7 @@ You are running inside Ycodium. The \`ycodium\` MCP server is the product-native
|
|
|
1111
1150
|
|
|
1112
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.
|
|
1113
1152
|
|
|
1114
|
-
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).
|
|
1115
1154
|
`;
|
|
1116
1155
|
var YCODIUM_THREAD_TOOL_INSTRUCTIONS = `
|
|
1117
1156
|
|
|
@@ -1279,11 +1318,13 @@ function buildCodexDeveloperInstructions(interactionMode, runtime, browserToolsA
|
|
|
1279
1318
|
}
|
|
1280
1319
|
|
|
1281
1320
|
// src/events.ts
|
|
1321
|
+
import * as NodeCrypto from "node:crypto";
|
|
1282
1322
|
var CODEX_PROVIDER = "codex";
|
|
1323
|
+
var EVENT_ID_PREFIX = `codex-evt-${NodeCrypto.randomUUID()}`;
|
|
1283
1324
|
var eventSeq = 0;
|
|
1284
1325
|
function nextEventId() {
|
|
1285
1326
|
eventSeq += 1;
|
|
1286
|
-
return
|
|
1327
|
+
return `${EVENT_ID_PREFIX}-${eventSeq}`;
|
|
1287
1328
|
}
|
|
1288
1329
|
function trimText(value) {
|
|
1289
1330
|
if (typeof value !== "string") return void 0;
|
|
@@ -1562,7 +1603,8 @@ function mapCollabAgentEvent(input) {
|
|
|
1562
1603
|
role,
|
|
1563
1604
|
...knownName ? { title: knownName } : {},
|
|
1564
1605
|
...agentPath ? { agentPath } : {},
|
|
1565
|
-
timelineBypass: true
|
|
1606
|
+
timelineBypass: true,
|
|
1607
|
+
rosterKind: "subagent"
|
|
1566
1608
|
};
|
|
1567
1609
|
const threadId = input.rootThreadId;
|
|
1568
1610
|
const method = input.method;
|
|
@@ -1639,16 +1681,17 @@ function mapCollabAgentEvent(input) {
|
|
|
1639
1681
|
const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
1640
1682
|
const totalTokens = total ? count(total["totalTokens"]) : void 0;
|
|
1641
1683
|
if (total === void 0 || totalTokens === void 0) return [];
|
|
1642
|
-
const
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1684
|
+
const inputTokens = count(total["inputTokens"]);
|
|
1685
|
+
const cachedInputTokens = count(total["cachedInputTokens"]);
|
|
1686
|
+
const outputTokens = count(total["outputTokens"]);
|
|
1687
|
+
const reasoningOutputTokens = count(total["reasoningOutputTokens"]);
|
|
1688
|
+
const typedUsage = {
|
|
1689
|
+
totalTokens,
|
|
1690
|
+
...inputTokens !== void 0 ? { inputTokens } : {},
|
|
1691
|
+
...cachedInputTokens !== void 0 ? { cachedInputTokens } : {},
|
|
1692
|
+
...outputTokens !== void 0 ? { outputTokens } : {},
|
|
1693
|
+
...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens } : {}
|
|
1694
|
+
};
|
|
1652
1695
|
return [
|
|
1653
1696
|
baseEvent({
|
|
1654
1697
|
type: "task.progress",
|
|
@@ -2316,7 +2359,7 @@ function elicitationOptions(params) {
|
|
|
2316
2359
|
];
|
|
2317
2360
|
}
|
|
2318
2361
|
function mapServerRequestToEvents(input) {
|
|
2319
|
-
const requestId =
|
|
2362
|
+
const requestId = input.requestId;
|
|
2320
2363
|
if (input.method === "item/tool/requestUserInput") {
|
|
2321
2364
|
const questions = toUserInputQuestions(input.params);
|
|
2322
2365
|
if (!questions) return [];
|
|
@@ -2378,6 +2421,9 @@ function mapServerRequestToEvents(input) {
|
|
|
2378
2421
|
}
|
|
2379
2422
|
|
|
2380
2423
|
// src/sessionRuntime.ts
|
|
2424
|
+
function cancelWireFor(pending) {
|
|
2425
|
+
return pending.kind === "mcp-elicitation" ? toMcpElicitationResponse("cancel", pending.params) : { decision: "cancel" };
|
|
2426
|
+
}
|
|
2381
2427
|
var sleep = (ms) => new Promise((resolve2) => {
|
|
2382
2428
|
setTimeout(resolve2, ms);
|
|
2383
2429
|
});
|
|
@@ -2430,8 +2476,18 @@ var CodexSessionRuntime = class {
|
|
|
2430
2476
|
memoryThreadIds = /* @__PURE__ */ new Set();
|
|
2431
2477
|
appServerArgs;
|
|
2432
2478
|
host;
|
|
2479
|
+
attachments;
|
|
2433
2480
|
rpc;
|
|
2434
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
|
+
}
|
|
2435
2491
|
/** 这一轮该用哪套三字段组合:只由这一轮的档决定(见 `codexNativeModeFor`)。 */
|
|
2436
2492
|
#threadConfig() {
|
|
2437
2493
|
return codexThreadConfigForNativeMode(codexNativeModeFor(this.runtimeMode, this.nativeModeId));
|
|
@@ -2451,7 +2507,8 @@ var CodexSessionRuntime = class {
|
|
|
2451
2507
|
error: error instanceof Error ? error.message : String(error)
|
|
2452
2508
|
});
|
|
2453
2509
|
}
|
|
2454
|
-
constructor(host, rpc, emit, input, cwd, appServerArgs, grants, declineRelay) {
|
|
2510
|
+
constructor(host, rpc, emit, input, cwd, appServerArgs, grants, declineRelay, attachments) {
|
|
2511
|
+
this.attachments = attachments;
|
|
2455
2512
|
this.host = host;
|
|
2456
2513
|
this.rpc = rpc;
|
|
2457
2514
|
this.emit = emit;
|
|
@@ -2510,7 +2567,7 @@ var CodexSessionRuntime = class {
|
|
|
2510
2567
|
if (kind === "drop") return;
|
|
2511
2568
|
if (isFatalStderr(line)) {
|
|
2512
2569
|
this.emit({
|
|
2513
|
-
eventId:
|
|
2570
|
+
eventId: nextEventId(),
|
|
2514
2571
|
provider: CODEX_DRIVER_KIND,
|
|
2515
2572
|
threadId: this.threadId,
|
|
2516
2573
|
createdAt: nowIso(),
|
|
@@ -2553,21 +2610,24 @@ var CodexSessionRuntime = class {
|
|
|
2553
2610
|
}
|
|
2554
2611
|
dispatchMapped(method, params) {
|
|
2555
2612
|
if (method === "serverRequest/resolved") {
|
|
2556
|
-
const
|
|
2557
|
-
if (
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
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
|
+
});
|
|
2571
2631
|
return;
|
|
2572
2632
|
}
|
|
2573
2633
|
for (const event of mapNotificationToEvents({
|
|
@@ -2746,9 +2806,9 @@ var CodexSessionRuntime = class {
|
|
|
2746
2806
|
return;
|
|
2747
2807
|
}
|
|
2748
2808
|
if (method === "item/tool/requestUserInput") {
|
|
2749
|
-
const requestId =
|
|
2809
|
+
const requestId = this.requestIdFor(id);
|
|
2750
2810
|
const events = mapServerRequestToEvents({
|
|
2751
|
-
|
|
2811
|
+
requestId,
|
|
2752
2812
|
method,
|
|
2753
2813
|
params,
|
|
2754
2814
|
threadId: this.threadId,
|
|
@@ -2779,9 +2839,9 @@ var CodexSessionRuntime = class {
|
|
|
2779
2839
|
await this.rpc.respond(id, { decision });
|
|
2780
2840
|
return;
|
|
2781
2841
|
}
|
|
2782
|
-
const requestId =
|
|
2842
|
+
const requestId = this.requestIdFor(id);
|
|
2783
2843
|
const events = mapServerRequestToEvents({
|
|
2784
|
-
|
|
2844
|
+
requestId,
|
|
2785
2845
|
method,
|
|
2786
2846
|
params,
|
|
2787
2847
|
threadId: this.threadId,
|
|
@@ -2798,6 +2858,10 @@ var CodexSessionRuntime = class {
|
|
|
2798
2858
|
settle: (decision) => {
|
|
2799
2859
|
this.pendingApprovals.delete(requestId);
|
|
2800
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();
|
|
2801
2865
|
}
|
|
2802
2866
|
});
|
|
2803
2867
|
for (const event of events) this.emit(event);
|
|
@@ -2805,7 +2869,7 @@ var CodexSessionRuntime = class {
|
|
|
2805
2869
|
}
|
|
2806
2870
|
async respondToRequest(requestId, decision, reason, persist) {
|
|
2807
2871
|
const pending = this.pendingApprovals.get(requestId);
|
|
2808
|
-
if (!pending) throw new Error(`
|
|
2872
|
+
if (!pending) throw new Error(`Unknown pending Codex approval request: ${requestId}`);
|
|
2809
2873
|
if (decision === "deny" && reason) this.declineRelay.queue(this.threadId, reason);
|
|
2810
2874
|
let wire = { decision: decision === "allow" ? "accept" : "decline" };
|
|
2811
2875
|
if (decision === "allow" && pending.kind === "mcp-elicitation") {
|
|
@@ -2821,7 +2885,7 @@ var CodexSessionRuntime = class {
|
|
|
2821
2885
|
pending.settle(wire);
|
|
2822
2886
|
const delivery = await ack.catch(() => "closed");
|
|
2823
2887
|
this.emit({
|
|
2824
|
-
eventId:
|
|
2888
|
+
eventId: nextEventId(),
|
|
2825
2889
|
provider: CODEX_DRIVER_KIND,
|
|
2826
2890
|
threadId: this.threadId,
|
|
2827
2891
|
createdAt: nowIso(),
|
|
@@ -2838,7 +2902,7 @@ var CodexSessionRuntime = class {
|
|
|
2838
2902
|
}
|
|
2839
2903
|
async respondToUserInput(requestId, answers) {
|
|
2840
2904
|
const pending = this.pendingUserInputs.get(requestId);
|
|
2841
|
-
if (!pending) throw new Error(`
|
|
2905
|
+
if (!pending) throw new Error(`Unknown pending Codex user input request: ${requestId}`);
|
|
2842
2906
|
const mapped = toCodexUserInputAnswers(answers);
|
|
2843
2907
|
if (mapped === void 0) {
|
|
2844
2908
|
throw new Error(`invalid user input answers for request ${requestId}`);
|
|
@@ -2847,7 +2911,7 @@ var CodexSessionRuntime = class {
|
|
|
2847
2911
|
pending.settle({ answers: mapped });
|
|
2848
2912
|
const delivery = await ack.catch(() => "closed");
|
|
2849
2913
|
this.emit({
|
|
2850
|
-
eventId:
|
|
2914
|
+
eventId: nextEventId(),
|
|
2851
2915
|
provider: CODEX_DRIVER_KIND,
|
|
2852
2916
|
threadId: this.threadId,
|
|
2853
2917
|
createdAt: nowIso(),
|
|
@@ -2899,7 +2963,7 @@ var CodexSessionRuntime = class {
|
|
|
2899
2963
|
}
|
|
2900
2964
|
async openThread(input) {
|
|
2901
2965
|
const resume = input.discardResumeCursor === true ? void 0 : parseCodexResumeCursor(input.resumeCursor);
|
|
2902
|
-
const requestedModel =
|
|
2966
|
+
const requestedModel = modelIdFromSelection(input.modelSelection);
|
|
2903
2967
|
const startParams = {
|
|
2904
2968
|
...this.threadConfigParams(),
|
|
2905
2969
|
...requestedModel ? { model: requestedModel } : {}
|
|
@@ -2935,7 +2999,7 @@ var CodexSessionRuntime = class {
|
|
|
2935
2999
|
...this.model ? { model: this.model } : {}
|
|
2936
3000
|
};
|
|
2937
3001
|
this.emit({
|
|
2938
|
-
eventId:
|
|
3002
|
+
eventId: nextEventId(),
|
|
2939
3003
|
provider: CODEX_DRIVER_KIND,
|
|
2940
3004
|
threadId: this.threadId,
|
|
2941
3005
|
createdAt: nowIso(),
|
|
@@ -2953,24 +3017,45 @@ var CodexSessionRuntime = class {
|
|
|
2953
3017
|
const effort = modelSelectionStringOption(input.modelSelection, "reasoningEffort") ?? DEFAULT_TEXT_GENERATION_REASONING_EFFORT;
|
|
2954
3018
|
return buildCodexDeveloperInstructions(mode, { model, reasoningEffort: effort }, tools, tools);
|
|
2955
3019
|
}
|
|
2956
|
-
turnInput(input) {
|
|
3020
|
+
async turnInput(input) {
|
|
3021
|
+
const images = await this.#imageInputs(input.attachments);
|
|
2957
3022
|
const items = [];
|
|
2958
3023
|
const declined = this.declineRelay.take(this.threadId);
|
|
2959
3024
|
const text = [declined, input.input ?? ""].filter((part) => part && part.length > 0).join("\n\n");
|
|
2960
3025
|
if (text.length > 0) items.push({ type: "text", text });
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
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");
|
|
2966
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
|
+
});
|
|
2967
3047
|
}
|
|
2968
3048
|
return items;
|
|
2969
3049
|
}
|
|
2970
3050
|
async sendTurn(input) {
|
|
2971
3051
|
if (!this.nativeThreadId) throw new Error("codex session has no native thread");
|
|
2972
3052
|
if (hasConfiguredMcpServer(this.appServerArgs)) {
|
|
2973
|
-
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
|
+
});
|
|
2974
3059
|
}
|
|
2975
3060
|
const sandboxPolicy = this.#turnSandboxPolicy();
|
|
2976
3061
|
const config = this.#threadConfig();
|
|
@@ -2979,7 +3064,7 @@ var CodexSessionRuntime = class {
|
|
|
2979
3064
|
const serviceTier = modelSelectionStringOption(input.modelSelection, "serviceTier");
|
|
2980
3065
|
const params = {
|
|
2981
3066
|
threadId: this.nativeThreadId,
|
|
2982
|
-
input: this.turnInput(input),
|
|
3067
|
+
input: await this.turnInput(input),
|
|
2983
3068
|
approvalPolicy: config.approvalPolicy,
|
|
2984
3069
|
approvalsReviewer: config.approvalsReviewer,
|
|
2985
3070
|
sandboxPolicy,
|
|
@@ -3039,24 +3124,29 @@ var CodexSessionRuntime = class {
|
|
|
3039
3124
|
);
|
|
3040
3125
|
if (!this.nativeThreadId) return;
|
|
3041
3126
|
const target = turnId ?? this.activeTurnId;
|
|
3042
|
-
if (
|
|
3043
|
-
|
|
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
|
+
}
|
|
3044
3146
|
}
|
|
3045
3147
|
async compactContext() {
|
|
3046
3148
|
if (!this.nativeThreadId) return;
|
|
3047
|
-
await this.rpc.request("thread/compact", { threadId: this.nativeThreadId }, 0)
|
|
3048
|
-
}
|
|
3049
|
-
async uploadFeedback(reason) {
|
|
3050
|
-
const result = await this.rpc.request(
|
|
3051
|
-
"feedback/upload",
|
|
3052
|
-
{
|
|
3053
|
-
threadId: this.nativeThreadId,
|
|
3054
|
-
...reason ? { reason } : {}
|
|
3055
|
-
},
|
|
3056
|
-
0
|
|
3057
|
-
);
|
|
3058
|
-
const id = (isRecord3(result) ? asString(result["feedbackId"]) : null) ?? `codex-feedback-${Date.now()}`;
|
|
3059
|
-
return { feedbackId: id };
|
|
3149
|
+
await this.rpc.request("thread/compact/start", { threadId: this.nativeThreadId }, 0);
|
|
3060
3150
|
}
|
|
3061
3151
|
async readThread() {
|
|
3062
3152
|
if (!this.nativeThreadId) return { threadId: this.threadId, turns: this.turns };
|
|
@@ -3111,7 +3201,7 @@ var CodexSessionRuntime = class {
|
|
|
3111
3201
|
}
|
|
3112
3202
|
this.closeCompleted = true;
|
|
3113
3203
|
this.emit({
|
|
3114
|
-
eventId:
|
|
3204
|
+
eventId: nextEventId(),
|
|
3115
3205
|
provider: CODEX_DRIVER_KIND,
|
|
3116
3206
|
threadId: this.threadId,
|
|
3117
3207
|
createdAt: nowIso(),
|
|
@@ -3466,10 +3556,6 @@ function serviceTierDescriptor(row) {
|
|
|
3466
3556
|
currentValue: defaultTier
|
|
3467
3557
|
};
|
|
3468
3558
|
}
|
|
3469
|
-
function isSupersededModel(row) {
|
|
3470
|
-
if (isRecord3(row["upgradeInfo"])) return true;
|
|
3471
|
-
return (asString(row["upgrade"]) ?? "").length > 0;
|
|
3472
|
-
}
|
|
3473
3559
|
function codexProbeModel(row) {
|
|
3474
3560
|
if (!isRecord3(row)) return null;
|
|
3475
3561
|
const slug = asString(row["model"]);
|
|
@@ -3482,7 +3568,6 @@ function codexProbeModel(row) {
|
|
|
3482
3568
|
slug,
|
|
3483
3569
|
...displayName ? { name: codexModelDisplayName(displayName) } : {},
|
|
3484
3570
|
...row["isDefault"] === true ? { isDefault: true } : {},
|
|
3485
|
-
...isSupersededModel(row) ? { isLegacy: true } : {},
|
|
3486
3571
|
...row["hidden"] === true ? { hidden: true } : {},
|
|
3487
3572
|
// 空表也给 `capabilities`:`{}` 与 `null` 对宿主是一回事(都不画那一格),
|
|
3488
3573
|
// 但给对象保住「这只手确实答过这一问」这层意思。
|
|
@@ -3515,6 +3600,47 @@ async function listCodexModels(rpc) {
|
|
|
3515
3600
|
return models;
|
|
3516
3601
|
}
|
|
3517
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
|
+
|
|
3518
3644
|
// src/signIn.ts
|
|
3519
3645
|
var DEVICE_CODE_URL = /https?:\/\/\S+/;
|
|
3520
3646
|
var DEVICE_CODE_VALUE = /\b([A-Z0-9]{4,}(?:-[A-Z0-9]{4,})+)\b/;
|
|
@@ -3704,8 +3830,7 @@ import * as NodeFSP2 from "node:fs/promises";
|
|
|
3704
3830
|
import * as NodeOS3 from "node:os";
|
|
3705
3831
|
import * as NodePath5 from "node:path";
|
|
3706
3832
|
import {
|
|
3707
|
-
mergeLaunchEnv as mergeLaunchEnv2
|
|
3708
|
-
brainSlotEnvKeys as brainSlotEnvKeys2
|
|
3833
|
+
mergeLaunchEnv as mergeLaunchEnv2
|
|
3709
3834
|
} from "@ycodium-ai/plugin-api/executor";
|
|
3710
3835
|
var CODEX_TIMEOUT_MS = 18e4;
|
|
3711
3836
|
var EARLIER_CONTENT_TRUNCATION_MARKER = "[Earlier content truncated]\n\n";
|
|
@@ -3852,7 +3977,7 @@ async function collectStream(stream) {
|
|
|
3852
3977
|
return out;
|
|
3853
3978
|
}
|
|
3854
3979
|
function createCodexTextGeneration(input) {
|
|
3855
|
-
const run = async (operation, cwd, prompt, schema, modelSelection,
|
|
3980
|
+
const run = async (operation, cwd, prompt, schema, modelSelection, imagePaths, signal) => {
|
|
3856
3981
|
if (input.recipe.binaryPath.length === 0) {
|
|
3857
3982
|
throw new Error("Codex CLI is required but not available on PATH.");
|
|
3858
3983
|
}
|
|
@@ -3861,33 +3986,16 @@ function createCodexTextGeneration(input) {
|
|
|
3861
3986
|
const outputPath = NodePath5.join(tmp, "output.json");
|
|
3862
3987
|
await NodeFSP2.writeFile(schemaPath, JSON.stringify(schema));
|
|
3863
3988
|
await NodeFSP2.writeFile(outputPath, "");
|
|
3864
|
-
const launchRecord = isRecord3(launch) ? launch : {};
|
|
3865
|
-
const processEnv = isRecord3(launchRecord.processEnv) ? Object.fromEntries(
|
|
3866
|
-
Object.entries(launchRecord.processEnv).filter(
|
|
3867
|
-
(entry) => typeof entry[1] === "string"
|
|
3868
|
-
)
|
|
3869
|
-
) : {};
|
|
3870
|
-
const overlay = Array.isArray(launchRecord.launchArgOverrides) && launchRecord.launchArgOverrides.length > 0 ? encodeCodexConfigOverrides(
|
|
3871
|
-
launchRecord.launchArgOverrides.flatMap((entry) => {
|
|
3872
|
-
if (!isRecord3(entry) || typeof entry.key !== "string") return [];
|
|
3873
|
-
const value = entry.value;
|
|
3874
|
-
if (typeof value !== "string" && typeof value !== "boolean") return [];
|
|
3875
|
-
return [{ key: entry.key, value }];
|
|
3876
|
-
})
|
|
3877
|
-
) : "";
|
|
3878
|
-
const launchArgs = [input.recipe.launchArgs, overlay].filter((value) => value.length > 0).join(" ");
|
|
3879
3989
|
const reasoningEffort = modelSelectionStringOption(modelSelection, "reasoningEffort") ?? DEFAULT_TEXT_GENERATION_REASONING_EFFORT;
|
|
3880
3990
|
const serviceTier = modelSelectionStringOption(modelSelection, "serviceTier");
|
|
3881
|
-
const model =
|
|
3991
|
+
const model = modelIdFromSelection(modelSelection) ?? "gpt-5.6-luna";
|
|
3882
3992
|
const env = mergeLaunchEnv2({
|
|
3883
|
-
brainSlots: brainSlotEnvKeys2(CODEX_CARD),
|
|
3884
3993
|
accountHome: input.layout.effectiveHomePath ? { CODEX_HOME: input.layout.effectiveHomePath } : input.recipe.homePath.trim() ? { CODEX_HOME: expandHomePath(input.recipe.homePath) } : {},
|
|
3885
|
-
instanceTable: input.recipe.environment
|
|
3886
|
-
launch: processEnv
|
|
3994
|
+
instanceTable: input.recipe.environment
|
|
3887
3995
|
});
|
|
3888
3996
|
const args = [
|
|
3889
3997
|
"exec",
|
|
3890
|
-
...codexExecLaunchArgs(launchArgs),
|
|
3998
|
+
...codexExecLaunchArgs(input.recipe.launchArgs),
|
|
3891
3999
|
"--ephemeral",
|
|
3892
4000
|
"--skip-git-repo-check",
|
|
3893
4001
|
"-s",
|
|
@@ -3985,7 +4093,6 @@ function createCodexTextGeneration(input) {
|
|
|
3985
4093
|
prompt,
|
|
3986
4094
|
includeBranch ? COMMIT_WITH_BRANCH_SCHEMA : COMMIT_SCHEMA,
|
|
3987
4095
|
inputRecord.modelSelection,
|
|
3988
|
-
inputRecord.launch,
|
|
3989
4096
|
[],
|
|
3990
4097
|
signal
|
|
3991
4098
|
);
|
|
@@ -4036,7 +4143,6 @@ function createCodexTextGeneration(input) {
|
|
|
4036
4143
|
prompt,
|
|
4037
4144
|
PR_SCHEMA,
|
|
4038
4145
|
inputRecord.modelSelection,
|
|
4039
|
-
inputRecord.launch,
|
|
4040
4146
|
[],
|
|
4041
4147
|
signal
|
|
4042
4148
|
);
|
|
@@ -4076,7 +4182,6 @@ function createCodexTextGeneration(input) {
|
|
|
4076
4182
|
prompt,
|
|
4077
4183
|
BRANCH_SCHEMA,
|
|
4078
4184
|
inputRecord.modelSelection,
|
|
4079
|
-
inputRecord.launch,
|
|
4080
4185
|
absoluteImagePaths(attachments),
|
|
4081
4186
|
signal
|
|
4082
4187
|
);
|
|
@@ -4113,7 +4218,6 @@ ${extra}` : ""}`;
|
|
|
4113
4218
|
prompt,
|
|
4114
4219
|
TITLE_SCHEMA,
|
|
4115
4220
|
inputRecord.modelSelection,
|
|
4116
|
-
inputRecord.launch,
|
|
4117
4221
|
absoluteImagePaths(attachments),
|
|
4118
4222
|
signal
|
|
4119
4223
|
);
|
|
@@ -4142,7 +4246,6 @@ ${extra}` : ""}`;
|
|
|
4142
4246
|
prompt,
|
|
4143
4247
|
HANDOVER_SCHEMA,
|
|
4144
4248
|
inputRecord.modelSelection,
|
|
4145
|
-
inputRecord.launch,
|
|
4146
4249
|
[],
|
|
4147
4250
|
signal
|
|
4148
4251
|
);
|
|
@@ -4201,18 +4304,15 @@ function accountToAuth2(account) {
|
|
|
4201
4304
|
return { status: "unknown" };
|
|
4202
4305
|
}
|
|
4203
4306
|
}
|
|
4204
|
-
function layoutFor(recipe
|
|
4307
|
+
function layoutFor(recipe) {
|
|
4205
4308
|
const environmentHome = recipe.environment["CODEX_HOME"];
|
|
4206
|
-
if (nativeSubscription && launchHome?.trim()) {
|
|
4207
|
-
return resolveCodexHomeLayout({ homePath: launchHome.trim(), shadowHomePath: "" });
|
|
4208
|
-
}
|
|
4209
4309
|
return resolveCodexHomeLayout({
|
|
4210
|
-
homePath:
|
|
4310
|
+
homePath: recipe.homePath,
|
|
4211
4311
|
shadowHomePath: recipe.shadowHomePath,
|
|
4212
4312
|
...environmentHome === void 0 ? {} : { environmentHome }
|
|
4213
4313
|
});
|
|
4214
4314
|
}
|
|
4215
|
-
var createCodexInstance = (config, host) => {
|
|
4315
|
+
var createCodexInstance = (config, host, attachments) => {
|
|
4216
4316
|
const recipe = parseCodexInstanceConfig(config);
|
|
4217
4317
|
const events = new EventBus();
|
|
4218
4318
|
const threads = /* @__PURE__ */ new Map();
|
|
@@ -4252,10 +4352,11 @@ var createCodexInstance = (config, host) => {
|
|
|
4252
4352
|
await rpc.request("initialize", CODEX_INITIALIZE_PARAMS)
|
|
4253
4353
|
);
|
|
4254
4354
|
await rpc.notify("initialized");
|
|
4255
|
-
|
|
4355
|
+
const models = input.listModels ? await listCodexModels(rpc) : void 0;
|
|
4356
|
+
return { version, models, skills: await listCodexSkills(rpc) };
|
|
4256
4357
|
} catch (error) {
|
|
4257
4358
|
if (error instanceof ExecutorSpawnRefusedError) throw error;
|
|
4258
|
-
return { version, models: void 0 };
|
|
4359
|
+
return { version, models: void 0, skills: void 0 };
|
|
4259
4360
|
} finally {
|
|
4260
4361
|
await rpc?.close().catch(() => void 0);
|
|
4261
4362
|
await handle?.kill("SIGTERM").catch(() => void 0);
|
|
@@ -4282,7 +4383,9 @@ var createCodexInstance = (config, host) => {
|
|
|
4282
4383
|
};
|
|
4283
4384
|
}
|
|
4284
4385
|
const connectionError = codexApiConnectionError(recipe, recipe.environment);
|
|
4285
|
-
const { version, models } = await probeAppServer({
|
|
4386
|
+
const { version, models, skills } = await probeAppServer({
|
|
4387
|
+
listModels: connectionError === null
|
|
4388
|
+
});
|
|
4286
4389
|
if (connectionError) {
|
|
4287
4390
|
return {
|
|
4288
4391
|
installed: true,
|
|
@@ -4290,6 +4393,7 @@ var createCodexInstance = (config, host) => {
|
|
|
4290
4393
|
readiness: "error",
|
|
4291
4394
|
auth: probeAuth(),
|
|
4292
4395
|
detail: connectionError,
|
|
4396
|
+
...skills === void 0 ? {} : { skills },
|
|
4293
4397
|
...CODEX_NATIVE_MODE_REPORT
|
|
4294
4398
|
};
|
|
4295
4399
|
}
|
|
@@ -4299,6 +4403,7 @@ var createCodexInstance = (config, host) => {
|
|
|
4299
4403
|
readiness: models === void 0 ? "unchecked" : "ready",
|
|
4300
4404
|
auth: probeAuth(),
|
|
4301
4405
|
...models === void 0 ? {} : { models },
|
|
4406
|
+
...skills === void 0 ? {} : { skills },
|
|
4302
4407
|
// 宿主拿的是这一份。登录核验那份(`verifyCodexSignIn`)报了档、这里没报,
|
|
4303
4408
|
// 界面就一直画宿主四档——2026-09-20 真发生过,浏览器里才看得见。
|
|
4304
4409
|
...CODEX_NATIVE_MODE_REPORT
|
|
@@ -4317,22 +4422,17 @@ var createCodexInstance = (config, host) => {
|
|
|
4317
4422
|
await stopBooked(existing);
|
|
4318
4423
|
threads.delete(input.threadId);
|
|
4319
4424
|
}
|
|
4320
|
-
const
|
|
4321
|
-
const layout = layoutFor(recipe, input.launch.homePath, nativeSubscription);
|
|
4425
|
+
const layout = layoutFor(recipe);
|
|
4322
4426
|
await materializeCodexShadowHome(layout, host.log);
|
|
4323
4427
|
const mcp = codexMcpLaunchConfig(input.mcpServers ?? []);
|
|
4324
4428
|
const env = buildCodexSpawnEnv({
|
|
4325
4429
|
recipe,
|
|
4326
4430
|
layout,
|
|
4327
|
-
// MCP 那几个变量是宿主挑好的 server
|
|
4328
|
-
launchEnv:
|
|
4329
|
-
crushApiKeys:
|
|
4431
|
+
// MCP 那几个变量是宿主挑好的 server 带来的,铺在最终层:实例表盖不得。
|
|
4432
|
+
launchEnv: mcp.environment,
|
|
4433
|
+
crushApiKeys: shouldCrushApiKeys(recipe.authMode)
|
|
4330
4434
|
});
|
|
4331
|
-
const args = [
|
|
4332
|
-
...codexSpawnArgs(recipe, layout, nativeSubscription),
|
|
4333
|
-
...codexOverrideArgs(input.launch.launchArgOverrides),
|
|
4334
|
-
...mcp.args
|
|
4335
|
-
];
|
|
4435
|
+
const args = [...codexSpawnArgs(recipe, layout), ...mcp.args];
|
|
4336
4436
|
const handle = await host.process.spawn({
|
|
4337
4437
|
program: recipe.binaryPath,
|
|
4338
4438
|
args,
|
|
@@ -4367,7 +4467,17 @@ var createCodexInstance = (config, host) => {
|
|
|
4367
4467
|
}
|
|
4368
4468
|
}
|
|
4369
4469
|
});
|
|
4370
|
-
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
|
+
);
|
|
4371
4481
|
try {
|
|
4372
4482
|
await rpc.request("initialize", CODEX_INITIALIZE_PARAMS);
|
|
4373
4483
|
await rpc.notify("initialized");
|
|
@@ -4435,10 +4545,6 @@ var createCodexInstance = (config, host) => {
|
|
|
4435
4545
|
const booked = requireThread(threadId);
|
|
4436
4546
|
return await booked.runtime.readThread();
|
|
4437
4547
|
},
|
|
4438
|
-
uploadFeedback: async (input) => {
|
|
4439
|
-
const booked = requireThread(input.threadId);
|
|
4440
|
-
return await booked.runtime.uploadFeedback(input.reason);
|
|
4441
|
-
},
|
|
4442
4548
|
events,
|
|
4443
4549
|
dispose: async () => {
|
|
4444
4550
|
if (disposed) return;
|
|
@@ -4624,13 +4730,15 @@ var createCodexInstance = (config, host) => {
|
|
|
4624
4730
|
};
|
|
4625
4731
|
|
|
4626
4732
|
// src/index.ts
|
|
4733
|
+
var capturedAttachments;
|
|
4627
4734
|
var executors = [
|
|
4628
4735
|
{
|
|
4629
4736
|
card: CODEX_CARD,
|
|
4630
4737
|
createInstance: async (config, host) => {
|
|
4631
4738
|
host.log.info("createInstance");
|
|
4632
|
-
return createCodexInstance(config, host);
|
|
4633
|
-
}
|
|
4739
|
+
return createCodexInstance(config, host, capturedAttachments);
|
|
4740
|
+
},
|
|
4741
|
+
probeAccount: async ({ accountPath }) => probeCodexAccount(accountPath)
|
|
4634
4742
|
}
|
|
4635
4743
|
];
|
|
4636
4744
|
var index_default = definePlugin({
|
|
@@ -4638,6 +4746,7 @@ var index_default = definePlugin({
|
|
|
4638
4746
|
apiVersion: 2,
|
|
4639
4747
|
executors,
|
|
4640
4748
|
activate(ctx) {
|
|
4749
|
+
capturedAttachments = ctx.attachments;
|
|
4641
4750
|
ctx.log.info("codex executor ready");
|
|
4642
4751
|
}
|
|
4643
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,8 @@
|
|
|
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",
|
|
25
|
+
"ycodium-executor-testing": "0.0.0"
|
|
24
26
|
},
|
|
25
27
|
"ycodium": {
|
|
26
28
|
"plugin": "./dist/index.js",
|