@ycodium-ai/agent-codex 0.2.2106 → 0.2.2130

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.
Files changed (2) hide show
  1. package/dist/index.js +1040 -362
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -43,6 +43,8 @@ var CODEX_CARD = {
43
43
  sessionModelSwitch: "in-session",
44
44
  exclusiveSessionWriter: true,
45
45
  runtimeModeSwitch: "next-turn",
46
+ // 三字段组合在 openThread 时定,切档要重开会话(与 runtimeModeSwitch 同为 next-turn)。
47
+ nativeModeSwitch: true,
46
48
  contextCompaction: "native"
47
49
  },
48
50
  credentialSources: [],
@@ -69,7 +71,22 @@ var CODEX_CARD = {
69
71
  acceptsExternalEndpoints: true,
70
72
  nativePathSuffix: "/responses",
71
73
  credentialEnvKey: "CODEX_API_KEY",
72
- supportsNativeSubscription: true
74
+ supportsNativeSubscription: true,
75
+ // codex 不认「地址一个变量」:端点写在 `-c model_providers.<id>.base_url` 上,
76
+ // 凭据只能经环境变量、变量名还得告诉它。`$providerId` / `$credentialEnvKey`
77
+ // 由宿主按 binding 生成,同一台机器上两份绑定因此不抢同一个变量名。
78
+ //
79
+ // `forced_login_method=api` 要写在最后:chatgpt 实例自己先注了
80
+ // `forced_login_method=chatgpt`,不盖掉的话手仍走订阅登录,代理地址白注。
81
+ extraLaunchArgs: [
82
+ { key: "model_provider", value: "$providerId" },
83
+ { key: "model_providers.$providerId.name", value: "Ycodium API" },
84
+ { key: "model_providers.$providerId.base_url", value: "$baseUrl" },
85
+ { key: "model_providers.$providerId.env_key", value: "$credentialEnvKey" },
86
+ { key: "model_providers.$providerId.requires_openai_auth", value: false },
87
+ { key: "model_providers.$providerId.wire_api", value: "responses" },
88
+ { key: "forced_login_method", value: "api" }
89
+ ]
73
90
  }
74
91
  };
75
92
 
@@ -172,6 +189,7 @@ var readCodexAccount = (homePath) => {
172
189
  };
173
190
 
174
191
  // src/config.ts
192
+ import { modelSelectionStringOption } from "@ycodium-ai/plugin-api/executor";
175
193
  var CODEX_INSTANCE_API_KEY_ENV = "YCODIUM_CODEX_API_KEY";
176
194
  var CODEX_INSTANCE_MODEL_PROVIDER = "ycodium_api";
