@ycodium-ai/agent-codex 0.2.3484 → 0.2.3502

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 +184 -188
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -55,7 +55,10 @@ var CODEX_CARD = {
55
55
  // 出处不是 provider 文档:这是产品此前对所有 provider 统一施加的单图上限(10 MiB),
56
56
  // codex 在这个上限下一直把图片以 data URL 内联。没有查到 Codex / OpenAI 对单图的明文
57
57
  // 上限,所以不声明尺寸与合计上限;查到后改这里即可,宿主不需要跟着变。
58
- inlineImages: { maxBytes: 10 * 1024 * 1024 }
58
+ inlineImages: { maxBytes: 10 * 1024 * 1024 },
59
+ // 其它文件夹每轮进 sandboxPolicy.writableRoots(codexTurnSandboxPolicyForNativeMode)。
60
+ // CLI 不读这些文件夹里的 AGENTS.md,说明文件由宿主的开场说明点出。
61
+ acceptsAdditionalDirectories: true
59
62
  },
60
63
  credentialSources: [],
61
64
  diagnostics: [],
@@ -1682,11 +1685,11 @@ function codexThreadConfigForNativeMode(nativeModeId) {
1682
1685
  return runtimeModeToThreadConfig("approval-required");
1683
1686
  }
1684
1687
  }
