claude2codex 0.3.5 → 0.3.7

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.
package/README.md CHANGED
@@ -231,7 +231,8 @@ Codex then enters its goal-continuation loop: after each turn, it checks "did I
231
231
  | `CODEX_MODEL` | Codex default | Model override |
232
232
  | `CODEX_APPROVAL_POLICY` | `never` | Approval policy (autonomous by default) |
233
233
  | `CODEX_PERMISSIONS` | Unset | Permissions profile passed to Codex |
234
- | `CODEX_JOB_TIMEOUT_MS` | `1800000` | Maximum job duration (30 min) |
234
+ | `CODEX_JOB_TIMEOUT_MS` | `7200000` | Overall maximum job duration (2 hours) |
235
+ | `C2C_IDLE_TIMEOUT_MS` | `1200000` | Timeout after no Codex notifications during a running attempt (20 min); `0` disables. Queued/dependency wait time excluded |
235
236
  | `CODEX_QUIET_MS` | `30000` | Quiet period before treating an active goal as finished |
236
237
  | `GOAL_OBJECTIVE_MAX` | `2000` | Maximum rendered goal-objective length; `codex_implement` rejects over-limit contracts at submit time |
237
238
  | `C2C_STATE_DIR` | `~/.claude2codex/jobs` | Persistent job registry (atomic per-job JSON files) |
package/README.zh-CN.md CHANGED
@@ -252,7 +252,8 @@ Python 3.12,Poetry。从项目根目录运行。
252
252
  | `CODEX_MODEL` | Codex 默认 | 模型覆盖 |
253
253
  | `CODEX_APPROVAL_POLICY` | `never` | 审批策略(默认自主执行) |
254
254
  | `CODEX_PERMISSIONS` | 未设置 | 权限配置透传 |
255
- | `CODEX_JOB_TIMEOUT_MS` | `1800000` | 单任务最大时长(30 分钟) |
255
+ | `CODEX_JOB_TIMEOUT_MS` | `7200000` | 单任务总时长硬上限(2 小时) |
256
+ | `C2C_IDLE_TIMEOUT_MS` | `1200000` | 运行中的尝试连续无 Codex 通知时超时(20 分钟),`0` 禁用;排队和等待依赖的时间不计入 |
256
257
  | `CODEX_QUIET_MS` | `30000` | 判定活跃目标已完成前的静默等待时间 |
257
258
  | `GOAL_OBJECTIVE_MAX` | `2000` | 渲染后 goal-objective 的最大长度;超限的合同会被 `codex_implement` 在提交时拒绝 |
258
259
  | `C2C_STATE_DIR` | `~/.claude2codex/jobs` | 持久化任务注册表(每任务一个原子写入的 JSON 文件) |
