claudish 7.67.0 → 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 +311 -159
  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.0";
734
+ var VERSION = "8.0.0";
735
735
 
736
736
  // src/logger.ts
737
737
  var exports_logger = {};
@@ -28268,8 +28268,10 @@ function truncateToolName(name, maxLength) {
28268
28268
  log(`[ToolName] Truncated: "${name}" -> "${truncated}" (${name.length} -> ${truncated.length} chars)`);
28269
28269
  return truncated;
28270
28270
  }
28271
+ var TOOL_NAME_SOURCE = "[A-Za-z_][A-Za-z0-9_.-]{0,63}", TOOL_NAME_SHAPE;
28271
28272
  var init_tool_name_utils = __esm(() => {
28272
28273
  init_logger();
28274
+ TOOL_NAME_SHAPE = new RegExp(`^${TOOL_NAME_SOURCE}$`);
28273
28275
  });
28274
28276
 
28275
28277
  // src/handlers/shared/format/openai-messages.ts
@@ -29589,9 +29591,32 @@ function filterIdentity(content) {
29589
29591
  }
29590
29592
 
29591
29593
  // src/handlers/shared/tool-call-recovery.ts
29592
- function extractToolCallsFromText(text) {
29594
+ function hasExtractableFunctionTag(text) {
29595
+ return FUNCTION_TAG_PRESENT.test(text);
29596
+ }
29597
+ function keepOnlyRealTools(extracted, knownToolNames) {
29598
+ const kept = [];
29599
+ for (const call of extracted) {
29600
+ if (!TOOL_NAME_SHAPE.test(call.name)) {
29601
+ log(`[ToolRecovery] Dropped extracted call: name is not an identifier: ${JSON.stringify(call.name.slice(0, 120))}`);
29602
+ continue;
29603
+ }
29604
+ if (!knownToolNames || knownToolNames.length === 0) {
29605
+ kept.push(call);
29606
+ continue;
29607
+ }
29608
+ const canonical = knownToolNames.find((t) => t.toLowerCase() === call.name.toLowerCase());
29609
+ if (!canonical) {
29610
+ log(`[ToolRecovery] Dropped extracted call for unadvertised tool: ${call.name}`);
29611
+ continue;
29612
+ }
29613
+ kept.push(canonical === call.name ? call : { ...call, name: canonical });
29614
+ }
29615
+ return kept;
29616
+ }
29617
+ function extractToolCallsFromText(text, knownToolNames) {
29593
29618
  const extracted = [];
29594
- const qwenPattern = /<function=([^>]+)>([\s\S]*?)(?=<function=|$)/gi;
29619
+ const qwenPattern = new RegExp(FUNCTION_TAG_SOURCE, "gi");
29595
29620
  let match;
29596
29621
  while ((match = qwenPattern.exec(text)) !== null) {
29597
29622
  const funcName = match[1];
@@ -29685,7 +29710,7 @@ function extractToolCallsFromText(text) {
29685
29710
  }
29686
29711
  } catch (e) {}
29687
29712
  }
29688
- const knownTools = [
29713
+ const knownTools = knownToolNames && knownToolNames.length > 0 ? knownToolNames : [
29689
29714
  "Task",
29690
29715
  "Read",
29691
29716
  "Write",
@@ -29793,7 +29818,7 @@ function extractToolCallsFromText(text) {
29793
29818
  }
29794
29819
  }
29795
29820
  }
29796
- return extracted;
29821
+ return keepOnlyRealTools(extracted, knownToolNames);
29797
29822
  }
29798
29823
  function inferMissingParameters(toolName2, args, missingParams, context) {
29799
29824
  const inferred = { ...args };
@@ -29958,8 +29983,12 @@ function validateAndRepairToolCall(toolName2, argsStr, toolSchemas, textContent)
29958
29983
  }
29959
29984
  return { valid: false, args: repairedArgs, repaired: false, missingParams: stillMissing };
29960
29985
  }
29986
+ var FUNCTION_TAG_SOURCE, FUNCTION_TAG_PRESENT;
29961
29987
  var init_tool_call_recovery = __esm(() => {
29988
+ init_tool_name_utils();
29962
29989
  init_logger();
29990
+ FUNCTION_TAG_SOURCE = `<function=(${TOOL_NAME_SOURCE})>([\\s\\S]*?)(?=<function=|$)`;
29991
+ FUNCTION_TAG_PRESENT = new RegExp(`<function=${TOOL_NAME_SOURCE}>`);
29963
29992
  });
29964
29993
 
29965
29994
  // src/handlers/shared/web-search-detector.ts
@@ -30102,7 +30131,10 @@ data: ${JSON.stringify(d)}
30102
30131
  const preview = state.accumulatedText.slice(0, 500).replace(/\n/g, "\\n");
30103
30132
  log(`[Streaming] Accumulated text (${state.accumulatedText.length} chars): ${preview}...`);
30104
30133
  }
30105
- const textToolCalls = extractToolCallsFromText(state.accumulatedText);
30134
+ const textToolCalls = state.tools.size > 0 ? [] : extractToolCallsFromText(state.accumulatedText, toolSchemas?.map((t) => t?.name).filter((n) => !!n));
30135
+ if (state.tools.size > 0 && state.accumulatedText.length > 0) {
30136
+ log(`[Streaming] Skipping text-based tool extraction: ${state.tools.size} structured tool call(s) already present`);
30137
+ }
30106
30138
  log(`[Streaming] Text-based tool calls found: ${textToolCalls.length}`);
