@kynver-app/runtime 0.1.51 → 0.1.59

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -169,12 +169,14 @@ var FORBIDDEN_WORKER_ENV_KEYS = [
169
169
  "NEXTAUTH_SECRET",
170
170
  "DATABASE_URL",
171
171
  "PRODUCTION_DATABASE_URL",
172
+ "KYNVER_PRODUCTION_DATABASE_URL",
172
173
  "REDIS_URL",
173
174
  "GOOGLE_CLIENT_SECRET",
174
175
  "GITHUB_CLIENT_SECRET",
175
176
  "KYNVER_API_KEY",
176
177
  "KYNVER_SERVICE_SECRET",
177
178
  "KYNVER_RUNTIME_SECRET",
179
+ "KYNVER_CRON_SECRET",
178
180
  "OPENCLAW_CRON_SECRET",
179
181
  "QSTASH_TOKEN",
180
182
  "QSTASH_CURRENT_SIGNING_KEY",
@@ -455,7 +457,7 @@ function presentUserConfig(config) {
455
457
  }
456
458
  function inferSetupFields(existing, args) {
457
459
  const creds = loadCredentialsFile();
458
- 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();
460
+ const apiBaseUrl = (typeof args.apiBaseUrl === "string" ? args.apiBaseUrl : void 0) || existing.apiBaseUrl?.trim() || process.env.KYNVER_API_URL?.trim() || process.env.KYNVER_CRON_FIRE_BASE_URL?.trim() || process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim();
459
461
  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);
460
462
  const explicitRepo = typeof args.repo === "string" ? args.repo : args.discoverRepo === true || args.discoverRepo === "true" ? discoverDefaultRepo()?.repo : void 0;
461
463
  const defaultRepo = explicitRepo || existing.defaultRepo?.trim() || process.env.KYNVER_DEFAULT_REPO?.trim() || process.env.KYNVER_HARNESS_REPO?.trim() || discoverDefaultRepo()?.repo;
@@ -507,17 +509,17 @@ function saveRunnerToken(agentOsId, token) {
507
509
  }
508
510
  function resolveBaseUrl(argsBaseUrl) {
509
511
  const baseUrl = resolveConfiguredBaseUrl(argsBaseUrl);
510
- if (!baseUrl) failConfig("requires --base-url, KYNVER_API_URL, OPENCLAW_CRON_FIRE_BASE_URL, or ~/.kynver/config.json apiBaseUrl");
512
+ if (!baseUrl) failConfig("requires --base-url, KYNVER_API_URL, KYNVER_CRON_FIRE_BASE_URL, or ~/.kynver/config.json apiBaseUrl");
511
513
  return baseUrl;
512
514
  }
513
515
  function resolveConfiguredBaseUrl(argsBaseUrl) {
514
- const baseUrl = argsBaseUrl || process.env.KYNVER_API_URL || process.env.OPENCLAW_CRON_FIRE_BASE_URL || loadUserConfig().apiBaseUrl;
516
+ const baseUrl = argsBaseUrl || process.env.KYNVER_API_URL || process.env.KYNVER_CRON_FIRE_BASE_URL || process.env.OPENCLAW_CRON_FIRE_BASE_URL || loadUserConfig().apiBaseUrl;
515
517
  return baseUrl ? trimTrailingSlash(String(baseUrl)) : void 0;
516
518
  }
517
519
  function resolveConfiguredCallbackSecret(argsSecret, agentOsId) {
518
520
  const scoped = argsSecret || loadRunnerToken(agentOsId) || (agentOsId ? void 0 : loadRunnerToken(loadUserConfig().agentOsId));
519
521
  if (scoped) return String(scoped);
520
- const globalSecret = process.env.KYNVER_RUNTIME_SECRET || process.env.OPENCLAW_CRON_SECRET;
522
+ const globalSecret = process.env.KYNVER_RUNTIME_SECRET || process.env.KYNVER_CRON_SECRET || process.env.OPENCLAW_CRON_SECRET;
521
523
  if (globalSecret) {
522
524
  console.warn(
523
525
  "[kynver] using deployment-level callback secret; run `kynver runner credential --agent-os-id <id>` for a scoped token"
@@ -530,7 +532,7 @@ function resolveCallbackSecret(argsSecret, agentOsId) {
530
532
  const configured = resolveConfiguredCallbackSecret(argsSecret, agentOsId);
531
533
  if (configured) return configured;
532
534
  failConfig(
533
- "requires --secret, KYNVER_RUNNER_TOKEN, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, KYNVER_API_KEY with an API base URL to mint one, or (legacy) KYNVER_RUNTIME_SECRET / OPENCLAW_CRON_SECRET"
535
+ "requires --secret, KYNVER_RUNNER_TOKEN, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, KYNVER_API_KEY with an API base URL to mint one, or (legacy) KYNVER_RUNTIME_SECRET / KYNVER_CRON_SECRET / OPENCLAW_CRON_SECRET"
534
536
  );
535
537
  }
536
538
  async function resolveCallbackSecretWithMint(argsSecret, agentOsId, opts) {
@@ -548,7 +550,7 @@ async function resolveCallbackSecretWithMint(argsSecret, agentOsId, opts) {
548
550
  }
549
551
  }
550
552
  failConfig(
551
- "requires --secret, KYNVER_RUNNER_TOKEN, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, KYNVER_API_KEY with an API base URL to mint one, or (legacy) KYNVER_RUNTIME_SECRET / OPENCLAW_CRON_SECRET"
553
+ "requires --secret, KYNVER_RUNNER_TOKEN, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, KYNVER_API_KEY with an API base URL to mint one, or (legacy) KYNVER_RUNTIME_SECRET / KYNVER_CRON_SECRET / OPENCLAW_CRON_SECRET"
552
554
  );
553
555
  }
554
556
  async function refreshRunnerToken(agentOsId, opts) {
@@ -708,6 +710,11 @@ function buildHarnessCallbackHeaders(secret) {
708
710
  }
709
711
  return {
710
712
  "Content-Type": "application/json",
713
+ // Canonical header. We keep sending the legacy `X-OpenClaw-Cron-Secret`
714
+ // (and `X-Kynver-Runtime-Secret`) so an un-upgraded Kynver server that
715
+ // only reads the old header still authenticates this runner during the
716
+ // rename compat window.
717
+ "X-Kynver-Cron-Secret": trimmed,
711
718
  "X-OpenClaw-Cron-Secret": trimmed,
712
719
  "X-Kynver-Runtime-Secret": trimmed
713
720
  };
@@ -751,18 +758,88 @@ async function getJson(url, secret) {
751
758
  }
752
759
 
753
760
  // src/disk-gate.ts
754
- import { statfsSync } from "node:fs";
761
+ import { statfsSync as statfsSync2 } from "node:fs";
762
+
763
+ // src/wsl-host.ts
764
+ import { existsSync as existsSync5, readFileSync as readFileSync5, statfsSync } from "node:fs";
765
+ var DEFAULT_WSL_HOST_WARN_FREE_BYTES = 25 * 1024 * 1024 * 1024;
766
+ var DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES = 12 * 1024 * 1024 * 1024;
767
+ var DEFAULT_WSL_HOST_MOUNT = "/mnt/c";
768
+ function isWslHost() {
769
+ if (process.platform !== "linux") return false;
770
+ for (const probe of ["/proc/sys/kernel/osrelease", "/proc/version"]) {
771
+ try {
772
+ if (!existsSync5(probe)) continue;
773
+ const text = readFileSync5(probe, "utf8");
774
+ if (/microsoft|wsl/i.test(text)) return true;
775
+ } catch {
776
+ }
777
+ }
778
+ return false;
779
+ }
780
+ function observeWslHostDisk(options = {}) {
781
+ const wsl = options.forceWsl === void 0 ? isWslHost() : options.forceWsl;
782
+ if (!wsl) return null;
783
+ const path44 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
784
+ const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;
785
+ const criticalBelowBytes = options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;
786
+ const statfs = options.statfs ?? statfsSync;
787
+ let stats;
788
+ try {
789
+ stats = statfs(path44);
790
+ } catch (error) {
791
+ return {
792
+ ok: false,
793
+ path: path44,
794
+ freeBytes: 0,
795
+ totalBytes: 0,
796
+ usedPercent: 100,
797
+ warnBelowBytes,
798
+ criticalBelowBytes,
799
+ reason: `Windows host disk probe failed at ${path44}: ${error.message}`,
800
+ probeError: error.message
801
+ };
802
+ }
803
+ const freeBytes = Number(stats.bavail) * Number(stats.bsize);
804
+ const totalBytes = Number(stats.blocks) * Number(stats.bsize);
805
+ const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
806
+ const lowFree = freeBytes < warnBelowBytes;
807
+ const criticalFree = freeBytes < criticalBelowBytes;
808
+ const ok = !lowFree && !criticalFree;
809
+ const freeGiB = (freeBytes / (1024 * 1024 * 1024)).toFixed(1);
810
+ let reason = null;
811
+ if (!ok) {
812
+ const tag = criticalFree ? "critical" : "warning";
813
+ reason = `Windows host disk ${path44} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
814
+ }
815
+ return {
816
+ ok,
817
+ path: path44,
818
+ freeBytes,
819
+ totalBytes,
820
+ usedPercent,
821
+ warnBelowBytes,
822
+ criticalBelowBytes,
823
+ reason,
824
+ probeError: null
825
+ };
826
+ }
827
+ function summarizeWslRecoverySteps() {
828
+ return "Recovery: 1) free Windows C: (empty Recycle Bin / Storage Sense / clear %TEMP%); 2) shut down WSL (`wsl --shutdown`) then compact the VHDX (`Optimize-VHD` or `diskpart compact vdisk`); 3) clear local node_modules / .next / harness worktrees before restarting workers. Full runbook: docs/runbooks/wsl-disk-pressure.md.";
829
+ }
830
+
831
+ // src/disk-gate.ts
755
832
  var DEFAULT_WARN_FREE_BYTES = 30 * 1024 * 1024 * 1024;
756
833
  var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
757
834
  var DEFAULT_MAX_USED_PERCENT = 80;
758
835
  var DEFAULT_HARD_MAX_USED_PERCENT = 90;
759
836
  function observeRunnerDiskGate(input = {}) {
760
- const path40 = input.diskPath?.trim() || "/";
837
+ const path44 = input.diskPath?.trim() || "/";
761
838
  const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
762
839
  const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
763
840
  const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
764
841
  const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
765
- const stats = statfsSync(path40);
842
+ const stats = statfsSync2(path44);
766
843
  const freeBytes = Number(stats.bavail) * Number(stats.bsize);
767
844
  const totalBytes = Number(stats.blocks) * Number(stats.bsize);
768
845
  const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
@@ -770,19 +847,22 @@ function observeRunnerDiskGate(input = {}) {
770
847
  const criticalFree = freeBytes < criticalBelowBytes;
771
848
  const highUse = usedPercent > maxUsedPercent;
772
849
  const hardHighUse = usedPercent > hardMaxUsedPercent;
773
- const ok = !lowFree && !criticalFree && !highUse && !hardHighUse;
850
+ const localOk = !lowFree && !criticalFree && !highUse && !hardHighUse;
851
+ const wslHost = input.skipWslHostCheck ? null : observeWslHostDisk(input.wslHost);
852
+ const ok = localOk && (wslHost ? wslHost.ok : true);
774
853
  let reason = null;
775
854
  if (!ok) {
776
855
  reason = [
777
856
  criticalFree ? `free space below critical ${criticalBelowBytes} bytes` : null,
778
857
  lowFree ? `free space below warning ${warnBelowBytes} bytes` : null,
779
858
  hardHighUse ? `used percent above hard cap ${hardMaxUsedPercent}%` : null,
780
- highUse ? `used percent above cap ${maxUsedPercent}%` : null
859
+ highUse ? `used percent above cap ${maxUsedPercent}%` : null,
860
+ wslHost && !wslHost.ok ? wslHost.reason : null
781
861
  ].filter(Boolean).join("; ");
782
862
  }
783
863
  return {
784
864
  ok,
785
- path: path40,
865
+ path: path44,
786
866
  freeBytes,
787
867
  totalBytes,
788
868
  usedPercent,
@@ -790,7 +870,8 @@ function observeRunnerDiskGate(input = {}) {
790
870
  criticalBelowBytes,
791
871
  maxUsedPercent,
792
872
  hardMaxUsedPercent,
793
- reason
873
+ reason,
874
+ wslHost
794
875
  };
795
876
  }
796
877
 
@@ -798,7 +879,7 @@ function observeRunnerDiskGate(input = {}) {
798
879
  import os2 from "node:os";
799
880
 
800
881
  // src/bounded-build/meminfo.ts
801
- import { readFileSync as readFileSync5 } from "node:fs";
882
+ import { readFileSync as readFileSync6 } from "node:fs";
802
883
  import os from "node:os";
803
884
  function readMemAvailableBytes(meminfoText) {
804
885
  if (meminfoText !== void 0) {
@@ -808,7 +889,7 @@ function readMemAvailableBytes(meminfoText) {
808
889
  }
809
890
  if (process.platform === "linux") {
810
891
  try {
811
- const meminfo = readFileSync5("/proc/meminfo", "utf8");
892
+ const meminfo = readFileSync6("/proc/meminfo", "utf8");
812
893
  const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m);
813
894
  if (match) return Number(match[1]) * 1024;
814
895
  } catch {
@@ -821,11 +902,11 @@ function readMemAvailableBytes(meminfoText) {
821
902
  import path7 from "node:path";
822
903
 
823
904
  // src/run-store.ts
824
- import { existsSync as existsSync6, readdirSync as readdirSync2 } from "node:fs";
905
+ import { existsSync as existsSync7, readdirSync as readdirSync2 } from "node:fs";
825
906
  import path6 from "node:path";
826
907
 
827
908
  // src/paths.ts
828
- import { existsSync as existsSync5 } from "node:fs";
909
+ import { existsSync as existsSync6 } from "node:fs";
829
910
  import { homedir as homedir4 } from "node:os";
830
911
  import path5 from "node:path";
831
912
  var LEGACY_ROOT = path5.join(homedir4(), ".openclaw", "harness");
@@ -835,8 +916,8 @@ function resolveHarnessRoot() {
835
916
  const configured = loadUserConfig().harnessRoot?.trim();
836
917
  if (configured) return resolveUserPath(configured);
837
918
  const kynverRoot = path5.join(homedir4(), ".kynver", "harness");
838
- if (existsSync5(kynverRoot)) return kynverRoot;
839
- if (existsSync5(LEGACY_ROOT)) return LEGACY_ROOT;
919
+ if (existsSync6(kynverRoot)) return kynverRoot;
920
+ if (existsSync6(LEGACY_ROOT)) return LEGACY_ROOT;
840
921
  return kynverRoot;
841
922
  }
842
923
  function getHarnessPaths() {
@@ -861,7 +942,7 @@ function loadRun(id) {
861
942
  }
862
943
  function listRunRecords() {
863
944
  const { runsDir } = getPaths();
864
- if (!existsSync6(runsDir)) return [];
945
+ if (!existsSync7(runsDir)) return [];
865
946
  const runs = [];
866
947
  for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
867
948
  if (!entry.isDirectory()) continue;
@@ -893,7 +974,7 @@ function runDirectory(id) {
893
974
  }
894
975
 
895
976
  // src/heartbeat.ts
896
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
977
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
897
978
  var HEARTBEAT_FUTURE_SKEW_MS = 6e4;
898
979
  function isTerminalHeartbeatPhase(phase) {
899
980
  return phase === "complete";
@@ -912,10 +993,10 @@ function parseHeartbeat(file) {
912
993
  heartbeatBlocker: null,
913
994
  timestampAnomalies: []
914
995
  };
915
- if (!existsSync7(file)) return result;
996
+ if (!existsSync8(file)) return result;
916
997
  const maxFutureMs = Date.now() + HEARTBEAT_FUTURE_SKEW_MS;
917
998
  const clampedTo = new Date(maxFutureMs).toISOString();
918
- const lines = readFileSync6(file, "utf8").split("\n").filter(Boolean);
999
+ const lines = readFileSync7(file, "utf8").split("\n").filter(Boolean);
919
1000
  for (const line of lines) {
920
1001
  const entry = safeJson(line);
921
1002
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
@@ -942,7 +1023,7 @@ function parseHeartbeat(file) {
942
1023
  }
943
1024
 
944
1025
  // src/stream.ts
945
- import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
1026
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "node:fs";
946
1027
 
947
1028
  // src/shell-command-outcome.ts
948
1029
  var NPM_AUDIT_RE = /\bnpm\s+audit\b/i;
@@ -1146,8 +1227,8 @@ function parseHarnessStream(file) {
1146
1227
  error: null,
1147
1228
  lastShellOutcome: null
1148
1229
  };
1149
- if (!existsSync8(file)) return result;
1150
- const lines = readFileSync7(file, "utf8").split("\n").filter(Boolean);
1230
+ if (!existsSync9(file)) return result;
1231
+ const lines = readFileSync8(file, "utf8").split("\n").filter(Boolean);
1151
1232
  for (const line of lines) {
1152
1233
  const event = safeJson(line);
1153
1234
  if (!event) continue;
@@ -2173,7 +2254,7 @@ function hasLiveWorkerForTask(runId, taskId) {
2173
2254
  }
2174
2255
 
2175
2256
  // src/supervisor.ts
2176
- import { existsSync as existsSync12, mkdirSync as mkdirSync3 } from "node:fs";
2257
+ import { existsSync as existsSync13, mkdirSync as mkdirSync3 } from "node:fs";
2177
2258
  import path14 from "node:path";
2178
2259
 
2179
2260
  // src/prompt.ts
@@ -2194,7 +2275,7 @@ function buildPrompt(input) {
2194
2275
  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."
2195
2276
  ];
2196
2277
  const mergeGateLines = compact ? [
2197
- "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)."
2278
+ "Merge-gate cost control: run `node scripts/agent-os-pr-merge-gate.mjs --pr <url> --agent-os-id <id>` (or `verify-pr-local.mjs --from-pr` + `collect-pr-vercel-evidence.mjs` + POST pr-merge-gate/refresh) before any GitHub Actions run; request merge-gate only via refresh then POST pr-merge-gate/request-run (one Actions run per PR head unless human approves extra)."
2198
2279
  ] : [
2199
2280
  "GitHub Actions merge-gate cost control (Kynver/Hermes PRs):",
2200
2281
  "- Prefer local cached package verification (`node scripts/verify-pr-local.mjs --emit-json`) and Vercel preview evidence before GitHub Actions.",
@@ -2221,7 +2302,7 @@ function buildPrompt(input) {
2221
2302
  "After each major step, append one JSON line to the heartbeat file with fields: ts, phase, summary, changedFiles, blocker.",
2222
2303
  "Final response must include files changed, verification commands, and unresolved risks.",
2223
2304
  "Structured final result (recommended): record completion as JSON with summary, laneExpertise { whatChanged, why, files, prUrls, verification, risks, blockers, lessonsLearned, laneGuidance }, and targetPrReconciliation [{ prUrl, outcome: merged|skipped|blocked, mergeCommit?, reason? }] for every target PR on landing-only tasks.",
2224
- "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.",
2305
+ "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). One-off helper scripts must be removed (`kynver worker discard-disposable --path <file>`) or committed before completion \u2014 maintenance/board-drain workers are not exempt. Exiting with only dirty files and no PR routes to salvage review, not production review.",
2225
2306
  "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.",
2226
2307
  "Expert review / production-review workers (Dalton/Lorentz, plan-review-task, scheduledJob reviewer children): do NOT open new implementation PRs \u2014 review the parent task's existing PR and record reviewVerdict in finalResult; landing-contract targetPrReconciliation does not apply.",
2227
2308
  "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.",
@@ -2243,12 +2324,12 @@ function buildPrompt(input) {
2243
2324
  }
2244
2325
 
2245
2326
  // src/providers/cursor.ts
2246
- import { closeSync as closeSync2, existsSync as existsSync10, openSync as openSync2 } from "node:fs";
2327
+ import { closeSync as closeSync2, existsSync as existsSync11, openSync as openSync2 } from "node:fs";
2247
2328
  import { spawn as spawn2 } from "node:child_process";
2248
2329
  import path10 from "node:path";
2249
2330
 
2250
2331
  // src/providers/cursor-windows.ts
2251
- import { existsSync as existsSync9, readdirSync as readdirSync3 } from "node:fs";
2332
+ import { existsSync as existsSync10, readdirSync as readdirSync3 } from "node:fs";
2252
2333
  import path9 from "node:path";
2253
2334
  var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
2254
2335
  function parseCursorVersionSortKey(versionName) {
@@ -2261,7 +2342,7 @@ function parseCursorVersionSortKey(versionName) {
2261
2342
  }
2262
2343
  function pickLatestCursorVersionDir(agentRoot) {
2263
2344
  const versionsRoot = path9.join(agentRoot, "versions");
2264
- if (!existsSync9(versionsRoot)) return null;
2345
+ if (!existsSync10(versionsRoot)) return null;
2265
2346
  let bestDir = null;
2266
2347
  let bestKey = -1;
2267
2348
  for (const entry of readdirSync3(versionsRoot, { withFileTypes: true })) {
@@ -2277,14 +2358,14 @@ function resolveWindowsCursorBundled(agentRoot) {
2277
2358
  const root = agentRoot?.trim() || path9.join(process.env.LOCALAPPDATA || "", "cursor-agent");
2278
2359
  const directNode = path9.join(root, "node.exe");
2279
2360
  const directIndex = path9.join(root, "index.js");
2280
- if (existsSync9(directNode) && existsSync9(directIndex)) {
2361
+ if (existsSync10(directNode) && existsSync10(directIndex)) {
2281
2362
  return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
2282
2363
  }
2283
2364
  const versionDir = pickLatestCursorVersionDir(root);
2284
2365
  if (!versionDir) return null;
2285
2366
  const nodeExe = path9.join(versionDir, "node.exe");
2286
2367
  const indexJs = path9.join(versionDir, "index.js");
2287
- if (!existsSync9(nodeExe) || !existsSync9(indexJs)) return null;
2368
+ if (!existsSync10(nodeExe) || !existsSync10(indexJs)) return null;
2288
2369
  return { nodeExe, indexJs, versionDir };
2289
2370
  }
2290
2371
 
@@ -2302,7 +2383,7 @@ function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
2302
2383
  function resolveCursorSpawn(agentBin) {
2303
2384
  if (process.platform === "win32") {
2304
2385
  const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
2305
- const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync10(path10.join(path10.dirname(agentBin), "index.js"));
2386
+ const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync11(path10.join(path10.dirname(agentBin), "index.js"));
2306
2387
  const isDefaultShim = agentBin === "agent";
2307
2388
  if (isCursorWrapper || isBundledNode || isDefaultShim) {
2308
2389
  const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path10.dirname(agentBin)) : isBundledNode ? {
@@ -2329,7 +2410,7 @@ function resolveAgentBin() {
2329
2410
  );
2330
2411
  if (bundled) return bundled.nodeExe;
2331
2412
  const localAgent = path10.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
2332
- if (existsSync10(localAgent)) return localAgent;
2413
+ if (existsSync11(localAgent)) return localAgent;
2333
2414
  }
2334
2415
  return "agent";
2335
2416
  }
@@ -2412,7 +2493,7 @@ function resolveWorkerProvider(name) {
2412
2493
 
2413
2494
  // src/auto-complete.ts
2414
2495
  import { spawn as spawn3 } from "node:child_process";
2415
- import { existsSync as existsSync11, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
2496
+ import { existsSync as existsSync12, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
2416
2497
  import path13 from "node:path";
2417
2498
  import { fileURLToPath as fileURLToPath3 } from "node:url";
2418
2499
 
@@ -2911,6 +2992,133 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
2911
2992
  };
2912
2993
  }
2913
2994
 
2995
+ // src/material-worktree-changes.ts
2996
+ function materialWorktreeChanges(changedFiles) {
2997
+ return changedFiles.filter((line) => {
2998
+ const trimmed = line.trim();
2999
+ const pathPart = trimmed.startsWith("??") ? trimmed.slice(2).trim() : trimmed.length > 3 ? trimmed.slice(3).trim() : trimmed;
3000
+ return pathPart !== "node_modules" && !pathPart.startsWith("node_modules/");
3001
+ });
3002
+ }
3003
+ function pathFromGitStatusLine(line) {
3004
+ const trimmed = line.trim();
3005
+ if (trimmed.startsWith("??")) return trimmed.slice(2).trim();
3006
+ if (trimmed.length > 3 && /^[ MADRCU?!]{2} /.test(trimmed.slice(0, 3))) {
3007
+ return trimmed.slice(3).trim();
3008
+ }
3009
+ return trimmed;
3010
+ }
3011
+
3012
+ // src/disposable-artifacts.ts
3013
+ function stringList(value) {
3014
+ if (!Array.isArray(value)) return [];
3015
+ return value.map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
3016
+ }
3017
+ function asRecord2(value) {
3018
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
3019
+ }
3020
+ function extractDisposableArtifactsRemoved(finalResult) {
3021
+ const record = asRecord2(finalResult);
3022
+ if (!record) return [];
3023
+ const nested = asRecord2(record.worktreeHandoff);
3024
+ const fromNested = stringList(nested?.disposableArtifactsRemoved);
3025
+ if (fromNested.length) return fromNested;
3026
+ return stringList(record.disposableArtifactsRemoved);
3027
+ }
3028
+ function normalizeRelativePath(value) {
3029
+ return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
3030
+ }
3031
+ function dirtyPathsCoveredByDisposableRemoval(changedFiles, removed) {
3032
+ const material = materialWorktreeChanges(changedFiles);
3033
+ if (material.length === 0) return true;
3034
+ if (removed.length === 0) return false;
3035
+ const removedSet = new Set(removed.map((p) => normalizeRelativePath(p)));
3036
+ return material.every((line) => {
3037
+ const path44 = normalizeRelativePath(pathFromGitStatusLine(line));
3038
+ return removedSet.has(path44);
3039
+ });
3040
+ }
3041
+
3042
+ // src/worktree-completion-handoff.ts
3043
+ function trimOrNull5(value) {
3044
+ if (typeof value !== "string") return null;
3045
+ const t = value.trim();
3046
+ return t.length ? t : null;
3047
+ }
3048
+ function stringList2(value) {
3049
+ if (!Array.isArray(value)) return [];
3050
+ return value.map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
3051
+ }
3052
+ function hasFinalResult4(value) {
3053
+ if (value === void 0 || value === null) return false;
3054
+ if (typeof value === "string") return value.trim().length > 0;
3055
+ if (typeof value === "boolean") return value;
3056
+ if (Array.isArray(value)) return value.length > 0;
3057
+ if (typeof value === "object") return Object.keys(value).length > 0;
3058
+ return true;
3059
+ }
3060
+ function mergedDisposableRemoved(input) {
3061
+ const fromWorker = stringList2(input.disposableArtifactsRemoved);
3062
+ const fromResult = extractDisposableArtifactsRemoved(input.finalResult);
3063
+ return [.../* @__PURE__ */ new Set([...fromWorker, ...fromResult])];
3064
+ }
3065
+ function assessWorktreeCompletionHandoff(input) {
3066
+ const rawDirty = stringList2(input.changedFiles);
3067
+ const materialDirty = materialWorktreeChanges(rawDirty);
3068
+ const removed = mergedDisposableRemoved(input);
3069
+ const effectivelyClean = materialDirty.length === 0 || dirtyPathsCoveredByDisposableRemoval(rawDirty, removed);
3070
+ if (trimOrNull5(input.prUrl)) {
3071
+ if (!effectivelyClean) {
3072
+ return {
3073
+ allowed: false,
3074
+ state: "dirty_worktree",
3075
+ materialDirtyCount: materialDirty.length,
3076
+ detail: `Worktree has ${materialDirty.length} uncommitted change(s) with a PR attached; commit or discard before completing`
3077
+ };
3078
+ }
3079
+ return { allowed: true, state: "pr_handoff", materialDirtyCount: 0 };
3080
+ }
3081
+ if (trimOrNull5(input.headCommit)) {
3082
+ if (!effectivelyClean) {
3083
+ return {
3084
+ allowed: false,
3085
+ state: "dirty_worktree",
3086
+ materialDirtyCount: materialDirty.length,
3087
+ detail: `Worktree has ${materialDirty.length} uncommitted change(s) on top of a commit; commit or discard before completing`
3088
+ };
3089
+ }
3090
+ return { allowed: true, state: "commit_handoff", materialDirtyCount: 0 };
3091
+ }
3092
+ if (trimOrNull5(input.artifactBundlePath) || trimOrNull5(input.patchPath)) {
3093
+ if (!effectivelyClean) {
3094
+ return {
3095
+ allowed: false,
3096
+ state: "dirty_worktree",
3097
+ materialDirtyCount: materialDirty.length,
3098
+ detail: `Worktree has ${materialDirty.length} uncommitted change(s) with a patch/bundle handoff; clean the tree before completing`
3099
+ };
3100
+ }
3101
+ return { allowed: true, state: "commit_handoff", materialDirtyCount: 0 };
3102
+ }
3103
+ if (effectivelyClean) {
3104
+ return { allowed: true, state: "clean", materialDirtyCount: 0 };
3105
+ }
3106
+ if (hasFinalResult4(input.finalResult)) {
3107
+ return {
3108
+ allowed: false,
3109
+ state: "dirty_worktree_no_pr",
3110
+ materialDirtyCount: materialDirty.length,
3111
+ detail: `Worktree has ${materialDirty.length} uncommitted change(s) with no commit or PR; commit, open a PR, discard, or remove one-off files via \`kynver worker discard-disposable\` before completing`
3112
+ };
3113
+ }
3114
+ return {
3115
+ allowed: false,
3116
+ state: "dirty_worktree",
3117
+ materialDirtyCount: materialDirty.length,
3118
+ detail: `Worktree has ${materialDirty.length} uncommitted change(s) with no final result`
3119
+ };
3120
+ }
3121
+
2914
3122
  // src/worker-lifecycle.ts
2915
3123
  import path11 from "node:path";
2916
3124
  var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
@@ -3007,7 +3215,7 @@ function completionErrorText(parsed) {
3007
3215
  }
3008
3216
  return void 0;
3009
3217
  }
3010
- function asRecord2(value) {
3218
+ function asRecord3(value) {
3011
3219
  return value && typeof value === "object" && !Array.isArray(value) ? value : null;
3012
3220
  }
3013
3221
  function asString2(value) {
@@ -3084,26 +3292,48 @@ async function tryCompleteWorker(args) {
3084
3292
  if (worker.localOnly) {
3085
3293
  return { ok: true, skipped: true, reason: "local-only-worker" };
3086
3294
  }
3295
+ const headCommit = status.gitAncestry.headIsAncestorOfBase === false && status.gitAncestry.head ? status.gitAncestry.head : status.headCommit;
3296
+ const handoff = assessWorktreeCompletionHandoff({
3297
+ changedFiles: status.changedFiles,
3298
+ finalResult: status.finalResult,
3299
+ prUrl: status.prUrl,
3300
+ headCommit,
3301
+ disposableArtifactsRemoved: worker.disposableArtifactsRemoved
3302
+ });
3303
+ if (!handoff.allowed) {
3304
+ const reason2 = handoff.detail ?? `worktree completion blocked (${handoff.state})`;
3305
+ persistCompletionBlocker(worker, reason2);
3306
+ return {
3307
+ ok: false,
3308
+ reason: reason2,
3309
+ nextAction: "Clean the worktree (commit, open a PR, or `kynver worker discard-disposable --path <file>`), then rerun `kynver worker complete`.",
3310
+ completionBlocked: true
3311
+ };
3312
+ }
3087
3313
  const skipPrHandoff = args.skipPrHandoff === true || args.skipPrHandoff === "true";
3088
3314
  if (!skipPrHandoff && worker.dispatched && taskId) {
3089
- const handoff = ensurePrReadyHandoff({ worker, run, status });
3090
- if (!handoff.ok) {
3091
- persistCompletionBlocker(worker, handoff.reason);
3315
+ const handoff2 = ensurePrReadyHandoff({ worker, run, status });
3316
+ if (!handoff2.ok) {
3317
+ persistCompletionBlocker(worker, handoff2.reason);
3092
3318
  return {
3093
3319
  ok: false,
3094
- reason: handoff.reason,
3095
- nextAction: handoff.nextAction,
3320
+ reason: handoff2.reason,
3321
+ nextAction: handoff2.nextAction,
3096
3322
  completionBlocked: true
3097
3323
  };
3098
3324
  }
3099
- if (handoff.prUrl || handoff.headCommit) {
3100
- status = applyPrHandoffToStatus(status, handoff);
3325
+ if (handoff2.prUrl || handoff2.headCommit) {
3326
+ status = applyPrHandoffToStatus(status, handoff2);
3101
3327
  }
3102
3328
  }
3103
3329
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
3104
3330
  const explicitSecret = args.secret ? String(args.secret) : void 0;
3105
3331
  let secret = await resolveCallbackSecretWithMint(explicitSecret, agentOsId, { baseUrl: base });
3106
3332
  const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/harness/completion`;
3333
+ const statusPayload = { ...status };
3334
+ if (worker.disposableArtifactsRemoved?.length) {
3335
+ statusPayload.disposableArtifactsRemoved = worker.disposableArtifactsRemoved;
3336
+ }
3107
3337
  const body = {
3108
3338
  source: "kynver-harness",
3109
3339
  agentOsId,
@@ -3112,10 +3342,11 @@ async function tryCompleteWorker(args) {
3112
3342
  taskId,
3113
3343
  startedAt: worker.startedAt,
3114
3344
  finishedAt: status.lastActivityAt || (/* @__PURE__ */ new Date()).toISOString(),
3115
- status,
3345
+ status: statusPayload,
3116
3346
  workerInjection: {
3117
3347
  instructionPolicyFingerprint: worker.instructionPolicyFingerprint ?? null,
3118
3348
  instructionPolicyEvidence: worker.instructionPolicyEvidence ?? null,
3349
+ memoryQualityCapture: worker.memoryQualityCapture ?? null,
3119
3350
  policyAt: worker.instructionPolicyEvidence && typeof worker.instructionPolicyEvidence === "object" && "policyAt" in worker.instructionPolicyEvidence && typeof worker.instructionPolicyEvidence.policyAt === "string" ? worker.instructionPolicyEvidence.policyAt : null,
3120
3351
  personaSlug: worker.personaSlug ?? null,
3121
3352
  personaEvidence: worker.personaEvidence ?? null
@@ -3256,8 +3487,8 @@ function buildRunBoard(runId) {
3256
3487
  const completionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
3257
3488
  const boardStatus = completionBlocker ? "blocked" : status.status;
3258
3489
  const boardAttention = completionBlocker ? "blocked" : status.attention.state;
3259
- const completionResponse = asRecord2(worker.completionResponse);
3260
- const completionTask = asRecord2(completionResponse?.task);
3490
+ const completionResponse = asRecord3(worker.completionResponse);
3491
+ const completionTask = asRecord3(completionResponse?.task);
3261
3492
  const completionOutcome = asString2(completionResponse?.outcome);
3262
3493
  const completionRouteStatus = asString2(completionResponse?.status);
3263
3494
  const completionWarnings = Array.isArray(completionResponse?.warnings) ? completionResponse.warnings.filter((w) => typeof w === "string" && w.trim().length > 0) : [];
@@ -3538,7 +3769,7 @@ function resolveDefaultCliPath() {
3538
3769
  }
3539
3770
  function spawnCompletionSidecar(opts) {
3540
3771
  const cliPath = opts.cliPath ?? resolveDefaultCliPath();
3541
- if (!existsSync11(cliPath)) return void 0;
3772
+ if (!existsSync12(cliPath)) return void 0;
3542
3773
  const logPath = path13.join(opts.workerDir, "auto-complete.log");
3543
3774
  let logFd;
3544
3775
  try {
@@ -3625,7 +3856,7 @@ function spawnWorkerProcess(run, opts) {
3625
3856
  mkdirSync3(workerDir, { recursive: true });
3626
3857
  const worktreePath = path14.join(worktreesDir, run.id, name);
3627
3858
  const branch = opts.branch || `agent/${run.id}/${name}`;
3628
- if (existsSync12(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
3859
+ if (existsSync13(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
3629
3860
  git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
3630
3861
  git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
3631
3862
  const stdoutPath = path14.join(workerDir, "stdout.jsonl");
@@ -3681,6 +3912,7 @@ function spawnWorkerProcess(run, opts) {
3681
3912
  ...opts.planId ? { planId: String(opts.planId) } : {},
3682
3913
  ...opts.instructionPolicyFingerprint ? { instructionPolicyFingerprint: String(opts.instructionPolicyFingerprint) } : {},
3683
3914
  ...opts.instructionPolicyEvidence ? { instructionPolicyEvidence: opts.instructionPolicyEvidence } : {},
3915
+ ...opts.memoryQualityCapture ? { memoryQualityCapture: opts.memoryQualityCapture } : {},
3684
3916
  ...opts.personaSlug ? { personaSlug: String(opts.personaSlug) } : {},
3685
3917
  ...opts.personaEvidence ? { personaEvidence: opts.personaEvidence } : {},
3686
3918
  ...opts.leaseOwner ? { leaseOwner: String(opts.leaseOwner) } : {},
@@ -4015,8 +4247,8 @@ function isTmpOnlyPath(filePath) {
4015
4247
 
4016
4248
  // src/plan-persist/outbox-store.ts
4017
4249
  import {
4018
- existsSync as existsSync14,
4019
- readFileSync as readFileSync8,
4250
+ existsSync as existsSync15,
4251
+ readFileSync as readFileSync9,
4020
4252
  renameSync,
4021
4253
  readdirSync as readdirSync4,
4022
4254
  writeFileSync as writeFileSync3,
@@ -4042,9 +4274,9 @@ function findOutboxByIdempotencyKey(key) {
4042
4274
  return null;
4043
4275
  }
4044
4276
  function readOutboxItem(jsonPath) {
4045
- if (!existsSync14(jsonPath)) return null;
4277
+ if (!existsSync15(jsonPath)) return null;
4046
4278
  try {
4047
- return JSON.parse(readFileSync8(jsonPath, "utf8"));
4279
+ return JSON.parse(readFileSync9(jsonPath, "utf8"));
4048
4280
  } catch {
4049
4281
  return null;
4050
4282
  }
@@ -4052,7 +4284,7 @@ function readOutboxItem(jsonPath) {
4052
4284
  function readOutboxBody(item) {
4053
4285
  const { outboxDir } = ensurePlanOutboxDirs();
4054
4286
  const bodyFile = path16.join(outboxDir, item.bodyPath);
4055
- return readFileSync8(bodyFile, "utf8");
4287
+ return readFileSync9(bodyFile, "utf8");
4056
4288
  }
4057
4289
  function writeOutboxItem(input, opts) {
4058
4290
  const { outboxDir } = ensurePlanOutboxDirs();
@@ -4106,8 +4338,8 @@ function archiveOutboxItem(item) {
4106
4338
  const bodySrc = path16.join(outboxDir, item.bodyPath);
4107
4339
  const jsonDst = path16.join(archiveDir, `${item.id}.json`);
4108
4340
  const bodyDst = path16.join(archiveDir, item.bodyPath);
4109
- if (existsSync14(jsonSrc)) renameSync(jsonSrc, jsonDst);
4110
- if (existsSync14(bodySrc)) renameSync(bodySrc, bodyDst);
4341
+ if (existsSync15(jsonSrc)) renameSync(jsonSrc, jsonDst);
4342
+ if (existsSync15(bodySrc)) renameSync(bodySrc, bodyDst);
4111
4343
  }
4112
4344
  function outboxItemPaths(item) {
4113
4345
  const { outboxDir } = ensurePlanOutboxDirs();
@@ -4376,10 +4608,12 @@ function readHarnessWorkerContext(decision) {
4376
4608
  const personaSlug = typeof ctx.personaEvidence === "object" && ctx.personaEvidence && typeof ctx.personaEvidence.injectedPersonaSlug === "string" ? ctx.personaEvidence.injectedPersonaSlug : null;
4377
4609
  const personaEvidence = ctx.personaEvidence && typeof ctx.personaEvidence === "object" ? ctx.personaEvidence : null;
4378
4610
  const personaInjectionReady = ctx.personaInjectionReady === true;
4611
+ const memoryQualityCapture = ctx.memoryQualityCapture && typeof ctx.memoryQualityCapture === "object" ? ctx.memoryQualityCapture : null;
4379
4612
  return {
4380
4613
  instructionPolicyMarkdown: markdown,
4381
4614
  instructionPolicyFingerprint: fingerprint,
4382
4615
  instructionPolicyEvidence: evidence,
4616
+ memoryQualityCapture,
4383
4617
  personaMarkdown,
4384
4618
  personaSlug,
4385
4619
  personaEvidence,
@@ -4568,6 +4802,7 @@ async function dispatchRun(args) {
4568
4802
  instructionPolicyMarkdown: harnessContext?.instructionPolicyMarkdown ?? null,
4569
4803
  instructionPolicyFingerprint: harnessContext?.instructionPolicyFingerprint ?? null,
4570
4804
  instructionPolicyEvidence: harnessContext?.instructionPolicyEvidence ?? null,
4805
+ memoryQualityCapture: harnessContext?.memoryQualityCapture ?? null,
4571
4806
  personaMarkdown: harnessContext?.personaMarkdown ?? null,
4572
4807
  personaSlug: harnessContext?.personaSlug ?? expectedPersona,
4573
4808
  personaEvidence: harnessContext?.personaEvidence ?? null,
@@ -4657,8 +4892,175 @@ function redactHarness(text, secret) {
4657
4892
  return out;
4658
4893
  }
4659
4894
 
4895
+ // src/db-credential-resolver.ts
4896
+ import { execFileSync, spawnSync as spawnSync3 } from "node:child_process";
4897
+ import * as fs from "node:fs";
4898
+ import * as path19 from "node:path";
4899
+
4900
+ // src/db-url-hint.ts
4901
+ function safeDatabaseUrlHint(url) {
4902
+ if (!url?.trim()) return "(not set)";
4903
+ try {
4904
+ const u = new URL(url);
4905
+ const name = (u.pathname || "").replace(/^\//, "").split("?")[0] || "(no database name)";
4906
+ return `${u.hostname} / ${name}`;
4907
+ } catch {
4908
+ return "(invalid URL)";
4909
+ }
4910
+ }
4911
+
4912
+ // src/db-credential-resolver.ts
4913
+ var PRODUCTION_ENV_KEYS = [
4914
+ "KYNVER_PRODUCTION_DATABASE_URL",
4915
+ "PRODUCTION_DATABASE_URL"
4916
+ ];
4917
+ var ENV_FILE_KEYS = [...PRODUCTION_ENV_KEYS];
4918
+ function readEnvValue(env, key) {
4919
+ const value = env[key]?.trim();
4920
+ return value || void 0;
4921
+ }
4922
+ function readProductionDbKeysFromEnvFile(envPath) {
4923
+ if (!fs.existsSync(envPath)) return {};
4924
+ const out = {};
4925
+ const allowed = new Set(ENV_FILE_KEYS);
4926
+ for (const line of fs.readFileSync(envPath, "utf8").split("\n")) {
4927
+ const m = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
4928
+ if (!m || !allowed.has(m[1])) continue;
4929
+ const key = m[1];
4930
+ out[key] = m[2].replace(/^["']|["']$/g, "").trim();
4931
+ }
4932
+ return out;
4933
+ }
4934
+ function resolveFromProcessEnv(env) {
4935
+ for (const key of PRODUCTION_ENV_KEYS) {
4936
+ const value = readEnvValue(env, key);
4937
+ if (value) {
4938
+ return {
4939
+ ok: true,
4940
+ databaseUrl: value,
4941
+ source: `env:${key}`,
4942
+ hint: safeDatabaseUrlHint(value)
4943
+ };
4944
+ }
4945
+ }
4946
+ return null;
4947
+ }
4948
+ function resolveFromEnvFile(cwd) {
4949
+ const envPath = path19.join(cwd, ".env");
4950
+ const parsed = readProductionDbKeysFromEnvFile(envPath);
4951
+ for (const key of ENV_FILE_KEYS) {
4952
+ const value = parsed[key]?.trim();
4953
+ if (value) {
4954
+ return {
4955
+ ok: true,
4956
+ databaseUrl: value,
4957
+ source: `env-file:${key}`,
4958
+ hint: safeDatabaseUrlHint(value)
4959
+ };
4960
+ }
4961
+ }
4962
+ return null;
4963
+ }
4964
+ function defaultNeonConnectionString(args) {
4965
+ const neonBin = process.platform === "win32" ? "neon.cmd" : "neon";
4966
+ const argv = [
4967
+ "connection-string",
4968
+ args.branch,
4969
+ "--prisma",
4970
+ "-o",
4971
+ "json",
4972
+ "--no-color",
4973
+ "--no-analytics"
4974
+ ];
4975
+ if (args.projectId) argv.push("--project-id", args.projectId);
4976
+ if (args.databaseName) argv.push("--database-name", args.databaseName);
4977
+ if (args.roleName) argv.push("--role-name", args.roleName);
4978
+ if (args.pooled) argv.push("--pooled");
4979
+ let stdout;
4980
+ try {
4981
+ const result = spawnSync3(neonBin, argv, {
4982
+ encoding: "utf8",
4983
+ env: process.env,
4984
+ timeout: 3e4
4985
+ });
4986
+ if (result.status !== 0) return null;
4987
+ stdout = (result.stdout || "").trim();
4988
+ } catch {
4989
+ try {
4990
+ stdout = execFileSync("neonctl", argv, {
4991
+ encoding: "utf8",
4992
+ env: process.env,
4993
+ timeout: 3e4
4994
+ }).trim();
4995
+ } catch {
4996
+ return null;
4997
+ }
4998
+ }
4999
+ if (!stdout) return null;
5000
+ try {
5001
+ const parsed = JSON.parse(stdout);
5002
+ const fromField = typeof parsed.connection_uri === "string" && parsed.connection_uri || typeof parsed.connection_string === "string" && parsed.connection_string || typeof parsed.connectionString === "string" && parsed.connectionString;
5003
+ if (fromField) return fromField;
5004
+ } catch {
5005
+ }
5006
+ if (/^postgres(ql)?:\/\//i.test(stdout)) return stdout;
5007
+ return null;
5008
+ }
5009
+ function resolveFromNeonCli(env, neonFn) {
5010
+ const branch = readEnvValue(env, "KYNVER_NEON_BRANCH") || readEnvValue(env, "NEON_BRANCH") || "production";
5011
+ const url = neonFn({
5012
+ branch,
5013
+ projectId: readEnvValue(env, "KYNVER_NEON_PROJECT_ID") || readEnvValue(env, "NEON_PROJECT_ID"),
5014
+ databaseName: readEnvValue(env, "KYNVER_NEON_DATABASE_NAME") || readEnvValue(env, "NEON_DATABASE_NAME"),
5015
+ roleName: readEnvValue(env, "KYNVER_NEON_ROLE_NAME") || readEnvValue(env, "NEON_ROLE_NAME"),
5016
+ pooled: readEnvValue(env, "KYNVER_NEON_POOLED") === "1" || readEnvValue(env, "NEON_POOLED") === "1"
5017
+ });
5018
+ if (!url) return null;
5019
+ return {
5020
+ ok: true,
5021
+ databaseUrl: url,
5022
+ source: "neon-cli",
5023
+ hint: safeDatabaseUrlHint(url)
5024
+ };
5025
+ }
5026
+ function resolveProductionDatabaseUrl(options = {}) {
5027
+ const env = options.env ?? process.env;
5028
+ const cwd = options.cwd ?? process.cwd();
5029
+ const neonFn = options.neonConnectionString ?? defaultNeonConnectionString;
5030
+ const attempted = [];
5031
+ const fromNeon = resolveFromNeonCli(env, neonFn);
5032
+ attempted.push("neon-cli");
5033
+ if (fromNeon) return fromNeon;
5034
+ for (const key of PRODUCTION_ENV_KEYS) {
5035
+ attempted.push(`env:${key}`);
5036
+ }
5037
+ const fromEnv = resolveFromProcessEnv(env);
5038
+ if (fromEnv) return fromEnv;
5039
+ for (const key of ENV_FILE_KEYS) {
5040
+ attempted.push(`env-file:${key}`);
5041
+ }
5042
+ const fromFile = resolveFromEnvFile(cwd);
5043
+ if (fromFile) return fromFile;
5044
+ return {
5045
+ ok: false,
5046
+ reason: "No production database credentials found. Authenticate Neon CLI (`neon auth`), set KYNVER_NEON_* / NEON_* project hints, or provide PRODUCTION_DATABASE_URL / KYNVER_PRODUCTION_DATABASE_URL (process env or selective .env keys only).",
5047
+ attempted
5048
+ };
5049
+ }
5050
+ function applyProductionDatabaseToProcess(options = {}) {
5051
+ const resolved = resolveProductionDatabaseUrl(options);
5052
+ if (!resolved.ok) {
5053
+ throw new Error(resolved.reason);
5054
+ }
5055
+ process.env.DATABASE_URL = resolved.databaseUrl;
5056
+ console.error(
5057
+ `[kynver-cli] database target: ${resolved.hint} (source: ${resolved.source})`
5058
+ );
5059
+ return resolved;
5060
+ }
5061
+
4660
5062
  // src/validate.ts
4661
- import path19 from "node:path";
5063
+ import path20 from "node:path";
4662
5064
  var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
4663
5065
  var WORKER_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
4664
5066
  function validateRunId(runId) {
@@ -4672,15 +5074,15 @@ function validateWorkerName(name) {
4672
5074
  return trimmed;
4673
5075
  }
4674
5076
  function validateRepo(repo) {
4675
- const resolved = path19.resolve(repo);
5077
+ const resolved = path20.resolve(repo);
4676
5078
  if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
4677
5079
  return resolved;
4678
5080
  }
4679
5081
  function validateOwnedPaths(repoRoot, ownedPaths) {
4680
5082
  return ownedPaths.map((owned) => {
4681
- const resolved = path19.resolve(repoRoot, owned);
4682
- const rel = path19.relative(repoRoot, resolved);
4683
- if (rel.startsWith("..") || path19.isAbsolute(rel)) {
5083
+ const resolved = path20.resolve(repoRoot, owned);
5084
+ const rel = path20.relative(repoRoot, resolved);
5085
+ if (rel.startsWith("..") || path20.isAbsolute(rel)) {
4684
5086
  throw new Error(`owned path escapes repo: ${owned}`);
4685
5087
  }
4686
5088
  return resolved;
@@ -4692,13 +5094,13 @@ function validateTailLines(lines) {
4692
5094
  }
4693
5095
 
4694
5096
  // src/worktree.ts
4695
- import { existsSync as existsSync15, mkdirSync as mkdirSync5 } from "node:fs";
4696
- import path21 from "node:path";
5097
+ import { existsSync as existsSync17, mkdirSync as mkdirSync5 } from "node:fs";
5098
+ import path22 from "node:path";
4697
5099
 
4698
5100
  // src/default-repo.ts
4699
- import path20 from "node:path";
5101
+ import path21 from "node:path";
4700
5102
  function expandConfiguredRepo(value) {
4701
- return path20.resolve(resolveUserPath(value.trim()));
5103
+ return path21.resolve(resolveUserPath(value.trim()));
4702
5104
  }
4703
5105
  function fromConfigured(value, source, persistedInConfig) {
4704
5106
  const trimmed = value?.trim();
@@ -4732,7 +5134,7 @@ function resolveDefaultRepo(opts = {}) {
4732
5134
  function persistDefaultRepo(repo, existing) {
4733
5135
  const config = {
4734
5136
  ...existing ?? loadUserConfig(),
4735
- defaultRepo: redactHomePath(path20.resolve(repo))
5137
+ defaultRepo: redactHomePath(path21.resolve(repo))
4736
5138
  };
4737
5139
  saveUserConfig(config);
4738
5140
  return config;
@@ -4778,7 +5180,7 @@ function createRun(args) {
4778
5180
  ensureGitRepo(repo);
4779
5181
  const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
4780
5182
  const dir = runDirectory(id);
4781
- if (existsSync15(dir)) failExists(`run already exists: ${id}`);
5183
+ if (existsSync17(dir)) failExists(`run already exists: ${id}`);
4782
5184
  mkdirSync5(dir, { recursive: true });
4783
5185
  const base = String(args.base || "origin/main");
4784
5186
  const baseCommit = git(repo, ["rev-parse", base]).trim();
@@ -4792,12 +5194,12 @@ function createRun(args) {
4792
5194
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4793
5195
  workers: {}
4794
5196
  };
4795
- writeJson(path21.join(dir, "run.json"), run);
5197
+ writeJson(path22.join(dir, "run.json"), run);
4796
5198
  console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
4797
5199
  }
4798
5200
  function listRuns() {
4799
5201
  const { runsDir } = getPaths();
4800
- const rows = listRunIds(runsDir).map((id) => readJson(path21.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
5202
+ const rows = listRunIds(runsDir).map((id) => readJson(path22.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
4801
5203
  id: run.id,
4802
5204
  name: run.name,
4803
5205
  status: run.status,
@@ -4812,7 +5214,7 @@ function failExists(message) {
4812
5214
  }
4813
5215
 
4814
5216
  // src/sweep.ts
4815
- import path22 from "node:path";
5217
+ import path23 from "node:path";
4816
5218
  async function sweepRun(args) {
4817
5219
  const pipeline = args.pipeline === true || args.pipeline === "true";
4818
5220
  try {
@@ -4825,7 +5227,7 @@ async function sweepRun(args) {
4825
5227
  const releasedLocalOrphans = [];
4826
5228
  for (const name of Object.keys(run.workers || {})) {
4827
5229
  const worker = readJson(
4828
- path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
5230
+ path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4829
5231
  void 0
4830
5232
  );
4831
5233
  if (!worker || !worker.dispatched || !worker.taskId) continue;
@@ -4867,69 +5269,182 @@ async function sweepRun(args) {
4867
5269
  }
4868
5270
  }
4869
5271
 
4870
- // src/cli.ts
4871
- import { mkdirSync as mkdirSync7, realpathSync } from "node:fs";
4872
- import { fileURLToPath as fileURLToPath5 } from "node:url";
4873
-
4874
- // src/pipeline-tick.ts
4875
- import path32 from "node:path";
5272
+ // src/harness-storage-snapshot.ts
5273
+ import { existsSync as existsSync19, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
5274
+ import path25 from "node:path";
4876
5275
 
4877
- // src/pipeline-dispatch.ts
4878
- var RESERVED_REVIEW_STARTS = 1;
4879
- function countDispatchStarts(result) {
4880
- if (!result || typeof result !== "object") return 0;
4881
- const startedCount = result.startedCount;
4882
- if (typeof startedCount === "number") return startedCount;
4883
- const outcomes = result.outcomes;
4884
- if (!Array.isArray(outcomes)) return 0;
4885
- return outcomes.filter((o) => o.started).length;
4886
- }
4887
- function stripCliMaxStarts(args) {
4888
- const { maxStarts: _maxStarts, ...rest } = args;
4889
- return rest;
5276
+ // src/cleanup-dir-size.ts
5277
+ import { existsSync as existsSync18, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
5278
+ import path24 from "node:path";
5279
+ function directorySizeBytes(root, maxEntries = 5e4) {
5280
+ if (!existsSync18(root)) return 0;
5281
+ let total = 0;
5282
+ let seen = 0;
5283
+ const stack = [root];
5284
+ while (stack.length > 0) {
5285
+ const current = stack.pop();
5286
+ let entries;
5287
+ try {
5288
+ entries = readdirSync5(current);
5289
+ } catch {
5290
+ continue;
5291
+ }
5292
+ for (const name of entries) {
5293
+ if (seen++ > maxEntries) return null;
5294
+ const full = path24.join(current, name);
5295
+ let st;
5296
+ try {
5297
+ st = statSync2(full);
5298
+ } catch {
5299
+ continue;
5300
+ }
5301
+ if (st.isDirectory()) stack.push(full);
5302
+ else total += st.size;
5303
+ }
5304
+ }
5305
+ return total;
4890
5306
  }
4891
- async function runPipelineDispatch(args, slots) {
4892
- if (slots <= 0) {
4893
- return { ok: true, skipped: true, reason: "no slots", maxStarts: 0, startedCount: 0 };
5307
+
5308
+ // src/harness-storage-snapshot.ts
5309
+ function harnessStorageSnapshot(opts = {}) {
5310
+ const harnessRoot = opts.harnessRoot ?? resolveHarnessRoot();
5311
+ const worktreesDir = path25.join(harnessRoot, "worktrees");
5312
+ const now = opts.now ?? Date.now();
5313
+ const scannedAt = new Date(now).toISOString();
5314
+ if (!existsSync19(worktreesDir)) {
5315
+ return {
5316
+ harnessRoot,
5317
+ worktreesDir,
5318
+ worktreesBytes: 0,
5319
+ runCount: 0,
5320
+ workerCount: 0,
5321
+ oldestRunAt: null,
5322
+ scannedAt
5323
+ };
4894
5324
  }
4895
- const base = stripCliMaxStarts(args);
4896
- const reviewBudget = Math.min(slots, RESERVED_REVIEW_STARTS);
4897
- const workBudget = Math.max(0, slots - reviewBudget);
4898
- const review = await dispatchRun({
4899
- ...base,
4900
- execute: true,
4901
- pipeline: true,
4902
- lane: "review",
4903
- maxStarts: String(reviewBudget)
4904
- });
4905
- const reviewStarted = countDispatchStarts(review);
4906
- const workSlots = workBudget + (reviewBudget - reviewStarted);
4907
- if (workSlots <= 0) {
5325
+ let totalBytes = 0;
5326
+ let runCount = 0;
5327
+ let workerCount = 0;
5328
+ let oldestMs = null;
5329
+ let entries;
5330
+ try {
5331
+ entries = readdirSync6(worktreesDir, { withFileTypes: true });
5332
+ } catch {
4908
5333
  return {
4909
- ...typeof review === "object" && review !== null ? review : {},
4910
- passes: { review },
4911
- startedCount: reviewStarted
5334
+ harnessRoot,
5335
+ worktreesDir,
5336
+ worktreesBytes: null,
5337
+ runCount: 0,
5338
+ workerCount: 0,
5339
+ oldestRunAt: null,
5340
+ scannedAt
4912
5341
  };
4913
5342
  }
4914
- const work = await dispatchRun({
4915
- ...base,
4916
- execute: true,
4917
- pipeline: true,
4918
- maxStarts: String(workSlots)
4919
- });
4920
- const workStarted = countDispatchStarts(work);
5343
+ for (const runEntry of entries) {
5344
+ if (!runEntry.isDirectory()) continue;
5345
+ runCount += 1;
5346
+ const runPath = path25.join(worktreesDir, runEntry.name);
5347
+ try {
5348
+ const st = statSync3(runPath);
5349
+ oldestMs = oldestMs === null ? st.mtimeMs : Math.min(oldestMs, st.mtimeMs);
5350
+ } catch {
5351
+ }
5352
+ try {
5353
+ for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
5354
+ if (workerEntry.isDirectory()) workerCount += 1;
5355
+ }
5356
+ } catch {
5357
+ }
5358
+ if (totalBytes !== null && opts.perRunEntryCap !== null) {
5359
+ const cap = opts.perRunEntryCap ?? 5e4;
5360
+ if (cap <= 0) {
5361
+ totalBytes = null;
5362
+ } else {
5363
+ const runBytes = directorySizeBytes(runPath, cap);
5364
+ if (runBytes === null) totalBytes = null;
5365
+ else totalBytes += runBytes;
5366
+ }
5367
+ }
5368
+ }
4921
5369
  return {
4922
- passes: { review, work },
4923
- startedCount: reviewStarted + workStarted,
4924
- ok: true
5370
+ harnessRoot,
5371
+ worktreesDir,
5372
+ worktreesBytes: totalBytes,
5373
+ runCount,
5374
+ workerCount,
5375
+ oldestRunAt: oldestMs === null ? null : new Date(oldestMs).toISOString(),
5376
+ scannedAt
4925
5377
  };
4926
5378
  }
4927
5379
 
4928
- // src/stale-reconcile.ts
4929
- import path24 from "node:path";
5380
+ // src/cleanup-orphan-safety.ts
5381
+ import { existsSync as existsSync20, statSync as statSync4 } from "node:fs";
5382
+ import path26 from "node:path";
5383
+
5384
+ // src/cleanup-guards-helpers.ts
5385
+ function materialWorktreeChanges2(changedFiles) {
5386
+ return changedFiles.filter((line) => {
5387
+ const trimmed = line.trim();
5388
+ const pathPart = trimmed.startsWith("??") ? trimmed.slice(2).trim() : trimmed.length > 3 ? trimmed.slice(3).trim() : trimmed;
5389
+ return pathPart !== "node_modules" && !pathPart.startsWith("node_modules/");
5390
+ });
5391
+ }
5392
+
5393
+ // src/cleanup-orphan-safety.ts
5394
+ var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
5395
+ function assessOrphanWorktreeSafety(input) {
5396
+ const now = input.now ?? Date.now();
5397
+ const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
5398
+ if (!existsSync20(input.worktreePath)) return null;
5399
+ if (input.runId && input.workerName) {
5400
+ const heartbeatPath = path26.join(
5401
+ input.harnessRoot,
5402
+ "runs",
5403
+ input.runId,
5404
+ "workers",
5405
+ input.workerName,
5406
+ "heartbeat.jsonl"
5407
+ );
5408
+ try {
5409
+ const mtime = statSync4(heartbeatPath).mtimeMs;
5410
+ if (now - mtime < heartbeatFreshMs) return "active_worker";
5411
+ } catch {
5412
+ }
5413
+ }
5414
+ const gitDir = path26.join(input.worktreePath, ".git");
5415
+ if (!existsSync20(gitDir)) return null;
5416
+ const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
5417
+ if (porcelain.status !== 0) return "pr_or_unmerged_commits";
5418
+ const dirtyLines = porcelain.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
5419
+ if (materialWorktreeChanges2(dirtyLines).length > 0) return "dirty_worktree";
5420
+ const upstreamAhead = gitCapture(input.worktreePath, [
5421
+ "rev-list",
5422
+ "--count",
5423
+ "@{u}..HEAD"
5424
+ ]);
5425
+ if (upstreamAhead.status === 0) {
5426
+ const count = Number(upstreamAhead.stdout.trim());
5427
+ if (Number.isFinite(count) && count > 0) return "pr_or_unmerged_commits";
5428
+ }
5429
+ const mainAhead = gitCapture(input.worktreePath, [
5430
+ "rev-list",
5431
+ "--count",
5432
+ "origin/main..HEAD"
5433
+ ]);
5434
+ if (mainAhead.status !== 0) {
5435
+ if (upstreamAhead.status !== 0) return "pr_or_unmerged_commits";
5436
+ return null;
5437
+ }
5438
+ const mainCount = Number(mainAhead.stdout.trim());
5439
+ if (Number.isFinite(mainCount) && mainCount > 0) return "pr_or_unmerged_commits";
5440
+ return null;
5441
+ }
5442
+
5443
+ // src/cleanup.ts
5444
+ import path31 from "node:path";
4930
5445
 
4931
5446
  // src/finalize.ts
4932
- import path23 from "node:path";
5447
+ import path27 from "node:path";
4933
5448
  var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set([
4934
5449
  "running",
4935
5450
  "dispatching",
@@ -4947,7 +5462,7 @@ function deriveTerminalRunStatus(run) {
4947
5462
  let anyLandingBlocked = false;
4948
5463
  for (const name of names) {
4949
5464
  const worker = readJson(
4950
- path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
5465
+ path27.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4951
5466
  void 0
4952
5467
  );
4953
5468
  if (!worker) continue;
@@ -4986,188 +5501,6 @@ function finalizeStaleRuns() {
4986
5501
  return finalized;
4987
5502
  }
4988
5503
 
4989
- // src/stale-reconcile.ts
4990
- var STALE_RECONCILE_HEARTBEAT_MS = 15 * 60 * 1e3;
4991
- function staleReconcileDisabled() {
4992
- return process.env.KYNVER_NO_STALE_CLEANUP === "1";
4993
- }
4994
- function reconcileStaleWorkers() {
4995
- if (staleReconcileDisabled()) {
4996
- return { workers: [], finalizedRuns: finalizeStaleRuns() };
4997
- }
4998
- const outcomes = [];
4999
- const now = Date.now();
5000
- for (const run of listRunRecords()) {
5001
- for (const name of Object.keys(run.workers || {})) {
5002
- const workerPath = path24.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
5003
- const worker = readJson(workerPath, void 0);
5004
- if (!worker || worker.status !== "running") {
5005
- outcomes.push({
5006
- runId: run.id,
5007
- worker: name,
5008
- action: "skipped",
5009
- reason: worker ? `worker status is ${worker.status}` : "worker.json missing"
5010
- });
5011
- continue;
5012
- }
5013
- const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
5014
- if (status.finalResult) {
5015
- if (worker.status === "running") {
5016
- const nextStatus = status.attention.state === "blocked" ? "blocked" : status.attention.state === "done" || status.status === "done" ? "done" : "exited";
5017
- worker.status = nextStatus;
5018
- worker.reconciledAt = (/* @__PURE__ */ new Date()).toISOString();
5019
- worker.reconcileReason = "synced finished worker record after terminal stdout/heartbeat";
5020
- saveWorker(run.id, worker);
5021
- outcomes.push({
5022
- runId: run.id,
5023
- worker: name,
5024
- action: "marked_exited",
5025
- reason: worker.reconcileReason
5026
- });
5027
- } else {
5028
- outcomes.push({ runId: run.id, worker: name, action: "skipped", reason: "final result present" });
5029
- }
5030
- continue;
5031
- }
5032
- if (!status.alive) {
5033
- const nextStatus = status.attention.state === "blocked" ? "blocked" : status.status === "done" ? "done" : "exited";
5034
- worker.status = nextStatus;
5035
- worker.reconciledAt = (/* @__PURE__ */ new Date()).toISOString();
5036
- worker.reconcileReason = status.attention.reason;
5037
- saveWorker(run.id, worker);
5038
- outcomes.push({
5039
- runId: run.id,
5040
- worker: name,
5041
- action: "marked_exited",
5042
- reason: status.attention.reason
5043
- });
5044
- continue;
5045
- }
5046
- if (status.attention.state === "stale" && worker.pid && isPidAlive(worker.pid)) {
5047
- const hbMs = status.lastHeartbeatAt ? Date.parse(status.lastHeartbeatAt) : NaN;
5048
- const actMs = status.lastActivityAt ? Date.parse(status.lastActivityAt) : NaN;
5049
- const hbStale = !Number.isFinite(hbMs) || now - hbMs > STALE_RECONCILE_HEARTBEAT_MS;
5050
- const actStale = Number.isFinite(actMs) && now - actMs > STALE_MS;
5051
- if (hbStale && actStale) {
5052
- killWorkerProcess(worker.pid, "SIGTERM");
5053
- worker.status = "exited";
5054
- worker.reconciledAt = (/* @__PURE__ */ new Date()).toISOString();
5055
- worker.reconcileReason = `reconciled stale worker: ${status.attention.reason}`;
5056
- saveWorker(run.id, worker);
5057
- outcomes.push({
5058
- runId: run.id,
5059
- worker: name,
5060
- action: "killed_stale",
5061
- reason: status.attention.reason
5062
- });
5063
- continue;
5064
- }
5065
- }
5066
- outcomes.push({
5067
- runId: run.id,
5068
- worker: name,
5069
- action: "skipped",
5070
- reason: status.attention.reason
5071
- });
5072
- }
5073
- }
5074
- return { workers: outcomes, finalizedRuns: finalizeStaleRuns() };
5075
- }
5076
- function reconcileRunsCli() {
5077
- const result = reconcileStaleWorkers();
5078
- const markedExited = result.workers.filter((w) => w.action === "marked_exited").length;
5079
- const killedStale = result.workers.filter((w) => w.action === "killed_stale").length;
5080
- const skipped = result.workers.filter((w) => w.action === "skipped").length;
5081
- console.log(
5082
- JSON.stringify(
5083
- {
5084
- ok: true,
5085
- workers: { markedExited, killedStale, skipped, total: result.workers.length },
5086
- finalizedRuns: result.finalizedRuns.length,
5087
- details: { workers: result.workers, finalizedRuns: result.finalizedRuns }
5088
- },
5089
- null,
5090
- 2
5091
- )
5092
- );
5093
- }
5094
-
5095
- // src/plan-progress-daemon-sync.ts
5096
- import path25 from "node:path";
5097
-
5098
- // src/plan-progress-sync.ts
5099
- async function syncPlanProgress(args) {
5100
- const base = resolveBaseUrl(args.baseUrl);
5101
- const secret = await resolveCallbackSecretWithMint(args.secret, args.agentOsId, { baseUrl: base });
5102
- const url = `${base}/api/agent-os/by-id/${encodeURIComponent(args.agentOsId)}/tasks/${encodeURIComponent(args.taskId)}/plan-progress-sync`;
5103
- const res = await postJsonWithCredentialRefresh(url, secret, {
5104
- phase: args.phase,
5105
- taskId: args.taskId,
5106
- blocker: args.blocker,
5107
- artifact: args.artifact
5108
- }, { agentOsId: args.agentOsId, baseUrl: base });
5109
- return { ok: res.ok, status: res.status, response: res.response };
5110
- }
5111
-
5112
- // src/plan-progress-daemon-sync.ts
5113
- async function syncActiveWorkerPlanProgress(runId, args) {
5114
- const run = loadRun(runId);
5115
- const agentOsId = String(args.agentOsId || "");
5116
- if (!agentOsId) return [];
5117
- const outcomes = [];
5118
- for (const name of Object.keys(run.workers || {})) {
5119
- const worker = readJson(
5120
- path25.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
5121
- void 0
5122
- );
5123
- if (!worker?.dispatched || !worker.taskId) continue;
5124
- const status = computeWorkerStatus(worker);
5125
- if (!status.heartbeatBlocker) continue;
5126
- if (worker.lastSyncedHeartbeatBlocker === status.heartbeatBlocker) {
5127
- outcomes.push({ worker: name, phase: "heartbeat_blocker", ok: true, skipped: true });
5128
- continue;
5129
- }
5130
- const res = await syncPlanProgress({
5131
- agentOsId,
5132
- taskId: worker.taskId,
5133
- phase: "heartbeat_blocker",
5134
- blocker: status.heartbeatBlocker,
5135
- baseUrl: args.baseUrl ? String(args.baseUrl) : void 0,
5136
- secret: args.secret ? String(args.secret) : void 0
5137
- });
5138
- if (res.ok) {
5139
- worker.lastSyncedHeartbeatBlocker = status.heartbeatBlocker;
5140
- saveWorker(run.id, worker);
5141
- }
5142
- outcomes.push({ worker: name, phase: "heartbeat_blocker", ok: res.ok });
5143
- }
5144
- return outcomes;
5145
- }
5146
-
5147
- // src/workspace-runtime-config.ts
5148
- async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
5149
- const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
5150
- const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, { baseUrl: base });
5151
- const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/runtime`;
5152
- try {
5153
- const res = await getJson(url, secret);
5154
- if (!res.ok) return null;
5155
- const body = res.response;
5156
- const raw = body?.preferences?.maxConcurrentWorkers;
5157
- if (raw === null || raw === void 0) {
5158
- return { maxConcurrentWorkers: null };
5159
- }
5160
- const n = Number(raw);
5161
- if (!Number.isFinite(n) || n <= 0) return { maxConcurrentWorkers: null };
5162
- return { maxConcurrentWorkers: Math.floor(n) };
5163
- } catch {
5164
- return null;
5165
- }
5166
- }
5167
-
5168
- // src/cleanup.ts
5169
- import path30 from "node:path";
5170
-
5171
5504
  // src/cleanup-run-liveness.ts
5172
5505
  function isWorkerProcessLive(indexed) {
5173
5506
  if (indexed.status.alive) return true;
@@ -5209,29 +5542,25 @@ function isPrOrUnmergedWork(status) {
5209
5542
  if (status.changedFiles.length > 0 && status.finalResult) return true;
5210
5543
  return false;
5211
5544
  }
5212
- function materialWorktreeChanges(changedFiles) {
5213
- return changedFiles.filter((line) => {
5214
- const trimmed = line.trim();
5215
- const pathPart = trimmed.startsWith("??") ? trimmed.slice(2).trim() : trimmed.length > 3 ? trimmed.slice(3).trim() : trimmed;
5216
- return pathPart !== "node_modules" && !pathPart.startsWith("node_modules/");
5217
- });
5218
- }
5219
5545
  function hasUnrestorableWorktreeChanges(status) {
5220
- if (materialWorktreeChanges(status.changedFiles).length > 0) return true;
5546
+ if (materialWorktreeChanges2(status.changedFiles).length > 0) return true;
5221
5547
  if (status.gitAncestry?.relation === "diverged") return true;
5222
5548
  return false;
5223
5549
  }
5224
5550
  function skipWorktreeRemoval(input) {
5225
- const { indexed, includeOrphans, worktreesAgeMs, ageMs } = input;
5226
- if (worktreesAgeMs <= 0) return "worktrees_disabled";
5227
- if (ageMs < worktreesAgeMs) return "below_age_threshold";
5228
- if (!indexed) return includeOrphans ? null : "orphan_without_flag";
5551
+ const { indexed, includeOrphans, worktreesAgeMs, ageMs, orphanSafety, worktreeRemovalGuard } = input;
5552
+ if (!indexed) {
5553
+ if (!includeOrphans) return "orphan_without_flag";
5554
+ return orphanSafety ?? null;
5555
+ }
5556
+ if (worktreesAgeMs <= 0 && !includeOrphans) return "worktrees_disabled";
5557
+ if (worktreesAgeMs > 0 && ageMs < worktreesAgeMs) return "below_age_threshold";
5229
5558
  if (isWorkerProcessLive(indexed)) return "active_worker";
5230
5559
  if (indexed.worker.completionBlocker) return "completion_blocked";
5231
5560
  if (runBlocksWorktreeRemoval(indexed)) return "run_still_active";
5232
5561
  if (!isFinishedWorkerStatus(indexed.status)) return "run_still_active";
5233
5562
  if (isPrOrUnmergedWork(indexed.status)) return "pr_or_unmerged_commits";
5234
- if (materialWorktreeChanges(indexed.status.changedFiles).length > 0) return "dirty_worktree";
5563
+ if (materialWorktreeChanges2(indexed.status.changedFiles).length > 0) return "dirty_worktree";
5235
5564
  const landing = assessWorkerLanding({
5236
5565
  finalResult: indexed.status.finalResult,
5237
5566
  changedFiles: indexed.status.changedFiles,
@@ -5239,6 +5568,17 @@ function skipWorktreeRemoval(input) {
5239
5568
  prUrl: prUrlFromFinalResult(indexed.status.finalResult)
5240
5569
  });
5241
5570
  if (landing.blocked) return "landing_blocked";
5571
+ if (worktreeRemovalGuard && input.worktreePath) {
5572
+ const overlay = worktreeRemovalGuard({
5573
+ worktreePath: input.worktreePath,
5574
+ indexed: Boolean(indexed),
5575
+ runId: indexed?.runId,
5576
+ worker: indexed?.workerName
5577
+ });
5578
+ if (overlay) {
5579
+ return overlay.detail ? { reason: overlay.reason, detail: overlay.detail } : overlay.reason;
5580
+ }
5581
+ }
5242
5582
  return null;
5243
5583
  }
5244
5584
  function skipNodeModulesRemoval(input) {
@@ -5255,51 +5595,17 @@ function skipNodeModulesRemoval(input) {
5255
5595
  gitAncestry: indexed.status.gitAncestry,
5256
5596
  prUrl: prUrlFromFinalResult(indexed.status.finalResult)
5257
5597
  });
5258
- if (landing.blocked && materialWorktreeChanges(indexed.status.changedFiles).length > 0) {
5598
+ if (landing.blocked && materialWorktreeChanges2(indexed.status.changedFiles).length > 0) {
5259
5599
  return "landing_blocked";
5260
5600
  }
5261
5601
  return null;
5262
5602
  }
5263
5603
 
5264
5604
  // src/cleanup-execute.ts
5265
- import { existsSync as existsSync17, rmSync } from "node:fs";
5266
- import path27 from "node:path";
5267
-
5268
- // src/cleanup-dir-size.ts
5269
- import { existsSync as existsSync16, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
5270
- import path26 from "node:path";
5271
- function directorySizeBytes(root, maxEntries = 5e4) {
5272
- if (!existsSync16(root)) return 0;
5273
- let total = 0;
5274
- let seen = 0;
5275
- const stack = [root];
5276
- while (stack.length > 0) {
5277
- const current = stack.pop();
5278
- let entries;
5279
- try {
5280
- entries = readdirSync5(current);
5281
- } catch {
5282
- continue;
5283
- }
5284
- for (const name of entries) {
5285
- if (seen++ > maxEntries) return null;
5286
- const full = path26.join(current, name);
5287
- let st;
5288
- try {
5289
- st = statSync2(full);
5290
- } catch {
5291
- continue;
5292
- }
5293
- if (st.isDirectory()) stack.push(full);
5294
- else total += st.size;
5295
- }
5296
- }
5297
- return total;
5298
- }
5299
-
5300
- // src/cleanup-execute.ts
5605
+ import { existsSync as existsSync21, rmSync } from "node:fs";
5606
+ import path28 from "node:path";
5301
5607
  function removeNodeModules(candidate, execute) {
5302
- if (!existsSync17(candidate.path)) {
5608
+ if (!existsSync21(candidate.path)) {
5303
5609
  return {
5304
5610
  ...candidate,
5305
5611
  executed: false,
@@ -5330,7 +5636,7 @@ function removeNodeModules(candidate, execute) {
5330
5636
  }
5331
5637
  }
5332
5638
  function removeWorktree(candidate, execute) {
5333
- if (!existsSync17(candidate.path)) {
5639
+ if (!existsSync21(candidate.path)) {
5334
5640
  return {
5335
5641
  ...candidate,
5336
5642
  executed: false,
@@ -5347,7 +5653,7 @@ function removeWorktree(candidate, execute) {
5347
5653
  if (repo) {
5348
5654
  git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
5349
5655
  }
5350
- if (existsSync17(candidate.path)) {
5656
+ if (existsSync21(candidate.path)) {
5351
5657
  rmSync(candidate.path, { recursive: true, force: true });
5352
5658
  }
5353
5659
  return {
@@ -5367,40 +5673,40 @@ function removeWorktree(candidate, execute) {
5367
5673
  }
5368
5674
  }
5369
5675
  function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
5370
- const resolved = path27.resolve(targetPath);
5371
- const nm = resolved.endsWith(`${path27.sep}node_modules`) ? resolved : null;
5676
+ const resolved = path28.resolve(targetPath);
5677
+ const nm = resolved.endsWith(`${path28.sep}node_modules`) ? resolved : null;
5372
5678
  if (!nm) return "path_outside_harness";
5373
- const rel = path27.relative(worktreesDir, nm);
5374
- if (rel.startsWith("..") || path27.isAbsolute(rel)) return "path_outside_harness";
5375
- const parts = rel.split(path27.sep);
5679
+ const rel = path28.relative(worktreesDir, nm);
5680
+ if (rel.startsWith("..") || path28.isAbsolute(rel)) return "path_outside_harness";
5681
+ const parts = rel.split(path28.sep);
5376
5682
  if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
5377
- if (!resolved.startsWith(path27.resolve(harnessRoot))) return "path_outside_harness";
5683
+ if (!resolved.startsWith(path28.resolve(harnessRoot))) return "path_outside_harness";
5378
5684
  return null;
5379
5685
  }
5380
5686
 
5381
5687
  // src/cleanup-scan.ts
5382
- import { existsSync as existsSync18, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
5383
- import path28 from "node:path";
5688
+ import { existsSync as existsSync22, readdirSync as readdirSync7, statSync as statSync5 } from "node:fs";
5689
+ import path29 from "node:path";
5384
5690
  function pathAgeMs(target, now) {
5385
5691
  try {
5386
- const mtime = statSync3(target).mtimeMs;
5692
+ const mtime = statSync5(target).mtimeMs;
5387
5693
  return Math.max(0, now - mtime);
5388
5694
  } catch {
5389
5695
  return 0;
5390
5696
  }
5391
5697
  }
5392
5698
  function isPathInside(child, parent) {
5393
- const rel = path28.relative(parent, child);
5394
- return rel === "" || !rel.startsWith("..") && !path28.isAbsolute(rel);
5699
+ const rel = path29.relative(parent, child);
5700
+ return rel === "" || !rel.startsWith("..") && !path29.isAbsolute(rel);
5395
5701
  }
5396
5702
  function scanNodeModulesCandidates(opts) {
5397
5703
  const candidates = [];
5398
5704
  const seen = /* @__PURE__ */ new Set();
5399
5705
  for (const entry of opts.index.values()) {
5400
5706
  if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
5401
- const nm = path28.join(entry.worktreePath, "node_modules");
5402
- if (!existsSync18(nm)) continue;
5403
- const resolved = path28.resolve(nm);
5707
+ const nm = path29.join(entry.worktreePath, "node_modules");
5708
+ if (!existsSync22(nm)) continue;
5709
+ const resolved = path29.resolve(nm);
5404
5710
  if (seen.has(resolved)) continue;
5405
5711
  seen.add(resolved);
5406
5712
  candidates.push({
@@ -5413,16 +5719,16 @@ function scanNodeModulesCandidates(opts) {
5413
5719
  ageMs: pathAgeMs(resolved, opts.now)
5414
5720
  });
5415
5721
  }
5416
- if (!opts.includeOrphans || !existsSync18(opts.worktreesDir)) return candidates;
5417
- for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
5722
+ if (!opts.includeOrphans || !existsSync22(opts.worktreesDir)) return candidates;
5723
+ for (const runEntry of readdirSync7(opts.worktreesDir, { withFileTypes: true })) {
5418
5724
  if (!runEntry.isDirectory()) continue;
5419
- const runPath = path28.join(opts.worktreesDir, runEntry.name);
5420
- for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
5725
+ const runPath = path29.join(opts.worktreesDir, runEntry.name);
5726
+ for (const workerEntry of readdirSync7(runPath, { withFileTypes: true })) {
5421
5727
  if (!workerEntry.isDirectory()) continue;
5422
- const worktreePath = path28.join(runPath, workerEntry.name);
5423
- const nm = path28.join(worktreePath, "node_modules");
5424
- if (!existsSync18(nm)) continue;
5425
- const resolved = path28.resolve(nm);
5728
+ const worktreePath = path29.join(runPath, workerEntry.name);
5729
+ const nm = path29.join(worktreePath, "node_modules");
5730
+ if (!existsSync22(nm)) continue;
5731
+ const resolved = path29.resolve(nm);
5426
5732
  if (seen.has(resolved)) continue;
5427
5733
  if (!isPathInside(resolved, opts.harnessRoot)) continue;
5428
5734
  seen.add(resolved);
@@ -5439,40 +5745,76 @@ function scanNodeModulesCandidates(opts) {
5439
5745
  return candidates;
5440
5746
  }
5441
5747
  function scanWorktreeCandidates(opts) {
5442
- if (opts.worktreesAgeMs <= 0) return [];
5748
+ const indexedEnabled = opts.worktreesAgeMs > 0 || opts.includeOrphans;
5749
+ const orphanEnabled = opts.includeOrphans;
5750
+ if (!indexedEnabled && !orphanEnabled) return [];
5443
5751
  const candidates = [];
5444
5752
  const seen = /* @__PURE__ */ new Set();
5753
+ if (indexedEnabled) {
5754
+ for (const entry of opts.index.values()) {
5755
+ if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
5756
+ const resolved = entry.worktreePath;
5757
+ if (!existsSync22(resolved)) continue;
5758
+ if (seen.has(resolved)) continue;
5759
+ seen.add(resolved);
5760
+ candidates.push({
5761
+ kind: "remove_worktree",
5762
+ path: resolved,
5763
+ bytes: null,
5764
+ runId: entry.runId,
5765
+ worker: entry.workerName,
5766
+ repo: entry.run.repo,
5767
+ ageMs: pathAgeMs(resolved, opts.now)
5768
+ });
5769
+ }
5770
+ }
5771
+ if (!orphanEnabled || !existsSync22(opts.worktreesDir)) return candidates;
5772
+ const indexedPaths = /* @__PURE__ */ new Set();
5445
5773
  for (const entry of opts.index.values()) {
5446
- if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
5447
- const resolved = entry.worktreePath;
5448
- if (!existsSync18(resolved)) continue;
5449
- if (seen.has(resolved)) continue;
5450
- seen.add(resolved);
5451
- candidates.push({
5452
- kind: "remove_worktree",
5453
- path: resolved,
5454
- bytes: null,
5455
- runId: entry.runId,
5456
- worker: entry.workerName,
5457
- repo: entry.run.repo,
5458
- ageMs: pathAgeMs(resolved, opts.now)
5459
- });
5774
+ indexedPaths.add(path29.resolve(entry.worktreePath));
5775
+ }
5776
+ for (const runEntry of readdirSync7(opts.worktreesDir, { withFileTypes: true })) {
5777
+ if (!runEntry.isDirectory()) continue;
5778
+ if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
5779
+ const runPath = path29.join(opts.worktreesDir, runEntry.name);
5780
+ let workerEntries;
5781
+ try {
5782
+ workerEntries = readdirSync7(runPath, { withFileTypes: true });
5783
+ } catch {
5784
+ continue;
5785
+ }
5786
+ for (const workerEntry of workerEntries) {
5787
+ if (!workerEntry.isDirectory()) continue;
5788
+ const worktreePath = path29.resolve(path29.join(runPath, workerEntry.name));
5789
+ if (seen.has(worktreePath)) continue;
5790
+ if (indexedPaths.has(worktreePath)) continue;
5791
+ if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
5792
+ seen.add(worktreePath);
5793
+ candidates.push({
5794
+ kind: "remove_worktree",
5795
+ path: worktreePath,
5796
+ bytes: null,
5797
+ runId: runEntry.name,
5798
+ worker: workerEntry.name,
5799
+ ageMs: pathAgeMs(worktreePath, opts.now)
5800
+ });
5801
+ }
5460
5802
  }
5461
5803
  return candidates;
5462
5804
  }
5463
5805
 
5464
5806
  // src/cleanup-worktree-index.ts
5465
- import path29 from "node:path";
5807
+ import path30 from "node:path";
5466
5808
  function buildWorktreeIndex() {
5467
5809
  const index = /* @__PURE__ */ new Map();
5468
5810
  for (const run of listRunRecords()) {
5469
5811
  for (const name of Object.keys(run.workers || {})) {
5470
- const workerPath = path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
5812
+ const workerPath = path30.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
5471
5813
  const worker = readJson(workerPath, void 0);
5472
5814
  if (!worker?.worktreePath) continue;
5473
5815
  const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
5474
- index.set(path29.resolve(worker.worktreePath), {
5475
- worktreePath: path29.resolve(worker.worktreePath),
5816
+ index.set(path30.resolve(worker.worktreePath), {
5817
+ worktreePath: path30.resolve(worker.worktreePath),
5476
5818
  runId: run.id,
5477
5819
  workerName: name,
5478
5820
  run,
@@ -5518,157 +5860,465 @@ function resolveHarnessRetention(options = {}) {
5518
5860
  accountBytes
5519
5861
  };
5520
5862
  }
5521
- function resolvePipelineHarnessRetention(runId) {
5522
- const scopeAll = process.env.KYNVER_CLEANUP_SCOPE === "all";
5523
- const envWorktrees = Number(process.env.KYNVER_CLEANUP_WORKTREES_AGE_MS);
5524
- const worktreesAgeMs = scopeAll ? Number.isFinite(envWorktrees) && envWorktrees > 0 ? envWorktrees : DEFAULT_WORKTREES_AGE_MS : Number.isFinite(envWorktrees) && envWorktrees > 0 ? envWorktrees : 0;
5525
- return resolveHarnessRetention({
5526
- runIdFilter: scopeAll ? void 0 : runId,
5527
- worktreesAgeMs,
5528
- finalizeStaleRuns: true,
5529
- accountBytes: true
5530
- });
5863
+ function resolvePipelineHarnessRetention(runId) {
5864
+ const scopeAll = process.env.KYNVER_CLEANUP_SCOPE === "all";
5865
+ const envWorktrees = Number(process.env.KYNVER_CLEANUP_WORKTREES_AGE_MS);
5866
+ const worktreesAgeMs = scopeAll ? Number.isFinite(envWorktrees) && envWorktrees > 0 ? envWorktrees : DEFAULT_WORKTREES_AGE_MS : Number.isFinite(envWorktrees) && envWorktrees > 0 ? envWorktrees : 0;
5867
+ return resolveHarnessRetention({
5868
+ runIdFilter: scopeAll ? void 0 : runId,
5869
+ worktreesAgeMs,
5870
+ finalizeStaleRuns: true,
5871
+ accountBytes: true
5872
+ });
5873
+ }
5874
+
5875
+ // src/cleanup.ts
5876
+ function resolvePaths(options = {}) {
5877
+ const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
5878
+ const { worktreesDir } = options.harnessRoot ? { worktreesDir: path31.join(harnessRoot, "worktrees") } : getHarnessPaths();
5879
+ const now = options.now ?? Date.now();
5880
+ return { harnessRoot, worktreesDir, now };
5881
+ }
5882
+ function normalizeGuardSkip(skip) {
5883
+ if (typeof skip === "string") return { reason: skip };
5884
+ return skip;
5885
+ }
5886
+ function recordSkip(skips, pathValue, reason, detail) {
5887
+ skips.push({ path: pathValue, reason, ...detail ? { detail } : {} });
5888
+ }
5889
+ function attachCandidateBytes(candidate, accountBytes) {
5890
+ if (!accountBytes || candidate.bytes != null) return candidate;
5891
+ return { ...candidate, bytes: directorySizeBytes(candidate.path) };
5892
+ }
5893
+ function tallySkipReasons(actions, skips) {
5894
+ const counts = {};
5895
+ for (const skip of skips) {
5896
+ counts[skip.reason] = (counts[skip.reason] ?? 0) + 1;
5897
+ }
5898
+ for (const action of actions) {
5899
+ if (action.skipReason) {
5900
+ counts[action.skipReason] = (counts[action.skipReason] ?? 0) + 1;
5901
+ }
5902
+ }
5903
+ return counts;
5904
+ }
5905
+ function runHarnessCleanup(options = {}) {
5906
+ const retention = resolveHarnessRetention(options);
5907
+ const paths = resolvePaths(options);
5908
+ const finalizedRuns = retention.finalizeStaleRuns ? finalizeStaleRuns().map((f) => ({ runId: f.runId, from: f.from, to: f.to })) : [];
5909
+ const index = buildWorktreeIndex();
5910
+ const scanOpts = {
5911
+ harnessRoot: paths.harnessRoot,
5912
+ worktreesDir: paths.worktreesDir,
5913
+ nodeModulesAgeMs: retention.nodeModulesAgeMs,
5914
+ worktreesAgeMs: retention.worktreesAgeMs,
5915
+ includeOrphans: retention.includeOrphans,
5916
+ runIdFilter: retention.runIdFilter,
5917
+ index,
5918
+ now: paths.now
5919
+ };
5920
+ const skips = [];
5921
+ const actions = [];
5922
+ for (const raw of scanNodeModulesCandidates(scanOpts)) {
5923
+ const candidate = attachCandidateBytes(raw, retention.accountBytes);
5924
+ const pathSkip = isHarnessNodeModulesPath(candidate.path, paths.harnessRoot, paths.worktreesDir);
5925
+ if (pathSkip) {
5926
+ recordSkip(skips, candidate.path, pathSkip);
5927
+ actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
5928
+ continue;
5929
+ }
5930
+ const worktreePath = path31.resolve(candidate.path, "..");
5931
+ const indexed = index.get(worktreePath) ?? null;
5932
+ const guardReason = skipNodeModulesRemoval({
5933
+ indexed,
5934
+ includeOrphans: retention.includeOrphans,
5935
+ nodeModulesAgeMs: retention.nodeModulesAgeMs,
5936
+ ageMs: candidate.ageMs
5937
+ });
5938
+ if (guardReason) {
5939
+ recordSkip(skips, candidate.path, guardReason);
5940
+ actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
5941
+ continue;
5942
+ }
5943
+ actions.push(removeNodeModules(candidate, retention.execute));
5944
+ }
5945
+ for (const raw of scanWorktreeCandidates(scanOpts)) {
5946
+ const candidate = attachCandidateBytes(raw, retention.accountBytes);
5947
+ const indexed = index.get(path31.resolve(candidate.path)) ?? null;
5948
+ const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
5949
+ worktreePath: candidate.path,
5950
+ harnessRoot: paths.harnessRoot,
5951
+ runId: candidate.runId,
5952
+ workerName: candidate.worker,
5953
+ now: paths.now
5954
+ });
5955
+ const guardSkip = skipWorktreeRemoval({
5956
+ indexed,
5957
+ worktreePath: path31.resolve(candidate.path),
5958
+ includeOrphans: retention.includeOrphans,
5959
+ worktreesAgeMs: retention.worktreesAgeMs,
5960
+ ageMs: candidate.ageMs,
5961
+ orphanSafety,
5962
+ worktreeRemovalGuard: options.worktreeRemovalGuard
5963
+ });
5964
+ if (guardSkip) {
5965
+ const { reason: guardReason, detail: guardDetail } = normalizeGuardSkip(guardSkip);
5966
+ recordSkip(skips, candidate.path, guardReason, guardDetail);
5967
+ actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
5968
+ continue;
5969
+ }
5970
+ actions.push(removeWorktree(candidate, retention.execute));
5971
+ }
5972
+ let candidateBytes = 0;
5973
+ let reclaimableBytes = 0;
5974
+ let removedBytes = 0;
5975
+ let removedPaths = 0;
5976
+ let skippedPaths = 0;
5977
+ for (const action of actions) {
5978
+ if (action.bytes) candidateBytes += action.bytes;
5979
+ if (!action.skipped && !action.executed && action.bytes) reclaimableBytes += action.bytes;
5980
+ if (action.executed) {
5981
+ removedPaths += 1;
5982
+ removedBytes += action.bytes ?? 0;
5983
+ } else if (action.skipped) {
5984
+ skippedPaths += 1;
5985
+ if (action.skipReason === "dry_run" && action.bytes) reclaimableBytes += action.bytes;
5986
+ }
5987
+ }
5988
+ const storage = retention.accountBytes ? harnessStorageSnapshot({ harnessRoot: paths.harnessRoot, now: paths.now }) : void 0;
5989
+ return {
5990
+ harnessRoot: paths.harnessRoot,
5991
+ dryRun: !retention.execute,
5992
+ execute: retention.execute,
5993
+ nodeModulesAgeMs: retention.nodeModulesAgeMs,
5994
+ worktreesAgeMs: retention.worktreesAgeMs,
5995
+ includeOrphans: retention.includeOrphans,
5996
+ scannedAt: new Date(paths.now).toISOString(),
5997
+ finalizedRuns,
5998
+ actions,
5999
+ skips,
6000
+ totals: {
6001
+ candidateBytes,
6002
+ reclaimableBytes,
6003
+ removedBytes,
6004
+ removedPaths,
6005
+ skippedPaths,
6006
+ skipReasons: tallySkipReasons(actions, skips)
6007
+ },
6008
+ ...storage ? { storage } : {}
6009
+ };
6010
+ }
6011
+ function runPipelineHarnessCleanup(runId) {
6012
+ const retention = resolvePipelineHarnessRetention(runId);
6013
+ return runHarnessCleanup({
6014
+ execute: retention.execute,
6015
+ finalizeStaleRuns: retention.finalizeStaleRuns,
6016
+ accountBytes: retention.accountBytes,
6017
+ nodeModulesAgeMs: retention.nodeModulesAgeMs,
6018
+ worktreesAgeMs: retention.worktreesAgeMs,
6019
+ includeOrphans: retention.includeOrphans,
6020
+ runIdFilter: retention.runIdFilter
6021
+ });
6022
+ }
6023
+ function isPipelineCleanupEnabled() {
6024
+ return process.env.KYNVER_PIPELINE_CLEANUP !== "0";
6025
+ }
6026
+
6027
+ // src/cli.ts
6028
+ import { mkdirSync as mkdirSync7, realpathSync } from "node:fs";
6029
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
6030
+
6031
+ // src/discard-disposable.ts
6032
+ import { existsSync as existsSync23, rmSync as rmSync2 } from "node:fs";
6033
+ import path32 from "node:path";
6034
+ function normalizeRelativePath2(value) {
6035
+ const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
6036
+ if (!normalized || normalized.startsWith("/") || normalized.includes("..")) {
6037
+ throw new Error(`unsafe path: ${value}`);
6038
+ }
6039
+ return normalized;
6040
+ }
6041
+ function parsePathsArg(raw) {
6042
+ if (typeof raw !== "string" || !raw.trim()) return [];
6043
+ return raw.split(",").map((p) => p.trim()).filter(Boolean);
6044
+ }
6045
+ function discardDisposableArtifacts(args) {
6046
+ const worker = loadWorker(String(args.run), String(args.name));
6047
+ const paths = [
6048
+ ...parsePathsArg(args.path),
6049
+ ...Array.isArray(args.paths) ? args.paths : []
6050
+ ];
6051
+ if (paths.length === 0) {
6052
+ return { ok: false, removed: [], reason: "requires at least one --path" };
6053
+ }
6054
+ const worktreeRoot = path32.resolve(worker.worktreePath);
6055
+ const removed = [];
6056
+ for (const raw of paths) {
6057
+ const rel = normalizeRelativePath2(raw);
6058
+ const abs = path32.resolve(worktreeRoot, rel);
6059
+ if (!abs.startsWith(worktreeRoot + path32.sep) && abs !== worktreeRoot) {
6060
+ return { ok: false, removed, reason: `path escapes worktree: ${raw}` };
6061
+ }
6062
+ if (!existsSync23(abs)) {
6063
+ return { ok: false, removed, reason: `path not found: ${raw}` };
6064
+ }
6065
+ rmSync2(abs, { recursive: true, force: true });
6066
+ removed.push(rel);
6067
+ }
6068
+ const prior = Array.isArray(worker.disposableArtifactsRemoved) ? worker.disposableArtifactsRemoved.filter((p) => typeof p === "string") : [];
6069
+ worker.disposableArtifactsRemoved = [.../* @__PURE__ */ new Set([...prior, ...removed])];
6070
+ saveWorker(worker.runId, worker);
6071
+ const status = computeWorkerStatus(worker);
6072
+ return {
6073
+ ok: true,
6074
+ removed,
6075
+ ...status.changedFiles.length ? { reason: "worktree still has other changes" } : {}
6076
+ };
6077
+ }
6078
+ function discardDisposableCli(args) {
6079
+ const result = discardDisposableArtifacts(args);
6080
+ console.log(JSON.stringify(result, null, 2));
6081
+ if (!result.ok) process.exit(1);
6082
+ }
6083
+
6084
+ // src/pipeline-tick.ts
6085
+ import path36 from "node:path";
6086
+
6087
+ // src/pipeline-dispatch.ts
6088
+ var RESERVED_REVIEW_STARTS = 1;
6089
+ function countDispatchStarts(result) {
6090
+ if (!result || typeof result !== "object") return 0;
6091
+ const startedCount = result.startedCount;
6092
+ if (typeof startedCount === "number") return startedCount;
6093
+ const outcomes = result.outcomes;
6094
+ if (!Array.isArray(outcomes)) return 0;
6095
+ return outcomes.filter((o) => o.started).length;
6096
+ }
6097
+ function stripCliMaxStarts(args) {
6098
+ const { maxStarts: _maxStarts, ...rest } = args;
6099
+ return rest;
6100
+ }
6101
+ async function runPipelineDispatch(args, slots) {
6102
+ if (slots <= 0) {
6103
+ return { ok: true, skipped: true, reason: "no slots", maxStarts: 0, startedCount: 0 };
6104
+ }
6105
+ const base = stripCliMaxStarts(args);
6106
+ const reviewBudget = Math.min(slots, RESERVED_REVIEW_STARTS);
6107
+ const workBudget = Math.max(0, slots - reviewBudget);
6108
+ const review = await dispatchRun({
6109
+ ...base,
6110
+ execute: true,
6111
+ pipeline: true,
6112
+ lane: "review",
6113
+ maxStarts: String(reviewBudget)
6114
+ });
6115
+ const reviewStarted = countDispatchStarts(review);
6116
+ const workSlots = workBudget + (reviewBudget - reviewStarted);
6117
+ if (workSlots <= 0) {
6118
+ return {
6119
+ ...typeof review === "object" && review !== null ? review : {},
6120
+ passes: { review },
6121
+ startedCount: reviewStarted
6122
+ };
6123
+ }
6124
+ const work = await dispatchRun({
6125
+ ...base,
6126
+ execute: true,
6127
+ pipeline: true,
6128
+ maxStarts: String(workSlots)
6129
+ });
6130
+ const workStarted = countDispatchStarts(work);
6131
+ return {
6132
+ passes: { review, work },
6133
+ startedCount: reviewStarted + workStarted,
6134
+ ok: true
6135
+ };
6136
+ }
6137
+
6138
+ // src/stale-reconcile.ts
6139
+ import path33 from "node:path";
6140
+ var STALE_RECONCILE_HEARTBEAT_MS = 15 * 60 * 1e3;
6141
+ function staleReconcileDisabled() {
6142
+ return process.env.KYNVER_NO_STALE_CLEANUP === "1";
6143
+ }
6144
+ function reconcileStaleWorkers() {
6145
+ if (staleReconcileDisabled()) {
6146
+ return { workers: [], finalizedRuns: finalizeStaleRuns() };
6147
+ }
6148
+ const outcomes = [];
6149
+ const now = Date.now();
6150
+ for (const run of listRunRecords()) {
6151
+ for (const name of Object.keys(run.workers || {})) {
6152
+ const workerPath = path33.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
6153
+ const worker = readJson(workerPath, void 0);
6154
+ if (!worker || worker.status !== "running") {
6155
+ outcomes.push({
6156
+ runId: run.id,
6157
+ worker: name,
6158
+ action: "skipped",
6159
+ reason: worker ? `worker status is ${worker.status}` : "worker.json missing"
6160
+ });
6161
+ continue;
6162
+ }
6163
+ const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
6164
+ if (status.finalResult) {
6165
+ if (worker.status === "running") {
6166
+ const nextStatus = status.attention.state === "blocked" ? "blocked" : status.attention.state === "done" || status.status === "done" ? "done" : "exited";
6167
+ worker.status = nextStatus;
6168
+ worker.reconciledAt = (/* @__PURE__ */ new Date()).toISOString();
6169
+ worker.reconcileReason = "synced finished worker record after terminal stdout/heartbeat";
6170
+ saveWorker(run.id, worker);
6171
+ outcomes.push({
6172
+ runId: run.id,
6173
+ worker: name,
6174
+ action: "marked_exited",
6175
+ reason: worker.reconcileReason
6176
+ });
6177
+ } else {
6178
+ outcomes.push({ runId: run.id, worker: name, action: "skipped", reason: "final result present" });
6179
+ }
6180
+ continue;
6181
+ }
6182
+ if (!status.alive) {
6183
+ const nextStatus = status.attention.state === "blocked" ? "blocked" : status.status === "done" ? "done" : "exited";
6184
+ worker.status = nextStatus;
6185
+ worker.reconciledAt = (/* @__PURE__ */ new Date()).toISOString();
6186
+ worker.reconcileReason = status.attention.reason;
6187
+ saveWorker(run.id, worker);
6188
+ outcomes.push({
6189
+ runId: run.id,
6190
+ worker: name,
6191
+ action: "marked_exited",
6192
+ reason: status.attention.reason
6193
+ });
6194
+ continue;
6195
+ }
6196
+ if (status.attention.state === "stale" && worker.pid && isPidAlive(worker.pid)) {
6197
+ const hbMs = status.lastHeartbeatAt ? Date.parse(status.lastHeartbeatAt) : NaN;
6198
+ const actMs = status.lastActivityAt ? Date.parse(status.lastActivityAt) : NaN;
6199
+ const hbStale = !Number.isFinite(hbMs) || now - hbMs > STALE_RECONCILE_HEARTBEAT_MS;
6200
+ const actStale = Number.isFinite(actMs) && now - actMs > STALE_MS;
6201
+ if (hbStale && actStale) {
6202
+ killWorkerProcess(worker.pid, "SIGTERM");
6203
+ worker.status = "exited";
6204
+ worker.reconciledAt = (/* @__PURE__ */ new Date()).toISOString();
6205
+ worker.reconcileReason = `reconciled stale worker: ${status.attention.reason}`;
6206
+ saveWorker(run.id, worker);
6207
+ outcomes.push({
6208
+ runId: run.id,
6209
+ worker: name,
6210
+ action: "killed_stale",
6211
+ reason: status.attention.reason
6212
+ });
6213
+ continue;
6214
+ }
6215
+ }
6216
+ outcomes.push({
6217
+ runId: run.id,
6218
+ worker: name,
6219
+ action: "skipped",
6220
+ reason: status.attention.reason
6221
+ });
6222
+ }
6223
+ }
6224
+ return { workers: outcomes, finalizedRuns: finalizeStaleRuns() };
6225
+ }
6226
+ function reconcileRunsCli() {
6227
+ const result = reconcileStaleWorkers();
6228
+ const markedExited = result.workers.filter((w) => w.action === "marked_exited").length;
6229
+ const killedStale = result.workers.filter((w) => w.action === "killed_stale").length;
6230
+ const skipped = result.workers.filter((w) => w.action === "skipped").length;
6231
+ console.log(
6232
+ JSON.stringify(
6233
+ {
6234
+ ok: true,
6235
+ workers: { markedExited, killedStale, skipped, total: result.workers.length },
6236
+ finalizedRuns: result.finalizedRuns.length,
6237
+ details: { workers: result.workers, finalizedRuns: result.finalizedRuns }
6238
+ },
6239
+ null,
6240
+ 2
6241
+ )
6242
+ );
5531
6243
  }
5532
6244
 
5533
- // src/cleanup.ts
5534
- function resolvePaths(options = {}) {
5535
- const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
5536
- const { worktreesDir } = options.harnessRoot ? { worktreesDir: path30.join(harnessRoot, "worktrees") } : getHarnessPaths();
5537
- const now = options.now ?? Date.now();
5538
- return { harnessRoot, worktreesDir, now };
5539
- }
5540
- function recordSkip(skips, pathValue, reason, detail) {
5541
- skips.push({ path: pathValue, reason, ...detail ? { detail } : {} });
5542
- }
5543
- function attachCandidateBytes(candidate, accountBytes) {
5544
- if (!accountBytes || candidate.bytes != null) return candidate;
5545
- return { ...candidate, bytes: directorySizeBytes(candidate.path) };
5546
- }
5547
- function tallySkipReasons(actions, skips) {
5548
- const counts = {};
5549
- for (const skip of skips) {
5550
- counts[skip.reason] = (counts[skip.reason] ?? 0) + 1;
5551
- }
5552
- for (const action of actions) {
5553
- if (action.skipReason) {
5554
- counts[action.skipReason] = (counts[action.skipReason] ?? 0) + 1;
5555
- }
5556
- }
5557
- return counts;
6245
+ // src/plan-progress-daemon-sync.ts
6246
+ import path34 from "node:path";
6247
+
6248
+ // src/plan-progress-sync.ts
6249
+ async function syncPlanProgress(args) {
6250
+ const base = resolveBaseUrl(args.baseUrl);
6251
+ const secret = await resolveCallbackSecretWithMint(args.secret, args.agentOsId, { baseUrl: base });
6252
+ const url = `${base}/api/agent-os/by-id/${encodeURIComponent(args.agentOsId)}/tasks/${encodeURIComponent(args.taskId)}/plan-progress-sync`;
6253
+ const res = await postJsonWithCredentialRefresh(url, secret, {
6254
+ phase: args.phase,
6255
+ taskId: args.taskId,
6256
+ blocker: args.blocker,
6257
+ artifact: args.artifact
6258
+ }, { agentOsId: args.agentOsId, baseUrl: base });
6259
+ return { ok: res.ok, status: res.status, response: res.response };
5558
6260
  }
5559
- function runHarnessCleanup(options = {}) {
5560
- const retention = resolveHarnessRetention(options);
5561
- const paths = resolvePaths(options);
5562
- const finalizedRuns = retention.finalizeStaleRuns ? finalizeStaleRuns().map((f) => ({ runId: f.runId, from: f.from, to: f.to })) : [];
5563
- const index = buildWorktreeIndex();
5564
- const scanOpts = {
5565
- harnessRoot: paths.harnessRoot,
5566
- worktreesDir: paths.worktreesDir,
5567
- nodeModulesAgeMs: retention.nodeModulesAgeMs,
5568
- worktreesAgeMs: retention.worktreesAgeMs,
5569
- includeOrphans: retention.includeOrphans,
5570
- runIdFilter: retention.runIdFilter,
5571
- index,
5572
- now: paths.now
5573
- };
5574
- const skips = [];
5575
- const actions = [];
5576
- for (const raw of scanNodeModulesCandidates(scanOpts)) {
5577
- const candidate = attachCandidateBytes(raw, retention.accountBytes);
5578
- const pathSkip = isHarnessNodeModulesPath(candidate.path, paths.harnessRoot, paths.worktreesDir);
5579
- if (pathSkip) {
5580
- recordSkip(skips, candidate.path, pathSkip);
5581
- actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
5582
- continue;
5583
- }
5584
- const worktreePath = path30.resolve(candidate.path, "..");
5585
- const indexed = index.get(worktreePath) ?? null;
5586
- const guardReason = skipNodeModulesRemoval({
5587
- indexed,
5588
- includeOrphans: retention.includeOrphans,
5589
- nodeModulesAgeMs: retention.nodeModulesAgeMs,
5590
- ageMs: candidate.ageMs
5591
- });
5592
- if (guardReason) {
5593
- recordSkip(skips, candidate.path, guardReason);
5594
- actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
6261
+
6262
+ // src/plan-progress-daemon-sync.ts
6263
+ async function syncActiveWorkerPlanProgress(runId, args) {
6264
+ const run = loadRun(runId);
6265
+ const agentOsId = String(args.agentOsId || "");
6266
+ if (!agentOsId) return [];
6267
+ const outcomes = [];
6268
+ for (const name of Object.keys(run.workers || {})) {
6269
+ const worker = readJson(
6270
+ path34.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
6271
+ void 0
6272
+ );
6273
+ if (!worker?.dispatched || !worker.taskId) continue;
6274
+ const status = computeWorkerStatus(worker);
6275
+ if (!status.heartbeatBlocker) continue;
6276
+ if (worker.lastSyncedHeartbeatBlocker === status.heartbeatBlocker) {
6277
+ outcomes.push({ worker: name, phase: "heartbeat_blocker", ok: true, skipped: true });
5595
6278
  continue;
5596
6279
  }
5597
- actions.push(removeNodeModules(candidate, retention.execute));
5598
- }
5599
- for (const raw of scanWorktreeCandidates(scanOpts)) {
5600
- const candidate = attachCandidateBytes(raw, retention.accountBytes);
5601
- const indexed = index.get(path30.resolve(candidate.path)) ?? null;
5602
- const guardReason = skipWorktreeRemoval({
5603
- indexed,
5604
- includeOrphans: retention.includeOrphans,
5605
- worktreesAgeMs: retention.worktreesAgeMs,
5606
- ageMs: candidate.ageMs
6280
+ const res = await syncPlanProgress({
6281
+ agentOsId,
6282
+ taskId: worker.taskId,
6283
+ phase: "heartbeat_blocker",
6284
+ blocker: status.heartbeatBlocker,
6285
+ baseUrl: args.baseUrl ? String(args.baseUrl) : void 0,
6286
+ secret: args.secret ? String(args.secret) : void 0
5607
6287
  });
5608
- if (guardReason) {
5609
- recordSkip(skips, candidate.path, guardReason);
5610
- actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
5611
- continue;
6288
+ if (res.ok) {
6289
+ worker.lastSyncedHeartbeatBlocker = status.heartbeatBlocker;
6290
+ saveWorker(run.id, worker);
5612
6291
  }
5613
- actions.push(removeWorktree(candidate, retention.execute));
6292
+ outcomes.push({ worker: name, phase: "heartbeat_blocker", ok: res.ok });
5614
6293
  }
5615
- let candidateBytes = 0;
5616
- let reclaimableBytes = 0;
5617
- let removedBytes = 0;
5618
- let removedPaths = 0;
5619
- let skippedPaths = 0;
5620
- for (const action of actions) {
5621
- if (action.bytes) candidateBytes += action.bytes;
5622
- if (!action.skipped && !action.executed && action.bytes) reclaimableBytes += action.bytes;
5623
- if (action.executed) {
5624
- removedPaths += 1;
5625
- removedBytes += action.bytes ?? 0;
5626
- } else if (action.skipped) {
5627
- skippedPaths += 1;
5628
- if (action.skipReason === "dry_run" && action.bytes) reclaimableBytes += action.bytes;
6294
+ return outcomes;
6295
+ }
6296
+
6297
+ // src/workspace-runtime-config.ts
6298
+ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
6299
+ const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
6300
+ const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, { baseUrl: base });
6301
+ const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/runtime`;
6302
+ try {
6303
+ const res = await getJson(url, secret);
6304
+ if (!res.ok) return null;
6305
+ const body = res.response;
6306
+ const raw = body?.preferences?.maxConcurrentWorkers;
6307
+ if (raw === null || raw === void 0) {
6308
+ return { maxConcurrentWorkers: null };
5629
6309
  }
6310
+ const n = Number(raw);
6311
+ if (!Number.isFinite(n) || n <= 0) return { maxConcurrentWorkers: null };
6312
+ return { maxConcurrentWorkers: Math.floor(n) };
6313
+ } catch {
6314
+ return null;
5630
6315
  }
5631
- return {
5632
- harnessRoot: paths.harnessRoot,
5633
- dryRun: !retention.execute,
5634
- execute: retention.execute,
5635
- nodeModulesAgeMs: retention.nodeModulesAgeMs,
5636
- worktreesAgeMs: retention.worktreesAgeMs,
5637
- includeOrphans: retention.includeOrphans,
5638
- scannedAt: new Date(paths.now).toISOString(),
5639
- finalizedRuns,
5640
- actions,
5641
- skips,
5642
- totals: {
5643
- candidateBytes,
5644
- reclaimableBytes,
5645
- removedBytes,
5646
- removedPaths,
5647
- skippedPaths,
5648
- skipReasons: tallySkipReasons(actions, skips)
5649
- }
5650
- };
5651
- }
5652
- function runPipelineHarnessCleanup(runId) {
5653
- const retention = resolvePipelineHarnessRetention(runId);
5654
- return runHarnessCleanup({
5655
- execute: retention.execute,
5656
- finalizeStaleRuns: retention.finalizeStaleRuns,
5657
- accountBytes: retention.accountBytes,
5658
- nodeModulesAgeMs: retention.nodeModulesAgeMs,
5659
- worktreesAgeMs: retention.worktreesAgeMs,
5660
- includeOrphans: retention.includeOrphans,
5661
- runIdFilter: retention.runIdFilter
5662
- });
5663
- }
5664
- function isPipelineCleanupEnabled() {
5665
- return process.env.KYNVER_PIPELINE_CLEANUP !== "0";
5666
6316
  }
5667
6317
 
5668
6318
  // src/installed-package-versions.ts
5669
6319
  import { readFile } from "node:fs/promises";
5670
6320
  import { homedir as homedir6 } from "node:os";
5671
- import path31 from "node:path";
6321
+ import path35 from "node:path";
5672
6322
  var MANAGED_PACKAGES = [
5673
6323
  "@kynver-app/runtime",
5674
6324
  "@kynver-app/openclaw-agent-os",
@@ -5683,12 +6333,12 @@ function unique(values) {
5683
6333
  }
5684
6334
  function moduleRoots() {
5685
6335
  const home = homedir6();
5686
- const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path31.join(home, ".openclaw", "npm");
5687
- const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path31.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path31.join(home, ".npm-global", "lib", "node_modules"));
6336
+ const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path35.join(home, ".openclaw", "npm");
6337
+ const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path35.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path35.join(home, ".npm-global", "lib", "node_modules"));
5688
6338
  return unique([
5689
- path31.join(openClawPrefix, "lib", "node_modules"),
5690
- path31.join(openClawPrefix, "node_modules"),
5691
- npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path31.join(npmGlobalRoot, "lib", "node_modules")
6339
+ path35.join(openClawPrefix, "lib", "node_modules"),
6340
+ path35.join(openClawPrefix, "node_modules"),
6341
+ npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path35.join(npmGlobalRoot, "lib", "node_modules")
5692
6342
  ]);
5693
6343
  }
5694
6344
  async function readVersion(packageJsonPath) {
@@ -5704,7 +6354,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
5704
6354
  const out = {};
5705
6355
  for (const packageName of MANAGED_PACKAGES) {
5706
6356
  for (const root of roots) {
5707
- const packageJsonPath = path31.join(root, packageName, "package.json");
6357
+ const packageJsonPath = path35.join(root, packageName, "package.json");
5708
6358
  const version = await readVersion(packageJsonPath);
5709
6359
  if (!version) continue;
5710
6360
  out[packageName] = { version, observedAt, path: packageJsonPath };
@@ -5720,7 +6370,7 @@ async function completeFinishedWorkers(runId, args) {
5720
6370
  const outcomes = [];
5721
6371
  for (const name of Object.keys(run.workers || {})) {
5722
6372
  const worker = readJson(
5723
- path32.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
6373
+ path36.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
5724
6374
  void 0
5725
6375
  );
5726
6376
  if (!worker?.taskId || worker.localOnly) continue;
@@ -5862,7 +6512,7 @@ async function runDaemon(args) {
5862
6512
  }
5863
6513
 
5864
6514
  // src/plan-progress.ts
5865
- import path33 from "node:path";
6515
+ import path37 from "node:path";
5866
6516
 
5867
6517
  // src/bounded-build/constants.ts
5868
6518
  var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
@@ -5900,7 +6550,7 @@ function formatNodeOptionsFlag(mb = resolveNodeOldSpaceSizeMb()) {
5900
6550
  }
5901
6551
 
5902
6552
  // src/bounded-build/systemd-wrap.ts
5903
- import { spawnSync as spawnSync3 } from "node:child_process";
6553
+ import { spawnSync as spawnSync4 } from "node:child_process";
5904
6554
  var systemdAvailableCache;
5905
6555
  function isSystemdRunAvailable() {
5906
6556
  if (process.env.KYNVER_BUILD_SKIP_SYSTEMD === "1" || process.env.KYNVER_BUILD_SKIP_SYSTEMD === "true") {
@@ -5911,7 +6561,7 @@ function isSystemdRunAvailable() {
5911
6561
  systemdAvailableCache = false;
5912
6562
  return false;
5913
6563
  }
5914
- const res = spawnSync3("systemd-run", ["--version"], { encoding: "utf8", stdio: ["ignore", "ignore", "pipe"] });
6564
+ const res = spawnSync4("systemd-run", ["--version"], { encoding: "utf8", stdio: ["ignore", "ignore", "pipe"] });
5915
6565
  systemdAvailableCache = res.status === 0;
5916
6566
  return systemdAvailableCache;
5917
6567
  }
@@ -5935,7 +6585,7 @@ function buildSystemdRunArgv(opts) {
5935
6585
  }
5936
6586
 
5937
6587
  // src/bounded-build/admission.ts
5938
- import { spawnSync as spawnSync4 } from "node:child_process";
6588
+ import { spawnSync as spawnSync5 } from "node:child_process";
5939
6589
  function positiveInt3(value, fallback) {
5940
6590
  const n = Number(value);
5941
6591
  if (!Number.isFinite(n) || n <= 0) return fallback;
@@ -5971,7 +6621,7 @@ function assessBuildAdmission(opts = {}) {
5971
6621
  }
5972
6622
  function sleepMs2(ms) {
5973
6623
  if (ms <= 0) return;
5974
- spawnSync4(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
6624
+ spawnSync5(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
5975
6625
  stdio: "ignore"
5976
6626
  });
5977
6627
  }
@@ -5992,7 +6642,7 @@ function waitForBuildAdmission(timeoutMs, pollMs = 2e3, opts = {}) {
5992
6642
  }
5993
6643
 
5994
6644
  // src/bounded-build/exec.ts
5995
- import { spawnSync as spawnSync5 } from "node:child_process";
6645
+ import { spawnSync as spawnSync6 } from "node:child_process";
5996
6646
  function envArgv(env) {
5997
6647
  const out = [];
5998
6648
  for (const [key, value] of Object.entries(env)) {
@@ -6002,7 +6652,7 @@ function envArgv(env) {
6002
6652
  return out;
6003
6653
  }
6004
6654
  function runSpawn(argv, opts) {
6005
- const res = spawnSync5(argv[0], argv.slice(1), {
6655
+ const res = spawnSync6(argv[0], argv.slice(1), {
6006
6656
  cwd: opts.cwd,
6007
6657
  env: opts.env,
6008
6658
  encoding: "utf8",
@@ -6148,7 +6798,7 @@ async function emitPlanProgress(args) {
6148
6798
  }
6149
6799
  function verifyPlanLocal(args) {
6150
6800
  const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
6151
- const cwd = path33.resolve(worktree);
6801
+ const cwd = path37.resolve(worktree);
6152
6802
  const summary = runHarnessVerifyCommands(cwd);
6153
6803
  const emitJson = args.json === true || args.json === "true";
6154
6804
  const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
@@ -6197,9 +6847,9 @@ async function verifyPlan(args) {
6197
6847
  }
6198
6848
 
6199
6849
  // src/harness-verify-cli.ts
6200
- import path34 from "node:path";
6850
+ import path38 from "node:path";
6201
6851
  function runHarnessVerifyCli(args) {
6202
- const cwd = path34.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
6852
+ const cwd = path38.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
6203
6853
  const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
6204
6854
  const commands = [];
6205
6855
  const rawCmd = args.command;
@@ -6243,7 +6893,7 @@ function runHarnessVerifyCli(args) {
6243
6893
  }
6244
6894
 
6245
6895
  // src/plan-persist-cli.ts
6246
- import { readFileSync as readFileSync9 } from "node:fs";
6896
+ import { readFileSync as readFileSync11 } from "node:fs";
6247
6897
  var OPERATIONS = ["create", "add_version", "update_metadata"];
6248
6898
  var FAILURE_KINDS = [
6249
6899
  "approval_guard",
@@ -6255,7 +6905,7 @@ var FAILURE_KINDS = [
6255
6905
  function readBodyArg(args) {
6256
6906
  const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
6257
6907
  if (bodyFile) {
6258
- return { body: readFileSync9(bodyFile, "utf8"), bodyPathHint: bodyFile };
6908
+ return { body: readFileSync11(bodyFile, "utf8"), bodyPathHint: bodyFile };
6259
6909
  }
6260
6910
  const inline = args.body ? String(args.body) : void 0;
6261
6911
  if (inline) return { body: inline };
@@ -6345,7 +6995,7 @@ function runCleanupCli(args) {
6345
6995
  }
6346
6996
 
6347
6997
  // src/monitor/monitor.service.ts
6348
- import path36 from "node:path";
6998
+ import path40 from "node:path";
6349
6999
 
6350
7000
  // src/monitor/monitor.classify.ts
6351
7001
  function expectedLeaseOwner(runId) {
@@ -6401,11 +7051,11 @@ function classifyWorkerHealth(input) {
6401
7051
  }
6402
7052
 
6403
7053
  // src/monitor/monitor.store.ts
6404
- import { existsSync as existsSync19, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
6405
- import path35 from "node:path";
7054
+ import { existsSync as existsSync24, mkdirSync as mkdirSync6, readdirSync as readdirSync8, unlinkSync as unlinkSync2 } from "node:fs";
7055
+ import path39 from "node:path";
6406
7056
  function monitorsDir() {
6407
7057
  const { harnessRoot } = getHarnessPaths();
6408
- const dir = path35.join(harnessRoot, "monitors");
7058
+ const dir = path39.join(harnessRoot, "monitors");
6409
7059
  mkdirSync6(dir, { recursive: true });
6410
7060
  return dir;
6411
7061
  }
@@ -6413,7 +7063,7 @@ function monitorIdFor(runId, workerName) {
6413
7063
  return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
6414
7064
  }
6415
7065
  function monitorPath(monitorId) {
6416
- return path35.join(monitorsDir(), `${monitorId}.json`);
7066
+ return path39.join(monitorsDir(), `${monitorId}.json`);
6417
7067
  }
6418
7068
  function loadMonitorSession(monitorId) {
6419
7069
  return readJson(monitorPath(monitorId), void 0);
@@ -6423,18 +7073,18 @@ function saveMonitorSession(session) {
6423
7073
  }
6424
7074
  function deleteMonitorSession(monitorId) {
6425
7075
  const file = monitorPath(monitorId);
6426
- if (!existsSync19(file)) return false;
7076
+ if (!existsSync24(file)) return false;
6427
7077
  unlinkSync2(file);
6428
7078
  return true;
6429
7079
  }
6430
7080
  function listMonitorSessions() {
6431
7081
  const dir = monitorsDir();
6432
- if (!existsSync19(dir)) return [];
7082
+ if (!existsSync24(dir)) return [];
6433
7083
  const entries = [];
6434
- for (const name of readdirSync7(dir)) {
7084
+ for (const name of readdirSync8(dir)) {
6435
7085
  if (!name.endsWith(".json")) continue;
6436
7086
  const session = readJson(
6437
- path35.join(dir, name),
7087
+ path39.join(dir, name),
6438
7088
  void 0
6439
7089
  );
6440
7090
  if (!session?.monitorId) continue;
@@ -6525,7 +7175,7 @@ async function fetchTaskLeasesForWorkers(input) {
6525
7175
  // src/monitor/monitor.service.ts
6526
7176
  function workerRecord2(runId, name) {
6527
7177
  return readJson(
6528
- path36.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
7178
+ path40.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
6529
7179
  void 0
6530
7180
  );
6531
7181
  }
@@ -6727,18 +7377,18 @@ async function runMonitorLoop(args) {
6727
7377
 
6728
7378
  // src/monitor/monitor-spawn.ts
6729
7379
  import { spawn as spawn4 } from "node:child_process";
6730
- import { closeSync as closeSync4, existsSync as existsSync20, openSync as openSync4 } from "node:fs";
6731
- import path37 from "node:path";
7380
+ import { closeSync as closeSync4, existsSync as existsSync25, openSync as openSync4 } from "node:fs";
7381
+ import path41 from "node:path";
6732
7382
  import { fileURLToPath as fileURLToPath4 } from "node:url";
6733
7383
  function resolveDefaultCliPath2() {
6734
- return path37.join(fileURLToPath4(new URL(".", import.meta.url)), "..", "cli.js");
7384
+ return path41.join(fileURLToPath4(new URL(".", import.meta.url)), "cli.js");
6735
7385
  }
6736
7386
  function spawnMonitorSidecar(opts) {
6737
7387
  const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
6738
- if (!existsSync20(cliPath)) return void 0;
7388
+ if (!existsSync25(cliPath)) return void 0;
6739
7389
  const monitorId = monitorIdFor(opts.runId, opts.workerName);
6740
7390
  const { harnessRoot } = getHarnessPaths();
6741
- const logPath = path37.join(harnessRoot, "monitors", `${monitorId}.log`);
7391
+ const logPath = path41.join(harnessRoot, "monitors", `${monitorId}.log`);
6742
7392
  let logFd;
6743
7393
  try {
6744
7394
  logFd = openSync4(logPath, "a");
@@ -6858,16 +7508,16 @@ async function monitorTickCli(args) {
6858
7508
  }
6859
7509
 
6860
7510
  // src/doctor/runtime-takeover.ts
6861
- import path39 from "node:path";
7511
+ import path43 from "node:path";
6862
7512
 
6863
7513
  // src/doctor/runtime-takeover.probes.ts
6864
- import { accessSync, constants, existsSync as existsSync21, readFileSync as readFileSync10 } from "node:fs";
7514
+ import { accessSync, constants, existsSync as existsSync26, readFileSync as readFileSync12 } from "node:fs";
6865
7515
  import { homedir as homedir7 } from "node:os";
6866
- import path38 from "node:path";
6867
- import { spawnSync as spawnSync6 } from "node:child_process";
7516
+ import path42 from "node:path";
7517
+ import { spawnSync as spawnSync7 } from "node:child_process";
6868
7518
  function captureCommand(bin, args) {
6869
7519
  try {
6870
- const res = spawnSync6(bin, args, { encoding: "utf8" });
7520
+ const res = spawnSync7(bin, args, { encoding: "utf8" });
6871
7521
  const stdout = (res.stdout || "").trim();
6872
7522
  const stderr = (res.stderr || "").trim();
6873
7523
  const ok = res.status === 0;
@@ -6892,7 +7542,7 @@ function tokenPrefix(token) {
6892
7542
  return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
6893
7543
  }
6894
7544
  function isWritable(target) {
6895
- if (!existsSync21(target)) return false;
7545
+ if (!existsSync26(target)) return false;
6896
7546
  try {
6897
7547
  accessSync(target, constants.W_OK);
6898
7548
  return true;
@@ -6905,15 +7555,15 @@ var defaultRuntimeTakeoverProbes = {
6905
7555
  commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
6906
7556
  kynverVersion: (bin) => captureCommand(bin, ["--version"]),
6907
7557
  loadConfig: () => loadUserConfig(),
6908
- configFilePath: () => path38.join(homedir7(), ".kynver", "config.json"),
6909
- credentialsFilePath: () => path38.join(homedir7(), ".kynver", "credentials"),
7558
+ configFilePath: () => path42.join(homedir7(), ".kynver", "config.json"),
7559
+ credentialsFilePath: () => path42.join(homedir7(), ".kynver", "credentials"),
6910
7560
  readCredentials: () => {
6911
- const credPath = path38.join(homedir7(), ".kynver", "credentials");
6912
- if (!existsSync21(credPath)) {
7561
+ const credPath = path42.join(homedir7(), ".kynver", "credentials");
7562
+ if (!existsSync26(credPath)) {
6913
7563
  return { hasApiKey: false };
6914
7564
  }
6915
7565
  try {
6916
- const parsed = JSON.parse(readFileSync10(credPath, "utf8"));
7566
+ const parsed = JSON.parse(readFileSync12(credPath, "utf8"));
6917
7567
  return {
6918
7568
  hasApiKey: Boolean(parsed.apiKey?.trim()),
6919
7569
  runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
@@ -6932,6 +7582,7 @@ var defaultRuntimeTakeoverProbes = {
6932
7582
  kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
6933
7583
  opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
6934
7584
  kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0,
7585
+ openclawCronStorePath: Boolean(process.env.OPENCLAW_CRON_STORE_PATH?.trim()),
6935
7586
  qstashTokenPresent: Boolean(process.env.QSTASH_TOKEN?.trim()),
6936
7587
  kynverHostedDeployment: (() => {
6937
7588
  const v = process.env.KYNVER_HOSTED_DEPLOYMENT?.trim().toLowerCase();
@@ -6939,18 +7590,59 @@ var defaultRuntimeTakeoverProbes = {
6939
7590
  })()
6940
7591
  }),
6941
7592
  harnessRoot: () => resolveHarnessRoot(),
6942
- legacyOpenclawHarnessRoot: () => path38.join(homedir7(), ".openclaw", "harness"),
6943
- pathExists: (target) => existsSync21(target),
7593
+ legacyOpenclawHarnessRoot: () => path42.join(homedir7(), ".openclaw", "harness"),
7594
+ pathExists: (target) => existsSync26(target),
6944
7595
  pathWritable: (target) => isWritable(target),
6945
7596
  vercelVersion: () => captureCommand("vercel", ["--version"]),
6946
7597
  vercelWhoami: () => captureCommand("vercel", ["whoami"])
6947
7598
  };
6948
7599
 
6949
7600
  // src/doctor/runtime-takeover-scheduler.ts
7601
+ function hasLocalOpenClawDependency(env, ctx) {
7602
+ return env.kynverSchedulerProvider === "kynver-cron" || env.kynverSchedulerProvider === "openclaw-cron" || ctx.deploymentSchedulerProvider === "kynver-cron" || ctx.deploymentSchedulerProvider === "openclaw-cron" || Boolean(env.openclawCronStorePath);
7603
+ }
7604
+ function hasQstashCutover(env, ctx) {
7605
+ return env.kynverSchedulerProvider === "qstash" || ctx.deploymentSchedulerProvider === "qstash";
7606
+ }
6950
7607
  function check(partial) {
6951
7608
  return partial;
6952
7609
  }
6953
7610
  function assessRuntimeTakeoverScheduler(env, ctx) {
7611
+ const schedulerDetails = {
7612
+ schedulerProvider: env.kynverSchedulerProvider ?? null,
7613
+ deploymentSchedulerProvider: ctx.deploymentSchedulerProvider ?? null,
7614
+ openclawCronStorePath: Boolean(env.openclawCronStorePath)
7615
+ };
7616
+ if (hasQstashCutover(env, ctx) && !hasLocalOpenClawDependency(env, ctx)) {
7617
+ const source = env.kynverSchedulerProvider === "qstash" ? "KYNVER_SCHEDULER_PROVIDER=qstash on this host" : "deploymentSchedulerProvider=qstash in ~/.kynver/config.json";
7618
+ return check({
7619
+ id: "hotspot_openclaw_scheduler",
7620
+ label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
7621
+ status: "pass",
7622
+ summary: `AgentOS scheduler cut over to QStash (${source})`,
7623
+ details: schedulerDetails
7624
+ });
7625
+ }
7626
+ if (hasLocalOpenClawDependency(env, ctx)) {
7627
+ const parts = [];
7628
+ if (env.kynverSchedulerProvider === "openclaw-cron") {
7629
+ parts.push("KYNVER_SCHEDULER_PROVIDER=openclaw-cron");
7630
+ }
7631
+ if (ctx.deploymentSchedulerProvider === "openclaw-cron") {
7632
+ parts.push("deploymentSchedulerProvider=openclaw-cron in config");
7633
+ }
7634
+ if (env.openclawCronStorePath) {
7635
+ parts.push("OPENCLAW_CRON_STORE_PATH set (local cron bridge)");
7636
+ }
7637
+ return check({
7638
+ id: "hotspot_openclaw_scheduler",
7639
+ label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
7640
+ status: "warn",
7641
+ summary: `OpenClaw local cron still active (${parts.join("; ")})`,
7642
+ remediation: "On the Kynver deployment: set KYNVER_SCHEDULER_PROVIDER=qstash with QSTASH_TOKEN configured. On user runners: unset KYNVER_SCHEDULER_PROVIDER and OPENCLAW_CRON_STORE_PATH; set deploymentSchedulerProvider to qstash in ~/.kynver/config.json after Vercel env is updated.",
7643
+ details: schedulerDetails
7644
+ });
7645
+ }
6954
7646
  const runnerOpenclaw = env.kynverSchedulerProvider === "openclaw-cron";
6955
7647
  const runnerQstash = env.kynverSchedulerProvider === "qstash";
6956
7648
  const hostedDeployment = Boolean(env.qstashTokenPresent) || Boolean(env.kynverHostedDeployment);
@@ -7227,8 +7919,8 @@ function assessVercelCli(probes) {
7227
7919
  }
7228
7920
  function assessHarnessDirs(probes) {
7229
7921
  const harnessRoot = probes.harnessRoot();
7230
- const runsDir = path39.join(harnessRoot, "runs");
7231
- const worktreesDir = path39.join(harnessRoot, "worktrees");
7922
+ const runsDir = path43.join(harnessRoot, "runs");
7923
+ const worktreesDir = path43.join(harnessRoot, "worktrees");
7232
7924
  const displayHarnessRoot = redactHomePath(harnessRoot);
7233
7925
  const displayRunsDir = redactHomePath(runsDir);
7234
7926
  const displayWorktreesDir = redactHomePath(worktreesDir);
@@ -7337,7 +8029,8 @@ function assessOpenclawHotspots(probes) {
7337
8029
  assessRuntimeTakeoverScheduler(env, {
7338
8030
  agentOsId: targetAgentOsId ?? null,
7339
8031
  apiBaseUrl: config.apiBaseUrl?.trim() ?? env.kynverApiUrl ?? null,
7340
- hasScopedRunnerToken
8032
+ hasScopedRunnerToken,
8033
+ deploymentSchedulerProvider: config.deploymentSchedulerProvider === "qstash" || config.deploymentSchedulerProvider === "kynver-cron" || config.deploymentSchedulerProvider === "openclaw-cron" ? config.deploymentSchedulerProvider : void 0
7341
8034
  }),
7342
8035
  check2({
7343
8036
  id: "hotspot_lease_source_names",
@@ -7443,6 +8136,7 @@ function usage(code = 0) {
7443
8136
  " kynver worker tail --run RUN_ID --name worker [--lines 40] [--raw]",
7444
8137
  " kynver worker stop --run RUN_ID --name worker",
7445
8138
  " kynver worker complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--task-id TASK_ID] [--base-url URL] [--secret SECRET]",
8139
+ " kynver worker discard-disposable --run RUN_ID --name worker --path scripts/helper.mjs[,other]",
7446
8140
  " 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]",
7447
8141
  " kynver run reconcile",
7448
8142
  " 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]",
@@ -7452,6 +8146,7 @@ function usage(code = 0) {
7452
8146
  " kynver plan outbox list",
7453
8147
  " kynver plan outbox drain [--max N] [--id OUTBOX_ID]",
7454
8148
  " kynver cleanup [--execute] [--node-modules-age-ms MS] [--worktrees-age-ms MS] [--harness-root PATH] [--include-orphans] [--skip-finalize]",
8149
+ " --include-orphans also scans whole worktree directories (<harnessRoot>/worktrees/<runId>/<workerId>/) that no run/worker.json references; orphans pass salvage gates (recent heartbeat, dirty git, ahead of origin/main) before removal.",
7455
8150
  " kynver monitor start --run RUN_ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS]",
7456
8151
  " kynver monitor status [--run RUN_ID] [--name worker] [--tick]",
7457
8152
  " kynver monitor stop --run RUN_ID [--name worker]",
@@ -7512,6 +8207,7 @@ async function main(argv = process.argv.slice(2)) {
7512
8207
  if (scope === "worker" && action === "tail") return tailWorker(args);
7513
8208
  if (scope === "worker" && action === "stop") return stopWorker(args);
7514
8209
  if (scope === "worker" && action === "complete") return void await completeWorker(args);
8210
+ if (scope === "worker" && action === "discard-disposable") return discardDisposableCli(args);
7515
8211
  if (scope === "worker" && action === "auto-complete") return void await autoCompleteWorkerCli(args);
7516
8212
  if (scope === "monitor" && action === "start") {
7517
8213
  const result = await startMonitorCli(args);
@@ -7650,7 +8346,7 @@ function pickVercelStatusContext(statuses) {
7650
8346
  }
7651
8347
 
7652
8348
  // src/vercel/vercel-evidence.ts
7653
- import { spawnSync as spawnSync7 } from "node:child_process";
8349
+ import { spawnSync as spawnSync8 } from "node:child_process";
7654
8350
  var DEFAULT_INSPECT_WAIT_SECONDS = 120;
7655
8351
  function mapGitHubStateToEvidence(state) {
7656
8352
  if (state === "success") return "ready";
@@ -7716,7 +8412,7 @@ function evidenceFromGitHubVercelStatus(statuses, options = {}) {
7716
8412
  }
7717
8413
  function defaultRunVercelInspect(target, waitSeconds) {
7718
8414
  const args = ["inspect", target, "--wait", String(waitSeconds)];
7719
- const result = spawnSync7("vercel", args, {
8415
+ const result = spawnSync8("vercel", args, {
7720
8416
  encoding: "utf8",
7721
8417
  stdio: ["ignore", "pipe", "pipe"]
7722
8418
  });
@@ -7825,11 +8521,16 @@ function collectVercelEvidence(input) {
7825
8521
  export {
7826
8522
  DEFAULT_DISPATCH_LEASE_MS,
7827
8523
  DEFAULT_HARNESS_VERIFY_COMMANDS,
8524
+ DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES,
8525
+ DEFAULT_WSL_HOST_MOUNT,
8526
+ DEFAULT_WSL_HOST_WARN_FREE_BYTES,
7828
8527
  FORBIDDEN_WORKER_ENV_KEYS,
7829
8528
  PACKAGE_VERSION,
8529
+ applyProductionDatabaseToProcess,
7830
8530
  assessAutoCompleteEligibility,
7831
8531
  assessBuildAdmission,
7832
8532
  assessExitedWorkerSalvage,
8533
+ assessOrphanWorktreeSafety,
7833
8534
  assessPrHandoffRequirement,
7834
8535
  assessWorkerLanding,
7835
8536
  assessWorkerLandingContract,
@@ -7862,6 +8563,7 @@ export {
7862
8563
  getHarnessPaths,
7863
8564
  getMonitorStatus,
7864
8565
  gitRepoRoot,
8566
+ harnessStorageSnapshot,
7865
8567
  hashPlanBody,
7866
8568
  isDashboardVercelUrl,
7867
8569
  isEngagementRequiredSkip,
@@ -7869,9 +8571,11 @@ export {
7869
8571
  isForbiddenWorkerEnvKey,
7870
8572
  isKynverMonorepoRoot,
7871
8573
  isLandingBlockedWorkerStatus,
8574
+ isPipelineCleanupEnabled,
7872
8575
  isSystemdRunAvailable,
7873
8576
  isTerminalHeartbeatPhase,
7874
8577
  isVercelStatusContext,
8578
+ isWslHost,
7875
8579
  landingContractAttentionReason,
7876
8580
  listForbiddenWorkerEnvKeys,
7877
8581
  listMonitors,
@@ -7882,6 +8586,7 @@ export {
7882
8586
  mergeNodeOptionsForBuildCheck,
7883
8587
  normalizeCursorModelAlias,
7884
8588
  observeRunnerDiskGate,
8589
+ observeWslHostDisk,
7885
8590
  parseArgs,
7886
8591
  parseClaudeStream,
7887
8592
  parseHarnessStream,
@@ -7892,6 +8597,7 @@ export {
7892
8597
  postJson,
7893
8598
  preflightCursorModel,
7894
8599
  readMemAvailableBytes,
8600
+ readProductionDbKeysFromEnvFile,
7895
8601
  reconcileRunsCli,
7896
8602
  reconcileStaleWorkers,
7897
8603
  redactHarness,
@@ -7901,12 +8607,16 @@ export {
7901
8607
  resolveCallbackSecretWithMint,
7902
8608
  resolveDefaultRepo,
7903
8609
  resolveHarnessRoot,
8610
+ resolveProductionDatabaseUrl,
7904
8611
  resolveVercelInspectTarget,
7905
8612
  runBoundedBuildCheck,
7906
8613
  runDaemon,
8614
+ runHarnessCleanup,
7907
8615
  runHarnessVerifyCommands,
7908
8616
  runMonitorTick,
8617
+ runPipelineHarnessCleanup,
7909
8618
  runStatus,
8619
+ safeDatabaseUrlHint,
7910
8620
  saveUserConfig,
7911
8621
  scrubClaudeEnv,
7912
8622
  scrubWorkerEnv,
@@ -7918,6 +8628,7 @@ export {
7918
8628
  summarizeEvent,
7919
8629
  summarizeNpmAuditReport,
7920
8630
  summarizeShellToolCallEvent,
8631
+ summarizeWslRecoverySteps,
7921
8632
  sweepRun,
7922
8633
  tailWorker,
7923
8634
  terminalFinalResultFromHeartbeat,