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.
Files changed (162) hide show
  1. package/dist/bin.js +1 -1
  2. package/dist/{cli.d.ts → cli/cli.d.ts} +19 -4
  3. package/dist/{cli.js → cli/cli.js} +38 -7
  4. package/dist/cli/help.d.ts +2 -0
  5. package/dist/cli/help.js +44 -0
  6. package/dist/cli/plan-phase.d.ts +38 -0
  7. package/dist/cli/plan-phase.js +290 -0
  8. package/dist/cli/preflight.d.ts +15 -0
  9. package/dist/cli/preflight.js +139 -0
  10. package/dist/cli/resume.d.ts +27 -0
  11. package/dist/cli/resume.js +312 -0
  12. package/dist/{settings.d.ts → cli/settings.d.ts} +1 -2
  13. package/dist/{settings.js → cli/settings.js} +2 -13
  14. package/dist/core/_version.d.ts +1 -0
  15. package/dist/{_version.js → core/_version.js} +1 -1
  16. package/dist/core/auth.d.ts +20 -0
  17. package/dist/core/auth.js +20 -0
  18. package/dist/core/jwt-signer.d.ts +59 -0
  19. package/dist/core/jwt-signer.js +124 -0
  20. package/dist/core/key-vault.d.ts +11 -0
  21. package/dist/core/key-vault.js +63 -0
  22. package/dist/core/rate-limiter.d.ts +48 -0
  23. package/dist/core/rate-limiter.js +116 -0
  24. package/dist/core/secret-manager.d.ts +8 -0
  25. package/dist/core/secret-manager.js +51 -0
  26. package/dist/core/token-cache.d.ts +56 -0
  27. package/dist/core/token-cache.js +120 -0
  28. package/dist/core/token-manager.d.ts +32 -0
  29. package/dist/core/token-manager.js +130 -0
  30. package/dist/{types.d.ts → core/types.d.ts} +7 -12
  31. package/dist/index.js +51 -776
  32. package/dist/middleware/index.d.ts +1 -0
  33. package/dist/middleware/index.js +1 -0
  34. package/dist/middleware/rate-limit.d.ts +49 -0
  35. package/dist/middleware/rate-limit.js +65 -0
  36. package/dist/planner/coach/coach.d.ts +19 -0
  37. package/dist/planner/coach/coach.js +215 -0
  38. package/dist/planner/coach/context.d.ts +23 -0
  39. package/dist/planner/coach/context.js +154 -0
  40. package/dist/planner/coach/schema.d.ts +111 -0
  41. package/dist/planner/coach/schema.js +101 -0
  42. package/dist/planner/coach/settings.d.ts +8 -0
  43. package/dist/planner/coach/settings.js +24 -0
  44. package/dist/planner/json.d.ts +4 -0
  45. package/dist/planner/json.js +106 -0
  46. package/dist/{planner.d.ts → planner/planner.d.ts} +5 -5
  47. package/dist/{planner.js → planner/planner.js} +35 -19
  48. package/dist/planner/postprocess.d.ts +2 -0
  49. package/dist/planner/postprocess.js +106 -0
  50. package/dist/planner/query.d.ts +28 -0
  51. package/dist/{planner-query.js → planner/query.js} +47 -332
  52. package/dist/{steering.d.ts → planner/steering.d.ts} +3 -3
  53. package/dist/{steering.js → planner/steering.js} +7 -7
  54. package/dist/planner/throttle.d.ts +48 -0
  55. package/dist/planner/throttle.js +87 -0
  56. package/dist/providers/cursor-env.d.ts +53 -0
  57. package/dist/providers/cursor-env.js +283 -0
  58. package/dist/providers/cursor-picker.d.ts +7 -0
  59. package/dist/providers/cursor-picker.js +306 -0
  60. package/dist/providers/cursor-proxy.d.ts +26 -0
  61. package/dist/providers/cursor-proxy.js +459 -0
  62. package/dist/providers/index.d.ts +70 -0
  63. package/dist/providers/index.js +324 -0
  64. package/dist/run/budget.d.ts +6 -0
  65. package/dist/run/budget.js +33 -0
  66. package/dist/run/circuit-breaker-state.d.ts +16 -0
  67. package/dist/run/circuit-breaker-state.js +18 -0
  68. package/dist/run/health.d.ts +5 -0
  69. package/dist/run/health.js +71 -0
  70. package/dist/run/review.d.ts +24 -0
  71. package/dist/run/review.js +32 -0
  72. package/dist/{run.d.ts → run/run.d.ts} +2 -2
  73. package/dist/run/run.js +576 -0
  74. package/dist/run/summary.d.ts +38 -0
  75. package/dist/run/summary.js +174 -0
  76. package/dist/run/throttle.d.ts +21 -0
  77. package/dist/run/throttle.js +48 -0
  78. package/dist/run/wave-loop.d.ts +71 -0
  79. package/dist/run/wave-loop.js +458 -0
  80. package/dist/{state.d.ts → state/state.d.ts} +1 -1
  81. package/dist/{state.js → state/state.js} +4 -4
  82. package/dist/swarm/agent-run.d.ts +34 -0
  83. package/dist/swarm/agent-run.js +475 -0
  84. package/dist/swarm/config.d.ts +38 -0
  85. package/dist/swarm/config.js +50 -0
  86. package/dist/swarm/errors.d.ts +13 -0
  87. package/dist/swarm/errors.js +54 -0
  88. package/dist/swarm/merge-autocommit.d.ts +1 -0
  89. package/dist/swarm/merge-autocommit.js +93 -0
  90. package/dist/swarm/merge-helpers.d.ts +15 -0
  91. package/dist/swarm/merge-helpers.js +175 -0
  92. package/dist/{merge.d.ts → swarm/merge.d.ts} +4 -17
  93. package/dist/swarm/merge.js +180 -0
  94. package/dist/swarm/message-handler.d.ts +39 -0
  95. package/dist/swarm/message-handler.js +215 -0
  96. package/dist/{swarm.d.ts → swarm/swarm.d.ts} +35 -50
  97. package/dist/swarm/swarm.js +473 -0
  98. package/dist/ui/bars.d.ts +12 -0
  99. package/dist/ui/bars.js +183 -0
  100. package/dist/ui/footer-state.d.ts +12 -0
  101. package/dist/ui/footer-state.js +93 -0
  102. package/dist/ui/footer.d.ts +11 -0
  103. package/dist/ui/footer.js +42 -0
  104. package/dist/ui/header.d.ts +14 -0
  105. package/dist/ui/header.js +91 -0
  106. package/dist/ui/input.d.ts +17 -0
  107. package/dist/ui/input.js +346 -0
  108. package/dist/ui/overlay.d.ts +11 -0
  109. package/dist/ui/overlay.js +54 -0
  110. package/dist/ui/primitives.d.ts +33 -0
  111. package/dist/ui/primitives.js +111 -0
  112. package/dist/ui/run-body.d.ts +6 -0
  113. package/dist/ui/run-body.js +169 -0
  114. package/dist/ui/settings.d.ts +13 -0
  115. package/dist/ui/settings.js +112 -0
  116. package/dist/ui/shell.d.ts +8 -0
  117. package/dist/ui/shell.js +34 -0
  118. package/dist/ui/steering-body.d.ts +13 -0
  119. package/dist/ui/steering-body.js +101 -0
  120. package/dist/ui/store.d.ts +66 -0
  121. package/dist/ui/store.js +44 -0
  122. package/dist/ui/summary.d.ts +2 -0
  123. package/dist/ui/summary.js +71 -0
  124. package/dist/ui/types.d.ts +50 -0
  125. package/dist/ui/types.js +4 -0
  126. package/dist/ui/ui.d.ts +46 -0
  127. package/dist/ui/ui.js +260 -0
  128. package/docs/CURSOR_PROXY.md +63 -0
  129. package/package.json +7 -4
  130. package/plugins/claude-overnight/.claude-plugin/plugin.json +1 -1
  131. package/plugins/lsp-first/.claude-plugin/plugin.json +18 -0
  132. package/plugins/lsp-first/README.md +38 -0
  133. package/plugins/lsp-first/hooks/lsp-first-guard.js +112 -0
  134. package/dist/_version.d.ts +0 -1
  135. package/dist/auth.d.ts +0 -19
  136. package/dist/auth.js +0 -82
  137. package/dist/coach.d.ts +0 -49
  138. package/dist/coach.js +0 -498
  139. package/dist/interactive-panel.d.ts +0 -42
  140. package/dist/interactive-panel.js +0 -169
  141. package/dist/merge.js +0 -442
  142. package/dist/planner-query.d.ts +0 -60
  143. package/dist/providers.d.ts +0 -159
  144. package/dist/providers.js +0 -1442
  145. package/dist/render.d.ts +0 -79
  146. package/dist/render.js +0 -768
  147. package/dist/run.js +0 -1257
  148. package/dist/swarm.js +0 -1059
  149. package/dist/test-coach.d.ts +0 -1
  150. package/dist/ui.d.ts +0 -162
  151. package/dist/ui.js +0 -1011
  152. /package/dist/{cursor-models.d.ts → core/cursor-models.d.ts} +0 -0
  153. /package/dist/{cursor-models.js → core/cursor-models.js} +0 -0
  154. /package/dist/{models.d.ts → core/models.d.ts} +0 -0
  155. /package/dist/{models.js → core/models.js} +0 -0
  156. /package/dist/{proxy-port.d.ts → core/proxy-port.d.ts} +0 -0
  157. /package/dist/{proxy-port.js → core/proxy-port.js} +0 -0
  158. /package/dist/{transcripts.d.ts → core/transcripts.d.ts} +0 -0
  159. /package/dist/{transcripts.js → core/transcripts.js} +0 -0
  160. /package/dist/{turns.d.ts → core/turns.d.ts} +0 -0
  161. /package/dist/{turns.js → core/turns.js} +0 -0
  162. /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, PermMode, MergeStrategy } from "./types.js";
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 "./auth.js";
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
- export declare function makeProgressLog(): (text: string) => void;
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", "perm"]);
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 "./auth.js";
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", "permissionMode", "allowedTools", "beforeWave", "afterWave", "afterRun", "worktrees", "mergeStrategy", "usageCap", "flexiblePlan",
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
  }
@@ -0,0 +1,2 @@
1
+ export declare function printVersion(): void;
2
+ export declare function printHelp(): void;
@@ -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>;