@ycodium-ai/agent-codex 0.2.2130 → 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 +70 -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";
@@ -755,6 +756,10 @@ function asString(value) {
755
756
  function asNumber(value) {
756
757
  return typeof value === "number" && Number.isFinite(value) ? value : null;
757
758
  }
759
+ function codexVersionFromInitialize(initializeResult) {
760
+ const userAgent = isRecord3(initializeResult) ? asString(initializeResult["userAgent"]) : null;
761
+ return userAgent?.match(/\/(\S+)/)?.[1] ?? null;
762
+ }
758
763
  function decodeThreadOpen(result) {
759
764
  const thread = isRecord3(result) ? isRecord3(result["thread"]) ? result["thread"] : result : null;
760
765
  const id = thread ? asString(thread["id"]) : null;
@@ -992,7 +997,15 @@ async function createCodexRpcClient(handle, handlers) {
992
997
  const settled = new Promise((resolve2, reject) => {
993
998
  pending.set(id, { resolve: resolve2, reject, method });
994
999
  });
995
- 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
+ }
996
1009
  const limit = timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
997
1010
  if (limit <= 0) return await settled;
998
1011
  let timer;
@@ -1033,7 +1046,15 @@ async function createCodexRpcClient(handle, handlers) {
1033
1046
  failAll("the client was closed");
1034
1047
  await Promise.all([pumpStdout, pumpStderr]);
1035
1048
  };
1036
- 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
+ };
1037
1058
  }
1038
1059
 
1039
1060
  // src/approvalGrant.ts
@@ -2341,6 +2362,11 @@ function pathLeaf2(value) {
2341
2362
  const parts = value.split("/").filter((part) => part.length > 0);
2342
2363
  return parts.at(-1);
2343
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
+ }
2344
2370
  var CodexSessionRuntime = class {
2345
2371
  threadId;
2346
2372
  cwd;
@@ -2379,6 +2405,15 @@ var CodexSessionRuntime = class {
2379
2405
  codexNativeModeFor(this.runtimeMode, this.nativeModeId)
2380
2406
  );
2381
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
+ }
2382
2417
  constructor(host, rpc, emit, input, cwd, appServerArgs, grants, declineRelay) {
2383
2418
  this.host = host;
2384
2419
  this.rpc = rpc;
@@ -2648,7 +2683,7 @@ var CodexSessionRuntime = class {
2648
2683
  jsonRpcId: id,
2649
2684
  settle: (result) => {
2650
2685
  this.pendingUserInputs.delete(requestId);
2651
- void this.rpc.respond(id, result).finally(resolve2);
2686
+ void this.rpc.respond(id, result).catch((error) => this.#warnAnswerUndelivered(method, id, error)).finally(resolve2);
2652
2687
  }
2653
2688
  });
2654
2689
  for (const event of events) this.emit(event);
@@ -2682,7 +2717,7 @@ var CodexSessionRuntime = class {
2682
2717
  identity,
2683
2718
  settle: (decision) => {
2684
2719
  this.pendingApprovals.delete(requestId);
2685
- void this.rpc.respond(id, decision).finally(resolve2);
2720
+ void this.rpc.respond(id, decision).catch((error) => this.#warnAnswerUndelivered(method, id, error)).finally(resolve2);
2686
2721
  }
2687
2722
  });
2688
2723
  for (const event of events) this.emit(event);
@@ -2704,7 +2739,7 @@ var CodexSessionRuntime = class {
2704
2739
  }
2705
2740
  const ack = this.registerWriteAck(pending.jsonRpcId);
2706
2741
  pending.settle(wire);
2707
- await ack.catch(() => "closed");
2742
+ const delivery = await ack.catch(() => "closed");
2708
2743
  this.emit({
2709
2744
  eventId: `codex-req-${requestId}`,
2710
2745
  provider: CODEX_DRIVER_KIND,
@@ -2719,6 +2754,7 @@ var CodexSessionRuntime = class {
2719
2754
  ...reason ? { reason } : {}
2720
2755
  }
2721
2756
  });
2757
+ if (delivery !== "written") throw undeliveredAnswerError(requestId, delivery);
2722
2758
  }