177
195
  function isRecord2(value) {
@@ -299,6 +317,40 @@ function codexExecLaunchArgs(launchArgs) {
299
317
  function hasConfiguredMcpServer(appServerArgs) {
300
318
  return appServerArgs.some((arg) => arg.includes("mcp_servers."));
301
319
  }
320
+ var BUILTIN_MCP_SERVER_NAME = "ycodium";
321
+ function codexBearerTokenEnvVar(name) {
322
+ return name === BUILTIN_MCP_SERVER_NAME ? "YCODIUM_MCP_BEARER_TOKEN" : `YCODIUM_MCP_TOKEN_${name.toUpperCase().replaceAll(/[^A-Z0-9]/g, "_")}`;
323
+ }
324
+ function codexMcpLaunchConfig(servers) {
325
+ const args = [];
326
+ const environment = {};
327
+ const toml = (value) => JSON.stringify(value);
328
+ for (const server of servers) {
329
+ const key = `mcp_servers.${server.name}`;
330
+ if (server.transport.type === "stdio") {
331
+ args.push("-c", `${key}.command=${toml(server.transport.command)}`);
332
+ const commandArgs = server.transport.args ?? [];
333
+ if (commandArgs.length > 0) {
334
+ args.push("-c", `${key}.args=[${commandArgs.map(toml).join(",")}]`);
335
+ }
336
+ for (const [name, value] of Object.entries(server.transport.env ?? {})) {
337
+ args.push("-c", `${key}.env.${name}=${toml(value)}`);
338
+ }
339
+ continue;
340
+ }
341
+ args.push("-c", `${key}.url=${server.transport.url}`);
342
+ const bearer = server.transport.headers?.["Authorization"]?.replace(/^Bearer\s+/, "");
343
+ if (bearer) {
344
+ const envVar = codexBearerTokenEnvVar(server.name);
345
+ environment[envVar] = bearer;
346
+ args.push("-c", `${key}.bearer_token_env_var=${toml(envVar)}`);
347
+ }
348
+ }
349
+ return { args, environment };
350
+ }
351
+ function codexOverrideArgs(overrides) {
352
+ return overrides.flatMap(({ key, value }) => ["-c", `${key}=${JSON.stringify(value)}`]);
353
+ }
302
354
  function encodeCodexConfigOverrides(overrides) {
303
355
  return overrides.map(({ key, value }) => `-c ${JSON.stringify(`${key}=${JSON.stringify(value)}`)}`).join(" ");
304
356
  }
@@ -309,44 +361,9 @@ function modelIdFromSelection(selection) {
309
361
  const model = selection["model"];
310
362
  return typeof model === "string" ? model.trim() || void 0 : void 0;
311
363
  }
312
- function modelSelectionStringOption(selection, optionId) {
313
- if (typeof selection !== "object" || selection === null || Array.isArray(selection)) {
314
- return void 0;
315
- }
316
- const options = selection["options"];
317
- if (!Array.isArray(options)) return void 0;
318
- for (const entry of options) {
319
- if (typeof entry !== "object" || entry === null || Array.isArray(entry)) continue;
320
- const id = entry["id"];
321
- if (id !== optionId) continue;
322
- const value = entry["value"];
323
- return typeof value === "string" ? value.trim() || void 0 : void 0;
324
- }
325
- return void 0;
326
- }
327
-
328
- // src/paths.ts
329
- import * as NodeOS from "node:os";
330
- import * as NodePath2 from "node:path";
331
- function expandHomePath(value) {
332
- if (value === "~") return NodeOS.homedir();
333
- if (value.startsWith("~/") || value.startsWith("~\\")) {
334
- return NodePath2.join(NodeOS.homedir(), value.slice(2));
335
- }
336
- return value;
337
- }
338
- function compactEnv(env) {
339
- const out = {};
340
- for (const [key, value] of Object.entries(env)) {
341
- if (value !== void 0) out[key] = value;
342
- }
343
- return out;
344
- }
345
- function nowIso() {
346
- return (/* @__PURE__ */ new Date()).toISOString();
347
- }
348
364
 
349
365
  // src/connection.ts
366
+ import { mergeLaunchEnv, brainSlotEnvKeys } from "@ycodium-ai/plugin-api/executor";
350
367
  function codexApiConnectionError(recipe, environment) {
351
368
  if (recipe.authMode !== "apiKey") return null;
352
369
  try {
@@ -386,38 +403,40 @@ function connectionArgv(recipe, layout) {
386
403
  }
387
404
  return args;
388
405
  }
389
- function codexSpawnArgs(recipe, layout) {
406
+ function codexSpawnArgs(recipe, layout, nativeSubscription = false) {
407
+ if (nativeSubscription) {
408
+ return [
409
+ ...tokenizeLaunchArgs(recipe.launchArgs),
410
+ "-c",
411
+ `model_provider=${JSON.stringify("openai")}`,
412
+ "-c",
413
+ `forced_login_method=${JSON.stringify("chatgpt")}`
414
+ ];
415
+ }
390
416
  const needsOverrides = layout.mode === "authOverlay" || recipe.authMode !== "default";
391
417
  if (!needsOverrides) return tokenizeLaunchArgs(recipe.launchArgs);
392
418
  return connectionArgv(recipe, layout);
393
419
  }
394
- function crushInheritedApiKeys(env) {
395
- const out = { ...env };
396
- delete out["OPENAI_API_KEY"];
397
- delete out["CODEX_API_KEY"];
398
- return out;
399
- }
420
+ var CRUSHED_API_KEYS = {
421
+ OPENAI_API_KEY: void 0,
422
+ CODEX_API_KEY: void 0
423
+ };
400
424
  function buildCodexSpawnEnv(input) {
401
- const merged = {
402
- ...compactEnv(input.processEnv),
403
- ...input.layout.effectiveHomePath !== void 0 ? { CODEX_HOME: input.layout.effectiveHomePath } : {},
404
- ...input.recipe.environment
405
- };
406
- const withoutHomeOverride = { ...merged };
407
- if (input.processEnv["HOME"] !== void 0) {
408
- withoutHomeOverride["HOME"] = input.processEnv["HOME"];
409
- } else {
410
- delete withoutHomeOverride["HOME"];
411
- }
412
- return input.crushApiKeys ? crushInheritedApiKeys(withoutHomeOverride) : withoutHomeOverride;
425
+ const { HOME: _instanceHome, ...instanceTable } = input.recipe.environment;
426
+ return mergeLaunchEnv({
427
+ brainSlots: brainSlotEnvKeys(CODEX_CARD),
428
+ accountHome: input.layout.effectiveHomePath !== void 0 ? { CODEX_HOME: input.layout.effectiveHomePath } : {},
429
+ instanceTable,
430
+ handOverrides: input.crushApiKeys ? CRUSHED_API_KEYS : {},
431
+ launch: input.launchEnv
432
+ });
413
433
  }
414
434
  function nativeLoginSpawnEnv(processEnv, isolatedHome) {
415
- const merged = compactEnv(processEnv);
416
- delete merged["OPENAI_API_KEY"];
417
- delete merged["CODEX_API_KEY"];
418
- delete merged["CODEX_HOME"];
419
- merged["CODEX_HOME"] = isolatedHome;
420
- return merged;
435
+ return mergeLaunchEnv({
436
+ brainSlots: brainSlotEnvKeys(CODEX_CARD),
437
+ inherited: processEnv,
438
+ handOverrides: { ...CRUSHED_API_KEYS, CODEX_HOME: isolatedHome }
439
+ });
421
440
  }
422
441
  function shouldCrushApiKeys(authMode) {
423
442
  return authMode === "chatgpt";
@@ -427,6 +446,22 @@ function shouldCrushApiKeys(authMode) {
427
446
  import * as NodeFSP from "node:fs/promises";
428
447
  import * as NodeOS2 from "node:os";
429
448
  import * as NodePath3 from "node:path";
449
+
450
+ // src/paths.ts
451
+ import * as NodeOS from "node:os";
452
+ import * as NodePath2 from "node:path";
453
+ function expandHomePath(value) {
454
+ if (value === "~") return NodeOS.homedir();
455
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
456
+ return NodePath2.join(NodeOS.homedir(), value.slice(2));
457
+ }
458
+ return value;
459
+ }
460
+ function nowIso() {
461
+ return (/* @__PURE__ */ new Date()).toISOString();
462
+ }
463
+
464
+ // src/homeLayout.ts
430
465
  var KNOWN_SHARED_DIRECTORIES = [
431
466
  "sessions",
432
467
  "archived_sessions",
@@ -465,6 +500,13 @@ function resolveCodexHomeLayout(input) {
465
500
  continuationKey: `codex:home:${sharedHomePath}`
466
501
  };
467
502
  }
503
+ function codexContinuationKey(input) {
504
+ return resolveCodexHomeLayout({
505
+ homePath: input.homePath,
506
+ shadowHomePath: "",
507
+ environmentHome: input.instanceEnvironment["CODEX_HOME"] ?? input.inheritedEnvironment["CODEX_HOME"]
508
+ }).continuationKey;
509
+ }
468
510
  async function readLinkState(linkPath) {
469
511
  try {
470
512
  const target = await NodeFSP.readlink(linkPath);
@@ -512,6 +554,16 @@ async function ensureSymlink(input) {
512
554
  await createEntryLink(target, link);
513
555
  }
514
556
  }
557
+ async function makeDirectory(directoryPath) {
558
+ try {
559
+ await NodeFSP.mkdir(directoryPath, { recursive: true });
560
+ } catch (error) {
561
+ throw new Error(
562
+ `Codex shadow home filesystem operation 'makeDirectory' failed for '${directoryPath}': ${String(error)}`,
563
+ { cause: error }
564
+ );
565
+ }
566
+ }
515
567
  async function materializeCodexShadowHome(layout, log) {
516
568
  if (layout.mode !== "authOverlay") return;
517
569
  const effectiveHomePath = layout.effectiveHomePath;
@@ -521,11 +573,11 @@ async function materializeCodexShadowHome(layout, log) {
521
573
  `Codex shadow home path '${effectiveHomePath}' must be different from the shared home path '${layout.sharedHomePath}'.`
522
574
  );
523
575
  }
524
- await NodeFSP.mkdir(layout.sharedHomePath, { recursive: true });
525
- await NodeFSP.mkdir(effectiveHomePath, { recursive: true });
576
+ await makeDirectory(layout.sharedHomePath);
577
+ await makeDirectory(effectiveHomePath);
526
578
  await Promise.all(
527
579
  KNOWN_SHARED_DIRECTORIES.map(
528
- (directory) => NodeFSP.mkdir(NodePath3.join(layout.sharedHomePath, directory), { recursive: true })
580
+ (directory) => makeDirectory(NodePath3.join(layout.sharedHomePath, directory))
529
581
  )
530
582
  );
531
583
  let sharedEntryNames = [];
@@ -584,6 +636,73 @@ var ACTIVE_WRITER_ATTEMPTS = 5;
584
636
  var ACTIVE_WRITER_DELAY_MS = 200;
585
637
  var DEFAULT_MODEL = "gpt-5.6-sol";
586
638
  var DEFAULT_TEXT_GENERATION_REASONING_EFFORT = "low";
639
+ var CODEX_NATIVE_MODES = [
640
+ {
641
+ id: "read-only",
642
+ name: "Read only",
643
+ description: "Sandboxed to reading. Asks before anything that writes or runs."
644
+ },
645
+ {
646
+ id: "workspace-write",
647
+ name: "Workspace write",
648
+ description: "Writes inside the workspace. Asks you before stepping outside it."
649
+ },
650
+ {
651
+ id: "auto-review",
652
+ name: "Auto review",
653
+ description: "Writes inside the workspace. Codex approves its own requests, not you."
654
+ },
655
+ {
656
+ id: "full-access",
657
+ name: "Full access",
658
+ description: "No sandbox, never asks. Every command runs immediately."
659
+ }
660
+ ];
661
+ var CODEX_DEFAULT_NATIVE_MODE_ID = "read-only";
662
+ var CODEX_NATIVE_MODE_BY_RUNTIME_MODE = {
663
+ "approval-required": "read-only",
664
+ "auto-accept-edits": "workspace-write",
665
+ auto: "auto-review",
666
+ "full-access": "full-access"
667
+ };
668
+ var CODEX_NATIVE_MODE_REPORT = {
669
+ nativeModes: CODEX_NATIVE_MODES.map((mode) => ({
670
+ id: mode.id,
671
+ name: mode.name,
672
+ description: mode.description
673
+ })),
674
+ currentNativeModeId: CODEX_DEFAULT_NATIVE_MODE_ID,
675
+ nativeModeByRuntimeMode: CODEX_NATIVE_MODE_BY_RUNTIME_MODE
676
+ };
677
+ function isCodexNativeModeId(value) {
678
+ return CODEX_NATIVE_MODES.some((mode) => mode.id === value);
679
+ }
680
+ function codexNativeModeFor(runtimeMode, nativeModeId) {
681
+ return nativeModeId !== void 0 && isCodexNativeModeId(nativeModeId) ? nativeModeId : CODEX_NATIVE_MODE_BY_RUNTIME_MODE[runtimeMode];
682
+ }
683
+ function codexThreadConfigForNativeMode(nativeModeId) {
684
+ switch (nativeModeId) {
685
+ case "workspace-write":
686
+ return runtimeModeToThreadConfig("auto-accept-edits");
687
+ case "auto-review":
688
+ return runtimeModeToThreadConfig("auto");
689
+ case "full-access":
690
+ return runtimeModeToThreadConfig("full-access");
691
+ default:
692
+ return runtimeModeToThreadConfig("approval-required");
693
+ }
694
+ }
695
+ function codexTurnSandboxPolicyForNativeMode(nativeModeId) {
696
+ switch (nativeModeId) {
697
+ case "workspace-write":
698
+ case "auto-review":
699
+ return runtimeModeToTurnSandboxPolicy("auto-accept-edits");
700
+ case "full-access":
701
+ return runtimeModeToTurnSandboxPolicy("full-access");
702
+ default:
703
+ return runtimeModeToTurnSandboxPolicy("approval-required");
704
+ }
705
+ }
587
706
  function runtimeModeToThreadConfig(mode) {
588
707
  switch (mode) {
589
708
  case "approval-required":
@@ -617,6 +736,16 @@ function runtimeModeToTurnSandboxPolicy(mode) {
617
736
  return { type: "dangerFullAccess" };
618
737
  }
619
738
  }
739
+ function codexCollaborationMode(input) {
740
+ const effort = input.reasoningEffort?.trim();
741
+ return {
742
+ mode: input.interactionMode === "plan" ? "plan" : "default",
743
+ settings: {
744
+ model: input.model,
745
+ ...effort ? { reasoning_effort: effort } : {}
746
+ }
747
+ };
748
+ }
620
749
  function isRecord3(value) {
621
750
  return typeof value === "object" && value !== null && !Array.isArray(value);
622
751
  }
@@ -697,11 +826,19 @@ var CHILD_AGENT_EVENT_METHODS = /* @__PURE__ */ new Set([
697
826
  ]);
698
827
  var CHILD_CHATTER_METHODS = /* @__PURE__ */ new Set([
699
828
  "item/agentMessage/delta",
700
- "item/reasoning/delta",
701
- "item/reasoning/summaryDelta",
829
+ "item/reasoning/textDelta",
830
+ "item/reasoning/summaryTextDelta",
702
831
  "item/reasoning/summaryPartAdded",
832
+ "item/commandExecution/outputDelta",
833
+ "item/fileChange/outputDelta",
834
+ "item/fileChange/patchUpdated",
703
835
  "item/plan/delta",
836
+ "turn/plan/updated",
837
+ "turn/diff/updated",
704
838
  "thread/name/updated",
839
+ "thread/settings/updated",
840
+ "rawResponseItem/completed",
841
+ // 子线程自己的生命周期通知不能改父线程状态(旧宿主 runtime 同名单)。
705
842
  "thread/archived",
706
843
  "thread/unarchived",
707
844
  "thread/compacted",
@@ -1120,95 +1257,6 @@ function buildCodexDeveloperInstructions(interactionMode, runtime, browserToolsA
1120
1257
  <runtime_info>In case you're asked: you are running in Ycodium through the Codex harness, as ${toSingleLine(runtime.model)} with ${toSingleLine(runtime.reasoningEffort)} reasoning effort. No need to mention this otherwise.</runtime_info>`;
1121
1258
  }
1122
1259
 
1123
- // src/imageMime.ts
1124
- var IMAGE_EXTENSION_BY_MIME_TYPE = {
1125
- "image/avif": ".avif",
1126
- "image/bmp": ".bmp",
1127
- "image/gif": ".gif",
1128
- "image/heic": ".heic",
1129
- "image/heif": ".heif",
1130
- "image/jpeg": ".jpg",
1131
- "image/jpg": ".jpg",
1132
- "image/png": ".png",
1133
- "image/svg+xml": ".svg",
1134
- "image/tiff": ".tiff",
1135
- "image/webp": ".webp"
1136
- };
1137
- var SAFE_IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
1138
- ".avif",
1139
- ".bmp",
1140
- ".gif",
1141
- ".heic",
1142
- ".heif",
1143
- ".ico",
1144
- ".jpeg",
1145
- ".jpg",
1146
- ".png",
1147
- ".svg",
1148
- ".tiff",
1149
- ".webp"
1150
- ]);
1151
- function isBase64Char(code) {
1152
- return code >= 97 && code <= 122 || code >= 65 && code <= 90 || code >= 48 && code <= 57 || code === 43 || code === 47 || code === 61;
1153
- }
1154
- function isBase64Whitespace(code) {
1155
- return code === 13 || code === 10 || code === 32;
1156
- }
1157
- function parseBase64DataUrl(dataUrl) {
1158
- const trimmed = dataUrl.trim();
1159
- if (trimmed.slice(0, 5).toLowerCase() !== "data:") return null;
1160
- const commaIndex = trimmed.indexOf(",");
1161
- if (commaIndex === -1) return null;
1162
- const header = trimmed.slice(5, commaIndex);
1163
- if (header.length === 0) return null;
1164
- const headerParts = [];
1165
- for (const part of header.split(";")) {
1166
- const partTrimmed = part.trim();
1167
- if (partTrimmed.length > 0) headerParts.push(partTrimmed);
1168
- }
1169
- if (headerParts.length < 2) return null;
1170
- const trailingToken = headerParts.at(-1)?.toLowerCase();
1171
- if (trailingToken !== "base64") return null;
1172
- const mimeType = headerParts[0]?.toLowerCase();
1173
- if (!mimeType) return null;
1174
- const payload = trimmed.slice(commaIndex + 1);
1175
- const runs = [];
1176
- let runStart = -1;
1177
- for (let index = 0; index < payload.length; index += 1) {
1178
- const code = payload.charCodeAt(index);
1179
- if (isBase64Char(code)) {
1180
- if (runStart === -1) runStart = index;
1181
- continue;
1182
- }
1183
- if (!isBase64Whitespace(code)) return null;
1184
- if (runStart !== -1) {
1185
- runs.push(payload.slice(runStart, index));
1186
- runStart = -1;
1187
- }
1188
- }
1189
- if (runStart !== -1) runs.push(payload.slice(runStart));
1190
- const base64 = runs.length === 1 ? runs[0] : runs.join("");
1191
- if (base64.length === 0 || base64.length % 4 !== 0) return null;
1192
- const firstPad = base64.indexOf("=");
1193
- if (firstPad !== -1) {
1194
- if (base64.length - firstPad > 2) return null;
1195
- for (let index = firstPad; index < base64.length; index += 1) {
1196
- if (base64.charCodeAt(index) !== 61) return null;
1197
- }
1198
- }
1199
- return { mimeType, base64 };
1200
- }
1201
- function inferImageExtension(input) {
1202
- const key = input.mimeType.toLowerCase();
1203
- const fromMime = Object.hasOwn(IMAGE_EXTENSION_BY_MIME_TYPE, key) ? IMAGE_EXTENSION_BY_MIME_TYPE[key] : void 0;
1204
- if (fromMime) return fromMime;
1205
- const fileName = input.fileName?.trim() ?? "";
1206
- const extensionMatch = /\.([a-z0-9]{1,8})$/i.exec(fileName);
1207
- const fileNameExtension = extensionMatch ? `.${extensionMatch[1].toLowerCase()}` : "";
1208
- if (SAFE_IMAGE_FILE_EXTENSIONS.has(fileNameExtension)) return fileNameExtension;
1209
- return ".bin";
1210
- }
1211
-
1212
1260
  // src/events.ts
1213
1261
  var CODEX_PROVIDER = "codex";
1214
1262
  var eventSeq = 0;
@@ -1225,28 +1273,27 @@ function nowIso2() {
1225
1273
  return (/* @__PURE__ */ new Date()).toISOString();
1226
1274
  }
1227
1275
  function normalizeItemType(value) {
1228
- if (!value) return "unknown";
1229
- return value.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_/.-]+/g, " ").toLowerCase().trim();
1276
+ if (!value) return "item";
1277
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[._/-]/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
1230
1278
  }
1231
1279
  function toCanonicalItemType(raw) {
1232
1280
  const type = normalizeItemType(raw);
1233
- if (type.includes("user") && type.includes("message")) return "user";
1234
- if (type.includes("agent") && type.includes("message")) return "assistant";
1235
- if (type.includes("assistant")) return "assistant";
1236
- if (type.includes("reason") || type.includes("thought")) return "reasoning";
1281
+ if (type.includes("user")) return "user_message";
1282
+ if (type.includes("agent message") || type.includes("assistant")) return "assistant_message";
1283
+ if (type.includes("reasoning") || type.includes("thought")) return "reasoning";
1237
1284
  if (type.includes("plan") || type.includes("todo")) return "plan";
1238
- if (type.includes("command")) return "command";
1285
+ if (type.includes("command")) return "command_execution";
1239
1286
  if (type.includes("file change") || type.includes("patch") || type.includes("edit")) {
1240
- return "fileChange";
1241
- }
1242
- if (type.includes("mcp")) return "mcp";
1243
- if (type.includes("dynamic tool")) return "dynamicTool";
1244
- if (type.includes("collab")) return "collab";
1245
- if (type.includes("web search")) return "webSearch";
1246
- if (type.includes("image")) return "image";
1247
- if (type.includes("review entered")) return "reviewEntered";
1248
- if (type.includes("review exited")) return "reviewExited";
1249
- if (type.includes("compact")) return "compact";
1287
+ return "file_change";
1288
+ }
1289
+ if (type.includes("mcp")) return "mcp_tool_call";
1290
+ if (type.includes("dynamic tool")) return "dynamic_tool_call";
1291
+ if (type.includes("collab")) return "collab_agent_tool_call";
1292
+ if (type.includes("web search")) return "web_search";
1293
+ if (type.includes("image")) return "image_view";
1294
+ if (type.includes("review entered")) return "review_entered";
1295
+ if (type.includes("review exited")) return "review_exited";
1296
+ if (type.includes("compact")) return "context_compaction";
1250
1297
  if (type.includes("error")) return "error";
1251
1298
  return "unknown";
1252
1299
  }
@@ -1255,6 +1302,11 @@ function itemIdOf(params) {
1255
1302
  const item = isRecord3(params["item"]) ? params["item"] : params;
1256
1303
  return asString(item["id"]) ?? asString(params["itemId"]) ?? void 0;
1257
1304
  }
1305
+ function itemStatusOf(params) {
1306
+ if (!isRecord3(params)) return void 0;
1307
+ const item = isRecord3(params["item"]) ? params["item"] : params;
1308
+ return asString(item["status"]) ?? void 0;
1309
+ }
1258
1310
  function turnIdOf(params) {
1259
1311
  if (!isRecord3(params)) return void 0;
1260
1312
  return asString(params["turnId"]) ?? (isRecord3(params["turn"]) ? asString(params["turn"]["id"]) : null) ?? void 0;
@@ -1287,45 +1339,45 @@ function toTurnStatus(status) {
1287
1339
  return "completed";
1288
1340
  }
1289
1341
  }
1290
- function toThreadState(status) {
1291
- if (status === "idle") return "idle";
1292
- if (status === "systemError" || status === "error") return "error";
1342
+ function threadStateFromStatus(status) {
1343
+ const type = isRecord3(status) ? asString(status["type"]) : asString(status);
1344
+ if (type === "idle") return "idle";
1345
+ if (type === "systemError" || type === "error") return "error";
1293
1346
  return "active";
1294
1347
  }
1295
- function contentStreamKindFromMethod(method) {
1296
- if (method.includes("agentMessage")) return "assistant";
1297
- if (method.includes("reasoning")) return "reasoning";
1298
- if (method.includes("plan")) return "plan";
1299
- return void 0;
1348
+ function codexCompactionId(threadId, turnId) {
1349
+ return `codex:${threadId}:${turnId ?? "session"}`;
1300
1350
  }
1301
- function normalizeCodexTokenUsage(params) {
1302
- if (!isRecord3(params)) return void 0;
1303
- const usage = isRecord3(params["usage"]) ? params["usage"] : params;
1304
- const last = isRecord3(usage["last"]) ? usage["last"] : usage;
1305
- const total = isRecord3(usage["total"]) ? usage["total"] : void 0;
1306
- const usedTokens = asNumber(last["totalTokens"]) ?? asNumber(last["total_tokens"]);
1307
- if (usedTokens === null || usedTokens <= 0) return void 0;
1308
- const totalProcessed = total ? asNumber(total["totalTokens"]) ?? asNumber(total["total_tokens"]) : null;
1309
- const payload = {
1351
+ function normalizeCodexTokenUsage(tokenUsage) {
1352
+ if (!isRecord3(tokenUsage)) return void 0;
1353
+ const last = isRecord3(tokenUsage["last"]) ? tokenUsage["last"] : void 0;
1354
+ const total = isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
1355
+ const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1356
+ const usedTokens = count(last?.["totalTokens"]);
1357
+ if (usedTokens === void 0 || usedTokens <= 0) return void 0;
1358
+ const totalProcessedTokens = count(total?.["totalTokens"]);
1359
+ const maxTokens = count(tokenUsage["modelContextWindow"]);
1360
+ const inputTokens = count(last?.["inputTokens"]);
1361
+ const cachedInputTokens = count(last?.["cachedInputTokens"]);
1362
+ const outputTokens = count(last?.["outputTokens"]);
1363
+ const reasoningOutputTokens = count(last?.["reasoningOutputTokens"]);
1364
+ return {
1310
1365
  usedTokens,
1366
+ lastUsedTokens: usedTokens,
1367
+ ...totalProcessedTokens !== void 0 && totalProcessedTokens > usedTokens ? { totalProcessedTokens } : {},
1368
+ ...maxTokens !== void 0 && maxTokens > 0 ? { maxTokens } : {},
1369
+ ...inputTokens !== void 0 ? { inputTokens, lastInputTokens: inputTokens } : {},
1370
+ ...cachedInputTokens !== void 0 ? { cachedInputTokens, lastCachedInputTokens: cachedInputTokens } : {},
1371
+ ...outputTokens !== void 0 ? { outputTokens, lastOutputTokens: outputTokens } : {},
1372
+ ...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens, lastReasoningOutputTokens: reasoningOutputTokens } : {},
1311
1373
  compactsAutomatically: true
1312
1374
  };
1313
- if (totalProcessed !== null && totalProcessed > usedTokens) {
1314
- payload["totalProcessedTokens"] = totalProcessed;
1315
- }
1316
- const maxTokens = asNumber(params["modelContextWindow"]) ?? asNumber(usage["modelContextWindow"]);
1317
- if (maxTokens !== null && maxTokens > 0) payload["maxTokens"] = maxTokens;
1318
- const lastInput = asNumber(last["inputTokens"]) ?? asNumber(last["input_tokens"]);
1319
- const lastOutput = asNumber(last["outputTokens"]) ?? asNumber(last["output_tokens"]);
1320
- if (lastInput !== null) payload["lastInputTokens"] = lastInput;
1321
- if (lastOutput !== null) payload["lastOutputTokens"] = lastOutput;
1322
- return payload;
1323
1375
  }
1324
1376
  function turnCompletedControlFlow(params) {
1325
1377
  const turn = isRecord3(params) ? isRecord3(params["turn"]) ? params["turn"] : params : {};
1326
- const status = toTurnStatus(asString(turn["status"]) ?? void 0);
1378
+ const state = toTurnStatus(asString(turn["status"]) ?? void 0);
1327
1379
  const error = isRecord3(turn["error"]) ? trimText(turn["error"]["message"]) : void 0;
1328
- return error ? { status, errorMessage: error } : { status };
1380
+ return error ? { state, errorMessage: error } : { state };
1329
1381
  }
1330
1382
  function toApplyPatchFileChanges(params) {
1331
1383
  if (!isRecord3(params)) return [];
@@ -1343,10 +1395,33 @@ function toApplyPatchFileChanges(params) {
1343
1395
  if (!path) continue;
1344
1396
  const kind = (asString(entry["type"]) ?? asString(entry["kind"]) ?? "update").toLowerCase();
1345
1397
  const movePath = asString(entry["move_path"]) ?? asString(entry["movePath"]);
1346
- if (kind === "add") out.push({ path, kind: "add" });
1347
- else if (kind === "delete") out.push({ path, kind: "delete" });
1348
- else if (movePath) out.push({ path, kind: "rename", to: movePath });
1349
- else out.push({ path, kind: "modify" });
1398
+ const diff = asString(entry["unified_diff"]) ?? asString(entry["unifiedDiff"]);
1399
+ const content = asString(entry["content"]);
1400
+ if (kind === "add") {
1401
+ out.push({
1402
+ path,
1403
+ changeKind: "add",
1404
+ ...content !== void 0 ? { content: { kind: "replace", newText: content } } : {}
1405
+ });
1406
+ } else if (kind === "delete") {
1407
+ out.push({
1408
+ path,
1409
+ changeKind: "delete",
1410
+ ...content !== void 0 ? { content: { kind: "replace", oldText: content, newText: "" } } : {}
1411
+ });
1412
+ } else if (movePath) {
1413
+ out.push({
1414
+ path,
1415
+ changeKind: "rename",
1416
+ ...diff ? { content: { kind: "unified", diff } } : {}
1417
+ });
1418
+ } else {
1419
+ out.push({
1420
+ path,
1421
+ changeKind: "modify",
1422
+ ...diff ? { content: { kind: "unified", diff } } : {}
1423
+ });
1424
+ }
1350
1425
  }
1351
1426
  return out;
1352
1427
  }
@@ -1359,7 +1434,18 @@ function toUserInputQuestions(params) {
1359
1434
  const id = asString(question["id"]);
1360
1435
  const header = asString(question["header"]);
1361
1436
  const text = asString(question["question"]);
1362
- const options = Array.isArray(question["options"]) ? question["options"] : [];
1437
+ const rawOptions = Array.isArray(question["options"]) ? question["options"] : [];
1438
+ const options = [];
1439
+ for (const option of rawOptions) {
1440
+ if (!isRecord3(option)) continue;
1441
+ const label = trimText(option["label"]);
1442
+ if (!label) continue;
1443
+ options.push({
1444
+ ...asString(option["id"]) ? { id: asString(option["id"]) } : {},
1445
+ label,
1446
+ description: trimText(option["description"]) ?? ""
1447
+ });
1448
+ }
1363
1449
  if (!id || !header || !text || options.length === 0) return void 0;
1364
1450
  mapped.push({
1365
1451
  id,
@@ -1372,15 +1458,32 @@ function toUserInputQuestions(params) {
1372
1458
  return mapped.length > 0 ? mapped : void 0;
1373
1459
  }
1374
1460
  function toCodexUserInputAnswers(answers) {
1375
- if (typeof answers === "string") return [answers];
1376
- if (Array.isArray(answers) && answers.every((entry) => typeof entry === "string")) {
1377
- return answers;
1378
- }
1379
- if (isRecord3(answers) && Array.isArray(answers["answers"])) {
1380
- const values = answers["answers"];
1381
- if (values.every((entry) => typeof entry === "string")) return values;
1461
+ if (!isRecord3(answers)) return void 0;
1462
+ const out = {};
1463
+ for (const [questionId, value] of Object.entries(answers)) {
1464
+ if (typeof value === "string") {
1465
+ out[questionId] = { answers: [value] };
1466
+ continue;
1467
+ }
1468
+ if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
1469
+ out[questionId] = { answers: [...value] };
1470
+ continue;
1471
+ }
1472
+ if (isRecord3(value) && Array.isArray(value["answers"]) && value["answers"].every((entry) => typeof entry === "string")) {
1473
+ out[questionId] = { answers: [...value["answers"]] };
1474
+ continue;
1475
+ }
1476
+ return void 0;
1382
1477
  }
1383
- return void 0;
1478
+ return out;
1479
+ }
1480
+ function toCanonicalUserInputAnswers(answers) {
1481
+ return Object.fromEntries(
1482
+ Object.entries(answers).map(([questionId, value]) => [
1483
+ questionId,
1484
+ value.answers.length === 1 ? value.answers[0] : [...value.answers]
1485
+ ])
1486
+ );
1384
1487
  }
1385
1488
  function pathLeaf(value) {
1386
1489
  if (!value) return void 0;
@@ -1418,10 +1521,11 @@ function mapCollabAgentEvent(input) {
1418
1521
  const agentPath = asString(input.params["agentPath"]) ?? asString(input.params["path"]) ?? void 0;
1419
1522
  const nickname = asString(input.params["agentNickname"]) ?? asString(input.params["nickname"]);
1420
1523
  const role = asString(input.params["agentRole"]) ?? asString(input.params["role"]) ?? pathLeaf(agentPath) ?? "general-purpose";
1421
- const title = nickname ?? pathLeaf(agentPath) ?? agentThreadId;
1524
+ const knownName = nickname ?? pathLeaf(agentPath);
1525
+ const title = knownName ?? agentThreadId;
1422
1526
  const linkage = {
1423
1527
  role,
1424
- title,
1528
+ ...knownName ? { title: knownName } : {},
1425
1529
  ...agentPath ? { agentPath } : {},
1426
1530
  timelineBypass: true
1427
1531
  };
@@ -1439,14 +1543,17 @@ function mapCollabAgentEvent(input) {
1439
1543
  ];
1440
1544
  }
1441
1545
  if (method === "collabAgent/progress" || method === "task.progress") {
1546
+ const summary = trimText(input.params["detail"]) ?? trimText(input.params["text"]);
1442
1547
  return [
1443
1548
  baseEvent({
1444
1549
  type: "task.progress",
1445
1550
  threadId,
1551
+ // 契约 TaskProgressPayload 的 description 是必填;子代理行靠它显示名字。
1446
1552
  payload: {
1447
1553
  taskId: agentThreadId,
1448
- ...linkage,
1449
- detail: trimText(input.params["detail"]) ?? trimText(input.params["text"])
1554
+ description: title,
1555
+ ...summary ? { summary } : {},
1556
+ ...linkage
1450
1557
  },
1451
1558
  method,
1452
1559
  providerInstanceId: input.providerInstanceId
@@ -1456,37 +1563,53 @@ function mapCollabAgentEvent(input) {
1456
1563
  if (method === "collabAgent/completed" || method === "task.completed") {
1457
1564
  return [
1458
1565
  baseEvent({
1459
- type: "task.completed",
1566
+ type: "task.updated",
1460
1567
  threadId,
1461
- payload: { taskId: agentThreadId, ...linkage },
1568
+ payload: { taskId: agentThreadId, status: "idle", ...linkage },
1462
1569
  method,
1463
1570
  providerInstanceId: input.providerInstanceId
1464
1571
  })
1465
1572
  ];
1466
1573
  }
1467
1574
  if (method === "collabAgent/statusChanged") {
1575
+ const raw = asString(input.params["status"]);
1576
+ const status = raw === "systemError" || raw === "error" ? "failed" : raw === "idle" ? "idle" : "running";
1468
1577
  return [
1469
1578
  baseEvent({
1470
1579
  type: "task.updated",
1471
1580
  threadId,
1472
- payload: {
1473
- taskId: agentThreadId,
1474
- ...linkage,
1475
- status: asString(input.params["status"]) ?? "running"
1476
- },
1581
+ payload: { taskId: agentThreadId, status, ...linkage },
1477
1582
  method,
1478
1583
  providerInstanceId: input.providerInstanceId
1479
1584
  })
1480
1585
  ];
1481
1586
  }
1482
1587
  if (method === "collabAgent/tokenUsage") {
1483
- const usage = normalizeCodexTokenUsage(input.params);
1484
- if (!usage) return [];
1588
+ const tokenUsage = isRecord3(input.params["tokenUsage"]) ? input.params["tokenUsage"] : void 0;
1589
+ const total = tokenUsage && isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
1590
+ const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1591
+ const totalTokens = total ? count(total["totalTokens"]) : void 0;
1592
+ if (total === void 0 || totalTokens === void 0) return [];
1593
+ const typedUsage = { totalTokens };
1594
+ for (const key of [
1595
+ "inputTokens",
1596
+ "cachedInputTokens",
1597
+ "outputTokens",
1598
+ "reasoningOutputTokens"
1599
+ ]) {
1600
+ const value = count(total[key]);
1601
+ if (value !== void 0) typedUsage[key] = value;
1602
+ }
1485
1603
  return [
1486
1604
  baseEvent({
1487
- type: "thread.token-usage.updated",
1488
- threadId: agentThreadId,
1489
- payload: usage,
1605
+ type: "task.progress",
1606
+ threadId,
1607
+ payload: {
1608
+ taskId: agentThreadId,
1609
+ description: title,
1610
+ typedUsage,
1611
+ ...linkage
1612
+ },
1490
1613
  method,
1491
1614
  providerInstanceId: input.providerInstanceId
1492
1615
  })
@@ -1581,52 +1704,6 @@ function toMcpElicitationResponse(decision, params) {
1581
1704
  ...persist ? { _meta: { persist } } : {}
1582
1705
  };
1583
1706
  }
1584
- function producedImages(params) {
1585
- if (!isRecord3(params)) return [];
1586
- const item = isRecord3(params["item"]) ? params["item"] : params;
1587
- const type = toCanonicalItemType(asString(item["type"]) ?? void 0);
1588
- const events = [];
1589
- const threadId = conversationIdFromNotification(params) ?? "";
1590
- const emit = (payload) => {
1591
- events.push(
1592
- baseEvent({
1593
- type: "assistant.attachment.produced",
1594
- threadId,
1595
- payload,
1596
- itemId: asString(item["id"]) ?? void 0,
1597
- turnId: turnIdOf(params),
1598
- method: "item/completed"
1599
- })
1600
- );
1601
- };
1602
- if (type === "image") {
1603
- const savedPath = asString(item["savedPath"]) ?? asString(item["path"]);
1604
- if (savedPath) emit({ path: savedPath, mimeType: "image/png" });
1605
- }
1606
- const content = Array.isArray(item["content"]) ? item["content"] : [];
1607
- for (const entry of content) {
1608
- if (!isRecord3(entry)) continue;
1609
- if (entry["type"] === "image" && typeof entry["data"] === "string") {
1610
- const mimeType = asString(entry["mimeType"]) ?? "image/png";
1611
- emit({
1612
- mimeType,
1613
- data: entry["data"],
1614
- extension: inferImageExtension({ mimeType })
1615
- });
1616
- }
1617
- if (entry["type"] === "inputImage" && typeof entry["imageUrl"] === "string") {
1618
- const parsed = parseBase64DataUrl(entry["imageUrl"]);
1619
- if (parsed) {
1620
- emit({
1621
- mimeType: parsed.mimeType,
1622
- data: parsed.base64,
1623
- extension: inferImageExtension({ mimeType: parsed.mimeType })
1624
- });
1625
- }
1626
- }
1627
- }
1628
- return events;
1629
- }
1630
1707
  function mapNotificationToEvents(input) {
1631
1708
  const { method, params, threadId, providerInstanceId } = input;
1632
1709
  if (method.startsWith("collabAgent/")) {
@@ -1678,58 +1755,121 @@ function mapNotificationToEvents(input) {
1678
1755
  const itemId = itemIdOf(params);
1679
1756
  const type = itemTypeOf(params);
1680
1757
  switch (method) {
1681
- case "thread/started":
1758
+ case "thread/started": {
1759
+ const startedThread = isRecord3(params) ? params["thread"] : void 0;
1760
+ const providerThreadId = isRecord3(startedThread) ? trimText(startedThread["id"]) : void 0;
1682
1761
  return [
1683
1762
  baseEvent({
1684
1763
  type: "thread.started",
1685
1764
  threadId,
1686
- payload: isRecord3(params) ? params : {},
1765
+ payload: providerThreadId ? { providerThreadId } : {},
1687
1766
  method,
1688
1767
  providerInstanceId
1689
1768
  })
1690
1769
  ];
1770
+ }
1691
1771
  case "thread/status/changed": {
1692
- const status = isRecord3(params) ? asString(params["status"]) : void 0;
1772
+ const status = isRecord3(params) ? params["status"] : void 0;
1693
1773
  return [
1694
1774
  baseEvent({
1695
1775
  type: "thread.state.changed",
1696
1776
  threadId,
1697
- payload: { state: toThreadState(status ?? void 0), status },
1777
+ payload: {
1778
+ state: threadStateFromStatus(status),
1779
+ ...params !== void 0 ? { detail: params } : {}
1780
+ },
1698
1781
  method,
1699
1782
  providerInstanceId
1700
1783
  })
1701
1784
  ];
1702
1785
  }
1703
- case "thread/name/updated":
1786
+ case "thread/archived":
1787
+ case "thread/unarchived":
1788
+ return [
1789
+ baseEvent({
1790
+ type: "thread.state.changed",
1791
+ threadId,
1792
+ payload: {
1793
+ state: method === "thread/archived" ? "archived" : "active",
1794
+ ...params !== void 0 ? { detail: params } : {}
1795
+ },
1796
+ method,
1797
+ providerInstanceId
1798
+ })
1799
+ ];
1800
+ case "thread/closed":
1801
+ return [
1802
+ baseEvent({
1803
+ type: "thread.state.changed",
1804
+ threadId,
1805
+ payload: {
1806
+ state: "closed",
1807
+ ...params !== void 0 ? { detail: params } : {}
1808
+ },
1809
+ method,
1810
+ providerInstanceId
1811
+ })
1812
+ ];
1813
+ case "thread/compacted":
1814
+ return [
1815
+ baseEvent({
1816
+ type: "thread.state.changed",
1817
+ threadId,
1818
+ payload: {
1819
+ state: "compacted",
1820
+ compaction: {
1821
+ compactionId: codexCompactionId(threadId, turnId),
1822
+ // Codex 不报触发方,手动压缩也走同一条通知。
1823
+ trigger: "automatic"
1824
+ },
1825
+ ...params !== void 0 ? { detail: params } : {}
1826
+ },
1827
+ method,
1828
+ providerInstanceId
1829
+ })
1830
+ ];
1831
+ case "thread/name/updated": {
1832
+ const threadName = isRecord3(params) ? trimText(params["threadName"]) ?? trimText(params["name"]) : void 0;
1833
+ const nativeThreadId = isRecord3(params) ? asString(params["threadId"]) : void 0;
1704
1834
  return [
1705
1835
  baseEvent({
1706
1836
  type: "thread.metadata.updated",
1707
1837
  threadId,
1708
- payload: { title: isRecord3(params) ? asString(params["name"]) : null },
1838
+ payload: {
1839
+ ...threadName ? { name: threadName } : {},
1840
+ ...nativeThreadId || threadName !== void 0 ? {
1841
+ metadata: {
1842
+ ...nativeThreadId ? { threadId: nativeThreadId } : {},
1843
+ ...threadName !== void 0 ? { threadName } : {}
1844
+ }
1845
+ } : {}
1846
+ },
1709
1847
  method,
1710
1848
  providerInstanceId
1711
1849
  })
1712
1850
  ];
1851
+ }
1713
1852
  case "thread/tokenUsage/updated": {
1714
- const usage = normalizeCodexTokenUsage(params);
1853
+ const usage = normalizeCodexTokenUsage(
1854
+ isRecord3(params) ? params["tokenUsage"] ?? params["usage"] : void 0
1855
+ );
1715
1856
  if (!usage) return [];
1716
1857
  return [
1717
1858
  baseEvent({
1718
1859
  type: "thread.token-usage.updated",
1719
1860
  threadId,
1720
- payload: usage,
1861
+ payload: { usage },
1721
1862
  method,
1722
1863
  providerInstanceId
1723
1864
  })
1724
1865
  ];
1725
1866
  }
1726
- case "thread/closed":
1727
1867
  case "session/closed":
1728
1868
  return [
1729
1869
  baseEvent({
1730
1870
  type: "session.exited",
1731
1871
  threadId,
1732
- payload: {},
1872
+ payload: { exitKind: "graceful" },
1733
1873
  method,
1734
1874
  providerInstanceId
1735
1875
  })
@@ -1740,43 +1880,85 @@ function mapNotificationToEvents(input) {
1740
1880
  type: "turn.started",
1741
1881
  threadId,
1742
1882
  turnId,
1743
- payload: { turnId },
1883
+ payload: {},
1744
1884
  method,
1745
1885
  providerInstanceId
1746
1886
  })
1747
1887
  ];
1748
- case "turn/completed": {
1749
- const flow = turnCompletedControlFlow(params);
1750
- const typeName = flow.status === "cancelled" || flow.status === "interrupted" ? "turn.aborted" : "turn.completed";
1888
+ case "turn/completed":
1751
1889
  return [
1752
1890
  baseEvent({
1753
- type: typeName,
1891
+ type: "turn.completed",
1754
1892
  threadId,
1755
1893
  turnId,
1756
- payload: flow,
1894
+ payload: turnCompletedControlFlow(params),
1895
+ method,
1896
+ providerInstanceId
1897
+ })
1898
+ ];
1899
+ case "turn/aborted":
1900
+ return [
1901
+ baseEvent({
1902
+ type: "turn.aborted",
1903
+ threadId,
1904
+ turnId,
1905
+ payload: {
1906
+ reason: (isRecord3(params) ? trimText(params["reason"]) : void 0) ?? (isRecord3(params) ? trimText(params["message"]) : void 0) ?? "Turn aborted"
1907
+ },
1757
1908
  method,
1758
1909
  providerInstanceId
1759
1910
  })
1760
1911
  ];
1761
- }
1762
1912
  case "turn/plan":
1913
+ case "turn/plan/updated": {
1914
+ const rawPlan = isRecord3(params) && Array.isArray(params["plan"]) ? params["plan"] : [];
1915
+ const plan = rawPlan.filter(isRecord3).map((step) => ({
1916
+ step: trimText(step["step"]) ?? "step",
1917
+ status: step["status"] === "completed" || step["status"] === "inProgress" ? step["status"] : "pending"
1918
+ }));
1919
+ const explanation = isRecord3(params) ? trimText(params["explanation"]) : void 0;
1763
1920
  return [
1764
1921
  baseEvent({
1765
1922
  type: "turn.plan.updated",
1766
1923
  threadId,
1767
1924
  turnId,
1768
- payload: params,
1925
+ payload: {
1926
+ ...explanation ? { explanation } : {},
1927
+ plan
1928
+ },
1929
+ method,
1930
+ providerInstanceId
1931
+ })
1932
+ ];
1933
+ }
1934
+ case "turn/diff/updated": {
1935
+ const unifiedDiff = isRecord3(params) ? asString(params["diff"]) : void 0;
1936
+ if (unifiedDiff === void 0) return [];
1937
+ return [
1938
+ baseEvent({
1939
+ type: "turn.diff.updated",
1940
+ threadId,
1941
+ turnId,
1942
+ payload: { unifiedDiff },
1769
1943
  method,
1770
1944
  providerInstanceId
1771
1945
  })
1772
1946
  ];
1947
+ }
1773
1948
  case "item/started": {
1774
1949
  if (type && normalizeItemType(type).includes("compact")) {
1775
1950
  return [
1776
1951
  baseEvent({
1777
1952
  type: "thread.state.changed",
1778
1953
  threadId,
1779
- payload: { state: "compacting" },
1954
+ payload: {
1955
+ state: "compacting",
1956
+ compaction: {
1957
+ compactionId: codexCompactionId(threadId, turnId),
1958
+ trigger: "automatic"
1959
+ },
1960
+ ...params !== void 0 ? { detail: params } : {}
1961
+ },
1780
1962
  turnId,
1781
1963
  itemId,
1782
1964
  method,
@@ -1784,6 +1966,8 @@ function mapNotificationToEvents(input) {
1784
1966
  })
1785
1967
  ];
1786
1968
  }
1969
+ const canonical = toCanonicalItemType(type);
1970
+ if (canonical === "unknown") return [];
1787
1971
  return [
1788
1972
  baseEvent({
1789
1973
  type: "item.started",
@@ -1791,9 +1975,11 @@ function mapNotificationToEvents(input) {
1791
1975
  turnId,
1792
1976
  itemId,
1793
1977
  payload: {
1794
- itemType: toCanonicalItemType(type),
1795
- title: itemTitle(params),
1796
- detail: itemDetail(params)
1978
+ itemType: canonical,
1979
+ status: "inProgress",
1980
+ ...itemTitle(params) ? { title: itemTitle(params) } : {},
1981
+ ...itemDetail(params) ? { detail: itemDetail(params) } : {},
1982
+ ...params !== void 0 ? { data: params } : {}
1797
1983
  },
1798
1984
  method,
1799
1985
  providerInstanceId
@@ -1806,7 +1992,14 @@ function mapNotificationToEvents(input) {
1806
1992
  baseEvent({
1807
1993
  type: "thread.state.changed",
1808
1994
  threadId,
1809
- payload: { state: "compacted" },
1995
+ payload: {
1996
+ state: "compacted",
1997
+ compaction: {
1998
+ compactionId: codexCompactionId(threadId, turnId),
1999
+ trigger: "automatic"
2000
+ },
2001
+ ...params !== void 0 ? { detail: params } : {}
2002
+ },
1810
2003
  turnId,
1811
2004
  itemId,
1812
2005
  method,
@@ -1815,18 +2008,24 @@ function mapNotificationToEvents(input) {
1815
2008
  ];
1816
2009
  }
1817
2010
  if (type && normalizeItemType(type).includes("plan")) {
2011
+ const planMarkdown = itemDetail(params);
2012
+ if (!planMarkdown) return [];
1818
2013
  return [
1819
2014
  baseEvent({
1820
2015
  type: "turn.proposed.completed",
1821
2016
  threadId,
1822
2017
  turnId,
1823
2018
  itemId,
1824
- payload: params,
2019
+ payload: { planMarkdown },
1825
2020
  method,
1826
2021
  providerInstanceId
1827
2022
  })
1828
2023
  ];
1829
2024
  }
2025
+ const canonical = toCanonicalItemType(type);
2026
+ if (canonical === "unknown") return [];
2027
+ const rawStatus = itemStatusOf(params);
2028
+ const status = rawStatus === "failed" || rawStatus === "declined" ? rawStatus : "completed";
1830
2029
  return [
1831
2030
  baseEvent({
1832
2031
  type: "item.completed",
@@ -1834,45 +2033,62 @@ function mapNotificationToEvents(input) {
1834
2033
  turnId,
1835
2034
  itemId,
1836
2035
  payload: {
1837
- itemType: toCanonicalItemType(type),
1838
- title: itemTitle(params),
1839
- detail: itemDetail(params)
2036
+ itemType: canonical,
2037
+ status,
2038
+ ...itemTitle(params) ? { title: itemTitle(params) } : {},
2039
+ ...itemDetail(params) ? { detail: itemDetail(params) } : {},
2040
+ ...params !== void 0 ? { data: params } : {}
1840
2041
  },
1841
2042
  method,
1842
2043
  providerInstanceId
1843
- }),
1844
- ...producedImages(params).map((event) => ({ ...event, threadId }))
2044
+ })
1845
2045
  ];
1846
2046
  }
1847
- case "item/plan/delta":
2047
+ case "item/plan/delta": {
2048
+ const delta = isRecord3(params) ? asString(params["delta"]) : void 0;
2049
+ if (!delta) return [];
1848
2050
  return [
1849
2051
  baseEvent({
1850
2052
  type: "turn.proposed.delta",
1851
2053
  threadId,
1852
2054
  turnId,
1853
2055
  itemId,
1854
- payload: params,
2056
+ payload: { delta },
1855
2057
  method,
1856
2058
  providerInstanceId
1857
2059
  })
1858
2060
  ];
2061
+ }
1859
2062
  case "item/agentMessage/delta":
1860
- case "item/reasoning/delta":
1861
- case "item/reasoning/summaryDelta": {
2063
+ case "item/reasoning/textDelta":
2064
+ case "item/reasoning/summaryTextDelta":
2065
+ case "item/commandExecution/outputDelta":
2066
+ case "item/fileChange/outputDelta": {
1862
2067
  const delta = isRecord3(params) ? asString(params["delta"]) ?? asString(params["text"]) ?? "" : "";
2068
+ if (delta.length === 0) return [];
2069
+ const streamKind = method === "item/agentMessage/delta" ? "assistant_text" : method === "item/reasoning/textDelta" ? "reasoning_text" : method === "item/reasoning/summaryTextDelta" ? "reasoning_summary_text" : method === "item/commandExecution/outputDelta" ? "command_output" : "file_change_output";
2070
+ const summaryIndex = method === "item/reasoning/summaryTextDelta" && isRecord3(params) ? asNumber(params["summaryIndex"]) : null;
2071
+ const contentIndex = method === "item/reasoning/textDelta" && isRecord3(params) ? asNumber(params["contentIndex"]) : null;
1863
2072
  return [
1864
2073
  baseEvent({
1865
2074
  type: "content.delta",
1866
2075
  threadId,
1867
2076
  turnId,
1868
2077
  itemId,
1869
- payload: { kind: contentStreamKindFromMethod(method), delta },
2078
+ payload: {
2079
+ streamKind,
2080
+ delta,
2081
+ ...summaryIndex !== null ? { summaryIndex } : {},
2082
+ ...contentIndex !== null ? { contentIndex } : {}
2083
+ },
1870
2084
  method,
1871
2085
  providerInstanceId
1872
2086
  })
1873
2087
  ];
1874
2088
  }
1875
- case "item/reasoning/summaryPartAdded":
2089
+ case "item/reasoning/summaryPartAdded": {
2090
+ const summaryIndex = isRecord3(params) ? asNumber(params["summaryIndex"]) : null;
2091
+ if (summaryIndex === null) return [];
1876
2092
  return [
1877
2093
  baseEvent({
1878
2094
  type: "content.delta",
@@ -1880,59 +2096,176 @@ function mapNotificationToEvents(input) {
1880
2096
  turnId,
1881
2097
  itemId,
1882
2098
  payload: {
1883
- kind: "reasoning",
2099
+ streamKind: "reasoning_summary_text",
1884
2100
  delta: "",
1885
- summaryIndex: isRecord3(params) ? asNumber(params["summaryIndex"]) : null
2101
+ summaryIndex
2102
+ },
2103
+ method,
2104
+ providerInstanceId
2105
+ })
2106
+ ];
2107
+ }
2108
+ case "item/mcpToolCall/progress": {
2109
+ const summary = isRecord3(params) ? trimText(params["message"]) : void 0;
2110
+ if (!summary) return [];
2111
+ return [
2112
+ baseEvent({
2113
+ type: "tool.progress",
2114
+ threadId,
2115
+ turnId,
2116
+ itemId,
2117
+ payload: { summary },
2118
+ method,
2119
+ providerInstanceId
2120
+ })
2121
+ ];
2122
+ }
2123
+ case "item/commandExecution/terminalInteraction":
2124
+ return [
2125
+ baseEvent({
2126
+ type: "item.updated",
2127
+ threadId,
2128
+ turnId,
2129
+ itemId,
2130
+ payload: {
2131
+ itemType: "command_execution",
2132
+ ...params !== void 0 ? { data: params } : {}
1886
2133
  },
1887
2134
  method,
1888
2135
  providerInstanceId
1889
2136
  })
1890
2137
  ];
1891
2138
  case "account/updated":
2139
+ return [
2140
+ baseEvent({
2141
+ type: "account.updated",
2142
+ threadId,
2143
+ payload: { account: params ?? {} },
2144
+ method,
2145
+ providerInstanceId
2146
+ })
2147
+ ];
1892
2148
  case "account/rateLimits/updated":
1893
2149
  return [
1894
2150
  baseEvent({
1895
- type: method === "account/updated" ? "account.updated" : "account.rate-limits.updated",
2151
+ type: "account.rate-limits.updated",
1896
2152
  threadId,
1897
- payload: params,
2153
+ payload: {
2154
+ rateLimits: isRecord3(params) && "rateLimits" in params ? params["rateLimits"] : params ?? {}
2155
+ },
1898
2156
  method,
1899
2157
  providerInstanceId
1900
2158
  })
1901
2159
  ];
1902
- case "model/rerouted":
2160
+ case "model/rerouted": {
2161
+ const fromModel = isRecord3(params) ? trimText(params["fromModel"]) : void 0;
2162
+ const toModel = isRecord3(params) ? trimText(params["toModel"]) : void 0;
2163
+ const reason = isRecord3(params) ? trimText(params["reason"]) : void 0;
2164
+ if (!fromModel || !toModel || !reason) return [];
1903
2165
  return [
1904
2166
  baseEvent({
1905
2167
  type: "model.rerouted",
1906
2168
  threadId,
1907
- payload: params,
2169
+ turnId,
2170
+ payload: { fromModel, toModel, reason },
1908
2171
  method,
1909
2172
  providerInstanceId
1910
2173
  })
1911
2174
  ];
1912
- case "config/warning":
2175
+ }
2176
+ case "configWarning":
2177
+ case "deprecationNotice": {
2178
+ const summary = isRecord3(params) ? trimText(params["summary"]) : void 0;
2179
+ if (!summary) return [];
2180
+ const details = isRecord3(params) ? trimText(params["details"]) : void 0;
1913
2181
  return [
1914
2182
  baseEvent({
1915
- type: "config.warning",
2183
+ type: method === "configWarning" ? "config.warning" : "deprecation.notice",
1916
2184
  threadId,
1917
- payload: params,
2185
+ payload: {
2186
+ summary,
2187
+ ...details ? { details } : {},
2188
+ ...method === "configWarning" && isRecord3(params) ? {
2189
+ ...trimText(params["path"]) ? { path: trimText(params["path"]) } : {},
2190
+ ...params["range"] !== void 0 && params["range"] !== null ? { range: params["range"] } : {}
2191
+ } : {}
2192
+ },
1918
2193
  method,
1919
2194
  providerInstanceId
1920
2195
  })
1921
2196
  ];
1922
- case "mcp/oauth/completed":
2197
+ }
2198
+ case "mcpServer/oauthLogin/completed": {
2199
+ const success = isRecord3(params) ? params["success"] : void 0;
2200
+ if (typeof success !== "boolean") return [];
2201
+ const name = isRecord3(params) ? trimText(params["name"]) : void 0;
2202
+ const error = isRecord3(params) ? trimText(params["error"]) : void 0;
1923
2203
  return [
1924
2204
  baseEvent({
1925
2205
  type: "mcp.oauth.completed",
1926
2206
  threadId,
1927
- payload: params,
2207
+ payload: {
2208
+ success,
2209
+ ...name ? { name } : {},
2210
+ ...error ? { error } : {}
2211
+ },
1928
2212
  method,
1929
2213
  providerInstanceId
1930
2214
  })
1931
2215
  ];
2216
+ }
1932
2217
  default:
1933
2218
  return [];
1934
2219
  }
1935
2220
  }
2221
+ function toRequestTypeFromKind(kind) {
2222
+ switch (kind) {
2223
+ case "command":
2224
+ return "command_execution_approval";
2225
+ case "file-read":
2226
+ return "file_read_approval";
2227
+ case "file-change":
2228
+ return "file_change_approval";
2229
+ case "apply-patch":
2230
+ return "apply_patch_approval";
2231
+ case "mcp-elicitation":
2232
+ return "mcp_elicitation_approval";
2233
+ default:
2234
+ return "unknown";
2235
+ }
2236
+ }
2237
+ function toRequestTypeFromMethod(method) {
2238
+ switch (method) {
2239
+ case "item/commandExecution/requestApproval":
2240
+ return "command_execution_approval";
2241
+ case "execCommandApproval":
2242
+ return "exec_command_approval";
2243
+ case "item/fileRead/requestApproval":
2244
+ return "file_read_approval";
2245
+ case "item/fileChange/requestApproval":
2246
+ return "file_change_approval";
2247
+ case "applyPatchApproval":
2248
+ return "apply_patch_approval";
2249
+ case "mcpServer/elicitation/request":
2250
+ return "mcp_elicitation_approval";
2251
+ case "item/tool/requestUserInput":
2252
+ return "tool_user_input";
2253
+ case "item/tool/call":
2254
+ return "dynamic_tool_call";
2255
+ default:
2256
+ return "unknown";
2257
+ }
2258
+ }
2259
+ function elicitationOptions(params) {
2260
+ const persist = persistFromMeta(isRecord3(params) ? params["_meta"] : void 0);
2261
+ return [
2262
+ { decision: "cancel", label: "Cancel" },
2263
+ { decision: "decline", label: "Decline" },
2264
+ ...persist === "session" ? [{ decision: "acceptForSession", label: "Always allow this session" }] : [],
2265
+ ...persist === "always" ? [{ decision: "acceptAlways", label: "Always allow" }] : [],
2266
+ { decision: "accept", label: "Approve" }
2267
+ ];
2268
+ }
1936
2269
  function mapServerRequestToEvents(input) {
1937
2270
  const requestId = String(input.id);
1938
2271
  if (input.method === "item/tool/requestUserInput") {
@@ -1950,18 +2283,44 @@ function mapServerRequestToEvents(input) {
1950
2283
  })
1951
2284
  ];
1952
2285
  }
1953
- const kind = input.method === "item/commandExecution/requestApproval" ? "command" : input.method === "item/fileChange/requestApproval" ? "file-change" : input.method === "mcpServer/elicitation/request" ? "mcp-elicitation" : input.method.includes("applyPatch") ? "apply-patch" : "command";
1954
- const args = kind === "command" ? describeCommandApproval(input.params) : kind === "file-change" || kind === "apply-patch" ? describeFileChangeApproval(input.params) : describeMcpElicitation(input.params);
2286
+ const requestType = toRequestTypeFromMethod(input.method);
2287
+ const payload = {
2288
+ requestType,
2289
+ // Codex 的审批回复只有一个枚举,理由随下一条 prompt 送达。
2290
+ declineReasonRelay: "queued"
2291
+ };
2292
+ if (input.method === "mcpServer/elicitation/request") {
2293
+ const described = describeMcpElicitation(input.params);
2294
+ const appName = described.appName ?? described.serverName ?? void 0;
2295
+ payload["options"] = elicitationOptions(input.params);
2296
+ if (appName) payload["appName"] = appName;
2297
+ if (described.message) payload["detail"] = described.message;
2298
+ payload["args"] = input.params;
2299
+ } else if (input.method === "item/fileChange/requestApproval" || input.method.includes("applyPatch")) {
2300
+ const described = describeFileChangeApproval(input.params);
2301
+ if (described.reason) payload["detail"] = described.reason;
2302
+ if (described.fileChanges.length > 0) payload["fileChanges"] = described.fileChanges;
2303
+ payload["args"] = {
2304
+ ...described.callId ? { callId: described.callId } : {},
2305
+ ...described.reason ? { reason: described.reason } : {},
2306
+ ...described.grantRoot ? { grantRoot: described.grantRoot } : {}
2307
+ };
2308
+ } else {
2309
+ const described = describeCommandApproval(input.params);
2310
+ const detail = described.command || described.reason;
2311
+ if (detail) payload["detail"] = detail;
2312
+ payload["args"] = {
2313
+ command: described.command,
2314
+ ...described.cwd ? { cwd: described.cwd } : {},
2315
+ ...described.reason ? { reason: described.reason } : {}
2316
+ };
2317
+ }
1955
2318
  return [
1956
2319
  baseEvent({
1957
2320
  type: "request.opened",
1958
2321
  threadId: input.threadId,
1959
2322
  requestId,
1960
- payload: {
1961
- kind,
1962
- args,
1963
- declineReasonRelay: "queued"
1964
- },
2323
+ payload,
1965
2324
  method: input.method,
1966
2325
  source: "codex.app-server.request",
1967
2326
  providerInstanceId: input.providerInstanceId
@@ -1990,12 +2349,15 @@ var CodexSessionRuntime = class {
1990
2349
  activeTurnId;
1991
2350
  model;
1992
2351
  runtimeMode;
2352
+ /** 宿主解好的这一轮的档(座位画的就是它)。缺席 = 宿主没给,按 `runtimeMode` 查表。 */
2353
+ nativeModeId;
1993
2354
  interactionMode;
1994
2355
  closed = false;
1995
2356
  closeCompleted = false;
1996
2357
  turns = [];
1997
2358
  pendingApprovals = /* @__PURE__ */ new Map();
1998
2359
  pendingUserInputs = /* @__PURE__ */ new Map();
2360
+ requestKinds = /* @__PURE__ */ new Map();
1999
2361
  writeAcks = /* @__PURE__ */ new Map();
2000
2362
  grants;
2001
2363
  declineRelay;
@@ -2007,6 +2369,16 @@ var CodexSessionRuntime = class {
2007
2369
  host;
2008
2370
  rpc;
2009
2371
  emit;
2372
+ /** 这一轮该用哪套三字段组合:只由这一轮的档决定(见 `codexNativeModeFor`)。 */
2373
+ #threadConfig() {
2374
+ return codexThreadConfigForNativeMode(codexNativeModeFor(this.runtimeMode, this.nativeModeId));
2375
+ }
2376
+ /** 同上,沙箱策略那一位。 */
2377
+ #turnSandboxPolicy() {
2378
+ return codexTurnSandboxPolicyForNativeMode(
2379
+ codexNativeModeFor(this.runtimeMode, this.nativeModeId)
2380
+ );
2381
+ }
2010
2382
  constructor(host, rpc, emit, input, cwd, appServerArgs, grants, declineRelay) {
2011
2383
  this.host = host;
2012
2384
  this.rpc = rpc;
@@ -2014,6 +2386,7 @@ var CodexSessionRuntime = class {
2014
2386
  this.threadId = input.threadId;
2015
2387
  this.cwd = cwd;
2016
2388
  this.runtimeMode = input.runtimeMode;
2389
+ this.nativeModeId = input.nativeModeId;
2017
2390
  this.appServerArgs = appServerArgs;
2018
2391
  this.grants = grants ?? createApprovalGrantStore();
2019
2392
  this.declineRelay = declineRelay ?? createDeclineReasonRelay();
@@ -2023,6 +2396,7 @@ var CodexSessionRuntime = class {
2023
2396
  providerInstanceId: host.instanceId,
2024
2397
  status: "idle",
2025
2398
  runtimeMode: input.runtimeMode,
2399
+ nativeModeId: codexNativeModeFor(input.runtimeMode, input.nativeModeId),
2026
2400
  cwd,
2027
2401
  threadId: input.threadId,
2028
2402
  createdAt,
@@ -2102,6 +2476,24 @@ var CodexSessionRuntime = class {
2102
2476
  this.dispatchMapped(method, params);
2103
2477
  }
2104
2478
  dispatchMapped(method, params) {
2479
+ if (method === "serverRequest/resolved") {
2480
+ const resolvedId = (isRecord3(params) ? asString(params["requestId"]) : null) ?? void 0;
2481
+ if (resolvedId !== void 0) {
2482
+ this.emit({
2483
+ eventId: `codex-req-resolved-${resolvedId}`,
2484
+ provider: CODEX_DRIVER_KIND,
2485
+ threadId: this.threadId,
2486
+ createdAt: nowIso(),
2487
+ type: "request.resolved",
2488
+ requestId: resolvedId,
2489
+ payload: {
2490
+ requestType: toRequestTypeFromKind(this.requestKinds.get(resolvedId)),
2491
+ resolution: params
2492
+ }
2493
+ });
2494
+ }
2495
+ return;
2496
+ }
2105
2497
  for (const event of mapNotificationToEvents({
2106
2498
  method,
2107
2499
  params,
@@ -2280,6 +2672,7 @@ var CodexSessionRuntime = class {
2280
2672
  threadId: this.threadId,
2281
2673
  providerInstanceId: this.host.instanceId
2282
2674
  });
2675
+ this.requestKinds.set(requestId, kind);
2283
2676
  await new Promise((resolve2) => {
2284
2677
  this.pendingApprovals.set(requestId, {
2285
2678
  jsonRpcId: id,
@@ -2319,14 +2712,22 @@ var CodexSessionRuntime = class {
2319
2712
  createdAt: nowIso(),
2320
2713
  type: "request.resolved",
2321
2714
  requestId,
2322
- payload: { decision }
2715
+ // requestType 是契约必填;缺了它宿主整条解不动,卡片停在「等待决议」。
2716
+ payload: {
2717
+ requestType: toRequestTypeFromKind(pending.kind),
2718
+ decision,
2719
+ ...reason ? { reason } : {}
2720
+ }
2323
2721
  });
2324
2722
  }
2325
2723
  async respondToUserInput(requestId, answers) {
2326
2724
  const pending = this.pendingUserInputs.get(requestId);
2327
2725
  if (!pending) throw new Error(`unknown user-input ${requestId}`);
2328
2726
  const mapped = toCodexUserInputAnswers(answers);
2329
- pending.settle(mapped === void 0 ? {} : { answers: mapped });
2727
+ if (mapped === void 0) {
2728
+ throw new Error(`invalid user input answers for request ${requestId}`);
2729
+ }
2730
+ pending.settle({ answers: mapped });
2330
2731
  this.emit({
2331
2732
  eventId: `codex-ui-${requestId}`,
2332
2733
  provider: CODEX_DRIVER_KIND,
@@ -2334,7 +2735,8 @@ var CodexSessionRuntime = class {
2334
2735
  createdAt: nowIso(),
2335
2736
  type: "user-input.resolved",
2336
2737
  requestId,
2337
- payload: { answers: mapped ?? [] }
2738
+ // 契约的 answers 是 questionId → 答案 的 record,不是数组。
2739
+ payload: { answers: toCanonicalUserInputAnswers(mapped) }
2338
2740
  });
2339
2741
  }
2340
2742
  async withActiveWriterRecovery(run, allowUnsubscribe) {
@@ -2368,7 +2770,7 @@ var CodexSessionRuntime = class {
2368
2770
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
2369
2771
  }
2370
2772
  threadConfigParams() {
2371
- const config = runtimeModeToThreadConfig(this.runtimeMode);
2773
+ const config = this.#threadConfig();
2372
2774
  return {
2373
2775
  approvalPolicy: config.approvalPolicy,
2374
2776
  sandbox: config.sandbox,
@@ -2419,7 +2821,7 @@ var CodexSessionRuntime = class {
2419
2821
  threadId: this.threadId,
2420
2822
  createdAt: nowIso(),
2421
2823
  type: "session.started",
2422
- payload: { resumeCursor: this.session.resumeCursor }
2824
+ payload: { resume: this.session.resumeCursor }
2423
2825
  });
2424
2826
  return this.session;
2425
2827
  }
@@ -2451,8 +2853,8 @@ var CodexSessionRuntime = class {
2451
2853
  if (hasConfiguredMcpServer(this.appServerArgs)) {
2452
2854
  await this.rpc.request("mcpServer/reload", {}, 0).catch(() => void 0);
2453
2855
  }
2454
- const sandboxPolicy = runtimeModeToTurnSandboxPolicy(this.runtimeMode);
2455
- const config = runtimeModeToThreadConfig(this.runtimeMode);
2856
+ const sandboxPolicy = this.#turnSandboxPolicy();
2857
+ const config = this.#threadConfig();
2456
2858
  const developerInstructions = this.developerInstructions(input);
2457
2859
  const requestedModel = modelIdFromSelection(input.modelSelection) ?? this.model;
2458
2860
  const params = {
@@ -2464,15 +2866,22 @@ var CodexSessionRuntime = class {
2464
2866
  ...requestedModel ? { model: requestedModel } : {},
2465
2867
  ...developerInstructions ? { developerInstructions } : {}
2466
2868
  };
2467
- if (input.interactionMode !== void 0) {
2468
- params["collaborationMode"] = input.interactionMode === "plan" ? "plan" : "default";
2869
+ const interactionMode = typeof input.interactionMode === "string" ? input.interactionMode : void 0;
2870
+ if (interactionMode !== void 0) {
2871
+ params["collaborationMode"] = codexCollaborationMode({
2872
+ interactionMode,
2873
+ model: requestedModel ?? DEFAULT_MODEL,
2874
+ reasoningEffort: modelSelectionStringOption(input.modelSelection, "reasoningEffort") ?? void 0
2875
+ });
2469
2876
  }
2470
2877
  const isSteer = input.expectedTurnId !== void 0;
2471
2878
  const result = isSteer ? await this.rpc.request(
2472
2879
  "turn/steer",
2473
2880
  {
2474
2881
  ...params,
2475
- turnId: input.expectedTurnId
2882
+ // 这一位的名字是 `expectedTurnId`,不是 `turnId`:0.155.1 少了它整条拒收
2883
+ // (`Invalid request: missing field \`expectedTurnId\``),插队的那句话发不出去。
2884
+ expectedTurnId: input.expectedTurnId
2476
2885
  },
2477
2886
  0
2478
2887
  ) : await this.withActiveWriterRecovery(() => this.rpc.request("turn/start", params, 0), false);
@@ -2569,6 +2978,7 @@ var CodexSessionRuntime = class {
2569
2978
  async close() {
2570
2979
  if (this.closeCompleted) return;
2571
2980
  this.closed = true;
2981
+ this.requestKinds.clear();
2572
2982
  this.failPendingNativeWrites("session closed before write confirmation");
2573
2983
  this.settlePendingApprovals("cancel");
2574
2984
  this.settlePendingUserInputs({});
@@ -2792,6 +3202,198 @@ async function readRolloutModel(input, rolloutPath, sameDirectory) {
2792
3202
  return model;
2793
3203
  }
2794
3204
 
3205
+ // src/mcpOAuth.ts
3206
+ async function startCodexMcpOAuth(input) {
3207
+ if (input.recipe.binaryPath.length === 0) {
3208
+ throw new Error("Codex program path is not configured");
3209
+ }
3210
+ const mcp = codexMcpLaunchConfig([input.server]);
3211
+ const env = buildCodexSpawnEnv({
3212
+ recipe: input.recipe,
3213
+ layout: input.layout,
3214
+ launchEnv: { ...input.processEnv, ...mcp.environment },
3215
+ crushApiKeys: shouldCrushApiKeys(input.recipe.authMode)
3216
+ });
3217
+ const handle = await input.host.process.spawn({
3218
+ program: input.recipe.binaryPath,
3219
+ args: [...tokenizeLaunchArgs(input.recipe.launchArgs), ...mcp.args],
3220
+ cwd: input.cwd,
3221
+ owner: { bindingId: input.host.bindingId },
3222
+ env
3223
+ });
3224
+ let settle;
3225
+ const completed = new Promise((resolve2) => {
3226
+ settle = resolve2;
3227
+ });
3228
+ const rpc = await createCodexRpcClient(handle, {
3229
+ onNotification: (notification) => {
3230
+ if (notification.method !== "mcpServer/oauthLogin/completed") return;
3231
+ const params = isRecord3(notification.params) ? notification.params : {};
3232
+ if (asString(params["name"]) !== input.server.name) return;
3233
+ const error = asString(params["error"]);
3234
+ settle?.({
3235
+ success: params["success"] === true,
3236
+ ...error === null || error === void 0 ? {} : { error }
3237
+ });
3238
+ },
3239
+ onServerRequest: () => void 0,
3240
+ onStderrLine: () => void 0
3241
+ });
3242
+ const close = async () => {
3243
+ settle?.({ success: false, error: "cancelled" });
3244
+ await rpc.close().catch(() => void 0);
3245
+ };
3246
+ try {
3247
+ await rpc.request("initialize", CODEX_INITIALIZE_PARAMS);
3248
+ await rpc.notify("initialized");
3249
+ const response = await rpc.request("mcpServer/oauth/login", {
3250
+ name: input.server.name,
3251
+ ...input.timeoutSecs === void 0 ? {} : { timeoutSecs: input.timeoutSecs }
3252
+ });
3253
+ const authorizationUrl = isRecord3(response) ? asString(response["authorizationUrl"]) : void 0;
3254
+ if (!authorizationUrl) {
3255
+ await close();
3256
+ throw new Error(`Codex did not return an authorization URL for '${input.server.name}'.`);
3257
+ }
3258
+ return { authorizationUrl, handle, completed, close };
3259
+ } catch (error) {
3260
+ await close();
3261
+ throw error;
3262
+ }
3263
+ }
3264
+
3265
+ // src/models.ts
3266
+ var MODEL_LIST_MAX_PAGES = 20;
3267
+ var MODEL_LIST_TIMEOUT_MS = 15e3;
3268
+ var REASONING_EFFORT_LABELS = {
3269
+ none: "None",
3270
+ minimal: "Minimal",
3271
+ low: "Low",
3272
+ medium: "Medium",
3273
+ high: "High",
3274
+ xhigh: "Extra High",
3275
+ max: "Max",
3276
+ ultra: "Ultra"
3277
+ };
3278
+ var DEFAULT_SERVICE_TIER_ID = "default";
3279
+ var reasoningEffortLabel = (effort) => REASONING_EFFORT_LABELS[effort] ?? effort;
3280
+ function codexModelDisplayName(displayName) {
3281
+ return displayName.replace(/^gpt/i, "GPT").replace(/-([a-z])/g, (_, c) => `-${c.toUpperCase()}`);
3282
+ }
3283
+ function reasoningDescriptor(row) {
3284
+ const supported = row["supportedReasoningEfforts"];
3285
+ if (!Array.isArray(supported) || supported.length === 0) return null;
3286
+ const defaultEffort = asString(row["defaultReasoningEffort"]);
3287
+ const options = supported.flatMap((entry) => {
3288
+ if (!isRecord3(entry)) return [];
3289
+ const id = asString(entry["reasoningEffort"]);
3290
+ if (!id) return [];
3291
+ const description = asString(entry["description"]);
3292
+ return [
3293
+ {
3294
+ id,
3295
+ label: reasoningEffortLabel(id),
3296
+ ...description ? { description } : {},
3297
+ ...id === defaultEffort ? { isDefault: true } : {}
3298
+ }
3299
+ ];
3300
+ });
3301
+ if (options.length === 0) return null;
3302
+ return {
3303
+ id: "reasoningEffort",
3304
+ label: "Reasoning",
3305
+ type: "select",
3306
+ options,
3307
+ ...defaultEffort ? { currentValue: defaultEffort } : {}
3308
+ };
3309
+ }
3310
+ function serviceTierDescriptor(row) {
3311
+ const declared = Array.isArray(row["serviceTiers"]) ? row["serviceTiers"] : [];
3312
+ const tiers = declared.length > 0 ? declared.flatMap((entry) => {
3313
+ if (!isRecord3(entry)) return [];
3314
+ const id = asString(entry["id"]);
3315
+ if (!id) return [];
3316
+ const name = asString(entry["name"]);
3317
+ const description = asString(entry["description"]);
3318
+ return [{ id, name: name ?? id, ...description ? { description } : {} }];
3319
+ }) : (Array.isArray(row["additionalSpeedTiers"]) ? row["additionalSpeedTiers"] : []).flatMap(
3320
+ (entry) => {
3321
+ const id = typeof entry === "string" ? entry : null;
3322
+ return id === null ? [] : [{ id, name: id === "fast" ? "Fast" : id }];
3323
+ }
3324
+ );
3325
+ if (tiers.length === 0) return null;
3326
+ const declaredDefault = asString(row["defaultServiceTier"]);
3327
+ const defaultTier = declaredDefault && tiers.some((tier) => tier.id === declaredDefault) ? declaredDefault : DEFAULT_SERVICE_TIER_ID;
3328
+ return {
3329
+ id: "serviceTier",
3330
+ label: "Service Tier",
3331
+ type: "select",
3332
+ options: [
3333
+ {
3334
+ id: DEFAULT_SERVICE_TIER_ID,
3335
+ label: "Standard",
3336
+ ...defaultTier === DEFAULT_SERVICE_TIER_ID ? { isDefault: true } : {}
3337
+ },
3338
+ ...tiers.map((tier) => ({
3339
+ id: tier.id,
3340
+ label: tier.name,
3341
+ ...tier.description ? { description: tier.description } : {},
3342
+ ...defaultTier === tier.id ? { isDefault: true } : {}
3343
+ }))
3344
+ ],
3345
+ currentValue: defaultTier
3346
+ };
3347
+ }
3348
+ function isSupersededModel(row) {
3349
+ if (isRecord3(row["upgradeInfo"])) return true;
3350
+ return (asString(row["upgrade"]) ?? "").length > 0;
3351
+ }
3352
+ function codexProbeModel(row) {
3353
+ if (!isRecord3(row)) return null;
3354
+ const slug = asString(row["model"]);
3355
+ if (!slug) return null;
3356
+ const displayName = asString(row["displayName"]);
3357
+ const optionDescriptors = [reasoningDescriptor(row), serviceTierDescriptor(row)].filter(
3358
+ (descriptor) => descriptor !== null
3359
+ );
3360
+ return {
3361
+ slug,
3362
+ ...displayName ? { name: codexModelDisplayName(displayName) } : {},
3363
+ ...row["isDefault"] === true ? { isDefault: true } : {},
3364
+ ...isSupersededModel(row) ? { isLegacy: true } : {},
3365
+ ...row["hidden"] === true ? { hidden: true } : {},
3366
+ // 空表也给 `capabilities`:`{}` 与 `null` 对宿主是一回事(都不画那一格),
3367
+ // 但给对象保住「这只手确实答过这一问」这层意思。
3368
+ capabilities: { optionDescriptors }
3369
+ };
3370
+ }
3371
+ async function listCodexModels(rpc) {
3372
+ const models = [];
3373
+ let cursor;
3374
+ for (let page = 0; page < MODEL_LIST_MAX_PAGES; page++) {
3375
+ let response;
3376
+ try {
3377
+ response = await rpc.request(
3378
+ "model/list",
3379
+ cursor === void 0 ? {} : { cursor },
3380
+ MODEL_LIST_TIMEOUT_MS
3381
+ );
3382
+ } catch {
3383
+ return void 0;
3384
+ }
3385
+ if (!isRecord3(response) || !Array.isArray(response["data"])) return void 0;
3386
+ for (const row of response["data"]) {
3387
+ const model = codexProbeModel(row);
3388
+ if (model !== null) models.push(model);
3389
+ }
3390
+ const next = asString(response["nextCursor"]);
3391
+ if (!next || next === cursor) return models;
3392
+ cursor = next;
3393
+ }
3394
+ return models;
3395
+ }
3396
+
2795
3397
  // src/signIn.ts
2796
3398
  var DEVICE_CODE_URL = /https?:\/\/\S+/;
2797
3399
  var DEVICE_CODE_VALUE = /\b([A-Z0-9]{4,}(?:-[A-Z0-9]{4,})+)\b/;
@@ -2947,22 +3549,26 @@ async function verifyCodexSignIn(input) {
2947
3549
  installed: true,
2948
3550
  version: null,
2949
3551
  readiness: "unchecked",
2950
- auth: { status: "unauthenticated" }
3552
+ auth: { status: "unauthenticated" },
3553
+ ...CODEX_NATIVE_MODE_REPORT
2951
3554
  };
2952
3555
  }
2953
- await rpc.request("model/list", {}, 15e3).catch(() => void 0);
3556
+ const models = await listCodexModels(rpc);
2954
3557
  return {
2955
3558
  installed: true,
2956
3559
  version: null,
2957
3560
  readiness: "ready",
2958
- auth
3561
+ auth,
3562
+ ...models === void 0 ? {} : { models },
3563
+ ...CODEX_NATIVE_MODE_REPORT
2959
3564
  };
2960
3565
  } catch {
2961
3566
  return {
2962
3567
  installed: true,
2963
3568
  version: null,
2964
3569
  readiness: "unchecked",
2965
- auth: { status: "unknown" }
3570
+ auth: { status: "unknown" },
3571
+ ...CODEX_NATIVE_MODE_REPORT
2966
3572
  };
2967
3573
  } finally {
2968
3574
  await rpc.close().catch(() => void 0);
@@ -2973,6 +3579,10 @@ async function verifyCodexSignIn(input) {
2973
3579
  import * as NodeFSP2 from "node:fs/promises";
2974
3580
  import * as NodeOS3 from "node:os";
2975
3581
  import * as NodePath5 from "node:path";
3582
+ import {
3583
+ mergeLaunchEnv as mergeLaunchEnv2,
3584
+ brainSlotEnvKeys as brainSlotEnvKeys2
3585
+ } from "@ycodium-ai/plugin-api/executor";
2976
3586
  var CODEX_TIMEOUT_MS = 18e4;
2977
3587
  var EARLIER_CONTENT_TRUNCATION_MARKER = "[Earlier content truncated]\n\n";
2978
3588
  function limitSection(value, maxChars) {
@@ -3145,14 +3755,12 @@ function createCodexTextGeneration(input) {
3145
3755
  const reasoningEffort = modelSelectionStringOption(modelSelection, "reasoningEffort") ?? DEFAULT_TEXT_GENERATION_REASONING_EFFORT;
3146
3756
  const serviceTier = modelSelectionStringOption(modelSelection, "serviceTier");
3147
3757
  const model = asString(launchRecord.requestedModelId) ?? modelIdFromSelection(modelSelection) ?? "gpt-5.6-luna";
3148
- const env = {
3149
- ...processEnv,
3150
- ...input.recipe.environment,
3151
- ...input.layout.effectiveHomePath ? { CODEX_HOME: input.layout.effectiveHomePath } : input.recipe.homePath.trim() ? { CODEX_HOME: expandHomePath(input.recipe.homePath) } : {}
3152
- };
3153
- if (input.recipe.environment[CODEX_INSTANCE_API_KEY_ENV]) {
3154
- env[CODEX_INSTANCE_API_KEY_ENV] = input.recipe.environment[CODEX_INSTANCE_API_KEY_ENV];
3155
- }
3758
+ const env = mergeLaunchEnv2({
3759
+ brainSlots: brainSlotEnvKeys2(CODEX_CARD),
3760
+ accountHome: input.layout.effectiveHomePath ? { CODEX_HOME: input.layout.effectiveHomePath } : input.recipe.homePath.trim() ? { CODEX_HOME: expandHomePath(input.recipe.homePath) } : {},
3761
+ instanceTable: input.recipe.environment,
3762
+ launch: processEnv
3763
+ });
3156
3764
  const args = [
3157
3765
  "exec",
3158
3766
  ...codexExecLaunchArgs(launchArgs),
@@ -3469,8 +4077,11 @@ function accountToAuth2(account) {
3469
4077
  return { status: "unknown" };
3470
4078
  }
3471
4079
  }
3472
- function layoutFor(recipe, launchHome) {
4080
+ function layoutFor(recipe, launchHome, nativeSubscription = false) {
3473
4081
  const environmentHome = recipe.environment["CODEX_HOME"];
4082
+ if (nativeSubscription && launchHome?.trim()) {
4083
+ return resolveCodexHomeLayout({ homePath: launchHome.trim(), shadowHomePath: "" });
4084
+ }
3474
4085
  return resolveCodexHomeLayout({
3475
4086
  homePath: launchHome?.trim() || recipe.homePath,
3476
4087
  shadowHomePath: recipe.shadowHomePath,
@@ -3490,6 +4101,38 @@ var createCodexInstance = (config, host) => {
3490
4101
  return booked;
3491
4102
  };
3492
4103
  const probeAuth = () => accountToAuth2(readCodexAccount(accountHomeFromLayout(layoutFor(recipe))));
4104
+ const probeModels = async () => {
4105
+ const layout = layoutFor(recipe);
4106
+ const env = buildCodexSpawnEnv({
4107
+ recipe,
4108
+ layout,
4109
+ crushApiKeys: shouldCrushApiKeys(recipe.authMode)
4110
+ });
4111
+ let handle;
4112
+ let rpc;
4113
+ try {
4114
+ handle = await host.process.spawn({
4115
+ program: recipe.binaryPath,
4116
+ args: [...codexSpawnArgs(recipe, layout)],
4117
+ owner: { bindingId: host.bindingId },
4118
+ ...Object.keys(env).length > 0 ? { env } : {}
4119
+ });
4120
+ trackHandle(handle, `${recipe.binaryPath} (probe)`);
4121
+ rpc = await createCodexRpcClient(handle, {
4122
+ onNotification: () => void 0,
4123
+ onServerRequest: () => void 0,
4124
+ onStderrLine: () => void 0
4125
+ });
4126
+ await rpc.request("initialize", CODEX_INITIALIZE_PARAMS);
4127
+ await rpc.notify("initialized");
4128
+ return await listCodexModels(rpc);
4129
+ } catch {
4130
+ return void 0;
4131
+ } finally {
4132
+ await rpc?.close().catch(() => void 0);
4133
+ await handle?.kill("SIGTERM").catch(() => void 0);
4134
+ }
4135
+ };
3493
4136
  const trackHandle = (handle, command, threadId) => {
3494
4137
  live.set(handle.pid, { command, ...threadId === void 0 ? {} : { threadId } });
3495
4138
  void handle.exited.finally(() => {
@@ -3517,14 +4160,20 @@ var createCodexInstance = (config, host) => {
3517
4160
  version: null,
3518
4161
  readiness: "error",
3519
4162
  auth: probeAuth(),
3520
- detail: connectionError
4163
+ detail: connectionError,
4164
+ ...CODEX_NATIVE_MODE_REPORT
3521
4165
  };
3522
4166
  }
4167
+ const models = await probeModels();
3523
4168
  return {
3524
4169
  installed: true,
3525
4170
  version: null,
3526
- readiness: "unchecked",
3527
- auth: probeAuth()
4171
+ readiness: models === void 0 ? "unchecked" : "ready",
4172
+ auth: probeAuth(),
4173
+ ...models === void 0 ? {} : { models },
4174
+ // 宿主拿的是这一份。登录核验那份(`verifyCodexSignIn`)报了档、这里没报,
4175
+ // 界面就一直画宿主四档——2026-09-20 真发生过,浏览器里才看得见。
4176
+ ...CODEX_NATIVE_MODE_REPORT
3528
4177
  };
3529
4178
  },
3530
4179
  startSession: async (input, signal) => {
@@ -3540,15 +4189,22 @@ var createCodexInstance = (config, host) => {
3540
4189
  await stopBooked(existing);
3541
4190
  threads.delete(input.threadId);
3542
4191
  }
3543
- const layout = layoutFor(recipe, input.launch.homePath);
4192
+ const nativeSubscription = input.launch.nativeSubscription === true;
4193
+ const layout = layoutFor(recipe, input.launch.homePath, nativeSubscription);
3544
4194
  await materializeCodexShadowHome(layout, host.log);
4195
+ const mcp = codexMcpLaunchConfig(input.mcpServers ?? []);
3545
4196
  const env = buildCodexSpawnEnv({
3546
4197
  recipe,
3547
4198
  layout,
3548
- processEnv: input.launch.processEnv,
3549
- crushApiKeys: shouldCrushApiKeys(recipe.authMode)
4199
+ // MCP 那几个变量是宿主挑好的 server 带来的,与绑定同一层:实例表盖不得。
4200
+ launchEnv: { ...input.launch.processEnv, ...mcp.environment },
4201
+ crushApiKeys: nativeSubscription || shouldCrushApiKeys(recipe.authMode)
3550
4202
  });
3551
- const args = [...codexSpawnArgs(recipe, layout)];
4203
+ const args = [
4204
+ ...codexSpawnArgs(recipe, layout, nativeSubscription),
4205
+ ...codexOverrideArgs(input.launch.launchArgOverrides),
4206
+ ...mcp.args
4207
+ ];
3552
4208
  const handle = await host.process.spawn({
3553
4209
  program: recipe.binaryPath,
3554
4210
  args,
@@ -3668,6 +4324,11 @@ var createCodexInstance = (config, host) => {
3668
4324
  recipe,
3669
4325
  layout: layoutFor(recipe)
3670
4326
  }),
4327
+ continuationKey: codexContinuationKey({
4328
+ homePath: recipe.homePath,
4329
+ instanceEnvironment: recipe.environment,
4330
+ inheritedEnvironment: process.env
4331
+ }),
3671
4332
  sessions: {
3672
4333
  scan: async (input) => {
3673
4334
  if (recipe.binaryPath.length === 0) {
@@ -3677,7 +4338,6 @@ var createCodexInstance = (config, host) => {
3677
4338
  const env = buildCodexSpawnEnv({
3678
4339
  recipe,
3679
4340
  layout,
3680
- processEnv: {},
3681
4341
  crushApiKeys: shouldCrushApiKeys(recipe.authMode)
3682
4342
  });
3683
4343
  const args = [...codexSpawnArgs(recipe, layout)];
@@ -3715,7 +4375,6 @@ var createCodexInstance = (config, host) => {
3715
4375
  const env = buildCodexSpawnEnv({
3716
4376
  recipe,
3717
4377
  layout,
3718
- processEnv: {},
3719
4378
  crushApiKeys: shouldCrushApiKeys(recipe.authMode)
3720
4379
  });
3721
4380
  const args = [...codexSpawnArgs(recipe, layout)];
@@ -3776,6 +4435,25 @@ var createCodexInstance = (config, host) => {
3776
4435
  processEnv: process.env
3777
4436
  })
3778
4437
  },
4438
+ mcpOAuth: {
4439
+ start: async (input) => {
4440
+ const started = await startCodexMcpOAuth({
4441
+ host,
4442
+ recipe,
4443
+ layout: layoutFor(recipe),
4444
+ processEnv: process.env,
4445
+ server: input.server,
4446
+ cwd: input.cwd,
4447
+ ...input.timeoutSecs === void 0 ? {} : { timeoutSecs: input.timeoutSecs }
4448
+ });
4449
+ trackHandle(started.handle, `${recipe.binaryPath} app-server (mcp oauth)`);
4450
+ return {
4451
+ authorizationUrl: started.authorizationUrl,
4452
+ completed: started.completed,
4453
+ close: started.close
4454
+ };
4455
+ }
4456
+ },
3779
4457
  listLiveProcesses: async () => [...live.entries()].map(([pid, entry]) => ({
3780
4458
  pid,
3781
4459
  command: entry.command,