1685
- function codexTurnSandboxPolicyForNativeMode(nativeModeId) {
1688
+ function codexTurnSandboxPolicyForNativeMode(nativeModeId, additionalDirectories = []) {
1686
1689
  switch (nativeModeId) {
1687
1690
  case "workspace-write":
1688
1691
  case "auto-review":
1689
- return runtimeModeToTurnSandboxPolicy("auto-accept-edits");
1692
+ return additionalDirectories.length > 0 ? { type: "workspaceWrite", writableRoots: additionalDirectories } : runtimeModeToTurnSandboxPolicy("auto-accept-edits");
1690
1693
  case "full-access":
1691
1694
  return runtimeModeToTurnSandboxPolicy("full-access");
1692
1695
  default:
@@ -2452,49 +2455,30 @@ async function spawnCodexAppServer(input) {
2452
2455
  }
2453
2456
 
2454
2457
  // src/cliVersion.ts
2458
+ import {
2459
+ runProcessOperation,
2460
+ isOperationCleanupError
2461
+ } from "@ycodium-ai/plugin-api/process-operation";
2455
2462
  var CODEX_VERSION_TIMEOUT_MS = 15e3;
2456
2463
  var VERSION_PATTERN = /(?<![\d.])(\d+\.\d+\.\d+(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?)/;
2457
- async function collect(stream) {
2458
- const chunks = [];
2459
- for await (const chunk of stream) {
2460
- chunks.push(Buffer.from(chunk));
2461
- if (Buffer.concat(chunks).length > 64 * 1024) break;
2462
- }
2463
- return Buffer.concat(chunks).toString("utf8");
2464
- }
2465
2464
  async function readCodexCliVersion(input) {
2465
+ input.signal?.throwIfAborted();
2466
2466
  if (input.recipe.binaryPath.length === 0) return null;
2467
- let handle;
2468
2467
  try {
2469
- handle = await input.host.process.spawn({
2468
+ const { stdout, stderr, exitCode } = await runProcessOperation(input.host, {
2470
2469
  program: input.recipe.binaryPath,
2471
2470
  args: ["--version"],
2472
- owner: { bindingId: input.host.bindingId }
2471
+ owner: { bindingId: input.host.bindingId },
2472
+ signal: input.signal,
2473
+ timeoutMs: CODEX_VERSION_TIMEOUT_MS,
2474
+ maxOutputBytes: 64 * 1024
2473
2475
  });
2474
- } catch {
2475
- return null;
2476
- }
2477
- const collected = Promise.all([collect(handle.stdout), collect(handle.stderr), handle.exited]);
2478
- let timer;
2479
- const timeout = new Promise((resolve4) => {
2480
- timer = setTimeout(() => resolve4("timeout"), CODEX_VERSION_TIMEOUT_MS);
2481
- timer.unref?.();
2482
- });
2483
- try {
2484
- const settled = await Promise.race([collected, timeout]);
2485
- if (settled === "timeout") {
2486
- collected.catch(() => void 0);
2487
- return null;
2488
- }
2489
- const [stdout, stderr, exitCode] = settled;
2490
2476
  if (exitCode !== 0) return null;
2491
- return VERSION_PATTERN.exec(`${stdout}
2492
- ${stderr}`)?.[1] ?? null;
2493
- } catch {
2477
+ return VERSION_PATTERN.exec(stdout + "\n" + stderr)?.[1] ?? null;
2478
+ } catch (error) {
2479
+ if (isOperationCleanupError(error)) throw error;
2480
+ input.signal?.throwIfAborted();
2494
2481
  return null;
2495
- } finally {
2496
- if (timer !== void 0) clearTimeout(timer);
2497
- await handle.kill("SIGTERM").catch(() => void 0);
2498
2482
  }
2499
2483
  }
2500
2484
 
@@ -2726,6 +2710,88 @@ async function createCodexRpcClient(handle, handlers) {
2726
2710
  };
2727
2711
  }
2728
2712
 
2713
+ // src/processReuse.ts
2714
+ import * as NodeUtil from "node:util";
2715
+
2716
+ // src/skills.ts
2717
+ var SKILLS_LIST_TIMEOUT_MS = 15e3;
2718
+ function codexProbeSkill(raw, includeRepo = false) {
2719
+ if (!isRecord2(raw)) return null;
2720
+ const name = asString(raw["name"]);
2721
+ const path = asString(raw["path"]);
2722
+ const scope = asString(raw["scope"]);
2723
+ if (!name || !path || scope === "repo" && !includeRepo) return null;
2724
+ const face = isRecord2(raw["interface"]) ? raw["interface"] : void 0;
2725
+ const description = asString(raw["description"]);
2726
+ const displayName = asString(face?.["displayName"]);
2727
+ const shortDescription = asString(face?.["shortDescription"]) || asString(raw["shortDescription"]);
2728
+ return {
2729
+ name,
2730
+ path,
2731
+ enabled: raw["enabled"] !== false,
2732
+ ...scope ? { scope } : {},
2733
+ ...description ? { description } : {},
2734
+ ...displayName ? { displayName } : {},
2735
+ ...shortDescription ? { shortDescription } : {}
2736
+ };
2737
+ }
2738
+ async function listCodexWorkspaceSkills(rpc, cwd) {
2739
+ const response = await rpc.request("skills/list", { cwds: [cwd] }, SKILLS_LIST_TIMEOUT_MS);
2740
+ if (!isRecord2(response) || !Array.isArray(response["data"])) {
2741
+ throw new Error("skills/list returned an unexpected shape");
2742
+ }
2743
+ const skills = /* @__PURE__ */ new Map();
2744
+ for (const entry of response["data"]) {
2745
+ if (!isRecord2(entry) || !Array.isArray(entry["skills"])) continue;
2746
+ for (const raw of entry["skills"]) {
2747
+ const skill = codexProbeSkill(raw, true);
2748
+ if (skill !== null) skills.set(skill.path, skill);
2749
+ }
2750
+ }
2751
+ return [...skills.values()].toSorted((left, right) => left.name.localeCompare(right.name));
2752
+ }
2753
+ async function listCodexSkills(rpc) {
2754
+ let response;
2755
+ try {
2756
+ response = await rpc.request("skills/list", {}, SKILLS_LIST_TIMEOUT_MS);
2757
+ } catch {
2758
+ return void 0;
2759
+ }
2760
+ if (!isRecord2(response) || !Array.isArray(response["data"])) return void 0;
2761
+ const skills = /* @__PURE__ */ new Map();
2762
+ for (const entry of response["data"]) {
2763
+ if (!isRecord2(entry) || !Array.isArray(entry["skills"])) continue;
2764
+ for (const raw of entry["skills"]) {
2765
+ const skill = codexProbeSkill(raw);
2766
+ if (skill !== null) skills.set(skill.path, skill);
2767
+ }
2768
+ }
2769
+ return [...skills.values()].toSorted((left, right) => left.name.localeCompare(right.name));
2770
+ }
2771
+ var codexSessionSkillRoot = (skills) => skills === void 0 || skills.names.length === 0 ? void 0 : skills.skillsDir;
2772
+ async function setCodexSessionSkillRoots(rpc, skills, log, threadId) {
2773
+ const root = codexSessionSkillRoot(skills);
2774
+ if (root === void 0) return;
2775
+ try {
2776
+ await rpc.request("skills/extraRoots/set", { extraRoots: [root] });
2777
+ } catch (error) {
2778
+ log.warn("codex did not take the session skill root; skills stay on the built-in tools", {
2779
+ threadId,
2780
+ error: error instanceof Error ? error.message : String(error)
2781
+ });
2782
+ }
2783
+ }
2784
+
2785
+ // src/processReuse.ts
2786
+ var reusesCodexProcess = (running, input, cwd) => {
2787
+ const { runtime, launch } = running;
2788
+ const resume = input.discardResumeCursor === true ? void 0 : parseCodexResumeCursor(input.resumeCursor);
2789
+ return !runtime.closing && runtime.nativeThreadId !== void 0 && resume?.threadId === runtime.nativeThreadId && runtime.cwd === cwd && codexNativeModeFor(runtime.runtimeMode, runtime.nativeModeId) === codexNativeModeFor(input.runtimeMode, input.nativeModeId) && NodeUtil.isDeepStrictEqual(
2790
+ codexMcpThreadConfig(launch.mcpServers ?? []),
2791
+ codexMcpThreadConfig(input.mcpServers ?? [])
2792
+ ) && codexSessionSkillRoot(launch.skills) === codexSessionSkillRoot(input.skills);
2793
+ };
2794
+
2729
2795
  // src/sessionRuntime.ts
2730
2796
  import * as NodeCrypto4 from "node:crypto";
2731
2797
 
@@ -5225,6 +5291,8 @@ var CodexSessionRuntime = class {
5225
5291
  /** 这个会话见过的主额度快照(按字段合并),额度用尽停轮时点名窗口用。 */
5226
5292
  rateLimits;
5227
5293
  appServerArgs;
5294
+ /** 会话除 cwd 外的文件夹,每轮随 `sandboxPolicy` 发;变了由 `setAdditionalDirectories` 换上,进程不重启。 */
5295
+ #additionalDirectories;
5228
5296
  hasConfiguredMcpServer;
5229
5297
  host;
5230
5298
  attachments;
@@ -5250,7 +5318,8 @@ var CodexSessionRuntime = class {
5250
5318
  /** 同上,沙箱策略那一位。 */
5251
5319
  #turnSandboxPolicy() {
5252
5320
  return codexTurnSandboxPolicyForNativeMode(
5253
- codexNativeModeFor(this.runtimeMode, this.nativeModeId)
5321
+ codexNativeModeFor(this.runtimeMode, this.nativeModeId),
5322
+ this.#additionalDirectories
5254
5323
  );
5255
5324
  }
5256
5325
  constructor(host, rpc, emit, input, cwd, appServerArgs, grants, declineRelay, attachments, closeOptions = {}) {
@@ -5272,6 +5341,7 @@ var CodexSessionRuntime = class {
5272
5341
  this.runtimeMode = input.runtimeMode;
5273
5342
  this.nativeModeId = input.nativeModeId;
5274
5343
  this.appServerArgs = appServerArgs;
5344
+ this.#additionalDirectories = input.additionalDirectories ?? [];
5275
5345
  this.hasConfiguredMcpServer = (input.mcpServers?.length ?? 0) > 0;
5276
5346
  this.grants = grants ?? createApprovalGrantStore();
5277
5347
  this.declineRelay = declineRelay ?? createDeclineReasonRelay();
@@ -5295,6 +5365,10 @@ var CodexSessionRuntime = class {
5295
5365
  this.writeAcks.set(jsonRpcId, { resolve: resolve4 });
5296
5366
  });
5297
5367
  }
5368
+ /** 下一轮(含插话)起按这份发可写根;正在跑的一轮不变。 */
5369
+ setAdditionalDirectories(directories) {
5370
+ this.#additionalDirectories = directories;
5371
+ }
5298
5372
  settleNativeWrite(requestId, status) {
5299
5373
  const ack = this.writeAcks.get(requestId);
5300
5374
  if (!ack) return;
@@ -5906,73 +5980,6 @@ async function listCodexModels(rpc) {
5906
5980
  return models;
5907
5981
  }
5908
5982
 
5909
- // src/skills.ts
5910
- var SKILLS_LIST_TIMEOUT_MS = 15e3;
5911
- function codexProbeSkill(raw, includeRepo = false) {
5912
- if (!isRecord2(raw)) return null;
5913
- const name = asString(raw["name"]);
5914
- const path = asString(raw["path"]);
5915
- const scope = asString(raw["scope"]);
5916
- if (!name || !path || scope === "repo" && !includeRepo) return null;
5917
- const face = isRecord2(raw["interface"]) ? raw["interface"] : void 0;
5918
- const description = asString(raw["description"]);
5919
- const displayName = asString(face?.["displayName"]);
5920
- const shortDescription = asString(face?.["shortDescription"]) || asString(raw["shortDescription"]);
5921
- return {
5922
- name,
5923
- path,
5924
- enabled: raw["enabled"] !== false,
5925
- ...scope ? { scope } : {},
5926
- ...description ? { description } : {},
5927
- ...displayName ? { displayName } : {},
5928
- ...shortDescription ? { shortDescription } : {}
5929
- };
5930
- }
5931
- async function listCodexWorkspaceSkills(rpc, cwd) {
5932
- const response = await rpc.request("skills/list", { cwds: [cwd] }, SKILLS_LIST_TIMEOUT_MS);
5933
- if (!isRecord2(response) || !Array.isArray(response["data"])) {
5934
- throw new Error("skills/list returned an unexpected shape");
5935
- }
5936
- const skills = /* @__PURE__ */ new Map();
5937
- for (const entry of response["data"]) {
5938
- if (!isRecord2(entry) || !Array.isArray(entry["skills"])) continue;
5939
- for (const raw of entry["skills"]) {
5940
- const skill = codexProbeSkill(raw, true);
5941
- if (skill !== null) skills.set(skill.path, skill);
5942
- }
5943
- }
5944
- return [...skills.values()].toSorted((left, right) => left.name.localeCompare(right.name));
5945
- }
5946
- async function listCodexSkills(rpc) {
5947
- let response;
5948
- try {
5949
- response = await rpc.request("skills/list", {}, SKILLS_LIST_TIMEOUT_MS);
5950
- } catch {
5951
- return void 0;
5952
- }
5953
- if (!isRecord2(response) || !Array.isArray(response["data"])) return void 0;
5954
- const skills = /* @__PURE__ */ new Map();
5955
- for (const entry of response["data"]) {
5956
- if (!isRecord2(entry) || !Array.isArray(entry["skills"])) continue;
5957
- for (const raw of entry["skills"]) {
5958
- const skill = codexProbeSkill(raw);
5959
- if (skill !== null) skills.set(skill.path, skill);
5960
- }
5961
- }
5962
- return [...skills.values()].toSorted((left, right) => left.name.localeCompare(right.name));
5963
- }
5964
- async function setCodexSessionSkillRoots(rpc, skills, log, threadId) {
5965
- if (skills === void 0 || skills.names.length === 0) return;
5966
- try {
5967
- await rpc.request("skills/extraRoots/set", { extraRoots: [skills.skillsDir] });
5968
- } catch (error) {
5969
- log.warn("codex did not take the session skill root; skills stay on the built-in tools", {
5970
- threadId,
5971
- error: error instanceof Error ? error.message : String(error)
5972
- });
5973
- }
5974
- }
5975
-
5976
5983
  // src/workspaceInventory.ts
5977
5984
  import * as NodeFS6 from "node:fs";
5978
5985
  import * as NodePath8 from "node:path";
@@ -6366,6 +6373,10 @@ import * as NodePath9 from "node:path";
6366
6373
  import {
6367
6374
  mergeLaunchEnv as mergeLaunchEnv2
6368
6375
  } from "@ycodium-ai/plugin-api/executor";
6376
+ import {
6377
+ OperationCleanupError,
6378
+ runProcessOperation as runProcessOperation2
6379
+ } from "@ycodium-ai/plugin-api/process-operation";
6369
6380
  var CODEX_TIMEOUT_MS = 18e4;
6370
6381
  var EARLIER_CONTENT_TRUNCATION_MARKER = "[Earlier content truncated]\n\n";
6371
6382
  function limitSection(value, maxChars) {
@@ -6518,98 +6529,78 @@ function absoluteImagePaths(attachments) {
6518
6529
  }
6519
6530
  return paths;
6520
6531
  }
6521
- async function collectStream(stream) {
6522
- const decoder = new TextDecoder();
6523
- let out = "";
6524
- for await (const chunk of stream) out += decoder.decode(chunk, { stream: true });
6525
- return out;
6526
- }
6527
6532
  function createCodexTextGeneration(input) {
6528
6533
  const run = async (operation, cwd, prompt, schema, modelSelection, imagePaths, signal) => {
6529
6534
  if (input.recipe.binaryPath.length === 0) {
6530
6535
  throw new Error("Codex CLI is required but not available on PATH.");
6531
6536
  }
6537
+ signal?.throwIfAborted();
6532
6538
  const tmp = await NodeFSP5.mkdtemp(NodePath9.join(NodeOS3.tmpdir(), "ycodium-codex-tg-"));
6533
- const schemaPath = NodePath9.join(tmp, "schema.json");
6534
- const outputPath = NodePath9.join(tmp, "output.json");
6535
- await NodeFSP5.writeFile(schemaPath, JSON.stringify(schema));
6536
- await NodeFSP5.writeFile(outputPath, "");
6537
- const reasoningEffort = modelSelectionStringOption(modelSelection, "reasoningEffort") ?? DEFAULT_TEXT_GENERATION_REASONING_EFFORT;
6538
- const serviceTier = modelSelectionStringOption(modelSelection, "serviceTier");
6539
- const model = modelIdFromSelection(modelSelection) ?? "gpt-5.6-luna";
6540
- const env = mergeLaunchEnv2({
6541
- accountHome: input.layout.effectiveHomePath ? { CODEX_HOME: input.layout.effectiveHomePath } : input.recipe.homePath.trim() ? { CODEX_HOME: expandHomePath(input.recipe.homePath) } : {},
6542
- instanceTable: input.recipe.environment
6543
- });
6544
- const args = [
6545
- "exec",
6546
- ...codexExecLaunchArgs(input.recipe.launchArgs),
6547
- "--ephemeral",
6548
- "--skip-git-repo-check",
6549
- "-s",
6550
- "read-only",
6551
- "--model",
6552
- model,
6553
- "--config",
6554
- `model_reasoning_effort="${reasoningEffort}"`,
6555
- ...serviceTier ? ["--config", `service_tier="${serviceTier}"`] : [],
6556
- "--output-schema",
6557
- schemaPath,
6558
- "--output-last-message",
6559
- outputPath,
6560
- ...imagePaths.flatMap((imagePath) => ["--image", imagePath]),
6561
- "-"
6562
- ];
6563
- const handle = await input.host.process.spawn({
6564
- program: input.recipe.binaryPath,
6565
- args,
6566
- cwd: operation === "summarizeTurn" ? tmp : cwd,
6567
- env,
6568
- owner: { bindingId: input.host.bindingId }
6569
- });
6570
- const onAbort = () => {
6571
- void handle.kill("SIGTERM").catch(() => void 0);
6572
- };
6573
- signal?.addEventListener("abort", onAbort);
6574
- let timer;
6539
+ let safeToRemove = true;
6575
6540
  try {
6576
- await handle.writeStdin(prompt);
6577
- await handle.closeStdin();
6578
- const timedOut = await Promise.race([
6579
- handle.exited.then(() => false),
6580
- new Promise((resolve4) => {
6581
- timer = setTimeout(() => resolve4(true), CODEX_TIMEOUT_MS);
6582
- })
6583
- ]);
6584
- if (timedOut) {
6585
- await handle.kill("SIGTERM").catch(() => void 0);
6586
- throw new Error("Codex CLI request timed out.");
6587
- }
6588
- const [stdout, stderr, exitCode] = await Promise.all([
6589
- collectStream(handle.stdout),
6590
- collectStream(handle.stderr),
6591
- handle.exited
6592
- ]);
6593
- if (exitCode !== 0) {
6594
- const detail = stderr.trim() || stdout.trim();
6595
- throw new Error(
6596
- detail.length > 0 ? `Codex CLI command failed: ${detail}` : `Codex CLI command failed with code ${String(exitCode)}.`
6597
- );
6598
- }
6599
- const raw = await NodeFSP5.readFile(outputPath, "utf8");
6600
- let parsed;
6541
+ const schemaPath = NodePath9.join(tmp, "schema.json");
6542
+ const outputPath = NodePath9.join(tmp, "output.json");
6543
+ await NodeFSP5.writeFile(schemaPath, JSON.stringify(schema));
6544
+ await NodeFSP5.writeFile(outputPath, "");
6545
+ const reasoningEffort = modelSelectionStringOption(modelSelection, "reasoningEffort") ?? DEFAULT_TEXT_GENERATION_REASONING_EFFORT;
6546
+ const serviceTier = modelSelectionStringOption(modelSelection, "serviceTier");
6547
+ const model = modelIdFromSelection(modelSelection) ?? "gpt-5.6-luna";
6548
+ const env = mergeLaunchEnv2({
6549
+ accountHome: input.layout.effectiveHomePath ? { CODEX_HOME: input.layout.effectiveHomePath } : input.recipe.homePath.trim() ? { CODEX_HOME: expandHomePath(input.recipe.homePath) } : {},
6550
+ instanceTable: input.recipe.environment
6551
+ });
6552
+ const args = [
6553
+ "exec",
6554
+ ...codexExecLaunchArgs(input.recipe.launchArgs),
6555
+ "--ephemeral",
6556
+ "--skip-git-repo-check",
6557
+ "-s",
6558
+ "read-only",
6559
+ "--model",
6560
+ model,
6561
+ "--config",
6562
+ `model_reasoning_effort="${reasoningEffort}"`,
6563
+ ...serviceTier ? ["--config", `service_tier="${serviceTier}"`] : [],
6564
+ "--output-schema",
6565
+ schemaPath,
6566
+ "--output-last-message",
6567
+ outputPath,
6568
+ ...imagePaths.flatMap((imagePath) => ["--image", imagePath]),
6569
+ "-"
6570
+ ];
6601
6571
  try {
6602
- parsed = JSON.parse(raw);
6572
+ const { stdout, stderr, exitCode } = await runProcessOperation2(input.host, {
6573
+ program: input.recipe.binaryPath,
6574
+ args,
6575
+ cwd: operation === "summarizeTurn" ? tmp : cwd,
6576
+ env,
6577
+ owner: { bindingId: input.host.bindingId },
6578
+ signal,
6579
+ timeoutMs: CODEX_TIMEOUT_MS,
6580
+ input: prompt
6581
+ });
6582
+ if (exitCode !== 0) {
6583
+ const detail = stderr.trim() || stdout.trim();
6584
+ throw new Error(
6585
+ detail.length > 0 ? `Codex CLI command failed: ${detail}` : `Codex CLI command failed with code ${String(exitCode)}.`
6586
+ );
6587
+ }
6588
+ const raw = await NodeFSP5.readFile(outputPath, "utf8");
6589
+ let parsed;
6590
+ try {
6591
+ parsed = JSON.parse(raw);
6592
+ } catch (cause) {
6593
+ throw new Error("Codex returned invalid structured output.", { cause });
6594
+ }
6595
+ if (!isRecord2(parsed)) throw new Error("Codex returned invalid structured output.");
6596
+ return parsed;
6603
6597
  } catch (cause) {
6604
- throw new Error("Codex returned invalid structured output.", { cause });
6598
+ if (cause instanceof OperationCleanupError) safeToRemove = false;
6599
+ throw cause;
6605
6600
  }
6606
- if (!isRecord2(parsed)) throw new Error("Codex returned invalid structured output.");
6607
- return parsed;
6608
6601
  } finally {
6609
- if (timer !== void 0) clearTimeout(timer);
6610
- signal?.removeEventListener("abort", onAbort);
6611
- await NodeFSP5.rm(tmp, { recursive: true, force: true }).catch(() => void 0);
6612
- void operation;
6602
+ if (safeToRemove)
6603
+ await NodeFSP5.rm(tmp, { recursive: true, force: true }).catch(() => void 0);
6613
6604
  }
6614
6605
  };
6615
6606
  return {
@@ -7611,7 +7602,7 @@ var createCodexInstance = (config, host, attachments, cache) => {
7611
7602
  ...CODEX_NATIVE_MODE_REPORT
7612
7603
  };
7613
7604
  },
7614
- readCliVersion: () => readCodexCliVersion({ recipe, host }),
7605
+ readCliVersion: (signal) => readCodexCliVersion({ recipe, host, ...signal ? { signal } : {} }),
7615
7606
  // 有意不处理 input.readOnlyDirectories:codex 的沙箱只限制写入,读不受限,
7616
7607
  // 附件目录本来就能读,无需放行。
7617
7608
  startSession: async (input, signal) => {
@@ -7625,6 +7616,11 @@ var createCodexInstance = (config, host, attachments, cache) => {
7625
7616
  const existing = threads.get(input.threadId);
7626
7617
  if (existing !== void 0) {
7627
7618
  if (existing.closing) throw new Error(`thread ${input.threadId} is closing`);
7619
+ if (reusesCodexProcess(existing, input, cwd)) {
7620
+ existing.runtime.setAdditionalDirectories(input.additionalDirectories ?? []);
7621
+ host.log.info("session reused", { threadId: input.threadId });
7622
+ return existing.runtime.session;
7623
+ }
7628
7624
  await stopBooked(input.threadId, existing);
7629
7625
  }
7630
7626
  const layout = layoutFor(recipe);
@@ -7679,7 +7675,7 @@ var createCodexInstance = (config, host, attachments, cache) => {
7679
7675
  attachments,
7680
7676
  { requestClose: () => stopBooked(input.threadId, booked, "process") }
7681
7677
  );
7682
- booked = { runtime, rpc, handle };
7678
+ booked = { runtime, rpc, handle, launch: input };
7683
7679
  threads.set(input.threadId, booked);
7684
7680
  try {
7685
7681
  await rpc.request("initialize", CODEX_INITIALIZE_PARAMS);
@@ -7924,7 +7920,7 @@ var executors = [
7924
7920
  ];
7925
7921
  var index_default = definePlugin({
7926
7922
  id: "agent-codex",
7927
- apiVersion: 39,
7923
+ apiVersion: 41,
7928
7924
  executors,
7929
7925
  activate(ctx) {
7930
7926
  capturedAttachments = ctx.attachments;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ycodium-ai/agent-codex",
3
- "version": "0.2.3484",
3
+ "version": "0.2.3502",
4
4
  "description": "Codex executor: Codex CLI over the app-server JSON-RPC protocol.",
5
5
  "keywords": [
6
6
  "ycodium",
@@ -29,9 +29,9 @@
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.3484",
32
+ "ycodium-jsonl-stream": "0.0.0",
33
33
  "ycodium-executor-testing": "0.0.0",
34
- "ycodium-jsonl-stream": "0.0.0"
34
+ "@ycodium-ai/plugin-api": "0.2.3502"
35
35
  },
36
36
  "ycodium": {
37
37
  "plugin": "./dist/index.js",