claude-overnight 1.25.43 → 1.25.46
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/bin.js +1 -1
- package/dist/{cli.d.ts → cli/cli.d.ts} +19 -4
- package/dist/{cli.js → cli/cli.js} +38 -7
- package/dist/cli/help.d.ts +2 -0
- package/dist/cli/help.js +44 -0
- package/dist/cli/plan-phase.d.ts +38 -0
- package/dist/cli/plan-phase.js +290 -0
- package/dist/cli/preflight.d.ts +15 -0
- package/dist/cli/preflight.js +139 -0
- package/dist/cli/resume.d.ts +27 -0
- package/dist/cli/resume.js +312 -0
- package/dist/{settings.d.ts → cli/settings.d.ts} +1 -2
- package/dist/{settings.js → cli/settings.js} +2 -13
- package/dist/core/_version.d.ts +1 -0
- package/dist/{_version.js → core/_version.js} +1 -1
- package/dist/core/auth.d.ts +20 -0
- package/dist/core/auth.js +20 -0
- package/dist/core/jwt-signer.d.ts +59 -0
- package/dist/core/jwt-signer.js +124 -0
- package/dist/core/key-vault.d.ts +11 -0
- package/dist/core/key-vault.js +63 -0
- package/dist/core/rate-limiter.d.ts +48 -0
- package/dist/core/rate-limiter.js +116 -0
- package/dist/core/secret-manager.d.ts +8 -0
- package/dist/core/secret-manager.js +51 -0
- package/dist/core/token-cache.d.ts +56 -0
- package/dist/core/token-cache.js +120 -0
- package/dist/core/token-manager.d.ts +32 -0
- package/dist/core/token-manager.js +130 -0
- package/dist/{types.d.ts → core/types.d.ts} +7 -12
- package/dist/index.js +51 -776
- package/dist/middleware/index.d.ts +1 -0
- package/dist/middleware/index.js +1 -0
- package/dist/middleware/rate-limit.d.ts +49 -0
- package/dist/middleware/rate-limit.js +65 -0
- package/dist/planner/coach/coach.d.ts +19 -0
- package/dist/planner/coach/coach.js +215 -0
- package/dist/planner/coach/context.d.ts +23 -0
- package/dist/planner/coach/context.js +154 -0
- package/dist/planner/coach/schema.d.ts +111 -0
- package/dist/planner/coach/schema.js +101 -0
- package/dist/planner/coach/settings.d.ts +8 -0
- package/dist/planner/coach/settings.js +24 -0
- package/dist/planner/json.d.ts +4 -0
- package/dist/planner/json.js +106 -0
- package/dist/{planner.d.ts → planner/planner.d.ts} +5 -5
- package/dist/{planner.js → planner/planner.js} +35 -19
- package/dist/planner/postprocess.d.ts +2 -0
- package/dist/planner/postprocess.js +106 -0
- package/dist/planner/query.d.ts +28 -0
- package/dist/{planner-query.js → planner/query.js} +47 -332
- package/dist/{steering.d.ts → planner/steering.d.ts} +3 -3
- package/dist/{steering.js → planner/steering.js} +7 -7
- package/dist/planner/throttle.d.ts +48 -0
- package/dist/planner/throttle.js +87 -0
- package/dist/providers/cursor-env.d.ts +53 -0
- package/dist/providers/cursor-env.js +283 -0
- package/dist/providers/cursor-picker.d.ts +7 -0
- package/dist/providers/cursor-picker.js +306 -0
- package/dist/providers/cursor-proxy.d.ts +26 -0
- package/dist/providers/cursor-proxy.js +459 -0
- package/dist/providers/index.d.ts +70 -0
- package/dist/providers/index.js +324 -0
- package/dist/run/budget.d.ts +6 -0
- package/dist/run/budget.js +33 -0
- package/dist/run/circuit-breaker-state.d.ts +16 -0
- package/dist/run/circuit-breaker-state.js +18 -0
- package/dist/run/health.d.ts +5 -0
- package/dist/run/health.js +71 -0
- package/dist/run/review.d.ts +24 -0
- package/dist/run/review.js +32 -0
- package/dist/{run.d.ts → run/run.d.ts} +2 -2
- package/dist/run/run.js +576 -0
- package/dist/run/summary.d.ts +38 -0
- package/dist/run/summary.js +174 -0
- package/dist/run/throttle.d.ts +21 -0
- package/dist/run/throttle.js +48 -0
- package/dist/run/wave-loop.d.ts +71 -0
- package/dist/run/wave-loop.js +458 -0
- package/dist/{state.d.ts → state/state.d.ts} +1 -1
- package/dist/{state.js → state/state.js} +4 -4
- package/dist/swarm/agent-run.d.ts +34 -0
- package/dist/swarm/agent-run.js +475 -0
- package/dist/swarm/config.d.ts +38 -0
- package/dist/swarm/config.js +50 -0
- package/dist/swarm/errors.d.ts +13 -0
- package/dist/swarm/errors.js +54 -0
- package/dist/swarm/merge-autocommit.d.ts +1 -0
- package/dist/swarm/merge-autocommit.js +93 -0
- package/dist/swarm/merge-helpers.d.ts +15 -0
- package/dist/swarm/merge-helpers.js +175 -0
- package/dist/{merge.d.ts → swarm/merge.d.ts} +4 -17
- package/dist/swarm/merge.js +180 -0
- package/dist/swarm/message-handler.d.ts +39 -0
- package/dist/swarm/message-handler.js +215 -0
- package/dist/{swarm.d.ts → swarm/swarm.d.ts} +35 -50
- package/dist/swarm/swarm.js +473 -0
- package/dist/ui/bars.d.ts +12 -0
- package/dist/ui/bars.js +183 -0
- package/dist/ui/footer-state.d.ts +12 -0
- package/dist/ui/footer-state.js +93 -0
- package/dist/ui/footer.d.ts +11 -0
- package/dist/ui/footer.js +42 -0
- package/dist/ui/header.d.ts +14 -0
- package/dist/ui/header.js +91 -0
- package/dist/ui/input.d.ts +17 -0
- package/dist/ui/input.js +346 -0
- package/dist/ui/overlay.d.ts +11 -0
- package/dist/ui/overlay.js +54 -0
- package/dist/ui/primitives.d.ts +33 -0
- package/dist/ui/primitives.js +111 -0
- package/dist/ui/run-body.d.ts +6 -0
- package/dist/ui/run-body.js +169 -0
- package/dist/ui/settings.d.ts +13 -0
- package/dist/ui/settings.js +112 -0
- package/dist/ui/shell.d.ts +8 -0
- package/dist/ui/shell.js +34 -0
- package/dist/ui/steering-body.d.ts +13 -0
- package/dist/ui/steering-body.js +101 -0
- package/dist/ui/store.d.ts +66 -0
- package/dist/ui/store.js +44 -0
- package/dist/ui/summary.d.ts +2 -0
- package/dist/ui/summary.js +71 -0
- package/dist/ui/types.d.ts +50 -0
- package/dist/ui/types.js +4 -0
- package/dist/ui/ui.d.ts +46 -0
- package/dist/ui/ui.js +260 -0
- package/docs/CURSOR_PROXY.md +63 -0
- package/package.json +7 -4
- package/plugins/claude-overnight/.claude-plugin/plugin.json +1 -1
- package/plugins/lsp-first/.claude-plugin/plugin.json +18 -0
- package/plugins/lsp-first/README.md +38 -0
- package/plugins/lsp-first/hooks/lsp-first-guard.js +112 -0
- package/dist/_version.d.ts +0 -1
- package/dist/auth.d.ts +0 -19
- package/dist/auth.js +0 -82
- package/dist/coach.d.ts +0 -49
- package/dist/coach.js +0 -498
- package/dist/interactive-panel.d.ts +0 -42
- package/dist/interactive-panel.js +0 -169
- package/dist/merge.js +0 -442
- package/dist/planner-query.d.ts +0 -60
- package/dist/providers.d.ts +0 -159
- package/dist/providers.js +0 -1442
- package/dist/render.d.ts +0 -79
- package/dist/render.js +0 -768
- package/dist/run.js +0 -1257
- package/dist/swarm.js +0 -1059
- package/dist/test-coach.d.ts +0 -1
- package/dist/ui.d.ts +0 -162
- package/dist/ui.js +0 -1011
- /package/dist/{cursor-models.d.ts → core/cursor-models.d.ts} +0 -0
- /package/dist/{cursor-models.js → core/cursor-models.js} +0 -0
- /package/dist/{models.d.ts → core/models.d.ts} +0 -0
- /package/dist/{models.js → core/models.js} +0 -0
- /package/dist/{proxy-port.d.ts → core/proxy-port.d.ts} +0 -0
- /package/dist/{proxy-port.js → core/proxy-port.js} +0 -0
- /package/dist/{transcripts.d.ts → core/transcripts.d.ts} +0 -0
- /package/dist/{transcripts.js → core/transcripts.js} +0 -0
- /package/dist/{turns.d.ts → core/turns.d.ts} +0 -0
- /package/dist/{turns.js → core/turns.js} +0 -0
- /package/dist/{types.js → core/types.js} +0 -0
package/dist/bin.js
CHANGED
|
@@ -30,7 +30,7 @@ if (process.stdout.isTTY && !quiet && !process.env.CLAUDE_OVERNIGHT_UPDATED) {
|
|
|
30
30
|
writeFileSync(tsFile, String(Date.now())); // stamp first so failures don't re-trigger
|
|
31
31
|
const { execFileSync, spawnSync } = await import("node:child_process");
|
|
32
32
|
const latest = execFileSync("npm", ["show", "claude-overnight", "version"], { encoding: "utf-8", timeout: 6000 }).trim();
|
|
33
|
-
const { VERSION } = await import("./_version.js");
|
|
33
|
+
const { VERSION } = await import("./core/_version.js");
|
|
34
34
|
if (latest !== VERSION) {
|
|
35
35
|
process.stdout.write(`\r\x1b[2K 🌙 claude-overnight \x1b[33m${VERSION} → ${latest}\x1b[0m updating…\n`);
|
|
36
36
|
execFileSync("npm", ["i", "-g", `claude-overnight@${latest}`], { stdio: "inherit", timeout: 60000 });
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { ModelInfo } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
-
import type { Task,
|
|
2
|
+
import type { Task, MergeStrategy } from "../core/types.js";
|
|
3
3
|
export declare function parseCliFlags(argv: string[]): {
|
|
4
4
|
flags: Record<string, string>;
|
|
5
5
|
positional: string[];
|
|
6
6
|
};
|
|
7
|
-
import { isJWTAuthError } from "
|
|
7
|
+
import { isJWTAuthError } from "../core/auth.js";
|
|
8
8
|
/** @deprecated Use isJWTAuthError from auth.ts instead. */
|
|
9
9
|
export declare const isAuthError: typeof isJWTAuthError;
|
|
10
10
|
export { isJWTAuthError };
|
|
@@ -51,7 +51,6 @@ export interface FileArgs {
|
|
|
51
51
|
objective?: string;
|
|
52
52
|
concurrency?: number;
|
|
53
53
|
model?: string;
|
|
54
|
-
permissionMode?: PermMode;
|
|
55
54
|
cwd?: string;
|
|
56
55
|
allowedTools?: string[];
|
|
57
56
|
beforeWave?: string | string[];
|
|
@@ -62,10 +61,26 @@ export interface FileArgs {
|
|
|
62
61
|
usageCap?: number;
|
|
63
62
|
flexiblePlan?: boolean;
|
|
64
63
|
}
|
|
64
|
+
/** Load a markdown plan file. Extracts the first H1 as objective and returns the full body as planContent. */
|
|
65
|
+
export declare function loadPlanFile(file: string): {
|
|
66
|
+
objective: string;
|
|
67
|
+
planContent: string;
|
|
68
|
+
};
|
|
65
69
|
export declare function loadTaskFile(file: string): FileArgs;
|
|
66
70
|
export declare function validateConcurrency(value: unknown): asserts value is number;
|
|
67
71
|
export declare function isGitRepo(cwd: string): boolean;
|
|
68
72
|
export declare function validateGitRepo(cwd: string): void;
|
|
69
73
|
export declare function showPlan(tasks: Task[]): void;
|
|
70
74
|
export declare const BRAILLE: readonly ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
71
|
-
|
|
75
|
+
/** Dual-mode progress renderer.
|
|
76
|
+
*
|
|
77
|
+
* - `status` (default): transient single-line ticker — clears itself each frame.
|
|
78
|
+
* Use for elapsed-time / cost / rolling tail of model text.
|
|
79
|
+
* - `event`: permanent log line — scrolls up, ticker redraws underneath.
|
|
80
|
+
* Use for tool calls and notable state changes.
|
|
81
|
+
*
|
|
82
|
+
* The two modes cooperate: an event clears the current ticker, writes the
|
|
83
|
+
* event on its own line, and the next status tick redraws the ticker below.
|
|
84
|
+
* That gives the user a visible history of what the planner did, with a live
|
|
85
|
+
* "now" indicator that always stays pinned at the bottom. */
|
|
86
|
+
export declare function makeProgressLog(): (text: string, kind?: "status" | "event") => void;
|
|
@@ -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", "merge"
|
|
10
|
-
const booleans = new Set(["--dry-run", "-h", "--help", "-v", "--version", "--no-flex", "--allow-extra-usage", "--worktrees", "--no-worktrees", "--yolo"]);
|
|
9
|
+
const known = new Set(["concurrency", "model", "timeout", "budget", "usage-cap", "extra-usage-budget", "merge"]);
|
|
10
|
+
const booleans = new Set(["--dry-run", "-h", "--help", "-v", "--version", "--flex", "--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++) {
|
|
@@ -30,7 +30,7 @@ export function parseCliFlags(argv) {
|
|
|
30
30
|
return { flags, positional };
|
|
31
31
|
}
|
|
32
32
|
// ── Auth error detection (re-exported from auth module for backward compatibility) ──
|
|
33
|
-
import { isJWTAuthError } from "
|
|
33
|
+
import { isJWTAuthError } from "../core/auth.js";
|
|
34
34
|
/** @deprecated Use isJWTAuthError from auth.ts instead. */
|
|
35
35
|
export const isAuthError = isJWTAuthError;
|
|
36
36
|
export { isJWTAuthError };
|
|
@@ -332,8 +332,25 @@ 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", "
|
|
335
|
+
"tasks", "objective", "concurrency", "cwd", "model", "allowedTools", "beforeWave", "afterWave", "afterRun", "worktrees", "mergeStrategy", "usageCap", "flexiblePlan",
|
|
336
336
|
]);
|
|
337
|
+
/** Load a markdown plan file. Extracts the first H1 as objective and returns the full body as planContent. */
|
|
338
|
+
export function loadPlanFile(file) {
|
|
339
|
+
const path = resolve(file);
|
|
340
|
+
let raw;
|
|
341
|
+
try {
|
|
342
|
+
raw = readFileSync(path, "utf-8");
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
throw new Error(`Cannot read plan file: ${path}`);
|
|
346
|
+
}
|
|
347
|
+
const body = raw.trim();
|
|
348
|
+
if (!body)
|
|
349
|
+
throw new Error(`Plan file is empty: ${path}`);
|
|
350
|
+
const h1 = body.match(/^#\s+(.+)$/m);
|
|
351
|
+
const objective = (h1?.[1] ?? body.split("\n").find(l => l.trim())).trim();
|
|
352
|
+
return { objective, planContent: body };
|
|
353
|
+
}
|
|
337
354
|
export function loadTaskFile(file) {
|
|
338
355
|
const path = resolve(file);
|
|
339
356
|
let raw;
|
|
@@ -392,7 +409,6 @@ export function loadTaskFile(file) {
|
|
|
392
409
|
concurrency: parsed.concurrency,
|
|
393
410
|
model: parsed.model,
|
|
394
411
|
cwd: parsed.cwd ? resolve(parsed.cwd) : undefined,
|
|
395
|
-
permissionMode: parsed.permissionMode,
|
|
396
412
|
allowedTools: parsed.allowedTools,
|
|
397
413
|
beforeWave: parsed.beforeWave,
|
|
398
414
|
afterWave: parsed.afterWave,
|
|
@@ -437,13 +453,28 @@ export function showPlan(tasks) {
|
|
|
437
453
|
console.log(chalk.dim(` ${"─".repeat(ruleLen)}\n`));
|
|
438
454
|
}
|
|
439
455
|
export const BRAILLE = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
456
|
+
/** Dual-mode progress renderer.
|
|
457
|
+
*
|
|
458
|
+
* - `status` (default): transient single-line ticker — clears itself each frame.
|
|
459
|
+
* Use for elapsed-time / cost / rolling tail of model text.
|
|
460
|
+
* - `event`: permanent log line — scrolls up, ticker redraws underneath.
|
|
461
|
+
* Use for tool calls and notable state changes.
|
|
462
|
+
*
|
|
463
|
+
* The two modes cooperate: an event clears the current ticker, writes the
|
|
464
|
+
* event on its own line, and the next status tick redraws the ticker below.
|
|
465
|
+
* That gives the user a visible history of what the planner did, with a live
|
|
466
|
+
* "now" indicator that always stays pinned at the bottom. */
|
|
440
467
|
export function makeProgressLog() {
|
|
441
468
|
let frame = 0;
|
|
442
|
-
return (text) => {
|
|
443
|
-
const spin = chalk.cyan(BRAILLE[frame++ % BRAILLE.length]);
|
|
469
|
+
return (text, kind = "status") => {
|
|
444
470
|
const maxW = (process.stdout.columns ?? 80) - 6;
|
|
445
471
|
const clean = text.replace(/\n/g, " ");
|
|
446
472
|
const line = clean.length > maxW ? clean.slice(0, maxW - 1) + "\u2026" : clean;
|
|
473
|
+
if (kind === "event") {
|
|
474
|
+
process.stdout.write(`\x1B[2K\r ${chalk.cyan("›")} ${chalk.dim(line)}\n`);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const spin = chalk.cyan(BRAILLE[frame++ % BRAILLE.length]);
|
|
447
478
|
process.stdout.write(`\x1B[2K\r ${spin} ${chalk.dim(line)}`);
|
|
448
479
|
};
|
|
449
480
|
}
|
package/dist/cli/help.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { dirname, join } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { VERSION } from "../core/_version.js";
|
|
6
|
+
export function printVersion() {
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const pkg = JSON.parse(readFileSync(join(__dirname, "..", "..", "package.json"), "utf-8"));
|
|
9
|
+
console.log(`claude-overnight v${pkg.version}`);
|
|
10
|
+
}
|
|
11
|
+
export function printHelp() {
|
|
12
|
+
console.log(`
|
|
13
|
+
${chalk.bold("🌙 claude-overnight")} ${chalk.dim("v" + VERSION + " -- background lane for your Claude Max plan")}
|
|
14
|
+
${chalk.dim("─".repeat(60))}
|
|
15
|
+
|
|
16
|
+
${chalk.cyan("Usage")}
|
|
17
|
+
claude-overnight ${chalk.dim("interactive mode")}
|
|
18
|
+
claude-overnight tasks.json ${chalk.dim("task file mode")}
|
|
19
|
+
claude-overnight "fix auth" "add tests" ${chalk.dim("inline tasks")}
|
|
20
|
+
|
|
21
|
+
${chalk.cyan("Flags")}
|
|
22
|
+
-h, --help Show this help
|
|
23
|
+
-v, --version Print version
|
|
24
|
+
--dry-run Show planned tasks without running them
|
|
25
|
+
--budget=N Target number of agent runs ${chalk.dim("(default: 10)")}
|
|
26
|
+
--concurrency=N Max parallel agents ${chalk.dim("(default: 5)")}
|
|
27
|
+
--model=NAME Worker model override ${chalk.dim("(interactive mode picks planner + worker separately -- supports 'Other…' for Qwen / OpenRouter / etc.)")}
|
|
28
|
+
--fast-model=NAME Fast worker model for quick tasks ${chalk.dim("(optional -- checked by next wave's workers)")}
|
|
29
|
+
--usage-cap=N Stop at N% utilization ${chalk.dim("(e.g. 90 to save 10% for other work)")}
|
|
30
|
+
--allow-extra-usage Allow extra/overage usage ${chalk.dim("(default: stop when plan limits hit)")}
|
|
31
|
+
--extra-usage-budget=N Max $ for extra usage ${chalk.dim("(implies --allow-extra-usage)")}
|
|
32
|
+
--timeout=SECONDS Agent inactivity timeout ${chalk.dim("(default: 900s, nudges at timeout, kills at 2×)")}
|
|
33
|
+
--no-flex Disable adaptive multi-wave planning ${chalk.dim("(run all tasks in one shot)")}
|
|
34
|
+
--worktrees Force worktree isolation on ${chalk.dim("(default: auto-detect git repo)")}
|
|
35
|
+
--no-worktrees Disable worktree isolation ${chalk.dim("(all agents work in real cwd)")}
|
|
36
|
+
--merge=MODE Merge strategy: yolo or branch ${chalk.dim("(default: yolo)")}
|
|
37
|
+
--yolo Shorthand for --no-worktrees
|
|
38
|
+
--no-coach Skip the setup coach ${chalk.dim("(raw objective, no preflight rewrite)")}
|
|
39
|
+
--coach-model Re-pick coach model ${chalk.dim("(overrides saved choice)")}
|
|
40
|
+
|
|
41
|
+
${chalk.cyan("Defaults")} ${chalk.dim("(non-interactive)")}
|
|
42
|
+
model: first available concurrency: 5 worktrees: auto merge: yolo
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ProviderConfig, EnvResolver } from "../providers/index.js";
|
|
2
|
+
import type { Task, MergeStrategy, WaveSummary } from "../core/types.js";
|
|
3
|
+
export interface PlanPhaseInput {
|
|
4
|
+
objective: string | undefined;
|
|
5
|
+
noTTY: boolean;
|
|
6
|
+
flex: boolean;
|
|
7
|
+
budget: number | undefined;
|
|
8
|
+
concurrency: number;
|
|
9
|
+
cwd: string;
|
|
10
|
+
plannerModel: string;
|
|
11
|
+
workerModel: string;
|
|
12
|
+
fastModel: string | undefined;
|
|
13
|
+
plannerProvider: ProviderConfig | undefined;
|
|
14
|
+
workerProvider: ProviderConfig | undefined;
|
|
15
|
+
fastProvider: ProviderConfig | undefined;
|
|
16
|
+
usageCap: number | undefined;
|
|
17
|
+
allowExtraUsage: boolean;
|
|
18
|
+
extraUsageBudget: number | undefined;
|
|
19
|
+
useWorktrees: boolean;
|
|
20
|
+
mergeStrategy: MergeStrategy;
|
|
21
|
+
agentTimeoutMs: number | undefined;
|
|
22
|
+
runDir: string;
|
|
23
|
+
designDir: string;
|
|
24
|
+
previousKnowledge: string;
|
|
25
|
+
envForModel: EnvResolver;
|
|
26
|
+
coachedOriginal: string | undefined;
|
|
27
|
+
coachedAt: number | undefined;
|
|
28
|
+
}
|
|
29
|
+
export interface PlanPhaseResult {
|
|
30
|
+
tasks: Task[];
|
|
31
|
+
thinkingHistory?: WaveSummary;
|
|
32
|
+
thinkingUsed: number;
|
|
33
|
+
thinkingCost: number;
|
|
34
|
+
thinkingIn: number;
|
|
35
|
+
thinkingOut: number;
|
|
36
|
+
thinkingTools: number;
|
|
37
|
+
}
|
|
38
|
+
export declare function runPlanPhase(input: PlanPhaseInput): Promise<PlanPhaseResult>;
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, mkdirSync, writeFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
5
|
+
import { Swarm } from "../swarm/swarm.js";
|
|
6
|
+
import { identifyThemes, buildThinkingTasks, orchestrate, planTasks, refinePlan } from "../planner/planner.js";
|
|
7
|
+
import { RunDisplay } from "../ui/ui.js";
|
|
8
|
+
import { renderSummary } from "../ui/summary.js";
|
|
9
|
+
import { isCursorProxyProvider } from "../providers/index.js";
|
|
10
|
+
import { readMdDir, saveRunState } from "../state/state.js";
|
|
11
|
+
import { selectKey, ask, showPlan, makeProgressLog, isJWTAuthError } from "./cli.js";
|
|
12
|
+
export async function runPlanPhase(input) {
|
|
13
|
+
const { objective, noTTY, flex, budget, concurrency, cwd, plannerModel, workerModel, fastModel, plannerProvider, workerProvider, fastProvider, usageCap, allowExtraUsage, extraUsageBudget, useWorktrees, mergeStrategy, agentTimeoutMs, runDir, designDir, previousKnowledge, envForModel, coachedOriginal, coachedAt, } = input;
|
|
14
|
+
let tasks = [];
|
|
15
|
+
let thinkingHistory;
|
|
16
|
+
let thinkingUsed = 0, thinkingCost = 0, thinkingIn = 0, thinkingOut = 0, thinkingTools = 0;
|
|
17
|
+
// Persist an early planning-phase state so the run is visible to the resume
|
|
18
|
+
// picker even if orchestrate dies before executeRun gets a chance to run.
|
|
19
|
+
// Without this, a crashed plan phase leaves no run.json and the run vanishes
|
|
20
|
+
// from findIncompleteRuns -- you pay for orchestration and can't see it.
|
|
21
|
+
if (objective) {
|
|
22
|
+
try {
|
|
23
|
+
saveRunState(runDir, {
|
|
24
|
+
id: runDir.split(/[/\\]/).pop() ?? "",
|
|
25
|
+
objective, budget: budget ?? 10, remaining: budget ?? 10,
|
|
26
|
+
workerModel, plannerModel, fastModel,
|
|
27
|
+
workerProviderId: workerProvider?.id, plannerProviderId: plannerProvider?.id,
|
|
28
|
+
fastProviderId: fastProvider?.id,
|
|
29
|
+
concurrency,
|
|
30
|
+
usageCap, allowExtraUsage, extraUsageBudget,
|
|
31
|
+
flex, useWorktrees, mergeStrategy,
|
|
32
|
+
waveNum: 0, currentTasks: [],
|
|
33
|
+
accCost: 0, accCompleted: 0, accFailed: 0,
|
|
34
|
+
accIn: 0, accOut: 0, accTools: 0,
|
|
35
|
+
branches: [],
|
|
36
|
+
phase: "planning",
|
|
37
|
+
startedAt: new Date().toISOString(),
|
|
38
|
+
cwd,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
catch { }
|
|
42
|
+
}
|
|
43
|
+
if (noTTY) {
|
|
44
|
+
console.error(chalk.red(" No tasks provided and stdin is not a TTY."));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
process.stdout.write("\x1B[?25l");
|
|
48
|
+
const planRestore = () => process.stdout.write("\x1B[?25h");
|
|
49
|
+
const useThinking = flex && (budget ?? 10) > concurrency * 3;
|
|
50
|
+
const thinkingCount = useThinking ? Math.min(Math.max(concurrency, Math.ceil((budget ?? 10) * 0.005)), 10) : 0;
|
|
51
|
+
try {
|
|
52
|
+
if (useThinking) {
|
|
53
|
+
// Persist themes as a Markdown doc so a planning-phase crash leaves a
|
|
54
|
+
// readable record (and a future resume can skip identifyThemes).
|
|
55
|
+
const saveThemesMd = (list) => {
|
|
56
|
+
try {
|
|
57
|
+
writeFileSync(join(runDir, "themes.md"), `# Themes\n\n**Objective:** ${objective}\n\n${list.map((t, i) => `${i + 1}. ${t}`).join("\n")}\n`, "utf-8");
|
|
58
|
+
}
|
|
59
|
+
catch { }
|
|
60
|
+
};
|
|
61
|
+
let themes = await identifyThemes(objective, thinkingCount, cwd, plannerModel, makeProgressLog(), "themes");
|
|
62
|
+
saveThemesMd(themes);
|
|
63
|
+
process.stdout.write(`\x1B[2K\r ${chalk.green(`✓ ${themes.length} themes`)}\n\n`);
|
|
64
|
+
planRestore();
|
|
65
|
+
let reviewing = true;
|
|
66
|
+
while (reviewing) {
|
|
67
|
+
for (let i = 0; i < themes.length; i++)
|
|
68
|
+
console.log(chalk.dim(` ${String(i + 1).padStart(3)}.`) + ` ${themes[i]}`);
|
|
69
|
+
console.log(chalk.dim(`\n ${thinkingCount} thinking agents → orchestrate → ${(budget ?? 10) - thinkingCount} execution sessions\n`));
|
|
70
|
+
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" }]);
|
|
71
|
+
if (action === "r") {
|
|
72
|
+
reviewing = false;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
if (action === "e") {
|
|
76
|
+
const feedback = await ask(`\n ${chalk.bold("What should change?")}\n ${chalk.cyan(">")} `);
|
|
77
|
+
if (!feedback)
|
|
78
|
+
continue;
|
|
79
|
+
process.stdout.write("\x1B[?25l");
|
|
80
|
+
try {
|
|
81
|
+
themes = await identifyThemes(`${objective}\n\nUser feedback: ${feedback}`, thinkingCount, cwd, plannerModel, makeProgressLog(), "themes-refine");
|
|
82
|
+
saveThemesMd(themes);
|
|
83
|
+
process.stdout.write(`\x1B[2K\r ${chalk.green(`✓ ${themes.length} themes`)}\n\n`);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
console.error(chalk.red(`\n Re-planning failed: ${err.message}\n`));
|
|
87
|
+
}
|
|
88
|
+
planRestore();
|
|
89
|
+
}
|
|
90
|
+
else if (action === "c") {
|
|
91
|
+
const question = await ask(`\n ${chalk.bold("Ask about the themes:")}\n ${chalk.cyan(">")} `);
|
|
92
|
+
if (!question)
|
|
93
|
+
continue;
|
|
94
|
+
process.stdout.write("\x1B[?25l");
|
|
95
|
+
try {
|
|
96
|
+
let answer = "";
|
|
97
|
+
const plannerEnv = envForModel(plannerModel);
|
|
98
|
+
for await (const msg of query({
|
|
99
|
+
prompt: `You're planning work for: "${objective}"\n\nThemes identified:\n${themes.map((t, i) => `${i + 1}. ${t}`).join("\n")}\n\nUser question: ${question}`,
|
|
100
|
+
options: { cwd, model: plannerModel, permissionMode: "bypassPermissions", allowDangerouslySkipPermissions: true, persistSession: false, ...(plannerEnv && { env: plannerEnv }) },
|
|
101
|
+
})) {
|
|
102
|
+
if (msg.type === "result" && msg.subtype === "success")
|
|
103
|
+
answer = msg.result || "";
|
|
104
|
+
}
|
|
105
|
+
planRestore();
|
|
106
|
+
if (answer)
|
|
107
|
+
console.log(chalk.dim(`\n ${answer.slice(0, 500)}\n`));
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
planRestore();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
console.log(chalk.dim("\n Aborted.\n"));
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
process.stdout.write("\x1B[?25l");
|
|
119
|
+
mkdirSync(designDir, { recursive: true });
|
|
120
|
+
const existingDesigns = readMdDir(designDir);
|
|
121
|
+
if (existingDesigns) {
|
|
122
|
+
const designFiles = readdirSync(designDir).filter(f => f.endsWith(".md")).sort();
|
|
123
|
+
console.log(chalk.green(`\n ✓ Reusing ${designFiles.length} design docs`) + chalk.dim(` (from prior attempt)`));
|
|
124
|
+
for (const f of designFiles) {
|
|
125
|
+
try {
|
|
126
|
+
const firstLine = readFileSync(join(designDir, f), "utf-8").split("\n")[0].replace(/^#+\s*/, "").trim();
|
|
127
|
+
if (firstLine)
|
|
128
|
+
console.log(chalk.dim(` ${firstLine.slice(0, 80)}`));
|
|
129
|
+
}
|
|
130
|
+
catch { }
|
|
131
|
+
}
|
|
132
|
+
console.log("");
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
const researchModel = fastModel ? workerModel : plannerModel;
|
|
136
|
+
const thinkingTasks = buildThinkingTasks(objective, themes, designDir, researchModel, previousKnowledge || undefined);
|
|
137
|
+
console.log(chalk.cyan(`\n ◆ Thinking: ${thinkingTasks.length} agents exploring...\n`));
|
|
138
|
+
const thinkingSwarm = new Swarm({
|
|
139
|
+
tasks: thinkingTasks, concurrency, cwd, model: researchModel,
|
|
140
|
+
useWorktrees: false, mergeStrategy: "yolo", agentTimeoutMs, usageCap, allowExtraUsage, extraUsageBudget,
|
|
141
|
+
envForModel,
|
|
142
|
+
cursorProxy: [plannerProvider, workerProvider, fastProvider].some(p => p && isCursorProxyProvider(p)),
|
|
143
|
+
});
|
|
144
|
+
const thinkRunInfo = { accIn: 0, accOut: 0, accCost: 0, accCompleted: 0, accFailed: 0, sessionsBudget: budget ?? 10, waveNum: -1, remaining: budget ?? 10, model: researchModel, startedAt: Date.now() };
|
|
145
|
+
const thinkDisplay = new RunDisplay(thinkRunInfo, { remaining: 0, usageCap, concurrency, paused: false, dirty: false });
|
|
146
|
+
thinkDisplay.setWave(thinkingSwarm);
|
|
147
|
+
thinkDisplay.start();
|
|
148
|
+
// Save thinking-wave state on every exit path (normal, abort, double-q).
|
|
149
|
+
const saveThinkingState = () => {
|
|
150
|
+
thinkingUsed = thinkingSwarm.completed + thinkingSwarm.failed;
|
|
151
|
+
thinkingCost = thinkingSwarm.totalCostUsd;
|
|
152
|
+
thinkingIn = thinkingSwarm.totalInputTokens;
|
|
153
|
+
thinkingOut = thinkingSwarm.totalOutputTokens;
|
|
154
|
+
thinkingTools = thinkingSwarm.agents.reduce((sum, a) => sum + a.toolCalls, 0);
|
|
155
|
+
try {
|
|
156
|
+
saveRunState(runDir, {
|
|
157
|
+
id: runDir.split(/[/\\]/).pop() ?? "",
|
|
158
|
+
objective: objective, budget: budget ?? 10, remaining: (budget ?? 10) - thinkingUsed,
|
|
159
|
+
workerModel, plannerModel, fastModel,
|
|
160
|
+
workerProviderId: workerProvider?.id, plannerProviderId: plannerProvider?.id,
|
|
161
|
+
fastProviderId: fastProvider?.id,
|
|
162
|
+
concurrency,
|
|
163
|
+
usageCap, allowExtraUsage, extraUsageBudget,
|
|
164
|
+
flex, useWorktrees, mergeStrategy,
|
|
165
|
+
waveNum: 0, currentTasks: [],
|
|
166
|
+
accCost: thinkingCost, accCompleted: thinkingUsed, accFailed: 0,
|
|
167
|
+
accIn: thinkingIn, accOut: thinkingOut, accTools: thinkingTools,
|
|
168
|
+
branches: [],
|
|
169
|
+
phase: "planning",
|
|
170
|
+
startedAt: new Date().toISOString(),
|
|
171
|
+
cwd,
|
|
172
|
+
coachedObjective: coachedOriginal,
|
|
173
|
+
coachedAt,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
catch { }
|
|
177
|
+
};
|
|
178
|
+
// Catch double-q / hard exit during thinking wave
|
|
179
|
+
const exitHandler = () => { try {
|
|
180
|
+
saveThinkingState();
|
|
181
|
+
}
|
|
182
|
+
catch { } };
|
|
183
|
+
process.on("exit", exitHandler);
|
|
184
|
+
try {
|
|
185
|
+
await thinkingSwarm.run();
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
thinkDisplay.pause();
|
|
189
|
+
console.log(renderSummary(thinkingSwarm));
|
|
190
|
+
thinkDisplay.stop();
|
|
191
|
+
saveThinkingState();
|
|
192
|
+
process.removeListener("exit", exitHandler);
|
|
193
|
+
}
|
|
194
|
+
thinkingHistory = { wave: -1, tasks: thinkingSwarm.agents.map(a => ({ prompt: a.task.prompt.slice(0, 200), status: a.status, filesChanged: a.filesChanged, error: a.error })) };
|
|
195
|
+
if (thinkingSwarm.rateLimitResetsAt) {
|
|
196
|
+
const waitMs = thinkingSwarm.rateLimitResetsAt - Date.now();
|
|
197
|
+
if (waitMs > 0) {
|
|
198
|
+
console.log(chalk.dim(` Waiting ${Math.ceil(waitMs / 1000)}s for rate limit reset...`));
|
|
199
|
+
await new Promise(r => setTimeout(r, waitMs + 2000));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const designs = readMdDir(designDir);
|
|
204
|
+
const taskFile = join(runDir, "tasks.json");
|
|
205
|
+
if (designs) {
|
|
206
|
+
const orchBudget = Math.min(50, Math.max(concurrency, Math.ceil(((budget ?? 10) - thinkingUsed) * 0.5)));
|
|
207
|
+
const flexNote = `This is wave 1 of an adaptive multi-wave run (total budget: ${(budget ?? 10) - thinkingUsed}). Plan the highest-impact foundational work first. Future waves will iterate based on what's learned.`;
|
|
208
|
+
console.log(chalk.cyan(`\n ◆ Orchestrating plan...\n`));
|
|
209
|
+
tasks = await orchestrate(objective, designs, cwd, plannerModel, workerModel, orchBudget, concurrency, makeProgressLog(), flexNote, taskFile);
|
|
210
|
+
process.stdout.write(`\x1B[2K\r ${chalk.green(`✓ ${tasks.length} tasks`)}\n\n`);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
console.log(chalk.yellow(`\n No design docs -- falling back to direct planning\n`));
|
|
214
|
+
const waveBudget = Math.min(50, Math.max(concurrency, Math.ceil(((budget ?? 10) - thinkingUsed) * 0.5)));
|
|
215
|
+
tasks = await planTasks(objective, cwd, plannerModel, workerModel, waveBudget, concurrency, makeProgressLog(), undefined, taskFile);
|
|
216
|
+
process.stdout.write(`\x1B[2K\r ${chalk.green(`✓ ${tasks.length} tasks`)}\n\n`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
const waveBudget = flex ? Math.min(50, Math.max(concurrency, Math.ceil((budget ?? 10) * 0.5))) : budget;
|
|
221
|
+
const flexNote = flex ? `This is wave 1 of an adaptive multi-wave run (total budget: ${budget}). Plan the highest-impact foundational work first. Future waves will iterate, polish, and expand based on what's learned.` : undefined;
|
|
222
|
+
console.log(chalk.cyan(`\n ◆ Planning${flex ? " wave 1" : ""}...\n`));
|
|
223
|
+
tasks = await planTasks(objective, cwd, plannerModel, workerModel, waveBudget, concurrency, makeProgressLog(), flexNote);
|
|
224
|
+
process.stdout.write(`\x1B[2K\r ${chalk.green(`✓ ${tasks.length} tasks`)}${flex ? chalk.dim(` · wave 1`) : ""}\n\n`);
|
|
225
|
+
planRestore();
|
|
226
|
+
let reviewing = true;
|
|
227
|
+
while (reviewing) {
|
|
228
|
+
showPlan(tasks);
|
|
229
|
+
const action = await selectKey(`${chalk.white(`${tasks.length} tasks`)} ${chalk.dim(`· ${concurrency} concurrent`)}`, [{ key: "r", desc: "un" }, { key: "e", desc: "dit" }, { key: "c", desc: "hat" }, { key: "q", desc: "uit" }]);
|
|
230
|
+
switch (action) {
|
|
231
|
+
case "r":
|
|
232
|
+
reviewing = false;
|
|
233
|
+
break;
|
|
234
|
+
case "e": {
|
|
235
|
+
const feedback = await ask(`\n ${chalk.bold("What should change?")}\n ${chalk.cyan(">")} `);
|
|
236
|
+
if (!feedback)
|
|
237
|
+
break;
|
|
238
|
+
console.log(chalk.cyan("\n ◆ Re-planning...\n"));
|
|
239
|
+
process.stdout.write("\x1B[?25l");
|
|
240
|
+
try {
|
|
241
|
+
tasks = await refinePlan(objective, tasks, feedback, cwd, plannerModel, workerModel, budget, concurrency, makeProgressLog());
|
|
242
|
+
process.stdout.write(`\x1B[2K\r ${chalk.green(`✓ ${tasks.length} tasks`)}\n\n`);
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
console.error(chalk.red(`\n Re-planning failed: ${err.message}\n`));
|
|
246
|
+
}
|
|
247
|
+
planRestore();
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
case "c": {
|
|
251
|
+
const question = await ask(`\n ${chalk.bold("Ask about the plan:")}\n ${chalk.cyan(">")} `);
|
|
252
|
+
if (!question)
|
|
253
|
+
break;
|
|
254
|
+
process.stdout.write("\x1B[?25l");
|
|
255
|
+
try {
|
|
256
|
+
let answer = "";
|
|
257
|
+
const plannerEnv = envForModel(plannerModel);
|
|
258
|
+
for await (const msg of query({
|
|
259
|
+
prompt: `You planned these tasks for the objective "${objective}":\n${tasks.map((t, i) => `${i + 1}. ${t.prompt}`).join("\n")}\n\nUser question: ${question}`,
|
|
260
|
+
options: { cwd, model: plannerModel, permissionMode: "bypassPermissions", allowDangerouslySkipPermissions: true, persistSession: false, ...(plannerEnv && { env: plannerEnv }) },
|
|
261
|
+
})) {
|
|
262
|
+
if (msg.type === "result" && msg.subtype === "success")
|
|
263
|
+
answer = msg.result || "";
|
|
264
|
+
}
|
|
265
|
+
planRestore();
|
|
266
|
+
if (answer)
|
|
267
|
+
console.log(chalk.dim(`\n ${answer.slice(0, 500)}\n`));
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
planRestore();
|
|
271
|
+
}
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
case "q":
|
|
275
|
+
console.log(chalk.dim("\n Aborted.\n"));
|
|
276
|
+
process.exit(0);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
catch (err) {
|
|
282
|
+
planRestore();
|
|
283
|
+
if (isJWTAuthError(err))
|
|
284
|
+
console.error(chalk.red(`\n Authentication failed -- check your API key or run: claude auth\n`));
|
|
285
|
+
else
|
|
286
|
+
console.error(chalk.red(`\n Planning failed: ${err.message}\n`));
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
return { tasks, thinkingHistory, thinkingUsed, thinkingCost, thinkingIn, thinkingOut, thinkingTools };
|
|
290
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ProviderConfig } from "../providers/index.js";
|
|
2
|
+
export interface PreflightInput {
|
|
3
|
+
plannerModel: string;
|
|
4
|
+
plannerProvider?: ProviderConfig | undefined;
|
|
5
|
+
workerModel: string;
|
|
6
|
+
workerProvider?: ProviderConfig | undefined;
|
|
7
|
+
fastModel?: string | undefined;
|
|
8
|
+
fastProvider?: ProviderConfig | undefined;
|
|
9
|
+
cwd: string;
|
|
10
|
+
}
|
|
11
|
+
export interface PreflightResult {
|
|
12
|
+
/** true when the fast provider failed preflight and the caller should drop it */
|
|
13
|
+
fastDegraded: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function runProviderPreflight(input: PreflightInput): Promise<PreflightResult>;
|