@chit-run/cli 0.7.0 → 0.8.0

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/chit.js +135 -12
  2. package/package.json +1 -1
package/dist/chit.js CHANGED
@@ -10820,6 +10820,11 @@ class ClaudeCliAdapter {
10820
10820
  if (noProgress)
10821
10821
  throw new Error(`claude --print made no progress for ${noProgressMs}ms`);
10822
10822
  if (exitCode !== 0) {
10823
+ const rateLimit = detectClaudeRateLimit(`${stdoutText}
10824
+ ${stderrText}`);
10825
+ if (rateLimit !== undefined) {
10826
+ throw new Error(`claude --print rate limited${rateLimit ? `: ${rateLimit}` : ` (exit ${exitCode})`}`);
10827
+ }
10823
10828
  const cleaned = sanitize(stderrText || stdoutText, sensitive);
10824
10829
  const tail = cleaned.trim().split(`
10825
10830
  `).slice(-5).join(`
@@ -10953,6 +10958,42 @@ function parseClaudeResult(stdout) {
10953
10958
  }
10954
10959
  return result;
10955
10960
  }
10961
+ function detectClaudeRateLimit(stdout) {
10962
+ const pickStr = (v) => typeof v === "string" && v.trim() !== "" ? v.trim().replace(/\s+/g, " ").slice(0, 120) : undefined;
10963
+ const pickNum = (v) => typeof v === "number" && Number.isFinite(v) ? v : undefined;
10964
+ let found = false;
10965
+ let detail = "";
10966
+ for (const line of stdout.split(`
10967
+ `)) {
10968
+ const trimmed = line.trim();
10969
+ if (!trimmed)
10970
+ continue;
10971
+ let evt;
10972
+ try {
10973
+ evt = JSON.parse(trimmed);
10974
+ } catch {
10975
+ continue;
10976
+ }
10977
+ if (evt.type !== "rate_limit_event")
10978
+ continue;
10979
+ found = true;
10980
+ const rl = evt.rate_limit_info && typeof evt.rate_limit_info === "object" ? evt.rate_limit_info : evt.rate_limit && typeof evt.rate_limit === "object" ? evt.rate_limit : evt;
10981
+ const parts = [];
10982
+ const status = pickStr(rl.status);
10983
+ if (status)
10984
+ parts.push(`status=${status}`);
10985
+ const resetsAt = pickStr(rl.resetsAt);
10986
+ if (resetsAt)
10987
+ parts.push(`resets ${resetsAt}`);
10988
+ const retryAfter = pickNum(rl.retryAfter) ?? pickNum(rl.resetInSeconds);
10989
+ if (retryAfter !== undefined)
10990
+ parts.push(`retry after ${retryAfter}s`);
10991
+ if (rl.isUsingOverage === false)
10992
+ parts.push("overage disabled");
10993
+ detail = parts.join(", ");
10994
+ }
10995
+ return found ? detail : undefined;
10996
+ }
10956
10997
 
10957
10998
  // src/adapters/codex-exec.ts
10958
10999
  var DEFAULT_CALL_TIMEOUT_MS2 = 15 * 60000;
@@ -10972,7 +11013,7 @@ class CodexExecAdapter {
10972
11013
  const sensitive = findSensitiveValues(this.config.env);
10973
11014
  try {
10974
11015
  const priorThreadId = getCodexThreadId(req.session);
10975
- const cmd = this.buildCommand(priorThreadId);
11016
+ const cmd = this.buildCommand(priorThreadId, req.filesystem);
10976
11017
  const proc = Bun.spawn({
10977
11018
  cmd,
10978
11019
  cwd: req.cwd,
@@ -11049,7 +11090,7 @@ class CodexExecAdapter {
11049
11090
  throw new Error(message);
11050
11091
  }
11051
11092
  }
11052
- buildCommand(priorThreadId) {
11093
+ buildCommand(priorThreadId, filesystem) {
11053
11094
  if (priorThreadId) {
11054
11095
  return ["codex", "exec", "resume", "--json", "--skip-git-repo-check", priorThreadId, "-"];
11055
11096
  }
@@ -11059,7 +11100,8 @@ class CodexExecAdapter {
11059
11100
  if (this.config.reasoningEffort) {
11060
11101
  cmd.push("-c", `model_reasoning_effort="${this.config.reasoningEffort}"`);
11061
11102
  }
11062
- cmd.push("--sandbox", "read-only", "--skip-git-repo-check", "-");
11103
+ const sandbox = filesystem === "write" ? "workspace-write" : "read-only";
11104
+ cmd.push("--sandbox", sandbox, "--skip-git-repo-check", "-");
11063
11105
  return cmd;
11064
11106
  }
11065
11107
  }
@@ -12146,6 +12188,9 @@ function computeFingerprint(input) {
12146
12188
  reasoningEffort: agent.reasoningEffort ?? null,
12147
12189
  passModelOnResume: agent.adapter === "claude-cli" ? agent.passModelOnResume : null,
12148
12190
  strictMcp: agent.adapter === "claude-cli" ? agent.strictMcp !== false : null,
12191
+ ...agent.adapter === "codex-exec" && {
12192
+ codexSandbox: participant.permissions.filesystem === "write" ? "workspace-write" : "read-only"
12193
+ },
12149
12194
  baseUrl,
12150
12195
  role: participant.role,
12151
12196
  session: participant.session,
@@ -12554,7 +12599,8 @@ async function runConvergeIteration(ctx) {
12554
12599
  if (!result.ok) {
12555
12600
  return {
12556
12601
  ok: false,
12557
- failure: `manifest run failed at step "${result.failedStep}": ${result.error}`
12602
+ failure: `manifest run failed at step "${result.failedStep}": ${result.error}`,
12603
+ ...result.auditRunId && { auditRunId: result.auditRunId }
12558
12604
  };
12559
12605
  }
12560
12606
  const reviewText = result.outputs.review ?? "";
@@ -12923,7 +12969,12 @@ async function runJobWorker(jobId, deps) {
12923
12969
  const workerToken = crypto.randomUUID();
12924
12970
  const setPhase = (phase) => {
12925
12971
  try {
12926
- store.update(jobId, (c) => ({ ...c, phase, lastHeartbeatAt: iso2(now()) }));
12972
+ store.update(jobId, (c) => ({
12973
+ ...c,
12974
+ phase,
12975
+ ...c.phase !== phase && { phaseStartedAt: iso2(now()) },
12976
+ lastHeartbeatAt: iso2(now())
12977
+ }));
12927
12978
  } catch {}
12928
12979
  };
12929
12980
  const controller = new AbortController;
@@ -12946,7 +12997,8 @@ async function runJobWorker(jobId, deps) {
12946
12997
  pgid: process.pid,
12947
12998
  workerToken,
12948
12999
  lastHeartbeatAt: iso2(now()),
12949
- phase: "starting"
13000
+ phase: "starting",
13001
+ phaseStartedAt: iso2(now())
12950
13002
  }));
12951
13003
  const resolved = resolveExecute(job);
12952
13004
  if (!resolved.ok) {
@@ -12981,6 +13033,7 @@ async function runJobWorker(jobId, deps) {
12981
13033
  ...c,
12982
13034
  iteration: i,
12983
13035
  phase: "implementing",
13036
+ ...c.phase !== "implementing" && { phaseStartedAt: iso2(now()) },
12984
13037
  lastHeartbeatAt: iso2(now())
12985
13038
  }));
12986
13039
  let iter;
@@ -13010,6 +13063,10 @@ async function runJobWorker(jobId, deps) {
13010
13063
  return;
13011
13064
  }
13012
13065
  if (!iter.ok) {
13066
+ if (iter.auditRunId) {
13067
+ const ref = iter.auditRunId;
13068
+ store.update(jobId, (c) => ({ ...c, auditRefs: [...c.auditRefs, ref] }));
13069
+ }
13013
13070
  if (controller.signal.aborted) {
13014
13071
  stopLoopSafely(job, "cancelled", "cancelled mid-iteration (signal)");
13015
13072
  finish(store, jobId, now, "cancelled", { stopStatus: "cancelled" });
@@ -13022,6 +13079,7 @@ async function runJobWorker(jobId, deps) {
13022
13079
  store.update(jobId, (c) => ({
13023
13080
  ...c,
13024
13081
  phase: "recording",
13082
+ ...c.phase !== "recording" && { phaseStartedAt: iso2(now()) },
13025
13083
  iterationsCompleted: i,
13026
13084
  lastVerdict: iter.verdict,
13027
13085
  auditRefs: iter.auditRunId ? [...c.auditRefs, iter.auditRunId] : c.auditRefs,
@@ -13062,6 +13120,7 @@ function finish(store, jobId, now, state, extra) {
13062
13120
  state,
13063
13121
  endedAt: iso2(now()),
13064
13122
  phase: undefined,
13123
+ phaseStartedAt: undefined,
13065
13124
  lastHeartbeatAt: iso2(now()),
13066
13125
  ...extra.stopStatus !== undefined && { stopStatus: extra.stopStatus },
13067
13126
  ...extra.failure !== undefined && { failure: extra.failure }
@@ -36126,6 +36185,40 @@ function isStale(job, nowMs, staleAfterMs = STALE_AFTER_MS) {
36126
36185
  const heartbeatOld = !Number.isFinite(beat) || nowMs - beat > staleAfterMs;
36127
36186
  return heartbeatOld || !pidAlive(job.pid);
36128
36187
  }
36188
+ function jobTiming(job, nowMs) {
36189
+ const inFlight = job.state === "queued" || job.state === "running";
36190
+ const timing = {};
36191
+ const startMs = Date.parse(job.startedAt ?? job.createdAt);
36192
+ const endMs = job.endedAt ? Date.parse(job.endedAt) : nowMs;
36193
+ if (Number.isFinite(startMs) && Number.isFinite(endMs) && endMs >= startMs) {
36194
+ timing.elapsedMs = endMs - startMs;
36195
+ }
36196
+ if (inFlight && job.lastHeartbeatAt) {
36197
+ const beat = Date.parse(job.lastHeartbeatAt);
36198
+ if (Number.isFinite(beat) && nowMs >= beat)
36199
+ timing.lastHeartbeatAgeMs = nowMs - beat;
36200
+ }
36201
+ if (job.phase !== undefined && job.phaseStartedAt) {
36202
+ const phaseStart = Date.parse(job.phaseStartedAt);
36203
+ if (Number.isFinite(phaseStart) && nowMs >= phaseStart) {
36204
+ timing.phaseElapsedMs = nowMs - phaseStart;
36205
+ }
36206
+ }
36207
+ return timing;
36208
+ }
36209
+ function formatDuration(ms) {
36210
+ const totalSec = Math.floor(ms / 1000);
36211
+ if (totalSec < 60)
36212
+ return `${totalSec}s`;
36213
+ const totalMin = Math.floor(totalSec / 60);
36214
+ if (totalMin < 60) {
36215
+ const sec = totalSec % 60;
36216
+ return sec ? `${totalMin}m${sec}s` : `${totalMin}m`;
36217
+ }
36218
+ const hours = Math.floor(totalMin / 60);
36219
+ const min = totalMin % 60;
36220
+ return min ? `${hours}h${min}m` : `${hours}h`;
36221
+ }
36129
36222
 
36130
36223
  // src/surfaces/mcp/converge-engine.ts
36131
36224
  class ConvergeEngineError extends Error {
@@ -36578,10 +36671,22 @@ function summarizeRunForStatus(run) {
36578
36671
  audited: run.recorder !== undefined && run.recorder.lastError === undefined
36579
36672
  };
36580
36673
  }
36674
+ function runningNextAction(job, timing) {
36675
+ const parts = [];
36676
+ if (timing.elapsedMs !== undefined)
36677
+ parts.push(`running for ${formatDuration(timing.elapsedMs)}`);
36678
+ if (job.phase) {
36679
+ parts.push(timing.phaseElapsedMs !== undefined ? `${job.phase} for ${formatDuration(timing.phaseElapsedMs)}` : job.phase);
36680
+ }
36681
+ const lead = parts.length > 0 ? parts.join(", ") : "in progress";
36682
+ return `${lead}; chit_job_status / chit_job_cancel "${job.jobId}"`;
36683
+ }
36581
36684
  function summarizeJobForStatus(job, nowMs) {
36582
36685
  const stale = isStale(job, nowMs);
36583
36686
  const display = stale ? "stale" : job.state;
36584
- const nextAction = display === "running" ? `in progress${job.phase ? ` (${job.phase})` : ""}; chit_job_status / chit_job_cancel "${job.jobId}"` : display === "queued" ? "queued; the worker is starting" : display === "stale" ? `worker appears dead; chit_job_status "${job.jobId}" to inspect, then start a fresh job` : `${display}${job.stopStatus ? ` (${job.stopStatus})` : ""}; chit_job_status "${job.jobId}" or chit_audit_show <ref>`;
36687
+ const timing = jobTiming(job, nowMs);
36688
+ const latestRef = job.auditRefs.at(-1);
36689
+ const nextAction = display === "running" ? runningNextAction(job, timing) : display === "queued" ? "queued; the worker is starting" : display === "stale" ? `worker appears dead; chit_job_status "${job.jobId}" to inspect, then start a fresh job` : `${display}${job.stopStatus ? ` (${job.stopStatus})` : ""}; chit_job_status "${job.jobId}"${latestRef ? ` or chit_audit_show ${latestRef}` : ""}`;
36585
36690
  return {
36586
36691
  jobId: job.jobId,
36587
36692
  loopId: job.loopId,
@@ -36594,6 +36699,11 @@ function summarizeJobForStatus(job, nowMs) {
36594
36699
  ...job.stopStatus !== undefined && { stopStatus: job.stopStatus },
36595
36700
  auditRefs: job.auditRefs,
36596
36701
  createdAt: job.createdAt,
36702
+ ...timing.elapsedMs !== undefined && { elapsedMs: timing.elapsedMs },
36703
+ ...timing.lastHeartbeatAgeMs !== undefined && {
36704
+ lastHeartbeatAgeMs: timing.lastHeartbeatAgeMs
36705
+ },
36706
+ ...timing.phaseElapsedMs !== undefined && { phaseElapsedMs: timing.phaseElapsedMs },
36597
36707
  nextAction
36598
36708
  };
36599
36709
  }
@@ -37118,7 +37228,13 @@ function describeJob(job) {
37118
37228
  };
37119
37229
  }
37120
37230
  } catch {}
37121
- const nextAction = display === "running" ? "in progress; chit_job_cancel to stop, or wait and poll again" : display === "queued" ? "queued; the worker is starting" : display === "stale" ? "worker appears dead; inspect with chit_job_status (and chit_audit_show <auditRef> for transcripts), then start a fresh job" : `${display}${job.stopStatus ? ` (${job.stopStatus})` : ""}; open a transcript with chit_audit_show <auditRef>`;
37231
+ const timing = jobTiming(job, now);
37232
+ const latestRef = job.auditRefs.at(-1);
37233
+ const runningDetail = [
37234
+ timing.elapsedMs !== undefined ? `running for ${formatDuration(timing.elapsedMs)}` : undefined,
37235
+ job.phase ? timing.phaseElapsedMs !== undefined ? `${job.phase} for ${formatDuration(timing.phaseElapsedMs)}` : job.phase : undefined
37236
+ ].filter(Boolean);
37237
+ const nextAction = display === "running" ? `${runningDetail.length > 0 ? `${runningDetail.join(", ")}; ` : ""}chit_job_cancel to stop, or wait and poll again` : display === "queued" ? "queued; the worker is starting" : display === "stale" ? `worker appears dead; inspect with chit_job_status${latestRef ? ` (chit_audit_show ${latestRef} for the transcript)` : ""}, then start a fresh job` : `${display}${job.stopStatus ? ` (${job.stopStatus})` : ""}; ${latestRef ? `open a transcript with chit_audit_show ${latestRef}` : "no audit transcript was recorded"}`;
37122
37238
  return {
37123
37239
  jobId: job.jobId,
37124
37240
  loopId: job.loopId,
@@ -37140,12 +37256,18 @@ function describeJob(job) {
37140
37256
  ...job.startedAt !== undefined && { startedAt: job.startedAt },
37141
37257
  ...job.endedAt !== undefined && { endedAt: job.endedAt },
37142
37258
  ...job.lastHeartbeatAt !== undefined && { lastHeartbeatAt: job.lastHeartbeatAt },
37259
+ ...job.phaseStartedAt !== undefined && { phaseStartedAt: job.phaseStartedAt },
37260
+ ...timing.elapsedMs !== undefined && { elapsedMs: timing.elapsedMs },
37261
+ ...timing.lastHeartbeatAgeMs !== undefined && {
37262
+ lastHeartbeatAgeMs: timing.lastHeartbeatAgeMs
37263
+ },
37264
+ ...timing.phaseElapsedMs !== undefined && { phaseElapsedMs: timing.phaseElapsedMs },
37143
37265
  ...latest !== undefined && { latest },
37144
37266
  nextAction
37145
37267
  };
37146
37268
  }
37147
37269
  server.registerTool("chit_job_status", {
37148
- description: "Show one background job: state (queued/running/completed/cancelled/failed, or derived `stale` when the worker is gone), current phase, loop id, iterations, last verdict, audit refs, and the latest iteration's changed files / workspace warnings / usage. Read-only.",
37270
+ description: "Show one background job: state (queued/running/completed/cancelled/failed, or derived `stale` when the worker is gone), current phase, timing fields (elapsedMs, lastHeartbeatAgeMs, phaseElapsedMs), loop id, iterations, last verdict, audit refs, and the latest iteration's changed files / workspace warnings / usage. Read-only.",
37149
37271
  inputSchema: { job_id: exports_external.string() }
37150
37272
  }, async ({ job_id }) => {
37151
37273
  const job = jobStore.get(job_id);
@@ -38092,9 +38214,10 @@ Permission enforcement: each participant's declared permissions are checked
38092
38214
  against the chosen adapter's capabilities at install time. If the adapter
38093
38215
  cannot enforce a permission, the run is rejected unless
38094
38216
  --allow-unenforced-permissions is set. Both built-in adapters enforce
38095
- filesystem read_only today: codex-exec via --sandbox read-only (a hard OS
38096
- sandbox), claude-cli via --permission-mode plan (Claude plan-mode permissions,
38097
- not an OS/filesystem sandbox).
38217
+ filesystem read_only today: codex-exec via an OS sandbox (--sandbox read-only
38218
+ for a reviewer, --sandbox workspace-write for a write-capable implementer),
38219
+ claude-cli via --permission-mode plan (a Claude plan-mode permission, not an
38220
+ OS/filesystem sandbox).
38098
38221
 
38099
38222
  Limitations in this build:
38100
38223
  - file[] inputs are not yet supported via the CLI.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chit-run/cli",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Versioned, cross-vendor agent routines with an audit trail. Stop being the glue between your agents.",
5
5
  "type": "module",
6
6
  "license": "MIT",