claudish 7.67.1 → 8.0.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/index.js +270 -152
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -731,7 +731,7 @@ var init_onepassword_config = __esm(() => {
731
731
  });
732
732
 
733
733
  // src/version.ts
734
- var VERSION = "7.67.1";
734
+ var VERSION = "8.0.0";
735
735
 
736
736
  // src/logger.ts
737
737
  var exports_logger = {};
@@ -48403,10 +48403,12 @@ var init_team_stream_capture = __esm(() => {
48403
48403
  });
48404
48404
 
48405
48405
  // src/channel/stream-json-reducer.ts
48406
- function reachesAnswer(parsedType8, isJson) {
48406
+ function reachesAnswer(parsedType8, isJson, keepUnrecognizedJson) {
48407
48407
  if (!isJson)
48408
48408
  return true;
48409
- return parsedType8 !== null && STREAM_JSON_EVENT_TYPES.has(parsedType8);
48409
+ if (parsedType8 !== null && STREAM_JSON_EVENT_TYPES.has(parsedType8))
48410
+ return true;
48411
+ return keepUnrecognizedJson;
48410
48412
  }
48411
48413
  function labelFor(type, subtype) {
48412
48414
  if (type === null)
@@ -48478,6 +48480,9 @@ class StreamJsonReducer {
48478
48480
  get toolUseCount() {
48479
48481
  return this._toolUseCount;
48480
48482
  }
48483
+ get idleMs() {
48484
+ return Math.max(0, Date.now() - this.lastFrameAt);
48485
+ }
48481
48486
  get terminalReason() {
48482
48487
  return this._terminalReason;
48483
48488
  }
@@ -48570,7 +48575,7 @@ class StreamJsonReducer {
48570
48575
  this.opts.onSemanticLine?.(line, labelFor(type, frame ? asString(frame.subtype) : null));
48571
48576
  if (frame && type !== null)
48572
48577
  this.applyFrame(frame, type);
48573
- if (!reachesAnswer(type, frame !== null))
48578
+ if (!reachesAnswer(type, frame !== null, this.opts.keepUnrecognizedJson ?? false))
48574
48579
  return "";
48575
48580
  const prose = this.capture.write(terminated ? `${line}
48576
48581
  ` : line);
@@ -49319,6 +49324,16 @@ function resolveClaudishSpawn(env = process.env) {
49319
49324
  }
49320
49325
  var CLAUDISH_BIN_ENV = "CLAUDISH_BIN";
49321
49326
 
49327
+ // src/stdio-decode.ts
49328
+ import { StringDecoder } from "string_decoder";
49329
+ function decodeChunk(decoder, chunk) {
49330
+ return typeof chunk === "string" ? chunk : decoder.write(chunk);
49331
+ }
49332
+ function newStdioDecoder() {
49333
+ return new StringDecoder("utf8");
49334
+ }
49335
+ var init_stdio_decode = () => {};
49336
+
49322
49337
  // src/team-stats.ts
49323
49338
  import { existsSync as existsSync19, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
49324
49339
  import { join as join28 } from "path";
@@ -49494,22 +49509,25 @@ var init_team_stats = () => {};
49494
49509
  var exports_team_orchestrator = {};
49495
49510
  __export(exports_team_orchestrator, {
49496
49511
  DEFAULT_MIN_OUTPUT_BYTES: () => DEFAULT_MIN_OUTPUT_BYTES,
49497
- DEFAULT_STALL_SECONDS: () => DEFAULT_STALL_SECONDS2,
49498
- DRAIN_TIMEOUT_MS: () => DRAIN_TIMEOUT_MS,
49499
- GRACE_INTERVAL_MS: () => GRACE_INTERVAL_MS,
49500
49512
  STDOUT_TAIL_LIMIT: () => STDOUT_TAIL_LIMIT,
49501
49513
  TEAM_CAPTURE_ENV_VAR: () => TEAM_CAPTURE_ENV_VAR,
49502
49514
  aggregateVerdict: () => aggregateVerdict,
49503
49515
  buildJudgePrompt: () => buildJudgePrompt,
49516
+ cancelTeamRun: () => cancelTeamRun,
49504
49517
  classifyRunOutput: () => classifyRunOutput,
49505
49518
  fisherYatesShuffle: () => fisherYatesShuffle,
49506
49519
  getStatus: () => getStatus,
49507
49520
  judgeResponses: () => judgeResponses,
49508
49521
  meaningfulStderr: () => meaningfulStderr,
49509
49522
  parseJudgeVotes: () => parseJudgeVotes,
49523
+ readTeamInputFile: () => readTeamInputFile,
49510
49524
  resolveCaptureMode: () => resolveCaptureMode,
49511
49525
  runModels: () => runModels,
49512
49526
  setupSession: () => setupSession,
49527
+ shutdownAllTeamRuns: () => shutdownAllTeamRuns,
49528
+ startModels: () => startModels,
49529
+ teamSlotActivity: () => teamSlotActivity,
49530
+ teamSlotIdleSeconds: () => teamSlotIdleSeconds,
49513
49531
  validateSessionPath: () => validateSessionPath
49514
49532
  });
49515
49533
  import { spawn as spawn2 } from "child_process";
@@ -49521,12 +49539,57 @@ import {
49521
49539
  readdirSync as readdirSync5,
49522
49540
  writeFileSync as writeFileSync11
49523
49541
  } from "fs";
49524
- import { join as join29, resolve as resolve3 } from "path";
49542
+ import { basename as basename2, join as join29, resolve as resolve3 } from "path";
49525
49543
  function resolveCaptureMode(explicit, env = process.env) {
49526
49544
  if (explicit)
49527
49545
  return explicit;
49528
49546
  return env[TEAM_CAPTURE_ENV_VAR]?.trim().toLowerCase() === "print" ? "print" : "stream-json";
49529
49547
  }
49548
+ function teamSlotIdleSeconds(teamSessionId) {
49549
+ const run = liveTeamRuns.get(teamSessionId);
49550
+ if (!run)
49551
+ return null;
49552
+ const out = {};
49553
+ for (const slotId of run.processes.keys()) {
49554
+ const idle = run.idleMsFor(slotId);
49555
+ if (idle !== null)
49556
+ out[slotId] = Math.round(idle / 1000);
49557
+ }
49558
+ return out;
49559
+ }
49560
+ function teamSlotActivity(teamSessionId) {
49561
+ const run = liveTeamRuns.get(teamSessionId);
49562
+ if (!run)
49563
+ return null;
49564
+ const out = {};
49565
+ for (const slotId of run.processes.keys()) {
49566
+ const activity = run.activityFor(slotId);
49567
+ if (activity !== null)
49568
+ out[slotId] = activity;
49569
+ }
49570
+ return out;
49571
+ }
49572
+ async function cancelTeamRun(teamSessionId, slotId) {
49573
+ const run = liveTeamRuns.get(teamSessionId);
49574
+ if (!run)
49575
+ return { found: false, cancelled: [] };
49576
+ const targets = slotId ? run.processes.has(slotId) ? [slotId] : [] : [...run.processes.keys()];
49577
+ const cancelled = [];
49578
+ for (const id of targets) {
49579
+ const proc = run.processes.get(id);
49580
+ if (!proc)
49581
+ continue;
49582
+ run.cancelledSlots.add(id);
49583
+ await terminateChildTree(proc);
49584
+ cancelled.push(id);
49585
+ }
49586
+ return { found: true, cancelled };
49587
+ }
49588
+ async function shutdownAllTeamRuns() {
49589
+ await Promise.all([...liveTeamRuns.keys()].map((id) => cancelTeamRun(id).catch(() => {
49590
+ return;
49591
+ })));
49592
+ }
49530
49593
  function classifyRunOutput(opts) {
49531
49594
  const {
49532
49595
  outputSize,
@@ -49606,6 +49669,21 @@ function validateSessionPath(sessionPath) {
49606
49669
  }
49607
49670
  return resolved;
49608
49671
  }
49672
+ function readTeamInputFile(inputPath) {
49673
+ const resolved = resolve3(inputPath);
49674
+ const cwd = process.cwd();
49675
+ if (!resolved.startsWith(`${cwd}/`) && resolved !== cwd) {
49676
+ throw new Error(`Input file must be within current directory: ${inputPath}`);
49677
+ }
49678
+ if (!existsSync20(resolved)) {
49679
+ throw new Error(`Input file not found: ${resolved}`);
49680
+ }
49681
+ const text = readFileSync19(resolved, "utf-8");
49682
+ if (text.trim().length === 0) {
49683
+ throw new Error(`Input file is empty: ${resolved}`);
49684
+ }
49685
+ return text;
49686
+ }
49609
49687
  function setupSession(sessionPath, models, input) {
49610
49688
  if (models.length === 0) {
49611
49689
  throw new Error("At least one model is required");
@@ -49672,8 +49750,7 @@ function readFullOutputIfNeeded(opts) {
49672
49750
  return;
49673
49751
  }
49674
49752
  }
49675
- async function runModels(sessionPath, opts = {}) {
49676
- const timeoutMs = (opts.timeout ?? 300) * 1000;
49753
+ async function startModels(sessionPath, opts = {}) {
49677
49754
  assertValidRequirePattern(opts.requirePattern);
49678
49755
  const manifest = JSON.parse(readFileSync19(join29(sessionPath, "manifest.json"), "utf-8"));
49679
49756
  const statusPath = join29(sessionPath, "status.json");
@@ -49719,6 +49796,7 @@ async function runModels(sessionPath, opts = {}) {
49719
49796
  mkdirSync11(statsDir(sessionPath), { recursive: true });
49720
49797
  const processes = new Map;
49721
49798
  const runtimes = new Map;
49799
+ const cancelledSlots = new Set;
49722
49800
  const sigintHandler = () => {
49723
49801
  for (const [, proc] of processes) {
49724
49802
  signalProcessTree(proc, "SIGTERM");
@@ -49730,6 +49808,7 @@ async function runModels(sessionPath, opts = {}) {
49730
49808
  for (const [anonId, entry] of Object.entries(manifest.models)) {
49731
49809
  const outputPath = join29(sessionPath, `response-${anonId}.md`);
49732
49810
  const errorLogPath = join29(sessionPath, "errors", `${anonId}.log`);
49811
+ const upstreamErrorLogPath = join29(sessionPath, "errors", `${anonId}-upstream.jsonl`);
49733
49812
  const spawnModel = spawnPlan.pinned.get(entry.model) ?? entry.model;
49734
49813
  const args = [
49735
49814
  "--model",
@@ -49750,21 +49829,37 @@ async function runModels(sessionPath, opts = {}) {
49750
49829
  detached: KILL_PROCESS_GROUP,
49751
49830
  env: {
49752
49831
  ...process.env,
49753
- CLAUDISH_TOKEN_FILE: tokenFileFor(sessionPath, anonId)
49832
+ [ENV.CLAUDISH_TOKEN_FILE]: tokenFileFor(sessionPath, anonId),
49833
+ [UPSTREAM_ERROR_LOG_ENV]: upstreamErrorLogPath
49754
49834
  }
49755
49835
  });
49836
+ let lastOutputAt = Date.now();
49837
+ const stampLiveness = () => {
49838
+ lastOutputAt = Date.now();
49839
+ };
49840
+ const stdoutDecoder = newStdioDecoder();
49841
+ const stderrDecoder = newStdioDecoder();
49842
+ proc.stdout?.on("data", stampLiveness);
49843
+ proc.stderr?.on("data", stampLiveness);
49756
49844
  let byteCount = 0;
49757
49845
  let stdoutTail = "";
49758
49846
  const outputStream = createWriteStream(outputPath);
49759
49847
  let flushPartial = () => {};
49848
+ let reducer = null;
49760
49849
  if (captureMode === "print") {
49761
49850
  proc.stdout?.on("data", (chunk) => {
49762
49851
  byteCount += chunk.length;
49763
- stdoutTail = (stdoutTail + chunk.toString()).slice(-STDOUT_TAIL_LIMIT);
49852
+ stdoutTail = (stdoutTail + decodeChunk(stdoutDecoder, chunk)).slice(-STDOUT_TAIL_LIMIT);
49764
49853
  });
49765
49854
  proc.stdout?.pipe(outputStream);
49766
49855
  } else {
49767
- const capture = createAssistantTextCapture();
49856
+ const slotReducer = new StreamJsonReducer({
49857
+ sessionId: anonId,
49858
+ stallSeconds: 0,
49859
+ keepUnrecognizedJson: true,
49860
+ callback: () => {}
49861
+ });
49862
+ reducer = slotReducer;
49768
49863
  const absorb = (text) => {
49769
49864
  if (text.length === 0)
49770
49865
  return;
@@ -49772,14 +49867,15 @@ async function runModels(sessionPath, opts = {}) {
49772
49867
  stdoutTail = (stdoutTail + text).slice(-STDOUT_TAIL_LIMIT);
49773
49868
  outputStream.write(text);
49774
49869
  };
49775
- proc.stdout?.on("data", (chunk) => absorb(capture.write(chunk.toString())));
49776
- flushPartial = () => absorb(capture.end());
49870
+ proc.stdout?.on("data", (chunk) => absorb(slotReducer.feed(decodeChunk(stdoutDecoder, chunk))));
49871
+ flushPartial = () => absorb(slotReducer.end());
49777
49872
  let captureFinalized = false;
49778
49873
  const finalizeCapture = () => {
49779
49874
  if (captureFinalized)
49780
49875
  return;
49781
49876
  captureFinalized = true;
49782
- absorb(capture.end());
49877
+ absorb(slotReducer.end());
49878
+ slotReducer.dispose();
49783
49879
  outputStream.end();
49784
49880
  };
49785
49881
  proc.stdout?.on("end", finalizeCapture);
@@ -49787,7 +49883,7 @@ async function runModels(sessionPath, opts = {}) {
49787
49883
  }
49788
49884
  let stderr = "";
49789
49885
  proc.stderr?.on("data", (chunk) => {
49790
- stderr += chunk.toString();
49886
+ stderr += decodeChunk(stderrDecoder, chunk);
49791
49887
  });
49792
49888
  const command = `claudish ${args.join(" ")}`;
49793
49889
  runtimes.set(anonId, {
@@ -49796,6 +49892,8 @@ async function runModels(sessionPath, opts = {}) {
49796
49892
  getStderr: () => stderr,
49797
49893
  getStdoutTail: () => stdoutTail,
49798
49894
  getByteCount: () => byteCount,
49895
+ getIdleMs: () => Math.max(0, Date.now() - lastOutputAt),
49896
+ getActivity: () => reducer?.state ?? null,
49799
49897
  flushPartial: () => flushPartial()
49800
49898
  });
49801
49899
  proc.stdin?.write(inputContent);
@@ -49833,8 +49931,9 @@ async function runModels(sessionPath, opts = {}) {
49833
49931
  const failed = crashed || degraded !== null;
49834
49932
  const state = crashed ? "FAILED" : degraded ? "EMPTY" : "COMPLETED";
49835
49933
  if (failed) {
49836
- const reason = crashed ? "nonzero_exit" : degraded.reason;
49837
- const detail = crashed ? `Child exited with code ${exitCode}.` : degraded.detail;
49934
+ const wasCancelled = cancelledSlots.has(anonId);
49935
+ const reason = wasCancelled ? "cancelled" : crashed ? "nonzero_exit" : degraded.reason;
49936
+ const detail = wasCancelled ? `Stopped on the caller's instruction via team(mode:"cancel"). ` + "Whatever the child had written up to that point is in its response file." : crashed ? `Child exited with code ${exitCode}.` : degraded.detail;
49838
49937
  persistErrorLog(errorLogPath, `${state}: ${detail}`, stderr, stdoutTail);
49839
49938
  updateModelStatus(anonId, {
49840
49939
  state,
@@ -49849,6 +49948,7 @@ async function runModels(sessionPath, opts = {}) {
49849
49948
  stderrSnippet: stderr ? redactSecrets(stderr).slice(-2000) : undefined,
49850
49949
  stdoutSnippet: stdoutTail ? redactSecrets(stdoutTail).slice(-2000) : undefined,
49851
49950
  errorLogPath,
49951
+ upstreamErrorLogPath: existsSync20(upstreamErrorLogPath) ? upstreamErrorLogPath : undefined,
49852
49952
  workDir: sessionPath
49853
49953
  }
49854
49954
  });
@@ -49909,102 +50009,36 @@ async function runModels(sessionPath, opts = {}) {
49909
50009
  emitProgress();
49910
50010
  const progressHandle = setInterval(() => emitProgress("running"), POLL_MS);
49911
50011
  progressHandle.unref?.();
49912
- const graceEnabled = opts.graceExtension ?? true;
49913
- const maxGraceMs = Math.max(0, (opts.maxGraceSeconds ?? timeoutMs / 1000) * 1000);
49914
- const stallMs = Math.max(0, (opts.stallSeconds ?? DEFAULT_STALL_SECONDS2) * 1000);
49915
- const idleMsFor = (id) => {
49916
- const s = readTokenStats(sessionPath, id);
49917
- if (!s || typeof s.updated_at !== "number" || s.updated_at <= 0)
49918
- return null;
49919
- return Math.max(0, Date.now() - s.updated_at);
49920
- };
49921
- const graceStartedAt = new Map;
49922
- const graceUsedMs = (id, now2) => {
49923
- const start = graceStartedAt.get(id);
49924
- return start === undefined ? 0 : Math.max(0, now2 - start);
49925
- };
49926
- const runningIds = () => [...processes.keys()].filter((id) => statusCache.models[id]?.state === "RUNNING");
49927
- const timeoutModel = async (id, why) => {
49928
- const proc = processes.get(id);
49929
- if (!proc || statusCache.models[id]?.state !== "RUNNING")
49930
- return;
49931
- const rt = runtimes.get(id);
49932
- rt?.flushPartial();
49933
- const stderr = rt?.getStderr() ?? "";
49934
- const stdoutTail = rt?.getStdoutTail() ?? "";
49935
- const bytes2 = rt?.getByteCount() ?? 0;
49936
- const grace = graceUsedMs(id, Date.now());
49937
- const detail = `Killed by the orchestrator after ${(timeoutMs + grace) / 1000}s ` + `(deadline ${timeoutMs / 1000}s${grace ? ` + ${grace / 1000}s grace` : ""}) ` + `with ${bytes2} B of stdout \u2014 ${why}. ` + "That figure counts the ANSWER, not the wire format, so 0 B means the child had " + `not produced an assistant message yet \u2014 "did not finish", not "produced nothing".`;
49938
- if (rt)
49939
- persistErrorLog(rt.errorLogPath, `TIMEOUT: ${detail}`, stderr, stdoutTail);
49940
- updateModelStatus(id, {
49941
- state: "TIMEOUT",
49942
- completedAt: new Date().toISOString(),
49943
- outputSize: bytes2,
49944
- error: rt ? {
49945
- model: id,
49946
- command: rt.command,
49947
- reason: "timeout",
49948
- detail,
49949
- stderrSnippet: stderr ? redactSecrets(stderr).slice(-2000) : undefined,
49950
- stdoutSnippet: stdoutTail ? redactSecrets(stdoutTail).slice(-2000) : undefined,
49951
- errorLogPath: rt.errorLogPath,
49952
- workDir: sessionPath
49953
- } : undefined
49954
- });
49955
- opts.onStatusChange?.(id, statusCache.models[id]);
49956
- const stopped = await terminateChildTree(proc);
49957
- if (!stopped) {
49958
- persistErrorLog(rt?.errorLogPath ?? join29(sessionPath, "errors", `${id}.log`), "TIMEOUT: child survived SIGKILL \u2014 it may still be running and billing", stderr, stdoutTail);
50012
+ const teamSessionId = basename2(sessionPath);
50013
+ liveTeamRuns.set(teamSessionId, {
50014
+ sessionPath,
50015
+ processes,
50016
+ idleMsFor: (slotId) => runtimes.get(slotId)?.getIdleMs() ?? null,
50017
+ activityFor: (slotId) => runtimes.get(slotId)?.getActivity() ?? null,
50018
+ cancelledSlots
50019
+ });
50020
+ const done = (async () => {
50021
+ try {
50022
+ await Promise.all(completionPromises);
50023
+ } finally {
50024
+ clearInterval(progressHandle);
50025
+ emitProgress("settled");
50026
+ process.off("SIGINT", sigintHandler);
50027
+ liveTeamRuns.delete(teamSessionId);
49959
50028
  }
50029
+ return statusCache;
50030
+ })();
50031
+ done.catch(() => {});
50032
+ return {
50033
+ teamSessionId,
50034
+ sessionPath,
50035
+ slots: Object.fromEntries(Object.entries(manifest.models).map(([anonId, entry]) => [entry.model, anonId])),
50036
+ done
49960
50037
  };
49961
- const allDone = Promise.all(completionPromises);
49962
- let settled = false;
49963
- allDone.then(() => {
49964
- settled = true;
49965
- }, () => {
49966
- settled = true;
49967
- });
49968
- const deadlineWatcher = (async () => {
49969
- await delay(timeoutMs);
49970
- for (;; ) {
49971
- if (settled)
49972
- return;
49973
- const running = runningIds();
49974
- if (running.length === 0)
49975
- return;
49976
- const extended = [];
49977
- const now2 = Date.now();
49978
- for (const id of running) {
49979
- const idleMs = idleMsFor(id);
49980
- const usedGrace = graceUsedMs(id, now2);
49981
- if (!graceEnabled) {
49982
- await timeoutModel(id, "deadline reached (grace extension disabled)");
49983
- } else if (usedGrace >= maxGraceMs) {
49984
- await timeoutModel(id, `grace exhausted after ${Math.round(usedGrace / 1000)}s of extra time`);
49985
- } else if (idleMs === null) {
49986
- await timeoutModel(id, "deadline reached with no measurable progress to extend for");
49987
- } else if (idleMs >= stallMs) {
49988
- await timeoutModel(id, `no measurable progress for ${Math.round(idleMs / 1000)}s`);
49989
- } else {
49990
- if (!graceStartedAt.has(id))
49991
- graceStartedAt.set(id, now2);
49992
- extended.push(id);
49993
- }
49994
- }
49995
- if (extended.length === 0)
49996
- return;
49997
- emitProgress("running");
49998
- await delay(Math.min(GRACE_INTERVAL_MS, Math.max(1000, stallMs)));
49999
- }
50000
- })().catch(() => {});
50001
- await Promise.race([allDone, deadlineWatcher]);
50002
- if (!settled)
50003
- await Promise.race([allDone, delay(DRAIN_TIMEOUT_MS)]);
50004
- clearInterval(progressHandle);
50005
- emitProgress("settled");
50006
- process.off("SIGINT", sigintHandler);
50007
- return statusCache;
50038
+ }
50039
+ async function runModels(sessionPath, opts = {}) {
50040
+ const handle = await startModels(sessionPath, opts);
50041
+ return handle.done;
50008
50042
  }
50009
50043
  async function judgeResponses(sessionPath, opts = {}) {
50010
50044
  const responseFiles = readdirSync5(sessionPath).filter((f) => f.startsWith("response-") && f.endsWith(".md")).sort();
@@ -50186,16 +50220,17 @@ function formatVerdict(verdict, sessionPath) {
50186
50220
  }
50187
50221
  return output;
50188
50222
  }
50189
- var TEAM_CAPTURE_ENV_VAR = "CLAUDISH_TEAM_CAPTURE", STDOUT_TAIL_LIMIT = 4000, API_ERROR_RE, BG_CEILING_RE, DEFAULT_MIN_OUTPUT_BYTES = 0, DRAIN_TIMEOUT_MS = 1e4, GRACE_INTERVAL_MS = 60000, DEFAULT_STALL_SECONDS2 = 90, delay = (ms) => new Promise((resolve4) => {
50190
- const t = setTimeout(resolve4, ms);
50191
- t.unref?.();
50192
- }), BENIGN_STDERR_PATTERNS;
50223
+ var TEAM_CAPTURE_ENV_VAR = "CLAUDISH_TEAM_CAPTURE", liveTeamRuns, STDOUT_TAIL_LIMIT = 4000, API_ERROR_RE, BG_CEILING_RE, DEFAULT_MIN_OUTPUT_BYTES = 0, BENIGN_STDERR_PATTERNS;
50193
50224
  var init_team_orchestrator = __esm(() => {
50194
50225
  init_prehydrate();
50226
+ init_stream_json_reducer();
50227
+ init_config2();
50228
+ init_upstream_error_capture();
50195
50229
  init_process_tree();
50196
50230
  init_redact();
50231
+ init_stdio_decode();
50197
50232
  init_team_stats();
50198
- init_team_stream_capture();
50233
+ liveTeamRuns = new Map;
50199
50234
  API_ERROR_RE = /\[API Error:\s*([^\]]{0,300})\]/i;
50200
50235
  BG_CEILING_RE = /Background tasks still running after (\d+)s; terminating/i;
50201
50236
  BENIGN_STDERR_PATTERNS = [/^\s*\[claude-code:unrecognized_model\]/];
@@ -50217,7 +50252,7 @@ import {
50217
50252
  } from "fs";
50218
50253
  import { homedir as homedir28 } from "os";
50219
50254
  import { join as join30, resolve as resolve4, sep } from "path";
50220
- import { StringDecoder } from "string_decoder";
50255
+ import { StringDecoder as StringDecoder2 } from "string_decoder";
50221
50256
  function buildChannelSpawnArgs(opts) {
50222
50257
  return [
50223
50258
  "--model",
@@ -50248,9 +50283,6 @@ function assertNoReservedFlags(flags) {
50248
50283
  }
50249
50284
  }
50250
50285
  }
50251
- function decodeChunk(decoder, chunk) {
50252
- return typeof chunk === "string" ? chunk : decoder.write(chunk);
50253
- }
50254
50286
  function readTailText(path, maxBytes) {
50255
50287
  let fd = null;
50256
50288
  try {
@@ -50359,11 +50391,14 @@ class SessionManager {
50359
50391
  throw new Error(`Max sessions (${this.maxSessions}) reached`);
50360
50392
  }
50361
50393
  assertNoReservedFlags(opts.claudishFlags);
50362
- const sessionId2 = randomUUID4().slice(0, 8);
50394
+ if (opts.sessionId !== undefined && this.sessions.has(opts.sessionId)) {
50395
+ throw new Error(`Session id already in use: ${opts.sessionId}`);
50396
+ }
50397
+ const sessionId2 = opts.sessionId ?? randomUUID4().slice(0, 8);
50363
50398
  const claudeSessionId = randomUUID4();
50364
50399
  const timeout = Math.min(opts.timeoutSeconds ?? DEFAULT_TIMEOUT, MAX_TIMEOUT);
50365
50400
  const startedAt = new Date().toISOString();
50366
- const sessionDir = join30(this.sessionsDir, sessionId2);
50401
+ const sessionDir = opts.sessionDir ?? join30(this.sessionsDir, sessionId2);
50367
50402
  mkdirSync12(sessionDir, { recursive: true });
50368
50403
  if (opts.prompt) {
50369
50404
  writeFileSync12(join30(sessionDir, "prompt.md"), opts.prompt, "utf-8");
@@ -50373,7 +50408,7 @@ class SessionManager {
50373
50408
  claudeSessionId,
50374
50409
  claudishFlags: opts.claudishFlags
50375
50410
  });
50376
- const tokenFile = join30(sessionDir, "tokens.json");
50411
+ const tokenFile = opts.tokenFile ?? join30(sessionDir, "tokens.json");
50377
50412
  const eventLogPath = join30(sessionDir, "events.jsonl");
50378
50413
  const upstreamErrorLogPath = join30(sessionDir, "upstream-errors.jsonl");
50379
50414
  const cwd = opts.cwd ?? process.cwd();
@@ -50399,6 +50434,7 @@ class SessionManager {
50399
50434
  status: "starting",
50400
50435
  pid: proc.pid ?? null,
50401
50436
  startedAt,
50437
+ idleSeconds: 0,
50402
50438
  completedAt: null,
50403
50439
  exitCode: null,
50404
50440
  turnsCompleted: 0,
@@ -50419,8 +50455,8 @@ class SessionManager {
50419
50455
  evictHandle: null,
50420
50456
  stderr: "",
50421
50457
  stderrTruncated: false,
50422
- stdoutDecoder: new StringDecoder("utf8"),
50423
- stderrDecoder: new StringDecoder("utf8"),
50458
+ stdoutDecoder: new StringDecoder2("utf8"),
50459
+ stderrDecoder: new StringDecoder2("utf8"),
50424
50460
  outputLogStream,
50425
50461
  sessionDir,
50426
50462
  eventLogPath,
@@ -50441,6 +50477,7 @@ class SessionManager {
50441
50477
  entry.reducer = new StreamJsonReducer({
50442
50478
  sessionId: sessionId2,
50443
50479
  stallSeconds: this.stallSeconds,
50480
+ keepUnrecognizedJson: opts.keepUnrecognizedJson,
50444
50481
  callback: (sid, data) => {
50445
50482
  const current = this.sessions.get(sid);
50446
50483
  if (!current)
@@ -50527,6 +50564,7 @@ class SessionManager {
50527
50564
  if (!entry)
50528
50565
  return this.diskOutput(this.requireDiskRecord(sessionId2), tailLines);
50529
50566
  entry.info.elapsedSeconds = this.getElapsed(entry.info.startedAt);
50567
+ entry.info.idleSeconds = entry.reducer ? Math.round(entry.reducer.idleMs / 1000) : null;
50530
50568
  this.refreshAccounting(entry);
50531
50569
  const lines = entry.scrollback.getLines(tailLines);
50532
50570
  return {
@@ -50537,7 +50575,8 @@ class SessionManager {
50537
50575
  totalLines: entry.scrollback.totalLines,
50538
50576
  turnsCompleted: entry.info.turnsCompleted,
50539
50577
  tokensUsed: entry.info.tokensUsed,
50540
- elapsedSeconds: entry.info.elapsedSeconds
50578
+ elapsedSeconds: entry.info.elapsedSeconds,
50579
+ idleSeconds: entry.info.idleSeconds
50541
50580
  };
50542
50581
  }
50543
50582
  getDiagnostics(sessionId2, eventLimit = DEFAULT_EVENT_LIMIT) {
@@ -50547,6 +50586,7 @@ class SessionManager {
50547
50586
  return this.diskDiagnostics(this.requireDiskRecord(sessionId2), limit);
50548
50587
  this.refreshAccounting(entry);
50549
50588
  entry.info.elapsedSeconds = this.getElapsed(entry.info.startedAt);
50589
+ entry.info.idleSeconds = entry.reducer ? Math.round(entry.reducer.idleMs / 1000) : null;
50550
50590
  return {
50551
50591
  sessionId: sessionId2,
50552
50592
  status: entry.info.status,
@@ -50555,6 +50595,7 @@ class SessionManager {
50555
50595
  exitCode: entry.info.exitCode,
50556
50596
  terminalReason: entry.info.terminalReason,
50557
50597
  elapsedSeconds: entry.info.elapsedSeconds,
50598
+ idleSeconds: entry.info.idleSeconds,
50558
50599
  timeoutSeconds: entry.timeoutSeconds,
50559
50600
  outputBytes: entry.proseBytes,
50560
50601
  turnsCompleted: entry.info.turnsCompleted,
@@ -50613,6 +50654,7 @@ class SessionManager {
50613
50654
  continue;
50614
50655
  if (!isTerminal2) {
50615
50656
  entry.info.elapsedSeconds = this.getElapsed(entry.info.startedAt);
50657
+ entry.info.idleSeconds = entry.reducer ? Math.round(entry.reducer.idleMs / 1000) : null;
50616
50658
  this.refreshAccounting(entry);
50617
50659
  }
50618
50660
  sessions2.push({ ...entry.info });
@@ -50624,6 +50666,7 @@ class SessionManager {
50624
50666
  if (!entry)
50625
50667
  return this.requireDiskRecord(sessionId2).info;
50626
50668
  entry.info.elapsedSeconds = this.getElapsed(entry.info.startedAt);
50669
+ entry.info.idleSeconds = entry.reducer ? Math.round(entry.reducer.idleMs / 1000) : null;
50627
50670
  this.refreshAccounting(entry);
50628
50671
  return { ...entry.info };
50629
50672
  }
@@ -50706,6 +50749,7 @@ class SessionManager {
50706
50749
  model: metaString(meta3?.model) ?? "unknown",
50707
50750
  spawnModel: metaString(meta3?.spawnModel),
50708
50751
  status: metaStatus(meta3?.status) ?? "failed",
50752
+ idleSeconds: null,
50709
50753
  pid: null,
50710
50754
  startedAt,
50711
50755
  completedAt,
@@ -50735,7 +50779,8 @@ class SessionManager {
50735
50779
  totalLines: buffer.totalLines,
50736
50780
  turnsCompleted: record4.info.turnsCompleted,
50737
50781
  tokensUsed: record4.info.tokensUsed,
50738
- elapsedSeconds: record4.info.elapsedSeconds
50782
+ elapsedSeconds: record4.info.elapsedSeconds,
50783
+ idleSeconds: null
50739
50784
  };
50740
50785
  }
50741
50786
  diskDiagnostics(record4, limit) {
@@ -50753,6 +50798,7 @@ class SessionManager {
50753
50798
  exitCode: info.exitCode,
50754
50799
  terminalReason: info.terminalReason,
50755
50800
  elapsedSeconds: info.elapsedSeconds,
50801
+ idleSeconds: null,
50756
50802
  timeoutSeconds: 0,
50757
50803
  outputBytes: diskProseBytes(outputTail, fileSize(outputLogPath)),
50758
50804
  turnsCompleted: info.turnsCompleted,
@@ -50912,6 +50958,7 @@ ${STDERR_TRUNCATION_MARKER} ${STDERR_SIDE_LIMIT} bytes per end \u2026
50912
50958
  entry.info.exitCode = code;
50913
50959
  entry.info.completedAt = at;
50914
50960
  entry.info.elapsedSeconds = this.getElapsed(entry.info.startedAt);
50961
+ entry.info.idleSeconds = entry.reducer ? Math.round(entry.reducer.idleMs / 1000) : null;
50915
50962
  entry.stdinClosed = true;
50916
50963
  this.flushDecoders(entry);
50917
50964
  const priorVerdict = TERMINAL_STATUSES.includes(entry.info.status) ? entry.info.status : null;
@@ -51076,13 +51123,14 @@ ${STDERR_TRUNCATION_MARKER} ${STDERR_SIDE_LIMIT} bytes per end \u2026
51076
51123
  }
51077
51124
  }
51078
51125
  }
51079
- var DEFAULT_MAX_SESSIONS = 20, DEFAULT_SCROLLBACK = 2000, DEFAULT_TIMEOUT = 600, MAX_TIMEOUT = 3600, KILL_GRACE_MS = 5000, TERMINAL_RETENTION_MS, MAX_TERMINAL_SESSIONS = 50, STDERR_SIDE_LIMIT, EVENT_LOG_LIMIT, EVENT_RING_SIZE = 200, EVENT_PREVIEW_CHARS = 800, DEFAULT_EVENT_LIMIT = 40, UPSTREAM_ERROR_TAIL_BYTES, STDERR_TRUNCATION_MARKER = "[claudish] \u2026 stderr truncated to", TERMINAL_STATUSES, KNOWN_STATUSES, SESSION_ID_RE, META_READ_LIMIT, OUTPUT_TAIL_BYTES, STDERR_READ_BYTES, EVENT_TAIL_BYTES, NO_TERMINAL_RECORD = "claudish_no_terminal_record", RESERVED_FLAG_ALIASES, TRANSPORT_BREAKING_FLAGS, RESERVED_CHILD_FLAGS, metaString = (v) => typeof v === "string" && v.length > 0 ? v : null, metaNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : null, metaStatus = (v) => typeof v === "string" && KNOWN_STATUSES.includes(v) ? v : null, CLAUDISH_NOTE_PREFIX = "[claudish] ";
51126
+ var DEFAULT_MAX_SESSIONS = 20, DRAIN_TIMEOUT_MS = 1e4, DEFAULT_SCROLLBACK = 2000, DEFAULT_TIMEOUT = 600, MAX_TIMEOUT = 3600, KILL_GRACE_MS = 5000, TERMINAL_RETENTION_MS, MAX_TERMINAL_SESSIONS = 50, STDERR_SIDE_LIMIT, EVENT_LOG_LIMIT, EVENT_RING_SIZE = 200, EVENT_PREVIEW_CHARS = 800, DEFAULT_EVENT_LIMIT = 40, UPSTREAM_ERROR_TAIL_BYTES, STDERR_TRUNCATION_MARKER = "[claudish] \u2026 stderr truncated to", TERMINAL_STATUSES, KNOWN_STATUSES, SESSION_ID_RE, META_READ_LIMIT, OUTPUT_TAIL_BYTES, STDERR_READ_BYTES, EVENT_TAIL_BYTES, NO_TERMINAL_RECORD = "claudish_no_terminal_record", RESERVED_FLAG_ALIASES, TRANSPORT_BREAKING_FLAGS, RESERVED_CHILD_FLAGS, metaString = (v) => typeof v === "string" && v.length > 0 ? v : null, metaNumber = (v) => typeof v === "number" && Number.isFinite(v) ? v : null, metaStatus = (v) => typeof v === "string" && KNOWN_STATUSES.includes(v) ? v : null, CLAUDISH_NOTE_PREFIX = "[claudish] ";
51080
51127
  var init_session_manager = __esm(() => {
51081
51128
  init_config2();
51082
51129
  init_upstream_error_capture();
51083
51130
  init_process_tree();
51084
51131
  init_redact();
51085
51132
  init_session_discovery();
51133
+ init_stdio_decode();
51086
51134
  init_team_orchestrator();
51087
51135
  init_team_stats();
51088
51136
  init_scrollback_buffer();
@@ -56687,14 +56735,18 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56687
56735
  });
56688
56736
  tools.push({
56689
56737
  name: "team",
56690
- description: "Run AI models on a task with anonymized outputs and optional blind judging. Modes: 'run' (execute models), 'judge' (blind-vote on existing outputs), 'run-and-judge' (full pipeline), 'status' (check progress).",
56738
+ description: "Run AI models on a task with anonymized outputs and optional blind judging. " + "Modes: 'run' (START the models and return a slot map immediately \u2014 it does NOT " + "wait), 'status' (per-slot state, plus how long each slot has been silent), " + "'cancel' (stop one slot or the whole run), 'judge' (blind-vote on existing " + "outputs), 'run-and-judge' (the blocking pipeline). " + "NO SLOT IS EVER KILLED ON A TIMER. A team slot is a full Claude Code session and " + "may work for a long time; a slot inside a build or test suite emits nothing for " + "minutes and is working, not stuck. Poll 'status', judge the silence against the " + "task you set, and use 'cancel' if you decide a slot is wedged.",
56691
56739
  inputSchema: {
56692
56740
  type: "object",
56693
56741
  properties: {
56694
56742
  mode: {
56695
56743
  type: "string",
56696
- enum: ["run", "judge", "run-and-judge", "status"],
56697
- description: "Operation mode"
56744
+ enum: ["run", "judge", "run-and-judge", "status", "cancel"],
56745
+ description: "Operation mode. 'run' STARTS the models and returns immediately with a " + "slot map \u2014 it does not wait. Poll 'status' for progress, then 'judge' once " + "the slots have finished. 'run-and-judge' is the blocking pipeline and holds " + "the call open for the whole run. 'cancel' stops one slot or the whole run."
56746
+ },
56747
+ slot: {
56748
+ type: "string",
56749
+ description: "For 'cancel': the anonymised slot id to stop (e.g. '02'), from the slot map " + "'run' returned. Omit to cancel every slot in the run."
56698
56750
  },
56699
56751
  path: {
56700
56752
  type: "string",
@@ -56710,11 +56762,14 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56710
56762
  items: { type: "string" },
56711
56763
  description: "Model IDs to use as judges (default: same as runners)"
56712
56764
  },
56765
+ input_file: {
56766
+ type: "string",
56767
+ description: "PREFERRED. Path to a file holding the task prompt, relative to the working " + "directory. Use this rather than `input` for anything longer than a sentence: " + "a prompt passed inline is echoed verbatim in the caller's terminal, where a " + "200-line review brief buries every other argument and makes the call " + "unreadable. Write the brief to the session directory first (input.md is the " + "conventional name) and point here."
56768
+ },
56713
56769
  input: {
56714
56770
  type: "string",
56715
- description: "Task prompt text (or place input.md in the session directory before calling)"
56771
+ description: "Task prompt as inline text. Prefer `input_file` \u2014 inline text is rendered in " + "full in the caller's terminal. Passing both is an error. If neither is given, " + "an input.md already present in the session directory is used."
56716
56772
  },
56717
- timeout: { type: "number", description: "Per-model timeout in seconds (default: 300)" },
56718
56773
  require_pattern: {
56719
56774
  type: "string",
56720
56775
  description: "Regex the response MUST match, or the slot is reported FAILED (state EMPTY, " + "reason 'shape_mismatch') instead of succeeded. Strongly recommended whenever " + "your prompt mandates an output shape \u2014 e.g. '```vote' for a voting panel. " + "Exit code 0 is not a success oracle: it is 0 on API errors and on a child " + "that simply never followed the format. Answers are no longer LOST to print " + "mode (every assistant message is captured), so a mismatch now means the model " + "did not produce the shape, not that the shape was discarded."
@@ -56742,8 +56797,12 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56742
56797
  const path = args.path;
56743
56798
  const models = args.models;
56744
56799
  const judges = args.judges;
56745
- const input = args.input;
56746
- const timeout = args.timeout;
56800
+ const inlineInput = args.input;
56801
+ const inputFile = args.input_file;
56802
+ if (inlineInput !== undefined && inputFile !== undefined) {
56803
+ throw new Error("Pass `input_file` or `input`, not both. Prefer `input_file` \u2014 inline text is " + "rendered verbatim in the caller's terminal.");
56804
+ }
56805
+ const input = inputFile !== undefined ? readTeamInputFile(inputFile) : inlineInput;
56747
56806
  const requirePattern = args.require_pattern;
56748
56807
  const minOutputBytes = args.min_output_bytes;
56749
56808
  const childFlags = buildChildClaudeFlags(args.agent, args.claude_flags);
@@ -56752,7 +56811,6 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56752
56811
  const teamSessionId = resolved.split("/").filter(Boolean).pop() ?? "team";
56753
56812
  const teamCreatedAt = new Date().toISOString();
56754
56813
  const runOpts = {
56755
- timeout,
56756
56814
  requirePattern,
56757
56815
  minOutputBytes,
56758
56816
  claudeFlags: childFlags,
@@ -56773,9 +56831,51 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56773
56831
  if (!models?.length)
56774
56832
  throw new Error("'models' is required for 'run' mode");
56775
56833
  setupSession(resolved, models, input);
56776
- const status = await runModels(resolved, runOpts);
56834
+ const handle = await startModels(resolved, runOpts);
56777
56835
  return {
56778
- content: [{ type: "text", text: formatTeamResult(status, resolved) }]
56836
+ content: [
56837
+ {
56838
+ type: "text",
56839
+ text: JSON.stringify({
56840
+ started: true,
56841
+ team_session_id: handle.teamSessionId,
56842
+ session_path: handle.sessionPath,
56843
+ slots: handle.slots,
56844
+ next: {
56845
+ status: `team(mode:"status", path:"${handle.sessionPath}")`,
56846
+ cancel: `team(mode:"cancel", path:"${handle.sessionPath}", slot:"<id>")`,
56847
+ judge: `team(mode:"judge", path:"${handle.sessionPath}") once every slot has finished`
56848
+ },
56849
+ note: "Nothing terminates a slot on a timer. `status` reports how many " + "seconds each slot has been silent; a slot inside a long build is " + "quiet and working. You decide whether to cancel."
56850
+ }, null, 2)
56851
+ }
56852
+ ]
56853
+ };
56854
+ }
56855
+ case "cancel": {
56856
+ const slot = args.slot;
56857
+ const teamSessionId2 = resolved.split("/").filter(Boolean).pop() ?? "team";
56858
+ const result = await cancelTeamRun(teamSessionId2, slot);
56859
+ if (!result.found) {
56860
+ return {
56861
+ content: [
56862
+ {
56863
+ type: "text",
56864
+ text: JSON.stringify({
56865
+ cancelled: [],
56866
+ note: "No live run for that path. It already settled (read `status`), or " + "it was started by a different process \u2014 this server can only stop " + "children it spawned."
56867
+ })
56868
+ }
56869
+ ]
56870
+ };
56871
+ }
56872
+ return {
56873
+ content: [
56874
+ {
56875
+ type: "text",
56876
+ text: JSON.stringify({ cancelled: result.cancelled }, null, 2)
56877
+ }
56878
+ ]
56779
56879
  };
56780
56880
  }
56781
56881
  case "judge": {
@@ -56792,7 +56892,25 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56792
56892
  }
56793
56893
  case "status": {
56794
56894
  const status = getStatus(resolved);
56795
- return { content: [{ type: "text", text: JSON.stringify(status, null, 2) }] };
56895
+ const teamSessionId2 = resolved.split("/").filter(Boolean).pop() ?? "team";
56896
+ const idle = teamSlotIdleSeconds(teamSessionId2);
56897
+ const settled = !Object.values(status.models).some((m) => m.state === "RUNNING");
56898
+ return {
56899
+ content: [
56900
+ {
56901
+ type: "text",
56902
+ text: JSON.stringify({
56903
+ ...status,
56904
+ idle_seconds_by_slot: idle,
56905
+ activity_by_slot: teamSlotActivity(teamSessionId2),
56906
+ ...idle ? {
56907
+ note: "idle_seconds_by_slot is how long each slot has been silent; " + "read it against activity_by_slot. Silence in tool_executing " + "is a build or test suite running, and is not a failure " + 'signal. Nothing cancels on your behalf \u2014 use mode:"cancel" ' + "if you decide to."
56908
+ } : {},
56909
+ ...settled ? { summary: formatTeamResult(status, resolved) } : {}
56910
+ }, null, 2)
56911
+ }
56912
+ ]
56913
+ };
56796
56914
  }
56797
56915
  default:
56798
56916
  throw new Error(`Unknown mode: ${mode}`);
@@ -57112,7 +57230,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
57112
57230
  });
57113
57231
  tools.push({
57114
57232
  name: "list_sessions",
57115
- description: "List all active channel sessions. Optionally include completed sessions.",
57233
+ description: "List all active channel sessions. Optionally include completed sessions. " + "Each session reports `idleSeconds`: how long since the child last emitted " + "anything. Nothing kills a session for being idle \u2014 a child inside a long " + "Bash call is silent and working \u2014 so this is yours to judge against the " + "task you set, and `cancel_session` is yours to call if the answer is no.",
57116
57234
  inputSchema: {
57117
57235
  type: "object",
57118
57236
  properties: {
@@ -57132,7 +57250,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
57132
57250
  });
57133
57251
  tools.push({
57134
57252
  name: "get_diagnostics",
57135
- description: "Explain what a channel session actually did \u2014 stderr, upstream error bodies, the " + "recent event frames, the resolved model chain, accounting, and the paths to the " + "full records. Call this FIRST whenever a session fails, times out, or completes " + "with empty or surprising output; it needs no re-run and no debug flag.",
57253
+ description: "Explain what a channel session actually did \u2014 stderr, upstream error bodies, the " + "recent event frames, the resolved model chain, accounting, and the paths to the " + "full records. Call this FIRST whenever a session fails, times out, or completes " + "with empty or surprising output; it needs no re-run and no debug flag. " + "`idleSeconds` reports how long since the child last emitted a frame, and is " + "null once the session is no longer live. It is information, never a verdict: " + "claudish does not terminate a session for silence.",
57136
57254
  inputSchema: {
57137
57255
  type: "object",
57138
57256
  properties: {
@@ -57289,6 +57407,7 @@ Call get_diagnostics with session_id: "${sessionId2}" for the stderr, the upstre
57289
57407
  await server.connect(transport);
57290
57408
  process.on("SIGTERM", () => {
57291
57409
  sessionManager.shutdownAll().catch(() => {});
57410
+ shutdownAllTeamRuns().catch(() => {});
57292
57411
  });
57293
57412
  }
57294
57413
  function startMcpServer() {
@@ -57356,7 +57475,8 @@ var init_mcp_server = __esm(() => {
57356
57475
  ALL_MODELS_CACHE_PATH2 = join34(CLAUDISH_CACHE_DIR, "all-models.json");
57357
57476
  NEXT_STEP = {
57358
57477
  nonzero_exit: "read the evidence log, then retry or drop the model",
57359
- timeout: "raise `timeout`, or pick a faster model",
57478
+ cancelled: "you stopped this slot; nothing is wrong with it. Re-run it if you still want its vote",
57479
+ timeout: "grid mode only \u2014 magmux ended the pane. The orchestrator has no deadline",
57360
57480
  api_error: "retry once, or route via a different provider (or@<model>)",
57361
57481
  background_task_ceiling: "set CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0 for children, or forbid background work in the prompt",
57362
57482
  empty_output: "retry once; if it repeats, drop the model",
@@ -58239,7 +58359,8 @@ Options (run / run-and-judge):
58239
58359
  --path <dir> Session directory (default: .)
58240
58360
  --models <a,b,...> Comma-separated model IDs to run
58241
58361
  --input <text> Task prompt (or create input.md in --path beforehand)
58242
- --timeout <secs> Timeout per model in seconds (default: 300)
58362
+ --timeout <secs> Grid modes only: magmux's own per-pane timeout (default: 300).
58363
+ json mode has no deadline \u2014 nothing kills a working model.
58243
58364
  --grid Show all models in a magmux grid with live output + status bar
58244
58365
 
58245
58366
  Options (judge / run-and-judge):
@@ -58306,7 +58427,6 @@ async function teamCommand(args) {
58306
58427
  if (effectiveMode === "json") {
58307
58428
  setupSession(sessionPath, models, input);
58308
58429
  const runStatus = await runModels(sessionPath, {
58309
- timeout,
58310
58430
  onStatusChange: (id, s) => {
58311
58431
  process.stderr.write(`[team] ${id}: ${s.state}
58312
58432
  `);
@@ -58335,7 +58455,6 @@ async function teamCommand(args) {
58335
58455
  }
58336
58456
  setupSession(sessionPath, models, input);
58337
58457
  const status = await runModels(sessionPath, {
58338
- timeout,
58339
58458
  onStatusChange: (id, s) => {
58340
58459
  process.stderr.write(`[team] ${id}: ${s.state}
58341
58460
  `);
@@ -66218,9 +66337,9 @@ var require_internal = __commonJS(function(exports, module) {
66218
66337
  }
66219
66338
  InternalCodec.prototype.encoder = InternalEncoder;
66220
66339
  InternalCodec.prototype.decoder = InternalDecoder;
66221
- var StringDecoder2 = __require("string_decoder").StringDecoder;
66340
+ var StringDecoder3 = __require("string_decoder").StringDecoder;
66222
66341
  function InternalDecoder(options, codec2) {
66223
- this.decoder = new StringDecoder2(codec2.enc);
66342
+ this.decoder = new StringDecoder3(codec2.enc);
66224
66343
  }
66225
66344
  InternalDecoder.prototype.write = function(buf) {
66226
66345
  if (!Buffer2.isBuffer(buf)) {
@@ -86111,7 +86230,7 @@ var init_widgets = __esm(() => {
86111
86230
 
86112
86231
  // src/session/conversation.ts
86113
86232
  import { closeSync as closeSync9, openSync as openSync9, readSync as readSync4, statSync as statSync9 } from "fs";
86114
- import { StringDecoder as StringDecoder2 } from "string_decoder";
86233
+ import { StringDecoder as StringDecoder3 } from "string_decoder";
86115
86234
  function looksLikeTurn(line) {
86116
86235
  const assistant = line.includes('"type":"assistant"');
86117
86236
  if (!assistant && !line.includes('"type":"user"'))
@@ -86168,7 +86287,7 @@ function readConversation(file2, opts = {}) {
86168
86287
  const size = statSync9(file2).size;
86169
86288
  fd = openSync9(file2, "r");
86170
86289
  const buf = Buffer.allocUnsafe(chunkBytes);
86171
- const decoder = new StringDecoder2("utf-8");
86290
+ const decoder = new StringDecoder3("utf-8");
86172
86291
  let pending = "";
86173
86292
  let pos = 0;
86174
86293
  const consume = (line) => {
@@ -88463,7 +88582,6 @@ async function runCli() {
88463
88582
  const { setupSession: setupSession2, runModels: runModels2 } = await Promise.resolve().then(() => (init_team_orchestrator(), exports_team_orchestrator));
88464
88583
  setupSession2(sessionPath, cliConfig.team, prompt);
88465
88584
  const status2 = await runModels2(sessionPath, {
88466
- timeout: 300,
88467
88585
  claudeFlags: ["--json"]
88468
88586
  });
88469
88587
  const result = { ...status2, responses: {} };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "7.67.1",
3
+ "version": "8.0.0",
4
4
  "description": "Run Claude Code with any model - OpenRouter, Ollama, LM Studio & local models",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -60,10 +60,10 @@
60
60
  "ai"
61
61
  ],
62
62
  "optionalDependencies": {
63
- "@claudish/magmux-darwin-arm64": "7.67.1",
64
- "@claudish/magmux-darwin-x64": "7.67.1",
65
- "@claudish/magmux-linux-arm64": "7.67.1",
66
- "@claudish/magmux-linux-x64": "7.67.1"
63
+ "@claudish/magmux-darwin-arm64": "8.0.0",
64
+ "@claudish/magmux-darwin-x64": "8.0.0",
65
+ "@claudish/magmux-linux-arm64": "8.0.0",
66
+ "@claudish/magmux-linux-x64": "8.0.0"
67
67
  },
68
68
  "author": "Jack Rudenko <i@madappgang.com>",
69
69
  "license": "MIT",