@kynver-app/runtime 0.1.1 → 0.1.3
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/README.md +12 -1
- package/dist/cli.js +122 -33
- package/dist/cli.js.map +4 -4
- package/dist/index.js +125 -36
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -236,12 +236,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
|
236
236
|
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
237
237
|
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
238
238
|
function observeRunnerDiskGate(input = {}) {
|
|
239
|
-
const
|
|
239
|
+
const path14 = input.diskPath?.trim() || "/";
|
|
240
240
|
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
241
241
|
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
242
242
|
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
243
243
|
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
244
|
-
const stats = statfsSync(
|
|
244
|
+
const stats = statfsSync(path14);
|
|
245
245
|
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
246
246
|
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
247
247
|
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
@@ -261,7 +261,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
261
261
|
}
|
|
262
262
|
return {
|
|
263
263
|
ok,
|
|
264
|
-
path:
|
|
264
|
+
path: path14,
|
|
265
265
|
freeBytes,
|
|
266
266
|
totalBytes,
|
|
267
267
|
usedPercent,
|
|
@@ -372,7 +372,8 @@ function parseClaudeStream(file) {
|
|
|
372
372
|
for (const line of lines) {
|
|
373
373
|
const event = safeJson(line);
|
|
374
374
|
if (!event) continue;
|
|
375
|
-
const
|
|
375
|
+
const tsMs = event.timestamp_ms;
|
|
376
|
+
const ts = event.timestamp || event.ts || (tsMs ? new Date(tsMs).toISOString() : void 0);
|
|
376
377
|
if (ts) {
|
|
377
378
|
result.firstEventAt ||= ts;
|
|
378
379
|
result.lastEventAt = ts;
|
|
@@ -631,8 +632,8 @@ function observeRunnerResourceGate(input) {
|
|
|
631
632
|
}
|
|
632
633
|
|
|
633
634
|
// src/supervisor.ts
|
|
634
|
-
import { existsSync as
|
|
635
|
-
import
|
|
635
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3 } from "node:fs";
|
|
636
|
+
import path7 from "node:path";
|
|
636
637
|
|
|
637
638
|
// src/prompt.ts
|
|
638
639
|
function buildPrompt(input) {
|
|
@@ -691,9 +692,97 @@ var claudeProvider = {
|
|
|
691
692
|
}
|
|
692
693
|
};
|
|
693
694
|
|
|
695
|
+
// src/providers/cursor.ts
|
|
696
|
+
import { closeSync as closeSync2, existsSync as existsSync6, openSync as openSync2, readdirSync as readdirSync2 } from "node:fs";
|
|
697
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
698
|
+
import path6 from "node:path";
|
|
699
|
+
var DEFAULT_CURSOR_MODEL = "composer-2.5";
|
|
700
|
+
function latestVersionDir(versionsRoot) {
|
|
701
|
+
if (!existsSync6(versionsRoot)) return null;
|
|
702
|
+
const versions = readdirSync2(versionsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && /^\d{4}\.\d/.test(entry.name)).map((entry) => entry.name).sort((a, b) => b.localeCompare(a));
|
|
703
|
+
return versions[0] ? path6.join(versionsRoot, versions[0]) : null;
|
|
704
|
+
}
|
|
705
|
+
function resolveBundledCursor(versionDir) {
|
|
706
|
+
const nodeExe = path6.join(versionDir, "node.exe");
|
|
707
|
+
const indexJs = path6.join(versionDir, "index.js");
|
|
708
|
+
if (!existsSync6(nodeExe) || !existsSync6(indexJs)) return null;
|
|
709
|
+
return { executable: nodeExe, prefixArgs: [indexJs], shell: false, detached: true };
|
|
710
|
+
}
|
|
711
|
+
function resolveWindowsCursorSpawn(agentBin) {
|
|
712
|
+
const agentRoot = path6.dirname(agentBin);
|
|
713
|
+
const direct = resolveBundledCursor(agentRoot);
|
|
714
|
+
if (direct) return direct;
|
|
715
|
+
const versionDir = latestVersionDir(path6.join(agentRoot, "versions"));
|
|
716
|
+
return versionDir ? resolveBundledCursor(versionDir) : null;
|
|
717
|
+
}
|
|
718
|
+
function resolveCursorSpawn(agentBin) {
|
|
719
|
+
if (process.platform === "win32" && /\.(cmd|bat)$/i.test(agentBin)) {
|
|
720
|
+
const bundled = resolveWindowsCursorSpawn(agentBin);
|
|
721
|
+
if (bundled) return bundled;
|
|
722
|
+
return { executable: agentBin, prefixArgs: [], shell: true, detached: false };
|
|
723
|
+
}
|
|
724
|
+
return { executable: agentBin, prefixArgs: [], shell: false, detached: true };
|
|
725
|
+
}
|
|
726
|
+
function resolveAgentBin() {
|
|
727
|
+
const configured = process.env.KYNVER_CURSOR_AGENT_BIN?.trim() || process.env.CURSOR_AGENT_BIN?.trim();
|
|
728
|
+
if (configured) return configured;
|
|
729
|
+
if (process.platform === "win32") {
|
|
730
|
+
const localAgent = path6.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
|
|
731
|
+
if (existsSync6(localAgent)) return localAgent;
|
|
732
|
+
}
|
|
733
|
+
return "agent";
|
|
734
|
+
}
|
|
735
|
+
var cursorProvider = {
|
|
736
|
+
name: "cursor",
|
|
737
|
+
start(opts) {
|
|
738
|
+
const model = opts.model || DEFAULT_CURSOR_MODEL;
|
|
739
|
+
const stdoutFd = openSync2(opts.stdoutPath, "a");
|
|
740
|
+
const stderrFd = openSync2(opts.stderrPath, "a");
|
|
741
|
+
const agentBin = resolveAgentBin();
|
|
742
|
+
const spawnTarget = resolveCursorSpawn(agentBin);
|
|
743
|
+
const child = spawn2(
|
|
744
|
+
spawnTarget.executable,
|
|
745
|
+
[
|
|
746
|
+
...spawnTarget.prefixArgs,
|
|
747
|
+
"-p",
|
|
748
|
+
"--force",
|
|
749
|
+
"--trust",
|
|
750
|
+
"--workspace",
|
|
751
|
+
opts.worktreePath,
|
|
752
|
+
"--output-format",
|
|
753
|
+
"stream-json",
|
|
754
|
+
"--stream-partial-output",
|
|
755
|
+
"--model",
|
|
756
|
+
model,
|
|
757
|
+
opts.prompt
|
|
758
|
+
],
|
|
759
|
+
{
|
|
760
|
+
cwd: opts.worktreePath,
|
|
761
|
+
detached: spawnTarget.detached,
|
|
762
|
+
shell: spawnTarget.shell,
|
|
763
|
+
stdio: ["ignore", stdoutFd, stderrFd],
|
|
764
|
+
env: {
|
|
765
|
+
...process.env,
|
|
766
|
+
...spawnTarget.prefixArgs.length > 0 ? { CURSOR_INVOKED_AS: path6.basename(agentBin) } : {}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
);
|
|
770
|
+
closeSync2(stdoutFd);
|
|
771
|
+
closeSync2(stderrFd);
|
|
772
|
+
if (!child.pid) {
|
|
773
|
+
throw new Error(
|
|
774
|
+
`failed to spawn Cursor agent worker (is \`${agentBin}\` on PATH? run \`agent login\` or set CURSOR_API_KEY)`
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
child.unref();
|
|
778
|
+
return { pid: child.pid, model };
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
|
|
694
782
|
// src/providers/registry.ts
|
|
695
783
|
var BUILTIN = {
|
|
696
|
-
claude: claudeProvider
|
|
784
|
+
claude: claudeProvider,
|
|
785
|
+
cursor: cursorProvider
|
|
697
786
|
};
|
|
698
787
|
var overrideProvider = null;
|
|
699
788
|
function resolveWorkerProvider(name) {
|
|
@@ -713,16 +802,16 @@ function spawnWorkerProcess(run, opts) {
|
|
|
713
802
|
if (run.workers?.[name]) throw new Error(`worker already exists in run ${run.id}: ${name}`);
|
|
714
803
|
if (!opts.task) throw new Error(`missing task text for worker ${name}`);
|
|
715
804
|
const { worktreesDir } = getPaths();
|
|
716
|
-
const workerDir =
|
|
805
|
+
const workerDir = path7.join(runDirectory(run.id), "workers", name);
|
|
717
806
|
mkdirSync3(workerDir, { recursive: true });
|
|
718
|
-
const worktreePath =
|
|
807
|
+
const worktreePath = path7.join(worktreesDir, run.id, name);
|
|
719
808
|
const branch = opts.branch || `agent/${run.id}/${name}`;
|
|
720
|
-
if (
|
|
809
|
+
if (existsSync7(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
|
|
721
810
|
git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
|
|
722
811
|
git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
|
|
723
|
-
const stdoutPath =
|
|
724
|
-
const stderrPath =
|
|
725
|
-
const heartbeatPath =
|
|
812
|
+
const stdoutPath = path7.join(workerDir, "stdout.jsonl");
|
|
813
|
+
const stderrPath = path7.join(workerDir, "stderr.log");
|
|
814
|
+
const heartbeatPath = path7.join(workerDir, "heartbeat.jsonl");
|
|
726
815
|
const prompt = buildPrompt({
|
|
727
816
|
task: opts.task,
|
|
728
817
|
ownedPaths: opts.ownedPaths || [],
|
|
@@ -771,7 +860,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
771
860
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
772
861
|
};
|
|
773
862
|
saveWorker(run.id, worker);
|
|
774
|
-
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath:
|
|
863
|
+
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path7.join(workerDir, "worker.json") } };
|
|
775
864
|
run.status = "running";
|
|
776
865
|
saveRun(run);
|
|
777
866
|
return worker;
|
|
@@ -976,7 +1065,7 @@ function redactHarness(text, secret) {
|
|
|
976
1065
|
}
|
|
977
1066
|
|
|
978
1067
|
// src/validate.ts
|
|
979
|
-
import
|
|
1068
|
+
import path8 from "node:path";
|
|
980
1069
|
var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
981
1070
|
var WORKER_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
|
|
982
1071
|
function validateRunId(runId) {
|
|
@@ -990,15 +1079,15 @@ function validateWorkerName(name) {
|
|
|
990
1079
|
return trimmed;
|
|
991
1080
|
}
|
|
992
1081
|
function validateRepo(repo) {
|
|
993
|
-
const resolved =
|
|
1082
|
+
const resolved = path8.resolve(repo);
|
|
994
1083
|
if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
|
|
995
1084
|
return resolved;
|
|
996
1085
|
}
|
|
997
1086
|
function validateOwnedPaths(repoRoot, ownedPaths) {
|
|
998
1087
|
return ownedPaths.map((owned) => {
|
|
999
|
-
const resolved =
|
|
1000
|
-
const rel =
|
|
1001
|
-
if (rel.startsWith("..") ||
|
|
1088
|
+
const resolved = path8.resolve(repoRoot, owned);
|
|
1089
|
+
const rel = path8.relative(repoRoot, resolved);
|
|
1090
|
+
if (rel.startsWith("..") || path8.isAbsolute(rel)) {
|
|
1002
1091
|
throw new Error(`owned path escapes repo: ${owned}`);
|
|
1003
1092
|
}
|
|
1004
1093
|
return resolved;
|
|
@@ -1010,14 +1099,14 @@ function validateTailLines(lines) {
|
|
|
1010
1099
|
}
|
|
1011
1100
|
|
|
1012
1101
|
// src/worktree.ts
|
|
1013
|
-
import { existsSync as
|
|
1014
|
-
import
|
|
1102
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync4 } from "node:fs";
|
|
1103
|
+
import path9 from "node:path";
|
|
1015
1104
|
function createRun(args) {
|
|
1016
1105
|
const repo = validateRepo(required(String(args.repo || ""), "--repo"));
|
|
1017
1106
|
ensureGitRepo(repo);
|
|
1018
1107
|
const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
|
|
1019
1108
|
const dir = runDirectory(id);
|
|
1020
|
-
if (
|
|
1109
|
+
if (existsSync8(dir)) failExists(`run already exists: ${id}`);
|
|
1021
1110
|
mkdirSync4(dir, { recursive: true });
|
|
1022
1111
|
const base = String(args.base || "origin/main");
|
|
1023
1112
|
const baseCommit = git(repo, ["rev-parse", base]).trim();
|
|
@@ -1031,12 +1120,12 @@ function createRun(args) {
|
|
|
1031
1120
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1032
1121
|
workers: {}
|
|
1033
1122
|
};
|
|
1034
|
-
writeJson(
|
|
1123
|
+
writeJson(path9.join(dir, "run.json"), run);
|
|
1035
1124
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
1036
1125
|
}
|
|
1037
1126
|
function listRuns() {
|
|
1038
1127
|
const { runsDir } = getPaths();
|
|
1039
|
-
const rows = listRunIds(runsDir).map((id) => readJson(
|
|
1128
|
+
const rows = listRunIds(runsDir).map((id) => readJson(path9.join(runDirectory(id), "run.json"), null)).filter(Boolean).map((run) => ({
|
|
1040
1129
|
id: run.id,
|
|
1041
1130
|
name: run.name,
|
|
1042
1131
|
status: run.status,
|
|
@@ -1051,7 +1140,7 @@ function failExists(message) {
|
|
|
1051
1140
|
}
|
|
1052
1141
|
|
|
1053
1142
|
// src/sweep.ts
|
|
1054
|
-
import
|
|
1143
|
+
import path10 from "node:path";
|
|
1055
1144
|
async function sweepRun(args) {
|
|
1056
1145
|
const pipeline = args.pipeline === true || args.pipeline === "true";
|
|
1057
1146
|
try {
|
|
@@ -1063,7 +1152,7 @@ async function sweepRun(args) {
|
|
|
1063
1152
|
const releasedLocalOrphans = [];
|
|
1064
1153
|
for (const name of Object.keys(run.workers || {})) {
|
|
1065
1154
|
const worker = readJson(
|
|
1066
|
-
|
|
1155
|
+
path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1067
1156
|
null
|
|
1068
1157
|
);
|
|
1069
1158
|
if (!worker || !worker.dispatched || !worker.taskId) continue;
|
|
@@ -1106,7 +1195,7 @@ async function sweepRun(args) {
|
|
|
1106
1195
|
}
|
|
1107
1196
|
|
|
1108
1197
|
// src/worker-ops.ts
|
|
1109
|
-
import
|
|
1198
|
+
import path11 from "node:path";
|
|
1110
1199
|
async function tryCompleteWorker(args) {
|
|
1111
1200
|
const worker = loadWorker(String(args.run), String(args.name));
|
|
1112
1201
|
const status = computeWorkerStatus(worker);
|
|
@@ -1199,7 +1288,7 @@ async function completeWorker(args) {
|
|
|
1199
1288
|
function workerStatus(args) {
|
|
1200
1289
|
const worker = loadWorker(String(args.run), String(args.name));
|
|
1201
1290
|
const status = computeWorkerStatus(worker);
|
|
1202
|
-
writeJson(
|
|
1291
|
+
writeJson(path11.join(worker.workerDir, "last-status.json"), status);
|
|
1203
1292
|
console.log(JSON.stringify(status, null, 2));
|
|
1204
1293
|
}
|
|
1205
1294
|
function runStatus(args) {
|
|
@@ -1207,7 +1296,7 @@ function runStatus(args) {
|
|
|
1207
1296
|
const names = Object.keys(run.workers || {});
|
|
1208
1297
|
const workers = names.map((name) => {
|
|
1209
1298
|
const worker = readJson(
|
|
1210
|
-
|
|
1299
|
+
path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1211
1300
|
null
|
|
1212
1301
|
);
|
|
1213
1302
|
if (!worker) {
|
|
@@ -1239,7 +1328,7 @@ function runStatus(args) {
|
|
|
1239
1328
|
needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
|
|
1240
1329
|
workers
|
|
1241
1330
|
};
|
|
1242
|
-
writeJson(
|
|
1331
|
+
writeJson(path11.join(runDirectory(run.id), "last-board.json"), board);
|
|
1243
1332
|
console.log(JSON.stringify(board, null, 2));
|
|
1244
1333
|
}
|
|
1245
1334
|
function tailWorker(args) {
|
|
@@ -1273,11 +1362,11 @@ function stopWorker(args) {
|
|
|
1273
1362
|
|
|
1274
1363
|
// src/cli.ts
|
|
1275
1364
|
import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
1276
|
-
import
|
|
1365
|
+
import path13 from "node:path";
|
|
1277
1366
|
import { fileURLToPath } from "node:url";
|
|
1278
1367
|
|
|
1279
1368
|
// src/pipeline-tick.ts
|
|
1280
|
-
import
|
|
1369
|
+
import path12 from "node:path";
|
|
1281
1370
|
|
|
1282
1371
|
// src/workspace-runtime-config.ts
|
|
1283
1372
|
async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
@@ -1306,7 +1395,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
1306
1395
|
const outcomes = [];
|
|
1307
1396
|
for (const name of Object.keys(run.workers || {})) {
|
|
1308
1397
|
const worker = readJson(
|
|
1309
|
-
|
|
1398
|
+
path12.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1310
1399
|
null
|
|
1311
1400
|
);
|
|
1312
1401
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -1439,14 +1528,14 @@ function usage(code = 0) {
|
|
|
1439
1528
|
[
|
|
1440
1529
|
"Usage:",
|
|
1441
1530
|
" kynver login --api-key KEY",
|
|
1442
|
-
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--max-workers N]",
|
|
1531
|
+
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--max-workers N] [--provider claude|cursor]",
|
|
1443
1532
|
" kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS]",
|
|
1444
1533
|
" kynver run create --repo /path/repo [--name name] [--base origin/main]",
|
|
1445
1534
|
" kynver run list",
|
|
1446
1535
|
" kynver run status --run RUN_ID",
|
|
1447
1536
|
" kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-7] [--disk-path /]",
|
|
1448
1537
|
" kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
|
|
1449
|
-
' kynver worker start --run RUN_ID --name worker --task "..." [--owned path[,path]] [--model claude
|
|
1538
|
+
' kynver worker start --run RUN_ID --name worker --task "..." [--owned path[,path]] [--model MODEL] [--provider claude|cursor] [--agent-os-id AOS_ID] [--task-id TASK_ID]',
|
|
1450
1539
|
" kynver worker status --run RUN_ID --name worker",
|
|
1451
1540
|
" kynver worker tail --run RUN_ID --name worker [--lines 40] [--raw]",
|
|
1452
1541
|
" kynver worker stop --run RUN_ID --name worker",
|
|
@@ -1485,7 +1574,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1485
1574
|
if (scope === "worker" && action === "complete") return void await completeWorker(args);
|
|
1486
1575
|
unknownCommand(scope, action);
|
|
1487
1576
|
}
|
|
1488
|
-
var isCliEntry = process.argv[1] &&
|
|
1577
|
+
var isCliEntry = process.argv[1] && path13.resolve(process.argv[1]) === path13.resolve(fileURLToPath(import.meta.url));
|
|
1489
1578
|
if (isCliEntry) {
|
|
1490
1579
|
void main().catch((error) => {
|
|
1491
1580
|
console.error(error);
|