@kynver-app/runtime 0.1.39 → 0.1.47

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.
Files changed (117) hide show
  1. package/README.md +15 -0
  2. package/dist/auto-complete.d.ts +60 -0
  3. package/dist/bounded-build/admission.d.ts +30 -0
  4. package/dist/bounded-build/constants.d.ts +10 -0
  5. package/dist/bounded-build/exec.d.ts +26 -0
  6. package/dist/bounded-build/index.d.ts +6 -0
  7. package/dist/bounded-build/meminfo.d.ts +5 -0
  8. package/dist/bounded-build/node-options.d.ts +9 -0
  9. package/dist/bounded-build/systemd-wrap.d.ts +17 -0
  10. package/dist/callback-headers.d.ts +2 -0
  11. package/dist/callbacks.d.ts +38 -0
  12. package/dist/cleanup-cli.d.ts +1 -0
  13. package/dist/cleanup-dir-size.d.ts +4 -0
  14. package/dist/cleanup-execute.d.ts +4 -0
  15. package/dist/cleanup-guards.d.ts +18 -0
  16. package/dist/cleanup-scan.d.ts +14 -0
  17. package/dist/cleanup-types.d.ts +56 -0
  18. package/dist/cleanup-worktree-index.d.ts +13 -0
  19. package/dist/cleanup.d.ts +5 -0
  20. package/dist/cli.d.ts +3 -0
  21. package/dist/cli.js +1398 -199
  22. package/dist/cli.js.map +4 -4
  23. package/dist/command-center-contract-cli.d.ts +1 -0
  24. package/dist/completion-ack.d.ts +10 -0
  25. package/dist/completion-response.d.ts +15 -0
  26. package/dist/config.d.ts +61 -0
  27. package/dist/daemon.d.ts +5 -0
  28. package/dist/disk-gate.d.ts +9 -0
  29. package/dist/dispatch.d.ts +16 -0
  30. package/dist/doctor/doctor.types.d.ts +25 -0
  31. package/dist/doctor/index.d.ts +4 -0
  32. package/dist/doctor/runtime-takeover-cli.d.ts +1 -0
  33. package/dist/doctor/runtime-takeover.d.ts +3 -0
  34. package/dist/doctor/runtime-takeover.probes.d.ts +37 -0
  35. package/dist/exit-classify.d.ts +12 -0
  36. package/dist/exited-salvage.d.ts +22 -0
  37. package/dist/finalize.d.ts +16 -0
  38. package/dist/fortress-engagement-gate.d.ts +5 -0
  39. package/dist/git.d.ts +37 -0
  40. package/dist/github-repo.d.ts +5 -0
  41. package/dist/harness-verify-cli.d.ts +1 -0
  42. package/dist/harness-verify.d.ts +19 -0
  43. package/dist/heartbeat.d.ts +22 -0
  44. package/dist/index.d.ts +33 -0
  45. package/dist/index.js +1733 -202
  46. package/dist/index.js.map +4 -4
  47. package/dist/installed-package-versions.d.ts +6 -0
  48. package/dist/landing-contract-gate.d.ts +24 -0
  49. package/dist/landing-gate.d.ts +24 -0
  50. package/dist/lease-renewal.d.ts +15 -0
  51. package/dist/model-routing-task-enrich.d.ts +8 -0
  52. package/dist/model-routing.d.ts +29 -0
  53. package/dist/monitor/index.d.ts +7 -0
  54. package/dist/monitor/monitor-cli.d.ts +9 -0
  55. package/dist/monitor/monitor-loop.d.ts +1 -0
  56. package/dist/monitor/monitor-spawn.d.ts +18 -0
  57. package/dist/monitor/monitor.classify.d.ts +15 -0
  58. package/dist/monitor/monitor.service.d.ts +17 -0
  59. package/dist/monitor/monitor.store.d.ts +6 -0
  60. package/dist/monitor/monitor.task-lease.d.ts +11 -0
  61. package/dist/monitor/monitor.terminal.d.ts +11 -0
  62. package/dist/monitor/monitor.types.d.ts +74 -0
  63. package/dist/package-version.d.ts +5 -0
  64. package/dist/path-values.d.ts +5 -0
  65. package/dist/paths.d.ts +7 -0
  66. package/dist/pipeline-dispatch.d.ts +7 -0
  67. package/dist/pipeline-tick.d.ts +45 -0
  68. package/dist/plan-persist/agentos-api.d.ts +28 -0
  69. package/dist/plan-persist/body-hash.d.ts +3 -0
  70. package/dist/plan-persist/drain.d.ts +7 -0
  71. package/dist/plan-persist/errors.d.ts +9 -0
  72. package/dist/plan-persist/handoff.d.ts +7 -0
  73. package/dist/plan-persist/idempotency.d.ts +2 -0
  74. package/dist/plan-persist/index.d.ts +8 -0
  75. package/dist/plan-persist/outbox-store.d.ts +18 -0
  76. package/dist/plan-persist/paths.d.ts +8 -0
  77. package/dist/plan-persist/persist.d.ts +10 -0
  78. package/dist/plan-persist/readback.d.ts +17 -0
  79. package/dist/plan-persist/types.d.ts +91 -0
  80. package/dist/plan-persist-cli.d.ts +3 -0
  81. package/dist/plan-progress-daemon-sync.d.ts +7 -0
  82. package/dist/plan-progress-sync.d.ts +21 -0
  83. package/dist/plan-progress.d.ts +10 -0
  84. package/dist/pr-handoff/index.d.ts +4 -0
  85. package/dist/pr-handoff/pr-handoff-assess.d.ts +26 -0
  86. package/dist/pr-handoff/pr-handoff-gh.d.ts +44 -0
  87. package/dist/pr-handoff/pr-handoff.d.ts +8 -0
  88. package/dist/pr-handoff/pr-handoff.types.d.ts +45 -0
  89. package/dist/prompt.d.ts +14 -0
  90. package/dist/providers/claude.d.ts +4 -0
  91. package/dist/providers/cursor-windows.d.ts +7 -0
  92. package/dist/providers/cursor.d.ts +11 -0
  93. package/dist/providers/model-preflight.d.ts +31 -0
  94. package/dist/providers/registry.d.ts +4 -0
  95. package/dist/providers/types.d.ts +32 -0
  96. package/dist/redact.d.ts +1 -0
  97. package/dist/resource-gate.d.ts +53 -0
  98. package/dist/retry-limits.d.ts +8 -0
  99. package/dist/run-store.d.ts +30 -0
  100. package/dist/shell-command-outcome.d.ts +32 -0
  101. package/dist/stale-reconcile.d.ts +25 -0
  102. package/dist/status.d.ts +161 -0
  103. package/dist/stream.d.ts +20 -0
  104. package/dist/supervisor.d.ts +25 -0
  105. package/dist/sweep.d.ts +1 -0
  106. package/dist/util.d.ts +22 -0
  107. package/dist/validate.d.ts +5 -0
  108. package/dist/vercel/index.d.ts +3 -0
  109. package/dist/vercel/vercel-evidence.d.ts +48 -0
  110. package/dist/vercel/vercel-github-status.d.ts +19 -0
  111. package/dist/vercel/vercel-url.d.ts +16 -0
  112. package/dist/worker-env.d.ts +15 -0
  113. package/dist/worker-lifecycle.d.ts +28 -0
  114. package/dist/worker-ops.d.ts +20 -0
  115. package/dist/workspace-runtime-config.d.ts +8 -0
  116. package/dist/worktree.d.ts +4 -0
  117. package/package.json +8 -4
package/dist/cli.js CHANGED
@@ -6,12 +6,39 @@ import { fileURLToPath as fileURLToPath4 } from "node:url";
6
6
 
7
7
  // src/config.ts
8
8
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
9
+ import { homedir as homedir2 } from "node:os";
10
+ import path3 from "node:path";
11
+
12
+ // src/path-values.ts
9
13
  import { homedir } from "node:os";
10
- import path2 from "node:path";
14
+ import path from "node:path";
15
+ function expandHomePath(value) {
16
+ if (value === "~") return homedir();
17
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
18
+ return path.join(homedir(), value.slice(2));
19
+ }
20
+ return value;
21
+ }
22
+ function resolveUserPath(value) {
23
+ return path.resolve(expandHomePath(value));
24
+ }
25
+ function redactHomePath(value) {
26
+ const expanded = expandHomePath(value);
27
+ const resolved = path.resolve(expanded);
28
+ const home = path.resolve(homedir());
29
+ if (resolved === home) return "~";
30
+ if (resolved.startsWith(`${home}${path.sep}`)) {
31
+ return `~/${path.relative(home, resolved).split(path.sep).join("/")}`;
32
+ }
33
+ return resolved.replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^\/Users\/[^/]+(?=\/|$)/, "~");
34
+ }
35
+ function displayUserPath(value) {
36
+ return redactHomePath(value);
37
+ }
11
38
 
12
39
  // src/util.ts
13
40
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
14
- import path from "node:path";
41
+ import path2 from "node:path";
15
42
  function fail(message) {
16
43
  console.error(message);
17
44
  process.exit(1);
@@ -40,7 +67,7 @@ function readJson(file, fallback) {
40
67
  }
41
68
  }
42
69
  function writeJson(file, value) {
43
- mkdirSync(path.dirname(file), { recursive: true });
70
+ mkdirSync(path2.dirname(file), { recursive: true });
44
71
  writeFileSync(file, `${JSON.stringify(value, null, 2)}
45
72
  `);
46
73
  }
@@ -76,7 +103,7 @@ function tailFile(file, lines) {
76
103
  return data.split("\n").slice(-lines).join("\n");
77
104
  }
78
105
  function readMaybeFile(file) {
79
- return file ? readFileSync(path.resolve(file), "utf8") : "";
106
+ return file ? readFileSync(path2.resolve(file), "utf8") : "";
80
107
  }
