claude-overnight 1.25.29 → 1.25.31
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 +80 -169
- package/dist/providers.d.ts +11 -2
- package/dist/providers.js +19 -8
- package/dist/proxy-port.d.ts +4 -0
- package/dist/proxy-port.js +28 -0
- 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/swarm.d.ts +9 -2
- package/dist/swarm.js +23 -3
- 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.31";
|
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.31";
|
package/dist/index.js
CHANGED
|
@@ -7,16 +7,18 @@ 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
|
+
import { getProxyPort, buildProxyUrl } from "./proxy-port.js";
|
|
13
14
|
import { pickModel, loadProviders, preflightProvider, buildEnvResolver, healthCheckCursorProxy, PROXY_DEFAULT_URL, isCursorProxyProvider, readCursorProxyLogTail, ensureCursorProxyRunning, bundledComposerProxyShellCommand, warnMacCursorAgentShellPatchIfNeeded, hasCursorAgentToken, } from "./providers.js";
|
|
14
15
|
import { RunDisplay } from "./ui.js";
|
|
15
16
|
import { renderSummary, wrap } from "./render.js";
|
|
16
17
|
import { executeRun } from "./run.js";
|
|
17
|
-
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";
|
|
18
19
|
import { loadRunState, findIncompleteRuns, findOrphanedDesigns, backfillOrphanedPlans, formatTimeAgo, showRunHistory, readPreviousRunKnowledge, createRunDir, updateLatestSymlink, readMdDir, saveRunState, autoMergeBranches, } from "./state.js";
|
|
19
20
|
import { runSetupCoach, loadUserSettings, saveUserSettings, COACH_MODEL } from "./coach.js";
|
|
21
|
+
import { editRunSettings, formatSettingsSummary } from "./settings.js";
|
|
20
22
|
function countTasksInFile(path) {
|
|
21
23
|
try {
|
|
22
24
|
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
@@ -65,8 +67,6 @@ async function promptResumeOverrides(state, cliFlags, argv, noTTY, runDir) {
|
|
|
65
67
|
catch { }
|
|
66
68
|
return;
|
|
67
69
|
}
|
|
68
|
-
// Kick off model fetch in the background so it's ready if the user picks Edit.
|
|
69
|
-
const modelsPromise = fetchModels(20_000).catch(() => []);
|
|
70
70
|
// ── Interactive review ──
|
|
71
71
|
const fmtSummary = () => {
|
|
72
72
|
const remaining = Math.max(1, state.remaining);
|
|
@@ -89,6 +89,7 @@ async function promptResumeOverrides(state, cliFlags, argv, noTTY, runDir) {
|
|
|
89
89
|
console.log(` ${chalk.dim("concur ")}${chalk.white(String(state.concurrency))}`);
|
|
90
90
|
console.log(` ${chalk.dim("usage cap ")}${chalk.white(capStr)}`);
|
|
91
91
|
console.log(` ${chalk.dim("extra ")}${chalk.white(extraStr)}`);
|
|
92
|
+
console.log(` ${chalk.dim("perms ")}${chalk.white(state.permissionMode === "bypassPermissions" ? "yolo" : state.permissionMode)}`);
|
|
92
93
|
};
|
|
93
94
|
fmtSummary();
|
|
94
95
|
const action = await selectKey("", [
|
|
@@ -100,64 +101,24 @@ async function promptResumeOverrides(state, cliFlags, argv, noTTY, runDir) {
|
|
|
100
101
|
process.exit(0);
|
|
101
102
|
if (action === "r")
|
|
102
103
|
return;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (!isNaN(parsedRem) && parsedRem > 0) {
|
|
122
|
-
state.remaining = parsedRem;
|
|
123
|
-
state.budget = state.accCompleted + state.accFailed + parsedRem;
|
|
124
|
-
}
|
|
125
|
-
const concAns = await ask(`\n ${chalk.cyan("③")} Concurrency ${chalk.dim(`[${state.concurrency}]:`)} `);
|
|
126
|
-
const parsedConc = parseInt(concAns);
|
|
127
|
-
if (!isNaN(parsedConc) && parsedConc >= 1)
|
|
128
|
-
state.concurrency = parsedConc;
|
|
129
|
-
const currentCap = state.usageCap != null ? String(Math.round(state.usageCap * 100)) : "off";
|
|
130
|
-
const capAns = await ask(`\n ${chalk.cyan("④")} Usage cap % ${chalk.dim(`[${currentCap}]`)} ${chalk.dim("(0 = off):")} `);
|
|
131
|
-
if (capAns.trim()) {
|
|
132
|
-
const v = parseFloat(capAns);
|
|
133
|
-
if (!isNaN(v) && v >= 0 && v <= 100)
|
|
134
|
-
state.usageCap = v > 0 ? v / 100 : undefined;
|
|
135
|
-
}
|
|
136
|
-
const currentExtra = state.allowExtraUsage
|
|
137
|
-
? (state.extraUsageBudget ? `$${state.extraUsageBudget}` : "unlimited")
|
|
138
|
-
: "off";
|
|
139
|
-
const extraChoice = await select(`${chalk.cyan("⑤")} Extra usage ${chalk.dim(`[current: ${currentExtra}]`)}:`, [
|
|
140
|
-
{ name: "Keep current", value: "keep" },
|
|
141
|
-
{ name: "Off", value: "off", hint: "stop at plan limit" },
|
|
142
|
-
{ name: "With $ cap", value: "budget", hint: "set a spending cap" },
|
|
143
|
-
{ name: "Unlimited", value: "unlimited", hint: "no cap, billed as overage" },
|
|
144
|
-
]);
|
|
145
|
-
if (extraChoice === "off") {
|
|
146
|
-
state.allowExtraUsage = false;
|
|
147
|
-
state.extraUsageBudget = undefined;
|
|
148
|
-
}
|
|
149
|
-
else if (extraChoice === "budget") {
|
|
150
|
-
const bAns = await ask(` ${chalk.dim("Max extra $:")} `);
|
|
151
|
-
const bVal = parseFloat(bAns);
|
|
152
|
-
if (!isNaN(bVal) && bVal > 0) {
|
|
153
|
-
state.extraUsageBudget = bVal;
|
|
154
|
-
state.allowExtraUsage = true;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
else if (extraChoice === "unlimited") {
|
|
158
|
-
state.allowExtraUsage = true;
|
|
159
|
-
state.extraUsageBudget = undefined;
|
|
160
|
-
}
|
|
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);
|
|
161
122
|
try {
|
|
162
123
|
saveRunState(runDir, state);
|
|
163
124
|
}
|
|
@@ -636,7 +597,6 @@ async function main() {
|
|
|
636
597
|
objective = coachResult.improvedObjective;
|
|
637
598
|
}
|
|
638
599
|
}
|
|
639
|
-
const modelsPromise = fetchModels();
|
|
640
600
|
const defaultBudget = coachResult?.recommended.budget ?? 10;
|
|
641
601
|
const budgetAns = await ask(`\n ${chalk.cyan("②")} ${chalk.dim("Budget")} ${chalk.dim("[")}${chalk.white(String(defaultBudget))}${chalk.dim("]:")} `);
|
|
642
602
|
budget = parseInt(budgetAns) || defaultBudget;
|
|
@@ -644,91 +604,42 @@ async function main() {
|
|
|
644
604
|
console.error(chalk.red(` Budget must be a positive number`));
|
|
645
605
|
process.exit(1);
|
|
646
606
|
}
|
|
647
|
-
// ③ Max concurrency (skip if --concurrency set)
|
|
648
|
-
if (cliFlags.concurrency) {
|
|
649
|
-
concurrency = parseInt(cliFlags.concurrency);
|
|
650
|
-
}
|
|
651
|
-
else {
|
|
652
|
-
const defaultC = Math.min(coachResult?.recommended.concurrency ?? 5, budget);
|
|
653
|
-
const concAns = await ask(`\n ${chalk.cyan("③")} ${chalk.dim("Max concurrency")} ${chalk.dim("[")}${chalk.white(String(defaultC))}${chalk.dim("]:")} `);
|
|
654
|
-
concurrency = parseInt(concAns) || defaultC;
|
|
655
|
-
if (concurrency < 1)
|
|
656
|
-
concurrency = 1;
|
|
657
|
-
}
|
|
658
|
-
let modelFrame = 0;
|
|
659
|
-
const modelSpinner = setInterval(() => {
|
|
660
|
-
process.stdout.write(`\x1B[2K\r ${chalk.cyan(BRAILLE[modelFrame++ % BRAILLE.length])} ${chalk.dim("loading models...")}`);
|
|
661
|
-
}, 120);
|
|
662
|
-
let models;
|
|
663
|
-
try {
|
|
664
|
-
models = await modelsPromise;
|
|
665
|
-
}
|
|
666
|
-
finally {
|
|
667
|
-
clearInterval(modelSpinner);
|
|
668
|
-
process.stdout.write(`\x1B[2K\r`);
|
|
669
|
-
}
|
|
670
|
-
const plannerPick = await pickModel(`${chalk.cyan("④")} Planner model ${chalk.dim("(thinking, steering -- use your strongest)")}:`, models, coachResult?.recommended.plannerModel);
|
|
671
|
-
plannerModel = plannerPick.model;
|
|
672
|
-
plannerProvider = plannerPick.provider;
|
|
673
|
-
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);
|
|
674
|
-
workerModel = workerPick.model;
|
|
675
|
-
workerProvider = workerPick.provider;
|
|
676
|
-
// ⑤b Optional fast model for quick tasks that will be verified
|
|
677
|
-
const suggestFast = !!coachResult?.recommended.fastModel;
|
|
678
|
-
const fastChoice = await select(`${chalk.cyan("⑤b")} Fast model ${chalk.dim("(optional -- Haiku/Qwen for quick tasks, checked by worker)")}:`, [
|
|
679
|
-
{ name: "Skip", value: "skip", hint: "two-tier mode only (current setup)" },
|
|
680
|
-
{ name: "Pick a fast model", value: "pick", hint: "Haiku, Qwen, or any provider -- for well-scoped tasks" },
|
|
681
|
-
], suggestFast ? 1 : 0);
|
|
682
|
-
if (fastChoice === "pick") {
|
|
683
|
-
const fastPick = await pickModel(`${chalk.cyan("⑤c")} Fast model:`, models, coachResult?.recommended.fastModel ?? undefined);
|
|
684
|
-
fastModel = fastPick.model;
|
|
685
|
-
fastProvider = fastPick.provider;
|
|
686
|
-
}
|
|
687
|
-
const usageCapItems = [
|
|
688
|
-
{ name: "Unlimited", value: undefined, hint: "full capacity, wait through rate limits" },
|
|
689
|
-
{ name: "90%", value: 0.9, hint: "leave 10% for other work" },
|
|
690
|
-
{ name: "75%", value: 0.75, hint: "conservative, plenty of headroom" },
|
|
691
|
-
{ name: "50%", value: 0.5, hint: "use half, keep the rest" },
|
|
692
|
-
];
|
|
693
|
-
const coachCap = coachResult?.recommended.usageCap;
|
|
694
|
-
const usageCapDefault = coachCap == null ? 0
|
|
695
|
-
: coachCap >= 0.85 ? 1
|
|
696
|
-
: coachCap >= 0.6 ? 2
|
|
697
|
-
: 3;
|
|
698
|
-
usageCap = await select(`${chalk.cyan("⑥")} Usage cap:`, usageCapItems, usageCapDefault);
|
|
699
|
-
const extraChoice = await select(`${chalk.cyan("⑦")} Allow extra usage ${chalk.dim("(billed separately)")}:`, [
|
|
700
|
-
{ name: "No", value: "no", hint: "stop when plan limits are reached" },
|
|
701
|
-
{ name: "Yes, with $ limit", value: "budget", hint: "set a spending cap" },
|
|
702
|
-
{ name: "Yes, unlimited", value: "unlimited", hint: "keep going no matter what" },
|
|
703
|
-
]);
|
|
704
|
-
if (extraChoice === "budget") {
|
|
705
|
-
const budgetAns2 = await ask(` ${chalk.dim("Max extra usage $:")} `);
|
|
706
|
-
extraUsageBudget = parseFloat(budgetAns2);
|
|
707
|
-
if (!extraUsageBudget || extraUsageBudget <= 0)
|
|
708
|
-
extraUsageBudget = 5;
|
|
709
|
-
allowExtraUsage = true;
|
|
710
|
-
}
|
|
711
|
-
else if (extraChoice === "unlimited")
|
|
712
|
-
allowExtraUsage = true;
|
|
713
|
-
// ⑧ Permission mode (skip if --yolo or --perm set)
|
|
714
607
|
const cliYolo = argv.includes("--yolo");
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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)
|
|
732
643
|
const gitRepo = isGitRepo(cwd);
|
|
733
644
|
if (cliYolo || argv.includes("--no-worktrees")) {
|
|
734
645
|
useWorktrees = false;
|
|
@@ -739,7 +650,7 @@ async function main() {
|
|
|
739
650
|
mergeStrategy = cliFlags.merge || "yolo";
|
|
740
651
|
}
|
|
741
652
|
else if (gitRepo) {
|
|
742
|
-
const wtChoice = await select(`${chalk.cyan("
|
|
653
|
+
const wtChoice = await select(`${chalk.cyan("④b")} Git isolation:`, [
|
|
743
654
|
{ name: "Worktrees + yolo merge", value: "wt-yolo", hint: "isolate agents, merge into current branch" },
|
|
744
655
|
{ name: "Worktrees + new branch", value: "wt-branch", hint: "isolate agents, merge into a new branch" },
|
|
745
656
|
{ name: "No worktrees", value: "no-wt", hint: "all agents share the working directory" },
|
|
@@ -751,31 +662,20 @@ async function main() {
|
|
|
751
662
|
useWorktrees = false;
|
|
752
663
|
mergeStrategy = "yolo";
|
|
753
664
|
}
|
|
754
|
-
const
|
|
755
|
-
|
|
756
|
-
parts.push(`${modelDisplayName(plannerModel)} → ${modelDisplayName(workerModel)} + ${modelDisplayName(fastModel)}`);
|
|
757
|
-
else if (workerModel !== plannerModel)
|
|
758
|
-
parts.push(`${modelDisplayName(workerModel)} → ${modelDisplayName(plannerModel)}`);
|
|
759
|
-
else
|
|
760
|
-
parts.push(modelDisplayName(workerModel));
|
|
761
|
-
parts.push(`budget ${budget}`, `${concurrency}×`);
|
|
665
|
+
const inner = formatSettingsSummary({ ...settings, permissionMode });
|
|
666
|
+
const parts2 = [`budget ${budget}`, `${concurrency}×`];
|
|
762
667
|
if (budget > 2)
|
|
763
|
-
|
|
764
|
-
if (usageCap != null)
|
|
765
|
-
parts.push(`cap ${Math.round(usageCap * 100)}%`);
|
|
766
|
-
parts.push(allowExtraUsage ? (extraUsageBudget ? `extra $${extraUsageBudget}` : "extra ∞") : "no extra");
|
|
767
|
-
if (permissionMode !== "auto")
|
|
768
|
-
parts.push(permissionMode === "bypassPermissions" ? "yolo" : "prompt");
|
|
668
|
+
parts2.push("flex");
|
|
769
669
|
if (useWorktrees)
|
|
770
|
-
|
|
670
|
+
parts2.push(mergeStrategy === "branch" ? "wt→branch" : "wt→yolo");
|
|
771
671
|
else
|
|
772
|
-
|
|
672
|
+
parts2.push("no wt");
|
|
773
673
|
if (completedRuns.length > 0)
|
|
774
|
-
|
|
775
|
-
const
|
|
776
|
-
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;
|
|
777
677
|
console.log(chalk.dim(`\n ╭${"─".repeat(innerLen + 4)}╮`));
|
|
778
|
-
console.log(chalk.dim(" │") + ` ${
|
|
678
|
+
console.log(chalk.dim(" │") + ` ${fullLine} ` + chalk.dim("│"));
|
|
779
679
|
console.log(chalk.dim(` ╰${"─".repeat(innerLen + 4)}╯`));
|
|
780
680
|
}
|
|
781
681
|
else {
|
|
@@ -889,7 +789,18 @@ async function main() {
|
|
|
889
789
|
}
|
|
890
790
|
// Auto-start cursor proxy before pinging (restarts when a token exists so stale listeners get CURSOR_API_KEY).
|
|
891
791
|
if (cursorProxies.length > 0) {
|
|
892
|
-
|
|
792
|
+
const resolvedPort = getProxyPort(cwd);
|
|
793
|
+
const resolvedUrl = buildProxyUrl(resolvedPort);
|
|
794
|
+
await ensureCursorProxyRunning(resolvedUrl);
|
|
795
|
+
// Sync providers to the resolved port (may differ from default if per-project port was picked)
|
|
796
|
+
for (const p of cursorProxies) {
|
|
797
|
+
if (!p.baseURL || p.baseURL === PROXY_DEFAULT_URL) {
|
|
798
|
+
p.baseURL = resolvedUrl;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
if (resolvedUrl !== PROXY_DEFAULT_URL) {
|
|
802
|
+
console.log(chalk.dim(` Proxy port: ${resolvedPort}`));
|
|
803
|
+
}
|
|
893
804
|
if (!hasCursorAgentToken()) {
|
|
894
805
|
console.error(chalk.red(` ✗ Cursor models require a User API key — add it via ${chalk.bold("Cursor…")} setup, or set ` +
|
|
895
806
|
`${chalk.bold("CURSOR_API_KEY")} / ${chalk.bold("CURSOR_BRIDGE_API_KEY")}, or ${chalk.bold("cursorApiKey")} in providers.json.`));
|
package/dist/providers.d.ts
CHANGED
|
@@ -108,6 +108,11 @@ export declare function healthCheckCursorProxy(baseUrl?: string): Promise<boolea
|
|
|
108
108
|
* Returns model IDs like ["auto", "composer", "composer-2", "opus-4.6", ...].
|
|
109
109
|
*/
|
|
110
110
|
export declare function fetchCursorModels(baseUrl?: string): Promise<string[]>;
|
|
111
|
+
/** Options for {@link ensureCursorProxyRunning}. */
|
|
112
|
+
export interface EnsureProxyOptions {
|
|
113
|
+
forceRestart?: boolean;
|
|
114
|
+
projectRoot?: string;
|
|
115
|
+
}
|
|
111
116
|
/**
|
|
112
117
|
* Auto-start the cursor-composer-in-claude as a detached background process.
|
|
113
118
|
*
|
|
@@ -123,9 +128,13 @@ export declare function fetchCursorModels(baseUrl?: string): Promise<string[]>;
|
|
|
123
128
|
* When `forceRestart` is true, any listener on the port is killed and the
|
|
124
129
|
* bundled proxy is spawned (same as a version mismatch).
|
|
125
130
|
*
|
|
126
|
-
*
|
|
131
|
+
* When `projectRoot` is provided and `baseUrl` is the default, a per-project
|
|
132
|
+
* port is resolved from `.claude-overnight/config.json` so concurrent runs
|
|
133
|
+
* in different repos don't collide on port 8765.
|
|
134
|
+
*
|
|
135
|
+
* Returns true when the proxy is reachable.
|
|
127
136
|
*/
|
|
128
|
-
export declare function ensureCursorProxyRunning(baseUrl?: string,
|
|
137
|
+
export declare function ensureCursorProxyRunning(baseUrl?: string, opts?: EnsureProxyOptions): Promise<boolean>;
|
|
129
138
|
/**
|
|
130
139
|
* Full install + configure flow for cursor-composer-in-claude.
|
|
131
140
|
* Walks through CLI install, API key config, and proxy start.
|
package/dist/providers.js
CHANGED
|
@@ -11,6 +11,7 @@ import { getBearerToken, clearTokenCache } from "./auth.js";
|
|
|
11
11
|
import { DEFAULT_MODEL } from "./models.js";
|
|
12
12
|
import { CURSOR_PRIORITY_MODELS, CURSOR_KNOWN_MODELS, KNOWN_CURSOR_MODEL_IDS, cursorModelHint, } from "./cursor-models.js";
|
|
13
13
|
import { VERSION } from "./_version.js";
|
|
14
|
+
import { getProxyPort, buildProxyUrl } from "./proxy-port.js";
|
|
14
15
|
/** Cached system Node.js and agent script paths — resolved once, reused across envFor calls. */
|
|
15
16
|
let _cachedAgentNode = null;
|
|
16
17
|
let _cachedAgentScript = null;
|
|
@@ -812,13 +813,23 @@ async function isPortInUse(port, host = "127.0.0.1") {
|
|
|
812
813
|
* When `forceRestart` is true, any listener on the port is killed and the
|
|
813
814
|
* bundled proxy is spawned (same as a version mismatch).
|
|
814
815
|
*
|
|
815
|
-
*
|
|
816
|
+
* When `projectRoot` is provided and `baseUrl` is the default, a per-project
|
|
817
|
+
* port is resolved from `.claude-overnight/config.json` so concurrent runs
|
|
818
|
+
* in different repos don't collide on port 8765.
|
|
819
|
+
*
|
|
820
|
+
* Returns true when the proxy is reachable.
|
|
816
821
|
*/
|
|
817
|
-
export async function ensureCursorProxyRunning(baseUrl = PROXY_DEFAULT_URL,
|
|
822
|
+
export async function ensureCursorProxyRunning(baseUrl = PROXY_DEFAULT_URL, opts) {
|
|
818
823
|
warnMacCursorAgentShellPatchIfNeeded();
|
|
819
|
-
|
|
820
|
-
const
|
|
821
|
-
|
|
824
|
+
// Resolve per-project port if no explicit base URL was given and projectRoot is available
|
|
825
|
+
const resolvedPort = opts?.projectRoot && baseUrl === PROXY_DEFAULT_URL
|
|
826
|
+
? getProxyPort(opts.projectRoot)
|
|
827
|
+
: null;
|
|
828
|
+
const effectiveBaseUrl = resolvedPort != null ? buildProxyUrl(resolvedPort) : baseUrl;
|
|
829
|
+
const url = new URL(effectiveBaseUrl);
|
|
830
|
+
const port = resolvedPort ?? (parseInt(url.port, 10) || 80);
|
|
831
|
+
const forceRestart = opts?.forceRestart ?? false;
|
|
832
|
+
// Stale listener may have been started without CURSOR_API_KEY for the agent child.
|
|
822
833
|
// When we have a token, replace the listener by default so the bundled proxy always inherits it.
|
|
823
834
|
// Opt out: CURSOR_OVERNIGHT_NO_PROXY_RESTART=1 (e.g. shared port / external proxy).
|
|
824
835
|
const token = resolveCursorAgentToken();
|
|
@@ -961,7 +972,7 @@ async function startProxyProcess(baseUrl, url, port) {
|
|
|
961
972
|
catch { }
|
|
962
973
|
const logFd = openSync(logPath, "a");
|
|
963
974
|
console.log(chalk.dim(` Spawning proxy… ${chalk.dim(`(logs: ${logPath})`)}`));
|
|
964
|
-
const child = spawn(process.execPath, [composerCli], {
|
|
975
|
+
const child = spawn(process.execPath, [composerCli, "--port", String(port)], {
|
|
965
976
|
detached: true,
|
|
966
977
|
stdio: ["ignore", logFd, logFd],
|
|
967
978
|
env: proxyEnv,
|
|
@@ -1196,7 +1207,7 @@ export async function setupCursorProxy() {
|
|
|
1196
1207
|
{ key: "c", desc: "ancel" },
|
|
1197
1208
|
]);
|
|
1198
1209
|
if (choice === "r") {
|
|
1199
|
-
if (await ensureCursorProxyRunning(PROXY_DEFAULT_URL, true)) {
|
|
1210
|
+
if (await ensureCursorProxyRunning(PROXY_DEFAULT_URL, { forceRestart: true })) {
|
|
1200
1211
|
console.log(chalk.green("\n ✓ Proxy is running and healthy"));
|
|
1201
1212
|
return true;
|
|
1202
1213
|
}
|
|
@@ -1262,7 +1273,7 @@ async function pickCursorModel() {
|
|
|
1262
1273
|
{ key: "c", desc: "ancel" },
|
|
1263
1274
|
]);
|
|
1264
1275
|
if (choice === "r") {
|
|
1265
|
-
if (await ensureCursorProxyRunning(PROXY_DEFAULT_URL, true)) {
|
|
1276
|
+
if (await ensureCursorProxyRunning(PROXY_DEFAULT_URL, { forceRestart: true })) {
|
|
1266
1277
|
console.log(chalk.green(" ✓ Proxy started"));
|
|
1267
1278
|
break;
|
|
1268
1279
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
const CONFIG_FILE = "config.json";
|
|
4
|
+
/** Resolve proxy port (reads from config, or allocates and persists a new one). */
|
|
5
|
+
export function getProxyPort(projectRoot) {
|
|
6
|
+
const dir = join(projectRoot, ".claude-overnight");
|
|
7
|
+
const file = join(dir, CONFIG_FILE);
|
|
8
|
+
try {
|
|
9
|
+
const cfg = JSON.parse(readFileSync(file, "utf-8"));
|
|
10
|
+
if (typeof cfg.proxyPort === "number" && cfg.proxyPort >= 1024 && cfg.proxyPort <= 65535) {
|
|
11
|
+
return cfg.proxyPort;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
catch { /* not found or malformed */ }
|
|
15
|
+
const port = 61000 + Math.floor(Math.random() * 4536);
|
|
16
|
+
try {
|
|
17
|
+
if (!existsSync(dir))
|
|
18
|
+
mkdirSync(dir, { recursive: true });
|
|
19
|
+
const existing = existsSync(file) ? JSON.parse(readFileSync(file, "utf-8")) : {};
|
|
20
|
+
writeFileSync(file, JSON.stringify({ ...existing, proxyPort: port }, null, 2));
|
|
21
|
+
}
|
|
22
|
+
catch { /* best effort */ }
|
|
23
|
+
return port;
|
|
24
|
+
}
|
|
25
|
+
/** Build the full proxy URL for a per-project port. */
|
|
26
|
+
export function buildProxyUrl(port) {
|
|
27
|
+
return `http://127.0.0.1:${port}`;
|
|
28
|
+
}
|
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/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
|
@@ -5,7 +5,7 @@ import chalk from "chalk";
|
|
|
5
5
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
6
6
|
import { NudgeError, RATE_LIMIT_WINDOW_SHORT, extractToolTarget, sumUsageTokens } from "./types.js";
|
|
7
7
|
import { gitExec, autoCommit, mergeAllBranches, warnDirtyTree, cleanStaleWorktrees, writeSwarmLog } from "./merge.js";
|
|
8
|
-
import { ensureCursorProxyRunning } from "./providers.js";
|
|
8
|
+
import { ensureCursorProxyRunning, PROXY_DEFAULT_URL } from "./providers.js";
|
|
9
9
|
import { getModelCapability } from "./models.js";
|
|
10
10
|
import { createTurn, beginTurn, endTurn, updateTurn } from "./turns.js";
|
|
11
11
|
const SIMPLIFY_PROMPT = `You just finished your task. Now review and simplify your changes.
|
|
@@ -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) {
|
|
@@ -330,7 +350,7 @@ export class Swarm {
|
|
|
330
350
|
// attempt to restart it before the next task.
|
|
331
351
|
if (this.config.cursorProxy) {
|
|
332
352
|
this.log(-1, " Checking cursor proxy health…");
|
|
333
|
-
const restarted = await ensureCursorProxyRunning();
|
|
353
|
+
const restarted = await ensureCursorProxyRunning(PROXY_DEFAULT_URL, { projectRoot: this.config.cwd });
|
|
334
354
|
if (!restarted) {
|
|
335
355
|
this.log(-1, chalk.yellow(" ⚠ Proxy still down — remaining tasks may fail"));
|
|
336
356
|
}
|
|
@@ -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.31",
|
|
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.31",
|
|
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"
|