@kynver-app/runtime 0.1.11 → 0.1.13

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
@@ -381,12 +381,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
381
381
  var DEFAULT_MAX_USED_PERCENT = 80;
382
382
  var DEFAULT_HARD_MAX_USED_PERCENT = 90;
383
383
  function observeRunnerDiskGate(input = {}) {
384
- const path14 = input.diskPath?.trim() || "/";
384
+ const path15 = input.diskPath?.trim() || "/";
385
385
  const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
386
386
  const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
387
387
  const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
388
388
  const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
389
- const stats = statfsSync(path14);
389
+ const stats = statfsSync(path15);
390
390
  const freeBytes = Number(stats.bavail) * Number(stats.bsize);
391
391
  const totalBytes = Number(stats.blocks) * Number(stats.bsize);
392
392
  const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
@@ -406,7 +406,7 @@ function observeRunnerDiskGate(input = {}) {
406
406
  }
407
407
  return {
408
408
  ok,
409
- path: path14,
409
+ path: path15,
410
410
  freeBytes,
411
411
  totalBytes,
412
412
  usedPercent,
@@ -419,10 +419,12 @@ function observeRunnerDiskGate(input = {}) {
419
419
  }
420
420
 
421
421
  // src/resource-gate.ts
422
+ import { readFileSync as readFileSync5 } from "node:fs";
422
423
  import os from "node:os";
423
424
  import path5 from "node:path";
424
425
 
425
426
  // src/run-store.ts
427
+ import { existsSync as existsSync4, readdirSync as readdirSync2 } from "node:fs";
426
428
  import path4 from "node:path";
427
429
 
428
430
  // src/paths.ts
@@ -458,6 +460,20 @@ function loadRun(id) {
458
460
  const { runsDir } = getPaths();
459
461
  return readJson(path4.join(runDir(runsDir, safeSlug(id)), "run.json"));
460
462
  }
463
+ function listRunRecords() {
464
+ const { runsDir } = getPaths();
465
+ if (!existsSync4(runsDir)) return [];
466
+ const runs = [];
467
+ for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
468
+ if (!entry.isDirectory()) continue;
469
+ const run = readJson(
470
+ path4.join(runsDir, entry.name, "run.json"),
471
+ void 0
472
+ );
473
+ if (run?.id) runs.push(run);
474
+ }
475
+ return runs;
476
+ }
461
477
  function loadWorker(runId, name) {
462
478
  const { runsDir } = getPaths();
463
479
  return readJson(
@@ -478,7 +494,7 @@ function runDirectory(id) {
478
494
  }
479
495
 
480
496
  // src/heartbeat.ts
481
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
497
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
482
498
  function parseHeartbeat(file) {
483
499
  const result = {
484
500
  heartbeatCount: 0,
@@ -487,7 +503,7 @@ function parseHeartbeat(file) {
487
503
  lastHeartbeatSummary: null,
488
504
  heartbeatBlocker: null
489
505
  };
490
- if (!existsSync4(file)) return result;
506
+ if (!existsSync5(file)) return result;
491
507
  const lines = readFileSync3(file, "utf8").split("\n").filter(Boolean);
492
508
  for (const line of lines) {
493
509
  const entry = safeJson(line);
@@ -503,7 +519,7 @@ function parseHeartbeat(file) {
503
519
  }
504
520
 
505
521
  // src/stream.ts
506
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
522
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
507
523
  function parseClaudeStream(file) {
508
524
  const result = {
509
525
  firstEventAt: null,
@@ -512,7 +528,7 @@ function parseClaudeStream(file) {
512
528
  finalResult: null,
513
529
  error: null
514
530
  };
515
- if (!existsSync5(file)) return result;
531
+ if (!existsSync6(file)) return result;
516
532
  const lines = readFileSync4(file, "utf8").split("\n").filter(Boolean);
517
533
  for (const line of lines) {
518
534
  const event = safeJson(line);
@@ -805,13 +821,23 @@ function computeAutoMaxWorkers(totalMemBytes, opts = {}) {
805
821
  const raw = Math.max(1, Math.floor(budgetBytes / perWorkerMemBytes));
806
822
  return Math.min(raw, AUTO_MAX_WORKERS_CEILING);
807
823
  }
808
- function countActiveWorkers(runId) {
809
- const run = loadRun(runId);
824
+ function readAvailableMemBytes() {
825
+ if (process.platform === "linux") {
826
+ try {
827
+ const meminfo = readFileSync5("/proc/meminfo", "utf8");
828
+ const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m);
829
+ if (match) return Number(match[1]) * 1024;
830
+ } catch {
831
+ }
832
+ }
833
+ return os.freemem();
834
+ }
835
+ function countActiveWorkersForRun(run) {
810
836
  let active = 0;
811
837
  for (const name of Object.keys(run.workers || {})) {
812
838
  const worker = readJson(
813
839
  path5.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
814
- null
840
+ void 0
815
841
  );
816
842
  if (!worker) continue;
817
843
  const status = computeWorkerStatus(worker);
@@ -821,14 +847,19 @@ function countActiveWorkers(runId) {
821
847
  }
822
848
  return active;
823
849
  }
850
+ function countActiveWorkersGlobal() {
851
+ let active = 0;
852
+ for (const run of listRunRecords()) active += countActiveWorkersForRun(run);
853
+ return active;
854
+ }
824
855
  function observeRunnerResourceGate(input) {
825
856
  const { perWorkerMemBytes, memReserveBytes, memUtilization, configuredMaxWorkers } = resolveResourceConfig(
826
857
  input.config,
827
858
  input.configuredMaxWorkersOverride
828
859
  );
829
860
  const totalMemBytes = input.totalMemBytes ?? os.totalmem();
830
- const freeMemBytes = input.freeMemBytes ?? os.freemem();
831
- const activeWorkers = input.activeWorkers ?? countActiveWorkers(input.runId);
861
+ const freeMemBytes = input.freeMemBytes ?? readAvailableMemBytes();
862
+ const activeWorkers = input.activeWorkers ?? countActiveWorkersGlobal();
832
863
  const budgetBytes = Math.max(0, Math.floor(totalMemBytes * memUtilization) - memReserveBytes);
833
864
  const capacityFromTotal = Math.max(0, Math.floor(budgetBytes / perWorkerMemBytes));
834
865
  const capacityFromFree = Math.max(0, Math.floor(Math.max(0, freeMemBytes - memReserveBytes) / perWorkerMemBytes));
@@ -836,13 +867,13 @@ function observeRunnerResourceGate(input) {
836
867
  const targetCap = configuredMaxWorkers ?? autoCap;
837
868
  const maxConcurrentWorkers = Math.max(0, Math.min(targetCap, capacityFromTotal));
838
869
  const slotsByCapacity = Math.max(0, maxConcurrentWorkers - activeWorkers);
839
- const slotsByFreeMem = Math.max(0, capacityFromFree - activeWorkers);
870
+ const slotsByFreeMem = capacityFromFree;
840
871
  const slotsAvailable = Math.min(slotsByCapacity, slotsByFreeMem);
841
872
  let reason = null;
842
873
  if (slotsAvailable <= 0) {
843
874
  if (activeWorkers >= maxConcurrentWorkers) {
844
875
  reason = `at worker limit (${activeWorkers}/${maxConcurrentWorkers} running)`;
845
- } else if (capacityFromFree <= activeWorkers) {
876
+ } else if (capacityFromFree <= 0) {
846
877
  reason = "insufficient free memory \u2014 waiting for workers to finish";
847
878
  } else {
848
879
  reason = "no worker slots available";
@@ -865,7 +896,7 @@ function observeRunnerResourceGate(input) {
865
896
  }
866
897
 
867
898
  // src/supervisor.ts
868
- import { existsSync as existsSync7, mkdirSync as mkdirSync3 } from "node:fs";
899
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3 } from "node:fs";
869
900
  import path7 from "node:path";
870
901
 
871
902
  // src/prompt.ts
@@ -938,19 +969,19 @@ var claudeProvider = {
938
969
  };
939
970
 
940
971
  // src/providers/cursor.ts
941
- import { closeSync as closeSync2, existsSync as existsSync6, openSync as openSync2, readdirSync as readdirSync2 } from "node:fs";
972
+ import { closeSync as closeSync2, existsSync as existsSync7, openSync as openSync2, readdirSync as readdirSync3 } from "node:fs";
942
973
  import { spawn as spawn2 } from "node:child_process";
943
974
  import path6 from "node:path";
944
975
  var DEFAULT_CURSOR_MODEL = "composer-2.5";
945
976
  function latestVersionDir(versionsRoot) {
946
- if (!existsSync6(versionsRoot)) return null;
947
- 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));
977
+ if (!existsSync7(versionsRoot)) return null;
978
+ const versions = readdirSync3(versionsRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && /^\d{4}\.\d/.test(entry.name)).map((entry) => entry.name).sort((a, b) => b.localeCompare(a));
948
979
  return versions[0] ? path6.join(versionsRoot, versions[0]) : null;
949
980
  }
950
981
  function resolveBundledCursor(versionDir) {
951
982
  const nodeExe = path6.join(versionDir, "node.exe");
952
983
  const indexJs = path6.join(versionDir, "index.js");
953
- if (!existsSync6(nodeExe) || !existsSync6(indexJs)) return null;
984
+ if (!existsSync7(nodeExe) || !existsSync7(indexJs)) return null;
954
985
  return { executable: nodeExe, prefixArgs: [indexJs], shell: false, detached: true };
955
986
  }
956
987
  function resolveWindowsCursorSpawn(agentBin) {
@@ -973,7 +1004,7 @@ function resolveAgentBin() {
973
1004
  if (configured) return configured;
974
1005
  if (process.platform === "win32") {
975
1006
  const localAgent = path6.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
976
- if (existsSync6(localAgent)) return localAgent;
1007
+ if (existsSync7(localAgent)) return localAgent;
977
1008
  }
978
1009
  return "agent";
979
1010
  }
@@ -1042,8 +1073,11 @@ function resolveWorkerProvider(name) {
1042
1073
 
1043
1074
  // src/supervisor.ts
1044
1075
  function spawnWorkerProcess(run, opts) {
1045
- const name = safeSlug(opts.name);
1046
- if (!opts.name) throw new Error("worker name is required");
1076
+ const rawName = typeof opts.name === "string" ? opts.name.trim() : "";
1077
+ if (!rawName || rawName === "undefined" || rawName === "null") {
1078
+ throw new Error(`worker name is required and must be a real identifier (got: ${JSON.stringify(opts.name)})`);
1079
+ }
1080
+ const name = safeSlug(rawName);
1047
1081
  if (run.workers?.[name]) throw new Error(`worker already exists in run ${run.id}: ${name}`);
1048
1082
  if (!opts.task) throw new Error(`missing task text for worker ${name}`);
1049
1083
  const { worktreesDir } = getPaths();
@@ -1051,7 +1085,7 @@ function spawnWorkerProcess(run, opts) {
1051
1085
  mkdirSync3(workerDir, { recursive: true });
1052
1086
  const worktreePath = path7.join(worktreesDir, run.id, name);
1053
1087
  const branch = opts.branch || `agent/${run.id}/${name}`;
1054
- if (existsSync7(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
1088
+ if (existsSync8(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
1055
1089
  git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
1056
1090
  git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
1057
1091
  const stdoutPath = path7.join(workerDir, "stdout.jsonl");
@@ -1113,6 +1147,11 @@ function spawnWorkerProcess(run, opts) {
1113
1147
  }
1114
1148
  function startWorker(args) {
1115
1149
  const run = loadRun(String(args.run));
1150
+ const name = typeof args.name === "string" ? args.name.trim() : "";
1151
+ if (!name) {
1152
+ console.error("worker start failed: --name is required");
1153
+ process.exit(1);
1154
+ }
1116
1155
  const task = args.task ? String(args.task) : readMaybeFile(args.taskFile ? String(args.taskFile) : void 0);
1117
1156
  if (!task) {
1118
1157
  console.error("missing --task or --task-file");
@@ -1120,7 +1159,7 @@ function startWorker(args) {
1120
1159
  }
1121
1160
  try {
1122
1161
  const worker = spawnWorkerProcess(run, {
1123
- name: String(args.name),
1162
+ name,
1124
1163
  task,
1125
1164
  ownedPaths: args.owned ? String(args.owned).split(",").map((s) => s.trim()).filter(Boolean) : [],
1126
1165
  model: args.model ? String(args.model) : void 0,
@@ -1347,14 +1386,14 @@ function validateTailLines(lines) {
1347
1386
  }
1348
1387
 
1349
1388
  // src/worktree.ts
1350
- import { existsSync as existsSync8, mkdirSync as mkdirSync4 } from "node:fs";
1389
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4 } from "node:fs";
1351
1390
  import path9 from "node:path";
1352
1391
  function createRun(args) {
1353
1392
  const repo = validateRepo(required(String(args.repo || ""), "--repo"));
1354
1393
  ensureGitRepo(repo);
1355
1394
  const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
1356
1395
  const dir = runDirectory(id);
1357
- if (existsSync8(dir)) failExists(`run already exists: ${id}`);
1396
+ if (existsSync9(dir)) failExists(`run already exists: ${id}`);
1358
1397
  mkdirSync4(dir, { recursive: true });
1359
1398
  const base = String(args.base || "origin/main");
1360
1399
  const baseCommit = git(repo, ["rev-parse", base]).trim();
@@ -1373,7 +1412,7 @@ function createRun(args) {
1373
1412
  }
1374
1413
  function listRuns() {
1375
1414
  const { runsDir } = getPaths();
1376
- const rows = listRunIds(runsDir).map((id) => readJson(path9.join(runDirectory(id), "run.json"), null)).filter(Boolean).map((run) => ({
1415
+ const rows = listRunIds(runsDir).map((id) => readJson(path9.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
1377
1416
  id: run.id,
1378
1417
  name: run.name,
1379
1418
  status: run.status,
@@ -1401,7 +1440,7 @@ async function sweepRun(args) {
1401
1440
  for (const name of Object.keys(run.workers || {})) {
1402
1441
  const worker = readJson(
1403
1442
  path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1404
- null
1443
+ void 0
1405
1444
  );
1406
1445
  if (!worker || !worker.dispatched || !worker.taskId) continue;
1407
1446
  const status = computeWorkerStatus(worker);
@@ -1541,7 +1580,7 @@ function runStatus(args) {
1541
1580
  const workers = names.map((name) => {
1542
1581
  const worker = readJson(
1543
1582
  path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1544
- null
1583
+ void 0
1545
1584
  );
1546
1585
  if (!worker) {
1547
1586
  return { worker: name, status: "missing", attention: "needs_attention", attentionReason: "worker.json not found" };
@@ -1611,10 +1650,48 @@ import { mkdirSync as mkdirSync5, realpathSync } from "node:fs";
1611
1650
  import { fileURLToPath } from "node:url";
1612
1651
 
1613
1652
  // src/pipeline-tick.ts
1614
- import path13 from "node:path";
1653
+ import path14 from "node:path";
1615
1654
 
1616
- // src/plan-progress-daemon-sync.ts
1655
+ // src/finalize.ts
1617
1656
  import path12 from "node:path";
1657
+ var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
1658
+ function terminalStatusFor(run) {
1659
+ const names = Object.keys(run.workers || {});
1660
+ if (names.length === 0) return "failed";
1661
+ let anyAlive = false;
1662
+ let anyResult = false;
1663
+ for (const name of names) {
1664
+ const worker = readJson(
1665
+ path12.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1666
+ void 0
1667
+ );
1668
+ if (!worker) continue;
1669
+ const status = computeWorkerStatus(worker);
1670
+ if (status.alive && !status.finalResult) {
1671
+ anyAlive = true;
1672
+ break;
1673
+ }
1674
+ if (status.finalResult) anyResult = true;
1675
+ }
1676
+ if (anyAlive) return null;
1677
+ return anyResult ? "completed" : "failed";
1678
+ }
1679
+ function finalizeStaleRuns() {
1680
+ const finalized = [];
1681
+ for (const run of listRunRecords()) {
1682
+ if (!ACTIVE_RUN_STATUSES.has(run.status)) continue;
1683
+ const next = terminalStatusFor(run);
1684
+ if (!next || next === run.status) continue;
1685
+ const from = run.status;
1686
+ run.status = next;
1687
+ saveRun(run);
1688
+ finalized.push({ runId: run.id, from, to: next });
1689
+ }
1690
+ return finalized;
1691
+ }
1692
+
1693
+ // src/plan-progress-daemon-sync.ts
1694
+ import path13 from "node:path";
1618
1695
 
1619
1696
  // src/plan-progress-sync.ts
1620
1697
  async function syncPlanProgress(args) {
@@ -1638,8 +1715,8 @@ async function syncActiveWorkerPlanProgress(runId, args) {
1638
1715
  const outcomes = [];
1639
1716
  for (const name of Object.keys(run.workers || {})) {
1640
1717
  const worker = readJson(
1641
- path12.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1642
- null
1718
+ path13.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1719
+ void 0
1643
1720
  );
1644
1721
  if (!worker?.dispatched || !worker.taskId) continue;
1645
1722
  const status = computeWorkerStatus(worker);
@@ -1692,13 +1769,12 @@ async function completeFinishedWorkers(runId, args) {
1692
1769
  const outcomes = [];
1693
1770
  for (const name of Object.keys(run.workers || {})) {
1694
1771
  const worker = readJson(
1695
- path13.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1696
- null
1772
+ path14.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1773
+ void 0
1697
1774
  );
1698
1775
  if (!worker?.dispatched || !worker.taskId) continue;
1699
1776
  const status = computeWorkerStatus(worker);
1700
1777
  if (!isFinishedWorkerStatus(status)) continue;
1701
- if (!status.finalResult) continue;
1702
1778
  const result = await tryCompleteWorker({
1703
1779
  run: runId,
1704
1780
  name,
@@ -1726,6 +1802,7 @@ async function runPipelineTick(args) {
1726
1802
  const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
1727
1803
  const execute = args.execute !== false && args.execute !== "false";
1728
1804
  runStatus({ run: runId });
1805
+ const finalizedStaleRuns = finalizeStaleRuns();
1729
1806
  const completedWorkers = await completeFinishedWorkers(runId, args);
1730
1807
  const planProgressSync = await syncActiveWorkerPlanProgress(runId, args);
1731
1808
  const workspacePrefs = await fetchWorkspaceRuntimePreferences(agentOsId, args);
@@ -1767,6 +1844,7 @@ async function runPipelineTick(args) {
1767
1844
  execute,
1768
1845
  resourceGate,
1769
1846
  completedWorkers,
1847
+ finalizedStaleRuns,
1770
1848
  planProgressSync,
1771
1849
  operatorTick,
1772
1850
  sweep,