claude-overnight 1.25.1 β 1.25.6
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/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/bin.js +31 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +4 -1
- package/dist/index.js +42 -31
- package/dist/planner-query.js +2 -2
- package/dist/render.d.ts +3 -2
- package/dist/render.js +45 -28
- package/dist/run.d.ts +6 -0
- package/dist/run.js +68 -1
- package/dist/state.d.ts +1 -1
- package/dist/state.js +50 -28
- package/dist/types.d.ts +12 -0
- package/dist/ui.d.ts +16 -9
- package/dist/ui.js +44 -26
- package/package.json +1 -1
- package/plugins/claude-overnight/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-overnight/skills/claude-overnight/SKILL.md +14 -0
package/dist/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "1.25.
|
|
1
|
+
export declare const VERSION = "1.25.6";
|
package/dist/_version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by build β do not edit manually.
|
|
2
|
-
export const VERSION = "1.25.
|
|
2
|
+
export const VERSION = "1.25.6";
|
package/dist/bin.js
CHANGED
|
@@ -12,6 +12,37 @@
|
|
|
12
12
|
process.env.CURSOR_SKIP_KEYCHAIN = "1";
|
|
13
13
|
const argv = process.argv.slice(2);
|
|
14
14
|
const quiet = argv.includes("-h") || argv.includes("--help") || argv.includes("-v") || argv.includes("--version");
|
|
15
|
+
// Auto-update: check npm at most once every 4 hours, install and re-exec if newer.
|
|
16
|
+
// Skipped in non-TTY (CI/pipe) mode, on --help/--version, and if CLAUDE_OVERNIGHT_UPDATED is set.
|
|
17
|
+
if (process.stdout.isTTY && !quiet && !process.env.CLAUDE_OVERNIGHT_UPDATED) {
|
|
18
|
+
const UPDATE_INTERVAL_MS = 4 * 60 * 60 * 1000;
|
|
19
|
+
const { homedir } = await import("node:os");
|
|
20
|
+
const { join } = await import("node:path");
|
|
21
|
+
const { readFileSync, writeFileSync } = await import("node:fs");
|
|
22
|
+
const tsFile = join(homedir(), ".claude-overnight-update-ts");
|
|
23
|
+
let shouldCheck = true;
|
|
24
|
+
try {
|
|
25
|
+
shouldCheck = Date.now() - parseInt(readFileSync(tsFile, "utf-8").trim(), 10) > UPDATE_INTERVAL_MS;
|
|
26
|
+
}
|
|
27
|
+
catch { }
|
|
28
|
+
if (shouldCheck) {
|
|
29
|
+
try {
|
|
30
|
+
writeFileSync(tsFile, String(Date.now())); // stamp first so failures don't re-trigger
|
|
31
|
+
const { execFileSync, spawnSync } = await import("node:child_process");
|
|
32
|
+
const latest = execFileSync("npm", ["show", "claude-overnight", "version"], { encoding: "utf-8", timeout: 6000 }).trim();
|
|
33
|
+
const { VERSION } = await import("./_version.js");
|
|
34
|
+
if (latest !== VERSION) {
|
|
35
|
+
process.stdout.write(`\r\x1b[2K π claude-overnight \x1b[33m${VERSION} β ${latest}\x1b[0m updatingβ¦\n`);
|
|
36
|
+
execFileSync("npm", ["i", "-g", `claude-overnight@${latest}`], { stdio: "inherit", timeout: 60000 });
|
|
37
|
+
const r = spawnSync(process.argv[0], process.argv.slice(1), {
|
|
38
|
+
stdio: "inherit", env: { ...process.env, CLAUDE_OVERNIGHT_UPDATED: "1" },
|
|
39
|
+
});
|
|
40
|
+
process.exit(r.status ?? 0);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch { } // silent β never block startup for a failed update check
|
|
44
|
+
}
|
|
45
|
+
}
|
|
15
46
|
if (!quiet && process.stdout.isTTY) {
|
|
16
47
|
const frames = ["β ", "β ", "β Ή", "β Έ", "β Ό", "β ΄", "β ¦", "β §", "β ", "β "];
|
|
17
48
|
let i = 0;
|
package/dist/cli.d.ts
CHANGED
|
@@ -54,6 +54,9 @@ export interface FileArgs {
|
|
|
54
54
|
permissionMode?: PermMode;
|
|
55
55
|
cwd?: string;
|
|
56
56
|
allowedTools?: string[];
|
|
57
|
+
beforeWave?: string | string[];
|
|
58
|
+
afterWave?: string | string[];
|
|
59
|
+
afterRun?: string | string[];
|
|
57
60
|
useWorktrees?: boolean;
|
|
58
61
|
mergeStrategy?: MergeStrategy;
|
|
59
62
|
usageCap?: number;
|
package/dist/cli.js
CHANGED
|
@@ -332,7 +332,7 @@ export async function selectKey(label, options) {
|
|
|
332
332
|
});
|
|
333
333
|
}
|
|
334
334
|
const KNOWN_TASK_FILE_KEYS = new Set([
|
|
335
|
-
"tasks", "objective", "concurrency", "cwd", "model", "permissionMode", "allowedTools", "worktrees", "mergeStrategy", "usageCap", "flexiblePlan",
|
|
335
|
+
"tasks", "objective", "concurrency", "cwd", "model", "permissionMode", "allowedTools", "beforeWave", "afterWave", "afterRun", "worktrees", "mergeStrategy", "usageCap", "flexiblePlan",
|
|
336
336
|
]);
|
|
337
337
|
export function loadTaskFile(file) {
|
|
338
338
|
const path = resolve(file);
|
|
@@ -394,6 +394,9 @@ export function loadTaskFile(file) {
|
|
|
394
394
|
cwd: parsed.cwd ? resolve(parsed.cwd) : undefined,
|
|
395
395
|
permissionMode: parsed.permissionMode,
|
|
396
396
|
allowedTools: parsed.allowedTools,
|
|
397
|
+
beforeWave: parsed.beforeWave,
|
|
398
|
+
afterWave: parsed.afterWave,
|
|
399
|
+
afterRun: parsed.afterRun,
|
|
397
400
|
useWorktrees: parsed.worktrees,
|
|
398
401
|
mergeStrategy: parsed.mergeStrategy,
|
|
399
402
|
usageCap,
|
package/dist/index.js
CHANGED
|
@@ -271,6 +271,9 @@ async function main() {
|
|
|
271
271
|
const nonInteractive = noTTY || fileCfg !== undefined || tasks.length > 0;
|
|
272
272
|
const cwd = fileCfg?.cwd ?? process.cwd();
|
|
273
273
|
const allowedTools = fileCfg?.allowedTools;
|
|
274
|
+
const beforeWave = fileCfg?.beforeWave;
|
|
275
|
+
const afterWave = fileCfg?.afterWave;
|
|
276
|
+
const afterRun = fileCfg?.afterRun;
|
|
274
277
|
if (!existsSync(cwd)) {
|
|
275
278
|
console.error(chalk.red(` Working directory does not exist: ${cwd}`));
|
|
276
279
|
process.exit(1);
|
|
@@ -329,7 +332,7 @@ async function main() {
|
|
|
329
332
|
if (action === "q")
|
|
330
333
|
process.exit(0);
|
|
331
334
|
if (action === "h") {
|
|
332
|
-
showRunHistory(allRuns, cwd);
|
|
335
|
+
await showRunHistory(allRuns, cwd);
|
|
333
336
|
continue;
|
|
334
337
|
}
|
|
335
338
|
if (action === "c") {
|
|
@@ -382,7 +385,7 @@ async function main() {
|
|
|
382
385
|
break;
|
|
383
386
|
}
|
|
384
387
|
if (action === "h") {
|
|
385
|
-
showRunHistory(allRuns, cwd);
|
|
388
|
+
await showRunHistory(allRuns, cwd);
|
|
386
389
|
continue;
|
|
387
390
|
}
|
|
388
391
|
resuming = true;
|
|
@@ -426,7 +429,7 @@ async function main() {
|
|
|
426
429
|
break;
|
|
427
430
|
}
|
|
428
431
|
if (action === "h") {
|
|
429
|
-
showRunHistory(allRuns, cwd);
|
|
432
|
+
await showRunHistory(allRuns, cwd);
|
|
430
433
|
continue;
|
|
431
434
|
}
|
|
432
435
|
const idx = parseInt(action) - 1;
|
|
@@ -979,6 +982,39 @@ async function main() {
|
|
|
979
982
|
const thinkDisplay = new RunDisplay(thinkRunInfo, { remaining: 0, usageCap, concurrency, paused: false, dirty: false });
|
|
980
983
|
thinkDisplay.setWave(thinkingSwarm);
|
|
981
984
|
thinkDisplay.start();
|
|
985
|
+
// Save thinking-wave state on every exit path (normal, abort, double-q).
|
|
986
|
+
const saveThinkingState = () => {
|
|
987
|
+
thinkingUsed = thinkingSwarm.completed + thinkingSwarm.failed;
|
|
988
|
+
thinkingCost = thinkingSwarm.totalCostUsd;
|
|
989
|
+
thinkingIn = thinkingSwarm.totalInputTokens;
|
|
990
|
+
thinkingOut = thinkingSwarm.totalOutputTokens;
|
|
991
|
+
thinkingTools = thinkingSwarm.agents.reduce((sum, a) => sum + a.toolCalls, 0);
|
|
992
|
+
try {
|
|
993
|
+
saveRunState(runDir, {
|
|
994
|
+
id: runDir.split(/[/\\]/).pop() ?? "",
|
|
995
|
+
objective: objective, budget: budget ?? 10, remaining: (budget ?? 10) - thinkingUsed,
|
|
996
|
+
workerModel, plannerModel,
|
|
997
|
+
workerProviderId: workerProvider?.id, plannerProviderId: plannerProvider?.id,
|
|
998
|
+
concurrency, permissionMode,
|
|
999
|
+
usageCap, allowExtraUsage, extraUsageBudget,
|
|
1000
|
+
flex, useWorktrees, mergeStrategy,
|
|
1001
|
+
waveNum: 0, currentTasks: [],
|
|
1002
|
+
accCost: thinkingCost, accCompleted: thinkingUsed, accFailed: 0,
|
|
1003
|
+
accIn: thinkingIn, accOut: thinkingOut, accTools: thinkingTools,
|
|
1004
|
+
branches: [],
|
|
1005
|
+
phase: "planning",
|
|
1006
|
+
startedAt: new Date().toISOString(),
|
|
1007
|
+
cwd,
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
catch { }
|
|
1011
|
+
};
|
|
1012
|
+
// Catch double-q / hard exit during thinking wave
|
|
1013
|
+
const exitHandler = () => { try {
|
|
1014
|
+
saveThinkingState();
|
|
1015
|
+
}
|
|
1016
|
+
catch { } };
|
|
1017
|
+
process.on("exit", exitHandler);
|
|
982
1018
|
try {
|
|
983
1019
|
await thinkingSwarm.run();
|
|
984
1020
|
}
|
|
@@ -986,35 +1022,10 @@ async function main() {
|
|
|
986
1022
|
thinkDisplay.pause();
|
|
987
1023
|
console.log(renderSummary(thinkingSwarm));
|
|
988
1024
|
thinkDisplay.stop();
|
|
1025
|
+
saveThinkingState();
|
|
1026
|
+
process.removeListener("exit", exitHandler);
|
|
989
1027
|
}
|
|
990
|
-
thinkingUsed = thinkingSwarm.completed + thinkingSwarm.failed;
|
|
991
|
-
thinkingCost = thinkingSwarm.totalCostUsd;
|
|
992
|
-
thinkingIn = thinkingSwarm.totalInputTokens;
|
|
993
|
-
thinkingOut = thinkingSwarm.totalOutputTokens;
|
|
994
|
-
thinkingTools = thinkingSwarm.agents.reduce((sum, a) => sum + a.toolCalls, 0);
|
|
995
1028
|
thinkingHistory = { wave: -1, tasks: thinkingSwarm.agents.map(a => ({ prompt: a.task.prompt.slice(0, 200), status: a.status, filesChanged: a.filesChanged, error: a.error })) };
|
|
996
|
-
// Persist thinking cost/count into run.json so if the user quits
|
|
997
|
-
// between thinking and orchestrate, resume still sees the real spend
|
|
998
|
-
// and the run stays visible in the picker (designs on disk = resumable).
|
|
999
|
-
try {
|
|
1000
|
-
saveRunState(runDir, {
|
|
1001
|
-
id: runDir.split(/[/\\]/).pop() ?? "",
|
|
1002
|
-
objective: objective, budget: budget ?? 10, remaining: (budget ?? 10) - thinkingUsed,
|
|
1003
|
-
workerModel, plannerModel,
|
|
1004
|
-
workerProviderId: workerProvider?.id, plannerProviderId: plannerProvider?.id,
|
|
1005
|
-
concurrency, permissionMode,
|
|
1006
|
-
usageCap, allowExtraUsage, extraUsageBudget,
|
|
1007
|
-
flex, useWorktrees, mergeStrategy,
|
|
1008
|
-
waveNum: 0, currentTasks: [],
|
|
1009
|
-
accCost: thinkingCost, accCompleted: thinkingUsed, accFailed: 0,
|
|
1010
|
-
accIn: thinkingIn, accOut: thinkingOut, accTools: thinkingTools,
|
|
1011
|
-
branches: [],
|
|
1012
|
-
phase: "planning",
|
|
1013
|
-
startedAt: new Date().toISOString(),
|
|
1014
|
-
cwd,
|
|
1015
|
-
});
|
|
1016
|
-
}
|
|
1017
|
-
catch { }
|
|
1018
1029
|
if (thinkingSwarm.rateLimitResetsAt) {
|
|
1019
1030
|
const waitMs = thinkingSwarm.rateLimitResetsAt - Date.now();
|
|
1020
1031
|
if (waitMs > 0) {
|
|
@@ -1124,7 +1135,7 @@ async function main() {
|
|
|
1124
1135
|
tasks, objective, budget: budget ?? tasks.length, workerModel, plannerModel, fastModel,
|
|
1125
1136
|
workerProvider, plannerProvider, fastProvider, concurrency,
|
|
1126
1137
|
permissionMode, useWorktrees, mergeStrategy, usageCap, allowExtraUsage, extraUsageBudget,
|
|
1127
|
-
flex, agentTimeoutMs, cwd, allowedTools, runDir, previousKnowledge,
|
|
1138
|
+
flex, agentTimeoutMs, cwd, allowedTools, beforeWave, afterWave, afterRun, runDir, previousKnowledge,
|
|
1128
1139
|
resuming, resumeState: resumeState ?? undefined,
|
|
1129
1140
|
thinkingUsed, thinkingCost, thinkingIn, thinkingOut, thinkingTools, thinkingHistory,
|
|
1130
1141
|
runStartedAt: resuming && resumeState?.startedAt ? new Date(resumeState.startedAt).getTime() : Date.now(),
|
package/dist/planner-query.js
CHANGED
|
@@ -115,8 +115,8 @@ async function runPlannerQueryOnce(prompt, opts, onLog) {
|
|
|
115
115
|
options: {
|
|
116
116
|
cwd: opts.cwd,
|
|
117
117
|
model: opts.model,
|
|
118
|
-
tools: ["Read", "Glob", "Grep", "Write"],
|
|
119
|
-
allowedTools: ["Read", "Glob", "Grep", "Write"],
|
|
118
|
+
tools: ["Read", "Glob", "Grep", "Write", "Bash", "WebFetch", "WebSearch", "TodoWrite", "Agent"],
|
|
119
|
+
allowedTools: ["Read", "Glob", "Grep", "Write", "Bash", "WebFetch", "WebSearch", "TodoWrite", "Agent"],
|
|
120
120
|
permissionMode: opts.permissionMode,
|
|
121
121
|
...(opts.permissionMode === "bypassPermissions" && { allowDangerouslySkipPermissions: true }),
|
|
122
122
|
persistSession: true,
|
package/dist/render.d.ts
CHANGED
|
@@ -34,6 +34,7 @@ export declare function renderUnifiedFrame(params: {
|
|
|
34
34
|
content: ContentRenderer;
|
|
35
35
|
hotkeyRow?: string;
|
|
36
36
|
extraFooterRows?: string[];
|
|
37
|
+
maxRows?: number;
|
|
37
38
|
}): string;
|
|
38
39
|
type RLGetter = () => {
|
|
39
40
|
utilization: number;
|
|
@@ -41,7 +42,7 @@ type RLGetter = () => {
|
|
|
41
42
|
windows: Map<string, RateLimitWindow>;
|
|
42
43
|
resetsAt?: number;
|
|
43
44
|
};
|
|
44
|
-
export declare function renderFrame(swarm: Swarm, showHotkeys: boolean, runInfo?: RunInfo, selectedAgentId?: number): string;
|
|
45
|
+
export declare function renderFrame(swarm: Swarm, showHotkeys: boolean, runInfo?: RunInfo, selectedAgentId?: number, maxRows?: number, debrief?: string): string;
|
|
45
46
|
export interface SteeringViewData {
|
|
46
47
|
/** The ephemeral ticker heartbeat -- elapsed, tool count, cost, current reasoning snippet. */
|
|
47
48
|
statusLine: string;
|
|
@@ -50,6 +51,6 @@ export interface SteeringViewData {
|
|
|
50
51
|
/** Optional context read from disk at setSteering() time. */
|
|
51
52
|
context?: SteeringContext;
|
|
52
53
|
}
|
|
53
|
-
export declare function renderSteeringFrame(runInfo: RunInfo, data: SteeringViewData, showHotkeys: boolean, rlGetter?: RLGetter): string;
|
|
54
|
+
export declare function renderSteeringFrame(runInfo: RunInfo, data: SteeringViewData, showHotkeys: boolean, rlGetter?: RLGetter, maxRows?: number, debrief?: string): string;
|
|
54
55
|
export declare function renderSummary(swarm: Swarm): string;
|
|
55
56
|
export {};
|
package/dist/render.js
CHANGED
|
@@ -141,9 +141,9 @@ function renderUsageBars(out, w, swarm) {
|
|
|
141
141
|
// ββ Unified frame renderer ββ
|
|
142
142
|
export function renderUnifiedFrame(params) {
|
|
143
143
|
const w = Math.max((process.stdout.columns ?? 80) || 80, 60);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
renderHeader(
|
|
144
|
+
// ββ Header (fixed) ββ
|
|
145
|
+
const header = [];
|
|
146
|
+
renderHeader(header, w, {
|
|
147
147
|
model: params.model,
|
|
148
148
|
phase: params.phase,
|
|
149
149
|
barPct: params.barPct,
|
|
@@ -160,42 +160,46 @@ export function renderUnifiedFrame(params) {
|
|
|
160
160
|
sessionsBudget: params.sessionsBudget,
|
|
161
161
|
remaining: params.remaining,
|
|
162
162
|
});
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
163
|
+
if (params.usageBarRender)
|
|
164
|
+
params.usageBarRender(header, w);
|
|
165
|
+
header.push("");
|
|
166
|
+
// ββ Footer (fixed) ββ
|
|
167
|
+
const footer = [""];
|
|
168
|
+
if (params.hotkeyRow)
|
|
169
|
+
footer.push(params.hotkeyRow);
|
|
170
|
+
if (params.extraFooterRows)
|
|
171
|
+
for (const row of params.extraFooterRows)
|
|
172
|
+
footer.push(row);
|
|
173
|
+
footer.push("");
|
|
174
|
+
// ββ Content (elastic β shrinks to fit) ββ
|
|
175
|
+
const contentBudget = params.maxRows != null
|
|
176
|
+
? Math.max(0, params.maxRows - header.length - footer.length)
|
|
177
|
+
: Infinity;
|
|
178
|
+
const content = [];
|
|
169
179
|
const sections = params.content.sections();
|
|
170
180
|
for (const sec of sections) {
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
|
|
181
|
+
if (content.length >= contentBudget)
|
|
182
|
+
break;
|
|
183
|
+
if (sec.title)
|
|
184
|
+
section(content, w, sec.title);
|
|
174
185
|
for (const row of sec.rows) {
|
|
175
|
-
|
|
186
|
+
if (content.length >= contentBudget)
|
|
187
|
+
break;
|
|
188
|
+
content.push(row);
|
|
176
189
|
}
|
|
177
190
|
}
|
|
178
|
-
|
|
179
|
-
out.push("");
|
|
180
|
-
if (params.hotkeyRow) {
|
|
181
|
-
out.push(params.hotkeyRow);
|
|
182
|
-
}
|
|
183
|
-
if (params.extraFooterRows) {
|
|
184
|
-
for (const row of params.extraFooterRows) {
|
|
185
|
-
out.push(row);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
out.push("");
|
|
189
|
-
return out.join("\n");
|
|
191
|
+
return [...header, ...content, ...footer].join("\n");
|
|
190
192
|
}
|
|
191
|
-
export function renderFrame(swarm, showHotkeys, runInfo, selectedAgentId) {
|
|
193
|
+
export function renderFrame(swarm, showHotkeys, runInfo, selectedAgentId, maxRows, debrief) {
|
|
194
|
+
const allDone = swarm.agents.length > 0 && swarm.agents.every(a => a.status !== "running");
|
|
195
|
+
const doneTag = allDone && !swarm.aborted ? chalk.green("COMPLETE") : "";
|
|
192
196
|
const stoppingTag = swarm.aborted ? chalk.yellow("STOPPING") : "";
|
|
193
197
|
const pausedTag = swarm.paused ? chalk.yellow("PAUSED") : "";
|
|
194
198
|
const stallTag = swarm.stallLevel >= 3 ? chalk.red("STALL") : swarm.stallLevel > 0 ? chalk.yellow(`STALL L${swarm.stallLevel}`) : "";
|
|
195
199
|
const phaseLabel = swarm.phase === "planning" ? chalk.magenta("PLANNING")
|
|
196
200
|
: swarm.phase === "merging" ? chalk.yellow("MERGING")
|
|
197
201
|
: swarm.rateLimitPaused > 0 ? chalk.yellow("COOLING") : "";
|
|
198
|
-
const phase = [phaseLabel, pausedTag, stallTag, stoppingTag].filter(Boolean).join(" ");
|
|
202
|
+
const phase = [phaseLabel, doneTag, pausedTag, stallTag, stoppingTag].filter(Boolean).join(" ");
|
|
199
203
|
const waveUsed = swarm.completed + swarm.failed;
|
|
200
204
|
const running = swarm.agents.filter(a => a.status === "running");
|
|
201
205
|
const finished = swarm.agents.filter(a => a.status !== "running");
|
|
@@ -259,6 +263,11 @@ export function renderFrame(swarm, showHotkeys, runInfo, selectedAgentId) {
|
|
|
259
263
|
// Event log (undecorated)
|
|
260
264
|
const ww = Math.max((process.stdout.columns ?? 80) || 80, 60);
|
|
261
265
|
const eventRows = [chalk.gray(" \u2500\u2500\u2500 Events " + "\u2500".repeat(Math.min(ww - 16, 90)))];
|
|
266
|
+
// All-done indicator: visible immediately when swarm finishes, before summary / steering
|
|
267
|
+
if (allDone && swarm.phase !== "done") {
|
|
268
|
+
eventRows.push(chalk.dim(" (all tasks done \u2014 processing)"));
|
|
269
|
+
eventRows.push("");
|
|
270
|
+
}
|
|
262
271
|
const logN = Math.min(12, swarm.logs.length);
|
|
263
272
|
for (const entry of swarm.logs.slice(-logN)) {
|
|
264
273
|
const t = new Date(entry.time).toLocaleTimeString("en", { hour12: false });
|
|
@@ -281,6 +290,8 @@ export function renderFrame(swarm, showHotkeys, runInfo, selectedAgentId) {
|
|
|
281
290
|
// Build footer
|
|
282
291
|
let hotkeyRow;
|
|
283
292
|
const extraFooterRows = [];
|
|
293
|
+
if (debrief)
|
|
294
|
+
extraFooterRows.push(chalk.dim(` ${debrief}`));
|
|
284
295
|
if (showHotkeys) {
|
|
285
296
|
const pending = runInfo?.pendingSteer ?? 0;
|
|
286
297
|
const chip = pending > 0 ? chalk.cyan(` \u270E ${pending} steer queued`) : "";
|
|
@@ -314,6 +325,7 @@ export function renderFrame(swarm, showHotkeys, runInfo, selectedAgentId) {
|
|
|
314
325
|
content,
|
|
315
326
|
hotkeyRow,
|
|
316
327
|
extraFooterRows,
|
|
328
|
+
maxRows,
|
|
317
329
|
});
|
|
318
330
|
}
|
|
319
331
|
function section(out, w, title) {
|
|
@@ -387,7 +399,7 @@ function renderStatusBlock(out, w, status) {
|
|
|
387
399
|
for (const ln of lines)
|
|
388
400
|
out.push(` ${chalk.dim(truncate(ln.trim(), w - 4))}`);
|
|
389
401
|
}
|
|
390
|
-
export function renderSteeringFrame(runInfo, data, showHotkeys, rlGetter) {
|
|
402
|
+
export function renderSteeringFrame(runInfo, data, showHotkeys, rlGetter, maxRows, debrief) {
|
|
391
403
|
const totalUsed = runInfo.accCompleted + runInfo.accFailed;
|
|
392
404
|
const ctx = data.context;
|
|
393
405
|
const content = {
|
|
@@ -457,6 +469,9 @@ export function renderSteeringFrame(runInfo, data, showHotkeys, rlGetter) {
|
|
|
457
469
|
: undefined;
|
|
458
470
|
// Footer
|
|
459
471
|
let hotkeyRow;
|
|
472
|
+
const extraFooterRows = [];
|
|
473
|
+
if (debrief)
|
|
474
|
+
extraFooterRows.push(chalk.dim(` ${debrief}`));
|
|
460
475
|
if (showHotkeys) {
|
|
461
476
|
const pending = runInfo?.pendingSteer ?? 0;
|
|
462
477
|
const chip = pending > 0 ? chalk.cyan(` \u270E ${pending} steer queued`) : "";
|
|
@@ -480,6 +495,8 @@ export function renderSteeringFrame(runInfo, data, showHotkeys, rlGetter) {
|
|
|
480
495
|
usageBarRender,
|
|
481
496
|
content,
|
|
482
497
|
hotkeyRow,
|
|
498
|
+
extraFooterRows,
|
|
499
|
+
maxRows,
|
|
483
500
|
});
|
|
484
501
|
}
|
|
485
502
|
export function renderSummary(swarm) {
|
package/dist/run.d.ts
CHANGED
|
@@ -17,6 +17,12 @@ export interface RunConfig extends RunConfigBase {
|
|
|
17
17
|
cwd: string;
|
|
18
18
|
/** Allowlist of SDK tool names agents are permitted to use. */
|
|
19
19
|
allowedTools?: string[];
|
|
20
|
+
/** Shell command(s) to run in cwd before each wave starts (e.g. "pnpm run generate"). */
|
|
21
|
+
beforeWave?: string | string[];
|
|
22
|
+
/** Shell command(s) to run in cwd after each wave completes (e.g. "supabase db push"). */
|
|
23
|
+
afterWave?: string | string[];
|
|
24
|
+
/** Shell command(s) to run in cwd once after the entire run finishes (e.g. "vercel deploy"). */
|
|
25
|
+
afterRun?: string | string[];
|
|
20
26
|
/** Persisted run directory path. */
|
|
21
27
|
runDir: string;
|
|
22
28
|
/** Knowledge about the codebase from a pre-run thinking wave. */
|
package/dist/run.js
CHANGED
|
@@ -17,7 +17,7 @@ export async function executeRun(cfg) {
|
|
|
17
17
|
process.stdout.write("\x1B[?25h\n");
|
|
18
18
|
}
|
|
19
19
|
catch { } };
|
|
20
|
-
const { objective, cwd, workerModel, plannerModel, fastModel, concurrency, permissionMode, allowedTools, runDir, previousKnowledge, } = cfg;
|
|
20
|
+
const { objective, cwd, workerModel, plannerModel, fastModel, concurrency, permissionMode, allowedTools, beforeWave: beforeWaveCmds, afterWave: afterWaveCmds, afterRun: afterRunCmds, runDir, previousKnowledge, } = cfg;
|
|
21
21
|
const envForModel = buildEnvResolver({
|
|
22
22
|
plannerModel, plannerProvider: cfg.plannerProvider,
|
|
23
23
|
workerModel, workerProvider: cfg.workerProvider,
|
|
@@ -170,6 +170,21 @@ export async function executeRun(cfg) {
|
|
|
170
170
|
else
|
|
171
171
|
display.updateSteeringStatus(text);
|
|
172
172
|
};
|
|
173
|
+
const runDebrief = (label) => {
|
|
174
|
+
const debriefModel = fastModel || workerModel;
|
|
175
|
+
const memory = readRunMemory(runDir, previousKnowledge || undefined);
|
|
176
|
+
const cap = (s, n) => s && s.length > n ? s.slice(0, n) + "β¦" : (s || "");
|
|
177
|
+
const ctx = [
|
|
178
|
+
objective ? `Objective: ${objective}` : "",
|
|
179
|
+
memory.status ? `Status:\n${cap(memory.status, 800)}` : "",
|
|
180
|
+
waveHistory.length ? `Waves done: ${waveHistory.length}` : "",
|
|
181
|
+
memory.reflections ? `Reflections:\n${cap(memory.reflections, 600)}` : "",
|
|
182
|
+
].filter(Boolean).join("\n\n");
|
|
183
|
+
const prompt = `${label}\n\n${ctx}\n\nWrite one short sentence (max 120 chars) summarising progress and what's next. No preamble.`;
|
|
184
|
+
void runPlannerQuery(prompt, { cwd, model: debriefModel, permissionMode }, () => { })
|
|
185
|
+
.then(text => { display.setDebrief(text.trim().slice(0, 140)); })
|
|
186
|
+
.catch(() => { });
|
|
187
|
+
};
|
|
173
188
|
// For flex + branch strategy: create one target branch
|
|
174
189
|
let runBranch;
|
|
175
190
|
let originalRef;
|
|
@@ -359,6 +374,22 @@ export async function executeRun(cfg) {
|
|
|
359
374
|
await throttleBeforeWave(() => getPlannerRateLimitInfo(), (text) => display.appendSteeringEvent(text), () => stopping);
|
|
360
375
|
if (stopping)
|
|
361
376
|
break;
|
|
377
|
+
// Before-wave commands: run in cwd before each wave starts (e.g. generate types from schema).
|
|
378
|
+
if (beforeWaveCmds) {
|
|
379
|
+
const cmds = Array.isArray(beforeWaveCmds) ? beforeWaveCmds : [beforeWaveCmds];
|
|
380
|
+
for (const cmd of cmds) {
|
|
381
|
+
display.appendSteeringEvent(`Before-wave: ${cmd}`);
|
|
382
|
+
try {
|
|
383
|
+
const out = execSync(cmd, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
384
|
+
if (out.trim())
|
|
385
|
+
display.appendSteeringEvent(` β ${out.trim().slice(0, 200)}`);
|
|
386
|
+
}
|
|
387
|
+
catch (err) {
|
|
388
|
+
const msg = (err.stderr || err.stdout || err.message || "").trim().slice(0, 300);
|
|
389
|
+
display.appendSteeringEvent(` β ${cmd}: ${msg}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
362
393
|
const swarm = new Swarm({
|
|
363
394
|
tasks: currentTasks, concurrency, cwd, model: workerModel, permissionMode, allowedTools,
|
|
364
395
|
useWorktrees, mergeStrategy: waveMerge, agentTimeoutMs: cfg.agentTimeoutMs,
|
|
@@ -424,6 +455,25 @@ export async function executeRun(cfg) {
|
|
|
424
455
|
wave: waveNum,
|
|
425
456
|
tasks: swarm.agents.map(a => ({ prompt: a.task.prompt, status: a.status, filesChanged: a.filesChanged, error: a.error })),
|
|
426
457
|
});
|
|
458
|
+
// Fire-and-forget debrief after each wave.
|
|
459
|
+
runDebrief(`Wave ${waveNum + 1} just finished.`);
|
|
460
|
+
// After-wave commands: run shell commands in cwd after each wave (e.g. "supabase db push").
|
|
461
|
+
// Runs regardless of flex mode so migrations are applied before review/steering.
|
|
462
|
+
if (afterWaveCmds && !swarm.aborted && !swarm.cappedOut) {
|
|
463
|
+
const cmds = Array.isArray(afterWaveCmds) ? afterWaveCmds : [afterWaveCmds];
|
|
464
|
+
for (const cmd of cmds) {
|
|
465
|
+
display.appendSteeringEvent(`After-wave: ${cmd}`);
|
|
466
|
+
try {
|
|
467
|
+
const out = execSync(cmd, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
468
|
+
if (out.trim())
|
|
469
|
+
display.appendSteeringEvent(` β ${out.trim().slice(0, 200)}`);
|
|
470
|
+
}
|
|
471
|
+
catch (err) {
|
|
472
|
+
const msg = (err.stderr || err.stdout || err.message || "").trim().slice(0, 300);
|
|
473
|
+
display.appendSteeringEvent(` β ${cmd}: ${msg}`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
427
477
|
// Post-wave review: a single review agent against the consolidated diff.
|
|
428
478
|
// Runs only when there was real work (not first wave, not abort/cap).
|
|
429
479
|
if (flex && remaining > 0 && !swarm.aborted && !swarm.cappedOut && waveNum > 0) {
|
|
@@ -530,6 +580,23 @@ export async function executeRun(cfg) {
|
|
|
530
580
|
}
|
|
531
581
|
catch { }
|
|
532
582
|
}
|
|
583
|
+
// After-run commands: run once after the entire run finishes (e.g. deploy).
|
|
584
|
+
if (afterRunCmds) {
|
|
585
|
+
const cmds = Array.isArray(afterRunCmds) ? afterRunCmds : [afterRunCmds];
|
|
586
|
+
for (const cmd of cmds) {
|
|
587
|
+
console.log(chalk.dim(` After-run: ${cmd}`));
|
|
588
|
+
try {
|
|
589
|
+
const out = execSync(cmd, { cwd, encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"] });
|
|
590
|
+
if (out.trim())
|
|
591
|
+
console.log(chalk.dim(` β ${out.trim().slice(0, 300)}`));
|
|
592
|
+
}
|
|
593
|
+
catch (err) {
|
|
594
|
+
const msg = (err.stderr || err.stdout || err.message || "").trim().slice(0, 400);
|
|
595
|
+
console.log(chalk.red(` β ${cmd}: ${msg}`));
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
console.log("");
|
|
599
|
+
}
|
|
533
600
|
// ββ Final summary ββ
|
|
534
601
|
const waves = waveNum + 1;
|
|
535
602
|
const elapsed = Math.round((Date.now() - cfg.runStartedAt) / 1000);
|
package/dist/state.d.ts
CHANGED
|
@@ -53,7 +53,7 @@ export declare function formatTimeAgo(isoStr: string): string;
|
|
|
53
53
|
export declare function showRunHistory(allRuns: {
|
|
54
54
|
dir: string;
|
|
55
55
|
state: RunState;
|
|
56
|
-
}[], filterCwd: string): void
|
|
56
|
+
}[], filterCwd: string): Promise<void>;
|
|
57
57
|
export declare function readPreviousRunKnowledge(rootDir: string): string;
|
|
58
58
|
export declare function createRunDir(rootDir: string): string;
|
|
59
59
|
export declare function updateLatestSymlink(rootDir: string, runDir: string): void;
|
package/dist/state.js
CHANGED
|
@@ -4,6 +4,7 @@ import { join } from "path";
|
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import { forceMergeOverlay } from "./merge.js";
|
|
6
6
|
import { FALLBACK_MODEL } from "./models.js";
|
|
7
|
+
import { selectKey } from "./cli.js";
|
|
7
8
|
// ββ File I/O helpers ββ
|
|
8
9
|
export function readMdDir(dir) {
|
|
9
10
|
try {
|
|
@@ -193,12 +194,13 @@ export function findIncompleteRuns(rootDir, filterCwd) {
|
|
|
193
194
|
const state = loadRunState(runDir);
|
|
194
195
|
if (!state || state.phase === "done" || state.cwd !== filterCwd)
|
|
195
196
|
continue;
|
|
196
|
-
// Planning-phase runs are resumable if
|
|
197
|
-
//
|
|
198
|
-
//
|
|
197
|
+
// Planning-phase runs are resumable if: tasks.json exists (orchestrate
|
|
198
|
+
// completed), design docs exist (thinking wave produced output), or the
|
|
199
|
+
// run already consumed tokens (thinking wave started but was killed).
|
|
199
200
|
if (state.phase === "planning"
|
|
200
201
|
&& !existsSync(join(runDir, "tasks.json"))
|
|
201
|
-
&& !readMdDir(join(runDir, "designs"))
|
|
202
|
+
&& !readMdDir(join(runDir, "designs"))
|
|
203
|
+
&& state.accCompleted === 0 && state.accCost === 0)
|
|
202
204
|
continue;
|
|
203
205
|
results.push({ dir: runDir, state });
|
|
204
206
|
}
|
|
@@ -303,34 +305,54 @@ export function formatTimeAgo(isoStr) {
|
|
|
303
305
|
return `${hours}h ago`;
|
|
304
306
|
return `${Math.floor(hours / 24)}d ago`;
|
|
305
307
|
}
|
|
306
|
-
export function showRunHistory(allRuns, filterCwd) {
|
|
307
|
-
const runs = allRuns.filter(r => r.state.cwd === filterCwd);
|
|
308
|
+
export async function showRunHistory(allRuns, filterCwd) {
|
|
309
|
+
const runs = allRuns.filter(r => r.state.cwd === filterCwd && r.state.phase === "done");
|
|
308
310
|
if (runs.length === 0) {
|
|
309
|
-
console.log(chalk.dim("\n No
|
|
311
|
+
console.log(chalk.dim("\n No completed runs.\n"));
|
|
310
312
|
return;
|
|
311
313
|
}
|
|
312
|
-
const
|
|
313
|
-
|
|
314
|
-
let
|
|
315
|
-
|
|
316
|
-
const
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
314
|
+
const PAGE = 5;
|
|
315
|
+
const pages = Math.ceil(runs.length / PAGE);
|
|
316
|
+
let page = 0;
|
|
317
|
+
while (true) {
|
|
318
|
+
const w = Math.min((process.stdout.columns ?? 80) - 6, 50);
|
|
319
|
+
const pageLabel = pages > 1 ? ` (${page + 1}/${pages})` : "";
|
|
320
|
+
console.log(chalk.dim(`\n ββ Run History${pageLabel} ${"β".repeat(Math.max(0, w - 16 - pageLabel.length))}\n`));
|
|
321
|
+
for (const run of runs.slice(page * PAGE, (page + 1) * PAGE)) {
|
|
322
|
+
const s = run.state;
|
|
323
|
+
const date = s.startedAt?.slice(0, 16).replace("T", " ") || "unknown";
|
|
324
|
+
const cost = s.accCost > 0 ? ` Β· $${s.accCost.toFixed(2)}` : "";
|
|
325
|
+
const obj = s.objective?.slice(0, 50) || "";
|
|
326
|
+
const merged = s.branches.filter(b => b.status === "merged").length;
|
|
327
|
+
console.log(` ${chalk.green("β")} ${chalk.dim(date)} Β· ${s.accCompleted}/${s.budget}${cost}${merged ? ` Β· ${merged} merged` : ""}`);
|
|
328
|
+
console.log(` ${obj}${obj.length >= 50 ? "β¦" : ""}`);
|
|
329
|
+
let status = "";
|
|
330
|
+
try {
|
|
331
|
+
status = readFileSync(join(run.dir, "status.md"), "utf-8").trim().split("\n")[0].slice(0, 70);
|
|
332
|
+
}
|
|
333
|
+
catch { }
|
|
334
|
+
if (status)
|
|
335
|
+
console.log(chalk.dim(` ${status}`));
|
|
336
|
+
console.log("");
|
|
337
|
+
}
|
|
338
|
+
if (pages === 1)
|
|
339
|
+
break;
|
|
340
|
+
const opts = [];
|
|
341
|
+
if (page < pages - 1)
|
|
342
|
+
opts.push({ key: "n", desc: "ext" });
|
|
343
|
+
if (page > 0)
|
|
344
|
+
opts.push({ key: "p", desc: "rev" });
|
|
345
|
+
opts.push({ key: "b", desc: "ack" });
|
|
346
|
+
const action = await selectKey("", opts);
|
|
347
|
+
if (action === "n") {
|
|
348
|
+
page++;
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (action === "p") {
|
|
352
|
+
page--;
|
|
353
|
+
continue;
|
|
329
354
|
}
|
|
330
|
-
|
|
331
|
-
if (status)
|
|
332
|
-
console.log(chalk.dim(` ${status}`));
|
|
333
|
-
console.log("");
|
|
355
|
+
break;
|
|
334
356
|
}
|
|
335
357
|
}
|
|
336
358
|
export function readPreviousRunKnowledge(rootDir) {
|
package/dist/types.d.ts
CHANGED
|
@@ -25,6 +25,12 @@ export interface TaskFile {
|
|
|
25
25
|
permissionMode?: PermMode;
|
|
26
26
|
/** Allowlist of SDK tool names agents are permitted to use. */
|
|
27
27
|
allowedTools?: string[];
|
|
28
|
+
/** Shell command(s) to run in cwd before each wave starts (e.g. "pnpm run generate"). */
|
|
29
|
+
beforeWave?: string | string[];
|
|
30
|
+
/** Shell command(s) to run in cwd after each wave completes (e.g. "supabase db push"). */
|
|
31
|
+
afterWave?: string | string[];
|
|
32
|
+
/** Shell command(s) to run in cwd once after the entire run finishes (e.g. "vercel deploy"). */
|
|
33
|
+
afterRun?: string | string[];
|
|
28
34
|
/** Merge strategy: "yolo" merges into current branch, "branch" creates a new branch. */
|
|
29
35
|
mergeStrategy?: MergeStrategy;
|
|
30
36
|
/** Stop dispatching new tasks when rate-limit utilization reaches this percentage (0-100). */
|
|
@@ -183,6 +189,12 @@ export interface RunConfigBase {
|
|
|
183
189
|
flex: boolean;
|
|
184
190
|
/** Use git worktree isolation for agents. */
|
|
185
191
|
useWorktrees: boolean;
|
|
192
|
+
/** Shell command(s) to run in cwd before each wave starts (e.g. "pnpm run generate"). */
|
|
193
|
+
beforeWave?: string | string[];
|
|
194
|
+
/** Shell command(s) to run in cwd after each wave completes (e.g. "supabase db push"). */
|
|
195
|
+
afterWave?: string | string[];
|
|
196
|
+
/** Shell command(s) to run in cwd once after the entire run finishes (e.g. "vercel deploy"). */
|
|
197
|
+
afterRun?: string | string[];
|
|
186
198
|
/** How worktree branches are merged. */
|
|
187
199
|
mergeStrategy: MergeStrategy;
|
|
188
200
|
}
|
package/dist/ui.d.ts
CHANGED
|
@@ -74,6 +74,11 @@ export declare class RunDisplay {
|
|
|
74
74
|
private navState;
|
|
75
75
|
private onSteer?;
|
|
76
76
|
private onAsk?;
|
|
77
|
+
private debriefText?;
|
|
78
|
+
/** Get the latest debrief line for footer rendering. */
|
|
79
|
+
getDebrief(): string | undefined;
|
|
80
|
+
/** Set or clear the debrief text shown in the footer. */
|
|
81
|
+
setDebrief(text: string | undefined): void;
|
|
77
82
|
constructor(runInfo: RunInfo, liveConfig?: LiveConfig, callbacks?: {
|
|
78
83
|
onSteer?: (text: string) => void;
|
|
79
84
|
onAsk?: (text: string) => void;
|
|
@@ -113,7 +118,9 @@ export declare class RunDisplay {
|
|
|
113
118
|
resume(): void;
|
|
114
119
|
stop(): void;
|
|
115
120
|
private resumeInterval;
|
|
116
|
-
/** Write the full frame to stdout, clamped to terminal height.
|
|
121
|
+
/** Write the full frame to stdout, clamped to terminal height.
|
|
122
|
+
* Layout: header + content (elastic) + footer + input/ask (fixed).
|
|
123
|
+
* The content area shrinks so input prompts are never clipped. */
|
|
117
124
|
private flush;
|
|
118
125
|
private render;
|
|
119
126
|
private renderInputPrompt;
|
|
@@ -124,16 +131,16 @@ export declare class RunDisplay {
|
|
|
124
131
|
private handlePaste;
|
|
125
132
|
/** Handle a typed (non-pasted) chunk. Returns true if the frame needs a redraw.
|
|
126
133
|
*
|
|
127
|
-
* Demux pipeline -- routes
|
|
134
|
+
* Demux pipeline -- routes escape sequences and modifiers BEFORE hotkey matching:
|
|
128
135
|
* Raw stdin chunk β splitPaste
|
|
129
|
-
* ββ paste β handlePaste
|
|
136
|
+
* ββ paste β handlePaste
|
|
130
137
|
* ββ typed β demux
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
138
|
+
* 1. ESC + [A/B/C/D β navigate; other CSI β swallow
|
|
139
|
+
* 2. ESC + non-[ β Alt/Option+key β swallow
|
|
140
|
+
* 3. ESC alone β cancel input / close detail / dismiss panel
|
|
141
|
+
* 4. numeric input β digits, Enter, Backspace
|
|
142
|
+
* 5. text input β printable chars, Enter, Backspace, ESC (with lookahead)
|
|
143
|
+
* 6. hotkey mode β b, t, c, e, p, s, q, ?, d, 0-9
|
|
137
144
|
*/
|
|
138
145
|
private handleTyped;
|
|
139
146
|
private plainTick;
|
package/dist/ui.js
CHANGED
|
@@ -34,6 +34,11 @@ export class RunDisplay {
|
|
|
34
34
|
navState = { focusSection: 0, focusRow: 0, scrollOffset: 0 };
|
|
35
35
|
onSteer;
|
|
36
36
|
onAsk;
|
|
37
|
+
debriefText;
|
|
38
|
+
/** Get the latest debrief line for footer rendering. */
|
|
39
|
+
getDebrief() { return this.debriefText; }
|
|
40
|
+
/** Set or clear the debrief text shown in the footer. */
|
|
41
|
+
setDebrief(text) { this.debriefText = text; }
|
|
37
42
|
constructor(runInfo, liveConfig, callbacks) {
|
|
38
43
|
this.runInfo = runInfo;
|
|
39
44
|
this.liveConfig = liveConfig;
|
|
@@ -345,37 +350,42 @@ export class RunDisplay {
|
|
|
345
350
|
}
|
|
346
351
|
this.interval = setInterval(() => this.flush(), 250);
|
|
347
352
|
}
|
|
348
|
-
/** Write the full frame to stdout, clamped to terminal height.
|
|
353
|
+
/** Write the full frame to stdout, clamped to terminal height.
|
|
354
|
+
* Layout: header + content (elastic) + footer + input/ask (fixed).
|
|
355
|
+
* The content area shrinks so input prompts are never clipped. */
|
|
349
356
|
flush() {
|
|
350
357
|
try {
|
|
351
358
|
const maxRows = (process.stdout.rows || 40) - 1;
|
|
352
|
-
const frame = this.render();
|
|
353
|
-
|
|
354
|
-
process.stdout.write("\x1B[H\x1B[J");
|
|
355
|
-
process.stdout.write(lines.length > maxRows ? lines.slice(0, maxRows).join("\n") : frame);
|
|
359
|
+
const frame = this.render(maxRows);
|
|
360
|
+
process.stdout.write("\x1B[H\x1B[J" + frame);
|
|
356
361
|
}
|
|
357
362
|
catch {
|
|
358
363
|
this.pause();
|
|
359
364
|
}
|
|
360
365
|
}
|
|
361
|
-
render() {
|
|
366
|
+
render(maxRows) {
|
|
367
|
+
// Compute how many rows the input prompt + ask panel need so the
|
|
368
|
+
// main frame can shrink its content area to leave room.
|
|
369
|
+
const inputPrompt = this.renderInputPrompt();
|
|
370
|
+
const askPanel = this.renderAskPanel();
|
|
371
|
+
const bottom = inputPrompt + askPanel;
|
|
372
|
+
const bottomRows = bottom ? (bottom.match(/\n/g) || []).length : 0;
|
|
373
|
+
const frameBudget = maxRows != null ? maxRows - bottomRows : undefined;
|
|
362
374
|
let frame = "";
|
|
363
375
|
if (this.swarm) {
|
|
364
|
-
frame = renderFrame(this.swarm, this.hasHotkeys(), this.runInfo, this.selectedAgentId);
|
|
376
|
+
frame = renderFrame(this.swarm, this.hasHotkeys(), this.runInfo, this.selectedAgentId, frameBudget, this.debriefText);
|
|
365
377
|
}
|
|
366
378
|
else if (this.steeringActive) {
|
|
367
379
|
frame = renderSteeringFrame(this.runInfo, {
|
|
368
380
|
statusLine: this.steeringStatusLine,
|
|
369
381
|
events: this.steeringEvents,
|
|
370
382
|
context: this.steeringContext,
|
|
371
|
-
}, this.hasHotkeys(), this.rlGetter);
|
|
383
|
+
}, this.hasHotkeys(), this.rlGetter, frameBudget, this.debriefText);
|
|
372
384
|
}
|
|
373
385
|
else {
|
|
374
386
|
return "";
|
|
375
387
|
}
|
|
376
|
-
frame
|
|
377
|
-
frame += this.renderAskPanel();
|
|
378
|
-
return frame;
|
|
388
|
+
return frame + bottom;
|
|
379
389
|
}
|
|
380
390
|
renderInputPrompt() {
|
|
381
391
|
if (this.inputMode === "none")
|
|
@@ -482,16 +492,16 @@ export class RunDisplay {
|
|
|
482
492
|
}
|
|
483
493
|
/** Handle a typed (non-pasted) chunk. Returns true if the frame needs a redraw.
|
|
484
494
|
*
|
|
485
|
-
* Demux pipeline -- routes
|
|
495
|
+
* Demux pipeline -- routes escape sequences and modifiers BEFORE hotkey matching:
|
|
486
496
|
* Raw stdin chunk β splitPaste
|
|
487
|
-
* ββ paste β handlePaste
|
|
497
|
+
* ββ paste β handlePaste
|
|
488
498
|
* ββ typed β demux
|
|
489
|
-
*
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
499
|
+
* 1. ESC + [A/B/C/D β navigate; other CSI β swallow
|
|
500
|
+
* 2. ESC + non-[ β Alt/Option+key β swallow
|
|
501
|
+
* 3. ESC alone β cancel input / close detail / dismiss panel
|
|
502
|
+
* 4. numeric input β digits, Enter, Backspace
|
|
503
|
+
* 5. text input β printable chars, Enter, Backspace, ESC (with lookahead)
|
|
504
|
+
* 6. hotkey mode β b, t, c, e, p, s, q, ?, d, 0-9
|
|
495
505
|
*/
|
|
496
506
|
handleTyped(s) {
|
|
497
507
|
const lc = this.liveConfig;
|
|
@@ -517,7 +527,11 @@ export class RunDisplay {
|
|
|
517
527
|
// Other ANSI sequences -- swallow silently
|
|
518
528
|
return true;
|
|
519
529
|
}
|
|
520
|
-
// ββ 2.
|
|
530
|
+
// ββ 2. Alt/Option+key: \x1B followed by a non-bracket byte (e.g. \x1Bb, \x1Bf) ββ
|
|
531
|
+
if (s.length >= 2 && s[0] === "\x1B" && s[1] !== "[") {
|
|
532
|
+
return false; // swallow β don't cancel input, don't trigger hotkeys
|
|
533
|
+
}
|
|
534
|
+
// ββ 3. Standalone ESC ββ
|
|
521
535
|
if (s === "\x1B") {
|
|
522
536
|
if (this.inputMode !== "none") {
|
|
523
537
|
this.inputMode = "none";
|
|
@@ -535,7 +549,7 @@ export class RunDisplay {
|
|
|
535
549
|
}
|
|
536
550
|
return false;
|
|
537
551
|
}
|
|
538
|
-
// ββ
|
|
552
|
+
// ββ 4. Input mode: budget / threshold / concurrency / extra ββ
|
|
539
553
|
if (this.inputMode === "budget" || this.inputMode === "threshold" || this.inputMode === "concurrency" || this.inputMode === "extra") {
|
|
540
554
|
let dirty = false;
|
|
541
555
|
for (const ch of s) {
|
|
@@ -586,7 +600,7 @@ export class RunDisplay {
|
|
|
586
600
|
}
|
|
587
601
|
return dirty;
|
|
588
602
|
}
|
|
589
|
-
// ββ
|
|
603
|
+
// ββ 5. Input mode: steer / ask ββ
|
|
590
604
|
if (this.inputMode === "steer" || this.inputMode === "ask") {
|
|
591
605
|
let dirty = false;
|
|
592
606
|
for (let ci = 0; ci < s.length; ci++) {
|
|
@@ -609,9 +623,13 @@ export class RunDisplay {
|
|
|
609
623
|
this.inputSegs = [];
|
|
610
624
|
return true;
|
|
611
625
|
}
|
|
612
|
-
// ESC
|
|
613
|
-
//
|
|
626
|
+
// ESC: if next byte exists it's part of an Alt+key sequence β skip both.
|
|
627
|
+
// Standalone ESC (no following byte) cancels input mode.
|
|
614
628
|
if (ch === "\x1B") {
|
|
629
|
+
if (ci + 1 < s.length) {
|
|
630
|
+
ci++;
|
|
631
|
+
continue;
|
|
632
|
+
}
|
|
615
633
|
this.inputMode = "none";
|
|
616
634
|
this.inputSegs = [];
|
|
617
635
|
return true;
|
|
@@ -633,7 +651,7 @@ export class RunDisplay {
|
|
|
633
651
|
}
|
|
634
652
|
return dirty;
|
|
635
653
|
}
|
|
636
|
-
// ββ
|
|
654
|
+
// ββ 6. Hotkey mode ββ
|
|
637
655
|
// Enter
|
|
638
656
|
if (s === "\r" || s === "\n") {
|
|
639
657
|
if (this.askTempFile) {
|
|
@@ -717,7 +735,7 @@ export class RunDisplay {
|
|
|
717
735
|
if (this.askState && !this.askState.streaming) {
|
|
718
736
|
this.askState = undefined;
|
|
719
737
|
this.clearAskTempFile();
|
|
720
|
-
return
|
|
738
|
+
return true;
|
|
721
739
|
}
|
|
722
740
|
this.inputMode = "ask";
|
|
723
741
|
this.inputSegs = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-overnight",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.6",
|
|
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.25.
|
|
3
|
+
"version": "1.25.6",
|
|
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"
|
|
@@ -31,6 +31,20 @@ claude-overnight "task a" "task b" # inline
|
|
|
31
31
|
|
|
32
32
|
Common flags: `--budget=N`, `--concurrency=N`, `--model=<name>`, `--usage-cap=N`, `--allow-extra-usage`, `--extra-usage-budget=N`, `--timeout=SECONDS`, `--no-flex`, `--dry-run`.
|
|
33
33
|
|
|
34
|
+
Task file supports lifecycle hooks β shell commands run in `cwd` at key points:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"objective": "...",
|
|
39
|
+
"beforeWave": "pnpm run db:generate",
|
|
40
|
+
"afterWave": "supabase db push",
|
|
41
|
+
"afterRun": "vercel deploy --prod",
|
|
42
|
+
"tasks": []
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`beforeWave` runs before each wave starts Β· `afterWave` runs after workers merge (before review/steering) Β· `afterRun` runs once after the entire run. All accept a string or `string[]`. Failures are surfaced but never abort the run.
|
|
47
|
+
|
|
34
48
|
Live keys while running: `b` change budget Β· `t` change usage cap Β· `q` graceful stop (twice = force).
|
|
35
49
|
|
|
36
50
|
Exit codes: `0` all ok Β· `1` some failed Β· `2` all/none.
|