30107
30139
  if (textToolCalls.length > 0) {
30108
30140
  log(`[Streaming] Found ${textToolCalls.length} text-based tool call(s), converting to structured format`);
@@ -30309,7 +30341,7 @@ data: ${JSON.stringify(d)}
30309
30341
  }
30310
30342
  if (res.cleanedText) {
30311
30343
  state.accumulatedText += res.cleanedText;
30312
- const hasStructuredToolPattern = /<function=[^>]+>/.test(state.accumulatedText) || /\{\s*"(?:name|tool)"\s*:\s*"(?:Task|Read|Write|Edit|Bash|Grep|Glob)"/i.test(state.accumulatedText) || /<tool_call>/.test(state.accumulatedText);
30344
+ const hasStructuredToolPattern = hasExtractableFunctionTag(state.accumulatedText) || /\{\s*"(?:name|tool)"\s*:\s*"(?:Task|Read|Write|Edit|Bash|Grep|Glob)"/i.test(state.accumulatedText) || /<tool_call>/.test(state.accumulatedText);
30313
30345
  const shouldHoldBack = hasStructuredToolPattern && state.accumulatedText.length < 1000;
30314
30346
  if (shouldHoldBack) {
30315
30347
  log(`[Streaming] Text held back (structured tool pattern): ${state.accumulatedText.length} chars accumulated`);
@@ -43030,7 +43062,8 @@ class TokenTracker {
43030
43062
  this.config = config2;
43031
43063
  }
43032
43064
  recordToolUse(name) {
43033
- const key = name.trim() || "unknown";
43065
+ const trimmed2 = name.trim();
43066
+ const key = !trimmed2 ? "unknown" : TOOL_NAME_SHAPE.test(trimmed2) ? trimmed2 : "malformed";
43034
43067
  this.toolCallsByName.set(key, (this.toolCallsByName.get(key) ?? 0) + 1);
43035
43068
  }
43036
43069
  getToolCallCount() {
@@ -43203,6 +43236,7 @@ class TokenTracker {
43203
43236
  }
43204
43237
  }
43205
43238
  var init_token_tracker = __esm(() => {
43239
+ init_tool_name_utils();
43206
43240
  init_types2();
43207
43241
  init_logger();
43208
43242
  init_remote_provider_types();
@@ -48369,10 +48403,12 @@ var init_team_stream_capture = __esm(() => {
48369
48403
  });
48370
48404
 
48371
48405
  // src/channel/stream-json-reducer.ts
48372
- function reachesAnswer(parsedType8, isJson) {
48406
+ function reachesAnswer(parsedType8, isJson, keepUnrecognizedJson) {
48373
48407
  if (!isJson)
48374
48408
  return true;
48375
- 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;
48376
48412
  }
48377
48413
  function labelFor(type, subtype) {
48378
48414
  if (type === null)
@@ -48444,6 +48480,9 @@ class StreamJsonReducer {
48444
48480
  get toolUseCount() {
48445
48481
  return this._toolUseCount;
48446
48482
  }
48483
+ get idleMs() {
48484
+ return Math.max(0, Date.now() - this.lastFrameAt);
48485
+ }
48447
48486
  get terminalReason() {
48448
48487
  return this._terminalReason;
48449
48488
  }
@@ -48536,7 +48575,7 @@ class StreamJsonReducer {
48536
48575
  this.opts.onSemanticLine?.(line, labelFor(type, frame ? asString(frame.subtype) : null));
48537
48576
  if (frame && type !== null)
48538
48577
  this.applyFrame(frame, type);
48539
- if (!reachesAnswer(type, frame !== null))
48578
+ if (!reachesAnswer(type, frame !== null, this.opts.keepUnrecognizedJson ?? false))
48540
48579
  return "";
48541
48580
  const prose = this.capture.write(terminated ? `${line}
48542
48581
  ` : line);
@@ -49285,6 +49324,16 @@ function resolveClaudishSpawn(env = process.env) {
49285
49324
  }
49286
49325
  var CLAUDISH_BIN_ENV = "CLAUDISH_BIN";
49287
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
+
49288
49337
  // src/team-stats.ts
49289
49338
  import { existsSync as existsSync19, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
49290
49339
  import { join as join28 } from "path";
@@ -49460,22 +49509,25 @@ var init_team_stats = () => {};
49460
49509
  var exports_team_orchestrator = {};
49461
49510
  __export(exports_team_orchestrator, {
49462
49511
  DEFAULT_MIN_OUTPUT_BYTES: () => DEFAULT_MIN_OUTPUT_BYTES,
49463
- DEFAULT_STALL_SECONDS: () => DEFAULT_STALL_SECONDS2,
49464
- DRAIN_TIMEOUT_MS: () => DRAIN_TIMEOUT_MS,
49465
- GRACE_INTERVAL_MS: () => GRACE_INTERVAL_MS,
49466
49512
  STDOUT_TAIL_LIMIT: () => STDOUT_TAIL_LIMIT,
49467
49513
  TEAM_CAPTURE_ENV_VAR: () => TEAM_CAPTURE_ENV_VAR,
49468
49514
  aggregateVerdict: () => aggregateVerdict,
49469
49515
  buildJudgePrompt: () => buildJudgePrompt,
49516
+ cancelTeamRun: () => cancelTeamRun,
49470
49517
  classifyRunOutput: () => classifyRunOutput,
49471
49518
  fisherYatesShuffle: () => fisherYatesShuffle,
49472
49519
  getStatus: () => getStatus,
49473
49520
  judgeResponses: () => judgeResponses,
49474
49521
  meaningfulStderr: () => meaningfulStderr,
49475
49522
  parseJudgeVotes: () => parseJudgeVotes,
49523
+ readTeamInputFile: () => readTeamInputFile,
49476
49524
  resolveCaptureMode: () => resolveCaptureMode,
49477
49525
  runModels: () => runModels,
49478
49526
  setupSession: () => setupSession,
49527
+ shutdownAllTeamRuns: () => shutdownAllTeamRuns,
49528
+ startModels: () => startModels,
49529
+ teamSlotActivity: () => teamSlotActivity,
49530
+ teamSlotIdleSeconds: () => teamSlotIdleSeconds,
49479
49531
  validateSessionPath: () => validateSessionPath
49480
49532
  });
49481
49533
  import { spawn as spawn2 } from "child_process";
@@ -49487,12 +49539,57 @@ import {
49487
49539
  readdirSync as readdirSync5,
49488
49540
  writeFileSync as writeFileSync11
49489
49541
  } from "fs";
49490
- import { join as join29, resolve as resolve3 } from "path";
49542
+ import { basename as basename2, join as join29, resolve as resolve3 } from "path";
49491
49543
  function resolveCaptureMode(explicit, env = process.env) {
49492
49544
  if (explicit)
49493
49545
  return explicit;
49494
49546
  return env[TEAM_CAPTURE_ENV_VAR]?.trim().toLowerCase() === "print" ? "print" : "stream-json";
49495
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
+ }
49496
49593
  function classifyRunOutput(opts) {
49497
49594
  const {
49498
49595
  outputSize,
@@ -49572,6 +49669,21 @@ function validateSessionPath(sessionPath) {
49572
49669
  }
49573
49670
  return resolved;
49574
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
+ }
49575
49687
  function setupSession(sessionPath, models, input) {
49576
49688
  if (models.length === 0) {
49577
49689
  throw new Error("At least one model is required");
@@ -49638,8 +49750,7 @@ function readFullOutputIfNeeded(opts) {
49638
49750
  return;
49639
49751
  }
49640
49752
  }
49641
- async function runModels(sessionPath, opts = {}) {
49642
- const timeoutMs = (opts.timeout ?? 300) * 1000;
49753
+ async function startModels(sessionPath, opts = {}) {
49643
49754
  assertValidRequirePattern(opts.requirePattern);
49644
49755
  const manifest = JSON.parse(readFileSync19(join29(sessionPath, "manifest.json"), "utf-8"));
49645
49756
  const statusPath = join29(sessionPath, "status.json");
@@ -49685,6 +49796,7 @@ async function runModels(sessionPath, opts = {}) {
49685
49796
  mkdirSync11(statsDir(sessionPath), { recursive: true });
49686
49797
  const processes = new Map;
49687
49798
  const runtimes = new Map;
49799
+ const cancelledSlots = new Set;
49688
49800
  const sigintHandler = () => {
49689
49801
  for (const [, proc] of processes) {
49690
49802
  signalProcessTree(proc, "SIGTERM");
@@ -49696,6 +49808,7 @@ async function runModels(sessionPath, opts = {}) {
49696
49808
  for (const [anonId, entry] of Object.entries(manifest.models)) {
49697
49809
  const outputPath = join29(sessionPath, `response-${anonId}.md`);
49698
49810
  const errorLogPath = join29(sessionPath, "errors", `${anonId}.log`);
49811
+ const upstreamErrorLogPath = join29(sessionPath, "errors", `${anonId}-upstream.jsonl`);
49699
49812
  const spawnModel = spawnPlan.pinned.get(entry.model) ?? entry.model;
49700
49813
  const args = [
49701
49814
  "--model",
@@ -49716,21 +49829,37 @@ async function runModels(sessionPath, opts = {}) {
49716
49829
  detached: KILL_PROCESS_GROUP,
49717
49830
  env: {
49718
49831
  ...process.env,
49719
- CLAUDISH_TOKEN_FILE: tokenFileFor(sessionPath, anonId)
49832
+ [ENV.CLAUDISH_TOKEN_FILE]: tokenFileFor(sessionPath, anonId),
49833
+ [UPSTREAM_ERROR_LOG_ENV]: upstreamErrorLogPath
49720
49834
  }
49721
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);
49722
49844
  let byteCount = 0;
49723
49845
  let stdoutTail = "";
49724
49846
  const outputStream = createWriteStream(outputPath);
49725
49847
  let flushPartial = () => {};
49848
+ let reducer = null;
49726
49849
  if (captureMode === "print") {
49727
49850
  proc.stdout?.on("data", (chunk) => {
49728
49851
  byteCount += chunk.length;
49729
- stdoutTail = (stdoutTail + chunk.toString()).slice(-STDOUT_TAIL_LIMIT);
49852
+ stdoutTail = (stdoutTail + decodeChunk(stdoutDecoder, chunk)).slice(-STDOUT_TAIL_LIMIT);
49730
49853
  });
49731
49854
  proc.stdout?.pipe(outputStream);
49732
49855
  } else {
49733
- const capture = createAssistantTextCapture();
49856
+ const slotReducer = new StreamJsonReducer({
49857
+ sessionId: anonId,
49858
+ stallSeconds: 0,
49859
+ keepUnrecognizedJson: true,
49860
+ callback: () => {}
49861
+ });
49862
+ reducer = slotReducer;
49734
49863
  const absorb = (text) => {
49735
49864
  if (text.length === 0)
49736
49865
  return;
@@ -49738,14 +49867,15 @@ async function runModels(sessionPath, opts = {}) {
49738
49867
  stdoutTail = (stdoutTail + text).slice(-STDOUT_TAIL_LIMIT);
49739
49868
  outputStream.write(text);
49740
49869
  };
49741
- proc.stdout?.on("data", (chunk) => absorb(capture.write(chunk.toString())));
49742
- flushPartial = () => absorb(capture.end());
49870
+ proc.stdout?.on("data", (chunk) => absorb(slotReducer.feed(decodeChunk(stdoutDecoder, chunk))));
49871
+ flushPartial = () => absorb(slotReducer.end());
49743
49872
  let captureFinalized = false;
49744
49873
  const finalizeCapture = () => {
49745
49874
  if (captureFinalized)
49746
49875
  return;
49747
49876
  captureFinalized = true;
49748
- absorb(capture.end());
49877
+ absorb(slotReducer.end());
49878
+ slotReducer.dispose();
49749
49879
  outputStream.end();
49750
49880
  };
49751
49881
  proc.stdout?.on("end", finalizeCapture);
@@ -49753,7 +49883,7 @@ async function runModels(sessionPath, opts = {}) {
49753
49883
  }
49754
49884
  let stderr = "";
49755
49885
  proc.stderr?.on("data", (chunk) => {
49756
- stderr += chunk.toString();
49886
+ stderr += decodeChunk(stderrDecoder, chunk);
49757
49887
  });
49758
49888
  const command = `claudish ${args.join(" ")}`;
49759
49889
  runtimes.set(anonId, {
@@ -49762,6 +49892,8 @@ async function runModels(sessionPath, opts = {}) {
49762
49892
  getStderr: () => stderr,
49763
49893
  getStdoutTail: () => stdoutTail,
49764
49894
  getByteCount: () => byteCount,
49895
+ getIdleMs: () => Math.max(0, Date.now() - lastOutputAt),
49896
+ getActivity: () => reducer?.state ?? null,
49765
49897
  flushPartial: () => flushPartial()
49766
49898
  });
49767
49899
  proc.stdin?.write(inputContent);
@@ -49799,8 +49931,9 @@ async function runModels(sessionPath, opts = {}) {
49799
49931
  const failed = crashed || degraded !== null;
49800
49932
  const state = crashed ? "FAILED" : degraded ? "EMPTY" : "COMPLETED";
49801
49933
  if (failed) {
49802
- const reason = crashed ? "nonzero_exit" : degraded.reason;
49803
- 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;
49804
49937
  persistErrorLog(errorLogPath, `${state}: ${detail}`, stderr, stdoutTail);
49805
49938
  updateModelStatus(anonId, {
49806
49939
  state,
@@ -49815,6 +49948,7 @@ async function runModels(sessionPath, opts = {}) {
49815
49948
  stderrSnippet: stderr ? redactSecrets(stderr).slice(-2000) : undefined,
49816
49949
  stdoutSnippet: stdoutTail ? redactSecrets(stdoutTail).slice(-2000) : undefined,
49817
49950
  errorLogPath,
49951
+ upstreamErrorLogPath: existsSync20(upstreamErrorLogPath) ? upstreamErrorLogPath : undefined,
49818
49952
  workDir: sessionPath
49819
49953
  }
49820
49954
  });
@@ -49875,102 +50009,36 @@ async function runModels(sessionPath, opts = {}) {
49875
50009
  emitProgress();
49876
50010
  const progressHandle = setInterval(() => emitProgress("running"), POLL_MS);
49877
50011
  progressHandle.unref?.();
49878
- const graceEnabled = opts.graceExtension ?? true;
49879
- const maxGraceMs = Math.max(0, (opts.maxGraceSeconds ?? timeoutMs / 1000) * 1000);
49880
- const stallMs = Math.max(0, (opts.stallSeconds ?? DEFAULT_STALL_SECONDS2) * 1000);
49881
- const idleMsFor = (id) => {
49882
- const s = readTokenStats(sessionPath, id);
49883
- if (!s || typeof s.updated_at !== "number" || s.updated_at <= 0)
49884
- return null;
49885
- return Math.max(0, Date.now() - s.updated_at);
49886
- };
49887
- const graceStartedAt = new Map;
49888
- const graceUsedMs = (id, now2) => {
49889
- const start = graceStartedAt.get(id);
49890
- return start === undefined ? 0 : Math.max(0, now2 - start);
49891
- };
49892
- const runningIds = () => [...processes.keys()].filter((id) => statusCache.models[id]?.state === "RUNNING");
49893
- const timeoutModel = async (id, why) => {
49894
- const proc = processes.get(id);
49895
- if (!proc || statusCache.models[id]?.state !== "RUNNING")
49896
- return;
49897
- const rt = runtimes.get(id);
49898
- rt?.flushPartial();
49899
- const stderr = rt?.getStderr() ?? "";
49900
- const stdoutTail = rt?.getStdoutTail() ?? "";
49901
- const bytes2 = rt?.getByteCount() ?? 0;
49902
- const grace = graceUsedMs(id, Date.now());
49903
- 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".`;
49904
- if (rt)
49905
- persistErrorLog(rt.errorLogPath, `TIMEOUT: ${detail}`, stderr, stdoutTail);
49906
- updateModelStatus(id, {
49907
- state: "TIMEOUT",
49908
- completedAt: new Date().toISOString(),
49909
- outputSize: bytes2,
49910
- error: rt ? {
49911
- model: id,
49912
- command: rt.command,
49913
- reason: "timeout",
49914
- detail,
49915
- stderrSnippet: stderr ? redactSecrets(stderr).slice(-2000) : undefined,
49916
- stdoutSnippet: stdoutTail ? redactSecrets(stdoutTail).slice(-2000) : undefined,
49917
- errorLogPath: rt.errorLogPath,
49918
- workDir: sessionPath
49919
- } : undefined
49920
- });
49921
- opts.onStatusChange?.(id, statusCache.models[id]);
49922
- const stopped = await terminateChildTree(proc);
49923
- if (!stopped) {
49924
- 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);
49925
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
49926
50037
  };
49927
- const allDone = Promise.all(completionPromises);
49928
- let settled = false;
49929
- allDone.then(() => {
49930
- settled = true;
49931
- }, () => {
49932
- settled = true;
49933
- });
49934
- const deadlineWatcher = (async () => {
49935
- await delay(timeoutMs);
49936
- for (;; ) {
49937
- if (settled)
49938
- return;
49939
- const running = runningIds();
49940
- if (running.length === 0)
49941
- return;
49942
- const extended = [];
49943
- const now2 = Date.now();
49944
- for (const id of running) {
49945
- const idleMs = idleMsFor(id);
49946
- const usedGrace = graceUsedMs(id, now2);
49947
- if (!graceEnabled) {
49948
- await timeoutModel(id, "deadline reached (grace extension disabled)");
49949
- } else if (usedGrace >= maxGraceMs) {
49950
- await timeoutModel(id, `grace exhausted after ${Math.round(usedGrace / 1000)}s of extra time`);
49951
- } else if (idleMs === null) {
49952
- await timeoutModel(id, "deadline reached with no measurable progress to extend for");
49953
- } else if (idleMs >= stallMs) {
49954
- await timeoutModel(id, `no measurable progress for ${Math.round(idleMs / 1000)}s`);
49955
- } else {
49956
- if (!graceStartedAt.has(id))
49957
- graceStartedAt.set(id, now2);
49958
- extended.push(id);
49959
- }
49960
- }
49961
- if (extended.length === 0)
49962
- return;
49963
- emitProgress("running");
49964
- await delay(Math.min(GRACE_INTERVAL_MS, Math.max(1000, stallMs)));
49965
- }
49966
- })().catch(() => {});
49967
- await Promise.race([allDone, deadlineWatcher]);
49968
- if (!settled)
49969
- await Promise.race([allDone, delay(DRAIN_TIMEOUT_MS)]);
49970
- clearInterval(progressHandle);
49971
- emitProgress("settled");
49972
- process.off("SIGINT", sigintHandler);
49973
- return statusCache;
50038
+ }
50039
+ async function runModels(sessionPath, opts = {}) {
50040
+ const handle = await startModels(sessionPath, opts);
50041
+ return handle.done;
49974
50042
  }
49975
50043
  async function judgeResponses(sessionPath, opts = {}) {
49976
50044
  const responseFiles = readdirSync5(sessionPath).filter((f) => f.startsWith("response-") && f.endsWith(".md")).sort();
@@ -50152,16 +50220,17 @@ function formatVerdict(verdict, sessionPath) {
50152
50220
  }
50153
50221
  return output;
50154
50222
  }
50155
- 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) => {
50156
- const t = setTimeout(resolve4, ms);
50157
- t.unref?.();
50158
- }), 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;
50159
50224
  var init_team_orchestrator = __esm(() => {
50160
50225
  init_prehydrate();
50226
+ init_stream_json_reducer();
50227
+ init_config2();
50228
+ init_upstream_error_capture();
50161
50229
  init_process_tree();
50162
50230
  init_redact();
50231
+ init_stdio_decode();
50163
50232
  init_team_stats();
50164
- init_team_stream_capture();
50233
+ liveTeamRuns = new Map;
50165
50234
  API_ERROR_RE = /\[API Error:\s*([^\]]{0,300})\]/i;
50166
50235
  BG_CEILING_RE = /Background tasks still running after (\d+)s; terminating/i;
50167
50236
  BENIGN_STDERR_PATTERNS = [/^\s*\[claude-code:unrecognized_model\]/];
@@ -50183,7 +50252,7 @@ import {
50183
50252
  } from "fs";
50184
50253
  import { homedir as homedir28 } from "os";
50185
50254
  import { join as join30, resolve as resolve4, sep } from "path";
50186
- import { StringDecoder } from "string_decoder";
50255
+ import { StringDecoder as StringDecoder2 } from "string_decoder";
50187
50256
  function buildChannelSpawnArgs(opts) {
50188
50257
  return [
50189
50258
  "--model",
@@ -50214,9 +50283,6 @@ function assertNoReservedFlags(flags) {
50214
50283
  }
50215
50284
  }
50216
50285
  }
50217
- function decodeChunk(decoder, chunk) {
50218
- return typeof chunk === "string" ? chunk : decoder.write(chunk);
50219
- }
50220
50286
  function readTailText(path, maxBytes) {
50221
50287
  let fd = null;
50222
50288
  try {
@@ -50325,11 +50391,14 @@ class SessionManager {
50325
50391
  throw new Error(`Max sessions (${this.maxSessions}) reached`);
50326
50392
  }
50327
50393
  assertNoReservedFlags(opts.claudishFlags);
50328
- 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);
50329
50398
  const claudeSessionId = randomUUID4();
50330
50399
  const timeout = Math.min(opts.timeoutSeconds ?? DEFAULT_TIMEOUT, MAX_TIMEOUT);
50331
50400
  const startedAt = new Date().toISOString();
50332
- const sessionDir = join30(this.sessionsDir, sessionId2);
50401
+ const sessionDir = opts.sessionDir ?? join30(this.sessionsDir, sessionId2);
50333
50402
  mkdirSync12(sessionDir, { recursive: true });
50334
50403
  if (opts.prompt) {
50335
50404
  writeFileSync12(join30(sessionDir, "prompt.md"), opts.prompt, "utf-8");
@@ -50339,7 +50408,7 @@ class SessionManager {
50339
50408
  claudeSessionId,
50340
50409
  claudishFlags: opts.claudishFlags
50341
50410
  });
50342
- const tokenFile = join30(sessionDir, "tokens.json");
50411
+ const tokenFile = opts.tokenFile ?? join30(sessionDir, "tokens.json");
50343
50412
  const eventLogPath = join30(sessionDir, "events.jsonl");
50344
50413
  const upstreamErrorLogPath = join30(sessionDir, "upstream-errors.jsonl");
50345
50414
  const cwd = opts.cwd ?? process.cwd();
@@ -50365,6 +50434,7 @@ class SessionManager {
50365
50434
  status: "starting",
50366
50435
  pid: proc.pid ?? null,
50367
50436
  startedAt,
50437
+ idleSeconds: 0,
50368
50438
  completedAt: null,
50369
50439
  exitCode: null,
50370
50440
  turnsCompleted: 0,
@@ -50385,8 +50455,8 @@ class SessionManager {
50385
50455
  evictHandle: null,
50386
50456
  stderr: "",
50387
50457
  stderrTruncated: false,
50388
- stdoutDecoder: new StringDecoder("utf8"),
50389
- stderrDecoder: new StringDecoder("utf8"),
50458
+ stdoutDecoder: new StringDecoder2("utf8"),
50459
+ stderrDecoder: new StringDecoder2("utf8"),
50390
50460
  outputLogStream,
50391
50461
  sessionDir,
50392
50462
  eventLogPath,
@@ -50407,6 +50477,7 @@ class SessionManager {
50407
50477
  entry.reducer = new StreamJsonReducer({
50408
50478
  sessionId: sessionId2,
50409
50479
  stallSeconds: this.stallSeconds,
50480
+ keepUnrecognizedJson: opts.keepUnrecognizedJson,
50410
50481
  callback: (sid, data) => {
50411
50482
  const current = this.sessions.get(sid);
50412
50483
  if (!current)
@@ -50493,6 +50564,7 @@ class SessionManager {
50493
50564
  if (!entry)
50494
50565
  return this.diskOutput(this.requireDiskRecord(sessionId2), tailLines);
50495
50566
  entry.info.elapsedSeconds = this.getElapsed(entry.info.startedAt);
50567
+ entry.info.idleSeconds = entry.reducer ? Math.round(entry.reducer.idleMs / 1000) : null;
50496
50568
  this.refreshAccounting(entry);
50497
50569
  const lines = entry.scrollback.getLines(tailLines);
50498
50570
  return {
@@ -50503,7 +50575,8 @@ class SessionManager {
50503
50575
  totalLines: entry.scrollback.totalLines,
50504
50576
  turnsCompleted: entry.info.turnsCompleted,
50505
50577
  tokensUsed: entry.info.tokensUsed,
50506
- elapsedSeconds: entry.info.elapsedSeconds
50578
+ elapsedSeconds: entry.info.elapsedSeconds,
50579
+ idleSeconds: entry.info.idleSeconds
50507
50580
  };
50508
50581
  }
50509
50582
  getDiagnostics(sessionId2, eventLimit = DEFAULT_EVENT_LIMIT) {
@@ -50513,6 +50586,7 @@ class SessionManager {
50513
50586
  return this.diskDiagnostics(this.requireDiskRecord(sessionId2), limit);
50514
50587
  this.refreshAccounting(entry);
50515
50588
  entry.info.elapsedSeconds = this.getElapsed(entry.info.startedAt);
50589
+ entry.info.idleSeconds = entry.reducer ? Math.round(entry.reducer.idleMs / 1000) : null;
50516
50590
  return {
50517
50591
  sessionId: sessionId2,
50518
50592
  status: entry.info.status,
@@ -50521,6 +50595,7 @@ class SessionManager {
50521
50595
  exitCode: entry.info.exitCode,
50522
50596
  terminalReason: entry.info.terminalReason,
50523
50597
  elapsedSeconds: entry.info.elapsedSeconds,
50598
+ idleSeconds: entry.info.idleSeconds,
50524
50599
  timeoutSeconds: entry.timeoutSeconds,
50525
50600
  outputBytes: entry.proseBytes,
50526
50601
  turnsCompleted: entry.info.turnsCompleted,
@@ -50579,6 +50654,7 @@ class SessionManager {
50579
50654
  continue;
50580
50655
  if (!isTerminal2) {
50581
50656
  entry.info.elapsedSeconds = this.getElapsed(entry.info.startedAt);
50657
+ entry.info.idleSeconds = entry.reducer ? Math.round(entry.reducer.idleMs / 1000) : null;
50582
50658
  this.refreshAccounting(entry);
50583
50659
  }
50584
50660
  sessions2.push({ ...entry.info });
@@ -50590,6 +50666,7 @@ class SessionManager {
50590
50666
  if (!entry)
50591
50667
  return this.requireDiskRecord(sessionId2).info;
50592
50668
  entry.info.elapsedSeconds = this.getElapsed(entry.info.startedAt);
50669
+ entry.info.idleSeconds = entry.reducer ? Math.round(entry.reducer.idleMs / 1000) : null;
50593
50670
  this.refreshAccounting(entry);
50594
50671
  return { ...entry.info };
50595
50672
  }
@@ -50672,6 +50749,7 @@ class SessionManager {
50672
50749
  model: metaString(meta3?.model) ?? "unknown",
50673
50750
  spawnModel: metaString(meta3?.spawnModel),
50674
50751
  status: metaStatus(meta3?.status) ?? "failed",
50752
+ idleSeconds: null,
50675
50753
  pid: null,
50676
50754
  startedAt,
50677
50755
  completedAt,
@@ -50701,7 +50779,8 @@ class SessionManager {
50701
50779
  totalLines: buffer.totalLines,
50702
50780
  turnsCompleted: record4.info.turnsCompleted,
50703
50781
  tokensUsed: record4.info.tokensUsed,
50704
- elapsedSeconds: record4.info.elapsedSeconds
50782
+ elapsedSeconds: record4.info.elapsedSeconds,
50783
+ idleSeconds: null
50705
50784
  };
50706
50785
  }
50707
50786
  diskDiagnostics(record4, limit) {
@@ -50719,6 +50798,7 @@ class SessionManager {
50719
50798
  exitCode: info.exitCode,
50720
50799
  terminalReason: info.terminalReason,
50721
50800
  elapsedSeconds: info.elapsedSeconds,
50801
+ idleSeconds: null,
50722
50802
  timeoutSeconds: 0,
50723
50803
  outputBytes: diskProseBytes(outputTail, fileSize(outputLogPath)),
50724
50804
  turnsCompleted: info.turnsCompleted,
@@ -50878,6 +50958,7 @@ ${STDERR_TRUNCATION_MARKER} ${STDERR_SIDE_LIMIT} bytes per end \u2026
50878
50958
  entry.info.exitCode = code;
50879
50959
  entry.info.completedAt = at;
50880
50960
  entry.info.elapsedSeconds = this.getElapsed(entry.info.startedAt);
50961
+ entry.info.idleSeconds = entry.reducer ? Math.round(entry.reducer.idleMs / 1000) : null;
50881
50962
  entry.stdinClosed = true;
50882
50963
  this.flushDecoders(entry);
50883
50964
  const priorVerdict = TERMINAL_STATUSES.includes(entry.info.status) ? entry.info.status : null;
@@ -51042,13 +51123,14 @@ ${STDERR_TRUNCATION_MARKER} ${STDERR_SIDE_LIMIT} bytes per end \u2026
51042
51123
  }
51043
51124
  }
51044
51125
  }
51045
- 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] ";
51046
51127
  var init_session_manager = __esm(() => {
51047
51128
  init_config2();
51048
51129
  init_upstream_error_capture();
51049
51130
  init_process_tree();
51050
51131
  init_redact();
51051
51132
  init_session_discovery();
51133
+ init_stdio_decode();
51052
51134
  init_team_orchestrator();
51053
51135
  init_team_stats();
51054
51136
  init_scrollback_buffer();
@@ -56653,14 +56735,18 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56653
56735
  });
56654
56736
  tools.push({
56655
56737
  name: "team",
56656
- 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.",
56657
56739
  inputSchema: {
56658
56740
  type: "object",
56659
56741
  properties: {
56660
56742
  mode: {
56661
56743
  type: "string",
56662
- enum: ["run", "judge", "run-and-judge", "status"],
56663
- 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."
56664
56750
  },
56665
56751
  path: {
56666
56752
  type: "string",
@@ -56676,11 +56762,14 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56676
56762
  items: { type: "string" },
56677
56763
  description: "Model IDs to use as judges (default: same as runners)"
56678
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
+ },
56679
56769
  input: {
56680
56770
  type: "string",
56681
- 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."
56682
56772
  },
56683
- timeout: { type: "number", description: "Per-model timeout in seconds (default: 300)" },
56684
56773
  require_pattern: {
56685
56774
  type: "string",
56686
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."
@@ -56708,8 +56797,12 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56708
56797
  const path = args.path;
56709
56798
  const models = args.models;
56710
56799
  const judges = args.judges;
56711
- const input = args.input;
56712
- 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;
56713
56806
  const requirePattern = args.require_pattern;
56714
56807
  const minOutputBytes = args.min_output_bytes;
56715
56808
  const childFlags = buildChildClaudeFlags(args.agent, args.claude_flags);
@@ -56718,7 +56811,6 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56718
56811
  const teamSessionId = resolved.split("/").filter(Boolean).pop() ?? "team";
56719
56812
  const teamCreatedAt = new Date().toISOString();
56720
56813
  const runOpts = {
56721
- timeout,
56722
56814
  requirePattern,
56723
56815
  minOutputBytes,
56724
56816
  claudeFlags: childFlags,
@@ -56739,9 +56831,51 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56739
56831
  if (!models?.length)
56740
56832
  throw new Error("'models' is required for 'run' mode");
56741
56833
  setupSession(resolved, models, input);
56742
- const status = await runModels(resolved, runOpts);
56834
+ const handle = await startModels(resolved, runOpts);
56743
56835
  return {
56744
- 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
+ ]
56745
56879
  };
56746
56880
  }
56747
56881
  case "judge": {
@@ -56758,7 +56892,25 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
56758
56892
  }
56759
56893
  case "status": {
56760
56894
  const status = getStatus(resolved);
56761
- 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
+ };
56762
56914
  }
56763
56915
  default:
56764
56916
  throw new Error(`Unknown mode: ${mode}`);
@@ -57078,7 +57230,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
57078
57230
  });
57079
57231
  tools.push({
57080
57232
  name: "list_sessions",
57081
- 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.",
57082
57234
  inputSchema: {
57083
57235
  type: "object",
57084
57236
  properties: {
@@ -57098,7 +57250,7 @@ Report manually at https://github.com/anthropics/claudish/issues${autoSendHint}`
57098
57250
  });
57099
57251
  tools.push({
57100
57252
  name: "get_diagnostics",
57101
- 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.",
57102
57254
  inputSchema: {
57103
57255
  type: "object",
57104
57256
  properties: {
@@ -57255,6 +57407,7 @@ Call get_diagnostics with session_id: "${sessionId2}" for the stderr, the upstre
57255
57407
  await server.connect(transport);
57256
57408
  process.on("SIGTERM", () => {
57257
57409
  sessionManager.shutdownAll().catch(() => {});
57410
+ shutdownAllTeamRuns().catch(() => {});
57258
57411
  });
57259
57412
  }
57260
57413
  function startMcpServer() {
@@ -57322,7 +57475,8 @@ var init_mcp_server = __esm(() => {
57322
57475
  ALL_MODELS_CACHE_PATH2 = join34(CLAUDISH_CACHE_DIR, "all-models.json");
57323
57476
  NEXT_STEP = {
57324
57477
  nonzero_exit: "read the evidence log, then retry or drop the model",
57325
- 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",
57326
57480
  api_error: "retry once, or route via a different provider (or@<model>)",
57327
57481
  background_task_ceiling: "set CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0 for children, or forbid background work in the prompt",
57328
57482
  empty_output: "retry once; if it repeats, drop the model",
@@ -58205,7 +58359,8 @@ Options (run / run-and-judge):
58205
58359
  --path <dir> Session directory (default: .)
58206
58360
  --models <a,b,...> Comma-separated model IDs to run
58207
58361
  --input <text> Task prompt (or create input.md in --path beforehand)
58208
- --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.
58209
58364
  --grid Show all models in a magmux grid with live output + status bar
58210
58365
 
58211
58366
  Options (judge / run-and-judge):
@@ -58272,7 +58427,6 @@ async function teamCommand(args) {
58272
58427
  if (effectiveMode === "json") {
58273
58428
  setupSession(sessionPath, models, input);
58274
58429
  const runStatus = await runModels(sessionPath, {
58275
- timeout,
58276
58430
  onStatusChange: (id, s) => {
58277
58431
  process.stderr.write(`[team] ${id}: ${s.state}
58278
58432
  `);
@@ -58301,7 +58455,6 @@ async function teamCommand(args) {
58301
58455
  }
58302
58456
  setupSession(sessionPath, models, input);
58303
58457
  const status = await runModels(sessionPath, {
58304
- timeout,
58305
58458
  onStatusChange: (id, s) => {
58306
58459
  process.stderr.write(`[team] ${id}: ${s.state}
58307
58460
  `);
@@ -66184,9 +66337,9 @@ var require_internal = __commonJS(function(exports, module) {
66184
66337
  }
66185
66338
  InternalCodec.prototype.encoder = InternalEncoder;
66186
66339
  InternalCodec.prototype.decoder = InternalDecoder;
66187
- var StringDecoder2 = __require("string_decoder").StringDecoder;
66340
+ var StringDecoder3 = __require("string_decoder").StringDecoder;
66188
66341
  function InternalDecoder(options, codec2) {
66189
- this.decoder = new StringDecoder2(codec2.enc);
66342
+ this.decoder = new StringDecoder3(codec2.enc);
66190
66343
  }
66191
66344
  InternalDecoder.prototype.write = function(buf) {
66192
66345
  if (!Buffer2.isBuffer(buf)) {
@@ -86077,7 +86230,7 @@ var init_widgets = __esm(() => {
86077
86230
 
86078
86231
  // src/session/conversation.ts
86079
86232
  import { closeSync as closeSync9, openSync as openSync9, readSync as readSync4, statSync as statSync9 } from "fs";
86080
- import { StringDecoder as StringDecoder2 } from "string_decoder";
86233
+ import { StringDecoder as StringDecoder3 } from "string_decoder";
86081
86234
  function looksLikeTurn(line) {
86082
86235
  const assistant = line.includes('"type":"assistant"');
86083
86236
  if (!assistant && !line.includes('"type":"user"'))
@@ -86134,7 +86287,7 @@ function readConversation(file2, opts = {}) {
86134
86287
  const size = statSync9(file2).size;
86135
86288
  fd = openSync9(file2, "r");
86136
86289
  const buf = Buffer.allocUnsafe(chunkBytes);
86137
- const decoder = new StringDecoder2("utf-8");
86290
+ const decoder = new StringDecoder3("utf-8");
86138
86291
  let pending = "";
86139
86292
  let pos = 0;
86140
86293
  const consume = (line) => {
@@ -88429,7 +88582,6 @@ async function runCli() {
88429
88582
  const { setupSession: setupSession2, runModels: runModels2 } = await Promise.resolve().then(() => (init_team_orchestrator(), exports_team_orchestrator));
88430
88583
  setupSession2(sessionPath, cliConfig.team, prompt);
88431
88584
  const status2 = await runModels2(sessionPath, {
88432
- timeout: 300,
88433
88585
  claudeFlags: ["--json"]
88434
88586
  });
88435
88587
  const result = { ...status2, responses: {} };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "7.67.0",
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.0",
64
- "@claudish/magmux-darwin-x64": "7.67.0",
65
- "@claudish/magmux-linux-arm64": "7.67.0",
66
- "@claudish/magmux-linux-x64": "7.67.0"
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",