@ycodium-ai/agent-codex 0.2.2596 → 0.2.2658

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 +260 -40
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -424,7 +424,10 @@ var probeCodexAccount = (accountPath) => {
424
424
 
425
425
  // src/codexInstance.ts
426
426
  import * as NodeProcess from "node:process";
427
- import { ExecutorSpawnRefusedError } from "@ycodium-ai/plugin-api/executor";
427
+ import {
428
+ describeExecutorSpawnFailure,
429
+ ExecutorSpawnRefusedError
430
+ } from "@ycodium-ai/plugin-api/executor";
428
431
 
429
432
  // src/config.ts
430
433
  import { modelSelectionStringOption } from "@ycodium-ai/plugin-api/executor";
@@ -782,7 +785,8 @@ function codexCollaborationMode(input) {
782
785
  mode: input.interactionMode === "plan" ? "plan" : "default",
783
786
  settings: {
784
787
  model: input.model,
785
- ...effort ? { reasoning_effort: effort } : {}
788
+ ...effort ? { reasoning_effort: effort } : {},
789
+ ...input.developerInstructions ? { developer_instructions: input.developerInstructions } : {}
786
790
  }
787
791
  };
788
792
  }
@@ -868,6 +872,8 @@ var CHILD_AGENT_EVENT_METHODS = /* @__PURE__ */ new Set([
868
872
  "turn/completed",
869
873
  "thread/status/changed",
870
874
  "thread/tokenUsage/updated",
875
+ "thread/settings/updated",
876
+ "model/rerouted",
871
877
  "item/started",
872
878
  "item/completed",
873
879
  "thread/closed",
@@ -885,7 +891,6 @@ var CHILD_CHATTER_METHODS = /* @__PURE__ */ new Set([
885
891
  "turn/plan/updated",
886
892
  "turn/diff/updated",
887
893
  "thread/name/updated",
888
- "thread/settings/updated",
889
894
  "rawResponseItem/completed",
890
895
  // 子线程自己的生命周期通知不能改父线程状态(旧宿主 runtime 同名单)。
891
896
  "thread/archived",
@@ -1584,6 +1589,28 @@ function toUserInputQuestions(params) {
1584
1589
  }
1585
1590
  return mapped.length > 0 ? mapped : void 0;
1586
1591
  }
1592
+ function toAsyncQuestions(params) {
1593
+ if (!isRecord3(params)) return void 0;
1594
+ const item = isRecord3(params["item"]) ? params["item"] : params;
1595
+ if (item["delivery"] !== "async" || !Array.isArray(item["questions"])) return void 0;
1596
+ const mapped = [];
1597
+ item["questions"].forEach((question, index) => {
1598
+ if (!isRecord3(question)) return;
1599
+ const title = trimText(question["title"]);
1600
+ if (!title) return;
1601
+ const labels = Array.isArray(question["options"]) ? question["options"] : [];
1602
+ const options = labels.map((label) => trimText(label)).filter((label) => label !== void 0).map((label) => ({ label, description: "" }));
1603
+ mapped.push({
1604
+ id: `q${index}`,
1605
+ header: title,
1606
+ question: title,
1607
+ options,
1608
+ allowFreeform: true,
1609
+ multiSelect: false
1610
+ });
1611
+ });
1612
+ return mapped.length > 0 ? mapped : void 0;
1613
+ }
1587
1614
  function toCodexUserInputAnswers(answers) {
1588
1615
  if (!isRecord3(answers)) return void 0;
1589
1616
  const out = {};
@@ -1650,9 +1677,13 @@ function mapCollabAgentEvent(input) {
1650
1677
  const role = asString(input.params["agentRole"]) ?? asString(input.params["role"]) ?? pathLeaf(agentPath) ?? "general-purpose";
1651
1678
  const knownName = nickname ?? pathLeaf(agentPath);
1652
1679
  const title = knownName ?? agentThreadId;
1680
+ const model = trimText(input.params["model"]);
1681
+ const effort = trimText(input.params["effort"]);
1653
1682
  const linkage = {
1654
1683
  role,
1655
1684
  ...knownName ? { title: knownName } : {},
1685
+ ...model ? { model } : {},
1686
+ ...effort ? { effort } : {},
1656
1687
  ...agentPath ? { agentPath } : {},
1657
1688
  timelineBypass: true,
1658
1689
  rosterKind: "subagent"
@@ -1713,6 +1744,17 @@ function mapCollabAgentEvent(input) {
1713
1744
  })
1714
1745
  ];
1715
1746
  }
1747
+ if (method === "collabAgent/metadataUpdated") {
1748
+ return [
1749
+ baseEvent({
1750
+ type: "task.updated",
1751
+ threadId,
1752
+ payload: { taskId: agentThreadId, ...linkage },
1753
+ method,
1754
+ providerInstanceId: input.providerInstanceId
1755
+ })
1756
+ ];
1757
+ }
1716
1758
  if (method === "collabAgent/metadata") {
1717
1759
  const status = asCollabChildStatus(input.params["status"]);
1718
1760
  if (status === void 0) return [];
@@ -1788,6 +1830,42 @@ function describeFileChangeApproval(params) {
1788
1830
  fileChanges: toApplyPatchFileChanges(record)
1789
1831
  };
1790
1832
  }
1833
+ function describePermissionsApproval(params) {
1834
+ const record = isRecord3(params) ? params : {};
1835
+ const permissions = isRecord3(record["permissions"]) ? record["permissions"] : {};
1836
+ const fileSystem = isRecord3(permissions["fileSystem"]) ? permissions["fileSystem"] : {};
1837
+ const network = isRecord3(permissions["network"]) ? permissions["network"] : {};
1838
+ const pathsOf = (value) => Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.length > 0 ? [entry] : []) : [];
1839
+ const read = pathsOf(fileSystem["read"]);
1840
+ const write = pathsOf(fileSystem["write"]);
1841
+ const entries = Array.isArray(fileSystem["entries"]) ? fileSystem["entries"] : [];
1842
+ for (const entry of entries) {
1843
+ if (!isRecord3(entry) || !isRecord3(entry["path"])) continue;
1844
+ const target = entry["path"];
1845
+ const path = target["type"] === "path" ? asString(target["path"]) : target["type"] === "glob_pattern" ? asString(target["pattern"]) : void 0;
1846
+ if (!path) continue;
1847
+ if (entry["access"] === "read") read.push(path);
1848
+ if (entry["access"] === "write") write.push(path);
1849
+ }
1850
+ const reason = trimText(record["reason"]);
1851
+ return {
1852
+ ...reason ? { reason } : {},
1853
+ read: [...new Set(read)],
1854
+ write: [...new Set(write)],
1855
+ network: network["enabled"] === true
1856
+ };
1857
+ }
1858
+ function toPermissionsResponse(decision, params, persistent) {
1859
+ if (decision !== "allow") return { permissions: {} };
1860
+ const requested = isRecord3(params) && isRecord3(params["permissions"]) ? params["permissions"] : {};
1861
+ return {
1862
+ permissions: {
1863
+ ...requested["fileSystem"] !== void 0 ? { fileSystem: requested["fileSystem"] } : {},
1864
+ ...requested["network"] !== void 0 ? { network: requested["network"] } : {}
1865
+ },
1866
+ scope: persistent ? "session" : "turn"
1867
+ };
1868
+ }
1791
1869
  function describeMcpElicitation(params) {
1792
1870
  const record = isRecord3(params) ? params : {};
1793
1871
  const appName = asString(record["appName"]) ?? asString(record["serverName"]) ?? void 0;
@@ -2171,19 +2249,31 @@ function mapNotificationToEvents(input) {
2171
2249
  if (canonical === "unknown") return [];
2172
2250
  const rawStatus = itemStatusOf(params);
2173
2251
  const status = rawStatus === "failed" || rawStatus === "declined" ? rawStatus : "completed";
2252
+ const completed = baseEvent({
2253
+ type: "item.completed",
2254
+ threadId,
2255
+ turnId,
2256
+ itemId,
2257
+ payload: {
2258
+ itemType: canonical,
2259
+ status,
2260
+ ...itemTitle(params) ? { title: itemTitle(params) } : {},
2261
+ ...itemDetail(params) ? { detail: itemDetail(params) } : {},
2262
+ ...params !== void 0 ? { data: params } : {}
2263
+ },
2264
+ method,
2265
+ providerInstanceId
2266
+ });
2267
+ const asyncQuestions = canonical === "assistant_message" && itemId ? toAsyncQuestions(params) : void 0;
2268
+ if (!asyncQuestions) return [completed];
2174
2269
  return [
2270
+ completed,
2175
2271
  baseEvent({
2176
- type: "item.completed",
2272
+ type: "user-input.requested",
2177
2273
  threadId,
2178
2274
  turnId,
2179
- itemId,
2180
- payload: {
2181
- itemType: canonical,
2182
- status,
2183
- ...itemTitle(params) ? { title: itemTitle(params) } : {},
2184
- ...itemDetail(params) ? { detail: itemDetail(params) } : {},
2185
- ...params !== void 0 ? { data: params } : {}
2186
- },
2275
+ requestId: `codex-async:${itemId}`,
2276
+ payload: { questions: asyncQuestions, responseMode: "message" },
2187
2277
  method,
2188
2278
  providerInstanceId
2189
2279
  })
@@ -2375,6 +2465,8 @@ function toRequestTypeFromKind(kind) {
2375
2465
  return "apply_patch_approval";
2376
2466
  case "mcp-elicitation":
2377
2467
  return "mcp_elicitation_approval";
2468
+ // 契约没有单独的权限审批类型;宿主把 unknown 当 tool 类审批卡片渲染。
2469
+ case "permission":
2378
2470
  default:
2379
2471
  return "unknown";
2380
2472
  }
@@ -2397,6 +2489,7 @@ function toRequestTypeFromMethod(method) {
2397
2489
  return "tool_user_input";
2398
2490
  case "item/tool/call":
2399
2491
  return "dynamic_tool_call";
2492
+ case "item/permissions/requestApproval":
2400
2493
  default:
2401
2494
  return "unknown";
2402
2495
  }
@@ -2441,6 +2534,17 @@ function mapServerRequestToEvents(input) {
2441
2534
  if (appName) payload["appName"] = appName;
2442
2535
  if (described.message) payload["detail"] = described.message;
2443
2536
  payload["args"] = input.params;
2537
+ } else if (input.method === "item/permissions/requestApproval") {
2538
+ const described = describePermissionsApproval(input.params);
2539
+ const paths = [...described.read, ...described.write];
2540
+ const detail = described.reason ?? (paths.length > 0 ? paths.join(", ") : void 0);
2541
+ if (detail) payload["detail"] = detail;
2542
+ payload["args"] = {
2543
+ ...described.reason ? { reason: described.reason } : {},
2544
+ ...described.read.length > 0 ? { read: described.read.join(", ") } : {},
2545
+ ...described.write.length > 0 ? { write: described.write.join(", ") } : {},
2546
+ ...described.network ? { network: "true" } : {}
2547
+ };
2444
2548
  } else if (input.method === "item/fileChange/requestApproval" || input.method.includes("applyPatch")) {
2445
2549
  const described = describeFileChangeApproval(input.params);
2446
2550
  if (described.detail) payload["detail"] = described.detail;
@@ -2474,7 +2578,13 @@ function mapServerRequestToEvents(input) {
2474
2578
  }
2475
2579
 
2476
2580
  // src/sessionRuntime.ts
2581
+ var COLLAB_META_LOOKUP_TIMEOUT_MS = 5e3;
2582
+ function metaValue(value) {
2583
+ const text = asString(value)?.trim();
2584
+ return text === void 0 || text.length === 0 ? void 0 : text;
2585
+ }
2477
2586
  function cancelWireFor(pending) {
2587
+ if (pending.kind === "permission") return toPermissionsResponse("cancel", pending.params, false);
2478
2588
  return pending.kind === "mcp-elicitation" ? toMcpElicitationResponse("cancel", pending.params) : { decision: "cancel" };
2479
2589
  }
2480
2590
  var sleep = (ms) => new Promise((resolve2) => {
@@ -2524,6 +2634,7 @@ var CodexSessionRuntime = class {
2524
2634
  grants;
2525
2635
  declineRelay;
2526
2636
  collabChildren = /* @__PURE__ */ new Map();
2637
+ collabChildMeta = /* @__PURE__ */ new Map();
2527
2638
  liveForeignTurns = /* @__PURE__ */ new Map();
2528
2639
  collabReceiverTurns = /* @__PURE__ */ new Map();
2529
2640
  memoryThreadIds = /* @__PURE__ */ new Set();
@@ -2732,14 +2843,28 @@ var CodexSessionRuntime = class {
2732
2843
  }
2733
2844
  this.upsertCollabChild(agentThreadId, { agentPath: activity.agentPath });
2734
2845
  }
2846
+ if (method === "thread/settings/updated" || method === "model/rerouted") {
2847
+ const settings = isRecord3(params) && isRecord3(params["threadSettings"]) ? params["threadSettings"] : {};
2848
+ const reroute = method === "model/rerouted";
2849
+ const model = metaValue(
2850
+ reroute ? isRecord3(params) ? params["toModel"] : void 0 : settings["model"]
2851
+ );
2852
+ const effort = reroute ? void 0 : metaValue(settings["effort"]);
2853
+ this.updateCollabChildMeta(conversationId, { model, effort }, true);
2854
+ return;
2855
+ }
2735
2856
  const child = this.collabChildren.get(conversationId);
2736
2857
  if (method === "turn/started") {
2858
+ this.reopenCollabChildMeta(conversationId);
2737
2859
  const turnId = isRecord3(params) ? asString(params["turnId"]) ?? (isRecord3(params["turn"]) ? asString(params["turn"]["id"]) : null) : null;
2738
2860
  if (turnId) this.liveForeignTurns.set(conversationId, turnId);
2739
2861
  }
2740
2862
  if (method === "turn/completed" || method === "thread/closed") {
2741
2863
  this.liveForeignTurns.delete(conversationId);
2742
2864
  }
2865
+ if (method === "thread/closed" && this.collabChildren.has(conversationId)) {
2866
+ this.metaOf(conversationId).closed = true;
2867
+ }
2743
2868
  if (!child) {
2744
2869
  if (CHILD_CHATTER_METHODS.has(method) || !CHILD_AGENT_EVENT_METHODS.has(method)) return;
2745
2870
  return;
@@ -2800,6 +2925,7 @@ var CodexSessionRuntime = class {
2800
2925
  };
2801
2926
  this.collabChildren.set(agentThreadId, child);
2802
2927
  this.emitCollab("collabAgent/started", child);
2928
+ this.startCollabChildMetaLookup(agentThreadId);
2803
2929
  return;
2804
2930
  }
2805
2931
  if (agentPath === existing.agentPath && role === existing.role && nickname === existing.nickname) {
@@ -2814,12 +2940,64 @@ var CodexSessionRuntime = class {
2814
2940
  this.collabChildren.set(agentThreadId, merged);
2815
2941
  this.emitCollab("collabAgent/metadata", merged, { status: merged.status });
2816
2942
  }
2943
+ metaOf(agentThreadId) {
2944
+ let meta = this.collabChildMeta.get(agentThreadId);
2945
+ if (meta === void 0) {
2946
+ meta = { lookupStarted: false, closed: false };
2947
+ this.collabChildMeta.set(agentThreadId, meta);
2948
+ }
2949
+ return meta;
2950
+ }
2951
+ reopenCollabChildMeta(agentThreadId) {
2952
+ const meta = this.collabChildMeta.get(agentThreadId);
2953
+ if (meta !== void 0) meta.closed = false;
2954
+ }
2955
+ /**
2956
+ * 合并子线程的型号与推理档;变了且子代理已登记就补发一条不带状态的更新。
2957
+ * 通知报的是当前值(overwrite),回读只补缺:回读的回包可能比通知晚到,不能盖掉更新的值。
2958
+ */
2959
+ updateCollabChildMeta(agentThreadId, update, overwrite) {
2960
+ const meta = this.metaOf(agentThreadId);
2961
+ const model = update.model !== void 0 && (overwrite || !meta.model) ? update.model : meta.model;
2962
+ const effort = update.effort !== void 0 && (overwrite || !meta.effort) ? update.effort : meta.effort;
2963
+ if (model === meta.model && effort === meta.effort) return;
2964
+ if (model !== void 0) meta.model = model;
2965
+ if (effort !== void 0) meta.effort = effort;
2966
+ const child = this.collabChildren.get(agentThreadId);
2967
+ if (child !== void 0 && !meta.closed) this.emitCollab("collabAgent/metadataUpdated", child);
2968
+ }
2969
+ /**
2970
+ * 子线程已经在 CLI 里加载着:thread/resume 只是重新接上它,不开新一轮;
2971
+ * excludeTurns 避免把它的历史再读一遍。回包带当前的型号与推理档。
2972
+ */
2973
+ startCollabChildMetaLookup(agentThreadId) {
2974
+ const meta = this.metaOf(agentThreadId);
2975
+ if (meta.lookupStarted || meta.closed) return;
2976
+ meta.lookupStarted = true;
2977
+ void this.rpc.request(
2978
+ "thread/resume",
2979
+ { threadId: agentThreadId, excludeTurns: true },
2980
+ COLLAB_META_LOOKUP_TIMEOUT_MS
2981
+ ).then((response) => {
2982
+ if (this.closed || meta.closed || !isRecord3(response)) return;
2983
+ const thread = response["thread"];
2984
+ if (!isRecord3(thread) || thread["id"] !== agentThreadId) return;
2985
+ this.updateCollabChildMeta(
2986
+ agentThreadId,
2987
+ { model: metaValue(response["model"]), effort: metaValue(response["reasoningEffort"]) },
2988
+ false
2989
+ );
2990
+ }).catch(() => void 0);
2991
+ }
2817
2992
  emitCollab(method, child, extra) {
2993
+ const meta = this.collabChildMeta.get(child.agentThreadId);
2818
2994
  const params = {
2819
2995
  agentThreadId: child.agentThreadId,
2820
2996
  agentPath: child.agentPath,
2821
2997
  agentRole: child.role ?? pathLeaf2(child.agentPath) ?? "general-purpose",
2822
2998
  agentNickname: child.nickname,
2999
+ ...meta?.model ? { model: meta.model } : {},
3000
+ ...meta?.effort ? { effort: meta.effort } : {},
2823
3001
  ...isRecord3(extra) ? extra : {}
2824
3002
  };
2825
3003
  for (const event of mapNotificationToEvents({
@@ -2858,6 +3036,16 @@ var CodexSessionRuntime = class {
2858
3036
  await this.handleApproval(id, method, params, "mcp-elicitation", described.identity);
2859
3037
  return;
2860
3038
  }
3039
+ if (method === "item/permissions/requestApproval") {
3040
+ await this.handleApproval(
3041
+ id,
3042
+ method,
3043
+ params,
3044
+ "permission",
3045
+ isRecord3(params) ? asString(params["itemId"]) ?? "permission" : "permission"
3046
+ );
3047
+ return;
3048
+ }
2861
3049
  if (method === "item/tool/requestUserInput") {
2862
3050
  const requestId = this.requestIdFor(id);
2863
3051
  const events = mapServerRequestToEvents({
@@ -2886,7 +3074,7 @@ var CodexSessionRuntime = class {
2886
3074
  await this.rpc.respondError(id, -32601, `Method not found: ${method}`);
2887
3075
  }
2888
3076
  async handleApproval(id, method, params, kind, identity) {
2889
- const grant = this.grants.lookup(codexApprovalGrantKey(kind, identity));
3077
+ const grant = kind === "permission" ? void 0 : this.grants.lookup(codexApprovalGrantKey(kind, identity));
2890
3078
  if (grant) {
2891
3079
  const decision = wireDecisionFromPersist(kind, grant);
2892
3080
  await this.rpc.respond(id, { decision });
@@ -2928,7 +3116,9 @@ var CodexSessionRuntime = class {
2928
3116
  if (decision === "allow" && pending.kind === "mcp-elicitation") {
2929
3117
  wire = toMcpElicitationResponse("allow", pending.params);
2930
3118
  }
2931
- if (decision === "allow" && persist) {
3119
+ if (pending.kind === "permission") {
3120
+ wire = toPermissionsResponse(decision, pending.params, persist !== void 0);
3121
+ } else if (decision === "allow" && persist) {
2932
3122
  this.grants.remember(codexApprovalGrantKey(pending.kind, pending.identity), persist);
2933
3123
  if (pending.kind !== "mcp-elicitation") {
2934
3124
  wire = { decision: wireDecisionFromPersist(pending.kind, persist) };
@@ -3061,10 +3251,13 @@ var CodexSessionRuntime = class {
3061
3251
  });
3062
3252
  return this.session;
3063
3253
  }
3064
- developerInstructions(input) {
3065
- const mode = typeof input.interactionMode === "string" ? input.interactionMode : void 0;
3254
+ /** 宿主没给模式时沿用上一轮的模式;从没给过按 default。指令每轮都要带上。 */
3255
+ turnInteractionMode(input) {
3256
+ const mode = typeof input.interactionMode === "string" ? input.interactionMode : this.interactionMode ?? "default";
3066
3257
  this.interactionMode = mode;
3067
- if (mode === void 0) return void 0;
3258
+ return mode;
3259
+ }
3260
+ developerInstructions(input, mode) {
3068
3261
  const tools = hasConfiguredMcpServer(this.appServerArgs);
3069
3262
  const model = this.model ?? DEFAULT_MODEL;
3070
3263
  const effort = modelSelectionStringOption(input.modelSelection, "reasoningEffort") ?? DEFAULT_TEXT_GENERATION_REASONING_EFFORT;
@@ -3112,7 +3305,8 @@ var CodexSessionRuntime = class {
3112
3305
  }
3113
3306
  const sandboxPolicy = this.#turnSandboxPolicy();
3114
3307
  const config = this.#threadConfig();
3115
- const developerInstructions = this.developerInstructions(input);
3308
+ const interactionMode = this.turnInteractionMode(input);
3309
+ const developerInstructions = this.developerInstructions(input, interactionMode);
3116
3310
  const requestedModel = modelIdFromSelection(input.modelSelection) ?? this.model;
3117
3311
  const serviceTier = modelSelectionStringOption(input.modelSelection, "serviceTier");
3118
3312
  const params = {
@@ -3123,16 +3317,14 @@ var CodexSessionRuntime = class {
3123
3317
  sandboxPolicy,
3124
3318
  ...requestedModel ? { model: requestedModel } : {},
3125
3319
  ...serviceTier ? { serviceTier } : {},
3126
- ...developerInstructions ? { developerInstructions } : {}
3127
- };
3128
- const interactionMode = typeof input.interactionMode === "string" ? input.interactionMode : void 0;
3129
- if (interactionMode !== void 0) {
3130
- params["collaborationMode"] = codexCollaborationMode({
3320
+ // 指令只能走这里:turn 顶层的 developerInstructions 会被 CLI 静默丢掉(见 codexCollaborationMode)。
3321
+ collaborationMode: codexCollaborationMode({
3131
3322
  interactionMode,
3132
3323
  model: requestedModel ?? DEFAULT_MODEL,
3133
- reasoningEffort: modelSelectionStringOption(input.modelSelection, "reasoningEffort") ?? void 0
3134
- });
3135
- }
3324
+ reasoningEffort: modelSelectionStringOption(input.modelSelection, "reasoningEffort") ?? void 0,
3325
+ developerInstructions
3326
+ })
3327
+ };
3136
3328
  const isSteer = input.expectedTurnId !== void 0;
3137
3329
  const result = await markProviderRejection(
3138
3330
  () => isSteer ? this.rpc.request(
@@ -3231,9 +3423,10 @@ var CodexSessionRuntime = class {
3231
3423
  }
3232
3424
  return { threadId: this.threadId, turns: this.turns };
3233
3425
  }
3234
- settlePendingApprovals(decision) {
3426
+ /** 会话收口时把还挂着的审批答成「取消」:应答形状按审批种类,权限审批不认 decision。 */
3427
+ settlePendingApprovals() {
3235
3428
  for (const [requestId, pending] of this.pendingApprovals) {
3236
- pending.settle({ decision });
3429
+ pending.settle(cancelWireFor(pending));
3237
3430
  this.pendingApprovals.delete(requestId);
3238
3431
  }
3239
3432
  }
@@ -3248,7 +3441,7 @@ var CodexSessionRuntime = class {
3248
3441
  this.closed = true;
3249
3442
  this.requestKinds.clear();
3250
3443
  this.failPendingNativeWrites("session closed before write confirmation");
3251
- this.settlePendingApprovals("cancel");
3444
+ this.settlePendingApprovals();
3252
3445
  this.settlePendingUserInputs({});
3253
3446
  this.declineRelay.clear(this.threadId);
3254
3447
  this.grants.clear();
@@ -3837,7 +4030,7 @@ async function verifyCodexSignIn(input) {
3837
4030
  return {
3838
4031
  installed: false,
3839
4032
  version: null,
3840
- readiness: "error",
4033
+ readiness: "not-installed",
3841
4034
  auth: { status: "unknown" },
3842
4035
  detail: "Codex program path is not configured"
3843
4036
  };
@@ -4347,6 +4540,10 @@ ${extra}` : ""}`;
4347
4540
  }
4348
4541
 
4349
4542
  // src/codexInstance.ts
4543
+ function missingBinaryDetail(binaryPath) {
4544
+ const relyOnPath = !/[\\/]/.test(binaryPath);
4545
+ return `Could not start Codex (${binaryPath}). Check the "Program path" setting of this provider on this machine.` + (relyOnPath ? " The Codex desktop app may not add codex to PATH; enter the full path to the codex executable there." : " Make sure the configured file exists and can be run.");
4546
+ }
4350
4547
  var yieldTick = () => new Promise((resolve2) => {
4351
4548
  setImmediate(resolve2);
4352
4549
  });
@@ -4428,12 +4625,26 @@ var createCodexInstance = (config, host, attachments) => {
4428
4625
  let rpc;
4429
4626
  let version = null;
4430
4627
  try {
4431
- handle = await host.process.spawn({
4432
- program: recipe.binaryPath,
4433
- args: [...codexSpawnArgs(recipe, layout)],
4434
- owner: { bindingId: host.bindingId },
4435
- ...Object.keys(env).length > 0 ? { env } : {}
4436
- });
4628
+ try {
4629
+ handle = await host.process.spawn({
4630
+ program: recipe.binaryPath,
4631
+ args: [...codexSpawnArgs(recipe, layout)],
4632
+ owner: { bindingId: host.bindingId },
4633
+ ...Object.keys(env).length > 0 ? { env } : {}
4634
+ });
4635
+ } catch (error) {
4636
+ if (error instanceof ExecutorSpawnRefusedError) throw error;
4637
+ host.log.warn("codex binary could not be started", {
4638
+ program: recipe.binaryPath,
4639
+ error: error instanceof Error ? error.message : String(error)
4640
+ });
4641
+ return {
4642
+ version: null,
4643
+ models: void 0,
4644
+ skills: void 0,
4645
+ spawnFailed: describeExecutorSpawnFailure(error)
4646
+ };
4647
+ }
4437
4648
  trackHandle(handle, `${recipe.binaryPath} (probe)`);
4438
4649
  rpc = await createCodexRpcClient(handle, {
4439
4650
  onNotification: () => void 0,
@@ -4445,10 +4656,10 @@ var createCodexInstance = (config, host, attachments) => {
4445
4656
  );
4446
4657
  await rpc.notify("initialized");
4447
4658
  const models = input.listModels ? await listCodexModels(rpc) : void 0;
4448
- return { version, models, skills: await listCodexSkills(rpc) };
4659
+ return { version, models, skills: await listCodexSkills(rpc), spawnFailed: null };
4449
4660
  } catch (error) {
4450
4661
  if (error instanceof ExecutorSpawnRefusedError) throw error;
4451
- return { version, models: void 0, skills: void 0 };
4662
+ return { version, models: void 0, skills: void 0, spawnFailed: null };
4452
4663
  } finally {
4453
4664
  await rpc?.close().catch(() => void 0);
4454
4665
  await handle?.kill("SIGTERM").catch(() => void 0);
@@ -4469,15 +4680,24 @@ var createCodexInstance = (config, host, attachments) => {
4469
4680
  return {
4470
4681
  installed: false,
4471
4682
  version: null,
4472
- readiness: "error",
4683
+ readiness: "not-installed",
4473
4684
  auth: { status: "unknown" },
4474
4685
  detail: "Codex program path is not configured"
4475
4686
  };
4476
4687
  }
4477
4688
  const connectionError = codexApiConnectionError(recipe, recipe.environment);
4478
- const { version, models, skills } = await probeAppServer({
4689
+ const { version, models, skills, spawnFailed } = await probeAppServer({
4479
4690
  listModels: connectionError === null
4480
4691
  });
4692
+ if (spawnFailed !== null) {
4693
+ return {
4694
+ installed: false,
4695
+ version: null,
4696
+ readiness: spawnFailed.readiness,
4697
+ auth: { status: "unknown" },
4698
+ detail: spawnFailed.readiness === "not-installed" ? missingBinaryDetail(recipe.binaryPath) : spawnFailed.detail
4699
+ };
4700
+ }
4481
4701
  if (connectionError) {
4482
4702
  return {
4483
4703
  installed: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/agent-codex",
3
- "version": "0.2.2596",
3
+ "version": "0.2.2658",
4
4
  "description": "Codex executor: Codex CLI over the app-server JSON-RPC protocol.",
5
5
  "keywords": [
6
6
  "ycodium",
@@ -29,7 +29,7 @@
29
29
  "tailwindcss": "^4.0.0",
30
30
  "typescript": "~6.0.3",
31
31
  "vite-plus": "0.2.2",
32
- "@ycodium-ai/plugin-api": "0.2.2596",
32
+ "@ycodium-ai/plugin-api": "0.2.2658",
33
33
  "ycodium-executor-testing": "0.0.0"
34
34
  },
35
35
  "ycodium": {