@ycodium-ai/agent-codex 0.2.2098 → 0.2.2126

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 +1028 -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",
@@ -512,6 +547,16 @@ async function ensureSymlink(input) {
512
547
  await createEntryLink(target, link);
513
548
  }
514
549
  }
550
+ async function makeDirectory(directoryPath) {
551
+ try {
552
+ await NodeFSP.mkdir(directoryPath, { recursive: true });
553
+ } catch (error) {
554
+ throw new Error(
555
+ `Codex shadow home filesystem operation 'makeDirectory' failed for '${directoryPath}': ${String(error)}`,
556
+ { cause: error }
557
+ );
558
+ }
559
+ }
515
560
  async function materializeCodexShadowHome(layout, log) {
516
561
  if (layout.mode !== "authOverlay") return;
517
562
  const effectiveHomePath = layout.effectiveHomePath;
@@ -521,11 +566,11 @@ async function materializeCodexShadowHome(layout, log) {
521
566
  `Codex shadow home path '${effectiveHomePath}' must be different from the shared home path '${layout.sharedHomePath}'.`
522
567
  );
523
568
  }
524
- await NodeFSP.mkdir(layout.sharedHomePath, { recursive: true });
525
- await NodeFSP.mkdir(effectiveHomePath, { recursive: true });
569
+ await makeDirectory(layout.sharedHomePath);
570
+ await makeDirectory(effectiveHomePath);
526
571
  await Promise.all(
527
572
  KNOWN_SHARED_DIRECTORIES.map(
528
- (directory) => NodeFSP.mkdir(NodePath3.join(layout.sharedHomePath, directory), { recursive: true })
573
+ (directory) => makeDirectory(NodePath3.join(layout.sharedHomePath, directory))
529
574
  )
530
575
  );
531
576
  let sharedEntryNames = [];
@@ -584,6 +629,73 @@ var ACTIVE_WRITER_ATTEMPTS = 5;
584
629
  var ACTIVE_WRITER_DELAY_MS = 200;
585
630
  var DEFAULT_MODEL = "gpt-5.6-sol";
586
631
  var DEFAULT_TEXT_GENERATION_REASONING_EFFORT = "low";
632
+ var CODEX_NATIVE_MODES = [
633
+ {
634
+ id: "read-only",
635
+ name: "Read only",
636
+ description: "Sandboxed to reading. Asks before anything that writes or runs."
637
+ },
638
+ {
639
+ id: "workspace-write",
640
+ name: "Workspace write",
641
+ description: "Writes inside the workspace. Asks you before stepping outside it."
642
+ },
643
+ {
644
+ id: "auto-review",
645
+ name: "Auto review",
646
+ description: "Writes inside the workspace. Codex approves its own requests, not you."
647
+ },
648
+ {
649
+ id: "full-access",
650
+ name: "Full access",
651
+ description: "No sandbox, never asks. Every command runs immediately."
652
+ }
653
+ ];
654
+ var CODEX_DEFAULT_NATIVE_MODE_ID = "read-only";
655
+ var CODEX_NATIVE_MODE_BY_RUNTIME_MODE = {
656
+ "approval-required": "read-only",
657
+ "auto-accept-edits": "workspace-write",
658
+ auto: "auto-review",
659
+ "full-access": "full-access"
660
+ };
661
+ var CODEX_NATIVE_MODE_REPORT = {
662
+ nativeModes: CODEX_NATIVE_MODES.map((mode) => ({
663
+ id: mode.id,
664
+ name: mode.name,
665
+ description: mode.description
666
+ })),
667
+ currentNativeModeId: CODEX_DEFAULT_NATIVE_MODE_ID,
668
+ nativeModeByRuntimeMode: CODEX_NATIVE_MODE_BY_RUNTIME_MODE
669
+ };
670
+ function isCodexNativeModeId(value) {
671
+ return CODEX_NATIVE_MODES.some((mode) => mode.id === value);
672
+ }
673
+ function codexNativeModeFor(runtimeMode, nativeModeId) {
674
+ return nativeModeId !== void 0 && isCodexNativeModeId(nativeModeId) ? nativeModeId : CODEX_NATIVE_MODE_BY_RUNTIME_MODE[runtimeMode];
675
+ }
676
+ function codexThreadConfigForNativeMode(nativeModeId) {
677
+ switch (nativeModeId) {
678
+ case "workspace-write":
679
+ return runtimeModeToThreadConfig("auto-accept-edits");
680
+ case "auto-review":
681
+ return runtimeModeToThreadConfig("auto");
682
+ case "full-access":
683
+ return runtimeModeToThreadConfig("full-access");
684
+ default:
685
+ return runtimeModeToThreadConfig("approval-required");
686
+ }
687
+ }
688
+ function codexTurnSandboxPolicyForNativeMode(nativeModeId) {
689
+ switch (nativeModeId) {
690
+ case "workspace-write":
691
+ case "auto-review":
692
+ return runtimeModeToTurnSandboxPolicy("auto-accept-edits");
693
+ case "full-access":
694
+ return runtimeModeToTurnSandboxPolicy("full-access");
695
+ default:
696
+ return runtimeModeToTurnSandboxPolicy("approval-required");
697
+ }
698
+ }
587
699
  function runtimeModeToThreadConfig(mode) {
588
700
  switch (mode) {
589
701
  case "approval-required":
@@ -617,6 +729,16 @@ function runtimeModeToTurnSandboxPolicy(mode) {
617
729
  return { type: "dangerFullAccess" };
618
730
  }
619
731
  }
732
+ function codexCollaborationMode(input) {
733
+ const effort = input.reasoningEffort?.trim();
734
+ return {
735
+ mode: input.interactionMode === "plan" ? "plan" : "default",
736
+ settings: {
737
+ model: input.model,
738
+ ...effort ? { reasoning_effort: effort } : {}
739
+ }
740
+ };
741
+ }
620
742
  function isRecord3(value) {
621
743
  return typeof value === "object" && value !== null && !Array.isArray(value);
622
744
  }
@@ -697,11 +819,19 @@ var CHILD_AGENT_EVENT_METHODS = /* @__PURE__ */ new Set([
697
819
  ]);
698
820
  var CHILD_CHATTER_METHODS = /* @__PURE__ */ new Set([
699
821
  "item/agentMessage/delta",
700
- "item/reasoning/delta",
701
- "item/reasoning/summaryDelta",
822
+ "item/reasoning/textDelta",
823
+ "item/reasoning/summaryTextDelta",
702
824
  "item/reasoning/summaryPartAdded",
825
+ "item/commandExecution/outputDelta",
826
+ "item/fileChange/outputDelta",
827
+ "item/fileChange/patchUpdated",
703
828
  "item/plan/delta",
829
+ "turn/plan/updated",
830
+ "turn/diff/updated",
704
831
  "thread/name/updated",
832
+ "thread/settings/updated",
833
+ "rawResponseItem/completed",
834
+ // 子线程自己的生命周期通知不能改父线程状态(旧宿主 runtime 同名单)。
705
835
  "thread/archived",
706
836
  "thread/unarchived",
707
837
  "thread/compacted",
@@ -1120,95 +1250,6 @@ function buildCodexDeveloperInstructions(interactionMode, runtime, browserToolsA
1120
1250
  <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
1251
  }
1122
1252
 
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
1253
  // src/events.ts
1213
1254
  var CODEX_PROVIDER = "codex";
1214
1255
  var eventSeq = 0;
@@ -1225,28 +1266,27 @@ function nowIso2() {
1225
1266
  return (/* @__PURE__ */ new Date()).toISOString();
1226
1267
  }
1227
1268
  function normalizeItemType(value) {
1228
- if (!value) return "unknown";
1229
- return value.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_/.-]+/g, " ").toLowerCase().trim();
1269
+ if (!value) return "item";
1270
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[._/-]/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
1230
1271
  }
1231
1272
  function toCanonicalItemType(raw) {
1232
1273
  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";
1274
+ if (type.includes("user")) return "user_message";
1275
+ if (type.includes("agent message") || type.includes("assistant")) return "assistant_message";
1276
+ if (type.includes("reasoning") || type.includes("thought")) return "reasoning";
1237
1277
  if (type.includes("plan") || type.includes("todo")) return "plan";
1238
- if (type.includes("command")) return "command";
1278
+ if (type.includes("command")) return "command_execution";
1239
1279
  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";
1280
+ return "file_change";
1281
+ }
1282
+ if (type.includes("mcp")) return "mcp_tool_call";
1283
+ if (type.includes("dynamic tool")) return "dynamic_tool_call";
1284
+ if (type.includes("collab")) return "collab_agent_tool_call";
1285
+ if (type.includes("web search")) return "web_search";
1286
+ if (type.includes("image")) return "image_view";
1287
+ if (type.includes("review entered")) return "review_entered";
1288
+ if (type.includes("review exited")) return "review_exited";
1289
+ if (type.includes("compact")) return "context_compaction";
1250
1290
  if (type.includes("error")) return "error";
1251
1291
  return "unknown";
1252
1292
  }
@@ -1255,6 +1295,11 @@ function itemIdOf(params) {
1255
1295
  const item = isRecord3(params["item"]) ? params["item"] : params;
1256
1296
  return asString(item["id"]) ?? asString(params["itemId"]) ?? void 0;
1257
1297
  }
