@kynver-app/runtime 0.1.39 → 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/cli.js CHANGED
@@ -6,12 +6,39 @@ import { fileURLToPath as fileURLToPath4 } from "node:url";
6
6
 
7
7
  // src/config.ts
8
8
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
9
+ import { homedir as homedir2 } from "node:os";
10
+ import path3 from "node:path";
11
+
12
+ // src/path-values.ts
9
13
  import { homedir } from "node:os";
10
- import path2 from "node:path";
14
+ import path from "node:path";
15
+ function expandHomePath(value) {
16
+ if (value === "~") return homedir();
17
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
18
+ return path.join(homedir(), value.slice(2));
19
+ }
20
+ return value;
21
+ }
22
+ function resolveUserPath(value) {
23
+ return path.resolve(expandHomePath(value));
24
+ }
25
+ function redactHomePath(value) {
26
+ const expanded = expandHomePath(value);
27
+ const resolved = path.resolve(expanded);
28
+ const home = path.resolve(homedir());
29
+ if (resolved === home) return "~";
30
+ if (resolved.startsWith(`${home}${path.sep}`)) {
31
+ return `~/${path.relative(home, resolved).split(path.sep).join("/")}`;
32
+ }
33
+ return resolved.replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^\/Users\/[^/]+(?=\/|$)/, "~");
34
+ }
35
+ function displayUserPath(value) {
36
+ return redactHomePath(value);
37
+ }
11
38
 
12
39
  // src/util.ts
13
40
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
14
- import path from "node:path";
41
+ import path2 from "node:path";
15
42
  function fail(message) {
16
43
  console.error(message);
17
44
  process.exit(1);
@@ -40,7 +67,7 @@ function readJson(file, fallback) {
40
67
  }
41
68
  }
42
69
  function writeJson(file, value) {
43
- mkdirSync(path.dirname(file), { recursive: true });
70
+ mkdirSync(path2.dirname(file), { recursive: true });
44
71
  writeFileSync(file, `${JSON.stringify(value, null, 2)}
45
72
  `);
46
73
  }
@@ -76,7 +103,7 @@ function tailFile(file, lines) {
76
103
  return data.split("\n").slice(-lines).join("\n");
77
104
  }
78
105
  function readMaybeFile(file) {
79
- return file ? readFileSync(path.resolve(file), "utf8") : "";
106
+ return file ? readFileSync(path2.resolve(file), "utf8") : "";
80
107
  }
