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 +60 -8
- package/dist/steering.js +4 -2
- package/dist/ui.d.ts +2 -0
- package/dist/ui.js +36 -10
- package/package.json +1 -1
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.
|
|
240
|
-
|
|
241
|
-
|
|
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" :
|
|
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
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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 === "\
|
|
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.
|
|
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": {
|