@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/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,11 +2024,24 @@ 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
 
2030
+ // src/completion-ack.ts
2031
+ function hasCompletionAck(worker) {
2032
+ return Boolean(worker.completionReportedAt?.trim());
2033
+ }
2034
+ function persistCompletionAck(worker, runId, fields) {
2035
+ worker.completionReportedAt = fields.completionReportedAt;
2036
+ worker.completionOutcome = fields.completionOutcome;
2037
+ if (fields.completionResponse !== void 0) {
2038
+ worker.completionResponse = fields.completionResponse;
2039
+ }
2040
+ saveWorker(runId, worker);
2041
+ }
2042
+
1965
2043
  // src/worker-ops.ts
1966
- import path10 from "node:path";
2044
+ import path11 from "node:path";
1967
2045
 
1968
2046
  // src/pr-handoff/pr-handoff-assess.ts
1969
2047
  var REVIEW_LANE_RULE = /^(lane:)?(review|deep_review|planning|landing)(:|$)/i;
@@ -2299,21 +2377,8 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
2299
2377
  };
2300
2378
  }
2301
2379
 
2302
- // src/completion-ack.ts
2303
- function hasCompletionAck(worker) {
2304
- return Boolean(worker.completionReportedAt?.trim());
2305
- }
2306
- function persistCompletionAck(worker, runId, fields) {
2307
- worker.completionReportedAt = fields.completionReportedAt;
2308
- worker.completionOutcome = fields.completionOutcome;
2309
- if (fields.completionResponse !== void 0) {
2310
- worker.completionResponse = fields.completionResponse;
2311
- }
2312
- saveWorker(runId, worker);
2313
- }
2314
-
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) {
@@ -2808,7 +2874,7 @@ async function autoCompleteWorker(raw) {
2808
2874
  const maxTotalMs = args.maxTotalMs ?? DEFAULT_MAX_TOTAL_MS;
2809
2875
  const completeAttempts = args.completeAttempts ?? DEFAULT_COMPLETE_ATTEMPTS;
2810
2876
  const completeBackoffMs = args.completeBackoffMs ?? DEFAULT_COMPLETE_BACKOFF_MS;
2811
- const worker = loadWorker(args.run, args.name);
2877
+ let worker = loadWorker(args.run, args.name);
2812
2878
  if (!worker.agentOsId || !worker.taskId) {
2813
2879
  return {
2814
2880
  worker: worker.name,
@@ -2818,8 +2884,29 @@ async function autoCompleteWorker(raw) {
2818
2884
  reason: "worker has no agentOsId/taskId \u2014 nothing to attribute completion to"
2819
2885
  };
2820
2886
  }
2887
+ if (hasCompletionAck(worker)) {
2888
+ return {
2889
+ worker: worker.name,
2890
+ runId: worker.runId,
2891
+ outcome: "completed",
2892
+ httpStatus: 200,
2893
+ attempts: 0,
2894
+ reason: "completion-already-acknowledged"
2895
+ };
2896
+ }
2821
2897
  const startMs = Date.now();
2822
2898
  while (true) {
2899
+ worker = loadWorker(args.run, args.name);
2900
+ if (hasCompletionAck(worker)) {
2901
+ return {
2902
+ worker: worker.name,
2903
+ runId: worker.runId,
2904
+ outcome: "completed",
2905
+ httpStatus: 200,
2906
+ attempts: 0,
2907
+ reason: "completion-already-acknowledged"
2908
+ };
2909
+ }
2823
2910
  const status = computeWorkerStatus(worker);
2824
2911
  if (isFinishedWorkerStatus(status)) break;
2825
2912
  if (!isPidAlive(worker.pid)) break;
@@ -2883,20 +2970,21 @@ async function autoCompleteWorkerCli(raw) {
2883
2970
  const outcome = await autoCompleteWorker(raw);
2884
2971
  console.log(JSON.stringify(outcome, null, 2));
2885
2972
  if (outcome.outcome === "missing_link" || outcome.outcome === "timed_out") {
2886
- process.exitCode = 1;
2973
+ process.exit(1);
2887
2974
  }
2975
+ process.exit(0);
2888
2976
  } catch (error) {
2889
2977
  console.error(`worker auto-complete failed: ${error.message}`);
2890
- process.exitCode = 1;
2978
+ process.exit(1);
2891
2979
  }
2892
2980
  }
2893
2981
  function resolveDefaultCliPath() {
2894
- return path11.join(fileURLToPath(new URL(".", import.meta.url)), "cli.js");
2982
+ return path12.join(fileURLToPath(new URL(".", import.meta.url)), "cli.js");
2895
2983
  }
2896
2984
  function spawnCompletionSidecar(opts) {
2897
2985
  const cliPath = opts.cliPath ?? resolveDefaultCliPath();
2898
2986
  if (!existsSync9(cliPath)) return void 0;
2899
- const logPath = path11.join(opts.workerDir, "auto-complete.log");
2987
+ const logPath = path12.join(opts.workerDir, "auto-complete.log");
2900
2988
  let logFd;
2901
2989
  try {
2902
2990
  logFd = openSync3(logPath, "a");
@@ -2978,16 +3066,16 @@ function spawnWorkerProcess(run, opts) {
2978
3066
  launchModel = preflight.model;
2979
3067
  }
2980
3068
  const { worktreesDir } = getPaths();
2981
- const workerDir = path12.join(runDirectory(run.id), "workers", name);
3069
+ const workerDir = path13.join(runDirectory(run.id), "workers", name);
2982
3070
  mkdirSync3(workerDir, { recursive: true });
2983
- const worktreePath = path12.join(worktreesDir, run.id, name);
3071
+ const worktreePath = path13.join(worktreesDir, run.id, name);
2984
3072
  const branch = opts.branch || `agent/${run.id}/${name}`;
2985
3073
  if (existsSync10(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
2986
3074
  git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
2987
3075
  git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
2988
- const stdoutPath = path12.join(workerDir, "stdout.jsonl");
2989
- const stderrPath = path12.join(workerDir, "stderr.log");
2990
- 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");
2991
3079
  const prompt = buildPrompt({
2992
3080
  task: opts.task,
2993
3081
  ownedPaths: opts.ownedPaths || [],
@@ -3048,7 +3136,7 @@ function spawnWorkerProcess(run, opts) {
3048
3136
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
3049
3137
  };
3050
3138
  saveWorker(run.id, worker);
3051
- 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") } };
3052
3140
  run.status = "running";
3053
3141
  saveRun(run);
3054
3142
  if (worker.agentOsId && worker.taskId) {
@@ -3340,18 +3428,18 @@ function buildPlanPersistIdempotencyKey(input) {
3340
3428
 
3341
3429
  // src/plan-persist/paths.ts
3342
3430
  import { mkdirSync as mkdirSync4 } from "node:fs";
3343
- import { homedir as homedir3 } from "node:os";
3344
- import path13 from "node:path";
3431
+ import { homedir as homedir4 } from "node:os";
3432
+ import path14 from "node:path";
3345
3433
  function resolveKynverStateRoot() {
3346
3434
  const env = process.env.KYNVER_STATE_ROOT;
3347
- if (env) return path13.resolve(env);
3348
- return path13.join(homedir3(), ".kynver", "state");
3435
+ if (env) return path14.resolve(env);
3436
+ return path14.join(homedir4(), ".kynver", "state");
3349
3437
  }
3350
3438
  function planOutboxDir() {
3351
- return path13.join(resolveKynverStateRoot(), "plan-outbox");
3439
+ return path14.join(resolveKynverStateRoot(), "plan-outbox");
3352
3440
  }
3353
3441
  function planOutboxArchiveDir() {
3354
- return path13.join(resolveKynverStateRoot(), "plan-outbox-archive");
3442
+ return path14.join(resolveKynverStateRoot(), "plan-outbox-archive");
3355
3443
  }
3356
3444
  function ensurePlanOutboxDirs() {
3357
3445
  const outboxDir = planOutboxDir();
@@ -3362,8 +3450,8 @@ function ensurePlanOutboxDirs() {
3362
3450
  }
3363
3451
  function isTmpOnlyPath(filePath) {
3364
3452
  if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
3365
- const resolved = path13.resolve(filePath);
3366
- 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"));
3367
3455
  }
3368
3456
 
3369
3457
  // src/plan-persist/outbox-store.ts
@@ -3375,7 +3463,7 @@ import {
3375
3463
  writeFileSync as writeFileSync3,
3376
3464
  unlinkSync
3377
3465
  } from "node:fs";
3378
- import path14 from "node:path";
3466
+ import path15 from "node:path";
3379
3467
  import { randomUUID } from "node:crypto";
3380
3468
  var DEFAULT_MAX_RETRIES = 12;
3381
3469
  function listOutboxItems() {
@@ -3383,7 +3471,7 @@ function listOutboxItems() {
3383
3471
  const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
3384
3472
  const items = [];
3385
3473
  for (const file of files) {
3386
- const item = readOutboxItem(path14.join(outboxDir, file));
3474
+ const item = readOutboxItem(path15.join(outboxDir, file));
3387
3475
  if (item && item.queueStatus === "queued") items.push(item);
3388
3476
  }
3389
3477
  return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
@@ -3404,7 +3492,7 @@ function readOutboxItem(jsonPath) {
3404
3492
  }
3405
3493
  function readOutboxBody(item) {
3406
3494
  const { outboxDir } = ensurePlanOutboxDirs();
3407
- const bodyFile = path14.join(outboxDir, item.bodyPath);
3495
+ const bodyFile = path15.join(outboxDir, item.bodyPath);
3408
3496
  return readFileSync6(bodyFile, "utf8");
3409
3497
  }
3410
3498
  function writeOutboxItem(input, opts) {
@@ -3412,8 +3500,8 @@ function writeOutboxItem(input, opts) {
3412
3500
  const now = (/* @__PURE__ */ new Date()).toISOString();
3413
3501
  const id = opts.existing?.id ?? randomUUID();
3414
3502
  const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
3415
- const jsonPath = path14.join(outboxDir, `${id}.json`);
3416
- const bodyFile = path14.join(outboxDir, bodyPath);
3503
+ const jsonPath = path15.join(outboxDir, `${id}.json`);
3504
+ const bodyFile = path15.join(outboxDir, bodyPath);
3417
3505
  if (!opts.existing) {
3418
3506
  writeFileSync3(bodyFile, input.body, "utf8");
3419
3507
  }
@@ -3449,24 +3537,24 @@ function writeOutboxItem(input, opts) {
3449
3537
  }
3450
3538
  function saveOutboxItem(item) {
3451
3539
  const { outboxDir } = ensurePlanOutboxDirs();
3452
- const jsonPath = path14.join(outboxDir, `${item.id}.json`);
3540
+ const jsonPath = path15.join(outboxDir, `${item.id}.json`);
3453
3541
  writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
3454
3542
  `, { mode: 384 });
3455
3543
  }
3456
3544
  function archiveOutboxItem(item) {
3457
3545
  const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
3458
- const jsonSrc = path14.join(outboxDir, `${item.id}.json`);
3459
- const bodySrc = path14.join(outboxDir, item.bodyPath);
3460
- const jsonDst = path14.join(archiveDir, `${item.id}.json`);
3461
- 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);
3462
3550
  if (existsSync12(jsonSrc)) renameSync(jsonSrc, jsonDst);
3463
3551
  if (existsSync12(bodySrc)) renameSync(bodySrc, bodyDst);
3464
3552
  }
3465
3553
  function outboxItemPaths(item) {
3466
3554
  const { outboxDir } = ensurePlanOutboxDirs();
3467
3555
  return {
3468
- jsonPath: path14.join(outboxDir, `${item.id}.json`),
3469
- bodyPath: path14.join(outboxDir, item.bodyPath)
3556
+ jsonPath: path15.join(outboxDir, `${item.id}.json`),
3557
+ bodyPath: path15.join(outboxDir, item.bodyPath)
3470
3558
  };
3471
3559
  }
3472
3560
  function outboxInputFromItem(item, body) {
@@ -3652,7 +3740,7 @@ function markOutboxFailed(item, message) {
3652
3740
  }
3653
3741
 
3654
3742
  // src/plan-persist/drain.ts
3655
- import path15 from "node:path";
3743
+ import path16 from "node:path";
3656
3744
  async function drainPlanOutbox(opts = {}, deps = {}) {
3657
3745
  const items = listOutboxItems().filter(
3658
3746
  (item) => opts.outboxId ? item.id === opts.outboxId : true
@@ -3686,7 +3774,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
3686
3774
  return result;
3687
3775
  }
3688
3776
  function loadOutboxById(outboxId) {
3689
- const jsonPath = path15.join(planOutboxDir(), `${outboxId}.json`);
3777
+ const jsonPath = path16.join(planOutboxDir(), `${outboxId}.json`);
3690
3778
  return readOutboxItem(jsonPath);
3691
3779
  }
3692
3780
 
@@ -3786,7 +3874,7 @@ async function dispatchRun(args) {
3786
3874
  const activeHarnessWorkers = [];
3787
3875
  for (const name of Object.keys(run.workers || {})) {
3788
3876
  const worker = readJson(
3789
- path16.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
3877
+ path17.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
3790
3878
  void 0
3791
3879
  );
3792
3880
  if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
@@ -3990,7 +4078,7 @@ async function dispatchRun(args) {
3990
4078
  }
3991
4079
 
3992
4080
  // src/sweep.ts
3993
- import path17 from "node:path";
4081
+ import path18 from "node:path";
3994
4082
  async function sweepRun(args) {
3995
4083
  const pipeline = args.pipeline === true || args.pipeline === "true";
3996
4084
  try {
@@ -4003,7 +4091,7 @@ async function sweepRun(args) {
4003
4091
  const releasedLocalOrphans = [];
4004
4092
  for (const name of Object.keys(run.workers || {})) {
4005
4093
  const worker = readJson(
4006
- path17.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4094
+ path18.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4007
4095
  void 0
4008
4096
  );
4009
4097
  if (!worker || !worker.dispatched || !worker.taskId) continue;
@@ -4047,10 +4135,10 @@ async function sweepRun(args) {
4047
4135
 
4048
4136
  // src/worktree.ts
4049
4137
  import { existsSync as existsSync13, mkdirSync as mkdirSync5 } from "node:fs";
4050
- import path19 from "node:path";
4138
+ import path20 from "node:path";
4051
4139
 
4052
4140
  // src/validate.ts
4053
- import path18 from "node:path";
4141
+ import path19 from "node:path";
4054
4142
  var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
4055
4143
  function validateRunId(runId) {
4056
4144
  const trimmed = runId.trim();
@@ -4058,7 +4146,7 @@ function validateRunId(runId) {
4058
4146
  return trimmed;
4059
4147
  }
4060
4148
  function validateRepo(repo) {
4061
- const resolved = path18.resolve(repo);
4149
+ const resolved = path19.resolve(repo);
4062
4150
  if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
4063
4151
  return resolved;
4064
4152
  }
@@ -4083,12 +4171,12 @@ function createRun(args) {
4083
4171
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4084
4172
  workers: {}
4085
4173
  };
4086
- writeJson(path19.join(dir, "run.json"), run);
4174
+ writeJson(path20.join(dir, "run.json"), run);
4087
4175
  console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
4088
4176
  }
4089
4177
  function listRuns() {
4090
4178
  const { runsDir } = getPaths();
4091
- 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) => ({
4092
4180
  id: run.id,
4093
4181
  name: run.name,
4094
4182
  status: run.status,
@@ -4103,7 +4191,7 @@ function failExists(message) {
4103
4191
  }
4104
4192
 
4105
4193
  // src/pipeline-tick.ts
4106
- import path28 from "node:path";
4194
+ import path29 from "node:path";
4107
4195
 
4108
4196
  // src/pipeline-dispatch.ts
4109
4197
  var RESERVED_REVIEW_STARTS = 1;
@@ -4157,10 +4245,10 @@ async function runPipelineDispatch(args, slots) {
4157
4245
  }
4158
4246
 
4159
4247
  // src/stale-reconcile.ts
4160
- import path21 from "node:path";
4248
+ import path22 from "node:path";
4161
4249
 
4162
4250
  // src/finalize.ts
4163
- import path20 from "node:path";
4251
+ import path21 from "node:path";
4164
4252
  var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
4165
4253
  function terminalStatusFor(run) {
4166
4254
  const names = Object.keys(run.workers || {});
@@ -4171,7 +4259,7 @@ function terminalStatusFor(run) {
4171
4259
  let anyLandingBlocked = false;
4172
4260
  for (const name of names) {
4173
4261
  const worker = readJson(
4174
- path20.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4262
+ path21.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4175
4263
  void 0
4176
4264
  );
4177
4265
  if (!worker) continue;
@@ -4223,7 +4311,7 @@ function reconcileStaleWorkers() {
4223
4311
  const now = Date.now();
4224
4312
  for (const run of listRunRecords()) {
4225
4313
  for (const name of Object.keys(run.workers || {})) {
4226
- 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");
4227
4315
  const worker = readJson(workerPath, void 0);
4228
4316
  if (!worker || worker.status !== "running") {
4229
4317
  outcomes.push({
@@ -4297,9 +4385,27 @@ function reconcileStaleWorkers() {
4297
4385
  }
4298
4386
  return { workers: outcomes, finalizedRuns: finalizeStaleRuns() };
4299
4387
  }
4388
+ function reconcileRunsCli() {
4389
+ const result = reconcileStaleWorkers();
4390
+ const markedExited = result.workers.filter((w) => w.action === "marked_exited").length;
4391
+ const killedStale = result.workers.filter((w) => w.action === "killed_stale").length;
4392
+ const skipped = result.workers.filter((w) => w.action === "skipped").length;
4393
+ console.log(
4394
+ JSON.stringify(
4395
+ {
4396
+ ok: true,
4397
+ workers: { markedExited, killedStale, skipped, total: result.workers.length },
4398
+ finalizedRuns: result.finalizedRuns.length,
4399
+ details: { workers: result.workers, finalizedRuns: result.finalizedRuns }
4400
+ },
4401
+ null,
4402
+ 2
4403
+ )
4404
+ );
4405
+ }
4300
4406
 
4301
4407
  // src/plan-progress-daemon-sync.ts
4302
- import path22 from "node:path";
4408
+ import path23 from "node:path";
4303
4409
 
4304
4410
  // src/plan-progress-sync.ts
4305
4411
  async function syncPlanProgress(args) {
@@ -4323,7 +4429,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
4323
4429
  const outcomes = [];
4324
4430
  for (const name of Object.keys(run.workers || {})) {
4325
4431
  const worker = readJson(
4326
- path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4432
+ path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4327
4433
  void 0
4328
4434
  );
4329
4435
  if (!worker?.dispatched || !worker.taskId) continue;
@@ -4372,7 +4478,7 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
4372
4478
  }
4373
4479
 
4374
4480
  // src/cleanup.ts
4375
- import path27 from "node:path";
4481
+ import path28 from "node:path";
4376
4482
 
4377
4483
  // src/cleanup-types.ts
4378
4484
  var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
@@ -4444,11 +4550,11 @@ function skipNodeModulesRemoval(input) {
4444
4550
 
4445
4551
  // src/cleanup-execute.ts
4446
4552
  import { existsSync as existsSync15, rmSync } from "node:fs";
4447
- import path24 from "node:path";
4553
+ import path25 from "node:path";
4448
4554
 
4449
4555
  // src/cleanup-dir-size.ts
4450
4556
  import { existsSync as existsSync14, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
4451
- import path23 from "node:path";
4557
+ import path24 from "node:path";
4452
4558
  function directorySizeBytes(root, maxEntries = 5e4) {
4453
4559
  if (!existsSync14(root)) return 0;
4454
4560
  let total = 0;
@@ -4464,7 +4570,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
4464
4570
  }
4465
4571
  for (const name of entries) {
4466
4572
  if (seen++ > maxEntries) return null;
4467
- const full = path23.join(current, name);
4573
+ const full = path24.join(current, name);
4468
4574
  let st;
4469
4575
  try {
4470
4576
  st = statSync2(full);
@@ -4548,20 +4654,20 @@ function removeWorktree(candidate, execute) {
4548
4654
  }
4549
4655
  }
4550
4656
  function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
4551
- const resolved = path24.resolve(targetPath);
4552
- 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;
4553
4659
  if (!nm) return "path_outside_harness";
4554
- const rel = path24.relative(worktreesDir, nm);
4555
- if (rel.startsWith("..") || path24.isAbsolute(rel)) return "path_outside_harness";
4556
- 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);
4557
4663
  if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
4558
- if (!resolved.startsWith(path24.resolve(harnessRoot))) return "path_outside_harness";
4664
+ if (!resolved.startsWith(path25.resolve(harnessRoot))) return "path_outside_harness";
4559
4665
  return null;
4560
4666
  }
4561
4667
 
4562
4668
  // src/cleanup-scan.ts
4563
4669
  import { existsSync as existsSync16, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
4564
- import path25 from "node:path";
4670
+ import path26 from "node:path";
4565
4671
  function pathAgeMs(target, now) {
4566
4672
  try {
4567
4673
  const mtime = statSync3(target).mtimeMs;
@@ -4571,17 +4677,17 @@ function pathAgeMs(target, now) {
4571
4677
  }
4572
4678
  }
4573
4679
  function isPathInside(child, parent) {
4574
- const rel = path25.relative(parent, child);
4575
- return rel === "" || !rel.startsWith("..") && !path25.isAbsolute(rel);
4680
+ const rel = path26.relative(parent, child);
4681
+ return rel === "" || !rel.startsWith("..") && !path26.isAbsolute(rel);
4576
4682
  }
4577
4683
  function scanNodeModulesCandidates(opts) {
4578
4684
  const candidates = [];
4579
4685
  const seen = /* @__PURE__ */ new Set();
4580
4686
  for (const entry of opts.index.values()) {
4581
4687
  if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
4582
- const nm = path25.join(entry.worktreePath, "node_modules");
4688
+ const nm = path26.join(entry.worktreePath, "node_modules");
4583
4689
  if (!existsSync16(nm)) continue;
4584
- const resolved = path25.resolve(nm);
4690
+ const resolved = path26.resolve(nm);
4585
4691
  if (seen.has(resolved)) continue;
4586
4692
  seen.add(resolved);
4587
4693
  candidates.push({
@@ -4597,13 +4703,13 @@ function scanNodeModulesCandidates(opts) {
4597
4703
  if (!opts.includeOrphans || !existsSync16(opts.worktreesDir)) return candidates;
4598
4704
  for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
4599
4705
  if (!runEntry.isDirectory()) continue;
4600
- const runPath = path25.join(opts.worktreesDir, runEntry.name);
4706
+ const runPath = path26.join(opts.worktreesDir, runEntry.name);
4601
4707
  for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
4602
4708
  if (!workerEntry.isDirectory()) continue;
4603
- const worktreePath = path25.join(runPath, workerEntry.name);
4604
- const nm = path25.join(worktreePath, "node_modules");
4709
+ const worktreePath = path26.join(runPath, workerEntry.name);
4710
+ const nm = path26.join(worktreePath, "node_modules");
4605
4711
  if (!existsSync16(nm)) continue;
4606
- const resolved = path25.resolve(nm);
4712
+ const resolved = path26.resolve(nm);
4607
4713
  if (seen.has(resolved)) continue;
4608
4714
  if (!isPathInside(resolved, opts.harnessRoot)) continue;
4609
4715
  seen.add(resolved);
@@ -4643,17 +4749,17 @@ function scanWorktreeCandidates(opts) {
4643
4749
  }
4644
4750
 
4645
4751
  // src/cleanup-worktree-index.ts
4646
- import path26 from "node:path";
4752
+ import path27 from "node:path";
4647
4753
  function buildWorktreeIndex() {
4648
4754
  const index = /* @__PURE__ */ new Map();
4649
4755
  for (const run of listRunRecords()) {
4650
4756
  for (const name of Object.keys(run.workers || {})) {
4651
- 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");
4652
4758
  const worker = readJson(workerPath, void 0);
4653
4759
  if (!worker?.worktreePath) continue;
4654
4760
  const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
4655
- index.set(path26.resolve(worker.worktreePath), {
4656
- worktreePath: path26.resolve(worker.worktreePath),
4761
+ index.set(path27.resolve(worker.worktreePath), {
4762
+ worktreePath: path27.resolve(worker.worktreePath),
4657
4763
  runId: run.id,
4658
4764
  workerName: name,
4659
4765
  run,
@@ -4667,8 +4773,8 @@ function buildWorktreeIndex() {
4667
4773
 
4668
4774
  // src/cleanup.ts
4669
4775
  function resolveOptions(options = {}) {
4670
- const harnessRoot = options.harnessRoot ? path27.resolve(options.harnessRoot) : resolveHarnessRoot();
4671
- 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();
4672
4778
  const execute = options.execute === true;
4673
4779
  const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
4674
4780
  const worktreesAgeMs = options.worktreesAgeMs ?? 0;
@@ -4712,7 +4818,7 @@ function runHarnessCleanup(options = {}) {
4712
4818
  actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
4713
4819
  continue;
4714
4820
  }
4715
- const worktreePath = path27.resolve(candidate.path, "..");
4821
+ const worktreePath = path28.resolve(candidate.path, "..");
4716
4822
  const indexed = index.get(worktreePath) ?? null;
4717
4823
  const guardReason = skipNodeModulesRemoval({
4718
4824
  indexed,
@@ -4728,7 +4834,7 @@ function runHarnessCleanup(options = {}) {
4728
4834
  actions.push(removeNodeModules(candidate, resolved.execute));
4729
4835
  }
4730
4836
  for (const candidate of scanWorktreeCandidates(scanOpts)) {
4731
- const indexed = index.get(path27.resolve(candidate.path)) ?? null;
4837
+ const indexed = index.get(path28.resolve(candidate.path)) ?? null;
4732
4838
  const guardReason = skipWorktreeRemoval({
4733
4839
  indexed,
4734
4840
  includeOrphans: resolved.includeOrphans,
@@ -4797,7 +4903,7 @@ async function completeFinishedWorkers(runId, args) {
4797
4903
  const outcomes = [];
4798
4904
  for (const name of Object.keys(run.workers || {})) {
4799
4905
  const worker = readJson(
4800
- path28.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4906
+ path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4801
4907
  void 0
4802
4908
  );
4803
4909
  if (!worker?.taskId || worker.localOnly) continue;
@@ -5132,7 +5238,7 @@ function runCleanupCli(args) {
5132
5238
  }
5133
5239
 
5134
5240
  // src/monitor/monitor.service.ts
5135
- import path30 from "node:path";
5241
+ import path31 from "node:path";
5136
5242
 
5137
5243
  // src/monitor/monitor.classify.ts
5138
5244
  function expectedLeaseOwner(runId) {
@@ -5189,10 +5295,10 @@ function classifyWorkerHealth(input) {
5189
5295
 
5190
5296
  // src/monitor/monitor.store.ts
5191
5297
  import { existsSync as existsSync17, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
5192
- import path29 from "node:path";
5298
+ import path30 from "node:path";
5193
5299
  function monitorsDir() {
5194
5300
  const { harnessRoot } = getHarnessPaths();
5195
- const dir = path29.join(harnessRoot, "monitors");
5301
+ const dir = path30.join(harnessRoot, "monitors");
5196
5302
  mkdirSync6(dir, { recursive: true });
5197
5303
  return dir;
5198
5304
  }
@@ -5200,7 +5306,7 @@ function monitorIdFor(runId, workerName) {
5200
5306
  return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
5201
5307
  }
5202
5308
  function monitorPath(monitorId) {
5203
- return path29.join(monitorsDir(), `${monitorId}.json`);
5309
+ return path30.join(monitorsDir(), `${monitorId}.json`);
5204
5310
  }
5205
5311
  function loadMonitorSession(monitorId) {
5206
5312
  return readJson(monitorPath(monitorId), void 0);
@@ -5221,7 +5327,7 @@ function listMonitorSessions() {
5221
5327
  for (const name of readdirSync7(dir)) {
5222
5328
  if (!name.endsWith(".json")) continue;
5223
5329
  const session = readJson(
5224
- path29.join(dir, name),
5330
+ path30.join(dir, name),
5225
5331
  void 0
5226
5332
  );
5227
5333
  if (!session?.monitorId) continue;
@@ -5312,7 +5418,7 @@ async function fetchTaskLeasesForWorkers(input) {
5312
5418
  // src/monitor/monitor.service.ts
5313
5419
  function workerRecord2(runId, name) {
5314
5420
  return readJson(
5315
- path30.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
5421
+ path31.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
5316
5422
  void 0
5317
5423
  );
5318
5424
  }
@@ -5515,17 +5621,17 @@ async function runMonitorLoop(args) {
5515
5621
  // src/monitor/monitor-spawn.ts
5516
5622
  import { spawn as spawn4 } from "node:child_process";
5517
5623
  import { closeSync as closeSync4, existsSync as existsSync18, openSync as openSync4 } from "node:fs";
5518
- import path31 from "node:path";
5624
+ import path32 from "node:path";
5519
5625
  import { fileURLToPath as fileURLToPath2 } from "node:url";
5520
5626
  function resolveDefaultCliPath2() {
5521
- return path31.join(fileURLToPath2(new URL(".", import.meta.url)), "..", "cli.js");
5627
+ return path32.join(fileURLToPath2(new URL(".", import.meta.url)), "..", "cli.js");
5522
5628
  }
5523
5629
  function spawnMonitorSidecar(opts) {
5524
5630
  const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
5525
5631
  if (!existsSync18(cliPath)) return void 0;
5526
5632
  const monitorId = monitorIdFor(opts.runId, opts.workerName);
5527
5633
  const { harnessRoot } = getHarnessPaths();
5528
- const logPath = path31.join(harnessRoot, "monitors", `${monitorId}.log`);
5634
+ const logPath = path32.join(harnessRoot, "monitors", `${monitorId}.log`);
5529
5635
  let logFd;
5530
5636
  try {
5531
5637
  logFd = openSync4(logPath, "a");
@@ -5680,6 +5786,422 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
5680
5786
  return true;
5681
5787
  }
5682
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
+
5683
6205
  // src/cli.ts
5684
6206
  function isHelpFlag(arg) {
5685
6207
  return arg === "help" || arg === "--help" || arg === "-h";
@@ -5701,7 +6223,7 @@ function usage(code = 0) {
5701
6223
  " kynver run create --repo /path/repo [--name name] [--base origin/main]",
5702
6224
  " kynver run list",
5703
6225
  " kynver run status --run RUN_ID",
5704
- " 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 /]",
6226
+ " 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 /]",
5705
6227
  " kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
5706
6228
  ' 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]',
5707
6229
  " kynver worker status --run RUN_ID --name worker",
@@ -5709,6 +6231,7 @@ function usage(code = 0) {
5709
6231
  " kynver worker stop --run RUN_ID --name worker",
5710
6232
  " kynver worker complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--task-id TASK_ID] [--base-url URL] [--secret SECRET]",
5711
6233
  " 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]",
6234
+ " kynver run reconcile",
5712
6235
  " 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]",
5713
6236
  " kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override]",
5714
6237
  " 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]",
@@ -5721,7 +6244,8 @@ function usage(code = 0) {
5721
6244
  " kynver monitor list",
5722
6245
  " kynver monitor tick --run RUN_ID [--name worker] [--agent-os-id AOS_ID] [--auto-complete] [--renew-leases]",
5723
6246
  " kynver monitor auto-complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--base-url URL] [--secret SECRET]",
5724
- " 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"
5725
6249
  ].join("\n")
5726
6250
  );
5727
6251
  process.exit(code);
@@ -5732,7 +6256,7 @@ async function main(argv = process.argv.slice(2)) {
5732
6256
  const scope = argv.shift();
5733
6257
  let action;
5734
6258
  let rest;
5735
- 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") {
5736
6260
  action = argv.shift();
5737
6261
  rest = argv;
5738
6262
  } else {
@@ -5757,11 +6281,13 @@ async function main(argv = process.argv.slice(2)) {
5757
6281
  unknownCommand("plan", `outbox ${outboxAction ?? ""}`.trim());
5758
6282
  }
5759
6283
  if (scope === "cleanup") return runCleanupCli(args);
6284
+ if (scope === "doctor" && action === "runtime-takeover") return runRuntimeTakeoverDoctorCli();
5760
6285
  if (scope === "run" && action === "create") return createRun(args);
5761
6286
  if (scope === "run" && action === "list") return listRuns();
5762
6287
  if (scope === "run" && action === "status") return runStatus(args);
5763
6288
  if (scope === "run" && action === "dispatch") return void await dispatchRun(args);
5764
6289
  if (scope === "run" && action === "sweep") return void await sweepRun(args);
6290
+ if (scope === "run" && action === "reconcile") return reconcileRunsCli();
5765
6291
  if (scope === "worker" && action === "start") return void await startWorker(args);
5766
6292
  if (scope === "worker" && action === "status") return workerStatus(args);
5767
6293
  if (scope === "worker" && action === "tail") return tailWorker(args);