@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/index.js CHANGED
@@ -35,16 +35,43 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
35
35
  }
36
36
 
37
37
  // src/dispatch.ts
38
- import path16 from "node:path";
38
+ import path17 from "node:path";
39
39
 
40
40
  // src/config.ts
41
41
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
42
+ import { homedir as homedir2 } from "node:os";
43
+ import path3 from "node:path";
44
+
45
+ // src/path-values.ts
42
46
  import { homedir } from "node:os";
43
- import path2 from "node:path";
47
+ import path from "node:path";
48
+ function expandHomePath(value) {
49
+ if (value === "~") return homedir();
50
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
51
+ return path.join(homedir(), value.slice(2));
52
+ }
53
+ return value;
54
+ }
55
+ function resolveUserPath(value) {
56
+ return path.resolve(expandHomePath(value));
57
+ }
58
+ function redactHomePath(value) {
59
+ const expanded = expandHomePath(value);
60
+ const resolved = path.resolve(expanded);
61
+ const home = path.resolve(homedir());
62
+ if (resolved === home) return "~";
63
+ if (resolved.startsWith(`${home}${path.sep}`)) {
64
+ return `~/${path.relative(home, resolved).split(path.sep).join("/")}`;
65
+ }
66
+ return resolved.replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^\/Users\/[^/]+(?=\/|$)/, "~");
67
+ }
68
+ function displayUserPath(value) {
69
+ return redactHomePath(value);
70
+ }
44
71
 
45
72
  // src/util.ts
46
73
  import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync } from "node:fs";
47
- import path from "node:path";
74
+ import path2 from "node:path";
48
75
  function fail(message) {
49
76
  console.error(message);
50
77
  process.exit(1);
@@ -73,7 +100,7 @@ function readJson(file, fallback) {
73
100
  }
74
101
  }
75
102
  function writeJson(file, value) {
76
- mkdirSync(path.dirname(file), { recursive: true });
103
+ mkdirSync(path2.dirname(file), { recursive: true });
77
104
  writeFileSync(file, `${JSON.stringify(value, null, 2)}
78
105
  `);
79
106
  }
@@ -109,7 +136,7 @@ function tailFile(file, lines) {
109
136
  return data.split("\n").slice(-lines).join("\n");
110
137
  }
111
138
  function readMaybeFile(file) {
112
- return file ? readFileSync2(path.resolve(file), "utf8") : "";
139
+ return file ? readFileSync2(path2.resolve(file), "utf8") : "";
113
140
  }