1298
+ function itemStatusOf(params) {
1299
+ if (!isRecord3(params)) return void 0;
1300
+ const item = isRecord3(params["item"]) ? params["item"] : params;
1301
+ return asString(item["status"]) ?? void 0;
1302
+ }
1258
1303
  function turnIdOf(params) {
1259
1304
  if (!isRecord3(params)) return void 0;
1260
1305
  return asString(params["turnId"]) ?? (isRecord3(params["turn"]) ? asString(params["turn"]["id"]) : null) ?? void 0;
@@ -1287,45 +1332,45 @@ function toTurnStatus(status) {
1287
1332
  return "completed";
1288
1333
  }
1289
1334
  }
1290
- function toThreadState(status) {
1291
- if (status === "idle") return "idle";
1292
- if (status === "systemError" || status === "error") return "error";
1335
+ function threadStateFromStatus(status) {
1336
+ const type = isRecord3(status) ? asString(status["type"]) : asString(status);
1337
+ if (type === "idle") return "idle";
1338
+ if (type === "systemError" || type === "error") return "error";
1293
1339
  return "active";
1294
1340
  }
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;
1341
+ function codexCompactionId(threadId, turnId) {
1342
+ return `codex:${threadId}:${turnId ?? "session"}`;
1300
1343
  }
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 = {
1344
+ function normalizeCodexTokenUsage(tokenUsage) {
1345
+ if (!isRecord3(tokenUsage)) return void 0;
1346
+ const last = isRecord3(tokenUsage["last"]) ? tokenUsage["last"] : void 0;
1347
+ const total = isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
1348
+ const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1349
+ const usedTokens = count(last?.["totalTokens"]);
1350
+ if (usedTokens === void 0 || usedTokens <= 0) return void 0;
1351
+ const totalProcessedTokens = count(total?.["totalTokens"]);
1352
+ const maxTokens = count(tokenUsage["modelContextWindow"]);
1353
+ const inputTokens = count(last?.["inputTokens"]);
1354
+ const cachedInputTokens = count(last?.["cachedInputTokens"]);
1355
+ const outputTokens = count(last?.["outputTokens"]);
1356
+ const reasoningOutputTokens = count(last?.["reasoningOutputTokens"]);
1357
+ return {
1310
1358
  usedTokens,
1359
+ lastUsedTokens: usedTokens,
1360
+ ...totalProcessedTokens !== void 0 && totalProcessedTokens > usedTokens ? { totalProcessedTokens } : {},
1361
+ ...maxTokens !== void 0 && maxTokens > 0 ? { maxTokens } : {},
1362
+ ...inputTokens !== void 0 ? { inputTokens, lastInputTokens: inputTokens } : {},
1363
+ ...cachedInputTokens !== void 0 ? { cachedInputTokens, lastCachedInputTokens: cachedInputTokens } : {},
1364
+ ...outputTokens !== void 0 ? { outputTokens, lastOutputTokens: outputTokens } : {},
1365
+ ...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens, lastReasoningOutputTokens: reasoningOutputTokens } : {},
1311
1366
  compactsAutomatically: true
1312
1367
  };
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
1368
  }
1324
1369
  function turnCompletedControlFlow(params) {
1325
1370
  const turn = isRecord3(params) ? isRecord3(params["turn"]) ? params["turn"] : params : {};
1326
- const status = toTurnStatus(asString(turn["status"]) ?? void 0);
1371
+ const state = toTurnStatus(asString(turn["status"]) ?? void 0);
1327
1372
  const error = isRecord3(turn["error"]) ? trimText(turn["error"]["message"]) : void 0;
1328
- return error ? { status, errorMessage: error } : { status };
1373
+ return error ? { state, errorMessage: error } : { state };
1329
1374
  }
1330
1375
  function toApplyPatchFileChanges(params) {
1331
1376
  if (!isRecord3(params)) return [];
@@ -1343,10 +1388,33 @@ function toApplyPatchFileChanges(params) {
1343
1388
  if (!path) continue;
1344
1389
  const kind = (asString(entry["type"]) ?? asString(entry["kind"]) ?? "update").toLowerCase();
1345
1390
  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" });
1391
+ const diff = asString(entry["unified_diff"]) ?? asString(entry["unifiedDiff"]);
1392
+ const content = asString(entry["content"]);
1393
+ if (kind === "add") {
1394
+ out.push({
1395
+ path,
1396
+ changeKind: "add",
1397
+ ...content !== void 0 ? { content: { kind: "replace", newText: content } } : {}
1398
+ });
1399
+ } else if (kind === "delete") {
1400
+ out.push({
1401
+ path,
1402
+ changeKind: "delete",
1403
+ ...content !== void 0 ? { content: { kind: "replace", oldText: content, newText: "" } } : {}
1404
+ });
1405
+ } else if (movePath) {
1406
+ out.push({
1407
+ path,
1408
+ changeKind: "rename",
1409
+ ...diff ? { content: { kind: "unified", diff } } : {}
1410
+ });
1411
+ } else {
1412
+ out.push({
1413
+ path,
1414
+ changeKind: "modify",
1415
+ ...diff ? { content: { kind: "unified", diff } } : {}
1416
+ });
1417
+ }
1350
1418
  }
1351
1419
  return out;
1352
1420
  }
@@ -1359,7 +1427,18 @@ function toUserInputQuestions(params) {
1359
1427
  const id = asString(question["id"]);
1360
1428
  const header = asString(question["header"]);
1361
1429
  const text = asString(question["question"]);
1362
- const options = Array.isArray(question["options"]) ? question["options"] : [];
1430
+ const rawOptions = Array.isArray(question["options"]) ? question["options"] : [];
1431
+ const options = [];
1432
+ for (const option of rawOptions) {
1433
+ if (!isRecord3(option)) continue;
1434
+ const label = trimText(option["label"]);
1435
+ if (!label) continue;
1436
+ options.push({
1437
+ ...asString(option["id"]) ? { id: asString(option["id"]) } : {},
1438
+ label,
1439
+ description: trimText(option["description"]) ?? ""
1440
+ });
1441
+ }
1363
1442
  if (!id || !header || !text || options.length === 0) return void 0;
1364
1443
  mapped.push({
1365
1444
  id,
@@ -1372,15 +1451,32 @@ function toUserInputQuestions(params) {
1372
1451
  return mapped.length > 0 ? mapped : void 0;
1373
1452
  }
1374
1453
  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;
1454
+ if (!isRecord3(answers)) return void 0;
1455
+ const out = {};
1456
+ for (const [questionId, value] of Object.entries(answers)) {
1457
+ if (typeof value === "string") {
1458
+ out[questionId] = { answers: [value] };
1459
+ continue;
1460
+ }
1461
+ if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
1462
+ out[questionId] = { answers: [...value] };
1463
+ continue;
1464
+ }
1465
+ if (isRecord3(value) && Array.isArray(value["answers"]) && value["answers"].every((entry) => typeof entry === "string")) {
1466
+ out[questionId] = { answers: [...value["answers"]] };
1467
+ continue;
1468
+ }
1469
+ return void 0;
1382
1470
  }
1383
- return void 0;
1471
+ return out;
1472
+ }
1473
+ function toCanonicalUserInputAnswers(answers) {
1474
+ return Object.fromEntries(
1475
+ Object.entries(answers).map(([questionId, value]) => [
1476
+ questionId,
1477
+ value.answers.length === 1 ? value.answers[0] : [...value.answers]
1478
+ ])
1479
+ );
1384
1480
  }
1385
1481
  function pathLeaf(value) {
1386
1482
  if (!value) return void 0;
@@ -1418,10 +1514,11 @@ function mapCollabAgentEvent(input) {
1418
1514
  const agentPath = asString(input.params["agentPath"]) ?? asString(input.params["path"]) ?? void 0;
1419
1515
  const nickname = asString(input.params["agentNickname"]) ?? asString(input.params["nickname"]);
1420
1516
  const role = asString(input.params["agentRole"]) ?? asString(input.params["role"]) ?? pathLeaf(agentPath) ?? "general-purpose";
1421
- const title = nickname ?? pathLeaf(agentPath) ?? agentThreadId;
1517
+ const knownName = nickname ?? pathLeaf(agentPath);
1518
+ const title = knownName ?? agentThreadId;
1422
1519
  const linkage = {
1423
1520
  role,
1424
- title,
1521
+ ...knownName ? { title: knownName } : {},
1425
1522
  ...agentPath ? { agentPath } : {},
1426
1523
  timelineBypass: true
1427
1524
  };
@@ -1439,14 +1536,17 @@ function mapCollabAgentEvent(input) {
1439
1536
  ];
1440
1537
  }
1441
1538
  if (method === "collabAgent/progress" || method === "task.progress") {
1539
+ const summary = trimText(input.params["detail"]) ?? trimText(input.params["text"]);
1442
1540
  return [
1443
1541
  baseEvent({
1444
1542
  type: "task.progress",
1445
1543
  threadId,
1544
+ // 契约 TaskProgressPayload 的 description 是必填;子代理行靠它显示名字。
1446
1545
  payload: {
1447
1546
  taskId: agentThreadId,
1448
- ...linkage,
1449
- detail: trimText(input.params["detail"]) ?? trimText(input.params["text"])
1547
+ description: title,
1548
+ ...summary ? { summary } : {},
1549
+ ...linkage
1450
1550
  },
1451
1551
  method,
1452
1552
  providerInstanceId: input.providerInstanceId
@@ -1456,37 +1556,53 @@ function mapCollabAgentEvent(input) {
1456
1556
  if (method === "collabAgent/completed" || method === "task.completed") {
1457
1557
  return [
1458
1558
  baseEvent({
1459
- type: "task.completed",
1559
+ type: "task.updated",
1460
1560
  threadId,
1461
- payload: { taskId: agentThreadId, ...linkage },
1561
+ payload: { taskId: agentThreadId, status: "idle", ...linkage },
1462
1562
  method,
1463
1563
  providerInstanceId: input.providerInstanceId
1464
1564
  })
1465
1565
  ];
1466
1566
  }
1467
1567
  if (method === "collabAgent/statusChanged") {
1568
+ const raw = asString(input.params["status"]);
1569
+ const status = raw === "systemError" || raw === "error" ? "failed" : raw === "idle" ? "idle" : "running";
1468
1570
  return [
1469
1571
  baseEvent({
1470
1572
  type: "task.updated",
1471
1573
  threadId,
1472
- payload: {
1473
- taskId: agentThreadId,
1474
- ...linkage,
1475
- status: asString(input.params["status"]) ?? "running"
1476
- },
1574
+ payload: { taskId: agentThreadId, status, ...linkage },
1477
1575
  method,
1478
1576
  providerInstanceId: input.providerInstanceId
1479
1577
  })
1480
1578
  ];
1481
1579
  }
1482
1580
  if (method === "collabAgent/tokenUsage") {
1483
- const usage = normalizeCodexTokenUsage(input.params);
1484
- if (!usage) return [];
1581
+ const tokenUsage = isRecord3(input.params["tokenUsage"]) ? input.params["tokenUsage"] : void 0;
1582
+ const total = tokenUsage && isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
1583
+ const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1584
+ const totalTokens = total ? count(total["totalTokens"]) : void 0;
1585
+ if (total === void 0 || totalTokens === void 0) return [];
1586
+ const typedUsage = { totalTokens };
1587
+ for (const key of [
1588
+ "inputTokens",
1589
+ "cachedInputTokens",
1590
+ "outputTokens",
1591
+ "reasoningOutputTokens"
1592
+ ]) {
1593
+ const value = count(total[key]);
1594
+ if (value !== void 0) typedUsage[key] = value;
1595
+ }
1485
1596
  return [
1486
1597
  baseEvent({
1487
- type: "thread.token-usage.updated",
1488
- threadId: agentThreadId,
1489
- payload: usage,
1598
+ type: "task.progress",
1599
+ threadId,
1600
+ payload: {
1601
+ taskId: agentThreadId,
1602
+ description: title,
1603
+ typedUsage,
1604
+ ...linkage
1605
+ },
1490
1606
  method,
1491
1607
  providerInstanceId: input.providerInstanceId
1492
1608
  })
@@ -1581,52 +1697,6 @@ function toMcpElicitationResponse(decision, params) {
1581
1697
  ...persist ? { _meta: { persist } } : {}
1582
1698
  };
1583
1699
  }
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
1700
  function mapNotificationToEvents(input) {
1631
1701
  const { method, params, threadId, providerInstanceId } = input;
1632
1702
  if (method.startsWith("collabAgent/")) {
@@ -1678,58 +1748,121 @@ function mapNotificationToEvents(input) {
1678
1748
  const itemId = itemIdOf(params);
1679
1749
  const type = itemTypeOf(params);
1680
1750
  switch (method) {
1681
- case "thread/started":
1751
+ case "thread/started": {
1752
+ const startedThread = isRecord3(params) ? params["thread"] : void 0;
1753
+ const providerThreadId = isRecord3(startedThread) ? trimText(startedThread["id"]) : void 0;
1682
1754
  return [
1683
1755
  baseEvent({
1684
1756
  type: "thread.started",
1685
1757
  threadId,
1686
- payload: isRecord3(params) ? params : {},
1758
+ payload: providerThreadId ? { providerThreadId } : {},
1687
1759
  method,
1688
1760
  providerInstanceId
1689
1761
  })
1690
1762
  ];
1763
+ }
1691
1764
  case "thread/status/changed": {
1692
- const status = isRecord3(params) ? asString(params["status"]) : void 0;
1765
+ const status = isRecord3(params) ? params["status"] : void 0;
1693
1766
  return [
1694
1767
  baseEvent({
1695
1768
  type: "thread.state.changed",
1696
1769
  threadId,
1697
- payload: { state: toThreadState(status ?? void 0), status },
1770
+ payload: {
1771
+ state: threadStateFromStatus(status),
1772
+ ...params !== void 0 ? { detail: params } : {}
1773
+ },
1698
1774
  method,
1699
1775
  providerInstanceId
1700
1776
  })
1701
1777
  ];
1702
1778
  }
1703
- case "thread/name/updated":
1779
+ case "thread/archived":
1780
+ case "thread/unarchived":
1781
+ return [
1782
+ baseEvent({
1783
+ type: "thread.state.changed",
1784
+ threadId,
1785
+ payload: {
1786
+ state: method === "thread/archived" ? "archived" : "active",
1787
+ ...params !== void 0 ? { detail: params } : {}
1788
+ },
1789
+ method,
1790
+ providerInstanceId
1791
+ })
1792
+ ];
1793
+ case "thread/closed":
1794
+ return [
1795
+ baseEvent({
1796
+ type: "thread.state.changed",
1797
+ threadId,
1798
+ payload: {
1799
+ state: "closed",
1800
+ ...params !== void 0 ? { detail: params } : {}
1801
+ },
1802
+ method,
1803
+ providerInstanceId
1804
+ })
1805
+ ];
1806
+ case "thread/compacted":
1807
+ return [
1808
+ baseEvent({
1809
+ type: "thread.state.changed",
1810
+ threadId,
1811
+ payload: {
1812
+ state: "compacted",
1813
+ compaction: {
1814
+ compactionId: codexCompactionId(threadId, turnId),
1815
+ // Codex 不报触发方,手动压缩也走同一条通知。
1816
+ trigger: "automatic"
1817
+ },
1818
+ ...params !== void 0 ? { detail: params } : {}
1819
+ },
1820
+ method,
1821
+ providerInstanceId
1822
+ })
1823
+ ];
1824
+ case "thread/name/updated": {
1825
+ const threadName = isRecord3(params) ? trimText(params["threadName"]) ?? trimText(params["name"]) : void 0;
1826
+ const nativeThreadId = isRecord3(params) ? asString(params["threadId"]) : void 0;
1704
1827
  return [
1705
1828
  baseEvent({
1706
1829
  type: "thread.metadata.updated",
1707
1830
  threadId,
1708
- payload: { title: isRecord3(params) ? asString(params["name"]) : null },
1831
+ payload: {
1832
+ ...threadName ? { name: threadName } : {},
1833
+ ...nativeThreadId || threadName !== void 0 ? {
1834
+ metadata: {
1835
+ ...nativeThreadId ? { threadId: nativeThreadId } : {},
1836
+ ...threadName !== void 0 ? { threadName } : {}
1837
+ }
1838
+ } : {}
1839
+ },
1709
1840
  method,
1710
1841
  providerInstanceId
1711
1842
  })
1712
1843
  ];
1844
+ }
1713
1845
  case "thread/tokenUsage/updated": {
1714
- const usage = normalizeCodexTokenUsage(params);
1846
+ const usage = normalizeCodexTokenUsage(
1847
+ isRecord3(params) ? params["tokenUsage"] ?? params["usage"] : void 0
1848
+ );
1715
1849
  if (!usage) return [];
1716
1850
  return [
1717
1851
  baseEvent({
1718
1852
  type: "thread.token-usage.updated",
1719
1853
  threadId,
1720
- payload: usage,
1854
+ payload: { usage },
1721
1855
  method,
1722
1856
  providerInstanceId
1723
1857
  })
1724
1858
  ];
1725
1859
  }
1726
- case "thread/closed":
1727
1860
  case "session/closed":
1728
1861
  return [
1729
1862
  baseEvent({
1730
1863
  type: "session.exited",
1731
1864
  threadId,
1732
- payload: {},
1865
+ payload: { exitKind: "graceful" },
1733
1866
  method,
1734
1867
  providerInstanceId
1735
1868
  })
@@ -1740,43 +1873,85 @@ function mapNotificationToEvents(input) {
1740
1873
  type: "turn.started",
1741
1874
  threadId,
1742
1875
  turnId,
1743
- payload: { turnId },
1876
+ payload: {},
1744
1877
  method,
1745
1878
  providerInstanceId
1746
1879
  })
1747
1880
  ];
1748
- case "turn/completed": {
1749
- const flow = turnCompletedControlFlow(params);
1750
- const typeName = flow.status === "cancelled" || flow.status === "interrupted" ? "turn.aborted" : "turn.completed";
1881
+ case "turn/completed":
1751
1882
  return [
1752
1883
  baseEvent({
1753
- type: typeName,
1884
+ type: "turn.completed",
1754
1885
  threadId,
1755
1886
  turnId,
1756
- payload: flow,
1887
+ payload: turnCompletedControlFlow(params),
1888
+ method,
1889
+ providerInstanceId
1890
+ })
1891
+ ];
1892
+ case "turn/aborted":
1893
+ return [
1894
+ baseEvent({
1895
+ type: "turn.aborted",
1896
+ threadId,
1897
+ turnId,
1898
+ payload: {
1899
+ reason: (isRecord3(params) ? trimText(params["reason"]) : void 0) ?? (isRecord3(params) ? trimText(params["message"]) : void 0) ?? "Turn aborted"
1900
+ },
1757
1901
  method,
1758
1902
  providerInstanceId
1759
1903
  })
1760
1904
  ];
1761
- }
1762
1905
  case "turn/plan":
1906
+ case "turn/plan/updated": {
1907
+ const rawPlan = isRecord3(params) && Array.isArray(params["plan"]) ? params["plan"] : [];
1908
+ const plan = rawPlan.filter(isRecord3).map((step) => ({
1909
+ step: trimText(step["step"]) ?? "step",
1910
+ status: step["status"] === "completed" || step["status"] === "inProgress" ? step["status"] : "pending"
1911
+ }));
1912
+ const explanation = isRecord3(params) ? trimText(params["explanation"]) : void 0;
1763
1913
  return [
1764
1914
  baseEvent({
1765
1915
  type: "turn.plan.updated",
1766
1916
  threadId,
1767
1917
  turnId,
1768
- payload: params,
1918
+ payload: {
1919
+ ...explanation ? { explanation } : {},
1920
+ plan
1921
+ },
1922
+ method,
1923
+ providerInstanceId
1924
+ })
1925
+ ];
1926
+ }
1927
+ case "turn/diff/updated": {
1928
+ const unifiedDiff = isRecord3(params) ? asString(params["diff"]) : void 0;
1929
+ if (unifiedDiff === void 0) return [];
1930
+ return [
1931
+ baseEvent({
1932
+ type: "turn.diff.updated",
1933
+ threadId,
1934
+ turnId,
1935
+ payload: { unifiedDiff },
1769
1936
  method,
1770
1937
  providerInstanceId
1771
1938
  })
1772
1939
  ];
1940
+ }
1773
1941
  case "item/started": {
1774
1942
  if (type && normalizeItemType(type).includes("compact")) {
1775
1943
  return [
1776
1944
  baseEvent({
1777
1945
  type: "thread.state.changed",
1778
1946
  threadId,
1779
- payload: { state: "compacting" },
1947
+ payload: {
1948
+ state: "compacting",
1949
+ compaction: {
1950
+ compactionId: codexCompactionId(threadId, turnId),
1951
+ trigger: "automatic"
1952
+ },
1953
+ ...params !== void 0 ? { detail: params } : {}
1954
+ },
1780
1955
  turnId,
1781
1956
  itemId,
1782
1957
  method,
@@ -1784,6 +1959,8 @@ function mapNotificationToEvents(input) {
1784
1959
  })
1785
1960
  ];
1786
1961
  }
1962
+ const canonical = toCanonicalItemType(type);
1963
+ if (canonical === "unknown") return [];
1787
1964
  return [
1788
1965
  baseEvent({
1789
1966
  type: "item.started",
@@ -1791,9 +1968,11 @@ function mapNotificationToEvents(input) {
1791
1968
  turnId,
1792
1969
  itemId,
1793
1970
  payload: {
1794
- itemType: toCanonicalItemType(type),
1795
- title: itemTitle(params),
1796
- detail: itemDetail(params)
1971
+ itemType: canonical,
1972
+ status: "inProgress",
1973
+ ...itemTitle(params) ? { title: itemTitle(params) } : {},
1974
+ ...itemDetail(params) ? { detail: itemDetail(params) } : {},
1975
+ ...params !== void 0 ? { data: params } : {}
1797
1976
  },
1798
1977
  method,
1799
1978
  providerInstanceId
@@ -1806,7 +1985,14 @@ function mapNotificationToEvents(input) {
1806
1985
  baseEvent({
1807
1986
  type: "thread.state.changed",
1808
1987
  threadId,
1809
- payload: { state: "compacted" },
1988
+ payload: {
1989
+ state: "compacted",
1990
+ compaction: {
1991
+ compactionId: codexCompactionId(threadId, turnId),
1992
+ trigger: "automatic"
1993
+ },
1994
+ ...params !== void 0 ? { detail: params } : {}
1995
+ },
1810
1996
  turnId,
1811
1997
  itemId,
1812
1998
  method,
@@ -1815,18 +2001,24 @@ function mapNotificationToEvents(input) {
1815
2001
  ];
1816
2002
  }
1817
2003
  if (type && normalizeItemType(type).includes("plan")) {
2004
+ const planMarkdown = itemDetail(params);
2005
+ if (!planMarkdown) return [];
1818
2006
  return [
1819
2007
  baseEvent({
1820
2008
  type: "turn.proposed.completed",
1821
2009
  threadId,
1822
2010
  turnId,
1823
2011
  itemId,
1824
- payload: params,
2012
+ payload: { planMarkdown },
1825
2013
  method,
1826
2014
  providerInstanceId
1827
2015
  })
1828
2016
  ];
1829
2017
  }
2018
+ const canonical = toCanonicalItemType(type);
2019
+ if (canonical === "unknown") return [];
2020
+ const rawStatus = itemStatusOf(params);
2021
+ const status = rawStatus === "failed" || rawStatus === "declined" ? rawStatus : "completed";
1830
2022
  return [
1831
2023
  baseEvent({
1832
2024
  type: "item.completed",
@@ -1834,45 +2026,62 @@ function mapNotificationToEvents(input) {
1834
2026
  turnId,
1835
2027
  itemId,
1836
2028
  payload: {
1837
- itemType: toCanonicalItemType(type),
1838
- title: itemTitle(params),
1839
- detail: itemDetail(params)
2029
+ itemType: canonical,
2030
+ status,
2031
+ ...itemTitle(params) ? { title: itemTitle(params) } : {},
2032
+ ...itemDetail(params) ? { detail: itemDetail(params) } : {},
2033
+ ...params !== void 0 ? { data: params } : {}
1840
2034
  },
1841
2035
  method,
1842
2036
  providerInstanceId
1843
- }),
1844
- ...producedImages(params).map((event) => ({ ...event, threadId }))
2037
+ })
1845
2038
  ];
1846
2039
  }
1847
- case "item/plan/delta":
2040
+ case "item/plan/delta": {
2041
+ const delta = isRecord3(params) ? asString(params["delta"]) : void 0;
2042
+ if (!delta) return [];
1848
2043
  return [
1849
2044
  baseEvent({
1850
2045
  type: "turn.proposed.delta",
1851
2046
  threadId,
1852
2047
  turnId,
1853
2048
  itemId,
1854
- payload: params,
2049
+ payload: { delta },
1855
2050
  method,
1856
2051
  providerInstanceId
1857
2052
  })
1858
2053
  ];
2054
+ }
1859
2055
  case "item/agentMessage/delta":
1860
- case "item/reasoning/delta":
1861
- case "item/reasoning/summaryDelta": {
2056
+ case "item/reasoning/textDelta":
2057
+ case "item/reasoning/summaryTextDelta":
2058
+ case "item/commandExecution/outputDelta":
2059
+ case "item/fileChange/outputDelta": {
1862
2060
  const delta = isRecord3(params) ? asString(params["delta"]) ?? asString(params["text"]) ?? "" : "";
2061
+ if (delta.length === 0) return [];
2062
+ 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";
2063
+ const summaryIndex = method === "item/reasoning/summaryTextDelta" && isRecord3(params) ? asNumber(params["summaryIndex"]) : null;
2064
+ const contentIndex = method === "item/reasoning/textDelta" && isRecord3(params) ? asNumber(params["contentIndex"]) : null;
1863
2065
  return [
1864
2066
  baseEvent({
1865
2067
  type: "content.delta",
1866
2068
  threadId,
1867
2069
  turnId,
1868
2070
  itemId,
1869
- payload: { kind: contentStreamKindFromMethod(method), delta },
2071
+ payload: {
2072
+ streamKind,
2073
+ delta,
2074
+ ...summaryIndex !== null ? { summaryIndex } : {},
2075
+ ...contentIndex !== null ? { contentIndex } : {}
2076
+ },
1870
2077
  method,
1871
2078
  providerInstanceId
1872
2079
  })
1873
2080
  ];
1874
2081
  }
1875
- case "item/reasoning/summaryPartAdded":
2082
+ case "item/reasoning/summaryPartAdded": {
2083
+ const summaryIndex = isRecord3(params) ? asNumber(params["summaryIndex"]) : null;
2084
+ if (summaryIndex === null) return [];
1876
2085
  return [
1877
2086
  baseEvent({
1878
2087
  type: "content.delta",
@@ -1880,59 +2089,176 @@ function mapNotificationToEvents(input) {
1880
2089
  turnId,
1881
2090
  itemId,
1882
2091
  payload: {
1883
- kind: "reasoning",
2092
+ streamKind: "reasoning_summary_text",
1884
2093
  delta: "",
1885
- summaryIndex: isRecord3(params) ? asNumber(params["summaryIndex"]) : null
2094
+ summaryIndex
2095
+ },
2096
+ method,
2097
+ providerInstanceId
2098
+ })
2099
+ ];
2100
+ }
2101
+ case "item/mcpToolCall/progress": {
2102
+ const summary = isRecord3(params) ? trimText(params["message"]) : void 0;
2103
+ if (!summary) return [];
2104
+ return [
2105
+ baseEvent({
2106
+ type: "tool.progress",
2107
+ threadId,
2108
+ turnId,
2109
+ itemId,
2110
+ payload: { summary },
2111
+ method,
2112
+ providerInstanceId
2113
+ })
2114
+ ];
2115
+ }
2116
+ case "item/commandExecution/terminalInteraction":
2117
+ return [
2118
+ baseEvent({
2119
+ type: "item.updated",
2120
+ threadId,
2121
+ turnId,
2122
+ itemId,
2123
+ payload: {
2124
+ itemType: "command_execution",
2125
+ ...params !== void 0 ? { data: params } : {}
1886
2126
  },
1887
2127
  method,
1888
2128
  providerInstanceId
1889
2129
  })
1890
2130
  ];
1891
2131
  case "account/updated":
2132
+ return [
2133
+ baseEvent({
2134
+ type: "account.updated",
2135
+ threadId,
2136
+ payload: { account: params ?? {} },
2137
+ method,
2138
+ providerInstanceId
2139
+ })
2140
+ ];
1892
2141
  case "account/rateLimits/updated":
1893
2142
  return [
1894
2143
  baseEvent({
1895
- type: method === "account/updated" ? "account.updated" : "account.rate-limits.updated",
2144
+ type: "account.rate-limits.updated",
1896
2145
  threadId,
1897
- payload: params,
2146
+ payload: {
2147
+ rateLimits: isRecord3(params) && "rateLimits" in params ? params["rateLimits"] : params ?? {}
2148
+ },
1898
2149
  method,
1899
2150
  providerInstanceId
1900
2151
  })
1901
2152
  ];
1902
- case "model/rerouted":
2153
+ case "model/rerouted": {
2154
+ const fromModel = isRecord3(params) ? trimText(params["fromModel"]) : void 0;
2155
+ const toModel = isRecord3(params) ? trimText(params["toModel"]) : void 0;
2156
+ const reason = isRecord3(params) ? trimText(params["reason"]) : void 0;
2157
+ if (!fromModel || !toModel || !reason) return [];
1903
2158
  return [
1904
2159
  baseEvent({
1905
2160
  type: "model.rerouted",
1906
2161
  threadId,
1907
- payload: params,
2162
+ turnId,
2163
+ payload: { fromModel, toModel, reason },
1908
2164
  method,
1909
2165
  providerInstanceId
1910
2166
  })
1911
2167
  ];
1912
- case "config/warning":
2168
+ }
2169
+ case "configWarning":
2170
+ case "deprecationNotice": {
2171
+ const summary = isRecord3(params) ? trimText(params["summary"]) : void 0;
2172
+ if (!summary) return [];
2173
+ const details = isRecord3(params) ? trimText(params["details"]) : void 0;
1913
2174
  return [
1914
2175
  baseEvent({
1915
- type: "config.warning",
2176
+ type: method === "configWarning" ? "config.warning" : "deprecation.notice",
1916
2177
  threadId,
1917
- payload: params,
2178
+ payload: {
2179
+ summary,
2180
+ ...details ? { details } : {},
2181
+ ...method === "configWarning" && isRecord3(params) ? {
2182
+ ...trimText(params["path"]) ? { path: trimText(params["path"]) } : {},
2183
+ ...params["range"] !== void 0 && params["range"] !== null ? { range: params["range"] } : {}
2184
+ } : {}
2185
+ },
1918
2186
  method,
1919
2187
  providerInstanceId
1920
2188
  })
1921
2189
  ];
1922
- case "mcp/oauth/completed":
2190
+ }
2191
+ case "mcpServer/oauthLogin/completed": {
2192
+ const success = isRecord3(params) ? params["success"] : void 0;
2193
+ if (typeof success !== "boolean") return [];
2194
+ const name = isRecord3(params) ? trimText(params["name"]) : void 0;
2195
+ const error = isRecord3(params) ? trimText(params["error"]) : void 0;
1923
2196
  return [
1924
2197
  baseEvent({
1925
2198
  type: "mcp.oauth.completed",
1926
2199
  threadId,
1927
- payload: params,
2200
+ payload: {
2201
+ success,
2202
+ ...name ? { name } : {},
2203
+ ...error ? { error } : {}
2204
+ },
1928
2205
  method,
1929
2206
  providerInstanceId
1930
2207
  })
1931
2208
  ];
2209
+ }
1932
2210
  default:
1933
2211
  return [];
1934
2212
  }
1935
2213
  }
2214
+ function toRequestTypeFromKind(kind) {
2215
+ switch (kind) {
2216
+ case "command":
2217
+ return "command_execution_approval";
2218
+ case "file-read":
2219
+ return "file_read_approval";
2220
+ case "file-change":
2221
+ return "file_change_approval";
2222
+ case "apply-patch":
2223
+ return "apply_patch_approval";
2224
+ case "mcp-elicitation":
2225
+ return "mcp_elicitation_approval";
2226
+ default:
2227
+ return "unknown";
2228
+ }
2229
+ }
2230
+ function toRequestTypeFromMethod(method) {
2231
+ switch (method) {
2232
+ case "item/commandExecution/requestApproval":
2233
+ return "command_execution_approval";
2234
+ case "execCommandApproval":
2235
+ return "exec_command_approval";
2236
+ case "item/fileRead/requestApproval":
2237
+ return "file_read_approval";
2238
+ case "item/fileChange/requestApproval":
2239
+ return "file_change_approval";
2240
+ case "applyPatchApproval":
2241
+ return "apply_patch_approval";
2242
+ case "mcpServer/elicitation/request":
2243
+ return "mcp_elicitation_approval";
2244
+ case "item/tool/requestUserInput":
2245
+ return "tool_user_input";
2246
+ case "item/tool/call":
2247
+ return "dynamic_tool_call";
2248
+ default:
2249
+ return "unknown";
2250
+ }
2251
+ }
2252
+ function elicitationOptions(params) {
2253
+ const persist = persistFromMeta(isRecord3(params) ? params["_meta"] : void 0);
2254
+ return [
2255
+ { decision: "cancel", label: "Cancel" },
2256
+ { decision: "decline", label: "Decline" },
2257
+ ...persist === "session" ? [{ decision: "acceptForSession", label: "Always allow this session" }] : [],
2258
+ ...persist === "always" ? [{ decision: "acceptAlways", label: "Always allow" }] : [],
2259
+ { decision: "accept", label: "Approve" }
2260
+ ];
2261
+ }
1936
2262
  function mapServerRequestToEvents(input) {
1937
2263
  const requestId = String(input.id);
1938
2264
  if (input.method === "item/tool/requestUserInput") {
@@ -1950,18 +2276,44 @@ function mapServerRequestToEvents(input) {
1950
2276
  })
1951
2277
  ];
1952
2278
  }
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);
2279
+ const requestType = toRequestTypeFromMethod(input.method);
2280
+ const payload = {
2281
+ requestType,
2282
+ // Codex 的审批回复只有一个枚举,理由随下一条 prompt 送达。
2283
+ declineReasonRelay: "queued"
2284
+ };
2285
+ if (input.method === "mcpServer/elicitation/request") {
2286
+ const described = describeMcpElicitation(input.params);
2287
+ const appName = described.appName ?? described.serverName ?? void 0;
2288
+ payload["options"] = elicitationOptions(input.params);
2289
+ if (appName) payload["appName"] = appName;
2290
+ if (described.message) payload["detail"] = described.message;
2291
+ payload["args"] = input.params;
2292
+ } else if (input.method === "item/fileChange/requestApproval" || input.method.includes("applyPatch")) {
2293
+ const described = describeFileChangeApproval(input.params);
2294
+ if (described.reason) payload["detail"] = described.reason;
2295
+ if (described.fileChanges.length > 0) payload["fileChanges"] = described.fileChanges;
2296
+ payload["args"] = {
2297
+ ...described.callId ? { callId: described.callId } : {},
2298
+ ...described.reason ? { reason: described.reason } : {},
2299
+ ...described.grantRoot ? { grantRoot: described.grantRoot } : {}
2300
+ };
2301
+ } else {
2302
+ const described = describeCommandApproval(input.params);
2303
+ const detail = described.command || described.reason;
2304
+ if (detail) payload["detail"] = detail;
2305
+ payload["args"] = {
2306
+ command: described.command,
2307
+ ...described.cwd ? { cwd: described.cwd } : {},
2308
+ ...described.reason ? { reason: described.reason } : {}
2309
+ };
2310
+ }
1955
2311
  return [
1956
2312
  baseEvent({
1957
2313
  type: "request.opened",
1958
2314
  threadId: input.threadId,
1959
2315
  requestId,
1960
- payload: {
1961
- kind,
1962
- args,
1963
- declineReasonRelay: "queued"
1964
- },
2316
+ payload,
1965
2317
  method: input.method,
1966
2318
  source: "codex.app-server.request",
1967
2319
  providerInstanceId: input.providerInstanceId
@@ -1990,12 +2342,15 @@ var CodexSessionRuntime = class {
1990
2342
  activeTurnId;
1991
2343
  model;
1992
2344
  runtimeMode;
2345
+ /** 宿主解好的这一轮的档(座位画的就是它)。缺席 = 宿主没给,按 `runtimeMode` 查表。 */
2346
+ nativeModeId;
1993
2347
  interactionMode;
1994
2348
  closed = false;
1995
2349
  closeCompleted = false;
1996
2350
  turns = [];
1997
2351
  pendingApprovals = /* @__PURE__ */ new Map();
1998
2352
  pendingUserInputs = /* @__PURE__ */ new Map();
2353
+ requestKinds = /* @__PURE__ */ new Map();
1999
2354
  writeAcks = /* @__PURE__ */ new Map();
2000
2355
  grants;
2001
2356
  declineRelay;
@@ -2007,6 +2362,16 @@ var CodexSessionRuntime = class {
2007
2362
  host;
2008
2363
  rpc;
2009
2364
  emit;
2365
+ /** 这一轮该用哪套三字段组合:只由这一轮的档决定(见 `codexNativeModeFor`)。 */
2366
+ #threadConfig() {
2367
+ return codexThreadConfigForNativeMode(codexNativeModeFor(this.runtimeMode, this.nativeModeId));
2368
+ }
2369
+ /** 同上,沙箱策略那一位。 */
2370
+ #turnSandboxPolicy() {
2371
+ return codexTurnSandboxPolicyForNativeMode(
2372
+ codexNativeModeFor(this.runtimeMode, this.nativeModeId)
2373
+ );
2374
+ }
2010
2375
  constructor(host, rpc, emit, input, cwd, appServerArgs, grants, declineRelay) {
2011
2376
  this.host = host;
2012
2377
  this.rpc = rpc;
@@ -2014,6 +2379,7 @@ var CodexSessionRuntime = class {
2014
2379
  this.threadId = input.threadId;
2015
2380
  this.cwd = cwd;
2016
2381
  this.runtimeMode = input.runtimeMode;
2382
+ this.nativeModeId = input.nativeModeId;
2017
2383
  this.appServerArgs = appServerArgs;
2018
2384
  this.grants = grants ?? createApprovalGrantStore();
2019
2385
  this.declineRelay = declineRelay ?? createDeclineReasonRelay();
@@ -2023,6 +2389,7 @@ var CodexSessionRuntime = class {
2023
2389
  providerInstanceId: host.instanceId,
2024
2390
  status: "idle",
2025
2391
  runtimeMode: input.runtimeMode,
2392
+ nativeModeId: codexNativeModeFor(input.runtimeMode, input.nativeModeId),
2026
2393
  cwd,
2027
2394
  threadId: input.threadId,
2028
2395
  createdAt,
@@ -2102,6 +2469,24 @@ var CodexSessionRuntime = class {
2102
2469
  this.dispatchMapped(method, params);
2103
2470
  }
2104
2471
  dispatchMapped(method, params) {
2472
+ if (method === "serverRequest/resolved") {
2473
+ const resolvedId = (isRecord3(params) ? asString(params["requestId"]) : null) ?? void 0;
2474
+ if (resolvedId !== void 0) {
2475
+ this.emit({
2476
+ eventId: `codex-req-resolved-${resolvedId}`,
2477
+ provider: CODEX_DRIVER_KIND,
2478
+ threadId: this.threadId,
2479
+ createdAt: nowIso(),
2480
+ type: "request.resolved",
2481
+ requestId: resolvedId,
2482
+ payload: {
2483
+ requestType: toRequestTypeFromKind(this.requestKinds.get(resolvedId)),
2484
+ resolution: params
2485
+ }
2486
+ });
2487
+ }
2488
+ return;
2489
+ }
2105
2490
  for (const event of mapNotificationToEvents({
2106
2491
  method,
2107
2492
  params,
@@ -2280,6 +2665,7 @@ var CodexSessionRuntime = class {
2280
2665
  threadId: this.threadId,
2281
2666
  providerInstanceId: this.host.instanceId
2282
2667
  });
2668
+ this.requestKinds.set(requestId, kind);
2283
2669
  await new Promise((resolve2) => {
2284
2670
  this.pendingApprovals.set(requestId, {
2285
2671
  jsonRpcId: id,
@@ -2319,14 +2705,22 @@ var CodexSessionRuntime = class {
2319
2705
  createdAt: nowIso(),
2320
2706
  type: "request.resolved",
2321
2707
  requestId,
2322
- payload: { decision }
2708
+ // requestType 是契约必填;缺了它宿主整条解不动,卡片停在「等待决议」。
2709
+ payload: {
2710
+ requestType: toRequestTypeFromKind(pending.kind),
2711
+ decision,
2712
+ ...reason ? { reason } : {}
2713
+ }
2323
2714
  });
2324
2715
  }
2325
2716
  async respondToUserInput(requestId, answers) {
2326
2717
  const pending = this.pendingUserInputs.get(requestId);
2327
2718
  if (!pending) throw new Error(`unknown user-input ${requestId}`);
2328
2719
  const mapped = toCodexUserInputAnswers(answers);
2329
- pending.settle(mapped === void 0 ? {} : { answers: mapped });
2720
+ if (mapped === void 0) {
2721
+ throw new Error(`invalid user input answers for request ${requestId}`);
2722
+ }
2723
+ pending.settle({ answers: mapped });
2330
2724
  this.emit({
2331
2725
  eventId: `codex-ui-${requestId}`,
2332
2726
  provider: CODEX_DRIVER_KIND,
@@ -2334,7 +2728,8 @@ var CodexSessionRuntime = class {
2334
2728
  createdAt: nowIso(),
2335
2729
  type: "user-input.resolved",
2336
2730
  requestId,
2337
- payload: { answers: mapped ?? [] }
2731
+ // 契约的 answers 是 questionId → 答案 的 record,不是数组。
2732
+ payload: { answers: toCanonicalUserInputAnswers(mapped) }
2338
2733
  });
2339
2734
  }
2340
2735
  async withActiveWriterRecovery(run, allowUnsubscribe) {
@@ -2368,7 +2763,7 @@ var CodexSessionRuntime = class {
2368
2763
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
2369
2764
  }
2370
2765
  threadConfigParams() {
2371
- const config = runtimeModeToThreadConfig(this.runtimeMode);
2766
+ const config = this.#threadConfig();
2372
2767
  return {
2373
2768
  approvalPolicy: config.approvalPolicy,
2374
2769
  sandbox: config.sandbox,
@@ -2419,7 +2814,7 @@ var CodexSessionRuntime = class {
2419
2814
  threadId: this.threadId,
2420
2815
  createdAt: nowIso(),
2421
2816
  type: "session.started",
2422
- payload: { resumeCursor: this.session.resumeCursor }
2817
+ payload: { resume: this.session.resumeCursor }
2423
2818
  });
2424
2819
  return this.session;
2425
2820
  }
@@ -2451,8 +2846,8 @@ var CodexSessionRuntime = class {
2451
2846
  if (hasConfiguredMcpServer(this.appServerArgs)) {
2452
2847
  await this.rpc.request("mcpServer/reload", {}, 0).catch(() => void 0);
2453
2848
  }
2454
- const sandboxPolicy = runtimeModeToTurnSandboxPolicy(this.runtimeMode);
2455
- const config = runtimeModeToThreadConfig(this.runtimeMode);
2849
+ const sandboxPolicy = this.#turnSandboxPolicy();
2850
+ const config = this.#threadConfig();
2456
2851
  const developerInstructions = this.developerInstructions(input);
2457
2852
  const requestedModel = modelIdFromSelection(input.modelSelection) ?? this.model;
2458
2853
  const params = {
@@ -2464,15 +2859,22 @@ var CodexSessionRuntime = class {
2464
2859
  ...requestedModel ? { model: requestedModel } : {},
2465
2860
  ...developerInstructions ? { developerInstructions } : {}
2466
2861
  };
2467
- if (input.interactionMode !== void 0) {
2468
- params["collaborationMode"] = input.interactionMode === "plan" ? "plan" : "default";
2862
+ const interactionMode = typeof input.interactionMode === "string" ? input.interactionMode : void 0;
2863
+ if (interactionMode !== void 0) {
2864
+ params["collaborationMode"] = codexCollaborationMode({
2865
+ interactionMode,
2866
+ model: requestedModel ?? DEFAULT_MODEL,
2867
+ reasoningEffort: modelSelectionStringOption(input.modelSelection, "reasoningEffort") ?? void 0
2868
+ });
2469
2869
  }
2470
2870
  const isSteer = input.expectedTurnId !== void 0;
2471
2871
  const result = isSteer ? await this.rpc.request(
2472
2872
  "turn/steer",
2473
2873
  {
2474
2874
  ...params,
2475
- turnId: input.expectedTurnId
2875
+ // 这一位的名字是 `expectedTurnId`,不是 `turnId`:0.155.1 少了它整条拒收
2876
+ // (`Invalid request: missing field \`expectedTurnId\``),插队的那句话发不出去。
2877
+ expectedTurnId: input.expectedTurnId
2476
2878
  },
2477
2879
  0
2478
2880
  ) : await this.withActiveWriterRecovery(() => this.rpc.request("turn/start", params, 0), false);
@@ -2569,6 +2971,7 @@ var CodexSessionRuntime = class {
2569
2971
  async close() {
2570
2972
  if (this.closeCompleted) return;
2571
2973
  this.closed = true;
2974
+ this.requestKinds.clear();
2572
2975
  this.failPendingNativeWrites("session closed before write confirmation");
2573
2976
  this.settlePendingApprovals("cancel");
2574
2977
  this.settlePendingUserInputs({});
@@ -2792,6 +3195,198 @@ async function readRolloutModel(input, rolloutPath, sameDirectory) {
2792
3195
  return model;
2793
3196
  }
2794
3197
 
3198
+ // src/mcpOAuth.ts
3199
+ async function startCodexMcpOAuth(input) {
3200
+ if (input.recipe.binaryPath.length === 0) {
3201
+ throw new Error("Codex program path is not configured");
3202
+ }
3203
+ const mcp = codexMcpLaunchConfig([input.server]);
3204
+ const env = buildCodexSpawnEnv({
3205
+ recipe: input.recipe,
3206
+ layout: input.layout,
3207
+ launchEnv: { ...input.processEnv, ...mcp.environment },
3208
+ crushApiKeys: shouldCrushApiKeys(input.recipe.authMode)
3209
+ });
3210
+ const handle = await input.host.process.spawn({
3211
+ program: input.recipe.binaryPath,
3212
+ args: [...tokenizeLaunchArgs(input.recipe.launchArgs), ...mcp.args],
3213
+ cwd: input.cwd,
3214
+ owner: { bindingId: input.host.bindingId },
3215
+ env
3216
+ });
3217
+ let settle;
3218
+ const completed = new Promise((resolve2) => {
3219
+ settle = resolve2;
3220
+ });
3221
+ const rpc = await createCodexRpcClient(handle, {
3222
+ onNotification: (notification) => {
3223
+ if (notification.method !== "mcpServer/oauthLogin/completed") return;
3224
+ const params = isRecord3(notification.params) ? notification.params : {};
3225
+ if (asString(params["name"]) !== input.server.name) return;
3226
+ const error = asString(params["error"]);
3227
+ settle?.({
3228
+ success: params["success"] === true,
3229
+ ...error === null || error === void 0 ? {} : { error }
3230
+ });
3231
+ },
3232
+ onServerRequest: () => void 0,
3233
+ onStderrLine: () => void 0
3234
+ });
3235
+ const close = async () => {
3236
+ settle?.({ success: false, error: "cancelled" });
3237
+ await rpc.close().catch(() => void 0);
3238
+ };
3239
+ try {
3240
+ await rpc.request("initialize", CODEX_INITIALIZE_PARAMS);
3241
+ await rpc.notify("initialized");
3242
+ const response = await rpc.request("mcpServer/oauth/login", {
3243
+ name: input.server.name,
3244
+ ...input.timeoutSecs === void 0 ? {} : { timeoutSecs: input.timeoutSecs }
3245
+ });
3246
+ const authorizationUrl = isRecord3(response) ? asString(response["authorizationUrl"]) : void 0;
3247
+ if (!authorizationUrl) {
3248
+ await close();
3249
+ throw new Error(`Codex did not return an authorization URL for '${input.server.name}'.`);
3250
+ }
3251
+ return { authorizationUrl, handle, completed, close };
3252
+ } catch (error) {
3253
+ await close();
3254
+ throw error;
3255
+ }
3256
+ }
3257
+
3258
+ // src/models.ts
3259
+ var MODEL_LIST_MAX_PAGES = 20;
3260
+ var MODEL_LIST_TIMEOUT_MS = 15e3;
3261
+ var REASONING_EFFORT_LABELS = {
3262
+ none: "None",
3263
+ minimal: "Minimal",
3264
+ low: "Low",
3265
+ medium: "Medium",
3266
+ high: "High",
3267
+ xhigh: "Extra High",
3268
+ max: "Max",
3269
+ ultra: "Ultra"
3270
+ };
3271
+ var DEFAULT_SERVICE_TIER_ID = "default";
3272
+ var reasoningEffortLabel = (effort) => REASONING_EFFORT_LABELS[effort] ?? effort;
3273
+ function codexModelDisplayName(displayName) {
3274
+ return displayName.replace(/^gpt/i, "GPT").replace(/-([a-z])/g, (_, c) => `-${c.toUpperCase()}`);
3275
+ }
3276
+ function reasoningDescriptor(row) {
3277
+ const supported = row["supportedReasoningEfforts"];
3278
+ if (!Array.isArray(supported) || supported.length === 0) return null;
3279
+ const defaultEffort = asString(row["defaultReasoningEffort"]);
3280
+ const options = supported.flatMap((entry) => {
3281
+ if (!isRecord3(entry)) return [];
3282
+ const id = asString(entry["reasoningEffort"]);
3283
+ if (!id) return [];
3284
+ const description = asString(entry["description"]);
3285
+ return [
3286
+ {
3287
+ id,
3288
+ label: reasoningEffortLabel(id),
3289
+ ...description ? { description } : {},
3290
+ ...id === defaultEffort ? { isDefault: true } : {}
3291
+ }
3292
+ ];
3293
+ });
3294
+ if (options.length === 0) return null;
3295
+ return {
3296
+ id: "reasoningEffort",
3297
+ label: "Reasoning",
3298
+ type: "select",
3299
+ options,
3300
+ ...defaultEffort ? { currentValue: defaultEffort } : {}
3301
+ };
3302
+ }
3303
+ function serviceTierDescriptor(row) {
3304
+ const declared = Array.isArray(row["serviceTiers"]) ? row["serviceTiers"] : [];
3305
+ const tiers = declared.length > 0 ? declared.flatMap((entry) => {
3306
+ if (!isRecord3(entry)) return [];
3307
+ const id = asString(entry["id"]);
3308
+ if (!id) return [];
3309
+ const name = asString(entry["name"]);
3310
+ const description = asString(entry["description"]);
3311
+ return [{ id, name: name ?? id, ...description ? { description } : {} }];
3312
+ }) : (Array.isArray(row["additionalSpeedTiers"]) ? row["additionalSpeedTiers"] : []).flatMap(
3313
+ (entry) => {
3314
+ const id = typeof entry === "string" ? entry : null;
3315
+ return id === null ? [] : [{ id, name: id === "fast" ? "Fast" : id }];
3316
+ }
3317
+ );
3318
+ if (tiers.length === 0) return null;
3319
+ const declaredDefault = asString(row["defaultServiceTier"]);
3320
+ const defaultTier = declaredDefault && tiers.some((tier) => tier.id === declaredDefault) ? declaredDefault : DEFAULT_SERVICE_TIER_ID;
3321
+ return {
3322
+ id: "serviceTier",
3323
+ label: "Service Tier",
3324
+ type: "select",
3325
+ options: [
3326
+ {
3327
+ id: DEFAULT_SERVICE_TIER_ID,
3328
+ label: "Standard",
3329
+ ...defaultTier === DEFAULT_SERVICE_TIER_ID ? { isDefault: true } : {}
3330
+ },
3331
+ ...tiers.map((tier) => ({
3332
+ id: tier.id,
3333
+ label: tier.name,
3334
+ ...tier.description ? { description: tier.description } : {},
3335
+ ...defaultTier === tier.id ? { isDefault: true } : {}
3336
+ }))
3337
+ ],
3338
+ currentValue: defaultTier
3339
+ };
3340
+ }
3341
+ function isSupersededModel(row) {
3342
+ if (isRecord3(row["upgradeInfo"])) return true;
3343
+ return (asString(row["upgrade"]) ?? "").length > 0;
3344
+ }
3345
+ function codexProbeModel(row) {
3346
+ if (!isRecord3(row)) return null;
3347
+ const slug = asString(row["model"]);
3348
+ if (!slug) return null;
3349
+ const displayName = asString(row["displayName"]);
3350
+ const optionDescriptors = [reasoningDescriptor(row), serviceTierDescriptor(row)].filter(
3351
+ (descriptor) => descriptor !== null
3352
+ );
3353
+ return {
3354
+ slug,
3355
+ ...displayName ? { name: codexModelDisplayName(displayName) } : {},
3356
+ ...row["isDefault"] === true ? { isDefault: true } : {},
3357
+ ...isSupersededModel(row) ? { isLegacy: true } : {},
3358
+ ...row["hidden"] === true ? { hidden: true } : {},
3359
+ // 空表也给 `capabilities`:`{}` 与 `null` 对宿主是一回事(都不画那一格),
3360
+ // 但给对象保住「这只手确实答过这一问」这层意思。
3361
+ capabilities: { optionDescriptors }
3362
+ };
3363
+ }
3364
+ async function listCodexModels(rpc) {
3365
+ const models = [];
3366
+ let cursor;
3367
+ for (let page = 0; page < MODEL_LIST_MAX_PAGES; page++) {
3368
+ let response;
3369
+ try {
3370
+ response = await rpc.request(
3371
+ "model/list",
3372
+ cursor === void 0 ? {} : { cursor },
3373
+ MODEL_LIST_TIMEOUT_MS
3374
+ );
3375
+ } catch {
3376
+ return void 0;
3377
+ }
3378
+ if (!isRecord3(response) || !Array.isArray(response["data"])) return void 0;
3379
+ for (const row of response["data"]) {
3380
+ const model = codexProbeModel(row);
3381
+ if (model !== null) models.push(model);
3382
+ }
3383
+ const next = asString(response["nextCursor"]);
3384
+ if (!next || next === cursor) return models;
3385
+ cursor = next;
3386
+ }
3387
+ return models;
3388
+ }
3389
+
2795
3390
  // src/signIn.ts
2796
3391
  var DEVICE_CODE_URL = /https?:\/\/\S+/;
2797
3392
  var DEVICE_CODE_VALUE = /\b([A-Z0-9]{4,}(?:-[A-Z0-9]{4,})+)\b/;
@@ -2947,22 +3542,26 @@ async function verifyCodexSignIn(input) {
2947
3542
  installed: true,
2948
3543
  version: null,
2949
3544
  readiness: "unchecked",
2950
- auth: { status: "unauthenticated" }
3545
+ auth: { status: "unauthenticated" },
3546
+ ...CODEX_NATIVE_MODE_REPORT
2951
3547
  };
2952
3548
  }
2953
- await rpc.request("model/list", {}, 15e3).catch(() => void 0);
3549
+ const models = await listCodexModels(rpc);
2954
3550
  return {
2955
3551
  installed: true,
2956
3552
  version: null,
2957
3553
  readiness: "ready",
2958
- auth
3554
+ auth,
3555
+ ...models === void 0 ? {} : { models },
3556
+ ...CODEX_NATIVE_MODE_REPORT
2959
3557
  };
2960
3558
  } catch {
2961
3559
  return {
2962
3560
  installed: true,
2963
3561
  version: null,
2964
3562
  readiness: "unchecked",
2965
- auth: { status: "unknown" }
3563
+ auth: { status: "unknown" },
3564
+ ...CODEX_NATIVE_MODE_REPORT
2966
3565
  };
2967
3566
  } finally {
2968
3567
  await rpc.close().catch(() => void 0);
@@ -2973,6 +3572,10 @@ async function verifyCodexSignIn(input) {
2973
3572
  import * as NodeFSP2 from "node:fs/promises";
2974
3573
  import * as NodeOS3 from "node:os";
2975
3574
  import * as NodePath5 from "node:path";
3575
+ import {
3576
+ mergeLaunchEnv as mergeLaunchEnv2,
3577
+ brainSlotEnvKeys as brainSlotEnvKeys2
3578
+ } from "@ycodium-ai/plugin-api/executor";
2976
3579
  var CODEX_TIMEOUT_MS = 18e4;
2977
3580
  var EARLIER_CONTENT_TRUNCATION_MARKER = "[Earlier content truncated]\n\n";
2978
3581
  function limitSection(value, maxChars) {
@@ -3145,14 +3748,12 @@ function createCodexTextGeneration(input) {
3145
3748
  const reasoningEffort = modelSelectionStringOption(modelSelection, "reasoningEffort") ?? DEFAULT_TEXT_GENERATION_REASONING_EFFORT;
3146
3749
  const serviceTier = modelSelectionStringOption(modelSelection, "serviceTier");
3147
3750
  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
- }
3751
+ const env = mergeLaunchEnv2({
3752
+ brainSlots: brainSlotEnvKeys2(CODEX_CARD),
3753
+ accountHome: input.layout.effectiveHomePath ? { CODEX_HOME: input.layout.effectiveHomePath } : input.recipe.homePath.trim() ? { CODEX_HOME: expandHomePath(input.recipe.homePath) } : {},
3754
+ instanceTable: input.recipe.environment,
3755
+ launch: processEnv
3756
+ });
3156
3757
  const args = [
3157
3758
  "exec",
3158
3759
  ...codexExecLaunchArgs(launchArgs),
@@ -3469,8 +4070,11 @@ function accountToAuth2(account) {
3469
4070
  return { status: "unknown" };
3470
4071
  }
3471
4072
  }
3472
- function layoutFor(recipe, launchHome) {
4073
+ function layoutFor(recipe, launchHome, nativeSubscription = false) {
3473
4074
  const environmentHome = recipe.environment["CODEX_HOME"];
4075
+ if (nativeSubscription && launchHome?.trim()) {
4076
+ return resolveCodexHomeLayout({ homePath: launchHome.trim(), shadowHomePath: "" });
4077
+ }
3474
4078
  return resolveCodexHomeLayout({
3475
4079
  homePath: launchHome?.trim() || recipe.homePath,
3476
4080
  shadowHomePath: recipe.shadowHomePath,
@@ -3490,6 +4094,38 @@ var createCodexInstance = (config, host) => {
3490
4094
  return booked;
3491
4095
  };
3492
4096
  const probeAuth = () => accountToAuth2(readCodexAccount(accountHomeFromLayout(layoutFor(recipe))));
4097
+ const probeModels = async () => {
4098
+ const layout = layoutFor(recipe);
4099
+ const env = buildCodexSpawnEnv({
4100
+ recipe,
4101
+ layout,
4102
+ crushApiKeys: shouldCrushApiKeys(recipe.authMode)
4103
+ });
4104
+ let handle;
4105
+ let rpc;
4106
+ try {
4107
+ handle = await host.process.spawn({
4108
+ program: recipe.binaryPath,
4109
+ args: [...codexSpawnArgs(recipe, layout)],
4110
+ owner: { bindingId: host.bindingId },
4111
+ ...Object.keys(env).length > 0 ? { env } : {}
4112
+ });
4113
+ trackHandle(handle, `${recipe.binaryPath} (probe)`);
4114
+ rpc = await createCodexRpcClient(handle, {
4115
+ onNotification: () => void 0,
4116
+ onServerRequest: () => void 0,
4117
+ onStderrLine: () => void 0
4118
+ });
4119
+ await rpc.request("initialize", CODEX_INITIALIZE_PARAMS);
4120
+ await rpc.notify("initialized");
4121
+ return await listCodexModels(rpc);
4122
+ } catch {
4123
+ return void 0;
4124
+ } finally {
4125
+ await rpc?.close().catch(() => void 0);
4126
+ await handle?.kill("SIGTERM").catch(() => void 0);
4127
+ }
4128
+ };
3493
4129
  const trackHandle = (handle, command, threadId) => {
3494
4130
  live.set(handle.pid, { command, ...threadId === void 0 ? {} : { threadId } });
3495
4131
  void handle.exited.finally(() => {
@@ -3517,14 +4153,20 @@ var createCodexInstance = (config, host) => {
3517
4153
  version: null,
3518
4154
  readiness: "error",
3519
4155
  auth: probeAuth(),
3520
- detail: connectionError
4156
+ detail: connectionError,
4157
+ ...CODEX_NATIVE_MODE_REPORT
3521
4158
  };
3522
4159
  }
4160
+ const models = await probeModels();
3523
4161
  return {
3524
4162
  installed: true,
3525
4163
  version: null,
3526
- readiness: "unchecked",
3527
- auth: probeAuth()
4164
+ readiness: models === void 0 ? "unchecked" : "ready",
4165
+ auth: probeAuth(),
4166
+ ...models === void 0 ? {} : { models },
4167
+ // 宿主拿的是这一份。登录核验那份(`verifyCodexSignIn`)报了档、这里没报,
4168
+ // 界面就一直画宿主四档——2026-09-20 真发生过,浏览器里才看得见。
4169
+ ...CODEX_NATIVE_MODE_REPORT
3528
4170
  };
3529
4171
  },
3530
4172
  startSession: async (input, signal) => {
@@ -3540,15 +4182,22 @@ var createCodexInstance = (config, host) => {
3540
4182
  await stopBooked(existing);
3541
4183
  threads.delete(input.threadId);
3542
4184
  }
3543
- const layout = layoutFor(recipe, input.launch.homePath);
4185
+ const nativeSubscription = input.launch.nativeSubscription === true;
4186
+ const layout = layoutFor(recipe, input.launch.homePath, nativeSubscription);
3544
4187
  await materializeCodexShadowHome(layout, host.log);
4188
+ const mcp = codexMcpLaunchConfig(input.mcpServers ?? []);
3545
4189
  const env = buildCodexSpawnEnv({
3546
4190
  recipe,
3547
4191
  layout,
3548
- processEnv: input.launch.processEnv,
3549
- crushApiKeys: shouldCrushApiKeys(recipe.authMode)
4192
+ // MCP 那几个变量是宿主挑好的 server 带来的,与绑定同一层:实例表盖不得。
4193
+ launchEnv: { ...input.launch.processEnv, ...mcp.environment },
4194
+ crushApiKeys: nativeSubscription || shouldCrushApiKeys(recipe.authMode)
3550
4195
  });
3551
- const args = [...codexSpawnArgs(recipe, layout)];
4196
+ const args = [
4197
+ ...codexSpawnArgs(recipe, layout, nativeSubscription),
4198
+ ...codexOverrideArgs(input.launch.launchArgOverrides),
4199
+ ...mcp.args
4200
+ ];
3552
4201
  const handle = await host.process.spawn({
3553
4202
  program: recipe.binaryPath,
3554
4203
  args,
@@ -3677,7 +4326,6 @@ var createCodexInstance = (config, host) => {
3677
4326
  const env = buildCodexSpawnEnv({
3678
4327
  recipe,
3679
4328
  layout,
3680
- processEnv: {},
3681
4329
  crushApiKeys: shouldCrushApiKeys(recipe.authMode)
3682
4330
  });
3683
4331
  const args = [...codexSpawnArgs(recipe, layout)];
@@ -3715,7 +4363,6 @@ var createCodexInstance = (config, host) => {
3715
4363
  const env = buildCodexSpawnEnv({
3716
4364
  recipe,
3717
4365
  layout,
3718
- processEnv: {},
3719
4366
  crushApiKeys: shouldCrushApiKeys(recipe.authMode)
3720
4367
  });
3721
4368
  const args = [...codexSpawnArgs(recipe, layout)];
@@ -3776,6 +4423,25 @@ var createCodexInstance = (config, host) => {
3776
4423
  processEnv: process.env
3777
4424
  })
3778
4425
  },
4426
+ mcpOAuth: {
4427
+ start: async (input) => {
4428
+ const started = await startCodexMcpOAuth({
4429
+ host,
4430
+ recipe,
4431
+ layout: layoutFor(recipe),
4432
+ processEnv: process.env,
4433
+ server: input.server,
4434
+ cwd: input.cwd,
4435
+ ...input.timeoutSecs === void 0 ? {} : { timeoutSecs: input.timeoutSecs }
4436
+ });
4437
+ trackHandle(started.handle, `${recipe.binaryPath} app-server (mcp oauth)`);
4438
+ return {
4439
+ authorizationUrl: started.authorizationUrl,
4440
+ completed: started.completed,
4441
+ close: started.close
4442
+ };
4443
+ }
4444
+ },
3779
4445
  listLiveProcesses: async () => [...live.entries()].map(([pid, entry]) => ({
3780
4446
  pid,
3781
4447
  command: entry.command,