@kynver-app/runtime 0.1.38 → 0.1.42

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
@@ -35,16 +35,43 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
35
35
  }
36
36
 
37
37
  // src/dispatch.ts
38
- import path16 from "node:path";
38
+ import path17 from "node:path";
39
39
 
40
40
  // src/config.ts
41
41
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
42
+ import { homedir as homedir2 } from "node:os";
43
+ import path3 from "node:path";
44
+
45
+ // src/path-values.ts
42
46
  import { homedir } from "node:os";
43
- import path2 from "node:path";
47
+ import path from "node:path";
48
+ function expandHomePath(value) {
49
+ if (value === "~") return homedir();
50
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
51
+ return path.join(homedir(), value.slice(2));
52
+ }
53
+ return value;
54
+ }
55
+ function resolveUserPath(value) {
56
+ return path.resolve(expandHomePath(value));
57
+ }
58
+ function redactHomePath(value) {
59
+ const expanded = expandHomePath(value);
60
+ const resolved = path.resolve(expanded);
61
+ const home = path.resolve(homedir());
62
+ if (resolved === home) return "~";
63
+ if (resolved.startsWith(`${home}${path.sep}`)) {
64
+ return `~/${path.relative(home, resolved).split(path.sep).join("/")}`;
65
+ }
66
+ return resolved.replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^\/Users\/[^/]+(?=\/|$)/, "~");
67
+ }
68
+ function displayUserPath(value) {
69
+ return redactHomePath(value);
70
+ }
44
71
 
45
72
  // src/util.ts
46
73
  import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync } from "node:fs";
47
- import path from "node:path";
74
+ import path2 from "node:path";
48
75
  function fail(message) {
49
76
  console.error(message);
50
77
  process.exit(1);
@@ -73,7 +100,7 @@ function readJson(file, fallback) {
73
100
  }
74
101
  }
75
102
  function writeJson(file, value) {
76
- mkdirSync(path.dirname(file), { recursive: true });
103
+ mkdirSync(path2.dirname(file), { recursive: true });
77
104
  writeFileSync(file, `${JSON.stringify(value, null, 2)}
78
105
  `);
79
106
  }
@@ -109,7 +136,7 @@ function tailFile(file, lines) {
109
136
  return data.split("\n").slice(-lines).join("\n");
110
137
  }
111
138
  function readMaybeFile(file) {
112
- return file ? readFileSync2(path.resolve(file), "utf8") : "";
139
+ return file ? readFileSync2(path2.resolve(file), "utf8") : "";
113
140
  }
