claude-overnight 1.50.6 → 1.51.1

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.
@@ -1 +1 @@
1
- export declare const VERSION = "1.50.6";
1
+ export declare const VERSION = "1.51.1";
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by build — do not edit manually.
2
- export const VERSION = "1.50.6";
2
+ export const VERSION = "1.51.1";
package/dist/run/run.js CHANGED
@@ -545,10 +545,18 @@ export async function executeRun(cfg) {
545
545
  display.stop();
546
546
  // ── Finalize ──
547
547
  const trulyDone = objectiveComplete || (!flex && remaining <= 0);
548
- // User-initiated quit (or abort via 'q' / SIGINT / stall-watchdog) ⇒ save as
549
- // "stopped" so resume.ts offers the run and the incomplete work comes back.
550
548
  const userQuit = stopping || lastAborted;
551
549
  const wasCapped = lastCapped && !userQuit;
550
+ // Determine specific exit reason for the end brief
551
+ let exitReason;
552
+ if (trulyDone)
553
+ exitReason = "done";
554
+ else if (userQuit)
555
+ exitReason = "user-interrupted";
556
+ else if (wasCapped || remaining <= 0)
557
+ exitReason = "budget-exhausted";
558
+ else
559
+ exitReason = "planner-gave-up"; // steering returned false, planner couldn't produce tasks
552
560
  const finalPhase = trulyDone ? "done"
553
561
  : userQuit ? "stopped"
554
562
  : wasCapped ? "capped"
@@ -632,7 +640,7 @@ export async function executeRun(cfg) {
632
640
  runDir, runBranch, objective, waveNum, runStartedAt: cfg.runStartedAt,
633
641
  branches, waveHistory,
634
642
  accCost, accCompleted, accFailed, accTools, accIn, accOut,
635
- remaining, lastCapped, lastAborted, stopping, trulyDone,
643
+ remaining, lastCapped, lastAborted, stopping, trulyDone, exitReason,
636
644
  peakWorkerCtxTokens, peakWorkerCtxPct,
637
645
  currentSwarmLogFile: currentSwarm?.logFile,
638
646
  narrativeDeps: {
@@ -11,6 +11,7 @@ export interface FinalNarrativeDeps {
11
11
  /** Generate a longer narrative summary at run end. Awaited (not fire-and-forget)
12
12
  * because the caller wants the text inline in the final status block. */
13
13
  export declare function generateFinalNarrative(deps: FinalNarrativeDeps, phase: string): Promise<string>;
14
+ export type ExitReason = "done" | "budget-exhausted" | "user-interrupted" | "planner-gave-up" | "circuit-breaker" | "stalled";
14
15
  export interface SummaryArgs {
15
16
  runDir: string;
16
17
  runBranch?: string;
@@ -30,6 +31,7 @@ export interface SummaryArgs {
30
31
  lastAborted: boolean;
31
32
  stopping: boolean;
32
33
  trulyDone: boolean;
34
+ exitReason: ExitReason;
33
35
  peakWorkerCtxTokens: number;
34
36
  peakWorkerCtxPct: number;
35
37
  currentSwarmLogFile?: string;
@@ -32,7 +32,7 @@ export async function generateFinalNarrative(deps, phase) {
32
32
  }
33
33
  }
34
34
  export async function printFinalSummary(args) {
35
- const { runDir, runBranch, objective, waveNum, runStartedAt, branches, waveHistory, accCost, accCompleted, accFailed, accTools, accIn, accOut, remaining, lastCapped, lastAborted, stopping, trulyDone, peakWorkerCtxTokens, peakWorkerCtxPct, currentSwarmLogFile, narrativeDeps, } = args;
35
+ const { runDir, runBranch, objective, waveNum, runStartedAt, branches, waveHistory, accCost, accCompleted, accFailed, accTools, accIn, accOut, remaining, lastCapped, exitReason, peakWorkerCtxTokens, peakWorkerCtxPct, currentSwarmLogFile, narrativeDeps, } = args;
36
36
  const waves = waveNum + 1;
37
37
  const elapsed = Math.round((Date.now() - runStartedAt) / 1000);
38
38
  const elapsedStr = elapsed < 60 ? `${elapsed}s` : elapsed < 3600 ? `${Math.floor(elapsed / 60)}m ${elapsed % 60}s` : `${Math.floor(elapsed / 3600)}h ${Math.floor((elapsed % 3600) / 60)}m`;
@@ -40,26 +40,31 @@ export async function printFinalSummary(args) {
40
40
  const totalConflicts = branches.filter(b => b.status === "merge-failed").length;
41
41
  const termW = Math.max((process.stdout.columns ?? 80) || 80, 50);
42
42
  const rule = (c = "─") => chalk.dim(` ${c.repeat(Math.min(termW - 4, 60))}`);
43
- const phaseWord = trulyDone ? "complete"
44
- : remaining <= 0 || lastCapped ? "budget exhausted"
45
- : stopping || lastAborted ? "interrupted"
46
- : "stopped";
43
+ const bannerChar = accFailed === 0 ? "" : "─";
44
+ // Banner: title + subtitle explaining why the run ended
45
+ const banner = {
46
+ done: { icon: "", title: "CLAUDE OVERNIGHT -- COMPLETE", color: chalk.green, explain: "The planner determined the objective was achieved." },
47
+ "budget-exhausted": { icon: "⚠", title: "CLAUDE OVERNIGHT -- BUDGET EXHAUSTED", color: chalk.yellow, explain: "All allocated sessions were consumed." },
48
+ "user-interrupted": { icon: "⚠", title: "CLAUDE OVERNIGHT -- INTERRUPTED", color: chalk.yellow, explain: "You quit mid-run with [q] or a signal." },
49
+ "planner-gave-up": { icon: "⚠", title: "CLAUDE OVERNIGHT -- PLANNER GAVE UP", color: chalk.magenta, explain: "The planner could not decompose the remaining work into actionable tasks." },
50
+ "circuit-breaker": { icon: "⚠", title: "CLAUDE OVERNIGHT -- HALTED", color: chalk.red, explain: "2+ consecutive waves produced no merged changes." },
51
+ stalled: { icon: "⚠", title: "CLAUDE OVERNIGHT -- STALLED", color: chalk.magenta, explain: "No progress detected; the run was halted to preserve budget." },
52
+ }[exitReason] ?? { icon: "⚠", title: "CLAUDE OVERNIGHT -- STOPPED", color: chalk.magenta, explain: "The run ended without a clear reason." };
53
+ const narrativePhase = exitReason === "done" ? "complete"
54
+ : exitReason === "budget-exhausted" ? "budget exhausted"
55
+ : exitReason === "user-interrupted" ? "interrupted"
56
+ : exitReason === "planner-gave-up" ? "planner gave up"
57
+ : exitReason === "circuit-breaker" ? "circuit breaker"
58
+ : exitReason === "stalled" ? "stalled"
59
+ : "stopped";
47
60
  process.stdout.write(chalk.dim(`\n Writing final summary…`));
48
- const narrative = await generateFinalNarrative(narrativeDeps, phaseWord);
61
+ const narrative = await generateFinalNarrative(narrativeDeps, narrativePhase);
49
62
  process.stdout.write("\r" + " ".repeat(40) + "\r");
50
63
  console.log("");
51
- const bannerChar = accFailed === 0 ? "━" : "─";
52
- const bannerColor = trulyDone ? chalk.green : (stopping || lastAborted) ? chalk.yellow : chalk.magenta;
53
- console.log(bannerColor(` ${bannerChar.repeat(Math.min(termW - 4, 60))}`));
54
- if (trulyDone)
55
- console.log(chalk.bold.green(` ✓ CLAUDE OVERNIGHT -- COMPLETE`));
56
- else if (remaining <= 0 || lastCapped)
57
- console.log(chalk.bold.yellow(` ⚠ CLAUDE OVERNIGHT -- BUDGET EXHAUSTED`));
58
- else if (stopping || lastAborted)
59
- console.log(chalk.bold.yellow(` ⚠ CLAUDE OVERNIGHT -- INTERRUPTED`));
60
- else
61
- console.log(chalk.bold.yellow(` ⚠ CLAUDE OVERNIGHT -- STOPPED`));
62
- console.log(bannerColor(` ${bannerChar.repeat(Math.min(termW - 4, 60))}`));
64
+ console.log(banner.color(` ${bannerChar.repeat(Math.min(termW - 4, 60))}`));
65
+ console.log(chalk.bold(banner.color(` ${banner.icon} ${banner.title}`)));
66
+ console.log(chalk.dim(` ${banner.explain}`));
67
+ console.log(banner.color(` ${bannerChar.repeat(Math.min(termW - 4, 60))}`));
63
68
  console.log("");
64
69
  if (objective) {
65
70
  console.log(chalk.bold(" Objective"));
@@ -162,15 +167,34 @@ export async function printFinalSummary(args) {
162
167
  if (currentSwarmLogFile)
163
168
  console.log(chalk.dim(` Log: ${currentSwarmLogFile}`));
164
169
  console.log("");
165
- console.log(bannerColor(` ${bannerChar.repeat(Math.min(termW - 4, 60))}`));
166
- if (trulyDone)
167
- console.log(chalk.bold.green(` Done. Review the diff, then ship it.`));
168
- else if (remaining <= 0 || lastCapped)
169
- console.log(chalk.bold.yellow(` Paused on budget. Re-run with --resume to continue.`));
170
- else if (stopping || lastAborted)
171
- console.log(chalk.bold.yellow(` Interrupted. --resume to pick up where this left off.`));
172
- else
173
- console.log(chalk.bold.yellow(` Stopped. --resume to continue.`));
174
- console.log(bannerColor(` ${bannerChar.repeat(Math.min(termW - 4, 60))}`));
170
+ console.log(banner.color(` ${bannerChar.repeat(Math.min(termW - 4, 60))}`));
171
+ // Actionable next-steps based on exit reason
172
+ const endMsg = (() => {
173
+ switch (exitReason) {
174
+ case "done":
175
+ return "Review the diff, then ship it.";
176
+ case "budget-exhausted":
177
+ return remaining > 0
178
+ ? "Budget sessions remaining but usage cap hit. Raise the cap or re-run with --resume."
179
+ : "All sessions spent. Re-run with --resume to continue, or raise the budget.";
180
+ case "user-interrupted":
181
+ return "Run preserved. Use --resume to pick up where this left off.";
182
+ case "planner-gave-up": {
183
+ const lines = ["Planner could not decompose remaining work."];
184
+ if (remaining > 0)
185
+ lines.push(`${remaining} sessions unused — the work may be too vague or out of scope.`);
186
+ lines.push("Refine the objective or break it down manually, then re-run.");
187
+ return lines.join(" ");
188
+ }
189
+ case "circuit-breaker":
190
+ return "No changes landed in 2+ waves. Check for merge conflicts or agent errors in the log.";
191
+ case "stalled":
192
+ return "Run halted to preserve budget. Inspect status.md for blockers, then --resume.";
193
+ default:
194
+ return "Run preserved. --resume to continue.";
195
+ }
196
+ })();
197
+ console.log(chalk.bold(banner.color(` ${endMsg}`)));
198
+ console.log(banner.color(` ${bannerChar.repeat(Math.min(termW - 4, 60))}`));
175
199
  console.log("");
176
200
  }
@@ -362,12 +362,13 @@ export async function runWaveLoop(host, ctx) {
362
362
  const librarianStart = Date.now();
363
363
  let librarianPromoted = 0, librarianPatched = 0, librarianQuarantined = 0, librarianRejected = 0;
364
364
  try {
365
+ const librarianModel = host.fastModel ?? host.workerModel;
365
366
  const lr = await runLibrarian({
366
367
  fingerprint: host.repoFingerprint,
367
368
  runId: host.runId,
368
369
  wave: host.waveNum,
369
370
  cwd: ctx.cwd,
370
- model: host.plannerModel,
371
+ model: librarianModel,
371
372
  envForModel: ctx.envForModel,
372
373
  });
373
374
  librarianPromoted = lr.promoted;
@@ -1,4 +1,3 @@
1
- import { query } from "@anthropic-ai/claude-agent-sdk";
2
1
  import { readFileSync, writeFileSync, mkdirSync, renameSync, existsSync, readdirSync, appendFileSync, } from "node:fs";
3
2
  import { join } from "node:path";
4
3
  import { openSkillsDb } from "./index-db.js";
@@ -84,44 +83,44 @@ function buildSubagentInput(canon, candidates, abOutcomes) {
84
83
  return JSON.stringify({ canon, candidates, ab_outcomes: abOutcomes });
85
84
  }
86
85
  // ── Subagent call ──
86
+ // Direct POST /v1/messages — no tools needed, so the Agent SDK's CLI subprocess
87
+ // (with its multi-KB built-in system prompt and turn loop) is pure overhead and
88
+ // also pre-flight-rejects non-Anthropic model ids (qwen, composer-2, ...) even
89
+ // when routed through an Anthropic-compatible proxy.
87
90
  async function callLibrarianSubagent(input, data) {
88
91
  const env = input.envForModel?.(input.model);
89
92
  const prompt = renderPrompt("40_skills/40-3_librarian-wrap", { vars: { data } });
90
- let timedOut = false;
91
- const timer = setTimeout(() => { timedOut = true; }, LIBRARIAN_TIMEOUT_MS);
93
+ const baseUrl = (env?.ANTHROPIC_BASE_URL ?? process.env.ANTHROPIC_BASE_URL ?? "https://api.anthropic.com").replace(/\/$/, "");
94
+ const headers = {
95
+ "Content-Type": "application/json",
96
+ "anthropic-version": "2023-06-01",
97
+ };
98
+ const bearer = env?.ANTHROPIC_AUTH_TOKEN ?? process.env.ANTHROPIC_AUTH_TOKEN;
99
+ const apiKey = env?.ANTHROPIC_API_KEY ?? process.env.ANTHROPIC_API_KEY;
100
+ if (bearer)
101
+ headers["Authorization"] = `Bearer ${bearer}`;
102
+ else if (apiKey)
103
+ headers["x-api-key"] = apiKey;
92
104
  try {
93
- const pq = query({
94
- prompt,
95
- options: {
96
- cwd: input.cwd,
97
- model: input.model,
98
- permissionMode: "bypassPermissions",
99
- allowDangerouslySkipPermissions: true,
100
- maxTurns: 8,
101
- ...(env && { env }),
102
- },
105
+ const res = await fetch(`${baseUrl}/v1/messages`, {
106
+ method: "POST",
107
+ headers,
108
+ body: JSON.stringify({ model: input.model, max_tokens: 8192, messages: [{ role: "user", content: prompt }] }),
109
+ signal: AbortSignal.timeout(LIBRARIAN_TIMEOUT_MS),
103
110
  });
104
- let resultText = "";
105
- for await (const msg of pq) {
106
- if (timedOut) {
107
- pq.interrupt().catch(() => { });
108
- break;
109
- }
110
- if (msg.type === "result" && msg.subtype === "success") {
111
- resultText = msg.result || "";
112
- }
113
- }
114
- pq.close();
115
- if (timedOut) {
116
- process.stderr.write("[librarian] subagent timed out\n");
111
+ if (!res.ok) {
112
+ process.stderr.write(`[librarian] HTTP ${res.status}: ${(await res.text().catch(() => "")).slice(0, 200)}\n`);
117
113
  return null;
118
114
  }
119
- // Parse JSON try direct parse first, then strip markdown fences
115
+ const body = await res.json();
116
+ const resultText = body.content?.map(c => c.text ?? "").join("") ?? "";
120
117
  const cleaned = resultText.replace(/^```(?:json)?\s*\n([\s\S]*?)\n```\s*$/, "$1").trim();
121
118
  return JSON.parse(cleaned);
122
119
  }
123
- finally {
124
- clearTimeout(timer);
120
+ catch (err) {
121
+ const msg = err instanceof Error ? err.message : String(err);
122
+ process.stderr.write(`[librarian] ${msg.includes("aborted") ? "timed out" : `error: ${msg}`}\n`);
123
+ return null;
125
124
  }
126
125
  }
127
126
  // ── Action application ──
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-overnight",
3
- "version": "1.50.6",
3
+ "version": "1.51.1",
4
4
  "description": "Parallel Claude agents in git worktrees with a usage cap that reserves headroom for your interactive Claude Code. Crash-safe resume. Provider-agnostic model catalog (Anthropic, Cursor, OpenAI, Gemini, DeepSeek, Llama, Qwen) with capability-based task scoping.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-overnight",
3
- "version": "1.50.6",
3
+ "version": "1.51.1",
4
4
  "description": "Claude Code skill for understanding, installing, and inspecting claude-overnight runs -- parallel Claude agents in git worktrees with thinking waves, multi-wave steering, and crash-safe resume. Supports Cursor API Proxy, Qwen, OpenRouter.",
5
5
  "author": {
6
6
  "name": "Francesco Fornace"