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