114
141
  function listRunIds(runsDir) {
115
142
  if (!existsSync2(runsDir)) return [];
@@ -152,9 +179,9 @@ function secsAgo(ms) {
152
179
  }
153
180
 
154
181
  // src/config.ts
155
- var CONFIG_DIR = path2.join(homedir(), ".kynver");
156
- var CONFIG_FILE = path2.join(CONFIG_DIR, "config.json");
157
- var CREDENTIALS_FILE = path2.join(CONFIG_DIR, "credentials");
182
+ var CONFIG_DIR = path3.join(homedir2(), ".kynver");
183
+ var CONFIG_FILE = path3.join(CONFIG_DIR, "config.json");
184
+ var CREDENTIALS_FILE = path3.join(CONFIG_DIR, "credentials");
158
185
  function loadUserConfig() {
159
186
  if (!existsSync3(CONFIG_FILE)) return {};
160
187
  try {
@@ -165,9 +192,33 @@ function loadUserConfig() {
165
192
  }
166
193
  function saveUserConfig(config) {
167
194
  mkdirSync2(CONFIG_DIR, { recursive: true });
168
- writeFileSync2(CONFIG_FILE, `${JSON.stringify(config, null, 2)}
195
+ writeFileSync2(CONFIG_FILE, `${JSON.stringify(normalizeConfigPaths(config), null, 2)}
169
196
  `, { mode: 384 });
170
197
  }
198
+ function normalizeConfigPaths(config) {
199
+ return {
200
+ ...config,
201
+ ...config.harnessRoot?.trim() ? { harnessRoot: redactHomePath(config.harnessRoot.trim()) } : {},
202
+ ...config.defaultRepo?.trim() ? { defaultRepo: redactHomePath(config.defaultRepo.trim()) } : {}
203
+ };
204
+ }
205
+ function presentUserConfig(config) {
206
+ return normalizeConfigPaths(config);
207
+ }
208
+ function inferSetupFields(existing, args) {
209
+ const creds = loadCredentialsFile();
210
+ const apiBaseUrl = (typeof args.apiBaseUrl === "string" ? args.apiBaseUrl : void 0) || existing.apiBaseUrl?.trim() || process.env.KYNVER_API_URL?.trim() || process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim();
211
+ const agentOsId = (typeof args.agentOsId === "string" ? args.agentOsId : void 0) || existing.agentOsId?.trim() || process.env.KYNVER_AGENT_OS_ID?.trim() || (creds.runnerToken?.trim().startsWith("krc1.") ? creds.runnerTokenAgentOsId?.trim() : void 0);
212
+ const defaultRepo = (typeof args.repo === "string" ? args.repo : void 0) || existing.defaultRepo?.trim() || process.env.KYNVER_DEFAULT_REPO?.trim() || process.env.KYNVER_HARNESS_REPO?.trim();
213
+ const harnessRoot = (typeof args.harnessRoot === "string" ? args.harnessRoot : void 0) || existing.harnessRoot?.trim() || process.env.KYNVER_HARNESS_ROOT?.trim() || process.env.OPUS_HARNESS_ROOT?.trim();
214
+ return {
215
+ ...apiBaseUrl ? { apiBaseUrl: trimTrailingSlash(apiBaseUrl) } : {},
216
+ ...agentOsId ? { agentOsId } : {},
217
+ ...defaultRepo ? { defaultRepo } : {},
218
+ ...harnessRoot ? { harnessRoot } : {},
219
+ ...typeof args.agentOsSlug === "string" ? { agentOsSlug: args.agentOsSlug } : existing.agentOsSlug ? { agentOsSlug: existing.agentOsSlug } : {}
220
+ };
221
+ }
171
222
  function loadCredentialsFile() {
172
223
  if (!existsSync3(CREDENTIALS_FILE)) return {};
173
224
  try {
@@ -320,7 +371,7 @@ async function mintRunnerCredential(args) {
320
371
  {
321
372
  ok: true,
322
373
  agentOsId,
323
- credentialsPath: CREDENTIALS_FILE,
374
+ credentialsPath: displayUserPath(CREDENTIALS_FILE),
324
375
  tokenPrefix: `${token.slice(0, 12)}\u2026`,
325
376
  note: "Scoped runner token saved; callbacks use X-Kynver-Runner-Token."
326
377
  },
@@ -355,16 +406,12 @@ function parseArgs(argv) {
355
406
  async function runSetup(args) {
356
407
  const existing = loadUserConfig();
357
408
  const maxWorkersRaw = typeof args.maxWorkers === "string" ? args.maxWorkers : typeof args.maxConcurrentWorkers === "string" ? args.maxConcurrentWorkers : void 0;
358
- const config = {
409
+ const config = normalizeConfigPaths({
359
410
  ...existing,
360
- ...typeof args.apiBaseUrl === "string" ? { apiBaseUrl: args.apiBaseUrl } : {},
361
- ...typeof args.agentOsSlug === "string" ? { agentOsSlug: args.agentOsSlug } : {},
362
- ...typeof args.agentOsId === "string" ? { agentOsId: args.agentOsId } : {},
363
- ...typeof args.repo === "string" ? { defaultRepo: args.repo } : {},
364
- ...typeof args.harnessRoot === "string" ? { harnessRoot: args.harnessRoot } : {},
411
+ ...inferSetupFields(existing, args),
365
412
  ...maxWorkersRaw ? { maxConcurrentWorkers: Math.max(1, Math.floor(Number(maxWorkersRaw))) } : {},
366
413
  workerProvider: typeof args.provider === "string" ? args.provider : existing.workerProvider || "claude"
367
- };
414
+ });
368
415
  saveUserConfig(config);
369
416
  let runnerCredentialNote;
370
417
  const apiKey = loadApiKey();
@@ -385,8 +432,8 @@ async function runSetup(args) {
385
432
  JSON.stringify(
386
433
  {
387
434
  ok: true,
388
- configPath: CONFIG_FILE,
389
- config,
435
+ configPath: displayUserPath(CONFIG_FILE),
436
+ config: presentUserConfig(config),
390
437
  note: runnerCredentialNote ?? "Set worker limit once with --max-workers N (or omit to auto-size from RAM). Run `kynver login` + `kynver runner credential` for scoped callbacks."
391
438
  },
392
439
  null,
@@ -398,7 +445,7 @@ async function runLogin(args) {
398
445
  const apiKey = typeof args.apiKey === "string" ? args.apiKey : process.env.KYNVER_API_KEY;
399
446
  if (!apiKey) failConfig("kynver login requires --api-key or KYNVER_API_KEY");
400
447
  saveApiKey(apiKey);
401
- console.log(JSON.stringify({ ok: true, credentialsPath: CREDENTIALS_FILE }, null, 2));
448
+ console.log(JSON.stringify({ ok: true, credentialsPath: displayUserPath(CREDENTIALS_FILE) }, null, 2));
402
449
  }
403
450
 
404
451
  // src/callback-headers.ts
@@ -461,12 +508,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
461
508
  var DEFAULT_MAX_USED_PERCENT = 80;
462
509
  var DEFAULT_HARD_MAX_USED_PERCENT = 90;
463
510
  function observeRunnerDiskGate(input = {}) {
464
- const path32 = input.diskPath?.trim() || "/";
511
+ const path35 = input.diskPath?.trim() || "/";
465
512
  const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
466
513
  const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
467
514
  const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
468
515
  const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
469
- const stats = statfsSync(path32);
516
+ const stats = statfsSync(path35);
470
517
  const freeBytes = Number(stats.bavail) * Number(stats.bsize);
471
518
  const totalBytes = Number(stats.blocks) * Number(stats.bsize);
472
519
  const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
@@ -486,7 +533,7 @@ function observeRunnerDiskGate(input = {}) {
486
533
  }
487
534
  return {
488
535
  ok,
489
- path: path32,
536
+ path: path35,
490
537
  freeBytes,
491
538
  totalBytes,
492
539
  usedPercent,
@@ -501,21 +548,23 @@ function observeRunnerDiskGate(input = {}) {
501
548
  // src/resource-gate.ts
502
549
  import { readFileSync as readFileSync6 } from "node:fs";
503
550
  import os from "node:os";
504
- import path5 from "node:path";
551
+ import path6 from "node:path";
505
552
 
506
553
  // src/run-store.ts
507
554
  import { existsSync as existsSync5, readdirSync as readdirSync2 } from "node:fs";
508
- import path4 from "node:path";
555
+ import path5 from "node:path";
509
556
 
510
557
  // src/paths.ts
511
558
  import { existsSync as existsSync4 } from "node:fs";
512
- import { homedir as homedir2 } from "node:os";
513
- import path3 from "node:path";
514
- var LEGACY_ROOT = path3.join(homedir2(), ".openclaw", "harness");
559
+ import { homedir as homedir3 } from "node:os";
560
+ import path4 from "node:path";
561
+ var LEGACY_ROOT = path4.join(homedir3(), ".openclaw", "harness");
515
562
  function resolveHarnessRoot() {
516
563
  const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
517
- if (env) return path3.resolve(env);
518
- const kynverRoot = path3.join(homedir2(), ".kynver", "harness");
564
+ if (env) return resolveUserPath(env);
565
+ const configured = loadUserConfig().harnessRoot?.trim();
566
+ if (configured) return resolveUserPath(configured);
567
+ const kynverRoot = path4.join(homedir3(), ".kynver", "harness");
519
568
  if (existsSync4(kynverRoot)) return kynverRoot;
520
569
  if (existsSync4(LEGACY_ROOT)) return LEGACY_ROOT;
521
570
  return kynverRoot;
@@ -524,12 +573,12 @@ function getHarnessPaths() {
524
573
  const harnessRoot = resolveHarnessRoot();
525
574
  return {
526
575
  harnessRoot,
527
- runsDir: path3.join(harnessRoot, "runs"),
528
- worktreesDir: path3.join(harnessRoot, "worktrees")
576
+ runsDir: path4.join(harnessRoot, "runs"),
577
+ worktreesDir: path4.join(harnessRoot, "worktrees")
529
578
  };
530
579
  }
531
580
  function runDir(runsDir, id) {
532
- return path3.join(runsDir, safeSlug(id));
581
+ return path4.join(runsDir, safeSlug(id));
533
582
  }
534
583
 
535
584
  // src/run-store.ts
@@ -538,7 +587,7 @@ function getPaths() {
538
587
  }
539
588
  function loadRun(id) {
540
589
  const { runsDir } = getPaths();
541
- return readJson(path4.join(runDir(runsDir, safeSlug(id)), "run.json"));
590
+ return readJson(path5.join(runDir(runsDir, safeSlug(id)), "run.json"));
542
591
  }
543
592
  function listRunRecords() {
544
593
  const { runsDir } = getPaths();
@@ -547,7 +596,7 @@ function listRunRecords() {
547
596
  for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
548
597
  if (!entry.isDirectory()) continue;
549
598
  const run = readJson(
550
- path4.join(runsDir, entry.name, "run.json"),
599
+ path5.join(runsDir, entry.name, "run.json"),
551
600
  void 0
552
601
  );
553
602
  if (run?.id) runs.push(run);
@@ -557,16 +606,16 @@ function listRunRecords() {
557
606
  function loadWorker(runId, name) {
558
607
  const { runsDir } = getPaths();
559
608
  return readJson(
560
- path4.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
609
+ path5.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
561
610
  );
562
611
  }
563
612
  function saveRun(run) {
564
613
  const { runsDir } = getPaths();
565
- writeJson(path4.join(runDir(runsDir, run.id), "run.json"), run);
614
+ writeJson(path5.join(runDir(runsDir, run.id), "run.json"), run);
566
615
  }
567
616
  function saveWorker(runId, worker) {
568
617
  const { runsDir } = getPaths();
569
- writeJson(path4.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
618
+ writeJson(path5.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
570
619
  }
571
620
  function runDirectory(id) {
572
621
  const { runsDir } = getPaths();
@@ -1322,7 +1371,7 @@ function countActiveWorkersForRun(run) {
1322
1371
  let active = 0;
1323
1372
  for (const name of Object.keys(run.workers || {})) {
1324
1373
  const worker = readJson(
1325
- path5.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1374
+ path6.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1326
1375
  void 0
1327
1376
  );
1328
1377
  if (!worker || !isActiveHarnessWorker(worker)) continue;
@@ -1530,6 +1579,7 @@ var claudeProvider = {
1530
1579
  const model = preflight.model;
1531
1580
  const stdoutFd = openSync(opts.stdoutPath, "a");
1532
1581
  const stderrFd = openSync(opts.stderrPath, "a");
1582
+ const stdio = ["ignore", stdoutFd, stderrFd];
1533
1583
  const child = spawn(
1534
1584
  "claude",
1535
1585
  [
@@ -1547,7 +1597,7 @@ var claudeProvider = {
1547
1597
  hiddenSpawnOptions({
1548
1598
  cwd: opts.worktreePath,
1549
1599
  detached: true,
1550
- stdio: ["ignore", stdoutFd, stderrFd],
1600
+ stdio,
1551
1601
  env: scrubClaudeEnv(process.env)
1552
1602
  })
1553
1603
  );
@@ -1706,10 +1756,10 @@ function readHarnessRetryLimits() {
1706
1756
  }
1707
1757
 
1708
1758
  // src/lease-renewal.ts
1709
- import path6 from "node:path";
1759
+ import path7 from "node:path";
1710
1760
  function workerRecord(runId, name) {
1711
1761
  return readJson(
1712
- path6.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
1762
+ path7.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
1713
1763
  void 0
1714
1764
  );
1715
1765
  }
@@ -1776,7 +1826,7 @@ function hasLiveWorkerForTask(runId, taskId) {
1776
1826
 
1777
1827
  // src/supervisor.ts
1778
1828
  import { existsSync as existsSync11, mkdirSync as mkdirSync3 } from "node:fs";
1779
- import path12 from "node:path";
1829
+ import path13 from "node:path";
1780
1830
 
1781
1831
  // src/prompt.ts
1782
1832
  function buildPrompt(input) {
@@ -1795,6 +1845,16 @@ function buildPrompt(input) {
1795
1845
  "- After implementation: wait for report_reviewer then deep_reviewer confirmation (via MCP/session agents) before follow-up rows close.",
1796
1846
  input.planId ? `Active planId: ${input.planId}${input.taskId ? ` \xB7 taskId: ${input.taskId}` : ""}` : "No planId on this worker \u2014 still emit progress when you touch plan-scoped work."
1797
1847
  ];
1848
+ const mergeGateLines = compact ? [
1849
+ "Merge-gate cost control: run `node scripts/verify-pr-local.mjs --emit-json` and `node scripts/collect-pr-vercel-evidence.mjs --pr <url> --emit-json` (trusts GitHub Vercel status for dashboard target_url; never passes dashboard URLs to `vercel inspect`) before any GitHub Actions run; request merge-gate only via POST pr-merge-gate/request-run (one Actions run per PR head unless human approves extra)."
1850
+ ] : [
1851
+ "GitHub Actions merge-gate cost control (Kynver/Hermes PRs):",
1852
+ "- Prefer local cached package verification (`node scripts/verify-pr-local.mjs --emit-json`) and Vercel preview evidence before GitHub Actions.",
1853
+ "- Do not push empty commits to re-trigger CI. One budgeted merge-gate Actions run per PR candidate (head SHA) unless a human approves extra.",
1854
+ "- Record evidence: POST `/api/agent-os/by-id/<agentOsId>/pr-merge-gate/refresh` with prUrl + local/vercel payloads.",
1855
+ "- Request the final Actions run only when local + Vercel are green: POST `.../pr-merge-gate/request-run` (applies `merge-gate` label).",
1856
+ "- Empty failed Actions jobs (no runner/steps/logs) are infra/quota \u2014 do not enter repair loops; escalate to operator."
1857
+ ];
1798
1858
  const planArtifactLines = compact ? [
1799
1859
  "Plan artifacts: when authoring/revising docs/superpowers/plans/, open a GitHub PR early and iterate from that PR branch; do not leave the canonical plan only in the harness worktree."
1800
1860
  ] : [
@@ -1816,10 +1876,14 @@ function buildPrompt(input) {
1816
1876
  "Completion handoff (required): before you stop, ensure the harness records a final result \u2014 summarize outcome in your last message and append a heartbeat line with phase `complete`. If you leave uncommitted changes or committed work without a PR, the orchestrator blocks completion until a GitHub PR exists (or you discard/commit cleanly). Exiting with only dirty files and no PR routes to salvage review, not production review.",
1817
1877
  "PR-ready handoff: for substantial implementation work, commit, push, and open a GitHub PR (draft OK) on your branch before finishing \u2014 or rely on the harness to run `gh pr create` at completion when `gh` is authenticated.",
1818
1878
  "Worker resource guard: do not run full monorepo verification (`npm run typecheck`, `npm run build`, or equivalent) from this worker lane unless an operator explicitly requests it. Use targeted checks for touched paths and rely on CI/operator lanes for heavy gates.",
1879
+ "npm publish boundary: do not run `npm publish`, do not republish `@kynver-app/*` packages, and do not block on an operator to publish. When you need newer runtime code than npm, use this repo checkout (`npm run kynver:build`, `npm run kynver`) and record evidence: packages/kynver-runtime/package.json version + git ref in your completion report.",
1819
1880
  "If verification fails (including OOM), append a heartbeat line immediately with the last command, failure reason, dirty-file status, commit/PR handoff state, and next action so recovery does not require log spelunking.",
1881
+ "Landing-wrapper cleanup on a git ref: use `node scripts/agent-os-land-pr-cleanup-verify.mjs --ref origin/main` (JSON, exit 0). Do not use `git show <ref>:scripts/agent-os-land-pr.mjs | rg \u2026` \u2014 ripgrep exit 1 when markers are absent is reported as a failed command in Telegram/status tooling.",
1820
1882
  "",
1821
1883
  ...progressLines,
1822
1884
  "",
1885
+ ...mergeGateLines,
1886
+ "",
1823
1887
  ...planArtifactLines,
1824
1888
  "",
1825
1889
  ...input.personaMarkdown?.trim() ? [input.personaMarkdown.trim(), ""] : [],
@@ -1832,11 +1896,11 @@ function buildPrompt(input) {
1832
1896
  // src/providers/cursor.ts
1833
1897
  import { closeSync as closeSync2, existsSync as existsSync9, openSync as openSync2 } from "node:fs";
1834
1898
  import { spawn as spawn2 } from "node:child_process";
1835
- import path8 from "node:path";
1899
+ import path9 from "node:path";
1836
1900
 
1837
1901
  // src/providers/cursor-windows.ts
1838
1902
  import { existsSync as existsSync8, readdirSync as readdirSync3 } from "node:fs";
1839
- import path7 from "node:path";
1903
+ import path8 from "node:path";
1840
1904
  var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
1841
1905
  function parseCursorVersionSortKey(versionName) {
1842
1906
  const datePart = versionName.split("-")[0];
@@ -1847,7 +1911,7 @@ function parseCursorVersionSortKey(versionName) {
1847
1911
  return Number(`${year}${month.padStart(2, "0")}${day.padStart(2, "0")}`);
1848
1912
  }
1849
1913
  function pickLatestCursorVersionDir(agentRoot) {
1850
- const versionsRoot = path7.join(agentRoot, "versions");
1914
+ const versionsRoot = path8.join(agentRoot, "versions");
1851
1915
  if (!existsSync8(versionsRoot)) return null;
1852
1916
  let bestDir = null;
1853
1917
  let bestKey = -1;
@@ -1856,21 +1920,21 @@ function pickLatestCursorVersionDir(agentRoot) {
1856
1920
  const key = parseCursorVersionSortKey(entry.name);
1857
1921
  if (key == null || key <= bestKey) continue;
1858
1922
  bestKey = key;
1859
- bestDir = path7.join(versionsRoot, entry.name);
1923
+ bestDir = path8.join(versionsRoot, entry.name);
1860
1924
  }
1861
1925
  return bestDir;
1862
1926
  }
1863
1927
  function resolveWindowsCursorBundled(agentRoot) {
1864
- const root = agentRoot?.trim() || path7.join(process.env.LOCALAPPDATA || "", "cursor-agent");
1865
- const directNode = path7.join(root, "node.exe");
1866
- const directIndex = path7.join(root, "index.js");
1928
+ const root = agentRoot?.trim() || path8.join(process.env.LOCALAPPDATA || "", "cursor-agent");
1929
+ const directNode = path8.join(root, "node.exe");
1930
+ const directIndex = path8.join(root, "index.js");
1867
1931
  if (existsSync8(directNode) && existsSync8(directIndex)) {
1868
1932
  return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
1869
1933
  }
1870
1934
  const versionDir = pickLatestCursorVersionDir(root);
1871
1935
  if (!versionDir) return null;
1872
- const nodeExe = path7.join(versionDir, "node.exe");
1873
- const indexJs = path7.join(versionDir, "index.js");
1936
+ const nodeExe = path8.join(versionDir, "node.exe");
1937
+ const indexJs = path8.join(versionDir, "index.js");
1874
1938
  if (!existsSync8(nodeExe) || !existsSync8(indexJs)) return null;
1875
1939
  return { nodeExe, indexJs, versionDir };
1876
1940
  }
@@ -1889,13 +1953,13 @@ function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
1889
1953
  function resolveCursorSpawn(agentBin) {
1890
1954
  if (process.platform === "win32") {
1891
1955
  const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
1892
- const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync9(path8.join(path8.dirname(agentBin), "index.js"));
1956
+ const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync9(path9.join(path9.dirname(agentBin), "index.js"));
1893
1957
  const isDefaultShim = agentBin === "agent";
1894
1958
  if (isCursorWrapper || isBundledNode || isDefaultShim) {
1895
- const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path8.dirname(agentBin)) : isBundledNode ? {
1959
+ const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path9.dirname(agentBin)) : isBundledNode ? {
1896
1960
  nodeExe: agentBin,
1897
- indexJs: path8.join(path8.dirname(agentBin), "index.js"),
1898
- versionDir: path8.dirname(agentBin)
1961
+ indexJs: path9.join(path9.dirname(agentBin), "index.js"),
1962
+ versionDir: path9.dirname(agentBin)
1899
1963
  } : resolveWindowsCursorBundled();
1900
1964
  if (bundled) {
1901
1965
  return bundledSpawnTarget(bundled.nodeExe, bundled.indexJs, bundled.versionDir);
@@ -1915,7 +1979,7 @@ function resolveAgentBin() {
1915
1979
  process.env.KYNVER_CURSOR_AGENT_ROOT?.trim() || void 0
1916
1980
  );
1917
1981
  if (bundled) return bundled.nodeExe;
1918
- const localAgent = path8.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
1982
+ const localAgent = path9.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
1919
1983
  if (existsSync9(localAgent)) return localAgent;
1920
1984
  }
1921
1985
  return "agent";
@@ -1925,7 +1989,7 @@ function cursorWorkerEnv(agentBin, spawnTarget) {
1925
1989
  ...process.env,
1926
1990
  CI: "1",
1927
1991
  NO_COLOR: "1",
1928
- ...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path8.basename(agentBin) || "agent.cmd" } : {}
1992
+ ...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path9.basename(agentBin) || "agent.cmd" } : {}
1929
1993
  };
1930
1994
  }
1931
1995
  var cursorProvider = {
@@ -1942,6 +2006,7 @@ var cursorProvider = {
1942
2006
  const model = preflight.model;
1943
2007
  const stdoutFd = openSync2(opts.stdoutPath, "a");
1944
2008
  const stderrFd = openSync2(opts.stderrPath, "a");
2009
+ const stdio = ["ignore", stdoutFd, stderrFd];
1945
2010
  const agentBin = resolveAgentBin();
1946
2011
  const spawnTarget = resolveCursorSpawn(agentBin);
1947
2012
  const child = spawn2(
@@ -1964,7 +2029,7 @@ var cursorProvider = {
1964
2029
  cwd: opts.worktreePath,
1965
2030
  detached: spawnTarget.detached,
1966
2031
  shell: spawnTarget.shell,
1967
- stdio: ["ignore", stdoutFd, stderrFd],
2032
+ stdio,
1968
2033
  env: cursorWorkerEnv(agentBin, spawnTarget)
1969
2034
  })
1970
2035
  );
@@ -1999,11 +2064,24 @@ function resolveWorkerProvider(name) {
1999
2064
  // src/auto-complete.ts
2000
2065
  import { spawn as spawn3 } from "node:child_process";
2001
2066
  import { existsSync as existsSync10, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
2002
- import path11 from "node:path";
2067
+ import path12 from "node:path";
2003
2068
  import { fileURLToPath as fileURLToPath2 } from "node:url";
2004
2069
 
2070
+ // src/completion-ack.ts
2071
+ function hasCompletionAck(worker) {
2072
+ return Boolean(worker.completionReportedAt?.trim());
2073
+ }
2074
+ function persistCompletionAck(worker, runId, fields) {
2075
+ worker.completionReportedAt = fields.completionReportedAt;
2076
+ worker.completionOutcome = fields.completionOutcome;
2077
+ if (fields.completionResponse !== void 0) {
2078
+ worker.completionResponse = fields.completionResponse;
2079
+ }
2080
+ saveWorker(runId, worker);
2081
+ }
2082
+
2005
2083
  // src/worker-ops.ts
2006
- import path10 from "node:path";
2084
+ import path11 from "node:path";
2007
2085
 
2008
2086
  // src/pr-handoff/pr-handoff-assess.ts
2009
2087
  var REVIEW_LANE_RULE = /^(lane:)?(review|deep_review|planning|landing)(:|$)/i;
@@ -2339,21 +2417,8 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
2339
2417
  };
2340
2418
  }
2341
2419
 
2342
- // src/completion-ack.ts
2343
- function hasCompletionAck(worker) {
2344
- return Boolean(worker.completionReportedAt?.trim());
2345
- }
2346
- function persistCompletionAck(worker, runId, fields) {
2347
- worker.completionReportedAt = fields.completionReportedAt;
2348
- worker.completionOutcome = fields.completionOutcome;
2349
- if (fields.completionResponse !== void 0) {
2350
- worker.completionResponse = fields.completionResponse;
2351
- }
2352
- saveWorker(runId, worker);
2353
- }
2354
-
2355
2420
  // src/worker-lifecycle.ts
2356
- import path9 from "node:path";
2421
+ import path10 from "node:path";
2357
2422
  var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
2358
2423
  "awaiting_review",
2359
2424
  "blocked",
@@ -2409,7 +2474,7 @@ function syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick) {
2409
2474
  const synced = [];
2410
2475
  for (const name of Object.keys(run.workers || {})) {
2411
2476
  const worker = readJson(
2412
- path9.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2477
+ path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2413
2478
  void 0
2414
2479
  );
2415
2480
  if (!worker?.taskId || isCompletionAcknowledged(worker)) continue;
@@ -2646,7 +2711,7 @@ function workerStatus(args) {
2646
2711
  const worker = loadWorker(String(args.run), String(args.name));
2647
2712
  const run = loadRun(worker.runId);
2648
2713
  const status = computeWorkerStatus(worker, workerStatusOptions(run));
2649
- writeJson(path10.join(worker.workerDir, "last-status.json"), status);
2714
+ writeJson(path11.join(worker.workerDir, "last-status.json"), status);
2650
2715
  console.log(JSON.stringify(status, null, 2));
2651
2716
  }
2652
2717
  function buildRunBoard(runId) {
@@ -2654,7 +2719,7 @@ function buildRunBoard(runId) {
2654
2719
  const names = Object.keys(run.workers || {});
2655
2720
  const workers = names.map((name) => {
2656
2721
  const worker = readJson(
2657
- path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2722
+ path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2658
2723
  void 0
2659
2724
  );
2660
2725
  if (!worker) {
@@ -2682,16 +2747,17 @@ function buildRunBoard(runId) {
2682
2747
  const completionRouteStatus = asString(completionResponse?.status);
2683
2748
  const completionWarnings = Array.isArray(completionResponse?.warnings) ? completionResponse.warnings.filter((w) => typeof w === "string" && w.trim().length > 0) : [];
2684
2749
  const prUrl = asString(completionTask?.prUrl) ?? asString(completionResponse?.prUrl);
2750
+ const completionReportedAt = asString(worker.completionReportedAt);
2685
2751
  const lifecycleStage = deriveLifecycleStage({
2686
2752
  finished: isFinishedWorkerStatus(status),
2687
2753
  completionBlocker,
2688
2754
  completionOutcome,
2689
- completionReportedAt: worker.completionReportedAt ?? null
2755
+ completionReportedAt
2690
2756
  });
2691
2757
  const nextAction = deriveNextAction({
2692
2758
  completionBlocker,
2693
2759
  completionOutcome,
2694
- completionReportedAt: worker.completionReportedAt ?? null,
2760
+ completionReportedAt,
2695
2761
  finished: isFinishedWorkerStatus(status)
2696
2762
  });
2697
2763
  const handoffState = deriveHandoffState({
@@ -2737,7 +2803,7 @@ function buildRunBoard(runId) {
2737
2803
  gitAncestry: status.gitAncestry,
2738
2804
  finalResult: status.finalResult,
2739
2805
  lifecycleStage,
2740
- completionReportedAt: worker.completionReportedAt ?? null,
2806
+ completionReportedAt,
2741
2807
  completionOutcome: worker.completionOutcome ?? null,
2742
2808
  completionRouteStatus,
2743
2809
  completionRouteOutcome: completionOutcome,
@@ -2764,7 +2830,7 @@ function buildRunBoard(runId) {
2764
2830
  needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
2765
2831
  workers
2766
2832
  };
2767
- writeJson(path10.join(runDirectory(run.id), "last-board.json"), board);
2833
+ writeJson(path11.join(runDirectory(run.id), "last-board.json"), board);
2768
2834
  return board;
2769
2835
  }
2770
2836
  async function publishHarnessBoardSnapshot(args, source) {
@@ -2848,7 +2914,7 @@ async function autoCompleteWorker(raw) {
2848
2914
  const maxTotalMs = args.maxTotalMs ?? DEFAULT_MAX_TOTAL_MS;
2849
2915
  const completeAttempts = args.completeAttempts ?? DEFAULT_COMPLETE_ATTEMPTS;
2850
2916
  const completeBackoffMs = args.completeBackoffMs ?? DEFAULT_COMPLETE_BACKOFF_MS;
2851
- const worker = loadWorker(args.run, args.name);
2917
+ let worker = loadWorker(args.run, args.name);
2852
2918
  if (!worker.agentOsId || !worker.taskId) {
2853
2919
  return {
2854
2920
  worker: worker.name,
@@ -2858,8 +2924,29 @@ async function autoCompleteWorker(raw) {
2858
2924
  reason: "worker has no agentOsId/taskId \u2014 nothing to attribute completion to"
2859
2925
  };
2860
2926
  }
2927
+ if (hasCompletionAck(worker)) {
2928
+ return {
2929
+ worker: worker.name,
2930
+ runId: worker.runId,
2931
+ outcome: "completed",
2932
+ httpStatus: 200,
2933
+ attempts: 0,
2934
+ reason: "completion-already-acknowledged"
2935
+ };
2936
+ }
2861
2937
  const startMs = Date.now();
2862
2938
  while (true) {
2939
+ worker = loadWorker(args.run, args.name);
2940
+ if (hasCompletionAck(worker)) {
2941
+ return {
2942
+ worker: worker.name,
2943
+ runId: worker.runId,
2944
+ outcome: "completed",
2945
+ httpStatus: 200,
2946
+ attempts: 0,
2947
+ reason: "completion-already-acknowledged"
2948
+ };
2949
+ }
2863
2950
  const status = computeWorkerStatus(worker);
2864
2951
  if (isFinishedWorkerStatus(status)) break;
2865
2952
  if (!isPidAlive(worker.pid)) break;
@@ -2923,20 +3010,21 @@ async function autoCompleteWorkerCli(raw) {
2923
3010
  const outcome = await autoCompleteWorker(raw);
2924
3011
  console.log(JSON.stringify(outcome, null, 2));
2925
3012
  if (outcome.outcome === "missing_link" || outcome.outcome === "timed_out") {
2926
- process.exitCode = 1;
3013
+ process.exit(1);
2927
3014
  }
3015
+ process.exit(0);
2928
3016
  } catch (error) {
2929
3017
  console.error(`worker auto-complete failed: ${error.message}`);
2930
- process.exitCode = 1;
3018
+ process.exit(1);
2931
3019
  }
2932
3020
  }
2933
3021
  function resolveDefaultCliPath() {
2934
- return path11.join(fileURLToPath2(new URL(".", import.meta.url)), "cli.js");
3022
+ return path12.join(fileURLToPath2(new URL(".", import.meta.url)), "cli.js");
2935
3023
  }
2936
3024
  function spawnCompletionSidecar(opts) {
2937
3025
  const cliPath = opts.cliPath ?? resolveDefaultCliPath();
2938
3026
  if (!existsSync10(cliPath)) return void 0;
2939
- const logPath = path11.join(opts.workerDir, "auto-complete.log");
3027
+ const logPath = path12.join(opts.workerDir, "auto-complete.log");
2940
3028
  let logFd;
2941
3029
  try {
2942
3030
  logFd = openSync3(logPath, "a");
@@ -3018,16 +3106,16 @@ function spawnWorkerProcess(run, opts) {
3018
3106
  launchModel = preflight.model;
3019
3107
  }
3020
3108
  const { worktreesDir } = getPaths();
3021
- const workerDir = path12.join(runDirectory(run.id), "workers", name);
3109
+ const workerDir = path13.join(runDirectory(run.id), "workers", name);
3022
3110
  mkdirSync3(workerDir, { recursive: true });
3023
- const worktreePath = path12.join(worktreesDir, run.id, name);
3111
+ const worktreePath = path13.join(worktreesDir, run.id, name);
3024
3112
  const branch = opts.branch || `agent/${run.id}/${name}`;
3025
3113
  if (existsSync11(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
3026
3114
  git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
3027
3115
  git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
3028
- const stdoutPath = path12.join(workerDir, "stdout.jsonl");
3029
- const stderrPath = path12.join(workerDir, "stderr.log");
3030
- const heartbeatPath = path12.join(workerDir, "heartbeat.jsonl");
3116
+ const stdoutPath = path13.join(workerDir, "stdout.jsonl");
3117
+ const stderrPath = path13.join(workerDir, "stderr.log");
3118
+ const heartbeatPath = path13.join(workerDir, "heartbeat.jsonl");
3031
3119
  const prompt = buildPrompt({
3032
3120
  task: opts.task,
3033
3121
  ownedPaths: opts.ownedPaths || [],
@@ -3088,7 +3176,7 @@ function spawnWorkerProcess(run, opts) {
3088
3176
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
3089
3177
  };
3090
3178
  saveWorker(run.id, worker);
3091
- run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path12.join(workerDir, "worker.json") } };
3179
+ run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path13.join(workerDir, "worker.json") } };
3092
3180
  run.status = "running";
3093
3181
  saveRun(run);
3094
3182
  if (worker.agentOsId && worker.taskId) {
@@ -3380,18 +3468,18 @@ function buildPlanPersistIdempotencyKey(input) {
3380
3468
 
3381
3469
  // src/plan-persist/paths.ts
3382
3470
  import { mkdirSync as mkdirSync4 } from "node:fs";
3383
- import { homedir as homedir3 } from "node:os";
3384
- import path13 from "node:path";
3471
+ import { homedir as homedir4 } from "node:os";
3472
+ import path14 from "node:path";
3385
3473
  function resolveKynverStateRoot() {
3386
3474
  const env = process.env.KYNVER_STATE_ROOT;
3387
- if (env) return path13.resolve(env);
3388
- return path13.join(homedir3(), ".kynver", "state");
3475
+ if (env) return path14.resolve(env);
3476
+ return path14.join(homedir4(), ".kynver", "state");
3389
3477
  }
3390
3478
  function planOutboxDir() {
3391
- return path13.join(resolveKynverStateRoot(), "plan-outbox");
3479
+ return path14.join(resolveKynverStateRoot(), "plan-outbox");
3392
3480
  }
3393
3481
  function planOutboxArchiveDir() {
3394
- return path13.join(resolveKynverStateRoot(), "plan-outbox-archive");
3482
+ return path14.join(resolveKynverStateRoot(), "plan-outbox-archive");
3395
3483
  }
3396
3484
  function ensurePlanOutboxDirs() {
3397
3485
  const outboxDir = planOutboxDir();
@@ -3402,8 +3490,8 @@ function ensurePlanOutboxDirs() {
3402
3490
  }
3403
3491
  function isTmpOnlyPath(filePath) {
3404
3492
  if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
3405
- const resolved = path13.resolve(filePath);
3406
- return resolved.startsWith("/tmp/") || resolved.startsWith(path13.join("/var", "folders"));
3493
+ const resolved = path14.resolve(filePath);
3494
+ return resolved.startsWith("/tmp/") || resolved.startsWith(path14.join("/var", "folders"));
3407
3495
  }
3408
3496
 
3409
3497
  // src/plan-persist/outbox-store.ts
@@ -3415,7 +3503,7 @@ import {
3415
3503
  writeFileSync as writeFileSync3,
3416
3504
  unlinkSync
3417
3505
  } from "node:fs";
3418
- import path14 from "node:path";
3506
+ import path15 from "node:path";
3419
3507
  import { randomUUID } from "node:crypto";
3420
3508
  var DEFAULT_MAX_RETRIES = 12;
3421
3509
  function listOutboxItems() {
@@ -3423,7 +3511,7 @@ function listOutboxItems() {
3423
3511
  const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
3424
3512
  const items = [];
3425
3513
  for (const file of files) {
3426
- const item = readOutboxItem(path14.join(outboxDir, file));
3514
+ const item = readOutboxItem(path15.join(outboxDir, file));
3427
3515
  if (item && item.queueStatus === "queued") items.push(item);
3428
3516
  }
3429
3517
  return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
@@ -3444,7 +3532,7 @@ function readOutboxItem(jsonPath) {
3444
3532
  }
3445
3533
  function readOutboxBody(item) {
3446
3534
  const { outboxDir } = ensurePlanOutboxDirs();
3447
- const bodyFile = path14.join(outboxDir, item.bodyPath);
3535
+ const bodyFile = path15.join(outboxDir, item.bodyPath);
3448
3536
  return readFileSync7(bodyFile, "utf8");
3449
3537
  }
3450
3538
  function writeOutboxItem(input, opts) {
@@ -3452,8 +3540,8 @@ function writeOutboxItem(input, opts) {
3452
3540
  const now = (/* @__PURE__ */ new Date()).toISOString();
3453
3541
  const id = opts.existing?.id ?? randomUUID();
3454
3542
  const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
3455
- const jsonPath = path14.join(outboxDir, `${id}.json`);
3456
- const bodyFile = path14.join(outboxDir, bodyPath);
3543
+ const jsonPath = path15.join(outboxDir, `${id}.json`);
3544
+ const bodyFile = path15.join(outboxDir, bodyPath);
3457
3545
  if (!opts.existing) {
3458
3546
  writeFileSync3(bodyFile, input.body, "utf8");
3459
3547
  }
@@ -3489,24 +3577,24 @@ function writeOutboxItem(input, opts) {
3489
3577
  }
3490
3578
  function saveOutboxItem(item) {
3491
3579
  const { outboxDir } = ensurePlanOutboxDirs();
3492
- const jsonPath = path14.join(outboxDir, `${item.id}.json`);
3580
+ const jsonPath = path15.join(outboxDir, `${item.id}.json`);
3493
3581
  writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
3494
3582
  `, { mode: 384 });
3495
3583
  }
3496
3584
  function archiveOutboxItem(item) {
3497
3585
  const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
3498
- const jsonSrc = path14.join(outboxDir, `${item.id}.json`);
3499
- const bodySrc = path14.join(outboxDir, item.bodyPath);
3500
- const jsonDst = path14.join(archiveDir, `${item.id}.json`);
3501
- const bodyDst = path14.join(archiveDir, item.bodyPath);
3586
+ const jsonSrc = path15.join(outboxDir, `${item.id}.json`);
3587
+ const bodySrc = path15.join(outboxDir, item.bodyPath);
3588
+ const jsonDst = path15.join(archiveDir, `${item.id}.json`);
3589
+ const bodyDst = path15.join(archiveDir, item.bodyPath);
3502
3590
  if (existsSync13(jsonSrc)) renameSync(jsonSrc, jsonDst);
3503
3591
  if (existsSync13(bodySrc)) renameSync(bodySrc, bodyDst);
3504
3592
  }
3505
3593
  function outboxItemPaths(item) {
3506
3594
  const { outboxDir } = ensurePlanOutboxDirs();
3507
3595
  return {
3508
- jsonPath: path14.join(outboxDir, `${item.id}.json`),
3509
- bodyPath: path14.join(outboxDir, item.bodyPath)
3596
+ jsonPath: path15.join(outboxDir, `${item.id}.json`),
3597
+ bodyPath: path15.join(outboxDir, item.bodyPath)
3510
3598
  };
3511
3599
  }
3512
3600
  function outboxInputFromItem(item, body) {
@@ -3692,7 +3780,7 @@ function markOutboxFailed(item, message) {
3692
3780
  }
3693
3781
 
3694
3782
  // src/plan-persist/drain.ts
3695
- import path15 from "node:path";
3783
+ import path16 from "node:path";
3696
3784
  async function drainPlanOutbox(opts = {}, deps = {}) {
3697
3785
  const items = listOutboxItems().filter(
3698
3786
  (item) => opts.outboxId ? item.id === opts.outboxId : true
@@ -3726,7 +3814,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
3726
3814
  return result;
3727
3815
  }
3728
3816
  function loadOutboxById(outboxId) {
3729
- const jsonPath = path15.join(planOutboxDir(), `${outboxId}.json`);
3817
+ const jsonPath = path16.join(planOutboxDir(), `${outboxId}.json`);
3730
3818
  return readOutboxItem(jsonPath);
3731
3819
  }
3732
3820
 
@@ -3826,7 +3914,7 @@ async function dispatchRun(args) {
3826
3914
  const activeHarnessWorkers = [];
3827
3915
  for (const name of Object.keys(run.workers || {})) {
3828
3916
  const worker = readJson(
3829
- path16.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
3917
+ path17.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
3830
3918
  void 0
3831
3919
  );
3832
3920
  if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
@@ -4039,7 +4127,7 @@ function redactHarness(text, secret) {
4039
4127
  }
4040
4128
 
4041
4129
  // src/validate.ts
4042
- import path17 from "node:path";
4130
+ import path18 from "node:path";
4043
4131
  var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
4044
4132
  var WORKER_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
4045
4133
  function validateRunId(runId) {
@@ -4053,15 +4141,15 @@ function validateWorkerName(name) {
4053
4141
  return trimmed;
4054
4142
  }
4055
4143
  function validateRepo(repo) {
4056
- const resolved = path17.resolve(repo);
4144
+ const resolved = path18.resolve(repo);
4057
4145
  if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
4058
4146
  return resolved;
4059
4147
  }
4060
4148
  function validateOwnedPaths(repoRoot, ownedPaths) {
4061
4149
  return ownedPaths.map((owned) => {
4062
- const resolved = path17.resolve(repoRoot, owned);
4063
- const rel = path17.relative(repoRoot, resolved);
4064
- if (rel.startsWith("..") || path17.isAbsolute(rel)) {
4150
+ const resolved = path18.resolve(repoRoot, owned);
4151
+ const rel = path18.relative(repoRoot, resolved);
4152
+ if (rel.startsWith("..") || path18.isAbsolute(rel)) {
4065
4153
  throw new Error(`owned path escapes repo: ${owned}`);
4066
4154
  }
4067
4155
  return resolved;
@@ -4074,7 +4162,7 @@ function validateTailLines(lines) {
4074
4162
 
4075
4163
  // src/worktree.ts
4076
4164
  import { existsSync as existsSync14, mkdirSync as mkdirSync5 } from "node:fs";
4077
- import path18 from "node:path";
4165
+ import path19 from "node:path";
4078
4166
  function createRun(args) {
4079
4167
  const repo = validateRepo(required(String(args.repo || ""), "--repo"));
4080
4168
  ensureGitRepo(repo);
@@ -4094,12 +4182,12 @@ function createRun(args) {
4094
4182
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4095
4183
  workers: {}
4096
4184
  };
4097
- writeJson(path18.join(dir, "run.json"), run);
4185
+ writeJson(path19.join(dir, "run.json"), run);
4098
4186
  console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
4099
4187
  }
4100
4188
  function listRuns() {
4101
4189
  const { runsDir } = getPaths();
4102
- const rows = listRunIds(runsDir).map((id) => readJson(path18.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
4190
+ const rows = listRunIds(runsDir).map((id) => readJson(path19.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
4103
4191
  id: run.id,
4104
4192
  name: run.name,
4105
4193
  status: run.status,
@@ -4114,7 +4202,7 @@ function failExists(message) {
4114
4202
  }
4115
4203
 
4116
4204
  // src/sweep.ts
4117
- import path19 from "node:path";
4205
+ import path20 from "node:path";
4118
4206
  async function sweepRun(args) {
4119
4207
  const pipeline = args.pipeline === true || args.pipeline === "true";
4120
4208
  try {
@@ -4127,7 +4215,7 @@ async function sweepRun(args) {
4127
4215
  const releasedLocalOrphans = [];
4128
4216
  for (const name of Object.keys(run.workers || {})) {
4129
4217
  const worker = readJson(
4130
- path19.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4218
+ path20.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4131
4219
  void 0
4132
4220
  );
4133
4221
  if (!worker || !worker.dispatched || !worker.taskId) continue;
@@ -4174,7 +4262,7 @@ import { mkdirSync as mkdirSync7, realpathSync } from "node:fs";
4174
4262
  import { fileURLToPath as fileURLToPath4 } from "node:url";
4175
4263
 
4176
4264
  // src/pipeline-tick.ts
4177
- import path28 from "node:path";
4265
+ import path29 from "node:path";
4178
4266
 
4179
4267
  // src/pipeline-dispatch.ts
4180
4268
  var RESERVED_REVIEW_STARTS = 1;
@@ -4228,10 +4316,10 @@ async function runPipelineDispatch(args, slots) {
4228
4316
  }
4229
4317
 
4230
4318
  // src/stale-reconcile.ts
4231
- import path21 from "node:path";
4319
+ import path22 from "node:path";
4232
4320
 
4233
4321
  // src/finalize.ts
4234
- import path20 from "node:path";
4322
+ import path21 from "node:path";
4235
4323
  var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
4236
4324
  function terminalStatusFor(run) {
4237
4325
  const names = Object.keys(run.workers || {});
@@ -4242,7 +4330,7 @@ function terminalStatusFor(run) {
4242
4330
  let anyLandingBlocked = false;
4243
4331
  for (const name of names) {
4244
4332
  const worker = readJson(
4245
- path20.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4333
+ path21.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4246
4334
  void 0
4247
4335
  );
4248
4336
  if (!worker) continue;
@@ -4294,7 +4382,7 @@ function reconcileStaleWorkers() {
4294
4382
  const now = Date.now();
4295
4383
  for (const run of listRunRecords()) {
4296
4384
  for (const name of Object.keys(run.workers || {})) {
4297
- const workerPath = path21.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
4385
+ const workerPath = path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
4298
4386
  const worker = readJson(workerPath, void 0);
4299
4387
  if (!worker || worker.status !== "running") {
4300
4388
  outcomes.push({
@@ -4368,9 +4456,27 @@ function reconcileStaleWorkers() {
4368
4456
  }
4369
4457
  return { workers: outcomes, finalizedRuns: finalizeStaleRuns() };
4370
4458
  }
4459
+ function reconcileRunsCli() {
4460
+ const result = reconcileStaleWorkers();
4461
+ const markedExited = result.workers.filter((w) => w.action === "marked_exited").length;
4462
+ const killedStale = result.workers.filter((w) => w.action === "killed_stale").length;
4463
+ const skipped = result.workers.filter((w) => w.action === "skipped").length;
4464
+ console.log(
4465
+ JSON.stringify(
4466
+ {
4467
+ ok: true,
4468
+ workers: { markedExited, killedStale, skipped, total: result.workers.length },
4469
+ finalizedRuns: result.finalizedRuns.length,
4470
+ details: { workers: result.workers, finalizedRuns: result.finalizedRuns }
4471
+ },
4472
+ null,
4473
+ 2
4474
+ )
4475
+ );
4476
+ }
4371
4477
 
4372
4478
  // src/plan-progress-daemon-sync.ts
4373
- import path22 from "node:path";
4479
+ import path23 from "node:path";
4374
4480
 
4375
4481
  // src/plan-progress-sync.ts
4376
4482
  async function syncPlanProgress(args) {
@@ -4394,7 +4500,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
4394
4500
  const outcomes = [];
4395
4501
  for (const name of Object.keys(run.workers || {})) {
4396
4502
  const worker = readJson(
4397
- path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4503
+ path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4398
4504
  void 0
4399
4505
  );
4400
4506
  if (!worker?.dispatched || !worker.taskId) continue;
@@ -4443,7 +4549,7 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
4443
4549
  }
4444
4550
 
4445
4551
  // src/cleanup.ts
4446
- import path27 from "node:path";
4552
+ import path28 from "node:path";
4447
4553
 
4448
4554
  // src/cleanup-types.ts
4449
4555
  var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
@@ -4515,11 +4621,11 @@ function skipNodeModulesRemoval(input) {
4515
4621
 
4516
4622
  // src/cleanup-execute.ts
4517
4623
  import { existsSync as existsSync16, rmSync } from "node:fs";
4518
- import path24 from "node:path";
4624
+ import path25 from "node:path";
4519
4625
 
4520
4626
  // src/cleanup-dir-size.ts
4521
4627
  import { existsSync as existsSync15, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
4522
- import path23 from "node:path";
4628
+ import path24 from "node:path";
4523
4629
  function directorySizeBytes(root, maxEntries = 5e4) {
4524
4630
  if (!existsSync15(root)) return 0;
4525
4631
  let total = 0;
@@ -4535,7 +4641,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
4535
4641
  }
4536
4642
  for (const name of entries) {
4537
4643
  if (seen++ > maxEntries) return null;
4538
- const full = path23.join(current, name);
4644
+ const full = path24.join(current, name);
4539
4645
  let st;
4540
4646
  try {
4541
4647
  st = statSync2(full);
@@ -4619,20 +4725,20 @@ function removeWorktree(candidate, execute) {
4619
4725
  }
4620
4726
  }
4621
4727
  function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
4622
- const resolved = path24.resolve(targetPath);
4623
- const nm = resolved.endsWith(`${path24.sep}node_modules`) ? resolved : null;
4728
+ const resolved = path25.resolve(targetPath);
4729
+ const nm = resolved.endsWith(`${path25.sep}node_modules`) ? resolved : null;
4624
4730
  if (!nm) return "path_outside_harness";
4625
- const rel = path24.relative(worktreesDir, nm);
4626
- if (rel.startsWith("..") || path24.isAbsolute(rel)) return "path_outside_harness";
4627
- const parts = rel.split(path24.sep);
4731
+ const rel = path25.relative(worktreesDir, nm);
4732
+ if (rel.startsWith("..") || path25.isAbsolute(rel)) return "path_outside_harness";
4733
+ const parts = rel.split(path25.sep);
4628
4734
  if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
4629
- if (!resolved.startsWith(path24.resolve(harnessRoot))) return "path_outside_harness";
4735
+ if (!resolved.startsWith(path25.resolve(harnessRoot))) return "path_outside_harness";
4630
4736
  return null;
4631
4737
  }
4632
4738
 
4633
4739
  // src/cleanup-scan.ts
4634
4740
  import { existsSync as existsSync17, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
4635
- import path25 from "node:path";
4741
+ import path26 from "node:path";
4636
4742
  function pathAgeMs(target, now) {
4637
4743
  try {
4638
4744
  const mtime = statSync3(target).mtimeMs;
@@ -4642,17 +4748,17 @@ function pathAgeMs(target, now) {
4642
4748
  }
4643
4749
  }
4644
4750
  function isPathInside(child, parent) {
4645
- const rel = path25.relative(parent, child);
4646
- return rel === "" || !rel.startsWith("..") && !path25.isAbsolute(rel);
4751
+ const rel = path26.relative(parent, child);
4752
+ return rel === "" || !rel.startsWith("..") && !path26.isAbsolute(rel);
4647
4753
  }
4648
4754
  function scanNodeModulesCandidates(opts) {
4649
4755
  const candidates = [];
4650
4756
  const seen = /* @__PURE__ */ new Set();
4651
4757
  for (const entry of opts.index.values()) {
4652
4758
  if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
4653
- const nm = path25.join(entry.worktreePath, "node_modules");
4759
+ const nm = path26.join(entry.worktreePath, "node_modules");
4654
4760
  if (!existsSync17(nm)) continue;
4655
- const resolved = path25.resolve(nm);
4761
+ const resolved = path26.resolve(nm);
4656
4762
  if (seen.has(resolved)) continue;
4657
4763
  seen.add(resolved);
4658
4764
  candidates.push({
@@ -4668,13 +4774,13 @@ function scanNodeModulesCandidates(opts) {
4668
4774
  if (!opts.includeOrphans || !existsSync17(opts.worktreesDir)) return candidates;
4669
4775
  for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
4670
4776
  if (!runEntry.isDirectory()) continue;
4671
- const runPath = path25.join(opts.worktreesDir, runEntry.name);
4777
+ const runPath = path26.join(opts.worktreesDir, runEntry.name);
4672
4778
  for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
4673
4779
  if (!workerEntry.isDirectory()) continue;
4674
- const worktreePath = path25.join(runPath, workerEntry.name);
4675
- const nm = path25.join(worktreePath, "node_modules");
4780
+ const worktreePath = path26.join(runPath, workerEntry.name);
4781
+ const nm = path26.join(worktreePath, "node_modules");
4676
4782
  if (!existsSync17(nm)) continue;
4677
- const resolved = path25.resolve(nm);
4783
+ const resolved = path26.resolve(nm);
4678
4784
  if (seen.has(resolved)) continue;
4679
4785
  if (!isPathInside(resolved, opts.harnessRoot)) continue;
4680
4786
  seen.add(resolved);
@@ -4714,17 +4820,17 @@ function scanWorktreeCandidates(opts) {
4714
4820
  }
4715
4821
 
4716
4822
  // src/cleanup-worktree-index.ts
4717
- import path26 from "node:path";
4823
+ import path27 from "node:path";
4718
4824
  function buildWorktreeIndex() {
4719
4825
  const index = /* @__PURE__ */ new Map();
4720
4826
  for (const run of listRunRecords()) {
4721
4827
  for (const name of Object.keys(run.workers || {})) {
4722
- const workerPath = path26.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
4828
+ const workerPath = path27.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
4723
4829
  const worker = readJson(workerPath, void 0);
4724
4830
  if (!worker?.worktreePath) continue;
4725
4831
  const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
4726
- index.set(path26.resolve(worker.worktreePath), {
4727
- worktreePath: path26.resolve(worker.worktreePath),
4832
+ index.set(path27.resolve(worker.worktreePath), {
4833
+ worktreePath: path27.resolve(worker.worktreePath),
4728
4834
  runId: run.id,
4729
4835
  workerName: name,
4730
4836
  run,
@@ -4738,8 +4844,8 @@ function buildWorktreeIndex() {
4738
4844
 
4739
4845
  // src/cleanup.ts
4740
4846
  function resolveOptions(options = {}) {
4741
- const harnessRoot = options.harnessRoot ? path27.resolve(options.harnessRoot) : resolveHarnessRoot();
4742
- const { worktreesDir } = options.harnessRoot ? { worktreesDir: path27.join(harnessRoot, "worktrees") } : getHarnessPaths();
4847
+ const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
4848
+ const { worktreesDir } = options.harnessRoot ? { worktreesDir: path28.join(harnessRoot, "worktrees") } : getHarnessPaths();
4743
4849
  const execute = options.execute === true;
4744
4850
  const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
4745
4851
  const worktreesAgeMs = options.worktreesAgeMs ?? 0;
@@ -4783,7 +4889,7 @@ function runHarnessCleanup(options = {}) {
4783
4889
  actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
4784
4890
  continue;
4785
4891
  }
4786
- const worktreePath = path27.resolve(candidate.path, "..");
4892
+ const worktreePath = path28.resolve(candidate.path, "..");
4787
4893
  const indexed = index.get(worktreePath) ?? null;
4788
4894
  const guardReason = skipNodeModulesRemoval({
4789
4895
  indexed,
@@ -4799,7 +4905,7 @@ function runHarnessCleanup(options = {}) {
4799
4905
  actions.push(removeNodeModules(candidate, resolved.execute));
4800
4906
  }
4801
4907
  for (const candidate of scanWorktreeCandidates(scanOpts)) {
4802
- const indexed = index.get(path27.resolve(candidate.path)) ?? null;
4908
+ const indexed = index.get(path28.resolve(candidate.path)) ?? null;
4803
4909
  const guardReason = skipWorktreeRemoval({
4804
4910
  indexed,
4805
4911
  includeOrphans: resolved.includeOrphans,
@@ -4868,7 +4974,7 @@ async function completeFinishedWorkers(runId, args) {
4868
4974
  const outcomes = [];
4869
4975
  for (const name of Object.keys(run.workers || {})) {
4870
4976
  const worker = readJson(
4871
- path28.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4977
+ path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4872
4978
  void 0
4873
4979
  );
4874
4980
  if (!worker?.taskId || worker.localOnly) continue;
@@ -5203,7 +5309,7 @@ function runCleanupCli(args) {
5203
5309
  }
5204
5310
 
5205
5311
  // src/monitor/monitor.service.ts
5206
- import path30 from "node:path";
5312
+ import path31 from "node:path";
5207
5313
 
5208
5314
  // src/monitor/monitor.classify.ts
5209
5315
  function expectedLeaseOwner(runId) {
@@ -5260,10 +5366,10 @@ function classifyWorkerHealth(input) {
5260
5366
 
5261
5367
  // src/monitor/monitor.store.ts
5262
5368
  import { existsSync as existsSync18, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
5263
- import path29 from "node:path";
5369
+ import path30 from "node:path";
5264
5370
  function monitorsDir() {
5265
5371
  const { harnessRoot } = getHarnessPaths();
5266
- const dir = path29.join(harnessRoot, "monitors");
5372
+ const dir = path30.join(harnessRoot, "monitors");
5267
5373
  mkdirSync6(dir, { recursive: true });
5268
5374
  return dir;
5269
5375
  }
@@ -5271,7 +5377,7 @@ function monitorIdFor(runId, workerName) {
5271
5377
  return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
5272
5378
  }
5273
5379
  function monitorPath(monitorId) {
5274
- return path29.join(monitorsDir(), `${monitorId}.json`);
5380
+ return path30.join(monitorsDir(), `${monitorId}.json`);
5275
5381
  }
5276
5382
  function loadMonitorSession(monitorId) {
5277
5383
  return readJson(monitorPath(monitorId), void 0);
@@ -5292,7 +5398,7 @@ function listMonitorSessions() {
5292
5398
  for (const name of readdirSync7(dir)) {
5293
5399
  if (!name.endsWith(".json")) continue;
5294
5400
  const session = readJson(
5295
- path29.join(dir, name),
5401
+ path30.join(dir, name),
5296
5402
  void 0
5297
5403
  );
5298
5404
  if (!session?.monitorId) continue;
@@ -5383,7 +5489,7 @@ async function fetchTaskLeasesForWorkers(input) {
5383
5489
  // src/monitor/monitor.service.ts
5384
5490
  function workerRecord2(runId, name) {
5385
5491
  return readJson(
5386
- path30.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
5492
+ path31.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
5387
5493
  void 0
5388
5494
  );
5389
5495
  }
@@ -5586,17 +5692,17 @@ async function runMonitorLoop(args) {
5586
5692
  // src/monitor/monitor-spawn.ts
5587
5693
  import { spawn as spawn4 } from "node:child_process";
5588
5694
  import { closeSync as closeSync4, existsSync as existsSync19, openSync as openSync4 } from "node:fs";
5589
- import path31 from "node:path";
5695
+ import path32 from "node:path";
5590
5696
  import { fileURLToPath as fileURLToPath3 } from "node:url";
5591
5697
  function resolveDefaultCliPath2() {
5592
- return path31.join(fileURLToPath3(new URL(".", import.meta.url)), "..", "cli.js");
5698
+ return path32.join(fileURLToPath3(new URL(".", import.meta.url)), "..", "cli.js");
5593
5699
  }
5594
5700
  function spawnMonitorSidecar(opts) {
5595
5701
  const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
5596
5702
  if (!existsSync19(cliPath)) return void 0;
5597
5703
  const monitorId = monitorIdFor(opts.runId, opts.workerName);
5598
5704
  const { harnessRoot } = getHarnessPaths();
5599
- const logPath = path31.join(harnessRoot, "monitors", `${monitorId}.log`);
5705
+ const logPath = path32.join(harnessRoot, "monitors", `${monitorId}.log`);
5600
5706
  let logFd;
5601
5707
  try {
5602
5708
  logFd = openSync4(logPath, "a");
@@ -5715,6 +5821,422 @@ async function monitorTickCli(args) {
5715
5821
  console.log(JSON.stringify(tick, null, 2));
5716
5822
  }
5717
5823
 
5824
+ // src/doctor/runtime-takeover.ts
5825
+ import path34 from "node:path";
5826
+
5827
+ // src/doctor/runtime-takeover.probes.ts
5828
+ import { accessSync, constants, existsSync as existsSync20, readFileSync as readFileSync9 } from "node:fs";
5829
+ import { homedir as homedir5 } from "node:os";
5830
+ import path33 from "node:path";
5831
+ import { spawnSync as spawnSync3 } from "node:child_process";
5832
+ function captureCommand(bin, args) {
5833
+ try {
5834
+ const res = spawnSync3(bin, args, { encoding: "utf8" });
5835
+ const stdout = (res.stdout || "").trim();
5836
+ const stderr = (res.stderr || "").trim();
5837
+ const ok = res.status === 0;
5838
+ return {
5839
+ ok,
5840
+ stdout,
5841
+ stderr,
5842
+ error: res.error?.message
5843
+ };
5844
+ } catch (error) {
5845
+ return {
5846
+ ok: false,
5847
+ stdout: "",
5848
+ stderr: "",
5849
+ error: error.message
5850
+ };
5851
+ }
5852
+ }
5853
+ function tokenPrefix(token) {
5854
+ const trimmed = token?.trim();
5855
+ if (!trimmed) return void 0;
5856
+ return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
5857
+ }
5858
+ function isWritable(target) {
5859
+ if (!existsSync20(target)) return false;
5860
+ try {
5861
+ accessSync(target, constants.W_OK);
5862
+ return true;
5863
+ } catch {
5864
+ return false;
5865
+ }
5866
+ }
5867
+ var defaultRuntimeTakeoverProbes = {
5868
+ packageVersion: () => PACKAGE_VERSION,
5869
+ commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
5870
+ kynverVersion: (bin) => captureCommand(bin, ["--version"]),
5871
+ loadConfig: () => loadUserConfig(),
5872
+ configFilePath: () => path33.join(homedir5(), ".kynver", "config.json"),
5873
+ credentialsFilePath: () => path33.join(homedir5(), ".kynver", "credentials"),
5874
+ readCredentials: () => {
5875
+ const credPath = path33.join(homedir5(), ".kynver", "credentials");
5876
+ if (!existsSync20(credPath)) {
5877
+ return { hasApiKey: false };
5878
+ }
5879
+ try {
5880
+ const parsed = JSON.parse(readFileSync9(credPath, "utf8"));
5881
+ return {
5882
+ hasApiKey: Boolean(parsed.apiKey?.trim()),
5883
+ runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
5884
+ runnerTokenAgentOsId: parsed.runnerTokenAgentOsId
5885
+ };
5886
+ } catch {
5887
+ return { hasApiKey: false };
5888
+ }
5889
+ },
5890
+ envSnapshot: () => ({
5891
+ kynverApiUrl: process.env.KYNVER_API_URL?.trim() || void 0,
5892
+ openclawCronFireBaseUrl: process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || void 0,
5893
+ kynverRunnerTokenPrefix: tokenPrefix(process.env.KYNVER_RUNNER_TOKEN),
5894
+ kynverRuntimeSecret: Boolean(process.env.KYNVER_RUNTIME_SECRET?.trim()),
5895
+ openclawCronSecret: Boolean(process.env.OPENCLAW_CRON_SECRET?.trim()),
5896
+ kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
5897
+ opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
5898
+ kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0
5899
+ }),
5900
+ harnessRoot: () => resolveHarnessRoot(),
5901
+ legacyOpenclawHarnessRoot: () => path33.join(homedir5(), ".openclaw", "harness"),
5902
+ pathExists: (target) => existsSync20(target),
5903
+ pathWritable: (target) => isWritable(target),
5904
+ vercelVersion: () => captureCommand("vercel", ["--version"]),
5905
+ vercelWhoami: () => captureCommand("vercel", ["whoami"])
5906
+ };
5907
+
5908
+ // src/doctor/runtime-takeover.ts
5909
+ function check(partial) {
5910
+ return partial;
5911
+ }
5912
+ function summarizeCounts(sections) {
5913
+ const counts = { pass: 0, warn: 0, fail: 0 };
5914
+ for (const section of sections) {
5915
+ for (const item of section.checks) {
5916
+ counts[item.status] += 1;
5917
+ }
5918
+ }
5919
+ return counts;
5920
+ }
5921
+ function assessCliPackage(probes) {
5922
+ const runningVersion = probes.packageVersion();
5923
+ const which = probes.commandOnPath("kynver");
5924
+ const onPath = which.ok && which.stdout.length > 0;
5925
+ const firstPath = onPath ? which.stdout.split(/\r?\n/)[0]?.trim() : void 0;
5926
+ const displayCliPath = firstPath ? displayUserPath(firstPath) : void 0;
5927
+ const checks = [
5928
+ check({
5929
+ id: "cli_running_version",
5930
+ label: "Running @kynver-app/runtime version",
5931
+ status: "pass",
5932
+ summary: `@kynver-app/runtime ${runningVersion}`,
5933
+ details: { version: runningVersion }
5934
+ }),
5935
+ check({
5936
+ id: "cli_on_path",
5937
+ label: "kynver executable on PATH",
5938
+ status: onPath ? "pass" : "warn",
5939
+ summary: onPath ? `Found ${displayCliPath}` : "kynver not found on PATH (invoked via node/npx?)",
5940
+ remediation: onPath ? void 0 : "Install globally (`npm i -g @kynver-app/runtime`) or invoke via `npx @kynver-app/runtime`.",
5941
+ details: { path: displayCliPath }
5942
+ })
5943
+ ];
5944
+ if (onPath && firstPath) {
5945
+ const versionProbe = probes.kynverVersion(firstPath);
5946
+ const installedVersion = versionProbe.stdout.replace(/^kynver\s+/i, "").trim() || void 0;
5947
+ const versionMatch = versionProbe.ok && (!installedVersion || installedVersion === runningVersion);
5948
+ checks.push(
5949
+ check({
5950
+ id: "cli_installed_version",
5951
+ label: "Installed kynver CLI version matches running package",
5952
+ status: versionMatch ? "pass" : "warn",
5953
+ summary: versionProbe.ok && installedVersion ? versionMatch ? `Installed ${installedVersion}` : `Installed ${installedVersion} differs from running ${runningVersion}` : versionProbe.error ? `Could not read installed version (${versionProbe.error})` : versionProbe.stderr || "Could not read installed version",
5954
+ remediation: versionMatch ? void 0 : "Reinstall or rebuild @kynver-app/runtime so PATH kynver matches the harness worktree version.",
5955
+ details: { installedVersion, runningVersion, path: displayCliPath }
5956
+ })
5957
+ );
5958
+ }
5959
+ return { id: "cli_package", label: "CLI / package", checks };
5960
+ }
5961
+ function assessUserConfig(probes) {
5962
+ const configPath = probes.configFilePath();
5963
+ const displayConfigPath = displayUserPath(configPath);
5964
+ const exists = probes.pathExists(configPath);
5965
+ const config = probes.loadConfig();
5966
+ const checks = [
5967
+ check({
5968
+ id: "config_file",
5969
+ label: "~/.kynver/config.json present",
5970
+ status: exists ? "pass" : "fail",
5971
+ summary: exists ? `Found ${displayConfigPath}` : `Missing ${displayConfigPath}`,
5972
+ remediation: exists ? void 0 : "Run `kynver setup --api-base-url <url> --agent-os-id <id> --repo <path>`.",
5973
+ details: { configPath: displayConfigPath }
5974
+ })
5975
+ ];
5976
+ if (exists) {
5977
+ const apiBaseUrl = config.apiBaseUrl?.trim();
5978
+ const agentOsId = config.agentOsId?.trim();
5979
+ const defaultRepo = config.defaultRepo?.trim();
5980
+ const displayDefaultRepo = defaultRepo ? displayUserPath(defaultRepo) : null;
5981
+ checks.push(
5982
+ check({
5983
+ id: "config_api_base_url",
5984
+ label: "Default API base URL",
5985
+ status: apiBaseUrl ? "pass" : "warn",
5986
+ summary: apiBaseUrl ?? "Not set in config (KYNVER_API_URL / --base-url required per command)",
5987
+ remediation: apiBaseUrl ? void 0 : "Set `apiBaseUrl` via `kynver setup --api-base-url https://\u2026`.",
5988
+ details: { apiBaseUrl: apiBaseUrl ?? null }
5989
+ }),
5990
+ check({
5991
+ id: "config_agent_os_id",
5992
+ label: "Default AgentOS id",
5993
+ status: agentOsId ? "pass" : "warn",
5994
+ summary: agentOsId ?? "Not set (pass --agent-os-id on daemon/dispatch/worker commands)",
5995
+ remediation: agentOsId ? void 0 : "Set `agentOsId` via `kynver setup --agent-os-id <uuid>`.",
5996
+ details: { agentOsId: agentOsId ?? null, agentOsSlug: config.agentOsSlug ?? null }
5997
+ }),
5998
+ check({
5999
+ id: "config_default_repo",
6000
+ label: "Default repo path",
6001
+ status: defaultRepo ? "pass" : "warn",
6002
+ summary: displayDefaultRepo ?? "Not set (pass --repo on `kynver run create`)",
6003
+ remediation: defaultRepo ? void 0 : "Set `defaultRepo` via `kynver setup --repo /path/to/repo`.",
6004
+ details: {
6005
+ defaultRepo: displayDefaultRepo,
6006
+ harnessRoot: config.harnessRoot ? redactHomePath(config.harnessRoot) : null
6007
+ }
6008
+ })
6009
+ );
6010
+ }
6011
+ return { id: "user_config", label: "User config (~/.kynver)", checks };
6012
+ }
6013
+ function assessRunnerToken(probes) {
6014
+ const config = probes.loadConfig();
6015
+ const targetAgentOsId = config.agentOsId?.trim();
6016
+ const creds = probes.readCredentials();
6017
+ const env = probes.envSnapshot();
6018
+ const credPath = probes.credentialsFilePath();
6019
+ const displayCredPath = displayUserPath(credPath);
6020
+ const envToken = env.kynverRunnerTokenPrefix;
6021
+ const savedToken = creds.runnerTokenPrefix;
6022
+ const savedAgentOsId = creds.runnerTokenAgentOsId;
6023
+ const scopedSaved = Boolean(savedToken) && (!targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId);
6024
+ const hasScoped = Boolean(envToken?.startsWith("krc1.")) || scopedSaved && savedToken?.startsWith("krc1.");
6025
+ const checks = [
6026
+ check({
6027
+ id: "runner_token_scoped",
6028
+ label: "Scoped runner token (krc1.*) ready",
6029
+ status: hasScoped ? "pass" : "fail",
6030
+ summary: hasScoped ? envToken ? "KYNVER_RUNNER_TOKEN is set (scoped prefix shown)" : `Saved scoped token for agentOsId ${savedAgentOsId ?? targetAgentOsId ?? "(unknown)"}` : "No scoped runner token for the configured AgentOS workspace",
6031
+ remediation: hasScoped ? void 0 : "Run `kynver login` then `kynver runner credential --agent-os-id <id>` (or `kynver setup` with API key).",
6032
+ details: {
6033
+ source: envToken ? "env" : savedToken ? "credentials" : "none",
6034
+ tokenPrefix: envToken ?? (scopedSaved ? savedToken : void 0),
6035
+ agentOsId: targetAgentOsId ?? savedAgentOsId ?? null,
6036
+ credentialsPath: displayCredPath
6037
+ }
6038
+ }),
6039
+ check({
6040
+ id: "runner_token_agent_os_match",
6041
+ label: "Saved runner token matches configured agentOsId",
6042
+ status: !savedToken || !targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId ? "pass" : "warn",
6043
+ summary: !savedToken || !savedAgentOsId ? "No saved token agentOsId to compare" : !targetAgentOsId ? "Config agentOsId unset \u2014 token scope not validated against config" : savedAgentOsId === targetAgentOsId ? "Saved token scoped to configured agentOsId" : `Saved token is for ${savedAgentOsId}, config expects ${targetAgentOsId}`,
6044
+ remediation: savedToken && targetAgentOsId && savedAgentOsId && savedAgentOsId !== targetAgentOsId ? "`kynver runner credential --agent-os-id <configured-id>` to mint a workspace-bound token." : void 0,
6045
+ details: { configuredAgentOsId: targetAgentOsId ?? null, savedAgentOsId: savedAgentOsId ?? null }
6046
+ }),
6047
+ check({
6048
+ id: "runner_api_key_for_refresh",
6049
+ label: "API key available for token refresh",
6050
+ status: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY?.trim()) ? "pass" : "warn",
6051
+ summary: creds.hasApiKey || process.env.KYNVER_API_KEY ? "KYNVER API key present for runner credential mint/refresh" : "No API key \u2014 401 callback recovery cannot auto-mint a replacement token",
6052
+ remediation: creds.hasApiKey || process.env.KYNVER_API_KEY ? void 0 : "Run `kynver login --api-key \u2026`.",
6053
+ details: { credentialsPath: displayCredPath, hasApiKey: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY) }
6054
+ })
6055
+ ];
6056
+ return { id: "runner_token", label: "Runner token readiness", checks };
6057
+ }
6058
+ function assessVercelCli(probes) {
6059
+ const version = probes.vercelVersion();
6060
+ const whoami = probes.vercelWhoami();
6061
+ const installed = version.ok;
6062
+ return {
6063
+ id: "vercel_cli",
6064
+ label: "Vercel CLI",
6065
+ checks: [
6066
+ check({
6067
+ id: "vercel_installed",
6068
+ label: "Vercel CLI installed",
6069
+ status: installed ? "pass" : "warn",
6070
+ summary: installed ? version.stdout || "vercel CLI found" : version.error ?? "vercel not found on PATH",
6071
+ remediation: installed ? void 0 : "Install Vercel CLI (`npm i -g vercel`) for deploy evidence and env pulls.",
6072
+ details: { stderr: version.stderr || null }
6073
+ }),
6074
+ check({
6075
+ id: "vercel_authenticated",
6076
+ label: "Vercel CLI authenticated",
6077
+ status: !installed ? "warn" : whoami.ok ? "pass" : "warn",
6078
+ summary: !installed ? "Skipped \u2014 Vercel CLI not installed" : whoami.ok ? `Authenticated as ${whoami.stdout}` : whoami.stderr || whoami.error || "Not logged in",
6079
+ remediation: installed && !whoami.ok ? "Run `vercel login` and link the Kynver project if needed." : void 0,
6080
+ details: { account: whoami.ok ? whoami.stdout : null }
6081
+ })
6082
+ ]
6083
+ };
6084
+ }
6085
+ function assessHarnessDirs(probes) {
6086
+ const harnessRoot = probes.harnessRoot();
6087
+ const runsDir = path34.join(harnessRoot, "runs");
6088
+ const worktreesDir = path34.join(harnessRoot, "worktrees");
6089
+ const displayHarnessRoot = redactHomePath(harnessRoot);
6090
+ const displayRunsDir = redactHomePath(runsDir);
6091
+ const displayWorktreesDir = redactHomePath(worktreesDir);
6092
+ const runsExists = probes.pathExists(runsDir);
6093
+ const worktreesExists = probes.pathExists(worktreesDir);
6094
+ return {
6095
+ id: "harness_dirs",
6096
+ label: "Harness / daemon directories",
6097
+ checks: [
6098
+ check({
6099
+ id: "harness_root",
6100
+ label: "Harness root resolved",
6101
+ status: "pass",
6102
+ summary: displayHarnessRoot,
6103
+ details: { harnessRoot: displayHarnessRoot }
6104
+ }),
6105
+ check({
6106
+ id: "harness_runs_dir",
6107
+ label: "Runs directory ready",
6108
+ status: runsExists && probes.pathWritable(runsDir) ? "pass" : runsExists ? "warn" : "warn",
6109
+ summary: runsExists ? probes.pathWritable(runsDir) ? `Writable ${displayRunsDir}` : `Exists but not writable: ${displayRunsDir}` : `Will be created on first run: ${displayRunsDir}`,
6110
+ remediation: runsExists && !probes.pathWritable(runsDir) ? `Fix permissions on ${displayRunsDir} or set KYNVER_HARNESS_ROOT to a writable path.` : void 0,
6111
+ details: { runsDir: displayRunsDir, exists: runsExists, writable: probes.pathWritable(runsDir) }
6112
+ }),
6113
+ check({
6114
+ id: "harness_worktrees_dir",
6115
+ label: "Worktrees directory ready",
6116
+ status: worktreesExists && probes.pathWritable(worktreesDir) ? "pass" : "warn",
6117
+ summary: worktreesExists ? probes.pathWritable(worktreesDir) ? `Writable ${displayWorktreesDir}` : `Exists but not writable: ${displayWorktreesDir}` : `Will be created on first worker: ${displayWorktreesDir}`,
6118
+ remediation: worktreesExists && !probes.pathWritable(worktreesDir) ? `Fix permissions on ${displayWorktreesDir}.` : void 0,
6119
+ details: { worktreesDir: displayWorktreesDir, exists: worktreesExists, writable: probes.pathWritable(worktreesDir) }
6120
+ })
6121
+ ]
6122
+ };
6123
+ }
6124
+ function assessCallbackAuth(probes) {
6125
+ const config = probes.loadConfig();
6126
+ const env = probes.envSnapshot();
6127
+ const baseUrl = env.kynverApiUrl ?? config.apiBaseUrl?.trim() ?? env.openclawCronFireBaseUrl;
6128
+ const usingLegacyBase = !env.kynverApiUrl && !config.apiBaseUrl && Boolean(env.openclawCronFireBaseUrl);
6129
+ const legacySecret = env.openclawCronSecret || env.kynverRuntimeSecret;
6130
+ const envScoped = env.kynverRunnerTokenPrefix?.startsWith("krc1.");
6131
+ const creds = probes.readCredentials();
6132
+ const savedScoped = creds.runnerTokenPrefix?.startsWith("krc1.");
6133
+ const checks = [
6134
+ check({
6135
+ id: "callback_base_url",
6136
+ label: "Callback base URL configured",
6137
+ status: baseUrl ? usingLegacyBase ? "warn" : "pass" : "fail",
6138
+ summary: baseUrl ? usingLegacyBase ? `Using legacy OPENCLAW_CRON_FIRE_BASE_URL (${baseUrl})` : baseUrl : "No KYNVER_API_URL, config apiBaseUrl, or legacy OpenClaw base URL",
6139
+ remediation: baseUrl ? usingLegacyBase ? "Migrate to KYNVER_API_URL or `kynver setup --api-base-url` and drop OPENCLAW_CRON_FIRE_BASE_URL." : void 0 : "Set KYNVER_API_URL or run `kynver setup --api-base-url https://\u2026`.",
6140
+ details: {
6141
+ source: env.kynverApiUrl ? "KYNVER_API_URL" : config.apiBaseUrl ? "config.apiBaseUrl" : env.openclawCronFireBaseUrl ? "OPENCLAW_CRON_FIRE_BASE_URL" : "none",
6142
+ baseUrl: baseUrl ?? null
6143
+ }
6144
+ }),
6145
+ check({
6146
+ id: "callback_auth_mode",
6147
+ label: "Callback auth uses scoped runner token",
6148
+ status: envScoped || savedScoped ? "pass" : legacySecret ? "warn" : "fail",
6149
+ summary: envScoped ? "KYNVER_RUNNER_TOKEN scoped token will be sent as X-Kynver-Runner-Token" : savedScoped ? "Saved krc1.* token in ~/.kynver/credentials" : legacySecret ? "Deployment-level KYNVER_RUNTIME_SECRET / OPENCLAW_CRON_SECRET in use (legacy headers)" : "No callback secret configured",
6150
+ remediation: envScoped || savedScoped ? void 0 : "Mint a scoped token: `kynver runner credential --agent-os-id <id>`. Avoid shared OPENCLAW_CRON_SECRET on user runners.",
6151
+ details: {
6152
+ mode: envScoped || savedScoped ? "scoped" : legacySecret ? "legacy_global_secret" : "missing",
6153
+ legacyHeadersWhenNotScoped: ["X-OpenClaw-Cron-Secret", "X-Kynver-Runtime-Secret"]
6154
+ }
6155
+ })
6156
+ ];
6157
+ return { id: "callback_auth", label: "Callback auth / config", checks };
6158
+ }
6159
+ function assessOpenclawHotspots(probes) {
6160
+ const env = probes.envSnapshot();
6161
+ const harnessRoot = probes.harnessRoot();
6162
+ const legacyRoot = probes.legacyOpenclawHarnessRoot();
6163
+ const displayHarnessRoot = redactHomePath(harnessRoot);
6164
+ const displayLegacyRoot = redactHomePath(legacyRoot);
6165
+ const displayOpusHarnessRoot = env.opusHarnessRoot ? redactHomePath(env.opusHarnessRoot) : null;
6166
+ const legacyHarnessActive = harnessRoot === legacyRoot && probes.pathExists(legacyRoot);
6167
+ const schedulerOpenclaw = !env.kynverSchedulerProvider || env.kynverSchedulerProvider === "openclaw-cron";
6168
+ const checks = [
6169
+ check({
6170
+ id: "hotspot_legacy_harness_root",
6171
+ label: "Legacy ~/.openclaw/harness still active",
6172
+ status: legacyHarnessActive ? "warn" : "pass",
6173
+ summary: legacyHarnessActive ? `Harness root is legacy ${displayLegacyRoot}` : env.opusHarnessRoot ? `OPUS_HARNESS_ROOT override in use (${displayOpusHarnessRoot})` : `Using ${displayHarnessRoot}`,
6174
+ remediation: legacyHarnessActive ? "Set KYNVER_HARNESS_ROOT=~/.kynver/harness (or run setup), migrate artifacts, retire OPUS_HARNESS_ROOT." : env.opusHarnessRoot ? "Prefer KYNVER_HARNESS_ROOT over OPUS_HARNESS_ROOT." : void 0,
6175
+ details: { harnessRoot: displayHarnessRoot, legacyRoot: displayLegacyRoot, opusHarnessRoot: displayOpusHarnessRoot }
6176
+ }),
6177
+ check({
6178
+ id: "hotspot_openclaw_env_secrets",
6179
+ label: "OpenClaw deployment secrets in runner env",
6180
+ status: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "warn" : "pass",
6181
+ summary: env.openclawCronSecret || env.openclawCronFireBaseUrl ? [
6182
+ env.openclawCronSecret ? "OPENCLAW_CRON_SECRET set" : null,
6183
+ env.openclawCronFireBaseUrl ? "OPENCLAW_CRON_FIRE_BASE_URL set" : null
6184
+ ].filter(Boolean).join("; ") : "No OpenClaw cron env overrides on this runner",
6185
+ remediation: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "Move to KYNVER_API_URL + scoped runner tokens; unset OpenClaw cron env on user-hosted runners." : void 0
6186
+ }),
6187
+ check({
6188
+ id: "hotspot_openclaw_scheduler",
6189
+ label: "openclaw-cron scheduler dependency (deployment)",
6190
+ status: schedulerOpenclaw ? "warn" : "pass",
6191
+ summary: schedulerOpenclaw ? env.kynverSchedulerProvider === "openclaw-cron" ? "KYNVER_SCHEDULER_PROVIDER=openclaw-cron \u2014 AgentOS ticks still routed via OpenClaw local cron adapter" : "KYNVER_SCHEDULER_PROVIDER unset \u2014 server may fall back to openclaw-cron when QStash is absent" : `KYNVER_SCHEDULER_PROVIDER=${env.kynverSchedulerProvider}`,
6192
+ remediation: schedulerOpenclaw ? "On Kynver-hosted scheduler: set KYNVER_SCHEDULER_PROVIDER=qstash where QStash is configured; retire openclaw-cron stub when runtime daemon owns dispatch." : void 0,
6193
+ details: { schedulerProvider: env.kynverSchedulerProvider ?? null }
6194
+ }),
6195
+ check({
6196
+ id: "hotspot_lease_source_names",
6197
+ label: "Harness lease/completion source names",
6198
+ status: "pass",
6199
+ summary: "Runtime uses kynver-harness:* lease owners and completion source kynver-harness (OpenClaw names retired in runtime)",
6200
+ details: {
6201
+ leaseOwnerPattern: "kynver-harness:<runId>",
6202
+ completionSource: "kynver-harness",
6203
+ note: "OpenClaw adapter remains optional in packages/kynver-openclaw-agent-os only"
6204
+ }
6205
+ })
6206
+ ];
6207
+ return { id: "openclaw_hotspots", label: "OpenClaw dependency hotspots", checks };
6208
+ }
6209
+ function assessRuntimeTakeoverReadiness(probes = defaultRuntimeTakeoverProbes) {
6210
+ const sections = [
6211
+ assessCliPackage(probes),
6212
+ assessUserConfig(probes),
6213
+ assessRunnerToken(probes),
6214
+ assessVercelCli(probes),
6215
+ assessHarnessDirs(probes),
6216
+ assessCallbackAuth(probes),
6217
+ assessOpenclawHotspots(probes)
6218
+ ];
6219
+ const counts = summarizeCounts(sections);
6220
+ const ready = counts.fail === 0;
6221
+ const summary = ready ? counts.warn > 0 ? `Ready with ${counts.warn} warning(s) \u2014 review remediation before retiring OpenClaw as primary runtime agent.` : "Ready \u2014 Kynver runtime can serve as primary runtime agent on this host." : `${counts.fail} blocking check(s) \u2014 fix failures before OpenClaw takeover.`;
6222
+ return {
6223
+ command: "doctor runtime-takeover",
6224
+ ready,
6225
+ summary,
6226
+ counts,
6227
+ sections
6228
+ };
6229
+ }
6230
+
6231
+ // src/doctor/runtime-takeover-cli.ts
6232
+ function runRuntimeTakeoverDoctorCli() {
6233
+ const report = assessRuntimeTakeoverReadiness();
6234
+ console.log(JSON.stringify(report, null, 2));
6235
+ if (!report.ready) {
6236
+ process.exitCode = 1;
6237
+ }
6238
+ }
6239
+
5718
6240
  // src/cli.ts
