@kynver-app/runtime 0.1.17 → 0.1.19
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/cli.js +282 -77
- package/dist/cli.js.map +4 -4
- package/dist/index.js +285 -80
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,6 +10,10 @@ function fail(message) {
|
|
|
10
10
|
console.error(message);
|
|
11
11
|
process.exit(1);
|
|
12
12
|
}
|
|
13
|
+
function hiddenSpawnOptions(opts) {
|
|
14
|
+
if (process.platform !== "win32") return opts;
|
|
15
|
+
return { windowsHide: true, ...opts };
|
|
16
|
+
}
|
|
13
17
|
function required(value, name) {
|
|
14
18
|
if (!value) fail(`missing ${name}`);
|
|
15
19
|
return value;
|
|
@@ -393,12 +397,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
|
393
397
|
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
394
398
|
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
395
399
|
function observeRunnerDiskGate(input = {}) {
|
|
396
|
-
const
|
|
400
|
+
const path17 = input.diskPath?.trim() || "/";
|
|
397
401
|
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
398
402
|
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
399
403
|
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
400
404
|
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
401
|
-
const stats = statfsSync(
|
|
405
|
+
const stats = statfsSync(path17);
|
|
402
406
|
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
403
407
|
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
404
408
|
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
@@ -418,7 +422,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
418
422
|
}
|
|
419
423
|
return {
|
|
420
424
|
ok,
|
|
421
|
-
path:
|
|
425
|
+
path: path17,
|
|
422
426
|
freeBytes,
|
|
423
427
|
totalBytes,
|
|
424
428
|
usedPercent,
|
|
@@ -605,6 +609,52 @@ function summarizeEvent(event) {
|
|
|
605
609
|
return void 0;
|
|
606
610
|
}
|
|
607
611
|
|
|
612
|
+
// src/exit-classify.ts
|
|
613
|
+
var FAILURE_PATTERNS = [
|
|
614
|
+
{
|
|
615
|
+
test: /\b(?:invalid|unknown|unsupported|unrecognized)\b[^.\n]*\bmodel\b/i,
|
|
616
|
+
label: "provider rejected the requested model"
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
test: /\bmodel\b[^.\n]*\b(?:not\s+(?:found|supported|available|recognized|valid)|is\s+not\s+valid|does\s+not\s+exist)/i,
|
|
620
|
+
label: "provider rejected the requested model"
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
test: /\b(?:did you mean|available models|choose (?:a|one of)|supported models)\b/i,
|
|
624
|
+
label: "provider rejected the requested model"
|
|
625
|
+
},
|
|
626
|
+
{
|
|
627
|
+
test: /model preflight failed/i,
|
|
628
|
+
label: "model/provider preflight failed"
|
|
629
|
+
},
|
|
630
|
+
{
|
|
631
|
+
test: /\b(?:command not found|ENOENT|is the .*CLI on PATH|executable not found|no such file or directory)\b/i,
|
|
632
|
+
label: "provider CLI is missing or not on PATH"
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
test: /\bfailed to spawn\b/i,
|
|
636
|
+
label: "provider failed to spawn the worker process"
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
test: /\b(?:not logged in|unauthorized|authentication (?:failed|required)|invalid api key|missing api key|401)\b/i,
|
|
640
|
+
label: "provider authentication failed"
|
|
641
|
+
}
|
|
642
|
+
];
|
|
643
|
+
function tidy(errorText, max = 240) {
|
|
644
|
+
const oneLine2 = errorText.replace(/\s+/g, " ").trim();
|
|
645
|
+
return oneLine2.length > max ? `${oneLine2.slice(0, max - 1)}\u2026` : oneLine2;
|
|
646
|
+
}
|
|
647
|
+
function classifyExitFailure(errorText) {
|
|
648
|
+
const text = (errorText ?? "").trim();
|
|
649
|
+
if (!text) return null;
|
|
650
|
+
for (const pattern of FAILURE_PATTERNS) {
|
|
651
|
+
if (pattern.test.test(text)) {
|
|
652
|
+
return { blocked: true, reason: `${pattern.label}: ${tidy(text)}` };
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return null;
|
|
656
|
+
}
|
|
657
|
+
|
|
608
658
|
// src/git.ts
|
|
609
659
|
import { spawnSync } from "node:child_process";
|
|
610
660
|
function git(cwd, args, options = {}) {
|
|
@@ -720,7 +770,15 @@ var STALE_MS = 6e5;
|
|
|
720
770
|
function computeAttention(input) {
|
|
721
771
|
const now = Date.now();
|
|
722
772
|
if (input.finalResult) return { state: "done", reason: "final result recorded" };
|
|
723
|
-
if (!input.alive)
|
|
773
|
+
if (!input.alive) {
|
|
774
|
+
const classified = classifyExitFailure(input.error);
|
|
775
|
+
if (classified) return { state: "blocked", reason: classified.reason };
|
|
776
|
+
const tail = input.error?.trim();
|
|
777
|
+
return {
|
|
778
|
+
state: "needs_attention",
|
|
779
|
+
reason: tail ? `process exited without a final result: ${tail}` : "process exited without a final result"
|
|
780
|
+
};
|
|
781
|
+
}
|
|
724
782
|
if (input.heartbeatBlocker) {
|
|
725
783
|
return { state: "blocked", reason: `worker heartbeat reported blocker: ${input.heartbeatBlocker}` };
|
|
726
784
|
}
|
|
@@ -750,6 +808,7 @@ function computeWorkerStatus(worker, options = {}) {
|
|
|
750
808
|
fileMtime(worker.stderrPath),
|
|
751
809
|
fileMtime(worker.heartbeatPath)
|
|
752
810
|
]);
|
|
811
|
+
const error = parsed.error || (!alive && !parsed.finalResult ? tailFile(worker.stderrPath, 10).trim() || void 0 : void 0);
|
|
753
812
|
const attention = computeAttention({
|
|
754
813
|
alive,
|
|
755
814
|
finalResult: parsed.finalResult,
|
|
@@ -758,7 +817,8 @@ function computeWorkerStatus(worker, options = {}) {
|
|
|
758
817
|
heartbeatBytes,
|
|
759
818
|
lastActivityAt,
|
|
760
819
|
heartbeatBlocker: heartbeat.heartbeatBlocker,
|
|
761
|
-
startedAt: worker.startedAt
|
|
820
|
+
startedAt: worker.startedAt,
|
|
821
|
+
error
|
|
762
822
|
});
|
|
763
823
|
return {
|
|
764
824
|
runId: worker.runId,
|
|
@@ -783,7 +843,7 @@ function computeWorkerStatus(worker, options = {}) {
|
|
|
783
843
|
lastHeartbeatSummary: heartbeat.lastHeartbeatSummary,
|
|
784
844
|
heartbeatBlocker: heartbeat.heartbeatBlocker,
|
|
785
845
|
finalResult: parsed.finalResult,
|
|
786
|
-
error
|
|
846
|
+
error,
|
|
787
847
|
changedFiles,
|
|
788
848
|
gitAncestry
|
|
789
849
|
};
|
|
@@ -908,8 +968,8 @@ function observeRunnerResourceGate(input) {
|
|
|
908
968
|
}
|
|
909
969
|
|
|
910
970
|
// src/supervisor.ts
|
|
911
|
-
import { existsSync as
|
|
912
|
-
import
|
|
971
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync3 } from "node:fs";
|
|
972
|
+
import path10 from "node:path";
|
|
913
973
|
|
|
914
974
|
// src/prompt.ts
|
|
915
975
|
function buildPrompt(input) {
|
|
@@ -943,10 +1003,74 @@ function buildPrompt(input) {
|
|
|
943
1003
|
// src/providers/claude.ts
|
|
944
1004
|
import { closeSync, openSync } from "node:fs";
|
|
945
1005
|
import { spawn } from "node:child_process";
|
|
1006
|
+
|
|
1007
|
+
// src/providers/model-preflight.ts
|
|
1008
|
+
var REASONING_SUFFIX_RE = /-(?:thinking(?:-(?:high|medium|low|minimal|max|none))?|high|medium|low|minimal)$/i;
|
|
1009
|
+
function stripReasoningSuffix(model) {
|
|
1010
|
+
return model.replace(REASONING_SUFFIX_RE, "");
|
|
1011
|
+
}
|
|
1012
|
+
var FOREIGN_MODEL_RE = /^(?:gpt-|gpt5|o1|o3|o4|gemini-|grok-|composer|deepseek|llama|mistral|qwen|command-)/i;
|
|
1013
|
+
function looksLikeClaudeModel(model) {
|
|
1014
|
+
return /^claude[-_]/i.test(model) || /^(?:opus|sonnet|haiku)\b/i.test(model);
|
|
1015
|
+
}
|
|
1016
|
+
function preflightClaudeModel(model, defaultModel) {
|
|
1017
|
+
const requested = (model ?? "").trim();
|
|
1018
|
+
if (!requested) {
|
|
1019
|
+
return { ok: true, model: defaultModel, normalized: false };
|
|
1020
|
+
}
|
|
1021
|
+
const stripped = stripReasoningSuffix(requested).trim();
|
|
1022
|
+
const launch = stripped || defaultModel;
|
|
1023
|
+
if (FOREIGN_MODEL_RE.test(launch) || !looksLikeClaudeModel(launch) && launch !== defaultModel) {
|
|
1024
|
+
return {
|
|
1025
|
+
ok: false,
|
|
1026
|
+
model: requested,
|
|
1027
|
+
normalized: false,
|
|
1028
|
+
requested,
|
|
1029
|
+
note: `model "${requested}" is not a Claude model \u2014 the "claude" provider drives the Claude CLI, which only accepts claude-* model ids (got "${launch}"). Pick a Claude model or switch the worker provider.`
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
if (launch !== requested) {
|
|
1033
|
+
return {
|
|
1034
|
+
ok: true,
|
|
1035
|
+
model: launch,
|
|
1036
|
+
normalized: true,
|
|
1037
|
+
requested,
|
|
1038
|
+
note: `normalized model "${requested}" \u2192 "${launch}" (the Claude CLI rejects reasoning-effort suffixes)`
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
return { ok: true, model: launch, normalized: false };
|
|
1042
|
+
}
|
|
1043
|
+
function preflightCursorModel(model, defaultModel) {
|
|
1044
|
+
const requested = (model ?? "").trim();
|
|
1045
|
+
if (!requested) {
|
|
1046
|
+
return { ok: true, model: defaultModel, normalized: false };
|
|
1047
|
+
}
|
|
1048
|
+
if (looksLikeClaudeModel(requested)) {
|
|
1049
|
+
return {
|
|
1050
|
+
ok: false,
|
|
1051
|
+
model: requested,
|
|
1052
|
+
normalized: false,
|
|
1053
|
+
requested,
|
|
1054
|
+
note: `model "${requested}" is a Claude model but the worker provider is "cursor". Switch the provider to "claude" or pick a Cursor model.`
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
return { ok: true, model: requested, normalized: false };
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// src/providers/claude.ts
|
|
1061
|
+
var CLAUDE_DEFAULT_MODEL = "claude-opus-4-7";
|
|
946
1062
|
var claudeProvider = {
|
|
947
1063
|
name: "claude",
|
|
1064
|
+
defaultModel: CLAUDE_DEFAULT_MODEL,
|
|
1065
|
+
preflightModel(model) {
|
|
1066
|
+
return preflightClaudeModel(model, CLAUDE_DEFAULT_MODEL);
|
|
1067
|
+
},
|
|
948
1068
|
start(opts) {
|
|
949
|
-
const
|
|
1069
|
+
const preflight = preflightClaudeModel(opts.model, CLAUDE_DEFAULT_MODEL);
|
|
1070
|
+
if (!preflight.ok) {
|
|
1071
|
+
throw new Error(`claude provider model preflight failed: ${preflight.note}`);
|
|
1072
|
+
}
|
|
1073
|
+
const model = preflight.model;
|
|
950
1074
|
const stdoutFd = openSync(opts.stdoutPath, "a");
|
|
951
1075
|
const stderrFd = openSync(opts.stderrPath, "a");
|
|
952
1076
|
const child = spawn(
|
|
@@ -963,12 +1087,12 @@ var claudeProvider = {
|
|
|
963
1087
|
"--include-partial-messages",
|
|
964
1088
|
opts.prompt
|
|
965
1089
|
],
|
|
966
|
-
{
|
|
1090
|
+
hiddenSpawnOptions({
|
|
967
1091
|
cwd: opts.worktreePath,
|
|
968
1092
|
detached: true,
|
|
969
1093
|
stdio: ["ignore", stdoutFd, stderrFd],
|
|
970
1094
|
env: scrubClaudeEnv(process.env)
|
|
971
|
-
}
|
|
1095
|
+
})
|
|
972
1096
|
);
|
|
973
1097
|
closeSync(stdoutFd);
|
|
974
1098
|
closeSync(stderrFd);
|
|
@@ -981,33 +1105,80 @@ var claudeProvider = {
|
|
|
981
1105
|
};
|
|
982
1106
|
|
|
983
1107
|
// src/providers/cursor.ts
|
|
984
|
-
import { closeSync as closeSync2, existsSync as
|
|
1108
|
+
import { closeSync as closeSync2, existsSync as existsSync8, openSync as openSync2 } from "node:fs";
|
|
985
1109
|
import { spawn as spawn2 } from "node:child_process";
|
|
1110
|
+
import path7 from "node:path";
|
|
1111
|
+
|
|
1112
|
+
// src/providers/cursor-windows.ts
|
|
1113
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3 } from "node:fs";
|
|
986
1114
|
import path6 from "node:path";
|
|
987
|
-
var
|
|
988
|
-
function
|
|
1115
|
+
var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
|
|
1116
|
+
function parseCursorVersionSortKey(versionName) {
|
|
1117
|
+
const datePart = versionName.split("-")[0];
|
|
1118
|
+
const parts = datePart.split(".");
|
|
1119
|
+
if (parts.length !== 3) return null;
|
|
1120
|
+
const [year, month, day] = parts;
|
|
1121
|
+
if (!year || !month || !day) return null;
|
|
1122
|
+
return Number(`${year}${month.padStart(2, "0")}${day.padStart(2, "0")}`);
|
|
1123
|
+
}
|
|
1124
|
+
function pickLatestCursorVersionDir(agentRoot) {
|
|
1125
|
+
const versionsRoot = path6.join(agentRoot, "versions");
|
|
989
1126
|
if (!existsSync7(versionsRoot)) return null;
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
}
|
|
993
|
-
|
|
1127
|
+
let bestDir = null;
|
|
1128
|
+
let bestKey = -1;
|
|
1129
|
+
for (const entry of readdirSync3(versionsRoot, { withFileTypes: true })) {
|
|
1130
|
+
if (!entry.isDirectory() || !CURSOR_VERSION_DIR.test(entry.name)) continue;
|
|
1131
|
+
const key = parseCursorVersionSortKey(entry.name);
|
|
1132
|
+
if (key == null || key <= bestKey) continue;
|
|
1133
|
+
bestKey = key;
|
|
1134
|
+
bestDir = path6.join(versionsRoot, entry.name);
|
|
1135
|
+
}
|
|
1136
|
+
return bestDir;
|
|
1137
|
+
}
|
|
1138
|
+
function resolveWindowsCursorBundled(agentRoot) {
|
|
1139
|
+
const root = agentRoot?.trim() || path6.join(process.env.LOCALAPPDATA || "", "cursor-agent");
|
|
1140
|
+
const directNode = path6.join(root, "node.exe");
|
|
1141
|
+
const directIndex = path6.join(root, "index.js");
|
|
1142
|
+
if (existsSync7(directNode) && existsSync7(directIndex)) {
|
|
1143
|
+
return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
|
|
1144
|
+
}
|
|
1145
|
+
const versionDir = pickLatestCursorVersionDir(root);
|
|
1146
|
+
if (!versionDir) return null;
|
|
994
1147
|
const nodeExe = path6.join(versionDir, "node.exe");
|
|
995
1148
|
const indexJs = path6.join(versionDir, "index.js");
|
|
996
1149
|
if (!existsSync7(nodeExe) || !existsSync7(indexJs)) return null;
|
|
997
|
-
return {
|
|
1150
|
+
return { nodeExe, indexJs, versionDir };
|
|
998
1151
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1152
|
+
|
|
1153
|
+
// src/providers/cursor.ts
|
|
1154
|
+
var DEFAULT_CURSOR_MODEL = "composer-2.5";
|
|
1155
|
+
function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
|
|
1156
|
+
return {
|
|
1157
|
+
executable: nodeExe,
|
|
1158
|
+
prefixArgs: [indexJs],
|
|
1159
|
+
shell: false,
|
|
1160
|
+
detached: true,
|
|
1161
|
+
bundledVersionDir: versionDir
|
|
1162
|
+
};
|
|
1005
1163
|
}
|
|
1006
1164
|
function resolveCursorSpawn(agentBin) {
|
|
1007
|
-
if (process.platform === "win32"
|
|
1008
|
-
const
|
|
1009
|
-
|
|
1010
|
-
|
|
1165
|
+
if (process.platform === "win32") {
|
|
1166
|
+
const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
|
|
1167
|
+
const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync8(path7.join(path7.dirname(agentBin), "index.js"));
|
|
1168
|
+
const isDefaultShim = agentBin === "agent";
|
|
1169
|
+
if (isCursorWrapper || isBundledNode || isDefaultShim) {
|
|
1170
|
+
const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path7.dirname(agentBin)) : isBundledNode ? {
|
|
1171
|
+
nodeExe: agentBin,
|
|
1172
|
+
indexJs: path7.join(path7.dirname(agentBin), "index.js"),
|
|
1173
|
+
versionDir: path7.dirname(agentBin)
|
|
1174
|
+
} : resolveWindowsCursorBundled();
|
|
1175
|
+
if (bundled) {
|
|
1176
|
+
return bundledSpawnTarget(bundled.nodeExe, bundled.indexJs, bundled.versionDir);
|
|
1177
|
+
}
|
|
1178
|
+
throw new Error(
|
|
1179
|
+
"Cursor Agent on Windows has no headless bundled node.exe under %LOCALAPPDATA%\\cursor-agent\\versions\\\u2026. Run `agent login` / update Cursor Agent CLI, use `--provider claude`, or set KYNVER_CURSOR_AGENT_ROOT to the cursor-agent folder."
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1011
1182
|
}
|
|
1012
1183
|
return { executable: agentBin, prefixArgs: [], shell: false, detached: true };
|
|
1013
1184
|
}
|
|
@@ -1015,15 +1186,35 @@ function resolveAgentBin() {
|
|
|
1015
1186
|
const configured = process.env.KYNVER_CURSOR_AGENT_BIN?.trim() || process.env.CURSOR_AGENT_BIN?.trim();
|
|
1016
1187
|
if (configured) return configured;
|
|
1017
1188
|
if (process.platform === "win32") {
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1189
|
+
const bundled = resolveWindowsCursorBundled(
|
|
1190
|
+
process.env.KYNVER_CURSOR_AGENT_ROOT?.trim() || void 0
|
|
1191
|
+
);
|
|
1192
|
+
if (bundled) return bundled.nodeExe;
|
|
1193
|
+
const localAgent = path7.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
|
|
1194
|
+
if (existsSync8(localAgent)) return localAgent;
|
|
1020
1195
|
}
|
|
1021
1196
|
return "agent";
|
|
1022
1197
|
}
|
|
1198
|
+
function cursorWorkerEnv(agentBin, spawnTarget) {
|
|
1199
|
+
return {
|
|
1200
|
+
...process.env,
|
|
1201
|
+
CI: "1",
|
|
1202
|
+
NO_COLOR: "1",
|
|
1203
|
+
...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path7.basename(agentBin) || "agent.cmd" } : {}
|
|
1204
|
+
};
|
|
1205
|
+
}
|
|
1023
1206
|
var cursorProvider = {
|
|
1024
1207
|
name: "cursor",
|
|
1208
|
+
defaultModel: DEFAULT_CURSOR_MODEL,
|
|
1209
|
+
preflightModel(model) {
|
|
1210
|
+
return preflightCursorModel(model, DEFAULT_CURSOR_MODEL);
|
|
1211
|
+
},
|
|
1025
1212
|
start(opts) {
|
|
1026
|
-
const
|
|
1213
|
+
const preflight = preflightCursorModel(opts.model, DEFAULT_CURSOR_MODEL);
|
|
1214
|
+
if (!preflight.ok) {
|
|
1215
|
+
throw new Error(`cursor provider model preflight failed: ${preflight.note}`);
|
|
1216
|
+
}
|
|
1217
|
+
const model = preflight.model;
|
|
1027
1218
|
const stdoutFd = openSync2(opts.stdoutPath, "a");
|
|
1028
1219
|
const stderrFd = openSync2(opts.stderrPath, "a");
|
|
1029
1220
|
const agentBin = resolveAgentBin();
|
|
@@ -1044,16 +1235,13 @@ var cursorProvider = {
|
|
|
1044
1235
|
model,
|
|
1045
1236
|
opts.prompt
|
|
1046
1237
|
],
|
|
1047
|
-
{
|
|
1238
|
+
hiddenSpawnOptions({
|
|
1048
1239
|
cwd: opts.worktreePath,
|
|
1049
1240
|
detached: spawnTarget.detached,
|
|
1050
1241
|
shell: spawnTarget.shell,
|
|
1051
1242
|
stdio: ["ignore", stdoutFd, stderrFd],
|
|
1052
|
-
env:
|
|
1053
|
-
|
|
1054
|
-
...spawnTarget.prefixArgs.length > 0 ? { CURSOR_INVOKED_AS: path6.basename(agentBin) } : {}
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1243
|
+
env: cursorWorkerEnv(agentBin, spawnTarget)
|
|
1244
|
+
})
|
|
1057
1245
|
);
|
|
1058
1246
|
closeSync2(stdoutFd);
|
|
1059
1247
|
closeSync2(stderrFd);
|
|
@@ -1085,12 +1273,12 @@ function resolveWorkerProvider(name) {
|
|
|
1085
1273
|
|
|
1086
1274
|
// src/auto-complete.ts
|
|
1087
1275
|
import { spawn as spawn3 } from "node:child_process";
|
|
1088
|
-
import { existsSync as
|
|
1089
|
-
import
|
|
1276
|
+
import { existsSync as existsSync9, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
|
|
1277
|
+
import path9 from "node:path";
|
|
1090
1278
|
import { fileURLToPath } from "node:url";
|
|
1091
1279
|
|
|
1092
1280
|
// src/worker-ops.ts
|
|
1093
|
-
import
|
|
1281
|
+
import path8 from "node:path";
|
|
1094
1282
|
async function postCompletion(url, secret, body) {
|
|
1095
1283
|
const res = await fetch(url, {
|
|
1096
1284
|
method: "POST",
|
|
@@ -1213,7 +1401,7 @@ async function completeWorker(args) {
|
|
|
1213
1401
|
function workerStatus(args) {
|
|
1214
1402
|
const worker = loadWorker(String(args.run), String(args.name));
|
|
1215
1403
|
const status = computeWorkerStatus(worker);
|
|
1216
|
-
writeJson(
|
|
1404
|
+
writeJson(path8.join(worker.workerDir, "last-status.json"), status);
|
|
1217
1405
|
console.log(JSON.stringify(status, null, 2));
|
|
1218
1406
|
}
|
|
1219
1407
|
function runStatus(args) {
|
|
@@ -1221,7 +1409,7 @@ function runStatus(args) {
|
|
|
1221
1409
|
const names = Object.keys(run.workers || {});
|
|
1222
1410
|
const workers = names.map((name) => {
|
|
1223
1411
|
const worker = readJson(
|
|
1224
|
-
|
|
1412
|
+
path8.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1225
1413
|
void 0
|
|
1226
1414
|
);
|
|
1227
1415
|
if (!worker) {
|
|
@@ -1257,7 +1445,7 @@ function runStatus(args) {
|
|
|
1257
1445
|
needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
|
|
1258
1446
|
workers
|
|
1259
1447
|
};
|
|
1260
|
-
writeJson(
|
|
1448
|
+
writeJson(path8.join(runDirectory(run.id), "last-board.json"), board);
|
|
1261
1449
|
console.log(JSON.stringify(board, null, 2));
|
|
1262
1450
|
}
|
|
1263
1451
|
function tailWorker(args) {
|
|
@@ -1396,12 +1584,12 @@ async function autoCompleteWorkerCli(raw) {
|
|
|
1396
1584
|
}
|
|
1397
1585
|
}
|
|
1398
1586
|
function resolveDefaultCliPath() {
|
|
1399
|
-
return
|
|
1587
|
+
return path9.join(fileURLToPath(new URL(".", import.meta.url)), "cli.js");
|
|
1400
1588
|
}
|
|
1401
1589
|
function spawnCompletionSidecar(opts) {
|
|
1402
1590
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath();
|
|
1403
|
-
if (!
|
|
1404
|
-
const logPath =
|
|
1591
|
+
if (!existsSync9(cliPath)) return void 0;
|
|
1592
|
+
const logPath = path9.join(opts.workerDir, "auto-complete.log");
|
|
1405
1593
|
let logFd;
|
|
1406
1594
|
try {
|
|
1407
1595
|
logFd = openSync3(logPath, "a");
|
|
@@ -1427,11 +1615,15 @@ function spawnCompletionSidecar(opts) {
|
|
|
1427
1615
|
if (opts.baseUrl) args.push("--base-url", opts.baseUrl);
|
|
1428
1616
|
if (opts.secret) args.push("--secret", opts.secret);
|
|
1429
1617
|
try {
|
|
1430
|
-
const child = spawn3(
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1618
|
+
const child = spawn3(
|
|
1619
|
+
nodeExecutable,
|
|
1620
|
+
args,
|
|
1621
|
+
hiddenSpawnOptions({
|
|
1622
|
+
detached: true,
|
|
1623
|
+
stdio,
|
|
1624
|
+
env: process.env
|
|
1625
|
+
})
|
|
1626
|
+
);
|
|
1435
1627
|
if (logFd !== void 0) closeSync3(logFd);
|
|
1436
1628
|
child.unref();
|
|
1437
1629
|
return { pid: child.pid, logPath, cliPath };
|
|
@@ -1455,31 +1647,44 @@ function spawnWorkerProcess(run, opts) {
|
|
|
1455
1647
|
const name = safeSlug(rawName);
|
|
1456
1648
|
if (run.workers?.[name]) throw new Error(`worker already exists in run ${run.id}: ${name}`);
|
|
1457
1649
|
if (!opts.task) throw new Error(`missing task text for worker ${name}`);
|
|
1650
|
+
const provider = resolveWorkerProvider(opts.provider);
|
|
1651
|
+
let launchModel = opts.model;
|
|
1652
|
+
if (provider.preflightModel) {
|
|
1653
|
+
const preflight = provider.preflightModel(opts.model);
|
|
1654
|
+
if (!preflight.ok) {
|
|
1655
|
+
throw new Error(
|
|
1656
|
+
`model preflight failed for provider "${provider.name}": ${preflight.note ?? "invalid model/provider combination"}`
|
|
1657
|
+
);
|
|
1658
|
+
}
|
|
1659
|
+
if (preflight.normalized) {
|
|
1660
|
+
console.error(`[supervisor] ${name}: ${preflight.note}`);
|
|
1661
|
+
}
|
|
1662
|
+
launchModel = preflight.model;
|
|
1663
|
+
}
|
|
1458
1664
|
const { worktreesDir } = getPaths();
|
|
1459
|
-
const workerDir =
|
|
1665
|
+
const workerDir = path10.join(runDirectory(run.id), "workers", name);
|
|
1460
1666
|
mkdirSync3(workerDir, { recursive: true });
|
|
1461
|
-
const worktreePath =
|
|
1667
|
+
const worktreePath = path10.join(worktreesDir, run.id, name);
|
|
1462
1668
|
const branch = opts.branch || `agent/${run.id}/${name}`;
|
|
1463
|
-
if (
|
|
1669
|
+
if (existsSync10(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
|
|
1464
1670
|
git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
|
|
1465
1671
|
git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
|
|
1466
|
-
const stdoutPath =
|
|
1467
|
-
const stderrPath =
|
|
1468
|
-
const heartbeatPath =
|
|
1672
|
+
const stdoutPath = path10.join(workerDir, "stdout.jsonl");
|
|
1673
|
+
const stderrPath = path10.join(workerDir, "stderr.log");
|
|
1674
|
+
const heartbeatPath = path10.join(workerDir, "heartbeat.jsonl");
|
|
1469
1675
|
const prompt = buildPrompt({
|
|
1470
1676
|
task: opts.task,
|
|
1471
1677
|
ownedPaths: opts.ownedPaths || [],
|
|
1472
1678
|
worktreePath,
|
|
1473
1679
|
heartbeatPath
|
|
1474
1680
|
});
|
|
1475
|
-
const provider = resolveWorkerProvider(opts.provider);
|
|
1476
1681
|
let started;
|
|
1477
1682
|
try {
|
|
1478
1683
|
started = provider.start({
|
|
1479
1684
|
name,
|
|
1480
1685
|
task: opts.task,
|
|
1481
1686
|
ownedPaths: opts.ownedPaths,
|
|
1482
|
-
model:
|
|
1687
|
+
model: launchModel,
|
|
1483
1688
|
branch,
|
|
1484
1689
|
worktreePath,
|
|
1485
1690
|
workerDir,
|
|
@@ -1493,7 +1698,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
1493
1698
|
git(run.repo, ["branch", "-D", branch], { allowFailure: true });
|
|
1494
1699
|
throw error;
|
|
1495
1700
|
}
|
|
1496
|
-
const model = started.model ||
|
|
1701
|
+
const model = started.model || launchModel || provider.defaultModel || "claude-opus-4-7";
|
|
1497
1702
|
const worker = {
|
|
1498
1703
|
name,
|
|
1499
1704
|
runId: run.id,
|
|
@@ -1515,7 +1720,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
1515
1720
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1516
1721
|
};
|
|
1517
1722
|
saveWorker(run.id, worker);
|
|
1518
|
-
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath:
|
|
1723
|
+
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path10.join(workerDir, "worker.json") } };
|
|
1519
1724
|
run.status = "running";
|
|
1520
1725
|
saveRun(run);
|
|
1521
1726
|
if (worker.agentOsId && worker.taskId) {
|
|
@@ -1738,7 +1943,7 @@ function redactHarness(text, secret) {
|
|
|
1738
1943
|
}
|
|
1739
1944
|
|
|
1740
1945
|
// src/validate.ts
|
|
1741
|
-
import
|
|
1946
|
+
import path11 from "node:path";
|
|
1742
1947
|
var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
1743
1948
|
var WORKER_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
|
|
1744
1949
|
function validateRunId(runId) {
|
|
@@ -1752,15 +1957,15 @@ function validateWorkerName(name) {
|
|
|
1752
1957
|
return trimmed;
|
|
1753
1958
|
}
|
|
1754
1959
|
function validateRepo(repo) {
|
|
1755
|
-
const resolved =
|
|
1960
|
+
const resolved = path11.resolve(repo);
|
|
1756
1961
|
if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
|
|
1757
1962
|
return resolved;
|
|
1758
1963
|
}
|
|
1759
1964
|
function validateOwnedPaths(repoRoot, ownedPaths) {
|
|
1760
1965
|
return ownedPaths.map((owned) => {
|
|
1761
|
-
const resolved =
|
|
1762
|
-
const rel =
|
|
1763
|
-
if (rel.startsWith("..") ||
|
|
1966
|
+
const resolved = path11.resolve(repoRoot, owned);
|
|
1967
|
+
const rel = path11.relative(repoRoot, resolved);
|
|
1968
|
+
if (rel.startsWith("..") || path11.isAbsolute(rel)) {
|
|
1764
1969
|
throw new Error(`owned path escapes repo: ${owned}`);
|
|
1765
1970
|
}
|
|
1766
1971
|
return resolved;
|
|
@@ -1772,14 +1977,14 @@ function validateTailLines(lines) {
|
|
|
1772
1977
|
}
|
|
1773
1978
|
|
|
1774
1979
|
// src/worktree.ts
|
|
1775
|
-
import { existsSync as
|
|
1776
|
-
import
|
|
1980
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4 } from "node:fs";
|
|
1981
|
+
import path12 from "node:path";
|
|
1777
1982
|
function createRun(args) {
|
|
1778
1983
|
const repo = validateRepo(required(String(args.repo || ""), "--repo"));
|
|
1779
1984
|
ensureGitRepo(repo);
|
|
1780
1985
|
const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
|
|
1781
1986
|
const dir = runDirectory(id);
|
|
1782
|
-
if (
|
|
1987
|
+
if (existsSync11(dir)) failExists(`run already exists: ${id}`);
|
|
1783
1988
|
mkdirSync4(dir, { recursive: true });
|
|
1784
1989
|
const base = String(args.base || "origin/main");
|
|
1785
1990
|
const baseCommit = git(repo, ["rev-parse", base]).trim();
|
|
@@ -1793,12 +1998,12 @@ function createRun(args) {
|
|
|
1793
1998
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1794
1999
|
workers: {}
|
|
1795
2000
|
};
|
|
1796
|
-
writeJson(
|
|
2001
|
+
writeJson(path12.join(dir, "run.json"), run);
|
|
1797
2002
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
1798
2003
|
}
|
|
1799
2004
|
function listRuns() {
|
|
1800
2005
|
const { runsDir } = getPaths();
|
|
1801
|
-
const rows = listRunIds(runsDir).map((id) => readJson(
|
|
2006
|
+
const rows = listRunIds(runsDir).map((id) => readJson(path12.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
|
|
1802
2007
|
id: run.id,
|
|
1803
2008
|
name: run.name,
|
|
1804
2009
|
status: run.status,
|
|
@@ -1813,7 +2018,7 @@ function failExists(message) {
|
|
|
1813
2018
|
}
|
|
1814
2019
|
|
|
1815
2020
|
// src/sweep.ts
|
|
1816
|
-
import
|
|
2021
|
+
import path13 from "node:path";
|
|
1817
2022
|
async function sweepRun(args) {
|
|
1818
2023
|
const pipeline = args.pipeline === true || args.pipeline === "true";
|
|
1819
2024
|
try {
|
|
@@ -1825,7 +2030,7 @@ async function sweepRun(args) {
|
|
|
1825
2030
|
const releasedLocalOrphans = [];
|
|
1826
2031
|
for (const name of Object.keys(run.workers || {})) {
|
|
1827
2032
|
const worker = readJson(
|
|
1828
|
-
|
|
2033
|
+
path13.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1829
2034
|
void 0
|
|
1830
2035
|
);
|
|
1831
2036
|
if (!worker || !worker.dispatched || !worker.taskId) continue;
|
|
@@ -1872,10 +2077,10 @@ import { mkdirSync as mkdirSync5, realpathSync } from "node:fs";
|
|
|
1872
2077
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
1873
2078
|
|
|
1874
2079
|
// src/pipeline-tick.ts
|
|
1875
|
-
import
|
|
2080
|
+
import path16 from "node:path";
|
|
1876
2081
|
|
|
1877
2082
|
// src/finalize.ts
|
|
1878
|
-
import
|
|
2083
|
+
import path14 from "node:path";
|
|
1879
2084
|
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
|
|
1880
2085
|
function terminalStatusFor(run) {
|
|
1881
2086
|
const names = Object.keys(run.workers || {});
|
|
@@ -1885,7 +2090,7 @@ function terminalStatusFor(run) {
|
|
|
1885
2090
|
let anyCompletionBlocked = false;
|
|
1886
2091
|
for (const name of names) {
|
|
1887
2092
|
const worker = readJson(
|
|
1888
|
-
|
|
2093
|
+
path14.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1889
2094
|
void 0
|
|
1890
2095
|
);
|
|
1891
2096
|
if (!worker) continue;
|
|
@@ -1918,7 +2123,7 @@ function finalizeStaleRuns() {
|
|
|
1918
2123
|
}
|
|
1919
2124
|
|
|
1920
2125
|
// src/plan-progress-daemon-sync.ts
|
|
1921
|
-
import
|
|
2126
|
+
import path15 from "node:path";
|
|
1922
2127
|
|
|
1923
2128
|
// src/plan-progress-sync.ts
|
|
1924
2129
|
async function syncPlanProgress(args) {
|
|
@@ -1942,7 +2147,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
1942
2147
|
const outcomes = [];
|
|
1943
2148
|
for (const name of Object.keys(run.workers || {})) {
|
|
1944
2149
|
const worker = readJson(
|
|
1945
|
-
|
|
2150
|
+
path15.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1946
2151
|
void 0
|
|
1947
2152
|
);
|
|
1948
2153
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -1996,7 +2201,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
1996
2201
|
const outcomes = [];
|
|
1997
2202
|
for (const name of Object.keys(run.workers || {})) {
|
|
1998
2203
|
const worker = readJson(
|
|
1999
|
-
|
|
2204
|
+
path16.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2000
2205
|
void 0
|
|
2001
2206
|
);
|
|
2002
2207
|
if (!worker?.taskId) continue;
|