114
141
  function listRunIds(runsDir) {
115
142
  if (!existsSync2(runsDir)) return [];
@@ -152,9 +179,9 @@ function secsAgo(ms) {
152
179
  }
153
180
 
154
181
  // src/config.ts
155
- var CONFIG_DIR = path2.join(homedir(), ".kynver");
156
- var CONFIG_FILE = path2.join(CONFIG_DIR, "config.json");
157
- var CREDENTIALS_FILE = path2.join(CONFIG_DIR, "credentials");
182
+ var CONFIG_DIR = path3.join(homedir2(), ".kynver");
183
+ var CONFIG_FILE = path3.join(CONFIG_DIR, "config.json");
184
+ var CREDENTIALS_FILE = path3.join(CONFIG_DIR, "credentials");
158
185
  function loadUserConfig() {
159
186
  if (!existsSync3(CONFIG_FILE)) return {};
160
187
  try {
@@ -165,9 +192,33 @@ function loadUserConfig() {
165
192
  }
166
193
  function saveUserConfig(config) {
167
194
  mkdirSync2(CONFIG_DIR, { recursive: true });
168
- writeFileSync2(CONFIG_FILE, `${JSON.stringify(config, null, 2)}
195
+ writeFileSync2(CONFIG_FILE, `${JSON.stringify(normalizeConfigPaths(config), null, 2)}
169
196
  `, { mode: 384 });
170
197
  }
198
+ function normalizeConfigPaths(config) {
199
+ return {
200
+ ...config,
201
+ ...config.harnessRoot?.trim() ? { harnessRoot: redactHomePath(config.harnessRoot.trim()) } : {},
202
+ ...config.defaultRepo?.trim() ? { defaultRepo: redactHomePath(config.defaultRepo.trim()) } : {}
203
+ };
204
+ }
205
+ function presentUserConfig(config) {
206
+ return normalizeConfigPaths(config);
207
+ }
208
+ function inferSetupFields(existing, args) {
209
+ const creds = loadCredentialsFile();
210
+ const apiBaseUrl = (typeof args.apiBaseUrl === "string" ? args.apiBaseUrl : void 0) || existing.apiBaseUrl?.trim() || process.env.KYNVER_API_URL?.trim() || process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim();
211
+ const agentOsId = (typeof args.agentOsId === "string" ? args.agentOsId : void 0) || existing.agentOsId?.trim() || process.env.KYNVER_AGENT_OS_ID?.trim() || (creds.runnerToken?.trim().startsWith("krc1.") ? creds.runnerTokenAgentOsId?.trim() : void 0);
212
+ const defaultRepo = (typeof args.repo === "string" ? args.repo : void 0) || existing.defaultRepo?.trim() || process.env.KYNVER_DEFAULT_REPO?.trim() || process.env.KYNVER_HARNESS_REPO?.trim();
213
+ const harnessRoot = (typeof args.harnessRoot === "string" ? args.harnessRoot : void 0) || existing.harnessRoot?.trim() || process.env.KYNVER_HARNESS_ROOT?.trim() || process.env.OPUS_HARNESS_ROOT?.trim();
214
+ return {
215
+ ...apiBaseUrl ? { apiBaseUrl: trimTrailingSlash(apiBaseUrl) } : {},
216
+ ...agentOsId ? { agentOsId } : {},
217
+ ...defaultRepo ? { defaultRepo } : {},
218
+ ...harnessRoot ? { harnessRoot } : {},
219
+ ...typeof args.agentOsSlug === "string" ? { agentOsSlug: args.agentOsSlug } : existing.agentOsSlug ? { agentOsSlug: existing.agentOsSlug } : {}
220
+ };
221
+ }
171
222
  function loadCredentialsFile() {
172
223
  if (!existsSync3(CREDENTIALS_FILE)) return {};
173
224
  try {
@@ -320,7 +371,7 @@ async function mintRunnerCredential(args) {
320
371
  {
321
372
  ok: true,
322
373
  agentOsId,
323
- credentialsPath: CREDENTIALS_FILE,
374
+ credentialsPath: displayUserPath(CREDENTIALS_FILE),
324
375
  tokenPrefix: `${token.slice(0, 12)}\u2026`,
325
376
  note: "Scoped runner token saved; callbacks use X-Kynver-Runner-Token."
326
377
  },
@@ -355,16 +406,12 @@ function parseArgs(argv) {
355
406
  async function runSetup(args) {
356
407
  const existing = loadUserConfig();
357
408
  const maxWorkersRaw = typeof args.maxWorkers === "string" ? args.maxWorkers : typeof args.maxConcurrentWorkers === "string" ? args.maxConcurrentWorkers : void 0;
358
- const config = {
409
+ const config = normalizeConfigPaths({
359
410
  ...existing,
360
- ...typeof args.apiBaseUrl === "string" ? { apiBaseUrl: args.apiBaseUrl } : {},
361
- ...typeof args.agentOsSlug === "string" ? { agentOsSlug: args.agentOsSlug } : {},
362
- ...typeof args.agentOsId === "string" ? { agentOsId: args.agentOsId } : {},
363
- ...typeof args.repo === "string" ? { defaultRepo: args.repo } : {},
364
- ...typeof args.harnessRoot === "string" ? { harnessRoot: args.harnessRoot } : {},
411
+ ...inferSetupFields(existing, args),
365
412
  ...maxWorkersRaw ? { maxConcurrentWorkers: Math.max(1, Math.floor(Number(maxWorkersRaw))) } : {},
366
413
  workerProvider: typeof args.provider === "string" ? args.provider : existing.workerProvider || "claude"
367
- };
414
+ });
368
415
  saveUserConfig(config);
369
416
  let runnerCredentialNote;
370
417
  const apiKey = loadApiKey();
@@ -385,8 +432,8 @@ async function runSetup(args) {
385
432
  JSON.stringify(
386
433
  {
387
434
  ok: true,
388
- configPath: CONFIG_FILE,
389
- config,
435
+ configPath: displayUserPath(CONFIG_FILE),
436
+ config: presentUserConfig(config),
390
437
  note: runnerCredentialNote ?? "Set worker limit once with --max-workers N (or omit to auto-size from RAM). Run `kynver login` + `kynver runner credential` for scoped callbacks."
391
438
  },
392
439
  null,
@@ -398,7 +445,7 @@ async function runLogin(args) {
398
445
  const apiKey = typeof args.apiKey === "string" ? args.apiKey : process.env.KYNVER_API_KEY;
399
446
  if (!apiKey) failConfig("kynver login requires --api-key or KYNVER_API_KEY");
400
447
  saveApiKey(apiKey);
401
- console.log(JSON.stringify({ ok: true, credentialsPath: CREDENTIALS_FILE }, null, 2));
448
+ console.log(JSON.stringify({ ok: true, credentialsPath: displayUserPath(CREDENTIALS_FILE) }, null, 2));
402
449
  }
403
450
 
404
451
  // src/callback-headers.ts
@@ -461,12 +508,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
461
508
  var DEFAULT_MAX_USED_PERCENT = 80;
462
509
  var DEFAULT_HARD_MAX_USED_PERCENT = 90;
463
510
  function observeRunnerDiskGate(input = {}) {
464
- const path32 = input.diskPath?.trim() || "/";
511
+ const path38 = input.diskPath?.trim() || "/";
465
512
  const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
466
513
  const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
467
514
  const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
468
515
  const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
469
- const stats = statfsSync(path32);
516
+ const stats = statfsSync(path38);
470
517
  const freeBytes = Number(stats.bavail) * Number(stats.bsize);
471
518
  const totalBytes = Number(stats.blocks) * Number(stats.bsize);
472
519
  const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
@@ -486,7 +533,7 @@ function observeRunnerDiskGate(input = {}) {
486
533
  }
487
534
  return {
488
535
  ok,
489
- path: path32,
536
+ path: path38,
490
537
  freeBytes,
491
538
  totalBytes,
492
539
  usedPercent,
@@ -499,23 +546,46 @@ function observeRunnerDiskGate(input = {}) {
499
546
  }
500
547
 
501
548
  // src/resource-gate.ts
502
- import { readFileSync as readFileSync6 } from "node:fs";
549
+ import os2 from "node:os";
550
+
551
+ // src/bounded-build/meminfo.ts
552
+ import { readFileSync as readFileSync4 } from "node:fs";
503
553
  import os from "node:os";
504
- import path5 from "node:path";
554
+ function readMemAvailableBytes(meminfoText) {
555
+ if (meminfoText !== void 0) {
556
+ const match = meminfoText.match(/^MemAvailable:\s+(\d+)\s*kB/m);
557
+ if (match) return Number(match[1]) * 1024;
558
+ return os.freemem();
559
+ }
560
+ if (process.platform === "linux") {
561
+ try {
562
+ const meminfo = readFileSync4("/proc/meminfo", "utf8");
563
+ const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m);
564
+ if (match) return Number(match[1]) * 1024;
565
+ } catch {
566
+ }
567
+ }
568
+ return os.freemem();
569
+ }
570
+
571
+ // src/resource-gate.ts
572
+ import path6 from "node:path";
505
573
 
506
574
  // src/run-store.ts
507
575
  import { existsSync as existsSync5, readdirSync as readdirSync2 } from "node:fs";
508
- import path4 from "node:path";
576
+ import path5 from "node:path";
509
577
 
510
578
  // src/paths.ts
511
579
  import { existsSync as existsSync4 } from "node:fs";
512
- import { homedir as homedir2 } from "node:os";
513
- import path3 from "node:path";
514
- var LEGACY_ROOT = path3.join(homedir2(), ".openclaw", "harness");
580
+ import { homedir as homedir3 } from "node:os";
581
+ import path4 from "node:path";
582
+ var LEGACY_ROOT = path4.join(homedir3(), ".openclaw", "harness");
515
583
  function resolveHarnessRoot() {
516
584
  const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
517
- if (env) return path3.resolve(env);
518
- const kynverRoot = path3.join(homedir2(), ".kynver", "harness");
585
+ if (env) return resolveUserPath(env);
586
+ const configured = loadUserConfig().harnessRoot?.trim();
587
+ if (configured) return resolveUserPath(configured);
588
+ const kynverRoot = path4.join(homedir3(), ".kynver", "harness");
519
589
  if (existsSync4(kynverRoot)) return kynverRoot;
520
590
  if (existsSync4(LEGACY_ROOT)) return LEGACY_ROOT;
521
591
  return kynverRoot;
@@ -524,12 +594,12 @@ function getHarnessPaths() {
524
594
  const harnessRoot = resolveHarnessRoot();
525
595
  return {
526
596
  harnessRoot,
527
- runsDir: path3.join(harnessRoot, "runs"),
528
- worktreesDir: path3.join(harnessRoot, "worktrees")
597
+ runsDir: path4.join(harnessRoot, "runs"),
598
+ worktreesDir: path4.join(harnessRoot, "worktrees")
529
599
  };
530
600
  }
531
601
  function runDir(runsDir, id) {
532
- return path3.join(runsDir, safeSlug(id));
602
+ return path4.join(runsDir, safeSlug(id));
533
603
  }
534
604
 
535
605
  // src/run-store.ts
@@ -538,7 +608,7 @@ function getPaths() {
538
608
  }
539
609
  function loadRun(id) {
540
610
  const { runsDir } = getPaths();
541
- return readJson(path4.join(runDir(runsDir, safeSlug(id)), "run.json"));
611
+ return readJson(path5.join(runDir(runsDir, safeSlug(id)), "run.json"));
542
612
  }
543
613
  function listRunRecords() {
544
614
  const { runsDir } = getPaths();
@@ -547,7 +617,7 @@ function listRunRecords() {
547
617
  for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
548
618
  if (!entry.isDirectory()) continue;
549
619
  const run = readJson(
550
- path4.join(runsDir, entry.name, "run.json"),
620
+ path5.join(runsDir, entry.name, "run.json"),
551
621
  void 0
552
622
  );
553
623
  if (run?.id) runs.push(run);
@@ -557,16 +627,16 @@ function listRunRecords() {
557
627
  function loadWorker(runId, name) {
558
628
  const { runsDir } = getPaths();
559
629
  return readJson(
560
- path4.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
630
+ path5.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
561
631
  );
562
632
  }
563
633
  function saveRun(run) {
564
634
  const { runsDir } = getPaths();
565
- writeJson(path4.join(runDir(runsDir, run.id), "run.json"), run);
635
+ writeJson(path5.join(runDir(runsDir, run.id), "run.json"), run);
566
636
  }
567
637
  function saveWorker(runId, worker) {
568
638
  const { runsDir } = getPaths();
569
- writeJson(path4.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
639
+ writeJson(path5.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
570
640
  }
571
641
  function runDirectory(id) {
572
642
  const { runsDir } = getPaths();
@@ -574,7 +644,7 @@ function runDirectory(id) {
574
644
  }
575
645
 
576
646
  // src/heartbeat.ts
577
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "node:fs";
647
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
578
648
  var HEARTBEAT_FUTURE_SKEW_MS = 6e4;
579
649
  function isTerminalHeartbeatPhase(phase) {
580
650
  return phase === "complete";
@@ -596,7 +666,7 @@ function parseHeartbeat(file) {
596
666
  if (!existsSync6(file)) return result;
597
667
  const maxFutureMs = Date.now() + HEARTBEAT_FUTURE_SKEW_MS;
598
668
  const clampedTo = new Date(maxFutureMs).toISOString();
599
- const lines = readFileSync4(file, "utf8").split("\n").filter(Boolean);
669
+ const lines = readFileSync5(file, "utf8").split("\n").filter(Boolean);
600
670
  for (const line of lines) {
601
671
  const entry = safeJson(line);
602
672
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
@@ -623,7 +693,155 @@ function parseHeartbeat(file) {
623
693
  }
624
694
 
625
695
  // src/stream.ts
626
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
696
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
697
+
698
+ // src/shell-command-outcome.ts
699
+ var NPM_AUDIT_RE = /\bnpm\s+audit\b/i;
700
+ function tidy(text, max = 200) {
701
+ const one = text.replace(/\s+/g, " ").trim();
702
+ return one.length > max ? `${one.slice(0, max - 1)}\u2026` : one;
703
+ }
704
+ function extractJsonObject(text) {
705
+ const trimmed = text.trim();
706
+ if (!trimmed) return null;
707
+ if (trimmed.startsWith("{")) {
708
+ try {
709
+ return JSON.parse(trimmed);
710
+ } catch {
711
+ }
712
+ }
713
+ const start = trimmed.indexOf("{");
714
+ const end = trimmed.lastIndexOf("}");
715
+ if (start >= 0 && end > start) {
716
+ try {
717
+ return JSON.parse(trimmed.slice(start, end + 1));
718
+ } catch {
719
+ return null;
720
+ }
721
+ }
722
+ return null;
723
+ }
724
+ function isRecord(value) {
725
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
726
+ }
727
+ function summarizeNpmAuditReport(report) {
728
+ const meta = report.metadata;
729
+ if (!isRecord(meta)) return null;
730
+ const vuln = meta.vulnerabilities;
731
+ if (!isRecord(vuln)) return null;
732
+ const num = (key) => typeof vuln[key] === "number" ? vuln[key] : 0;
733
+ const summary = {
734
+ info: num("info"),
735
+ low: num("low"),
736
+ moderate: num("moderate"),
737
+ high: num("high"),
738
+ critical: num("critical"),
739
+ total: num("total")
740
+ };
741
+ if (typeof vuln.total !== "number" && !summary.critical && !summary.high && !summary.moderate && !summary.low && !summary.info) {
742
+ return null;
743
+ }
744
+ return summary;
745
+ }
746
+ function formatAuditSummaryLine(audit) {
747
+ const parts = [];
748
+ if (audit.critical) parts.push(`${audit.critical} critical`);
749
+ if (audit.high) parts.push(`${audit.high} high`);
750
+ if (audit.moderate) parts.push(`${audit.moderate} moderate`);
751
+ if (audit.low) parts.push(`${audit.low} low`);
752
+ if (audit.info) parts.push(`${audit.info} info`);
753
+ const breakdown = parts.length ? parts.join(", ") : "see report";
754
+ return `npm audit: ${audit.total} vulnerabilit${audit.total === 1 ? "y" : "ies"} (${breakdown}) \u2014 remediation required`;
755
+ }
756
+ function npmAuditFailureReason(report, stderr) {
757
+ const err = report.error;
758
+ if (isRecord(err)) {
759
+ const summary = typeof err.summary === "string" ? err.summary.trim() : "";
760
+ const code = typeof err.code === "string" ? err.code.trim() : "";
761
+ if (summary) return code ? `${code}: ${summary}` : summary;
762
+ if (code) return code;
763
+ }
764
+ const detail = typeof report.message === "string" ? report.message.trim() : "";
765
+ if (detail) return detail;
766
+ const errTail = stderr.trim();
767
+ if (errTail) return tidy(errTail.split("\n").find(Boolean) ?? errTail, 160);
768
+ return "npm audit failed";
769
+ }
770
+ function classifyNpmAuditOutcome(input) {
771
+ const combined = `${input.stdout}
772
+ ${input.stderr}`.trim();
773
+ const parsed = extractJsonObject(combined);
774
+ if (!parsed || !isRecord(parsed)) {
775
+ const tail = tidy(combined || `exit ${input.exitCode}`, 180);
776
+ return {
777
+ kind: "command_failure",
778
+ exitCode: input.exitCode,
779
+ summary: `npm audit failed (invalid or missing JSON): ${tail}`,
780
+ parseError: "invalid_json"
781
+ };
782
+ }
783
+ if (isRecord(parsed.error)) {
784
+ return {
785
+ kind: "command_failure",
786
+ exitCode: input.exitCode,
787
+ summary: `npm audit command failed: ${npmAuditFailureReason(parsed, input.stderr)}`
788
+ };
789
+ }
790
+ const audit = summarizeNpmAuditReport(parsed);
791
+ if (!audit) {
792
+ return {
793
+ kind: "command_failure",
794
+ exitCode: input.exitCode,
795
+ summary: "npm audit failed: JSON response missing vulnerability metadata",
796
+ parseError: "missing_metadata"
797
+ };
798
+ }
799
+ if (input.exitCode === 0 && audit.total === 0) {
800
+ return {
801
+ kind: "success",
802
+ exitCode: 0,
803
+ summary: "npm audit: no vulnerabilities reported",
804
+ audit
805
+ };
806
+ }
807
+ return {
808
+ kind: "audit_findings",
809
+ exitCode: input.exitCode,
810
+ summary: formatAuditSummaryLine(audit),
811
+ audit
812
+ };
813
+ }
814
+ function isNpmAuditCommand(command) {
815
+ return NPM_AUDIT_RE.test(command);
816
+ }
817
+ function classifyShellCommandOutcome(input) {
818
+ const stdout = input.stdout ?? "";
819
+ const stderr = input.stderr ?? "";
820
+ const interleaved = input.interleavedOutput ?? "";
821
+ if (isNpmAuditCommand(input.command)) {
822
+ const body = stdout.trim() || interleaved.trim() || stderr.trim();
823
+ return classifyNpmAuditOutcome({
824
+ exitCode: input.exitCode,
825
+ stdout: body,
826
+ stderr
827
+ });
828
+ }
829
+ if (input.exitCode === 0) {
830
+ return {
831
+ kind: "success",
832
+ exitCode: 0,
833
+ summary: `command succeeded (exit 0)`
834
+ };
835
+ }
836
+ const tail = tidy(interleaved || stdout || stderr || `exit ${input.exitCode}`, 180);
837
+ return {
838
+ kind: "command_failure",
839
+ exitCode: input.exitCode,
840
+ summary: `command failed (exit ${input.exitCode}): ${tail}`
841
+ };
842
+ }
843
+
844
+ // src/stream.ts
627
845
  function eventTimestampIso(event) {
628
846
  const tsMs = event.timestamp_ms;
629
847
  return event.timestamp || event.ts || (tsMs ? new Date(tsMs).toISOString() : void 0);
@@ -644,16 +862,43 @@ function recordStreamResult(result, event) {
644
862
  result.error = String(event.result || event.api_error_status || "stream result error");
645
863
  }
646
864
  }
865
+ function shellPayloadFromCursorEvent(event) {
866
+ if (event.type !== "tool_call" || event.subtype !== "completed") return null;
867
+ const toolCall = event.tool_call && typeof event.tool_call === "object" && !Array.isArray(event.tool_call) ? event.tool_call : null;
868
+ const shell = toolCall?.shellToolCall;
869
+ if (!shell || typeof shell !== "object" || Array.isArray(shell)) return null;
870
+ const shellObj = shell;
871
+ const args = shellObj.args;
872
+ const command = args && typeof args === "object" && !Array.isArray(args) && typeof args.command === "string" ? String(args.command) : "";
873
+ const result = shellObj.result;
874
+ if (!result || typeof result !== "object" || Array.isArray(result)) return null;
875
+ const body = result.success ?? result.failure;
876
+ if (!body || typeof body !== "object" || Array.isArray(body)) return null;
877
+ const row = body;
878
+ const exitCode = typeof row.exitCode === "number" ? row.exitCode : 0;
879
+ return {
880
+ command,
881
+ exitCode,
882
+ stdout: typeof row.stdout === "string" ? row.stdout : "",
883
+ stderr: typeof row.stderr === "string" ? row.stderr : "",
884
+ interleaved: typeof row.interleavedOutput === "string" ? row.interleavedOutput : ""
885
+ };
886
+ }
887
+ function applyShellOutcome(parsed, outcome) {
888
+ if (outcome.kind === "success") return;
889
+ parsed.lastShellOutcome = outcome;
890
+ }
647
891
  function parseHarnessStream(file) {
648
892
  const result = {
649
893
  firstEventAt: null,
650
894
  lastEventAt: null,
651
895
  currentTool: null,
652
896
  finalResult: null,
653
- error: null
897
+ error: null,
898
+ lastShellOutcome: null
654
899
  };
655
900
  if (!existsSync7(file)) return result;
656
- const lines = readFileSync5(file, "utf8").split("\n").filter(Boolean);
901
+ const lines = readFileSync6(file, "utf8").split("\n").filter(Boolean);
657
902
  for (const line of lines) {
658
903
  const event = safeJson(line);
659
904
  if (!event) continue;
@@ -678,6 +923,19 @@ function parseHarnessStream(file) {
678
923
  const name = cursorToolNameFromCall(toolCall);
679
924
  if (name) result.currentTool = name;
680
925
  }
926
+ const shell = shellPayloadFromCursorEvent(event);
927
+ if (shell) {
928
+ applyShellOutcome(
929
+ result,
930
+ classifyShellCommandOutcome({
931
+ command: shell.command,
932
+ exitCode: shell.exitCode,
933
+ stdout: shell.stdout,
934
+ stderr: shell.stderr,
935
+ interleavedOutput: shell.interleaved
936
+ })
937
+ );
938
+ }
681
939
  if (event.type === "result") {
682
940
  recordStreamResult(result, event);
683
941
  }
@@ -687,6 +945,25 @@ function parseHarnessStream(file) {
687
945
  function parseClaudeStream(file) {
688
946
  return parseHarnessStream(file);
689
947
  }
948
+ function summarizeShellToolCallEvent(event) {
949
+ const shell = shellPayloadFromCursorEvent(event);
950
+ if (!shell) return void 0;
951
+ const outcome = classifyShellCommandOutcome({
952
+ command: shell.command,
953
+ exitCode: shell.exitCode,
954
+ stdout: shell.stdout,
955
+ stderr: shell.stderr,
956
+ interleavedOutput: shell.interleaved
957
+ });
958
+ const cmd = oneLine(shell.command).slice(0, 120);
959
+ if (outcome.kind === "audit_findings") {
960
+ return `[audit:findings] ${outcome.summary}${cmd ? ` \xB7 ${cmd}` : ""}`;
961
+ }
962
+ if (outcome.kind === "command_failure") {
963
+ return `[command:failed] ${outcome.summary}${cmd ? ` \xB7 ${cmd}` : ""}`;
964
+ }
965
+ return `[command:ok] exit 0${cmd ? ` \xB7 ${cmd}` : ""}`;
966
+ }
690
967
  function summarizeEvent(event) {
691
968
  if (event.type === "system" && event.subtype) {
692
969
  return `[system:${event.subtype}] ${String(event.status || event.cwd || "")}`.trim();
@@ -719,6 +996,8 @@ function summarizeEvent(event) {
719
996
  }
720
997
  if (event.type === "tool_call") {
721
998
  const subtype = String(event.subtype || "");
999
+ const shellSummary = subtype === "completed" ? summarizeShellToolCallEvent(event) : void 0;
1000
+ if (shellSummary) return shellSummary;
722
1001
  const toolCall = event.tool_call && typeof event.tool_call === "object" && !Array.isArray(event.tool_call) ? event.tool_call : void 0;
723
1002
  const name = cursorToolNameFromCall(toolCall) ?? "tool";
724
1003
  return `[tool:${subtype}] ${name}`;
@@ -760,7 +1039,7 @@ var FAILURE_PATTERNS = [
760
1039
  label: "provider authentication failed"
761
1040
  }
762
1041
  ];
763
- function tidy(errorText, max = 240) {
1042
+ function tidy2(errorText, max = 240) {
764
1043
  const oneLine2 = errorText.replace(/\s+/g, " ").trim();
765
1044
  return oneLine2.length > max ? `${oneLine2.slice(0, max - 1)}\u2026` : oneLine2;
766
1045
  }
@@ -769,7 +1048,7 @@ function classifyExitFailure(errorText) {
769
1048
  if (!text) return null;
770
1049
  for (const pattern of FAILURE_PATTERNS) {
771
1050
  if (pattern.test.test(text)) {
772
- return { blocked: true, reason: `${pattern.label}: ${tidy(text)}` };
1051
+ return { blocked: true, reason: `${pattern.label}: ${tidy2(text)}` };
773
1052
  }
774
1053
  }
775
1054
  return null;
@@ -835,6 +1114,63 @@ function assessExitedWorkerSalvage(input) {
835
1114
 
836
1115
  // src/git.ts
837
1116
  import { spawnSync } from "node:child_process";
1117
+
1118
+ // src/worker-env.ts
1119
+ var FORBIDDEN_WORKER_ENV_KEYS = [
1120
+ "ANTHROPIC_API_KEY",
1121
+ "ANALYST_API_KEY",
1122
+ "RECRUITER_API_KEY",
1123
+ "AUTH_SECRET",
1124
+ "NEXTAUTH_SECRET",
1125
+ "DATABASE_URL",
1126
+ "PRODUCTION_DATABASE_URL",
1127
+ "REDIS_URL",
1128
+ "GOOGLE_CLIENT_SECRET",
1129
+ "GITHUB_CLIENT_SECRET",
1130
+ "KYNVER_API_KEY",
1131
+ "KYNVER_SERVICE_SECRET",
1132
+ "KYNVER_RUNTIME_SECRET",
1133
+ "OPENCLAW_CRON_SECRET",
1134
+ "QSTASH_TOKEN",
1135
+ "QSTASH_CURRENT_SIGNING_KEY",
1136
+ "QSTASH_NEXT_SIGNING_KEY",
1137
+ "TOOL_SECRETS_KEK",
1138
+ "TOOL_EXECUTOR_DISPATCH_SECRET",
1139
+ "CLOUDFLARE_API_TOKEN",
1140
+ "STRIPE_SECRET_KEY",
1141
+ "STRIPE_WEBHOOK_SECRET",
1142
+ "STRIPE_IDENTITY_WEBHOOK_SECRET",
1143
+ "VOYAGE_API_KEY",
1144
+ "PERPLEXITY_API_KEY",
1145
+ "FRED_API_KEY",
1146
+ "FMP_API_KEY",
1147
+ "CURSOR_API_KEY"
1148
+ ];
1149
+ var FORBIDDEN_KEY_SET = new Set(FORBIDDEN_WORKER_ENV_KEYS);
1150
+ var FORBIDDEN_SUFFIXES = ["_SECRET", "_API_KEY"];
1151
+ function isForbiddenWorkerEnvKey(key) {
1152
+ if (FORBIDDEN_KEY_SET.has(key)) return true;
1153
+ return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));
1154
+ }
1155
+ function listForbiddenWorkerEnvKeys(env) {
1156
+ return Object.keys(env).filter(isForbiddenWorkerEnvKey).sort();
1157
+ }
1158
+ function scrubWorkerEnv(env) {
1159
+ const next = { ...env };
1160
+ for (const key of Object.keys(next)) {
1161
+ if (isForbiddenWorkerEnvKey(key)) delete next[key];
1162
+ }
1163
+ return next;
1164
+ }
1165
+ function auditWorkerEnv(env) {
1166
+ const forbiddenPresent = listForbiddenWorkerEnvKeys(env);
1167
+ return { forbiddenPresent, safe: forbiddenPresent.length === 0 };
1168
+ }
1169
+ function scrubClaudeEnv(env) {
1170
+ return scrubWorkerEnv(env);
1171
+ }
1172
+
1173
+ // src/git.ts
838
1174
  function git(cwd, args, options = {}) {
839
1175
  const res = spawnSync("git", args, { cwd, encoding: "utf8" });
840
1176
  if (res.status !== 0 && !options.allowFailure) {
@@ -950,11 +1286,6 @@ function unknownAncestry(base, error, head = null) {
950
1286
  error
951
1287
  };
952
1288
  }
953
- function scrubClaudeEnv(env) {
954
- const next = { ...env };
955
- delete next.ANTHROPIC_API_KEY;
956
- return next;
957
- }
958
1289
 
959
1290
  // src/landing-gate.ts
960
1291
  function trimOrNull2(value) {
@@ -1304,15 +1635,7 @@ function computeAutoMaxWorkers(totalMemBytes, opts = {}) {
1304
1635
  return Math.min(raw, AUTO_MAX_WORKERS_CEILING);
1305
1636
  }
1306
1637
  function readAvailableMemBytes() {
1307
- if (process.platform === "linux") {
1308
- try {
1309
- const meminfo = readFileSync6("/proc/meminfo", "utf8");
1310
- const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m);
1311
- if (match) return Number(match[1]) * 1024;
1312
- } catch {
1313
- }
1314
- }
1315
- return os.freemem();
1638
+ return readMemAvailableBytes();
1316
1639
  }
1317
1640
  function isActiveHarnessWorker(worker) {
1318
1641
  const status = computeWorkerStatus(worker);
@@ -1322,7 +1645,7 @@ function countActiveWorkersForRun(run) {
1322
1645
  let active = 0;
1323
1646
  for (const name of Object.keys(run.workers || {})) {
1324
1647
  const worker = readJson(
1325
- path5.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1648
+ path6.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1326
1649
  void 0
1327
1650
  );
1328
1651
  if (!worker || !isActiveHarnessWorker(worker)) continue;
@@ -1340,7 +1663,7 @@ function observeRunnerResourceGate(input) {
1340
1663
  input.config,
1341
1664
  input.configuredMaxWorkersOverride
1342
1665
  );
1343
- const totalMemBytes = input.totalMemBytes ?? os.totalmem();
1666
+ const totalMemBytes = input.totalMemBytes ?? os2.totalmem();
1344
1667
  const freeMemBytes = input.freeMemBytes ?? readAvailableMemBytes();
1345
1668
  const activeWorkers = input.activeWorkers ?? countActiveWorkersGlobal();
1346
1669
  const budgetBytes = Math.max(0, Math.floor(totalMemBytes * memUtilization) - memReserveBytes);
@@ -1530,6 +1853,7 @@ var claudeProvider = {
1530
1853
  const model = preflight.model;
1531
1854
  const stdoutFd = openSync(opts.stdoutPath, "a");
1532
1855
  const stderrFd = openSync(opts.stderrPath, "a");
1856
+ const stdio = ["ignore", stdoutFd, stderrFd];
1533
1857
  const child = spawn(
1534
1858
  "claude",
1535
1859
  [
@@ -1547,8 +1871,8 @@ var claudeProvider = {
1547
1871
  hiddenSpawnOptions({
1548
1872
  cwd: opts.worktreePath,
1549
1873
  detached: true,
1550
- stdio: ["ignore", stdoutFd, stderrFd],
1551
- env: scrubClaudeEnv(process.env)
1874
+ stdio,
1875
+ env: scrubWorkerEnv(process.env)
1552
1876
  })
1553
1877
  );
1554
1878
  closeSync(stdoutFd);
@@ -1706,10 +2030,10 @@ function readHarnessRetryLimits() {
1706
2030
  }
1707
2031
 
1708
2032
  // src/lease-renewal.ts
1709
- import path6 from "node:path";
2033
+ import path7 from "node:path";
1710
2034
  function workerRecord(runId, name) {
1711
2035
  return readJson(
1712
- path6.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
2036
+ path7.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
1713
2037
  void 0
1714
2038
  );
1715
2039
  }
@@ -1776,7 +2100,7 @@ function hasLiveWorkerForTask(runId, taskId) {
1776
2100
 
1777
2101
  // src/supervisor.ts
1778
2102
  import { existsSync as existsSync11, mkdirSync as mkdirSync3 } from "node:fs";
1779
- import path12 from "node:path";
2103
+ import path13 from "node:path";
1780
2104
 
1781
2105
  // src/prompt.ts
1782
2106
  function buildPrompt(input) {
@@ -1795,6 +2119,16 @@ function buildPrompt(input) {
1795
2119
  "- After implementation: wait for report_reviewer then deep_reviewer confirmation (via MCP/session agents) before follow-up rows close.",
1796
2120
  input.planId ? `Active planId: ${input.planId}${input.taskId ? ` \xB7 taskId: ${input.taskId}` : ""}` : "No planId on this worker \u2014 still emit progress when you touch plan-scoped work."
1797
2121
  ];
2122
+ const mergeGateLines = compact ? [
2123
+ "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)."
2124
+ ] : [
2125
+ "GitHub Actions merge-gate cost control (Kynver/Hermes PRs):",
2126
+ "- Prefer local cached package verification (`node scripts/verify-pr-local.mjs --emit-json`) and Vercel preview evidence before GitHub Actions.",
2127
+ "- 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.",
2128
+ "- Record evidence: POST `/api/agent-os/by-id/<agentOsId>/pr-merge-gate/refresh` with prUrl + local/vercel payloads.",
2129
+ "- Request the final Actions run only when local + Vercel are green: POST `.../pr-merge-gate/request-run` (applies `merge-gate` label).",
2130
+ "- Empty failed Actions jobs (no runner/steps/logs) are infra/quota \u2014 do not enter repair loops; escalate to operator."
2131
+ ];
1798
2132
  const planArtifactLines = compact ? [
1799
2133
  "Plan artifacts: when authoring/revising docs/superpowers/plans/, open a GitHub PR early and iterate from that PR branch; do not leave the canonical plan only in the harness worktree."
1800
2134
  ] : [
@@ -1816,10 +2150,14 @@ function buildPrompt(input) {
1816
2150
  "Completion handoff (required): before you stop, ensure the harness records a final result \u2014 summarize outcome in your last message and append a heartbeat line with phase `complete`. If you leave uncommitted changes or committed work without a PR, the orchestrator blocks completion until a GitHub PR exists (or you discard/commit cleanly). Exiting with only dirty files and no PR routes to salvage review, not production review.",
1817
2151
  "PR-ready handoff: for substantial implementation work, commit, push, and open a GitHub PR (draft OK) on your branch before finishing \u2014 or rely on the harness to run `gh pr create` at completion when `gh` is authenticated.",
1818
2152
  "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.",
2153
+ "npm publish boundary: do not run `npm publish`, do not republish `@kynver-app/*` packages, and do not block on an operator to publish. When you need newer runtime code than npm, use this repo checkout (`npm run kynver:build`, `npm run kynver`) and record evidence: packages/kynver-runtime/package.json version + git ref in your completion report.",
1819
2154
  "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.",
2155
+ "Landing-wrapper cleanup on a git ref: use `node scripts/agent-os-land-pr-cleanup-verify.mjs --ref origin/main` (JSON, exit 0). Do not use `git show <ref>:scripts/agent-os-land-pr.mjs | rg \u2026` \u2014 ripgrep exit 1 when markers are absent is reported as a failed command in Telegram/status tooling.",
1820
2156
  "",
1821
2157
  ...progressLines,
1822
2158
  "",
2159
+ ...mergeGateLines,
2160
+ "",
1823
2161
  ...planArtifactLines,
1824
2162
  "",
1825
2163
  ...input.personaMarkdown?.trim() ? [input.personaMarkdown.trim(), ""] : [],
@@ -1832,11 +2170,11 @@ function buildPrompt(input) {
1832
2170
  // src/providers/cursor.ts
1833
2171
  import { closeSync as closeSync2, existsSync as existsSync9, openSync as openSync2 } from "node:fs";
1834
2172
  import { spawn as spawn2 } from "node:child_process";
1835
- import path8 from "node:path";
2173
+ import path9 from "node:path";
1836
2174
 
1837
2175
  // src/providers/cursor-windows.ts
1838
2176
  import { existsSync as existsSync8, readdirSync as readdirSync3 } from "node:fs";
1839
- import path7 from "node:path";
2177
+ import path8 from "node:path";
1840
2178
  var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
1841
2179
  function parseCursorVersionSortKey(versionName) {
1842
2180
  const datePart = versionName.split("-")[0];
@@ -1847,7 +2185,7 @@ function parseCursorVersionSortKey(versionName) {
1847
2185
  return Number(`${year}${month.padStart(2, "0")}${day.padStart(2, "0")}`);
1848
2186
  }
1849
2187
  function pickLatestCursorVersionDir(agentRoot) {
1850
- const versionsRoot = path7.join(agentRoot, "versions");
2188
+ const versionsRoot = path8.join(agentRoot, "versions");
1851
2189
  if (!existsSync8(versionsRoot)) return null;
1852
2190
  let bestDir = null;
1853
2191
  let bestKey = -1;
@@ -1856,21 +2194,21 @@ function pickLatestCursorVersionDir(agentRoot) {
1856
2194
  const key = parseCursorVersionSortKey(entry.name);
1857
2195
  if (key == null || key <= bestKey) continue;
1858
2196
  bestKey = key;
1859
- bestDir = path7.join(versionsRoot, entry.name);
2197
+ bestDir = path8.join(versionsRoot, entry.name);
1860
2198
  }
1861
2199
  return bestDir;
1862
2200
  }
1863
2201
  function resolveWindowsCursorBundled(agentRoot) {
1864
- const root = agentRoot?.trim() || path7.join(process.env.LOCALAPPDATA || "", "cursor-agent");
1865
- const directNode = path7.join(root, "node.exe");
1866
- const directIndex = path7.join(root, "index.js");
2202
+ const root = agentRoot?.trim() || path8.join(process.env.LOCALAPPDATA || "", "cursor-agent");
2203
+ const directNode = path8.join(root, "node.exe");
2204
+ const directIndex = path8.join(root, "index.js");
1867
2205
  if (existsSync8(directNode) && existsSync8(directIndex)) {
1868
2206
  return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
1869
2207
  }
1870
2208
  const versionDir = pickLatestCursorVersionDir(root);
1871
2209
  if (!versionDir) return null;
1872
- const nodeExe = path7.join(versionDir, "node.exe");
1873
- const indexJs = path7.join(versionDir, "index.js");
2210
+ const nodeExe = path8.join(versionDir, "node.exe");
2211
+ const indexJs = path8.join(versionDir, "index.js");
1874
2212
  if (!existsSync8(nodeExe) || !existsSync8(indexJs)) return null;
1875
2213
  return { nodeExe, indexJs, versionDir };
1876
2214
  }
@@ -1889,13 +2227,13 @@ function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
1889
2227
  function resolveCursorSpawn(agentBin) {
1890
2228
  if (process.platform === "win32") {
1891
2229
  const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
1892
- const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync9(path8.join(path8.dirname(agentBin), "index.js"));
2230
+ const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync9(path9.join(path9.dirname(agentBin), "index.js"));
1893
2231
  const isDefaultShim = agentBin === "agent";
1894
2232
  if (isCursorWrapper || isBundledNode || isDefaultShim) {
1895
- const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path8.dirname(agentBin)) : isBundledNode ? {
2233
+ const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path9.dirname(agentBin)) : isBundledNode ? {
1896
2234
  nodeExe: agentBin,
1897
- indexJs: path8.join(path8.dirname(agentBin), "index.js"),
1898
- versionDir: path8.dirname(agentBin)
2235
+ indexJs: path9.join(path9.dirname(agentBin), "index.js"),
2236
+ versionDir: path9.dirname(agentBin)
1899
2237
  } : resolveWindowsCursorBundled();
1900
2238
  if (bundled) {
1901
2239
  return bundledSpawnTarget(bundled.nodeExe, bundled.indexJs, bundled.versionDir);
@@ -1915,18 +2253,18 @@ function resolveAgentBin() {
1915
2253
  process.env.KYNVER_CURSOR_AGENT_ROOT?.trim() || void 0
1916
2254
  );
1917
2255
  if (bundled) return bundled.nodeExe;
1918
- const localAgent = path8.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
2256
+ const localAgent = path9.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
1919
2257
  if (existsSync9(localAgent)) return localAgent;
1920
2258
  }
1921
2259
  return "agent";
1922
2260
  }
1923
2261
  function cursorWorkerEnv(agentBin, spawnTarget) {
1924
- return {
2262
+ return scrubWorkerEnv({
1925
2263
  ...process.env,
1926
2264
  CI: "1",
1927
2265
  NO_COLOR: "1",
1928
- ...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path8.basename(agentBin) || "agent.cmd" } : {}
1929
- };
2266
+ ...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path9.basename(agentBin) || "agent.cmd" } : {}
2267
+ });
1930
2268
  }
1931
2269
  var cursorProvider = {
1932
2270
  name: "cursor",
@@ -1942,6 +2280,7 @@ var cursorProvider = {
1942
2280
  const model = preflight.model;
1943
2281
  const stdoutFd = openSync2(opts.stdoutPath, "a");
1944
2282
  const stderrFd = openSync2(opts.stderrPath, "a");
2283
+ const stdio = ["ignore", stdoutFd, stderrFd];
1945
2284
  const agentBin = resolveAgentBin();
1946
2285
  const spawnTarget = resolveCursorSpawn(agentBin);
1947
2286
  const child = spawn2(
@@ -1964,7 +2303,7 @@ var cursorProvider = {
1964
2303
  cwd: opts.worktreePath,
1965
2304
  detached: spawnTarget.detached,
1966
2305
  shell: spawnTarget.shell,
1967
- stdio: ["ignore", stdoutFd, stderrFd],
2306
+ stdio,
1968
2307
  env: cursorWorkerEnv(agentBin, spawnTarget)
1969
2308
  })
1970
2309
  );
@@ -1999,7 +2338,7 @@ function resolveWorkerProvider(name) {
1999
2338
  // src/auto-complete.ts
2000
2339
  import { spawn as spawn3 } from "node:child_process";
2001
2340
  import { existsSync as existsSync10, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
2002
- import path11 from "node:path";
2341
+ import path12 from "node:path";
2003
2342
  import { fileURLToPath as fileURLToPath2 } from "node:url";
2004
2343
 
2005
2344
  // src/completion-ack.ts
@@ -2016,7 +2355,40 @@ function persistCompletionAck(worker, runId, fields) {
2016
2355
  }
2017
2356
 
2018
2357
  // src/worker-ops.ts
2019
- import path10 from "node:path";
2358
+ import path11 from "node:path";
2359
+
2360
+ // src/completion-response.ts
2361
+ function asRecord(value) {
2362
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
2363
+ }
2364
+ function asString(value) {
2365
+ if (typeof value !== "string") return null;
2366
+ const trimmed = value.trim();
2367
+ return trimmed.length ? trimmed : null;
2368
+ }
2369
+ var ADVANCED_OUTCOMES = /* @__PURE__ */ new Set([
2370
+ "review_scheduled",
2371
+ "review_already_scheduled"
2372
+ ]);
2373
+ function summarizeHarnessCompletionResponse(parsed) {
2374
+ const record = asRecord(parsed);
2375
+ if (!record) {
2376
+ return { routeOutcome: null, taskAdvanced: false, detail: null };
2377
+ }
2378
+ const outcome = asString(record.outcome);
2379
+ const detail = asString(record.detail) ?? asString(record.error);
2380
+ const task = asRecord(record.task);
2381
+ const taskStatus = task ? asString(task.status) : null;
2382
+ const taskAdvanced = outcome !== null && ADVANCED_OUTCOMES.has(outcome) || taskStatus === "awaiting_review" || taskStatus === "done";
2383
+ return {
2384
+ routeOutcome: outcome,
2385
+ taskAdvanced,
2386
+ detail
2387
+ };
2388
+ }
2389
+ function completionPostSucceeded(summary) {
2390
+ return summary.taskAdvanced;
2391
+ }
2020
2392
 
2021
2393
  // src/pr-handoff/pr-handoff-assess.ts
2022
2394
  var REVIEW_LANE_RULE = /^(lane:)?(review|deep_review|planning|landing)(:|$)/i;
@@ -2078,6 +2450,36 @@ function buildPrHandoffSnapshotFromStatus(status, extras) {
2078
2450
 
2079
2451
  // src/pr-handoff/pr-handoff-gh.ts
2080
2452
  import { spawnSync as spawnSync2 } from "node:child_process";
2453
+
2454
+ // src/github-repo.ts
2455
+ function parseGithubOwnerRepo(remoteUrl) {
2456
+ const trimmed = remoteUrl.trim();
2457
+ if (!trimmed) return null;
2458
+ const ssh = trimmed.match(/^git@github\.com:([^/]+\/[^/\s]+?)(?:\.git)?$/i);
2459
+ if (ssh) return normalizeOwnerRepo(ssh[1]);
2460
+ const scp = trimmed.match(/^ssh:\/\/git@github\.com\/([^/]+\/[^/\s]+?)(?:\.git)?$/i);
2461
+ if (scp) return normalizeOwnerRepo(scp[1]);
2462
+ try {
2463
+ const url = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`);
2464
+ if (url.hostname.toLowerCase() !== "github.com") return null;
2465
+ const parts = url.pathname.replace(/^\/+|\/+$/g, "").split("/");
2466
+ if (parts.length < 2) return null;
2467
+ const [owner, repo] = parts;
2468
+ if (!owner || !repo) return null;
2469
+ return `${owner}/${repo.replace(/\.git$/i, "")}`;
2470
+ } catch {
2471
+ return null;
2472
+ }
2473
+ }
2474
+ function normalizeOwnerRepo(value) {
2475
+ const parts = value.split("/").filter(Boolean);
2476
+ if (parts.length < 2) return null;
2477
+ const owner = parts[0];
2478
+ const repo = parts[1].replace(/\.git$/i, "");
2479
+ return owner && repo ? `${owner}/${repo}` : null;
2480
+ }
2481
+
2482
+ // src/pr-handoff/pr-handoff-gh.ts
2081
2483
  function capture(bin, cwd, args) {
2082
2484
  try {
2083
2485
  const res = spawnSync2(bin, args, { cwd, encoding: "utf8" });
@@ -2100,21 +2502,13 @@ var defaultPrHandoffExec = {
2100
2502
  git: (cwd, args) => gitCapture(cwd, args),
2101
2503
  gh: (cwd, args) => capture("gh", cwd, args)
2102
2504
  };
2103
- function parseGithubRepo(remoteUrl) {
2104
- const trimmed = remoteUrl.trim();
2105
- const ssh = trimmed.match(/git@github\.com:([^/]+\/[^/.]+)(?:\.git)?/i);
2106
- if (ssh) return ssh[1];
2107
- const https = trimmed.match(/github\.com[/:]([^/]+\/[^/.]+?)(?:\.git)?/i);
2108
- if (https) return https[1];
2109
- return null;
2110
- }
2111
2505
  function firstLine(text) {
2112
2506
  return text.split("\n").map((l) => l.trim()).find(Boolean) ?? "";
2113
2507
  }
2114
2508
  function resolveGithubRepo(worktreePath, exec) {
2115
2509
  const remote = exec.git(worktreePath, ["remote", "get-url", "origin"]);
2116
2510
  if (remote.status !== 0) return null;
2117
- return parseGithubRepo(remote.stdout);
2511
+ return parseGithubOwnerRepo(remote.stdout);
2118
2512
  }
2119
2513
  function resolveHeadCommit(worktreePath, exec) {
2120
2514
  const head = exec.git(worktreePath, ["rev-parse", "HEAD"]);
@@ -2353,7 +2747,7 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
2353
2747
  }
2354
2748
 
2355
2749
  // src/worker-lifecycle.ts
2356
- import path9 from "node:path";
2750
+ import path10 from "node:path";
2357
2751
  var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
2358
2752
  "awaiting_review",
2359
2753
  "blocked",
@@ -2409,7 +2803,7 @@ function syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick) {
2409
2803
  const synced = [];
2410
2804
  for (const name of Object.keys(run.workers || {})) {
2411
2805
  const worker = readJson(
2412
- path9.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2806
+ path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2413
2807
  void 0
2414
2808
  );
2415
2809
  if (!worker?.taskId || isCompletionAcknowledged(worker)) continue;
@@ -2448,10 +2842,10 @@ function completionErrorText(parsed) {
2448
2842
  }
2449
2843
  return void 0;
2450
2844
  }
2451
- function asRecord(value) {
2845
+ function asRecord2(value) {
2452
2846
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
2453
2847
  }
2454
- function asString(value) {
2848
+ function asString2(value) {
2455
2849
  if (typeof value !== "string") return null;
2456
2850
  const trimmed = value.trim();
2457
2851
  return trimmed.length ? trimmed : null;
@@ -2571,7 +2965,27 @@ async function tryCompleteWorker(args) {
2571
2965
  }
2572
2966
  }
2573
2967
  if (result.ok) {
2968
+ const summary = summarizeHarnessCompletionResponse(result.parsed);
2969
+ if (!completionPostSucceeded(summary)) {
2970
+ const detail2 = summary.detail ?? (summary.routeOutcome ? `harness completion returned ${summary.routeOutcome}` : "harness completion did not advance the linked task");
2971
+ const reason2 = `completion acknowledged but board not advanced: ${detail2}`;
2972
+ persistCompletionBlocker(worker, reason2);
2973
+ const ack2 = {
2974
+ completionReportedAt: (/* @__PURE__ */ new Date()).toISOString(),
2975
+ completionOutcome: "rejected",
2976
+ completionResponse: result.parsed
2977
+ };
2978
+ persistCompletionAck(worker, worker.runId, ack2);
2979
+ return {
2980
+ ok: false,
2981
+ httpStatus: result.status,
2982
+ response: result.parsed,
2983
+ reason: reason2,
2984
+ completionBlocked: true
2985
+ };
2986
+ }
2574
2987
  persistCompletionBlocker(worker, void 0);
2988
+ const routeOutcome = summary.routeOutcome ?? "acknowledged";
2575
2989
  const ack = {
2576
2990
  completionReportedAt: (/* @__PURE__ */ new Date()).toISOString(),
2577
2991
  completionOutcome: "acknowledged",
@@ -2584,6 +2998,7 @@ async function tryCompleteWorker(args) {
2584
2998
  ok: true,
2585
2999
  httpStatus: result.status,
2586
3000
  response: result.parsed,
3001
+ reason: routeOutcome,
2587
3002
  ...prUrl ? { prHandoff: { prUrl } } : {}
2588
3003
  };
2589
3004
  }
@@ -2646,7 +3061,7 @@ function workerStatus(args) {
2646
3061
  const worker = loadWorker(String(args.run), String(args.name));
2647
3062
  const run = loadRun(worker.runId);
2648
3063
  const status = computeWorkerStatus(worker, workerStatusOptions(run));
2649
- writeJson(path10.join(worker.workerDir, "last-status.json"), status);
3064
+ writeJson(path11.join(worker.workerDir, "last-status.json"), status);
2650
3065
  console.log(JSON.stringify(status, null, 2));
2651
3066
  }
2652
3067
  function buildRunBoard(runId) {
@@ -2654,7 +3069,7 @@ function buildRunBoard(runId) {
2654
3069
  const names = Object.keys(run.workers || {});
2655
3070
  const workers = names.map((name) => {
2656
3071
  const worker = readJson(
2657
- path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
3072
+ path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2658
3073
  void 0
2659
3074
  );
2660
3075
  if (!worker) {
@@ -2676,22 +3091,23 @@ function buildRunBoard(runId) {
2676
3091
  const completionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
2677
3092
  const boardStatus = completionBlocker ? "blocked" : status.status;
2678
3093
  const boardAttention = completionBlocker ? "blocked" : status.attention.state;
2679
- const completionResponse = asRecord(worker.completionResponse);
2680
- const completionTask = asRecord(completionResponse?.task);
2681
- const completionOutcome = asString(completionResponse?.outcome);
2682
- const completionRouteStatus = asString(completionResponse?.status);
3094
+ const completionResponse = asRecord2(worker.completionResponse);
3095
+ const completionTask = asRecord2(completionResponse?.task);
3096
+ const completionOutcome = asString2(completionResponse?.outcome);
3097
+ const completionRouteStatus = asString2(completionResponse?.status);
2683
3098
  const completionWarnings = Array.isArray(completionResponse?.warnings) ? completionResponse.warnings.filter((w) => typeof w === "string" && w.trim().length > 0) : [];
2684
- const prUrl = asString(completionTask?.prUrl) ?? asString(completionResponse?.prUrl);
3099
+ const prUrl = asString2(completionTask?.prUrl) ?? asString2(completionResponse?.prUrl);
3100
+ const completionReportedAt = asString2(worker.completionReportedAt);
2685
3101
  const lifecycleStage = deriveLifecycleStage({
2686
3102
  finished: isFinishedWorkerStatus(status),
2687
3103
  completionBlocker,
2688
3104
  completionOutcome,
2689
- completionReportedAt: worker.completionReportedAt ?? null
3105
+ completionReportedAt
2690
3106
  });
2691
3107
  const nextAction = deriveNextAction({
2692
3108
  completionBlocker,
2693
3109
  completionOutcome,
2694
- completionReportedAt: worker.completionReportedAt ?? null,
3110
+ completionReportedAt,
2695
3111
  finished: isFinishedWorkerStatus(status)
2696
3112
  });
2697
3113
  const handoffState = deriveHandoffState({
@@ -2737,7 +3153,7 @@ function buildRunBoard(runId) {
2737
3153
  gitAncestry: status.gitAncestry,
2738
3154
  finalResult: status.finalResult,
2739
3155
  lifecycleStage,
2740
- completionReportedAt: worker.completionReportedAt ?? null,
3156
+ completionReportedAt,
2741
3157
  completionOutcome: worker.completionOutcome ?? null,
2742
3158
  completionRouteStatus,
2743
3159
  completionRouteOutcome: completionOutcome,
@@ -2764,7 +3180,7 @@ function buildRunBoard(runId) {
2764
3180
  needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
2765
3181
  workers
2766
3182
  };
2767
- writeJson(path10.join(runDirectory(run.id), "last-board.json"), board);
3183
+ writeJson(path11.join(runDirectory(run.id), "last-board.json"), board);
2768
3184
  return board;
2769
3185
  }
2770
3186
  async function publishHarnessBoardSnapshot(args, source) {
@@ -2953,12 +3369,12 @@ async function autoCompleteWorkerCli(raw) {
2953
3369
  }
2954
3370
  }
2955
3371
  function resolveDefaultCliPath() {
2956
- return path11.join(fileURLToPath2(new URL(".", import.meta.url)), "cli.js");
3372
+ return path12.join(fileURLToPath2(new URL(".", import.meta.url)), "cli.js");
2957
3373
  }
2958
3374
  function spawnCompletionSidecar(opts) {
2959
3375
  const cliPath = opts.cliPath ?? resolveDefaultCliPath();
2960
3376
  if (!existsSync10(cliPath)) return void 0;
2961
- const logPath = path11.join(opts.workerDir, "auto-complete.log");
3377
+ const logPath = path12.join(opts.workerDir, "auto-complete.log");
2962
3378
  let logFd;
2963
3379
  try {
2964
3380
  logFd = openSync3(logPath, "a");
@@ -3040,16 +3456,16 @@ function spawnWorkerProcess(run, opts) {
3040
3456
  launchModel = preflight.model;
3041
3457
  }
3042
3458
  const { worktreesDir } = getPaths();
3043
- const workerDir = path12.join(runDirectory(run.id), "workers", name);
3459
+ const workerDir = path13.join(runDirectory(run.id), "workers", name);
3044
3460
  mkdirSync3(workerDir, { recursive: true });
3045
- const worktreePath = path12.join(worktreesDir, run.id, name);
3461
+ const worktreePath = path13.join(worktreesDir, run.id, name);
3046
3462
  const branch = opts.branch || `agent/${run.id}/${name}`;
3047
3463
  if (existsSync11(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
3048
3464
  git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
3049
3465
  git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
3050
- const stdoutPath = path12.join(workerDir, "stdout.jsonl");
3051
- const stderrPath = path12.join(workerDir, "stderr.log");
3052
- const heartbeatPath = path12.join(workerDir, "heartbeat.jsonl");
3466
+ const stdoutPath = path13.join(workerDir, "stdout.jsonl");
3467
+ const stderrPath = path13.join(workerDir, "stderr.log");
3468
+ const heartbeatPath = path13.join(workerDir, "heartbeat.jsonl");
3053
3469
  const prompt = buildPrompt({
3054
3470
  task: opts.task,
3055
3471
  ownedPaths: opts.ownedPaths || [],
@@ -3110,7 +3526,7 @@ function spawnWorkerProcess(run, opts) {
3110
3526
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
3111
3527
  };
3112
3528
  saveWorker(run.id, worker);
3113
- run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path12.join(workerDir, "worker.json") } };
3529
+ run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path13.join(workerDir, "worker.json") } };
3114
3530
  run.status = "running";
3115
3531
  saveRun(run);
3116
3532
  if (worker.agentOsId && worker.taskId) {
@@ -3402,18 +3818,18 @@ function buildPlanPersistIdempotencyKey(input) {
3402
3818
 
3403
3819
  // src/plan-persist/paths.ts
3404
3820
  import { mkdirSync as mkdirSync4 } from "node:fs";
3405
- import { homedir as homedir3 } from "node:os";
3406
- import path13 from "node:path";
3821
+ import { homedir as homedir4 } from "node:os";
3822
+ import path14 from "node:path";
3407
3823
  function resolveKynverStateRoot() {
3408
3824
  const env = process.env.KYNVER_STATE_ROOT;
3409
- if (env) return path13.resolve(env);
3410
- return path13.join(homedir3(), ".kynver", "state");
3825
+ if (env) return path14.resolve(env);
3826
+ return path14.join(homedir4(), ".kynver", "state");
3411
3827
  }
3412
3828
  function planOutboxDir() {
3413
- return path13.join(resolveKynverStateRoot(), "plan-outbox");
3829
+ return path14.join(resolveKynverStateRoot(), "plan-outbox");
3414
3830
  }
3415
3831
  function planOutboxArchiveDir() {
3416
- return path13.join(resolveKynverStateRoot(), "plan-outbox-archive");
3832
+ return path14.join(resolveKynverStateRoot(), "plan-outbox-archive");
3417
3833
  }
3418
3834
  function ensurePlanOutboxDirs() {
3419
3835
  const outboxDir = planOutboxDir();
@@ -3424,8 +3840,8 @@ function ensurePlanOutboxDirs() {
3424
3840
  }
3425
3841
  function isTmpOnlyPath(filePath) {
3426
3842
  if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
3427
- const resolved = path13.resolve(filePath);
3428
- return resolved.startsWith("/tmp/") || resolved.startsWith(path13.join("/var", "folders"));
3843
+ const resolved = path14.resolve(filePath);
3844
+ return resolved.startsWith("/tmp/") || resolved.startsWith(path14.join("/var", "folders"));
3429
3845
  }
3430
3846
 
3431
3847
  // src/plan-persist/outbox-store.ts
@@ -3437,7 +3853,7 @@ import {
3437
3853
  writeFileSync as writeFileSync3,
3438
3854
  unlinkSync
3439
3855
  } from "node:fs";
3440
- import path14 from "node:path";
3856
+ import path15 from "node:path";
3441
3857
  import { randomUUID } from "node:crypto";
3442
3858
  var DEFAULT_MAX_RETRIES = 12;
3443
3859
  function listOutboxItems() {
@@ -3445,7 +3861,7 @@ function listOutboxItems() {
3445
3861
  const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
3446
3862
  const items = [];
3447
3863
  for (const file of files) {
3448
- const item = readOutboxItem(path14.join(outboxDir, file));
3864
+ const item = readOutboxItem(path15.join(outboxDir, file));
3449
3865
  if (item && item.queueStatus === "queued") items.push(item);
3450
3866
  }
3451
3867
  return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
@@ -3466,7 +3882,7 @@ function readOutboxItem(jsonPath) {
3466
3882
  }
3467
3883
  function readOutboxBody(item) {
3468
3884
  const { outboxDir } = ensurePlanOutboxDirs();
3469
- const bodyFile = path14.join(outboxDir, item.bodyPath);
3885
+ const bodyFile = path15.join(outboxDir, item.bodyPath);
3470
3886
  return readFileSync7(bodyFile, "utf8");
3471
3887
  }
3472
3888
  function writeOutboxItem(input, opts) {
@@ -3474,8 +3890,8 @@ function writeOutboxItem(input, opts) {
3474
3890
  const now = (/* @__PURE__ */ new Date()).toISOString();
3475
3891
  const id = opts.existing?.id ?? randomUUID();
3476
3892
  const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
3477
- const jsonPath = path14.join(outboxDir, `${id}.json`);
3478
- const bodyFile = path14.join(outboxDir, bodyPath);
3893
+ const jsonPath = path15.join(outboxDir, `${id}.json`);
3894
+ const bodyFile = path15.join(outboxDir, bodyPath);
3479
3895
  if (!opts.existing) {
3480
3896
  writeFileSync3(bodyFile, input.body, "utf8");
3481
3897
  }
@@ -3511,24 +3927,24 @@ function writeOutboxItem(input, opts) {
3511
3927
  }
3512
3928
  function saveOutboxItem(item) {
3513
3929
  const { outboxDir } = ensurePlanOutboxDirs();
3514
- const jsonPath = path14.join(outboxDir, `${item.id}.json`);
3930
+ const jsonPath = path15.join(outboxDir, `${item.id}.json`);
3515
3931
  writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
3516
3932
  `, { mode: 384 });
3517
3933
  }
3518
3934
  function archiveOutboxItem(item) {
3519
3935
  const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
3520
- const jsonSrc = path14.join(outboxDir, `${item.id}.json`);
3521
- const bodySrc = path14.join(outboxDir, item.bodyPath);
3522
- const jsonDst = path14.join(archiveDir, `${item.id}.json`);
3523
- const bodyDst = path14.join(archiveDir, item.bodyPath);
3936
+ const jsonSrc = path15.join(outboxDir, `${item.id}.json`);
3937
+ const bodySrc = path15.join(outboxDir, item.bodyPath);
3938
+ const jsonDst = path15.join(archiveDir, `${item.id}.json`);
3939
+ const bodyDst = path15.join(archiveDir, item.bodyPath);
3524
3940
  if (existsSync13(jsonSrc)) renameSync(jsonSrc, jsonDst);
3525
3941
  if (existsSync13(bodySrc)) renameSync(bodySrc, bodyDst);
3526
3942
  }
3527
3943
  function outboxItemPaths(item) {
3528
3944
  const { outboxDir } = ensurePlanOutboxDirs();
3529
3945
  return {
3530
- jsonPath: path14.join(outboxDir, `${item.id}.json`),
3531
- bodyPath: path14.join(outboxDir, item.bodyPath)
3946
+ jsonPath: path15.join(outboxDir, `${item.id}.json`),
3947
+ bodyPath: path15.join(outboxDir, item.bodyPath)
3532
3948
  };
3533
3949
  }
3534
3950
  function outboxInputFromItem(item, body) {
@@ -3714,7 +4130,7 @@ function markOutboxFailed(item, message) {
3714
4130
  }
3715
4131
 
3716
4132
  // src/plan-persist/drain.ts
3717
- import path15 from "node:path";
4133
+ import path16 from "node:path";
3718
4134
  async function drainPlanOutbox(opts = {}, deps = {}) {
3719
4135
  const items = listOutboxItems().filter(
3720
4136
  (item) => opts.outboxId ? item.id === opts.outboxId : true
@@ -3748,7 +4164,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
3748
4164
  return result;
3749
4165
  }
3750
4166
  function loadOutboxById(outboxId) {
3751
- const jsonPath = path15.join(planOutboxDir(), `${outboxId}.json`);
4167
+ const jsonPath = path16.join(planOutboxDir(), `${outboxId}.json`);
3752
4168
  return readOutboxItem(jsonPath);
3753
4169
  }
3754
4170
 
@@ -3848,7 +4264,7 @@ async function dispatchRun(args) {
3848
4264
  const activeHarnessWorkers = [];
3849
4265
  for (const name of Object.keys(run.workers || {})) {
3850
4266
  const worker = readJson(
3851
- path16.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4267
+ path17.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
3852
4268
  void 0
3853
4269
  );
3854
4270
  if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
@@ -3872,7 +4288,8 @@ async function dispatchRun(args) {
3872
4288
  harnessBoardSnapshot: buildRunBoard(run.id),
3873
4289
  ...args.lane ? { lane: String(args.lane) } : {},
3874
4290
  executor: args.executor ? String(args.executor) : "harness",
3875
- ...args.diskPath ? { diskPath: String(args.diskPath) } : {}
4291
+ ...args.diskPath ? { diskPath: String(args.diskPath) } : {},
4292
+ ...args.targetTaskId ? { targetTaskId: String(args.targetTaskId) } : {}
3876
4293
  };
3877
4294
  const dispatch = await postJsonWithCredentialRefresh(dispatchUrl, secret, body, { agentOsId, baseUrl: base });
3878
4295
  const responseBody = dispatch.response;
@@ -4051,6 +4468,13 @@ async function dispatchRun(args) {
4051
4468
  }
4052
4469
  }
4053
4470
 
4471
+ // src/fortress-engagement-gate.ts
4472
+ function isEngagementRequiredSkip(skip) {
4473
+ if (skip.skipReason === "engagement_required") return true;
4474
+ const reason = skip.reason ?? "";
4475
+ return reason.includes("engagement_required") || reason.includes("engagement_not_found") || reason.includes("engagement_expired") || reason.includes("engagement_revoked");
4476
+ }
4477
+
4054
4478
  // src/redact.ts
4055
4479
  function redactHarness(text, secret) {
4056
4480
  let out = text;
@@ -4061,7 +4485,7 @@ function redactHarness(text, secret) {
4061
4485
  }
4062
4486
 
4063
4487
  // src/validate.ts
4064
- import path17 from "node:path";
4488
+ import path18 from "node:path";
4065
4489
  var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
4066
4490
  var WORKER_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
4067
4491
  function validateRunId(runId) {
@@ -4075,15 +4499,15 @@ function validateWorkerName(name) {
4075
4499
  return trimmed;
4076
4500
  }
4077
4501
  function validateRepo(repo) {
4078
- const resolved = path17.resolve(repo);
4502
+ const resolved = path18.resolve(repo);
4079
4503
  if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
4080
4504
  return resolved;
4081
4505
  }
4082
4506
  function validateOwnedPaths(repoRoot, ownedPaths) {
4083
4507
  return ownedPaths.map((owned) => {
4084
- const resolved = path17.resolve(repoRoot, owned);
4085
- const rel = path17.relative(repoRoot, resolved);
4086
- if (rel.startsWith("..") || path17.isAbsolute(rel)) {
4508
+ const resolved = path18.resolve(repoRoot, owned);
4509
+ const rel = path18.relative(repoRoot, resolved);
4510
+ if (rel.startsWith("..") || path18.isAbsolute(rel)) {
4087
4511
  throw new Error(`owned path escapes repo: ${owned}`);
4088
4512
  }
4089
4513
  return resolved;
@@ -4096,7 +4520,7 @@ function validateTailLines(lines) {
4096
4520
 
4097
4521
  // src/worktree.ts
4098
4522
  import { existsSync as existsSync14, mkdirSync as mkdirSync5 } from "node:fs";
4099
- import path18 from "node:path";
4523
+ import path19 from "node:path";
4100
4524
  function createRun(args) {
4101
4525
  const repo = validateRepo(required(String(args.repo || ""), "--repo"));
4102
4526
  ensureGitRepo(repo);
@@ -4116,12 +4540,12 @@ function createRun(args) {
4116
4540
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4117
4541
  workers: {}
4118
4542
  };
4119
- writeJson(path18.join(dir, "run.json"), run);
4543
+ writeJson(path19.join(dir, "run.json"), run);
4120
4544
  console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
4121
4545
  }
4122
4546
  function listRuns() {
4123
4547
  const { runsDir } = getPaths();
4124
- const rows = listRunIds(runsDir).map((id) => readJson(path18.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
4548
+ const rows = listRunIds(runsDir).map((id) => readJson(path19.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
4125
4549
  id: run.id,
4126
4550
  name: run.name,
4127
4551
  status: run.status,
@@ -4136,7 +4560,7 @@ function failExists(message) {
4136
4560
  }
4137
4561
 
4138
4562
  // src/sweep.ts
4139
- import path19 from "node:path";
4563
+ import path20 from "node:path";
4140
4564
  async function sweepRun(args) {
4141
4565
  const pipeline = args.pipeline === true || args.pipeline === "true";
4142
4566
  try {
@@ -4149,7 +4573,7 @@ async function sweepRun(args) {
4149
4573
  const releasedLocalOrphans = [];
4150
4574
  for (const name of Object.keys(run.workers || {})) {
4151
4575
  const worker = readJson(
4152
- path19.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4576
+ path20.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4153
4577
  void 0
4154
4578
  );
4155
4579
  if (!worker || !worker.dispatched || !worker.taskId) continue;
@@ -4196,7 +4620,7 @@ import { mkdirSync as mkdirSync7, realpathSync } from "node:fs";
4196
4620
  import { fileURLToPath as fileURLToPath4 } from "node:url";
4197
4621
 
4198
4622
  // src/pipeline-tick.ts
4199
- import path28 from "node:path";
4623
+ import path30 from "node:path";
4200
4624
 
4201
4625
  // src/pipeline-dispatch.ts
4202
4626
  var RESERVED_REVIEW_STARTS = 1;
@@ -4250,10 +4674,10 @@ async function runPipelineDispatch(args, slots) {
4250
4674
  }
4251
4675
 
4252
4676
  // src/stale-reconcile.ts
4253
- import path21 from "node:path";
4677
+ import path22 from "node:path";
4254
4678
 
4255
4679
  // src/finalize.ts
4256
- import path20 from "node:path";
4680
+ import path21 from "node:path";
4257
4681
  var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
4258
4682
  function terminalStatusFor(run) {
4259
4683
  const names = Object.keys(run.workers || {});
@@ -4264,7 +4688,7 @@ function terminalStatusFor(run) {
4264
4688
  let anyLandingBlocked = false;
4265
4689
  for (const name of names) {
4266
4690
  const worker = readJson(
4267
- path20.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4691
+ path21.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4268
4692
  void 0
4269
4693
  );
4270
4694
  if (!worker) continue;
@@ -4316,7 +4740,7 @@ function reconcileStaleWorkers() {
4316
4740
  const now = Date.now();
4317
4741
  for (const run of listRunRecords()) {
4318
4742
  for (const name of Object.keys(run.workers || {})) {
4319
- const workerPath = path21.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
4743
+ const workerPath = path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
4320
4744
  const worker = readJson(workerPath, void 0);
4321
4745
  if (!worker || worker.status !== "running") {
4322
4746
  outcomes.push({
@@ -4410,7 +4834,7 @@ function reconcileRunsCli() {
4410
4834
  }
4411
4835
 
4412
4836
  // src/plan-progress-daemon-sync.ts
4413
- import path22 from "node:path";
4837
+ import path23 from "node:path";
4414
4838
 
4415
4839
  // src/plan-progress-sync.ts
4416
4840
  async function syncPlanProgress(args) {
@@ -4434,7 +4858,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
4434
4858
  const outcomes = [];
4435
4859
  for (const name of Object.keys(run.workers || {})) {
4436
4860
  const worker = readJson(
4437
- path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4861
+ path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4438
4862
  void 0
4439
4863
  );
4440
4864
  if (!worker?.dispatched || !worker.taskId) continue;
@@ -4483,7 +4907,7 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
4483
4907
  }
4484
4908
 
4485
4909
  // src/cleanup.ts
4486
- import path27 from "node:path";
4910
+ import path28 from "node:path";
4487
4911
 
4488
4912
  // src/cleanup-types.ts
4489
4913
  var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
@@ -4555,11 +4979,11 @@ function skipNodeModulesRemoval(input) {
4555
4979
 
4556
4980
  // src/cleanup-execute.ts
4557
4981
  import { existsSync as existsSync16, rmSync } from "node:fs";
4558
- import path24 from "node:path";
4982
+ import path25 from "node:path";
4559
4983
 
4560
4984
  // src/cleanup-dir-size.ts
4561
4985
  import { existsSync as existsSync15, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
4562
- import path23 from "node:path";
4986
+ import path24 from "node:path";
4563
4987
  function directorySizeBytes(root, maxEntries = 5e4) {
4564
4988
  if (!existsSync15(root)) return 0;
4565
4989
  let total = 0;
@@ -4575,7 +4999,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
4575
4999
  }
4576
5000
  for (const name of entries) {
4577
5001
  if (seen++ > maxEntries) return null;
4578
- const full = path23.join(current, name);
5002
+ const full = path24.join(current, name);
4579
5003
  let st;
4580
5004
  try {
4581
5005
  st = statSync2(full);
@@ -4659,20 +5083,20 @@ function removeWorktree(candidate, execute) {
4659
5083
  }
4660
5084
  }
4661
5085
  function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
4662
- const resolved = path24.resolve(targetPath);
4663
- const nm = resolved.endsWith(`${path24.sep}node_modules`) ? resolved : null;
5086
+ const resolved = path25.resolve(targetPath);
5087
+ const nm = resolved.endsWith(`${path25.sep}node_modules`) ? resolved : null;
4664
5088
  if (!nm) return "path_outside_harness";
4665
- const rel = path24.relative(worktreesDir, nm);
4666
- if (rel.startsWith("..") || path24.isAbsolute(rel)) return "path_outside_harness";
4667
- const parts = rel.split(path24.sep);
5089
+ const rel = path25.relative(worktreesDir, nm);
5090
+ if (rel.startsWith("..") || path25.isAbsolute(rel)) return "path_outside_harness";
5091
+ const parts = rel.split(path25.sep);
4668
5092
  if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
4669
- if (!resolved.startsWith(path24.resolve(harnessRoot))) return "path_outside_harness";
5093
+ if (!resolved.startsWith(path25.resolve(harnessRoot))) return "path_outside_harness";
4670
5094
  return null;
4671
5095
  }
4672
5096
 
4673
5097
  // src/cleanup-scan.ts
4674
5098
  import { existsSync as existsSync17, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
4675
- import path25 from "node:path";
5099
+ import path26 from "node:path";
4676
5100
  function pathAgeMs(target, now) {
4677
5101
  try {
4678
5102
  const mtime = statSync3(target).mtimeMs;
@@ -4682,17 +5106,17 @@ function pathAgeMs(target, now) {
4682
5106
  }
4683
5107
  }
4684
5108
  function isPathInside(child, parent) {
4685
- const rel = path25.relative(parent, child);
4686
- return rel === "" || !rel.startsWith("..") && !path25.isAbsolute(rel);
5109
+ const rel = path26.relative(parent, child);
5110
+ return rel === "" || !rel.startsWith("..") && !path26.isAbsolute(rel);
4687
5111
  }
4688
5112
  function scanNodeModulesCandidates(opts) {
4689
5113
  const candidates = [];
4690
5114
  const seen = /* @__PURE__ */ new Set();
4691
5115
  for (const entry of opts.index.values()) {
4692
5116
  if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
4693
- const nm = path25.join(entry.worktreePath, "node_modules");
5117
+ const nm = path26.join(entry.worktreePath, "node_modules");
4694
5118
  if (!existsSync17(nm)) continue;
4695
- const resolved = path25.resolve(nm);
5119
+ const resolved = path26.resolve(nm);
4696
5120
  if (seen.has(resolved)) continue;
4697
5121
  seen.add(resolved);
4698
5122
  candidates.push({
@@ -4708,13 +5132,13 @@ function scanNodeModulesCandidates(opts) {
4708
5132
  if (!opts.includeOrphans || !existsSync17(opts.worktreesDir)) return candidates;
4709
5133
  for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
4710
5134
  if (!runEntry.isDirectory()) continue;
4711
- const runPath = path25.join(opts.worktreesDir, runEntry.name);
5135
+ const runPath = path26.join(opts.worktreesDir, runEntry.name);
4712
5136
  for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
4713
5137
  if (!workerEntry.isDirectory()) continue;
4714
- const worktreePath = path25.join(runPath, workerEntry.name);
4715
- const nm = path25.join(worktreePath, "node_modules");
5138
+ const worktreePath = path26.join(runPath, workerEntry.name);
5139
+ const nm = path26.join(worktreePath, "node_modules");
4716
5140
  if (!existsSync17(nm)) continue;
4717
- const resolved = path25.resolve(nm);
5141
+ const resolved = path26.resolve(nm);
4718
5142
  if (seen.has(resolved)) continue;
4719
5143
  if (!isPathInside(resolved, opts.harnessRoot)) continue;
4720
5144
  seen.add(resolved);
@@ -4754,17 +5178,17 @@ function scanWorktreeCandidates(opts) {
4754
5178
  }
4755
5179
 
4756
5180
  // src/cleanup-worktree-index.ts
4757
- import path26 from "node:path";
5181
+ import path27 from "node:path";
4758
5182
  function buildWorktreeIndex() {
4759
5183
  const index = /* @__PURE__ */ new Map();
4760
5184
  for (const run of listRunRecords()) {
4761
5185
  for (const name of Object.keys(run.workers || {})) {
4762
- const workerPath = path26.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
5186
+ const workerPath = path27.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
4763
5187
  const worker = readJson(workerPath, void 0);
4764
5188
  if (!worker?.worktreePath) continue;
4765
5189
  const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
4766
- index.set(path26.resolve(worker.worktreePath), {
4767
- worktreePath: path26.resolve(worker.worktreePath),
5190
+ index.set(path27.resolve(worker.worktreePath), {
5191
+ worktreePath: path27.resolve(worker.worktreePath),
4768
5192
  runId: run.id,
4769
5193
  workerName: name,
4770
5194
  run,
@@ -4778,8 +5202,8 @@ function buildWorktreeIndex() {
4778
5202
 
4779
5203
  // src/cleanup.ts
4780
5204
  function resolveOptions(options = {}) {
4781
- const harnessRoot = options.harnessRoot ? path27.resolve(options.harnessRoot) : resolveHarnessRoot();
4782
- const { worktreesDir } = options.harnessRoot ? { worktreesDir: path27.join(harnessRoot, "worktrees") } : getHarnessPaths();
5205
+ const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
5206
+ const { worktreesDir } = options.harnessRoot ? { worktreesDir: path28.join(harnessRoot, "worktrees") } : getHarnessPaths();
4783
5207
  const execute = options.execute === true;
4784
5208
  const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
4785
5209
  const worktreesAgeMs = options.worktreesAgeMs ?? 0;
@@ -4823,7 +5247,7 @@ function runHarnessCleanup(options = {}) {
4823
5247
  actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
4824
5248
  continue;
4825
5249
  }
4826
- const worktreePath = path27.resolve(candidate.path, "..");
5250
+ const worktreePath = path28.resolve(candidate.path, "..");
4827
5251
  const indexed = index.get(worktreePath) ?? null;
4828
5252
  const guardReason = skipNodeModulesRemoval({
4829
5253
  indexed,
@@ -4839,7 +5263,7 @@ function runHarnessCleanup(options = {}) {
4839
5263
  actions.push(removeNodeModules(candidate, resolved.execute));
4840
5264
  }
4841
5265
  for (const candidate of scanWorktreeCandidates(scanOpts)) {
4842
- const indexed = index.get(path27.resolve(candidate.path)) ?? null;
5266
+ const indexed = index.get(path28.resolve(candidate.path)) ?? null;
4843
5267
  const guardReason = skipWorktreeRemoval({
4844
5268
  indexed,
4845
5269
  includeOrphans: resolved.includeOrphans,
@@ -4902,13 +5326,62 @@ function isPipelineCleanupEnabled() {
4902
5326
  return process.env.KYNVER_PIPELINE_CLEANUP !== "0";
4903
5327
  }
4904
5328
 
5329
+ // src/installed-package-versions.ts
5330
+ import { readFile } from "node:fs/promises";
5331
+ import { homedir as homedir5 } from "node:os";
5332
+ import path29 from "node:path";
5333
+ var MANAGED_PACKAGES = [
5334
+ "@kynver-app/runtime",
5335
+ "@kynver-app/openclaw-agent-os",
5336
+ "@kynver-app/mcp-agent-os"
5337
+ ];
5338
+ function trim(value) {
5339
+ const out = value?.trim();
5340
+ return out ? out : null;
5341
+ }
5342
+ function unique(values) {
5343
+ return [...new Set(values.filter((value) => Boolean(value)))];
5344
+ }
5345
+ function moduleRoots() {
5346
+ const home = homedir5();
5347
+ const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path29.join(home, ".openclaw", "npm");
5348
+ 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"));
5349
+ return unique([
5350
+ path29.join(openClawPrefix, "lib", "node_modules"),
5351
+ path29.join(openClawPrefix, "node_modules"),
5352
+ npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path29.join(npmGlobalRoot, "lib", "node_modules")
5353
+ ]);
5354
+ }
5355
+ async function readVersion(packageJsonPath) {
5356
+ try {
5357
+ const parsed = JSON.parse(await readFile(packageJsonPath, "utf8"));
5358
+ return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : null;
5359
+ } catch {
5360
+ return null;
5361
+ }
5362
+ }
5363
+ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new Date()).toISOString()) {
5364
+ const roots = moduleRoots();
5365
+ const out = {};
5366
+ for (const packageName of MANAGED_PACKAGES) {
5367
+ for (const root of roots) {
5368
+ const packageJsonPath = path29.join(root, packageName, "package.json");
5369
+ const version = await readVersion(packageJsonPath);
5370
+ if (!version) continue;
5371
+ out[packageName] = { version, observedAt, path: packageJsonPath };
5372
+ break;
5373
+ }
5374
+ }
5375
+ return out;
5376
+ }
5377
+
4905
5378
  // src/pipeline-tick.ts
4906
5379
  async function completeFinishedWorkers(runId, args) {
4907
5380
  const run = loadRun(runId);
4908
5381
  const outcomes = [];
4909
5382
  for (const name of Object.keys(run.workers || {})) {
4910
5383
  const worker = readJson(
4911
- path28.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
5384
+ path30.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4912
5385
  void 0
4913
5386
  );
4914
5387
  if (!worker?.taskId || worker.localOnly) continue;
@@ -4939,12 +5412,14 @@ async function postOperatorTick(agentOsId, runId, resourceGate, args) {
4939
5412
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
4940
5413
  const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, { baseUrl: base });
4941
5414
  const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/operator/tick`;
5415
+ const packageVersions = await collectInstalledPackageVersions();
4942
5416
  const res = await postJson(url, secret, {
4943
5417
  agentOsId,
4944
5418
  runId,
4945
5419
  ingestHarness: true,
4946
5420
  harnessBoardSnapshot: buildRunBoard(runId),
4947
- resourceGate
5421
+ resourceGate,
5422
+ packageVersions
4948
5423
  });
4949
5424
  return { ok: res.ok, httpStatus: res.status, response: res.response };
4950
5425
  }
@@ -5047,6 +5522,231 @@ async function runDaemon(args) {
5047
5522
  console.error(JSON.stringify({ event: "daemon_stop", runId, agentOsId }));
5048
5523
  }
5049
5524
 
5525
+ // src/plan-progress.ts
5526
+ import path31 from "node:path";
5527
+
5528
+ // src/bounded-build/constants.ts
5529
+ var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
5530
+ var DEFAULT_BUILD_MEM_RESERVE_BYTES = 2 * 1024 * 1024 * 1024;
5531
+ var DEFAULT_NODE_OLD_SPACE_SIZE_MB = 1024;
5532
+ var DEFAULT_SYSTEMD_MEMORY_MAX = "1.5G";
5533
+ var DEFAULT_SYSTEMD_MEMORY_SWAP_MAX = "2G";
5534
+
5535
+ // src/bounded-build/node-options.ts
5536
+ var MAX_OLD_SPACE_RE = /--max-old-space-size=(\d+)/;
5537
+ function parsePositiveInt(value, fallback) {
5538
+ const n = Number(value);
5539
+ if (!Number.isFinite(n) || n <= 0) return fallback;
5540
+ return Math.floor(n);
5541
+ }
5542
+ function resolveNodeOldSpaceSizeMb() {
5543
+ return parsePositiveInt(process.env.KYNVER_NODE_OLD_SPACE_SIZE_MB, DEFAULT_NODE_OLD_SPACE_SIZE_MB);
5544
+ }
5545
+ function mergeNodeOptionsForBuildCheck(baseEnv = process.env) {
5546
+ const env = { ...baseEnv };
5547
+ if (process.env.KYNVER_NODE_OLD_SPACE_SIZE_MB === "0") {
5548
+ return env;
5549
+ }
5550
+ const existing = env.NODE_OPTIONS ?? "";
5551
+ if (MAX_OLD_SPACE_RE.test(existing)) {
5552
+ return env;
5553
+ }
5554
+ const mb = resolveNodeOldSpaceSizeMb();
5555
+ const flag = `--max-old-space-size=${mb}`;
5556
+ env.NODE_OPTIONS = existing.trim() ? `${existing.trim()} ${flag}` : flag;
5557
+ return env;
5558
+ }
5559
+ function formatNodeOptionsFlag(mb = resolveNodeOldSpaceSizeMb()) {
5560
+ return `--max-old-space-size=${mb}`;
5561
+ }
5562
+
5563
+ // src/bounded-build/systemd-wrap.ts
5564
+ import { spawnSync as spawnSync3 } from "node:child_process";
5565
+ var systemdAvailableCache;
5566
+ function isSystemdRunAvailable() {
5567
+ if (process.env.KYNVER_BUILD_SKIP_SYSTEMD === "1" || process.env.KYNVER_BUILD_SKIP_SYSTEMD === "true") {
5568
+ return false;
5569
+ }
5570
+ if (systemdAvailableCache !== void 0) return systemdAvailableCache;
5571
+ if (process.platform !== "linux") {
5572
+ systemdAvailableCache = false;
5573
+ return false;
5574
+ }
5575
+ const res = spawnSync3("systemd-run", ["--version"], { encoding: "utf8", stdio: ["ignore", "ignore", "pipe"] });
5576
+ systemdAvailableCache = res.status === 0;
5577
+ return systemdAvailableCache;
5578
+ }
5579
+ function buildSystemdRunArgv(opts) {
5580
+ const memoryMax = opts.memoryMax ?? process.env.KYNVER_BUILD_SYSTEMD_MEMORY_MAX ?? DEFAULT_SYSTEMD_MEMORY_MAX;
5581
+ const memorySwapMax = opts.memorySwapMax ?? process.env.KYNVER_BUILD_SYSTEMD_MEMORY_SWAP_MAX ?? DEFAULT_SYSTEMD_MEMORY_SWAP_MAX;
5582
+ const argv = [
5583
+ "systemd-run",
5584
+ "--scope",
5585
+ "--collect",
5586
+ "-p",
5587
+ `MemoryMax=${memoryMax}`,
5588
+ "-p",
5589
+ `MemorySwapMax=${memorySwapMax}`
5590
+ ];
5591
+ if (opts.cwd) {
5592
+ argv.push("--working-directory", opts.cwd);
5593
+ }
5594
+ argv.push("--", ...opts.command);
5595
+ return argv;
5596
+ }
5597
+
5598
+ // src/bounded-build/admission.ts
5599
+ import { spawnSync as spawnSync4 } from "node:child_process";
5600
+ function positiveInt3(value, fallback) {
5601
+ const n = Number(value);
5602
+ if (!Number.isFinite(n) || n <= 0) return fallback;
5603
+ return Math.floor(n);
5604
+ }
5605
+ function resolveBuildAdmissionConfig(config = loadUserConfig()) {
5606
+ const envBudget = process.env.KYNVER_BUILD_MEM_BUDGET_BYTES ? positiveInt3(process.env.KYNVER_BUILD_MEM_BUDGET_BYTES, DEFAULT_BUILD_MEM_BUDGET_BYTES) : void 0;
5607
+ const envReserve = process.env.KYNVER_BUILD_MEM_RESERVE_BYTES ? positiveInt3(process.env.KYNVER_BUILD_MEM_RESERVE_BYTES, DEFAULT_BUILD_MEM_RESERVE_BYTES) : void 0;
5608
+ return {
5609
+ perBuildBudgetBytes: envBudget ?? positiveInt3(config.perWorkerMemBytes, DEFAULT_BUILD_MEM_BUDGET_BYTES),
5610
+ reserveBytes: envReserve ?? positiveInt3(config.memReserveBytes, DEFAULT_BUILD_MEM_RESERVE_BYTES)
5611
+ };
5612
+ }
5613
+ var activeBuilds = 0;
5614
+ function registerBuildStart() {
5615
+ activeBuilds += 1;
5616
+ }
5617
+ function registerBuildEnd() {
5618
+ activeBuilds = Math.max(0, activeBuilds - 1);
5619
+ }
5620
+ function assessBuildAdmission(opts = {}) {
5621
+ const cfg = { ...resolveBuildAdmissionConfig(), ...opts };
5622
+ const memAvailableBytes = opts.memAvailableBytes ?? readMemAvailableBytes();
5623
+ const requiredBytes = cfg.perBuildBudgetBytes + cfg.reserveBytes;
5624
+ const admitted = memAvailableBytes >= requiredBytes;
5625
+ return {
5626
+ admitted,
5627
+ memAvailableBytes,
5628
+ requiredBytes,
5629
+ activeBuilds,
5630
+ reason: admitted ? null : `insufficient memory: need ${requiredBytes} bytes available (budget ${cfg.perBuildBudgetBytes} + reserve ${cfg.reserveBytes}), have ${memAvailableBytes}`
5631
+ };
5632
+ }
5633
+ function sleepMs2(ms) {
5634
+ if (ms <= 0) return;
5635
+ spawnSync4(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
5636
+ stdio: "ignore"
5637
+ });
5638
+ }
5639
+ function waitForBuildAdmission(timeoutMs, pollMs = 2e3, opts = {}) {
5640
+ const deadline = Date.now() + Math.max(0, timeoutMs);
5641
+ let verdict = assessBuildAdmission({
5642
+ ...opts,
5643
+ memAvailableBytes: opts.memAvailableBytes?.()
5644
+ });
5645
+ while (!verdict.admitted && Date.now() < deadline) {
5646
+ sleepMs2(Math.min(pollMs, deadline - Date.now()));
5647
+ verdict = assessBuildAdmission({
5648
+ ...opts,
5649
+ memAvailableBytes: opts.memAvailableBytes?.()
5650
+ });
5651
+ }
5652
+ return verdict;
5653
+ }
5654
+
5655
+ // src/bounded-build/exec.ts
5656
+ import { spawnSync as spawnSync5 } from "node:child_process";
5657
+ function envArgv(env) {
5658
+ const out = [];
5659
+ for (const [key, value] of Object.entries(env)) {
5660
+ if (value === void 0) continue;
5661
+ out.push(`${key}=${value}`);
5662
+ }
5663
+ return out;
5664
+ }
5665
+ function runSpawn(argv, opts) {
5666
+ const res = spawnSync5(argv[0], argv.slice(1), {
5667
+ cwd: opts.cwd,
5668
+ env: opts.env,
5669
+ encoding: "utf8",
5670
+ stdio: ["ignore", "pipe", "pipe"],
5671
+ shell: opts.shell,
5672
+ timeout: opts.timeoutMs
5673
+ });
5674
+ return {
5675
+ exitCode: res.status ?? 1,
5676
+ stdout: (res.stdout ?? "").trim(),
5677
+ stderr: (res.stderr ?? "").trim()
5678
+ };
5679
+ }
5680
+ function runBoundedBuildCheck(input) {
5681
+ const waitMs = input.waitForAdmissionMs ?? 6e5;
5682
+ const admission = waitMs > 0 ? waitForBuildAdmission(waitMs) : assessBuildAdmission();
5683
+ if (!admission.admitted) {
5684
+ return {
5685
+ ok: false,
5686
+ exitCode: 1,
5687
+ stdout: "",
5688
+ stderr: admission.reason ?? "build admission denied",
5689
+ admitted: false,
5690
+ wrappedWithSystemd: false,
5691
+ nodeOptionsFlag: formatNodeOptionsFlag(),
5692
+ admission,
5693
+ command: input.command
5694
+ };
5695
+ }
5696
+ const env = mergeNodeOptionsForBuildCheck({ ...process.env, ...input.env });
5697
+ const nodeOptionsFlag = formatNodeOptionsFlag();
5698
+ const useSystemd = isSystemdRunAvailable();
5699
+ registerBuildStart();
5700
+ try {
5701
+ let result;
5702
+ if (useSystemd) {
5703
+ const argv = buildSystemdRunArgv({
5704
+ cwd: input.cwd,
5705
+ command: ["/usr/bin/env", ...envArgv(env), "/bin/bash", "-lc", input.command]
5706
+ });
5707
+ result = runSpawn(argv, { cwd: input.cwd, env, timeoutMs: input.timeoutMs });
5708
+ } else {
5709
+ result = runSpawn([input.command], {
5710
+ cwd: input.cwd,
5711
+ env,
5712
+ shell: true,
5713
+ timeoutMs: input.timeoutMs
5714
+ });
5715
+ }
5716
+ return {
5717
+ ok: result.exitCode === 0,
5718
+ exitCode: result.exitCode,
5719
+ stdout: result.stdout,
5720
+ stderr: result.stderr,
5721
+ admitted: true,
5722
+ wrappedWithSystemd: useSystemd,
5723
+ nodeOptionsFlag,
5724
+ admission,
5725
+ command: input.command
5726
+ };
5727
+ } finally {
5728
+ registerBuildEnd();
5729
+ }
5730
+ }
5731
+
5732
+ // src/harness-verify.ts
5733
+ var DEFAULT_HARNESS_VERIFY_COMMANDS = ["npm run typecheck", "npm run test"];
5734
+ function runHarnessVerifyCommands(cwd, commands = DEFAULT_HARNESS_VERIFY_COMMANDS, opts = {}) {
5735
+ const steps = [];
5736
+ let passed = true;
5737
+ for (const command of commands) {
5738
+ const result = runBoundedBuildCheck({
5739
+ cwd,
5740
+ command,
5741
+ waitForAdmissionMs: opts.waitForAdmissionMs,
5742
+ timeoutMs: opts.timeoutMs
5743
+ });
5744
+ steps.push({ command, result });
5745
+ if (!result.ok) passed = false;
5746
+ }
5747
+ return { passed, steps };
5748
+ }
5749
+
5050
5750
  // src/plan-progress.ts
5051
5751
  function parseEvidenceArg(raw) {
5052
5752
  const idx = raw.indexOf(":");
@@ -5107,8 +5807,23 @@ async function emitPlanProgress(args) {
5107
5807
  }
5108
5808
  console.log(JSON.stringify(parsed, null, 2));
5109
5809
  }
5810
+ function verifyPlanLocal(args) {
5811
+ const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
5812
+ const cwd = path31.resolve(worktree);
5813
+ const summary = runHarnessVerifyCommands(cwd);
5814
+ const emitJson = args.json === true || args.json === "true";
5815
+ const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
5816
+ if (emitJson) console.log(JSON.stringify(payload, null, 2));
5817
+ else console.log(summary.passed ? "local plan verify passed" : "local plan verify failed");
5818
+ if (!summary.passed) process.exit(1);
5819
+ }
5110
5820
  async function verifyPlan(args) {
5111
5821
  const planId = required(args.plan ? String(args.plan) : void 0, "plan");
5822
+ const localOnly = args.local === true || args.local === "true";
5823
+ if (localOnly) {
5824
+ verifyPlanLocal(args);
5825
+ return;
5826
+ }
5112
5827
  const slug = loadUserConfig().agentOsSlug;
5113
5828
  if (!slug) {
5114
5829
  console.error("requires agentOsSlug in ~/.kynver/config.json for verify (session route)");
@@ -5142,6 +5857,52 @@ async function verifyPlan(args) {
5142
5857
  console.log(JSON.stringify(parsed, null, 2));
5143
5858
  }
5144
5859
 
5860
+ // src/harness-verify-cli.ts
5861
+ import path32 from "node:path";
5862
+ function runHarnessVerifyCli(args) {
5863
+ const cwd = path32.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
5864
+ const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
5865
+ const commands = [];
5866
+ const rawCmd = args.command;
5867
+ if (Array.isArray(rawCmd)) {
5868
+ for (const c of rawCmd) commands.push(String(c));
5869
+ } else if (typeof rawCmd === "string") {
5870
+ commands.push(rawCmd);
5871
+ }
5872
+ const summary = runHarnessVerifyCommands(
5873
+ cwd,
5874
+ commands.length ? commands : DEFAULT_HARNESS_VERIFY_COMMANDS,
5875
+ {
5876
+ waitForAdmissionMs: args.waitForAdmissionMs ? Number(args.waitForAdmissionMs) : void 0,
5877
+ timeoutMs: args.timeoutMs ? Number(args.timeoutMs) : void 0
5878
+ }
5879
+ );
5880
+ const payload = {
5881
+ passed: summary.passed,
5882
+ worktree: cwd,
5883
+ steps: summary.steps.map((s) => ({
5884
+ command: s.command,
5885
+ ok: s.result.ok,
5886
+ exitCode: s.result.exitCode,
5887
+ admitted: s.result.admitted,
5888
+ wrappedWithSystemd: s.result.wrappedWithSystemd,
5889
+ nodeOptionsFlag: s.result.nodeOptionsFlag,
5890
+ admission: s.result.admission,
5891
+ stderr: s.result.stderr.slice(0, 4e3)
5892
+ }))
5893
+ };
5894
+ if (emitJson) {
5895
+ console.log(JSON.stringify(payload, null, 2));
5896
+ } else {
5897
+ console.log(summary.passed ? "harness verify passed" : "harness verify failed");
5898
+ for (const step of payload.steps) {
5899
+ console.log(` ${step.ok ? "\u2713" : "\u2717"} ${step.command} (exit ${step.exitCode}, systemd=${step.wrappedWithSystemd})`);
5900
+ if (!step.ok && step.stderr) console.log(` ${step.stderr.split("\n")[0]}`);
5901
+ }
5902
+ }
5903
+ process.exit(summary.passed ? 0 : 1);
5904
+ }
5905
+
5145
5906
  // src/plan-persist-cli.ts
5146
5907
  import { readFileSync as readFileSync8 } from "node:fs";
5147
5908
  var OPERATIONS = ["create", "add_version", "update_metadata"];
@@ -5243,7 +6004,7 @@ function runCleanupCli(args) {
5243
6004
  }
5244
6005
 
5245
6006
  // src/monitor/monitor.service.ts
5246
- import path30 from "node:path";
6007
+ import path34 from "node:path";
5247
6008
 
5248
6009
  // src/monitor/monitor.classify.ts
5249
6010
  function expectedLeaseOwner(runId) {
@@ -5300,10 +6061,10 @@ function classifyWorkerHealth(input) {
5300
6061
 
5301
6062
  // src/monitor/monitor.store.ts
5302
6063
  import { existsSync as existsSync18, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
5303
- import path29 from "node:path";
6064
+ import path33 from "node:path";
5304
6065
  function monitorsDir() {
5305
6066
  const { harnessRoot } = getHarnessPaths();
5306
- const dir = path29.join(harnessRoot, "monitors");
6067
+ const dir = path33.join(harnessRoot, "monitors");
5307
6068
  mkdirSync6(dir, { recursive: true });
5308
6069
  return dir;
5309
6070
  }
@@ -5311,7 +6072,7 @@ function monitorIdFor(runId, workerName) {
5311
6072
  return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
5312
6073
  }
5313
6074
  function monitorPath(monitorId) {
5314
- return path29.join(monitorsDir(), `${monitorId}.json`);
6075
+ return path33.join(monitorsDir(), `${monitorId}.json`);
5315
6076
  }
5316
6077
  function loadMonitorSession(monitorId) {
5317
6078
  return readJson(monitorPath(monitorId), void 0);
@@ -5332,7 +6093,7 @@ function listMonitorSessions() {
5332
6093
  for (const name of readdirSync7(dir)) {
5333
6094
  if (!name.endsWith(".json")) continue;
5334
6095
  const session = readJson(
5335
- path29.join(dir, name),
6096
+ path33.join(dir, name),
5336
6097
  void 0
5337
6098
  );
5338
6099
  if (!session?.monitorId) continue;
@@ -5423,7 +6184,7 @@ async function fetchTaskLeasesForWorkers(input) {
5423
6184
  // src/monitor/monitor.service.ts
5424
6185
  function workerRecord2(runId, name) {
5425
6186
  return readJson(
5426
- path30.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
6187
+ path34.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
5427
6188
  void 0
5428
6189
  );
5429
6190
  }
@@ -5626,17 +6387,17 @@ async function runMonitorLoop(args) {
5626
6387
  // src/monitor/monitor-spawn.ts
5627
6388
  import { spawn as spawn4 } from "node:child_process";
5628
6389
  import { closeSync as closeSync4, existsSync as existsSync19, openSync as openSync4 } from "node:fs";
5629
- import path31 from "node:path";
6390
+ import path35 from "node:path";
5630
6391
  import { fileURLToPath as fileURLToPath3 } from "node:url";
5631
6392
  function resolveDefaultCliPath2() {
5632
- return path31.join(fileURLToPath3(new URL(".", import.meta.url)), "..", "cli.js");
6393
+ return path35.join(fileURLToPath3(new URL(".", import.meta.url)), "..", "cli.js");
5633
6394
  }
5634
6395
  function spawnMonitorSidecar(opts) {
5635
6396
  const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
5636
6397
  if (!existsSync19(cliPath)) return void 0;
5637
6398
  const monitorId = monitorIdFor(opts.runId, opts.workerName);
5638
6399
  const { harnessRoot } = getHarnessPaths();
5639
- const logPath = path31.join(harnessRoot, "monitors", `${monitorId}.log`);
6400
+ const logPath = path35.join(harnessRoot, "monitors", `${monitorId}.log`);
5640
6401
  let logFd;
5641
6402
  try {
5642
6403
  logFd = openSync4(logPath, "a");
@@ -5755,6 +6516,453 @@ async function monitorTickCli(args) {
5755
6516
  console.log(JSON.stringify(tick, null, 2));
5756
6517
  }
5757
6518
 
6519
+ // src/doctor/runtime-takeover.ts
6520
+ import path37 from "node:path";
6521
+
6522
+ // src/doctor/runtime-takeover.probes.ts
6523
+ import { accessSync, constants, existsSync as existsSync20, readFileSync as readFileSync9 } from "node:fs";
6524
+ import { homedir as homedir6 } from "node:os";
6525
+ import path36 from "node:path";
6526
+ import { spawnSync as spawnSync6 } from "node:child_process";
6527
+ function captureCommand(bin, args) {
6528
+ try {
6529
+ const res = spawnSync6(bin, args, { encoding: "utf8" });
6530
+ const stdout = (res.stdout || "").trim();
6531
+ const stderr = (res.stderr || "").trim();
6532
+ const ok = res.status === 0;
6533
+ return {
6534
+ ok,
6535
+ stdout,
6536
+ stderr,
6537
+ error: res.error?.message
6538
+ };
6539
+ } catch (error) {
6540
+ return {
6541
+ ok: false,
6542
+ stdout: "",
6543
+ stderr: "",
6544
+ error: error.message
6545
+ };
6546
+ }
6547
+ }
6548
+ function tokenPrefix(token) {
6549
+ const trimmed = token?.trim();
6550
+ if (!trimmed) return void 0;
6551
+ return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
6552
+ }
6553
+ function isWritable(target) {
6554
+ if (!existsSync20(target)) return false;
6555
+ try {
6556
+ accessSync(target, constants.W_OK);
6557
+ return true;
6558
+ } catch {
6559
+ return false;
6560
+ }
6561
+ }
6562
+ var defaultRuntimeTakeoverProbes = {
6563
+ packageVersion: () => PACKAGE_VERSION,
6564
+ commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
6565
+ kynverVersion: (bin) => captureCommand(bin, ["--version"]),
6566
+ loadConfig: () => loadUserConfig(),
6567
+ configFilePath: () => path36.join(homedir6(), ".kynver", "config.json"),
6568
+ credentialsFilePath: () => path36.join(homedir6(), ".kynver", "credentials"),
6569
+ readCredentials: () => {
6570
+ const credPath = path36.join(homedir6(), ".kynver", "credentials");
6571
+ if (!existsSync20(credPath)) {
6572
+ return { hasApiKey: false };
6573
+ }
6574
+ try {
6575
+ const parsed = JSON.parse(readFileSync9(credPath, "utf8"));
6576
+ return {
6577
+ hasApiKey: Boolean(parsed.apiKey?.trim()),
6578
+ runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
6579
+ runnerTokenAgentOsId: parsed.runnerTokenAgentOsId
6580
+ };
6581
+ } catch {
6582
+ return { hasApiKey: false };
6583
+ }
6584
+ },
6585
+ envSnapshot: () => ({
6586
+ kynverApiUrl: process.env.KYNVER_API_URL?.trim() || void 0,
6587
+ openclawCronFireBaseUrl: process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || void 0,
6588
+ kynverRunnerTokenPrefix: tokenPrefix(process.env.KYNVER_RUNNER_TOKEN),
6589
+ kynverRuntimeSecret: Boolean(process.env.KYNVER_RUNTIME_SECRET?.trim()),
6590
+ openclawCronSecret: Boolean(process.env.OPENCLAW_CRON_SECRET?.trim()),
6591
+ kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
6592
+ opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
6593
+ kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0
6594
+ }),
6595
+ harnessRoot: () => resolveHarnessRoot(),
6596
+ legacyOpenclawHarnessRoot: () => path36.join(homedir6(), ".openclaw", "harness"),
6597
+ pathExists: (target) => existsSync20(target),
6598
+ pathWritable: (target) => isWritable(target),
6599
+ vercelVersion: () => captureCommand("vercel", ["--version"]),
6600
+ vercelWhoami: () => captureCommand("vercel", ["whoami"])
6601
+ };
6602
+
6603
+ // src/doctor/runtime-takeover.ts
6604
+ function check(partial) {
6605
+ return partial;
6606
+ }
6607
+ function summarizeCounts(sections) {
6608
+ const counts = { pass: 0, warn: 0, fail: 0 };
6609
+ for (const section of sections) {
6610
+ for (const item of section.checks) {
6611
+ counts[item.status] += 1;
6612
+ }
6613
+ }
6614
+ return counts;
6615
+ }
6616
+ function assessCliPackage(probes) {
6617
+ const runningVersion = probes.packageVersion();
6618
+ const which = probes.commandOnPath("kynver");
6619
+ const onPath = which.ok && which.stdout.length > 0;
6620
+ const firstPath = onPath ? which.stdout.split(/\r?\n/)[0]?.trim() : void 0;
6621
+ const displayCliPath = firstPath ? displayUserPath(firstPath) : void 0;
6622
+ const checks = [
6623
+ check({
6624
+ id: "cli_running_version",
6625
+ label: "Running @kynver-app/runtime version",
6626
+ status: "pass",
6627
+ summary: `@kynver-app/runtime ${runningVersion}`,
6628
+ details: { version: runningVersion }
6629
+ }),
6630
+ check({
6631
+ id: "cli_on_path",
6632
+ label: "kynver executable on PATH",
6633
+ status: onPath ? "pass" : "warn",
6634
+ summary: onPath ? `Found ${displayCliPath}` : "kynver not found on PATH (invoked via node/npx?)",
6635
+ remediation: onPath ? void 0 : "Install globally (`npm i -g @kynver-app/runtime`) or invoke via `npx @kynver-app/runtime`.",
6636
+ details: { path: displayCliPath }
6637
+ })
6638
+ ];
6639
+ if (onPath && firstPath) {
6640
+ const versionProbe = probes.kynverVersion(firstPath);
6641
+ const installedVersion = versionProbe.stdout.replace(/^kynver\s+/i, "").trim() || void 0;
6642
+ const versionMatch = versionProbe.ok && (!installedVersion || installedVersion === runningVersion);
6643
+ checks.push(
6644
+ check({
6645
+ id: "cli_installed_version",
6646
+ label: "Installed kynver CLI version matches running package",
6647
+ status: versionMatch ? "pass" : "warn",
6648
+ 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",
6649
+ remediation: versionMatch ? void 0 : "Reinstall or rebuild @kynver-app/runtime so PATH kynver matches the harness worktree version.",
6650
+ details: { installedVersion, runningVersion, path: displayCliPath }
6651
+ })
6652
+ );
6653
+ }
6654
+ return { id: "cli_package", label: "CLI / package", checks };
6655
+ }
6656
+ function assessUserConfig(probes) {
6657
+ const configPath = probes.configFilePath();
6658
+ const displayConfigPath = displayUserPath(configPath);
6659
+ const exists = probes.pathExists(configPath);
6660
+ const config = probes.loadConfig();
6661
+ const checks = [
6662
+ check({
6663
+ id: "config_file",
6664
+ label: "~/.kynver/config.json present",
6665
+ status: exists ? "pass" : "fail",
6666
+ summary: exists ? `Found ${displayConfigPath}` : `Missing ${displayConfigPath}`,
6667
+ remediation: exists ? void 0 : "Run `kynver setup --api-base-url <url> --agent-os-id <id> --repo <path>`.",
6668
+ details: { configPath: displayConfigPath }
6669
+ })
6670
+ ];
6671
+ if (exists) {
6672
+ const apiBaseUrl = config.apiBaseUrl?.trim();
6673
+ const agentOsId = config.agentOsId?.trim();
6674
+ const defaultRepo = config.defaultRepo?.trim();
6675
+ const displayDefaultRepo = defaultRepo ? displayUserPath(defaultRepo) : null;
6676
+ checks.push(
6677
+ check({
6678
+ id: "config_api_base_url",
6679
+ label: "Default API base URL",
6680
+ status: apiBaseUrl ? "pass" : "warn",
6681
+ summary: apiBaseUrl ?? "Not set in config (KYNVER_API_URL / --base-url required per command)",
6682
+ remediation: apiBaseUrl ? void 0 : "Set `apiBaseUrl` via `kynver setup --api-base-url https://\u2026`.",
6683
+ details: { apiBaseUrl: apiBaseUrl ?? null }
6684
+ }),
6685
+ check({
6686
+ id: "config_agent_os_id",
6687
+ label: "Default AgentOS id",
6688
+ status: agentOsId ? "pass" : "warn",
6689
+ summary: agentOsId ?? "Not set (pass --agent-os-id on daemon/dispatch/worker commands)",
6690
+ remediation: agentOsId ? void 0 : "Set `agentOsId` via `kynver setup --agent-os-id <uuid>`.",
6691
+ details: { agentOsId: agentOsId ?? null, agentOsSlug: config.agentOsSlug ?? null }
6692
+ }),
6693
+ check({
6694
+ id: "config_default_repo",
6695
+ label: "Default repo path",
6696
+ status: defaultRepo ? "pass" : "warn",
6697
+ summary: displayDefaultRepo ?? "Not set (pass --repo on `kynver run create`)",
6698
+ remediation: defaultRepo ? void 0 : "Set `defaultRepo` via `kynver setup --repo /path/to/repo`.",
6699
+ details: {
6700
+ defaultRepo: displayDefaultRepo,
6701
+ harnessRoot: config.harnessRoot ? redactHomePath(config.harnessRoot) : null
6702
+ }
6703
+ })
6704
+ );
6705
+ }
6706
+ return { id: "user_config", label: "User config (~/.kynver)", checks };
6707
+ }
6708
+ function assessRunnerToken(probes) {
6709
+ const config = probes.loadConfig();
6710
+ const targetAgentOsId = config.agentOsId?.trim();
6711
+ const creds = probes.readCredentials();
6712
+ const env = probes.envSnapshot();
6713
+ const credPath = probes.credentialsFilePath();
6714
+ const displayCredPath = displayUserPath(credPath);
6715
+ const envToken = env.kynverRunnerTokenPrefix;
6716
+ const savedToken = creds.runnerTokenPrefix;
6717
+ const savedAgentOsId = creds.runnerTokenAgentOsId;
6718
+ const scopedSaved = Boolean(savedToken) && (!targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId);
6719
+ const hasScoped = Boolean(envToken?.startsWith("krc1.")) || scopedSaved && savedToken?.startsWith("krc1.");
6720
+ const checks = [
6721
+ check({
6722
+ id: "runner_token_scoped",
6723
+ label: "Scoped runner token (krc1.*) ready",
6724
+ status: hasScoped ? "pass" : "fail",
6725
+ 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",
6726
+ remediation: hasScoped ? void 0 : "Run `kynver login` then `kynver runner credential --agent-os-id <id>` (or `kynver setup` with API key).",
6727
+ details: {
6728
+ source: envToken ? "env" : savedToken ? "credentials" : "none",
6729
+ tokenPrefix: envToken ?? (scopedSaved ? savedToken : void 0),
6730
+ agentOsId: targetAgentOsId ?? savedAgentOsId ?? null,
6731
+ credentialsPath: displayCredPath
6732
+ }
6733
+ }),
6734
+ check({
6735
+ id: "runner_token_agent_os_match",
6736
+ label: "Saved runner token matches configured agentOsId",
6737
+ status: !savedToken || !targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId ? "pass" : "warn",
6738
+ 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}`,
6739
+ remediation: savedToken && targetAgentOsId && savedAgentOsId && savedAgentOsId !== targetAgentOsId ? "`kynver runner credential --agent-os-id <configured-id>` to mint a workspace-bound token." : void 0,
6740
+ details: { configuredAgentOsId: targetAgentOsId ?? null, savedAgentOsId: savedAgentOsId ?? null }
6741
+ }),
6742
+ check({
6743
+ id: "runner_api_key_for_refresh",
6744
+ label: "API key available for token refresh",
6745
+ status: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY?.trim()) ? "pass" : "warn",
6746
+ 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",
6747
+ remediation: creds.hasApiKey || process.env.KYNVER_API_KEY ? void 0 : "Run `kynver login --api-key \u2026`.",
6748
+ details: { credentialsPath: displayCredPath, hasApiKey: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY) }
6749
+ })
6750
+ ];
6751
+ return { id: "runner_token", label: "Runner token readiness", checks };
6752
+ }
6753
+ function assessVercelCli(probes) {
6754
+ const version = probes.vercelVersion();
6755
+ const whoami = probes.vercelWhoami();
6756
+ const installed = version.ok;
6757
+ return {
6758
+ id: "vercel_cli",
6759
+ label: "Vercel CLI",
6760
+ checks: [
6761
+ check({
6762
+ id: "vercel_installed",
6763
+ label: "Vercel CLI installed",
6764
+ status: installed ? "pass" : "warn",
6765
+ summary: installed ? version.stdout || "vercel CLI found" : version.error ?? "vercel not found on PATH",
6766
+ remediation: installed ? void 0 : "Install Vercel CLI (`npm i -g vercel`) for deploy evidence and env pulls.",
6767
+ details: { stderr: version.stderr || null }
6768
+ }),
6769
+ check({
6770
+ id: "vercel_authenticated",
6771
+ label: "Vercel CLI authenticated",
6772
+ status: !installed ? "warn" : whoami.ok ? "pass" : "warn",
6773
+ summary: !installed ? "Skipped \u2014 Vercel CLI not installed" : whoami.ok ? `Authenticated as ${whoami.stdout}` : whoami.stderr || whoami.error || "Not logged in",
6774
+ remediation: installed && !whoami.ok ? "Run `vercel login` and link the Kynver project if needed." : void 0,
6775
+ details: { account: whoami.ok ? whoami.stdout : null }
6776
+ })
6777
+ ]
6778
+ };
6779
+ }
6780
+ function assessHarnessDirs(probes) {
6781
+ const harnessRoot = probes.harnessRoot();
6782
+ const runsDir = path37.join(harnessRoot, "runs");
6783
+ const worktreesDir = path37.join(harnessRoot, "worktrees");
6784
+ const displayHarnessRoot = redactHomePath(harnessRoot);
6785
+ const displayRunsDir = redactHomePath(runsDir);
6786
+ const displayWorktreesDir = redactHomePath(worktreesDir);
6787
+ const runsExists = probes.pathExists(runsDir);
6788
+ const worktreesExists = probes.pathExists(worktreesDir);
6789
+ return {
6790
+ id: "harness_dirs",
6791
+ label: "Harness / daemon directories",
6792
+ checks: [
6793
+ check({
6794
+ id: "harness_root",
6795
+ label: "Harness root resolved",
6796
+ status: "pass",
6797
+ summary: displayHarnessRoot,
6798
+ details: { harnessRoot: displayHarnessRoot }
6799
+ }),
6800
+ check({
6801
+ id: "harness_runs_dir",
6802
+ label: "Runs directory ready",
6803
+ status: runsExists && probes.pathWritable(runsDir) ? "pass" : runsExists ? "warn" : "warn",
6804
+ summary: runsExists ? probes.pathWritable(runsDir) ? `Writable ${displayRunsDir}` : `Exists but not writable: ${displayRunsDir}` : `Will be created on first run: ${displayRunsDir}`,
6805
+ remediation: runsExists && !probes.pathWritable(runsDir) ? `Fix permissions on ${displayRunsDir} or set KYNVER_HARNESS_ROOT to a writable path.` : void 0,
6806
+ details: { runsDir: displayRunsDir, exists: runsExists, writable: probes.pathWritable(runsDir) }
6807
+ }),
6808
+ check({
6809
+ id: "harness_worktrees_dir",
6810
+ label: "Worktrees directory ready",
6811
+ status: worktreesExists && probes.pathWritable(worktreesDir) ? "pass" : "warn",
6812
+ summary: worktreesExists ? probes.pathWritable(worktreesDir) ? `Writable ${displayWorktreesDir}` : `Exists but not writable: ${displayWorktreesDir}` : `Will be created on first worker: ${displayWorktreesDir}`,
6813
+ remediation: worktreesExists && !probes.pathWritable(worktreesDir) ? `Fix permissions on ${displayWorktreesDir}.` : void 0,
6814
+ details: { worktreesDir: displayWorktreesDir, exists: worktreesExists, writable: probes.pathWritable(worktreesDir) }
6815
+ })
6816
+ ]
6817
+ };
6818
+ }
6819
+ function assessCallbackAuth(probes) {
6820
+ const config = probes.loadConfig();
6821
+ const env = probes.envSnapshot();
6822
+ const baseUrl = env.kynverApiUrl ?? config.apiBaseUrl?.trim() ?? env.openclawCronFireBaseUrl;
6823
+ const usingLegacyBase = !env.kynverApiUrl && !config.apiBaseUrl && Boolean(env.openclawCronFireBaseUrl);
6824
+ const legacySecret = env.openclawCronSecret || env.kynverRuntimeSecret;
6825
+ const envScoped = env.kynverRunnerTokenPrefix?.startsWith("krc1.");
6826
+ const creds = probes.readCredentials();
6827
+ const savedScoped = creds.runnerTokenPrefix?.startsWith("krc1.");
6828
+ const checks = [
6829
+ check({
6830
+ id: "callback_base_url",
6831
+ label: "Callback base URL configured",
6832
+ status: baseUrl ? usingLegacyBase ? "warn" : "pass" : "fail",
6833
+ summary: baseUrl ? usingLegacyBase ? `Using legacy OPENCLAW_CRON_FIRE_BASE_URL (${baseUrl})` : baseUrl : "No KYNVER_API_URL, config apiBaseUrl, or legacy OpenClaw base URL",
6834
+ 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`.",
6835
+ details: {
6836
+ source: env.kynverApiUrl ? "KYNVER_API_URL" : config.apiBaseUrl ? "config.apiBaseUrl" : env.openclawCronFireBaseUrl ? "OPENCLAW_CRON_FIRE_BASE_URL" : "none",
6837
+ baseUrl: baseUrl ?? null
6838
+ }
6839
+ }),
6840
+ check({
6841
+ id: "callback_auth_mode",
6842
+ label: "Callback auth uses scoped runner token",
6843
+ status: envScoped || savedScoped ? "pass" : legacySecret ? "warn" : "fail",
6844
+ 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",
6845
+ remediation: envScoped || savedScoped ? void 0 : "Mint a scoped token: `kynver runner credential --agent-os-id <id>`. Avoid shared OPENCLAW_CRON_SECRET on user runners.",
6846
+ details: {
6847
+ mode: envScoped || savedScoped ? "scoped" : legacySecret ? "legacy_global_secret" : "missing",
6848
+ legacyHeadersWhenNotScoped: ["X-OpenClaw-Cron-Secret", "X-Kynver-Runtime-Secret"]
6849
+ }
6850
+ })
6851
+ ];
6852
+ return { id: "callback_auth", label: "Callback auth / config", checks };
6853
+ }
6854
+ function assessOpenclawHotspots(probes) {
6855
+ const env = probes.envSnapshot();
6856
+ const harnessRoot = probes.harnessRoot();
6857
+ const legacyRoot = probes.legacyOpenclawHarnessRoot();
6858
+ const displayHarnessRoot = redactHomePath(harnessRoot);
6859
+ const displayLegacyRoot = redactHomePath(legacyRoot);
6860
+ const displayOpusHarnessRoot = env.opusHarnessRoot ? redactHomePath(env.opusHarnessRoot) : null;
6861
+ const legacyHarnessActive = harnessRoot === legacyRoot && probes.pathExists(legacyRoot);
6862
+ const schedulerOpenclaw = !env.kynverSchedulerProvider || env.kynverSchedulerProvider === "openclaw-cron";
6863
+ const checks = [
6864
+ check({
6865
+ id: "hotspot_legacy_harness_root",
6866
+ label: "Legacy ~/.openclaw/harness still active",
6867
+ status: legacyHarnessActive ? "warn" : "pass",
6868
+ summary: legacyHarnessActive ? `Harness root is legacy ${displayLegacyRoot}` : env.opusHarnessRoot ? `OPUS_HARNESS_ROOT override in use (${displayOpusHarnessRoot})` : `Using ${displayHarnessRoot}`,
6869
+ 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,
6870
+ details: { harnessRoot: displayHarnessRoot, legacyRoot: displayLegacyRoot, opusHarnessRoot: displayOpusHarnessRoot }
6871
+ }),
6872
+ check({
6873
+ id: "hotspot_openclaw_env_secrets",
6874
+ label: "OpenClaw deployment secrets in runner env",
6875
+ status: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "warn" : "pass",
6876
+ summary: env.openclawCronSecret || env.openclawCronFireBaseUrl ? [
6877
+ env.openclawCronSecret ? "OPENCLAW_CRON_SECRET set" : null,
6878
+ env.openclawCronFireBaseUrl ? "OPENCLAW_CRON_FIRE_BASE_URL set" : null
6879
+ ].filter(Boolean).join("; ") : "No OpenClaw cron env overrides on this runner",
6880
+ remediation: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "Move to KYNVER_API_URL + scoped runner tokens; unset OpenClaw cron env on user-hosted runners." : void 0
6881
+ }),
6882
+ check({
6883
+ id: "hotspot_openclaw_scheduler",
6884
+ label: "openclaw-cron scheduler dependency (deployment)",
6885
+ status: schedulerOpenclaw ? "warn" : "pass",
6886
+ 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}`,
6887
+ 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,
6888
+ details: { schedulerProvider: env.kynverSchedulerProvider ?? null }
6889
+ }),
6890
+ check({
6891
+ id: "hotspot_lease_source_names",
6892
+ label: "Harness lease/completion source names",
6893
+ status: "pass",
6894
+ summary: "Runtime uses kynver-harness:* lease owners and completion source kynver-harness (OpenClaw names retired in runtime)",
6895
+ details: {
6896
+ leaseOwnerPattern: "kynver-harness:<runId>",
6897
+ completionSource: "kynver-harness",
6898
+ note: "OpenClaw adapter remains optional in packages/kynver-openclaw-agent-os only"
6899
+ }
6900
+ })
6901
+ ];
6902
+ return { id: "openclaw_hotspots", label: "OpenClaw dependency hotspots", checks };
6903
+ }
6904
+ function assessRuntimeTakeoverReadiness(probes = defaultRuntimeTakeoverProbes) {
6905
+ const sections = [
6906
+ assessCliPackage(probes),
6907
+ assessUserConfig(probes),
6908
+ assessRunnerToken(probes),
6909
+ assessVercelCli(probes),
6910
+ assessHarnessDirs(probes),
6911
+ assessCallbackAuth(probes),
6912
+ assessOpenclawHotspots(probes)
6913
+ ];
6914
+ const counts = summarizeCounts(sections);
6915
+ const ready = counts.fail === 0;
6916
+ 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.`;
6917
+ return {
6918
+ command: "doctor runtime-takeover",
6919
+ ready,
6920
+ summary,
6921
+ counts,
6922
+ sections
6923
+ };
6924
+ }
6925
+
6926
+ // src/doctor/runtime-takeover-cli.ts
6927
+ function runRuntimeTakeoverDoctorCli() {
6928
+ const report = assessRuntimeTakeoverReadiness();
6929
+ console.log(JSON.stringify(report, null, 2));
6930
+ if (!report.ready) {
6931
+ process.exitCode = 1;
6932
+ }
6933
+ }
6934
+
6935
+ // src/command-center-contract-cli.ts
6936
+ async function runCommandCenterContractCli(args) {
6937
+ const config = loadUserConfig();
6938
+ const agentOsId = (args.agentOsId ? String(args.agentOsId) : config.agentOsId) || "";
6939
+ if (!agentOsId) {
6940
+ console.error("requires --agent-os-id or agentOsId in ~/.kynver/config.json");
6941
+ process.exit(1);
6942
+ }
6943
+ const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : config.apiBaseUrl);
6944
+ const secret = await resolveCallbackSecretWithMint(
6945
+ args.secret ? String(args.secret) : void 0,
6946
+ agentOsId,
6947
+ { baseUrl: base }
6948
+ );
6949
+ const qs = new URLSearchParams();
6950
+ if (typeof args.since === "string" && args.since.trim()) qs.set("since", args.since.trim());
6951
+ if (args.limit != null && String(args.limit).trim()) {
6952
+ const n = Number(args.limit);
6953
+ if (Number.isFinite(n) && n > 0) qs.set("limit", String(Math.floor(n)));
6954
+ }
6955
+ const suffix = qs.toString() ? `?${qs.toString()}` : "";
6956
+ const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/command-center/dashboard-contract${suffix}`;
6957
+ const res = await getJson(url, secret);
6958
+ if (!res.ok) {
6959
+ console.error(`dashboard-contract GET failed: HTTP ${res.status}`);
6960
+ if (res.response) console.error(JSON.stringify(res.response, null, 2));
6961
+ process.exit(1);
6962
+ }
6963
+ console.log(JSON.stringify(res.response, null, 2));
6964
+ }
6965
+
5758
6966
  // src/cli.ts
5759
6967
  function isHelpFlag(arg) {
5760
6968
  return arg === "help" || arg === "--help" || arg === "-h";
@@ -5776,7 +6984,7 @@ function usage(code = 0) {
5776
6984
  " kynver run create --repo /path/repo [--name name] [--base origin/main]",
5777
6985
  " kynver run list",
5778
6986
  " kynver run status --run RUN_ID",
5779
- " 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 /]",
6987
+ " 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 /]",
5780
6988
  " kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
5781
6989
  ' 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]',
5782
6990
  " kynver worker status --run RUN_ID --name worker",
@@ -5786,7 +6994,8 @@ function usage(code = 0) {
5786
6994
  " 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]",
5787
6995
  " kynver run reconcile",
5788
6996
  " 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]",
5789
- " kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override]",
6997
+ " kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override] [--local]",
6998
+ " kynver harness verify --worktree PATH [--command CMD] [--json] [--wait-for-admission-ms MS] [--timeout-ms MS]",
5790
6999
  " 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]",
5791
7000
  " kynver plan outbox list",
5792
7001
  " kynver plan outbox drain [--max N] [--id OUTBOX_ID]",
@@ -5797,7 +7006,9 @@ function usage(code = 0) {
5797
7006
  " kynver monitor list",
5798
7007
  " kynver monitor tick --run RUN_ID [--name worker] [--agent-os-id AOS_ID] [--auto-complete] [--renew-leases]",
5799
7008
  " kynver monitor auto-complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--base-url URL] [--secret SECRET]",
5800
- " kynver monitor run-loop --run RUN_ID --monitor-id ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS] [--auto-complete] [--renew-leases]"
7009
+ " kynver monitor run-loop --run RUN_ID --monitor-id ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS] [--auto-complete] [--renew-leases]",
7010
+ " kynver doctor runtime-takeover",
7011
+ " kynver board contract [--agent-os-id ID] [--base-url URL] [--since ISO] [--limit N]"
5801
7012
  ].join("\n")
5802
7013
  );
5803
7014
  process.exit(code);
@@ -5808,7 +7019,7 @@ async function main(argv = process.argv.slice(2)) {
5808
7019
  const scope = argv.shift();
5809
7020
  let action;
5810
7021
  let rest;
5811
- if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "monitor") {
7022
+ if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "harness" || scope === "monitor" || scope === "doctor" || scope === "board") {
5812
7023
  action = argv.shift();
5813
7024
  rest = argv;
5814
7025
  } else {
@@ -5825,6 +7036,7 @@ async function main(argv = process.argv.slice(2)) {
5825
7036
  if (scope === "daemon") return void await runDaemon(args);
5826
7037
  if (scope === "plan" && action === "progress") return void await emitPlanProgress(args);
5827
7038
  if (scope === "plan" && action === "verify") return void await verifyPlan(args);
7039
+ if (scope === "harness" && action === "verify") return runHarnessVerifyCli(args);
5828
7040
  if (scope === "plan" && action === "persist") return void await runPlanPersist(args);
5829
7041
  if (scope === "plan" && action === "outbox") {
5830
7042
  const outboxAction = rest.shift();
@@ -5833,6 +7045,10 @@ async function main(argv = process.argv.slice(2)) {
5833
7045
  unknownCommand("plan", `outbox ${outboxAction ?? ""}`.trim());
5834
7046
  }
5835
7047
  if (scope === "cleanup") return runCleanupCli(args);
7048
+ if (scope === "doctor" && action === "runtime-takeover") return runRuntimeTakeoverDoctorCli();
7049
+ if (scope === "board" && action === "contract") {
7050
+ return void await runCommandCenterContractCli(args);
7051
+ }
5836
7052
  if (scope === "run" && action === "create") return createRun(args);
5837
7053
  if (scope === "run" && action === "list") return listRuns();
5838
7054
  if (scope === "run" && action === "status") return runStatus(args);
@@ -5865,19 +7081,317 @@ if (isCliEntry) {
5865
7081
  process.exit(1);
5866
7082
  });
5867
7083
  }
7084
+
7085
+ // src/vercel/vercel-url.ts
7086
+ var VERCEL_HOST_RE = /(^|\.)vercel\.app$/i;
7087
+ function tryParseUrl(raw) {
7088
+ const trimmed = raw.trim();
7089
+ if (!trimmed) return null;
7090
+ try {
7091
+ return new URL(trimmed);
7092
+ } catch {
7093
+ return null;
7094
+ }
7095
+ }
7096
+ function parseDashboardDeployment(url) {
7097
+ const parts = url.pathname.split("/").filter(Boolean);
7098
+ if (parts.length < 3) return null;
7099
+ const deploymentId = parts[parts.length - 1]?.trim();
7100
+ if (!deploymentId || deploymentId === "deployments") return null;
7101
+ return deploymentId;
7102
+ }
7103
+ function parseDeploymentsSegment(url) {
7104
+ const parts = url.pathname.split("/").filter(Boolean);
7105
+ const idx = parts.indexOf("deployments");
7106
+ if (idx < 0 || idx >= parts.length - 1) return null;
7107
+ const deploymentId = parts[idx + 1]?.trim();
7108
+ return deploymentId || null;
7109
+ }
7110
+ function classifyVercelUrl(raw) {
7111
+ const empty = {
7112
+ kind: "unknown",
7113
+ previewUrl: null,
7114
+ inspectTarget: null,
7115
+ deploymentId: null
7116
+ };
7117
+ const trimmed = raw.trim();
7118
+ if (!trimmed) return empty;
7119
+ if (/^dpl_[a-z0-9]+$/i.test(trimmed)) {
7120
+ return {
7121
+ kind: "deployment_id",
7122
+ previewUrl: null,
7123
+ inspectTarget: trimmed,
7124
+ deploymentId: trimmed
7125
+ };
7126
+ }
7127
+ const url = tryParseUrl(trimmed);
7128
+ if (!url) return empty;
7129
+ if (VERCEL_HOST_RE.test(url.hostname)) {
7130
+ const hostUrl = url.origin;
7131
+ return {
7132
+ kind: "deployment_host",
7133
+ previewUrl: hostUrl,
7134
+ inspectTarget: hostUrl,
7135
+ deploymentId: null
7136
+ };
7137
+ }
7138
+ if (url.hostname === "vercel.com" || url.hostname.endsWith(".vercel.com")) {
7139
+ const deploymentId = parseDeploymentsSegment(url) ?? parseDashboardDeployment(url);
7140
+ return {
7141
+ kind: "dashboard",
7142
+ previewUrl: null,
7143
+ inspectTarget: deploymentId,
7144
+ deploymentId
7145
+ };
7146
+ }
7147
+ return empty;
7148
+ }
7149
+ function isDashboardVercelUrl(raw) {
7150
+ return classifyVercelUrl(raw).kind === "dashboard";
7151
+ }
7152
+
7153
+ // src/vercel/vercel-github-status.ts
7154
+ var VERCEL_CONTEXT_RE = /vercel/i;
7155
+ function normalizeGitHubStatusState(state) {
7156
+ const value = typeof state === "string" ? state.trim().toLowerCase() : "";
7157
+ if (value === "success") return "success";
7158
+ if (value === "pending") return "pending";
7159
+ if (value === "failure") return "failure";
7160
+ if (value === "error") return "error";
7161
+ return "unknown";
7162
+ }
7163
+ function isVercelStatusContext(context) {
7164
+ const label = typeof context === "string" ? context.trim() : "";
7165
+ return Boolean(label && VERCEL_CONTEXT_RE.test(label));
7166
+ }
7167
+ function pickVercelStatusContext(statuses) {
7168
+ const rows = Array.isArray(statuses) ? statuses : [];
7169
+ const vercelRows = rows.filter((row) => isVercelStatusContext(row.context));
7170
+ if (vercelRows.length === 0) return null;
7171
+ const score = (state) => {
7172
+ if (state === "failure" || state === "error") return 0;
7173
+ if (state === "pending") return 1;
7174
+ if (state === "success") return 2;
7175
+ return 1;
7176
+ };
7177
+ let best = null;
7178
+ let bestScore = Infinity;
7179
+ for (const row of vercelRows) {
7180
+ const rowScore = score(normalizeGitHubStatusState(row.state));
7181
+ if (rowScore < bestScore) {
7182
+ best = row;
7183
+ bestScore = rowScore;
7184
+ }
7185
+ }
7186
+ if (!best) return null;
7187
+ const targetUrl = typeof best.target_url === "string" && best.target_url.trim() ? best.target_url.trim() : null;
7188
+ const classified = targetUrl ? classifyVercelUrl(targetUrl) : null;
7189
+ return {
7190
+ context: String(best.context ?? "Vercel").trim(),
7191
+ state: normalizeGitHubStatusState(best.state),
7192
+ targetUrl,
7193
+ description: typeof best.description === "string" && best.description.trim() ? best.description.trim() : null,
7194
+ deploymentId: classified?.deploymentId ?? null,
7195
+ previewUrl: classified?.previewUrl ?? null,
7196
+ dashboardUrl: classified?.kind === "dashboard" ? targetUrl : null
7197
+ };
7198
+ }
7199
+
7200
+ // src/vercel/vercel-evidence.ts
7201
+ import { spawnSync as spawnSync7 } from "node:child_process";
7202
+ var DEFAULT_INSPECT_WAIT_SECONDS = 120;
7203
+ function mapGitHubStateToEvidence(state) {
7204
+ if (state === "success") return "ready";
7205
+ if (state === "pending") return "building";
7206
+ if (state === "failure" || state === "error") return "error";
7207
+ return "unavailable";
7208
+ }
7209
+ function evidenceSummary(parts) {
7210
+ return parts.filter(Boolean).join("; ");
7211
+ }
7212
+ function resolveVercelInspectTarget(rawUrl) {
7213
+ const trimmed = typeof rawUrl === "string" ? rawUrl.trim() : "";
7214
+ if (!trimmed) {
7215
+ return { target: null, classified: null, reason: "missing target_url" };
7216
+ }
7217
+ const classified = classifyVercelUrl(trimmed);
7218
+ if (classified.inspectTarget) {
7219
+ return { target: classified.inspectTarget, classified, reason: null };
7220
+ }
7221
+ if (classified.kind === "dashboard") {
7222
+ return {
7223
+ target: null,
7224
+ classified,
7225
+ reason: "dashboard URL is not valid for vercel inspect"
7226
+ };
7227
+ }
7228
+ return { target: null, classified, reason: "unrecognized Vercel URL" };
7229
+ }
7230
+ function evidenceFromGitHubVercelStatus(statuses, options = {}) {
7231
+ const observedAt = options.observedAt ?? (/* @__PURE__ */ new Date()).toISOString();
7232
+ const row = pickVercelStatusContext(statuses);
7233
+ if (!row) {
7234
+ return {
7235
+ status: "not_run",
7236
+ previewUrl: null,
7237
+ deploymentUrl: null,
7238
+ summary: "No Vercel GitHub status context on commit",
7239
+ observedAt,
7240
+ inspectSkipped: true,
7241
+ inspectReason: "no_vercel_status_context",
7242
+ githubState: null,
7243
+ vercelContext: null
7244
+ };
7245
+ }
7246
+ const status = mapGitHubStateToEvidence(row.state);
7247
+ const previewUrl = row.previewUrl ?? row.dashboardUrl;
7248
+ const deploymentUrl = row.previewUrl ?? row.dashboardUrl;
7249
+ return {
7250
+ status,
7251
+ previewUrl,
7252
+ deploymentUrl,
7253
+ summary: evidenceSummary([
7254
+ `GitHub ${row.context}=${row.state}`,
7255
+ row.description ?? "",
7256
+ row.dashboardUrl ? "dashboard target_url (inspect skipped)" : ""
7257
+ ]),
7258
+ observedAt,
7259
+ inspectSkipped: true,
7260
+ inspectReason: row.dashboardUrl && row.state === "success" ? "trusted_github_status_dashboard_url" : row.dashboardUrl ? "dashboard_url_not_inspectable" : "github_status_only",
7261
+ githubState: row.state,
7262
+ vercelContext: row.context
7263
+ };
7264
+ }
7265
+ function defaultRunVercelInspect(target, waitSeconds) {
7266
+ const args = ["inspect", target, "--wait", String(waitSeconds)];
7267
+ const result = spawnSync7("vercel", args, {
7268
+ encoding: "utf8",
7269
+ stdio: ["ignore", "pipe", "pipe"]
7270
+ });
7271
+ if (result.error) {
7272
+ return {
7273
+ ok: false,
7274
+ exitCode: result.status,
7275
+ stdout: "",
7276
+ stderr: "",
7277
+ error: result.error.message
7278
+ };
7279
+ }
7280
+ return {
7281
+ ok: result.status === 0,
7282
+ exitCode: result.status ?? 1,
7283
+ stdout: (result.stdout ?? "").trim(),
7284
+ stderr: (result.stderr ?? "").trim(),
7285
+ error: null
7286
+ };
7287
+ }
7288
+ function parseInspectReady(stdout, stderr) {
7289
+ const blob = `${stdout}
7290
+ ${stderr}`.toLowerCase();
7291
+ if (/\b(status|state)\s*[:=]\s*(ready|completed)\b/.test(blob)) return true;
7292
+ if (/\bready\b/.test(blob) && !/\bnot ready\b/.test(blob)) return true;
7293
+ return false;
7294
+ }
7295
+ function collectVercelEvidence(input) {
7296
+ const observedAt = input.observedAt ?? (/* @__PURE__ */ new Date()).toISOString();
7297
+ const base = evidenceFromGitHubVercelStatus(input.statuses ?? [], { observedAt });
7298
+ const row = pickVercelStatusContext(input.statuses ?? []);
7299
+ if (!row) return base;
7300
+ if (row.state === "success") {
7301
+ return {
7302
+ ...base,
7303
+ inspectSkipped: true,
7304
+ inspectReason: row.dashboardUrl ? "trusted_github_status_dashboard_url" : "trusted_github_status"
7305
+ };
7306
+ }
7307
+ if (!input.allowInspect) {
7308
+ return {
7309
+ ...base,
7310
+ inspectSkipped: true,
7311
+ inspectReason: "inspect_disabled"
7312
+ };
7313
+ }
7314
+ const { target, reason } = resolveVercelInspectTarget(row.targetUrl);
7315
+ if (!target) {
7316
+ return {
7317
+ ...base,
7318
+ inspectSkipped: true,
7319
+ inspectReason: reason ?? "not_inspectable",
7320
+ summary: evidenceSummary([
7321
+ base.summary ?? "",
7322
+ reason ?? "skipped vercel inspect"
7323
+ ])
7324
+ };
7325
+ }
7326
+ const waitSeconds = input.waitSeconds ?? DEFAULT_INSPECT_WAIT_SECONDS;
7327
+ const runInspect = input.runInspect ?? defaultRunVercelInspect;
7328
+ const inspected = runInspect(target, waitSeconds);
7329
+ const observed = (/* @__PURE__ */ new Date()).toISOString();
7330
+ if (inspected.error?.includes("ENOENT")) {
7331
+ return {
7332
+ status: "unavailable",
7333
+ previewUrl: base.previewUrl,
7334
+ deploymentUrl: base.deploymentUrl,
7335
+ summary: "vercel CLI not found on PATH",
7336
+ observedAt: observed,
7337
+ inspectSkipped: true,
7338
+ inspectReason: "vercel_cli_missing",
7339
+ githubState: row.state,
7340
+ vercelContext: row.context
7341
+ };
7342
+ }
7343
+ if (!inspected.ok) {
7344
+ const detail = [inspected.stderr, inspected.stdout, inspected.error].filter(Boolean).join("\n").trim().slice(0, 500);
7345
+ return {
7346
+ status: row.state === "pending" ? "building" : "error",
7347
+ previewUrl: base.previewUrl,
7348
+ deploymentUrl: target.startsWith("http") ? target : base.deploymentUrl,
7349
+ summary: evidenceSummary([
7350
+ `vercel inspect ${target} failed (exit ${inspected.exitCode ?? "?"})`,
7351
+ detail
7352
+ ]),
7353
+ observedAt: observed,
7354
+ inspectSkipped: false,
7355
+ inspectReason: null,
7356
+ githubState: row.state,
7357
+ vercelContext: row.context
7358
+ };
7359
+ }
7360
+ const ready = parseInspectReady(inspected.stdout, inspected.stderr);
7361
+ return {
7362
+ status: ready ? "ready" : row.state === "pending" ? "building" : "error",
7363
+ previewUrl: target.startsWith("http") ? target : base.previewUrl,
7364
+ deploymentUrl: target.startsWith("http") ? target : base.deploymentUrl,
7365
+ summary: ready ? `vercel inspect ${target} ready` : `vercel inspect ${target} completed without ready signal`,
7366
+ observedAt: observed,
7367
+ inspectSkipped: false,
7368
+ inspectReason: null,
7369
+ githubState: row.state,
7370
+ vercelContext: row.context
7371
+ };
7372
+ }
5868
7373
  export {
5869
7374
  DEFAULT_DISPATCH_LEASE_MS,
7375
+ DEFAULT_HARNESS_VERIFY_COMMANDS,
7376
+ FORBIDDEN_WORKER_ENV_KEYS,
5870
7377
  PACKAGE_VERSION,
5871
7378
  assessAutoCompleteEligibility,
7379
+ assessBuildAdmission,
5872
7380
  assessExitedWorkerSalvage,
5873
7381
  assessPrHandoffRequirement,
5874
7382
  assessWorkerLanding,
5875
7383
  assessWorkerLandingContract,
7384
+ auditWorkerEnv,
5876
7385
  autoCompleteWorker,
5877
7386
  autoCompleteWorkerCli,
5878
7387
  buildDispatchTaskText,
5879
7388
  buildPrompt,
7389
+ buildSystemdRunArgv,
7390
+ classifyNpmAuditOutcome,
7391
+ classifyShellCommandOutcome,
7392
+ classifyVercelUrl,
5880
7393
  classifyWorkerHealth,
7394
+ collectVercelEvidence,
5881
7395
  completeWorker,
5882
7396
  computeAttention,
5883
7397
  computeWorkerStatus,
@@ -5886,21 +7400,29 @@ export {
5886
7400
  dispatchRun,
5887
7401
  drainPlanOutbox,
5888
7402
  ensurePrReadyHandoff,
7403
+ evidenceFromGitHubVercelStatus,
5889
7404
  extractPlanOutboxFromTask,
5890
7405
  extractPrUrlFromText,
5891
7406
  formatPlanOutboxHandoffBlock,
5892
7407
  getHarnessPaths,
5893
7408
  getMonitorStatus,
5894
7409
  hashPlanBody,
7410
+ isDashboardVercelUrl,
7411
+ isEngagementRequiredSkip,
5895
7412
  isFinishedWorkerStatus,
7413
+ isForbiddenWorkerEnvKey,
5896
7414
  isLandingBlockedWorkerStatus,
7415
+ isSystemdRunAvailable,
5897
7416
  isTerminalHeartbeatPhase,
7417
+ isVercelStatusContext,
5898
7418
  landingContractAttentionReason,
7419
+ listForbiddenWorkerEnvKeys,
5899
7420
  listMonitors,
5900
7421
  listOutboxItems,
5901
7422
  listRuns,
5902
7423
  loadUserConfig,
5903
7424
  main,
7425
+ mergeNodeOptionsForBuildCheck,
5904
7426
  normalizeCursorModelAlias,
5905
7427
  observeRunnerDiskGate,
5906
7428
  parseArgs,
@@ -5908,8 +7430,10 @@ export {
5908
7430
  parseHarnessStream,
5909
7431
  parseHeartbeat,
5910
7432
  persistPlan,
7433
+ pickVercelStatusContext,
5911
7434
  postJson,
5912
7435
  preflightCursorModel,
7436
+ readMemAvailableBytes,
5913
7437
  reconcileRunsCli,
5914
7438
  reconcileStaleWorkers,
5915
7439
  redactHarness,
@@ -5917,16 +7441,23 @@ export {
5917
7441
  resolveCallbackSecret,
5918
7442
  resolveCallbackSecretWithMint,
5919
7443
  resolveHarnessRoot,
7444
+ resolveVercelInspectTarget,
7445
+ runBoundedBuildCheck,
5920
7446
  runDaemon,
7447
+ runHarnessVerifyCommands,
5921
7448
  runMonitorTick,
5922
7449
  runStatus,
5923
7450
  saveUserConfig,
7451
+ scrubClaudeEnv,
7452
+ scrubWorkerEnv,
5924
7453
  spawnCompletionSidecar,
5925
7454
  spawnMonitorSidecar,
5926
7455
  spawnWorkerProcess,
5927
7456
  startWorker,
5928
7457
  stopWorker,
5929
7458
  summarizeEvent,
7459
+ summarizeNpmAuditReport,
7460
+ summarizeShellToolCallEvent,
5930
7461
  sweepRun,
5931
7462
  tailWorker,
5932
7463
  terminalFinalResultFromHeartbeat,