package/dist/server.js CHANGED
@@ -262,7 +262,7 @@ var CodexAppServer = class {
262
262
  */
263
263
  async initialize() {
264
264
  const result = await this.request("initialize", {
265
- clientInfo: { name: "claude2codex", title: "Claude\u2194Codex Contract Bridge", version: "0.3.5" },
265
+ clientInfo: { name: "claude2codex", title: "Claude\u2194Codex Contract Bridge", version: "0.3.7" },
266
266
  capabilities: { experimentalApi: true }
267
267
  });
268
268
  this.notify("initialized", {});
@@ -641,6 +641,7 @@ async function runAttempt(job, opts, timeoutMs) {
641
641
  let lastCodexError;
642
642
  let quietTimer;
643
643
  let stallTimer;
644
+ let idleTimer;
644
645
  let settle;
645
646
  const doneSignal = new Promise((res) => settle = res);
646
647
  const progress = (event, message) => opts.onProgress?.({ jobId: job.id, event, message });
@@ -675,6 +676,14 @@ async function runAttempt(job, opts, timeoutMs) {
675
676
  settle();
676
677
  }, cfg.quietMs);
677
678
  }
679
+ function armIdleTimer() {
680
+ clearTimeout(idleTimer);
681
+ if (cfg.idleTimeoutMs === 0) return;
682
+ idleTimer = setTimeout(() => {
683
+ finalize("timeout", `no Codex notification for ${cfg.idleTimeoutMs}ms (C2C_IDLE_TIMEOUT_MS)`);
684
+ settle();
685
+ }, cfg.idleTimeoutMs);
686
+ }
678
687
  function clearStallTimer() {
679
688
  clearTimeout(stallTimer);
680
689
  stallTimer = void 0;
@@ -708,15 +717,22 @@ async function runAttempt(job, opts, timeoutMs) {
708
717
  writeLog("info", "job_resumed", { job_id: job.id, stalled_for_ms: stalledForMs });
709
718
  progress("resumed", `job resumed after ${stalledForMs}ms`);
710
719
  }
720
+ armIdleTimer();
711
721
  job.lastActivityAt = now.toISOString();
712
722
  store.save(job);
713
723
  if (activeTurn) armStallTimer();
714
724
  }
715
725
  function finalize(state, error) {
726
+ clearTimeout(idleTimer);
716
727
  clearStallTimer();
728
+ if (state === "done" && job.turns === 0) {
729
+ state = "error";
730
+ error = "job ended without running a turn";
731
+ }
717
732
  finish(job, state, client, error);
718
733
  }
719
734
  function handleNotification(method, params) {
735
+ if (job.state !== "starting" && job.state !== "running") return;
720
736
  writeLog("debug", "codex_notification", {
721
737
  job_id: job.id,
722
738
  attempt: job.attempts ?? 1,
@@ -767,7 +783,7 @@ async function runAttempt(job, opts, timeoutMs) {
767
783
  tokens_used: job.goal?.tokensUsed ?? null
768
784
  });
769
785
  progress("goal_updated", `goal status=${job.goal?.status ?? "unknown"}`);
770
- if (job.goal?.status && TERMINAL_GOAL_STATUSES.has(job.goal.status) && !activeTurn) {
786
+ if (job.goal?.status && TERMINAL_GOAL_STATUSES.has(job.goal.status) && !activeTurn && job.turns > 0) {
771
787
  finalize("done");
772
788
  settle();
773
789
  }
@@ -834,10 +850,11 @@ async function runAttempt(job, opts, timeoutMs) {
834
850
  if (!activeTurn) armQuietTimer();
835
851
  }
836
852
  const overallTimer = setTimeout(() => {
837
- finalize("timeout", `job exceeded ${cfg.jobTimeoutMs}ms`);
853
+ finalize("timeout", `job exceeded ${cfg.jobTimeoutMs}ms (CODEX_JOB_TIMEOUT_MS)`);
838
854
  settle();
839
855
  }, timeoutMs);
840
856
  try {
857
+ armIdleTimer();
841
858
  await client.initialize();
842
859
  const threadParams = {
843
860
  cwd: cfg.cwd,
@@ -858,11 +875,11 @@ async function runAttempt(job, opts, timeoutMs) {
858
875
  if (!job.threadId) throw new Error(`could not determine thread id from ${JSON.stringify(threadRes).slice(0, 300)}`);
859
876
  store.save(job);
860
877
  log(job, "thread", `thread ${job.threadId} ${opts.resumeThreadId ? "resumed" : "started"}`);
861
- if (opts.objective) {
878
+ if (opts.objective || opts.resumeThreadId) {
862
879
  try {
863
880
  const goalRes = await client.request("thread/goal/set", {
864
881
  threadId: job.threadId,
865
- objective: opts.objective,
882
+ ...opts.objective ? { objective: opts.objective } : { status: "active" },
866
883
  ...opts.tokenBudget ? { tokenBudget: opts.tokenBudget } : {}
867
884
  });
868
885
  job.goalSet = true;
@@ -886,6 +903,7 @@ async function runAttempt(job, opts, timeoutMs) {
886
903
  const failure = processError ?? (error instanceof Error ? error : new Error(String(error)));
887
904
  return { error: failure, processFailure: client.exited };
888
905
  } finally {
906
+ clearTimeout(idleTimer);
889
907
  clearTimeout(overallTimer);
890
908
  clearTimeout(quietTimer);
891
909
  clearStallTimer();
@@ -925,7 +943,8 @@ function config(cwd) {
925
943
  model: process.env.CODEX_MODEL || void 0,
926
944
  approvalPolicy: process.env.CODEX_APPROVAL_POLICY ?? "never",
927
945
  permissions: process.env.CODEX_PERMISSIONS || void 0,
928
- jobTimeoutMs: envInt("CODEX_JOB_TIMEOUT_MS", 18e5),
946
+ jobTimeoutMs: envInt("CODEX_JOB_TIMEOUT_MS", 72e5),
947
+ idleTimeoutMs: Math.max(0, envInt("C2C_IDLE_TIMEOUT_MS", 12e5)),
929
948
  quietMs: envInt("CODEX_QUIET_MS", 3e4),
930
949
  stallWarnMs: Math.max(0, envInt("C2C_STALL_WARN_MS", 12e4)),
931
950
  retries: Math.max(0, envInt("C2C_RETRIES", 1)),
@@ -968,7 +987,7 @@ var SERVER_INSTRUCTIONS = `When the user asks to delegate work to Codex (or to u
968
987
  If a Codex job itself fails (error, 0 tokens, no handoff), report it and wait; don't implement the task yourself unless the user explicitly asks.
969
988
  Full formats: the c2c-workflow prompt.`;
970
989
  var server = new McpServer(
971
- { name: "c2c-codex", version: "0.3.5" },
990
+ { name: "c2c-codex", version: "0.3.7" },
972
991
  { instructions: SERVER_INSTRUCTIONS }
973
992
  );
974
993
  var reasoningEffort = z.enum(["low", "medium", "high", "xhigh", "max", "ultra"]);
@@ -1127,7 +1146,7 @@ server.registerTool(
1127
1146
  "codex_rework",
1128
1147
  {
1129
1148
  title: "Send a Delta Contract to Codex",
1130
- description: "Resume the Codex thread from a previous implement job and hand it a Delta Contract (review findings + failed success conditions). The existing thread goal re-engages, so the /goal loop continues until the objective passes. Returns a new job_id.",
1149
+ description: "Resume the Codex thread from a previous implement job and hand it a Delta Contract (review findings + failed success conditions). The existing thread goal is reactivated, so the /goal loop continues until the objective passes. Returns a new job_id.",
1131
1150
  inputSchema: {
1132
1151
  job_id: z.string().optional().describe("The original implement job's id (preferred; resolves the thread to resume)."),
1133
1152
  thread_id: z.string().optional().describe("Codex thread id, if the job registry is gone (e.g. after a restart)."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude2codex",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "type": "module",
5
5
  "description": "MCP server bridging Claude (architect/reviewer) and Codex (implementer) via Goal/Delta Contracts and Codex's thread-goal loop.",
6
6
  "bin": {