@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/cli.js
CHANGED
|
@@ -16,6 +16,10 @@ function fail(message) {
|
|
|
16
16
|
console.error(message);
|
|
17
17
|
process.exit(1);
|
|
18
18
|
}
|
|
19
|
+
function hiddenSpawnOptions(opts) {
|
|
20
|
+
if (process.platform !== "win32") return opts;
|
|
21
|
+
return { windowsHide: true, ...opts };
|
|
22
|
+
}
|
|
19
23
|
function required(value, name) {
|
|
20
24
|
if (!value) fail(`missing ${name}`);
|
|
21
25
|
return value;
|
|
@@ -392,12 +396,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
|
392
396
|
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
393
397
|
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
394
398
|
function observeRunnerDiskGate(input = {}) {
|
|
395
|
-
const
|
|
399
|
+
const path17 = input.diskPath?.trim() || "/";
|
|
396
400
|
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
397
401
|
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
398
402
|
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
399
403
|
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
400
|
-
const stats = statfsSync(
|
|
404
|
+
const stats = statfsSync(path17);
|
|
401
405
|
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
402
406
|
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
403
407
|
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
@@ -417,7 +421,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
417
421
|
}
|
|
418
422
|
return {
|
|
419
423
|
ok,
|
|
420
|
-
path:
|
|
424
|
+
path: path17,
|
|
421
425
|
freeBytes,
|
|
422
426
|
totalBytes,
|
|
423
427
|
usedPercent,
|
|
@@ -604,6 +608,52 @@ function summarizeEvent(event) {
|
|
|
604
608
|
return void 0;
|
|
605
609
|
}
|
|
606
610
|
|
|
611
|
+
// src/exit-classify.ts
|
|
612
|
+
var FAILURE_PATTERNS = [
|
|
613
|
+
{
|
|
614
|
+
test: /\b(?:invalid|unknown|unsupported|unrecognized)\b[^.\n]*\bmodel\b/i,
|
|
615
|
+
label: "provider rejected the requested model"
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
test: /\bmodel\b[^.\n]*\b(?:not\s+(?:found|supported|available|recognized|valid)|is\s+not\s+valid|does\s+not\s+exist)/i,
|
|
619
|
+
label: "provider rejected the requested model"
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
test: /\b(?:did you mean|available models|choose (?:a|one of)|supported models)\b/i,
|
|
623
|
+
label: "provider rejected the requested model"
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
test: /model preflight failed/i,
|
|
627
|
+
label: "model/provider preflight failed"
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
test: /\b(?:command not found|ENOENT|is the .*CLI on PATH|executable not found|no such file or directory)\b/i,
|
|
631
|
+
label: "provider CLI is missing or not on PATH"
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
test: /\bfailed to spawn\b/i,
|
|
635
|
+
label: "provider failed to spawn the worker process"
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
test: /\b(?:not logged in|unauthorized|authentication (?:failed|required)|invalid api key|missing api key|401)\b/i,
|
|
639
|
+
label: "provider authentication failed"
|
|
640
|
+
}
|
|
641
|
+
];
|
|
642
|
+
function tidy(errorText, max = 240) {
|
|
643
|
+
const oneLine2 = errorText.replace(/\s+/g, " ").trim();
|
|
644
|
+
return oneLine2.length > max ? `${oneLine2.slice(0, max - 1)}\u2026` : oneLine2;
|
|
645
|
+
}
|
|
646
|
+
function classifyExitFailure(errorText) {
|
|
647
|
+
const text = (errorText ?? "").trim();
|
|
648
|
+
if (!text) return null;
|
|
649
|
+
for (const pattern of FAILURE_PATTERNS) {
|
|
650
|
+
if (pattern.test.test(text)) {
|
|
651
|
+
return { blocked: true, reason: `${pattern.label}: ${tidy(text)}` };
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
|
|
607
657
|
// src/git.ts
|
|
608
658
|
import { spawnSync } from "node:child_process";
|
|
609
659
|
function git(cwd, args, options = {}) {
|
|
@@ -719,7 +769,15 @@ var STALE_MS = 6e5;
|
|
|
719
769
|
function computeAttention(input) {
|
|
720
770
|
const now = Date.now();
|
|
721
771
|
if (input.finalResult) return { state: "done", reason: "final result recorded" };
|
|
722
|
-
if (!input.alive)
|
|
772
|
+
if (!input.alive) {
|
|
773
|
+
const classified = classifyExitFailure(input.error);
|
|
774
|
+
if (classified) return { state: "blocked", reason: classified.reason };
|
|
775
|
+
const tail = input.error?.trim();
|
|
776
|
+
return {
|
|
777
|
+
state: "needs_attention",
|
|
778
|
+
reason: tail ? `process exited without a final result: ${tail}` : "process exited without a final result"
|
|
779
|
+
};
|
|
780
|
+
}
|
|
723
781
|
if (input.heartbeatBlocker) {
|
|
724
782
|
return { state: "blocked", reason: `worker heartbeat reported blocker: ${input.heartbeatBlocker}` };
|
|
725
783
|
}
|
|
@@ -749,6 +807,7 @@ function computeWorkerStatus(worker, options = {}) {
|
|
|
749
807
|
fileMtime(worker.stderrPath),
|
|
750
808
|
fileMtime(worker.heartbeatPath)
|
|
751
809
|
]);
|
|
810
|
+
const error = parsed.error || (!alive && !parsed.finalResult ? tailFile(worker.stderrPath, 10).trim() || void 0 : void 0);
|
|
752
811
|
const attention = computeAttention({
|
|
753
812
|
alive,
|
|
754
813
|
finalResult: parsed.finalResult,
|
|
@@ -757,7 +816,8 @@ function computeWorkerStatus(worker, options = {}) {
|
|
|
757
816
|
heartbeatBytes,
|
|
758
817
|
lastActivityAt,
|
|
759
818
|
heartbeatBlocker: heartbeat.heartbeatBlocker,
|
|
760
|
-
startedAt: worker.startedAt
|
|
819
|
+
startedAt: worker.startedAt,
|
|
820
|
+
error
|
|
761
821
|
});
|
|
762
822
|
return {
|
|
763
823
|
runId: worker.runId,
|
|
@@ -782,7 +842,7 @@ function computeWorkerStatus(worker, options = {}) {
|
|
|
782
842
|
lastHeartbeatSummary: heartbeat.lastHeartbeatSummary,
|
|
783
843
|
heartbeatBlocker: heartbeat.heartbeatBlocker,
|
|
784
844
|
finalResult: parsed.finalResult,
|
|
785
|
-
error
|
|
845
|
+
error,
|
|
786
846
|
changedFiles,
|
|
787
847
|
gitAncestry
|
|
788
848
|
};
|
|
@@ -907,8 +967,8 @@ function observeRunnerResourceGate(input) {
|
|
|
907
967
|
}
|
|
908
968
|
|
|
909
969
|
// src/supervisor.ts
|
|
910
|
-
import { existsSync as
|
|
911
|
-
import
|
|
970
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync3 } from "node:fs";
|
|
971
|
+
import path10 from "node:path";
|
|
912
972
|
|
|
913
973
|
// src/prompt.ts
|
|
914
974
|
function buildPrompt(input) {
|
|
@@ -942,10 +1002,74 @@ function buildPrompt(input) {
|
|
|
942
1002
|
// src/providers/claude.ts
|
|
943
1003
|
import { closeSync, openSync } from "node:fs";
|
|
944
1004
|
import { spawn } from "node:child_process";
|
|
1005
|
+
|
|
1006
|
+
// src/providers/model-preflight.ts
|
|
1007
|
+
var REASONING_SUFFIX_RE = /-(?:thinking(?:-(?:high|medium|low|minimal|max|none))?|high|medium|low|minimal)$/i;
|
|
1008
|
+
function stripReasoningSuffix(model) {
|
|
1009
|
+
return model.replace(REASONING_SUFFIX_RE, "");
|
|
1010
|
+
}
|
|
1011
|
+
var FOREIGN_MODEL_RE = /^(?:gpt-|gpt5|o1|o3|o4|gemini-|grok-|composer|deepseek|llama|mistral|qwen|command-)/i;
|
|
1012
|
+
function looksLikeClaudeModel(model) {
|
|
1013
|
+
return /^claude[-_]/i.test(model) || /^(?:opus|sonnet|haiku)\b/i.test(model);
|
|
1014
|
+
}
|
|
1015
|
+
function preflightClaudeModel(model, defaultModel) {
|
|
1016
|
+
const requested = (model ?? "").trim();
|
|
1017
|
+
if (!requested) {
|
|
1018
|
+
return { ok: true, model: defaultModel, normalized: false };
|
|
1019
|
+
}
|
|
1020
|
+
const stripped = stripReasoningSuffix(requested).trim();
|
|
1021
|
+
const launch = stripped || defaultModel;
|
|
1022
|
+
if (FOREIGN_MODEL_RE.test(launch) || !looksLikeClaudeModel(launch) && launch !== defaultModel) {
|
|
1023
|
+
return {
|
|
1024
|
+
ok: false,
|
|
1025
|
+
model: requested,
|
|
1026
|
+
normalized: false,
|
|
1027
|
+
requested,
|
|
1028
|
+
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.`
|
|
1029
|
+
};
|
|
1030
|
+
}
|
|
1031
|
+
if (launch !== requested) {
|
|
1032
|
+
return {
|
|
1033
|
+
ok: true,
|
|
1034
|
+
model: launch,
|
|
1035
|
+
normalized: true,
|
|
1036
|
+
requested,
|
|
1037
|
+
note: `normalized model "${requested}" \u2192 "${launch}" (the Claude CLI rejects reasoning-effort suffixes)`
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
return { ok: true, model: launch, normalized: false };
|
|
1041
|
+
}
|
|
1042
|
+
function preflightCursorModel(model, defaultModel) {
|
|
1043
|
+
const requested = (model ?? "").trim();
|
|
1044
|
+
if (!requested) {
|
|
1045
|
+
return { ok: true, model: defaultModel, normalized: false };
|
|
1046
|
+
}
|
|
1047
|
+
if (looksLikeClaudeModel(requested)) {
|
|
1048
|
+
return {
|
|
1049
|
+
ok: false,
|
|
1050
|
+
model: requested,
|
|
1051
|
+
normalized: false,
|
|
1052
|
+
requested,
|
|
1053
|
+
note: `model "${requested}" is a Claude model but the worker provider is "cursor". Switch the provider to "claude" or pick a Cursor model.`
|
|
1054
|
+
};
|
|
1055
|
+
}
|
|
1056
|
+
return { ok: true, model: requested, normalized: false };
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
// src/providers/claude.ts
|
|
1060
|
+
var CLAUDE_DEFAULT_MODEL = "claude-opus-4-7";
|
|
945
1061
|
var claudeProvider = {
|
|
946
1062
|
name: "claude",
|
|
1063
|
+
defaultModel: CLAUDE_DEFAULT_MODEL,
|
|
1064
|
+
preflightModel(model) {
|
|
1065
|
+
return preflightClaudeModel(model, CLAUDE_DEFAULT_MODEL);
|
|
1066
|
+
},
|
|
947
1067
|
start(opts) {
|
|
948
|
-
const
|
|
1068
|
+
const preflight = preflightClaudeModel(opts.model, CLAUDE_DEFAULT_MODEL);
|
|
1069
|
+
if (!preflight.ok) {
|
|
1070
|
+
throw new Error(`claude provider model preflight failed: ${preflight.note}`);
|
|
1071
|
+
}
|
|
1072
|
+
const model = preflight.model;
|
|
949
1073
|
const stdoutFd = openSync(opts.stdoutPath, "a");
|
|
950
1074
|
const stderrFd = openSync(opts.stderrPath, "a");
|
|
951
1075
|
const child = spawn(
|
|
@@ -962,12 +1086,12 @@ var claudeProvider = {
|
|
|
962
1086
|
"--include-partial-messages",
|
|
963
1087
|
opts.prompt
|
|
964
1088
|
],
|
|
965
|
-
{
|
|
1089
|
+
hiddenSpawnOptions({
|
|
966
1090
|
cwd: opts.worktreePath,
|
|
967
1091
|
detached: true,
|
|
968
1092
|
stdio: ["ignore", stdoutFd, stderrFd],
|
|
969
1093
|
env: scrubClaudeEnv(process.env)
|
|
970
|
-
}
|
|
1094
|
+
})
|
|
971
1095
|
);
|
|
972
1096
|
closeSync(stdoutFd);
|
|
973
1097
|
closeSync(stderrFd);
|
|
@@ -980,33 +1104,80 @@ var claudeProvider = {
|
|
|
980
1104
|
};
|
|
981
1105
|
|
|
982
1106
|
// src/providers/cursor.ts
|
|
983
|
-
import { closeSync as closeSync2, existsSync as
|
|
1107
|
+
import { closeSync as closeSync2, existsSync as existsSync8, openSync as openSync2 } from "node:fs";
|
|
984
1108
|
import { spawn as spawn2 } from "node:child_process";
|
|
1109
|
+
import path7 from "node:path";
|
|
1110
|
+
|
|
1111
|
+
// src/providers/cursor-windows.ts
|
|
1112
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3 } from "node:fs";
|
|
985
1113
|
import path6 from "node:path";
|
|
986
|
-
var
|
|
987
|
-
function
|
|
1114
|
+
var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
|
|
1115
|
+
function parseCursorVersionSortKey(versionName) {
|
|
1116
|
+
const datePart = versionName.split("-")[0];
|
|
1117
|
+
const parts = datePart.split(".");
|
|
1118
|
+
if (parts.length !== 3) return null;
|
|
1119
|
+
const [year, month, day] = parts;
|
|
1120
|
+
if (!year || !month || !day) return null;
|
|
1121
|
+
return Number(`${year}${month.padStart(2, "0")}${day.padStart(2, "0")}`);
|
|
1122
|
+
}
|
|
1123
|
+
function pickLatestCursorVersionDir(agentRoot) {
|
|
1124
|
+
const versionsRoot = path6.join(agentRoot, "versions");
|
|
988
1125
|
if (!existsSync7(versionsRoot)) return null;
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
}
|
|
992
|
-
|
|
1126
|
+
let bestDir = null;
|
|
1127
|
+
let bestKey = -1;
|
|
1128
|
+
for (const entry of readdirSync3(versionsRoot, { withFileTypes: true })) {
|
|
1129
|
+
if (!entry.isDirectory() || !CURSOR_VERSION_DIR.test(entry.name)) continue;
|
|
1130
|
+
const key = parseCursorVersionSortKey(entry.name);
|
|
1131
|
+
if (key == null || key <= bestKey) continue;
|
|
1132
|
+
bestKey = key;
|
|
1133
|
+
bestDir = path6.join(versionsRoot, entry.name);
|
|
1134
|
+
}
|
|
1135
|
+
return bestDir;
|
|
1136
|
+
}
|
|
1137
|
+
function resolveWindowsCursorBundled(agentRoot) {
|
|
1138
|
+
const root = agentRoot?.trim() || path6.join(process.env.LOCALAPPDATA || "", "cursor-agent");
|
|
1139
|
+
const directNode = path6.join(root, "node.exe");
|
|
1140
|
+
const directIndex = path6.join(root, "index.js");
|
|
1141
|
+
if (existsSync7(directNode) && existsSync7(directIndex)) {
|
|
1142
|
+
return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
|
|
1143
|
+
}
|
|
1144
|
+
const versionDir = pickLatestCursorVersionDir(root);
|
|
1145
|
+
if (!versionDir) return null;
|
|
993
1146
|
const nodeExe = path6.join(versionDir, "node.exe");
|
|
994
1147
|
const indexJs = path6.join(versionDir, "index.js");
|
|
995
1148
|
if (!existsSync7(nodeExe) || !existsSync7(indexJs)) return null;
|
|
996
|
-
return {
|
|
1149
|
+
return { nodeExe, indexJs, versionDir };
|
|
997
1150
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1151
|
+
|
|
1152
|
+
// src/providers/cursor.ts
|
|
1153
|
+
var DEFAULT_CURSOR_MODEL = "composer-2.5";
|
|
1154
|
+
function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
|
|
1155
|
+
return {
|
|
1156
|
+
executable: nodeExe,
|
|
1157
|
+
prefixArgs: [indexJs],
|
|
1158
|
+
shell: false,
|
|
1159
|
+
detached: true,
|
|
1160
|
+
bundledVersionDir: versionDir
|
|
1161
|
+
};
|
|
1004
1162
|
}
|
|
1005
1163
|
function resolveCursorSpawn(agentBin) {
|
|
1006
|
-
if (process.platform === "win32"
|
|
1007
|
-
const
|
|
1008
|
-
|
|
1009
|
-
|
|
1164
|
+
if (process.platform === "win32") {
|
|
1165
|
+
const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
|
|
1166
|
+
const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync8(path7.join(path7.dirname(agentBin), "index.js"));
|
|
1167
|
+
const isDefaultShim = agentBin === "agent";
|
|
1168
|
+
if (isCursorWrapper || isBundledNode || isDefaultShim) {
|
|
1169
|
+
const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path7.dirname(agentBin)) : isBundledNode ? {
|
|
1170
|
+
nodeExe: agentBin,
|
|
1171
|
+
indexJs: path7.join(path7.dirname(agentBin), "index.js"),
|
|
1172
|
+
versionDir: path7.dirname(agentBin)
|
|
1173
|
+
} : resolveWindowsCursorBundled();
|
|
1174
|
+
if (bundled) {
|
|
1175
|
+
return bundledSpawnTarget(bundled.nodeExe, bundled.indexJs, bundled.versionDir);
|
|
1176
|
+
}
|
|
1177
|
+
throw new Error(
|
|
1178
|
+
"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."
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1010
1181
|
}
|
|
1011
1182
|
return { executable: agentBin, prefixArgs: [], shell: false, detached: true };
|
|
1012
1183
|
}
|
|
@@ -1014,15 +1185,35 @@ function resolveAgentBin() {
|
|
|
1014
1185
|
const configured = process.env.KYNVER_CURSOR_AGENT_BIN?.trim() || process.env.CURSOR_AGENT_BIN?.trim();
|
|
1015
1186
|
if (configured) return configured;
|
|
1016
1187
|
if (process.platform === "win32") {
|
|
1017
|
-
const
|
|
1018
|
-
|
|
1188
|
+
const bundled = resolveWindowsCursorBundled(
|
|
1189
|
+
process.env.KYNVER_CURSOR_AGENT_ROOT?.trim() || void 0
|
|
1190
|
+
);
|
|
1191
|
+
if (bundled) return bundled.nodeExe;
|
|
1192
|
+
const localAgent = path7.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
|
|
1193
|
+
if (existsSync8(localAgent)) return localAgent;
|
|
1019
1194
|
}
|
|
1020
1195
|
return "agent";
|
|
1021
1196
|
}
|
|
1197
|
+
function cursorWorkerEnv(agentBin, spawnTarget) {
|
|
1198
|
+
return {
|
|
1199
|
+
...process.env,
|
|
1200
|
+
CI: "1",
|
|
1201
|
+
NO_COLOR: "1",
|
|
1202
|
+
...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path7.basename(agentBin) || "agent.cmd" } : {}
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1022
1205
|
var cursorProvider = {
|
|
1023
1206
|
name: "cursor",
|
|
1207
|
+
defaultModel: DEFAULT_CURSOR_MODEL,
|
|
1208
|
+
preflightModel(model) {
|
|
1209
|
+
return preflightCursorModel(model, DEFAULT_CURSOR_MODEL);
|
|
1210
|
+
},
|
|
1024
1211
|
start(opts) {
|
|
1025
|
-
const
|
|
1212
|
+
const preflight = preflightCursorModel(opts.model, DEFAULT_CURSOR_MODEL);
|
|
1213
|
+
if (!preflight.ok) {
|
|
1214
|
+
throw new Error(`cursor provider model preflight failed: ${preflight.note}`);
|
|
1215
|
+
}
|
|
1216
|
+
const model = preflight.model;
|
|
1026
1217
|
const stdoutFd = openSync2(opts.stdoutPath, "a");
|
|
1027
1218
|
const stderrFd = openSync2(opts.stderrPath, "a");
|
|
1028
1219
|
const agentBin = resolveAgentBin();
|
|
@@ -1043,16 +1234,13 @@ var cursorProvider = {
|
|
|
1043
1234
|
model,
|
|
1044
1235
|
opts.prompt
|
|
1045
1236
|
],
|
|
1046
|
-
{
|
|
1237
|
+
hiddenSpawnOptions({
|
|
1047
1238
|
cwd: opts.worktreePath,
|
|
1048
1239
|
detached: spawnTarget.detached,
|
|
1049
1240
|
shell: spawnTarget.shell,
|
|
1050
1241
|
stdio: ["ignore", stdoutFd, stderrFd],
|
|
1051
|
-
env:
|
|
1052
|
-
|
|
1053
|
-
...spawnTarget.prefixArgs.length > 0 ? { CURSOR_INVOKED_AS: path6.basename(agentBin) } : {}
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1242
|
+
env: cursorWorkerEnv(agentBin, spawnTarget)
|
|
1243
|
+
})
|
|
1056
1244
|
);
|
|
1057
1245
|
closeSync2(stdoutFd);
|
|
1058
1246
|
closeSync2(stderrFd);
|
|
@@ -1084,12 +1272,12 @@ function resolveWorkerProvider(name) {
|
|
|
1084
1272
|
|
|
1085
1273
|
// src/auto-complete.ts
|
|
1086
1274
|
import { spawn as spawn3 } from "node:child_process";
|
|
1087
|
-
import { existsSync as
|
|
1088
|
-
import
|
|
1275
|
+
import { existsSync as existsSync9, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
|
|
1276
|
+
import path9 from "node:path";
|
|
1089
1277
|
import { fileURLToPath } from "node:url";
|
|
1090
1278
|
|
|
1091
1279
|
// src/worker-ops.ts
|
|
1092
|
-
import
|
|
1280
|
+
import path8 from "node:path";
|
|
1093
1281
|
async function postCompletion(url, secret, body) {
|
|
1094
1282
|
const res = await fetch(url, {
|
|
1095
1283
|
method: "POST",
|
|
@@ -1212,7 +1400,7 @@ async function completeWorker(args) {
|
|
|
1212
1400
|
function workerStatus(args) {
|
|
1213
1401
|
const worker = loadWorker(String(args.run), String(args.name));
|
|
1214
1402
|
const status = computeWorkerStatus(worker);
|
|
1215
|
-
writeJson(
|
|
1403
|
+
writeJson(path8.join(worker.workerDir, "last-status.json"), status);
|
|
1216
1404
|
console.log(JSON.stringify(status, null, 2));
|
|
1217
1405
|
}
|
|
1218
1406
|
function runStatus(args) {
|
|
@@ -1220,7 +1408,7 @@ function runStatus(args) {
|
|
|
1220
1408
|
const names = Object.keys(run.workers || {});
|
|
1221
1409
|
const workers = names.map((name) => {
|
|
1222
1410
|
const worker = readJson(
|
|
1223
|
-
|
|
1411
|
+
path8.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1224
1412
|
void 0
|
|
1225
1413
|
);
|
|
1226
1414
|
if (!worker) {
|
|
@@ -1256,7 +1444,7 @@ function runStatus(args) {
|
|
|
1256
1444
|
needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
|
|
1257
1445
|
workers
|
|
1258
1446
|
};
|
|
1259
|
-
writeJson(
|
|
1447
|
+
writeJson(path8.join(runDirectory(run.id), "last-board.json"), board);
|
|
1260
1448
|
console.log(JSON.stringify(board, null, 2));
|
|
1261
1449
|
}
|
|
1262
1450
|
function tailWorker(args) {
|
|
@@ -1395,12 +1583,12 @@ async function autoCompleteWorkerCli(raw) {
|
|
|
1395
1583
|
}
|
|
1396
1584
|
}
|
|
1397
1585
|
function resolveDefaultCliPath() {
|
|
1398
|
-
return
|
|
1586
|
+
return path9.join(fileURLToPath(new URL(".", import.meta.url)), "cli.js");
|
|
1399
1587
|
}
|
|
1400
1588
|
function spawnCompletionSidecar(opts) {
|
|
1401
1589
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath();
|
|
1402
|
-
if (!
|
|
1403
|
-
const logPath =
|
|
1590
|
+
if (!existsSync9(cliPath)) return void 0;
|
|
1591
|
+
const logPath = path9.join(opts.workerDir, "auto-complete.log");
|
|
1404
1592
|
let logFd;
|
|
1405
1593
|
try {
|
|
1406
1594
|
logFd = openSync3(logPath, "a");
|
|
@@ -1426,11 +1614,15 @@ function spawnCompletionSidecar(opts) {
|
|
|
1426
1614
|
if (opts.baseUrl) args.push("--base-url", opts.baseUrl);
|
|
1427
1615
|
if (opts.secret) args.push("--secret", opts.secret);
|
|
1428
1616
|
try {
|
|
1429
|
-
const child = spawn3(
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1617
|
+
const child = spawn3(
|
|
1618
|
+
nodeExecutable,
|
|
1619
|
+
args,
|
|
1620
|
+
hiddenSpawnOptions({
|
|
1621
|
+
detached: true,
|
|
1622
|
+
stdio,
|
|
1623
|
+
env: process.env
|
|
1624
|
+
})
|
|
1625
|
+
);
|
|
1434
1626
|
if (logFd !== void 0) closeSync3(logFd);
|
|
1435
1627
|
child.unref();
|
|
1436
1628
|
return { pid: child.pid, logPath, cliPath };
|
|
@@ -1454,31 +1646,44 @@ function spawnWorkerProcess(run, opts) {
|
|
|
1454
1646
|
const name = safeSlug(rawName);
|
|
1455
1647
|
if (run.workers?.[name]) throw new Error(`worker already exists in run ${run.id}: ${name}`);
|
|
1456
1648
|
if (!opts.task) throw new Error(`missing task text for worker ${name}`);
|
|
1649
|
+
const provider = resolveWorkerProvider(opts.provider);
|
|
1650
|
+
let launchModel = opts.model;
|
|
1651
|
+
if (provider.preflightModel) {
|
|
1652
|
+
const preflight = provider.preflightModel(opts.model);
|
|
1653
|
+
if (!preflight.ok) {
|
|
1654
|
+
throw new Error(
|
|
1655
|
+
`model preflight failed for provider "${provider.name}": ${preflight.note ?? "invalid model/provider combination"}`
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
if (preflight.normalized) {
|
|
1659
|
+
console.error(`[supervisor] ${name}: ${preflight.note}`);
|
|
1660
|
+
}
|
|
1661
|
+
launchModel = preflight.model;
|
|
1662
|
+
}
|
|
1457
1663
|
const { worktreesDir } = getPaths();
|
|
1458
|
-
const workerDir =
|
|
1664
|
+
const workerDir = path10.join(runDirectory(run.id), "workers", name);
|
|
1459
1665
|
mkdirSync3(workerDir, { recursive: true });
|
|
1460
|
-
const worktreePath =
|
|
1666
|
+
const worktreePath = path10.join(worktreesDir, run.id, name);
|
|
1461
1667
|
const branch = opts.branch || `agent/${run.id}/${name}`;
|
|
1462
|
-
if (
|
|
1668
|
+
if (existsSync10(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
|
|
1463
1669
|
git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
|
|
1464
1670
|
git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
|
|
1465
|
-
const stdoutPath =
|
|
1466
|
-
const stderrPath =
|
|
1467
|
-
const heartbeatPath =
|
|
1671
|
+
const stdoutPath = path10.join(workerDir, "stdout.jsonl");
|
|
1672
|
+
const stderrPath = path10.join(workerDir, "stderr.log");
|
|
1673
|
+
const heartbeatPath = path10.join(workerDir, "heartbeat.jsonl");
|
|
1468
1674
|
const prompt = buildPrompt({
|
|
1469
1675
|
task: opts.task,
|
|
1470
1676
|
ownedPaths: opts.ownedPaths || [],
|
|
1471
1677
|
worktreePath,
|
|
1472
1678
|
heartbeatPath
|
|
1473
1679
|
});
|
|
1474
|
-
const provider = resolveWorkerProvider(opts.provider);
|
|
1475
1680
|
let started;
|
|
1476
1681
|
try {
|
|
1477
1682
|
started = provider.start({
|
|
1478
1683
|
name,
|
|
1479
1684
|
task: opts.task,
|
|
1480
1685
|
ownedPaths: opts.ownedPaths,
|
|
1481
|
-
model:
|
|
1686
|
+
model: launchModel,
|
|
1482
1687
|
branch,
|
|
1483
1688
|
worktreePath,
|
|
1484
1689
|
workerDir,
|
|
@@ -1492,7 +1697,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
1492
1697
|
git(run.repo, ["branch", "-D", branch], { allowFailure: true });
|
|
1493
1698
|
throw error;
|
|
1494
1699
|
}
|
|
1495
|
-
const model = started.model ||
|
|
1700
|
+
const model = started.model || launchModel || provider.defaultModel || "claude-opus-4-7";
|
|
1496
1701
|
const worker = {
|
|
1497
1702
|
name,
|
|
1498
1703
|
runId: run.id,
|
|
@@ -1514,7 +1719,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
1514
1719
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1515
1720
|
};
|
|
1516
1721
|
saveWorker(run.id, worker);
|
|
1517
|
-
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath:
|
|
1722
|
+
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path10.join(workerDir, "worker.json") } };
|
|
1518
1723
|
run.status = "running";
|
|
1519
1724
|
saveRun(run);
|
|
1520
1725
|
if (worker.agentOsId && worker.taskId) {
|
|
@@ -1728,7 +1933,7 @@ async function dispatchRun(args) {
|
|
|
1728
1933
|
}
|
|
1729
1934
|
|
|
1730
1935
|
// src/sweep.ts
|
|
1731
|
-
import
|
|
1936
|
+
import path11 from "node:path";
|
|
1732
1937
|
async function sweepRun(args) {
|
|
1733
1938
|
const pipeline = args.pipeline === true || args.pipeline === "true";
|
|
1734
1939
|
try {
|
|
@@ -1740,7 +1945,7 @@ async function sweepRun(args) {
|
|
|
1740
1945
|
const releasedLocalOrphans = [];
|
|
1741
1946
|
for (const name of Object.keys(run.workers || {})) {
|
|
1742
1947
|
const worker = readJson(
|
|
1743
|
-
|
|
1948
|
+
path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1744
1949
|
void 0
|
|
1745
1950
|
);
|
|
1746
1951
|
if (!worker || !worker.dispatched || !worker.taskId) continue;
|
|
@@ -1783,11 +1988,11 @@ async function sweepRun(args) {
|
|
|
1783
1988
|
}
|
|
1784
1989
|
|
|
1785
1990
|
// src/worktree.ts
|
|
1786
|
-
import { existsSync as
|
|
1787
|
-
import
|
|
1991
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4 } from "node:fs";
|
|
1992
|
+
import path13 from "node:path";
|
|
1788
1993
|
|
|
1789
1994
|
// src/validate.ts
|
|
1790
|
-
import
|
|
1995
|
+
import path12 from "node:path";
|
|
1791
1996
|
var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
1792
1997
|
function validateRunId(runId) {
|
|
1793
1998
|
const trimmed = runId.trim();
|
|
@@ -1795,7 +2000,7 @@ function validateRunId(runId) {
|
|
|
1795
2000
|
return trimmed;
|
|
1796
2001
|
}
|
|
1797
2002
|
function validateRepo(repo) {
|
|
1798
|
-
const resolved =
|
|
2003
|
+
const resolved = path12.resolve(repo);
|
|
1799
2004
|
if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
|
|
1800
2005
|
return resolved;
|
|
1801
2006
|
}
|
|
@@ -1806,7 +2011,7 @@ function createRun(args) {
|
|
|
1806
2011
|
ensureGitRepo(repo);
|
|
1807
2012
|
const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
|
|
1808
2013
|
const dir = runDirectory(id);
|
|
1809
|
-
if (
|
|
2014
|
+
if (existsSync11(dir)) failExists(`run already exists: ${id}`);
|
|
1810
2015
|
mkdirSync4(dir, { recursive: true });
|
|
1811
2016
|
const base = String(args.base || "origin/main");
|
|
1812
2017
|
const baseCommit = git(repo, ["rev-parse", base]).trim();
|
|
@@ -1820,12 +2025,12 @@ function createRun(args) {
|
|
|
1820
2025
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1821
2026
|
workers: {}
|
|
1822
2027
|
};
|
|
1823
|
-
writeJson(
|
|
2028
|
+
writeJson(path13.join(dir, "run.json"), run);
|
|
1824
2029
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
1825
2030
|
}
|
|
1826
2031
|
function listRuns() {
|
|
1827
2032
|
const { runsDir } = getPaths();
|
|
1828
|
-
const rows = listRunIds(runsDir).map((id) => readJson(
|
|
2033
|
+
const rows = listRunIds(runsDir).map((id) => readJson(path13.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
|
|
1829
2034
|
id: run.id,
|
|
1830
2035
|
name: run.name,
|
|
1831
2036
|
status: run.status,
|
|
@@ -1840,10 +2045,10 @@ function failExists(message) {
|
|
|
1840
2045
|
}
|
|
1841
2046
|
|
|
1842
2047
|
// src/pipeline-tick.ts
|
|
1843
|
-
import
|
|
2048
|
+
import path16 from "node:path";
|
|
1844
2049
|
|
|
1845
2050
|
// src/finalize.ts
|
|
1846
|
-
import
|
|
2051
|
+
import path14 from "node:path";
|
|
1847
2052
|
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
|
|
1848
2053
|
function terminalStatusFor(run) {
|
|
1849
2054
|
const names = Object.keys(run.workers || {});
|
|
@@ -1853,7 +2058,7 @@ function terminalStatusFor(run) {
|
|
|
1853
2058
|
let anyCompletionBlocked = false;
|
|
1854
2059
|
for (const name of names) {
|
|
1855
2060
|
const worker = readJson(
|
|
1856
|
-
|
|
2061
|
+
path14.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1857
2062
|
void 0
|
|
1858
2063
|
);
|
|
1859
2064
|
if (!worker) continue;
|
|
@@ -1886,7 +2091,7 @@ function finalizeStaleRuns() {
|
|
|
1886
2091
|
}
|
|
1887
2092
|
|
|
1888
2093
|
// src/plan-progress-daemon-sync.ts
|
|
1889
|
-
import
|
|
2094
|
+
import path15 from "node:path";
|
|
1890
2095
|
|
|
1891
2096
|
// src/plan-progress-sync.ts
|
|
1892
2097
|
async function syncPlanProgress(args) {
|
|
@@ -1910,7 +2115,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
1910
2115
|
const outcomes = [];
|
|
1911
2116
|
for (const name of Object.keys(run.workers || {})) {
|
|
1912
2117
|
const worker = readJson(
|
|
1913
|
-
|
|
2118
|
+
path15.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1914
2119
|
void 0
|
|
1915
2120
|
);
|
|
1916
2121
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -1964,7 +2169,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
1964
2169
|
const outcomes = [];
|
|
1965
2170
|
for (const name of Object.keys(run.workers || {})) {
|
|
1966
2171
|
const worker = readJson(
|
|
1967
|
-
|
|
2172
|
+
path16.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1968
2173
|
void 0
|
|
1969
2174
|
);
|
|
1970
2175
|
if (!worker?.taskId) continue;
|