5719
6241
  function isHelpFlag(arg) {
5720
6242
  return arg === "help" || arg === "--help" || arg === "-h";
@@ -5736,7 +6258,7 @@ function usage(code = 0) {
5736
6258
  " kynver run create --repo /path/repo [--name name] [--base origin/main]",
5737
6259
  " kynver run list",
5738
6260
  " kynver run status --run RUN_ID",
5739
- " kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-7] [--disk-path /]",
6261
+ " kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-8] [--disk-path /]",
5740
6262
  " kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
5741
6263
  ' 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] [--wait]',
5742
6264
  " kynver worker status --run RUN_ID --name worker",
@@ -5744,6 +6266,7 @@ function usage(code = 0) {
5744
6266
  " kynver worker stop --run RUN_ID --name worker",
5745
6267
  " kynver worker complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--task-id TASK_ID] [--base-url URL] [--secret SECRET]",
5746
6268
  " kynver worker auto-complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--poll-ms 5000] [--max-total-ms 21600000] [--complete-attempts 3] [--complete-backoff-ms 5000] [--base-url URL] [--secret SECRET]",
6269
+ " kynver run reconcile",
5747
6270
  " kynver plan progress --plan PLAN_ID --row ROW_KEY --role ROLE --status STATUS [--task TASK_ID] [--note NOTE] [--evidence type:value] [--agent-os-id AOS_ID]",
5748
6271
  " kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override]",
5749
6272
  " kynver plan persist --operation create|add_version|update_metadata --title TITLE (--body-file PATH | --body TEXT) [--slug SLUG] [--plan PLAN_ID] [--summary TEXT] [--failure-kind approval_guard|auth|network|server|tool_interruption]",
@@ -5756,7 +6279,8 @@ function usage(code = 0) {
5756
6279
  " kynver monitor list",
5757
6280
  " kynver monitor tick --run RUN_ID [--name worker] [--agent-os-id AOS_ID] [--auto-complete] [--renew-leases]",
5758
6281
  " kynver monitor auto-complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--base-url URL] [--secret SECRET]",
5759
- " kynver monitor run-loop --run RUN_ID --monitor-id ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS] [--auto-complete] [--renew-leases]"
6282
+ " kynver monitor run-loop --run RUN_ID --monitor-id ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS] [--auto-complete] [--renew-leases]",
6283
+ " kynver doctor runtime-takeover"
5760
6284
  ].join("\n")
5761
6285
  );
5762
6286
  process.exit(code);
@@ -5767,7 +6291,7 @@ async function main(argv = process.argv.slice(2)) {
5767
6291
  const scope = argv.shift();
5768
6292
  let action;
5769
6293
  let rest;
5770
- if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "monitor") {
6294
+ if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "monitor" || scope === "doctor") {
5771
6295
  action = argv.shift();
5772
6296
  rest = argv;
5773
6297
  } else {
@@ -5792,11 +6316,13 @@ async function main(argv = process.argv.slice(2)) {
5792
6316
  unknownCommand("plan", `outbox ${outboxAction ?? ""}`.trim());
5793
6317
  }
5794
6318
  if (scope === "cleanup") return runCleanupCli(args);
6319
+ if (scope === "doctor" && action === "runtime-takeover") return runRuntimeTakeoverDoctorCli();
5795
6320
  if (scope === "run" && action === "create") return createRun(args);
5796
6321
  if (scope === "run" && action === "list") return listRuns();
5797
6322
  if (scope === "run" && action === "status") return runStatus(args);
5798
6323
  if (scope === "run" && action === "dispatch") return void await dispatchRun(args);
5799
6324
  if (scope === "run" && action === "sweep") return void await sweepRun(args);
6325
+ if (scope === "run" && action === "reconcile") return reconcileRunsCli();
5800
6326
  if (scope === "worker" && action === "start") return void await startWorker(args);
5801
6327
  if (scope === "worker" && action === "status") return workerStatus(args);
5802
6328
  if (scope === "worker" && action === "tail") return tailWorker(args);
@@ -5823,6 +6349,295 @@ if (isCliEntry) {
5823
6349
  process.exit(1);
5824
6350
  });
5825
6351
  }
6352
+
6353
+ // src/vercel/vercel-url.ts
6354
+ var VERCEL_HOST_RE = /(^|\.)vercel\.app$/i;
6355
+ function tryParseUrl(raw) {
6356
+ const trimmed = raw.trim();
6357
+ if (!trimmed) return null;
6358
+ try {
6359
+ return new URL(trimmed);
6360
+ } catch {
6361
+ return null;
6362
+ }
6363
+ }
6364
+ function parseDashboardDeployment(url) {
6365
+ const parts = url.pathname.split("/").filter(Boolean);
6366
+ if (parts.length < 3) return null;
6367
+ const deploymentId = parts[parts.length - 1]?.trim();
6368
+ if (!deploymentId || deploymentId === "deployments") return null;
6369
+ return deploymentId;
6370
+ }
6371
+ function parseDeploymentsSegment(url) {
6372
+ const parts = url.pathname.split("/").filter(Boolean);
6373
+ const idx = parts.indexOf("deployments");
6374
+ if (idx < 0 || idx >= parts.length - 1) return null;
6375
+ const deploymentId = parts[idx + 1]?.trim();
6376
+ return deploymentId || null;
6377
+ }
6378
+ function classifyVercelUrl(raw) {
6379
+ const empty = {
6380
+ kind: "unknown",
6381
+ previewUrl: null,
6382
+ inspectTarget: null,
6383
+ deploymentId: null
6384
+ };
6385
+ const trimmed = raw.trim();
6386
+ if (!trimmed) return empty;
6387
+ if (/^dpl_[a-z0-9]+$/i.test(trimmed)) {
6388
+ return {
6389
+ kind: "deployment_id",
6390
+ previewUrl: null,
6391
+ inspectTarget: trimmed,
6392
+ deploymentId: trimmed
6393
+ };
6394
+ }
6395
+ const url = tryParseUrl(trimmed);
6396
+ if (!url) return empty;
6397
+ if (VERCEL_HOST_RE.test(url.hostname)) {
6398
+ const hostUrl = url.origin;
6399
+ return {
6400
+ kind: "deployment_host",
6401
+ previewUrl: hostUrl,
6402
+ inspectTarget: hostUrl,
6403
+ deploymentId: null
6404
+ };
6405
+ }
6406
+ if (url.hostname === "vercel.com" || url.hostname.endsWith(".vercel.com")) {
6407
+ const deploymentId = parseDeploymentsSegment(url) ?? parseDashboardDeployment(url);
6408
+ return {
6409
+ kind: "dashboard",
6410
+ previewUrl: null,
6411
+ inspectTarget: deploymentId,
6412
+ deploymentId
6413
+ };
6414
+ }
6415
+ return empty;
6416
+ }
6417
+ function isDashboardVercelUrl(raw) {
6418
+ return classifyVercelUrl(raw).kind === "dashboard";
6419
+ }
6420
+
6421
+ // src/vercel/vercel-github-status.ts
6422
+ var VERCEL_CONTEXT_RE = /vercel/i;
6423
+ function normalizeGitHubStatusState(state) {
6424
+ const value = typeof state === "string" ? state.trim().toLowerCase() : "";
6425
+ if (value === "success") return "success";
6426
+ if (value === "pending") return "pending";
6427
+ if (value === "failure") return "failure";
6428
+ if (value === "error") return "error";
6429
+ return "unknown";
6430
+ }
6431
+ function isVercelStatusContext(context) {
6432
+ const label = typeof context === "string" ? context.trim() : "";
6433
+ return Boolean(label && VERCEL_CONTEXT_RE.test(label));
6434
+ }
6435
+ function pickVercelStatusContext(statuses) {
6436
+ const rows = Array.isArray(statuses) ? statuses : [];
6437
+ const vercelRows = rows.filter((row) => isVercelStatusContext(row.context));
6438
+ if (vercelRows.length === 0) return null;
6439
+ const score = (state) => {
6440
+ if (state === "failure" || state === "error") return 0;
6441
+ if (state === "pending") return 1;
6442
+ if (state === "success") return 2;
6443
+ return 1;
6444
+ };
6445
+ let best = null;
6446
+ let bestScore = -1;
6447
+ for (const row of vercelRows) {
6448
+ const rowScore = score(normalizeGitHubStatusState(row.state));
6449
+ if (rowScore > bestScore) {
6450
+ best = row;
6451
+ bestScore = rowScore;
6452
+ }
6453
+ }
6454
+ if (!best) return null;
6455
+ const targetUrl = typeof best.target_url === "string" && best.target_url.trim() ? best.target_url.trim() : null;
6456
+ const classified = targetUrl ? classifyVercelUrl(targetUrl) : null;
6457
+ return {
6458
+ context: String(best.context ?? "Vercel").trim(),
6459
+ state: normalizeGitHubStatusState(best.state),
6460
+ targetUrl,
6461
+ description: typeof best.description === "string" && best.description.trim() ? best.description.trim() : null,
6462
+ deploymentId: classified?.deploymentId ?? null,
6463
+ previewUrl: classified?.previewUrl ?? null,
6464
+ dashboardUrl: classified?.kind === "dashboard" ? targetUrl : null
6465
+ };
6466
+ }
6467
+
6468
+ // src/vercel/vercel-evidence.ts
6469
+ import { spawnSync as spawnSync4 } from "node:child_process";
6470
+ var DEFAULT_INSPECT_WAIT_SECONDS = 120;
6471
+ function mapGitHubStateToEvidence(state) {
6472
+ if (state === "success") return "ready";
6473
+ if (state === "pending") return "building";
6474
+ if (state === "failure" || state === "error") return "error";
6475
+ return "unavailable";
6476
+ }
6477
+ function evidenceSummary(parts) {
6478
+ return parts.filter(Boolean).join("; ");
6479
+ }
6480
+ function resolveVercelInspectTarget(rawUrl) {
6481
+ const trimmed = typeof rawUrl === "string" ? rawUrl.trim() : "";
6482
+ if (!trimmed) {
6483
+ return { target: null, classified: null, reason: "missing target_url" };
6484
+ }
6485
+ const classified = classifyVercelUrl(trimmed);
6486
+ if (classified.inspectTarget) {
6487
+ return { target: classified.inspectTarget, classified, reason: null };
6488
+ }
6489
+ if (classified.kind === "dashboard") {
6490
+ return {
6491
+ target: null,
6492
+ classified,
6493
+ reason: "dashboard URL is not valid for vercel inspect"
6494
+ };
6495
+ }
6496
+ return { target: null, classified, reason: "unrecognized Vercel URL" };
6497
+ }
6498
+ function evidenceFromGitHubVercelStatus(statuses, options = {}) {
6499
+ const observedAt = options.observedAt ?? (/* @__PURE__ */ new Date()).toISOString();
6500
+ const row = pickVercelStatusContext(statuses);
6501
+ if (!row) {
6502
+ return {
6503
+ status: "not_run",
6504
+ previewUrl: null,
6505
+ deploymentUrl: null,
6506
+ summary: "No Vercel GitHub status context on commit",
6507
+ observedAt,
6508
+ inspectSkipped: true,
6509
+ inspectReason: "no_vercel_status_context",
6510
+ githubState: null,
6511
+ vercelContext: null
6512
+ };
6513
+ }
6514
+ const status = mapGitHubStateToEvidence(row.state);
6515
+ const previewUrl = row.previewUrl ?? row.dashboardUrl;
6516
+ const deploymentUrl = row.previewUrl ?? row.dashboardUrl;
6517
+ return {
6518
+ status,
6519
+ previewUrl,
6520
+ deploymentUrl,
6521
+ summary: evidenceSummary([
6522
+ `GitHub ${row.context}=${row.state}`,
6523
+ row.description ?? "",
6524
+ row.dashboardUrl ? "dashboard target_url (inspect skipped)" : ""
6525
+ ]),
6526
+ observedAt,
6527
+ inspectSkipped: true,
6528
+ inspectReason: row.dashboardUrl && row.state === "success" ? "trusted_github_status_dashboard_url" : row.dashboardUrl ? "dashboard_url_not_inspectable" : "github_status_only",
6529
+ githubState: row.state,
6530
+ vercelContext: row.context
6531
+ };
6532
+ }
6533
+ function defaultRunVercelInspect(target, waitSeconds) {
6534
+ const args = ["inspect", target, "--wait", String(waitSeconds)];
6535
+ const result = spawnSync4("vercel", args, {
6536
+ encoding: "utf8",
6537
+ stdio: ["ignore", "pipe", "pipe"]
6538
+ });
6539
+ if (result.error) {
6540
+ return {
6541
+ ok: false,
6542
+ exitCode: result.status,
6543
+ stdout: "",
6544
+ stderr: "",
6545
+ error: result.error.message
6546
+ };
6547
+ }
6548
+ return {
6549
+ ok: result.status === 0,
6550
+ exitCode: result.status ?? 1,
6551
+ stdout: (result.stdout ?? "").trim(),
6552
+ stderr: (result.stderr ?? "").trim(),
6553
+ error: null
6554
+ };
6555
+ }
6556
+ function parseInspectReady(stdout, stderr) {
6557
+ const blob = `${stdout}
6558
+ ${stderr}`.toLowerCase();
6559
+ if (/\b(status|state)\s*[:=]\s*(ready|completed)\b/.test(blob)) return true;
6560
+ if (/\bready\b/.test(blob) && !/\bnot ready\b/.test(blob)) return true;
6561
+ return false;
6562
+ }
6563
+ function collectVercelEvidence(input) {
6564
+ const observedAt = input.observedAt ?? (/* @__PURE__ */ new Date()).toISOString();
6565
+ const base = evidenceFromGitHubVercelStatus(input.statuses ?? [], { observedAt });
6566
+ const row = pickVercelStatusContext(input.statuses ?? []);
6567
+ if (!row) return base;
6568
+ if (row.state === "success") {
6569
+ return {
6570
+ ...base,
6571
+ inspectSkipped: true,
6572
+ inspectReason: row.dashboardUrl ? "trusted_github_status_dashboard_url" : "trusted_github_status"
6573
+ };
6574
+ }
6575
+ if (!input.allowInspect) {
6576
+ return {
6577
+ ...base,
6578
+ inspectSkipped: true,
6579
+ inspectReason: "inspect_disabled"
6580
+ };
6581
+ }
6582
+ const { target, reason } = resolveVercelInspectTarget(row.targetUrl);
6583
+ if (!target) {
6584
+ return {
6585
+ ...base,
6586
+ inspectSkipped: true,
6587
+ inspectReason: reason ?? "not_inspectable",
6588
+ summary: evidenceSummary([
6589
+ base.summary ?? "",
6590
+ reason ?? "skipped vercel inspect"
6591
+ ])
6592
+ };
6593
+ }
6594
+ const waitSeconds = input.waitSeconds ?? DEFAULT_INSPECT_WAIT_SECONDS;
6595
+ const runInspect = input.runInspect ?? defaultRunVercelInspect;
6596
+ const inspected = runInspect(target, waitSeconds);
6597
+ const observed = (/* @__PURE__ */ new Date()).toISOString();
6598
+ if (inspected.error?.includes("ENOENT")) {
6599
+ return {
6600
+ status: "unavailable",
6601
+ previewUrl: base.previewUrl,
6602
+ deploymentUrl: base.deploymentUrl,
6603
+ summary: "vercel CLI not found on PATH",
6604
+ observedAt: observed,
6605
+ inspectSkipped: true,
6606
+ inspectReason: "vercel_cli_missing",
6607
+ githubState: row.state,
6608
+ vercelContext: row.context
6609
+ };
6610
+ }
6611
+ if (!inspected.ok) {
6612
+ const detail = [inspected.stderr, inspected.stdout, inspected.error].filter(Boolean).join("\n").trim().slice(0, 500);
6613
+ return {
6614
+ status: row.state === "pending" ? "building" : "error",
6615
+ previewUrl: base.previewUrl,
6616
+ deploymentUrl: target.startsWith("http") ? target : base.deploymentUrl,
6617
+ summary: evidenceSummary([
6618
+ `vercel inspect ${target} failed (exit ${inspected.exitCode ?? "?"})`,
6619
+ detail
6620
+ ]),
6621
+ observedAt: observed,
6622
+ inspectSkipped: false,
6623
+ inspectReason: null,
6624
+ githubState: row.state,
6625
+ vercelContext: row.context
6626
+ };
6627
+ }
6628
+ const ready = parseInspectReady(inspected.stdout, inspected.stderr);
6629
+ return {
6630
+ status: ready ? "ready" : row.state === "pending" ? "building" : "error",
6631
+ previewUrl: target.startsWith("http") ? target : base.previewUrl,
6632
+ deploymentUrl: target.startsWith("http") ? target : base.deploymentUrl,
6633
+ summary: ready ? `vercel inspect ${target} ready` : `vercel inspect ${target} completed without ready signal`,
6634
+ observedAt: observed,
6635
+ inspectSkipped: false,
6636
+ inspectReason: null,
6637
+ githubState: row.state,
6638
+ vercelContext: row.context
6639
+ };
6640
+ }
5826
6641
  export {
5827
6642
  DEFAULT_DISPATCH_LEASE_MS,
5828
6643
  PACKAGE_VERSION,
@@ -5835,7 +6650,9 @@ export {
5835
6650
  autoCompleteWorkerCli,
5836
6651
  buildDispatchTaskText,
5837
6652
  buildPrompt,
6653
+ classifyVercelUrl,
5838
6654
  classifyWorkerHealth,
6655
+ collectVercelEvidence,
5839
6656
  completeWorker,
5840
6657
  computeAttention,
5841
6658
  computeWorkerStatus,
@@ -5844,15 +6661,18 @@ export {
5844
6661
  dispatchRun,
5845
6662
  drainPlanOutbox,
5846
6663
  ensurePrReadyHandoff,
6664
+ evidenceFromGitHubVercelStatus,
5847
6665
  extractPlanOutboxFromTask,
5848
6666
  extractPrUrlFromText,
5849
6667
  formatPlanOutboxHandoffBlock,
5850
6668
  getHarnessPaths,
5851
6669
  getMonitorStatus,
5852
6670
  hashPlanBody,
6671
+ isDashboardVercelUrl,
5853
6672
  isFinishedWorkerStatus,
5854
6673
  isLandingBlockedWorkerStatus,
5855
6674
  isTerminalHeartbeatPhase,
6675
+ isVercelStatusContext,
5856
6676
  landingContractAttentionReason,
5857
6677
  listMonitors,
5858
6678
  listOutboxItems,
@@ -5866,13 +6686,17 @@ export {
5866
6686
  parseHarnessStream,
5867
6687
  parseHeartbeat,
5868
6688
  persistPlan,
6689
+ pickVercelStatusContext,
5869
6690
  postJson,
5870
6691
  preflightCursorModel,
6692
+ reconcileRunsCli,
6693
+ reconcileStaleWorkers,
5871
6694
  redactHarness,
5872
6695
  resolveBaseUrl,
5873
6696
  resolveCallbackSecret,
5874
6697
  resolveCallbackSecretWithMint,
5875
6698
  resolveHarnessRoot,
6699
+ resolveVercelInspectTarget,
5876
6700
  runDaemon,
5877
6701
  runMonitorTick,
5878
6702
  runStatus,