81
108
  function listRunIds(runsDir) {
82
109
  if (!existsSync(runsDir)) return [];
@@ -119,9 +146,9 @@ function secsAgo(ms) {
119
146
  }
120
147
 
121
148
  // src/config.ts
122
- var CONFIG_DIR = path2.join(homedir(), ".kynver");
123
- var CONFIG_FILE = path2.join(CONFIG_DIR, "config.json");
124
- var CREDENTIALS_FILE = path2.join(CONFIG_DIR, "credentials");
149
+ var CONFIG_DIR = path3.join(homedir2(), ".kynver");
150
+ var CONFIG_FILE = path3.join(CONFIG_DIR, "config.json");
151
+ var CREDENTIALS_FILE = path3.join(CONFIG_DIR, "credentials");
125
152
  function loadUserConfig() {
126
153
  if (!existsSync2(CONFIG_FILE)) return {};
127
154
  try {
@@ -132,9 +159,33 @@ function loadUserConfig() {
132
159
  }
133
160
  function saveUserConfig(config) {
134
161
  mkdirSync2(CONFIG_DIR, { recursive: true });
135
- writeFileSync2(CONFIG_FILE, `${JSON.stringify(config, null, 2)}
162
+ writeFileSync2(CONFIG_FILE, `${JSON.stringify(normalizeConfigPaths(config), null, 2)}
136
163
  `, { mode: 384 });
137
164
  }
165
+ function normalizeConfigPaths(config) {
166
+ return {
167
+ ...config,
168
+ ...config.harnessRoot?.trim() ? { harnessRoot: redactHomePath(config.harnessRoot.trim()) } : {},
169
+ ...config.defaultRepo?.trim() ? { defaultRepo: redactHomePath(config.defaultRepo.trim()) } : {}
170
+ };
171
+ }
172
+ function presentUserConfig(config) {
173
+ return normalizeConfigPaths(config);
174
+ }
175
+ function inferSetupFields(existing, args) {
176
+ const creds = loadCredentialsFile();
177
+ const apiBaseUrl = (typeof args.apiBaseUrl === "string" ? args.apiBaseUrl : void 0) || existing.apiBaseUrl?.trim() || process.env.KYNVER_API_URL?.trim() || process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim();
178
+ const agentOsId = (typeof args.agentOsId === "string" ? args.agentOsId : void 0) || existing.agentOsId?.trim() || process.env.KYNVER_AGENT_OS_ID?.trim() || (creds.runnerToken?.trim().startsWith("krc1.") ? creds.runnerTokenAgentOsId?.trim() : void 0);
179
+ const defaultRepo = (typeof args.repo === "string" ? args.repo : void 0) || existing.defaultRepo?.trim() || process.env.KYNVER_DEFAULT_REPO?.trim() || process.env.KYNVER_HARNESS_REPO?.trim();
180
+ const harnessRoot = (typeof args.harnessRoot === "string" ? args.harnessRoot : void 0) || existing.harnessRoot?.trim() || process.env.KYNVER_HARNESS_ROOT?.trim() || process.env.OPUS_HARNESS_ROOT?.trim();
181
+ return {
182
+ ...apiBaseUrl ? { apiBaseUrl: trimTrailingSlash(apiBaseUrl) } : {},
183
+ ...agentOsId ? { agentOsId } : {},
184
+ ...defaultRepo ? { defaultRepo } : {},
185
+ ...harnessRoot ? { harnessRoot } : {},
186
+ ...typeof args.agentOsSlug === "string" ? { agentOsSlug: args.agentOsSlug } : existing.agentOsSlug ? { agentOsSlug: existing.agentOsSlug } : {}
187
+ };
188
+ }
138
189
  function loadCredentialsFile() {
139
190
  if (!existsSync2(CREDENTIALS_FILE)) return {};
140
191
  try {
@@ -280,7 +331,7 @@ async function mintRunnerCredential(args) {
280
331
  {
281
332
  ok: true,
282
333
  agentOsId,
283
- credentialsPath: CREDENTIALS_FILE,
334
+ credentialsPath: displayUserPath(CREDENTIALS_FILE),
284
335
  tokenPrefix: `${token.slice(0, 12)}\u2026`,
285
336
  note: "Scoped runner token saved; callbacks use X-Kynver-Runner-Token."
286
337
  },
@@ -315,16 +366,12 @@ function parseArgs(argv) {
315
366
  async function runSetup(args) {
316
367
  const existing = loadUserConfig();
317
368
  const maxWorkersRaw = typeof args.maxWorkers === "string" ? args.maxWorkers : typeof args.maxConcurrentWorkers === "string" ? args.maxConcurrentWorkers : void 0;
318
- const config = {
369
+ const config = normalizeConfigPaths({
319
370
  ...existing,
320
- ...typeof args.apiBaseUrl === "string" ? { apiBaseUrl: args.apiBaseUrl } : {},
321
- ...typeof args.agentOsSlug === "string" ? { agentOsSlug: args.agentOsSlug } : {},
322
- ...typeof args.agentOsId === "string" ? { agentOsId: args.agentOsId } : {},
323
- ...typeof args.repo === "string" ? { defaultRepo: args.repo } : {},
324
- ...typeof args.harnessRoot === "string" ? { harnessRoot: args.harnessRoot } : {},
371
+ ...inferSetupFields(existing, args),
325
372
  ...maxWorkersRaw ? { maxConcurrentWorkers: Math.max(1, Math.floor(Number(maxWorkersRaw))) } : {},
326
373
  workerProvider: typeof args.provider === "string" ? args.provider : existing.workerProvider || "claude"
327
- };
374
+ });
328
375
  saveUserConfig(config);
329
376
  let runnerCredentialNote;
330
377
  const apiKey = loadApiKey();
@@ -345,8 +392,8 @@ async function runSetup(args) {
345
392
  JSON.stringify(
346
393
  {
347
394
  ok: true,
348
- configPath: CONFIG_FILE,
349
- config,
395
+ configPath: displayUserPath(CONFIG_FILE),
396
+ config: presentUserConfig(config),
350
397
  note: runnerCredentialNote ?? "Set worker limit once with --max-workers N (or omit to auto-size from RAM). Run `kynver login` + `kynver runner credential` for scoped callbacks."
351
398
  },
352
399
  null,
@@ -358,11 +405,11 @@ async function runLogin(args) {
358
405
  const apiKey = typeof args.apiKey === "string" ? args.apiKey : process.env.KYNVER_API_KEY;
359
406
  if (!apiKey) failConfig("kynver login requires --api-key or KYNVER_API_KEY");
360
407
  saveApiKey(apiKey);
361
- console.log(JSON.stringify({ ok: true, credentialsPath: CREDENTIALS_FILE }, null, 2));
408
+ console.log(JSON.stringify({ ok: true, credentialsPath: displayUserPath(CREDENTIALS_FILE) }, null, 2));
362
409
  }
363
410
 
364
411
  // src/dispatch.ts
365
- import path16 from "node:path";
412
+ import path17 from "node:path";
366
413
 
367
414
  // src/callback-headers.ts
368
415
  function buildHarnessCallbackHeaders(secret) {
@@ -424,12 +471,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
424
471
  var DEFAULT_MAX_USED_PERCENT = 80;
425
472
  var DEFAULT_HARD_MAX_USED_PERCENT = 90;
426
473
  function observeRunnerDiskGate(input = {}) {
427
- const path32 = input.diskPath?.trim() || "/";
474
+ const path38 = input.diskPath?.trim() || "/";
428
475
  const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
429
476
  const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
430
477
  const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
431
478
  const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
432
- const stats = statfsSync(path32);
479
+ const stats = statfsSync(path38);
433
480
  const freeBytes = Number(stats.bavail) * Number(stats.bsize);
434
481
  const totalBytes = Number(stats.blocks) * Number(stats.bsize);
435
482
  const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
@@ -449,7 +496,7 @@ function observeRunnerDiskGate(input = {}) {
449
496
  }
450
497
  return {
451
498
  ok,
452
- path: path32,
499
+ path: path38,
453
500
  freeBytes,
454
501
  totalBytes,
455
502
  usedPercent,
@@ -462,23 +509,46 @@ function observeRunnerDiskGate(input = {}) {
462
509
  }
463
510
 
464
511
  // src/resource-gate.ts
465
- import { readFileSync as readFileSync5 } from "node:fs";
512
+ import os2 from "node:os";
513
+
514
+ // src/bounded-build/meminfo.ts
515
+ import { readFileSync as readFileSync3 } from "node:fs";
466
516
  import os from "node:os";
467
- import path5 from "node:path";
517
+ function readMemAvailableBytes(meminfoText) {
518
+ if (meminfoText !== void 0) {
519
+ const match = meminfoText.match(/^MemAvailable:\s+(\d+)\s*kB/m);
520
+ if (match) return Number(match[1]) * 1024;
521
+ return os.freemem();
522
+ }
523
+ if (process.platform === "linux") {
524
+ try {
525
+ const meminfo = readFileSync3("/proc/meminfo", "utf8");
526
+ const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m);
527
+ if (match) return Number(match[1]) * 1024;
528
+ } catch {
529
+ }
530
+ }
531
+ return os.freemem();
532
+ }
533
+
534
+ // src/resource-gate.ts
535
+ import path6 from "node:path";
468
536
 
469
537
  // src/run-store.ts
470
538
  import { existsSync as existsSync4, readdirSync as readdirSync2 } from "node:fs";
471
- import path4 from "node:path";
539
+ import path5 from "node:path";
472
540
 
473
541
  // src/paths.ts
474
542
  import { existsSync as existsSync3 } from "node:fs";
475
- import { homedir as homedir2 } from "node:os";
476
- import path3 from "node:path";
477
- var LEGACY_ROOT = path3.join(homedir2(), ".openclaw", "harness");
543
+ import { homedir as homedir3 } from "node:os";
544
+ import path4 from "node:path";
545
+ var LEGACY_ROOT = path4.join(homedir3(), ".openclaw", "harness");
478
546
  function resolveHarnessRoot() {
479
547
  const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
480
- if (env) return path3.resolve(env);
481
- const kynverRoot = path3.join(homedir2(), ".kynver", "harness");
548
+ if (env) return resolveUserPath(env);
549
+ const configured = loadUserConfig().harnessRoot?.trim();
550
+ if (configured) return resolveUserPath(configured);
551
+ const kynverRoot = path4.join(homedir3(), ".kynver", "harness");
482
552
  if (existsSync3(kynverRoot)) return kynverRoot;
483
553
  if (existsSync3(LEGACY_ROOT)) return LEGACY_ROOT;
484
554
  return kynverRoot;
@@ -487,12 +557,12 @@ function getHarnessPaths() {
487
557
  const harnessRoot = resolveHarnessRoot();
488
558
  return {
489
559
  harnessRoot,
490
- runsDir: path3.join(harnessRoot, "runs"),
491
- worktreesDir: path3.join(harnessRoot, "worktrees")
560
+ runsDir: path4.join(harnessRoot, "runs"),
561
+ worktreesDir: path4.join(harnessRoot, "worktrees")
492
562
  };
493
563
  }
494
564
  function runDir(runsDir, id) {
495
- return path3.join(runsDir, safeSlug(id));
565
+ return path4.join(runsDir, safeSlug(id));
496
566
  }
497
567
 
498
568
  // src/run-store.ts
@@ -501,7 +571,7 @@ function getPaths() {
501
571
  }
502
572
  function loadRun(id) {
503
573
  const { runsDir } = getPaths();
504
- return readJson(path4.join(runDir(runsDir, safeSlug(id)), "run.json"));
574
+ return readJson(path5.join(runDir(runsDir, safeSlug(id)), "run.json"));
505
575
  }
506
576
  function listRunRecords() {
507
577
  const { runsDir } = getPaths();
@@ -510,7 +580,7 @@ function listRunRecords() {
510
580
  for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
511
581
  if (!entry.isDirectory()) continue;
512
582
  const run = readJson(
513
- path4.join(runsDir, entry.name, "run.json"),
583
+ path5.join(runsDir, entry.name, "run.json"),
514
584
  void 0
515
585
  );
516
586
  if (run?.id) runs.push(run);
@@ -520,16 +590,16 @@ function listRunRecords() {
520
590
  function loadWorker(runId, name) {
521
591
  const { runsDir } = getPaths();
522
592
  return readJson(
523
- path4.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
593
+ path5.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
524
594
  );
525
595
  }
526
596
  function saveRun(run) {
527
597
  const { runsDir } = getPaths();
528
- writeJson(path4.join(runDir(runsDir, run.id), "run.json"), run);
598
+ writeJson(path5.join(runDir(runsDir, run.id), "run.json"), run);
529
599
  }
530
600
  function saveWorker(runId, worker) {
531
601
  const { runsDir } = getPaths();
532
- writeJson(path4.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
602
+ writeJson(path5.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
533
603
  }
534
604
  function runDirectory(id) {
535
605
  const { runsDir } = getPaths();
@@ -537,7 +607,7 @@ function runDirectory(id) {
537
607
  }
538
608
 
539
609
  // src/heartbeat.ts
540
- import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
610
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
541
611
  var HEARTBEAT_FUTURE_SKEW_MS = 6e4;
542
612
  function isTerminalHeartbeatPhase(phase) {
543
613
  return phase === "complete";
@@ -559,7 +629,7 @@ function parseHeartbeat(file) {
559
629
  if (!existsSync5(file)) return result;
560
630
  const maxFutureMs = Date.now() + HEARTBEAT_FUTURE_SKEW_MS;
561
631
  const clampedTo = new Date(maxFutureMs).toISOString();
562
- const lines = readFileSync3(file, "utf8").split("\n").filter(Boolean);
632
+ const lines = readFileSync4(file, "utf8").split("\n").filter(Boolean);
563
633
  for (const line of lines) {
564
634
  const entry = safeJson(line);
565
635
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
@@ -586,7 +656,155 @@ function parseHeartbeat(file) {
586
656
  }
587
657
 
588
658
  // src/stream.ts
589
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
659
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
660
+
661
+ // src/shell-command-outcome.ts
662
+ var NPM_AUDIT_RE = /\bnpm\s+audit\b/i;
663
+ function tidy(text, max = 200) {
664
+ const one = text.replace(/\s+/g, " ").trim();
665
+ return one.length > max ? `${one.slice(0, max - 1)}\u2026` : one;
666
+ }
667
+ function extractJsonObject(text) {
668
+ const trimmed = text.trim();
669
+ if (!trimmed) return null;
670
+ if (trimmed.startsWith("{")) {
671
+ try {
672
+ return JSON.parse(trimmed);
673
+ } catch {
674
+ }
675
+ }
676
+ const start = trimmed.indexOf("{");
677
+ const end = trimmed.lastIndexOf("}");
678
+ if (start >= 0 && end > start) {
679
+ try {
680
+ return JSON.parse(trimmed.slice(start, end + 1));
681
+ } catch {
682
+ return null;
683
+ }
684
+ }
685
+ return null;
686
+ }
687
+ function isRecord(value) {
688
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
689
+ }
690
+ function summarizeNpmAuditReport(report) {
691
+ const meta = report.metadata;
692
+ if (!isRecord(meta)) return null;
693
+ const vuln = meta.vulnerabilities;
694
+ if (!isRecord(vuln)) return null;
695
+ const num = (key) => typeof vuln[key] === "number" ? vuln[key] : 0;
696
+ const summary = {
697
+ info: num("info"),
698
+ low: num("low"),
699
+ moderate: num("moderate"),
700
+ high: num("high"),
701
+ critical: num("critical"),
702
+ total: num("total")
703
+ };
704
+ if (typeof vuln.total !== "number" && !summary.critical && !summary.high && !summary.moderate && !summary.low && !summary.info) {
705
+ return null;
706
+ }
707
+ return summary;
708
+ }
709
+ function formatAuditSummaryLine(audit) {
710
+ const parts = [];
711
+ if (audit.critical) parts.push(`${audit.critical} critical`);
712
+ if (audit.high) parts.push(`${audit.high} high`);
713
+ if (audit.moderate) parts.push(`${audit.moderate} moderate`);
714
+ if (audit.low) parts.push(`${audit.low} low`);
715
+ if (audit.info) parts.push(`${audit.info} info`);
716
+ const breakdown = parts.length ? parts.join(", ") : "see report";
717
+ return `npm audit: ${audit.total} vulnerabilit${audit.total === 1 ? "y" : "ies"} (${breakdown}) \u2014 remediation required`;
718
+ }
719
+ function npmAuditFailureReason(report, stderr) {
720
+ const err = report.error;
721
+ if (isRecord(err)) {
722
+ const summary = typeof err.summary === "string" ? err.summary.trim() : "";
723
+ const code = typeof err.code === "string" ? err.code.trim() : "";
724
+ if (summary) return code ? `${code}: ${summary}` : summary;
725
+ if (code) return code;
726
+ }
727
+ const detail = typeof report.message === "string" ? report.message.trim() : "";
728
+ if (detail) return detail;
729
+ const errTail = stderr.trim();
730
+ if (errTail) return tidy(errTail.split("\n").find(Boolean) ?? errTail, 160);
731
+ return "npm audit failed";
732
+ }
733
+ function classifyNpmAuditOutcome(input) {
734
+ const combined = `${input.stdout}
735
+ ${input.stderr}`.trim();
736
+ const parsed = extractJsonObject(combined);
737
+ if (!parsed || !isRecord(parsed)) {
738
+ const tail = tidy(combined || `exit ${input.exitCode}`, 180);
739
+ return {
740
+ kind: "command_failure",
741
+ exitCode: input.exitCode,
742
+ summary: `npm audit failed (invalid or missing JSON): ${tail}`,
743
+ parseError: "invalid_json"
744
+ };
745
+ }
746
+ if (isRecord(parsed.error)) {
747
+ return {
748
+ kind: "command_failure",
749
+ exitCode: input.exitCode,
750
+ summary: `npm audit command failed: ${npmAuditFailureReason(parsed, input.stderr)}`
751
+ };
752
+ }
753
+ const audit = summarizeNpmAuditReport(parsed);
754
+ if (!audit) {
755
+ return {
756
+ kind: "command_failure",
757
+ exitCode: input.exitCode,
758
+ summary: "npm audit failed: JSON response missing vulnerability metadata",
759
+ parseError: "missing_metadata"
760
+ };
761
+ }
762
+ if (input.exitCode === 0 && audit.total === 0) {
763
+ return {
764
+ kind: "success",
765
+ exitCode: 0,
766
+ summary: "npm audit: no vulnerabilities reported",
767
+ audit
768
+ };
769
+ }
770
+ return {
771
+ kind: "audit_findings",
772
+ exitCode: input.exitCode,
773
+ summary: formatAuditSummaryLine(audit),
774
+ audit
775
+ };
776
+ }
777
+ function isNpmAuditCommand(command) {
778
+ return NPM_AUDIT_RE.test(command);
779
+ }
780
+ function classifyShellCommandOutcome(input) {
781
+ const stdout = input.stdout ?? "";
782
+ const stderr = input.stderr ?? "";
783
+ const interleaved = input.interleavedOutput ?? "";
784
+ if (isNpmAuditCommand(input.command)) {
785
+ const body = stdout.trim() || interleaved.trim() || stderr.trim();
786
+ return classifyNpmAuditOutcome({
787
+ exitCode: input.exitCode,
788
+ stdout: body,
789
+ stderr
790
+ });
791
+ }
792
+ if (input.exitCode === 0) {
793
+ return {
794
+ kind: "success",
795
+ exitCode: 0,
796
+ summary: `command succeeded (exit 0)`
797
+ };
798
+ }
799
+ const tail = tidy(interleaved || stdout || stderr || `exit ${input.exitCode}`, 180);
800
+ return {
801
+ kind: "command_failure",
802
+ exitCode: input.exitCode,
803
+ summary: `command failed (exit ${input.exitCode}): ${tail}`
804
+ };
805
+ }
806
+
807
+ // src/stream.ts
590
808
  function eventTimestampIso(event) {
591
809
  const tsMs = event.timestamp_ms;
592
810
  return event.timestamp || event.ts || (tsMs ? new Date(tsMs).toISOString() : void 0);
@@ -607,16 +825,43 @@ function recordStreamResult(result, event) {
607
825
  result.error = String(event.result || event.api_error_status || "stream result error");
608
826
  }
609
827
  }
828
+ function shellPayloadFromCursorEvent(event) {
829
+ if (event.type !== "tool_call" || event.subtype !== "completed") return null;
830
+ const toolCall = event.tool_call && typeof event.tool_call === "object" && !Array.isArray(event.tool_call) ? event.tool_call : null;
831
+ const shell = toolCall?.shellToolCall;
832
+ if (!shell || typeof shell !== "object" || Array.isArray(shell)) return null;
833
+ const shellObj = shell;
834
+ const args = shellObj.args;
835
+ const command = args && typeof args === "object" && !Array.isArray(args) && typeof args.command === "string" ? String(args.command) : "";
836
+ const result = shellObj.result;
837
+ if (!result || typeof result !== "object" || Array.isArray(result)) return null;
838
+ const body = result.success ?? result.failure;
839
+ if (!body || typeof body !== "object" || Array.isArray(body)) return null;
840
+ const row = body;
841
+ const exitCode = typeof row.exitCode === "number" ? row.exitCode : 0;
842
+ return {
843
+ command,
844
+ exitCode,
845
+ stdout: typeof row.stdout === "string" ? row.stdout : "",
846
+ stderr: typeof row.stderr === "string" ? row.stderr : "",
847
+ interleaved: typeof row.interleavedOutput === "string" ? row.interleavedOutput : ""
848
+ };
849
+ }
850
+ function applyShellOutcome(parsed, outcome) {
851
+ if (outcome.kind === "success") return;
852
+ parsed.lastShellOutcome = outcome;
853
+ }
610
854
  function parseHarnessStream(file) {
611
855
  const result = {
612
856
  firstEventAt: null,
613
857
  lastEventAt: null,
614
858
  currentTool: null,
615
859
  finalResult: null,
616
- error: null
860
+ error: null,
861
+ lastShellOutcome: null
617
862
  };
618
863
  if (!existsSync6(file)) return result;
619
- const lines = readFileSync4(file, "utf8").split("\n").filter(Boolean);
864
+ const lines = readFileSync5(file, "utf8").split("\n").filter(Boolean);
620
865
  for (const line of lines) {
621
866
  const event = safeJson(line);
622
867
  if (!event) continue;
@@ -641,12 +886,44 @@ function parseHarnessStream(file) {
641
886
  const name = cursorToolNameFromCall(toolCall);
642
887
  if (name) result.currentTool = name;
643
888
  }
889
+ const shell = shellPayloadFromCursorEvent(event);
890
+ if (shell) {
891
+ applyShellOutcome(
892
+ result,
893
+ classifyShellCommandOutcome({
894
+ command: shell.command,
895
+ exitCode: shell.exitCode,
896
+ stdout: shell.stdout,
897
+ stderr: shell.stderr,
898
+ interleavedOutput: shell.interleaved
899
+ })
900
+ );
901
+ }
644
902
  if (event.type === "result") {
645
903
  recordStreamResult(result, event);
646
904
  }
647
905
  }
648
906
  return result;
649
907
  }
908
+ function summarizeShellToolCallEvent(event) {
909
+ const shell = shellPayloadFromCursorEvent(event);
910
+ if (!shell) return void 0;
911
+ const outcome = classifyShellCommandOutcome({
912
+ command: shell.command,
913
+ exitCode: shell.exitCode,
914
+ stdout: shell.stdout,
915
+ stderr: shell.stderr,
916
+ interleavedOutput: shell.interleaved
917
+ });
918
+ const cmd = oneLine(shell.command).slice(0, 120);
919
+ if (outcome.kind === "audit_findings") {
920
+ return `[audit:findings] ${outcome.summary}${cmd ? ` \xB7 ${cmd}` : ""}`;
921
+ }
922
+ if (outcome.kind === "command_failure") {
923
+ return `[command:failed] ${outcome.summary}${cmd ? ` \xB7 ${cmd}` : ""}`;
924
+ }
925
+ return `[command:ok] exit 0${cmd ? ` \xB7 ${cmd}` : ""}`;
926
+ }
650
927
  function summarizeEvent(event) {
651
928
  if (event.type === "system" && event.subtype) {
652
929
  return `[system:${event.subtype}] ${String(event.status || event.cwd || "")}`.trim();
@@ -679,6 +956,8 @@ function summarizeEvent(event) {
679
956
  }
680
957
  if (event.type === "tool_call") {
681
958
  const subtype = String(event.subtype || "");
959
+ const shellSummary = subtype === "completed" ? summarizeShellToolCallEvent(event) : void 0;
960
+ if (shellSummary) return shellSummary;
682
961
  const toolCall = event.tool_call && typeof event.tool_call === "object" && !Array.isArray(event.tool_call) ? event.tool_call : void 0;
683
962
  const name = cursorToolNameFromCall(toolCall) ?? "tool";
684
963
  return `[tool:${subtype}] ${name}`;
@@ -720,7 +999,7 @@ var FAILURE_PATTERNS = [
720
999
  label: "provider authentication failed"
721
1000
  }
722
1001
  ];
723
- function tidy(errorText, max = 240) {
1002
+ function tidy2(errorText, max = 240) {
724
1003
  const oneLine2 = errorText.replace(/\s+/g, " ").trim();
725
1004
  return oneLine2.length > max ? `${oneLine2.slice(0, max - 1)}\u2026` : oneLine2;
726
1005
  }
@@ -729,7 +1008,7 @@ function classifyExitFailure(errorText) {
729
1008
  if (!text) return null;
730
1009
  for (const pattern of FAILURE_PATTERNS) {
731
1010
  if (pattern.test.test(text)) {
732
- return { blocked: true, reason: `${pattern.label}: ${tidy(text)}` };
1011
+ return { blocked: true, reason: `${pattern.label}: ${tidy2(text)}` };
733
1012
  }
734
1013
  }
735
1014
  return null;
@@ -795,6 +1074,53 @@ function assessExitedWorkerSalvage(input) {
795
1074
 
796
1075
  // src/git.ts
797
1076
  import { spawnSync } from "node:child_process";
1077
+
1078
+ // src/worker-env.ts
1079
+ var FORBIDDEN_WORKER_ENV_KEYS = [
1080
+ "ANTHROPIC_API_KEY",
1081
+ "ANALYST_API_KEY",
1082
+ "RECRUITER_API_KEY",
1083
+ "AUTH_SECRET",
1084
+ "NEXTAUTH_SECRET",
1085
+ "DATABASE_URL",
1086
+ "PRODUCTION_DATABASE_URL",
1087
+ "REDIS_URL",
1088
+ "GOOGLE_CLIENT_SECRET",
1089
+ "GITHUB_CLIENT_SECRET",
1090
+ "KYNVER_API_KEY",
1091
+ "KYNVER_SERVICE_SECRET",
1092
+ "KYNVER_RUNTIME_SECRET",
1093
+ "OPENCLAW_CRON_SECRET",
1094
+ "QSTASH_TOKEN",
1095
+ "QSTASH_CURRENT_SIGNING_KEY",
1096
+ "QSTASH_NEXT_SIGNING_KEY",
1097
+ "TOOL_SECRETS_KEK",
1098
+ "TOOL_EXECUTOR_DISPATCH_SECRET",
1099
+ "CLOUDFLARE_API_TOKEN",
1100
+ "STRIPE_SECRET_KEY",
1101
+ "STRIPE_WEBHOOK_SECRET",
1102
+ "STRIPE_IDENTITY_WEBHOOK_SECRET",
1103
+ "VOYAGE_API_KEY",
1104
+ "PERPLEXITY_API_KEY",
1105
+ "FRED_API_KEY",
1106
+ "FMP_API_KEY",
1107
+ "CURSOR_API_KEY"
1108
+ ];
1109
+ var FORBIDDEN_KEY_SET = new Set(FORBIDDEN_WORKER_ENV_KEYS);
1110
+ var FORBIDDEN_SUFFIXES = ["_SECRET", "_API_KEY"];
1111
+ function isForbiddenWorkerEnvKey(key) {
1112
+ if (FORBIDDEN_KEY_SET.has(key)) return true;
1113
+ return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));
1114
+ }
1115
+ function scrubWorkerEnv(env) {
1116
+ const next = { ...env };
1117
+ for (const key of Object.keys(next)) {
1118
+ if (isForbiddenWorkerEnvKey(key)) delete next[key];
1119
+ }
1120
+ return next;
1121
+ }
1122
+
1123
+ // src/git.ts
798
1124
  function git(cwd, args, options = {}) {
799
1125
  const res = spawnSync("git", args, { cwd, encoding: "utf8" });
800
1126
  if (res.status !== 0 && !options.allowFailure) {
@@ -910,11 +1236,6 @@ function unknownAncestry(base, error, head = null) {
910
1236
  error
911
1237
  };
912
1238
  }
913
- function scrubClaudeEnv(env) {
914
- const next = { ...env };
915
- delete next.ANTHROPIC_API_KEY;
916
- return next;
917
- }
918
1239
 
919
1240
  // src/landing-gate.ts
920
1241
  function trimOrNull2(value) {
@@ -1264,15 +1585,7 @@ function computeAutoMaxWorkers(totalMemBytes, opts = {}) {
1264
1585
  return Math.min(raw, AUTO_MAX_WORKERS_CEILING);
1265
1586
  }
1266
1587
  function readAvailableMemBytes() {
1267
- if (process.platform === "linux") {
1268
- try {
1269
- const meminfo = readFileSync5("/proc/meminfo", "utf8");
1270
- const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m);
1271
- if (match) return Number(match[1]) * 1024;
1272
- } catch {
1273
- }
1274
- }
1275
- return os.freemem();
1588
+ return readMemAvailableBytes();
1276
1589
  }
1277
1590
  function isActiveHarnessWorker(worker) {
1278
1591
  const status = computeWorkerStatus(worker);
@@ -1282,7 +1595,7 @@ function countActiveWorkersForRun(run) {
1282
1595
  let active = 0;
1283
1596
  for (const name of Object.keys(run.workers || {})) {
1284
1597
  const worker = readJson(
1285
- path5.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1598
+ path6.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1286
1599
  void 0
1287
1600
  );
1288
1601
  if (!worker || !isActiveHarnessWorker(worker)) continue;
@@ -1300,7 +1613,7 @@ function observeRunnerResourceGate(input) {
1300
1613
  input.config,
1301
1614
  input.configuredMaxWorkersOverride
1302
1615
  );
1303
- const totalMemBytes = input.totalMemBytes ?? os.totalmem();
1616
+ const totalMemBytes = input.totalMemBytes ?? os2.totalmem();
1304
1617
  const freeMemBytes = input.freeMemBytes ?? readAvailableMemBytes();
1305
1618
  const activeWorkers = input.activeWorkers ?? countActiveWorkersGlobal();
1306
1619
  const budgetBytes = Math.max(0, Math.floor(totalMemBytes * memUtilization) - memReserveBytes);
@@ -1490,6 +1803,7 @@ var claudeProvider = {
1490
1803
  const model = preflight.model;
1491
1804
  const stdoutFd = openSync(opts.stdoutPath, "a");
1492
1805
  const stderrFd = openSync(opts.stderrPath, "a");
1806
+ const stdio = ["ignore", stdoutFd, stderrFd];
1493
1807
  const child = spawn(
1494
1808
  "claude",
1495
1809
  [
@@ -1507,8 +1821,8 @@ var claudeProvider = {
1507
1821
  hiddenSpawnOptions({
1508
1822
  cwd: opts.worktreePath,
1509
1823
  detached: true,
1510
- stdio: ["ignore", stdoutFd, stderrFd],
1511
- env: scrubClaudeEnv(process.env)
1824
+ stdio,
1825
+ env: scrubWorkerEnv(process.env)
1512
1826
  })
1513
1827
  );
1514
1828
  closeSync(stdoutFd);
@@ -1666,10 +1980,10 @@ function readHarnessRetryLimits() {
1666
1980
  }
1667
1981
 
1668
1982
  // src/lease-renewal.ts
1669
- import path6 from "node:path";
1983
+ import path7 from "node:path";
1670
1984
  function workerRecord(runId, name) {
1671
1985
  return readJson(
1672
- path6.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
1986
+ path7.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
1673
1987
  void 0
1674
1988
  );
1675
1989
  }
@@ -1736,7 +2050,7 @@ function hasLiveWorkerForTask(runId, taskId) {
1736
2050
 
1737
2051
  // src/supervisor.ts
1738
2052
  import { existsSync as existsSync10, mkdirSync as mkdirSync3 } from "node:fs";
1739
- import path12 from "node:path";
2053
+ import path13 from "node:path";
1740
2054
 
1741
2055
  // src/prompt.ts
1742
2056
  function buildPrompt(input) {
@@ -1755,6 +2069,16 @@ function buildPrompt(input) {
1755
2069
  "- After implementation: wait for report_reviewer then deep_reviewer confirmation (via MCP/session agents) before follow-up rows close.",
1756
2070
  input.planId ? `Active planId: ${input.planId}${input.taskId ? ` \xB7 taskId: ${input.taskId}` : ""}` : "No planId on this worker \u2014 still emit progress when you touch plan-scoped work."
1757
2071
  ];
2072
+ const mergeGateLines = compact ? [
2073
+ "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)."
2074
+ ] : [
2075
+ "GitHub Actions merge-gate cost control (Kynver/Hermes PRs):",
2076
+ "- Prefer local cached package verification (`node scripts/verify-pr-local.mjs --emit-json`) and Vercel preview evidence before GitHub Actions.",
2077
+ "- 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.",
2078
+ "- Record evidence: POST `/api/agent-os/by-id/<agentOsId>/pr-merge-gate/refresh` with prUrl + local/vercel payloads.",
2079
+ "- Request the final Actions run only when local + Vercel are green: POST `.../pr-merge-gate/request-run` (applies `merge-gate` label).",
2080
+ "- Empty failed Actions jobs (no runner/steps/logs) are infra/quota \u2014 do not enter repair loops; escalate to operator."
2081
+ ];
1758
2082
  const planArtifactLines = compact ? [
1759
2083
  "Plan artifacts: when authoring/revising docs/superpowers/plans/, open a GitHub PR early and iterate from that PR branch; do not leave the canonical plan only in the harness worktree."
1760
2084
  ] : [
@@ -1776,10 +2100,14 @@ function buildPrompt(input) {
1776
2100
  "Completion handoff (required): before you stop, ensure the harness records a final result \u2014 summarize outcome in your last message and append a heartbeat line with phase `complete`. If you leave uncommitted changes or committed work without a PR, the orchestrator blocks completion until a GitHub PR exists (or you discard/commit cleanly). Exiting with only dirty files and no PR routes to salvage review, not production review.",
1777
2101
  "PR-ready handoff: for substantial implementation work, commit, push, and open a GitHub PR (draft OK) on your branch before finishing \u2014 or rely on the harness to run `gh pr create` at completion when `gh` is authenticated.",
1778
2102
  "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.",
2103
+ "npm publish boundary: do not run `npm publish`, do not republish `@kynver-app/*` packages, and do not block on an operator to publish. When you need newer runtime code than npm, use this repo checkout (`npm run kynver:build`, `npm run kynver`) and record evidence: packages/kynver-runtime/package.json version + git ref in your completion report.",
1779
2104
  "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.",
2105
+ "Landing-wrapper cleanup on a git ref: use `node scripts/agent-os-land-pr-cleanup-verify.mjs --ref origin/main` (JSON, exit 0). Do not use `git show <ref>:scripts/agent-os-land-pr.mjs | rg \u2026` \u2014 ripgrep exit 1 when markers are absent is reported as a failed command in Telegram/status tooling.",
1780
2106
  "",
1781
2107
  ...progressLines,
1782
2108
  "",
2109
+ ...mergeGateLines,
2110
+ "",
1783
2111
  ...planArtifactLines,
1784
2112
  "",
1785
2113
  ...input.personaMarkdown?.trim() ? [input.personaMarkdown.trim(), ""] : [],
@@ -1792,11 +2120,11 @@ function buildPrompt(input) {
1792
2120
  // src/providers/cursor.ts
1793
2121
  import { closeSync as closeSync2, existsSync as existsSync8, openSync as openSync2 } from "node:fs";
1794
2122
  import { spawn as spawn2 } from "node:child_process";
1795
- import path8 from "node:path";
2123
+ import path9 from "node:path";
1796
2124
 
1797
2125
  // src/providers/cursor-windows.ts
1798
2126
  import { existsSync as existsSync7, readdirSync as readdirSync3 } from "node:fs";
1799
- import path7 from "node:path";
2127
+ import path8 from "node:path";
1800
2128
  var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
1801
2129
  function parseCursorVersionSortKey(versionName) {
1802
2130
  const datePart = versionName.split("-")[0];
@@ -1807,7 +2135,7 @@ function parseCursorVersionSortKey(versionName) {
1807
2135
  return Number(`${year}${month.padStart(2, "0")}${day.padStart(2, "0")}`);
1808
2136
  }
1809
2137
  function pickLatestCursorVersionDir(agentRoot) {
1810
- const versionsRoot = path7.join(agentRoot, "versions");
2138
+ const versionsRoot = path8.join(agentRoot, "versions");
1811
2139
  if (!existsSync7(versionsRoot)) return null;
1812
2140
  let bestDir = null;
1813
2141
  let bestKey = -1;
@@ -1816,21 +2144,21 @@ function pickLatestCursorVersionDir(agentRoot) {
1816
2144
  const key = parseCursorVersionSortKey(entry.name);
1817
2145
  if (key == null || key <= bestKey) continue;
1818
2146
  bestKey = key;
1819
- bestDir = path7.join(versionsRoot, entry.name);
2147
+ bestDir = path8.join(versionsRoot, entry.name);
1820
2148
  }
1821
2149
  return bestDir;
1822
2150
  }
1823
2151
  function resolveWindowsCursorBundled(agentRoot) {
1824
- const root = agentRoot?.trim() || path7.join(process.env.LOCALAPPDATA || "", "cursor-agent");
1825
- const directNode = path7.join(root, "node.exe");
1826
- const directIndex = path7.join(root, "index.js");
2152
+ const root = agentRoot?.trim() || path8.join(process.env.LOCALAPPDATA || "", "cursor-agent");
2153
+ const directNode = path8.join(root, "node.exe");
2154
+ const directIndex = path8.join(root, "index.js");
1827
2155
  if (existsSync7(directNode) && existsSync7(directIndex)) {
1828
2156
  return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
1829
2157
  }
1830
2158
  const versionDir = pickLatestCursorVersionDir(root);
1831
2159
  if (!versionDir) return null;
1832
- const nodeExe = path7.join(versionDir, "node.exe");
1833
- const indexJs = path7.join(versionDir, "index.js");
2160
+ const nodeExe = path8.join(versionDir, "node.exe");
2161
+ const indexJs = path8.join(versionDir, "index.js");
1834
2162
  if (!existsSync7(nodeExe) || !existsSync7(indexJs)) return null;
1835
2163
  return { nodeExe, indexJs, versionDir };
1836
2164
  }
@@ -1849,13 +2177,13 @@ function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
1849
2177
  function resolveCursorSpawn(agentBin) {
1850
2178
  if (process.platform === "win32") {
1851
2179
  const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
1852
- const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync8(path8.join(path8.dirname(agentBin), "index.js"));
2180
+ const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync8(path9.join(path9.dirname(agentBin), "index.js"));
1853
2181
  const isDefaultShim = agentBin === "agent";
1854
2182
  if (isCursorWrapper || isBundledNode || isDefaultShim) {
1855
- const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path8.dirname(agentBin)) : isBundledNode ? {
2183
+ const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path9.dirname(agentBin)) : isBundledNode ? {
1856
2184
  nodeExe: agentBin,
1857
- indexJs: path8.join(path8.dirname(agentBin), "index.js"),
1858
- versionDir: path8.dirname(agentBin)
2185
+ indexJs: path9.join(path9.dirname(agentBin), "index.js"),
2186
+ versionDir: path9.dirname(agentBin)
1859
2187
  } : resolveWindowsCursorBundled();
1860
2188
  if (bundled) {
1861
2189
  return bundledSpawnTarget(bundled.nodeExe, bundled.indexJs, bundled.versionDir);
@@ -1875,18 +2203,18 @@ function resolveAgentBin() {
1875
2203
  process.env.KYNVER_CURSOR_AGENT_ROOT?.trim() || void 0
1876
2204
  );
1877
2205
  if (bundled) return bundled.nodeExe;
1878
- const localAgent = path8.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
2206
+ const localAgent = path9.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
1879
2207
  if (existsSync8(localAgent)) return localAgent;
1880
2208
  }
1881
2209
  return "agent";
1882
2210
  }
1883
2211
  function cursorWorkerEnv(agentBin, spawnTarget) {
1884
- return {
2212
+ return scrubWorkerEnv({
1885
2213
  ...process.env,
1886
2214
  CI: "1",
1887
2215
  NO_COLOR: "1",
1888
- ...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path8.basename(agentBin) || "agent.cmd" } : {}
1889
- };
2216
+ ...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path9.basename(agentBin) || "agent.cmd" } : {}
2217
+ });
1890
2218
  }
1891
2219
  var cursorProvider = {
1892
2220
  name: "cursor",
@@ -1902,6 +2230,7 @@ var cursorProvider = {
1902
2230
  const model = preflight.model;
1903
2231
  const stdoutFd = openSync2(opts.stdoutPath, "a");
1904
2232
  const stderrFd = openSync2(opts.stderrPath, "a");
2233
+ const stdio = ["ignore", stdoutFd, stderrFd];
1905
2234
  const agentBin = resolveAgentBin();
1906
2235
  const spawnTarget = resolveCursorSpawn(agentBin);
1907
2236
  const child = spawn2(
@@ -1924,7 +2253,7 @@ var cursorProvider = {
1924
2253
  cwd: opts.worktreePath,
1925
2254
  detached: spawnTarget.detached,
1926
2255
  shell: spawnTarget.shell,
1927
- stdio: ["ignore", stdoutFd, stderrFd],
2256
+ stdio,
1928
2257
  env: cursorWorkerEnv(agentBin, spawnTarget)
1929
2258
  })
1930
2259
  );
@@ -1959,7 +2288,7 @@ function resolveWorkerProvider(name) {
1959
2288
  // src/auto-complete.ts
1960
2289
  import { spawn as spawn3 } from "node:child_process";
1961
2290
  import { existsSync as existsSync9, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
1962
- import path11 from "node:path";
2291
+ import path12 from "node:path";
1963
2292
  import { fileURLToPath } from "node:url";
1964
2293
 
1965
2294
  // src/completion-ack.ts
@@ -1976,7 +2305,40 @@ function persistCompletionAck(worker, runId, fields) {
1976
2305
  }
1977
2306
 
1978
2307
  // src/worker-ops.ts
1979
- import path10 from "node:path";
2308
+ import path11 from "node:path";
2309
+
2310
+ // src/completion-response.ts
2311
+ function asRecord(value) {
2312
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
2313
+ }
2314
+ function asString(value) {
2315
+ if (typeof value !== "string") return null;
2316
+ const trimmed = value.trim();
2317
+ return trimmed.length ? trimmed : null;
2318
+ }
2319
+ var ADVANCED_OUTCOMES = /* @__PURE__ */ new Set([
2320
+ "review_scheduled",
2321
+ "review_already_scheduled"
2322
+ ]);
2323
+ function summarizeHarnessCompletionResponse(parsed) {
2324
+ const record = asRecord(parsed);
2325
+ if (!record) {
2326
+ return { routeOutcome: null, taskAdvanced: false, detail: null };
2327
+ }
2328
+ const outcome = asString(record.outcome);
2329
+ const detail = asString(record.detail) ?? asString(record.error);
2330
+ const task = asRecord(record.task);
2331
+ const taskStatus = task ? asString(task.status) : null;
2332
+ const taskAdvanced = outcome !== null && ADVANCED_OUTCOMES.has(outcome) || taskStatus === "awaiting_review" || taskStatus === "done";
2333
+ return {
2334
+ routeOutcome: outcome,
2335
+ taskAdvanced,
2336
+ detail
2337
+ };
2338
+ }
2339
+ function completionPostSucceeded(summary) {
2340
+ return summary.taskAdvanced;
2341
+ }
1980
2342
 
1981
2343
  // src/pr-handoff/pr-handoff-assess.ts
1982
2344
  var REVIEW_LANE_RULE = /^(lane:)?(review|deep_review|planning|landing)(:|$)/i;
@@ -2038,6 +2400,36 @@ function buildPrHandoffSnapshotFromStatus(status, extras) {
2038
2400
 
2039
2401
  // src/pr-handoff/pr-handoff-gh.ts
2040
2402
  import { spawnSync as spawnSync2 } from "node:child_process";
2403
+
2404
+ // src/github-repo.ts
2405
+ function parseGithubOwnerRepo(remoteUrl) {
2406
+ const trimmed = remoteUrl.trim();
2407
+ if (!trimmed) return null;
2408
+ const ssh = trimmed.match(/^git@github\.com:([^/]+\/[^/\s]+?)(?:\.git)?$/i);
2409
+ if (ssh) return normalizeOwnerRepo(ssh[1]);
2410
+ const scp = trimmed.match(/^ssh:\/\/git@github\.com\/([^/]+\/[^/\s]+?)(?:\.git)?$/i);
2411
+ if (scp) return normalizeOwnerRepo(scp[1]);
2412
+ try {
2413
+ const url = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`);
2414
+ if (url.hostname.toLowerCase() !== "github.com") return null;
2415
+ const parts = url.pathname.replace(/^\/+|\/+$/g, "").split("/");
2416
+ if (parts.length < 2) return null;
2417
+ const [owner, repo] = parts;
2418
+ if (!owner || !repo) return null;
2419
+ return `${owner}/${repo.replace(/\.git$/i, "")}`;
2420
+ } catch {
2421
+ return null;
2422
+ }
2423
+ }
2424
+ function normalizeOwnerRepo(value) {
2425
+ const parts = value.split("/").filter(Boolean);
2426
+ if (parts.length < 2) return null;
2427
+ const owner = parts[0];
2428
+ const repo = parts[1].replace(/\.git$/i, "");
2429
+ return owner && repo ? `${owner}/${repo}` : null;
2430
+ }
2431
+
2432
+ // src/pr-handoff/pr-handoff-gh.ts
2041
2433
  function capture(bin, cwd, args) {
2042
2434
  try {
2043
2435
  const res = spawnSync2(bin, args, { cwd, encoding: "utf8" });
@@ -2060,21 +2452,13 @@ var defaultPrHandoffExec = {
2060
2452
  git: (cwd, args) => gitCapture(cwd, args),
2061
2453
  gh: (cwd, args) => capture("gh", cwd, args)
2062
2454
  };
2063
- function parseGithubRepo(remoteUrl) {
2064
- const trimmed = remoteUrl.trim();
2065
- const ssh = trimmed.match(/git@github\.com:([^/]+\/[^/.]+)(?:\.git)?/i);
2066
- if (ssh) return ssh[1];
2067
- const https = trimmed.match(/github\.com[/:]([^/]+\/[^/.]+?)(?:\.git)?/i);
2068
- if (https) return https[1];
2069
- return null;
2070
- }
2071
2455
  function firstLine(text) {
2072
2456
  return text.split("\n").map((l) => l.trim()).find(Boolean) ?? "";
2073
2457
  }
2074
2458
  function resolveGithubRepo(worktreePath, exec) {
2075
2459
  const remote = exec.git(worktreePath, ["remote", "get-url", "origin"]);
2076
2460
  if (remote.status !== 0) return null;
2077
- return parseGithubRepo(remote.stdout);
2461
+ return parseGithubOwnerRepo(remote.stdout);
2078
2462
  }
2079
2463
  function resolveHeadCommit(worktreePath, exec) {
2080
2464
  const head = exec.git(worktreePath, ["rev-parse", "HEAD"]);
@@ -2313,7 +2697,7 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
2313
2697
  }
2314
2698
 
2315
2699
  // src/worker-lifecycle.ts
2316
- import path9 from "node:path";
2700
+ import path10 from "node:path";
2317
2701
  var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
2318
2702
  "awaiting_review",
2319
2703
  "blocked",
@@ -2369,7 +2753,7 @@ function syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick) {
2369
2753
  const synced = [];
2370
2754
  for (const name of Object.keys(run.workers || {})) {
2371
2755
  const worker = readJson(
2372
- path9.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2756
+ path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2373
2757
  void 0
2374
2758
  );
2375
2759
  if (!worker?.taskId || isCompletionAcknowledged(worker)) continue;
@@ -2408,10 +2792,10 @@ function completionErrorText(parsed) {
2408
2792
  }
2409
2793
  return void 0;
2410
2794
  }
2411
- function asRecord(value) {
2795
+ function asRecord2(value) {
2412
2796
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
2413
2797
  }
2414
- function asString(value) {
2798
+ function asString2(value) {
2415
2799
  if (typeof value !== "string") return null;
2416
2800
  const trimmed = value.trim();
2417
2801
  return trimmed.length ? trimmed : null;
@@ -2531,7 +2915,27 @@ async function tryCompleteWorker(args) {
2531
2915
  }
2532
2916
  }
2533
2917
  if (result.ok) {
2918
+ const summary = summarizeHarnessCompletionResponse(result.parsed);
2919
+ if (!completionPostSucceeded(summary)) {
2920
+ const detail2 = summary.detail ?? (summary.routeOutcome ? `harness completion returned ${summary.routeOutcome}` : "harness completion did not advance the linked task");
2921
+ const reason2 = `completion acknowledged but board not advanced: ${detail2}`;
2922
+ persistCompletionBlocker(worker, reason2);
2923
+ const ack2 = {
2924
+ completionReportedAt: (/* @__PURE__ */ new Date()).toISOString(),
2925
+ completionOutcome: "rejected",
2926
+ completionResponse: result.parsed
2927
+ };
2928
+ persistCompletionAck(worker, worker.runId, ack2);
2929
+ return {
2930
+ ok: false,
2931
+ httpStatus: result.status,
2932
+ response: result.parsed,
2933
+ reason: reason2,
2934
+ completionBlocked: true
2935
+ };
2936
+ }
2534
2937
  persistCompletionBlocker(worker, void 0);
2938
+ const routeOutcome = summary.routeOutcome ?? "acknowledged";
2535
2939
  const ack = {
2536
2940
  completionReportedAt: (/* @__PURE__ */ new Date()).toISOString(),
2537
2941
  completionOutcome: "acknowledged",
@@ -2544,6 +2948,7 @@ async function tryCompleteWorker(args) {
2544
2948
  ok: true,
2545
2949
  httpStatus: result.status,
2546
2950
  response: result.parsed,
2951
+ reason: routeOutcome,
2547
2952
  ...prUrl ? { prHandoff: { prUrl } } : {}
2548
2953
  };
2549
2954
  }
@@ -2606,7 +3011,7 @@ function workerStatus(args) {
2606
3011
  const worker = loadWorker(String(args.run), String(args.name));
2607
3012
  const run = loadRun(worker.runId);
2608
3013
  const status = computeWorkerStatus(worker, workerStatusOptions(run));
2609
- writeJson(path10.join(worker.workerDir, "last-status.json"), status);
3014
+ writeJson(path11.join(worker.workerDir, "last-status.json"), status);
2610
3015
  console.log(JSON.stringify(status, null, 2));
2611
3016
  }
2612
3017
  function buildRunBoard(runId) {
@@ -2614,7 +3019,7 @@ function buildRunBoard(runId) {
2614
3019
  const names = Object.keys(run.workers || {});
2615
3020
  const workers = names.map((name) => {
2616
3021
  const worker = readJson(
2617
- path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
3022
+ path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2618
3023
  void 0
2619
3024
  );
2620
3025
  if (!worker) {
@@ -2636,22 +3041,23 @@ function buildRunBoard(runId) {
2636
3041
  const completionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
2637
3042
  const boardStatus = completionBlocker ? "blocked" : status.status;
2638
3043
  const boardAttention = completionBlocker ? "blocked" : status.attention.state;
2639
- const completionResponse = asRecord(worker.completionResponse);
2640
- const completionTask = asRecord(completionResponse?.task);
2641
- const completionOutcome = asString(completionResponse?.outcome);
2642
- const completionRouteStatus = asString(completionResponse?.status);
3044
+ const completionResponse = asRecord2(worker.completionResponse);
3045
+ const completionTask = asRecord2(completionResponse?.task);
3046
+ const completionOutcome = asString2(completionResponse?.outcome);
3047
+ const completionRouteStatus = asString2(completionResponse?.status);
2643
3048
  const completionWarnings = Array.isArray(completionResponse?.warnings) ? completionResponse.warnings.filter((w) => typeof w === "string" && w.trim().length > 0) : [];
2644
- const prUrl = asString(completionTask?.prUrl) ?? asString(completionResponse?.prUrl);
3049
+ const prUrl = asString2(completionTask?.prUrl) ?? asString2(completionResponse?.prUrl);
3050
+ const completionReportedAt = asString2(worker.completionReportedAt);
2645
3051
  const lifecycleStage = deriveLifecycleStage({
2646
3052
  finished: isFinishedWorkerStatus(status),
2647
3053
  completionBlocker,
2648
3054
  completionOutcome,
2649
- completionReportedAt: worker.completionReportedAt ?? null
3055
+ completionReportedAt
2650
3056
  });
2651
3057
  const nextAction = deriveNextAction({
2652
3058
  completionBlocker,
2653
3059
  completionOutcome,
2654
- completionReportedAt: worker.completionReportedAt ?? null,
3060
+ completionReportedAt,
2655
3061
  finished: isFinishedWorkerStatus(status)
2656
3062
  });
2657
3063
  const handoffState = deriveHandoffState({
@@ -2697,7 +3103,7 @@ function buildRunBoard(runId) {
2697
3103
  gitAncestry: status.gitAncestry,
2698
3104
  finalResult: status.finalResult,
2699
3105
  lifecycleStage,
2700
- completionReportedAt: worker.completionReportedAt ?? null,
3106
+ completionReportedAt,
2701
3107
  completionOutcome: worker.completionOutcome ?? null,
2702
3108
  completionRouteStatus,
2703
3109
  completionRouteOutcome: completionOutcome,
@@ -2724,7 +3130,7 @@ function buildRunBoard(runId) {
2724
3130
  needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
2725
3131
  workers
2726
3132
  };
2727
- writeJson(path10.join(runDirectory(run.id), "last-board.json"), board);
3133
+ writeJson(path11.join(runDirectory(run.id), "last-board.json"), board);
2728
3134
  return board;
2729
3135
  }
2730
3136
  async function publishHarnessBoardSnapshot(args, source) {
@@ -2913,12 +3319,12 @@ async function autoCompleteWorkerCli(raw) {
2913
3319
  }
2914
3320
  }
2915
3321
  function resolveDefaultCliPath() {
2916
- return path11.join(fileURLToPath(new URL(".", import.meta.url)), "cli.js");
3322
+ return path12.join(fileURLToPath(new URL(".", import.meta.url)), "cli.js");
2917
3323
  }
2918
3324
  function spawnCompletionSidecar(opts) {
2919
3325
  const cliPath = opts.cliPath ?? resolveDefaultCliPath();
2920
3326
  if (!existsSync9(cliPath)) return void 0;
2921
- const logPath = path11.join(opts.workerDir, "auto-complete.log");
3327
+ const logPath = path12.join(opts.workerDir, "auto-complete.log");
2922
3328
  let logFd;
2923
3329
  try {
2924
3330
  logFd = openSync3(logPath, "a");
@@ -3000,16 +3406,16 @@ function spawnWorkerProcess(run, opts) {
3000
3406
  launchModel = preflight.model;
3001
3407
  }
3002
3408
  const { worktreesDir } = getPaths();
3003
- const workerDir = path12.join(runDirectory(run.id), "workers", name);
3409
+ const workerDir = path13.join(runDirectory(run.id), "workers", name);
3004
3410
  mkdirSync3(workerDir, { recursive: true });
3005
- const worktreePath = path12.join(worktreesDir, run.id, name);
3411
+ const worktreePath = path13.join(worktreesDir, run.id, name);
3006
3412
  const branch = opts.branch || `agent/${run.id}/${name}`;
3007
3413
  if (existsSync10(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
3008
3414
  git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
3009
3415
  git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
3010
- const stdoutPath = path12.join(workerDir, "stdout.jsonl");
3011
- const stderrPath = path12.join(workerDir, "stderr.log");
3012
- const heartbeatPath = path12.join(workerDir, "heartbeat.jsonl");
3416
+ const stdoutPath = path13.join(workerDir, "stdout.jsonl");
3417
+ const stderrPath = path13.join(workerDir, "stderr.log");
3418
+ const heartbeatPath = path13.join(workerDir, "heartbeat.jsonl");
3013
3419
  const prompt = buildPrompt({
3014
3420
  task: opts.task,
3015
3421
  ownedPaths: opts.ownedPaths || [],
@@ -3070,7 +3476,7 @@ function spawnWorkerProcess(run, opts) {
3070
3476
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
3071
3477
  };
3072
3478
  saveWorker(run.id, worker);
3073
- run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path12.join(workerDir, "worker.json") } };
3479
+ run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path13.join(workerDir, "worker.json") } };
3074
3480
  run.status = "running";
3075
3481
  saveRun(run);
3076
3482
  if (worker.agentOsId && worker.taskId) {
@@ -3362,18 +3768,18 @@ function buildPlanPersistIdempotencyKey(input) {
3362
3768
 
3363
3769
  // src/plan-persist/paths.ts
3364
3770
  import { mkdirSync as mkdirSync4 } from "node:fs";
3365
- import { homedir as homedir3 } from "node:os";
3366
- import path13 from "node:path";
3771
+ import { homedir as homedir4 } from "node:os";
3772
+ import path14 from "node:path";
3367
3773
  function resolveKynverStateRoot() {
3368
3774
  const env = process.env.KYNVER_STATE_ROOT;
3369
- if (env) return path13.resolve(env);
3370
- return path13.join(homedir3(), ".kynver", "state");
3775
+ if (env) return path14.resolve(env);
3776
+ return path14.join(homedir4(), ".kynver", "state");
3371
3777
  }
3372
3778
  function planOutboxDir() {
3373
- return path13.join(resolveKynverStateRoot(), "plan-outbox");
3779
+ return path14.join(resolveKynverStateRoot(), "plan-outbox");
3374
3780
  }
3375
3781
  function planOutboxArchiveDir() {
3376
- return path13.join(resolveKynverStateRoot(), "plan-outbox-archive");
3782
+ return path14.join(resolveKynverStateRoot(), "plan-outbox-archive");
3377
3783
  }
3378
3784
  function ensurePlanOutboxDirs() {
3379
3785
  const outboxDir = planOutboxDir();
@@ -3384,8 +3790,8 @@ function ensurePlanOutboxDirs() {
3384
3790
  }
3385
3791
  function isTmpOnlyPath(filePath) {
3386
3792
  if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
3387
- const resolved = path13.resolve(filePath);
3388
- return resolved.startsWith("/tmp/") || resolved.startsWith(path13.join("/var", "folders"));
3793
+ const resolved = path14.resolve(filePath);
3794
+ return resolved.startsWith("/tmp/") || resolved.startsWith(path14.join("/var", "folders"));
3389
3795
  }
3390
3796
 
3391
3797
  // src/plan-persist/outbox-store.ts
@@ -3397,7 +3803,7 @@ import {
3397
3803
  writeFileSync as writeFileSync3,
3398
3804
  unlinkSync
3399
3805
  } from "node:fs";
3400
- import path14 from "node:path";
3806
+ import path15 from "node:path";
3401
3807
  import { randomUUID } from "node:crypto";
3402
3808
  var DEFAULT_MAX_RETRIES = 12;
3403
3809
  function listOutboxItems() {
@@ -3405,7 +3811,7 @@ function listOutboxItems() {
3405
3811
  const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
3406
3812
  const items = [];
3407
3813
  for (const file of files) {
3408
- const item = readOutboxItem(path14.join(outboxDir, file));
3814
+ const item = readOutboxItem(path15.join(outboxDir, file));
3409
3815
  if (item && item.queueStatus === "queued") items.push(item);
3410
3816
  }
3411
3817
  return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
@@ -3426,7 +3832,7 @@ function readOutboxItem(jsonPath) {
3426
3832
  }
3427
3833
  function readOutboxBody(item) {
3428
3834
  const { outboxDir } = ensurePlanOutboxDirs();
3429
- const bodyFile = path14.join(outboxDir, item.bodyPath);
3835
+ const bodyFile = path15.join(outboxDir, item.bodyPath);
3430
3836
  return readFileSync6(bodyFile, "utf8");
3431
3837
  }
3432
3838
  function writeOutboxItem(input, opts) {
@@ -3434,8 +3840,8 @@ function writeOutboxItem(input, opts) {
3434
3840
  const now = (/* @__PURE__ */ new Date()).toISOString();
3435
3841
  const id = opts.existing?.id ?? randomUUID();
3436
3842
  const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
3437
- const jsonPath = path14.join(outboxDir, `${id}.json`);
3438
- const bodyFile = path14.join(outboxDir, bodyPath);
3843
+ const jsonPath = path15.join(outboxDir, `${id}.json`);
3844
+ const bodyFile = path15.join(outboxDir, bodyPath);
3439
3845
  if (!opts.existing) {
3440
3846
  writeFileSync3(bodyFile, input.body, "utf8");
3441
3847
  }
@@ -3471,24 +3877,24 @@ function writeOutboxItem(input, opts) {
3471
3877
  }
3472
3878
  function saveOutboxItem(item) {
3473
3879
  const { outboxDir } = ensurePlanOutboxDirs();
3474
- const jsonPath = path14.join(outboxDir, `${item.id}.json`);
3880
+ const jsonPath = path15.join(outboxDir, `${item.id}.json`);
3475
3881
  writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
3476
3882
  `, { mode: 384 });
3477
3883
  }
3478
3884
  function archiveOutboxItem(item) {
3479
3885
  const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
3480
- const jsonSrc = path14.join(outboxDir, `${item.id}.json`);
3481
- const bodySrc = path14.join(outboxDir, item.bodyPath);
3482
- const jsonDst = path14.join(archiveDir, `${item.id}.json`);
3483
- const bodyDst = path14.join(archiveDir, item.bodyPath);
3886
+ const jsonSrc = path15.join(outboxDir, `${item.id}.json`);
3887
+ const bodySrc = path15.join(outboxDir, item.bodyPath);
3888
+ const jsonDst = path15.join(archiveDir, `${item.id}.json`);
3889
+ const bodyDst = path15.join(archiveDir, item.bodyPath);
3484
3890
  if (existsSync12(jsonSrc)) renameSync(jsonSrc, jsonDst);
3485
3891
  if (existsSync12(bodySrc)) renameSync(bodySrc, bodyDst);
3486
3892
  }
3487
3893
  function outboxItemPaths(item) {
3488
3894
  const { outboxDir } = ensurePlanOutboxDirs();
3489
3895
  return {
3490
- jsonPath: path14.join(outboxDir, `${item.id}.json`),
3491
- bodyPath: path14.join(outboxDir, item.bodyPath)
3896
+ jsonPath: path15.join(outboxDir, `${item.id}.json`),
3897
+ bodyPath: path15.join(outboxDir, item.bodyPath)
3492
3898
  };
3493
3899
  }
3494
3900
  function outboxInputFromItem(item, body) {
@@ -3674,7 +4080,7 @@ function markOutboxFailed(item, message) {
3674
4080
  }
3675
4081
 
3676
4082
  // src/plan-persist/drain.ts
3677
- import path15 from "node:path";
4083
+ import path16 from "node:path";
3678
4084
  async function drainPlanOutbox(opts = {}, deps = {}) {
3679
4085
  const items = listOutboxItems().filter(
3680
4086
  (item) => opts.outboxId ? item.id === opts.outboxId : true
@@ -3708,7 +4114,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
3708
4114
  return result;
3709
4115
  }
3710
4116
  function loadOutboxById(outboxId) {
3711
- const jsonPath = path15.join(planOutboxDir(), `${outboxId}.json`);
4117
+ const jsonPath = path16.join(planOutboxDir(), `${outboxId}.json`);
3712
4118
  return readOutboxItem(jsonPath);
3713
4119
  }
3714
4120
 
@@ -3808,7 +4214,7 @@ async function dispatchRun(args) {
3808
4214
  const activeHarnessWorkers = [];
3809
4215
  for (const name of Object.keys(run.workers || {})) {
3810
4216
  const worker = readJson(
3811
- path16.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4217
+ path17.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
3812
4218
  void 0
3813
4219
  );
3814
4220
  if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
@@ -3832,7 +4238,8 @@ async function dispatchRun(args) {
3832
4238
  harnessBoardSnapshot: buildRunBoard(run.id),
3833
4239
  ...args.lane ? { lane: String(args.lane) } : {},
3834
4240
  executor: args.executor ? String(args.executor) : "harness",
3835
- ...args.diskPath ? { diskPath: String(args.diskPath) } : {}
4241
+ ...args.diskPath ? { diskPath: String(args.diskPath) } : {},
4242
+ ...args.targetTaskId ? { targetTaskId: String(args.targetTaskId) } : {}
3836
4243
  };
3837
4244
  const dispatch = await postJsonWithCredentialRefresh(dispatchUrl, secret, body, { agentOsId, baseUrl: base });
3838
4245
  const responseBody = dispatch.response;
@@ -4012,7 +4419,7 @@ async function dispatchRun(args) {
4012
4419
  }
4013
4420
 
4014
4421
  // src/sweep.ts
4015
- import path17 from "node:path";
4422
+ import path18 from "node:path";
4016
4423
  async function sweepRun(args) {
4017
4424
  const pipeline = args.pipeline === true || args.pipeline === "true";
4018
4425
  try {
@@ -4025,7 +4432,7 @@ async function sweepRun(args) {
4025
4432
  const releasedLocalOrphans = [];
4026
4433
  for (const name of Object.keys(run.workers || {})) {
4027
4434
  const worker = readJson(
4028
- path17.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4435
+ path18.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4029
4436
  void 0
4030
4437
  );
4031
4438
  if (!worker || !worker.dispatched || !worker.taskId) continue;
@@ -4069,10 +4476,10 @@ async function sweepRun(args) {
4069
4476
 
4070
4477
  // src/worktree.ts
4071
4478
  import { existsSync as existsSync13, mkdirSync as mkdirSync5 } from "node:fs";
4072
- import path19 from "node:path";
4479
+ import path20 from "node:path";
4073
4480
 
4074
4481
  // src/validate.ts
4075
- import path18 from "node:path";
4482
+ import path19 from "node:path";
4076
4483
  var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
4077
4484
  function validateRunId(runId) {
4078
4485
  const trimmed = runId.trim();
@@ -4080,7 +4487,7 @@ function validateRunId(runId) {
4080
4487
  return trimmed;
4081
4488
  }
4082
4489
  function validateRepo(repo) {
4083
- const resolved = path18.resolve(repo);
4490
+ const resolved = path19.resolve(repo);
4084
4491
  if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
4085
4492
  return resolved;
4086
4493
  }
@@ -4105,12 +4512,12 @@ function createRun(args) {
4105
4512
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4106
4513
  workers: {}
4107
4514
  };
4108
- writeJson(path19.join(dir, "run.json"), run);
4515
+ writeJson(path20.join(dir, "run.json"), run);
4109
4516
  console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
4110
4517
  }
4111
4518
  function listRuns() {
4112
4519
  const { runsDir } = getPaths();
4113
- const rows = listRunIds(runsDir).map((id) => readJson(path19.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
4520
+ const rows = listRunIds(runsDir).map((id) => readJson(path20.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
4114
4521
  id: run.id,
4115
4522
  name: run.name,
4116
4523
  status: run.status,
@@ -4125,7 +4532,7 @@ function failExists(message) {
4125
4532
  }
4126
4533
 
4127
4534
  // src/pipeline-tick.ts
4128
- import path28 from "node:path";
4535
+ import path30 from "node:path";
4129
4536
 
4130
4537
  // src/pipeline-dispatch.ts
4131
4538
  var RESERVED_REVIEW_STARTS = 1;
@@ -4179,10 +4586,10 @@ async function runPipelineDispatch(args, slots) {
4179
4586
  }
4180
4587
 
4181
4588
  // src/stale-reconcile.ts
4182
- import path21 from "node:path";
4589
+ import path22 from "node:path";
4183
4590
 
4184
4591
  // src/finalize.ts
4185
- import path20 from "node:path";
4592
+ import path21 from "node:path";
4186
4593
  var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
4187
4594
  function terminalStatusFor(run) {
4188
4595
  const names = Object.keys(run.workers || {});
@@ -4193,7 +4600,7 @@ function terminalStatusFor(run) {
4193
4600
  let anyLandingBlocked = false;
4194
4601
  for (const name of names) {
4195
4602
  const worker = readJson(
4196
- path20.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4603
+ path21.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4197
4604
  void 0
4198
4605
  );
4199
4606
  if (!worker) continue;
@@ -4245,7 +4652,7 @@ function reconcileStaleWorkers() {
4245
4652
  const now = Date.now();
4246
4653
  for (const run of listRunRecords()) {
4247
4654
  for (const name of Object.keys(run.workers || {})) {
4248
- const workerPath = path21.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
4655
+ const workerPath = path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
4249
4656
  const worker = readJson(workerPath, void 0);
4250
4657
  if (!worker || worker.status !== "running") {
4251
4658
  outcomes.push({
@@ -4339,7 +4746,7 @@ function reconcileRunsCli() {
4339
4746
  }
4340
4747
 
4341
4748
  // src/plan-progress-daemon-sync.ts
4342
- import path22 from "node:path";
4749
+ import path23 from "node:path";
4343
4750
 
4344
4751
  // src/plan-progress-sync.ts
4345
4752
  async function syncPlanProgress(args) {
@@ -4363,7 +4770,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
4363
4770
  const outcomes = [];
4364
4771
  for (const name of Object.keys(run.workers || {})) {
4365
4772
  const worker = readJson(
4366
- path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4773
+ path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4367
4774
  void 0
4368
4775
  );
4369
4776
  if (!worker?.dispatched || !worker.taskId) continue;
@@ -4412,7 +4819,7 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
4412
4819
  }
4413
4820
 
4414
4821
  // src/cleanup.ts
4415
- import path27 from "node:path";
4822
+ import path28 from "node:path";
4416
4823
 
4417
4824
  // src/cleanup-types.ts
4418
4825
  var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
@@ -4484,11 +4891,11 @@ function skipNodeModulesRemoval(input) {
4484
4891
 
4485
4892
  // src/cleanup-execute.ts
4486
4893
  import { existsSync as existsSync15, rmSync } from "node:fs";
4487
- import path24 from "node:path";
4894
+ import path25 from "node:path";
4488
4895
 
4489
4896
  // src/cleanup-dir-size.ts
4490
4897
  import { existsSync as existsSync14, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
4491
- import path23 from "node:path";
4898
+ import path24 from "node:path";
4492
4899
  function directorySizeBytes(root, maxEntries = 5e4) {
4493
4900
  if (!existsSync14(root)) return 0;
4494
4901
  let total = 0;
@@ -4504,7 +4911,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
4504
4911
  }
4505
4912
  for (const name of entries) {
4506
4913
  if (seen++ > maxEntries) return null;
4507
- const full = path23.join(current, name);
4914
+ const full = path24.join(current, name);
4508
4915
  let st;
4509
4916
  try {
4510
4917
  st = statSync2(full);
@@ -4588,20 +4995,20 @@ function removeWorktree(candidate, execute) {
4588
4995
  }
4589
4996
  }
4590
4997
  function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
4591
- const resolved = path24.resolve(targetPath);
4592
- const nm = resolved.endsWith(`${path24.sep}node_modules`) ? resolved : null;
4998
+ const resolved = path25.resolve(targetPath);
4999
+ const nm = resolved.endsWith(`${path25.sep}node_modules`) ? resolved : null;
4593
5000
  if (!nm) return "path_outside_harness";
4594
- const rel = path24.relative(worktreesDir, nm);
4595
- if (rel.startsWith("..") || path24.isAbsolute(rel)) return "path_outside_harness";
4596
- const parts = rel.split(path24.sep);
5001
+ const rel = path25.relative(worktreesDir, nm);
5002
+ if (rel.startsWith("..") || path25.isAbsolute(rel)) return "path_outside_harness";
5003
+ const parts = rel.split(path25.sep);
4597
5004
  if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
4598
- if (!resolved.startsWith(path24.resolve(harnessRoot))) return "path_outside_harness";
5005
+ if (!resolved.startsWith(path25.resolve(harnessRoot))) return "path_outside_harness";
4599
5006
  return null;
4600
5007
  }
4601
5008
 
4602
5009
  // src/cleanup-scan.ts
4603
5010
  import { existsSync as existsSync16, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
4604
- import path25 from "node:path";
5011
+ import path26 from "node:path";
4605
5012
  function pathAgeMs(target, now) {
4606
5013
  try {
4607
5014
  const mtime = statSync3(target).mtimeMs;
@@ -4611,17 +5018,17 @@ function pathAgeMs(target, now) {
4611
5018
  }
4612
5019
  }
4613
5020
  function isPathInside(child, parent) {
4614
- const rel = path25.relative(parent, child);
4615
- return rel === "" || !rel.startsWith("..") && !path25.isAbsolute(rel);
5021
+ const rel = path26.relative(parent, child);
5022
+ return rel === "" || !rel.startsWith("..") && !path26.isAbsolute(rel);
4616
5023
  }
4617
5024
  function scanNodeModulesCandidates(opts) {
4618
5025
  const candidates = [];
4619
5026
  const seen = /* @__PURE__ */ new Set();
4620
5027
  for (const entry of opts.index.values()) {
4621
5028
  if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
4622
- const nm = path25.join(entry.worktreePath, "node_modules");
5029
+ const nm = path26.join(entry.worktreePath, "node_modules");
4623
5030
  if (!existsSync16(nm)) continue;
4624
- const resolved = path25.resolve(nm);
5031
+ const resolved = path26.resolve(nm);
4625
5032
  if (seen.has(resolved)) continue;
4626
5033
  seen.add(resolved);
4627
5034
  candidates.push({
@@ -4637,13 +5044,13 @@ function scanNodeModulesCandidates(opts) {
4637
5044
  if (!opts.includeOrphans || !existsSync16(opts.worktreesDir)) return candidates;
4638
5045
  for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
4639
5046
  if (!runEntry.isDirectory()) continue;
4640
- const runPath = path25.join(opts.worktreesDir, runEntry.name);
5047
+ const runPath = path26.join(opts.worktreesDir, runEntry.name);
4641
5048
  for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
4642
5049
  if (!workerEntry.isDirectory()) continue;
4643
- const worktreePath = path25.join(runPath, workerEntry.name);
4644
- const nm = path25.join(worktreePath, "node_modules");
5050
+ const worktreePath = path26.join(runPath, workerEntry.name);
5051
+ const nm = path26.join(worktreePath, "node_modules");
4645
5052
  if (!existsSync16(nm)) continue;
4646
- const resolved = path25.resolve(nm);
5053
+ const resolved = path26.resolve(nm);
4647
5054
  if (seen.has(resolved)) continue;
4648
5055
  if (!isPathInside(resolved, opts.harnessRoot)) continue;
4649
5056
  seen.add(resolved);
@@ -4683,17 +5090,17 @@ function scanWorktreeCandidates(opts) {
4683
5090
  }
4684
5091
 
4685
5092
  // src/cleanup-worktree-index.ts
4686
- import path26 from "node:path";
5093
+ import path27 from "node:path";
4687
5094
  function buildWorktreeIndex() {
4688
5095
  const index = /* @__PURE__ */ new Map();
4689
5096
  for (const run of listRunRecords()) {
4690
5097
  for (const name of Object.keys(run.workers || {})) {
4691
- const workerPath = path26.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
5098
+ const workerPath = path27.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
4692
5099
  const worker = readJson(workerPath, void 0);
4693
5100
  if (!worker?.worktreePath) continue;
4694
5101
  const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
4695
- index.set(path26.resolve(worker.worktreePath), {
4696
- worktreePath: path26.resolve(worker.worktreePath),
5102
+ index.set(path27.resolve(worker.worktreePath), {
5103
+ worktreePath: path27.resolve(worker.worktreePath),
4697
5104
  runId: run.id,
4698
5105
  workerName: name,
4699
5106
  run,
@@ -4707,8 +5114,8 @@ function buildWorktreeIndex() {
4707
5114
 
4708
5115
  // src/cleanup.ts
4709
5116
  function resolveOptions(options = {}) {
4710
- const harnessRoot = options.harnessRoot ? path27.resolve(options.harnessRoot) : resolveHarnessRoot();
4711
- const { worktreesDir } = options.harnessRoot ? { worktreesDir: path27.join(harnessRoot, "worktrees") } : getHarnessPaths();
5117
+ const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
5118
+ const { worktreesDir } = options.harnessRoot ? { worktreesDir: path28.join(harnessRoot, "worktrees") } : getHarnessPaths();
4712
5119
  const execute = options.execute === true;
4713
5120
  const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
4714
5121
  const worktreesAgeMs = options.worktreesAgeMs ?? 0;
@@ -4752,7 +5159,7 @@ function runHarnessCleanup(options = {}) {
4752
5159
  actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
4753
5160
  continue;
4754
5161
  }
4755
- const worktreePath = path27.resolve(candidate.path, "..");
5162
+ const worktreePath = path28.resolve(candidate.path, "..");
4756
5163
  const indexed = index.get(worktreePath) ?? null;
4757
5164
  const guardReason = skipNodeModulesRemoval({
4758
5165
  indexed,
@@ -4768,7 +5175,7 @@ function runHarnessCleanup(options = {}) {
4768
5175
  actions.push(removeNodeModules(candidate, resolved.execute));
4769
5176
  }
4770
5177
  for (const candidate of scanWorktreeCandidates(scanOpts)) {
4771
- const indexed = index.get(path27.resolve(candidate.path)) ?? null;
5178
+ const indexed = index.get(path28.resolve(candidate.path)) ?? null;
4772
5179
  const guardReason = skipWorktreeRemoval({
4773
5180
  indexed,
4774
5181
  includeOrphans: resolved.includeOrphans,
@@ -4831,13 +5238,62 @@ function isPipelineCleanupEnabled() {
4831
5238
  return process.env.KYNVER_PIPELINE_CLEANUP !== "0";
4832
5239
  }
4833
5240
 
5241
+ // src/installed-package-versions.ts
5242
+ import { readFile } from "node:fs/promises";
5243
+ import { homedir as homedir5 } from "node:os";
5244
+ import path29 from "node:path";
5245
+ var MANAGED_PACKAGES = [
5246
+ "@kynver-app/runtime",
5247
+ "@kynver-app/openclaw-agent-os",
5248
+ "@kynver-app/mcp-agent-os"
5249
+ ];
5250
+ function trim(value) {
5251
+ const out = value?.trim();
5252
+ return out ? out : null;
5253
+ }
5254
+ function unique(values) {
5255
+ return [...new Set(values.filter((value) => Boolean(value)))];
5256
+ }
5257
+ function moduleRoots() {
5258
+ const home = homedir5();
5259
+ const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path29.join(home, ".openclaw", "npm");
5260
+ const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path29.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path29.join(home, ".npm-global", "lib", "node_modules"));
5261
+ return unique([
5262
+ path29.join(openClawPrefix, "lib", "node_modules"),
5263
+ path29.join(openClawPrefix, "node_modules"),
5264
+ npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path29.join(npmGlobalRoot, "lib", "node_modules")
5265
+ ]);
5266
+ }
5267
+ async function readVersion(packageJsonPath) {
5268
+ try {
5269
+ const parsed = JSON.parse(await readFile(packageJsonPath, "utf8"));
5270
+ return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : null;
5271
+ } catch {
5272
+ return null;
5273
+ }
5274
+ }
5275
+ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new Date()).toISOString()) {
5276
+ const roots = moduleRoots();
5277
+ const out = {};
5278
+ for (const packageName of MANAGED_PACKAGES) {
5279
+ for (const root of roots) {
5280
+ const packageJsonPath = path29.join(root, packageName, "package.json");
5281
+ const version = await readVersion(packageJsonPath);
5282
+ if (!version) continue;
5283
+ out[packageName] = { version, observedAt, path: packageJsonPath };
5284
+ break;
5285
+ }
5286
+ }
5287
+ return out;
5288
+ }
5289
+
4834
5290
  // src/pipeline-tick.ts
4835
5291
  async function completeFinishedWorkers(runId, args) {
4836
5292
  const run = loadRun(runId);
4837
5293
  const outcomes = [];
4838
5294
  for (const name of Object.keys(run.workers || {})) {
4839
5295
  const worker = readJson(
4840
- path28.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
5296
+ path30.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4841
5297
  void 0
4842
5298
  );
4843
5299
  if (!worker?.taskId || worker.localOnly) continue;
@@ -4868,12 +5324,14 @@ async function postOperatorTick(agentOsId, runId, resourceGate, args) {
4868
5324
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
4869
5325
  const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, { baseUrl: base });
4870
5326
  const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/operator/tick`;
5327
+ const packageVersions = await collectInstalledPackageVersions();
4871
5328
  const res = await postJson(url, secret, {
4872
5329
  agentOsId,
4873
5330
  runId,
4874
5331
  ingestHarness: true,
4875
5332
  harnessBoardSnapshot: buildRunBoard(runId),
4876
- resourceGate
5333
+ resourceGate,
5334
+ packageVersions
4877
5335
  });
4878
5336
  return { ok: res.ok, httpStatus: res.status, response: res.response };
4879
5337
  }
@@ -4976,6 +5434,231 @@ async function runDaemon(args) {
4976
5434
  console.error(JSON.stringify({ event: "daemon_stop", runId, agentOsId }));
4977
5435
  }
4978
5436
 
5437
+ // src/plan-progress.ts
5438
+ import path31 from "node:path";
5439
+
5440
+ // src/bounded-build/constants.ts
5441
+ var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
5442
+ var DEFAULT_BUILD_MEM_RESERVE_BYTES = 2 * 1024 * 1024 * 1024;
5443
+ var DEFAULT_NODE_OLD_SPACE_SIZE_MB = 1024;
5444
+ var DEFAULT_SYSTEMD_MEMORY_MAX = "1.5G";
5445
+ var DEFAULT_SYSTEMD_MEMORY_SWAP_MAX = "2G";
5446
+
5447
+ // src/bounded-build/node-options.ts
5448
+ var MAX_OLD_SPACE_RE = /--max-old-space-size=(\d+)/;
5449
+ function parsePositiveInt(value, fallback) {
5450
+ const n = Number(value);
5451
+ if (!Number.isFinite(n) || n <= 0) return fallback;
5452
+ return Math.floor(n);
5453
+ }
5454
+ function resolveNodeOldSpaceSizeMb() {
5455
+ return parsePositiveInt(process.env.KYNVER_NODE_OLD_SPACE_SIZE_MB, DEFAULT_NODE_OLD_SPACE_SIZE_MB);
5456
+ }
5457
+ function mergeNodeOptionsForBuildCheck(baseEnv = process.env) {
5458
+ const env = { ...baseEnv };
5459
+ if (process.env.KYNVER_NODE_OLD_SPACE_SIZE_MB === "0") {
5460
+ return env;
5461
+ }
5462
+ const existing = env.NODE_OPTIONS ?? "";
5463
+ if (MAX_OLD_SPACE_RE.test(existing)) {
5464
+ return env;
5465
+ }
5466
+ const mb = resolveNodeOldSpaceSizeMb();
5467
+ const flag = `--max-old-space-size=${mb}`;
5468
+ env.NODE_OPTIONS = existing.trim() ? `${existing.trim()} ${flag}` : flag;
5469
+ return env;
5470
+ }
5471
+ function formatNodeOptionsFlag(mb = resolveNodeOldSpaceSizeMb()) {
5472
+ return `--max-old-space-size=${mb}`;
5473
+ }
5474
+
5475
+ // src/bounded-build/systemd-wrap.ts
5476
+ import { spawnSync as spawnSync3 } from "node:child_process";
5477
+ var systemdAvailableCache;
5478
+ function isSystemdRunAvailable() {
5479
+ if (process.env.KYNVER_BUILD_SKIP_SYSTEMD === "1" || process.env.KYNVER_BUILD_SKIP_SYSTEMD === "true") {
5480
+ return false;
5481
+ }
5482
+ if (systemdAvailableCache !== void 0) return systemdAvailableCache;
5483
+ if (process.platform !== "linux") {
5484
+ systemdAvailableCache = false;
5485
+ return false;
5486
+ }
5487
+ const res = spawnSync3("systemd-run", ["--version"], { encoding: "utf8", stdio: ["ignore", "ignore", "pipe"] });
5488
+ systemdAvailableCache = res.status === 0;
5489
+ return systemdAvailableCache;
5490
+ }
5491
+ function buildSystemdRunArgv(opts) {
5492
+ const memoryMax = opts.memoryMax ?? process.env.KYNVER_BUILD_SYSTEMD_MEMORY_MAX ?? DEFAULT_SYSTEMD_MEMORY_MAX;
5493
+ const memorySwapMax = opts.memorySwapMax ?? process.env.KYNVER_BUILD_SYSTEMD_MEMORY_SWAP_MAX ?? DEFAULT_SYSTEMD_MEMORY_SWAP_MAX;
5494
+ const argv = [
5495
+ "systemd-run",
5496
+ "--scope",
5497
+ "--collect",
5498
+ "-p",
5499
+ `MemoryMax=${memoryMax}`,
5500
+ "-p",
5501
+ `MemorySwapMax=${memorySwapMax}`
5502
+ ];
5503
+ if (opts.cwd) {
5504
+ argv.push("--working-directory", opts.cwd);
5505
+ }
5506
+ argv.push("--", ...opts.command);
5507
+ return argv;
5508
+ }
5509
+
5510
+ // src/bounded-build/admission.ts
5511
+ import { spawnSync as spawnSync4 } from "node:child_process";
5512
+ function positiveInt3(value, fallback) {
5513
+ const n = Number(value);
5514
+ if (!Number.isFinite(n) || n <= 0) return fallback;
5515
+ return Math.floor(n);
5516
+ }
5517
+ function resolveBuildAdmissionConfig(config = loadUserConfig()) {
5518
+ const envBudget = process.env.KYNVER_BUILD_MEM_BUDGET_BYTES ? positiveInt3(process.env.KYNVER_BUILD_MEM_BUDGET_BYTES, DEFAULT_BUILD_MEM_BUDGET_BYTES) : void 0;
5519
+ const envReserve = process.env.KYNVER_BUILD_MEM_RESERVE_BYTES ? positiveInt3(process.env.KYNVER_BUILD_MEM_RESERVE_BYTES, DEFAULT_BUILD_MEM_RESERVE_BYTES) : void 0;
5520
+ return {
5521
+ perBuildBudgetBytes: envBudget ?? positiveInt3(config.perWorkerMemBytes, DEFAULT_BUILD_MEM_BUDGET_BYTES),
5522
+ reserveBytes: envReserve ?? positiveInt3(config.memReserveBytes, DEFAULT_BUILD_MEM_RESERVE_BYTES)
5523
+ };
5524
+ }
5525
+ var activeBuilds = 0;
5526
+ function registerBuildStart() {
5527
+ activeBuilds += 1;
5528
+ }
5529
+ function registerBuildEnd() {
5530
+ activeBuilds = Math.max(0, activeBuilds - 1);
5531
+ }
5532
+ function assessBuildAdmission(opts = {}) {
5533
+ const cfg = { ...resolveBuildAdmissionConfig(), ...opts };
5534
+ const memAvailableBytes = opts.memAvailableBytes ?? readMemAvailableBytes();
5535
+ const requiredBytes = cfg.perBuildBudgetBytes + cfg.reserveBytes;
5536
+ const admitted = memAvailableBytes >= requiredBytes;
5537
+ return {
5538
+ admitted,
5539
+ memAvailableBytes,
5540
+ requiredBytes,
5541
+ activeBuilds,
5542
+ reason: admitted ? null : `insufficient memory: need ${requiredBytes} bytes available (budget ${cfg.perBuildBudgetBytes} + reserve ${cfg.reserveBytes}), have ${memAvailableBytes}`
5543
+ };
5544
+ }
5545
+ function sleepMs2(ms) {
5546
+ if (ms <= 0) return;
5547
+ spawnSync4(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
5548
+ stdio: "ignore"
5549
+ });
5550
+ }
5551
+ function waitForBuildAdmission(timeoutMs, pollMs = 2e3, opts = {}) {
5552
+ const deadline = Date.now() + Math.max(0, timeoutMs);
5553
+ let verdict = assessBuildAdmission({
5554
+ ...opts,
5555
+ memAvailableBytes: opts.memAvailableBytes?.()
5556
+ });
5557
+ while (!verdict.admitted && Date.now() < deadline) {
5558
+ sleepMs2(Math.min(pollMs, deadline - Date.now()));
5559
+ verdict = assessBuildAdmission({
5560
+ ...opts,
5561
+ memAvailableBytes: opts.memAvailableBytes?.()
5562
+ });
5563
+ }
5564
+ return verdict;
5565
+ }
5566
+
5567
+ // src/bounded-build/exec.ts
5568
+ import { spawnSync as spawnSync5 } from "node:child_process";
5569
+ function envArgv(env) {
5570
+ const out = [];
5571
+ for (const [key, value] of Object.entries(env)) {
5572
+ if (value === void 0) continue;
5573
+ out.push(`${key}=${value}`);
5574
+ }
5575
+ return out;
5576
+ }
5577
+ function runSpawn(argv, opts) {
5578
+ const res = spawnSync5(argv[0], argv.slice(1), {
5579
+ cwd: opts.cwd,
5580
+ env: opts.env,
5581
+ encoding: "utf8",
5582
+ stdio: ["ignore", "pipe", "pipe"],
5583
+ shell: opts.shell,
5584
+ timeout: opts.timeoutMs
5585
+ });
5586
+ return {
5587
+ exitCode: res.status ?? 1,
5588
+ stdout: (res.stdout ?? "").trim(),
5589
+ stderr: (res.stderr ?? "").trim()
5590
+ };
5591
+ }
5592
+ function runBoundedBuildCheck(input) {
5593
+ const waitMs = input.waitForAdmissionMs ?? 6e5;
5594
+ const admission = waitMs > 0 ? waitForBuildAdmission(waitMs) : assessBuildAdmission();
5595
+ if (!admission.admitted) {
5596
+ return {
5597
+ ok: false,
5598
+ exitCode: 1,
5599
+ stdout: "",
5600
+ stderr: admission.reason ?? "build admission denied",
5601
+ admitted: false,
5602
+ wrappedWithSystemd: false,
5603
+ nodeOptionsFlag: formatNodeOptionsFlag(),
5604
+ admission,
5605
+ command: input.command
5606
+ };
5607
+ }
5608
+ const env = mergeNodeOptionsForBuildCheck({ ...process.env, ...input.env });
5609
+ const nodeOptionsFlag = formatNodeOptionsFlag();
5610
+ const useSystemd = isSystemdRunAvailable();
5611
+ registerBuildStart();
5612
+ try {
5613
+ let result;
5614
+ if (useSystemd) {
5615
+ const argv = buildSystemdRunArgv({
5616
+ cwd: input.cwd,
5617
+ command: ["/usr/bin/env", ...envArgv(env), "/bin/bash", "-lc", input.command]
5618
+ });
5619
+ result = runSpawn(argv, { cwd: input.cwd, env, timeoutMs: input.timeoutMs });
5620
+ } else {
5621
+ result = runSpawn([input.command], {
5622
+ cwd: input.cwd,
5623
+ env,
5624
+ shell: true,
5625
+ timeoutMs: input.timeoutMs
5626
+ });
5627
+ }
5628
+ return {
5629
+ ok: result.exitCode === 0,
5630
+ exitCode: result.exitCode,
5631
+ stdout: result.stdout,
5632
+ stderr: result.stderr,
5633
+ admitted: true,
5634
+ wrappedWithSystemd: useSystemd,
5635
+ nodeOptionsFlag,
5636
+ admission,
5637
+ command: input.command
5638
+ };
5639
+ } finally {
5640
+ registerBuildEnd();
5641
+ }
5642
+ }
5643
+
5644
+ // src/harness-verify.ts
5645
+ var DEFAULT_HARNESS_VERIFY_COMMANDS = ["npm run typecheck", "npm run test"];
5646
+ function runHarnessVerifyCommands(cwd, commands = DEFAULT_HARNESS_VERIFY_COMMANDS, opts = {}) {
5647
+ const steps = [];
5648
+ let passed = true;
5649
+ for (const command of commands) {
5650
+ const result = runBoundedBuildCheck({
5651
+ cwd,
5652
+ command,
5653
+ waitForAdmissionMs: opts.waitForAdmissionMs,
5654
+ timeoutMs: opts.timeoutMs
5655
+ });
5656
+ steps.push({ command, result });
5657
+ if (!result.ok) passed = false;
5658
+ }
5659
+ return { passed, steps };
5660
+ }
5661
+
4979
5662
  // src/plan-progress.ts
4980
5663
  function parseEvidenceArg(raw) {
4981
5664
  const idx = raw.indexOf(":");
@@ -5036,8 +5719,23 @@ async function emitPlanProgress(args) {
5036
5719
  }
5037
5720
  console.log(JSON.stringify(parsed, null, 2));
5038
5721
  }
5722
+ function verifyPlanLocal(args) {
5723
+ const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
5724
+ const cwd = path31.resolve(worktree);
5725
+ const summary = runHarnessVerifyCommands(cwd);
5726
+ const emitJson = args.json === true || args.json === "true";
5727
+ const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
5728
+ if (emitJson) console.log(JSON.stringify(payload, null, 2));
5729
+ else console.log(summary.passed ? "local plan verify passed" : "local plan verify failed");
5730
+ if (!summary.passed) process.exit(1);
5731
+ }
5039
5732
  async function verifyPlan(args) {
5040
5733
  const planId = required(args.plan ? String(args.plan) : void 0, "plan");
5734
+ const localOnly = args.local === true || args.local === "true";
5735
+ if (localOnly) {
5736
+ verifyPlanLocal(args);
5737
+ return;
5738
+ }
5041
5739
  const slug = loadUserConfig().agentOsSlug;
5042
5740
  if (!slug) {
5043
5741
  console.error("requires agentOsSlug in ~/.kynver/config.json for verify (session route)");
@@ -5071,6 +5769,52 @@ async function verifyPlan(args) {
5071
5769
  console.log(JSON.stringify(parsed, null, 2));
5072
5770
  }
5073
5771
 
5772
+ // src/harness-verify-cli.ts
5773
+ import path32 from "node:path";
5774
+ function runHarnessVerifyCli(args) {
5775
+ const cwd = path32.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
5776
+ const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
5777
+ const commands = [];
5778
+ const rawCmd = args.command;
5779
+ if (Array.isArray(rawCmd)) {
5780
+ for (const c of rawCmd) commands.push(String(c));
5781
+ } else if (typeof rawCmd === "string") {
5782
+ commands.push(rawCmd);
5783
+ }
5784
+ const summary = runHarnessVerifyCommands(
5785
+ cwd,
5786
+ commands.length ? commands : DEFAULT_HARNESS_VERIFY_COMMANDS,
5787
+ {
5788
+ waitForAdmissionMs: args.waitForAdmissionMs ? Number(args.waitForAdmissionMs) : void 0,
5789
+ timeoutMs: args.timeoutMs ? Number(args.timeoutMs) : void 0
5790
+ }
5791
+ );
5792
+ const payload = {
5793
+ passed: summary.passed,
5794
+ worktree: cwd,
5795
+ steps: summary.steps.map((s) => ({
5796
+ command: s.command,
5797
+ ok: s.result.ok,
5798
+ exitCode: s.result.exitCode,
5799
+ admitted: s.result.admitted,
5800
+ wrappedWithSystemd: s.result.wrappedWithSystemd,
5801
+ nodeOptionsFlag: s.result.nodeOptionsFlag,
5802
+ admission: s.result.admission,
5803
+ stderr: s.result.stderr.slice(0, 4e3)
5804
+ }))
5805
+ };
5806
+ if (emitJson) {
5807
+ console.log(JSON.stringify(payload, null, 2));
5808
+ } else {
5809
+ console.log(summary.passed ? "harness verify passed" : "harness verify failed");
5810
+ for (const step of payload.steps) {
5811
+ console.log(` ${step.ok ? "\u2713" : "\u2717"} ${step.command} (exit ${step.exitCode}, systemd=${step.wrappedWithSystemd})`);
5812
+ if (!step.ok && step.stderr) console.log(` ${step.stderr.split("\n")[0]}`);
5813
+ }
5814
+ }
5815
+ process.exit(summary.passed ? 0 : 1);
5816
+ }
5817
+
5074
5818
  // src/plan-persist-cli.ts
5075
5819
  import { readFileSync as readFileSync7 } from "node:fs";
5076
5820
  var OPERATIONS = ["create", "add_version", "update_metadata"];
@@ -5172,7 +5916,7 @@ function runCleanupCli(args) {
5172
5916
  }
5173
5917
 
5174
5918
  // src/monitor/monitor.service.ts
5175
- import path30 from "node:path";
5919
+ import path34 from "node:path";
5176
5920
 
5177
5921
  // src/monitor/monitor.classify.ts
5178
5922
  function expectedLeaseOwner(runId) {
@@ -5229,10 +5973,10 @@ function classifyWorkerHealth(input) {
5229
5973
 
5230
5974
  // src/monitor/monitor.store.ts
5231
5975
  import { existsSync as existsSync17, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
5232
- import path29 from "node:path";
5976
+ import path33 from "node:path";
5233
5977
  function monitorsDir() {
5234
5978
  const { harnessRoot } = getHarnessPaths();
5235
- const dir = path29.join(harnessRoot, "monitors");
5979
+ const dir = path33.join(harnessRoot, "monitors");
5236
5980
  mkdirSync6(dir, { recursive: true });
5237
5981
  return dir;
5238
5982
  }
@@ -5240,7 +5984,7 @@ function monitorIdFor(runId, workerName) {
5240
5984
  return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
5241
5985
  }
5242
5986
  function monitorPath(monitorId) {
5243
- return path29.join(monitorsDir(), `${monitorId}.json`);
5987
+ return path33.join(monitorsDir(), `${monitorId}.json`);
5244
5988
  }
5245
5989
  function loadMonitorSession(monitorId) {
5246
5990
  return readJson(monitorPath(monitorId), void 0);
@@ -5261,7 +6005,7 @@ function listMonitorSessions() {
5261
6005
  for (const name of readdirSync7(dir)) {
5262
6006
  if (!name.endsWith(".json")) continue;
5263
6007
  const session = readJson(
5264
- path29.join(dir, name),
6008
+ path33.join(dir, name),
5265
6009
  void 0
5266
6010
  );
5267
6011
  if (!session?.monitorId) continue;
@@ -5352,7 +6096,7 @@ async function fetchTaskLeasesForWorkers(input) {
5352
6096
  // src/monitor/monitor.service.ts
5353
6097
  function workerRecord2(runId, name) {
5354
6098
  return readJson(
5355
- path30.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
6099
+ path34.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
5356
6100
  void 0
5357
6101
  );
5358
6102
  }
@@ -5555,17 +6299,17 @@ async function runMonitorLoop(args) {
5555
6299
  // src/monitor/monitor-spawn.ts
5556
6300
  import { spawn as spawn4 } from "node:child_process";
5557
6301
  import { closeSync as closeSync4, existsSync as existsSync18, openSync as openSync4 } from "node:fs";
5558
- import path31 from "node:path";
6302
+ import path35 from "node:path";
5559
6303
  import { fileURLToPath as fileURLToPath2 } from "node:url";
5560
6304
  function resolveDefaultCliPath2() {
5561
- return path31.join(fileURLToPath2(new URL(".", import.meta.url)), "..", "cli.js");
6305
+ return path35.join(fileURLToPath2(new URL(".", import.meta.url)), "..", "cli.js");
5562
6306
  }
5563
6307
  function spawnMonitorSidecar(opts) {
5564
6308
  const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
5565
6309
  if (!existsSync18(cliPath)) return void 0;
5566
6310
  const monitorId = monitorIdFor(opts.runId, opts.workerName);
5567
6311
  const { harnessRoot } = getHarnessPaths();
5568
- const logPath = path31.join(harnessRoot, "monitors", `${monitorId}.log`);
6312
+ const logPath = path35.join(harnessRoot, "monitors", `${monitorId}.log`);
5569
6313
  let logFd;
5570
6314
  try {
5571
6315
  logFd = openSync4(logPath, "a");
@@ -5720,6 +6464,453 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
5720
6464
  return true;
5721
6465
  }
5722
6466
 
6467
+ // src/doctor/runtime-takeover.ts
6468
+ import path37 from "node:path";
6469
+
6470
+ // src/doctor/runtime-takeover.probes.ts
6471
+ import { accessSync, constants, existsSync as existsSync20, readFileSync as readFileSync9 } from "node:fs";
6472
+ import { homedir as homedir6 } from "node:os";
6473
+ import path36 from "node:path";
6474
+ import { spawnSync as spawnSync6 } from "node:child_process";
6475
+ function captureCommand(bin, args) {
6476
+ try {
6477
+ const res = spawnSync6(bin, args, { encoding: "utf8" });
6478
+ const stdout = (res.stdout || "").trim();
6479
+ const stderr = (res.stderr || "").trim();
6480
+ const ok = res.status === 0;
6481
+ return {
6482
+ ok,
6483
+ stdout,
6484
+ stderr,
6485
+ error: res.error?.message
6486
+ };
6487
+ } catch (error) {
6488
+ return {
6489
+ ok: false,
6490
+ stdout: "",
6491
+ stderr: "",
6492
+ error: error.message
6493
+ };
6494
+ }
6495
+ }
6496
+ function tokenPrefix(token) {
6497
+ const trimmed = token?.trim();
6498
+ if (!trimmed) return void 0;
6499
+ return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
6500
+ }
6501
+ function isWritable(target) {
6502
+ if (!existsSync20(target)) return false;
6503
+ try {
6504
+ accessSync(target, constants.W_OK);
6505
+ return true;
6506
+ } catch {
6507
+ return false;
6508
+ }
6509
+ }
6510
+ var defaultRuntimeTakeoverProbes = {
6511
+ packageVersion: () => PACKAGE_VERSION,
6512
+ commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
6513
+ kynverVersion: (bin) => captureCommand(bin, ["--version"]),
6514
+ loadConfig: () => loadUserConfig(),
6515
+ configFilePath: () => path36.join(homedir6(), ".kynver", "config.json"),
6516
+ credentialsFilePath: () => path36.join(homedir6(), ".kynver", "credentials"),
6517
+ readCredentials: () => {
6518
+ const credPath = path36.join(homedir6(), ".kynver", "credentials");
6519
+ if (!existsSync20(credPath)) {
6520
+ return { hasApiKey: false };
6521
+ }
6522
+ try {
6523
+ const parsed = JSON.parse(readFileSync9(credPath, "utf8"));
6524
+ return {
6525
+ hasApiKey: Boolean(parsed.apiKey?.trim()),
6526
+ runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
6527
+ runnerTokenAgentOsId: parsed.runnerTokenAgentOsId
6528
+ };
6529
+ } catch {
6530
+ return { hasApiKey: false };
6531
+ }
6532
+ },
6533
+ envSnapshot: () => ({
6534
+ kynverApiUrl: process.env.KYNVER_API_URL?.trim() || void 0,
6535
+ openclawCronFireBaseUrl: process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || void 0,
6536
+ kynverRunnerTokenPrefix: tokenPrefix(process.env.KYNVER_RUNNER_TOKEN),
6537
+ kynverRuntimeSecret: Boolean(process.env.KYNVER_RUNTIME_SECRET?.trim()),
6538
+ openclawCronSecret: Boolean(process.env.OPENCLAW_CRON_SECRET?.trim()),
6539
+ kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
6540
+ opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
6541
+ kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0
6542
+ }),
6543
+ harnessRoot: () => resolveHarnessRoot(),
6544
+ legacyOpenclawHarnessRoot: () => path36.join(homedir6(), ".openclaw", "harness"),
6545
+ pathExists: (target) => existsSync20(target),
6546
+ pathWritable: (target) => isWritable(target),
6547
+ vercelVersion: () => captureCommand("vercel", ["--version"]),
6548
+ vercelWhoami: () => captureCommand("vercel", ["whoami"])
6549
+ };
6550
+
6551
+ // src/doctor/runtime-takeover.ts
6552
+ function check(partial) {
6553
+ return partial;
6554
+ }
6555
+ function summarizeCounts(sections) {
6556
+ const counts = { pass: 0, warn: 0, fail: 0 };
6557
+ for (const section of sections) {
6558
+ for (const item of section.checks) {
6559
+ counts[item.status] += 1;
6560
+ }
6561
+ }
6562
+ return counts;
6563
+ }
6564
+ function assessCliPackage(probes) {
6565
+ const runningVersion = probes.packageVersion();
6566
+ const which = probes.commandOnPath("kynver");
6567
+ const onPath = which.ok && which.stdout.length > 0;
6568
+ const firstPath = onPath ? which.stdout.split(/\r?\n/)[0]?.trim() : void 0;
6569
+ const displayCliPath = firstPath ? displayUserPath(firstPath) : void 0;
6570
+ const checks = [
6571
+ check({
6572
+ id: "cli_running_version",
6573
+ label: "Running @kynver-app/runtime version",
6574
+ status: "pass",
6575
+ summary: `@kynver-app/runtime ${runningVersion}`,
6576
+ details: { version: runningVersion }
6577
+ }),
6578
+ check({
6579
+ id: "cli_on_path",
6580
+ label: "kynver executable on PATH",
6581
+ status: onPath ? "pass" : "warn",
6582
+ summary: onPath ? `Found ${displayCliPath}` : "kynver not found on PATH (invoked via node/npx?)",
6583
+ remediation: onPath ? void 0 : "Install globally (`npm i -g @kynver-app/runtime`) or invoke via `npx @kynver-app/runtime`.",
6584
+ details: { path: displayCliPath }
6585
+ })
6586
+ ];
6587
+ if (onPath && firstPath) {
6588
+ const versionProbe = probes.kynverVersion(firstPath);
6589
+ const installedVersion = versionProbe.stdout.replace(/^kynver\s+/i, "").trim() || void 0;
6590
+ const versionMatch = versionProbe.ok && (!installedVersion || installedVersion === runningVersion);
6591
+ checks.push(
6592
+ check({
6593
+ id: "cli_installed_version",
6594
+ label: "Installed kynver CLI version matches running package",
6595
+ status: versionMatch ? "pass" : "warn",
6596
+ 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",
6597
+ remediation: versionMatch ? void 0 : "Reinstall or rebuild @kynver-app/runtime so PATH kynver matches the harness worktree version.",
6598
+ details: { installedVersion, runningVersion, path: displayCliPath }
6599
+ })
6600
+ );
6601
+ }
6602
+ return { id: "cli_package", label: "CLI / package", checks };
6603
+ }
6604
+ function assessUserConfig(probes) {
6605
+ const configPath = probes.configFilePath();
6606
+ const displayConfigPath = displayUserPath(configPath);
6607
+ const exists = probes.pathExists(configPath);
6608
+ const config = probes.loadConfig();
6609
+ const checks = [
6610
+ check({
6611
+ id: "config_file",
6612
+ label: "~/.kynver/config.json present",
6613
+ status: exists ? "pass" : "fail",
6614
+ summary: exists ? `Found ${displayConfigPath}` : `Missing ${displayConfigPath}`,
6615
+ remediation: exists ? void 0 : "Run `kynver setup --api-base-url <url> --agent-os-id <id> --repo <path>`.",
6616
+ details: { configPath: displayConfigPath }
6617
+ })
6618
+ ];
6619
+ if (exists) {
6620
+ const apiBaseUrl = config.apiBaseUrl?.trim();
6621
+ const agentOsId = config.agentOsId?.trim();
6622
+ const defaultRepo = config.defaultRepo?.trim();
6623
+ const displayDefaultRepo = defaultRepo ? displayUserPath(defaultRepo) : null;
6624
+ checks.push(
6625
+ check({
6626
+ id: "config_api_base_url",
6627
+ label: "Default API base URL",
6628
+ status: apiBaseUrl ? "pass" : "warn",
6629
+ summary: apiBaseUrl ?? "Not set in config (KYNVER_API_URL / --base-url required per command)",
6630
+ remediation: apiBaseUrl ? void 0 : "Set `apiBaseUrl` via `kynver setup --api-base-url https://\u2026`.",
6631
+ details: { apiBaseUrl: apiBaseUrl ?? null }
6632
+ }),
6633
+ check({
6634
+ id: "config_agent_os_id",
6635
+ label: "Default AgentOS id",
6636
+ status: agentOsId ? "pass" : "warn",
6637
+ summary: agentOsId ?? "Not set (pass --agent-os-id on daemon/dispatch/worker commands)",
6638
+ remediation: agentOsId ? void 0 : "Set `agentOsId` via `kynver setup --agent-os-id <uuid>`.",
6639
+ details: { agentOsId: agentOsId ?? null, agentOsSlug: config.agentOsSlug ?? null }
6640
+ }),
6641
+ check({
6642
+ id: "config_default_repo",
6643
+ label: "Default repo path",
6644
+ status: defaultRepo ? "pass" : "warn",
6645
+ summary: displayDefaultRepo ?? "Not set (pass --repo on `kynver run create`)",
6646
+ remediation: defaultRepo ? void 0 : "Set `defaultRepo` via `kynver setup --repo /path/to/repo`.",
6647
+ details: {
6648
+ defaultRepo: displayDefaultRepo,
6649
+ harnessRoot: config.harnessRoot ? redactHomePath(config.harnessRoot) : null
6650
+ }
6651
+ })
6652
+ );
6653
+ }
6654
+ return { id: "user_config", label: "User config (~/.kynver)", checks };
6655
+ }
6656
+ function assessRunnerToken(probes) {
6657
+ const config = probes.loadConfig();
6658
+ const targetAgentOsId = config.agentOsId?.trim();
6659
+ const creds = probes.readCredentials();
6660
+ const env = probes.envSnapshot();
6661
+ const credPath = probes.credentialsFilePath();
6662
+ const displayCredPath = displayUserPath(credPath);
6663
+ const envToken = env.kynverRunnerTokenPrefix;
6664
+ const savedToken = creds.runnerTokenPrefix;
6665
+ const savedAgentOsId = creds.runnerTokenAgentOsId;
6666
+ const scopedSaved = Boolean(savedToken) && (!targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId);
6667
+ const hasScoped = Boolean(envToken?.startsWith("krc1.")) || scopedSaved && savedToken?.startsWith("krc1.");
6668
+ const checks = [
6669
+ check({
6670
+ id: "runner_token_scoped",
6671
+ label: "Scoped runner token (krc1.*) ready",
6672
+ status: hasScoped ? "pass" : "fail",
6673
+ 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",
6674
+ remediation: hasScoped ? void 0 : "Run `kynver login` then `kynver runner credential --agent-os-id <id>` (or `kynver setup` with API key).",
6675
+ details: {
6676
+ source: envToken ? "env" : savedToken ? "credentials" : "none",
6677
+ tokenPrefix: envToken ?? (scopedSaved ? savedToken : void 0),
6678
+ agentOsId: targetAgentOsId ?? savedAgentOsId ?? null,
6679
+ credentialsPath: displayCredPath
6680
+ }
6681
+ }),
6682
+ check({
6683
+ id: "runner_token_agent_os_match",
6684
+ label: "Saved runner token matches configured agentOsId",
6685
+ status: !savedToken || !targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId ? "pass" : "warn",
6686
+ 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}`,
6687
+ remediation: savedToken && targetAgentOsId && savedAgentOsId && savedAgentOsId !== targetAgentOsId ? "`kynver runner credential --agent-os-id <configured-id>` to mint a workspace-bound token." : void 0,
6688
+ details: { configuredAgentOsId: targetAgentOsId ?? null, savedAgentOsId: savedAgentOsId ?? null }
6689
+ }),
6690
+ check({
6691
+ id: "runner_api_key_for_refresh",
6692
+ label: "API key available for token refresh",
6693
+ status: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY?.trim()) ? "pass" : "warn",
6694
+ 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",
6695
+ remediation: creds.hasApiKey || process.env.KYNVER_API_KEY ? void 0 : "Run `kynver login --api-key \u2026`.",
6696
+ details: { credentialsPath: displayCredPath, hasApiKey: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY) }
6697
+ })
6698
+ ];
6699
+ return { id: "runner_token", label: "Runner token readiness", checks };
6700
+ }
6701
+ function assessVercelCli(probes) {
6702
+ const version = probes.vercelVersion();
6703
+ const whoami = probes.vercelWhoami();
6704
+ const installed = version.ok;
6705
+ return {
6706
+ id: "vercel_cli",
6707
+ label: "Vercel CLI",
6708
+ checks: [
6709
+ check({
6710
+ id: "vercel_installed",
6711
+ label: "Vercel CLI installed",
6712
+ status: installed ? "pass" : "warn",
6713
+ summary: installed ? version.stdout || "vercel CLI found" : version.error ?? "vercel not found on PATH",
6714
+ remediation: installed ? void 0 : "Install Vercel CLI (`npm i -g vercel`) for deploy evidence and env pulls.",
6715
+ details: { stderr: version.stderr || null }
6716
+ }),
6717
+ check({
6718
+ id: "vercel_authenticated",
6719
+ label: "Vercel CLI authenticated",
6720
+ status: !installed ? "warn" : whoami.ok ? "pass" : "warn",
6721
+ summary: !installed ? "Skipped \u2014 Vercel CLI not installed" : whoami.ok ? `Authenticated as ${whoami.stdout}` : whoami.stderr || whoami.error || "Not logged in",
6722
+ remediation: installed && !whoami.ok ? "Run `vercel login` and link the Kynver project if needed." : void 0,
6723
+ details: { account: whoami.ok ? whoami.stdout : null }
6724
+ })
6725
+ ]
6726
+ };
6727
+ }
6728
+ function assessHarnessDirs(probes) {
6729
+ const harnessRoot = probes.harnessRoot();
6730
+ const runsDir = path37.join(harnessRoot, "runs");
6731
+ const worktreesDir = path37.join(harnessRoot, "worktrees");
6732
+ const displayHarnessRoot = redactHomePath(harnessRoot);
6733
+ const displayRunsDir = redactHomePath(runsDir);
6734
+ const displayWorktreesDir = redactHomePath(worktreesDir);
6735
+ const runsExists = probes.pathExists(runsDir);
6736
+ const worktreesExists = probes.pathExists(worktreesDir);
6737
+ return {
6738
+ id: "harness_dirs",
6739
+ label: "Harness / daemon directories",
6740
+ checks: [
6741
+ check({
6742
+ id: "harness_root",
6743
+ label: "Harness root resolved",
6744
+ status: "pass",
6745
+ summary: displayHarnessRoot,
6746
+ details: { harnessRoot: displayHarnessRoot }
6747
+ }),
6748
+ check({
6749
+ id: "harness_runs_dir",
6750
+ label: "Runs directory ready",
6751
+ status: runsExists && probes.pathWritable(runsDir) ? "pass" : runsExists ? "warn" : "warn",
6752
+ summary: runsExists ? probes.pathWritable(runsDir) ? `Writable ${displayRunsDir}` : `Exists but not writable: ${displayRunsDir}` : `Will be created on first run: ${displayRunsDir}`,
6753
+ remediation: runsExists && !probes.pathWritable(runsDir) ? `Fix permissions on ${displayRunsDir} or set KYNVER_HARNESS_ROOT to a writable path.` : void 0,
6754
+ details: { runsDir: displayRunsDir, exists: runsExists, writable: probes.pathWritable(runsDir) }
6755
+ }),
6756
+ check({
6757
+ id: "harness_worktrees_dir",
6758
+ label: "Worktrees directory ready",
6759
+ status: worktreesExists && probes.pathWritable(worktreesDir) ? "pass" : "warn",
6760
+ summary: worktreesExists ? probes.pathWritable(worktreesDir) ? `Writable ${displayWorktreesDir}` : `Exists but not writable: ${displayWorktreesDir}` : `Will be created on first worker: ${displayWorktreesDir}`,
6761
+ remediation: worktreesExists && !probes.pathWritable(worktreesDir) ? `Fix permissions on ${displayWorktreesDir}.` : void 0,
6762
+ details: { worktreesDir: displayWorktreesDir, exists: worktreesExists, writable: probes.pathWritable(worktreesDir) }
6763
+ })
6764
+ ]
6765
+ };
6766
+ }
6767
+ function assessCallbackAuth(probes) {
6768
+ const config = probes.loadConfig();
6769
+ const env = probes.envSnapshot();
6770
+ const baseUrl = env.kynverApiUrl ?? config.apiBaseUrl?.trim() ?? env.openclawCronFireBaseUrl;
6771
+ const usingLegacyBase = !env.kynverApiUrl && !config.apiBaseUrl && Boolean(env.openclawCronFireBaseUrl);
6772
+ const legacySecret = env.openclawCronSecret || env.kynverRuntimeSecret;
6773
+ const envScoped = env.kynverRunnerTokenPrefix?.startsWith("krc1.");
6774
+ const creds = probes.readCredentials();
6775
+ const savedScoped = creds.runnerTokenPrefix?.startsWith("krc1.");
6776
+ const checks = [
6777
+ check({
6778
+ id: "callback_base_url",
6779
+ label: "Callback base URL configured",
6780
+ status: baseUrl ? usingLegacyBase ? "warn" : "pass" : "fail",
6781
+ summary: baseUrl ? usingLegacyBase ? `Using legacy OPENCLAW_CRON_FIRE_BASE_URL (${baseUrl})` : baseUrl : "No KYNVER_API_URL, config apiBaseUrl, or legacy OpenClaw base URL",
6782
+ 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`.",
6783
+ details: {
6784
+ source: env.kynverApiUrl ? "KYNVER_API_URL" : config.apiBaseUrl ? "config.apiBaseUrl" : env.openclawCronFireBaseUrl ? "OPENCLAW_CRON_FIRE_BASE_URL" : "none",
6785
+ baseUrl: baseUrl ?? null
6786
+ }
6787
+ }),
6788
+ check({
6789
+ id: "callback_auth_mode",
6790
+ label: "Callback auth uses scoped runner token",
6791
+ status: envScoped || savedScoped ? "pass" : legacySecret ? "warn" : "fail",
6792
+ 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",
6793
+ remediation: envScoped || savedScoped ? void 0 : "Mint a scoped token: `kynver runner credential --agent-os-id <id>`. Avoid shared OPENCLAW_CRON_SECRET on user runners.",
6794
+ details: {
6795
+ mode: envScoped || savedScoped ? "scoped" : legacySecret ? "legacy_global_secret" : "missing",
6796
+ legacyHeadersWhenNotScoped: ["X-OpenClaw-Cron-Secret", "X-Kynver-Runtime-Secret"]
6797
+ }
6798
+ })
6799
+ ];
6800
+ return { id: "callback_auth", label: "Callback auth / config", checks };
6801
+ }
6802
+ function assessOpenclawHotspots(probes) {
6803
+ const env = probes.envSnapshot();
6804
+ const harnessRoot = probes.harnessRoot();
6805
+ const legacyRoot = probes.legacyOpenclawHarnessRoot();
6806
+ const displayHarnessRoot = redactHomePath(harnessRoot);
6807
+ const displayLegacyRoot = redactHomePath(legacyRoot);
6808
+ const displayOpusHarnessRoot = env.opusHarnessRoot ? redactHomePath(env.opusHarnessRoot) : null;
6809
+ const legacyHarnessActive = harnessRoot === legacyRoot && probes.pathExists(legacyRoot);
6810
+ const schedulerOpenclaw = !env.kynverSchedulerProvider || env.kynverSchedulerProvider === "openclaw-cron";
6811
+ const checks = [
6812
+ check({
6813
+ id: "hotspot_legacy_harness_root",
6814
+ label: "Legacy ~/.openclaw/harness still active",
6815
+ status: legacyHarnessActive ? "warn" : "pass",
6816
+ summary: legacyHarnessActive ? `Harness root is legacy ${displayLegacyRoot}` : env.opusHarnessRoot ? `OPUS_HARNESS_ROOT override in use (${displayOpusHarnessRoot})` : `Using ${displayHarnessRoot}`,
6817
+ 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,
6818
+ details: { harnessRoot: displayHarnessRoot, legacyRoot: displayLegacyRoot, opusHarnessRoot: displayOpusHarnessRoot }
6819
+ }),
6820
+ check({
6821
+ id: "hotspot_openclaw_env_secrets",
6822
+ label: "OpenClaw deployment secrets in runner env",
6823
+ status: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "warn" : "pass",
6824
+ summary: env.openclawCronSecret || env.openclawCronFireBaseUrl ? [
6825
+ env.openclawCronSecret ? "OPENCLAW_CRON_SECRET set" : null,
6826
+ env.openclawCronFireBaseUrl ? "OPENCLAW_CRON_FIRE_BASE_URL set" : null
6827
+ ].filter(Boolean).join("; ") : "No OpenClaw cron env overrides on this runner",
6828
+ remediation: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "Move to KYNVER_API_URL + scoped runner tokens; unset OpenClaw cron env on user-hosted runners." : void 0
6829
+ }),
6830
+ check({
6831
+ id: "hotspot_openclaw_scheduler",
6832
+ label: "openclaw-cron scheduler dependency (deployment)",
6833
+ status: schedulerOpenclaw ? "warn" : "pass",
6834
+ 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}`,
6835
+ 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,
6836
+ details: { schedulerProvider: env.kynverSchedulerProvider ?? null }
6837
+ }),
6838
+ check({
6839
+ id: "hotspot_lease_source_names",
6840
+ label: "Harness lease/completion source names",
6841
+ status: "pass",
6842
+ summary: "Runtime uses kynver-harness:* lease owners and completion source kynver-harness (OpenClaw names retired in runtime)",
6843
+ details: {
6844
+ leaseOwnerPattern: "kynver-harness:<runId>",
6845
+ completionSource: "kynver-harness",
6846
+ note: "OpenClaw adapter remains optional in packages/kynver-openclaw-agent-os only"
6847
+ }
6848
+ })
6849
+ ];
6850
+ return { id: "openclaw_hotspots", label: "OpenClaw dependency hotspots", checks };
6851
+ }
6852
+ function assessRuntimeTakeoverReadiness(probes = defaultRuntimeTakeoverProbes) {
6853
+ const sections = [
6854
+ assessCliPackage(probes),
6855
+ assessUserConfig(probes),
6856
+ assessRunnerToken(probes),
6857
+ assessVercelCli(probes),
6858
+ assessHarnessDirs(probes),
6859
+ assessCallbackAuth(probes),
6860
+ assessOpenclawHotspots(probes)
6861
+ ];
6862
+ const counts = summarizeCounts(sections);
6863
+ const ready = counts.fail === 0;
6864
+ 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.`;
6865
+ return {
6866
+ command: "doctor runtime-takeover",
6867
+ ready,
6868
+ summary,
6869
+ counts,
6870
+ sections
6871
+ };
6872
+ }
6873
+
6874
+ // src/doctor/runtime-takeover-cli.ts
6875
+ function runRuntimeTakeoverDoctorCli() {
6876
+ const report = assessRuntimeTakeoverReadiness();
6877
+ console.log(JSON.stringify(report, null, 2));
6878
+ if (!report.ready) {
6879
+ process.exitCode = 1;
6880
+ }
6881
+ }
6882
+
6883
+ // src/command-center-contract-cli.ts
6884
+ async function runCommandCenterContractCli(args) {
6885
+ const config = loadUserConfig();
6886
+ const agentOsId = (args.agentOsId ? String(args.agentOsId) : config.agentOsId) || "";
6887
+ if (!agentOsId) {
6888
+ console.error("requires --agent-os-id or agentOsId in ~/.kynver/config.json");
6889
+ process.exit(1);
6890
+ }
6891
+ const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : config.apiBaseUrl);
6892
+ const secret = await resolveCallbackSecretWithMint(
6893
+ args.secret ? String(args.secret) : void 0,
6894
+ agentOsId,
6895
+ { baseUrl: base }
6896
+ );
6897
+ const qs = new URLSearchParams();
6898
+ if (typeof args.since === "string" && args.since.trim()) qs.set("since", args.since.trim());
6899
+ if (args.limit != null && String(args.limit).trim()) {
6900
+ const n = Number(args.limit);
6901
+ if (Number.isFinite(n) && n > 0) qs.set("limit", String(Math.floor(n)));
6902
+ }
6903
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
6904
+ const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/command-center/dashboard-contract${suffix}`;
6905
+ const res = await getJson(url, secret);
6906
+ if (!res.ok) {
6907
+ console.error(`dashboard-contract GET failed: HTTP ${res.status}`);
6908
+ if (res.response) console.error(JSON.stringify(res.response, null, 2));
6909
+ process.exit(1);
6910
+ }
6911
+ console.log(JSON.stringify(res.response, null, 2));
6912
+ }
6913
+
5723
6914
  // src/cli.ts
5724
6915
  function isHelpFlag(arg) {
5725
6916
  return arg === "help" || arg === "--help" || arg === "-h";
@@ -5741,7 +6932,7 @@ function usage(code = 0) {
5741
6932
  " kynver run create --repo /path/repo [--name name] [--base origin/main]",
5742
6933
  " kynver run list",
5743
6934
  " kynver run status --run RUN_ID",
5744
- " kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-8] [--disk-path /]",
6935
+ " kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--target-task-id TASK_ID] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-8] [--disk-path /]",
5745
6936
  " kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
5746
6937
  ' kynver worker start --run RUN_ID --name worker --task "..." [--owned path[,path]] [--model MODEL] [--provider claude|cursor] [--agent-os-id AOS_ID] [--task-id TASK_ID] [--wait]',
5747
6938
  " kynver worker status --run RUN_ID --name worker",
@@ -5751,7 +6942,8 @@ function usage(code = 0) {
5751
6942
  " kynver worker auto-complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--poll-ms 5000] [--max-total-ms 21600000] [--complete-attempts 3] [--complete-backoff-ms 5000] [--base-url URL] [--secret SECRET]",
5752
6943
  " kynver run reconcile",
5753
6944
  " kynver plan progress --plan PLAN_ID --row ROW_KEY --role ROLE --status STATUS [--task TASK_ID] [--note NOTE] [--evidence type:value] [--agent-os-id AOS_ID]",
5754
- " kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override]",
6945
+ " kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override] [--local]",
6946
+ " kynver harness verify --worktree PATH [--command CMD] [--json] [--wait-for-admission-ms MS] [--timeout-ms MS]",
5755
6947
  " kynver plan persist --operation create|add_version|update_metadata --title TITLE (--body-file PATH | --body TEXT) [--slug SLUG] [--plan PLAN_ID] [--summary TEXT] [--failure-kind approval_guard|auth|network|server|tool_interruption]",
5756
6948
  " kynver plan outbox list",
5757
6949
  " kynver plan outbox drain [--max N] [--id OUTBOX_ID]",
@@ -5762,7 +6954,9 @@ function usage(code = 0) {
5762
6954
  " kynver monitor list",
5763
6955
  " kynver monitor tick --run RUN_ID [--name worker] [--agent-os-id AOS_ID] [--auto-complete] [--renew-leases]",
5764
6956
  " kynver monitor auto-complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--base-url URL] [--secret SECRET]",
5765
- " kynver monitor run-loop --run RUN_ID --monitor-id ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS] [--auto-complete] [--renew-leases]"
6957
+ " kynver monitor run-loop --run RUN_ID --monitor-id ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS] [--auto-complete] [--renew-leases]",
6958
+ " kynver doctor runtime-takeover",
6959
+ " kynver board contract [--agent-os-id ID] [--base-url URL] [--since ISO] [--limit N]"
5766
6960
  ].join("\n")
5767
6961
  );
5768
6962
  process.exit(code);
@@ -5773,7 +6967,7 @@ async function main(argv = process.argv.slice(2)) {
5773
6967
  const scope = argv.shift();
5774
6968
  let action;
5775
6969
  let rest;
5776
- if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "monitor") {
6970
+ if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "harness" || scope === "monitor" || scope === "doctor" || scope === "board") {
5777
6971
  action = argv.shift();
5778
6972
  rest = argv;
5779
6973
  } else {
@@ -5790,6 +6984,7 @@ async function main(argv = process.argv.slice(2)) {
5790
6984
  if (scope === "daemon") return void await runDaemon(args);
5791
6985
  if (scope === "plan" && action === "progress") return void await emitPlanProgress(args);
5792
6986
  if (scope === "plan" && action === "verify") return void await verifyPlan(args);
6987
+ if (scope === "harness" && action === "verify") return runHarnessVerifyCli(args);
5793
6988
  if (scope === "plan" && action === "persist") return void await runPlanPersist(args);
5794
6989
  if (scope === "plan" && action === "outbox") {
5795
6990
  const outboxAction = rest.shift();
@@ -5798,6 +6993,10 @@ async function main(argv = process.argv.slice(2)) {
5798
6993
  unknownCommand("plan", `outbox ${outboxAction ?? ""}`.trim());
5799
6994
  }
5800
6995
  if (scope === "cleanup") return runCleanupCli(args);
6996
+ if (scope === "doctor" && action === "runtime-takeover") return runRuntimeTakeoverDoctorCli();
6997
+ if (scope === "board" && action === "contract") {
6998
+ return void await runCommandCenterContractCli(args);
6999
+ }
5801
7000
  if (scope === "run" && action === "create") return createRun(args);
5802
7001
  if (scope === "run" && action === "list") return listRuns();
5803
7002
  if (scope === "run" && action === "status") return runStatus(args);