2723
2759
  async respondToUserInput(requestId, answers) {
2724
2760
  const pending = this.pendingUserInputs.get(requestId);
@@ -2727,7 +2763,9 @@ var CodexSessionRuntime = class {
2727
2763
  if (mapped === void 0) {
2728
2764
  throw new Error(`invalid user input answers for request ${requestId}`);
2729
2765
  }
2766
+ const ack = this.registerWriteAck(pending.jsonRpcId);
2730
2767
  pending.settle({ answers: mapped });
2768
+ const delivery = await ack.catch(() => "closed");
2731
2769
  this.emit({
2732
2770
  eventId: `codex-ui-${requestId}`,
2733
2771
  provider: CODEX_DRIVER_KIND,
@@ -2738,6 +2776,7 @@ var CodexSessionRuntime = class {
2738
2776
  // 契约的 answers 是 questionId → 答案 的 record,不是数组。
2739
2777
  payload: { answers: toCanonicalUserInputAnswers(mapped) }
2740
2778
  });
2779
+ if (delivery !== "written") throw undeliveredAnswerError(requestId, delivery);
2741
2780
  }
2742
2781
  async withActiveWriterRecovery(run, allowUnsubscribe) {
2743
2782
  let lastError;
@@ -3539,15 +3578,18 @@ async function verifyCodexSignIn(input) {
3539
3578
  onServerRequest: () => void 0,
3540
3579
  onStderrLine: () => void 0
3541
3580
  });
3581
+ let version = null;
3542
3582
  try {
3543
- await rpc.request("initialize", CODEX_INITIALIZE_PARAMS, 15e3);
3583
+ version = codexVersionFromInitialize(
3584
+ await rpc.request("initialize", CODEX_INITIALIZE_PARAMS, 15e3)
3585
+ );
3544
3586
  await rpc.notify("initialized");
3545
3587
  const account = await rpc.request("account/read", { refreshToken: true }, 15e3);
3546
3588
  const type = isRecord3(account) ? asString(account["type"]) : null;
3547
3589
  if (type !== "chatgpt") {
3548
3590
  return {
3549
3591
  installed: true,
3550
- version: null,
3592
+ version,
3551
3593
  readiness: "unchecked",
3552
3594
  auth: { status: "unauthenticated" },
3553
3595
  ...CODEX_NATIVE_MODE_REPORT
@@ -3556,7 +3598,7 @@ async function verifyCodexSignIn(input) {
3556
3598
  const models = await listCodexModels(rpc);
3557
3599
  return {
3558
3600
  installed: true,
3559
- version: null,
3601
+ version,
3560
3602
  readiness: "ready",
3561
3603
  auth,
3562
3604
  ...models === void 0 ? {} : { models },
@@ -3565,7 +3607,7 @@ async function verifyCodexSignIn(input) {
3565
3607
  } catch {
3566
3608
  return {
3567
3609
  installed: true,
3568
- version: null,
3610
+ version,
3569
3611
  readiness: "unchecked",
3570
3612
  auth: { status: "unknown" },
3571
3613
  ...CODEX_NATIVE_MODE_REPORT
@@ -4101,7 +4143,7 @@ var createCodexInstance = (config, host) => {
4101
4143
  return booked;
4102
4144
  };
4103
4145
  const probeAuth = () => accountToAuth2(readCodexAccount(accountHomeFromLayout(layoutFor(recipe))));
4104
- const probeModels = async () => {
4146
+ const probeAppServer = async (input) => {
4105
4147
  const layout = layoutFor(recipe);
4106
4148
  const env = buildCodexSpawnEnv({
4107
4149
  recipe,
@@ -4110,6 +4152,7 @@ var createCodexInstance = (config, host) => {
4110
4152
  });
4111
4153
  let handle;
4112
4154
  let rpc;
4155
+ let version = null;
4113
4156
  try {
4114
4157
  handle = await host.process.spawn({
4115
4158
  program: recipe.binaryPath,
@@ -4123,11 +4166,14 @@ var createCodexInstance = (config, host) => {
4123
4166
  onServerRequest: () => void 0,
4124
4167
  onStderrLine: () => void 0
4125
4168
  });
4126
- await rpc.request("initialize", CODEX_INITIALIZE_PARAMS);
4169
+ version = codexVersionFromInitialize(
4170
+ await rpc.request("initialize", CODEX_INITIALIZE_PARAMS)
4171
+ );
4127
4172
  await rpc.notify("initialized");
4128
- return await listCodexModels(rpc);
4129
- } catch {
4130
- 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 };
4131
4177
  } finally {
4132
4178
  await rpc?.close().catch(() => void 0);
4133
4179
  await handle?.kill("SIGTERM").catch(() => void 0);
@@ -4154,20 +4200,20 @@ var createCodexInstance = (config, host) => {
4154
4200
  };
4155
4201
  }
4156
4202
  const connectionError = codexApiConnectionError(recipe, recipe.environment);
4203
+ const { version, models } = await probeAppServer({ listModels: connectionError === null });
4157
4204
  if (connectionError) {
4158
4205
  return {
4159
4206
  installed: true,
4160
- version: null,
4207
+ version,
4161
4208
  readiness: "error",
4162
4209
  auth: probeAuth(),
4163
4210
  detail: connectionError,
4164
4211
  ...CODEX_NATIVE_MODE_REPORT
4165
4212
  };
4166
4213
  }
4167
- const models = await probeModels();
4168
4214
  return {
4169
4215
  installed: true,
4170
- version: null,
4216
+ version,
4171
4217
  readiness: models === void 0 ? "unchecked" : "ready",
4172
4218
  auth: probeAuth(),
4173
4219
  ...models === void 0 ? {} : { models },
@@ -4219,7 +4265,13 @@ var createCodexInstance = (config, host) => {
4219
4265
  runtime?.handleNotification(notification.method, notification.params);
4220
4266
  },
4221
4267
  onServerRequest: (request) => {
4222
- 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
+ });
4223
4275
  },
4224
4276
  onStderrLine: (line) => {
4225
4277
  runtime?.handleStderrLine(line);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/agent-codex",
3
- "version": "0.2.2130",
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.2130"
23
+ "@ycodium-ai/plugin-api": "0.2.2143"
24
24
  },
25
25
  "ycodium": {
26
26
  "plugin": "./dist/index.js",