claude-overnight 1.11.6 → 1.11.9
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/index.js +70 -4
- package/dist/merge.js +7 -2
- package/dist/planner.d.ts +1 -0
- package/dist/planner.js +45 -2
- package/dist/run.js +10 -2
- package/dist/state.d.ts +13 -0
- package/dist/state.js +73 -4
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,13 +5,22 @@ import { fileURLToPath } from "url";
|
|
|
5
5
|
import chalk from "chalk";
|
|
6
6
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
7
7
|
import { Swarm } from "./swarm.js";
|
|
8
|
-
import { planTasks, refinePlan, identifyThemes, buildThinkingTasks, orchestrate } from "./planner.js";
|
|
8
|
+
import { planTasks, refinePlan, identifyThemes, buildThinkingTasks, orchestrate, salvageFromFile } from "./planner.js";
|
|
9
9
|
import { detectModelTier } from "./planner-query.js";
|
|
10
10
|
import { RunDisplay } from "./ui.js";
|
|
11
11
|
import { renderSummary } from "./render.js";
|
|
12
12
|
import { executeRun } from "./run.js";
|
|
13
13
|
import { parseCliFlags, isAuthError, fetchModels, ask, select, selectKey, loadTaskFile, validateConcurrency, isGitRepo, validateGitRepo, showPlan, BRAILLE, makeProgressLog, } from "./cli.js";
|
|
14
|
-
import { loadRunState, findIncompleteRuns, findOrphanedDesigns, formatTimeAgo, showRunHistory, readPreviousRunKnowledge, createRunDir, updateLatestSymlink, readMdDir, saveRunState, autoMergeBranches, } from "./state.js";
|
|
14
|
+
import { loadRunState, findIncompleteRuns, findOrphanedDesigns, backfillOrphanedPlans, formatTimeAgo, showRunHistory, readPreviousRunKnowledge, createRunDir, updateLatestSymlink, readMdDir, saveRunState, autoMergeBranches, } from "./state.js";
|
|
15
|
+
function countTasksInFile(path) {
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
18
|
+
return Array.isArray(parsed?.tasks) ? parsed.tasks.length : 0;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
15
24
|
async function main() {
|
|
16
25
|
const argv = process.argv.slice(2);
|
|
17
26
|
if (argv.includes("-v") || argv.includes("--version")) {
|
|
@@ -114,6 +123,12 @@ async function main() {
|
|
|
114
123
|
// ── Run history ──
|
|
115
124
|
const rootDir = join(cwd, ".claude-overnight");
|
|
116
125
|
const runsDir = join(rootDir, "runs");
|
|
126
|
+
// Backfill run.json for pre-1.11.7 orphaned plans so they become visible
|
|
127
|
+
// to the resume picker. One-shot, idempotent, silent if there's nothing.
|
|
128
|
+
const backfilled = backfillOrphanedPlans(rootDir, cwd);
|
|
129
|
+
if (backfilled > 0 && !noTTY) {
|
|
130
|
+
console.log(chalk.dim(`\n ↻ Recovered ${backfilled} orphaned plan${backfilled > 1 ? "s" : ""} from disk`));
|
|
131
|
+
}
|
|
117
132
|
const allRuns = [];
|
|
118
133
|
try {
|
|
119
134
|
for (const d of readdirSync(runsDir).sort().reverse()) {
|
|
@@ -162,8 +177,13 @@ async function main() {
|
|
|
162
177
|
lastStatus = readFileSync(join(run.dir, "status.md"), "utf-8").trim().slice(0, 120);
|
|
163
178
|
}
|
|
164
179
|
catch { }
|
|
180
|
+
const planTaskCount = prev.phase === "planning" ? countTasksInFile(join(run.dir, "tasks.json")) : 0;
|
|
165
181
|
console.log(chalk.yellow(`\n ⚠ Unfinished run`) + chalk.dim(` · ${ago}`));
|
|
166
|
-
const boxLines = [
|
|
182
|
+
const boxLines = prev.phase === "planning" ? [
|
|
183
|
+
`${obj}${obj.length >= 50 ? "…" : ""}`,
|
|
184
|
+
`Plan ready · ${planTaskCount} tasks · budget ${prev.budget} · ${prev.concurrency}× concurrent`,
|
|
185
|
+
`Plan phase · not yet executing`,
|
|
186
|
+
] : [
|
|
167
187
|
`${obj}${obj.length >= 50 ? "…" : ""}`,
|
|
168
188
|
`${prev.accCompleted}/${prev.budget} sessions · ${Math.max(1, (prev.budget ?? 0) - prev.accCompleted)} remaining · $${prev.accCost.toFixed(2)}`,
|
|
169
189
|
`Wave ${prev.waveNum + 1} · ${prev.phase}`,
|
|
@@ -207,7 +227,13 @@ async function main() {
|
|
|
207
227
|
}
|
|
208
228
|
catch { }
|
|
209
229
|
console.log(chalk.cyan(` ${i + 1}`) + ` ${obj}${obj.length >= 50 ? "…" : ""}`);
|
|
210
|
-
|
|
230
|
+
if (s.phase === "planning") {
|
|
231
|
+
const n = countTasksInFile(join(shown[i].dir, "tasks.json"));
|
|
232
|
+
console.log(chalk.dim(` plan ready · ${n} tasks · budget ${s.budget} · ${ago} · not yet executing`));
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
console.log(chalk.dim(` ${s.accCompleted}/${s.budget} · $${s.accCost.toFixed(2)} · ${ago} · ${s.phase} at wave ${s.waveNum + 1}${merged ? ` · ${merged} merged` : ""}`));
|
|
236
|
+
}
|
|
211
237
|
if (lastStatus)
|
|
212
238
|
console.log(chalk.dim(` ${lastStatus}`));
|
|
213
239
|
console.log("");
|
|
@@ -236,6 +262,23 @@ async function main() {
|
|
|
236
262
|
}
|
|
237
263
|
}
|
|
238
264
|
if (resuming && resumeState && resumeRunDir) {
|
|
265
|
+
// If currentTasks is empty but tasks.json exists on disk, reload it.
|
|
266
|
+
// Covers two cases:
|
|
267
|
+
// 1. Planning-phase resumes (the prior run died before executeRun).
|
|
268
|
+
// 2. Stopped/capped runs whose state was saved with currentTasks: []
|
|
269
|
+
// (saveRunState always stores [] — the plan is on disk in tasks.json).
|
|
270
|
+
if (resumeState.currentTasks.length === 0) {
|
|
271
|
+
const loaded = salvageFromFile(join(resumeRunDir, "tasks.json"), resumeState.budget, () => { }, "resume");
|
|
272
|
+
if (!loaded && resumeState.phase === "planning") {
|
|
273
|
+
console.error(chalk.red(`\n Planning-phase run has no usable tasks.json — start Fresh instead.\n`));
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
if (loaded) {
|
|
277
|
+
resumeState.currentTasks = loaded;
|
|
278
|
+
const label = resumeState.phase === "planning" ? "Resuming plan" : `Resuming ${resumeState.phase} run`;
|
|
279
|
+
console.log(chalk.green(`\n ✓ ${label} · ${loaded.length} tasks loaded from tasks.json`));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
239
282
|
const unmerged = resumeState.branches.filter(b => b.status === "unmerged").length;
|
|
240
283
|
if (unmerged > 0) {
|
|
241
284
|
console.log("");
|
|
@@ -479,6 +522,29 @@ async function main() {
|
|
|
479
522
|
const previousKnowledge = readPreviousRunKnowledge(rootDir);
|
|
480
523
|
const needsPlan = tasks.length === 0 && !resuming;
|
|
481
524
|
const designDir = join(runDir, "designs");
|
|
525
|
+
// Persist an early planning-phase state so the run is visible to the resume
|
|
526
|
+
// picker even if orchestrate dies before executeRun gets a chance to run.
|
|
527
|
+
// Without this, a crashed plan phase leaves no run.json and the run vanishes
|
|
528
|
+
// from findIncompleteRuns — you pay for orchestration and can't see it.
|
|
529
|
+
if (needsPlan && objective) {
|
|
530
|
+
try {
|
|
531
|
+
saveRunState(runDir, {
|
|
532
|
+
id: runDir.split(/[/\\]/).pop() ?? "",
|
|
533
|
+
objective, budget: budget ?? 10, remaining: budget ?? 10,
|
|
534
|
+
workerModel, plannerModel, concurrency, permissionMode,
|
|
535
|
+
usageCap, allowExtraUsage, extraUsageBudget,
|
|
536
|
+
flex, useWorktrees, mergeStrategy,
|
|
537
|
+
waveNum: 0, currentTasks: [],
|
|
538
|
+
accCost: 0, accCompleted: 0, accFailed: 0,
|
|
539
|
+
accIn: 0, accOut: 0, accTools: 0,
|
|
540
|
+
branches: [],
|
|
541
|
+
phase: "planning",
|
|
542
|
+
startedAt: new Date().toISOString(),
|
|
543
|
+
cwd,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
catch { }
|
|
547
|
+
}
|
|
482
548
|
if (needsPlan) {
|
|
483
549
|
if (noTTY) {
|
|
484
550
|
console.error(chalk.red(" No tasks provided and stdin is not a TTY."));
|
package/dist/merge.js
CHANGED
|
@@ -162,11 +162,16 @@ export function cleanStaleWorktrees(cwd, log) {
|
|
|
162
162
|
try {
|
|
163
163
|
const list = gitExec("git worktree list --porcelain", cwd);
|
|
164
164
|
const stale = [];
|
|
165
|
-
|
|
165
|
+
// Match any worktree whose path contains our mkdtemp prefix. We used to
|
|
166
|
+
// gate on `startsWith(tmpdir())` too, but on macOS `os.tmpdir()` returns
|
|
167
|
+
// `/var/folders/...` while git reports worktrees as `/private/var/...`
|
|
168
|
+
// (realpath-resolved), so the prefix never matched and stale worktrees
|
|
169
|
+
// silently accumulated. The `claude-overnight-` substring is unambiguous
|
|
170
|
+
// enough on its own — nothing else in the repo uses that prefix.
|
|
166
171
|
for (const line of list.split("\n")) {
|
|
167
172
|
if (line.startsWith("worktree ")) {
|
|
168
173
|
const wpath = line.slice("worktree ".length);
|
|
169
|
-
if (wpath.
|
|
174
|
+
if (wpath.includes("/claude-overnight-"))
|
|
170
175
|
stale.push(wpath);
|
|
171
176
|
}
|
|
172
177
|
}
|
package/dist/planner.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Task, PermMode } from "./types.js";
|
|
2
|
+
export declare function salvageFromFile(outFile: string | undefined, budget: number | undefined, onLog: (text: string, kind?: "status" | "event") => void, why: string): Task[] | null;
|
|
2
3
|
export declare const DESIGN_THINKING = "\nHOW TO THINK ABOUT EVERY TASK:\n\nStart from the user's job. What is someone hiring this product to do? \"I need to send money abroad cheaply\" \u2014 not \"I need a currency conversion API.\" Every decision \u2014 what to build, how fast it responds, what happens on error \u2014 flows from the job.\n\nThe experience IS the product. A 200ms server response is not a \"performance metric\" \u2014 it's the difference between an app that feels alive and one that feels broken. A loading state is not \"polish\" \u2014 it's the user knowing the app heard them. An error message is not \"error handling\" \u2014 it's the app being honest. There is no line between backend and UX. The server, the API, the database query, the render \u2014 they're all one experience the user either trusts or doesn't.\n\nBuild the core, verify it works, learn, iterate. Don't plan 20 features and build them all. Build the ONE thing that matters most, run it, see if it actually works from a user's chair. What you learn from seeing it run will change what you build next. Each wave should make what exists better before adding what doesn't exist yet.\n\nConsistency is what makes complex things feel simple. One design system, rigid rules, no exceptions. This is how Revolut ships a super-app with 30+ features that doesn't feel like chaos.\n";
|
|
3
4
|
export declare function planTasks(objective: string, cwd: string, plannerModel: string, workerModel: string, permissionMode: PermMode, budget: number | undefined, concurrency: number, onLog: (text: string) => void, flexNote?: string, outFile?: string): Promise<Task[]>;
|
|
4
5
|
export declare function identifyThemes(objective: string, count: number, cwd: string, model: string, permissionMode: PermMode, onLog?: (text: string) => void): Promise<string[]>;
|
package/dist/planner.js
CHANGED
|
@@ -1,4 +1,29 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
1
2
|
import { runPlannerQuery, extractTaskJson, attemptJsonParse, postProcess, detectModelTier, modelCapabilityBlock } from "./planner-query.js";
|
|
3
|
+
// Resilience: if the planner query throws but the agent already wrote valid
|
|
4
|
+
// tasks to `outFile` (via its Write tool), salvage them instead of discarding
|
|
5
|
+
// expensive work. Returns salvaged tasks on success, null if nothing usable on
|
|
6
|
+
// disk — caller should then re-throw the original error.
|
|
7
|
+
export function salvageFromFile(outFile, budget, onLog, why) {
|
|
8
|
+
if (!outFile)
|
|
9
|
+
return null;
|
|
10
|
+
try {
|
|
11
|
+
const parsed = attemptJsonParse(readFileSync(outFile, "utf-8"));
|
|
12
|
+
if (!parsed?.tasks?.length)
|
|
13
|
+
return null;
|
|
14
|
+
let tasks = parsed.tasks.map((t, i) => ({
|
|
15
|
+
id: String(i), prompt: typeof t === "string" ? t : t.prompt,
|
|
16
|
+
}));
|
|
17
|
+
tasks = postProcess(tasks, budget, onLog);
|
|
18
|
+
if (tasks.length === 0)
|
|
19
|
+
return null;
|
|
20
|
+
onLog(`Planner errored (${why}) — salvaged ${tasks.length} tasks from ${outFile}`, "event");
|
|
21
|
+
return tasks;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
2
27
|
// The core framing for all planning. Not a checklist — a way of thinking.
|
|
3
28
|
export const DESIGN_THINKING = `
|
|
4
29
|
HOW TO THINK ABOUT EVERY TASK:
|
|
@@ -152,7 +177,16 @@ export async function planTasks(objective, cwd, plannerModel, workerModel, permi
|
|
|
152
177
|
onLog("Analyzing codebase...");
|
|
153
178
|
const prompt = plannerPrompt(objective, workerModel, budget, concurrency, flexNote);
|
|
154
179
|
const fileInstruction = outFile ? `\n\nAFTER generating the JSON, also write it to ${outFile} using the Write tool.` : "";
|
|
155
|
-
|
|
180
|
+
let resultText;
|
|
181
|
+
try {
|
|
182
|
+
resultText = await runPlannerQuery(prompt + fileInstruction, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA }, onLog);
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
const salvaged = salvageFromFile(outFile, budget, onLog, err?.message ?? String(err));
|
|
186
|
+
if (salvaged)
|
|
187
|
+
return salvaged;
|
|
188
|
+
throw err;
|
|
189
|
+
}
|
|
156
190
|
const parsed = await extractTaskJson(resultText, async () => {
|
|
157
191
|
onLog("Retrying...");
|
|
158
192
|
return runPlannerQuery(`Your previous response was not valid JSON. Respond with ONLY a JSON object {"tasks":[{"prompt":"..."}]}.\n\n${prompt}`, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA }, onLog);
|
|
@@ -234,7 +268,16 @@ Requirements:
|
|
|
234
268
|
Respond with ONLY a JSON object (no markdown fences):
|
|
235
269
|
{"tasks": [{"prompt": "..."}]}${fileInstruction}`;
|
|
236
270
|
onLog("Synthesizing...");
|
|
237
|
-
|
|
271
|
+
let resultText;
|
|
272
|
+
try {
|
|
273
|
+
resultText = await runPlannerQuery(prompt, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA }, onLog);
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
const salvaged = salvageFromFile(outFile, budget, onLog, err?.message ?? String(err));
|
|
277
|
+
if (salvaged)
|
|
278
|
+
return salvaged;
|
|
279
|
+
throw err;
|
|
280
|
+
}
|
|
238
281
|
const parsed = await extractTaskJson(resultText, async () => {
|
|
239
282
|
onLog("Retrying...");
|
|
240
283
|
return runPlannerQuery(`Your previous response was not valid JSON. Respond with ONLY a JSON object {"tasks":[{"prompt":"..."}]}.\n\n${prompt}`, { cwd, model: plannerModel, permissionMode, outputFormat: TASKS_SCHEMA }, onLog);
|
package/dist/run.js
CHANGED
|
@@ -46,8 +46,16 @@ export async function executeRun(cfg) {
|
|
|
46
46
|
branches.push(...rs.branches);
|
|
47
47
|
flex = rs.flex;
|
|
48
48
|
waveHistory.push(...loadWaveHistory(runDir));
|
|
49
|
-
|
|
50
|
-
waveNum
|
|
49
|
+
// Planning-phase resume starts at wave 0 (nothing ran before); all other
|
|
50
|
+
// resumes bump to the next wave since rs.waveNum is the last completed one.
|
|
51
|
+
const fromPlanning = rs.phase === "planning";
|
|
52
|
+
if (fromPlanning && !existsSync(join(runDir, "goal.md")) && objective) {
|
|
53
|
+
writeFileSync(join(runDir, "goal.md"), `## Original Objective\n${objective}`, "utf-8");
|
|
54
|
+
}
|
|
55
|
+
const detail = fromPlanning ? `${currentTasks.length} tasks from plan` : `${waveHistory.length} prior waves`;
|
|
56
|
+
console.log(chalk.green(`\n ✓ Resumed`) + chalk.dim(` · wave ${waveNum + 1} · ${remaining} remaining · $${accCost.toFixed(2)} spent · ${detail}\n`));
|
|
57
|
+
if (!fromPlanning)
|
|
58
|
+
waveNum++;
|
|
51
59
|
}
|
|
52
60
|
else {
|
|
53
61
|
if (objective && !existsSync(join(runDir, "goal.md"))) {
|
package/dist/state.d.ts
CHANGED
|
@@ -36,6 +36,19 @@ export declare function findIncompleteRuns(rootDir: string, filterCwd: string):
|
|
|
36
36
|
state: RunState;
|
|
37
37
|
}[];
|
|
38
38
|
export declare function findOrphanedDesigns(rootDir: string): string | null;
|
|
39
|
+
/**
|
|
40
|
+
* Backfill run.json for pre-1.11.7 orphaned plans: runs where orchestrate's
|
|
41
|
+
* agent wrote tasks.json via its Write tool but the process died before
|
|
42
|
+
* executeRun ever got to saveRunState. Without this, those runs are invisible
|
|
43
|
+
* to findIncompleteRuns forever.
|
|
44
|
+
*
|
|
45
|
+
* Idempotent: runs with an existing run.json are skipped. Synthesizes a
|
|
46
|
+
* minimal "planning" state from what can be read off disk — dir name for
|
|
47
|
+
* timestamp, task count for budget, sane defaults for everything else.
|
|
48
|
+
* The cwd field is set to filterCwd so findIncompleteRuns picks it up on the
|
|
49
|
+
* current project (which is safe — rootDir is already scoped to `cwd`).
|
|
50
|
+
*/
|
|
51
|
+
export declare function backfillOrphanedPlans(rootDir: string, filterCwd: string): number;
|
|
39
52
|
export declare function formatTimeAgo(isoStr: string): string;
|
|
40
53
|
export declare function showRunHistory(allRuns: {
|
|
41
54
|
dir: string;
|
package/dist/state.js
CHANGED
|
@@ -187,10 +187,15 @@ export function findIncompleteRuns(rootDir, filterCwd) {
|
|
|
187
187
|
const dirs = readdirSync(runsDir).sort().reverse();
|
|
188
188
|
const results = [];
|
|
189
189
|
for (const d of dirs) {
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
const runDir = join(runsDir, d);
|
|
191
|
+
const state = loadRunState(runDir);
|
|
192
|
+
if (!state || state.phase === "done" || state.cwd !== filterCwd)
|
|
193
|
+
continue;
|
|
194
|
+
// Planning-phase runs are only resumable if tasks.json was actually
|
|
195
|
+
// written — resuming without tasks is nothing to resume.
|
|
196
|
+
if (state.phase === "planning" && !existsSync(join(runDir, "tasks.json")))
|
|
197
|
+
continue;
|
|
198
|
+
results.push({ dir: runDir, state });
|
|
194
199
|
}
|
|
195
200
|
return results;
|
|
196
201
|
}
|
|
@@ -214,6 +219,70 @@ export function findOrphanedDesigns(rootDir) {
|
|
|
214
219
|
catch { }
|
|
215
220
|
return null;
|
|
216
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Backfill run.json for pre-1.11.7 orphaned plans: runs where orchestrate's
|
|
224
|
+
* agent wrote tasks.json via its Write tool but the process died before
|
|
225
|
+
* executeRun ever got to saveRunState. Without this, those runs are invisible
|
|
226
|
+
* to findIncompleteRuns forever.
|
|
227
|
+
*
|
|
228
|
+
* Idempotent: runs with an existing run.json are skipped. Synthesizes a
|
|
229
|
+
* minimal "planning" state from what can be read off disk — dir name for
|
|
230
|
+
* timestamp, task count for budget, sane defaults for everything else.
|
|
231
|
+
* The cwd field is set to filterCwd so findIncompleteRuns picks it up on the
|
|
232
|
+
* current project (which is safe — rootDir is already scoped to `cwd`).
|
|
233
|
+
*/
|
|
234
|
+
export function backfillOrphanedPlans(rootDir, filterCwd) {
|
|
235
|
+
const runsDir = join(rootDir, "runs");
|
|
236
|
+
let count = 0;
|
|
237
|
+
try {
|
|
238
|
+
const dirs = readdirSync(runsDir);
|
|
239
|
+
for (const d of dirs) {
|
|
240
|
+
const runDir = join(runsDir, d);
|
|
241
|
+
if (existsSync(join(runDir, "run.json")))
|
|
242
|
+
continue;
|
|
243
|
+
const tasksFile = join(runDir, "tasks.json");
|
|
244
|
+
if (!existsSync(tasksFile))
|
|
245
|
+
continue;
|
|
246
|
+
let taskCount = 0;
|
|
247
|
+
try {
|
|
248
|
+
const parsed = JSON.parse(readFileSync(tasksFile, "utf-8"));
|
|
249
|
+
if (!Array.isArray(parsed?.tasks))
|
|
250
|
+
continue;
|
|
251
|
+
taskCount = parsed.tasks.length;
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (taskCount === 0)
|
|
257
|
+
continue;
|
|
258
|
+
// Dir name format: 2026-04-12T13-03-57 (UTC). Convert to ISO.
|
|
259
|
+
const m = d.match(/^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})$/);
|
|
260
|
+
const startedAt = m ? `${m[1]}T${m[2]}:${m[3]}:${m[4]}.000Z` : new Date(0).toISOString();
|
|
261
|
+
try {
|
|
262
|
+
saveRunState(runDir, {
|
|
263
|
+
id: d,
|
|
264
|
+
objective: `(recovered pre-1.11.7 plan · ${taskCount} tasks)`,
|
|
265
|
+
budget: taskCount, remaining: taskCount,
|
|
266
|
+
workerModel: "claude-opus-4-6", plannerModel: "claude-opus-4-6",
|
|
267
|
+
concurrency: 5, permissionMode: "bypassPermissions",
|
|
268
|
+
flex: false, useWorktrees: true, mergeStrategy: "yolo",
|
|
269
|
+
allowExtraUsage: false,
|
|
270
|
+
waveNum: 0, currentTasks: [],
|
|
271
|
+
accCost: 0, accCompleted: 0, accFailed: 0,
|
|
272
|
+
accIn: 0, accOut: 0, accTools: 0,
|
|
273
|
+
branches: [],
|
|
274
|
+
phase: "planning",
|
|
275
|
+
startedAt,
|
|
276
|
+
cwd: filterCwd,
|
|
277
|
+
});
|
|
278
|
+
count++;
|
|
279
|
+
}
|
|
280
|
+
catch { }
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
catch { }
|
|
284
|
+
return count;
|
|
285
|
+
}
|
|
217
286
|
// ── History display ──
|
|
218
287
|
export function formatTimeAgo(isoStr) {
|
|
219
288
|
const ms = Date.now() - new Date(isoStr).getTime();
|
package/dist/types.d.ts
CHANGED
|
@@ -172,7 +172,7 @@ export interface RunState {
|
|
|
172
172
|
accOut?: number;
|
|
173
173
|
accTools?: number;
|
|
174
174
|
branches: BranchRecord[];
|
|
175
|
-
phase: "steering" | "capped" | "done" | "stopped";
|
|
175
|
+
phase: "planning" | "steering" | "capped" | "done" | "stopped";
|
|
176
176
|
startedAt: string;
|
|
177
177
|
cwd: string;
|
|
178
178
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-overnight",
|
|
3
|
-
"version": "1.11.
|
|
3
|
+
"version": "1.11.9",
|
|
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": {
|