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.
- package/dist/index.js +311 -159
- 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 = "
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
49852
|
+
stdoutTail = (stdoutTail + decodeChunk(stdoutDecoder, chunk)).slice(-STDOUT_TAIL_LIMIT);
|
|
49730
49853
|
});
|
|
49731
49854
|
proc.stdout?.pipe(outputStream);
|
|
49732
49855
|
} else {
|
|
49733
|
-
const
|
|
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(
|
|
49742
|
-
flushPartial = () => absorb(
|
|
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(
|
|
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
|
|
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
|
|
49803
|
-
const
|
|
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
|
|
49879
|
-
|
|
49880
|
-
|
|
49881
|
-
|
|
49882
|
-
|
|
49883
|
-
|
|
49884
|
-
|
|
49885
|
-
|
|
49886
|
-
|
|
49887
|
-
|
|
49888
|
-
|
|
49889
|
-
|
|
49890
|
-
|
|
49891
|
-
|
|
49892
|
-
|
|
49893
|
-
|
|
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
|
-
|
|
49928
|
-
|
|
49929
|
-
|
|
49930
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
50389
|
-
stderrDecoder: new
|
|
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' (
|
|
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
|
|
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
|
|
56712
|
-
const
|
|
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
|
|
56834
|
+
const handle = await startModels(resolved, runOpts);
|
|
56743
56835
|
return {
|
|
56744
|
-
content: [
|
|
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
|
-
|
|
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
|
-
|
|
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>
|
|
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
|
|
66340
|
+
var StringDecoder3 = __require("string_decoder").StringDecoder;
|
|
66188
66341
|
function InternalDecoder(options, codec2) {
|
|
66189
|
-
this.decoder = new
|
|
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
|
|
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
|
|
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": "
|
|
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": "
|
|
64
|
-
"@claudish/magmux-darwin-x64": "
|
|
65
|
-
"@claudish/magmux-linux-arm64": "
|
|
66
|
-
"@claudish/magmux-linux-x64": "
|
|
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",
|