claudish 7.49.0 → 7.50.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 +186 -15
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -729,7 +729,7 @@ var init_onepassword_config = __esm(() => {
729
729
  });
730
730
 
731
731
  // src/version.ts
732
- var VERSION = "7.49.0";
732
+ var VERSION = "7.50.0";
733
733
 
734
734
  // src/logger.ts
735
735
  var exports_logger = {};
@@ -50087,12 +50087,132 @@ function writeStatusFile(sessionPath, manifest, status, opts) {
50087
50087
  var CHANNEL_LINE_BUDGET = 58;
50088
50088
  var init_team_stats = () => {};
50089
50089
 
50090
+ // src/team-stream-capture.ts
50091
+ function extractAssistantText(event) {
50092
+ if (event.type !== "assistant")
50093
+ return [];
50094
+ const message = event.message;
50095
+ const content = message?.content;
50096
+ if (typeof content === "string") {
50097
+ return content.length > 0 ? [content] : [];
50098
+ }
50099
+ if (!Array.isArray(content))
50100
+ return [];
50101
+ const out = [];
50102
+ for (const raw2 of content) {
50103
+ const block = raw2;
50104
+ if (block?.type !== "text")
50105
+ continue;
50106
+ if (typeof block.text !== "string" || block.text.length === 0)
50107
+ continue;
50108
+ out.push(block.text);
50109
+ }
50110
+ return out;
50111
+ }
50112
+ function createAssistantTextCapture() {
50113
+ let pending = "";
50114
+ let emittedAny = false;
50115
+ let endsWithNewline = false;
50116
+ let dedupeTail = "";
50117
+ let lastWasMessage = false;
50118
+ const record4 = (text, kind) => {
50119
+ emittedAny = true;
50120
+ lastWasMessage = kind === "message";
50121
+ endsWithNewline = text.endsWith(`
50122
+ `);
50123
+ dedupeTail = (dedupeTail + text).slice(-DEDUPE_TAIL_LIMIT);
50124
+ };
50125
+ const messageSeparator = () => {
50126
+ if (!emittedAny)
50127
+ return "";
50128
+ return endsWithNewline ? `
50129
+ ` : `
50130
+
50131
+ `;
50132
+ };
50133
+ const rawSeparator = () => {
50134
+ if (!emittedAny || endsWithNewline)
50135
+ return "";
50136
+ return `
50137
+ `;
50138
+ };
50139
+ const consumeLine = (line, terminated) => {
50140
+ if (line.trim().length === 0)
50141
+ return "";
50142
+ const passthrough = () => {
50143
+ const out = `${rawSeparator()}${line}${terminated ? `
50144
+ ` : ""}`;
50145
+ record4(out, "raw");
50146
+ return out;
50147
+ };
50148
+ let event;
50149
+ try {
50150
+ event = JSON.parse(line);
50151
+ } catch {
50152
+ return passthrough();
50153
+ }
50154
+ if (typeof event.type !== "string" || !STREAM_JSON_EVENT_TYPES.has(event.type)) {
50155
+ return passthrough();
50156
+ }
50157
+ const texts = extractAssistantText(event);
50158
+ if (texts.length > 0) {
50159
+ let out = "";
50160
+ for (const text of texts) {
50161
+ const piece = `${messageSeparator()}${text}`;
50162
+ out += piece;
50163
+ record4(piece, "message");
50164
+ }
50165
+ return out;
50166
+ }
50167
+ if (event.type === "result" && event.is_error === true) {
50168
+ const result = typeof event.result === "string" ? event.result : "";
50169
+ if (result.trim().length > 0 && !dedupeTail.includes(result)) {
50170
+ const out = `${messageSeparator()}${result}`;
50171
+ record4(out, "message");
50172
+ return out;
50173
+ }
50174
+ }
50175
+ return "";
50176
+ };
50177
+ return {
50178
+ write(chunk) {
50179
+ pending += chunk;
50180
+ let out = "";
50181
+ let newlineAt = pending.indexOf(`
50182
+ `);
50183
+ while (newlineAt !== -1) {
50184
+ const line = pending.slice(0, newlineAt);
50185
+ pending = pending.slice(newlineAt + 1);
50186
+ out += consumeLine(line, true);
50187
+ newlineAt = pending.indexOf(`
50188
+ `);
50189
+ }
50190
+ return out;
50191
+ },
50192
+ end() {
50193
+ let out = pending.length > 0 ? consumeLine(pending, false) : "";
50194
+ pending = "";
50195
+ if (emittedAny && !endsWithNewline && lastWasMessage) {
50196
+ out += `
50197
+ `;
50198
+ endsWithNewline = true;
50199
+ }
50200
+ return out;
50201
+ }
50202
+ };
50203
+ }
50204
+ var DEDUPE_TAIL_LIMIT = 4096, STREAM_JSON_EVENT_TYPES;
50205
+ var init_team_stream_capture = __esm(() => {
50206
+ STREAM_JSON_EVENT_TYPES = new Set(["system", "assistant", "user", "result"]);
50207
+ });
50208
+
50090
50209
  // src/team-orchestrator.ts
50091
50210
  var exports_team_orchestrator = {};
50092
50211
  __export(exports_team_orchestrator, {
50093
50212
  validateSessionPath: () => validateSessionPath,
50094
50213
  setupSession: () => setupSession,
50095
50214
  runModels: () => runModels,
50215
+ resolveCaptureMode: () => resolveCaptureMode,
50096
50216
  parseJudgeVotes: () => parseJudgeVotes,
50097
50217
  judgeResponses: () => judgeResponses,
50098
50218
  getStatus: () => getStatus,
@@ -50100,6 +50220,7 @@ __export(exports_team_orchestrator, {
50100
50220
  classifyRunOutput: () => classifyRunOutput,
50101
50221
  buildJudgePrompt: () => buildJudgePrompt,
50102
50222
  aggregateVerdict: () => aggregateVerdict,
50223
+ TEAM_CAPTURE_ENV_VAR: () => TEAM_CAPTURE_ENV_VAR,
50103
50224
  STDOUT_TAIL_LIMIT: () => STDOUT_TAIL_LIMIT,
50104
50225
  DEFAULT_MIN_OUTPUT_BYTES: () => DEFAULT_MIN_OUTPUT_BYTES
50105
50226
  });
@@ -50113,8 +50234,21 @@ import {
50113
50234
  writeFileSync as writeFileSync12
50114
50235
  } from "fs";
50115
50236
  import { join as join28, resolve as resolve3 } from "path";
50237
+ function resolveCaptureMode(explicit, env = process.env) {
50238
+ if (explicit)
50239
+ return explicit;
50240
+ return env[TEAM_CAPTURE_ENV_VAR]?.trim().toLowerCase() === "print" ? "print" : "stream-json";
50241
+ }
50116
50242
  function classifyRunOutput(opts) {
50117
- const { outputSize, stdoutTail, stderr, minOutputBytes, requirePattern, fullOutput } = opts;
50243
+ const {
50244
+ outputSize,
50245
+ stdoutTail,
50246
+ stderr,
50247
+ minOutputBytes,
50248
+ requirePattern,
50249
+ fullOutput,
50250
+ captureMode = "print"
50251
+ } = opts;
50118
50252
  const apiError = API_ERROR_RE.exec(stdoutTail);
50119
50253
  if (apiError) {
50120
50254
  return {
@@ -50151,9 +50285,10 @@ function classifyRunOutput(opts) {
50151
50285
  re = null;
50152
50286
  }
50153
50287
  if (re && !re.test(haystack)) {
50288
+ const cause = captureMode === "stream-json" ? "Every assistant message this child produced was captured and concatenated, " + "so this is not the print-mode dropout: the model genuinely never emitted the " + "required shape. Re-prompt it, or relax the contract." : "This is the signature of a child that answered and then took one more turn: " + "`claude -p` prints only the FINAL assistant message, so a background task " + "completing (or any late notification) replaces the real answer with an " + "epilogue about it. The answer was generated, it just was not the last thing " + "said \u2014 re-run with the default stream-json capture to keep it.";
50154
50289
  return {
50155
50290
  reason: "shape_mismatch",
50156
- detail: `Child exited 0 with ${outputSize} B, but the response does not match the ` + `required pattern /${requirePattern}/. This is the signature of a child that ` + "answered and then took one more turn: `claude -p` prints only the FINAL " + "assistant message, so a background task completing (or any late notification) " + "replaces the real answer with an epilogue about it. Check the child's " + "transcript \u2014 the answer was generated, it just was not the last thing said."
50291
+ detail: `Child exited 0 with ${outputSize} B, but the response does not match the ` + `required pattern /${requirePattern}/. ${cause}`
50157
50292
  };
50158
50293
  }
50159
50294
  }
@@ -50269,6 +50404,7 @@ async function runModels(sessionPath, opts = {}) {
50269
50404
  }
50270
50405
  const minOutputBytes = opts.minOutputBytes ?? DEFAULT_MIN_OUTPUT_BYTES;
50271
50406
  const requirePattern = opts.requirePattern;
50407
+ const captureMode = resolveCaptureMode(opts.captureMode);
50272
50408
  mkdirSync12(statsDir(sessionPath), { recursive: true });
50273
50409
  const processes = new Map;
50274
50410
  const runtimes = new Map;
@@ -50285,7 +50421,14 @@ async function runModels(sessionPath, opts = {}) {
50285
50421
  const outputPath = join28(sessionPath, `response-${anonId}.md`);
50286
50422
  const errorLogPath = join28(sessionPath, "errors", `${anonId}.log`);
50287
50423
  const spawnModel = spawnPlan.pinned.get(entry.model) ?? entry.model;
50288
- const args = ["--model", spawnModel, "-y", "--stdin", "--quiet", ...opts.claudeFlags ?? []];
50424
+ const args = [
50425
+ "--model",
50426
+ spawnModel,
50427
+ "-y",
50428
+ "--stdin",
50429
+ ...captureMode === "stream-json" ? ["--verbose", "--quiet", "--output-format", "stream-json"] : ["--quiet"],
50430
+ ...opts.claudeFlags ?? []
50431
+ ];
50289
50432
  updateModelStatus(anonId, {
50290
50433
  state: "RUNNING",
50291
50434
  startedAt: new Date().toISOString()
@@ -50301,12 +50444,36 @@ async function runModels(sessionPath, opts = {}) {
50301
50444
  });
50302
50445
  let byteCount = 0;
50303
50446
  let stdoutTail = "";
50304
- proc.stdout?.on("data", (chunk) => {
50305
- byteCount += chunk.length;
50306
- stdoutTail = (stdoutTail + chunk.toString()).slice(-STDOUT_TAIL_LIMIT);
50307
- });
50308
50447
  const outputStream = createWriteStream2(outputPath);
50309
- proc.stdout?.pipe(outputStream);
50448
+ let flushPartial = () => {};
50449
+ if (captureMode === "print") {
50450
+ proc.stdout?.on("data", (chunk) => {
50451
+ byteCount += chunk.length;
50452
+ stdoutTail = (stdoutTail + chunk.toString()).slice(-STDOUT_TAIL_LIMIT);
50453
+ });
50454
+ proc.stdout?.pipe(outputStream);
50455
+ } else {
50456
+ const capture = createAssistantTextCapture();
50457
+ const absorb = (text) => {
50458
+ if (text.length === 0)
50459
+ return;
50460
+ byteCount += Buffer.byteLength(text);
50461
+ stdoutTail = (stdoutTail + text).slice(-STDOUT_TAIL_LIMIT);
50462
+ outputStream.write(text);
50463
+ };
50464
+ proc.stdout?.on("data", (chunk) => absorb(capture.write(chunk.toString())));
50465
+ flushPartial = () => absorb(capture.end());
50466
+ let captureFinalized = false;
50467
+ const finalizeCapture = () => {
50468
+ if (captureFinalized)
50469
+ return;
50470
+ captureFinalized = true;
50471
+ absorb(capture.end());
50472
+ outputStream.end();
50473
+ };
50474
+ proc.stdout?.on("end", finalizeCapture);
50475
+ proc.stdout?.on("close", finalizeCapture);
50476
+ }
50310
50477
  let stderr = "";
50311
50478
  proc.stderr?.on("data", (chunk) => {
50312
50479
  stderr += chunk.toString();
@@ -50317,7 +50484,8 @@ async function runModels(sessionPath, opts = {}) {
50317
50484
  errorLogPath,
50318
50485
  getStderr: () => stderr,
50319
50486
  getStdoutTail: () => stdoutTail,
50320
- getByteCount: () => byteCount
50487
+ getByteCount: () => byteCount,
50488
+ flushPartial: () => flushPartial()
50321
50489
  });
50322
50490
  proc.stdin?.write(inputContent);
50323
50491
  proc.stdin?.end();
@@ -50347,7 +50515,8 @@ async function runModels(sessionPath, opts = {}) {
50347
50515
  stderr,
50348
50516
  minOutputBytes,
50349
50517
  requirePattern,
50350
- fullOutput
50518
+ fullOutput,
50519
+ captureMode
50351
50520
  });
50352
50521
  const failed = crashed || degraded !== null;
50353
50522
  const state = crashed ? "FAILED" : degraded ? "EMPTY" : "COMPLETED";
@@ -50444,10 +50613,11 @@ async function runModels(sessionPath, opts = {}) {
50444
50613
  if (!proc.killed)
50445
50614
  proc.kill("SIGTERM");
50446
50615
  const rt = runtimes.get(id);
50616
+ rt?.flushPartial();
50447
50617
  const stderr = rt?.getStderr() ?? "";
50448
50618
  const stdoutTail = rt?.getStdoutTail() ?? "";
50449
50619
  const bytes2 = rt?.getByteCount() ?? 0;
50450
- const detail = `Killed by the orchestrator after ${timeoutMs / 1000}s with ${bytes2} B of stdout. ` + "In --quiet print mode the child emits its answer only at the end, so 0 B means " + `"did not finish", not "produced nothing".`;
50620
+ const detail = `Killed by the orchestrator after ${timeoutMs / 1000}s with ${bytes2} B of stdout. ` + "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".`;
50451
50621
  if (rt)
50452
50622
  persistErrorLog(rt.errorLogPath, `TIMEOUT: ${detail}`, stderr, stdoutTail);
50453
50623
  updateModelStatus(id, {
@@ -50659,11 +50829,12 @@ function formatVerdict(verdict, sessionPath) {
50659
50829
  }
50660
50830
  return output;
50661
50831
  }
50662
- var STDOUT_TAIL_LIMIT = 4000, API_ERROR_RE, BG_CEILING_RE, DEFAULT_MIN_OUTPUT_BYTES = 0, SENTINEL_MODELS;
50832
+ var TEAM_CAPTURE_ENV_VAR = "CLAUDISH_TEAM_CAPTURE", STDOUT_TAIL_LIMIT = 4000, API_ERROR_RE, BG_CEILING_RE, DEFAULT_MIN_OUTPUT_BYTES = 0, SENTINEL_MODELS;
50663
50833
  var init_team_orchestrator = __esm(() => {
50664
50834
  init_prehydrate();
50665
50835
  init_redact();
50666
50836
  init_team_stats();
50837
+ init_team_stream_capture();
50667
50838
  API_ERROR_RE = /\[API Error:\s*([^\]]{0,300})\]/i;
50668
50839
  BG_CEILING_RE = /Background tasks still running after (\d+)s; terminating/i;
50669
50840
  SENTINEL_MODELS = new Set([
@@ -51189,7 +51360,7 @@ Use with: run_prompt(model="${results[0].model.id}", prompt="your prompt")`;
51189
51360
  timeout: { type: "number", description: "Per-model timeout in seconds (default: 300)" },
51190
51361
  require_pattern: {
51191
51362
  type: "string",
51192
- 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: a child that answers and then takes one " + "more turn (a background Task finishing, a late notification) has its real " + "answer replaced by a short epilogue, because print mode emits only the FINAL " + "assistant message. That lands as a few hundred bytes of plausible prose with " + "exit 0 and no error, and is otherwise indistinguishable from success."
51363
+ 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."
51193
51364
  },
51194
51365
  min_output_bytes: {
51195
51366
  type: "number",
@@ -51771,7 +51942,7 @@ var init_mcp_server = __esm(() => {
51771
51942
  api_error: "retry once, or route via a different provider (or@<model>)",
51772
51943
  background_task_ceiling: "set CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS=0 for children, or forbid background work in the prompt",
51773
51944
  empty_output: "retry once; if it repeats, drop the model",
51774
- shape_mismatch: "the answer was generated but not captured \u2014 print mode keeps only the FINAL " + "assistant message, so a late background task or notification overwrote it. " + "Retry, and forbid background work in the prompt; do NOT count this slot as a vote"
51945
+ shape_mismatch: "the response does not carry the shape you required. Every assistant message the " + "child emitted was captured, so nothing was lost in transit \u2014 the model did not " + "produce it. Re-prompt with the required format restated; do NOT count this slot " + "as a vote"
51775
51946
  };
51776
51947
  sanitize = sanitizeForReport;
51777
51948
  EVENT_TO_TASK_STATUS = new Map([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "7.49.0",
3
+ "version": "7.50.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.49.0",
64
- "@claudish/magmux-darwin-x64": "7.49.0",
65
- "@claudish/magmux-linux-arm64": "7.49.0",
66
- "@claudish/magmux-linux-x64": "7.49.0"
63
+ "@claudish/magmux-darwin-arm64": "7.50.0",
64
+ "@claudish/magmux-darwin-x64": "7.50.0",
65
+ "@claudish/magmux-linux-arm64": "7.50.0",
66
+ "@claudish/magmux-linux-x64": "7.50.0"
67
67
  },
68
68
  "author": "Jack Rudenko <i@madappgang.com>",
69
69
  "license": "MIT",