claude-overnight 1.10.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/cli.js +2 -2
- package/dist/index.js +113 -9
- package/dist/render.js +2 -1
- package/dist/run.js +60 -8
- package/dist/steering.js +4 -2
- package/dist/swarm.d.ts +2 -0
- package/dist/swarm.js +44 -11
- package/dist/ui.d.ts +2 -0
- package/dist/ui.js +39 -10
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6,8 +6,8 @@ import chalk from "chalk";
|
|
|
6
6
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
7
7
|
// ── CLI flag parsing ──
|
|
8
8
|
export function parseCliFlags(argv) {
|
|
9
|
-
const known = new Set(["concurrency", "model", "timeout", "budget", "usage-cap", "extra-usage-budget"]);
|
|
10
|
-
const booleans = new Set(["--dry-run", "-h", "--help", "-v", "--version", "--no-flex", "--allow-extra-usage"]);
|
|
9
|
+
const known = new Set(["concurrency", "model", "timeout", "budget", "usage-cap", "extra-usage-budget", "merge", "perm"]);
|
|
10
|
+
const booleans = new Set(["--dry-run", "-h", "--help", "-v", "--version", "--no-flex", "--allow-extra-usage", "--worktrees", "--no-worktrees", "--yolo"]);
|
|
11
11
|
const flags = {};
|
|
12
12
|
const positional = [];
|
|
13
13
|
for (let i = 0; i < argv.length; i++) {
|
package/dist/index.js
CHANGED
|
@@ -42,6 +42,11 @@ async function main() {
|
|
|
42
42
|
--extra-usage-budget=N Max $ for extra usage ${chalk.dim("(implies --allow-extra-usage)")}
|
|
43
43
|
--timeout=SECONDS Agent inactivity timeout ${chalk.dim("(default: 900s, nudges at timeout, kills at 2×)")}
|
|
44
44
|
--no-flex Disable adaptive multi-wave planning ${chalk.dim("(run all tasks in one shot)")}
|
|
45
|
+
--worktrees Force worktree isolation on ${chalk.dim("(default: auto-detect git repo)")}
|
|
46
|
+
--no-worktrees Disable worktree isolation ${chalk.dim("(all agents work in real cwd)")}
|
|
47
|
+
--merge=MODE Merge strategy: yolo or branch ${chalk.dim("(default: yolo)")}
|
|
48
|
+
--perm=MODE Permission mode: auto, bypassPermissions, default ${chalk.dim("(default: auto)")}
|
|
49
|
+
--yolo Shorthand for --perm=bypassPermissions --no-worktrees
|
|
45
50
|
|
|
46
51
|
${chalk.cyan("Defaults")} ${chalk.dim("(non-interactive)")}
|
|
47
52
|
model: first available concurrency: 5 worktrees: auto merge: yolo perms: auto
|
|
@@ -251,6 +256,9 @@ async function main() {
|
|
|
251
256
|
let usageCap;
|
|
252
257
|
let allowExtraUsage = false;
|
|
253
258
|
let extraUsageBudget;
|
|
259
|
+
let permissionMode = "auto";
|
|
260
|
+
let useWorktrees = false;
|
|
261
|
+
let mergeStrategy = "yolo";
|
|
254
262
|
if (resuming) {
|
|
255
263
|
workerModel = resumeState.workerModel;
|
|
256
264
|
plannerModel = resumeState.plannerModel;
|
|
@@ -260,6 +268,9 @@ async function main() {
|
|
|
260
268
|
usageCap = resumeState.usageCap;
|
|
261
269
|
allowExtraUsage = resumeState.allowExtraUsage ?? false;
|
|
262
270
|
extraUsageBudget = resumeState.extraUsageBudget;
|
|
271
|
+
permissionMode = resumeState.permissionMode;
|
|
272
|
+
useWorktrees = resumeState.useWorktrees;
|
|
273
|
+
mergeStrategy = resumeState.mergeStrategy;
|
|
263
274
|
}
|
|
264
275
|
else if (!nonInteractive) {
|
|
265
276
|
while (true) {
|
|
@@ -279,6 +290,17 @@ async function main() {
|
|
|
279
290
|
console.error(chalk.red(` Budget must be a positive number`));
|
|
280
291
|
process.exit(1);
|
|
281
292
|
}
|
|
293
|
+
// ③ Max concurrency (skip if --concurrency set)
|
|
294
|
+
if (cliFlags.concurrency) {
|
|
295
|
+
concurrency = parseInt(cliFlags.concurrency);
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
const defaultC = Math.min(5, budget);
|
|
299
|
+
const concAns = await ask(`\n ${chalk.cyan("③")} ${chalk.dim("Max concurrency")} ${chalk.dim("[")}${chalk.white(String(defaultC))}${chalk.dim("]:")} `);
|
|
300
|
+
concurrency = parseInt(concAns) || defaultC;
|
|
301
|
+
if (concurrency < 1)
|
|
302
|
+
concurrency = 1;
|
|
303
|
+
}
|
|
282
304
|
let modelFrame = 0;
|
|
283
305
|
const modelSpinner = setInterval(() => {
|
|
284
306
|
process.stdout.write(`\x1B[2K\r ${chalk.cyan(BRAILLE[modelFrame++ % BRAILLE.length])} ${chalk.dim("loading models...")}`);
|
|
@@ -293,19 +315,19 @@ async function main() {
|
|
|
293
315
|
}
|
|
294
316
|
plannerModel = models[0]?.value || "claude-sonnet-4-6";
|
|
295
317
|
if (models.length > 0) {
|
|
296
|
-
workerModel = await select(`${chalk.cyan("
|
|
318
|
+
workerModel = await select(`${chalk.cyan("④")} Worker model:`, models.map(m => ({ name: m.displayName, value: m.value, hint: m.description })));
|
|
297
319
|
}
|
|
298
320
|
else {
|
|
299
|
-
const ans = await ask(` ${chalk.cyan("
|
|
321
|
+
const ans = await ask(` ${chalk.cyan("④")} ${chalk.dim("Worker model [claude-sonnet-4-6]:")} `);
|
|
300
322
|
workerModel = ans || "claude-sonnet-4-6";
|
|
301
323
|
}
|
|
302
|
-
usageCap = await select(`${chalk.cyan("
|
|
324
|
+
usageCap = await select(`${chalk.cyan("⑤")} Usage cap:`, [
|
|
303
325
|
{ name: "Unlimited", value: undefined, hint: "full capacity, wait through rate limits" },
|
|
304
326
|
{ name: "90%", value: 0.9, hint: "leave 10% for other work" },
|
|
305
327
|
{ name: "75%", value: 0.75, hint: "conservative, plenty of headroom" },
|
|
306
328
|
{ name: "50%", value: 0.5, hint: "use half, keep the rest" },
|
|
307
329
|
]);
|
|
308
|
-
const extraChoice = await select(`${chalk.cyan("
|
|
330
|
+
const extraChoice = await select(`${chalk.cyan("⑥")} Allow extra usage ${chalk.dim("(billed separately)")}:`, [
|
|
309
331
|
{ name: "No", value: "no", hint: "stop when plan limits are reached" },
|
|
310
332
|
{ name: "Yes, with $ limit", value: "budget", hint: "set a spending cap" },
|
|
311
333
|
{ name: "Yes, unlimited", value: "unlimited", hint: "keep going no matter what" },
|
|
@@ -319,7 +341,44 @@ async function main() {
|
|
|
319
341
|
}
|
|
320
342
|
else if (extraChoice === "unlimited")
|
|
321
343
|
allowExtraUsage = true;
|
|
322
|
-
|
|
344
|
+
// ⑦ Permission mode (skip if --yolo or --perm set)
|
|
345
|
+
const cliYolo = argv.includes("--yolo");
|
|
346
|
+
if (cliFlags.perm) {
|
|
347
|
+
permissionMode = cliFlags.perm;
|
|
348
|
+
}
|
|
349
|
+
else if (cliYolo) {
|
|
350
|
+
permissionMode = "bypassPermissions";
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
permissionMode = await select(`${chalk.cyan("⑦")} Permissions:`, [
|
|
354
|
+
{ name: "Auto", value: "auto", hint: "accept low-risk, reject high-risk" },
|
|
355
|
+
{ name: "Bypass all", value: "bypassPermissions", hint: "agents can run anything (yolo)" },
|
|
356
|
+
{ name: "Prompt each", value: "default", hint: "ask for every dangerous op" },
|
|
357
|
+
]);
|
|
358
|
+
}
|
|
359
|
+
// ⑧ Worktrees + merge (skip if --yolo, --worktrees, --no-worktrees, or --merge set)
|
|
360
|
+
const gitRepo = isGitRepo(cwd);
|
|
361
|
+
if (cliYolo || argv.includes("--no-worktrees")) {
|
|
362
|
+
useWorktrees = false;
|
|
363
|
+
mergeStrategy = cliFlags.merge || "yolo";
|
|
364
|
+
}
|
|
365
|
+
else if (argv.includes("--worktrees")) {
|
|
366
|
+
useWorktrees = true;
|
|
367
|
+
mergeStrategy = cliFlags.merge || "yolo";
|
|
368
|
+
}
|
|
369
|
+
else if (gitRepo) {
|
|
370
|
+
const wtChoice = await select(`${chalk.cyan("⑧")} Git isolation:`, [
|
|
371
|
+
{ name: "Worktrees + yolo merge", value: "wt-yolo", hint: "isolate agents, merge into current branch" },
|
|
372
|
+
{ name: "Worktrees + new branch", value: "wt-branch", hint: "isolate agents, merge into a new branch" },
|
|
373
|
+
{ name: "No worktrees", value: "no-wt", hint: "all agents share the working directory" },
|
|
374
|
+
]);
|
|
375
|
+
useWorktrees = wtChoice !== "no-wt";
|
|
376
|
+
mergeStrategy = wtChoice === "wt-branch" ? "branch" : "yolo";
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
useWorktrees = false;
|
|
380
|
+
mergeStrategy = "yolo";
|
|
381
|
+
}
|
|
323
382
|
const parts = [];
|
|
324
383
|
if (workerModel !== plannerModel)
|
|
325
384
|
parts.push(`${detectModelTier(workerModel)} → ${detectModelTier(plannerModel)}`);
|
|
@@ -331,6 +390,12 @@ async function main() {
|
|
|
331
390
|
if (usageCap != null)
|
|
332
391
|
parts.push(`cap ${Math.round(usageCap * 100)}%`);
|
|
333
392
|
parts.push(allowExtraUsage ? (extraUsageBudget ? `extra $${extraUsageBudget}` : "extra ∞") : "no extra");
|
|
393
|
+
if (permissionMode !== "auto")
|
|
394
|
+
parts.push(permissionMode === "bypassPermissions" ? "yolo" : "prompt");
|
|
395
|
+
if (useWorktrees)
|
|
396
|
+
parts.push(mergeStrategy === "branch" ? "wt→branch" : "wt→yolo");
|
|
397
|
+
else
|
|
398
|
+
parts.push("no wt");
|
|
334
399
|
if (completedRuns.length > 0)
|
|
335
400
|
parts.push(`${completedRuns.length} prior`);
|
|
336
401
|
const inner = parts.join(chalk.dim(" · "));
|
|
@@ -375,11 +440,28 @@ async function main() {
|
|
|
375
440
|
}
|
|
376
441
|
}
|
|
377
442
|
validateConcurrency(concurrency);
|
|
378
|
-
|
|
379
|
-
|
|
443
|
+
// Resolve permissionMode, useWorktrees, mergeStrategy for non-interactive + non-resume
|
|
444
|
+
if (!resuming && nonInteractive) {
|
|
445
|
+
const yolo = argv.includes("--yolo");
|
|
446
|
+
permissionMode = cliFlags.perm ? cliFlags.perm
|
|
447
|
+
: yolo ? "bypassPermissions"
|
|
448
|
+
: (fileCfg?.permissionMode ?? "auto");
|
|
449
|
+
if (!["auto", "bypassPermissions", "default"].includes(permissionMode)) {
|
|
450
|
+
console.error(chalk.red(` --perm must be auto, bypassPermissions, or default (got ${permissionMode})`));
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
useWorktrees = argv.includes("--no-worktrees") || yolo ? false
|
|
454
|
+
: argv.includes("--worktrees") ? true
|
|
455
|
+
: (fileCfg?.useWorktrees ?? isGitRepo(cwd));
|
|
456
|
+
mergeStrategy = cliFlags.merge ? cliFlags.merge
|
|
457
|
+
: (fileCfg?.mergeStrategy ?? "yolo");
|
|
458
|
+
if (!["yolo", "branch"].includes(mergeStrategy)) {
|
|
459
|
+
console.error(chalk.red(` --merge must be yolo or branch (got ${mergeStrategy})`));
|
|
460
|
+
process.exit(1);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
380
463
|
if (useWorktrees)
|
|
381
464
|
validateGitRepo(cwd);
|
|
382
|
-
const mergeStrategy = resuming ? resumeState.mergeStrategy : (fileCfg?.mergeStrategy ?? "yolo");
|
|
383
465
|
if (nonInteractive) {
|
|
384
466
|
const capStr = usageCap != null ? ` cap=${Math.round(usageCap * 100)}%` : "";
|
|
385
467
|
const extraStr = allowExtraUsage ? (extraUsageBudget ? ` extra=$${extraUsageBudget}` : " extra=∞") : " extra=off";
|
|
@@ -416,7 +498,7 @@ async function main() {
|
|
|
416
498
|
for (let i = 0; i < themes.length; i++)
|
|
417
499
|
console.log(chalk.dim(` ${String(i + 1).padStart(3)}.`) + ` ${themes[i]}`);
|
|
418
500
|
console.log(chalk.dim(`\n ${thinkingCount} thinking agents → orchestrate → ${(budget ?? 10) - thinkingCount} execution sessions\n`));
|
|
419
|
-
const action = await selectKey(`${chalk.white(`${themes.length} themes`)} ${chalk.dim(`· ${thinkingCount} thinking · ${concurrency} concurrent`)}`, [{ key: "r", desc: "un" }, { key: "e", desc: "dit" }, { key: "q", desc: "uit" }]);
|
|
501
|
+
const action = await selectKey(`${chalk.white(`${themes.length} themes`)} ${chalk.dim(`· ${thinkingCount} thinking · ${concurrency} concurrent`)}`, [{ key: "r", desc: "un" }, { key: "e", desc: "dit" }, { key: "c", desc: "hat" }, { key: "q", desc: "uit" }]);
|
|
420
502
|
if (action === "r") {
|
|
421
503
|
reviewing = false;
|
|
422
504
|
break;
|
|
@@ -435,6 +517,28 @@ async function main() {
|
|
|
435
517
|
}
|
|
436
518
|
planRestore();
|
|
437
519
|
}
|
|
520
|
+
else if (action === "c") {
|
|
521
|
+
const question = await ask(`\n ${chalk.bold("Ask about the themes:")}\n ${chalk.cyan(">")} `);
|
|
522
|
+
if (!question)
|
|
523
|
+
continue;
|
|
524
|
+
process.stdout.write("\x1B[?25l");
|
|
525
|
+
try {
|
|
526
|
+
let answer = "";
|
|
527
|
+
for await (const msg of query({
|
|
528
|
+
prompt: `You're planning work for: "${objective}"\n\nThemes identified:\n${themes.map((t, i) => `${i + 1}. ${t}`).join("\n")}\n\nUser question: ${question}`,
|
|
529
|
+
options: { cwd, model: plannerModel, permissionMode, persistSession: false },
|
|
530
|
+
})) {
|
|
531
|
+
if (msg.type === "result" && msg.subtype === "success")
|
|
532
|
+
answer = msg.result || "";
|
|
533
|
+
}
|
|
534
|
+
planRestore();
|
|
535
|
+
if (answer)
|
|
536
|
+
console.log(chalk.dim(`\n ${answer.slice(0, 500)}\n`));
|
|
537
|
+
}
|
|
538
|
+
catch {
|
|
539
|
+
planRestore();
|
|
540
|
+
}
|
|
541
|
+
}
|
|
438
542
|
else {
|
|
439
543
|
console.log(chalk.dim("\n Aborted.\n"));
|
|
440
544
|
process.exit(0);
|
package/dist/render.js
CHANGED
|
@@ -183,7 +183,8 @@ export function renderFrame(swarm, showHotkeys, runInfo) {
|
|
|
183
183
|
if (showHotkeys) {
|
|
184
184
|
const pending = runInfo?.pendingSteer ?? 0;
|
|
185
185
|
const chip = pending > 0 ? chalk.cyan(` \u270E ${pending} steer queued`) : "";
|
|
186
|
-
|
|
186
|
+
const fixChip = swarm.failed > 0 && swarm.active > 0 ? chalk.yellow(" [f] fix") : "";
|
|
187
|
+
out.push(chalk.dim(" [b] budget [t] threshold [s] steer [?] ask [q] stop") + fixChip + chip);
|
|
187
188
|
}
|
|
188
189
|
out.push("");
|
|
189
190
|
return out.join("\n");
|
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/swarm.d.ts
CHANGED
|
@@ -59,6 +59,8 @@ export declare class Swarm {
|
|
|
59
59
|
get pending(): number;
|
|
60
60
|
run(): Promise<void>;
|
|
61
61
|
abort(): void;
|
|
62
|
+
/** Re-queue all errored agents' tasks for retry within this wave. */
|
|
63
|
+
requeueFailed(): number;
|
|
62
64
|
logSequence: number;
|
|
63
65
|
log(agentId: number, text: string): void;
|
|
64
66
|
cleanup(): void;
|
package/dist/swarm.js
CHANGED
|
@@ -110,6 +110,21 @@ export class Swarm {
|
|
|
110
110
|
this.activeQueries.forEach(q => q.close());
|
|
111
111
|
this.activeQueries.clear();
|
|
112
112
|
}
|
|
113
|
+
/** Re-queue all errored agents' tasks for retry within this wave. */
|
|
114
|
+
requeueFailed() {
|
|
115
|
+
const errored = this.agents.filter(a => a.status === "error");
|
|
116
|
+
if (errored.length === 0)
|
|
117
|
+
return 0;
|
|
118
|
+
for (const a of errored) {
|
|
119
|
+
this.queue.push(a.task);
|
|
120
|
+
a.status = "pending";
|
|
121
|
+
a.error = undefined;
|
|
122
|
+
a.finishedAt = undefined;
|
|
123
|
+
}
|
|
124
|
+
this.failed -= errored.length;
|
|
125
|
+
this.log(-1, `Re-queued ${errored.length} failed task(s)`);
|
|
126
|
+
return errored.length;
|
|
127
|
+
}
|
|
113
128
|
logSequence = 0;
|
|
114
129
|
log(agentId, text) {
|
|
115
130
|
const entry = { time: Date.now(), agentId, text };
|
|
@@ -201,21 +216,39 @@ export class Swarm {
|
|
|
201
216
|
this.agents.push(agent);
|
|
202
217
|
let agentCwd = task.cwd || this.config.cwd;
|
|
203
218
|
if (this.config.useWorktrees && this.worktreeBase && !task.noWorktree) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
219
|
+
const branch = `swarm/task-${id}`;
|
|
220
|
+
const dir = join(this.worktreeBase, `agent-${id}`);
|
|
221
|
+
let worktreeOk = false;
|
|
222
|
+
for (let wt = 0; wt < 2 && !worktreeOk; wt++) {
|
|
223
|
+
try {
|
|
224
|
+
gitExec(`git worktree add -b "${branch}" "${dir}" HEAD`, this.config.cwd);
|
|
225
|
+
worktreeOk = true;
|
|
226
|
+
}
|
|
227
|
+
catch (e) {
|
|
228
|
+
if (wt === 0) {
|
|
229
|
+
this.log(id, `Worktree failed, cleaning up: ${e.message?.slice(0, 50)}`);
|
|
230
|
+
try {
|
|
231
|
+
gitExec(`git branch -D "${branch}"`, this.config.cwd);
|
|
232
|
+
}
|
|
233
|
+
catch { }
|
|
234
|
+
try {
|
|
235
|
+
rmSync(dir, { recursive: true, force: true });
|
|
236
|
+
}
|
|
237
|
+
catch { }
|
|
238
|
+
try {
|
|
239
|
+
gitExec("git worktree prune", this.config.cwd);
|
|
240
|
+
}
|
|
241
|
+
catch { }
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (worktreeOk) {
|
|
208
246
|
agentCwd = dir;
|
|
209
247
|
agent.branch = branch;
|
|
210
248
|
this.log(id, `Worktree: ${branch}`);
|
|
211
249
|
}
|
|
212
|
-
|
|
213
|
-
this.log(id, `Worktree failed
|
|
214
|
-
agent.status = "error";
|
|
215
|
-
agent.error = "worktree creation failed";
|
|
216
|
-
agent.finishedAt = Date.now();
|
|
217
|
-
this.failed++;
|
|
218
|
-
return;
|
|
250
|
+
else {
|
|
251
|
+
this.log(id, `Worktree failed after retry — running without isolation`);
|
|
219
252
|
}
|
|
220
253
|
}
|
|
221
254
|
this.log(id, `Starting: ${task.prompt.slice(0, 60)}`);
|
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") {
|
|
@@ -266,11 +287,19 @@ export class RunDisplay {
|
|
|
266
287
|
this.inputBuf = "";
|
|
267
288
|
}
|
|
268
289
|
}
|
|
290
|
+
else if ((s === "f" || s === "F") && this.swarm && this.swarm.failed > 0 && this.swarm.active > 0) {
|
|
291
|
+
this.swarm.requeueFailed();
|
|
292
|
+
}
|
|
269
293
|
else if ((s === "s" || s === "S") && this.onSteer) {
|
|
270
294
|
this.inputMode = "steer";
|
|
271
295
|
this.inputBuf = "";
|
|
272
296
|
}
|
|
273
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
|
+
}
|
|
274
303
|
this.inputMode = "ask";
|
|
275
304
|
this.inputBuf = "";
|
|
276
305
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-overnight",
|
|
3
|
-
"version": "1.
|
|
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": {
|