claude-overnight 1.11.0 → 1.11.2

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/run.js CHANGED
@@ -30,7 +30,7 @@ export async function executeRun(cfg) {
30
30
  const waveHistory = [];
31
31
  let accCost, accCompleted, accFailed, accTools;
32
32
  let accIn = 0, accOut = 0;
33
- let lastCapped = false, lastAborted = false, objectiveComplete = false;
33
+ let lastCapped = false, lastAborted = false, objectiveComplete = false, lastHealed = false;
34
34
  const branches = [];
35
35
  if (cfg.resuming && cfg.resumeState) {
36
36
  const rs = cfg.resumeState;
@@ -161,7 +161,6 @@ export async function executeRun(cfg) {
161
161
  }
162
162
  const waveMerge = (flex && runBranch) ? "yolo" : mergeStrategy;
163
163
  let stopping = false;
164
- let steeringFailed = false;
165
164
  const gracefulStop = () => {
166
165
  if (stopping) {
167
166
  currentSwarm?.cleanup();
@@ -236,9 +235,17 @@ export async function executeRun(cfg) {
236
235
  display.appendSteeringEvent(`Steering failed (attempt ${steerAttempts}/3) — retrying...`);
237
236
  continue;
238
237
  }
239
- display.stop();
240
- console.log(chalk.yellow(` Steering failed after ${steerAttempts} attempts: ${err.message?.slice(0, 80)} — stopping\n`));
241
- steeringFailed = true;
238
+ display.appendSteeringEvent(`Steering failed ${steerAttempts}× — falling back`);
239
+ let fallbackStatus = "";
240
+ try {
241
+ fallbackStatus = readFileSync(join(runDir, "status.md"), "utf-8");
242
+ }
243
+ catch { }
244
+ currentTasks = [{
245
+ id: "fallback-0",
246
+ prompt: `Steering couldn't decide the next step. Read the project, assess what's done vs. remaining, and do the most impactful work.\n\nObjective: ${objective}${fallbackStatus ? `\n\nStatus:\n${fallbackStatus}` : ""}`,
247
+ }];
248
+ steered = true;
242
249
  break;
243
250
  }
244
251
  }
@@ -256,6 +263,16 @@ export async function executeRun(cfg) {
256
263
  display.start();
257
264
  // ── Main wave loop ──
258
265
  while (remaining > 0 && currentTasks.length > 0 && !stopping) {
266
+ if (!lastHealed) {
267
+ const healTask = checkProjectHealth(cwd);
268
+ if (healTask && remaining > 0) {
269
+ lastHealed = true;
270
+ currentTasks = [healTask];
271
+ }
272
+ }
273
+ else {
274
+ lastHealed = false;
275
+ }
259
276
  if (currentTasks.length > remaining)
260
277
  currentTasks = currentTasks.slice(0, remaining);
261
278
  syncRunInfo();
@@ -329,7 +346,7 @@ export async function executeRun(cfg) {
329
346
  // ── Finalize ──
330
347
  const trulyDone = objectiveComplete || (!flex && remaining <= 0);
331
348
  const wasCapped = lastCapped || lastAborted;
332
- const finalPhase = trulyDone ? "done" : steeringFailed ? "steering" : wasCapped ? "capped" : remaining <= 0 ? "capped" : "stopped";
349
+ const finalPhase = trulyDone ? "done" : wasCapped ? "capped" : remaining <= 0 ? "capped" : "stopped";
333
350
  saveRunState(runDir, {
334
351
  id: `run-${new Date().toISOString().slice(0, 19)}`, objective: objective ?? "", budget: cfg.budget,
335
352
  remaining, workerModel, plannerModel, concurrency, permissionMode,
@@ -370,8 +387,6 @@ export async function executeRun(cfg) {
370
387
  console.log(chalk.green(` ${bannerChar.repeat(Math.min(termW - 4, 60))}`));
371
388
  if (trulyDone)
372
389
  console.log(chalk.bold.green(` CLAUDE OVERNIGHT — COMPLETE`));
373
- else if (steeringFailed)
374
- console.log(chalk.bold.yellow(` CLAUDE OVERNIGHT — STEERING FAILED`));
375
390
  else if (remaining <= 0)
376
391
  console.log(chalk.bold.yellow(` CLAUDE OVERNIGHT — BUDGET EXHAUSTED`));
377
392
  else if (lastCapped)
@@ -426,3 +441,40 @@ export async function executeRun(cfg) {
426
441
  if (lastAborted || accCompleted === 0)
427
442
  process.exit(2);
428
443
  }
444
+ function checkProjectHealth(cwd) {
445
+ let pkg;
446
+ try {
447
+ pkg = JSON.parse(readFileSync(join(cwd, "package.json"), "utf-8"));
448
+ }
449
+ catch {
450
+ return undefined;
451
+ }
452
+ const scripts = pkg.scripts || {};
453
+ let scriptName;
454
+ for (const name of ["typecheck", "check:types", "type-check", "build"]) {
455
+ if (scripts[name]) {
456
+ scriptName = name;
457
+ break;
458
+ }
459
+ }
460
+ if (!scriptName)
461
+ return undefined;
462
+ const pm = existsSync(join(cwd, "pnpm-lock.yaml")) ? "pnpm"
463
+ : existsSync(join(cwd, "bun.lockb")) || existsSync(join(cwd, "bun.lock")) ? "bun"
464
+ : existsSync(join(cwd, "yarn.lock")) ? "yarn" : "npm";
465
+ const cmd = `${pm} run ${scriptName}`;
466
+ try {
467
+ execSync(cmd, { cwd, encoding: "utf-8", stdio: "pipe", timeout: 60_000 });
468
+ return undefined;
469
+ }
470
+ catch (err) {
471
+ if (err.killed)
472
+ return undefined;
473
+ const output = ((err.stdout || "") + "\n" + (err.stderr || "")).trim();
474
+ const trimmed = output.length > 4000 ? output.slice(0, 2000) + "\n…\n" + output.slice(-2000) : output;
475
+ return {
476
+ id: "heal-0",
477
+ prompt: `Fix the broken build. \`${cmd}\` fails after merging parallel work:\n\`\`\`\n${trimmed}\n\`\`\`\nFix every error. Run \`${cmd}\` when done to verify.`,
478
+ };
479
+ }
480
+ }
package/dist/steering.js CHANGED
@@ -26,11 +26,13 @@ export async function steerWave(objective, history, remainingBudget, cwd, planne
26
26
  const recentWaves = history.slice(-3);
27
27
  const recentText = recentWaves.length > 0 ? recentWaves.map(w => {
28
28
  const lines = w.tasks.map(t => {
29
- const files = t.filesChanged ? ` (${t.filesChanged} files)` : "";
29
+ const files = t.filesChanged ? ` (${t.filesChanged} files)` : " (0 files)";
30
30
  const err = t.error ? ` — ${t.error}` : "";
31
31
  return ` - [${t.status}] ${t.prompt.slice(0, 120)}${files}${err}`;
32
32
  }).join("\n");
33
- return `Wave ${w.wave + 1}:\n${lines}`;
33
+ const zeroChange = w.tasks.filter(t => t.status === "done" && !t.filesChanged).length;
34
+ const warn = zeroChange > w.tasks.length / 2 ? `\n ⚠ ${zeroChange}/${w.tasks.length} agents changed 0 files — tasks may be mis-scoped or blocked` : "";
35
+ return `Wave ${w.wave + 1}:\n${lines}${warn}`;
34
36
  }).join("\n\n") : "(first wave)";
35
37
  const cap = (s, max) => s.length > max ? s.slice(0, max) + "\n...(truncated)" : s;
36
38
  const statusBlock = runMemory?.status ? `\nCurrent project status:\n${runMemory.status}\n` : "";
package/dist/ui.d.ts CHANGED
@@ -87,6 +87,8 @@ export declare class RunDisplay {
87
87
  resume(): void;
88
88
  stop(): void;
89
89
  private resumeInterval;
90
+ /** Write the full frame to stdout, clamped to terminal height. */
91
+ private flush;
90
92
  private render;
91
93
  private renderInputPrompt;
92
94
  private renderAskPanel;
package/dist/ui.js CHANGED
@@ -112,15 +112,20 @@ export class RunDisplay {
112
112
  catch {
113
113
  return;
114
114
  }
115
- this.interval = setInterval(() => {
116
- try {
117
- process.stdout.write("\x1B[H\x1B[J");
118
- process.stdout.write(this.render());
119
- }
120
- catch {
121
- this.pause();
122
- }
123
- }, 250);
115
+ this.interval = setInterval(() => this.flush(), 250);
116
+ }
117
+ /** Write the full frame to stdout, clamped to terminal height. */
118
+ flush() {
119
+ try {
120
+ const maxRows = (process.stdout.rows || 40) - 1;
121
+ const frame = this.render();
122
+ const lines = frame.split("\n");
123
+ process.stdout.write("\x1B[H\x1B[J");
124
+ process.stdout.write(lines.length > maxRows ? lines.slice(0, maxRows).join("\n") : frame);
125
+ }
126
+ catch {
127
+ this.pause();
128
+ }
124
129
  }
125
130
  render() {
126
131
  let frame = "";
@@ -223,6 +228,7 @@ export class RunDisplay {
223
228
  else if (/^[0-9.]$/.test(s)) {
224
229
  this.inputBuf += s;
225
230
  }
231
+ this.flush();
226
232
  return;
227
233
  }
228
234
  if (this.inputMode === "steer" || this.inputMode === "ask") {
@@ -238,11 +244,20 @@ export class RunDisplay {
238
244
  else
239
245
  this.onSteer?.(text);
240
246
  }
247
+ this.flush();
241
248
  return;
242
249
  }
243
- if (ch === "\x1B" || ch === "\x03") {
250
+ if (ch === "\x03") {
244
251
  this.inputMode = "none";
245
252
  this.inputBuf = "";
253
+ this.flush();
254
+ return;
255
+ }
256
+ // Ignore raw ESC only — let ANSI sequences (arrows etc.) fall through
257
+ if (ch === "\x1B" && s.length === 1) {
258
+ this.inputMode = "none";
259
+ this.inputBuf = "";
260
+ this.flush();
246
261
  return;
247
262
  }
248
263
  if (ch === "\x7F" || ch === "\b") {
@@ -254,6 +269,12 @@ export class RunDisplay {
254
269
  this.inputBuf += ch;
255
270
  }
256
271
  }
272
+ this.flush();
273
+ return;
274
+ }
275
+ // Dismiss completed ask panel on Escape
276
+ if (s === "\x1B" && this.askState && !this.askState.streaming) {
277
+ this.askState = undefined;
257
278
  return;
258
279
  }
259
280
  if (s === "b" || s === "B") {
@@ -274,6 +295,11 @@ export class RunDisplay {
274
295
  this.inputBuf = "";
275
296
  }
276
297
  else if (s === "?" && this.onAsk && this.swarm && !this.askBusy) {
298
+ // If ask panel is showing a completed answer, dismiss it instead of opening new
299
+ if (this.askState && !this.askState.streaming) {
300
+ this.askState = undefined;
301
+ return;
302
+ }
277
303
  this.inputMode = "ask";
278
304
  this.inputBuf = "";
279
305
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-overnight",
3
- "version": "1.11.0",
3
+ "version": "1.11.2",
4
4
  "description": "Run 10, 100, or 1000 Claude agents overnight. Parallel autonomous AI coding with thinking waves, iterative quality steering, crash recovery, and rate limit handling. Built on the Claude Agent SDK.",
5
5
  "type": "module",
6
6
  "bin": {