claude-overnight 1.25.30 → 1.25.33
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/index.js +68 -169
- package/dist/models.js +2 -2
- package/dist/providers.js +2 -2
- package/dist/render.js +2 -2
- package/dist/run.js +12 -1
- package/dist/settings.d.ts +21 -0
- package/dist/settings.js +120 -0
- package/dist/steering.js +10 -1
- package/dist/swarm.d.ts +9 -2
- package/dist/swarm.js +21 -1
- package/dist/types.d.ts +17 -0
- package/dist/ui.d.ts +16 -4
- package/dist/ui.js +156 -70
- package/package.json +1 -1
- package/plugins/claude-overnight/.claude-plugin/plugin.json +1 -1
package/dist/_version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "1.25.
|
|
1
|
+
export declare const VERSION = "1.25.33";
|
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.33";
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import { VERSION } from "./_version.js";
|
|
|
7
7
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
8
8
|
import { Swarm } from "./swarm.js";
|
|
9
9
|
import { planTasks, refinePlan, identifyThemes, buildThinkingTasks, orchestrate, salvageFromFile } from "./planner.js";
|
|
10
|
-
import {
|
|
10
|
+
import { formatContextWindow, DEFAULT_MODEL } from "./models.js";
|
|
11
11
|
import { setPlannerEnvResolver } from "./planner-query.js";
|
|
12
12
|
import { setTranscriptRunDir } from "./transcripts.js";
|
|
13
13
|
import { getProxyPort, buildProxyUrl } from "./proxy-port.js";
|
|
@@ -15,9 +15,10 @@ import { pickModel, loadProviders, preflightProvider, buildEnvResolver, healthCh
|
|
|
15
15
|
import { RunDisplay } from "./ui.js";
|
|
16
16
|
import { renderSummary, wrap } from "./render.js";
|
|
17
17
|
import { executeRun } from "./run.js";
|
|
18
|
-
import { parseCliFlags, isAuthError, fetchModels, ask, select, selectKey, loadTaskFile, validateConcurrency, isGitRepo, validateGitRepo, showPlan,
|
|
18
|
+
import { parseCliFlags, isAuthError, fetchModels, ask, select, selectKey, loadTaskFile, validateConcurrency, isGitRepo, validateGitRepo, showPlan, makeProgressLog, } from "./cli.js";
|
|
19
19
|
import { loadRunState, findIncompleteRuns, findOrphanedDesigns, backfillOrphanedPlans, formatTimeAgo, showRunHistory, readPreviousRunKnowledge, createRunDir, updateLatestSymlink, readMdDir, saveRunState, autoMergeBranches, } from "./state.js";
|
|
20
20
|
import { runSetupCoach, loadUserSettings, saveUserSettings, COACH_MODEL } from "./coach.js";
|
|
21
|
+
import { editRunSettings, formatSettingsSummary } from "./settings.js";
|
|
21
22
|
function countTasksInFile(path) {
|
|
22
23
|
try {
|
|
23
24
|
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
@@ -66,8 +67,6 @@ async function promptResumeOverrides(state, cliFlags, argv, noTTY, runDir) {
|
|
|
66
67
|
catch { }
|
|
67
68
|
return;
|
|
68
69
|
}
|
|
69
|
-
// Kick off model fetch in the background so it's ready if the user picks Edit.
|
|
70
|
-
const modelsPromise = fetchModels(20_000).catch(() => []);
|
|
71
70
|
// ── Interactive review ──
|
|
72
71
|
const fmtSummary = () => {
|
|
73
72
|
const remaining = Math.max(1, state.remaining);
|
|
@@ -90,6 +89,7 @@ async function promptResumeOverrides(state, cliFlags, argv, noTTY, runDir) {
|
|
|
90
89
|
console.log(` ${chalk.dim("concur ")}${chalk.white(String(state.concurrency))}`);
|
|
91
90
|
console.log(` ${chalk.dim("usage cap ")}${chalk.white(capStr)}`);
|
|
92
91
|
console.log(` ${chalk.dim("extra ")}${chalk.white(extraStr)}`);
|
|
92
|
+
console.log(` ${chalk.dim("perms ")}${chalk.white(state.permissionMode === "bypassPermissions" ? "yolo" : state.permissionMode)}`);
|
|
93
93
|
};
|
|
94
94
|
fmtSummary();
|
|
95
95
|
const action = await selectKey("", [
|
|
@@ -101,64 +101,24 @@ async function promptResumeOverrides(state, cliFlags, argv, noTTY, runDir) {
|
|
|
101
101
|
process.exit(0);
|
|
102
102
|
if (action === "r")
|
|
103
103
|
return;
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (!isNaN(parsedRem) && parsedRem > 0) {
|
|
123
|
-
state.remaining = parsedRem;
|
|
124
|
-
state.budget = state.accCompleted + state.accFailed + parsedRem;
|
|
125
|
-
}
|
|
126
|
-
const concAns = await ask(`\n ${chalk.cyan("③")} Concurrency ${chalk.dim(`[${state.concurrency}]:`)} `);
|
|
127
|
-
const parsedConc = parseInt(concAns);
|
|
128
|
-
if (!isNaN(parsedConc) && parsedConc >= 1)
|
|
129
|
-
state.concurrency = parsedConc;
|
|
130
|
-
const currentCap = state.usageCap != null ? String(Math.round(state.usageCap * 100)) : "off";
|
|
131
|
-
const capAns = await ask(`\n ${chalk.cyan("④")} Usage cap % ${chalk.dim(`[${currentCap}]`)} ${chalk.dim("(0 = off):")} `);
|
|
132
|
-
if (capAns.trim()) {
|
|
133
|
-
const v = parseFloat(capAns);
|
|
134
|
-
if (!isNaN(v) && v >= 0 && v <= 100)
|
|
135
|
-
state.usageCap = v > 0 ? v / 100 : undefined;
|
|
136
|
-
}
|
|
137
|
-
const currentExtra = state.allowExtraUsage
|
|
138
|
-
? (state.extraUsageBudget ? `$${state.extraUsageBudget}` : "unlimited")
|
|
139
|
-
: "off";
|
|
140
|
-
const extraChoice = await select(`${chalk.cyan("⑤")} Extra usage ${chalk.dim(`[current: ${currentExtra}]`)}:`, [
|
|
141
|
-
{ name: "Keep current", value: "keep" },
|
|
142
|
-
{ name: "Off", value: "off", hint: "stop at plan limit" },
|
|
143
|
-
{ name: "With $ cap", value: "budget", hint: "set a spending cap" },
|
|
144
|
-
{ name: "Unlimited", value: "unlimited", hint: "no cap, billed as overage" },
|
|
145
|
-
]);
|
|
146
|
-
if (extraChoice === "off") {
|
|
147
|
-
state.allowExtraUsage = false;
|
|
148
|
-
state.extraUsageBudget = undefined;
|
|
149
|
-
}
|
|
150
|
-
else if (extraChoice === "budget") {
|
|
151
|
-
const bAns = await ask(` ${chalk.dim("Max extra $:")} `);
|
|
152
|
-
const bVal = parseFloat(bAns);
|
|
153
|
-
if (!isNaN(bVal) && bVal > 0) {
|
|
154
|
-
state.extraUsageBudget = bVal;
|
|
155
|
-
state.allowExtraUsage = true;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
else if (extraChoice === "unlimited") {
|
|
159
|
-
state.allowExtraUsage = true;
|
|
160
|
-
state.extraUsageBudget = undefined;
|
|
161
|
-
}
|
|
104
|
+
const settings = {
|
|
105
|
+
workerModel: state.workerModel,
|
|
106
|
+
plannerModel: state.plannerModel,
|
|
107
|
+
fastModel: state.fastModel,
|
|
108
|
+
workerProviderId: state.workerProviderId,
|
|
109
|
+
plannerProviderId: state.plannerProviderId,
|
|
110
|
+
fastProviderId: state.fastProviderId,
|
|
111
|
+
concurrency: state.concurrency,
|
|
112
|
+
usageCap: state.usageCap,
|
|
113
|
+
allowExtraUsage: state.allowExtraUsage ?? false,
|
|
114
|
+
extraUsageBudget: state.extraUsageBudget,
|
|
115
|
+
permissionMode: state.permissionMode,
|
|
116
|
+
};
|
|
117
|
+
await editRunSettings({
|
|
118
|
+
current: settings,
|
|
119
|
+
cliConcurrencySet: !!cliFlags.concurrency,
|
|
120
|
+
});
|
|
121
|
+
Object.assign(state, settings);
|
|
162
122
|
try {
|
|
163
123
|
saveRunState(runDir, state);
|
|
164
124
|
}
|
|
@@ -595,7 +555,7 @@ async function main() {
|
|
|
595
555
|
const qwenProviders = providers.filter(p => p.model?.toLowerCase().includes("qwen"));
|
|
596
556
|
const options = [];
|
|
597
557
|
if (hasAnthropicKey)
|
|
598
|
-
options.push({ key: "1", desc: ` — ${COACH_MODEL} (
|
|
558
|
+
options.push({ key: "1", desc: ` — ${COACH_MODEL} (cheapest)` });
|
|
599
559
|
if (qwenProviders.length > 0)
|
|
600
560
|
options.push({ key: "2", desc: ` — ${qwenProviders[0].displayName} (${qwenProviders[0].model})` });
|
|
601
561
|
if (cursorProviders.length > 0)
|
|
@@ -637,7 +597,6 @@ async function main() {
|
|
|
637
597
|
objective = coachResult.improvedObjective;
|
|
638
598
|
}
|
|
639
599
|
}
|
|
640
|
-
const modelsPromise = fetchModels();
|
|
641
600
|
const defaultBudget = coachResult?.recommended.budget ?? 10;
|
|
642
601
|
const budgetAns = await ask(`\n ${chalk.cyan("②")} ${chalk.dim("Budget")} ${chalk.dim("[")}${chalk.white(String(defaultBudget))}${chalk.dim("]:")} `);
|
|
643
602
|
budget = parseInt(budgetAns) || defaultBudget;
|
|
@@ -645,91 +604,42 @@ async function main() {
|
|
|
645
604
|
console.error(chalk.red(` Budget must be a positive number`));
|
|
646
605
|
process.exit(1);
|
|
647
606
|
}
|
|
648
|
-
// ③ Max concurrency (skip if --concurrency set)
|
|
649
|
-
if (cliFlags.concurrency) {
|
|
650
|
-
concurrency = parseInt(cliFlags.concurrency);
|
|
651
|
-
}
|
|
652
|
-
else {
|
|
653
|
-
const defaultC = Math.min(coachResult?.recommended.concurrency ?? 5, budget);
|
|
654
|
-
const concAns = await ask(`\n ${chalk.cyan("③")} ${chalk.dim("Max concurrency")} ${chalk.dim("[")}${chalk.white(String(defaultC))}${chalk.dim("]:")} `);
|
|
655
|
-
concurrency = parseInt(concAns) || defaultC;
|
|
656
|
-
if (concurrency < 1)
|
|
657
|
-
concurrency = 1;
|
|
658
|
-
}
|
|
659
|
-
let modelFrame = 0;
|
|
660
|
-
const modelSpinner = setInterval(() => {
|
|
661
|
-
process.stdout.write(`\x1B[2K\r ${chalk.cyan(BRAILLE[modelFrame++ % BRAILLE.length])} ${chalk.dim("loading models...")}`);
|
|
662
|
-
}, 120);
|
|
663
|
-
let models;
|
|
664
|
-
try {
|
|
665
|
-
models = await modelsPromise;
|
|
666
|
-
}
|
|
667
|
-
finally {
|
|
668
|
-
clearInterval(modelSpinner);
|
|
669
|
-
process.stdout.write(`\x1B[2K\r`);
|
|
670
|
-
}
|
|
671
|
-
const plannerPick = await pickModel(`${chalk.cyan("④")} Planner model ${chalk.dim("(thinking, steering -- use your strongest)")}:`, models, coachResult?.recommended.plannerModel);
|
|
672
|
-
plannerModel = plannerPick.model;
|
|
673
|
-
plannerProvider = plannerPick.provider;
|
|
674
|
-
const workerPick = await pickModel(`${chalk.cyan("⑤")} Worker model ${chalk.dim("(what runs the tasks -- Qwen 3.6 Plus / OpenRouter / etc via Other…)")}:`, models, coachResult?.recommended.workerModel);
|
|
675
|
-
workerModel = workerPick.model;
|
|
676
|
-
workerProvider = workerPick.provider;
|
|
677
|
-
// ⑤b Optional fast model for quick tasks that will be verified
|
|
678
|
-
const suggestFast = !!coachResult?.recommended.fastModel;
|
|
679
|
-
const fastChoice = await select(`${chalk.cyan("⑤b")} Fast model ${chalk.dim("(optional -- Haiku/Qwen for quick tasks, checked by worker)")}:`, [
|
|
680
|
-
{ name: "Skip", value: "skip", hint: "two-tier mode only (current setup)" },
|
|
681
|
-
{ name: "Pick a fast model", value: "pick", hint: "Haiku, Qwen, or any provider -- for well-scoped tasks" },
|
|
682
|
-
], suggestFast ? 1 : 0);
|
|
683
|
-
if (fastChoice === "pick") {
|
|
684
|
-
const fastPick = await pickModel(`${chalk.cyan("⑤c")} Fast model:`, models, coachResult?.recommended.fastModel ?? undefined);
|
|
685
|
-
fastModel = fastPick.model;
|
|
686
|
-
fastProvider = fastPick.provider;
|
|
687
|
-
}
|
|
688
|
-
const usageCapItems = [
|
|
689
|
-
{ name: "Unlimited", value: undefined, hint: "full capacity, wait through rate limits" },
|
|
690
|
-
{ name: "90%", value: 0.9, hint: "leave 10% for other work" },
|
|
691
|
-
{ name: "75%", value: 0.75, hint: "conservative, plenty of headroom" },
|
|
692
|
-
{ name: "50%", value: 0.5, hint: "use half, keep the rest" },
|
|
693
|
-
];
|
|
694
|
-
const coachCap = coachResult?.recommended.usageCap;
|
|
695
|
-
const usageCapDefault = coachCap == null ? 0
|
|
696
|
-
: coachCap >= 0.85 ? 1
|
|
697
|
-
: coachCap >= 0.6 ? 2
|
|
698
|
-
: 3;
|
|
699
|
-
usageCap = await select(`${chalk.cyan("⑥")} Usage cap:`, usageCapItems, usageCapDefault);
|
|
700
|
-
const extraChoice = await select(`${chalk.cyan("⑦")} Allow extra usage ${chalk.dim("(billed separately)")}:`, [
|
|
701
|
-
{ name: "No", value: "no", hint: "stop when plan limits are reached" },
|
|
702
|
-
{ name: "Yes, with $ limit", value: "budget", hint: "set a spending cap" },
|
|
703
|
-
{ name: "Yes, unlimited", value: "unlimited", hint: "keep going no matter what" },
|
|
704
|
-
]);
|
|
705
|
-
if (extraChoice === "budget") {
|
|
706
|
-
const budgetAns2 = await ask(` ${chalk.dim("Max extra usage $:")} `);
|
|
707
|
-
extraUsageBudget = parseFloat(budgetAns2);
|
|
708
|
-
if (!extraUsageBudget || extraUsageBudget <= 0)
|
|
709
|
-
extraUsageBudget = 5;
|
|
710
|
-
allowExtraUsage = true;
|
|
711
|
-
}
|
|
712
|
-
else if (extraChoice === "unlimited")
|
|
713
|
-
allowExtraUsage = true;
|
|
714
|
-
// ⑧ Permission mode (skip if --yolo or --perm set)
|
|
715
607
|
const cliYolo = argv.includes("--yolo");
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
608
|
+
const coach = coachResult?.recommended;
|
|
609
|
+
const settingsDefaults = {
|
|
610
|
+
workerModel: coach?.workerModel ?? DEFAULT_MODEL,
|
|
611
|
+
plannerModel: coach?.plannerModel ?? DEFAULT_MODEL,
|
|
612
|
+
fastModel: coach?.fastModel ?? undefined,
|
|
613
|
+
concurrency: Math.min(coach?.concurrency ?? 5, budget),
|
|
614
|
+
usageCap: coach?.usageCap ?? undefined,
|
|
615
|
+
allowExtraUsage: false,
|
|
616
|
+
permissionMode: cliYolo ? "bypassPermissions" : coach?.permissionMode ?? "auto",
|
|
617
|
+
};
|
|
618
|
+
const settings = await editRunSettings({
|
|
619
|
+
current: settingsDefaults,
|
|
620
|
+
cliConcurrencySet: !!cliFlags.concurrency,
|
|
621
|
+
defaults: coach ? {
|
|
622
|
+
plannerModel: coach.plannerModel,
|
|
623
|
+
workerModel: coach.workerModel,
|
|
624
|
+
fastModel: coach.fastModel ?? undefined,
|
|
625
|
+
concurrency: Math.min(coach.concurrency, budget),
|
|
626
|
+
usageCap: coach.usageCap,
|
|
627
|
+
permissionMode: cliYolo ? "bypassPermissions" : coach.permissionMode,
|
|
628
|
+
} : undefined,
|
|
629
|
+
});
|
|
630
|
+
plannerModel = settings.plannerModel;
|
|
631
|
+
workerModel = settings.workerModel;
|
|
632
|
+
fastModel = settings.fastModel;
|
|
633
|
+
concurrency = settings.concurrency;
|
|
634
|
+
usageCap = settings.usageCap;
|
|
635
|
+
allowExtraUsage = settings.allowExtraUsage;
|
|
636
|
+
extraUsageBudget = settings.extraUsageBudget;
|
|
637
|
+
permissionMode = cliFlags.perm ?? (cliYolo ? "bypassPermissions" : settings.permissionMode);
|
|
638
|
+
const savedProviders = loadProviders();
|
|
639
|
+
plannerProvider = settings.plannerProviderId ? savedProviders.find(p => p.id === settings.plannerProviderId) : undefined;
|
|
640
|
+
workerProvider = settings.workerProviderId ? savedProviders.find(p => p.id === settings.workerProviderId) : undefined;
|
|
641
|
+
fastProvider = settings.fastProviderId ? savedProviders.find(p => p.id === settings.fastProviderId) : undefined;
|
|
642
|
+
// ④ Worktrees + merge (skip if --yolo, --worktrees, --no-worktrees, or --merge set)
|
|
733
643
|
const gitRepo = isGitRepo(cwd);
|
|
734
644
|
if (cliYolo || argv.includes("--no-worktrees")) {
|
|
735
645
|
useWorktrees = false;
|
|
@@ -740,7 +650,7 @@ async function main() {
|
|
|
740
650
|
mergeStrategy = cliFlags.merge || "yolo";
|
|
741
651
|
}
|
|
742
652
|
else if (gitRepo) {
|
|
743
|
-
const wtChoice = await select(`${chalk.cyan("
|
|
653
|
+
const wtChoice = await select(`${chalk.cyan("④b")} Git isolation:`, [
|
|
744
654
|
{ name: "Worktrees + yolo merge", value: "wt-yolo", hint: "isolate agents, merge into current branch" },
|
|
745
655
|
{ name: "Worktrees + new branch", value: "wt-branch", hint: "isolate agents, merge into a new branch" },
|
|
746
656
|
{ name: "No worktrees", value: "no-wt", hint: "all agents share the working directory" },
|
|
@@ -752,31 +662,20 @@ async function main() {
|
|
|
752
662
|
useWorktrees = false;
|
|
753
663
|
mergeStrategy = "yolo";
|
|
754
664
|
}
|
|
755
|
-
const
|
|
756
|
-
|
|
757
|
-
parts.push(`${modelDisplayName(plannerModel)} → ${modelDisplayName(workerModel)} + ${modelDisplayName(fastModel)}`);
|
|
758
|
-
else if (workerModel !== plannerModel)
|
|
759
|
-
parts.push(`${modelDisplayName(workerModel)} → ${modelDisplayName(plannerModel)}`);
|
|
760
|
-
else
|
|
761
|
-
parts.push(modelDisplayName(workerModel));
|
|
762
|
-
parts.push(`budget ${budget}`, `${concurrency}×`);
|
|
665
|
+
const inner = formatSettingsSummary({ ...settings, permissionMode });
|
|
666
|
+
const parts2 = [`budget ${budget}`, `${concurrency}×`];
|
|
763
667
|
if (budget > 2)
|
|
764
|
-
|
|
765
|
-
if (usageCap != null)
|
|
766
|
-
parts.push(`cap ${Math.round(usageCap * 100)}%`);
|
|
767
|
-
parts.push(allowExtraUsage ? (extraUsageBudget ? `extra $${extraUsageBudget}` : "extra ∞") : "no extra");
|
|
768
|
-
if (permissionMode !== "auto")
|
|
769
|
-
parts.push(permissionMode === "bypassPermissions" ? "yolo" : "prompt");
|
|
668
|
+
parts2.push("flex");
|
|
770
669
|
if (useWorktrees)
|
|
771
|
-
|
|
670
|
+
parts2.push(mergeStrategy === "branch" ? "wt→branch" : "wt→yolo");
|
|
772
671
|
else
|
|
773
|
-
|
|
672
|
+
parts2.push("no wt");
|
|
774
673
|
if (completedRuns.length > 0)
|
|
775
|
-
|
|
776
|
-
const
|
|
777
|
-
const innerLen =
|
|
674
|
+
parts2.push(`${completedRuns.length} prior`);
|
|
675
|
+
const fullLine = inner + chalk.dim(" · ") + parts2.join(chalk.dim(" · "));
|
|
676
|
+
const innerLen = fullLine.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "").length;
|
|
778
677
|
console.log(chalk.dim(`\n ╭${"─".repeat(innerLen + 4)}╮`));
|
|
779
|
-
console.log(chalk.dim(" │") + ` ${
|
|
678
|
+
console.log(chalk.dim(" │") + ` ${fullLine} ` + chalk.dim("│"));
|
|
780
679
|
console.log(chalk.dim(` ╰${"─".repeat(innerLen + 4)}╯`));
|
|
781
680
|
}
|
|
782
681
|
else {
|
package/dist/models.js
CHANGED
|
@@ -124,7 +124,7 @@ export const FALLBACK_MODEL = "claude-opus-4-7"; // used for planner + worker re
|
|
|
124
124
|
* exact → substring match. Falls back to "unknown" entry.
|
|
125
125
|
*/
|
|
126
126
|
export function getModelCapability(model) {
|
|
127
|
-
const m =
|
|
127
|
+
const m = model.toLowerCase();
|
|
128
128
|
if (MODEL_CAPABILITIES[m])
|
|
129
129
|
return MODEL_CAPABILITIES[m];
|
|
130
130
|
for (const [key, cap] of Object.entries(MODEL_CAPABILITIES)) {
|
|
@@ -135,7 +135,7 @@ export function getModelCapability(model) {
|
|
|
135
135
|
}
|
|
136
136
|
/** Human-readable model name for display (e.g. in run labels). */
|
|
137
137
|
export function modelDisplayName(model) {
|
|
138
|
-
const resolved = model
|
|
138
|
+
const resolved = model;
|
|
139
139
|
const m = resolved.toLowerCase();
|
|
140
140
|
if (MODEL_CAPABILITIES[m]?.displayName)
|
|
141
141
|
return MODEL_CAPABILITIES[m].displayName;
|
package/dist/providers.js
CHANGED
|
@@ -196,8 +196,8 @@ export async function pickModel(label, anthropicModels, currentModelId) {
|
|
|
196
196
|
if (anthropicModels.length === 0) {
|
|
197
197
|
items.push({
|
|
198
198
|
name: DEFAULT_MODEL,
|
|
199
|
-
value: { kind: "anthropic", model: { value: DEFAULT_MODEL, displayName: DEFAULT_MODEL, description: "
|
|
200
|
-
hint: "
|
|
199
|
+
value: { kind: "anthropic", model: { value: DEFAULT_MODEL, displayName: DEFAULT_MODEL, description: DEFAULT_MODEL + " (model list unavailable)" } },
|
|
200
|
+
hint: DEFAULT_MODEL + " -- Anthropic model list unavailable",
|
|
201
201
|
});
|
|
202
202
|
}
|
|
203
203
|
for (const p of saved) {
|
package/dist/render.js
CHANGED
|
@@ -451,7 +451,7 @@ export function renderFrame(swarm, showHotkeys, runInfo, selectedAgentId, maxRow
|
|
|
451
451
|
const detailChip = swarm.active > 0 ? chalk.dim(" [d] detail") : "";
|
|
452
452
|
const selectChip = swarm.active > 0 && running.length <= 10 ? chalk.dim(" [0-9] select") : "";
|
|
453
453
|
const panelChip = panel?.visible ? chalk.green(` [Ctrl-O] ${panel.state.expanded ? "collapse" : "expand"}`) : "";
|
|
454
|
-
hotkeyRow = chalk.dim(` [
|
|
454
|
+
hotkeyRow = chalk.dim(` [s] settings ${pauseLabel} [i] inject [?] ask [q] stop`) + fixChip + retryChip + chip + detailChip + selectChip + panelChip;
|
|
455
455
|
if (swarm.blocked > 0 && swarm.blocked === swarm.active) {
|
|
456
456
|
extraFooterRows.push(chalk.yellow(` all workers rate-limited -- [r] retry-now, [c] reduce concurrency, [p] pause, [q] quit`));
|
|
457
457
|
}
|
|
@@ -641,7 +641,7 @@ export function renderSteeringFrame(runInfo, data, showHotkeys, rlGetter, maxRow
|
|
|
641
641
|
const pending = runInfo?.pendingSteer ?? 0;
|
|
642
642
|
const chip = pending > 0 ? chalk.cyan(` \u270E ${pending} steer queued`) : "";
|
|
643
643
|
const panelChip = panel?.visible ? chalk.green(` [Ctrl-O] ${panel.state.expanded ? "collapse" : "expand"}`) : "";
|
|
644
|
-
hotkeyRow = chalk.dim(" [
|
|
644
|
+
hotkeyRow = chalk.dim(" [s] settings [i] inject [q] stop") + chip + panelChip;
|
|
645
645
|
}
|
|
646
646
|
return renderUnifiedFrame({
|
|
647
647
|
model: runInfo.model,
|
package/dist/run.js
CHANGED
|
@@ -19,7 +19,8 @@ export async function executeRun(cfg) {
|
|
|
19
19
|
process.stdout.write("\x1B[?25h\n");
|
|
20
20
|
}
|
|
21
21
|
catch { } };
|
|
22
|
-
const { objective, cwd,
|
|
22
|
+
const { objective, cwd, allowedTools, beforeWave: beforeWaveCmds, afterWave: afterWaveCmds, afterRun: afterRunCmds, runDir, previousKnowledge } = cfg;
|
|
23
|
+
let { workerModel, plannerModel, fastModel, concurrency, permissionMode } = cfg;
|
|
23
24
|
const envForModel = buildEnvResolver({
|
|
24
25
|
plannerModel, plannerProvider: cfg.plannerProvider,
|
|
25
26
|
workerModel, workerProvider: cfg.workerProvider,
|
|
@@ -44,6 +45,7 @@ export async function executeRun(cfg) {
|
|
|
44
45
|
const liveConfig = {
|
|
45
46
|
remaining: 0, usageCap, concurrency, paused: false, dirty: false,
|
|
46
47
|
extraUsageBudget: cfg.extraUsageBudget,
|
|
48
|
+
workerModel, plannerModel, fastModel, permissionMode,
|
|
47
49
|
};
|
|
48
50
|
let waveNum;
|
|
49
51
|
const waveHistory = [];
|
|
@@ -530,6 +532,15 @@ export async function executeRun(cfg) {
|
|
|
530
532
|
remaining = liveConfig.remaining;
|
|
531
533
|
usageCap = liveConfig.usageCap;
|
|
532
534
|
cfg.extraUsageBudget = liveConfig.extraUsageBudget;
|
|
535
|
+
if (liveConfig.workerModel)
|
|
536
|
+
workerModel = liveConfig.workerModel;
|
|
537
|
+
if (liveConfig.plannerModel)
|
|
538
|
+
plannerModel = liveConfig.plannerModel;
|
|
539
|
+
if (liveConfig.fastModel !== undefined)
|
|
540
|
+
fastModel = liveConfig.fastModel;
|
|
541
|
+
if (liveConfig.permissionMode)
|
|
542
|
+
permissionMode = liveConfig.permissionMode;
|
|
543
|
+
concurrency = liveConfig.concurrency;
|
|
533
544
|
liveConfig.dirty = false;
|
|
534
545
|
}
|
|
535
546
|
liveConfig.remaining = remaining;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { MutableRunSettings, PermMode } from "./types.js";
|
|
2
|
+
interface EditSettingsOptions {
|
|
3
|
+
/** Existing settings to show as current values (resume) or blank defaults. */
|
|
4
|
+
current: MutableRunSettings;
|
|
5
|
+
/** CLI flags that already override concurrency (skip prompt if set). */
|
|
6
|
+
cliConcurrencySet?: boolean;
|
|
7
|
+
/** Coach-recommended defaults (initial setup only). */
|
|
8
|
+
defaults?: {
|
|
9
|
+
plannerModel?: string;
|
|
10
|
+
workerModel?: string;
|
|
11
|
+
fastModel?: string;
|
|
12
|
+
concurrency?: number;
|
|
13
|
+
usageCap?: number | null;
|
|
14
|
+
permissionMode?: PermMode;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/** Interactively edit all mutable run settings. Mutates `options.current` in place. */
|
|
18
|
+
export declare function editRunSettings(options: EditSettingsOptions): Promise<MutableRunSettings>;
|
|
19
|
+
/** Format a MutableRunSettings as a compact summary line for the terminal. */
|
|
20
|
+
export declare function formatSettingsSummary(s: MutableRunSettings): string;
|
|
21
|
+
export {};
|
package/dist/settings.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { modelDisplayName, formatContextWindow } from "./models.js";
|
|
3
|
+
import { fetchModels, ask, select, BRAILLE } from "./cli.js";
|
|
4
|
+
import { pickModel } from "./providers.js";
|
|
5
|
+
/** Interactively edit all mutable run settings. Mutates `options.current` in place. */
|
|
6
|
+
export async function editRunSettings(options) {
|
|
7
|
+
const s = options.current;
|
|
8
|
+
const modelsPromise = fetchModels(20_000).catch(() => []);
|
|
9
|
+
let modelFrame = 0;
|
|
10
|
+
const modelSpinner = setInterval(() => {
|
|
11
|
+
process.stdout.write(`\x1B[2K\r ${chalk.cyan(BRAILLE[modelFrame++ % BRAILLE.length])} ${chalk.dim("loading models...")}`);
|
|
12
|
+
}, 120);
|
|
13
|
+
let models;
|
|
14
|
+
try {
|
|
15
|
+
models = await modelsPromise;
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
clearInterval(modelSpinner);
|
|
19
|
+
process.stdout.write(`\x1B[2K\r`);
|
|
20
|
+
}
|
|
21
|
+
const plannerPick = await pickModel(`${chalk.cyan("①")} Planner model ${chalk.dim("(thinking, steering -- use your strongest)")}:`, models, options.defaults?.plannerModel ?? s.plannerModel);
|
|
22
|
+
s.plannerModel = plannerPick.model;
|
|
23
|
+
s.plannerProviderId = plannerPick.providerId;
|
|
24
|
+
const workerPick = await pickModel(`${chalk.cyan("②")} Worker model ${chalk.dim("(what runs the tasks -- Qwen 3.6 Plus / OpenRouter / etc via Other…)")}:`, models, options.defaults?.workerModel ?? s.workerModel);
|
|
25
|
+
s.workerModel = workerPick.model;
|
|
26
|
+
s.workerProviderId = workerPick.providerId;
|
|
27
|
+
const suggestFast = !!(options.defaults?.fastModel);
|
|
28
|
+
const fastChoice = await select(`${chalk.cyan("③")} Fast model ${chalk.dim("(optional -- Haiku/Qwen for quick tasks, checked by worker)")}:`, [
|
|
29
|
+
{ name: "Skip", value: "skip", hint: "two-tier mode only (current setup)" },
|
|
30
|
+
{ name: "Pick a fast model", value: "pick", hint: "Haiku, Qwen, or any provider -- for well-scoped tasks" },
|
|
31
|
+
], suggestFast ? 1 : 0);
|
|
32
|
+
if (fastChoice === "pick") {
|
|
33
|
+
const fastPick = await pickModel(`${chalk.cyan("③b")} Fast model:`, models, options.defaults?.fastModel ?? s.fastModel);
|
|
34
|
+
s.fastModel = fastPick.model;
|
|
35
|
+
s.fastProviderId = fastPick.providerId;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
s.fastModel = undefined;
|
|
39
|
+
s.fastProviderId = undefined;
|
|
40
|
+
}
|
|
41
|
+
if (!options.cliConcurrencySet) {
|
|
42
|
+
const defaultC = options.defaults?.concurrency ?? s.concurrency;
|
|
43
|
+
const concAns = await ask(`\n ${chalk.cyan("④")} ${chalk.dim("Max concurrency")} ${chalk.dim("[")}${chalk.white(String(defaultC))}${chalk.dim("]:")} `);
|
|
44
|
+
const parsed = parseInt(concAns);
|
|
45
|
+
if (!isNaN(parsed) && parsed >= 1)
|
|
46
|
+
s.concurrency = parsed;
|
|
47
|
+
}
|
|
48
|
+
const coachCap = options.defaults?.usageCap;
|
|
49
|
+
const usageCapItems = [
|
|
50
|
+
{ name: "Unlimited", value: undefined, hint: "full capacity, wait through rate limits" },
|
|
51
|
+
{ name: "90%", value: 0.9, hint: "leave 10% for other work" },
|
|
52
|
+
{ name: "75%", value: 0.75, hint: "conservative, plenty of headroom" },
|
|
53
|
+
{ name: "50%", value: 0.5, hint: "use half, keep the rest" },
|
|
54
|
+
];
|
|
55
|
+
const usageCapDefault = coachCap == null ? 0
|
|
56
|
+
: coachCap >= 0.85 ? 1
|
|
57
|
+
: coachCap >= 0.6 ? 2
|
|
58
|
+
: 3;
|
|
59
|
+
s.usageCap = await select(`${chalk.cyan("⑤")} Usage cap:`, usageCapItems, usageCapDefault);
|
|
60
|
+
const extraChoice = await select(`${chalk.cyan("⑥")} Allow extra usage ${chalk.dim("(billed separately)")}:`, [
|
|
61
|
+
{ name: "No", value: "no", hint: "stop when plan limits are reached" },
|
|
62
|
+
{ name: "Yes, with $ limit", value: "budget", hint: "set a spending cap" },
|
|
63
|
+
{ name: "Yes, unlimited", value: "unlimited", hint: "keep going no matter what" },
|
|
64
|
+
]);
|
|
65
|
+
if (extraChoice === "budget") {
|
|
66
|
+
const bAns = await ask(` ${chalk.dim("Max extra usage $:")} `);
|
|
67
|
+
const bVal = parseFloat(bAns);
|
|
68
|
+
s.allowExtraUsage = true;
|
|
69
|
+
s.extraUsageBudget = (!isNaN(bVal) && bVal > 0) ? bVal : 5;
|
|
70
|
+
}
|
|
71
|
+
else if (extraChoice === "unlimited") {
|
|
72
|
+
s.allowExtraUsage = true;
|
|
73
|
+
s.extraUsageBudget = undefined;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
s.allowExtraUsage = false;
|
|
77
|
+
s.extraUsageBudget = undefined;
|
|
78
|
+
}
|
|
79
|
+
const permItems = [
|
|
80
|
+
{ name: "Auto", value: "auto", hint: "accept low-risk, reject high-risk" },
|
|
81
|
+
{ name: "Bypass all", value: "bypassPermissions", hint: "agents can run anything (yolo)" },
|
|
82
|
+
{ name: "Prompt each", value: "default", hint: "ask for every dangerous op" },
|
|
83
|
+
];
|
|
84
|
+
const permDefault = options.defaults?.permissionMode === "bypassPermissions" ? 1
|
|
85
|
+
: options.defaults?.permissionMode === "default" ? 2 : 0;
|
|
86
|
+
s.permissionMode = await select(`${chalk.cyan("⑦")} Permissions:`, permItems, permDefault);
|
|
87
|
+
const modelLine = (label, m) => m ? `${chalk.dim(label.padEnd(11))}${chalk.white(m)} ${chalk.dim(`(${formatContextWindow(m)} context)`)}` : null;
|
|
88
|
+
const lines = [
|
|
89
|
+
modelLine("planner", s.plannerModel),
|
|
90
|
+
modelLine("worker", s.workerModel),
|
|
91
|
+
modelLine("fast", s.fastModel),
|
|
92
|
+
].filter(Boolean);
|
|
93
|
+
console.log();
|
|
94
|
+
for (const l of lines)
|
|
95
|
+
console.log(l);
|
|
96
|
+
const capStr = s.usageCap != null ? `${Math.round(s.usageCap * 100)}%` : "unlimited";
|
|
97
|
+
const extraStr = s.allowExtraUsage ? (s.extraUsageBudget ? `$${s.extraUsageBudget}` : "unlimited") : "off";
|
|
98
|
+
console.log(` ${chalk.dim("concur ")}${chalk.white(String(s.concurrency))}`);
|
|
99
|
+
console.log(` ${chalk.dim("usage cap ")}${chalk.white(capStr)}`);
|
|
100
|
+
console.log(` ${chalk.dim("extra ")}${chalk.white(extraStr)}`);
|
|
101
|
+
console.log(` ${chalk.dim("perms ")}${chalk.white(s.permissionMode === "bypassPermissions" ? "yolo" : s.permissionMode)}`);
|
|
102
|
+
console.log();
|
|
103
|
+
return s;
|
|
104
|
+
}
|
|
105
|
+
/** Format a MutableRunSettings as a compact summary line for the terminal. */
|
|
106
|
+
export function formatSettingsSummary(s) {
|
|
107
|
+
const parts = [];
|
|
108
|
+
if (s.fastModel)
|
|
109
|
+
parts.push(`${modelDisplayName(s.plannerModel)} → ${modelDisplayName(s.workerModel)} + ${modelDisplayName(s.fastModel)}`);
|
|
110
|
+
else if (s.workerModel !== s.plannerModel)
|
|
111
|
+
parts.push(`${modelDisplayName(s.workerModel)} → ${modelDisplayName(s.plannerModel)}`);
|
|
112
|
+
else
|
|
113
|
+
parts.push(modelDisplayName(s.workerModel));
|
|
114
|
+
if (s.usageCap != null)
|
|
115
|
+
parts.push(`cap ${Math.round(s.usageCap * 100)}%`);
|
|
116
|
+
parts.push(s.allowExtraUsage ? (s.extraUsageBudget ? `extra $${s.extraUsageBudget}` : "extra ∞") : "no extra");
|
|
117
|
+
if (s.permissionMode !== "auto")
|
|
118
|
+
parts.push(s.permissionMode === "bypassPermissions" ? "yolo" : "prompt");
|
|
119
|
+
return parts.join(chalk.dim(" · "));
|
|
120
|
+
}
|
package/dist/steering.js
CHANGED
|
@@ -136,10 +136,19 @@ If done: {"done": true, "reasoning": "...", "statusUpdate": "...", "estimatedSes
|
|
|
136
136
|
const statusUpdate = parsed.statusUpdate || undefined;
|
|
137
137
|
const estRaw = parsed.estimatedSessionsRemaining;
|
|
138
138
|
const estimatedSessionsRemaining = typeof estRaw === "number" && estRaw >= 0 ? Math.round(estRaw) : undefined;
|
|
139
|
+
// Resolve steering role strings ("worker"/"fast"/"planner") to actual model IDs.
|
|
140
|
+
const resolveModel = (role) => {
|
|
141
|
+
switch (role.toLowerCase()) {
|
|
142
|
+
case "worker": return workerModel;
|
|
143
|
+
case "planner": return plannerModel;
|
|
144
|
+
case "fast": return fastModel ?? workerModel;
|
|
145
|
+
default: return role; // already a real model ID
|
|
146
|
+
}
|
|
147
|
+
};
|
|
139
148
|
let tasks = (parsed.tasks || []).map((t, i) => ({
|
|
140
149
|
id: String(i),
|
|
141
150
|
prompt: typeof t === "string" ? t : t.prompt,
|
|
142
|
-
...(t.model && { model: t.model }),
|
|
151
|
+
...(t.model && { model: resolveModel(t.model) }),
|
|
143
152
|
...(t.noWorktree && { noWorktree: true }),
|
|
144
153
|
...(t.type && { type: t.type }),
|
|
145
154
|
}));
|
package/dist/swarm.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type PermMode } from "./types.js";
|
|
2
|
+
import type { Task, AgentState, SwarmPhase, MergeStrategy, RateLimitWindow } from "./types.js";
|
|
2
3
|
import type { MergeResult } from "./merge.js";
|
|
3
4
|
export interface SwarmConfig {
|
|
4
5
|
tasks: Task[];
|
|
@@ -77,12 +78,14 @@ export declare class Swarm {
|
|
|
77
78
|
private pendingTools;
|
|
78
79
|
private ctxWarned;
|
|
79
80
|
logFile?: string;
|
|
80
|
-
|
|
81
|
+
model: string | undefined;
|
|
81
82
|
usageCap: number | undefined;
|
|
82
83
|
readonly allowExtraUsage: boolean;
|
|
83
84
|
extraUsageBudget: number | undefined;
|
|
84
85
|
readonly baseCostUsd: number;
|
|
85
86
|
mergeBranch?: string;
|
|
87
|
+
/** Permission mode read from config on each agent dispatch. Writable for mid-run changes. */
|
|
88
|
+
private _permMode;
|
|
86
89
|
constructor(config: SwarmConfig);
|
|
87
90
|
get active(): number;
|
|
88
91
|
get blocked(): number;
|
|
@@ -100,6 +103,10 @@ export declare class Swarm {
|
|
|
100
103
|
retryRateLimitNow(): void;
|
|
101
104
|
/** Live-adjust the overage spend cap. `undefined` = unlimited. If already over the new cap, stop dispatch. */
|
|
102
105
|
setExtraUsageBudget(n: number | undefined): void;
|
|
106
|
+
/** Live-adjust the worker model. Picked up by next agent dispatch. */
|
|
107
|
+
setModel(m: string): void;
|
|
108
|
+
/** Live-adjust the SDK permission mode. Picked up by next agent dispatch. */
|
|
109
|
+
setPermissionMode(m: PermMode): void;
|
|
103
110
|
run(): Promise<void>;
|
|
104
111
|
abort(): void;
|
|
105
112
|
/** Re-queue all errored agents' tasks for retry within this wave. */
|
package/dist/swarm.js
CHANGED
|
@@ -93,6 +93,8 @@ export class Swarm {
|
|
|
93
93
|
extraUsageBudget;
|
|
94
94
|
baseCostUsd;
|
|
95
95
|
mergeBranch;
|
|
96
|
+
/** Permission mode read from config on each agent dispatch. Writable for mid-run changes. */
|
|
97
|
+
_permMode;
|
|
96
98
|
constructor(config) {
|
|
97
99
|
if (!config.tasks.length)
|
|
98
100
|
throw new Error("SwarmConfig: tasks array must not be empty");
|
|
@@ -115,6 +117,7 @@ export class Swarm {
|
|
|
115
117
|
this.queue = [...config.tasks];
|
|
116
118
|
this.total = config.tasks.length;
|
|
117
119
|
this.targetConcurrency = config.concurrency;
|
|
120
|
+
this._permMode = config.permissionMode;
|
|
118
121
|
}
|
|
119
122
|
get active() { return this.agents.filter(a => a.status === "running").length; }
|
|
120
123
|
get blocked() { return this.agents.filter(a => a.status === "running" && a.blockedAt != null).length; }
|
|
@@ -210,6 +213,23 @@ export class Swarm {
|
|
|
210
213
|
this.capForOverage(`Extra usage budget $${n} exceeded ($${this.overageCostUsd.toFixed(2)} spent) -- stopping dispatch`);
|
|
211
214
|
}
|
|
212
215
|
}
|
|
216
|
+
/** Live-adjust the worker model. Picked up by next agent dispatch. */
|
|
217
|
+
setModel(m) {
|
|
218
|
+
if (this.model === m)
|
|
219
|
+
return;
|
|
220
|
+
const prev = this.model;
|
|
221
|
+
this.model = m;
|
|
222
|
+
this.log(-1, `Worker model: ${prev} → ${m}`);
|
|
223
|
+
}
|
|
224
|
+
/** Live-adjust the SDK permission mode. Picked up by next agent dispatch. */
|
|
225
|
+
setPermissionMode(m) {
|
|
226
|
+
if (this._permMode === m)
|
|
227
|
+
return;
|
|
228
|
+
const prev = this._permMode ?? "auto";
|
|
229
|
+
this._permMode = m;
|
|
230
|
+
const label = m === "bypassPermissions" ? "yolo" : m;
|
|
231
|
+
this.log(-1, `Permission mode: ${prev === "bypassPermissions" ? "yolo" : prev} → ${label}`);
|
|
232
|
+
}
|
|
213
233
|
async run() {
|
|
214
234
|
try {
|
|
215
235
|
if (this.config.useWorktrees) {
|
|
@@ -528,7 +548,7 @@ export class Swarm {
|
|
|
528
548
|
agent.finishedAt = undefined;
|
|
529
549
|
}
|
|
530
550
|
try {
|
|
531
|
-
const perm = this.
|
|
551
|
+
const perm = this._permMode ?? "auto";
|
|
532
552
|
let resumeSessionId = task.resumeSessionId;
|
|
533
553
|
let resumePrompt = "Continue. Complete the task.";
|
|
534
554
|
const runOnce = async (isResume) => {
|
package/dist/types.d.ts
CHANGED
|
@@ -203,6 +203,23 @@ export interface RunMemory {
|
|
|
203
203
|
/** Pending user directives from the steer inbox, consumed by the next successful steering call. */
|
|
204
204
|
userGuidance?: string;
|
|
205
205
|
}
|
|
206
|
+
/** Mutable subset of RunConfigBase — settings that can be changed at any point, including mid-run. */
|
|
207
|
+
export interface MutableRunSettings {
|
|
208
|
+
workerModel: string;
|
|
209
|
+
plannerModel: string;
|
|
210
|
+
fastModel?: string;
|
|
211
|
+
workerProviderId?: string;
|
|
212
|
+
plannerProviderId?: string;
|
|
213
|
+
fastProviderId?: string;
|
|
214
|
+
concurrency: number;
|
|
215
|
+
usageCap?: number;
|
|
216
|
+
allowExtraUsage: boolean;
|
|
217
|
+
extraUsageBudget?: number;
|
|
218
|
+
permissionMode: PermMode;
|
|
219
|
+
beforeWave?: string | string[];
|
|
220
|
+
afterWave?: string | string[];
|
|
221
|
+
afterRun?: string | string[];
|
|
222
|
+
}
|
|
206
223
|
/** Shared configuration for a run -- both live (RunConfig) and persisted (RunState). */
|
|
207
224
|
export interface RunConfigBase {
|
|
208
225
|
/** Total session budget. */
|
package/dist/ui.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Swarm } from "./swarm.js";
|
|
2
|
-
import type { RLGetter, WaveSummary } from "./types.js";
|
|
2
|
+
import type { RLGetter, WaveSummary, PermMode } from "./types.js";
|
|
3
3
|
import { InteractivePanel } from "./interactive-panel.js";
|
|
4
4
|
/** Short-lived context the steering view renders around its live log. */
|
|
5
5
|
export interface SteeringContext {
|
|
@@ -34,8 +34,16 @@ export interface LiveConfig {
|
|
|
34
34
|
concurrency: number;
|
|
35
35
|
paused: boolean;
|
|
36
36
|
dirty: boolean;
|
|
37
|
-
/** Overage spend cap ($) -- undefined = unlimited. Synced from the [
|
|
37
|
+
/** Overage spend cap ($) -- undefined = unlimited. Synced from the [s] hotkey. */
|
|
38
38
|
extraUsageBudget?: number;
|
|
39
|
+
/** Worker model for agent tasks. Changed mid-run, picked up by next wave/agent dispatch. */
|
|
40
|
+
workerModel?: string;
|
|
41
|
+
/** Planner/steering model. Changed mid-run, picked up by next steer/planner call. */
|
|
42
|
+
plannerModel?: string;
|
|
43
|
+
/** Fast model for quick verification tasks. */
|
|
44
|
+
fastModel?: string;
|
|
45
|
+
/** SDK permission mode. Changed mid-run, picked up by next agent dispatch. */
|
|
46
|
+
permissionMode?: PermMode;
|
|
39
47
|
}
|
|
40
48
|
/** State of an in-flight or recently-completed ask side query. */
|
|
41
49
|
export interface AskState {
|
|
@@ -57,6 +65,8 @@ export declare class RunDisplay {
|
|
|
57
65
|
private interval?;
|
|
58
66
|
private keyHandler?;
|
|
59
67
|
private inputMode;
|
|
68
|
+
/** Which field the settings editor is currently editing. Order: budget, cap, conc, extra, worker, planner, fast, perms, pause. */
|
|
69
|
+
private settingsField;
|
|
60
70
|
private inputSegs;
|
|
61
71
|
private started;
|
|
62
72
|
private readonly isTTY;
|
|
@@ -120,6 +130,8 @@ export declare class RunDisplay {
|
|
|
120
130
|
* The content area shrinks so input prompts are never clipped. */
|
|
121
131
|
private flush;
|
|
122
132
|
private render;
|
|
133
|
+
/** Read the current value for a settings field from liveConfig/swarm. */
|
|
134
|
+
private currentFieldValue;
|
|
123
135
|
private renderInputPrompt;
|
|
124
136
|
private hasHotkeys;
|
|
125
137
|
private setupHotkeys;
|
|
@@ -127,7 +139,7 @@ export declare class RunDisplay {
|
|
|
127
139
|
private handlePaste;
|
|
128
140
|
/** Keyboard handler used only while the panel is expanded fullscreen.
|
|
129
141
|
* Handles scroll + close. Swallows everything else so the normal hotkeys
|
|
130
|
-
* (
|
|
142
|
+
* (s/p/i/?/d/0-9) do not fire while the user is reading. */
|
|
131
143
|
private handlePanelKey;
|
|
132
144
|
/** Handle a typed (non-pasted) chunk. Returns true if the frame needs a redraw.
|
|
133
145
|
*
|
|
@@ -141,7 +153,7 @@ export declare class RunDisplay {
|
|
|
141
153
|
* 3. ESC alone → cancel input / close detail / dismiss panel
|
|
142
154
|
* 4. numeric input → digits, Enter, Backspace
|
|
143
155
|
* 5. text input → printable chars, Enter, Backspace, ESC (with lookahead)
|
|
144
|
-
* 6. hotkey mode →
|
|
156
|
+
* 6. hotkey mode → s (settings), i (inject), q, ?, d, 0-9, f, r, p
|
|
145
157
|
*/
|
|
146
158
|
private handleTyped;
|
|
147
159
|
private plainTick;
|
package/dist/ui.js
CHANGED
|
@@ -10,6 +10,8 @@ import { allTurns, cycleFocused } from "./turns.js";
|
|
|
10
10
|
const MAX_STEERING_EVENTS = 60;
|
|
11
11
|
const MAX_INPUT_LEN = 600;
|
|
12
12
|
const MAX_ASK_LINES = 40;
|
|
13
|
+
const SETTINGS_FIELDS = ["budget", "cap", "conc", "extra", "worker", "planner", "fast", "perms", "pause"];
|
|
14
|
+
const NUMERIC_SETTINGS_FIELDS = new Set(["budget", "cap", "conc", "extra"]);
|
|
13
15
|
/** Visible lines for the ask panel, clamped to leave room for header/content/footer/input. */
|
|
14
16
|
function askDisplayCap() {
|
|
15
17
|
return Math.max(3, Math.min(MAX_ASK_LINES, (process.stdout.rows || 40) - 20));
|
|
@@ -28,6 +30,8 @@ export class RunDisplay {
|
|
|
28
30
|
interval;
|
|
29
31
|
keyHandler;
|
|
30
32
|
inputMode = "none";
|
|
33
|
+
/** Which field the settings editor is currently editing. Order: budget, cap, conc, extra, worker, planner, fast, perms, pause. */
|
|
34
|
+
settingsField = 0;
|
|
31
35
|
inputSegs = [];
|
|
32
36
|
started = false;
|
|
33
37
|
isTTY;
|
|
@@ -442,24 +446,54 @@ export class RunDisplay {
|
|
|
442
446
|
}
|
|
443
447
|
return frame + bottom;
|
|
444
448
|
}
|
|
449
|
+
/** Read the current value for a settings field from liveConfig/swarm. */
|
|
450
|
+
currentFieldValue(field) {
|
|
451
|
+
const lc = this.liveConfig;
|
|
452
|
+
const s = this.swarm;
|
|
453
|
+
switch (field) {
|
|
454
|
+
case "budget": return String(lc?.remaining ?? "—");
|
|
455
|
+
case "cap": return lc?.usageCap != null ? `${Math.round(lc.usageCap * 100)}%` : "unlimited";
|
|
456
|
+
case "conc": return String(lc?.concurrency ?? "—");
|
|
457
|
+
case "extra": return lc?.extraUsageBudget != null ? `$${lc.extraUsageBudget}` : "unlimited";
|
|
458
|
+
case "worker": return lc?.workerModel ?? s?.model ?? "—";
|
|
459
|
+
case "planner": return lc?.plannerModel ?? "—";
|
|
460
|
+
case "fast": return lc?.fastModel ?? "(none)";
|
|
461
|
+
case "perms": {
|
|
462
|
+
const p = lc?.permissionMode ?? "auto";
|
|
463
|
+
return p === "bypassPermissions" ? "yolo" : p;
|
|
464
|
+
}
|
|
465
|
+
case "pause": return s?.paused ? "paused" : "running";
|
|
466
|
+
default: return "";
|
|
467
|
+
}
|
|
468
|
+
}
|
|
445
469
|
renderInputPrompt() {
|
|
446
470
|
if (this.inputMode === "none")
|
|
447
471
|
return "";
|
|
448
472
|
const rendered = renderSegments(this.inputSegs);
|
|
449
|
-
if (this.inputMode === "
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
473
|
+
if (this.inputMode === "settings") {
|
|
474
|
+
const labels = [
|
|
475
|
+
"New budget (remaining sessions)",
|
|
476
|
+
"New usage cap (0-100%, 0=unlimited)",
|
|
477
|
+
"New concurrency (min 1)",
|
|
478
|
+
"Extra usage $ cap (0=stop on overage)",
|
|
479
|
+
"Worker model (for agent tasks)",
|
|
480
|
+
"Planner model (steering/thinking)",
|
|
481
|
+
"Fast model (optional, empty=skip)",
|
|
482
|
+
"Permission mode (auto/yolo/prompt)",
|
|
483
|
+
"Pause/resume workers",
|
|
484
|
+
];
|
|
485
|
+
const total = SETTINGS_FIELDS.length;
|
|
486
|
+
const field = SETTINGS_FIELDS[this.settingsField % total];
|
|
487
|
+
const label = labels[this.settingsField % total];
|
|
488
|
+
const idx = this.settingsField + 1;
|
|
489
|
+
const currentValue = this.currentFieldValue(field);
|
|
490
|
+
const hint = field === "pause"
|
|
491
|
+
? chalk.dim(` (Enter to toggle, Tab to skip, Esc to exit)`)
|
|
492
|
+
: chalk.dim(` [${idx}/${total}] Tab=next Esc=exit current: ${chalk.white(currentValue)}`);
|
|
493
|
+
return `\n ${chalk.cyan("◆")} ${chalk.bold(label)}${hint}\n ${rendered}\u2588`;
|
|
460
494
|
}
|
|
461
495
|
if (this.inputMode === "steer") {
|
|
462
|
-
return `\n ${chalk.cyan(">")} ${chalk.bold("
|
|
496
|
+
return `\n ${chalk.cyan(">")} ${chalk.bold("Inject next wave")} ${chalk.dim("(Enter to queue, Esc to cancel)")}\n ${rendered}\u2588`;
|
|
463
497
|
}
|
|
464
498
|
if (this.inputMode === "ask") {
|
|
465
499
|
return `\n ${chalk.cyan(">")} ${chalk.bold("Ask the planner")} ${chalk.dim("(Enter to send, Esc to cancel)")}\n ${rendered}\u2588`;
|
|
@@ -503,11 +537,18 @@ export class RunDisplay {
|
|
|
503
537
|
}
|
|
504
538
|
/** Handle a pasted block. Returns true if the frame needs a redraw. */
|
|
505
539
|
handlePaste(text) {
|
|
506
|
-
if (this.inputMode === "
|
|
507
|
-
const
|
|
508
|
-
if (
|
|
509
|
-
|
|
510
|
-
|
|
540
|
+
if (this.inputMode === "settings") {
|
|
541
|
+
const field = SETTINGS_FIELDS[this.settingsField % SETTINGS_FIELDS.length];
|
|
542
|
+
if (NUMERIC_SETTINGS_FIELDS.has(field)) {
|
|
543
|
+
const clean = text.replace(/[^0-9.]/g, "");
|
|
544
|
+
if (clean)
|
|
545
|
+
appendCharToSegments(this.inputSegs, clean);
|
|
546
|
+
return !!clean;
|
|
547
|
+
}
|
|
548
|
+
if (field !== "pause" && text.length + segmentsToString(this.inputSegs).length <= MAX_INPUT_LEN) {
|
|
549
|
+
appendPasteToSegments(this.inputSegs, text);
|
|
550
|
+
return true;
|
|
551
|
+
}
|
|
511
552
|
}
|
|
512
553
|
if (this.inputMode === "steer" || this.inputMode === "ask") {
|
|
513
554
|
if (segmentsToString(this.inputSegs).length + text.length > MAX_INPUT_LEN)
|
|
@@ -519,7 +560,7 @@ export class RunDisplay {
|
|
|
519
560
|
}
|
|
520
561
|
/** Keyboard handler used only while the panel is expanded fullscreen.
|
|
521
562
|
* Handles scroll + close. Swallows everything else so the normal hotkeys
|
|
522
|
-
* (
|
|
563
|
+
* (s/p/i/?/d/0-9) do not fire while the user is reading. */
|
|
523
564
|
handlePanelKey(s) {
|
|
524
565
|
const bodyRows = Math.max(3, (process.stdout.rows || 40) - 7);
|
|
525
566
|
// CSI sequences: arrows, PgUp/PgDn, Home/End
|
|
@@ -606,14 +647,14 @@ export class RunDisplay {
|
|
|
606
647
|
* 3. ESC alone → cancel input / close detail / dismiss panel
|
|
607
648
|
* 4. numeric input → digits, Enter, Backspace
|
|
608
649
|
* 5. text input → printable chars, Enter, Backspace, ESC (with lookahead)
|
|
609
|
-
* 6. hotkey mode →
|
|
650
|
+
* 6. hotkey mode → s (settings), i (inject), q, ?, d, 0-9, f, r, p
|
|
610
651
|
*/
|
|
611
652
|
handleTyped(s) {
|
|
612
653
|
const lc = this.liveConfig;
|
|
613
654
|
// ── 0. Fullscreen panel owns the keyboard ──
|
|
614
655
|
// While the interactive panel is expanded it takes over the screen and
|
|
615
656
|
// steals every key except Esc, Ctrl-O (close), Ctrl-C (abort), and scroll.
|
|
616
|
-
// Hotkeys like
|
|
657
|
+
// Hotkeys like s/i/p are intentionally swallowed so the user can
|
|
617
658
|
// read without triggering side effects.
|
|
618
659
|
if (this.panel.state.expanded) {
|
|
619
660
|
return this.handlePanelKey(s);
|
|
@@ -662,37 +703,93 @@ export class RunDisplay {
|
|
|
662
703
|
}
|
|
663
704
|
return false;
|
|
664
705
|
}
|
|
665
|
-
|
|
666
|
-
|
|
706
|
+
if (s === "\t" && this.inputMode === "settings") {
|
|
707
|
+
const field = SETTINGS_FIELDS[this.settingsField % SETTINGS_FIELDS.length];
|
|
708
|
+
if (field === "pause" && this.swarm) {
|
|
709
|
+
const next = !this.swarm.paused;
|
|
710
|
+
this.swarm.setPaused(next);
|
|
711
|
+
this.liveConfig.paused = next;
|
|
712
|
+
this.liveConfig.dirty = true;
|
|
713
|
+
}
|
|
714
|
+
this.settingsField++;
|
|
715
|
+
this.inputSegs = [];
|
|
716
|
+
if (this.settingsField >= SETTINGS_FIELDS.length)
|
|
717
|
+
this.inputMode = "none";
|
|
718
|
+
return true;
|
|
719
|
+
}
|
|
720
|
+
// ── 4. Settings mode: all mutable fields ──
|
|
721
|
+
if (this.inputMode === "settings") {
|
|
667
722
|
let dirty = false;
|
|
668
723
|
for (const ch of s) {
|
|
669
724
|
if (ch === "\r" || ch === "\n") {
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
725
|
+
const field = SETTINGS_FIELDS[this.settingsField % SETTINGS_FIELDS.length];
|
|
726
|
+
const raw = segmentsToString(this.inputSegs).trim();
|
|
727
|
+
if (field === "budget") {
|
|
728
|
+
const val = parseFloat(raw);
|
|
729
|
+
if (!isNaN(val) && val > 0) {
|
|
730
|
+
lc.remaining = Math.round(val);
|
|
731
|
+
lc.dirty = true;
|
|
732
|
+
this.swarm?.log(-1, `Budget changed to ${lc.remaining} remaining`);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
else if (field === "cap") {
|
|
736
|
+
const val = parseFloat(raw);
|
|
737
|
+
if (!isNaN(val) && val >= 0 && val <= 100) {
|
|
738
|
+
const frac = val / 100;
|
|
739
|
+
lc.usageCap = frac > 0 ? frac : undefined;
|
|
740
|
+
lc.dirty = true;
|
|
741
|
+
if (this.swarm)
|
|
742
|
+
this.swarm.usageCap = lc.usageCap;
|
|
743
|
+
this.swarm?.log(-1, `Usage cap changed to ${val > 0 ? val + "%" : "unlimited"}`);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
else if (field === "conc") {
|
|
747
|
+
const val = parseFloat(raw);
|
|
748
|
+
if (!isNaN(val) && val >= 1) {
|
|
749
|
+
const n = Math.round(val);
|
|
750
|
+
lc.concurrency = n;
|
|
751
|
+
lc.dirty = true;
|
|
752
|
+
this.swarm?.setConcurrency(n);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
else if (field === "extra") {
|
|
756
|
+
const val = parseFloat(raw);
|
|
757
|
+
if (!isNaN(val) && val >= 0) {
|
|
758
|
+
lc.extraUsageBudget = val;
|
|
759
|
+
lc.dirty = true;
|
|
760
|
+
this.swarm?.setExtraUsageBudget(val);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
else if (field === "worker" && raw) {
|
|
764
|
+
lc.workerModel = raw;
|
|
673
765
|
lc.dirty = true;
|
|
674
|
-
this.swarm?.
|
|
766
|
+
this.swarm?.setModel(raw);
|
|
675
767
|
}
|
|
676
|
-
else if (
|
|
677
|
-
|
|
678
|
-
lc.usageCap = frac > 0 ? frac : undefined;
|
|
768
|
+
else if (field === "planner" && raw) {
|
|
769
|
+
lc.plannerModel = raw;
|
|
679
770
|
lc.dirty = true;
|
|
680
|
-
if (this.swarm)
|
|
681
|
-
this.swarm.usageCap = lc.usageCap;
|
|
682
|
-
this.swarm?.log(-1, `Usage cap changed to ${val > 0 ? val + "%" : "unlimited"}`);
|
|
683
771
|
}
|
|
684
|
-
else if (
|
|
685
|
-
|
|
686
|
-
lc.concurrency = n;
|
|
772
|
+
else if (field === "fast") {
|
|
773
|
+
lc.fastModel = raw || undefined;
|
|
687
774
|
lc.dirty = true;
|
|
688
|
-
this.swarm?.setConcurrency(n);
|
|
689
775
|
}
|
|
690
|
-
else if (
|
|
691
|
-
|
|
776
|
+
else if (field === "perms" && raw) {
|
|
777
|
+
const m = raw.toLowerCase();
|
|
778
|
+
const mode = m.startsWith("yolo") || m.startsWith("bypass") ? "bypassPermissions"
|
|
779
|
+
: m.startsWith("prompt") || m === "default" ? "default" : "auto";
|
|
780
|
+
lc.permissionMode = mode;
|
|
692
781
|
lc.dirty = true;
|
|
693
|
-
this.swarm?.
|
|
782
|
+
this.swarm?.setPermissionMode(mode);
|
|
694
783
|
}
|
|
695
|
-
|
|
784
|
+
else if (field === "pause" && this.swarm) {
|
|
785
|
+
const next = !this.swarm.paused;
|
|
786
|
+
this.swarm.setPaused(next);
|
|
787
|
+
lc.paused = next;
|
|
788
|
+
lc.dirty = true;
|
|
789
|
+
}
|
|
790
|
+
this.settingsField++;
|
|
791
|
+
if (this.settingsField >= SETTINGS_FIELDS.length)
|
|
792
|
+
this.inputMode = "none";
|
|
696
793
|
this.inputSegs = [];
|
|
697
794
|
return true;
|
|
698
795
|
}
|
|
@@ -706,9 +803,19 @@ export class RunDisplay {
|
|
|
706
803
|
dirty = true;
|
|
707
804
|
continue;
|
|
708
805
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
806
|
+
const field = SETTINGS_FIELDS[this.settingsField % SETTINGS_FIELDS.length];
|
|
807
|
+
if (NUMERIC_SETTINGS_FIELDS.has(field)) {
|
|
808
|
+
if (/^[0-9.]$/.test(ch)) {
|
|
809
|
+
appendCharToSegments(this.inputSegs, ch);
|
|
810
|
+
dirty = true;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
else if (field !== "pause") {
|
|
814
|
+
const code = ch.charCodeAt(0);
|
|
815
|
+
if (code >= 0x20 && code <= 0x7E) {
|
|
816
|
+
appendCharToSegments(this.inputSegs, ch);
|
|
817
|
+
dirty = true;
|
|
818
|
+
}
|
|
712
819
|
}
|
|
713
820
|
}
|
|
714
821
|
return dirty;
|
|
@@ -803,35 +910,14 @@ export class RunDisplay {
|
|
|
803
910
|
const code = key.charCodeAt(0);
|
|
804
911
|
if (code < 0x20 || code > 0x7E)
|
|
805
912
|
return false;
|
|
806
|
-
if (key === "
|
|
807
|
-
this.
|
|
913
|
+
if (key === "s" || key === "S") {
|
|
914
|
+
if (!this.swarm)
|
|
915
|
+
return false;
|
|
916
|
+
this.inputMode = "settings";
|
|
917
|
+
this.settingsField = 0;
|
|
808
918
|
this.inputSegs = [];
|
|
809
919
|
return true;
|
|
810
920
|
}
|
|
811
|
-
if (key === "t" || key === "T") {
|
|
812
|
-
if (this.swarm) {
|
|
813
|
-
this.inputMode = "threshold";
|
|
814
|
-
this.inputSegs = [];
|
|
815
|
-
return true;
|
|
816
|
-
}
|
|
817
|
-
return false;
|
|
818
|
-
}
|
|
819
|
-
if (key === "c" || key === "C") {
|
|
820
|
-
if (this.swarm) {
|
|
821
|
-
this.inputMode = "concurrency";
|
|
822
|
-
this.inputSegs = [];
|
|
823
|
-
return true;
|
|
824
|
-
}
|
|
825
|
-
return false;
|
|
826
|
-
}
|
|
827
|
-
if (key === "e" || key === "E") {
|
|
828
|
-
if (this.swarm) {
|
|
829
|
-
this.inputMode = "extra";
|
|
830
|
-
this.inputSegs = [];
|
|
831
|
-
return true;
|
|
832
|
-
}
|
|
833
|
-
return false;
|
|
834
|
-
}
|
|
835
921
|
if (key === "p" || key === "P") {
|
|
836
922
|
if (this.swarm) {
|
|
837
923
|
const next = !this.swarm.paused;
|
|
@@ -850,7 +936,7 @@ export class RunDisplay {
|
|
|
850
936
|
this.swarm.retryRateLimitNow();
|
|
851
937
|
return true;
|
|
852
938
|
}
|
|
853
|
-
if ((key === "
|
|
939
|
+
if ((key === "i" || key === "I") && this.onSteer) {
|
|
854
940
|
this.inputMode = "steer";
|
|
855
941
|
this.inputSegs = [];
|
|
856
942
|
return true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-overnight",
|
|
3
|
-
"version": "1.25.
|
|
3
|
+
"version": "1.25.33",
|
|
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.33",
|
|
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"
|