@ycodium-ai/agent-codex 0.2.2126 → 0.2.2143

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 +82 -18
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -92,6 +92,7 @@ var CODEX_CARD = {
92
92
 
93
93
  // src/codexInstance.ts
94
94
  import * as NodeProcess from "node:process";
95
+ import { ExecutorSpawnRefusedError } from "@ycodium-ai/plugin-api/executor";
95
96
 
96
97
  // src/codexAccount.ts
97
98
  import * as NodeFS from "node:fs";
@@ -500,6 +501,13 @@ function resolveCodexHomeLayout(input) {
500
501
  continuationKey: `codex:home:${sharedHomePath}`
501
502
  };
502
503
  }
504
+ function codexContinuationKey(input) {
505
+ return resolveCodexHomeLayout({
506
+ homePath: input.homePath,
507
+ shadowHomePath: "",
508
+ environmentHome: input.instanceEnvironment["CODEX_HOME"] ?? input.inheritedEnvironment["CODEX_HOME"]
509
+ }).continuationKey;
510
+ }
503
511
  async function readLinkState(linkPath) {
504
512
  try {
505
513
  const target = await NodeFSP.readlink(linkPath);
@@ -748,6 +756,10 @@ function asString(value) {
748
756
  function asNumber(value) {
749
757
  return typeof value === "number" && Number.isFinite(value) ? value : null;
750
758
  }
759
+ function codexVersionFromInitialize(initializeResult) {
760
+ const userAgent = isRecord3(initializeResult) ? asString(initializeResult["userAgent"]) : null;
761
+ return userAgent?.match(/\/(\S+)/)?.[1] ?? null;
762
+ }
751
763
  function decodeThreadOpen(result) {
752
764
  const thread = isRecord3(result) ? isRecord3(result["thread"]) ? result["thread"] : result : null;
753
765
  const id = thread ? asString(thread["id"]) : null;
@@ -985,7 +997,15 @@ async function createCodexRpcClient(handle, handlers) {
985
997
  const settled = new Promise((resolve2, reject) => {
986
998
  pending.set(id, { resolve: resolve2, reject, method });
987
999
  });
988
- await writeLine({ id, method, ...params === void 0 ? {} : { params } });
1000
+ try {
1001
+ await Promise.race([
1002
+ writeLine({ id, method, ...params === void 0 ? {} : { params } }),
1003
+ settled
1004
+ ]);
1005
+ } catch (error) {
1006
+ pending.delete(id);
1007
+ throw error;
1008
+ }
989
1009
  const limit = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
990
1010
  if (limit <= 0) return await settled;
991
1011
  let timer;
@@ -1026,7 +1046,15 @@ async function createCodexRpcClient(handle, handlers) {
1026
1046
  failAll("the client was closed");
1027
1047
  await Promise.all([pumpStdout, pumpStderr]);
1028
1048
  };
1029
- return { request, notify, respond, respondError, close, pid: handle.pid };
1049
+ return {
1050
+ request,
1051
+ notify,
1052
+ respond,
1053
+ respondError,
1054
+ close,
1055
+ pid: handle.pid,
1056
+ pendingCountForTests: () => pending.size
1057
+ };
1030
1058
  }
1031
1059
 
1032
1060
  // src/approvalGrant.ts
@@ -2334,6 +2362,11 @@ function pathLeaf2(value) {
2334
2362
  const parts = value.split("/").filter((part) => part.length > 0);
2335
2363
  return parts.at(-1);
2336
2364
  }
2365
+ function undeliveredAnswerError(requestId, delivery) {
2366
+ return new Error(
2367
+ delivery === "closed" ? `codex did not receive the answer to request ${requestId}: the session closed before the answer was written` : `codex did not receive the answer to request ${requestId}: writing it to the CLI failed`
2368
+ );
2369
+ }
2337
2370
  var CodexSessionRuntime = class {
2338
2371
  threadId;
2339
2372
  cwd;
@@ -2372,6 +2405,15 @@ var CodexSessionRuntime = class {
2372
2405
  codexNativeModeFor(this.runtimeMode, this.nativeModeId)
2373
2406
  );
2374
2407
  }
2408
+ /** 给 CLI 的应答没写进去:带上线程与方法名记一行 warn。 */
2409
+ #warnAnswerUndelivered(method, jsonRpcId, error) {
2410
+ this.host.log.warn("codex did not receive an answer", {
2411
+ threadId: this.threadId,
2412
+ method,
2413
+ jsonRpcId,
2414
+ error: error instanceof Error ? error.message : String(error)
2415
+ });
2416
+ }
2375
2417
  constructor(host, rpc, emit, input, cwd, appServerArgs, grants, declineRelay) {
2376
2418
  this.host = host;
2377
2419
  this.rpc = rpc;
@@ -2641,7 +2683,7 @@ var CodexSessionRuntime = class {
2641
2683
  jsonRpcId: id,
2642
2684
  settle: (result) => {
2643
2685
  this.pendingUserInputs.delete(requestId);
2644
- void this.rpc.respond(id, result).finally(resolve2);
2686
+ void this.rpc.respond(id, result).catch((error) => this.#warnAnswerUndelivered(method, id, error)).finally(resolve2);
2645
2687
  }
2646
2688
  });
2647
2689
  for (const event of events) this.emit(event);
@@ -2675,7 +2717,7 @@ var CodexSessionRuntime = class {
2675
2717
  identity,
2676
2718
  settle: (decision) => {
2677
2719
  this.pendingApprovals.delete(requestId);
2678
- void this.rpc.respond(id, decision).finally(resolve2);
2720
+ void this.rpc.respond(id, decision).catch((error) => this.#warnAnswerUndelivered(method, id, error)).finally(resolve2);
2679
2721
  }
2680
2722
  });
2681
2723
  for (const event of events) this.emit(event);
@@ -2697,7 +2739,7 @@ var CodexSessionRuntime = class {
2697
2739
  }
2698
2740
  const ack = this.registerWriteAck(pending.jsonRpcId);
2699
2741
  pending.settle(wire);
2700
- await ack.catch(() => "closed");
2742
+ const delivery = await ack.catch(() => "closed");
2701
2743
  this.emit({
2702
2744
  eventId: `codex-req-${requestId}`,
2703
2745
  provider: CODEX_DRIVER_KIND,
@@ -2712,6 +2754,7 @@ var CodexSessionRuntime = class {
2712
2754
  ...reason ? { reason } : {}
2713
2755
  }
2714
2756
  });
2757
+ if (delivery !== "written") throw undeliveredAnswerError(requestId, delivery);
2715
2758
  }
2716
2759
  async respondToUserInput(requestId, answers) {
2717
2760
  const pending = this.pendingUserInputs.get(requestId);
@@ -2720,7 +2763,9 @@ var CodexSessionRuntime = class {
2720
2763
  if (mapped === void 0) {
2721
2764
  throw new Error(`invalid user input answers for request ${requestId}`);
2722
2765
  }
2766
+ const ack = this.registerWriteAck(pending.jsonRpcId);
2723
2767
  pending.settle({ answers: mapped });
2768
+ const delivery = await ack.catch(() => "closed");
2724
2769
  this.emit({
2725
2770
  eventId: `codex-ui-${requestId}`,
2726
2771
  provider: CODEX_DRIVER_KIND,
@@ -2731,6 +2776,7 @@ var CodexSessionRuntime = class {
2731
2776
  // 契约的 answers 是 questionId → 答案 的 record,不是数组。
2732
2777
  payload: { answers: toCanonicalUserInputAnswers(mapped) }
2733
2778
  });
2779
+ if (delivery !== "written") throw undeliveredAnswerError(requestId, delivery);
2734
2780
  }
2735
2781
  async withActiveWriterRecovery(run, allowUnsubscribe) {
2736
2782
  let lastError;
@@ -3532,15 +3578,18 @@ async function verifyCodexSignIn(input) {
3532
3578
  onServerRequest: () => void 0,
3533
3579
  onStderrLine: () => void 0
3534
3580
  });
3581
+ let version = null;
3535
3582
  try {
3536
- await rpc.request("initialize", CODEX_INITIALIZE_PARAMS, 15e3);
3583
+ version = codexVersionFromInitialize(
3584
+ await rpc.request("initialize", CODEX_INITIALIZE_PARAMS, 15e3)
3585
+ );
3537
3586
  await rpc.notify("initialized");
3538
3587
  const account = await rpc.request("account/read", { refreshToken: true }, 15e3);
3539
3588
  const type = isRecord3(account) ? asString(account["type"]) : null;
3540
3589
  if (type !== "chatgpt") {
3541
3590
  return {
3542
3591
  installed: true,
3543
- version: null,
3592
+ version,
3544
3593
  readiness: "unchecked",
3545
3594
  auth: { status: "unauthenticated" },
3546
3595
  ...CODEX_NATIVE_MODE_REPORT
@@ -3549,7 +3598,7 @@ async function verifyCodexSignIn(input) {
3549
3598
  const models = await listCodexModels(rpc);
3550
3599
  return {
3551
3600
  installed: true,
3552
- version: null,
3601
+ version,
3553
3602
  readiness: "ready",
3554
3603
  auth,
3555
3604
  ...models === void 0 ? {} : { models },
@@ -3558,7 +3607,7 @@ async function verifyCodexSignIn(input) {
3558
3607
  } catch {
3559
3608
  return {
3560
3609
  installed: true,
3561
- version: null,
3610
+ version,
3562
3611
  readiness: "unchecked",
3563
3612
  auth: { status: "unknown" },
3564
3613
  ...CODEX_NATIVE_MODE_REPORT
@@ -4094,7 +4143,7 @@ var createCodexInstance = (config, host) => {
4094
4143
  return booked;
4095
4144
  };
4096
4145
  const probeAuth = () => accountToAuth2(readCodexAccount(accountHomeFromLayout(layoutFor(recipe))));
4097
- const probeModels = async () => {
4146
+ const probeAppServer = async (input) => {
4098
4147
  const layout = layoutFor(recipe);
4099
4148
  const env = buildCodexSpawnEnv({
4100
4149
  recipe,
@@ -4103,6 +4152,7 @@ var createCodexInstance = (config, host) => {
4103
4152
  });
4104
4153
  let handle;
4105
4154
  let rpc;
4155
+ let version = null;
4106
4156
  try {
4107
4157
  handle = await host.process.spawn({
4108
4158
  program: recipe.binaryPath,
@@ -4116,11 +4166,14 @@ var createCodexInstance = (config, host) => {
4116
4166
  onServerRequest: () => void 0,
4117
4167
  onStderrLine: () => void 0
4118
4168
  });
4119
- await rpc.request("initialize", CODEX_INITIALIZE_PARAMS);
4169
+ version = codexVersionFromInitialize(
4170
+ await rpc.request("initialize", CODEX_INITIALIZE_PARAMS)
4171
+ );
4120
4172
  await rpc.notify("initialized");
4121
- return await listCodexModels(rpc);
4122
- } catch {
4123
- return void 0;
4173
+ return { version, models: input.listModels ? await listCodexModels(rpc) : void 0 };
4174
+ } catch (error) {
4175
+ if (error instanceof ExecutorSpawnRefusedError) throw error;
4176
+ return { version, models: void 0 };
4124
4177
  } finally {
4125
4178
  await rpc?.close().catch(() => void 0);
4126
4179
  await handle?.kill("SIGTERM").catch(() => void 0);
@@ -4147,20 +4200,20 @@ var createCodexInstance = (config, host) => {
4147
4200
  };
4148
4201
  }
4149
4202
  const connectionError = codexApiConnectionError(recipe, recipe.environment);
4203
+ const { version, models } = await probeAppServer({ listModels: connectionError === null });
4150
4204
  if (connectionError) {
4151
4205
  return {
4152
4206
  installed: true,
4153
- version: null,
4207
+ version,
4154
4208
  readiness: "error",
4155
4209
  auth: probeAuth(),
4156
4210
  detail: connectionError,
4157
4211
  ...CODEX_NATIVE_MODE_REPORT
4158
4212
  };
4159
4213
  }
4160
- const models = await probeModels();
4161
4214
  return {
4162
4215
  installed: true,
4163
- version: null,
4216
+ version,
4164
4217
  readiness: models === void 0 ? "unchecked" : "ready",
4165
4218
  auth: probeAuth(),
4166
4219
  ...models === void 0 ? {} : { models },
@@ -4212,7 +4265,13 @@ var createCodexInstance = (config, host) => {
4212
4265
  runtime?.handleNotification(notification.method, notification.params);
4213
4266
  },
4214
4267
  onServerRequest: (request) => {
4215
- void runtime?.handleServerRequest(request.id, request.method, request.params);
4268
+ void runtime?.handleServerRequest(request.id, request.method, request.params).catch((error) => {
4269
+ host.log.warn("codex server request was not answered", {
4270
+ threadId: input.threadId,
4271
+ method: request.method,
4272
+ error: error instanceof Error ? error.message : String(error)
4273
+ });
4274
+ });
4216
4275
  },
4217
4276
  onStderrLine: (line) => {
4218
4277
  runtime?.handleStderrLine(line);
@@ -4317,6 +4376,11 @@ var createCodexInstance = (config, host) => {
4317
4376
  recipe,
4318
4377
  layout: layoutFor(recipe)
4319
4378
  }),
4379
+ continuationKey: codexContinuationKey({
4380
+ homePath: recipe.homePath,
4381
+ instanceEnvironment: recipe.environment,
4382
+ inheritedEnvironment: process.env
4383
+ }),
4320
4384
  sessions: {
4321
4385
  scan: async (input) => {
4322
4386
  if (recipe.binaryPath.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/agent-codex",
3
- "version": "0.2.2126",
3
+ "version": "0.2.2143",
4
4
  "description": "Codex executor: Codex CLI over the app-server JSON-RPC protocol.",
5
5
  "keywords": [
6
6
  "ycodium",
@@ -20,7 +20,7 @@
20
20
  "@types/node": "24.12.4",
21
21
  "esbuild": "0.28.1",
22
22
  "vite-plus": "0.2.2",
23
- "@ycodium-ai/plugin-api": "0.2.2126"
23
+ "@ycodium-ai/plugin-api": "0.2.2143"
24
24
  },
25
25
  "ycodium": {
26
26
  "plugin": "./dist/index.js",