@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/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 path13 = input.diskPath?.trim() || "/";
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(path13);
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: path13,
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 ts = event.timestamp || event.ts;
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 existsSync6, mkdirSync as mkdirSync3 } from "node:fs";
635
- import path6 from "node:path";
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 = path6.join(runDirectory(run.id), "workers", name);
805
+ const workerDir = path7.join(runDirectory(run.id), "workers", name);
717
806
  mkdirSync3(workerDir, { recursive: true });
718
- const worktreePath = path6.join(worktreesDir, run.id, name);
807
+ const worktreePath = path7.join(worktreesDir, run.id, name);
719
808
  const branch = opts.branch || `agent/${run.id}/${name}`;
720
- if (existsSync6(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
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 = path6.join(workerDir, "stdout.jsonl");
724
- const stderrPath = path6.join(workerDir, "stderr.log");
725
- const heartbeatPath = path6.join(workerDir, "heartbeat.jsonl");
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: path6.join(workerDir, "worker.json") } };
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 path7 from "node:path";
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 = path7.resolve(repo);
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 = path7.resolve(repoRoot, owned);
1000
- const rel = path7.relative(repoRoot, resolved);
1001
- if (rel.startsWith("..") || path7.isAbsolute(rel)) {
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 existsSync7, mkdirSync as mkdirSync4 } from "node:fs";
1014
- import path8 from "node:path";
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 (existsSync7(dir)) failExists(`run already exists: ${id}`);
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(path8.join(dir, "run.json"), run);
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(path8.join(runDirectory(id), "run.json"), null)).filter(Boolean).map((run) => ({
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 path9 from "node:path";
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
- path9.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
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 path10 from "node:path";
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(path10.join(worker.workerDir, "last-status.json"), status);
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
- path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
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(path10.join(runDirectory(run.id), "last-board.json"), board);
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 path12 from "node:path";
1365
+ import path13 from "node:path";
1277
1366
  import { fileURLToPath } from "node:url";
1278
1367
 
1279
1368
  // src/pipeline-tick.ts
1280
- import path11 from "node:path";
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
- path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
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-opus-4-7] [--agent-os-id AOS_ID] [--task-id TASK_ID]',
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] && path12.resolve(process.argv[1]) === path12.resolve(fileURLToPath(import.meta.url));
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);