81
108
  function listRunIds(runsDir) {
82
109
  if (!existsSync(runsDir)) return [];
@@ -119,9 +146,9 @@ function secsAgo(ms) {
119
146
  }
120
147
 
121
148
  // src/config.ts
122
- var CONFIG_DIR = path2.join(homedir(), ".kynver");
123
- var CONFIG_FILE = path2.join(CONFIG_DIR, "config.json");
124
- var CREDENTIALS_FILE = path2.join(CONFIG_DIR, "credentials");
149
+ var CONFIG_DIR = path3.join(homedir2(), ".kynver");
150
+ var CONFIG_FILE = path3.join(CONFIG_DIR, "config.json");
151
+ var CREDENTIALS_FILE = path3.join(CONFIG_DIR, "credentials");
125
152
  function loadUserConfig() {
126
153
  if (!existsSync2(CONFIG_FILE)) return {};
127
154
  try {
@@ -132,9 +159,33 @@ function loadUserConfig() {
132
159
  }
133
160
  function saveUserConfig(config) {
134
161
  mkdirSync2(CONFIG_DIR, { recursive: true });
135
- writeFileSync2(CONFIG_FILE, `${JSON.stringify(config, null, 2)}
162
+ writeFileSync2(CONFIG_FILE, `${JSON.stringify(normalizeConfigPaths(config), null, 2)}
136
163
  `, { mode: 384 });
137
164
  }
165
+ function normalizeConfigPaths(config) {
166
+ return {
167
+ ...config,
168
+ ...config.harnessRoot?.trim() ? { harnessRoot: redactHomePath(config.harnessRoot.trim()) } : {},
169
+ ...config.defaultRepo?.trim() ? { defaultRepo: redactHomePath(config.defaultRepo.trim()) } : {}
170
+ };
171
+ }
172
+ function presentUserConfig(config) {
173
+ return normalizeConfigPaths(config);
174
+ }
175
+ function inferSetupFields(existing, args) {
176
+ const creds = loadCredentialsFile();
177
+ 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();
178
+ 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);
179
+ 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();
180
+ 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();
181
+ return {
182
+ ...apiBaseUrl ? { apiBaseUrl: trimTrailingSlash(apiBaseUrl) } : {},
183
+ ...agentOsId ? { agentOsId } : {},
184
+ ...defaultRepo ? { defaultRepo } : {},
185
+ ...harnessRoot ? { harnessRoot } : {},
186
+ ...typeof args.agentOsSlug === "string" ? { agentOsSlug: args.agentOsSlug } : existing.agentOsSlug ? { agentOsSlug: existing.agentOsSlug } : {}
187
+ };
188
+ }
138
189
  function loadCredentialsFile() {
139
190
  if (!existsSync2(CREDENTIALS_FILE)) return {};
140
191
  try {
@@ -280,7 +331,7 @@ async function mintRunnerCredential(args) {
280
331
  {
281
332
  ok: true,
282
333
  agentOsId,
283
- credentialsPath: CREDENTIALS_FILE,
334
+ credentialsPath: displayUserPath(CREDENTIALS_FILE),
284
335
  tokenPrefix: `${token.slice(0, 12)}\u2026`,
285
336
  note: "Scoped runner token saved; callbacks use X-Kynver-Runner-Token."
286
337
  },
@@ -315,16 +366,12 @@ function parseArgs(argv) {
315
366
  async function runSetup(args) {
316
367
  const existing = loadUserConfig();
317
368
  const maxWorkersRaw = typeof args.maxWorkers === "string" ? args.maxWorkers : typeof args.maxConcurrentWorkers === "string" ? args.maxConcurrentWorkers : void 0;
318
- const config = {
369
+ const config = normalizeConfigPaths({
319
370
  ...existing,
320
- ...typeof args.apiBaseUrl === "string" ? { apiBaseUrl: args.apiBaseUrl } : {},
321
- ...typeof args.agentOsSlug === "string" ? { agentOsSlug: args.agentOsSlug } : {},
322
- ...typeof args.agentOsId === "string" ? { agentOsId: args.agentOsId } : {},
323
- ...typeof args.repo === "string" ? { defaultRepo: args.repo } : {},
324
- ...typeof args.harnessRoot === "string" ? { harnessRoot: args.harnessRoot } : {},
371
+ ...inferSetupFields(existing, args),
325
372
  ...maxWorkersRaw ? { maxConcurrentWorkers: Math.max(1, Math.floor(Number(maxWorkersRaw))) } : {},
326
373
  workerProvider: typeof args.provider === "string" ? args.provider : existing.workerProvider || "claude"
327
- };
374
+ });
328
375
  saveUserConfig(config);
329
376
  let runnerCredentialNote;
330
377
  const apiKey = loadApiKey();
@@ -345,8 +392,8 @@ async function runSetup(args) {
345
392
  JSON.stringify(
346
393
  {
347
394
  ok: true,
348
- configPath: CONFIG_FILE,
349
- config,
395
+ configPath: displayUserPath(CONFIG_FILE),
396
+ config: presentUserConfig(config),
350
397
  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."
351
398
  },
352
399
  null,
@@ -358,11 +405,11 @@ async function runLogin(args) {
358
405
  const apiKey = typeof args.apiKey === "string" ? args.apiKey : process.env.KYNVER_API_KEY;
359
406
  if (!apiKey) failConfig("kynver login requires --api-key or KYNVER_API_KEY");
360
407
  saveApiKey(apiKey);
361
- console.log(JSON.stringify({ ok: true, credentialsPath: CREDENTIALS_FILE }, null, 2));
408
+ console.log(JSON.stringify({ ok: true, credentialsPath: displayUserPath(CREDENTIALS_FILE) }, null, 2));
362
409
  }
363
410
 
364
411
  // src/dispatch.ts
365
- import path16 from "node:path";
412
+ import path17 from "node:path";
366
413
 
367
414
  // src/callback-headers.ts
368
415
  function buildHarnessCallbackHeaders(secret) {
@@ -424,12 +471,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
424
471
  var DEFAULT_MAX_USED_PERCENT = 80;
425
472
  var DEFAULT_HARD_MAX_USED_PERCENT = 90;
426
473
  function observeRunnerDiskGate(input = {}) {
427
- const path32 = input.diskPath?.trim() || "/";
474
+ const path35 = input.diskPath?.trim() || "/";
428
475
  const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
429
476
  const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
430
477
  const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
431
478
  const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
432
- const stats = statfsSync(path32);
479
+ const stats = statfsSync(path35);
433
480
  const freeBytes = Number(stats.bavail) * Number(stats.bsize);
434
481
  const totalBytes = Number(stats.blocks) * Number(stats.bsize);
435
482
  const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
@@ -449,7 +496,7 @@ function observeRunnerDiskGate(input = {}) {
449
496
  }
450
497
  return {
451
498
  ok,
452
- path: path32,
499
+ path: path35,
453
500
  freeBytes,
454
501
  totalBytes,
455
502
  usedPercent,
@@ -464,21 +511,23 @@ function observeRunnerDiskGate(input = {}) {
464
511
  // src/resource-gate.ts
465
512
  import { readFileSync as readFileSync5 } from "node:fs";
466
513
  import os from "node:os";
467
- import path5 from "node:path";
514
+ import path6 from "node:path";
468
515
 
469
516
  // src/run-store.ts
470
517
  import { existsSync as existsSync4, readdirSync as readdirSync2 } from "node:fs";
471
- import path4 from "node:path";
518
+ import path5 from "node:path";
472
519
 
473
520
  // src/paths.ts
474
521
  import { existsSync as existsSync3 } from "node:fs";
475
- import { homedir as homedir2 } from "node:os";
476
- import path3 from "node:path";
477
- var LEGACY_ROOT = path3.join(homedir2(), ".openclaw", "harness");
522
+ import { homedir as homedir3 } from "node:os";
523
+ import path4 from "node:path";
524
+ var LEGACY_ROOT = path4.join(homedir3(), ".openclaw", "harness");
478
525
  function resolveHarnessRoot() {
479
526
  const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
480
- if (env) return path3.resolve(env);
481
- const kynverRoot = path3.join(homedir2(), ".kynver", "harness");
527
+ if (env) return resolveUserPath(env);
528
+ const configured = loadUserConfig().harnessRoot?.trim();
529
+ if (configured) return resolveUserPath(configured);
530
+ const kynverRoot = path4.join(homedir3(), ".kynver", "harness");
482
531
  if (existsSync3(kynverRoot)) return kynverRoot;
483
532
  if (existsSync3(LEGACY_ROOT)) return LEGACY_ROOT;
484
533
  return kynverRoot;
@@ -487,12 +536,12 @@ function getHarnessPaths() {
487
536
  const harnessRoot = resolveHarnessRoot();
488
537
  return {
489
538
  harnessRoot,
490
- runsDir: path3.join(harnessRoot, "runs"),
491
- worktreesDir: path3.join(harnessRoot, "worktrees")
539
+ runsDir: path4.join(harnessRoot, "runs"),
540
+ worktreesDir: path4.join(harnessRoot, "worktrees")
492
541
  };
493
542
  }
494
543
  function runDir(runsDir, id) {
495
- return path3.join(runsDir, safeSlug(id));
544
+ return path4.join(runsDir, safeSlug(id));
496
545
  }
497
546
 
498
547
  // src/run-store.ts
@@ -501,7 +550,7 @@ function getPaths() {
501
550
  }
502
551
  function loadRun(id) {
503
552
  const { runsDir } = getPaths();
504
- return readJson(path4.join(runDir(runsDir, safeSlug(id)), "run.json"));
553
+ return readJson(path5.join(runDir(runsDir, safeSlug(id)), "run.json"));
505
554
  }
506
555
  function listRunRecords() {
507
556
  const { runsDir } = getPaths();
@@ -510,7 +559,7 @@ function listRunRecords() {
510
559
  for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
511
560
  if (!entry.isDirectory()) continue;
512
561
  const run = readJson(
513
- path4.join(runsDir, entry.name, "run.json"),
562
+ path5.join(runsDir, entry.name, "run.json"),
514
563
  void 0
515
564
  );
516
565
  if (run?.id) runs.push(run);
@@ -520,16 +569,16 @@ function listRunRecords() {
520
569
  function loadWorker(runId, name) {
521
570
  const { runsDir } = getPaths();
522
571
  return readJson(
523
- path4.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
572
+ path5.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
524
573
  );
525
574
  }
526
575
  function saveRun(run) {
527
576
  const { runsDir } = getPaths();
528
- writeJson(path4.join(runDir(runsDir, run.id), "run.json"), run);
577
+ writeJson(path5.join(runDir(runsDir, run.id), "run.json"), run);
529
578
  }
530
579
  function saveWorker(runId, worker) {
531
580
  const { runsDir } = getPaths();
532
- writeJson(path4.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
581
+ writeJson(path5.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
533
582
  }
534
583
  function runDirectory(id) {
535
584
  const { runsDir } = getPaths();
@@ -1282,7 +1331,7 @@ function countActiveWorkersForRun(run) {
1282
1331
  let active = 0;
1283
1332
  for (const name of Object.keys(run.workers || {})) {
1284
1333
  const worker = readJson(
1285
- path5.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1334
+ path6.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1286
1335
  void 0
1287
1336
  );
1288
1337
  if (!worker || !isActiveHarnessWorker(worker)) continue;
@@ -1490,6 +1539,7 @@ var claudeProvider = {
1490
1539
  const model = preflight.model;
1491
1540
  const stdoutFd = openSync(opts.stdoutPath, "a");
1492
1541
  const stderrFd = openSync(opts.stderrPath, "a");
1542
+ const stdio = ["ignore", stdoutFd, stderrFd];
1493
1543
  const child = spawn(
1494
1544
  "claude",
1495
1545
  [
@@ -1507,7 +1557,7 @@ var claudeProvider = {
1507
1557
  hiddenSpawnOptions({
1508
1558
  cwd: opts.worktreePath,
1509
1559
  detached: true,
1510
- stdio: ["ignore", stdoutFd, stderrFd],
1560
+ stdio,
1511
1561
  env: scrubClaudeEnv(process.env)
1512
1562
  })
1513
1563
  );
@@ -1666,10 +1716,10 @@ function readHarnessRetryLimits() {
1666
1716
  }
1667
1717
 
1668
1718
  // src/lease-renewal.ts
1669
- import path6 from "node:path";
1719
+ import path7 from "node:path";
1670
1720
  function workerRecord(runId, name) {
1671
1721
  return readJson(
1672
- path6.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
1722
+ path7.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
1673
1723
  void 0
1674
1724
  );
1675
1725
  }
@@ -1736,7 +1786,7 @@ function hasLiveWorkerForTask(runId, taskId) {
1736
1786
 
1737
1787
  // src/supervisor.ts
1738
1788
  import { existsSync as existsSync10, mkdirSync as mkdirSync3 } from "node:fs";
1739
- import path12 from "node:path";
1789
+ import path13 from "node:path";
1740
1790
 
1741
1791
  // src/prompt.ts
1742
1792
  function buildPrompt(input) {
@@ -1755,6 +1805,16 @@ function buildPrompt(input) {
1755
1805
  "- After implementation: wait for report_reviewer then deep_reviewer confirmation (via MCP/session agents) before follow-up rows close.",
1756
1806
  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."
1757
1807
  ];
1808
+ const mergeGateLines = compact ? [
1809
+ "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)."
1810
+ ] : [
1811
+ "GitHub Actions merge-gate cost control (Kynver/Hermes PRs):",
1812
+ "- Prefer local cached package verification (`node scripts/verify-pr-local.mjs --emit-json`) and Vercel preview evidence before GitHub Actions.",
1813
+ "- 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.",
1814
+ "- Record evidence: POST `/api/agent-os/by-id/<agentOsId>/pr-merge-gate/refresh` with prUrl + local/vercel payloads.",
1815
+ "- Request the final Actions run only when local + Vercel are green: POST `.../pr-merge-gate/request-run` (applies `merge-gate` label).",
1816
+ "- Empty failed Actions jobs (no runner/steps/logs) are infra/quota \u2014 do not enter repair loops; escalate to operator."
1817
+ ];
1758
1818
  const planArtifactLines = compact ? [
1759
1819
  "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."
1760
1820
  ] : [
@@ -1776,10 +1836,14 @@ function buildPrompt(input) {
1776
1836
  "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.",
1777
1837
  "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.",
1778
1838
  "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.",
1839
+ "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.",
1779
1840
  "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.",
1841
+ "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.",
1780
1842
  "",
1781
1843
  ...progressLines,
1782
1844
  "",
1845
+ ...mergeGateLines,
1846
+ "",
1783
1847
  ...planArtifactLines,
1784
1848
  "",
1785
1849
  ...input.personaMarkdown?.trim() ? [input.personaMarkdown.trim(), ""] : [],
@@ -1792,11 +1856,11 @@ function buildPrompt(input) {
1792
1856
  // src/providers/cursor.ts
1793
1857
  import { closeSync as closeSync2, existsSync as existsSync8, openSync as openSync2 } from "node:fs";
1794
1858
  import { spawn as spawn2 } from "node:child_process";
1795
- import path8 from "node:path";
1859
+ import path9 from "node:path";
1796
1860
 
1797
1861
  // src/providers/cursor-windows.ts
1798
1862
  import { existsSync as existsSync7, readdirSync as readdirSync3 } from "node:fs";
1799
- import path7 from "node:path";
1863
+ import path8 from "node:path";
1800
1864
  var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
1801
1865
  function parseCursorVersionSortKey(versionName) {
1802
1866
  const datePart = versionName.split("-")[0];
@@ -1807,7 +1871,7 @@ function parseCursorVersionSortKey(versionName) {
1807
1871
  return Number(`${year}${month.padStart(2, "0")}${day.padStart(2, "0")}`);
1808
1872
  }
1809
1873
  function pickLatestCursorVersionDir(agentRoot) {
1810
- const versionsRoot = path7.join(agentRoot, "versions");
1874
+ const versionsRoot = path8.join(agentRoot, "versions");
1811
1875
  if (!existsSync7(versionsRoot)) return null;
1812
1876
  let bestDir = null;
1813
1877
  let bestKey = -1;
@@ -1816,21 +1880,21 @@ function pickLatestCursorVersionDir(agentRoot) {
1816
1880
  const key = parseCursorVersionSortKey(entry.name);
1817
1881
  if (key == null || key <= bestKey) continue;
1818
1882
  bestKey = key;
1819
- bestDir = path7.join(versionsRoot, entry.name);
1883
+ bestDir = path8.join(versionsRoot, entry.name);
1820
1884
  }
1821
1885
  return bestDir;
1822
1886
  }
1823
1887
  function resolveWindowsCursorBundled(agentRoot) {
1824
- const root = agentRoot?.trim() || path7.join(process.env.LOCALAPPDATA || "", "cursor-agent");
1825
- const directNode = path7.join(root, "node.exe");
1826
- const directIndex = path7.join(root, "index.js");
1888
+ const root = agentRoot?.trim() || path8.join(process.env.LOCALAPPDATA || "", "cursor-agent");
1889
+ const directNode = path8.join(root, "node.exe");
1890
+ const directIndex = path8.join(root, "index.js");
1827
1891
  if (existsSync7(directNode) && existsSync7(directIndex)) {
1828
1892
  return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
1829
1893
  }
1830
1894
  const versionDir = pickLatestCursorVersionDir(root);
1831
1895
  if (!versionDir) return null;
1832
- const nodeExe = path7.join(versionDir, "node.exe");
1833
- const indexJs = path7.join(versionDir, "index.js");
1896
+ const nodeExe = path8.join(versionDir, "node.exe");
1897
+ const indexJs = path8.join(versionDir, "index.js");
1834
1898
  if (!existsSync7(nodeExe) || !existsSync7(indexJs)) return null;
1835
1899
  return { nodeExe, indexJs, versionDir };
1836
1900
  }
@@ -1849,13 +1913,13 @@ function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
1849
1913
  function resolveCursorSpawn(agentBin) {
1850
1914
  if (process.platform === "win32") {
1851
1915
  const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
1852
- const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync8(path8.join(path8.dirname(agentBin), "index.js"));
1916
+ const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync8(path9.join(path9.dirname(agentBin), "index.js"));
1853
1917
  const isDefaultShim = agentBin === "agent";
1854
1918
  if (isCursorWrapper || isBundledNode || isDefaultShim) {
1855
- const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path8.dirname(agentBin)) : isBundledNode ? {
1919
+ const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path9.dirname(agentBin)) : isBundledNode ? {
1856
1920
  nodeExe: agentBin,
1857
- indexJs: path8.join(path8.dirname(agentBin), "index.js"),
1858
- versionDir: path8.dirname(agentBin)
1921
+ indexJs: path9.join(path9.dirname(agentBin), "index.js"),
1922
+ versionDir: path9.dirname(agentBin)
1859
1923
  } : resolveWindowsCursorBundled();
1860
1924
  if (bundled) {
1861
1925
  return bundledSpawnTarget(bundled.nodeExe, bundled.indexJs, bundled.versionDir);
@@ -1875,7 +1939,7 @@ function resolveAgentBin() {
1875
1939
  process.env.KYNVER_CURSOR_AGENT_ROOT?.trim() || void 0
1876
1940
  );
1877
1941
  if (bundled) return bundled.nodeExe;
1878
- const localAgent = path8.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
1942
+ const localAgent = path9.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
1879
1943
  if (existsSync8(localAgent)) return localAgent;
1880
1944
  }
1881
1945
  return "agent";
@@ -1885,7 +1949,7 @@ function cursorWorkerEnv(agentBin, spawnTarget) {
1885
1949
  ...process.env,
1886
1950
  CI: "1",
1887
1951
  NO_COLOR: "1",
1888
- ...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path8.basename(agentBin) || "agent.cmd" } : {}
1952
+ ...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path9.basename(agentBin) || "agent.cmd" } : {}
1889
1953
  };
1890
1954
  }
1891
1955
  var cursorProvider = {
@@ -1902,6 +1966,7 @@ var cursorProvider = {
1902
1966
  const model = preflight.model;
1903
1967
  const stdoutFd = openSync2(opts.stdoutPath, "a");
1904
1968
  const stderrFd = openSync2(opts.stderrPath, "a");
1969
+ const stdio = ["ignore", stdoutFd, stderrFd];
1905
1970
  const agentBin = resolveAgentBin();
1906
1971
  const spawnTarget = resolveCursorSpawn(agentBin);
1907
1972
  const child = spawn2(
@@ -1924,7 +1989,7 @@ var cursorProvider = {
1924
1989
  cwd: opts.worktreePath,
1925
1990
  detached: spawnTarget.detached,
1926
1991
  shell: spawnTarget.shell,
1927
- stdio: ["ignore", stdoutFd, stderrFd],
1992
+ stdio,
1928
1993
  env: cursorWorkerEnv(agentBin, spawnTarget)
1929
1994
  })
1930
1995
  );
@@ -1959,7 +2024,7 @@ function resolveWorkerProvider(name) {
1959
2024
  // src/auto-complete.ts
1960
2025
  import { spawn as spawn3 } from "node:child_process";
1961
2026
  import { existsSync as existsSync9, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
1962
- import path11 from "node:path";
2027
+ import path12 from "node:path";
1963
2028
  import { fileURLToPath } from "node:url";
1964
2029
 
1965
2030
  // src/completion-ack.ts
@@ -1976,7 +2041,7 @@ function persistCompletionAck(worker, runId, fields) {
1976
2041
  }
1977
2042
 
1978
2043
  // src/worker-ops.ts
1979
- import path10 from "node:path";
2044
+ import path11 from "node:path";
1980
2045
 
1981
2046
  // src/pr-handoff/pr-handoff-assess.ts
1982
2047
  var REVIEW_LANE_RULE = /^(lane:)?(review|deep_review|planning|landing)(:|$)/i;
@@ -2313,7 +2378,7 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
2313
2378
  }
2314
2379
 
2315
2380
  // src/worker-lifecycle.ts
2316
- import path9 from "node:path";
2381
+ import path10 from "node:path";
2317
2382
  var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
2318
2383
  "awaiting_review",
2319
2384
  "blocked",
@@ -2369,7 +2434,7 @@ function syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick) {
2369
2434
  const synced = [];
2370
2435
  for (const name of Object.keys(run.workers || {})) {
2371
2436
  const worker = readJson(
2372
- path9.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2437
+ path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2373
2438
  void 0
2374
2439
  );
2375
2440
  if (!worker?.taskId || isCompletionAcknowledged(worker)) continue;
@@ -2606,7 +2671,7 @@ function workerStatus(args) {
2606
2671
  const worker = loadWorker(String(args.run), String(args.name));
2607
2672
  const run = loadRun(worker.runId);
2608
2673
  const status = computeWorkerStatus(worker, workerStatusOptions(run));
2609
- writeJson(path10.join(worker.workerDir, "last-status.json"), status);
2674
+ writeJson(path11.join(worker.workerDir, "last-status.json"), status);
2610
2675
  console.log(JSON.stringify(status, null, 2));
2611
2676
  }
2612
2677
  function buildRunBoard(runId) {
@@ -2614,7 +2679,7 @@ function buildRunBoard(runId) {
2614
2679
  const names = Object.keys(run.workers || {});
2615
2680
  const workers = names.map((name) => {
2616
2681
  const worker = readJson(
2617
- path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2682
+ path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2618
2683
  void 0
2619
2684
  );
2620
2685
  if (!worker) {
@@ -2642,16 +2707,17 @@ function buildRunBoard(runId) {
2642
2707
  const completionRouteStatus = asString(completionResponse?.status);
2643
2708
  const completionWarnings = Array.isArray(completionResponse?.warnings) ? completionResponse.warnings.filter((w) => typeof w === "string" && w.trim().length > 0) : [];
2644
2709
  const prUrl = asString(completionTask?.prUrl) ?? asString(completionResponse?.prUrl);
2710
+ const completionReportedAt = asString(worker.completionReportedAt);
2645
2711
  const lifecycleStage = deriveLifecycleStage({
2646
2712
  finished: isFinishedWorkerStatus(status),
2647
2713
  completionBlocker,
2648
2714
  completionOutcome,
2649
- completionReportedAt: worker.completionReportedAt ?? null
2715
+ completionReportedAt
2650
2716
  });
2651
2717
  const nextAction = deriveNextAction({
2652
2718
  completionBlocker,
2653
2719
  completionOutcome,
2654
- completionReportedAt: worker.completionReportedAt ?? null,
2720
+ completionReportedAt,
2655
2721
  finished: isFinishedWorkerStatus(status)
2656
2722
  });
2657
2723
  const handoffState = deriveHandoffState({
@@ -2697,7 +2763,7 @@ function buildRunBoard(runId) {
2697
2763
  gitAncestry: status.gitAncestry,
2698
2764
  finalResult: status.finalResult,
2699
2765
  lifecycleStage,
2700
- completionReportedAt: worker.completionReportedAt ?? null,
2766
+ completionReportedAt,
2701
2767
  completionOutcome: worker.completionOutcome ?? null,
2702
2768
  completionRouteStatus,
2703
2769
  completionRouteOutcome: completionOutcome,
@@ -2724,7 +2790,7 @@ function buildRunBoard(runId) {
2724
2790
  needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
2725
2791
  workers
2726
2792
  };
2727
- writeJson(path10.join(runDirectory(run.id), "last-board.json"), board);
2793
+ writeJson(path11.join(runDirectory(run.id), "last-board.json"), board);
2728
2794
  return board;
2729
2795
  }
2730
2796
  async function publishHarnessBoardSnapshot(args, source) {
@@ -2913,12 +2979,12 @@ async function autoCompleteWorkerCli(raw) {
2913
2979
  }
2914
2980
  }
2915
2981
  function resolveDefaultCliPath() {
2916
- return path11.join(fileURLToPath(new URL(".", import.meta.url)), "cli.js");
2982
+ return path12.join(fileURLToPath(new URL(".", import.meta.url)), "cli.js");
2917
2983
  }
2918
2984
  function spawnCompletionSidecar(opts) {
2919
2985
  const cliPath = opts.cliPath ?? resolveDefaultCliPath();
2920
2986
  if (!existsSync9(cliPath)) return void 0;
2921
- const logPath = path11.join(opts.workerDir, "auto-complete.log");
2987
+ const logPath = path12.join(opts.workerDir, "auto-complete.log");
2922
2988
  let logFd;
2923
2989
  try {
2924
2990
  logFd = openSync3(logPath, "a");
@@ -3000,16 +3066,16 @@ function spawnWorkerProcess(run, opts) {
3000
3066
  launchModel = preflight.model;
3001
3067
  }
3002
3068
  const { worktreesDir } = getPaths();
3003
- const workerDir = path12.join(runDirectory(run.id), "workers", name);
3069
+ const workerDir = path13.join(runDirectory(run.id), "workers", name);
3004
3070
  mkdirSync3(workerDir, { recursive: true });
3005
- const worktreePath = path12.join(worktreesDir, run.id, name);
3071
+ const worktreePath = path13.join(worktreesDir, run.id, name);
3006
3072
  const branch = opts.branch || `agent/${run.id}/${name}`;
3007
3073
  if (existsSync10(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
3008
3074
  git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
3009
3075
  git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
3010
- const stdoutPath = path12.join(workerDir, "stdout.jsonl");
3011
- const stderrPath = path12.join(workerDir, "stderr.log");
3012
- const heartbeatPath = path12.join(workerDir, "heartbeat.jsonl");
3076
+ const stdoutPath = path13.join(workerDir, "stdout.jsonl");
3077
+ const stderrPath = path13.join(workerDir, "stderr.log");
3078
+ const heartbeatPath = path13.join(workerDir, "heartbeat.jsonl");
3013
3079
  const prompt = buildPrompt({
3014
3080
  task: opts.task,
3015
3081
  ownedPaths: opts.ownedPaths || [],
@@ -3070,7 +3136,7 @@ function spawnWorkerProcess(run, opts) {
3070
3136
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
3071
3137
  };
3072
3138
  saveWorker(run.id, worker);
3073
- run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path12.join(workerDir, "worker.json") } };
3139
+ run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path13.join(workerDir, "worker.json") } };
3074
3140
  run.status = "running";
3075
3141
  saveRun(run);
3076
3142
  if (worker.agentOsId && worker.taskId) {
@@ -3362,18 +3428,18 @@ function buildPlanPersistIdempotencyKey(input) {
3362
3428
 
3363
3429
  // src/plan-persist/paths.ts
3364
3430
  import { mkdirSync as mkdirSync4 } from "node:fs";
3365
- import { homedir as homedir3 } from "node:os";
3366
- import path13 from "node:path";
3431
+ import { homedir as homedir4 } from "node:os";
3432
+ import path14 from "node:path";
3367
3433
  function resolveKynverStateRoot() {
3368
3434
  const env = process.env.KYNVER_STATE_ROOT;
3369
- if (env) return path13.resolve(env);
3370
- return path13.join(homedir3(), ".kynver", "state");
3435
+ if (env) return path14.resolve(env);
3436
+ return path14.join(homedir4(), ".kynver", "state");
3371
3437
  }
3372
3438
  function planOutboxDir() {
3373
- return path13.join(resolveKynverStateRoot(), "plan-outbox");
3439
+ return path14.join(resolveKynverStateRoot(), "plan-outbox");
3374
3440
  }
3375
3441
  function planOutboxArchiveDir() {
3376
- return path13.join(resolveKynverStateRoot(), "plan-outbox-archive");
3442
+ return path14.join(resolveKynverStateRoot(), "plan-outbox-archive");
3377
3443
  }
3378
3444
  function ensurePlanOutboxDirs() {
3379
3445
  const outboxDir = planOutboxDir();
@@ -3384,8 +3450,8 @@ function ensurePlanOutboxDirs() {
3384
3450
  }
3385
3451
  function isTmpOnlyPath(filePath) {
3386
3452
  if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
3387
- const resolved = path13.resolve(filePath);
3388
- return resolved.startsWith("/tmp/") || resolved.startsWith(path13.join("/var", "folders"));
3453
+ const resolved = path14.resolve(filePath);
3454
+ return resolved.startsWith("/tmp/") || resolved.startsWith(path14.join("/var", "folders"));
3389
3455
  }
3390
3456
 
3391
3457
  // src/plan-persist/outbox-store.ts
@@ -3397,7 +3463,7 @@ import {
3397
3463
  writeFileSync as writeFileSync3,
3398
3464
  unlinkSync
3399
3465
  } from "node:fs";
3400
- import path14 from "node:path";
3466
+ import path15 from "node:path";
3401
3467
  import { randomUUID } from "node:crypto";
3402
3468
  var DEFAULT_MAX_RETRIES = 12;
3403
3469
  function listOutboxItems() {
@@ -3405,7 +3471,7 @@ function listOutboxItems() {
3405
3471
  const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
3406
3472
  const items = [];
3407
3473
  for (const file of files) {
3408
- const item = readOutboxItem(path14.join(outboxDir, file));
3474
+ const item = readOutboxItem(path15.join(outboxDir, file));
3409
3475
  if (item && item.queueStatus === "queued") items.push(item);
3410
3476
  }
3411
3477
  return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
@@ -3426,7 +3492,7 @@ function readOutboxItem(jsonPath) {
3426
3492
  }
3427
3493
  function readOutboxBody(item) {
3428
3494
  const { outboxDir } = ensurePlanOutboxDirs();
3429
- const bodyFile = path14.join(outboxDir, item.bodyPath);
3495
+ const bodyFile = path15.join(outboxDir, item.bodyPath);
3430
3496
  return readFileSync6(bodyFile, "utf8");
3431
3497
  }
3432
3498
  function writeOutboxItem(input, opts) {
@@ -3434,8 +3500,8 @@ function writeOutboxItem(input, opts) {
3434
3500
  const now = (/* @__PURE__ */ new Date()).toISOString();
3435
3501
  const id = opts.existing?.id ?? randomUUID();
3436
3502
  const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
3437
- const jsonPath = path14.join(outboxDir, `${id}.json`);
3438
- const bodyFile = path14.join(outboxDir, bodyPath);
3503
+ const jsonPath = path15.join(outboxDir, `${id}.json`);
3504
+ const bodyFile = path15.join(outboxDir, bodyPath);
3439
3505
  if (!opts.existing) {
3440
3506
  writeFileSync3(bodyFile, input.body, "utf8");
3441
3507
  }
@@ -3471,24 +3537,24 @@ function writeOutboxItem(input, opts) {
3471
3537
  }
3472
3538
  function saveOutboxItem(item) {
3473
3539
  const { outboxDir } = ensurePlanOutboxDirs();
3474
- const jsonPath = path14.join(outboxDir, `${item.id}.json`);
3540
+ const jsonPath = path15.join(outboxDir, `${item.id}.json`);
3475
3541
  writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
3476
3542
  `, { mode: 384 });
3477
3543
  }
3478
3544
  function archiveOutboxItem(item) {
3479
3545
  const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
3480
- const jsonSrc = path14.join(outboxDir, `${item.id}.json`);
3481
- const bodySrc = path14.join(outboxDir, item.bodyPath);
3482
- const jsonDst = path14.join(archiveDir, `${item.id}.json`);
3483
- const bodyDst = path14.join(archiveDir, item.bodyPath);
3546
+ const jsonSrc = path15.join(outboxDir, `${item.id}.json`);
3547
+ const bodySrc = path15.join(outboxDir, item.bodyPath);
3548
+ const jsonDst = path15.join(archiveDir, `${item.id}.json`);
3549
+ const bodyDst = path15.join(archiveDir, item.bodyPath);
3484
3550
  if (existsSync12(jsonSrc)) renameSync(jsonSrc, jsonDst);
3485
3551
  if (existsSync12(bodySrc)) renameSync(bodySrc, bodyDst);
3486
3552
  }
3487
3553
  function outboxItemPaths(item) {
3488
3554
  const { outboxDir } = ensurePlanOutboxDirs();
3489
3555
  return {
3490
- jsonPath: path14.join(outboxDir, `${item.id}.json`),
3491
- bodyPath: path14.join(outboxDir, item.bodyPath)
3556
+ jsonPath: path15.join(outboxDir, `${item.id}.json`),
3557
+ bodyPath: path15.join(outboxDir, item.bodyPath)
3492
3558
  };
3493
3559
  }
3494
3560
  function outboxInputFromItem(item, body) {
@@ -3674,7 +3740,7 @@ function markOutboxFailed(item, message) {
3674
3740
  }
3675
3741
 
3676
3742
  // src/plan-persist/drain.ts
3677
- import path15 from "node:path";
3743
+ import path16 from "node:path";
3678
3744
  async function drainPlanOutbox(opts = {}, deps = {}) {
3679
3745
  const items = listOutboxItems().filter(
3680
3746
  (item) => opts.outboxId ? item.id === opts.outboxId : true
@@ -3708,7 +3774,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
3708
3774
  return result;
3709
3775
  }
3710
3776
  function loadOutboxById(outboxId) {
3711
- const jsonPath = path15.join(planOutboxDir(), `${outboxId}.json`);
3777
+ const jsonPath = path16.join(planOutboxDir(), `${outboxId}.json`);
3712
3778
  return readOutboxItem(jsonPath);
3713
3779
  }
3714
3780
 
@@ -3808,7 +3874,7 @@ async function dispatchRun(args) {
3808
3874
  const activeHarnessWorkers = [];
3809
3875
  for (const name of Object.keys(run.workers || {})) {
3810
3876
  const worker = readJson(
3811
- path16.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
3877
+ path17.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
3812
3878
  void 0
3813
3879
  );
3814
3880
  if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
@@ -4012,7 +4078,7 @@ async function dispatchRun(args) {
4012
4078
  }
4013
4079
 
4014
4080
  // src/sweep.ts
4015
- import path17 from "node:path";
4081
+ import path18 from "node:path";
4016
4082
  async function sweepRun(args) {
4017
4083
  const pipeline = args.pipeline === true || args.pipeline === "true";
4018
4084
  try {
@@ -4025,7 +4091,7 @@ async function sweepRun(args) {
4025
4091
  const releasedLocalOrphans = [];
4026
4092
  for (const name of Object.keys(run.workers || {})) {
4027
4093
  const worker = readJson(
4028
- path17.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4094
+ path18.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4029
4095
  void 0
4030
4096
  );
4031
4097
  if (!worker || !worker.dispatched || !worker.taskId) continue;
@@ -4069,10 +4135,10 @@ async function sweepRun(args) {
4069
4135
 
4070
4136
  // src/worktree.ts
4071
4137
  import { existsSync as existsSync13, mkdirSync as mkdirSync5 } from "node:fs";
4072
- import path19 from "node:path";
4138
+ import path20 from "node:path";
4073
4139
 
4074
4140
  // src/validate.ts
4075
- import path18 from "node:path";
4141
+ import path19 from "node:path";
4076
4142
  var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
4077
4143
  function validateRunId(runId) {
4078
4144
  const trimmed = runId.trim();
@@ -4080,7 +4146,7 @@ function validateRunId(runId) {
4080
4146
  return trimmed;
4081
4147
  }
4082
4148
  function validateRepo(repo) {
4083
- const resolved = path18.resolve(repo);
4149
+ const resolved = path19.resolve(repo);
4084
4150
  if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
4085
4151
  return resolved;
4086
4152
  }
@@ -4105,12 +4171,12 @@ function createRun(args) {
4105
4171
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4106
4172
  workers: {}
4107
4173
  };
4108
- writeJson(path19.join(dir, "run.json"), run);
4174
+ writeJson(path20.join(dir, "run.json"), run);
4109
4175
  console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
4110
4176
  }
4111
4177
  function listRuns() {
4112
4178
  const { runsDir } = getPaths();
4113
- const rows = listRunIds(runsDir).map((id) => readJson(path19.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
4179
+ const rows = listRunIds(runsDir).map((id) => readJson(path20.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
4114
4180
  id: run.id,
4115
4181
  name: run.name,
4116
4182
  status: run.status,
@@ -4125,7 +4191,7 @@ function failExists(message) {
4125
4191
  }
4126
4192
 
4127
4193
  // src/pipeline-tick.ts
4128
- import path28 from "node:path";
4194
+ import path29 from "node:path";
4129
4195
 
4130
4196
  // src/pipeline-dispatch.ts
4131
4197
  var RESERVED_REVIEW_STARTS = 1;
@@ -4179,10 +4245,10 @@ async function runPipelineDispatch(args, slots) {
4179
4245
  }
4180
4246
 
4181
4247
  // src/stale-reconcile.ts
4182
- import path21 from "node:path";
4248
+ import path22 from "node:path";
4183
4249
 
4184
4250
  // src/finalize.ts
4185
- import path20 from "node:path";
4251
+ import path21 from "node:path";
4186
4252
  var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
4187
4253
  function terminalStatusFor(run) {
4188
4254
  const names = Object.keys(run.workers || {});
@@ -4193,7 +4259,7 @@ function terminalStatusFor(run) {
4193
4259
  let anyLandingBlocked = false;
4194
4260
  for (const name of names) {
4195
4261
  const worker = readJson(
4196
- path20.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4262
+ path21.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4197
4263
  void 0
4198
4264
  );
4199
4265
  if (!worker) continue;
@@ -4245,7 +4311,7 @@ function reconcileStaleWorkers() {
4245
4311
  const now = Date.now();
4246
4312
  for (const run of listRunRecords()) {
4247
4313
  for (const name of Object.keys(run.workers || {})) {
4248
- const workerPath = path21.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
4314
+ const workerPath = path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
4249
4315
  const worker = readJson(workerPath, void 0);
4250
4316
  if (!worker || worker.status !== "running") {
4251
4317
  outcomes.push({
@@ -4339,7 +4405,7 @@ function reconcileRunsCli() {
4339
4405
  }
4340
4406
 
4341
4407
  // src/plan-progress-daemon-sync.ts
4342
- import path22 from "node:path";
4408
+ import path23 from "node:path";
4343
4409
 
4344
4410
  // src/plan-progress-sync.ts
4345
4411
  async function syncPlanProgress(args) {
@@ -4363,7 +4429,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
4363
4429
  const outcomes = [];
4364
4430
  for (const name of Object.keys(run.workers || {})) {
4365
4431
  const worker = readJson(
4366
- path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4432
+ path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4367
4433
  void 0
4368
4434
  );
4369
4435
  if (!worker?.dispatched || !worker.taskId) continue;
@@ -4412,7 +4478,7 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
4412
4478
  }
4413
4479
 
4414
4480
  // src/cleanup.ts
4415
- import path27 from "node:path";
4481
+ import path28 from "node:path";
4416
4482
 
4417
4483
  // src/cleanup-types.ts
4418
4484
  var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
@@ -4484,11 +4550,11 @@ function skipNodeModulesRemoval(input) {
4484
4550
 
4485
4551
  // src/cleanup-execute.ts
4486
4552
  import { existsSync as existsSync15, rmSync } from "node:fs";
4487
- import path24 from "node:path";
4553
+ import path25 from "node:path";
4488
4554
 
4489
4555
  // src/cleanup-dir-size.ts
4490
4556
  import { existsSync as existsSync14, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
4491
- import path23 from "node:path";
4557
+ import path24 from "node:path";
4492
4558
  function directorySizeBytes(root, maxEntries = 5e4) {
4493
4559
  if (!existsSync14(root)) return 0;
4494
4560
  let total = 0;
@@ -4504,7 +4570,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
4504
4570
  }
4505
4571
  for (const name of entries) {
4506
4572
  if (seen++ > maxEntries) return null;
4507
- const full = path23.join(current, name);
4573
+ const full = path24.join(current, name);
4508
4574
  let st;
4509
4575
  try {
4510
4576
  st = statSync2(full);
@@ -4588,20 +4654,20 @@ function removeWorktree(candidate, execute) {
4588
4654
  }
4589
4655
  }
4590
4656
  function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
4591
- const resolved = path24.resolve(targetPath);
4592
- const nm = resolved.endsWith(`${path24.sep}node_modules`) ? resolved : null;
4657
+ const resolved = path25.resolve(targetPath);
4658
+ const nm = resolved.endsWith(`${path25.sep}node_modules`) ? resolved : null;
4593
4659
  if (!nm) return "path_outside_harness";
4594
- const rel = path24.relative(worktreesDir, nm);
4595
- if (rel.startsWith("..") || path24.isAbsolute(rel)) return "path_outside_harness";
4596
- const parts = rel.split(path24.sep);
4660
+ const rel = path25.relative(worktreesDir, nm);
4661
+ if (rel.startsWith("..") || path25.isAbsolute(rel)) return "path_outside_harness";
4662
+ const parts = rel.split(path25.sep);
4597
4663
  if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
4598
- if (!resolved.startsWith(path24.resolve(harnessRoot))) return "path_outside_harness";
4664
+ if (!resolved.startsWith(path25.resolve(harnessRoot))) return "path_outside_harness";
4599
4665
  return null;
4600
4666
  }
4601
4667
 
4602
4668
  // src/cleanup-scan.ts
4603
4669
  import { existsSync as existsSync16, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
4604
- import path25 from "node:path";
4670
+ import path26 from "node:path";
4605
4671
  function pathAgeMs(target, now) {
4606
4672
  try {
4607
4673
  const mtime = statSync3(target).mtimeMs;
@@ -4611,17 +4677,17 @@ function pathAgeMs(target, now) {
4611
4677
  }
4612
4678
  }
4613
4679
  function isPathInside(child, parent) {
4614
- const rel = path25.relative(parent, child);
4615
- return rel === "" || !rel.startsWith("..") && !path25.isAbsolute(rel);
4680
+ const rel = path26.relative(parent, child);
4681
+ return rel === "" || !rel.startsWith("..") && !path26.isAbsolute(rel);
4616
4682
  }
4617
4683
  function scanNodeModulesCandidates(opts) {
4618
4684
  const candidates = [];
4619
4685
  const seen = /* @__PURE__ */ new Set();
4620
4686
  for (const entry of opts.index.values()) {
4621
4687
  if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
4622
- const nm = path25.join(entry.worktreePath, "node_modules");
4688
+ const nm = path26.join(entry.worktreePath, "node_modules");
4623
4689
  if (!existsSync16(nm)) continue;
4624
- const resolved = path25.resolve(nm);
4690
+ const resolved = path26.resolve(nm);
4625
4691
  if (seen.has(resolved)) continue;
4626
4692
  seen.add(resolved);
4627
4693
  candidates.push({
@@ -4637,13 +4703,13 @@ function scanNodeModulesCandidates(opts) {
4637
4703
  if (!opts.includeOrphans || !existsSync16(opts.worktreesDir)) return candidates;
4638
4704
  for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
4639
4705
  if (!runEntry.isDirectory()) continue;
4640
- const runPath = path25.join(opts.worktreesDir, runEntry.name);
4706
+ const runPath = path26.join(opts.worktreesDir, runEntry.name);
4641
4707
  for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
4642
4708
  if (!workerEntry.isDirectory()) continue;
4643
- const worktreePath = path25.join(runPath, workerEntry.name);
4644
- const nm = path25.join(worktreePath, "node_modules");
4709
+ const worktreePath = path26.join(runPath, workerEntry.name);
4710
+ const nm = path26.join(worktreePath, "node_modules");
4645
4711
  if (!existsSync16(nm)) continue;
4646
- const resolved = path25.resolve(nm);
4712
+ const resolved = path26.resolve(nm);
4647
4713
  if (seen.has(resolved)) continue;
4648
4714
  if (!isPathInside(resolved, opts.harnessRoot)) continue;
4649
4715
  seen.add(resolved);
@@ -4683,17 +4749,17 @@ function scanWorktreeCandidates(opts) {
4683
4749
  }
4684
4750
 
4685
4751
  // src/cleanup-worktree-index.ts
4686
- import path26 from "node:path";
4752
+ import path27 from "node:path";
4687
4753
  function buildWorktreeIndex() {
4688
4754
  const index = /* @__PURE__ */ new Map();
4689
4755
  for (const run of listRunRecords()) {
4690
4756
  for (const name of Object.keys(run.workers || {})) {
4691
- const workerPath = path26.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
4757
+ const workerPath = path27.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
4692
4758
  const worker = readJson(workerPath, void 0);
4693
4759
  if (!worker?.worktreePath) continue;
4694
4760
  const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
4695
- index.set(path26.resolve(worker.worktreePath), {
4696
- worktreePath: path26.resolve(worker.worktreePath),
4761
+ index.set(path27.resolve(worker.worktreePath), {
4762
+ worktreePath: path27.resolve(worker.worktreePath),
4697
4763
  runId: run.id,
4698
4764
  workerName: name,
4699
4765
  run,
@@ -4707,8 +4773,8 @@ function buildWorktreeIndex() {
4707
4773
 
4708
4774
  // src/cleanup.ts
4709
4775
  function resolveOptions(options = {}) {
4710
- const harnessRoot = options.harnessRoot ? path27.resolve(options.harnessRoot) : resolveHarnessRoot();
4711
- const { worktreesDir } = options.harnessRoot ? { worktreesDir: path27.join(harnessRoot, "worktrees") } : getHarnessPaths();
4776
+ const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
4777
+ const { worktreesDir } = options.harnessRoot ? { worktreesDir: path28.join(harnessRoot, "worktrees") } : getHarnessPaths();
4712
4778
  const execute = options.execute === true;
4713
4779
  const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
4714
4780
  const worktreesAgeMs = options.worktreesAgeMs ?? 0;
@@ -4752,7 +4818,7 @@ function runHarnessCleanup(options = {}) {
4752
4818
  actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
4753
4819
  continue;
4754
4820
  }
4755
- const worktreePath = path27.resolve(candidate.path, "..");
4821
+ const worktreePath = path28.resolve(candidate.path, "..");
4756
4822
  const indexed = index.get(worktreePath) ?? null;
4757
4823
  const guardReason = skipNodeModulesRemoval({
4758
4824
  indexed,
@@ -4768,7 +4834,7 @@ function runHarnessCleanup(options = {}) {
4768
4834
  actions.push(removeNodeModules(candidate, resolved.execute));
4769
4835
  }
4770
4836
  for (const candidate of scanWorktreeCandidates(scanOpts)) {
4771
- const indexed = index.get(path27.resolve(candidate.path)) ?? null;
4837
+ const indexed = index.get(path28.resolve(candidate.path)) ?? null;
4772
4838
  const guardReason = skipWorktreeRemoval({
4773
4839
  indexed,
4774
4840
  includeOrphans: resolved.includeOrphans,
@@ -4837,7 +4903,7 @@ async function completeFinishedWorkers(runId, args) {
4837
4903
  const outcomes = [];
4838
4904
  for (const name of Object.keys(run.workers || {})) {
4839
4905
  const worker = readJson(
4840
- path28.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4906
+ path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4841
4907
  void 0
4842
4908
  );
4843
4909
  if (!worker?.taskId || worker.localOnly) continue;
@@ -5172,7 +5238,7 @@ function runCleanupCli(args) {
5172
5238
  }
5173
5239
 
5174
5240
  // src/monitor/monitor.service.ts
5175
- import path30 from "node:path";
5241
+ import path31 from "node:path";
5176
5242
 
5177
5243
  // src/monitor/monitor.classify.ts
5178
5244
  function expectedLeaseOwner(runId) {
@@ -5229,10 +5295,10 @@ function classifyWorkerHealth(input) {
5229
5295
 
5230
5296
  // src/monitor/monitor.store.ts
5231
5297
  import { existsSync as existsSync17, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
5232
- import path29 from "node:path";
5298
+ import path30 from "node:path";
5233
5299
  function monitorsDir() {
5234
5300
  const { harnessRoot } = getHarnessPaths();
5235
- const dir = path29.join(harnessRoot, "monitors");
5301
+ const dir = path30.join(harnessRoot, "monitors");
5236
5302
  mkdirSync6(dir, { recursive: true });
5237
5303
  return dir;
5238
5304
  }
@@ -5240,7 +5306,7 @@ function monitorIdFor(runId, workerName) {
5240
5306
  return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
5241
5307
  }
5242
5308
  function monitorPath(monitorId) {
5243
- return path29.join(monitorsDir(), `${monitorId}.json`);
5309
+ return path30.join(monitorsDir(), `${monitorId}.json`);
5244
5310
  }
5245
5311
  function loadMonitorSession(monitorId) {
5246
5312
  return readJson(monitorPath(monitorId), void 0);
@@ -5261,7 +5327,7 @@ function listMonitorSessions() {
5261
5327
  for (const name of readdirSync7(dir)) {
5262
5328
  if (!name.endsWith(".json")) continue;
5263
5329
  const session = readJson(
5264
- path29.join(dir, name),
5330
+ path30.join(dir, name),
5265
5331
  void 0
5266
5332
  );
5267
5333
  if (!session?.monitorId) continue;
@@ -5352,7 +5418,7 @@ async function fetchTaskLeasesForWorkers(input) {
5352
5418
  // src/monitor/monitor.service.ts
5353
5419
  function workerRecord2(runId, name) {
5354
5420
  return readJson(
5355
- path30.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
5421
+ path31.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
5356
5422
  void 0
5357
5423
  );
5358
5424
  }
@@ -5555,17 +5621,17 @@ async function runMonitorLoop(args) {
5555
5621
  // src/monitor/monitor-spawn.ts
5556
5622
  import { spawn as spawn4 } from "node:child_process";
5557
5623
  import { closeSync as closeSync4, existsSync as existsSync18, openSync as openSync4 } from "node:fs";
5558
- import path31 from "node:path";
5624
+ import path32 from "node:path";
5559
5625
  import { fileURLToPath as fileURLToPath2 } from "node:url";
5560
5626
  function resolveDefaultCliPath2() {
5561
- return path31.join(fileURLToPath2(new URL(".", import.meta.url)), "..", "cli.js");
5627
+ return path32.join(fileURLToPath2(new URL(".", import.meta.url)), "..", "cli.js");
5562
5628
  }
5563
5629
  function spawnMonitorSidecar(opts) {
5564
5630
  const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
5565
5631
  if (!existsSync18(cliPath)) return void 0;
5566
5632
  const monitorId = monitorIdFor(opts.runId, opts.workerName);
5567
5633
  const { harnessRoot } = getHarnessPaths();
5568
- const logPath = path31.join(harnessRoot, "monitors", `${monitorId}.log`);
5634
+ const logPath = path32.join(harnessRoot, "monitors", `${monitorId}.log`);
5569
5635
  let logFd;
5570
5636
  try {
5571
5637
  logFd = openSync4(logPath, "a");
@@ -5720,6 +5786,422 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
5720
5786
  return true;
5721
5787
  }
5722
5788
 
5789
+ // src/doctor/runtime-takeover.ts
5790
+ import path34 from "node:path";
5791
+
5792
+ // src/doctor/runtime-takeover.probes.ts
5793
+ import { accessSync, constants, existsSync as existsSync20, readFileSync as readFileSync9 } from "node:fs";
5794
+ import { homedir as homedir5 } from "node:os";
5795
+ import path33 from "node:path";
5796
+ import { spawnSync as spawnSync3 } from "node:child_process";
5797
+ function captureCommand(bin, args) {
5798
+ try {
5799
+ const res = spawnSync3(bin, args, { encoding: "utf8" });
5800
+ const stdout = (res.stdout || "").trim();
5801
+ const stderr = (res.stderr || "").trim();
5802
+ const ok = res.status === 0;
5803
+ return {
5804
+ ok,
5805
+ stdout,
5806
+ stderr,
5807
+ error: res.error?.message
5808
+ };
5809
+ } catch (error) {
5810
+ return {
5811
+ ok: false,
5812
+ stdout: "",
5813
+ stderr: "",
5814
+ error: error.message
5815
+ };
5816
+ }
5817
+ }
5818
+ function tokenPrefix(token) {
5819
+ const trimmed = token?.trim();
5820
+ if (!trimmed) return void 0;
5821
+ return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
5822
+ }
5823
+ function isWritable(target) {
5824
+ if (!existsSync20(target)) return false;
5825
+ try {
5826
+ accessSync(target, constants.W_OK);
5827
+ return true;
5828
+ } catch {
5829
+ return false;
5830
+ }
5831
+ }
5832
+ var defaultRuntimeTakeoverProbes = {
5833
+ packageVersion: () => PACKAGE_VERSION,
5834
+ commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
5835
+ kynverVersion: (bin) => captureCommand(bin, ["--version"]),
5836
+ loadConfig: () => loadUserConfig(),
5837
+ configFilePath: () => path33.join(homedir5(), ".kynver", "config.json"),
5838
+ credentialsFilePath: () => path33.join(homedir5(), ".kynver", "credentials"),
5839
+ readCredentials: () => {
5840
+ const credPath = path33.join(homedir5(), ".kynver", "credentials");
5841
+ if (!existsSync20(credPath)) {
5842
+ return { hasApiKey: false };
5843
+ }
5844
+ try {
5845
+ const parsed = JSON.parse(readFileSync9(credPath, "utf8"));
5846
+ return {
5847
+ hasApiKey: Boolean(parsed.apiKey?.trim()),
5848
+ runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
5849
+ runnerTokenAgentOsId: parsed.runnerTokenAgentOsId
5850
+ };
5851
+ } catch {
5852
+ return { hasApiKey: false };
5853
+ }
5854
+ },
5855
+ envSnapshot: () => ({
5856
+ kynverApiUrl: process.env.KYNVER_API_URL?.trim() || void 0,
5857
+ openclawCronFireBaseUrl: process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || void 0,
5858
+ kynverRunnerTokenPrefix: tokenPrefix(process.env.KYNVER_RUNNER_TOKEN),
5859
+ kynverRuntimeSecret: Boolean(process.env.KYNVER_RUNTIME_SECRET?.trim()),
5860
+ openclawCronSecret: Boolean(process.env.OPENCLAW_CRON_SECRET?.trim()),
5861
+ kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
5862
+ opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
5863
+ kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0
5864
+ }),
5865
+ harnessRoot: () => resolveHarnessRoot(),
5866
+ legacyOpenclawHarnessRoot: () => path33.join(homedir5(), ".openclaw", "harness"),
5867
+ pathExists: (target) => existsSync20(target),
5868
+ pathWritable: (target) => isWritable(target),
5869
+ vercelVersion: () => captureCommand("vercel", ["--version"]),
5870
+ vercelWhoami: () => captureCommand("vercel", ["whoami"])
5871
+ };
5872
+
5873
+ // src/doctor/runtime-takeover.ts
5874
+ function check(partial) {
5875
+ return partial;
5876
+ }
5877
+ function summarizeCounts(sections) {
5878
+ const counts = { pass: 0, warn: 0, fail: 0 };
5879
+ for (const section of sections) {
5880
+ for (const item of section.checks) {
5881
+ counts[item.status] += 1;
5882
+ }
5883
+ }
5884
+ return counts;
5885
+ }
5886
+ function assessCliPackage(probes) {
5887
+ const runningVersion = probes.packageVersion();
5888
+ const which = probes.commandOnPath("kynver");
5889
+ const onPath = which.ok && which.stdout.length > 0;
5890
+ const firstPath = onPath ? which.stdout.split(/\r?\n/)[0]?.trim() : void 0;
5891
+ const displayCliPath = firstPath ? displayUserPath(firstPath) : void 0;
5892
+ const checks = [
5893
+ check({
5894
+ id: "cli_running_version",
5895
+ label: "Running @kynver-app/runtime version",
5896
+ status: "pass",
5897
+ summary: `@kynver-app/runtime ${runningVersion}`,
5898
+ details: { version: runningVersion }
5899
+ }),
5900
+ check({
5901
+ id: "cli_on_path",
5902
+ label: "kynver executable on PATH",
5903
+ status: onPath ? "pass" : "warn",
5904
+ summary: onPath ? `Found ${displayCliPath}` : "kynver not found on PATH (invoked via node/npx?)",
5905
+ remediation: onPath ? void 0 : "Install globally (`npm i -g @kynver-app/runtime`) or invoke via `npx @kynver-app/runtime`.",
5906
+ details: { path: displayCliPath }
5907
+ })
5908
+ ];
5909
+ if (onPath && firstPath) {
5910
+ const versionProbe = probes.kynverVersion(firstPath);
5911
+ const installedVersion = versionProbe.stdout.replace(/^kynver\s+/i, "").trim() || void 0;
5912
+ const versionMatch = versionProbe.ok && (!installedVersion || installedVersion === runningVersion);
5913
+ checks.push(
5914
+ check({
5915
+ id: "cli_installed_version",
5916
+ label: "Installed kynver CLI version matches running package",
5917
+ status: versionMatch ? "pass" : "warn",
5918
+ 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",
5919
+ remediation: versionMatch ? void 0 : "Reinstall or rebuild @kynver-app/runtime so PATH kynver matches the harness worktree version.",
5920
+ details: { installedVersion, runningVersion, path: displayCliPath }
5921
+ })
5922
+ );
5923
+ }
5924
+ return { id: "cli_package", label: "CLI / package", checks };
5925
+ }
5926
+ function assessUserConfig(probes) {
5927
+ const configPath = probes.configFilePath();
5928
+ const displayConfigPath = displayUserPath(configPath);
5929
+ const exists = probes.pathExists(configPath);
5930
+ const config = probes.loadConfig();
5931
+ const checks = [
5932
+ check({
5933
+ id: "config_file",
5934
+ label: "~/.kynver/config.json present",
5935
+ status: exists ? "pass" : "fail",
5936
+ summary: exists ? `Found ${displayConfigPath}` : `Missing ${displayConfigPath}`,
5937
+ remediation: exists ? void 0 : "Run `kynver setup --api-base-url <url> --agent-os-id <id> --repo <path>`.",
5938
+ details: { configPath: displayConfigPath }
5939
+ })
5940
+ ];
5941
+ if (exists) {
5942
+ const apiBaseUrl = config.apiBaseUrl?.trim();
5943
+ const agentOsId = config.agentOsId?.trim();
5944
+ const defaultRepo = config.defaultRepo?.trim();
5945
+ const displayDefaultRepo = defaultRepo ? displayUserPath(defaultRepo) : null;
5946
+ checks.push(
5947
+ check({
5948
+ id: "config_api_base_url",
5949
+ label: "Default API base URL",
5950
+ status: apiBaseUrl ? "pass" : "warn",
5951
+ summary: apiBaseUrl ?? "Not set in config (KYNVER_API_URL / --base-url required per command)",
5952
+ remediation: apiBaseUrl ? void 0 : "Set `apiBaseUrl` via `kynver setup --api-base-url https://\u2026`.",
5953
+ details: { apiBaseUrl: apiBaseUrl ?? null }
5954
+ }),
5955
+ check({
5956
+ id: "config_agent_os_id",
5957
+ label: "Default AgentOS id",
5958
+ status: agentOsId ? "pass" : "warn",
5959
+ summary: agentOsId ?? "Not set (pass --agent-os-id on daemon/dispatch/worker commands)",
5960
+ remediation: agentOsId ? void 0 : "Set `agentOsId` via `kynver setup --agent-os-id <uuid>`.",
5961
+ details: { agentOsId: agentOsId ?? null, agentOsSlug: config.agentOsSlug ?? null }
5962
+ }),
5963
+ check({
5964
+ id: "config_default_repo",
5965
+ label: "Default repo path",
5966
+ status: defaultRepo ? "pass" : "warn",
5967
+ summary: displayDefaultRepo ?? "Not set (pass --repo on `kynver run create`)",
5968
+ remediation: defaultRepo ? void 0 : "Set `defaultRepo` via `kynver setup --repo /path/to/repo`.",
5969
+ details: {
5970
+ defaultRepo: displayDefaultRepo,
5971
+ harnessRoot: config.harnessRoot ? redactHomePath(config.harnessRoot) : null
5972
+ }
5973
+ })
5974
+ );
5975
+ }
5976
+ return { id: "user_config", label: "User config (~/.kynver)", checks };
5977
+ }
5978
+ function assessRunnerToken(probes) {
5979
+ const config = probes.loadConfig();
5980
+ const targetAgentOsId = config.agentOsId?.trim();
5981
+ const creds = probes.readCredentials();
5982
+ const env = probes.envSnapshot();
5983
+ const credPath = probes.credentialsFilePath();
5984
+ const displayCredPath = displayUserPath(credPath);
5985
+ const envToken = env.kynverRunnerTokenPrefix;
5986
+ const savedToken = creds.runnerTokenPrefix;
5987
+ const savedAgentOsId = creds.runnerTokenAgentOsId;
5988
+ const scopedSaved = Boolean(savedToken) && (!targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId);
5989
+ const hasScoped = Boolean(envToken?.startsWith("krc1.")) || scopedSaved && savedToken?.startsWith("krc1.");
5990
+ const checks = [
5991
+ check({
5992
+ id: "runner_token_scoped",
5993
+ label: "Scoped runner token (krc1.*) ready",
5994
+ status: hasScoped ? "pass" : "fail",
5995
+ 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",
5996
+ remediation: hasScoped ? void 0 : "Run `kynver login` then `kynver runner credential --agent-os-id <id>` (or `kynver setup` with API key).",
5997
+ details: {
5998
+ source: envToken ? "env" : savedToken ? "credentials" : "none",
5999
+ tokenPrefix: envToken ?? (scopedSaved ? savedToken : void 0),
6000
+ agentOsId: targetAgentOsId ?? savedAgentOsId ?? null,
6001
+ credentialsPath: displayCredPath
6002
+ }
6003
+ }),
6004
+ check({
6005
+ id: "runner_token_agent_os_match",
6006
+ label: "Saved runner token matches configured agentOsId",
6007
+ status: !savedToken || !targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId ? "pass" : "warn",
6008
+ 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}`,
6009
+ remediation: savedToken && targetAgentOsId && savedAgentOsId && savedAgentOsId !== targetAgentOsId ? "`kynver runner credential --agent-os-id <configured-id>` to mint a workspace-bound token." : void 0,
6010
+ details: { configuredAgentOsId: targetAgentOsId ?? null, savedAgentOsId: savedAgentOsId ?? null }
6011
+ }),
6012
+ check({
6013
+ id: "runner_api_key_for_refresh",
6014
+ label: "API key available for token refresh",
6015
+ status: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY?.trim()) ? "pass" : "warn",
6016
+ 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",
6017
+ remediation: creds.hasApiKey || process.env.KYNVER_API_KEY ? void 0 : "Run `kynver login --api-key \u2026`.",
6018
+ details: { credentialsPath: displayCredPath, hasApiKey: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY) }
6019
+ })
6020
+ ];
6021
+ return { id: "runner_token", label: "Runner token readiness", checks };
6022
+ }
6023
+ function assessVercelCli(probes) {
6024
+ const version = probes.vercelVersion();
6025
+ const whoami = probes.vercelWhoami();
6026
+ const installed = version.ok;
6027
+ return {
6028
+ id: "vercel_cli",
6029
+ label: "Vercel CLI",
6030
+ checks: [
6031
+ check({
6032
+ id: "vercel_installed",
6033
+ label: "Vercel CLI installed",
6034
+ status: installed ? "pass" : "warn",
6035
+ summary: installed ? version.stdout || "vercel CLI found" : version.error ?? "vercel not found on PATH",
6036
+ remediation: installed ? void 0 : "Install Vercel CLI (`npm i -g vercel`) for deploy evidence and env pulls.",
6037
+ details: { stderr: version.stderr || null }
6038
+ }),
6039
+ check({
6040
+ id: "vercel_authenticated",
6041
+ label: "Vercel CLI authenticated",
6042
+ status: !installed ? "warn" : whoami.ok ? "pass" : "warn",
6043
+ summary: !installed ? "Skipped \u2014 Vercel CLI not installed" : whoami.ok ? `Authenticated as ${whoami.stdout}` : whoami.stderr || whoami.error || "Not logged in",
6044
+ remediation: installed && !whoami.ok ? "Run `vercel login` and link the Kynver project if needed." : void 0,
6045
+ details: { account: whoami.ok ? whoami.stdout : null }
6046
+ })
6047
+ ]
6048
+ };
6049
+ }
6050
+ function assessHarnessDirs(probes) {
6051
+ const harnessRoot = probes.harnessRoot();
6052
+ const runsDir = path34.join(harnessRoot, "runs");
6053
+ const worktreesDir = path34.join(harnessRoot, "worktrees");
6054
+ const displayHarnessRoot = redactHomePath(harnessRoot);
6055
+ const displayRunsDir = redactHomePath(runsDir);
6056
+ const displayWorktreesDir = redactHomePath(worktreesDir);
6057
+ const runsExists = probes.pathExists(runsDir);
6058
+ const worktreesExists = probes.pathExists(worktreesDir);
6059
+ return {
6060
+ id: "harness_dirs",
6061
+ label: "Harness / daemon directories",
6062
+ checks: [
6063
+ check({
6064
+ id: "harness_root",
6065
+ label: "Harness root resolved",
6066
+ status: "pass",
6067
+ summary: displayHarnessRoot,
6068
+ details: { harnessRoot: displayHarnessRoot }
6069
+ }),
6070
+ check({
6071
+ id: "harness_runs_dir",
6072
+ label: "Runs directory ready",
6073
+ status: runsExists && probes.pathWritable(runsDir) ? "pass" : runsExists ? "warn" : "warn",
6074
+ summary: runsExists ? probes.pathWritable(runsDir) ? `Writable ${displayRunsDir}` : `Exists but not writable: ${displayRunsDir}` : `Will be created on first run: ${displayRunsDir}`,
6075
+ remediation: runsExists && !probes.pathWritable(runsDir) ? `Fix permissions on ${displayRunsDir} or set KYNVER_HARNESS_ROOT to a writable path.` : void 0,
6076
+ details: { runsDir: displayRunsDir, exists: runsExists, writable: probes.pathWritable(runsDir) }
6077
+ }),
6078
+ check({
6079
+ id: "harness_worktrees_dir",
6080
+ label: "Worktrees directory ready",
6081
+ status: worktreesExists && probes.pathWritable(worktreesDir) ? "pass" : "warn",
6082
+ summary: worktreesExists ? probes.pathWritable(worktreesDir) ? `Writable ${displayWorktreesDir}` : `Exists but not writable: ${displayWorktreesDir}` : `Will be created on first worker: ${displayWorktreesDir}`,
6083
+ remediation: worktreesExists && !probes.pathWritable(worktreesDir) ? `Fix permissions on ${displayWorktreesDir}.` : void 0,
6084
+ details: { worktreesDir: displayWorktreesDir, exists: worktreesExists, writable: probes.pathWritable(worktreesDir) }
6085
+ })
6086
+ ]
6087
+ };
6088
+ }
6089
+ function assessCallbackAuth(probes) {
6090
+ const config = probes.loadConfig();
6091
+ const env = probes.envSnapshot();
6092
+ const baseUrl = env.kynverApiUrl ?? config.apiBaseUrl?.trim() ?? env.openclawCronFireBaseUrl;
6093
+ const usingLegacyBase = !env.kynverApiUrl && !config.apiBaseUrl && Boolean(env.openclawCronFireBaseUrl);
6094
+ const legacySecret = env.openclawCronSecret || env.kynverRuntimeSecret;
6095
+ const envScoped = env.kynverRunnerTokenPrefix?.startsWith("krc1.");
6096
+ const creds = probes.readCredentials();
6097
+ const savedScoped = creds.runnerTokenPrefix?.startsWith("krc1.");
6098
+ const checks = [
6099
+ check({
6100
+ id: "callback_base_url",
6101
+ label: "Callback base URL configured",
6102
+ status: baseUrl ? usingLegacyBase ? "warn" : "pass" : "fail",
6103
+ summary: baseUrl ? usingLegacyBase ? `Using legacy OPENCLAW_CRON_FIRE_BASE_URL (${baseUrl})` : baseUrl : "No KYNVER_API_URL, config apiBaseUrl, or legacy OpenClaw base URL",
6104
+ 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`.",
6105
+ details: {
6106
+ source: env.kynverApiUrl ? "KYNVER_API_URL" : config.apiBaseUrl ? "config.apiBaseUrl" : env.openclawCronFireBaseUrl ? "OPENCLAW_CRON_FIRE_BASE_URL" : "none",
6107
+ baseUrl: baseUrl ?? null
6108
+ }
6109
+ }),
6110
+ check({
6111
+ id: "callback_auth_mode",
6112
+ label: "Callback auth uses scoped runner token",
6113
+ status: envScoped || savedScoped ? "pass" : legacySecret ? "warn" : "fail",
6114
+ 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",
6115
+ remediation: envScoped || savedScoped ? void 0 : "Mint a scoped token: `kynver runner credential --agent-os-id <id>`. Avoid shared OPENCLAW_CRON_SECRET on user runners.",
6116
+ details: {
6117
+ mode: envScoped || savedScoped ? "scoped" : legacySecret ? "legacy_global_secret" : "missing",
6118
+ legacyHeadersWhenNotScoped: ["X-OpenClaw-Cron-Secret", "X-Kynver-Runtime-Secret"]
6119
+ }
6120
+ })
6121
+ ];
6122
+ return { id: "callback_auth", label: "Callback auth / config", checks };
6123
+ }
6124
+ function assessOpenclawHotspots(probes) {
6125
+ const env = probes.envSnapshot();
6126
+ const harnessRoot = probes.harnessRoot();
6127
+ const legacyRoot = probes.legacyOpenclawHarnessRoot();
6128
+ const displayHarnessRoot = redactHomePath(harnessRoot);
6129
+ const displayLegacyRoot = redactHomePath(legacyRoot);
6130
+ const displayOpusHarnessRoot = env.opusHarnessRoot ? redactHomePath(env.opusHarnessRoot) : null;
6131
+ const legacyHarnessActive = harnessRoot === legacyRoot && probes.pathExists(legacyRoot);
6132
+ const schedulerOpenclaw = !env.kynverSchedulerProvider || env.kynverSchedulerProvider === "openclaw-cron";
6133
+ const checks = [
6134
+ check({
6135
+ id: "hotspot_legacy_harness_root",
6136
+ label: "Legacy ~/.openclaw/harness still active",
6137
+ status: legacyHarnessActive ? "warn" : "pass",
6138
+ summary: legacyHarnessActive ? `Harness root is legacy ${displayLegacyRoot}` : env.opusHarnessRoot ? `OPUS_HARNESS_ROOT override in use (${displayOpusHarnessRoot})` : `Using ${displayHarnessRoot}`,
6139
+ 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,
6140
+ details: { harnessRoot: displayHarnessRoot, legacyRoot: displayLegacyRoot, opusHarnessRoot: displayOpusHarnessRoot }
6141
+ }),
6142
+ check({
6143
+ id: "hotspot_openclaw_env_secrets",
6144
+ label: "OpenClaw deployment secrets in runner env",
6145
+ status: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "warn" : "pass",
6146
+ summary: env.openclawCronSecret || env.openclawCronFireBaseUrl ? [
6147
+ env.openclawCronSecret ? "OPENCLAW_CRON_SECRET set" : null,
6148
+ env.openclawCronFireBaseUrl ? "OPENCLAW_CRON_FIRE_BASE_URL set" : null
6149
+ ].filter(Boolean).join("; ") : "No OpenClaw cron env overrides on this runner",
6150
+ remediation: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "Move to KYNVER_API_URL + scoped runner tokens; unset OpenClaw cron env on user-hosted runners." : void 0
6151
+ }),
6152
+ check({
6153
+ id: "hotspot_openclaw_scheduler",
6154
+ label: "openclaw-cron scheduler dependency (deployment)",
6155
+ status: schedulerOpenclaw ? "warn" : "pass",
6156
+ 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}`,
6157
+ 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,
6158
+ details: { schedulerProvider: env.kynverSchedulerProvider ?? null }
6159
+ }),
6160
+ check({
6161
+ id: "hotspot_lease_source_names",
6162
+ label: "Harness lease/completion source names",
6163
+ status: "pass",
6164
+ summary: "Runtime uses kynver-harness:* lease owners and completion source kynver-harness (OpenClaw names retired in runtime)",
6165
+ details: {
6166
+ leaseOwnerPattern: "kynver-harness:<runId>",
6167
+ completionSource: "kynver-harness",
6168
+ note: "OpenClaw adapter remains optional in packages/kynver-openclaw-agent-os only"
6169
+ }
6170
+ })
6171
+ ];
6172
+ return { id: "openclaw_hotspots", label: "OpenClaw dependency hotspots", checks };
6173
+ }
6174
+ function assessRuntimeTakeoverReadiness(probes = defaultRuntimeTakeoverProbes) {
6175
+ const sections = [
6176
+ assessCliPackage(probes),
6177
+ assessUserConfig(probes),
6178
+ assessRunnerToken(probes),
6179
+ assessVercelCli(probes),
6180
+ assessHarnessDirs(probes),
6181
+ assessCallbackAuth(probes),
6182
+ assessOpenclawHotspots(probes)
6183
+ ];
6184
+ const counts = summarizeCounts(sections);
6185
+ const ready = counts.fail === 0;
6186
+ 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.`;
6187
+ return {
6188
+ command: "doctor runtime-takeover",
6189
+ ready,
6190
+ summary,
6191
+ counts,
6192
+ sections
6193
+ };
6194
+ }
6195
+
6196
+ // src/doctor/runtime-takeover-cli.ts
6197
+ function runRuntimeTakeoverDoctorCli() {
6198
+ const report = assessRuntimeTakeoverReadiness();
6199
+ console.log(JSON.stringify(report, null, 2));
6200
+ if (!report.ready) {
6201
+ process.exitCode = 1;
6202
+ }
6203
+ }
6204
+
5723
6205
  // src/cli.ts
5724
6206
  function isHelpFlag(arg) {
5725
6207
  return arg === "help" || arg === "--help" || arg === "-h";
@@ -5762,7 +6244,8 @@ function usage(code = 0) {
5762
6244
  " kynver monitor list",
5763
6245
  " kynver monitor tick --run RUN_ID [--name worker] [--agent-os-id AOS_ID] [--auto-complete] [--renew-leases]",
5764
6246
  " kynver monitor auto-complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--base-url URL] [--secret SECRET]",
5765
- " kynver monitor run-loop --run RUN_ID --monitor-id ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS] [--auto-complete] [--renew-leases]"
6247
+ " kynver monitor run-loop --run RUN_ID --monitor-id ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS] [--auto-complete] [--renew-leases]",
6248
+ " kynver doctor runtime-takeover"
5766
6249
  ].join("\n")
5767
6250
  );
5768
6251
  process.exit(code);
@@ -5773,7 +6256,7 @@ async function main(argv = process.argv.slice(2)) {
5773
6256
  const scope = argv.shift();
5774
6257
  let action;
5775
6258
  let rest;
5776
- if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "monitor") {
6259
+ if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "monitor" || scope === "doctor") {
5777
6260
  action = argv.shift();
5778
6261
  rest = argv;
5779
6262
  } else {
@@ -5798,6 +6281,7 @@ async function main(argv = process.argv.slice(2)) {
5798
6281
  unknownCommand("plan", `outbox ${outboxAction ?? ""}`.trim());
5799
6282
  }
5800
6283
  if (scope === "cleanup") return runCleanupCli(args);
6284
+ if (scope === "doctor" && action === "runtime-takeover") return runRuntimeTakeoverDoctorCli();
5801
6285
  if (scope === "run" && action === "create") return createRun(args);
5802
6286
  if (scope === "run" && action === "list") return listRuns();
5803
6287
  if (scope === "run" && action === "status") return runStatus(args);