@kynver-app/runtime 0.1.74 → 0.1.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/active-harness-workers.d.ts +11 -0
  2. package/dist/cleanup-evidence.d.ts +10 -0
  3. package/dist/cleanup-execute.d.ts +1 -0
  4. package/dist/cleanup-guards.d.ts +2 -0
  5. package/dist/cleanup-retention-config.d.ts +3 -0
  6. package/dist/cleanup-run-directory.d.ts +20 -0
  7. package/dist/cleanup-types.d.ts +25 -1
  8. package/dist/cli.js +2439 -751
  9. package/dist/cli.js.map +4 -4
  10. package/dist/cron/hermes-provider-notice.d.ts +1 -0
  11. package/dist/harness-lease-owner.d.ts +13 -0
  12. package/dist/index.d.ts +10 -3
  13. package/dist/index.js +2715 -760
  14. package/dist/index.js.map +4 -4
  15. package/dist/landing-contract-gate.d.ts +1 -0
  16. package/dist/model-routing.d.ts +4 -1
  17. package/dist/orchestration-providers/audit.d.ts +3 -0
  18. package/dist/orchestration-providers/capabilities.d.ts +3 -0
  19. package/dist/orchestration-providers/claude-oauth-binding.d.ts +6 -0
  20. package/dist/orchestration-providers/codex-oauth-binding.d.ts +5 -0
  21. package/dist/orchestration-providers/codex-orchestration-adapter.d.ts +13 -0
  22. package/dist/orchestration-providers/cost-router.d.ts +24 -0
  23. package/dist/orchestration-providers/cursor-oauth-binding.d.ts +5 -0
  24. package/dist/orchestration-providers/hermes-cli-adapter.d.ts +15 -0
  25. package/dist/orchestration-providers/hermes-openai-codex-binding.d.ts +15 -0
  26. package/dist/orchestration-providers/index.d.ts +13 -0
  27. package/dist/orchestration-providers/inventory.d.ts +31 -0
  28. package/dist/orchestration-providers/oauth-binding-utils.d.ts +21 -0
  29. package/dist/orchestration-providers/routing.d.ts +16 -0
  30. package/dist/orchestration-providers/types.d.ts +45 -0
  31. package/dist/paths.d.ts +9 -1
  32. package/dist/pipeline-tick.d.ts +3 -0
  33. package/dist/post-restart-unblock.d.ts +24 -0
  34. package/dist/providers/codex.d.ts +9 -0
  35. package/dist/providers/hermes-codex.d.ts +9 -0
  36. package/dist/providers/model-preflight.d.ts +2 -0
  37. package/dist/providers/openai-codex-resilience.d.ts +33 -0
  38. package/dist/retry-limits.d.ts +1 -1
  39. package/dist/stale-reconcile.d.ts +2 -0
  40. package/dist/status.d.ts +6 -0
  41. package/dist/supervisor.d.ts +1 -0
  42. package/dist/worker-metadata-paths.d.ts +26 -0
  43. package/dist/worker-metadata-reconcile.d.ts +19 -0
  44. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -645,9 +645,6 @@ async function runLogin(args) {
645
645
  console.log(JSON.stringify({ ok: true, credentialsPath: displayUserPath(CREDENTIALS_FILE) }, null, 2));
646
646
  }
647
647
 
648
- // src/dispatch.ts
649
- import path18 from "node:path";
650
-
651
648
  // src/callback-headers.ts
652
649
  function buildHarnessCallbackHeaders(secret) {
653
650
  const trimmed = String(secret).trim();
@@ -670,12 +667,29 @@ function buildHarnessCallbackHeaders(secret) {
670
667
  }
671
668
 
672
669
  // src/callbacks.ts
670
+ function callbackTimeoutMs() {
671
+ const parsed = Number(process.env.KYNVER_CALLBACK_TIMEOUT_MS);
672
+ if (Number.isFinite(parsed) && parsed > 0) return Math.floor(parsed);
673
+ return 3e4;
674
+ }
675
+ async function withTimeout(fn) {
676
+ const controller = new AbortController();
677
+ const timeout = setTimeout(() => controller.abort(), callbackTimeoutMs());
678
+ try {
679
+ return await fn(controller.signal);
680
+ } finally {
681
+ clearTimeout(timeout);
682
+ }
683
+ }
673
684
  async function postJson(url, secret, body) {
674
- const res = await fetch(url, {
675
- method: "POST",
676
- headers: buildHarnessCallbackHeaders(secret),
677
- body: JSON.stringify(body)
678
- });
685
+ const res = await withTimeout(
686
+ (signal) => fetch(url, {
687
+ method: "POST",
688
+ headers: buildHarnessCallbackHeaders(secret),
689
+ body: JSON.stringify(body),
690
+ signal
691
+ })
692
+ );
679
693
  let response = null;
680
694
  try {
681
695
  response = await res.json();
@@ -693,10 +707,13 @@ async function postJsonWithCredentialRefresh(url, secret, body, opts) {
693
707
  return { ...retry, refreshedAuth: true };
694
708
  }
695
709
  async function getJson(url, secret) {
696
- const res = await fetch(url, {
697
- method: "GET",
698
- headers: buildHarnessCallbackHeaders(secret)
699
- });
710
+ const res = await withTimeout(
711
+ (signal) => fetch(url, {
712
+ method: "GET",
713
+ headers: buildHarnessCallbackHeaders(secret),
714
+ signal
715
+ })
716
+ );
700
717
  let response = null;
701
718
  try {
702
719
  response = await res.json();
@@ -729,23 +746,23 @@ function isWslHost() {
729
746
  function observeWslHostDisk(options = {}) {
730
747
  const wsl = options.forceWsl === void 0 ? isWslHost() : options.forceWsl;
731
748
  if (!wsl) return null;
732
- const path50 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
749
+ const path59 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
733
750
  const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;
734
751
  const criticalBelowBytes = options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;
735
752
  const statfs = options.statfs ?? statfsSync;
736
753
  let stats;
737
754
  try {
738
- stats = statfs(path50);
755
+ stats = statfs(path59);
739
756
  } catch (error) {
740
757
  return {
741
758
  ok: false,
742
- path: path50,
759
+ path: path59,
743
760
  freeBytes: 0,
744
761
  totalBytes: 0,
745
762
  usedPercent: 100,
746
763
  warnBelowBytes,
747
764
  criticalBelowBytes,
748
- reason: `Windows host disk probe failed at ${path50}: ${error.message}`,
765
+ reason: `Windows host disk probe failed at ${path59}: ${error.message}`,
749
766
  probeError: error.message
750
767
  };
751
768
  }
@@ -759,11 +776,11 @@ function observeWslHostDisk(options = {}) {
759
776
  let reason = null;
760
777
  if (!ok) {
761
778
  const tag = criticalFree ? "critical" : "warning";
762
- reason = `Windows host disk ${path50} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
779
+ reason = `Windows host disk ${path59} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
763
780
  }
764
781
  return {
765
782
  ok,
766
- path: path50,
783
+ path: path59,
767
784
  freeBytes,
768
785
  totalBytes,
769
786
  usedPercent,
@@ -783,12 +800,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
783
800
  var DEFAULT_MAX_USED_PERCENT = 80;
784
801
  var DEFAULT_HARD_MAX_USED_PERCENT = 90;
785
802
  function observeRunnerDiskGate(input = {}) {
786
- const path50 = input.diskPath?.trim() || "/";
803
+ const path59 = input.diskPath?.trim() || "/";
787
804
  const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
788
805
  const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
789
806
  const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
790
807
  const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
791
- const stats = statfsSync2(path50);
808
+ const stats = statfsSync2(path59);
792
809
  const freeBytes = Number(stats.bavail) * Number(stats.bsize);
793
810
  const totalBytes = Number(stats.blocks) * Number(stats.bsize);
794
811
  const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
@@ -811,7 +828,7 @@ function observeRunnerDiskGate(input = {}) {
811
828
  }
812
829
  return {
813
830
  ok,
814
- path: path50,
831
+ path: path59,
815
832
  freeBytes,
816
833
  totalBytes,
817
834
  usedPercent,
@@ -851,7 +868,7 @@ function readMemAvailableBytes(meminfoText) {
851
868
  import path7 from "node:path";
852
869
 
853
870
  // src/run-store.ts
854
- import { existsSync as existsSync6, readdirSync as readdirSync2 } from "node:fs";
871
+ import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
855
872
  import path6 from "node:path";
856
873
 
857
874
  // src/paths.ts
@@ -859,22 +876,36 @@ import { existsSync as existsSync5 } from "node:fs";
859
876
  import { homedir as homedir4 } from "node:os";
860
877
  import path5 from "node:path";
861
878
  var LEGACY_ROOT = path5.join(homedir4(), ".openclaw", "harness");
879
+ var HARNESS_LAYOUT_DIR_NAMES = /* @__PURE__ */ new Set(["runs", "worktrees"]);
880
+ function normalizeHarnessRoot(root) {
881
+ let resolved = path5.resolve(resolveUserPath(root.trim()));
882
+ while (HARNESS_LAYOUT_DIR_NAMES.has(path5.basename(resolved))) {
883
+ resolved = path5.dirname(resolved);
884
+ }
885
+ return resolved;
886
+ }
862
887
  function resolveHarnessRoot() {
863
888
  const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
864
- if (env) return resolveUserPath(env);
889
+ if (env) return normalizeHarnessRoot(env);
865
890
  const configured = loadUserConfig().harnessRoot?.trim();
866
- if (configured) return resolveUserPath(configured);
891
+ if (configured) return normalizeHarnessRoot(configured);
867
892
  const kynverRoot = path5.join(homedir4(), ".kynver", "harness");
868
893
  if (existsSync5(kynverRoot)) return kynverRoot;
869
894
  if (existsSync5(LEGACY_ROOT)) return LEGACY_ROOT;
870
895
  return kynverRoot;
871
896
  }
897
+ function harnessRunsDir(harnessRoot) {
898
+ return path5.join(normalizeHarnessRoot(harnessRoot), "runs");
899
+ }
900
+ function harnessWorktreesDir(harnessRoot) {
901
+ return path5.join(normalizeHarnessRoot(harnessRoot), "worktrees");
902
+ }
872
903
  function getHarnessPaths() {
873
904
  const harnessRoot = resolveHarnessRoot();
874
905
  return {
875
906
  harnessRoot,
876
- runsDir: path5.join(harnessRoot, "runs"),
877
- worktreesDir: path5.join(harnessRoot, "worktrees")
907
+ runsDir: harnessRunsDir(harnessRoot),
908
+ worktreesDir: harnessWorktreesDir(harnessRoot)
878
909
  };
879
910
  }
880
911
  function runDir(runsDir, id) {
@@ -894,15 +925,24 @@ function listRunRecords() {
894
925
  return listRunRecordsAt(runsDir);
895
926
  }
896
927
  function listRunRecordsForHarnessRoot(harnessRoot) {
897
- return listRunRecordsAt(path6.join(harnessRoot, "runs"));
928
+ return listRunRecordsAt(harnessRunsDir(harnessRoot));
929
+ }
930
+ function isRunDirectoryEntry(runDirPath) {
931
+ try {
932
+ return statSync2(runDirPath).isDirectory();
933
+ } catch {
934
+ return false;
935
+ }
898
936
  }
899
937
  function listRunRecordsAt(runsDir) {
900
938
  if (!existsSync6(runsDir)) return [];
901
939
  const runs = [];
902
940
  for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
903
- if (!entry.isDirectory()) continue;
941
+ if (entry.name === "runs") continue;
942
+ const runDir2 = path6.join(runsDir, entry.name);
943
+ if (!isRunDirectoryEntry(runDir2)) continue;
904
944
  const run = readJson(
905
- path6.join(runsDir, entry.name, "run.json"),
945
+ path6.join(runDir2, "run.json"),
906
946
  void 0
907
947
  );
908
948
  if (run?.id) runs.push(run);
@@ -924,11 +964,11 @@ function saveWorker(runId, worker) {
924
964
  writeJson(path6.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
925
965
  }
926
966
  function runDirectory(id) {
927
- const { runsDir } = getPaths();
928
- return runDirectoryAt(runsDir, id);
967
+ const { harnessRoot } = getPaths();
968
+ return runDirectoryAt(harnessRoot, id);
929
969
  }
930
970
  function runDirectoryAt(harnessRoot, id) {
931
- return runDir(path6.join(harnessRoot, "runs"), safeSlug(id));
971
+ return runDir(harnessRunsDir(harnessRoot), safeSlug(id));
932
972
  }
933
973
 
934
974
  // src/heartbeat.ts
@@ -1490,13 +1530,29 @@ function parseReconciliation(finalResult) {
1490
1530
  }
1491
1531
  return out;
1492
1532
  }
1533
+ function stringList(value) {
1534
+ if (!Array.isArray(value)) return [];
1535
+ return value.map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
1536
+ }
1493
1537
  function workerPrUrls(snapshot, finalResult) {
1494
1538
  const urls = [];
1495
1539
  const fromSnapshot = normalizePrUrl(trimOrNull3(snapshot.prUrl) ?? "");
1496
1540
  if (fromSnapshot) urls.push(fromSnapshot);
1497
1541
  if (finalResult && typeof finalResult === "object" && !Array.isArray(finalResult)) {
1498
- const pr = normalizePrUrl(String(finalResult.prUrl ?? ""));
1542
+ const record = finalResult;
1543
+ const pr = normalizePrUrl(String(record.prUrl ?? ""));
1499
1544
  if (pr) urls.push(pr);
1545
+ const lane = record.laneExpertise ?? record.lane_expertise;
1546
+ if (lane && typeof lane === "object" && !Array.isArray(lane)) {
1547
+ for (const u of stringList(lane.prUrls)) {
1548
+ const n = normalizePrUrl(u);
1549
+ if (n) urls.push(n);
1550
+ }
1551
+ }
1552
+ for (const u of stringList(record.prUrls ?? record.pr_urls)) {
1553
+ const n = normalizePrUrl(u);
1554
+ if (n) urls.push(n);
1555
+ }
1500
1556
  }
1501
1557
  return [...new Set(urls)];
1502
1558
  }
@@ -1506,7 +1562,21 @@ function assessWorkerLandingContract(input) {
1506
1562
  if (!contract.landingOnly && contract.targetPrUrls.length === 0 && !contract.repairEnforceOriginalPr) {
1507
1563
  return { blocked: false };
1508
1564
  }
1509
- if (!hasFinalResult3(finalResult)) return { blocked: false };
1565
+ if (!hasFinalResult3(finalResult)) {
1566
+ const requiresEarly = contract.requiresTargetPrReconciliation ?? (contract.landingOnly || Boolean(contract.repairEnforceOriginalPr) || contract.targetPrUrls.length > 0);
1567
+ if (requiresEarly && contract.targetPrUrls.length > 0) {
1568
+ return {
1569
+ blocked: true,
1570
+ reason: "missing_target_pr_reconciliation",
1571
+ detail: `Final result required to reconcile target PR(s): ${contract.targetPrUrls.join(", ")}`
1572
+ };
1573
+ }
1574
+ return { blocked: false };
1575
+ }
1576
+ const requiresTargetPrReconciliation = contract.requiresTargetPrReconciliation ?? (contract.landingOnly || Boolean(contract.repairEnforceOriginalPr) || contract.targetPrUrls.length > 0);
1577
+ if (!requiresTargetPrReconciliation && !contract.repairEnforceOriginalPr) {
1578
+ return { blocked: false };
1579
+ }
1510
1580
  const repairTarget = contract.repairEnforceOriginalPr ? normalizePrUrl(trimOrNull3(contract.targetPrUrl) ?? "") ?? (contract.targetPrUrls.length === 1 ? normalizePrUrl(contract.targetPrUrls[0]) : null) : null;
1511
1581
  if (repairTarget) {
1512
1582
  const workerPrs2 = workerPrUrls(snapshot, finalResult);
@@ -1538,6 +1608,14 @@ function assessWorkerLandingContract(input) {
1538
1608
  contract.targetPrUrls.map((u) => normalizePrUrl(u) ?? u).filter(Boolean)
1539
1609
  );
1540
1610
  const workerPrs = workerPrUrls(snapshot, finalResult);
1611
+ const unrelatedReconciled = reconciliation.map((entry) => entry.prUrl).filter((pr) => pr && !targetSet.has(pr));
1612
+ if (unrelatedReconciled.length > 0 && (contract.landingOnly || contract.repairEnforceOriginalPr)) {
1613
+ return {
1614
+ blocked: true,
1615
+ reason: "unrelated_implementation_pr",
1616
+ detail: `Reconciliation must only cite contract target PR(s); unrelated: ${unrelatedReconciled.join(", ")}`
1617
+ };
1618
+ }
1541
1619
  if (contract.landingOnly) {
1542
1620
  for (const pr of workerPrs) {
1543
1621
  if (targetSet.size > 0 && !targetSet.has(pr)) {
@@ -1791,6 +1869,9 @@ function readAvailableMemBytes() {
1791
1869
  return readMemAvailableBytes();
1792
1870
  }
1793
1871
  function isActiveHarnessWorker(worker) {
1872
+ if (typeof worker.completionBlocker === "string" && worker.completionBlocker.trim()) {
1873
+ return false;
1874
+ }
1794
1875
  const status = computeWorkerStatus(worker);
1795
1876
  return status.alive && !status.finalResult && status.attention.state !== "done";
1796
1877
  }
@@ -1925,127 +2006,6 @@ function enrichTaskForModelRouting(task) {
1925
2006
  return { ...task, roleLane };
1926
2007
  }
1927
2008
 
1928
- // src/providers/claude.ts
1929
- import { closeSync, openSync } from "node:fs";
1930
- import { spawn } from "node:child_process";
1931
-
1932
- // src/providers/model-preflight.ts
1933
- var REASONING_SUFFIX_RE = /-(?:thinking(?:-(?:high|medium|low|minimal|max|none))?|high|medium|low|minimal)$/i;
1934
- function stripReasoningSuffix(model) {
1935
- return model.replace(REASONING_SUFFIX_RE, "");
1936
- }
1937
- var FOREIGN_MODEL_RE = /^(?:gpt-|gpt5|o1|o3|o4|gemini-|grok-|composer|deepseek|llama|mistral|qwen|command-)/i;
1938
- function looksLikeClaudeModel(model) {
1939
- return /^claude[-_]/i.test(model) || /^(?:opus|sonnet|haiku)\b/i.test(model);
1940
- }
1941
- var CURSOR_MODEL_ALIASES = /* @__PURE__ */ new Set(["cursor"]);
1942
- function normalizeCursorModelAlias(model) {
1943
- const key = model.trim().toLowerCase();
1944
- if (CURSOR_MODEL_ALIASES.has(key)) return "auto";
1945
- return null;
1946
- }
1947
- function preflightClaudeModel(model, defaultModel) {
1948
- const requested = (model ?? "").trim();
1949
- if (!requested) {
1950
- return { ok: true, model: defaultModel, normalized: false };
1951
- }
1952
- const stripped = stripReasoningSuffix(requested).trim();
1953
- const launch = stripped || defaultModel;
1954
- if (FOREIGN_MODEL_RE.test(launch) || !looksLikeClaudeModel(launch) && launch !== defaultModel) {
1955
- return {
1956
- ok: false,
1957
- model: requested,
1958
- normalized: false,
1959
- requested,
1960
- note: `model "${requested}" is not a Claude model \u2014 the "claude" provider drives the Claude CLI, which only accepts claude-* model ids (got "${launch}"). Pick a Claude model or switch the worker provider.`
1961
- };
1962
- }
1963
- if (launch !== requested) {
1964
- return {
1965
- ok: true,
1966
- model: launch,
1967
- normalized: true,
1968
- requested,
1969
- note: `normalized model "${requested}" \u2192 "${launch}" (the Claude CLI rejects reasoning-effort suffixes)`
1970
- };
1971
- }
1972
- return { ok: true, model: launch, normalized: false };
1973
- }
1974
- function preflightCursorModel(model, defaultModel) {
1975
- const requested = (model ?? "").trim();
1976
- if (!requested) {
1977
- return { ok: true, model: defaultModel, normalized: false };
1978
- }
1979
- const aliasLaunch = normalizeCursorModelAlias(requested);
1980
- if (aliasLaunch) {
1981
- return {
1982
- ok: true,
1983
- model: aliasLaunch,
1984
- normalized: true,
1985
- requested,
1986
- note: `normalized model "${requested}" \u2192 "${aliasLaunch}" (Cursor provider alias \u2014 use "auto" or a composer id, not "cursor")`
1987
- };
1988
- }
1989
- if (looksLikeClaudeModel(requested)) {
1990
- return {
1991
- ok: false,
1992
- model: requested,
1993
- normalized: false,
1994
- requested,
1995
- note: `model "${requested}" is a Claude model but the worker provider is "cursor". Switch the provider to "claude" or pick a Cursor model.`
1996
- };
1997
- }
1998
- return { ok: true, model: requested, normalized: false };
1999
- }
2000
-
2001
- // src/providers/claude.ts
2002
- var CLAUDE_DEFAULT_MODEL = "claude-sonnet-4-6";
2003
- var claudeProvider = {
2004
- name: "claude",
2005
- defaultModel: CLAUDE_DEFAULT_MODEL,
2006
- preflightModel(model) {
2007
- return preflightClaudeModel(model, CLAUDE_DEFAULT_MODEL);
2008
- },
2009
- start(opts) {
2010
- const preflight = preflightClaudeModel(opts.model, CLAUDE_DEFAULT_MODEL);
2011
- if (!preflight.ok) {
2012
- throw new Error(`claude provider model preflight failed: ${preflight.note}`);
2013
- }
2014
- const model = preflight.model;
2015
- const stdoutFd = openSync(opts.stdoutPath, "a");
2016
- const stderrFd = openSync(opts.stderrPath, "a");
2017
- const stdio = ["ignore", stdoutFd, stderrFd];
2018
- const child = spawn(
2019
- "claude",
2020
- [
2021
- "--model",
2022
- model,
2023
- "-p",
2024
- "--verbose",
2025
- "--permission-mode",
2026
- "bypassPermissions",
2027
- "--output-format",
2028
- "stream-json",
2029
- "--include-partial-messages",
2030
- opts.prompt
2031
- ],
2032
- hiddenSpawnOptions({
2033
- cwd: opts.worktreePath,
2034
- detached: true,
2035
- stdio,
2036
- env: scrubWorkerEnv(process.env)
2037
- })
2038
- );
2039
- closeSync(stdoutFd);
2040
- closeSync(stderrFd);
2041
- if (!child.pid) {
2042
- throw new Error("failed to spawn claude worker process (is the `claude` CLI on PATH?)");
2043
- }
2044
- child.unref();
2045
- return { pid: child.pid, model };
2046
- }
2047
- };
2048
-
2049
2009
  // src/worker-provider-policy.ts
2050
2010
  var DEFAULT_WORKER_PROVIDER = "cursor";
2051
2011
  var CLAUDE_FAMILY = /* @__PURE__ */ new Set(["claude", "opus", "anthropic"]);
@@ -2109,75 +2069,959 @@ function resolveConfiguredWorkerProvider(configured, fallback = DEFAULT_WORKER_P
2109
2069
  const trimmed = configured?.trim();
2110
2070
  if (!trimmed) return fallback;
2111
2071
  if (isClaudeFamilyProvider(trimmed)) return DEFAULT_WORKER_PROVIDER;
2072
+ if (trimmed === "codex") return "codex";
2112
2073
  return trimmed;
2113
2074
  }
2114
2075
 
2115
- // src/model-routing.ts
2116
- var GLOBAL_DEFAULT_MODEL = "claude-sonnet-4-6";
2117
- var CURSOR_DEFAULT_MODEL = "composer-2.5";
2118
- function taskString3(task, key) {
2119
- const v = task[key];
2120
- return typeof v === "string" ? v.trim() : "";
2121
- }
2122
- function normalizeRef(ref) {
2123
- return ref.toLowerCase();
2076
+ // src/orchestration-providers/capabilities.ts
2077
+ var CAPABILITIES = {
2078
+ codex: {
2079
+ id: "codex",
2080
+ displayName: "Codex (BYO OAuth / Hermes openai-codex)",
2081
+ costTier: "low",
2082
+ authSources: ["oauth_local", "api_key_env", "subscription_hermes"],
2083
+ supportsLowRiskOrchestration: true,
2084
+ supportsPrivilegedPlatformActions: false
2085
+ },
2086
+ cursor: {
2087
+ id: "cursor",
2088
+ displayName: "Cursor / Composer",
2089
+ costTier: "medium",
2090
+ authSources: ["oauth_local", "api_key_env"],
2091
+ supportsLowRiskOrchestration: true,
2092
+ supportsPrivilegedPlatformActions: true
2093
+ },
2094
+ claude: {
2095
+ id: "claude",
2096
+ displayName: "Claude Code",
2097
+ costTier: "high",
2098
+ authSources: ["oauth_local"],
2099
+ supportsLowRiskOrchestration: true,
2100
+ supportsPrivilegedPlatformActions: true
2101
+ }
2102
+ };
2103
+ function getOrchestrationProviderCapability(id) {
2104
+ return CAPABILITIES[id];
2124
2105
  }
2125
- function resolveGlobalDefaultModel(config = loadUserConfig()) {
2126
- const fromConfig = config.defaultModel?.trim();
2127
- if (fromConfig) return fromConfig;
2128
- const fromEnv = process.env.KYNVER_DEFAULT_MODEL?.trim();
2129
- if (fromEnv) return fromEnv;
2130
- return GLOBAL_DEFAULT_MODEL;
2106
+ function listOrchestrationProviderCapabilities() {
2107
+ return Object.values(CAPABILITIES);
2131
2108
  }
2132
- function inferProviderFromModel(model) {
2133
- const m = (model ?? "").toLowerCase();
2134
- if (!m) return "cursor";
2135
- if (m.includes("composer") || m.includes("cursor") || m.includes("codex") || m.startsWith("gpt-") || m.startsWith("gpt5")) {
2136
- return "cursor";
2109
+
2110
+ // src/orchestration-providers/codex-oauth-binding.ts
2111
+ import { existsSync as existsSync9 } from "node:fs";
2112
+ import { homedir as homedir5 } from "node:os";
2113
+ import path8 from "node:path";
2114
+
2115
+ // src/orchestration-providers/oauth-binding-utils.ts
2116
+ import { createHash } from "node:crypto";
2117
+ import { statSync as statSync3 } from "node:fs";
2118
+ import { spawnSync as spawnSync2 } from "node:child_process";
2119
+ function fingerprintAuthStore(filePath) {
2120
+ try {
2121
+ const st = statSync3(filePath);
2122
+ const material = `${filePath}|${st.size}|${st.mtimeMs}`;
2123
+ return createHash("sha256").update(material).digest("hex").slice(0, 16);
2124
+ } catch {
2125
+ return void 0;
2137
2126
  }
2138
- if (/^claude[-_]/i.test(m) || /^(?:opus|sonnet|haiku)\b/i.test(m)) {
2139
- return "claude";
2127
+ }
2128
+ function envKeyFingerprint(envKey) {
2129
+ return createHash("sha256").update(`env:${envKey}`).digest("hex").slice(0, 16);
2130
+ }
2131
+ function resolveCliBin(defaultBin, envKeys) {
2132
+ for (const key of envKeys) {
2133
+ const value = process.env[key]?.trim();
2134
+ if (value) return value;
2140
2135
  }
2141
- return "cursor";
2136
+ return defaultBin;
2142
2137
  }
2143
- function normalizeProviderAliasModel(model, explicitProvider) {
2144
- const alias = model.trim().toLowerCase();
2145
- const provider = explicitProvider?.trim();
2146
- if (alias === "cursor") {
2138
+ function probeCliCommand(bin, args, options = {}) {
2139
+ const timeoutMs = options.timeoutMs ?? 8e3;
2140
+ try {
2141
+ const result = spawnSync2(bin, args, {
2142
+ encoding: "utf8",
2143
+ timeout: timeoutMs,
2144
+ env: { ...process.env, CI: "1", NO_COLOR: "1" }
2145
+ });
2146
+ if (result.status === 0) {
2147
+ return { ok: true, note: options.okNote ?? `${bin} ${args.join(" ")}: ok` };
2148
+ }
2149
+ const stderr = (result.stderr || result.stdout || "").trim();
2150
+ const prefix = options.failPrefix ?? `${bin} ${args.join(" ")} failed`;
2147
2151
  return {
2148
- model: CURSOR_DEFAULT_MODEL,
2149
- provider: "cursor",
2150
- rule: provider && provider !== "cursor" ? "explicit:model_provider_alias_overrode_provider" : "explicit:model_provider_alias",
2151
- requestedModel: model
2152
+ ok: false,
2153
+ note: stderr ? `${prefix}: ${stderr.slice(0, 200)}` : prefix
2152
2154
  };
2153
- }
2154
- if (alias === "claude" || alias === "anthropic") {
2155
+ } catch (err) {
2155
2156
  return {
2156
- model: CLAUDE_DEFAULT_MODEL,
2157
- provider: "claude",
2158
- rule: provider && provider !== "claude" ? "explicit:model_provider_alias_overrode_provider" : "explicit:model_provider_alias",
2159
- requestedModel: model
2157
+ ok: false,
2158
+ note: err instanceof Error ? err.message : `${bin} probe failed`
2160
2159
  };
2161
2160
  }
2162
- return null;
2163
2161
  }
2164
- function isOpusLane(ref, title) {
2165
- if (ref.includes("deep") && ref.includes("review")) return true;
2166
- if (ref.includes("security")) return true;
2167
- if (ref.includes("plan_author") || ref.includes("plan-author")) return true;
2168
- if (title.includes("deep review") || title.includes("security review")) return true;
2169
- if (ref.includes("plan") && !ref.includes("review") && (ref.includes("author") || ref.includes("strategy"))) {
2170
- return true;
2162
+ function probeCliExitCodeOnly(bin, args, options = {}) {
2163
+ const timeoutMs = options.timeoutMs ?? 8e3;
2164
+ try {
2165
+ const result = spawnSync2(bin, args, {
2166
+ stdio: "ignore",
2167
+ timeout: timeoutMs,
2168
+ env: { ...process.env, CI: "1", NO_COLOR: "1" }
2169
+ });
2170
+ if (result.status === 0) {
2171
+ return { ok: true, note: options.okNote ?? `${bin} authenticated` };
2172
+ }
2173
+ return { ok: false, note: options.failNote ?? `${bin} not authenticated` };
2174
+ } catch (err) {
2175
+ return {
2176
+ ok: false,
2177
+ note: err instanceof Error ? err.message : `${bin} auth probe failed`
2178
+ };
2171
2179
  }
2172
- return false;
2173
2180
  }
2174
- function inferModelRoutingFromTask(task) {
2175
- const ref = normalizeRef(taskString3(task, "executorRef"));
2176
- const title = taskString3(task, "title").toLowerCase();
2177
- const priority = taskString3(task, "priority") || "normal";
2178
- const roleLane = normalizeRef(taskString3(task, "roleLane"));
2179
- if (ref.includes("cursor") || ref.includes("codex") || ref.includes("composer") || ref.includes("copilot") || roleLane === "implementer" || roleLane === "repair_implementer") {
2180
- return { provider: "cursor", rule: "lane:implementation" };
2181
+
2182
+ // src/orchestration-providers/codex-oauth-binding.ts
2183
+ var DEFAULT_CODEX_BIN = "codex";
2184
+ function authStoreCandidates() {
2185
+ const home = homedir5();
2186
+ return [
2187
+ path8.join(home, ".codex", "auth.json"),
2188
+ path8.join(home, ".config", "codex", "auth.json")
2189
+ ];
2190
+ }
2191
+ function probeCodexOAuthBinding(nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
2192
+ const bin = resolveCliBin(DEFAULT_CODEX_BIN, ["KYNVER_CODEX_BIN", "CODEX_BIN"]);
2193
+ if (process.env.CODEX_API_KEY?.trim()) {
2194
+ return {
2195
+ providerId: "codex",
2196
+ ready: true,
2197
+ authSource: "api_key_env",
2198
+ sessionFingerprint: envKeyFingerprint("CODEX_API_KEY"),
2199
+ checkedAt: nowIso,
2200
+ note: "CODEX_API_KEY present (env binding \u2014 cloud credits path, no token stored in Kynver)"
2201
+ };
2202
+ }
2203
+ const authPath = authStoreCandidates().find((p) => existsSync9(p));
2204
+ const login = probeCliCommand(bin, ["login", "status"], {
2205
+ okNote: "codex login status: authenticated",
2206
+ failPrefix: "codex login status"
2207
+ });
2208
+ const ready = Boolean(authPath) && login.ok;
2209
+ return {
2210
+ providerId: "codex",
2211
+ ready,
2212
+ authSource: "oauth_local",
2213
+ sessionFingerprint: authPath ? fingerprintAuthStore(authPath) : void 0,
2214
+ checkedAt: nowIso,
2215
+ note: ready ? "Local Codex OAuth session bound on runner" : [login.note, authPath ? void 0 : "no ~/.codex/auth.json"].filter(Boolean).join("; ")
2216
+ };
2217
+ }
2218
+
2219
+ // src/orchestration-providers/hermes-openai-codex-binding.ts
2220
+ import { existsSync as existsSync10 } from "node:fs";
2221
+ import { homedir as homedir6 } from "node:os";
2222
+ import path9 from "node:path";
2223
+ function hermesAuthStorePath() {
2224
+ const configured = process.env.HERMES_HOME?.trim();
2225
+ const home = configured ? path9.resolve(configured) : path9.join(homedir6(), ".hermes");
2226
+ return path9.join(home, "auth.json");
2227
+ }
2228
+ function probeHermesOpenAiCodexBinding(nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
2229
+ const bin = resolveCliBin("hermes", ["KYNVER_HERMES_BIN", "HERMES_BIN"]);
2230
+ const cli = probeCliCommand(bin, ["--version"], {
2231
+ okNote: `${bin} CLI available`,
2232
+ failPrefix: `${bin} CLI not available`
2233
+ });
2234
+ const authPath = hermesAuthStorePath();
2235
+ const authStorePresent = existsSync10(authPath);
2236
+ const auth = probeCliExitCodeOnly(bin, ["auth", "status", "openai-codex"], {
2237
+ okNote: "hermes openai-codex: logged in",
2238
+ failNote: "hermes openai-codex: not logged in"
2239
+ });
2240
+ const ready = cli.ok && auth.ok;
2241
+ const authSource = ready ? "subscription_hermes" : "none";
2242
+ return {
2243
+ path: "hermes_openai_codex",
2244
+ ready,
2245
+ authSource,
2246
+ sessionFingerprint: authStorePresent ? fingerprintAuthStore(authPath) : void 0,
2247
+ checkedAt: nowIso,
2248
+ note: ready ? "Hermes openai-codex subscription session bound (ChatGPT Codex OAuth)" : [cli.note, auth.note, authStorePresent ? void 0 : "no ~/.hermes/auth.json"].filter(Boolean).join("; ")
2249
+ };
2250
+ }
2251
+
2252
+ // src/orchestration-providers/codex-orchestration-adapter.ts
2253
+ function resolveCodexOrchestrationAdapter(nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
2254
+ const cliBinding = probeCodexOAuthBinding(nowIso);
2255
+ if (cliBinding.ready) {
2256
+ return { ...cliBinding, path: "codex_cli" };
2257
+ }
2258
+ const hermes = probeHermesOpenAiCodexBinding(nowIso);
2259
+ if (hermes.ready) {
2260
+ return {
2261
+ providerId: "codex",
2262
+ ready: true,
2263
+ authSource: hermes.authSource,
2264
+ sessionFingerprint: hermes.sessionFingerprint,
2265
+ checkedAt: hermes.checkedAt,
2266
+ note: hermes.note,
2267
+ path: "hermes_openai_codex",
2268
+ hermesOpenAiCodex: hermes
2269
+ };
2270
+ }
2271
+ return {
2272
+ providerId: "codex",
2273
+ ready: false,
2274
+ authSource: "none",
2275
+ checkedAt: nowIso,
2276
+ path: "none",
2277
+ hermesOpenAiCodex: hermes,
2278
+ note: [cliBinding.note, hermes.note].filter(Boolean).join("; ") || "Codex CLI and Hermes openai-codex unavailable"
2279
+ };
2280
+ }
2281
+
2282
+ // src/orchestration-providers/cost-router.ts
2283
+ var COST_TIER_ORDER = ["low", "medium", "high"];
2284
+ var AUTH_SOURCE_RANK = {
2285
+ oauth_local: 0,
2286
+ subscription_hermes: 0,
2287
+ runner_token: 1,
2288
+ api_key_env: 2,
2289
+ none: 3
2290
+ };
2291
+ function providerCapableForRisk(providerId, riskClass) {
2292
+ const cap = getOrchestrationProviderCapability(providerId);
2293
+ if (riskClass === "privileged") return cap.supportsPrivilegedPlatformActions;
2294
+ if (riskClass === "low") return cap.supportsLowRiskOrchestration;
2295
+ return true;
2296
+ }
2297
+ function compareProviderCandidates(a, b) {
2298
+ const capA = getOrchestrationProviderCapability(a.providerId);
2299
+ const capB = getOrchestrationProviderCapability(b.providerId);
2300
+ const tierDiff = COST_TIER_ORDER.indexOf(capA.costTier) - COST_TIER_ORDER.indexOf(capB.costTier);
2301
+ if (tierDiff !== 0) return tierDiff;
2302
+ const authDiff = AUTH_SOURCE_RANK[a.binding.authSource] - AUTH_SOURCE_RANK[b.binding.authSource];
2303
+ if (authDiff !== 0) return authDiff;
2304
+ return a.providerId.localeCompare(b.providerId);
2305
+ }
2306
+ function selectCheapestCapableProvider(input) {
2307
+ const candidates = listOrchestrationProviderCapabilities().map((cap) => ({
2308
+ providerId: cap.id,
2309
+ binding: input.inventory.bindings[cap.id]
2310
+ })).filter(
2311
+ (c) => c.binding.ready && providerCapableForRisk(c.providerId, input.riskClass)
2312
+ );
2313
+ if (candidates.length === 0) return null;
2314
+ const sorted = [...candidates].sort(compareProviderCandidates);
2315
+ const chosen = sorted[0];
2316
+ const lowTierProviderId = "codex";
2317
+ if (chosen.providerId === lowTierProviderId) {
2318
+ return { providerId: chosen.providerId, binding: chosen.binding };
2319
+ }
2320
+ const lowBinding = input.inventory.bindings[lowTierProviderId];
2321
+ let escalatedReason;
2322
+ if (!providerCapableForRisk(lowTierProviderId, input.riskClass)) {
2323
+ escalatedReason = `${input.riskClass} orchestration requires privileged-capable provider`;
2324
+ } else if (!lowBinding.ready) {
2325
+ escalatedReason = lowBinding.note ?? `${lowTierProviderId} not bound`;
2326
+ }
2327
+ return {
2328
+ providerId: chosen.providerId,
2329
+ binding: chosen.binding,
2330
+ escalatedFrom: lowTierProviderId,
2331
+ escalatedReason
2332
+ };
2333
+ }
2334
+
2335
+ // src/orchestration-providers/claude-oauth-binding.ts
2336
+ import { existsSync as existsSync11 } from "node:fs";
2337
+ import { homedir as homedir7 } from "node:os";
2338
+ import path10 from "node:path";
2339
+ var DEFAULT_CLAUDE_BIN = "claude";
2340
+ function claudeAuthStoreCandidates() {
2341
+ const home = homedir7();
2342
+ return [path10.join(home, ".claude", ".credentials.json")];
2343
+ }
2344
+ function probeClaudeOAuthBinding(nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
2345
+ const bin = resolveCliBin(DEFAULT_CLAUDE_BIN, ["KYNVER_CLAUDE_BIN", "CLAUDE_BIN"]);
2346
+ if (process.env.ANTHROPIC_API_KEY?.trim()) {
2347
+ return {
2348
+ providerId: "claude",
2349
+ ready: true,
2350
+ authSource: "api_key_env",
2351
+ sessionFingerprint: envKeyFingerprint("ANTHROPIC_API_KEY"),
2352
+ checkedAt: nowIso,
2353
+ note: "ANTHROPIC_API_KEY present (env binding \u2014 cloud credits path, no token stored in Kynver)"
2354
+ };
2355
+ }
2356
+ const cli = probeCliCommand(bin, ["--version"], {
2357
+ okNote: `${bin} CLI available`,
2358
+ failPrefix: `${bin} CLI not available`
2359
+ });
2360
+ const authPath = claudeAuthStoreCandidates().find((p) => existsSync11(p));
2361
+ const login = probeCliExitCodeOnly(bin, ["auth", "status"], {
2362
+ okNote: "claude auth status: authenticated",
2363
+ failNote: "claude auth status: not authenticated"
2364
+ });
2365
+ const ready = cli.ok && (login.ok || Boolean(authPath));
2366
+ return {
2367
+ providerId: "claude",
2368
+ ready,
2369
+ authSource: "oauth_local",
2370
+ sessionFingerprint: authPath ? fingerprintAuthStore(authPath) : void 0,
2371
+ checkedAt: nowIso,
2372
+ note: ready ? "Local Claude Code OAuth session bound on runner" : [cli.note, login.note, authPath ? void 0 : "no ~/.claude/.credentials.json"].filter(Boolean).join("; ")
2373
+ };
2374
+ }
2375
+
2376
+ // src/orchestration-providers/cursor-oauth-binding.ts
2377
+ import { existsSync as existsSync12 } from "node:fs";
2378
+ import { homedir as homedir8 } from "node:os";
2379
+ import path11 from "node:path";
2380
+ var DEFAULT_AGENT_BIN = "agent";
2381
+ function cursorAuthStoreCandidates() {
2382
+ const home = homedir8();
2383
+ return [path11.join(home, ".config", "cursor", "auth.json")];
2384
+ }
2385
+ function probeCursorOAuthBinding(nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
2386
+ const bin = resolveCliBin(DEFAULT_AGENT_BIN, [
2387
+ "KYNVER_CURSOR_AGENT_BIN",
2388
+ "CURSOR_AGENT_BIN"
2389
+ ]);
2390
+ if (process.env.CURSOR_API_KEY?.trim()) {
2391
+ return {
2392
+ providerId: "cursor",
2393
+ ready: true,
2394
+ authSource: "api_key_env",
2395
+ sessionFingerprint: envKeyFingerprint("CURSOR_API_KEY"),
2396
+ checkedAt: nowIso,
2397
+ note: "CURSOR_API_KEY present (env binding \u2014 cloud credits path, no token stored in Kynver)"
2398
+ };
2399
+ }
2400
+ const cli = probeCliCommand(bin, ["-v"], {
2401
+ okNote: `${bin} CLI available`,
2402
+ failPrefix: `${bin} CLI not available`
2403
+ });
2404
+ const authPath = cursorAuthStoreCandidates().find((p) => existsSync12(p));
2405
+ const ready = Boolean(authPath) && cli.ok;
2406
+ return {
2407
+ providerId: "cursor",
2408
+ ready,
2409
+ authSource: "oauth_local",
2410
+ sessionFingerprint: authPath ? fingerprintAuthStore(authPath) : void 0,
2411
+ checkedAt: nowIso,
2412
+ note: ready ? "Local Cursor/Composer OAuth session bound on runner" : [cli.note, authPath ? void 0 : "no ~/.config/cursor/auth.json"].filter(Boolean).join("; ")
2413
+ };
2414
+ }
2415
+
2416
+ // src/orchestration-providers/hermes-cli-adapter.ts
2417
+ import { existsSync as existsSync13, readFileSync as readFileSync8 } from "node:fs";
2418
+ import { homedir as homedir9 } from "node:os";
2419
+ import path12 from "node:path";
2420
+ var PROFILE_ENV_KEYS = [
2421
+ "OPENAI_API_KEY",
2422
+ "ANTHROPIC_API_KEY",
2423
+ "KYNVER_API_KEY",
2424
+ "KYNVER_BASE_URL"
2425
+ ];
2426
+ function hermesProfileEnvPath() {
2427
+ const configured = process.env.HERMES_HOME?.trim();
2428
+ const home = configured ? path12.resolve(configured) : path12.join(homedir9(), ".hermes");
2429
+ return path12.join(home, ".env");
2430
+ }
2431
+ function profileEnvKeyPresence(envPath) {
2432
+ try {
2433
+ const text = readFileSync8(envPath, "utf8");
2434
+ const present = [];
2435
+ for (const key of PROFILE_ENV_KEYS) {
2436
+ const re = new RegExp(`^${key}=`, "m");
2437
+ if (re.test(text)) present.push(key);
2438
+ }
2439
+ return present;
2440
+ } catch {
2441
+ return [];
2442
+ }
2443
+ }
2444
+ function probeHermesCliAdapter(nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
2445
+ const bin = resolveCliBin("hermes", ["KYNVER_HERMES_BIN", "HERMES_BIN"]);
2446
+ const cli = probeCliCommand(bin, ["--version"], {
2447
+ okNote: `${bin} CLI available`,
2448
+ failPrefix: `${bin} CLI not available`
2449
+ });
2450
+ const profilePath = hermesProfileEnvPath();
2451
+ const profileBound = existsSync13(profilePath);
2452
+ const envKeys = profileBound ? profileEnvKeyPresence(profilePath) : [];
2453
+ const hasApiKeys = envKeys.some((k) => k.endsWith("_API_KEY"));
2454
+ const authSource = hasApiKeys ? "api_key_env" : profileBound ? "oauth_local" : "none";
2455
+ const ready = cli.ok && profileBound;
2456
+ return {
2457
+ adapterId: "hermes",
2458
+ cliAvailable: cli.ok,
2459
+ profileBound,
2460
+ authSource,
2461
+ sessionFingerprint: profileBound ? fingerprintAuthStore(profilePath) : void 0,
2462
+ checkedAt: nowIso,
2463
+ note: ready ? `Hermes profile bound (${envKeys.length ? `keys: ${envKeys.join(", ")}` : "no API keys detected"})` : [cli.note, profileBound ? void 0 : "no ~/.hermes/.env"].filter(Boolean).join("; ")
2464
+ };
2465
+ }
2466
+
2467
+ // src/orchestration-providers/inventory.ts
2468
+ function buildOrchestrationProviderInventory(options = {}) {
2469
+ const generatedAt = options.nowIso?.() ?? (/* @__PURE__ */ new Date()).toISOString();
2470
+ const codexAdapter = resolveCodexOrchestrationAdapter(generatedAt);
2471
+ const bindings = {
2472
+ codex: codexAdapter,
2473
+ cursor: probeCursorOAuthBinding(generatedAt),
2474
+ claude: probeClaudeOAuthBinding(generatedAt)
2475
+ };
2476
+ const readyCount = Object.values(bindings).filter((b) => b.ready).length;
2477
+ return {
2478
+ generatedAt,
2479
+ bindings,
2480
+ cloudApiCredits: {
2481
+ anthropicApiKey: Boolean(process.env.ANTHROPIC_API_KEY?.trim()),
2482
+ openaiApiKey: Boolean(process.env.OPENAI_API_KEY?.trim()),
2483
+ codexApiKey: Boolean(process.env.CODEX_API_KEY?.trim())
2484
+ },
2485
+ hermes: probeHermesCliAdapter(generatedAt),
2486
+ hermesOpenAiCodex: probeHermesOpenAiCodexBinding(generatedAt),
2487
+ codexAdapterPath: codexAdapter.path,
2488
+ readyCount
2489
+ };
2490
+ }
2491
+
2492
+ // src/orchestration-providers/routing.ts
2493
+ var PRIVILEGED_MARKERS = [
2494
+ /\bprivileged\b/i,
2495
+ /\bproduction\s+db\b/i,
2496
+ /\bdb:push\b/i,
2497
+ /\bmigration\b/i,
2498
+ /\bdeploy\b/i,
2499
+ /\bsecrets?\b/i,
2500
+ /\bstripe\b/i,
2501
+ /\[require-approval\]/i,
2502
+ /\[operator-only\]/i
2503
+ ];
2504
+ var LOW_RISK_MARKERS = [
2505
+ /\borchestration\b/i,
2506
+ /\bplan[- ]?progress\b/i,
2507
+ /\bstatus\s+sync\b/i,
2508
+ /\bheartbeat\b/i,
2509
+ /\bboard[- ]?drain\b/i,
2510
+ /\bmaintenance\b/i,
2511
+ /\[orchestration:\s*low-risk\]/i
2512
+ ];
2513
+ function taskString3(task, key) {
2514
+ const v = task[key];
2515
+ return typeof v === "string" ? v.trim() : "";
2516
+ }
2517
+ function classifyOrchestrationRisk(task) {
2518
+ const ref = taskString3(task, "executorRef").toLowerCase();
2519
+ const title = taskString3(task, "title").toLowerCase();
2520
+ const description = taskString3(task, "description").toLowerCase();
2521
+ const blob = `${ref}
2522
+ ${title}
2523
+ ${description}`;
2524
+ if (PRIVILEGED_MARKERS.some((re) => re.test(blob))) return "privileged";
2525
+ if (ref.includes("provider:claude") || ref.includes("deep_review") || ref.includes("security")) {
2526
+ return "elevated";
2527
+ }
2528
+ if (ref.includes("landing") || ref.includes("review") || title.startsWith("land:") || ref.includes("implementer") || ref.includes("repair") || title.includes("implement")) {
2529
+ return "elevated";
2530
+ }
2531
+ if (LOW_RISK_MARKERS.some((re) => re.test(blob))) {
2532
+ return "low";
2533
+ }
2534
+ return "elevated";
2535
+ }
2536
+ function buildAudit(input) {
2537
+ const cap = getOrchestrationProviderCapability(input.provider);
2538
+ const costRationale = input.escalatedFrom && input.escalatedReason ? `Escalated from ${input.escalatedFrom} (${input.escalatedReason}); using ${cap.displayName} (${cap.costTier} tier)` : `${cap.displayName} selected for ${input.riskClass} orchestration (${cap.costTier} tier, auth=${input.authSource})`;
2539
+ return {
2540
+ provider: input.provider,
2541
+ model: input.model,
2542
+ authSource: input.authSource,
2543
+ costTier: cap.costTier,
2544
+ riskClass: input.riskClass,
2545
+ costRationale,
2546
+ routingRule: input.routingRule,
2547
+ escalatedFrom: input.escalatedFrom,
2548
+ escalatedReason: input.escalatedReason
2549
+ };
2550
+ }
2551
+ function resolveInventory(input) {
2552
+ const inventory = input.inventory ?? buildOrchestrationProviderInventory();
2553
+ if (!input.codexBinding) return inventory;
2554
+ return {
2555
+ ...inventory,
2556
+ bindings: { ...inventory.bindings, codex: input.codexBinding }
2557
+ };
2558
+ }
2559
+ function decisionForProvider(input) {
2560
+ const audit = buildAudit({
2561
+ provider: input.providerId,
2562
+ model: input.model,
2563
+ authSource: input.binding.authSource,
2564
+ riskClass: input.riskClass,
2565
+ routingRule: input.routingRule,
2566
+ escalatedFrom: input.escalatedFrom,
2567
+ escalatedReason: input.escalatedReason
2568
+ });
2569
+ return {
2570
+ provider: input.providerId,
2571
+ model: input.model,
2572
+ rule: audit.routingRule,
2573
+ audit
2574
+ };
2575
+ }
2576
+ function resolveOrchestrationRouting(input) {
2577
+ const task = input.task ?? {};
2578
+ const risk = classifyOrchestrationRisk(task);
2579
+ const inventory = resolveInventory({
2580
+ inventory: input.inventory,
2581
+ codexBinding: input.codexBinding ?? resolveCodexOrchestrationAdapter()
2582
+ });
2583
+ const explicit = input.preferLowCost === false ? null : input.explicitProvider?.trim().toLowerCase();
2584
+ const explicitModel = input.explicitModel?.trim() || void 0;
2585
+ const fallbackProvider = DEFAULT_WORKER_PROVIDER;
2586
+ if (explicit === "codex" || explicit === "provider:codex") {
2587
+ const binding = inventory.bindings.codex;
2588
+ if (!binding.ready) {
2589
+ const selection2 = selectCheapestCapableProvider({ inventory, riskClass: risk });
2590
+ if (selection2) {
2591
+ return decisionForProvider({
2592
+ providerId: selection2.providerId,
2593
+ binding: selection2.binding,
2594
+ riskClass: risk,
2595
+ routingRule: "orchestration:explicit_codex_unavailable_cost_aware",
2596
+ model: explicitModel,
2597
+ escalatedFrom: "codex",
2598
+ escalatedReason: binding.note ?? "codex oauth not bound"
2599
+ });
2600
+ }
2601
+ return decisionForProvider({
2602
+ providerId: fallbackProvider,
2603
+ binding: inventory.bindings[fallbackProvider],
2604
+ riskClass: risk,
2605
+ routingRule: "orchestration:codex_unavailable_escalate_cursor",
2606
+ model: explicitModel,
2607
+ escalatedFrom: "codex",
2608
+ escalatedReason: binding.note ?? "codex oauth not bound"
2609
+ });
2610
+ }
2611
+ return decisionForProvider({
2612
+ providerId: "codex",
2613
+ binding,
2614
+ riskClass: risk,
2615
+ routingRule: "orchestration:explicit_codex",
2616
+ model: explicitModel
2617
+ });
2618
+ }
2619
+ if (explicit === "cursor" || explicit === "provider:cursor") {
2620
+ const binding = inventory.bindings.cursor;
2621
+ if (binding.ready) {
2622
+ return decisionForProvider({
2623
+ providerId: "cursor",
2624
+ binding,
2625
+ riskClass: risk,
2626
+ routingRule: "orchestration:explicit_cursor",
2627
+ model: explicitModel
2628
+ });
2629
+ }
2630
+ }
2631
+ if (explicit === "claude" || explicit === "provider:claude") {
2632
+ const binding = inventory.bindings.claude;
2633
+ if (binding.ready) {
2634
+ return decisionForProvider({
2635
+ providerId: "claude",
2636
+ binding,
2637
+ riskClass: risk,
2638
+ routingRule: "orchestration:explicit_claude",
2639
+ model: explicitModel
2640
+ });
2641
+ }
2642
+ }
2643
+ const selection = selectCheapestCapableProvider({ inventory, riskClass: risk });
2644
+ if (selection) {
2645
+ return decisionForProvider({
2646
+ providerId: selection.providerId,
2647
+ binding: selection.binding,
2648
+ riskClass: risk,
2649
+ routingRule: `orchestration:cost_aware_${selection.providerId}`,
2650
+ model: explicitModel,
2651
+ escalatedFrom: selection.escalatedFrom,
2652
+ escalatedReason: selection.escalatedReason
2653
+ });
2654
+ }
2655
+ const codexBinding = inventory.bindings.codex;
2656
+ return decisionForProvider({
2657
+ providerId: fallbackProvider,
2658
+ binding: inventory.bindings[fallbackProvider],
2659
+ riskClass: risk,
2660
+ routingRule: codexBinding.ready ? "orchestration:inventory_empty_default_cursor" : "orchestration:codex_unavailable_default_cursor",
2661
+ model: explicitModel,
2662
+ ...codexBinding.ready ? {} : { escalatedFrom: "codex", escalatedReason: codexBinding.note ?? "codex oauth not bound" }
2663
+ });
2664
+ }
2665
+
2666
+ // src/providers/claude.ts
2667
+ import { closeSync, openSync } from "node:fs";
2668
+ import { spawn } from "node:child_process";
2669
+
2670
+ // src/providers/model-preflight.ts
2671
+ var REASONING_SUFFIX_RE = /-(?:thinking(?:-(?:high|medium|low|minimal|max|none))?|high|medium|low|minimal)$/i;
2672
+ function stripReasoningSuffix(model) {
2673
+ return model.replace(REASONING_SUFFIX_RE, "");
2674
+ }
2675
+ var FOREIGN_MODEL_RE = /^(?:gpt-|gpt5|o1|o3|o4|gemini-|grok-|composer|deepseek|llama|mistral|qwen|command-)/i;
2676
+ function looksLikeClaudeModel(model) {
2677
+ return /^claude[-_]/i.test(model) || /^(?:opus|sonnet|haiku)\b/i.test(model);
2678
+ }
2679
+ var CURSOR_MODEL_ALIASES = /* @__PURE__ */ new Set(["cursor"]);
2680
+ function normalizeCursorModelAlias(model) {
2681
+ const key = model.trim().toLowerCase();
2682
+ if (CURSOR_MODEL_ALIASES.has(key)) return "auto";
2683
+ return null;
2684
+ }
2685
+ function preflightClaudeModel(model, defaultModel) {
2686
+ const requested = (model ?? "").trim();
2687
+ if (!requested) {
2688
+ return { ok: true, model: defaultModel, normalized: false };
2689
+ }
2690
+ const stripped = stripReasoningSuffix(requested).trim();
2691
+ const launch = stripped || defaultModel;
2692
+ if (FOREIGN_MODEL_RE.test(launch) || !looksLikeClaudeModel(launch) && launch !== defaultModel) {
2693
+ return {
2694
+ ok: false,
2695
+ model: requested,
2696
+ normalized: false,
2697
+ requested,
2698
+ note: `model "${requested}" is not a Claude model \u2014 the "claude" provider drives the Claude CLI, which only accepts claude-* model ids (got "${launch}"). Pick a Claude model or switch the worker provider.`
2699
+ };
2700
+ }
2701
+ if (launch !== requested) {
2702
+ return {
2703
+ ok: true,
2704
+ model: launch,
2705
+ normalized: true,
2706
+ requested,
2707
+ note: `normalized model "${requested}" \u2192 "${launch}" (the Claude CLI rejects reasoning-effort suffixes)`
2708
+ };
2709
+ }
2710
+ return { ok: true, model: launch, normalized: false };
2711
+ }
2712
+ function preflightCodexModel(model, defaultModel) {
2713
+ const requested = (model ?? "").trim();
2714
+ if (!requested) {
2715
+ return { ok: true, model: defaultModel, normalized: false };
2716
+ }
2717
+ if (looksLikeClaudeModel(requested)) {
2718
+ return {
2719
+ ok: false,
2720
+ model: requested,
2721
+ normalized: false,
2722
+ requested,
2723
+ note: `model "${requested}" is a Claude model but the worker provider is "codex". Switch the provider or pick a Codex/OpenAI model.`
2724
+ };
2725
+ }
2726
+ return { ok: true, model: requested, normalized: false };
2727
+ }
2728
+ function preflightCursorModel(model, defaultModel) {
2729
+ const requested = (model ?? "").trim();
2730
+ if (!requested) {
2731
+ return { ok: true, model: defaultModel, normalized: false };
2732
+ }
2733
+ const aliasLaunch = normalizeCursorModelAlias(requested);
2734
+ if (aliasLaunch) {
2735
+ return {
2736
+ ok: true,
2737
+ model: aliasLaunch,
2738
+ normalized: true,
2739
+ requested,
2740
+ note: `normalized model "${requested}" \u2192 "${aliasLaunch}" (Cursor provider alias \u2014 use "auto" or a composer id, not "cursor")`
2741
+ };
2742
+ }
2743
+ if (looksLikeClaudeModel(requested)) {
2744
+ return {
2745
+ ok: false,
2746
+ model: requested,
2747
+ normalized: false,
2748
+ requested,
2749
+ note: `model "${requested}" is a Claude model but the worker provider is "cursor". Switch the provider to "claude" or pick a Cursor model.`
2750
+ };
2751
+ }
2752
+ return { ok: true, model: requested, normalized: false };
2753
+ }
2754
+
2755
+ // src/providers/claude.ts
2756
+ var CLAUDE_DEFAULT_MODEL = "claude-sonnet-4-6";
2757
+ var claudeProvider = {
2758
+ name: "claude",
2759
+ defaultModel: CLAUDE_DEFAULT_MODEL,
2760
+ preflightModel(model) {
2761
+ return preflightClaudeModel(model, CLAUDE_DEFAULT_MODEL);
2762
+ },
2763
+ start(opts) {
2764
+ const preflight = preflightClaudeModel(opts.model, CLAUDE_DEFAULT_MODEL);
2765
+ if (!preflight.ok) {
2766
+ throw new Error(`claude provider model preflight failed: ${preflight.note}`);
2767
+ }
2768
+ const model = preflight.model;
2769
+ const stdoutFd = openSync(opts.stdoutPath, "a");
2770
+ const stderrFd = openSync(opts.stderrPath, "a");
2771
+ const stdio = ["ignore", stdoutFd, stderrFd];
2772
+ const child = spawn(
2773
+ "claude",
2774
+ [
2775
+ "--model",
2776
+ model,
2777
+ "-p",
2778
+ "--verbose",
2779
+ "--permission-mode",
2780
+ "bypassPermissions",
2781
+ "--output-format",
2782
+ "stream-json",
2783
+ "--include-partial-messages",
2784
+ opts.prompt
2785
+ ],
2786
+ hiddenSpawnOptions({
2787
+ cwd: opts.worktreePath,
2788
+ detached: true,
2789
+ stdio,
2790
+ env: scrubWorkerEnv(process.env)
2791
+ })
2792
+ );
2793
+ closeSync(stdoutFd);
2794
+ closeSync(stderrFd);
2795
+ if (!child.pid) {
2796
+ throw new Error("failed to spawn claude worker process (is the `claude` CLI on PATH?)");
2797
+ }
2798
+ child.unref();
2799
+ return { pid: child.pid, model };
2800
+ }
2801
+ };
2802
+
2803
+ // src/providers/codex.ts
2804
+ import { closeSync as closeSync3, existsSync as existsSync14, openSync as openSync3 } from "node:fs";
2805
+ import { spawn as spawn3 } from "node:child_process";
2806
+
2807
+ // src/providers/hermes-codex.ts
2808
+ import { closeSync as closeSync2, openSync as openSync2 } from "node:fs";
2809
+ import { spawn as spawn2 } from "node:child_process";
2810
+ var HERMES_OPENAI_CODEX_DEFAULT_MODEL = "gpt-5.4";
2811
+ function hermesWorkerEnv() {
2812
+ return scrubWorkerEnv({
2813
+ ...process.env,
2814
+ CI: "1",
2815
+ NO_COLOR: "1",
2816
+ HERMES_ACCEPT_HOOKS: process.env.HERMES_ACCEPT_HOOKS ?? "1"
2817
+ });
2818
+ }
2819
+ function buildHermesOpenAiCodexChatArgv(model, prompt) {
2820
+ const maxTurns = process.env.KYNVER_HERMES_CODEX_MAX_TURNS?.trim() || "40";
2821
+ return [
2822
+ "chat",
2823
+ "-q",
2824
+ prompt,
2825
+ "--provider",
2826
+ "openai-codex",
2827
+ "-m",
2828
+ model,
2829
+ "-Q",
2830
+ "--accept-hooks",
2831
+ "--max-turns",
2832
+ maxTurns,
2833
+ "--toolsets",
2834
+ "hermes-cli"
2835
+ ];
2836
+ }
2837
+ var hermesCodexProvider = {
2838
+ name: "hermes-codex",
2839
+ defaultModel: HERMES_OPENAI_CODEX_DEFAULT_MODEL,
2840
+ preflightModel(model) {
2841
+ return preflightCodexModel(model, HERMES_OPENAI_CODEX_DEFAULT_MODEL);
2842
+ },
2843
+ start(opts) {
2844
+ const preflight = preflightCodexModel(opts.model, HERMES_OPENAI_CODEX_DEFAULT_MODEL);
2845
+ if (!preflight.ok) {
2846
+ throw new Error(`hermes-codex provider model preflight failed: ${preflight.note}`);
2847
+ }
2848
+ const model = preflight.model;
2849
+ const stdoutFd = openSync2(opts.stdoutPath, "a");
2850
+ const stderrFd = openSync2(opts.stderrPath, "a");
2851
+ const stdio = ["ignore", stdoutFd, stderrFd];
2852
+ const bin = resolveCliBin("hermes", ["KYNVER_HERMES_BIN", "HERMES_BIN"]);
2853
+ const args = buildHermesOpenAiCodexChatArgv(model, opts.prompt);
2854
+ const child = spawn2(
2855
+ bin,
2856
+ args,
2857
+ hiddenSpawnOptions({
2858
+ cwd: opts.worktreePath,
2859
+ detached: true,
2860
+ shell: false,
2861
+ stdio,
2862
+ env: hermesWorkerEnv()
2863
+ })
2864
+ );
2865
+ closeSync2(stdoutFd);
2866
+ closeSync2(stderrFd);
2867
+ if (!child.pid) {
2868
+ throw new Error(
2869
+ `failed to spawn Hermes openai-codex worker (is \`${bin}\` on PATH? run \`hermes auth status openai-codex\`)`
2870
+ );
2871
+ }
2872
+ child.unref();
2873
+ return { pid: child.pid, model };
2874
+ }
2875
+ };
2876
+
2877
+ // src/providers/codex.ts
2878
+ var CODEX_DEFAULT_MODEL = "gpt-5.4";
2879
+ function resolveCodexBin() {
2880
+ return process.env.KYNVER_CODEX_BIN?.trim() || process.env.CODEX_BIN?.trim() || "codex";
2881
+ }
2882
+ function codexWorkerEnv() {
2883
+ return scrubWorkerEnv({
2884
+ ...process.env,
2885
+ CI: "1",
2886
+ NO_COLOR: "1"
2887
+ });
2888
+ }
2889
+ function buildCodexExecArgv(model, prompt) {
2890
+ return [
2891
+ "exec",
2892
+ "--sandbox",
2893
+ "read-only",
2894
+ "--ask-for-approval",
2895
+ "never",
2896
+ "--model",
2897
+ model,
2898
+ prompt
2899
+ ];
2900
+ }
2901
+ function spawnCodexProcess(bin, args, opts) {
2902
+ const useScript = process.platform !== "win32" && existsSync14("/usr/bin/script");
2903
+ if (!useScript) {
2904
+ return spawn3(bin, args, opts);
2905
+ }
2906
+ const quoted = [bin, ...args].map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(" ");
2907
+ return spawn3("script", ["-qfc", quoted, "/dev/null"], opts);
2908
+ }
2909
+ var codexProvider = {
2910
+ name: "codex",
2911
+ defaultModel: CODEX_DEFAULT_MODEL,
2912
+ preflightModel(model) {
2913
+ return preflightCodexModel(model, CODEX_DEFAULT_MODEL);
2914
+ },
2915
+ start(opts) {
2916
+ const adapter = resolveCodexOrchestrationAdapter();
2917
+ if (adapter.path === "hermes_openai_codex") {
2918
+ return hermesCodexProvider.start(opts);
2919
+ }
2920
+ const preflight = preflightCodexModel(opts.model, CODEX_DEFAULT_MODEL);
2921
+ if (!preflight.ok) {
2922
+ throw new Error(`codex provider model preflight failed: ${preflight.note}`);
2923
+ }
2924
+ const model = preflight.model;
2925
+ const stdoutFd = openSync3(opts.stdoutPath, "a");
2926
+ const stderrFd = openSync3(opts.stderrPath, "a");
2927
+ const stdio = ["ignore", stdoutFd, stderrFd];
2928
+ const bin = resolveCodexBin();
2929
+ const args = buildCodexExecArgv(model, opts.prompt);
2930
+ const child = spawnCodexProcess(
2931
+ bin,
2932
+ args,
2933
+ hiddenSpawnOptions({
2934
+ cwd: opts.worktreePath,
2935
+ detached: true,
2936
+ shell: false,
2937
+ stdio,
2938
+ env: codexWorkerEnv()
2939
+ })
2940
+ );
2941
+ closeSync3(stdoutFd);
2942
+ closeSync3(stderrFd);
2943
+ if (!child.pid) {
2944
+ throw new Error(
2945
+ `failed to spawn Codex worker (is \`${bin}\` on PATH? run \`codex login\` or set CODEX_API_KEY)`
2946
+ );
2947
+ }
2948
+ child.unref();
2949
+ return { pid: child.pid, model };
2950
+ }
2951
+ };
2952
+
2953
+ // src/model-routing.ts
2954
+ var GLOBAL_DEFAULT_MODEL = "claude-sonnet-4-6";
2955
+ var CURSOR_DEFAULT_MODEL = "composer-2.5";
2956
+ function taskString4(task, key) {
2957
+ const v = task[key];
2958
+ return typeof v === "string" ? v.trim() : "";
2959
+ }
2960
+ function normalizeRef(ref) {
2961
+ return ref.toLowerCase();
2962
+ }
2963
+ function resolveGlobalDefaultModel(config = loadUserConfig()) {
2964
+ const fromConfig = config.defaultModel?.trim();
2965
+ if (fromConfig) return fromConfig;
2966
+ const fromEnv = process.env.KYNVER_DEFAULT_MODEL?.trim();
2967
+ if (fromEnv) return fromEnv;
2968
+ return GLOBAL_DEFAULT_MODEL;
2969
+ }
2970
+ function inferProviderFromModel(model) {
2971
+ const m = (model ?? "").toLowerCase();
2972
+ if (!m) return "cursor";
2973
+ if (m.includes("composer") || m.includes("cursor") || m.includes("codex") || m.startsWith("gpt-") || m.startsWith("gpt5")) {
2974
+ return "cursor";
2975
+ }
2976
+ if (/^claude[-_]/i.test(m) || /^(?:opus|sonnet|haiku)\b/i.test(m)) {
2977
+ return "claude";
2978
+ }
2979
+ return "cursor";
2980
+ }
2981
+ function normalizeProviderAliasModel(model, explicitProvider) {
2982
+ const alias = model.trim().toLowerCase();
2983
+ const provider = explicitProvider?.trim();
2984
+ if (alias === "cursor") {
2985
+ return {
2986
+ model: CURSOR_DEFAULT_MODEL,
2987
+ provider: "cursor",
2988
+ rule: provider && provider !== "cursor" ? "explicit:model_provider_alias_overrode_provider" : "explicit:model_provider_alias",
2989
+ requestedModel: model
2990
+ };
2991
+ }
2992
+ if (alias === "claude" || alias === "anthropic") {
2993
+ return {
2994
+ model: CLAUDE_DEFAULT_MODEL,
2995
+ provider: "claude",
2996
+ rule: provider && provider !== "claude" ? "explicit:model_provider_alias_overrode_provider" : "explicit:model_provider_alias",
2997
+ requestedModel: model
2998
+ };
2999
+ }
3000
+ return null;
3001
+ }
3002
+ function isOpusLane(ref, title) {
3003
+ if (ref.includes("deep") && ref.includes("review")) return true;
3004
+ if (ref.includes("security")) return true;
3005
+ if (ref.includes("plan_author") || ref.includes("plan-author")) return true;
3006
+ if (title.includes("deep review") || title.includes("security review")) return true;
3007
+ if (ref.includes("plan") && !ref.includes("review") && (ref.includes("author") || ref.includes("strategy"))) {
3008
+ return true;
3009
+ }
3010
+ return false;
3011
+ }
3012
+ function inferModelRoutingFromTask(task) {
3013
+ const ref = normalizeRef(taskString4(task, "executorRef"));
3014
+ const title = taskString4(task, "title").toLowerCase();
3015
+ const priority = taskString4(task, "priority") || "normal";
3016
+ const roleLane = normalizeRef(taskString4(task, "roleLane"));
3017
+ if (ref.includes("provider:codex") || ref.startsWith("codex:")) {
3018
+ return { provider: "codex", model: CODEX_DEFAULT_MODEL, rule: "lane:codex_orchestration" };
3019
+ }
3020
+ if (ref.includes("cursor") || ref.includes("composer") || ref.includes("copilot") || roleLane === "implementer" || roleLane === "repair_implementer") {
3021
+ return { provider: "cursor", rule: "lane:implementation" };
3022
+ }
3023
+ if (ref.includes("codex")) {
3024
+ return { provider: "codex", model: CODEX_DEFAULT_MODEL, rule: "lane:codex_orchestration" };
2181
3025
  }
2182
3026
  if (ref.includes("landing") || title.startsWith("land:") || title.includes(" merge")) {
2183
3027
  return { provider: "cursor", rule: "lane:landing" };
@@ -2237,12 +3081,33 @@ function resolveWorkerLaunch(input) {
2237
3081
  requestedModel: model
2238
3082
  };
2239
3083
  }
2240
- return enforceCursorWorkerProvider({
3084
+ const afterCursorPolicy = enforceCursorWorkerProvider({
2241
3085
  routing: decision,
2242
3086
  task: input.task,
2243
3087
  explicitProvider: input.explicitProvider,
2244
3088
  explicitProviderIsOperatorOverride: input.explicitProviderIsOperatorOverride
2245
3089
  });
3090
+ if (!input.task || Object.keys(input.task).length === 0) {
3091
+ return afterCursorPolicy;
3092
+ }
3093
+ if (afterCursorPolicy.rule === "explicit:model_provider_alias" || afterCursorPolicy.rule === "explicit:model_provider_alias_overrode_provider") {
3094
+ return afterCursorPolicy;
3095
+ }
3096
+ if (isClaudeFamilyProvider(afterCursorPolicy.provider) && (input.explicitProviderIsOperatorOverride || taskAllowsClaudeWorker(input.task))) {
3097
+ return afterCursorPolicy;
3098
+ }
3099
+ const orchestration = resolveOrchestrationRouting({
3100
+ task: input.task,
3101
+ explicitProvider: input.explicitProvider ?? afterCursorPolicy.provider,
3102
+ explicitModel: afterCursorPolicy.model
3103
+ });
3104
+ return {
3105
+ provider: orchestration.provider,
3106
+ model: orchestration.provider === "codex" ? orchestration.model ?? afterCursorPolicy.model ?? CODEX_DEFAULT_MODEL : afterCursorPolicy.model,
3107
+ rule: orchestration.rule,
3108
+ requestedModel: afterCursorPolicy.requestedModel,
3109
+ orchestrationAudit: orchestration.audit
3110
+ };
2246
3111
  }
2247
3112
  function resolveModelFallback(startedModel, launchModel, providerDefault) {
2248
3113
  return startedModel || launchModel || providerDefault || resolveGlobalDefaultModel() || CLAUDE_DEFAULT_MODEL;
@@ -2256,16 +3121,69 @@ function positiveInt2(value, fallback) {
2256
3121
  }
2257
3122
  function readHarnessRetryLimits() {
2258
3123
  return {
2259
- maxTaskAttempts: positiveInt2(process.env.KYNVER_MAX_TASK_ATTEMPTS, 3),
3124
+ maxTaskAttempts: positiveInt2(process.env.KYNVER_MAX_TASK_ATTEMPTS, 4),
2260
3125
  dispatchCooldownMs: positiveInt2(process.env.KYNVER_DISPATCH_COOLDOWN_MS, 5e3)
2261
3126
  };
2262
3127
  }
2263
3128
 
2264
3129
  // src/lease-renewal.ts
2265
- import path8 from "node:path";
3130
+ import path13 from "node:path";
3131
+
3132
+ // src/harness-lease-owner.ts
3133
+ var HARNESS_LEASE_PREFIX = "kynver-harness:";
3134
+ var RUNNER_MARKER = "@runner:";
3135
+ function trimOrNull4(value) {
3136
+ if (!value?.trim()) return null;
3137
+ return value.trim();
3138
+ }
3139
+ function parseHarnessLeaseRunId(leaseOwner) {
3140
+ const owner = trimOrNull4(leaseOwner);
3141
+ if (!owner?.startsWith(HARNESS_LEASE_PREFIX)) return null;
3142
+ const body = owner.slice(HARNESS_LEASE_PREFIX.length);
3143
+ const atRunner = body.indexOf(RUNNER_MARKER);
3144
+ if (atRunner >= 0) return body.slice(0, atRunner).trim() || null;
3145
+ return body.trim() || null;
3146
+ }
3147
+ function formatHarnessLeaseOwner(runId, runnerId) {
3148
+ const run = runId.trim();
3149
+ const runner = runnerId.trim();
3150
+ return `${HARNESS_LEASE_PREFIX}${run}${RUNNER_MARKER}${runner}`;
3151
+ }
3152
+ function harnessLeaseOwnerMatchesRun(leaseOwner, runId) {
3153
+ const expectedRun = trimOrNull4(runId);
3154
+ if (!expectedRun) return false;
3155
+ return parseHarnessLeaseRunId(leaseOwner) === expectedRun;
3156
+ }
3157
+ function resolveHarnessLeaseOwnerForRenewal(input) {
3158
+ const stored = trimOrNull4(input.workerLeaseOwner);
3159
+ if (stored && harnessLeaseOwnerMatchesRun(stored, input.runId)) {
3160
+ return stored;
3161
+ }
3162
+ return formatHarnessLeaseOwner(input.runId, input.runnerId);
3163
+ }
3164
+
3165
+ // src/runner-identity.ts
3166
+ import os3 from "node:os";
3167
+ function trimOrNull5(value) {
3168
+ if (!value?.trim()) return null;
3169
+ return value.trim();
3170
+ }
3171
+ function resolveRunnerPresencePayload(input = {}) {
3172
+ const env = input.env ?? process.env;
3173
+ const runnerId = trimOrNull5(env.KYNVER_RUNTIME_ID) ?? trimOrNull5(env.OPENCLAW_RUNTIME_ID) ?? trimOrNull5(env.HOSTNAME) ?? os3.hostname();
3174
+ return {
3175
+ runnerId,
3176
+ hostname: trimOrNull5(env.HOSTNAME) ?? os3.hostname(),
3177
+ profile: trimOrNull5(env.KYNVER_RUNNER_PROFILE) ?? trimOrNull5(env.OPENCLAW_RUNNER_PROFILE),
3178
+ harnessRepo: trimOrNull5(env.KYNVER_HARNESS_REPO) ?? trimOrNull5(env.KYNVER_DEFAULT_REPO),
3179
+ runId: input.runId ?? null
3180
+ };
3181
+ }
3182
+
3183
+ // src/lease-renewal.ts
2266
3184
  function workerRecord(runId, name) {
2267
3185
  return readJson(
2268
- path8.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
3186
+ path13.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
2269
3187
  void 0
2270
3188
  );
2271
3189
  }
@@ -2282,7 +3200,7 @@ async function renewActiveTaskLeases(runId, args) {
2282
3200
  { baseUrl: base }
2283
3201
  );
2284
3202
  const leaseDurationMs = Number(args.leaseMs) > 0 ? Math.floor(Number(args.leaseMs)) : DEFAULT_DISPATCH_LEASE_MS;
2285
- const leaseOwner = `kynver-harness:${runId}`;
3203
+ const runnerId = resolveRunnerPresencePayload({ runId }).runnerId;
2286
3204
  const renewed = [];
2287
3205
  const failed = [];
2288
3206
  const skipped = [];
@@ -2301,11 +3219,20 @@ async function renewActiveTaskLeases(runId, args) {
2301
3219
  skipped.push(name);
2302
3220
  continue;
2303
3221
  }
3222
+ const leaseOwner = resolveHarnessLeaseOwnerForRenewal({
3223
+ runId,
3224
+ workerLeaseOwner: worker.leaseOwner ?? null,
3225
+ runnerId
3226
+ });
2304
3227
  const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/${encodeURIComponent(worker.taskId)}/renew-lease`;
2305
3228
  const res = await postJsonWithCredentialRefresh(
2306
3229
  url,
2307
3230
  secret,
2308
- { leaseOwner, leaseDurationMs },
3231
+ {
3232
+ leaseOwner,
3233
+ leaseDurationMs,
3234
+ ...worker.leaseToken ? { leaseToken: worker.leaseToken } : {}
3235
+ },
2309
3236
  { agentOsId, baseUrl: base }
2310
3237
  );
2311
3238
  if (res.ok) {
@@ -2331,20 +3258,20 @@ function hasLiveWorkerForTask(runId, taskId) {
2331
3258
  }
2332
3259
 
2333
3260
  // src/supervisor.ts
2334
- import { existsSync as existsSync12, mkdirSync as mkdirSync3 } from "node:fs";
2335
- import path14 from "node:path";
3261
+ import { existsSync as existsSync18, mkdirSync as mkdirSync3 } from "node:fs";
3262
+ import path19 from "node:path";
2336
3263
 
2337
3264
  // src/harness-repair-target.ts
2338
3265
  var HARNESS_CONTRACT_RE = /<!--\s*harness-contract:\s*(\{[\s\S]*?\})\s*-->/i;
2339
3266
  var FIX_EXECUTOR_REF_PREFIX = "next-action-fix:";
2340
- function trimOrNull4(value) {
3267
+ function trimOrNull6(value) {
2341
3268
  if (typeof value !== "string") return null;
2342
3269
  const t = value.trim();
2343
3270
  return t.length ? t : null;
2344
3271
  }
2345
3272
  function normalizePrUrl2(url) {
2346
3273
  const m = url.trim().match(/github\.com\/([^/]+\/[^/]+)\/(?:pull|pulls)\/(\d+)/i);
2347
- if (!m) return trimOrNull4(url);
3274
+ if (!m) return trimOrNull6(url);
2348
3275
  return `https://github.com/${m[1]}/pull/${m[2]}`;
2349
3276
  }
2350
3277
  function isHarnessRepairTask(task) {
@@ -2366,10 +3293,10 @@ function parseRepairTargetContractFromDescription(description) {
2366
3293
  if (!m?.[1]) return empty;
2367
3294
  try {
2368
3295
  const parsed = JSON.parse(m[1]);
2369
- const url = trimOrNull4(
3296
+ const url = trimOrNull6(
2370
3297
  String(parsed.targetPrUrl ?? parsed.target_pr_url ?? "")
2371
3298
  );
2372
- const branch = trimOrNull4(
3299
+ const branch = trimOrNull6(
2373
3300
  String(parsed.targetPrBranch ?? parsed.target_pr_branch ?? "")
2374
3301
  );
2375
3302
  const enforce = parsed.repairEnforceOriginalPr === true || parsed.repair_enforce_original_pr === true;
@@ -2390,7 +3317,7 @@ function resolveHarnessRepairTargetFromTask(task) {
2390
3317
  if (!targetPrUrl) return null;
2391
3318
  return {
2392
3319
  targetPrUrl,
2393
- targetPrBranch: block.targetPrBranch ?? trimOrNull4(task.branch)
3320
+ targetPrBranch: block.targetPrBranch ?? trimOrNull6(task.branch)
2394
3321
  };
2395
3322
  }
2396
3323
  function repairTargetPromptLines(target) {
@@ -2478,13 +3405,13 @@ function buildPrompt(input) {
2478
3405
  }
2479
3406
 
2480
3407
  // src/providers/cursor.ts
2481
- import { closeSync as closeSync2, existsSync as existsSync10, openSync as openSync2 } from "node:fs";
2482
- import { spawn as spawn2 } from "node:child_process";
2483
- import path10 from "node:path";
3408
+ import { closeSync as closeSync4, existsSync as existsSync16, openSync as openSync4 } from "node:fs";
3409
+ import { spawn as spawn4 } from "node:child_process";
3410
+ import path15 from "node:path";
2484
3411
 
2485
3412
  // src/providers/cursor-windows.ts
2486
- import { existsSync as existsSync9, readdirSync as readdirSync3 } from "node:fs";
2487
- import path9 from "node:path";
3413
+ import { existsSync as existsSync15, readdirSync as readdirSync3 } from "node:fs";
3414
+ import path14 from "node:path";
2488
3415
  var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
2489
3416
  function parseCursorVersionSortKey(versionName) {
2490
3417
  const datePart = versionName.split("-")[0];
@@ -2495,8 +3422,8 @@ function parseCursorVersionSortKey(versionName) {
2495
3422
  return Number(`${year}${month.padStart(2, "0")}${day.padStart(2, "0")}`);
2496
3423
  }
2497
3424
  function pickLatestCursorVersionDir(agentRoot) {
2498
- const versionsRoot = path9.join(agentRoot, "versions");
2499
- if (!existsSync9(versionsRoot)) return null;
3425
+ const versionsRoot = path14.join(agentRoot, "versions");
3426
+ if (!existsSync15(versionsRoot)) return null;
2500
3427
  let bestDir = null;
2501
3428
  let bestKey = -1;
2502
3429
  for (const entry of readdirSync3(versionsRoot, { withFileTypes: true })) {
@@ -2504,22 +3431,22 @@ function pickLatestCursorVersionDir(agentRoot) {
2504
3431
  const key = parseCursorVersionSortKey(entry.name);
2505
3432
  if (key == null || key <= bestKey) continue;
2506
3433
  bestKey = key;
2507
- bestDir = path9.join(versionsRoot, entry.name);
3434
+ bestDir = path14.join(versionsRoot, entry.name);
2508
3435
  }
2509
3436
  return bestDir;
2510
3437
  }
2511
3438
  function resolveWindowsCursorBundled(agentRoot) {
2512
- const root = agentRoot?.trim() || path9.join(process.env.LOCALAPPDATA || "", "cursor-agent");
2513
- const directNode = path9.join(root, "node.exe");
2514
- const directIndex = path9.join(root, "index.js");
2515
- if (existsSync9(directNode) && existsSync9(directIndex)) {
3439
+ const root = agentRoot?.trim() || path14.join(process.env.LOCALAPPDATA || "", "cursor-agent");
3440
+ const directNode = path14.join(root, "node.exe");
3441
+ const directIndex = path14.join(root, "index.js");
3442
+ if (existsSync15(directNode) && existsSync15(directIndex)) {
2516
3443
  return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
2517
3444
  }
2518
3445
  const versionDir = pickLatestCursorVersionDir(root);
2519
3446
  if (!versionDir) return null;
2520
- const nodeExe = path9.join(versionDir, "node.exe");
2521
- const indexJs = path9.join(versionDir, "index.js");
2522
- if (!existsSync9(nodeExe) || !existsSync9(indexJs)) return null;
3447
+ const nodeExe = path14.join(versionDir, "node.exe");
3448
+ const indexJs = path14.join(versionDir, "index.js");
3449
+ if (!existsSync15(nodeExe) || !existsSync15(indexJs)) return null;
2523
3450
  return { nodeExe, indexJs, versionDir };
2524
3451
  }
2525
3452
 
@@ -2537,13 +3464,13 @@ function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
2537
3464
  function resolveCursorSpawn(agentBin) {
2538
3465
  if (process.platform === "win32") {
2539
3466
  const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
2540
- const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync10(path10.join(path10.dirname(agentBin), "index.js"));
3467
+ const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync16(path15.join(path15.dirname(agentBin), "index.js"));
2541
3468
  const isDefaultShim = agentBin === "agent";
2542
3469
  if (isCursorWrapper || isBundledNode || isDefaultShim) {
2543
- const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path10.dirname(agentBin)) : isBundledNode ? {
3470
+ const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path15.dirname(agentBin)) : isBundledNode ? {
2544
3471
  nodeExe: agentBin,
2545
- indexJs: path10.join(path10.dirname(agentBin), "index.js"),
2546
- versionDir: path10.dirname(agentBin)
3472
+ indexJs: path15.join(path15.dirname(agentBin), "index.js"),
3473
+ versionDir: path15.dirname(agentBin)
2547
3474
  } : resolveWindowsCursorBundled();
2548
3475
  if (bundled) {
2549
3476
  return bundledSpawnTarget(bundled.nodeExe, bundled.indexJs, bundled.versionDir);
@@ -2563,8 +3490,8 @@ function resolveAgentBin() {
2563
3490
  process.env.KYNVER_CURSOR_AGENT_ROOT?.trim() || void 0
2564
3491
  );
2565
3492
  if (bundled) return bundled.nodeExe;
2566
- const localAgent = path10.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
2567
- if (existsSync10(localAgent)) return localAgent;
3493
+ const localAgent = path15.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
3494
+ if (existsSync16(localAgent)) return localAgent;
2568
3495
  }
2569
3496
  return "agent";
2570
3497
  }
@@ -2573,7 +3500,7 @@ function cursorWorkerEnv(agentBin, spawnTarget) {
2573
3500
  ...process.env,
2574
3501
  CI: "1",
2575
3502
  NO_COLOR: "1",
2576
- ...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path10.basename(agentBin) || "agent.cmd" } : {}
3503
+ ...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path15.basename(agentBin) || "agent.cmd" } : {}
2577
3504
  });
2578
3505
  }
2579
3506
  var cursorProvider = {
@@ -2588,12 +3515,12 @@ var cursorProvider = {
2588
3515
  throw new Error(`cursor provider model preflight failed: ${preflight.note}`);
2589
3516
  }
2590
3517
  const model = preflight.model;
2591
- const stdoutFd = openSync2(opts.stdoutPath, "a");
2592
- const stderrFd = openSync2(opts.stderrPath, "a");
3518
+ const stdoutFd = openSync4(opts.stdoutPath, "a");
3519
+ const stderrFd = openSync4(opts.stderrPath, "a");
2593
3520
  const stdio = ["ignore", stdoutFd, stderrFd];
2594
3521
  const agentBin = resolveAgentBin();
2595
3522
  const spawnTarget = resolveCursorSpawn(agentBin);
2596
- const child = spawn2(
3523
+ const child = spawn4(
2597
3524
  spawnTarget.executable,
2598
3525
  [
2599
3526
  ...spawnTarget.prefixArgs,
@@ -2617,8 +3544,8 @@ var cursorProvider = {
2617
3544
  env: cursorWorkerEnv(agentBin, spawnTarget)
2618
3545
  })
2619
3546
  );
2620
- closeSync2(stdoutFd);
2621
- closeSync2(stderrFd);
3547
+ closeSync4(stdoutFd);
3548
+ closeSync4(stderrFd);
2622
3549
  if (!child.pid) {
2623
3550
  throw new Error(
2624
3551
  `failed to spawn Cursor agent worker (is \`${agentBin}\` on PATH? run \`agent login\` or set CURSOR_API_KEY)`
@@ -2632,6 +3559,7 @@ var cursorProvider = {
2632
3559
  // src/providers/registry.ts
2633
3560
  var BUILTIN = {
2634
3561
  claude: claudeProvider,
3562
+ codex: codexProvider,
2635
3563
  cursor: cursorProvider
2636
3564
  };
2637
3565
  var overrideProvider = null;
@@ -2654,9 +3582,9 @@ function resolveWorkerProvider(name) {
2654
3582
  }
2655
3583
 
2656
3584
  // src/auto-complete.ts
2657
- import { spawn as spawn3 } from "node:child_process";
2658
- import { existsSync as existsSync11, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
2659
- import path13 from "node:path";
3585
+ import { spawn as spawn5 } from "node:child_process";
3586
+ import { existsSync as existsSync17, openSync as openSync5, closeSync as closeSync5 } from "node:fs";
3587
+ import path18 from "node:path";
2660
3588
  import { fileURLToPath as fileURLToPath2 } from "node:url";
2661
3589
 
2662
3590
  // src/completion-ack.ts
@@ -2673,7 +3601,7 @@ function persistCompletionAck(worker, runId, fields) {
2673
3601
  }
2674
3602
 
2675
3603
  // src/worker-ops.ts
2676
- import path12 from "node:path";
3604
+ import path17 from "node:path";
2677
3605
 
2678
3606
  // src/completion-response.ts
2679
3607
  function asRecord(value) {
@@ -2741,7 +3669,7 @@ var NO_PR_COMMITS_BETWEEN_RE = /no commits between/i;
2741
3669
  function isGhNoCommitsBetweenError(detail) {
2742
3670
  return Boolean(detail && NO_PR_COMMITS_BETWEEN_RE.test(detail));
2743
3671
  }
2744
- function trimOrNull5(value) {
3672
+ function trimOrNull7(value) {
2745
3673
  if (typeof value !== "string") return null;
2746
3674
  const trimmed = value.trim();
2747
3675
  return trimmed.length ? trimmed : null;
@@ -2749,7 +3677,7 @@ function trimOrNull5(value) {
2749
3677
  function committedHead(ancestry) {
2750
3678
  if (!ancestry?.checked) return null;
2751
3679
  if (ancestry.headIsAncestorOfBase !== false) return null;
2752
- return trimOrNull5(ancestry.head);
3680
+ return trimOrNull7(ancestry.head);
2753
3681
  }
2754
3682
  function extractPrUrlFromText(value) {
2755
3683
  if (value === void 0 || value === null) return null;
@@ -2757,7 +3685,7 @@ function extractPrUrlFromText(value) {
2757
3685
  const m = text.match(
2758
3686
  /https?:\/\/[^\s)>"]+\/(?:pull|pulls|merge_requests|pull-requests)\/\d+/i
2759
3687
  );
2760
- return m ? trimOrNull5(m[0]) : null;
3688
+ return m ? trimOrNull7(m[0]) : null;
2761
3689
  }
2762
3690
  function countCommitsAheadOfBase(worktreePath, baseRef, exec) {
2763
3691
  const base = baseRef.trim();
@@ -2769,21 +3697,21 @@ function countCommitsAheadOfBase(worktreePath, baseRef, exec) {
2769
3697
  }
2770
3698
  function isReviewArtifactWorker(worker, snapshot) {
2771
3699
  if (snapshot.changedFiles.length > 0) return false;
2772
- const persona = trimOrNull5(worker.personaSlug)?.toLowerCase();
3700
+ const persona = trimOrNull7(worker.personaSlug)?.toLowerCase();
2773
3701
  if (persona && REVIEW_PERSONA_SLUGS.has(persona)) return true;
2774
- const rule = trimOrNull5(worker.routingRule) ?? "";
3702
+ const rule = trimOrNull7(worker.routingRule) ?? "";
2775
3703
  if (rule && REVIEW_LANE_RULE.test(rule)) return true;
2776
3704
  return false;
2777
3705
  }
2778
3706
  function hasWorkProduct(snapshot, options) {
2779
3707
  if (snapshot.changedFiles.length > 0) return true;
2780
- const baseRef = trimOrNull5(options?.baseRef);
3708
+ const baseRef = trimOrNull7(options?.baseRef);
2781
3709
  if (baseRef && options?.exec && options.worktreePath) {
2782
3710
  const ahead = countCommitsAheadOfBase(options.worktreePath, baseRef, options.exec);
2783
3711
  if (ahead === 0) return false;
2784
3712
  if (ahead !== null && ahead > 0) return true;
2785
3713
  }
2786
- if (trimOrNull5(snapshot.headCommit)) return true;
3714
+ if (trimOrNull7(snapshot.headCommit)) return true;
2787
3715
  if (committedHead(snapshot.gitAncestry)) return true;
2788
3716
  return false;
2789
3717
  }
@@ -2799,7 +3727,7 @@ function assessPrHandoffRequirement(input) {
2799
3727
  })) {
2800
3728
  return { required: false, reason: "expert_review_task" };
2801
3729
  }
2802
- const rule = trimOrNull5(input.routingRule) ?? "";
3730
+ const rule = trimOrNull7(input.routingRule) ?? "";
2803
3731
  if (rule && REVIEW_LANE_RULE.test(rule)) {
2804
3732
  return { required: false, reason: "review_lane" };
2805
3733
  }
@@ -2810,14 +3738,14 @@ function assessPrHandoffRequirement(input) {
2810
3738
  if (isReviewArtifactWorker(workerCtx, input.snapshot)) {
2811
3739
  return { required: false, reason: "review_artifact" };
2812
3740
  }
2813
- if (trimOrNull5(input.patchPath) || trimOrNull5(input.artifactBundlePath)) {
3741
+ if (trimOrNull7(input.patchPath) || trimOrNull7(input.artifactBundlePath)) {
2814
3742
  return { required: false, reason: "patch_or_bundle" };
2815
3743
  }
2816
- const repairTarget = trimOrNull5(input.repairTargetPrUrl);
3744
+ const repairTarget = trimOrNull7(input.repairTargetPrUrl);
2817
3745
  if (repairTarget) {
2818
3746
  return { required: false, reason: "repair_target_pr" };
2819
3747
  }
2820
- const prUrl = trimOrNull5(input.prUrl) ?? trimOrNull5(input.taskPrUrl) ?? trimOrNull5(input.snapshot.prUrl);
3748
+ const prUrl = trimOrNull7(input.prUrl) ?? trimOrNull7(input.taskPrUrl) ?? trimOrNull7(input.snapshot.prUrl);
2821
3749
  if (prUrl) {
2822
3750
  return { required: false, reason: "already_has_pr" };
2823
3751
  }
@@ -2838,13 +3766,13 @@ function buildPrHandoffSnapshotFromStatus(status, extras) {
2838
3766
  worktreePath: status.worktreePath,
2839
3767
  gitAncestry: status.gitAncestry,
2840
3768
  finalResult: status.finalResult,
2841
- headCommit: trimOrNull5(extras?.headCommit) ?? committedHead(status.gitAncestry),
2842
- prUrl: trimOrNull5(extras?.prUrl) ?? null
3769
+ headCommit: trimOrNull7(extras?.headCommit) ?? committedHead(status.gitAncestry),
3770
+ prUrl: trimOrNull7(extras?.prUrl) ?? null
2843
3771
  };
2844
3772
  }
2845
3773
 
2846
3774
  // src/pr-handoff/pr-handoff-gh.ts
2847
- import { spawnSync as spawnSync2 } from "node:child_process";
3775
+ import { spawnSync as spawnSync3 } from "node:child_process";
2848
3776
 
2849
3777
  // src/github-repo.ts
2850
3778
  function parseGithubOwnerRepo(remoteUrl) {
@@ -2877,7 +3805,7 @@ function normalizeOwnerRepo(value) {
2877
3805
  // src/pr-handoff/pr-handoff-gh.ts
2878
3806
  function capture(bin, cwd, args) {
2879
3807
  try {
2880
- const res = spawnSync2(bin, args, { cwd, encoding: "utf8" });
3808
+ const res = spawnSync3(bin, args, { cwd, encoding: "utf8" });
2881
3809
  return {
2882
3810
  status: res.status,
2883
3811
  stdout: (res.stdout || "").trim(),
@@ -3219,7 +4147,7 @@ function pathFromGitStatusLine(line) {
3219
4147
  }
3220
4148
 
3221
4149
  // src/disposable-artifacts.ts
3222
- function stringList(value) {
4150
+ function stringList2(value) {
3223
4151
  if (!Array.isArray(value)) return [];
3224
4152
  return value.map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
3225
4153
  }
@@ -3230,9 +4158,9 @@ function extractDisposableArtifactsRemoved(finalResult) {
3230
4158
  const record = asRecord2(finalResult);
3231
4159
  if (!record) return [];
3232
4160
  const nested = asRecord2(record.worktreeHandoff);
3233
- const fromNested = stringList(nested?.disposableArtifactsRemoved);
4161
+ const fromNested = stringList2(nested?.disposableArtifactsRemoved);
3234
4162
  if (fromNested.length) return fromNested;
3235
- return stringList(record.disposableArtifactsRemoved);
4163
+ return stringList2(record.disposableArtifactsRemoved);
3236
4164
  }
3237
4165
  function normalizeRelativePath(value) {
3238
4166
  return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
@@ -3243,18 +4171,18 @@ function dirtyPathsCoveredByDisposableRemoval(changedFiles, removed) {
3243
4171
  if (removed.length === 0) return false;
3244
4172
  const removedSet = new Set(removed.map((p) => normalizeRelativePath(p)));
3245
4173
  return material.every((line) => {
3246
- const path50 = normalizeRelativePath(pathFromGitStatusLine(line));
3247
- return removedSet.has(path50);
4174
+ const path59 = normalizeRelativePath(pathFromGitStatusLine(line));
4175
+ return removedSet.has(path59);
3248
4176
  });
3249
4177
  }
3250
4178
 
3251
4179
  // src/worktree-completion-handoff.ts
3252
- function trimOrNull6(value) {
4180
+ function trimOrNull8(value) {
3253
4181
  if (typeof value !== "string") return null;
3254
4182
  const t = value.trim();
3255
4183
  return t.length ? t : null;
3256
4184
  }
3257
- function stringList2(value) {
4185
+ function stringList3(value) {
3258
4186
  if (!Array.isArray(value)) return [];
3259
4187
  return value.map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
3260
4188
  }
@@ -3267,16 +4195,16 @@ function hasFinalResult4(value) {
3267
4195
  return true;
3268
4196
  }
3269
4197
  function mergedDisposableRemoved(input) {
3270
- const fromWorker = stringList2(input.disposableArtifactsRemoved);
4198
+ const fromWorker = stringList3(input.disposableArtifactsRemoved);
3271
4199
  const fromResult = extractDisposableArtifactsRemoved(input.finalResult);
3272
4200
  return [.../* @__PURE__ */ new Set([...fromWorker, ...fromResult])];
3273
4201
  }
3274
4202
  function assessWorktreeCompletionHandoff(input) {
3275
- const rawDirty = stringList2(input.changedFiles);
4203
+ const rawDirty = stringList3(input.changedFiles);
3276
4204
  const materialDirty = materialWorktreeChanges(rawDirty);
3277
4205
  const removed = mergedDisposableRemoved(input);
3278
4206
  const effectivelyClean = materialDirty.length === 0 || dirtyPathsCoveredByDisposableRemoval(rawDirty, removed);
3279
- if (trimOrNull6(input.prUrl)) {
4207
+ if (trimOrNull8(input.prUrl)) {
3280
4208
  if (!effectivelyClean) {
3281
4209
  return {
3282
4210
  allowed: false,
@@ -3287,7 +4215,7 @@ function assessWorktreeCompletionHandoff(input) {
3287
4215
  }
3288
4216
  return { allowed: true, state: "pr_handoff", materialDirtyCount: 0 };
3289
4217
  }
3290
- if (trimOrNull6(input.headCommit)) {
4218
+ if (trimOrNull8(input.headCommit)) {
3291
4219
  if (!effectivelyClean) {
3292
4220
  return {
3293
4221
  allowed: false,
@@ -3298,7 +4226,7 @@ function assessWorktreeCompletionHandoff(input) {
3298
4226
  }
3299
4227
  return { allowed: true, state: "commit_handoff", materialDirtyCount: 0 };
3300
4228
  }
3301
- if (trimOrNull6(input.artifactBundlePath) || trimOrNull6(input.patchPath)) {
4229
+ if (trimOrNull8(input.artifactBundlePath) || trimOrNull8(input.patchPath)) {
3302
4230
  if (!effectivelyClean) {
3303
4231
  return {
3304
4232
  allowed: false,
@@ -3329,7 +4257,7 @@ function assessWorktreeCompletionHandoff(input) {
3329
4257
  }
3330
4258
 
3331
4259
  // src/worker-lifecycle.ts
3332
- import path11 from "node:path";
4260
+ import path16 from "node:path";
3333
4261
  var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
3334
4262
  "awaiting_review",
3335
4263
  "blocked",
@@ -3385,7 +4313,7 @@ function syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick) {
3385
4313
  const synced = [];
3386
4314
  for (const name of Object.keys(run.workers || {})) {
3387
4315
  const worker = readJson(
3388
- path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4316
+ path16.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
3389
4317
  void 0
3390
4318
  );
3391
4319
  if (!worker?.taskId || isCompletionAcknowledged(worker)) continue;
@@ -3502,37 +4430,39 @@ async function tryCompleteWorker(args) {
3502
4430
  return { ok: true, skipped: true, reason: "local-only-worker" };
3503
4431
  }
3504
4432
  const headCommit = status.gitAncestry.headIsAncestorOfBase === false && status.gitAncestry.head ? status.gitAncestry.head : status.headCommit;
3505
- const handoff = assessWorktreeCompletionHandoff({
3506
- changedFiles: status.changedFiles,
3507
- finalResult: status.finalResult,
3508
- prUrl: status.prUrl,
3509
- headCommit,
3510
- disposableArtifactsRemoved: worker.disposableArtifactsRemoved
3511
- });
3512
- if (!handoff.allowed) {
3513
- const reason2 = handoff.detail ?? `worktree completion blocked (${handoff.state})`;
3514
- persistCompletionBlocker(worker, reason2);
3515
- return {
3516
- ok: false,
3517
- reason: reason2,
3518
- nextAction: "Clean the worktree (commit, open a PR, or `kynver worker discard-disposable --path <file>`), then rerun `kynver worker complete`.",
3519
- completionBlocked: true
3520
- };
4433
+ if (worker.dispatched) {
4434
+ const handoff = assessWorktreeCompletionHandoff({
4435
+ changedFiles: status.changedFiles,
4436
+ finalResult: status.finalResult,
4437
+ prUrl: status.prUrl,
4438
+ headCommit,
4439
+ disposableArtifactsRemoved: worker.disposableArtifactsRemoved
4440
+ });
4441
+ if (!handoff.allowed) {
4442
+ const reason2 = handoff.detail ?? `worktree completion blocked (${handoff.state})`;
4443
+ persistCompletionBlocker(worker, reason2);
4444
+ return {
4445
+ ok: false,
4446
+ reason: reason2,
4447
+ nextAction: "Clean the worktree (commit, open a PR, or `kynver worker discard-disposable --path <file>`), then rerun `kynver worker complete`.",
4448
+ completionBlocked: true
4449
+ };
4450
+ }
3521
4451
  }
3522
4452
  const skipPrHandoff = args.skipPrHandoff === true || args.skipPrHandoff === "true";
3523
4453
  if (!skipPrHandoff && worker.dispatched && taskId) {
3524
- const handoff2 = ensurePrReadyHandoff({ worker, run, status });
3525
- if (!handoff2.ok) {
3526
- persistCompletionBlocker(worker, handoff2.reason);
4454
+ const handoff = ensurePrReadyHandoff({ worker, run, status });
4455
+ if (!handoff.ok) {
4456
+ persistCompletionBlocker(worker, handoff.reason);
3527
4457
  return {
3528
4458
  ok: false,
3529
- reason: handoff2.reason,
3530
- nextAction: handoff2.nextAction,
4459
+ reason: handoff.reason,
4460
+ nextAction: handoff.nextAction,
3531
4461
  completionBlocked: true
3532
4462
  };
3533
4463
  }
3534
- if (handoff2.prUrl || handoff2.headCommit) {
3535
- status = applyPrHandoffToStatus(status, handoff2);
4464
+ if (handoff.prUrl || handoff.headCommit) {
4465
+ status = applyPrHandoffToStatus(status, handoff);
3536
4466
  }
3537
4467
  }
3538
4468
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
@@ -3549,9 +4479,10 @@ async function tryCompleteWorker(args) {
3549
4479
  runId: worker.runId,
3550
4480
  workerName: worker.name,
3551
4481
  taskId,
4482
+ leaseToken: worker.leaseToken ?? null,
3552
4483
  startedAt: worker.startedAt,
3553
4484
  finishedAt: status.lastActivityAt || (/* @__PURE__ */ new Date()).toISOString(),
3554
- status: statusPayload,
4485
+ status: worker.leaseToken ? { ...statusPayload, leaseToken: worker.leaseToken } : statusPayload,
3555
4486
  workerInjection: {
3556
4487
  instructionPolicyFingerprint: worker.instructionPolicyFingerprint ?? null,
3557
4488
  instructionPolicyEvidence: worker.instructionPolicyEvidence ?? null,
@@ -3666,7 +4597,7 @@ function workerStatus(args) {
3666
4597
  const worker = loadWorker(String(args.run), String(args.name));
3667
4598
  const run = loadRun(worker.runId);
3668
4599
  const status = computeWorkerStatus(worker, workerStatusOptions(run));
3669
- writeJson(path12.join(worker.workerDir, "last-status.json"), status);
4600
+ writeJson(path17.join(worker.workerDir, "last-status.json"), status);
3670
4601
  console.log(JSON.stringify(status, null, 2));
3671
4602
  }
3672
4603
  function buildRunBoard(runId) {
@@ -3674,7 +4605,7 @@ function buildRunBoard(runId) {
3674
4605
  const names = Object.keys(run.workers || {});
3675
4606
  const workers = names.map((name) => {
3676
4607
  const worker = readJson(
3677
- path12.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4608
+ path17.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
3678
4609
  void 0
3679
4610
  );
3680
4611
  if (!worker) {
@@ -3785,7 +4716,7 @@ function buildRunBoard(runId) {
3785
4716
  needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
3786
4717
  workers
3787
4718
  };
3788
- writeJson(path12.join(runDirectory(run.id), "last-board.json"), board);
4719
+ writeJson(path17.join(runDirectory(run.id), "last-board.json"), board);
3789
4720
  return board;
3790
4721
  }
3791
4722
  async function publishHarnessBoardSnapshot(args, source) {
@@ -3974,15 +4905,15 @@ async function autoCompleteWorkerCli(raw) {
3974
4905
  }
3975
4906
  }
3976
4907
  function resolveDefaultCliPath() {
3977
- return path13.join(fileURLToPath2(new URL(".", import.meta.url)), "cli.js");
4908
+ return path18.join(fileURLToPath2(new URL(".", import.meta.url)), "cli.js");
3978
4909
  }
3979
4910
  function spawnCompletionSidecar(opts) {
3980
4911
  const cliPath = opts.cliPath ?? resolveDefaultCliPath();
3981
- if (!existsSync11(cliPath)) return void 0;
3982
- const logPath = path13.join(opts.workerDir, "auto-complete.log");
4912
+ if (!existsSync17(cliPath)) return void 0;
4913
+ const logPath = path18.join(opts.workerDir, "auto-complete.log");
3983
4914
  let logFd;
3984
4915
  try {
3985
- logFd = openSync3(logPath, "a");
4916
+ logFd = openSync5(logPath, "a");
3986
4917
  } catch {
3987
4918
  logFd = void 0;
3988
4919
  }
@@ -4005,7 +4936,7 @@ function spawnCompletionSidecar(opts) {
4005
4936
  if (opts.baseUrl) args.push("--base-url", opts.baseUrl);
4006
4937
  if (opts.secret) args.push("--secret", opts.secret);
4007
4938
  try {
4008
- const child = spawn3(
4939
+ const child = spawn5(
4009
4940
  nodeExecutable,
4010
4941
  args,
4011
4942
  hiddenSpawnOptions({
@@ -4014,13 +4945,13 @@ function spawnCompletionSidecar(opts) {
4014
4945
  env: process.env
4015
4946
  })
4016
4947
  );
4017
- if (logFd !== void 0) closeSync3(logFd);
4948
+ if (logFd !== void 0) closeSync5(logFd);
4018
4949
  child.unref();
4019
4950
  return { pid: child.pid, logPath, cliPath };
4020
4951
  } catch {
4021
4952
  if (logFd !== void 0) {
4022
4953
  try {
4023
- closeSync3(logFd);
4954
+ closeSync5(logFd);
4024
4955
  } catch {
4025
4956
  }
4026
4957
  }
@@ -4075,21 +5006,21 @@ function spawnWorkerProcess(run, opts) {
4075
5006
  launchModel = preflight.model;
4076
5007
  }
4077
5008
  const { worktreesDir } = getPaths();
4078
- const workerDir = path14.join(runDirectory(run.id), "workers", name);
5009
+ const workerDir = path19.join(runDirectory(run.id), "workers", name);
4079
5010
  mkdirSync3(workerDir, { recursive: true });
4080
- const worktreePath = path14.join(worktreesDir, run.id, name);
5011
+ const worktreePath = path19.join(worktreesDir, run.id, name);
4081
5012
  const repairBranch = opts.repairTargetBranch?.trim() || void 0;
4082
5013
  const branch = repairBranch || opts.branch || `agent/${run.id}/${name}`;
4083
- if (existsSync12(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
5014
+ if (existsSync18(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
4084
5015
  git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
4085
5016
  if (repairBranch) {
4086
5017
  addWorktreeForRepairBranch(run.repo, worktreePath, repairBranch);
4087
5018
  } else {
4088
5019
  git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
4089
5020
  }
4090
- const stdoutPath = path14.join(workerDir, "stdout.jsonl");
4091
- const stderrPath = path14.join(workerDir, "stderr.log");
4092
- const heartbeatPath = path14.join(workerDir, "heartbeat.jsonl");
5021
+ const stdoutPath = path19.join(workerDir, "stdout.jsonl");
5022
+ const stderrPath = path19.join(workerDir, "stderr.log");
5023
+ const heartbeatPath = path19.join(workerDir, "heartbeat.jsonl");
4093
5024
  const prompt = buildPrompt({
4094
5025
  task: opts.task,
4095
5026
  ownedPaths: opts.ownedPaths || [],
@@ -4146,10 +5077,12 @@ function spawnWorkerProcess(run, opts) {
4146
5077
  ...opts.personaSlug ? { personaSlug: String(opts.personaSlug) } : {},
4147
5078
  ...opts.personaEvidence ? { personaEvidence: opts.personaEvidence } : {},
4148
5079
  ...opts.leaseOwner ? { leaseOwner: String(opts.leaseOwner) } : {},
5080
+ ...opts.leaseToken ? { leaseToken: String(opts.leaseToken) } : {},
4149
5081
  ...opts.dispatched ? { dispatched: true } : {},
4150
5082
  ...!opts.agentOsId || !opts.taskId ? { localOnly: true } : {},
4151
5083
  routingRule: routing.rule,
4152
5084
  ...routing.requestedModel ? { requestedModel: routing.requestedModel } : {},
5085
+ ...routing.orchestrationAudit ? { orchestrationAudit: routing.orchestrationAudit } : {},
4153
5086
  ...opts.executorRef ? { executorRef: String(opts.executorRef) } : {},
4154
5087
  ...opts.parentTaskId ? { parentTaskId: String(opts.parentTaskId) } : {},
4155
5088
  ...opts.taskTitle ? { taskTitle: String(opts.taskTitle) } : {},
@@ -4159,7 +5092,7 @@ function spawnWorkerProcess(run, opts) {
4159
5092
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
4160
5093
  };
4161
5094
  saveWorker(run.id, worker);
4162
- run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path14.join(workerDir, "worker.json") } };
5095
+ run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path19.join(workerDir, "worker.json") } };
4163
5096
  run.status = "running";
4164
5097
  saveRun(run);
4165
5098
  if (worker.agentOsId && worker.taskId) {
@@ -4253,17 +5186,50 @@ async function startWorker(args) {
4253
5186
  }
4254
5187
  }
4255
5188
 
5189
+ // src/active-harness-workers.ts
5190
+ import path20 from "node:path";
5191
+ function workerWriteSetFields(worker) {
5192
+ const ownedPaths = Array.isArray(worker.ownedPaths) ? worker.ownedPaths.filter((p) => typeof p === "string") : [];
5193
+ const writeSetPrefixes = Array.isArray(
5194
+ worker.writeSetPrefixes
5195
+ ) ? (worker.writeSetPrefixes ?? []).filter((p) => typeof p === "string") : [];
5196
+ return {
5197
+ ...ownedPaths.length ? { ownedPaths } : {},
5198
+ ...writeSetPrefixes.length ? { writeSetPrefixes } : {},
5199
+ ...worker.allowConcurrentHotspot ? { allowConcurrentHotspot: true } : {}
5200
+ };
5201
+ }
5202
+ function collectRunActiveHarnessWorkers(runId) {
5203
+ const run = loadRun(runId);
5204
+ const out = [];
5205
+ for (const name of Object.keys(run.workers || {})) {
5206
+ const worker = readJson(
5207
+ path20.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
5208
+ void 0
5209
+ );
5210
+ if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
5211
+ out.push({
5212
+ runId: run.id,
5213
+ workerName: name,
5214
+ taskId: worker.taskId,
5215
+ pid: worker.pid,
5216
+ ...workerWriteSetFields(worker)
5217
+ });
5218
+ }
5219
+ return out;
5220
+ }
5221
+
4256
5222
  // src/plan-persist/body-hash.ts
4257
- import { createHash } from "node:crypto";
5223
+ import { createHash as createHash2 } from "node:crypto";
4258
5224
  function hashPlanBody(body) {
4259
5225
  const normalized = body.replace(/\r\n/g, "\n").trimEnd();
4260
- return createHash("sha256").update(normalized, "utf8").digest("hex");
5226
+ return createHash2("sha256").update(normalized, "utf8").digest("hex");
4261
5227
  }
4262
5228
  function hashSummary(summary) {
4263
5229
  if (summary == null) return null;
4264
5230
  const trimmed = summary.trim();
4265
5231
  if (!trimmed) return null;
4266
- return createHash("sha256").update(trimmed, "utf8").digest("hex");
5232
+ return createHash2("sha256").update(trimmed, "utf8").digest("hex");
4267
5233
  }
4268
5234
 
4269
5235
  // src/plan-persist/errors.ts
@@ -4433,7 +5399,7 @@ function mergeSourceRefs(input) {
4433
5399
  }
4434
5400
 
4435
5401
  // src/plan-persist/idempotency.ts
4436
- import { createHash as createHash2 } from "node:crypto";
5402
+ import { createHash as createHash3 } from "node:crypto";
4437
5403
  function buildPlanPersistIdempotencyKey(input) {
4438
5404
  const payload = {
4439
5405
  operation: input.operation,
@@ -4446,23 +5412,23 @@ function buildPlanPersistIdempotencyKey(input) {
4446
5412
  changeSummary: input.changeSummary?.trim() ?? null,
4447
5413
  markCurrent: input.markCurrent ?? true
4448
5414
  };
4449
- return createHash2("sha256").update(JSON.stringify(payload), "utf8").digest("hex");
5415
+ return createHash3("sha256").update(JSON.stringify(payload), "utf8").digest("hex");
4450
5416
  }
4451
5417
 
4452
5418
  // src/plan-persist/paths.ts
4453
5419
  import { mkdirSync as mkdirSync4 } from "node:fs";
4454
- import { homedir as homedir5 } from "node:os";
4455
- import path15 from "node:path";
5420
+ import { homedir as homedir10 } from "node:os";
5421
+ import path21 from "node:path";
4456
5422
  function resolveKynverStateRoot() {
4457
5423
  const env = process.env.KYNVER_STATE_ROOT;
4458
- if (env) return path15.resolve(env);
4459
- return path15.join(homedir5(), ".kynver", "state");
5424
+ if (env) return path21.resolve(env);
5425
+ return path21.join(homedir10(), ".kynver", "state");
4460
5426
  }
4461
5427
  function planOutboxDir() {
4462
- return path15.join(resolveKynverStateRoot(), "plan-outbox");
5428
+ return path21.join(resolveKynverStateRoot(), "plan-outbox");
4463
5429
  }
4464
5430
  function planOutboxArchiveDir() {
4465
- return path15.join(resolveKynverStateRoot(), "plan-outbox-archive");
5431
+ return path21.join(resolveKynverStateRoot(), "plan-outbox-archive");
4466
5432
  }
4467
5433
  function ensurePlanOutboxDirs() {
4468
5434
  const outboxDir = planOutboxDir();
@@ -4473,20 +5439,20 @@ function ensurePlanOutboxDirs() {
4473
5439
  }
4474
5440
  function isTmpOnlyPath(filePath) {
4475
5441
  if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
4476
- const resolved = path15.resolve(filePath);
4477
- return resolved.startsWith("/tmp/") || resolved.startsWith(path15.join("/var", "folders"));
5442
+ const resolved = path21.resolve(filePath);
5443
+ return resolved.startsWith("/tmp/") || resolved.startsWith(path21.join("/var", "folders"));
4478
5444
  }
4479
5445
 
4480
5446
  // src/plan-persist/outbox-store.ts
4481
5447
  import {
4482
- existsSync as existsSync14,
4483
- readFileSync as readFileSync8,
5448
+ existsSync as existsSync20,
5449
+ readFileSync as readFileSync9,
4484
5450
  renameSync,
4485
5451
  readdirSync as readdirSync4,
4486
5452
  writeFileSync as writeFileSync3,
4487
5453
  unlinkSync
4488
5454
  } from "node:fs";
4489
- import path16 from "node:path";
5455
+ import path22 from "node:path";
4490
5456
  import { randomUUID } from "node:crypto";
4491
5457
  var DEFAULT_MAX_RETRIES = 12;
4492
5458
  function listOutboxItems() {
@@ -4494,7 +5460,7 @@ function listOutboxItems() {
4494
5460
  const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
4495
5461
  const items = [];
4496
5462
  for (const file of files) {
4497
- const item = readOutboxItem(path16.join(outboxDir, file));
5463
+ const item = readOutboxItem(path22.join(outboxDir, file));
4498
5464
  if (item && item.queueStatus === "queued") items.push(item);
4499
5465
  }
4500
5466
  return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
@@ -4506,25 +5472,25 @@ function findOutboxByIdempotencyKey(key) {
4506
5472
  return null;
4507
5473
  }
4508
5474
  function readOutboxItem(jsonPath) {
4509
- if (!existsSync14(jsonPath)) return null;
5475
+ if (!existsSync20(jsonPath)) return null;
4510
5476
  try {
4511
- return JSON.parse(readFileSync8(jsonPath, "utf8"));
5477
+ return JSON.parse(readFileSync9(jsonPath, "utf8"));
4512
5478
  } catch {
4513
5479
  return null;
4514
5480
  }
4515
5481
  }
4516
5482
  function readOutboxBody(item) {
4517
5483
  const { outboxDir } = ensurePlanOutboxDirs();
4518
- const bodyFile = path16.join(outboxDir, item.bodyPath);
4519
- return readFileSync8(bodyFile, "utf8");
5484
+ const bodyFile = path22.join(outboxDir, item.bodyPath);
5485
+ return readFileSync9(bodyFile, "utf8");
4520
5486
  }
4521
5487
  function writeOutboxItem(input, opts) {
4522
5488
  const { outboxDir } = ensurePlanOutboxDirs();
4523
5489
  const now = (/* @__PURE__ */ new Date()).toISOString();
4524
5490
  const id = opts.existing?.id ?? randomUUID();
4525
5491
  const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
4526
- const jsonPath = path16.join(outboxDir, `${id}.json`);
4527
- const bodyFile = path16.join(outboxDir, bodyPath);
5492
+ const jsonPath = path22.join(outboxDir, `${id}.json`);
5493
+ const bodyFile = path22.join(outboxDir, bodyPath);
4528
5494
  if (!opts.existing) {
4529
5495
  writeFileSync3(bodyFile, input.body, "utf8");
4530
5496
  }
@@ -4560,24 +5526,24 @@ function writeOutboxItem(input, opts) {
4560
5526
  }
4561
5527
  function saveOutboxItem(item) {
4562
5528
  const { outboxDir } = ensurePlanOutboxDirs();
4563
- const jsonPath = path16.join(outboxDir, `${item.id}.json`);
5529
+ const jsonPath = path22.join(outboxDir, `${item.id}.json`);
4564
5530
  writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
4565
5531
  `, { mode: 384 });
4566
5532
  }
4567
5533
  function archiveOutboxItem(item) {
4568
5534
  const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
4569
- const jsonSrc = path16.join(outboxDir, `${item.id}.json`);
4570
- const bodySrc = path16.join(outboxDir, item.bodyPath);
4571
- const jsonDst = path16.join(archiveDir, `${item.id}.json`);
4572
- const bodyDst = path16.join(archiveDir, item.bodyPath);
4573
- if (existsSync14(jsonSrc)) renameSync(jsonSrc, jsonDst);
4574
- if (existsSync14(bodySrc)) renameSync(bodySrc, bodyDst);
5535
+ const jsonSrc = path22.join(outboxDir, `${item.id}.json`);
5536
+ const bodySrc = path22.join(outboxDir, item.bodyPath);
5537
+ const jsonDst = path22.join(archiveDir, `${item.id}.json`);
5538
+ const bodyDst = path22.join(archiveDir, item.bodyPath);
5539
+ if (existsSync20(jsonSrc)) renameSync(jsonSrc, jsonDst);
5540
+ if (existsSync20(bodySrc)) renameSync(bodySrc, bodyDst);
4575
5541
  }
4576
5542
  function outboxItemPaths(item) {
4577
5543
  const { outboxDir } = ensurePlanOutboxDirs();
4578
5544
  return {
4579
- jsonPath: path16.join(outboxDir, `${item.id}.json`),
4580
- bodyPath: path16.join(outboxDir, item.bodyPath)
5545
+ jsonPath: path22.join(outboxDir, `${item.id}.json`),
5546
+ bodyPath: path22.join(outboxDir, item.bodyPath)
4581
5547
  };
4582
5548
  }
4583
5549
  function outboxInputFromItem(item, body) {
@@ -4763,7 +5729,7 @@ function markOutboxFailed(item, message) {
4763
5729
  }
4764
5730
 
4765
5731
  // src/plan-persist/drain.ts
4766
- import path17 from "node:path";
5732
+ import path23 from "node:path";
4767
5733
  async function drainPlanOutbox(opts = {}, deps = {}) {
4768
5734
  const items = listOutboxItems().filter(
4769
5735
  (item) => opts.outboxId ? item.id === opts.outboxId : true
@@ -4797,7 +5763,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
4797
5763
  return result;
4798
5764
  }
4799
5765
  function loadOutboxById(outboxId) {
4800
- const jsonPath = path17.join(planOutboxDir(), `${outboxId}.json`);
5766
+ const jsonPath = path23.join(planOutboxDir(), `${outboxId}.json`);
4801
5767
  return readOutboxItem(jsonPath);
4802
5768
  }
4803
5769
 
@@ -4827,24 +5793,6 @@ function extractPlanOutboxFromTask(task) {
4827
5793
  };
4828
5794
  }
4829
5795
 
4830
- // src/runner-identity.ts
4831
- import os3 from "node:os";
4832
- function trimOrNull7(value) {
4833
- if (!value?.trim()) return null;
4834
- return value.trim();
4835
- }
4836
- function resolveRunnerPresencePayload(input = {}) {
4837
- const env = input.env ?? process.env;
4838
- const runnerId = trimOrNull7(env.KYNVER_RUNTIME_ID) ?? trimOrNull7(env.OPENCLAW_RUNTIME_ID) ?? trimOrNull7(env.HOSTNAME) ?? os3.hostname();
4839
- return {
4840
- runnerId,
4841
- hostname: trimOrNull7(env.HOSTNAME) ?? os3.hostname(),
4842
- profile: trimOrNull7(env.KYNVER_RUNNER_PROFILE) ?? trimOrNull7(env.OPENCLAW_RUNNER_PROFILE),
4843
- harnessRepo: trimOrNull7(env.KYNVER_HARNESS_REPO) ?? trimOrNull7(env.KYNVER_DEFAULT_REPO),
4844
- runId: input.runId ?? null
4845
- };
4846
- }
4847
-
4848
5796
  // src/dispatch.ts
4849
5797
  var DEFAULT_DISPATCH_LEASE_MS = 60 * 60 * 1e3;
4850
5798
  function readHarnessWorkerContext(decision) {
@@ -4900,6 +5848,20 @@ function buildDispatchTaskText(task, agentOsId) {
4900
5848
  }
4901
5849
  return lines.join("\n");
4902
5850
  }
5851
+ function requestedTargetTaskIds(args) {
5852
+ const out = /* @__PURE__ */ new Set();
5853
+ if (args.targetTaskId) {
5854
+ out.add(String(args.targetTaskId));
5855
+ return out;
5856
+ }
5857
+ if (args.targetTaskIds) {
5858
+ for (const value of String(args.targetTaskIds).split(",")) {
5859
+ const trimmed = value.trim();
5860
+ if (trimmed) out.add(trimmed);
5861
+ }
5862
+ }
5863
+ return out;
5864
+ }
4903
5865
  async function dispatchRun(args) {
4904
5866
  const pipeline = args.pipeline === true || args.pipeline === "true";
4905
5867
  try {
@@ -4910,37 +5872,46 @@ async function dispatchRun(args) {
4910
5872
  const execute = args.execute === true || args.execute === "true";
4911
5873
  const dryRun = !execute;
4912
5874
  const runnerPresence = resolveRunnerPresencePayload({ runId: run.id });
4913
- const leaseOwner = `kynver-harness:${run.id}@runner:${runnerPresence.runnerId}`;
5875
+ const leaseOwner = formatHarnessLeaseOwner(run.id, runnerPresence.runnerId);
4914
5876
  const runnerDiskGate = args.diskPath ? observeRunnerDiskGate({ diskPath: String(args.diskPath) }) : observeRunnerDiskGate({ diskPath: run.repo });
4915
- const runnerResourceGate = observeRunnerResourceGate({ runId: run.id });
4916
- const requestedStarts = Number(args.maxStarts) > 0 ? Math.floor(Number(args.maxStarts)) : 1;
4917
- const cappedStarts = dryRun ? requestedStarts : Math.min(requestedStarts, runnerResourceGate.slotsAvailable);
4918
- const activeHarnessWorkers = [];
4919
- for (const name of Object.keys(run.workers || {})) {
4920
- const worker = readJson(
4921
- path18.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4922
- void 0
4923
- );
4924
- if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
4925
- const ownedPaths = Array.isArray(worker.ownedPaths) ? worker.ownedPaths.filter((p) => typeof p === "string") : [];
4926
- const writeSetPrefixes = Array.isArray(
4927
- worker.writeSetPrefixes
4928
- ) ? (worker.writeSetPrefixes ?? []).filter((p) => typeof p === "string") : [];
4929
- activeHarnessWorkers.push({
5877
+ const runnerResourceGate = observeRunnerResourceGate({ runId: run.id });
5878
+ const exactTargetIds = requestedTargetTaskIds(args);
5879
+ const exactTargetMode = exactTargetIds.size > 0;
5880
+ const requestedStarts = Number(args.maxStarts) > 0 ? Math.floor(Number(args.maxStarts)) : 1;
5881
+ const effectiveRequestedStarts = exactTargetMode ? Math.min(requestedStarts, exactTargetIds.size) : requestedStarts;
5882
+ const cappedStarts = dryRun ? effectiveRequestedStarts : Math.min(effectiveRequestedStarts, runnerResourceGate.slotsAvailable);
5883
+ if (!dryRun && cappedStarts <= 0) {
5884
+ const summary2 = {
4930
5885
  runId: run.id,
4931
- workerName: name,
4932
- taskId: worker.taskId,
4933
- pid: worker.pid,
4934
- ...ownedPaths.length ? { ownedPaths } : {},
4935
- ...writeSetPrefixes.length ? { writeSetPrefixes } : {},
4936
- ...worker.allowConcurrentHotspot ? { allowConcurrentHotspot: true } : {}
4937
- });
5886
+ agentOsId,
5887
+ dryRun: false,
5888
+ skipped: true,
5889
+ reason: runnerResourceGate.reason ?? "no resource slots",
5890
+ resourceGate: runnerResourceGate
5891
+ };
5892
+ if (pipeline) return { ok: true, ...summary2 };
5893
+ console.log(JSON.stringify(summary2, null, 2));
5894
+ return;
5895
+ }
5896
+ const activeHarnessWorkers = collectRunActiveHarnessWorkers(run.id);
5897
+ if (args.reconcileStaleBlockers === true || args.reconcileStaleBlockers === "true") {
5898
+ const laneHygieneUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/lane-hygiene`;
5899
+ try {
5900
+ await postJsonWithCredentialRefresh(
5901
+ laneHygieneUrl,
5902
+ secret,
5903
+ { agentOsId, dryRun: false, includeBoardReconcile: true },
5904
+ { agentOsId, baseUrl: base }
5905
+ );
5906
+ } catch (err) {
5907
+ console.error(`[dispatch] reconcile-stale-blockers lane-hygiene call failed: ${err.message}`);
5908
+ }
4938
5909
  }
4939
5910
  const dispatchUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/dispatch-next`;
4940
- const body = {
5911
+ const buildBody = (maxStarts) => ({
4941
5912
  agentOsId,
4942
5913
  dryRun,
4943
- maxStarts: cappedStarts,
5914
+ maxStarts,
4944
5915
  leaseOwner,
4945
5916
  leaseDurationMs: Number(args.leaseMs) > 0 ? Math.floor(Number(args.leaseMs)) : DEFAULT_DISPATCH_LEASE_MS,
4946
5917
  runnerDiskGate,
@@ -4951,25 +5922,32 @@ async function dispatchRun(args) {
4951
5922
  ...args.lane ? { lane: String(args.lane) } : {},
4952
5923
  executor: args.executor ? String(args.executor) : "harness",
4953
5924
  ...args.diskPath ? { diskPath: String(args.diskPath) } : {},
4954
- ...args.targetTaskId ? { targetTaskId: String(args.targetTaskId) } : {}
5925
+ ...args.targetTaskId ? { targetTaskId: String(args.targetTaskId) } : {},
5926
+ ...!args.targetTaskId && args.targetTaskIds ? {
5927
+ targetTaskIds: String(args.targetTaskIds).split(",").map((s) => s.trim()).filter(Boolean)
5928
+ } : {}
5929
+ });
5930
+ const requestDispatch = async (maxStarts) => {
5931
+ const dispatch = await postJsonWithCredentialRefresh(dispatchUrl, secret, buildBody(maxStarts), { agentOsId, baseUrl: base });
5932
+ const responseBody = dispatch.response;
5933
+ return { dispatch, result: responseBody?.result };
4955
5934
  };
4956
- const dispatch = await postJsonWithCredentialRefresh(dispatchUrl, secret, body, { agentOsId, baseUrl: base });
4957
- const responseBody = dispatch.response;
4958
- if (!dispatch.ok || !responseBody?.result) {
5935
+ const first = await requestDispatch(exactTargetMode || dryRun ? cappedStarts : 1);
5936
+ if (!first.dispatch.ok || !first.result) {
4959
5937
  const failure = {
4960
5938
  runId: run.id,
4961
5939
  agentOsId,
4962
5940
  action: "dispatch",
4963
- httpStatus: dispatch.status,
4964
- response: dispatch.response,
4965
- authRefreshed: dispatch.refreshedAuth === true,
4966
- authRefreshFailure: dispatch.authRefreshFailure
5941
+ httpStatus: first.dispatch.status,
5942
+ response: first.dispatch.response,
5943
+ authRefreshed: first.dispatch.refreshedAuth === true,
5944
+ authRefreshFailure: first.dispatch.authRefreshFailure
4967
5945
  };
4968
5946
  if (pipeline) return { ok: false, ...failure };
4969
5947
  console.log(JSON.stringify(failure, null, 2));
4970
5948
  process.exit(1);
4971
5949
  }
4972
- const result = responseBody.result;
5950
+ const result = first.result;
4973
5951
  if (dryRun) {
4974
5952
  const summary2 = {
4975
5953
  runId: run.id,
@@ -4991,25 +5969,41 @@ async function dispatchRun(args) {
4991
5969
  console.log(JSON.stringify(summary2, null, 2));
4992
5970
  return;
4993
5971
  }
4994
- if (!dryRun && cappedStarts <= 0) {
4995
- const summary2 = {
4996
- runId: run.id,
4997
- agentOsId,
4998
- dryRun: false,
4999
- skipped: true,
5000
- reason: runnerResourceGate.reason ?? "no resource slots",
5001
- resourceGate: runnerResourceGate
5002
- };
5003
- if (pipeline) return { ok: true, ...summary2 };
5004
- console.log(JSON.stringify(summary2, null, 2));
5005
- return;
5006
- }
5007
5972
  const retryLimits = readHarnessRetryLimits();
5008
5973
  const outcomes = [];
5009
- for (const decision of result.started) {
5974
+ const skipped = [];
5975
+ let inspected = Number(result.inspected) || 0;
5976
+ async function spawnClaimed(decision) {
5010
5977
  const task = decision.task;
5011
5978
  const harnessContext = readHarnessWorkerContext(decision);
5012
5979
  const taskId = String(task.id);
5980
+ if (exactTargetMode && !exactTargetIds.has(taskId)) {
5981
+ const releaseUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/${encodeURIComponent(taskId)}/release`;
5982
+ let release;
5983
+ try {
5984
+ release = await postJsonWithCredentialRefresh(
5985
+ releaseUrl,
5986
+ secret,
5987
+ {
5988
+ agentOsId,
5989
+ leaseOwner,
5990
+ reason: `exact target dispatch mismatch: requested ${[...exactTargetIds].join(",")} but dispatch-next returned ${taskId}`
5991
+ },
5992
+ { agentOsId, baseUrl: base }
5993
+ );
5994
+ } catch (relErr) {
5995
+ release = { ok: false, error: relErr.message };
5996
+ }
5997
+ outcomes.push({
5998
+ taskId,
5999
+ started: false,
6000
+ error: "exact_target_mismatch: dispatch-next returned a different task than requested",
6001
+ requestedTargetTaskIds: [...exactTargetIds],
6002
+ released: release.ok === true || release.response?.ok === true,
6003
+ releaseResponse: release.response ?? release
6004
+ });
6005
+ return false;
6006
+ }
5013
6007
  const expectedPersona = normalizePersonaSlug2(task.personaSlug);
5014
6008
  if (expectedPersona && (!harnessContext?.personaInjectionReady || !harnessContext.personaMarkdown)) {
5015
6009
  outcomes.push({
@@ -5017,7 +6011,7 @@ async function dispatchRun(args) {
5017
6011
  started: false,
5018
6012
  error: `persona_injection_required: missing anchored context-envelope persona block for "${expectedPersona}"`
5019
6013
  });
5020
- continue;
6014
+ return false;
5021
6015
  }
5022
6016
  if (hasLiveWorkerForTask(run.id, taskId)) {
5023
6017
  outcomes.push({
@@ -5025,16 +6019,16 @@ async function dispatchRun(args) {
5025
6019
  started: false,
5026
6020
  error: "duplicate_dispatch_prevented: live local worker already owns this task"
5027
6021
  });
5028
- continue;
6022
+ return false;
5029
6023
  }
5030
6024
  const attempt = Number(task.attempt) || 1;
5031
- if (attempt > retryLimits.maxTaskAttempts) {
6025
+ if (attempt >= retryLimits.maxTaskAttempts) {
5032
6026
  outcomes.push({
5033
6027
  taskId: task.id,
5034
6028
  started: false,
5035
6029
  error: `task attempt ${attempt} exceeds KYNVER_MAX_TASK_ATTEMPTS (${retryLimits.maxTaskAttempts})`
5036
6030
  });
5037
- continue;
6031
+ return false;
5038
6032
  }
5039
6033
  const name = safeSlug(`t-${task.id}-a${task.attempt}`);
5040
6034
  const routing = resolveWorkerLaunch({
@@ -5078,6 +6072,7 @@ async function dispatchRun(args) {
5078
6072
  personaSlug: harnessContext?.personaSlug ?? expectedPersona,
5079
6073
  personaEvidence: harnessContext?.personaEvidence ?? null,
5080
6074
  leaseOwner,
6075
+ leaseToken: task.leaseToken ? String(task.leaseToken) : void 0,
5081
6076
  dispatched: true
5082
6077
  });
5083
6078
  outcomes.push({
@@ -5104,6 +6099,7 @@ async function dispatchRun(args) {
5104
6099
  `[dispatch] task ${taskId}: persona context injected slug=${harnessContext.personaSlug}`
5105
6100
  );
5106
6101
  }
6102
+ return true;
5107
6103
  } catch (error) {
5108
6104
  const releaseUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/${encodeURIComponent(String(task.id))}/release`;
5109
6105
  let release;
@@ -5119,6 +6115,35 @@ async function dispatchRun(args) {
5119
6115
  released: release.ok === true || release.response?.ok === true,
5120
6116
  releaseResponse: release.response ?? release
5121
6117
  });
6118
+ return false;
6119
+ }
6120
+ }
6121
+ let shouldContinueDispatch = true;
6122
+ for (const decision of result.started) {
6123
+ shouldContinueDispatch = await spawnClaimed(decision) && shouldContinueDispatch;
6124
+ }
6125
+ skipped.push(...result.skipped ?? []);
6126
+ if (exactTargetMode) shouldContinueDispatch = false;
6127
+ while (shouldContinueDispatch && outcomes.length < cappedStarts) {
6128
+ if (exactTargetMode) break;
6129
+ const next = await requestDispatch(1);
6130
+ if (!next.dispatch.ok || !next.result) {
6131
+ outcomes.push({
6132
+ started: false,
6133
+ error: "dispatch_next request failed during top-up",
6134
+ httpStatus: next.dispatch.status,
6135
+ response: next.dispatch.response
6136
+ });
6137
+ break;
6138
+ }
6139
+ inspected += Number(next.result.inspected) || 0;
6140
+ skipped.push(...next.result.skipped ?? []);
6141
+ const started = next.result.started ?? [];
6142
+ if (started.length === 0) break;
6143
+ for (const decision of started) {
6144
+ if (outcomes.length >= cappedStarts) break;
6145
+ shouldContinueDispatch = await spawnClaimed(decision) && shouldContinueDispatch;
6146
+ if (!shouldContinueDispatch) break;
5122
6147
  }
5123
6148
  }
5124
6149
  const summary = {
@@ -5128,10 +6153,11 @@ async function dispatchRun(args) {
5128
6153
  leaseOwner,
5129
6154
  startedCount: outcomes.filter((o) => o.started).length,
5130
6155
  outcomes,
5131
- skipped: result.skipped.map((d) => ({
6156
+ skipped: skipped.map((d) => ({
5132
6157
  taskId: d.task.id,
5133
6158
  skipReason: d.skipReason
5134
6159
  })),
6160
+ inspected,
5135
6161
  diskGate: result.diskGate,
5136
6162
  resourceGate: result.resourceGate
5137
6163
  };
@@ -5148,7 +6174,7 @@ async function dispatchRun(args) {
5148
6174
  }
5149
6175
 
5150
6176
  // src/sweep.ts
5151
- import path19 from "node:path";
6177
+ import path24 from "node:path";
5152
6178
  async function sweepRun(args) {
5153
6179
  const pipeline = args.pipeline === true || args.pipeline === "true";
5154
6180
  try {
@@ -5156,18 +6182,23 @@ async function sweepRun(args) {
5156
6182
  const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
5157
6183
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
5158
6184
  const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, { baseUrl: base });
5159
- const leaseOwner = `kynver-harness:${run.id}`;
6185
+ const runnerId = resolveRunnerPresencePayload({ runId: run.id }).runnerId;
5160
6186
  const snapshotPublished = await publishHarnessBoardSnapshot({ run: run.id, agentOsId, ...args }, "run_sweep");
5161
6187
  const releasedLocalOrphans = [];
5162
6188
  for (const name of Object.keys(run.workers || {})) {
5163
6189
  const worker = readJson(
5164
- path19.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
6190
+ path24.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
5165
6191
  void 0
5166
6192
  );
5167
6193
  if (!worker || !worker.dispatched || !worker.taskId) continue;
5168
6194
  const status = computeWorkerStatus(worker);
5169
6195
  if (status.alive) continue;
5170
6196
  if (status.finalResult || worker.completionReportedAt) continue;
6197
+ const leaseOwner = resolveHarnessLeaseOwnerForRenewal({
6198
+ runId: run.id,
6199
+ workerLeaseOwner: worker.leaseOwner ?? null,
6200
+ runnerId
6201
+ });
5171
6202
  const releaseUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/${encodeURIComponent(String(worker.taskId))}/release`;
5172
6203
  let release;
5173
6204
  try {
@@ -5193,7 +6224,7 @@ async function sweepRun(args) {
5193
6224
  } catch (reapErr) {
5194
6225
  reap = { ok: false, error: reapErr.message };
5195
6226
  }
5196
- const summary = { runId: run.id, agentOsId, leaseOwner, snapshotPublished, releasedLocalOrphans, reap: reap.response ?? reap };
6227
+ const summary = { runId: run.id, agentOsId, snapshotPublished, releasedLocalOrphans, reap: reap.response ?? reap };
5197
6228
  if (pipeline) return { ok: true, ...summary };
5198
6229
  console.log(JSON.stringify(summary, null, 2));
5199
6230
  } catch (error) {
@@ -5204,18 +6235,18 @@ async function sweepRun(args) {
5204
6235
  }
5205
6236
 
5206
6237
  // src/worktree.ts
5207
- import { existsSync as existsSync16, mkdirSync as mkdirSync5 } from "node:fs";
5208
- import path25 from "node:path";
6238
+ import { existsSync as existsSync23, mkdirSync as mkdirSync5 } from "node:fs";
6239
+ import path32 from "node:path";
5209
6240
 
5210
6241
  // src/run-list.ts
5211
- import { existsSync as existsSync15, readFileSync as readFileSync9 } from "node:fs";
5212
- import path22 from "node:path";
6242
+ import { existsSync as existsSync22, readFileSync as readFileSync10 } from "node:fs";
6243
+ import path29 from "node:path";
5213
6244
 
5214
6245
  // src/stale-reconcile.ts
5215
- import path21 from "node:path";
6246
+ import path28 from "node:path";
5216
6247
 
5217
6248
  // src/finalize.ts
5218
- import path20 from "node:path";
6249
+ import path25 from "node:path";
5219
6250
  var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set([
5220
6251
  "running",
5221
6252
  "dispatching",
@@ -5233,7 +6264,7 @@ function deriveTerminalRunStatus(run) {
5233
6264
  let anyLandingBlocked = false;
5234
6265
  for (const name of names) {
5235
6266
  const worker = readJson(
5236
- path20.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
6267
+ path25.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
5237
6268
  void 0
5238
6269
  );
5239
6270
  if (!worker) continue;
@@ -5272,20 +6303,346 @@ function finalizeStaleRuns() {
5272
6303
  return finalized;
5273
6304
  }
5274
6305
 
6306
+ // src/worker-metadata-reconcile.ts
6307
+ import { existsSync as existsSync21, lstatSync, readdirSync as readdirSync5, readlinkSync, renameSync as renameSync2, rmSync } from "node:fs";
6308
+ import path27 from "node:path";
6309
+
6310
+ // src/worker-metadata-paths.ts
6311
+ import path26 from "node:path";
6312
+ var NESTED_RUNS = `${path26.sep}runs${path26.sep}runs${path26.sep}`;
6313
+ function hasNestedRunsSegment(filePath) {
6314
+ return filePath.includes(NESTED_RUNS);
6315
+ }
6316
+ function canonicalRunDir(harnessRoot, runId) {
6317
+ return path26.join(harnessRunsDir(normalizeHarnessRoot(harnessRoot)), safeSlug(runId));
6318
+ }
6319
+ function canonicalWorkerDir(harnessRoot, runId, workerName) {
6320
+ return path26.join(canonicalRunDir(harnessRoot, runId), "workers", safeSlug(workerName));
6321
+ }
6322
+ function legacyNestedWorkerDir(harnessRoot, runId, workerName) {
6323
+ const root = normalizeHarnessRoot(harnessRoot);
6324
+ return path26.join(root, "runs", "runs", safeSlug(runId), "workers", safeSlug(workerName));
6325
+ }
6326
+ function workerArtifactFileNames() {
6327
+ return [
6328
+ "stdout.jsonl",
6329
+ "stderr.log",
6330
+ "heartbeat.jsonl",
6331
+ "last-status.json",
6332
+ "auto-complete.log",
6333
+ "worker.json"
6334
+ ];
6335
+ }
6336
+ function workerArtifactPaths(workerDir) {
6337
+ return {
6338
+ workerJsonPath: path26.join(workerDir, "worker.json"),
6339
+ stdoutPath: path26.join(workerDir, "stdout.jsonl"),
6340
+ stderrPath: path26.join(workerDir, "stderr.log"),
6341
+ heartbeatPath: path26.join(workerDir, "heartbeat.jsonl"),
6342
+ lastStatusPath: path26.join(workerDir, "last-status.json")
6343
+ };
6344
+ }
6345
+ function resolveWorkerJsonPath(input) {
6346
+ const canonical = workerArtifactPaths(canonicalWorkerDir(input.harnessRoot, input.runId, input.workerName)).workerJsonPath;
6347
+ if (input.statusPath && !hasNestedRunsSegment(input.statusPath)) {
6348
+ return input.statusPath;
6349
+ }
6350
+ return canonical;
6351
+ }
6352
+
6353
+ // src/worker-metadata-reconcile.ts
6354
+ function materializeSymlinkedRunDir(harnessRoot, runId) {
6355
+ const canonical = canonicalRunDir(harnessRoot, runId);
6356
+ let stat;
6357
+ try {
6358
+ stat = lstatSync(canonical);
6359
+ } catch {
6360
+ return null;
6361
+ }
6362
+ if (!stat.isSymbolicLink()) return null;
6363
+ const linkedTarget = path27.resolve(path27.dirname(canonical), readlinkSync(canonical));
6364
+ const staging = `${canonical}.materialize-${Date.now()}`;
6365
+ renameSync2(linkedTarget, staging);
6366
+ rmSync(canonical);
6367
+ renameSync2(staging, canonical);
6368
+ return linkedTarget;
6369
+ }
6370
+ function relocateArtifacts(input) {
6371
+ const moved = [];
6372
+ for (const fileName of workerArtifactFileNames()) {
6373
+ const from = path27.join(input.fromDir, fileName);
6374
+ const to = path27.join(input.toDir, fileName);
6375
+ if (!existsSync21(from) || existsSync21(to)) continue;
6376
+ renameSync2(from, to);
6377
+ moved.push(fileName);
6378
+ }
6379
+ return moved;
6380
+ }
6381
+ function repairWorkerPathFields(worker, canonicalDir) {
6382
+ const artifacts = workerArtifactPaths(canonicalDir);
6383
+ let changed = false;
6384
+ if (worker.workerDir !== canonicalDir) {
6385
+ worker.workerDir = canonicalDir;
6386
+ changed = true;
6387
+ }
6388
+ if (worker.stdoutPath !== artifacts.stdoutPath) {
6389
+ worker.stdoutPath = artifacts.stdoutPath;
6390
+ changed = true;
6391
+ }
6392
+ if (worker.stderrPath !== artifacts.stderrPath) {
6393
+ worker.stderrPath = artifacts.stderrPath;
6394
+ changed = true;
6395
+ }
6396
+ if (worker.heartbeatPath !== artifacts.heartbeatPath) {
6397
+ worker.heartbeatPath = artifacts.heartbeatPath;
6398
+ changed = true;
6399
+ }
6400
+ return changed;
6401
+ }
6402
+ function deriveSynthesizedStatus(input) {
6403
+ const statusFromLast = typeof input.lastStatus?.status === "string" ? input.lastStatus.status : void 0;
6404
+ const attentionState = input.lastStatus?.attention && typeof input.lastStatus.attention === "object" ? input.lastStatus.attention.state : void 0;
6405
+ if (statusFromLast === "done" || attentionState === "done") return "done";
6406
+ if (input.stdoutFinalResult || input.heartbeatFinalResult) return "done";
6407
+ if (statusFromLast === "blocked") return "blocked";
6408
+ if (statusFromLast === "running") return "exited";
6409
+ return statusFromLast ?? "exited";
6410
+ }
6411
+ function synthesizeWorkerFromArtifacts(input) {
6412
+ const artifacts = workerArtifactPaths(input.canonicalDir);
6413
+ const lastStatus = readJson(artifacts.lastStatusPath, void 0);
6414
+ const stdoutExists = existsSync21(artifacts.stdoutPath);
6415
+ const heartbeatExists = existsSync21(artifacts.heartbeatPath);
6416
+ const hasArtifacts = stdoutExists || heartbeatExists || lastStatus != null;
6417
+ if (!hasArtifacts) return null;
6418
+ const parsedStdout = stdoutExists ? parseHarnessStream(artifacts.stdoutPath) : { finalResult: null };
6419
+ const heartbeat = heartbeatExists ? parseHeartbeat(artifacts.heartbeatPath) : parseHeartbeat("");
6420
+ const heartbeatFinalResult = terminalFinalResultFromHeartbeat(heartbeat);
6421
+ const status = deriveSynthesizedStatus({
6422
+ lastStatus,
6423
+ stdoutFinalResult: parsedStdout.finalResult,
6424
+ heartbeatFinalResult
6425
+ });
6426
+ const worker = {
6427
+ name: input.workerName,
6428
+ runId: input.run.id,
6429
+ status,
6430
+ branch: typeof lastStatus?.branch === "string" ? lastStatus.branch : `agent/${input.run.id}/${input.workerName}`,
6431
+ worktreePath: typeof lastStatus?.worktreePath === "string" ? lastStatus.worktreePath : path27.join(getPaths().worktreesDir, input.run.id, input.workerName),
6432
+ workerDir: input.canonicalDir,
6433
+ stdoutPath: artifacts.stdoutPath,
6434
+ stderrPath: artifacts.stderrPath,
6435
+ heartbeatPath: artifacts.heartbeatPath,
6436
+ startedAt: typeof lastStatus?.startedAt === "string" ? lastStatus.startedAt : input.run.createdAt,
6437
+ reconciledAt: (/* @__PURE__ */ new Date()).toISOString(),
6438
+ reconcileReason: "synthesized worker.json from on-disk artifacts after metadata repair"
6439
+ };
6440
+ if (typeof lastStatus?.taskId === "string") worker.taskId = lastStatus.taskId;
6441
+ if (typeof lastStatus?.agentOsId === "string") worker.agentOsId = lastStatus.agentOsId;
6442
+ if (typeof lastStatus?.planId === "string") worker.planId = lastStatus.planId;
6443
+ if (typeof lastStatus?.pid === "number") worker.pid = lastStatus.pid;
6444
+ if (parsedStdout.finalResult || heartbeatFinalResult) {
6445
+ worker.completionSnapshot = {
6446
+ finalResult: parsedStdout.finalResult ?? heartbeatFinalResult ?? "completed",
6447
+ summary: heartbeat.lastHeartbeatSummary
6448
+ };
6449
+ }
6450
+ repairWorkerPathFields(worker, input.canonicalDir);
6451
+ return worker;
6452
+ }
6453
+ function repairRunWorkerIndex(run, harnessRoot) {
6454
+ let changed = false;
6455
+ const nextWorkers = { ...run.workers || {} };
6456
+ for (const [name, ref] of Object.entries(nextWorkers)) {
6457
+ const canonicalDir = canonicalWorkerDir(harnessRoot, run.id, name);
6458
+ const canonicalStatus = path27.join(canonicalDir, "worker.json");
6459
+ const nested = hasNestedRunsSegment(ref.workerDir) || hasNestedRunsSegment(ref.statusPath) || ref.workerDir !== canonicalDir || ref.statusPath !== canonicalStatus;
6460
+ if (!nested) continue;
6461
+ nextWorkers[name] = { workerDir: canonicalDir, statusPath: canonicalStatus };
6462
+ changed = true;
6463
+ }
6464
+ if (changed) {
6465
+ run.workers = nextWorkers;
6466
+ saveRun(run);
6467
+ }
6468
+ return changed;
6469
+ }
6470
+ function reconcileOneWorker(input) {
6471
+ const outcomes = [];
6472
+ const canonicalDir = canonicalWorkerDir(input.harnessRoot, input.run.id, input.workerName);
6473
+ const legacyDir = legacyNestedWorkerDir(input.harnessRoot, input.run.id, input.workerName);
6474
+ const canonicalArtifacts = workerArtifactPaths(canonicalDir);
6475
+ const moved = relocateArtifacts({ fromDir: legacyDir, toDir: canonicalDir });
6476
+ if (moved.length > 0) {
6477
+ outcomes.push({
6478
+ runId: input.run.id,
6479
+ worker: input.workerName,
6480
+ action: "relocated_artifacts",
6481
+ reason: `moved ${moved.join(", ")} from nested runs/runs layout`
6482
+ });
6483
+ }
6484
+ const workerJsonPath = resolveWorkerJsonPath({
6485
+ harnessRoot: input.harnessRoot,
6486
+ runId: input.run.id,
6487
+ workerName: input.workerName,
6488
+ statusPath: input.statusPath
6489
+ });
6490
+ let worker = readJson(workerJsonPath, void 0);
6491
+ if (!worker && existsSync21(canonicalArtifacts.workerJsonPath)) {
6492
+ worker = readJson(canonicalArtifacts.workerJsonPath, void 0);
6493
+ }
6494
+ if (!worker) {
6495
+ const synthesized = synthesizeWorkerFromArtifacts({
6496
+ run: input.run,
6497
+ workerName: input.workerName,
6498
+ canonicalDir,
6499
+ harnessRoot: input.harnessRoot
6500
+ });
6501
+ if (synthesized) {
6502
+ saveWorker(input.run.id, synthesized);
6503
+ outcomes.push({
6504
+ runId: input.run.id,
6505
+ worker: input.workerName,
6506
+ action: "synthesized_worker_json",
6507
+ reason: synthesized.reconcileReason ?? "synthesized from artifacts"
6508
+ });
6509
+ return outcomes;
6510
+ }
6511
+ const dirExists = existsSync21(canonicalDir);
6512
+ const hasAnyArtifact = workerArtifactFileNames().some((f) => existsSync21(path27.join(canonicalDir, f)));
6513
+ if (dirExists && !hasAnyArtifact) {
6514
+ const abandoned = {
6515
+ name: input.workerName,
6516
+ runId: input.run.id,
6517
+ status: "exited",
6518
+ branch: `agent/${input.run.id}/${input.workerName}`,
6519
+ worktreePath: path27.join(getPaths().worktreesDir, input.run.id, input.workerName),
6520
+ workerDir: canonicalDir,
6521
+ stdoutPath: canonicalArtifacts.stdoutPath,
6522
+ stderrPath: canonicalArtifacts.stderrPath,
6523
+ heartbeatPath: canonicalArtifacts.heartbeatPath,
6524
+ startedAt: input.run.createdAt,
6525
+ reconciledAt: (/* @__PURE__ */ new Date()).toISOString(),
6526
+ reconcileReason: "empty worker dir \u2014 marked abandoned during metadata reconcile"
6527
+ };
6528
+ saveWorker(input.run.id, abandoned);
6529
+ outcomes.push({
6530
+ runId: input.run.id,
6531
+ worker: input.workerName,
6532
+ action: "marked_abandoned",
6533
+ reason: abandoned.reconcileReason ?? "empty worker dir"
6534
+ });
6535
+ return outcomes;
6536
+ }
6537
+ outcomes.push({
6538
+ runId: input.run.id,
6539
+ worker: input.workerName,
6540
+ action: "skipped",
6541
+ reason: "worker.json missing and no recoverable artifacts"
6542
+ });
6543
+ return outcomes;
6544
+ }
6545
+ if (repairWorkerPathFields(worker, canonicalDir)) {
6546
+ worker.reconciledAt = (/* @__PURE__ */ new Date()).toISOString();
6547
+ worker.reconcileReason = worker.reconcileReason ?? "repaired nested runs/runs path fields";
6548
+ saveWorker(input.run.id, worker);
6549
+ outcomes.push({
6550
+ runId: input.run.id,
6551
+ worker: input.workerName,
6552
+ action: "repaired_worker_paths",
6553
+ reason: worker.reconcileReason
6554
+ });
6555
+ } else {
6556
+ outcomes.push({
6557
+ runId: input.run.id,
6558
+ worker: input.workerName,
6559
+ action: "skipped",
6560
+ reason: "worker metadata already canonical"
6561
+ });
6562
+ }
6563
+ return outcomes;
6564
+ }
6565
+ function listOrphanWorkerNames(run, harnessRoot) {
6566
+ const workersDir = path27.join(runDirectory(run.id), "workers");
6567
+ if (!existsSync21(workersDir)) return [];
6568
+ const indexed = new Set(Object.keys(run.workers || {}));
6569
+ const orphans = [];
6570
+ for (const entry of readdirSync5(workersDir, { withFileTypes: true })) {
6571
+ if (!entry.isDirectory()) continue;
6572
+ if (indexed.has(entry.name)) continue;
6573
+ orphans.push(entry.name);
6574
+ }
6575
+ return orphans;
6576
+ }
6577
+ function reconcileWorkerMetadata() {
6578
+ const { harnessRoot } = getPaths();
6579
+ const outcomes = [];
6580
+ for (const run of listRunRecords()) {
6581
+ const materializedFrom = materializeSymlinkedRunDir(harnessRoot, run.id);
6582
+ if (materializedFrom) {
6583
+ outcomes.push({
6584
+ runId: run.id,
6585
+ worker: "*",
6586
+ action: "materialized_run_symlink",
6587
+ reason: `replaced symlink with real run dir (was ${materializedFrom})`
6588
+ });
6589
+ }
6590
+ if (repairRunWorkerIndex(run, harnessRoot)) {
6591
+ outcomes.push({
6592
+ runId: run.id,
6593
+ worker: "*",
6594
+ action: "repaired_run_index",
6595
+ reason: "repaired nested runs/runs workerDir/statusPath entries in run.json"
6596
+ });
6597
+ }
6598
+ const workerNames = new Set(Object.keys(run.workers || {}));
6599
+ for (const orphan of listOrphanWorkerNames(run, harnessRoot)) {
6600
+ workerNames.add(orphan);
6601
+ run.workers = {
6602
+ ...run.workers || {},
6603
+ [orphan]: {
6604
+ workerDir: canonicalWorkerDir(harnessRoot, run.id, orphan),
6605
+ statusPath: path27.join(canonicalWorkerDir(harnessRoot, run.id, orphan), "worker.json")
6606
+ }
6607
+ };
6608
+ saveRun(run);
6609
+ outcomes.push({
6610
+ runId: run.id,
6611
+ worker: orphan,
6612
+ action: "repaired_run_index",
6613
+ reason: "indexed orphan worker dir into run.json"
6614
+ });
6615
+ }
6616
+ for (const name of workerNames) {
6617
+ const ref = run.workers?.[name];
6618
+ outcomes.push(
6619
+ ...reconcileOneWorker({
6620
+ run,
6621
+ workerName: name,
6622
+ harnessRoot,
6623
+ statusPath: ref?.statusPath
6624
+ })
6625
+ );
6626
+ }
6627
+ }
6628
+ return { workers: outcomes };
6629
+ }
6630
+
5275
6631
  // src/stale-reconcile.ts
5276
6632
  var STALE_RECONCILE_HEARTBEAT_MS = 15 * 60 * 1e3;
5277
6633
  function staleReconcileDisabled() {
5278
6634
  return process.env.KYNVER_NO_STALE_CLEANUP === "1";
5279
6635
  }
5280
6636
  function reconcileStaleWorkers() {
6637
+ const metadataReconcile = reconcileWorkerMetadata();
5281
6638
  if (staleReconcileDisabled()) {
5282
- return { workers: [], finalizedRuns: finalizeStaleRuns() };
6639
+ return { workers: [], finalizedRuns: finalizeStaleRuns(), metadataReconcile };
5283
6640
  }
5284
6641
  const outcomes = [];
5285
6642
  const now = Date.now();
5286
6643
  for (const run of listRunRecords()) {
5287
6644
  for (const name of Object.keys(run.workers || {})) {
5288
- const workerPath = path21.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
6645
+ const workerPath = path28.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
5289
6646
  const worker = readJson(workerPath, void 0);
5290
6647
  if (!worker || worker.status !== "running") {
5291
6648
  outcomes.push({
@@ -5357,20 +6714,29 @@ function reconcileStaleWorkers() {
5357
6714
  });
5358
6715
  }
5359
6716
  }
5360
- return { workers: outcomes, finalizedRuns: finalizeStaleRuns() };
6717
+ return { workers: outcomes, finalizedRuns: finalizeStaleRuns(), metadataReconcile };
5361
6718
  }
5362
6719
  function reconcileRunsCli() {
5363
6720
  const result = reconcileStaleWorkers();
5364
6721
  const markedExited = result.workers.filter((w) => w.action === "marked_exited").length;
5365
6722
  const killedStale = result.workers.filter((w) => w.action === "killed_stale").length;
5366
6723
  const skipped = result.workers.filter((w) => w.action === "skipped").length;
6724
+ const metadataTotals = result.metadataReconcile.workers.reduce((acc, row) => {
6725
+ acc[row.action] = (acc[row.action] ?? 0) + 1;
6726
+ return acc;
6727
+ }, {});
5367
6728
  console.log(
5368
6729
  JSON.stringify(
5369
6730
  {
5370
6731
  ok: true,
5371
6732
  workers: { markedExited, killedStale, skipped, total: result.workers.length },
6733
+ metadataReconcile: { totals: metadataTotals, total: result.metadataReconcile.workers.length },
5372
6734
  finalizedRuns: result.finalizedRuns.length,
5373
- details: { workers: result.workers, finalizedRuns: result.finalizedRuns }
6735
+ details: {
6736
+ workers: result.workers,
6737
+ metadataReconcile: result.metadataReconcile.workers,
6738
+ finalizedRuns: result.finalizedRuns
6739
+ }
5374
6740
  },
5375
6741
  null,
5376
6742
  2
@@ -5380,15 +6746,15 @@ function reconcileRunsCli() {
5380
6746
 
5381
6747
  // src/run-list.ts
5382
6748
  function heartbeatByteLength(heartbeatPath) {
5383
- if (!heartbeatPath || !existsSync15(heartbeatPath)) return 0;
6749
+ if (!heartbeatPath || !existsSync22(heartbeatPath)) return 0;
5384
6750
  try {
5385
- return readFileSync9(heartbeatPath, "utf8").trim().length;
6751
+ return readFileSync10(heartbeatPath, "utf8").trim().length;
5386
6752
  } catch {
5387
6753
  return 0;
5388
6754
  }
5389
6755
  }
5390
6756
  function workerEvidence(run, workerName) {
5391
- const workerPath = path22.join(runDirectory(run.id), "workers", safeSlug(workerName), "worker.json");
6757
+ const workerPath = path29.join(runDirectory(run.id), "workers", safeSlug(workerName), "worker.json");
5392
6758
  const worker = readJson(workerPath, void 0);
5393
6759
  if (!worker) {
5394
6760
  return {
@@ -5445,7 +6811,7 @@ function aggregateRunAttention(workers) {
5445
6811
  function countOpenWorkers(run) {
5446
6812
  let open = 0;
5447
6813
  for (const name of Object.keys(run.workers || {})) {
5448
- const workerPath = path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
6814
+ const workerPath = path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
5449
6815
  const worker = readJson(workerPath, void 0);
5450
6816
  if (!worker) continue;
5451
6817
  const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
@@ -5494,9 +6860,9 @@ function listRunsCli() {
5494
6860
  }
5495
6861
 
5496
6862
  // src/default-repo.ts
5497
- import path23 from "node:path";
6863
+ import path30 from "node:path";
5498
6864
  function expandConfiguredRepo(value) {
5499
- return path23.resolve(resolveUserPath(value.trim()));
6865
+ return path30.resolve(resolveUserPath(value.trim()));
5500
6866
  }
5501
6867
  function fromConfigured(value, source, persistedInConfig) {
5502
6868
  const trimmed = value?.trim();
@@ -5536,7 +6902,7 @@ function formatResolvedDefaultRepo(resolved) {
5536
6902
  }
5537
6903
 
5538
6904
  // src/validate.ts
5539
- import path24 from "node:path";
6905
+ import path31 from "node:path";
5540
6906
  var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
5541
6907
  function validateRunId(runId) {
5542
6908
  const trimmed = runId.trim();
@@ -5544,7 +6910,7 @@ function validateRunId(runId) {
5544
6910
  return trimmed;
5545
6911
  }
5546
6912
  function validateRepo(repo) {
5547
- const resolved = path24.resolve(repo);
6913
+ const resolved = path31.resolve(repo);
5548
6914
  if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
5549
6915
  return resolved;
5550
6916
  }
@@ -5563,7 +6929,7 @@ function createRun(args) {
5563
6929
  ensureGitRepo(repo);
5564
6930
  const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
5565
6931
  const dir = runDirectory(id);
5566
- if (existsSync16(dir)) failExists(`run already exists: ${id}`);
6932
+ if (existsSync23(dir)) failExists(`run already exists: ${id}`);
5567
6933
  mkdirSync5(dir, { recursive: true });
5568
6934
  const base = String(args.base || "origin/main");
5569
6935
  const baseCommit = git(repo, ["rev-parse", base]).trim();
@@ -5577,7 +6943,7 @@ function createRun(args) {
5577
6943
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5578
6944
  workers: {}
5579
6945
  };
5580
- writeJson(path25.join(dir, "run.json"), run);
6946
+ writeJson(path32.join(dir, "run.json"), run);
5581
6947
  console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
5582
6948
  }
5583
6949
  function listRuns() {
@@ -5589,8 +6955,8 @@ function failExists(message) {
5589
6955
  }
5590
6956
 
5591
6957
  // src/discard-disposable.ts
5592
- import { existsSync as existsSync17, rmSync } from "node:fs";
5593
- import path26 from "node:path";
6958
+ import { existsSync as existsSync24, rmSync as rmSync2 } from "node:fs";
6959
+ import path33 from "node:path";
5594
6960
  function normalizeRelativePath2(value) {
5595
6961
  const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
5596
6962
  if (!normalized || normalized.startsWith("/") || normalized.includes("..")) {
@@ -5611,18 +6977,18 @@ function discardDisposableArtifacts(args) {
5611
6977
  if (paths.length === 0) {
5612
6978
  return { ok: false, removed: [], reason: "requires at least one --path" };
5613
6979
  }
5614
- const worktreeRoot = path26.resolve(worker.worktreePath);
6980
+ const worktreeRoot = path33.resolve(worker.worktreePath);
5615
6981
  const removed = [];
5616
6982
  for (const raw of paths) {
5617
6983
  const rel = normalizeRelativePath2(raw);
5618
- const abs = path26.resolve(worktreeRoot, rel);
5619
- if (!abs.startsWith(worktreeRoot + path26.sep) && abs !== worktreeRoot) {
6984
+ const abs = path33.resolve(worktreeRoot, rel);
6985
+ if (!abs.startsWith(worktreeRoot + path33.sep) && abs !== worktreeRoot) {
5620
6986
  return { ok: false, removed, reason: `path escapes worktree: ${raw}` };
5621
6987
  }
5622
- if (!existsSync17(abs)) {
6988
+ if (!existsSync24(abs)) {
5623
6989
  return { ok: false, removed, reason: `path not found: ${raw}` };
5624
6990
  }
5625
- rmSync(abs, { recursive: true, force: true });
6991
+ rmSync2(abs, { recursive: true, force: true });
5626
6992
  removed.push(rel);
5627
6993
  }
5628
6994
  const prior = Array.isArray(worker.disposableArtifactsRemoved) ? worker.disposableArtifactsRemoved.filter((p) => typeof p === "string") : [];
@@ -5642,7 +7008,7 @@ function discardDisposableCli(args) {
5642
7008
  }
5643
7009
 
5644
7010
  // src/pipeline-tick.ts
5645
- import path41 from "node:path";
7011
+ import path49 from "node:path";
5646
7012
 
5647
7013
  // src/pipeline-dispatch.ts
5648
7014
  var RESERVED_REVIEW_STARTS = 1;
@@ -5663,6 +7029,19 @@ async function runPipelineDispatch(args, slots) {
5663
7029
  return { ok: true, skipped: true, reason: "no slots", maxStarts: 0, startedCount: 0 };
5664
7030
  }
5665
7031
  const base = stripCliMaxStarts(args);
7032
+ if (base.targetTaskId || base.targetTaskIds) {
7033
+ const target = await dispatchRun({
7034
+ ...base,
7035
+ execute: true,
7036
+ pipeline: true,
7037
+ maxStarts: String(slots)
7038
+ });
7039
+ return {
7040
+ ...typeof target === "object" && target !== null ? target : {},
7041
+ passes: { target },
7042
+ startedCount: countDispatchStarts(target)
7043
+ };
7044
+ }
5666
7045
  const reviewBudget = Math.min(slots, RESERVED_REVIEW_STARTS);
5667
7046
  const workBudget = Math.max(0, slots - reviewBudget);
5668
7047
  const review = await dispatchRun({
@@ -5762,7 +7141,7 @@ function buildBoxResourceSnapshotFromGate(gate, input = {}) {
5762
7141
  }
5763
7142
 
5764
7143
  // src/plan-progress-daemon-sync.ts
5765
- import path27 from "node:path";
7144
+ import path34 from "node:path";
5766
7145
 
5767
7146
  // src/plan-progress-sync.ts
5768
7147
  async function syncPlanProgress(args) {
@@ -5786,7 +7165,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
5786
7165
  const outcomes = [];
5787
7166
  for (const name of Object.keys(run.workers || {})) {
5788
7167
  const worker = readJson(
5789
- path27.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
7168
+ path34.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
5790
7169
  void 0
5791
7170
  );
5792
7171
  if (!worker?.dispatched || !worker.taskId) continue;
@@ -5835,10 +7214,10 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
5835
7214
  }
5836
7215
 
5837
7216
  // src/cleanup.ts
5838
- import path39 from "node:path";
7217
+ import path47 from "node:path";
5839
7218
 
5840
7219
  // src/cleanup-guards.ts
5841
- import path28 from "node:path";
7220
+ import path35 from "node:path";
5842
7221
 
5843
7222
  // src/cleanup-run-liveness.ts
5844
7223
  function isWorkerProcessLive(indexed) {
@@ -5902,72 +7281,217 @@ function prUrlFromFinalResult(finalResult) {
5902
7281
  return null;
5903
7282
  }
5904
7283
  function isPrOrUnmergedWork(status) {
5905
- if (prUrlFromFinalResult(status.finalResult)) return true;
5906
7284
  const relation = status.gitAncestry?.relation;
7285
+ if (relation === "merged" || relation === "synced") {
7286
+ return materialWorktreeChanges2(status.changedFiles).length > 0;
7287
+ }
7288
+ if (prUrlFromFinalResult(status.finalResult)) return true;
5907
7289
  if (relation === "ahead" || relation === "diverged") return true;
5908
7290
  if (status.changedFiles.length > 0 && status.finalResult) return true;
5909
7291
  return false;
5910
7292
  }
5911
- function hasUnrestorableWorktreeChanges(status) {
5912
- if (materialWorktreeChanges2(status.changedFiles).length > 0) return true;
5913
- if (status.gitAncestry?.relation === "diverged") return true;
5914
- return false;
7293
+ function hasUnrestorableWorktreeChanges(status) {
7294
+ if (materialWorktreeChanges2(status.changedFiles).length > 0) return true;
7295
+ if (status.gitAncestry?.relation === "diverged") return true;
7296
+ return false;
7297
+ }
7298
+ function effectiveWorktreeAgeMs(input) {
7299
+ const { indexed, includeOrphans, worktreesAgeMs, terminalWorktreesAgeMs } = input;
7300
+ if (!indexed) return includeOrphans ? terminalWorktreesAgeMs : worktreesAgeMs;
7301
+ if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) {
7302
+ return terminalWorktreesAgeMs;
7303
+ }
7304
+ return worktreesAgeMs;
7305
+ }
7306
+ function skipWorktreeRemoval(input) {
7307
+ const { indexed, includeOrphans, worktreesAgeMs, ageMs, orphanSafety, worktreeRemovalGuard } = input;
7308
+ if (!indexed) {
7309
+ if (!includeOrphans) return "orphan_without_flag";
7310
+ return orphanSafety ?? null;
7311
+ }
7312
+ const ageThresholdMs = effectiveWorktreeAgeMs(input);
7313
+ if (worktreesAgeMs <= 0 && !includeOrphans && ageThresholdMs <= 0) return "worktrees_disabled";
7314
+ if (ageThresholdMs > 0 && ageMs < ageThresholdMs) return "below_age_threshold";
7315
+ if (isWorkerProcessLive(indexed)) return "active_worker";
7316
+ if (indexed.worker.completionBlocker) return "completion_blocked";
7317
+ if (runBlocksWorktreeRemoval(indexed)) return "run_still_active";
7318
+ if (!isFinishedWorkerStatus(indexed.status)) return "run_still_active";
7319
+ if (isPrOrUnmergedWork(indexed.status)) return "pr_or_unmerged_commits";
7320
+ if (materialWorktreeChanges2(indexed.status.changedFiles).length > 0) return "dirty_worktree";
7321
+ const landing = assessWorkerLanding({
7322
+ finalResult: indexed.status.finalResult,
7323
+ changedFiles: indexed.status.changedFiles,
7324
+ gitAncestry: indexed.status.gitAncestry,
7325
+ prUrl: prUrlFromFinalResult(indexed.status.finalResult)
7326
+ });
7327
+ if (landing.blocked) return "landing_blocked";
7328
+ if (worktreeRemovalGuard && input.worktreePath) {
7329
+ const overlay = worktreeRemovalGuard({
7330
+ worktreePath: input.worktreePath,
7331
+ indexed: Boolean(indexed),
7332
+ runId: indexed?.runId,
7333
+ worker: indexed?.workerName
7334
+ });
7335
+ if (overlay) {
7336
+ return overlay.detail ? { reason: overlay.reason, detail: overlay.detail } : overlay.reason;
7337
+ }
7338
+ }
7339
+ return null;
7340
+ }
7341
+ function skipDependencyCacheRemoval(input) {
7342
+ const { indexed, nodeModulesAgeMs, ageMs, worktreePath, activeWorktreePaths, diskPressure } = input;
7343
+ if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
7344
+ if (activeWorktreePaths.has(path35.resolve(worktreePath))) return "active_worker";
7345
+ if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
7346
+ if (indexed && hasUnrestorableWorktreeChanges(indexed.status)) return "dirty_worktree";
7347
+ return null;
7348
+ }
7349
+ function skipBuildCacheRemoval(input) {
7350
+ return skipDependencyCacheRemoval(input);
7351
+ }
7352
+
7353
+ // src/cleanup-types.ts
7354
+ var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
7355
+ var DEFAULT_WORKTREES_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
7356
+ var DEFAULT_TERMINAL_WORKTREES_AGE_MS = DEFAULT_NODE_MODULES_AGE_MS;
7357
+ var DEFAULT_RUN_DIRECTORIES_AGE_MS = 60 * 60 * 1e3;
7358
+ var DEFAULT_MAX_ACTIONS_PER_SWEEP = 120;
7359
+ var MAX_PRESERVED_LIVE_PATH_SAMPLES = 24;
7360
+
7361
+ // src/cleanup-evidence.ts
7362
+ var LIVE_SKIP_REASONS = /* @__PURE__ */ new Set([
7363
+ "active_worker",
7364
+ "run_still_active",
7365
+ "completion_blocked",
7366
+ "landing_blocked"
7367
+ ]);
7368
+ function collectPreservedLivePaths(actions, skips) {
7369
+ const out = [];
7370
+ const seen = /* @__PURE__ */ new Set();
7371
+ const push = (path59, reason, detail) => {
7372
+ const key = `${path59}\0${reason}`;
7373
+ if (seen.has(key) || out.length >= MAX_PRESERVED_LIVE_PATH_SAMPLES) return;
7374
+ seen.add(key);
7375
+ out.push({ path: path59, reason, ...detail ? { detail } : {} });
7376
+ };
7377
+ for (const skip2 of skips) {
7378
+ if (!LIVE_SKIP_REASONS.has(skip2.reason)) continue;
7379
+ push(skip2.path, skip2.reason, skip2.detail);
7380
+ }
7381
+ for (const action of actions) {
7382
+ if (!action.skipped || !action.skipReason) continue;
7383
+ if (!LIVE_SKIP_REASONS.has(action.skipReason)) continue;
7384
+ push(action.path, action.skipReason);
7385
+ }
7386
+ return out;
7387
+ }
7388
+
7389
+ // src/cleanup-run-directory.ts
7390
+ import { existsSync as existsSync25, readdirSync as readdirSync6, statSync as statSync4 } from "node:fs";
7391
+ import path37 from "node:path";
7392
+
7393
+ // src/cleanup-active-worktrees.ts
7394
+ import path36 from "node:path";
7395
+ function isActiveHarnessWorker2(worker, runBase, runBaseCommit) {
7396
+ const status = computeWorkerStatus(worker, { base: runBase, baseCommit: runBaseCommit });
7397
+ return status.alive && !status.finalResult && status.attention.state !== "done";
7398
+ }
7399
+ function collectActiveWorktreeGuards(harnessRoots) {
7400
+ const activeWorktreePaths = /* @__PURE__ */ new Set();
7401
+ const liveRunKeys = /* @__PURE__ */ new Set();
7402
+ for (const harnessRoot of harnessRoots) {
7403
+ for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
7404
+ let runHasLive = false;
7405
+ for (const name of Object.keys(run.workers || {})) {
7406
+ const worker = readJson(
7407
+ path36.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
7408
+ void 0
7409
+ );
7410
+ if (!worker?.worktreePath) continue;
7411
+ const worktreePath = path36.resolve(worker.worktreePath);
7412
+ if (!isActiveHarnessWorker2(worker, run.base, run.baseCommit)) continue;
7413
+ runHasLive = true;
7414
+ activeWorktreePaths.add(worktreePath);
7415
+ }
7416
+ if (runHasLive) liveRunKeys.add(`${harnessRoot}\0${run.id}`);
7417
+ }
7418
+ }
7419
+ return { activeWorktreePaths, liveRunKeys };
7420
+ }
7421
+ function isWorktreeOnLiveRun(worktreePath, harnessRoot, runId, liveRunKeys) {
7422
+ if (!runId) return false;
7423
+ return liveRunKeys.has(`${harnessRoot}\0${runId}`);
5915
7424
  }
5916
- function skipWorktreeRemoval(input) {
5917
- const { indexed, includeOrphans, worktreesAgeMs, ageMs, orphanSafety, worktreeRemovalGuard } = input;
5918
- if (!indexed) {
5919
- if (!includeOrphans) return "orphan_without_flag";
5920
- return orphanSafety ?? null;
7425
+
7426
+ // src/cleanup-run-directory.ts
7427
+ function pathAgeMs(target, now) {
7428
+ try {
7429
+ const mtime = statSync4(target).mtimeMs;
7430
+ return Math.max(0, now - mtime);
7431
+ } catch {
7432
+ return 0;
5921
7433
  }
5922
- if (worktreesAgeMs <= 0 && !includeOrphans) return "worktrees_disabled";
5923
- if (worktreesAgeMs > 0 && ageMs < worktreesAgeMs) return "below_age_threshold";
5924
- if (isWorkerProcessLive(indexed)) return "active_worker";
5925
- if (indexed.worker.completionBlocker) return "completion_blocked";
5926
- if (runBlocksWorktreeRemoval(indexed)) return "run_still_active";
5927
- if (!isFinishedWorkerStatus(indexed.status)) return "run_still_active";
5928
- if (isPrOrUnmergedWork(indexed.status)) return "pr_or_unmerged_commits";
5929
- if (materialWorktreeChanges2(indexed.status.changedFiles).length > 0) return "dirty_worktree";
5930
- const landing = assessWorkerLanding({
5931
- finalResult: indexed.status.finalResult,
5932
- changedFiles: indexed.status.changedFiles,
5933
- gitAncestry: indexed.status.gitAncestry,
5934
- prUrl: prUrlFromFinalResult(indexed.status.finalResult)
5935
- });
5936
- if (landing.blocked) return "landing_blocked";
5937
- if (worktreeRemovalGuard && input.worktreePath) {
5938
- const overlay = worktreeRemovalGuard({
5939
- worktreePath: input.worktreePath,
5940
- indexed: Boolean(indexed),
5941
- runId: indexed?.runId,
5942
- worker: indexed?.workerName
5943
- });
5944
- if (overlay) {
5945
- return overlay.detail ? { reason: overlay.reason, detail: overlay.detail } : overlay.reason;
5946
- }
7434
+ }
7435
+ function loadRunStatus(harnessRoot, runId) {
7436
+ const runPath = path37.join(harnessRoot, "runs", runId, "run.json");
7437
+ if (!existsSync25(runPath)) return null;
7438
+ return readJson(runPath, null);
7439
+ }
7440
+ function runDirectoryIsEmpty(runPath) {
7441
+ try {
7442
+ const entries = readdirSync6(runPath);
7443
+ return entries.length === 0;
7444
+ } catch {
7445
+ return false;
5947
7446
  }
5948
- return null;
5949
7447
  }
5950
- function skipDependencyCacheRemoval(input) {
5951
- const { indexed, nodeModulesAgeMs, ageMs, worktreePath, activeWorktreePaths, diskPressure } = input;
5952
- if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
5953
- if (activeWorktreePaths.has(path28.resolve(worktreePath))) return "active_worker";
5954
- if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
5955
- if (indexed && hasUnrestorableWorktreeChanges(indexed.status)) return "dirty_worktree";
7448
+ function skipRunDirectoryRemoval(input) {
7449
+ const { harnessRoot, runId, runPath, ageMs, runDirectoriesAgeMs, activeGuards } = input;
7450
+ if (isWorktreeOnLiveRun(runPath, harnessRoot, runId, activeGuards.liveRunKeys)) {
7451
+ return "run_still_active";
7452
+ }
7453
+ if (!runDirectoryIsEmpty(runPath)) return "run_still_active";
7454
+ const run = loadRunStatus(harnessRoot, runId);
7455
+ if (run && !TERMINAL_RUN_STATUSES.has(run.status)) return "run_still_active";
7456
+ if (runDirectoriesAgeMs > 0 && ageMs < runDirectoriesAgeMs) return "below_age_threshold";
5956
7457
  return null;
5957
7458
  }
5958
- function skipBuildCacheRemoval(input) {
5959
- return skipDependencyCacheRemoval(input);
7459
+ function scanStaleRunDirectoryCandidates(opts) {
7460
+ if (!existsSync25(opts.worktreesDir)) return [];
7461
+ const candidates = [];
7462
+ let entries;
7463
+ try {
7464
+ entries = readdirSync6(opts.worktreesDir, { withFileTypes: true });
7465
+ } catch {
7466
+ return [];
7467
+ }
7468
+ for (const runEntry of entries) {
7469
+ if (!runEntry.isDirectory()) continue;
7470
+ const runId = runEntry.name;
7471
+ if (opts.runIdFilter && runId !== opts.runIdFilter) continue;
7472
+ const runPath = path37.join(opts.worktreesDir, runId);
7473
+ if (!runDirectoryIsEmpty(runPath)) continue;
7474
+ candidates.push({
7475
+ kind: "remove_run_directory",
7476
+ path: runPath,
7477
+ bytes: null,
7478
+ harnessRoot: opts.harnessRoot,
7479
+ runId,
7480
+ ageMs: pathAgeMs(runPath, opts.now)
7481
+ });
7482
+ }
7483
+ return candidates;
5960
7484
  }
5961
7485
 
5962
7486
  // src/cleanup-execute.ts
5963
- import { existsSync as existsSync19, rmSync as rmSync2 } from "node:fs";
5964
- import path30 from "node:path";
7487
+ import { existsSync as existsSync27, rmSync as rmSync3 } from "node:fs";
7488
+ import path39 from "node:path";
5965
7489
 
5966
7490
  // src/cleanup-dir-size.ts
5967
- import { existsSync as existsSync18, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
5968
- import path29 from "node:path";
7491
+ import { existsSync as existsSync26, readdirSync as readdirSync7, statSync as statSync5 } from "node:fs";
7492
+ import path38 from "node:path";
5969
7493
  function directorySizeBytes(root, maxEntries = 5e4) {
5970
- if (!existsSync18(root)) return 0;
7494
+ if (!existsSync26(root)) return 0;
5971
7495
  let total = 0;
5972
7496
  let seen = 0;
5973
7497
  const stack = [root];
@@ -5975,16 +7499,16 @@ function directorySizeBytes(root, maxEntries = 5e4) {
5975
7499
  const current = stack.pop();
5976
7500
  let entries;
5977
7501
  try {
5978
- entries = readdirSync5(current);
7502
+ entries = readdirSync7(current);
5979
7503
  } catch {
5980
7504
  continue;
5981
7505
  }
5982
7506
  for (const name of entries) {
5983
7507
  if (seen++ > maxEntries) return null;
5984
- const full = path29.join(current, name);
7508
+ const full = path38.join(current, name);
5985
7509
  let st;
5986
7510
  try {
5987
- st = statSync2(full);
7511
+ st = statSync5(full);
5988
7512
  } catch {
5989
7513
  continue;
5990
7514
  }
@@ -5997,7 +7521,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
5997
7521
 
5998
7522
  // src/cleanup-execute.ts
5999
7523
  function removeDependencyCache(candidate, execute) {
6000
- if (!existsSync19(candidate.path)) {
7524
+ if (!existsSync27(candidate.path)) {
6001
7525
  return {
6002
7526
  ...candidate,
6003
7527
  executed: false,
@@ -6010,7 +7534,7 @@ function removeDependencyCache(candidate, execute) {
6010
7534
  }
6011
7535
  try {
6012
7536
  const bytesBefore = candidate.bytes ?? directorySizeBytes(candidate.path);
6013
- rmSync2(candidate.path, { recursive: true, force: true });
7537
+ rmSync3(candidate.path, { recursive: true, force: true });
6014
7538
  return {
6015
7539
  ...candidate,
6016
7540
  bytes: bytesBefore,
@@ -6036,8 +7560,39 @@ function removeNextCache(candidate, execute) {
6036
7560
  function removeBuildCache(candidate, execute) {
6037
7561
  return removeDependencyCache(candidate, execute);
6038
7562
  }
7563
+ function removeRunDirectory(candidate, execute) {
7564
+ if (!existsSync27(candidate.path)) {
7565
+ return {
7566
+ ...candidate,
7567
+ executed: false,
7568
+ skipped: true,
7569
+ skipReason: "missing_worktree"
7570
+ };
7571
+ }
7572
+ if (!execute) {
7573
+ return { ...candidate, executed: false, skipped: true, skipReason: "dry_run" };
7574
+ }
7575
+ try {
7576
+ const bytesBefore = candidate.bytes ?? directorySizeBytes(candidate.path);
7577
+ rmSync3(candidate.path, { recursive: true, force: true });
7578
+ return {
7579
+ ...candidate,
7580
+ bytes: bytesBefore,
7581
+ executed: true,
7582
+ skipped: false
7583
+ };
7584
+ } catch (error) {
7585
+ return {
7586
+ ...candidate,
7587
+ executed: false,
7588
+ skipped: true,
7589
+ skipReason: "remove_failed",
7590
+ error: error.message
7591
+ };
7592
+ }
7593
+ }
6039
7594
  function removeWorktree(candidate, execute) {
6040
- if (!existsSync19(candidate.path)) {
7595
+ if (!existsSync27(candidate.path)) {
6041
7596
  return {
6042
7597
  ...candidate,
6043
7598
  executed: false,
@@ -6054,8 +7609,8 @@ function removeWorktree(candidate, execute) {
6054
7609
  if (repo) {
6055
7610
  git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
6056
7611
  }
6057
- if (existsSync19(candidate.path)) {
6058
- rmSync2(candidate.path, { recursive: true, force: true });
7612
+ if (existsSync27(candidate.path)) {
7613
+ rmSync3(candidate.path, { recursive: true, force: true });
6059
7614
  }
6060
7615
  return {
6061
7616
  ...candidate,
@@ -6074,15 +7629,15 @@ function removeWorktree(candidate, execute) {
6074
7629
  }
6075
7630
  }
6076
7631
  function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
6077
- const resolved = path30.resolve(targetPath);
6078
- const suffix = `${path30.sep}${cacheDirName}`;
7632
+ const resolved = path39.resolve(targetPath);
7633
+ const suffix = `${path39.sep}${cacheDirName}`;
6079
7634
  const cachePath = resolved.endsWith(suffix) ? resolved : null;
6080
7635
  if (!cachePath) return "path_outside_harness";
6081
- const rel = path30.relative(worktreesDir, cachePath);
6082
- if (rel.startsWith("..") || path30.isAbsolute(rel)) return "path_outside_harness";
6083
- const parts = rel.split(path30.sep);
7636
+ const rel = path39.relative(worktreesDir, cachePath);
7637
+ if (rel.startsWith("..") || path39.isAbsolute(rel)) return "path_outside_harness";
7638
+ const parts = rel.split(path39.sep);
6084
7639
  if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
6085
- if (!resolved.startsWith(path30.resolve(harnessRoot))) return "path_outside_harness";
7640
+ if (!resolved.startsWith(path39.resolve(harnessRoot))) return "path_outside_harness";
6086
7641
  return null;
6087
7642
  }
6088
7643
  function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
@@ -6092,37 +7647,37 @@ function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
6092
7647
  return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
6093
7648
  }
6094
7649
  function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
6095
- const resolved = path30.resolve(targetPath);
6096
- const relToWt = path30.relative(worktreesDir, resolved);
6097
- if (relToWt.startsWith("..") || path30.isAbsolute(relToWt)) return "path_outside_harness";
6098
- const parts = relToWt.split(path30.sep);
7650
+ const resolved = path39.resolve(targetPath);
7651
+ const relToWt = path39.relative(worktreesDir, resolved);
7652
+ if (relToWt.startsWith("..") || path39.isAbsolute(relToWt)) return "path_outside_harness";
7653
+ const parts = relToWt.split(path39.sep);
6099
7654
  if (parts.length < 3) return "path_outside_harness";
6100
- if (!resolved.startsWith(path30.resolve(harnessRoot))) return "path_outside_harness";
7655
+ if (!resolved.startsWith(path39.resolve(harnessRoot))) return "path_outside_harness";
6101
7656
  return null;
6102
7657
  }
6103
7658
 
6104
7659
  // src/cleanup-scan.ts
6105
- import { existsSync as existsSync20, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
6106
- import path31 from "node:path";
6107
- function pathAgeMs(target, now) {
7660
+ import { existsSync as existsSync28, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
7661
+ import path40 from "node:path";
7662
+ function pathAgeMs2(target, now) {
6108
7663
  try {
6109
- const mtime = statSync3(target).mtimeMs;
7664
+ const mtime = statSync6(target).mtimeMs;
6110
7665
  return Math.max(0, now - mtime);
6111
7666
  } catch {
6112
7667
  return 0;
6113
7668
  }
6114
7669
  }
6115
7670
  function isPathInside(child, parent) {
6116
- const rel = path31.relative(parent, child);
6117
- return rel === "" || !rel.startsWith("..") && !path31.isAbsolute(rel);
7671
+ const rel = path40.relative(parent, child);
7672
+ return rel === "" || !rel.startsWith("..") && !path40.isAbsolute(rel);
6118
7673
  }
6119
7674
  function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
6120
7675
  const out = [];
6121
7676
  for (const rel of HARNESS_BUILD_CACHE_RELATIVE_PATHS) {
6122
7677
  if (rel === ".next") continue;
6123
- const target = path31.join(worktreePath, rel);
6124
- if (!existsSync20(target)) continue;
6125
- const resolved = path31.resolve(target);
7678
+ const target = path40.join(worktreePath, rel);
7679
+ if (!existsSync28(target)) continue;
7680
+ const resolved = path40.resolve(target);
6126
7681
  if (seen.has(resolved)) continue;
6127
7682
  if (!isPathInside(resolved, opts.harnessRoot)) continue;
6128
7683
  seen.add(resolved);
@@ -6133,7 +7688,7 @@ function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
6133
7688
  runId: meta.runId,
6134
7689
  worker: meta.worker,
6135
7690
  repo: meta.repo,
6136
- ageMs: pathAgeMs(resolved, opts.now)
7691
+ ageMs: pathAgeMs2(resolved, opts.now)
6137
7692
  });
6138
7693
  }
6139
7694
  return out;
@@ -6151,13 +7706,13 @@ function scanBuildCacheCandidates(opts) {
6151
7706
  })
6152
7707
  );
6153
7708
  }
6154
- if (!opts.includeOrphans || !existsSync20(opts.worktreesDir)) return candidates;
6155
- for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
7709
+ if (!opts.includeOrphans || !existsSync28(opts.worktreesDir)) return candidates;
7710
+ for (const runEntry of readdirSync8(opts.worktreesDir, { withFileTypes: true })) {
6156
7711
  if (!runEntry.isDirectory()) continue;
6157
- const runPath = path31.join(opts.worktreesDir, runEntry.name);
6158
- for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
7712
+ const runPath = path40.join(opts.worktreesDir, runEntry.name);
7713
+ for (const workerEntry of readdirSync8(runPath, { withFileTypes: true })) {
6159
7714
  if (!workerEntry.isDirectory()) continue;
6160
- const worktreePath = path31.join(runPath, workerEntry.name);
7715
+ const worktreePath = path40.join(runPath, workerEntry.name);
6161
7716
  candidates.push(
6162
7717
  ...collectBuildCacheForWorktree(worktreePath, opts, seen, {
6163
7718
  runId: runEntry.name,
@@ -6178,7 +7733,7 @@ function scanWorktreeCandidates(opts) {
6178
7733
  for (const entry of opts.index.values()) {
6179
7734
  if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
6180
7735
  const resolved = entry.worktreePath;
6181
- if (!existsSync20(resolved)) continue;
7736
+ if (!existsSync28(resolved)) continue;
6182
7737
  if (seen.has(resolved)) continue;
6183
7738
  seen.add(resolved);
6184
7739
  candidates.push({
@@ -6188,28 +7743,28 @@ function scanWorktreeCandidates(opts) {
6188
7743
  runId: entry.runId,
6189
7744
  worker: entry.workerName,
6190
7745
  repo: entry.run.repo,
6191
- ageMs: pathAgeMs(resolved, opts.now)
7746
+ ageMs: pathAgeMs2(resolved, opts.now)
6192
7747
  });
6193
7748
  }
6194
7749
  }
6195
- if (!orphanEnabled || !existsSync20(opts.worktreesDir)) return candidates;
7750
+ if (!orphanEnabled || !existsSync28(opts.worktreesDir)) return candidates;
6196
7751
  const indexedPaths = /* @__PURE__ */ new Set();
6197
7752
  for (const entry of opts.index.values()) {
6198
- indexedPaths.add(path31.resolve(entry.worktreePath));
7753
+ indexedPaths.add(path40.resolve(entry.worktreePath));
6199
7754
  }
6200
- for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
7755
+ for (const runEntry of readdirSync8(opts.worktreesDir, { withFileTypes: true })) {
6201
7756
  if (!runEntry.isDirectory()) continue;
6202
7757
  if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
6203
- const runPath = path31.join(opts.worktreesDir, runEntry.name);
7758
+ const runPath = path40.join(opts.worktreesDir, runEntry.name);
6204
7759
  let workerEntries;
6205
7760
  try {
6206
- workerEntries = readdirSync6(runPath, { withFileTypes: true });
7761
+ workerEntries = readdirSync8(runPath, { withFileTypes: true });
6207
7762
  } catch {
6208
7763
  continue;
6209
7764
  }
6210
7765
  for (const workerEntry of workerEntries) {
6211
7766
  if (!workerEntry.isDirectory()) continue;
6212
- const worktreePath = path31.resolve(path31.join(runPath, workerEntry.name));
7767
+ const worktreePath = path40.resolve(path40.join(runPath, workerEntry.name));
6213
7768
  if (seen.has(worktreePath)) continue;
6214
7769
  if (indexedPaths.has(worktreePath)) continue;
6215
7770
  if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
@@ -6220,7 +7775,7 @@ function scanWorktreeCandidates(opts) {
6220
7775
  bytes: null,
6221
7776
  runId: runEntry.name,
6222
7777
  worker: workerEntry.name,
6223
- ageMs: pathAgeMs(worktreePath, opts.now)
7778
+ ageMs: pathAgeMs2(worktreePath, opts.now)
6224
7779
  });
6225
7780
  }
6226
7781
  }
@@ -6228,27 +7783,27 @@ function scanWorktreeCandidates(opts) {
6228
7783
  }
6229
7784
 
6230
7785
  // src/cleanup-dependency-scan.ts
6231
- import { existsSync as existsSync21, readdirSync as readdirSync7, statSync as statSync4 } from "node:fs";
6232
- import path32 from "node:path";
7786
+ import { existsSync as existsSync29, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
7787
+ import path41 from "node:path";
6233
7788
  var DEPENDENCY_CACHE_DIRS = [
6234
7789
  { dirName: "node_modules", kind: "remove_node_modules" },
6235
7790
  { dirName: ".next", kind: "remove_next_cache" }
6236
7791
  ];
6237
- function pathAgeMs2(target, now) {
7792
+ function pathAgeMs3(target, now) {
6238
7793
  try {
6239
- const mtime = statSync4(target).mtimeMs;
7794
+ const mtime = statSync7(target).mtimeMs;
6240
7795
  return Math.max(0, now - mtime);
6241
7796
  } catch {
6242
7797
  return 0;
6243
7798
  }
6244
7799
  }
6245
7800
  function isPathInside2(child, parent) {
6246
- const rel = path32.relative(parent, child);
6247
- return rel === "" || !rel.startsWith("..") && !path32.isAbsolute(rel);
7801
+ const rel = path41.relative(parent, child);
7802
+ return rel === "" || !rel.startsWith("..") && !path41.isAbsolute(rel);
6248
7803
  }
6249
7804
  function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
6250
- if (!existsSync21(targetPath)) return;
6251
- const resolved = path32.resolve(targetPath);
7805
+ if (!existsSync29(targetPath)) return;
7806
+ const resolved = path41.resolve(targetPath);
6252
7807
  if (seen.has(resolved)) return;
6253
7808
  if (!isPathInside2(resolved, opts.harnessRoot)) return;
6254
7809
  seen.add(resolved);
@@ -6260,12 +7815,12 @@ function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
6260
7815
  runId: meta.runId,
6261
7816
  worker: meta.worker,
6262
7817
  repo: meta.repo,
6263
- ageMs: pathAgeMs2(resolved, opts.now)
7818
+ ageMs: pathAgeMs3(resolved, opts.now)
6264
7819
  });
6265
7820
  }
6266
7821
  function scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, meta) {
6267
7822
  for (const entry of DEPENDENCY_CACHE_DIRS) {
6268
- pushCandidate2(candidates, seen, opts, path32.join(worktreePath, entry.dirName), entry.kind, meta);
7823
+ pushCandidate2(candidates, seen, opts, path41.join(worktreePath, entry.dirName), entry.kind, meta);
6269
7824
  }
6270
7825
  }
6271
7826
  function scanDependencyCacheCandidates(opts) {
@@ -6279,20 +7834,20 @@ function scanDependencyCacheCandidates(opts) {
6279
7834
  repo: entry.run.repo
6280
7835
  });
6281
7836
  }
6282
- if (!existsSync21(opts.worktreesDir)) return candidates;
6283
- for (const runEntry of readdirSync7(opts.worktreesDir, { withFileTypes: true })) {
7837
+ if (!existsSync29(opts.worktreesDir)) return candidates;
7838
+ for (const runEntry of readdirSync9(opts.worktreesDir, { withFileTypes: true })) {
6284
7839
  if (!runEntry.isDirectory()) continue;
6285
7840
  if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
6286
- const runPath = path32.join(opts.worktreesDir, runEntry.name);
7841
+ const runPath = path41.join(opts.worktreesDir, runEntry.name);
6287
7842
  let workerEntries;
6288
7843
  try {
6289
- workerEntries = readdirSync7(runPath, { withFileTypes: true });
7844
+ workerEntries = readdirSync9(runPath, { withFileTypes: true });
6290
7845
  } catch {
6291
7846
  continue;
6292
7847
  }
6293
7848
  for (const workerEntry of workerEntries) {
6294
7849
  if (!workerEntry.isDirectory()) continue;
6295
- const worktreePath = path32.join(runPath, workerEntry.name);
7850
+ const worktreePath = path41.join(runPath, workerEntry.name);
6296
7851
  scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, {
6297
7852
  runId: runEntry.name,
6298
7853
  worker: workerEntry.name
@@ -6303,11 +7858,11 @@ function scanDependencyCacheCandidates(opts) {
6303
7858
  }
6304
7859
 
6305
7860
  // src/cleanup-duplicate-worktrees.ts
6306
- import { existsSync as existsSync22, statSync as statSync5 } from "node:fs";
6307
- import path33 from "node:path";
6308
- function pathAgeMs3(target, now) {
7861
+ import { existsSync as existsSync30, statSync as statSync8 } from "node:fs";
7862
+ import path42 from "node:path";
7863
+ function pathAgeMs4(target, now) {
6309
7864
  try {
6310
- const mtime = statSync5(target).mtimeMs;
7865
+ const mtime = statSync8(target).mtimeMs;
6311
7866
  return Math.max(0, now - mtime);
6312
7867
  } catch {
6313
7868
  return 0;
@@ -6334,8 +7889,8 @@ function parseWorktreePorcelain(output) {
6334
7889
  return records;
6335
7890
  }
6336
7891
  function isUnderWorktreesDir(worktreePath, worktreesDir) {
6337
- const rel = path33.relative(path33.resolve(worktreesDir), path33.resolve(worktreePath));
6338
- return rel !== "" && !rel.startsWith("..") && !path33.isAbsolute(rel);
7892
+ const rel = path42.relative(path42.resolve(worktreesDir), path42.resolve(worktreePath));
7893
+ return rel !== "" && !rel.startsWith("..") && !path42.isAbsolute(rel);
6339
7894
  }
6340
7895
  function isCleanWorktree(worktreePath, repoRoot) {
6341
7896
  try {
@@ -6348,14 +7903,14 @@ function isCleanWorktree(worktreePath, repoRoot) {
6348
7903
  }
6349
7904
  }
6350
7905
  function scanDuplicateWorktreeCandidates(opts) {
6351
- if (!opts.includeOrphans || !existsSync22(opts.worktreesDir)) return [];
7906
+ if (!opts.includeOrphans || !existsSync30(opts.worktreesDir)) return [];
6352
7907
  const repos = /* @__PURE__ */ new Set();
6353
7908
  for (const entry of opts.index.values()) {
6354
- if (entry.run.repo) repos.add(path33.resolve(entry.run.repo));
7909
+ if (entry.run.repo) repos.add(path42.resolve(entry.run.repo));
6355
7910
  }
6356
7911
  const indexedPaths = /* @__PURE__ */ new Set();
6357
7912
  for (const entry of opts.index.values()) {
6358
- indexedPaths.add(path33.resolve(entry.worktreePath));
7913
+ indexedPaths.add(path42.resolve(entry.worktreePath));
6359
7914
  }
6360
7915
  const candidates = [];
6361
7916
  const seen = /* @__PURE__ */ new Set();
@@ -6368,15 +7923,15 @@ function scanDuplicateWorktreeCandidates(opts) {
6368
7923
  }
6369
7924
  const worktrees = parseWorktreePorcelain(porcelain);
6370
7925
  for (const wt of worktrees) {
6371
- const resolved = path33.resolve(wt.path);
6372
- if (resolved === path33.resolve(repoRoot)) continue;
7926
+ const resolved = path42.resolve(wt.path);
7927
+ if (resolved === path42.resolve(repoRoot)) continue;
6373
7928
  if (!isUnderWorktreesDir(resolved, opts.worktreesDir)) continue;
6374
7929
  if (indexedPaths.has(resolved)) continue;
6375
7930
  if (seen.has(resolved)) continue;
6376
- if (!existsSync22(resolved)) continue;
7931
+ if (!existsSync30(resolved)) continue;
6377
7932
  if (!isCleanWorktree(resolved, repoRoot)) continue;
6378
- const rel = path33.relative(opts.worktreesDir, resolved);
6379
- const parts = rel.split(path33.sep);
7933
+ const rel = path42.relative(opts.worktreesDir, resolved);
7934
+ const parts = rel.split(path42.sep);
6380
7935
  const runId = parts[0];
6381
7936
  const worker = parts[1] ?? "unknown";
6382
7937
  seen.add(resolved);
@@ -6387,7 +7942,7 @@ function scanDuplicateWorktreeCandidates(opts) {
6387
7942
  runId,
6388
7943
  worker,
6389
7944
  repo: repoRoot,
6390
- ageMs: pathAgeMs3(resolved, opts.now)
7945
+ ageMs: pathAgeMs4(resolved, opts.now)
6391
7946
  });
6392
7947
  }
6393
7948
  }
@@ -6395,12 +7950,12 @@ function scanDuplicateWorktreeCandidates(opts) {
6395
7950
  }
6396
7951
 
6397
7952
  // src/cleanup-worktree-index.ts
6398
- import path34 from "node:path";
7953
+ import path43 from "node:path";
6399
7954
  function buildWorktreeIndexAt(harnessRoot) {
6400
7955
  const index = /* @__PURE__ */ new Map();
6401
7956
  for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
6402
7957
  for (const name of Object.keys(run.workers || {})) {
6403
- const workerPath = path34.join(
7958
+ const workerPath = path43.join(
6404
7959
  runDirectoryAt(harnessRoot, run.id),
6405
7960
  "workers",
6406
7961
  safeSlug(name),
@@ -6409,9 +7964,9 @@ function buildWorktreeIndexAt(harnessRoot) {
6409
7964
  const worker = readJson(workerPath, void 0);
6410
7965
  if (!worker?.worktreePath) continue;
6411
7966
  const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
6412
- index.set(path34.resolve(worker.worktreePath), {
7967
+ index.set(path43.resolve(worker.worktreePath), {
6413
7968
  harnessRoot,
6414
- worktreePath: path34.resolve(worker.worktreePath),
7969
+ worktreePath: path43.resolve(worker.worktreePath),
6415
7970
  runId: run.id,
6416
7971
  workerName: name,
6417
7972
  run,
@@ -6423,10 +7978,6 @@ function buildWorktreeIndexAt(harnessRoot) {
6423
7978
  return index;
6424
7979
  }
6425
7980
 
6426
- // src/cleanup-types.ts
6427
- var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
6428
- var DEFAULT_WORKTREES_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
6429
-
6430
7981
  // src/cleanup-retention-config.ts
6431
7982
  function envFlag(name) {
6432
7983
  const v = process.env[name];
@@ -6443,6 +7994,9 @@ function resolveHarnessRetention(options = {}) {
6443
7994
  const finalizeStaleRuns2 = options.finalizeStaleRuns !== false && !envFlag("KYNVER_CLEANUP_SKIP_FINALIZE");
6444
7995
  const nodeModulesAgeMs = options.nodeModulesAgeMs ?? envMs("KYNVER_CLEANUP_NODE_MODULES_AGE_MS", DEFAULT_NODE_MODULES_AGE_MS);
6445
7996
  const worktreesAgeMs = options.worktreesAgeMs ?? envMs("KYNVER_CLEANUP_WORKTREES_AGE_MS", 0);
7997
+ const terminalWorktreesAgeMs = options.terminalWorktreesAgeMs ?? envMs("KYNVER_CLEANUP_TERMINAL_WORKTREES_AGE_MS", DEFAULT_TERMINAL_WORKTREES_AGE_MS);
7998
+ const runDirectoriesAgeMs = options.runDirectoriesAgeMs ?? envMs("KYNVER_CLEANUP_RUN_DIRECTORIES_AGE_MS", DEFAULT_RUN_DIRECTORIES_AGE_MS);
7999
+ const maxActionsPerSweep = options.maxActionsPerSweep ?? envMs("KYNVER_CLEANUP_MAX_ACTIONS_PER_SWEEP", DEFAULT_MAX_ACTIONS_PER_SWEEP);
6446
8000
  const includeOrphans = options.includeOrphans === true || envFlag("KYNVER_CLEANUP_INCLUDE_ORPHANS");
6447
8001
  const scopeAll = envFlag("KYNVER_CLEANUP_SCOPE_ALL") || process.env.KYNVER_CLEANUP_SCOPE === "all";
6448
8002
  const runIdFilter = scopeAll ? options.runIdFilter : options.runIdFilter ?? (process.env.KYNVER_CLEANUP_RUN_ID || void 0);
@@ -6452,6 +8006,9 @@ function resolveHarnessRetention(options = {}) {
6452
8006
  finalizeStaleRuns: finalizeStaleRuns2,
6453
8007
  nodeModulesAgeMs,
6454
8008
  worktreesAgeMs: worktreesAgeMs > 0 ? worktreesAgeMs : 0,
8009
+ terminalWorktreesAgeMs: terminalWorktreesAgeMs >= 0 ? terminalWorktreesAgeMs : 0,
8010
+ runDirectoriesAgeMs: runDirectoriesAgeMs >= 0 ? runDirectoriesAgeMs : 0,
8011
+ maxActionsPerSweep: Number.isFinite(maxActionsPerSweep) && maxActionsPerSweep > 0 ? Math.floor(maxActionsPerSweep) : DEFAULT_MAX_ACTIONS_PER_SWEEP,
6455
8012
  includeOrphans,
6456
8013
  runIdFilter: runIdFilter ? String(runIdFilter) : void 0,
6457
8014
  accountBytes
@@ -6464,21 +8021,24 @@ function resolvePipelineHarnessRetention(runId) {
6464
8021
  return resolveHarnessRetention({
6465
8022
  runIdFilter: scopeAll ? void 0 : runId,
6466
8023
  worktreesAgeMs,
8024
+ terminalWorktreesAgeMs: scopeAll ? DEFAULT_TERMINAL_WORKTREES_AGE_MS : void 0,
8025
+ runDirectoriesAgeMs: scopeAll ? DEFAULT_RUN_DIRECTORIES_AGE_MS : void 0,
8026
+ includeOrphans: scopeAll ? true : void 0,
6467
8027
  finalizeStaleRuns: true,
6468
8028
  accountBytes: true
6469
8029
  });
6470
8030
  }
6471
8031
 
6472
8032
  // src/cleanup-orphan-safety.ts
6473
- import { existsSync as existsSync23, statSync as statSync6 } from "node:fs";
6474
- import path35 from "node:path";
8033
+ import { existsSync as existsSync31, statSync as statSync9 } from "node:fs";
8034
+ import path44 from "node:path";
6475
8035
  var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
6476
8036
  function assessOrphanWorktreeSafety(input) {
6477
8037
  const now = input.now ?? Date.now();
6478
8038
  const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
6479
- if (!existsSync23(input.worktreePath)) return null;
8039
+ if (!existsSync31(input.worktreePath)) return null;
6480
8040
  if (input.runId && input.workerName) {
6481
- const heartbeatPath = path35.join(
8041
+ const heartbeatPath = path44.join(
6482
8042
  input.harnessRoot,
6483
8043
  "runs",
6484
8044
  input.runId,
@@ -6487,13 +8047,13 @@ function assessOrphanWorktreeSafety(input) {
6487
8047
  "heartbeat.jsonl"
6488
8048
  );
6489
8049
  try {
6490
- const mtime = statSync6(heartbeatPath).mtimeMs;
8050
+ const mtime = statSync9(heartbeatPath).mtimeMs;
6491
8051
  if (now - mtime < heartbeatFreshMs) return "active_worker";
6492
8052
  } catch {
6493
8053
  }
6494
8054
  }
6495
- const gitDir = path35.join(input.worktreePath, ".git");
6496
- if (!existsSync23(gitDir)) return null;
8055
+ const gitDir = path44.join(input.worktreePath, ".git");
8056
+ if (!existsSync31(gitDir)) return null;
6497
8057
  const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
6498
8058
  if (porcelain.status !== 0) return "pr_or_unmerged_commits";
6499
8059
  const dirtyLines = porcelain.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
@@ -6522,14 +8082,14 @@ function assessOrphanWorktreeSafety(input) {
6522
8082
  }
6523
8083
 
6524
8084
  // src/harness-storage-snapshot.ts
6525
- import { existsSync as existsSync24, readdirSync as readdirSync8, statSync as statSync7 } from "node:fs";
6526
- import path36 from "node:path";
8085
+ import { existsSync as existsSync32, readdirSync as readdirSync10, statSync as statSync10 } from "node:fs";
8086
+ import path45 from "node:path";
6527
8087
  function harnessStorageSnapshot(opts = {}) {
6528
- const harnessRoot = opts.harnessRoot ?? resolveHarnessRoot();
6529
- const worktreesDir = path36.join(harnessRoot, "worktrees");
8088
+ const harnessRoot = normalizeHarnessRoot(opts.harnessRoot ?? resolveHarnessRoot());
8089
+ const worktreesDir = harnessWorktreesDir(harnessRoot);
6530
8090
  const now = opts.now ?? Date.now();
6531
8091
  const scannedAt = new Date(now).toISOString();
6532
- if (!existsSync24(worktreesDir)) {
8092
+ if (!existsSync32(worktreesDir)) {
6533
8093
  return {
6534
8094
  harnessRoot,
6535
8095
  worktreesDir,
@@ -6546,7 +8106,7 @@ function harnessStorageSnapshot(opts = {}) {
6546
8106
  let oldestMs = null;
6547
8107
  let entries;
6548
8108
  try {
6549
- entries = readdirSync8(worktreesDir, { withFileTypes: true });
8109
+ entries = readdirSync10(worktreesDir, { withFileTypes: true });
6550
8110
  } catch {
6551
8111
  return {
6552
8112
  harnessRoot,
@@ -6561,14 +8121,14 @@ function harnessStorageSnapshot(opts = {}) {
6561
8121
  for (const runEntry of entries) {
6562
8122
  if (!runEntry.isDirectory()) continue;
6563
8123
  runCount += 1;
6564
- const runPath = path36.join(worktreesDir, runEntry.name);
8124
+ const runPath = path45.join(worktreesDir, runEntry.name);
6565
8125
  try {
6566
- const st = statSync7(runPath);
8126
+ const st = statSync10(runPath);
6567
8127
  oldestMs = oldestMs === null ? st.mtimeMs : Math.min(oldestMs, st.mtimeMs);
6568
8128
  } catch {
6569
8129
  }
6570
8130
  try {
6571
- for (const workerEntry of readdirSync8(runPath, { withFileTypes: true })) {
8131
+ for (const workerEntry of readdirSync10(runPath, { withFileTypes: true })) {
6572
8132
  if (workerEntry.isDirectory()) workerCount += 1;
6573
8133
  }
6574
8134
  } catch {
@@ -6596,16 +8156,16 @@ function harnessStorageSnapshot(opts = {}) {
6596
8156
  }
6597
8157
 
6598
8158
  // src/cleanup-harness-roots.ts
6599
- import { existsSync as existsSync25 } from "node:fs";
6600
- import { homedir as homedir6 } from "node:os";
6601
- import path37 from "node:path";
8159
+ import { existsSync as existsSync33 } from "node:fs";
8160
+ import { homedir as homedir11 } from "node:os";
8161
+ import path46 from "node:path";
6602
8162
  var WELL_KNOWN_HARNESS_SCAN_ROOTS = [
6603
8163
  "/var/tmp/kynver-harness",
6604
- path37.join(homedir6(), ".openclaw", "harness")
8164
+ path46.join(homedir11(), ".openclaw", "harness")
6605
8165
  ];
6606
8166
  function addRoot(seen, roots, candidate) {
6607
8167
  if (!candidate?.trim()) return;
6608
- const resolved = path37.resolve(resolveUserPath(candidate.trim()));
8168
+ const resolved = normalizeHarnessRoot(candidate.trim());
6609
8169
  if (seen.has(resolved)) return;
6610
8170
  seen.add(resolved);
6611
8171
  roots.push(resolved);
@@ -6623,42 +8183,13 @@ function resolveHarnessScanRoots(options = {}) {
6623
8183
  for (const candidate of extra ?? []) addRoot(seen, roots, candidate);
6624
8184
  if (shouldScanWellKnownRoots(options)) {
6625
8185
  for (const candidate of WELL_KNOWN_HARNESS_SCAN_ROOTS) {
6626
- const resolved = path37.resolve(candidate);
6627
- if (!seen.has(resolved) && existsSync25(resolved)) addRoot(seen, roots, resolved);
8186
+ const resolved = path46.resolve(candidate);
8187
+ if (!seen.has(resolved) && existsSync33(resolved)) addRoot(seen, roots, resolved);
6628
8188
  }
6629
8189
  }
6630
8190
  return roots;
6631
8191
  }
6632
8192
 
6633
- // src/cleanup-active-worktrees.ts
6634
- import path38 from "node:path";
6635
- function isActiveHarnessWorker2(worker, runBase, runBaseCommit) {
6636
- const status = computeWorkerStatus(worker, { base: runBase, baseCommit: runBaseCommit });
6637
- return status.alive && !status.finalResult && status.attention.state !== "done";
6638
- }
6639
- function collectActiveWorktreeGuards(harnessRoots) {
6640
- const activeWorktreePaths = /* @__PURE__ */ new Set();
6641
- const liveRunKeys = /* @__PURE__ */ new Set();
6642
- for (const harnessRoot of harnessRoots) {
6643
- for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
6644
- let runHasLive = false;
6645
- for (const name of Object.keys(run.workers || {})) {
6646
- const worker = readJson(
6647
- path38.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
6648
- void 0
6649
- );
6650
- if (!worker?.worktreePath) continue;
6651
- const worktreePath = path38.resolve(worker.worktreePath);
6652
- if (!isActiveHarnessWorker2(worker, run.base, run.baseCommit)) continue;
6653
- runHasLive = true;
6654
- activeWorktreePaths.add(worktreePath);
6655
- }
6656
- if (runHasLive) liveRunKeys.add(`${harnessRoot}\0${run.id}`);
6657
- }
6658
- }
6659
- return { activeWorktreePaths, liveRunKeys };
6660
- }
6661
-
6662
8193
  // src/cleanup-disk-pressure.ts
6663
8194
  function envFlag2(name) {
6664
8195
  const v = process.env[name];
@@ -6695,14 +8226,14 @@ function applyDiskPressureToRetention(retention, pressure) {
6695
8226
 
6696
8227
  // src/cleanup.ts
6697
8228
  function resolvePaths(options = {}) {
6698
- const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
8229
+ const harnessRoot = options.harnessRoot ? normalizeHarnessRoot(options.harnessRoot) : resolveHarnessRoot();
6699
8230
  const scanRoots = resolveHarnessScanRoots({ harnessRoot });
6700
8231
  const now = options.now ?? Date.now();
6701
8232
  return { harnessRoot, scanRoots, now };
6702
8233
  }
6703
- function normalizeGuardSkip(skip) {
6704
- if (typeof skip === "string") return { reason: skip };
6705
- return skip;
8234
+ function normalizeGuardSkip(skip2) {
8235
+ if (typeof skip2 === "string") return { reason: skip2 };
8236
+ return skip2;
6706
8237
  }
6707
8238
  function recordSkip(skips, pathValue, reason, detail) {
6708
8239
  skips.push({ path: pathValue, reason, ...detail ? { detail } : {} });
@@ -6713,8 +8244,8 @@ function attachCandidateBytes(candidate, accountBytes) {
6713
8244
  }
6714
8245
  function tallySkipReasons(actions, skips) {
6715
8246
  const counts = {};
6716
- for (const skip of skips) {
6717
- counts[skip.reason] = (counts[skip.reason] ?? 0) + 1;
8247
+ for (const skip2 of skips) {
8248
+ counts[skip2.reason] = (counts[skip2.reason] ?? 0) + 1;
6718
8249
  }
6719
8250
  for (const action of actions) {
6720
8251
  if (action.skipReason) {
@@ -6742,9 +8273,9 @@ function mergeWorktreeIndexes(scanRoots) {
6742
8273
  }
6743
8274
  function worktreePathForCandidate(candidate, worktreesDir) {
6744
8275
  if (candidate.runId && candidate.worker) {
6745
- return path39.join(worktreesDir, candidate.runId, candidate.worker);
8276
+ return path47.join(worktreesDir, candidate.runId, candidate.worker);
6746
8277
  }
6747
- return path39.resolve(candidate.path, "..");
8278
+ return path47.resolve(candidate.path, "..");
6748
8279
  }
6749
8280
  function runHarnessCleanup(options = {}) {
6750
8281
  let retention = resolveHarnessRetention(options);
@@ -6757,8 +8288,11 @@ function runHarnessCleanup(options = {}) {
6757
8288
  const skips = [];
6758
8289
  const actions = [];
6759
8290
  const processedPaths = /* @__PURE__ */ new Set();
8291
+ const maxActions = retention.maxActionsPerSweep;
8292
+ const atSweepCap = () => actions.length >= maxActions;
6760
8293
  for (const harnessRoot of paths.scanRoots) {
6761
- const worktreesDir = path39.join(harnessRoot, "worktrees");
8294
+ if (atSweepCap()) break;
8295
+ const worktreesDir = path47.join(harnessRoot, "worktrees");
6762
8296
  const scanOpts = {
6763
8297
  harnessRoot,
6764
8298
  worktreesDir,
@@ -6770,8 +8304,9 @@ function runHarnessCleanup(options = {}) {
6770
8304
  now: paths.now
6771
8305
  };
6772
8306
  for (const raw of scanDependencyCacheCandidates(scanOpts)) {
8307
+ if (atSweepCap()) break;
6773
8308
  const candidate = attachCandidateBytes(raw, retention.accountBytes);
6774
- const resolved = path39.resolve(candidate.path);
8309
+ const resolved = path47.resolve(candidate.path);
6775
8310
  if (processedPaths.has(resolved)) continue;
6776
8311
  processedPaths.add(resolved);
6777
8312
  const pathSkip = pathGuardForDependencyCache(candidate, harnessRoot, worktreesDir);
@@ -6781,7 +8316,7 @@ function runHarnessCleanup(options = {}) {
6781
8316
  continue;
6782
8317
  }
6783
8318
  const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
6784
- const indexed = index.get(path39.resolve(worktreePath)) ?? null;
8319
+ const indexed = index.get(path47.resolve(worktreePath)) ?? null;
6785
8320
  const guardReason = skipDependencyCacheRemoval({
6786
8321
  indexed,
6787
8322
  includeOrphans: true,
@@ -6799,8 +8334,9 @@ function runHarnessCleanup(options = {}) {
6799
8334
  actions.push(removeDependencyCacheAction(candidate, retention.execute));
6800
8335
  }
6801
8336
  for (const raw of scanBuildCacheCandidates(scanOpts)) {
8337
+ if (atSweepCap()) break;
6802
8338
  const candidate = attachCandidateBytes(raw, retention.accountBytes);
6803
- const resolved = path39.resolve(candidate.path);
8339
+ const resolved = path47.resolve(candidate.path);
6804
8340
  if (processedPaths.has(resolved)) continue;
6805
8341
  processedPaths.add(resolved);
6806
8342
  const pathSkip = isHarnessBuildCachePath(candidate.path, harnessRoot, worktreesDir);
@@ -6810,7 +8346,7 @@ function runHarnessCleanup(options = {}) {
6810
8346
  continue;
6811
8347
  }
6812
8348
  const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
6813
- const indexed = index.get(path39.resolve(worktreePath)) ?? null;
8349
+ const indexed = index.get(path47.resolve(worktreePath)) ?? null;
6814
8350
  const guardReason = skipBuildCacheRemoval({
6815
8351
  indexed,
6816
8352
  includeOrphans: true,
@@ -6833,11 +8369,12 @@ function runHarnessCleanup(options = {}) {
6833
8369
  ];
6834
8370
  const worktreeSeen = /* @__PURE__ */ new Set();
6835
8371
  for (const raw of worktreeCandidates) {
6836
- const resolved = path39.resolve(raw.path);
8372
+ if (atSweepCap()) break;
8373
+ const resolved = path47.resolve(raw.path);
6837
8374
  if (worktreeSeen.has(resolved)) continue;
6838
8375
  worktreeSeen.add(resolved);
6839
8376
  const candidate = attachCandidateBytes({ ...raw, path: resolved }, retention.accountBytes);
6840
- const indexed = index.get(path39.resolve(candidate.path)) ?? null;
8377
+ const indexed = index.get(path47.resolve(candidate.path)) ?? null;
6841
8378
  const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
6842
8379
  worktreePath: candidate.path,
6843
8380
  harnessRoot,
@@ -6847,9 +8384,10 @@ function runHarnessCleanup(options = {}) {
6847
8384
  });
6848
8385
  const guardSkip = skipWorktreeRemoval({
6849
8386
  indexed,
6850
- worktreePath: path39.resolve(candidate.path),
8387
+ worktreePath: path47.resolve(candidate.path),
6851
8388
  includeOrphans: retention.includeOrphans,
6852
8389
  worktreesAgeMs: retention.worktreesAgeMs,
8390
+ terminalWorktreesAgeMs: retention.terminalWorktreesAgeMs,
6853
8391
  ageMs: candidate.ageMs,
6854
8392
  orphanSafety,
6855
8393
  worktreeRemovalGuard: options.worktreeRemovalGuard
@@ -6862,8 +8400,40 @@ function runHarnessCleanup(options = {}) {
6862
8400
  }
6863
8401
  actions.push(removeWorktree(candidate, retention.execute));
6864
8402
  }
8403
+ if (!atSweepCap() && retention.runDirectoriesAgeMs >= 0) {
8404
+ for (const raw of scanStaleRunDirectoryCandidates({
8405
+ harnessRoot,
8406
+ worktreesDir,
8407
+ runDirectoriesAgeMs: retention.runDirectoriesAgeMs,
8408
+ runIdFilter: retention.runIdFilter,
8409
+ activeGuards,
8410
+ now: paths.now
8411
+ })) {
8412
+ if (atSweepCap()) break;
8413
+ const candidate = attachCandidateBytes(raw, retention.accountBytes);
8414
+ const resolved = path47.resolve(candidate.path);
8415
+ if (processedPaths.has(resolved)) continue;
8416
+ processedPaths.add(resolved);
8417
+ const runId = candidate.runId ?? path47.basename(resolved);
8418
+ const dirSkip = skipRunDirectoryRemoval({
8419
+ harnessRoot,
8420
+ runId,
8421
+ runPath: resolved,
8422
+ ageMs: candidate.ageMs,
8423
+ runDirectoriesAgeMs: retention.runDirectoriesAgeMs,
8424
+ activeGuards
8425
+ });
8426
+ if (dirSkip) {
8427
+ recordSkip(skips, candidate.path, dirSkip);
8428
+ actions.push({ ...candidate, executed: false, skipped: true, skipReason: dirSkip });
8429
+ continue;
8430
+ }
8431
+ actions.push(removeRunDirectory(candidate, retention.execute));
8432
+ }
8433
+ }
6865
8434
  }
6866
8435
  let candidateBytes = 0;
8436
+ let removedRunDirectories = 0;
6867
8437
  let reclaimableBytes = 0;
6868
8438
  let removedBytes = 0;
6869
8439
  let removedPaths = 0;
@@ -6874,12 +8444,14 @@ function runHarnessCleanup(options = {}) {
6874
8444
  if (action.executed) {
6875
8445
  removedPaths += 1;
6876
8446
  removedBytes += action.bytes ?? 0;
8447
+ if (action.kind === "remove_run_directory") removedRunDirectories += 1;
6877
8448
  } else if (action.skipped) {
6878
8449
  skippedPaths += 1;
6879
8450
  if (action.skipReason === "dry_run" && action.bytes) reclaimableBytes += action.bytes;
6880
8451
  }
6881
8452
  }
6882
8453
  const storage = retention.accountBytes ? harnessStorageSnapshot({ harnessRoot: paths.harnessRoot, now: paths.now }) : void 0;
8454
+ const preservedLivePaths = collectPreservedLivePaths(actions, skips);
6883
8455
  return {
6884
8456
  harnessRoot: paths.harnessRoot,
6885
8457
  scanRoots: paths.scanRoots,
@@ -6908,7 +8480,9 @@ function runHarnessCleanup(options = {}) {
6908
8480
  skippedPaths,
6909
8481
  skipReasons: tallySkipReasons(actions, skips)
6910
8482
  },
6911
- ...storage ? { storage } : {}
8483
+ ...storage ? { storage } : {},
8484
+ ...preservedLivePaths.length > 0 ? { preservedLivePaths } : {},
8485
+ ...removedRunDirectories > 0 ? { removedRunDirectories } : {}
6912
8486
  };
6913
8487
  }
6914
8488
  function runPipelineHarnessCleanup(runId) {
@@ -6929,8 +8503,8 @@ function isPipelineCleanupEnabled() {
6929
8503
 
6930
8504
  // src/installed-package-versions.ts
6931
8505
  import { readFile } from "node:fs/promises";
6932
- import { homedir as homedir7 } from "node:os";
6933
- import path40 from "node:path";
8506
+ import { homedir as homedir12 } from "node:os";
8507
+ import path48 from "node:path";
6934
8508
  var MANAGED_PACKAGES = [
6935
8509
  "@kynver-app/runtime",
6936
8510
  "@kynver-app/openclaw-agent-os",
@@ -6944,13 +8518,13 @@ function unique(values) {
6944
8518
  return [...new Set(values.filter((value) => Boolean(value)))];
6945
8519
  }
6946
8520
  function moduleRoots() {
6947
- const home = homedir7();
6948
- const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path40.join(home, ".openclaw", "npm");
6949
- const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path40.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path40.join(home, ".npm-global", "lib", "node_modules"));
8521
+ const home = homedir12();
8522
+ const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path48.join(home, ".openclaw", "npm");
8523
+ const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path48.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path48.join(home, ".npm-global", "lib", "node_modules"));
6950
8524
  return unique([
6951
- path40.join(openClawPrefix, "lib", "node_modules"),
6952
- path40.join(openClawPrefix, "node_modules"),
6953
- npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path40.join(npmGlobalRoot, "lib", "node_modules")
8525
+ path48.join(openClawPrefix, "lib", "node_modules"),
8526
+ path48.join(openClawPrefix, "node_modules"),
8527
+ npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path48.join(npmGlobalRoot, "lib", "node_modules")
6954
8528
  ]);
6955
8529
  }
6956
8530
  async function readVersion(packageJsonPath) {
@@ -6966,7 +8540,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
6966
8540
  const out = {};
6967
8541
  for (const packageName of MANAGED_PACKAGES) {
6968
8542
  for (const root of roots) {
6969
- const packageJsonPath = path40.join(root, packageName, "package.json");
8543
+ const packageJsonPath = path48.join(root, packageName, "package.json");
6970
8544
  const version = await readVersion(packageJsonPath);
6971
8545
  if (!version) continue;
6972
8546
  out[packageName] = { version, observedAt, path: packageJsonPath };
@@ -6982,7 +8556,7 @@ async function completeFinishedWorkers(runId, args) {
6982
8556
  const outcomes = [];
6983
8557
  for (const name of Object.keys(run.workers || {})) {
6984
8558
  const worker = readJson(
6985
- path41.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
8559
+ path49.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
6986
8560
  void 0
6987
8561
  );
6988
8562
  if (!worker?.taskId || worker.localOnly) continue;
@@ -7014,21 +8588,7 @@ async function postOperatorTick(agentOsId, runId, resourceGate, args, harnessCle
7014
8588
  const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, { baseUrl: base });
7015
8589
  const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/operator/tick`;
7016
8590
  const packageVersions = await collectInstalledPackageVersions();
7017
- const activeHarnessWorkers = [];
7018
- const run = loadRun(runId);
7019
- for (const name of Object.keys(run.workers || {})) {
7020
- const worker = readJson(
7021
- path41.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
7022
- void 0
7023
- );
7024
- if (!worker?.taskId) continue;
7025
- activeHarnessWorkers.push({
7026
- runId: run.id,
7027
- workerName: name,
7028
- taskId: worker.taskId,
7029
- pid: worker.pid
7030
- });
7031
- }
8591
+ const activeHarnessWorkers = collectRunActiveHarnessWorkers(runId);
7032
8592
  const res = await postJson(url, secret, {
7033
8593
  agentOsId,
7034
8594
  runId,
@@ -7059,9 +8619,13 @@ async function runPipelineTick(args) {
7059
8619
  const completionAckSync = syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick);
7060
8620
  const leaseRenewal = await renewActiveTaskLeases(runId, args);
7061
8621
  const completedWorkers = await completeFinishedWorkers(runId, args);
8622
+ const dispatchResourceGate = observeRunnerResourceGate({
8623
+ runId,
8624
+ configuredMaxWorkersOverride: workspacePrefs?.maxConcurrentWorkers
8625
+ });
7062
8626
  const staleReconcile = reconcileStaleWorkers();
7063
8627
  const planProgressSync = await syncActiveWorkerPlanProgress(runId, args);
7064
- const maxStartsAdvice = resolvePipelineMaxStarts(resourceGate, operatorTick);
8628
+ const maxStartsAdvice = resolvePipelineMaxStarts(dispatchResourceGate, operatorTick);
7065
8629
  let maxStarts = maxStartsAdvice.maxStarts;
7066
8630
  const sweep = await sweepRun({ run: runId, agentOsId, pipeline: true, ...args });
7067
8631
  let dispatch = null;
@@ -7078,7 +8642,7 @@ async function runPipelineTick(args) {
7078
8642
  dispatch = {
7079
8643
  ok: true,
7080
8644
  skipped: true,
7081
- reason: execute ? resourceGate.reason ?? "no slots or queued work" : "execute disabled",
8645
+ reason: execute ? dispatchResourceGate.reason ?? "no slots or queued work" : "execute disabled",
7082
8646
  maxStarts: 0
7083
8647
  };
7084
8648
  }
@@ -7089,6 +8653,7 @@ async function runPipelineTick(args) {
7089
8653
  agentOsId,
7090
8654
  execute,
7091
8655
  resourceGate,
8656
+ dispatchResourceGate,
7092
8657
  leaseRenewal,
7093
8658
  completedWorkers,
7094
8659
  staleReconcile,
@@ -7140,7 +8705,7 @@ async function runDaemon(args) {
7140
8705
  }
7141
8706
 
7142
8707
  // src/plan-progress.ts
7143
- import path42 from "node:path";
8708
+ import path50 from "node:path";
7144
8709
 
7145
8710
  // src/bounded-build/constants.ts
7146
8711
  var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
@@ -7178,7 +8743,7 @@ function formatNodeOptionsFlag(mb = resolveNodeOldSpaceSizeMb()) {
7178
8743
  }
7179
8744
 
7180
8745
  // src/bounded-build/systemd-wrap.ts
7181
- import { spawnSync as spawnSync3 } from "node:child_process";
8746
+ import { spawnSync as spawnSync4 } from "node:child_process";
7182
8747
  var systemdAvailableCache;
7183
8748
  function isSystemdRunAvailable() {
7184
8749
  if (process.env.KYNVER_BUILD_SKIP_SYSTEMD === "1" || process.env.KYNVER_BUILD_SKIP_SYSTEMD === "true") {
@@ -7189,7 +8754,7 @@ function isSystemdRunAvailable() {
7189
8754
  systemdAvailableCache = false;
7190
8755
  return false;
7191
8756
  }
7192
- const res = spawnSync3("systemd-run", ["--version"], { encoding: "utf8", stdio: ["ignore", "ignore", "pipe"] });
8757
+ const res = spawnSync4("systemd-run", ["--version"], { encoding: "utf8", stdio: ["ignore", "ignore", "pipe"] });
7193
8758
  systemdAvailableCache = res.status === 0;
7194
8759
  return systemdAvailableCache;
7195
8760
  }
@@ -7213,7 +8778,7 @@ function buildSystemdRunArgv(opts) {
7213
8778
  }
7214
8779
 
7215
8780
  // src/bounded-build/admission.ts
7216
- import { spawnSync as spawnSync4 } from "node:child_process";
8781
+ import { spawnSync as spawnSync5 } from "node:child_process";
7217
8782
  function positiveInt3(value, fallback) {
7218
8783
  const n = Number(value);
7219
8784
  if (!Number.isFinite(n) || n <= 0) return fallback;
@@ -7249,7 +8814,7 @@ function assessBuildAdmission(opts = {}) {
7249
8814
  }
7250
8815
  function sleepMs2(ms) {
7251
8816
  if (ms <= 0) return;
7252
- spawnSync4(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
8817
+ spawnSync5(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
7253
8818
  stdio: "ignore"
7254
8819
  });
7255
8820
  }
@@ -7270,7 +8835,7 @@ function waitForBuildAdmission(timeoutMs, pollMs = 2e3, opts = {}) {
7270
8835
  }
7271
8836
 
7272
8837
  // src/bounded-build/exec.ts
7273
- import { spawnSync as spawnSync5 } from "node:child_process";
8838
+ import { spawnSync as spawnSync6 } from "node:child_process";
7274
8839
  function envArgv(env) {
7275
8840
  const out = [];
7276
8841
  for (const [key, value] of Object.entries(env)) {
@@ -7280,7 +8845,7 @@ function envArgv(env) {
7280
8845
  return out;
7281
8846
  }
7282
8847
  function runSpawn(argv, opts) {
7283
- const res = spawnSync5(argv[0], argv.slice(1), {
8848
+ const res = spawnSync6(argv[0], argv.slice(1), {
7284
8849
  cwd: opts.cwd,
7285
8850
  env: opts.env,
7286
8851
  encoding: "utf8",
@@ -7427,7 +8992,7 @@ async function emitPlanProgress(args) {
7427
8992
  }
7428
8993
  function verifyPlanLocal(args) {
7429
8994
  const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
7430
- const cwd = path42.resolve(worktree);
8995
+ const cwd = path50.resolve(worktree);
7431
8996
  const summary = runHarnessVerifyCommands(cwd);
7432
8997
  const emitJson = args.json === true || args.json === "true";
7433
8998
  const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
@@ -7476,9 +9041,9 @@ async function verifyPlan(args) {
7476
9041
  }
7477
9042
 
7478
9043
  // src/harness-verify-cli.ts
7479
- import path43 from "node:path";
9044
+ import path51 from "node:path";
7480
9045
  function runHarnessVerifyCli(args) {
7481
- const cwd = path43.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
9046
+ const cwd = path51.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
7482
9047
  const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
7483
9048
  const commands = [];
7484
9049
  const rawCmd = args.command;
@@ -7522,7 +9087,7 @@ function runHarnessVerifyCli(args) {
7522
9087
  }
7523
9088
 
7524
9089
  // src/plan-persist-cli.ts
7525
- import { readFileSync as readFileSync10 } from "node:fs";
9090
+ import { readFileSync as readFileSync11 } from "node:fs";
7526
9091
  var OPERATIONS = ["create", "add_version", "update_metadata"];
7527
9092
  var FAILURE_KINDS = [
7528
9093
  "approval_guard",
@@ -7534,7 +9099,7 @@ var FAILURE_KINDS = [
7534
9099
  function readBodyArg(args) {
7535
9100
  const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
7536
9101
  if (bodyFile) {
7537
- return { body: readFileSync10(bodyFile, "utf8"), bodyPathHint: bodyFile };
9102
+ return { body: readFileSync11(bodyFile, "utf8"), bodyPathHint: bodyFile };
7538
9103
  }
7539
9104
  const inline = args.body ? String(args.body) : void 0;
7540
9105
  if (inline) return { body: inline };
@@ -7678,21 +9243,17 @@ function formatMonitorTickNotice(tick) {
7678
9243
  }
7679
9244
 
7680
9245
  // src/monitor/monitor.service.ts
7681
- import path45 from "node:path";
9246
+ import path53 from "node:path";
7682
9247
 
7683
9248
  // src/monitor/monitor.classify.ts
7684
- function expectedLeaseOwner(runId) {
7685
- return `kynver-harness:${runId}`;
7686
- }
7687
9249
  function classifyWorkerHealth(input) {
7688
9250
  const { worker, status, taskLease } = input;
7689
9251
  const leaseOwner = taskLease?.leaseOwner ?? null;
7690
- const expectedOwner = expectedLeaseOwner(worker.runId);
7691
9252
  if (worker.dispatched && taskLease) {
7692
- if (taskLease.status === "running" && leaseOwner && leaseOwner !== expectedOwner) {
9253
+ if (taskLease.status === "running" && leaseOwner && !harnessLeaseOwnerMatchesRun(leaseOwner, worker.runId)) {
7693
9254
  return {
7694
9255
  health: "orphaned",
7695
- reason: `task lease held by ${leaseOwner}, expected ${expectedOwner}`
9256
+ reason: `task lease held by ${leaseOwner}, expected harness run ${worker.runId}`
7696
9257
  };
7697
9258
  }
7698
9259
  if (taskLease.status === "running" && !status.alive && !status.finalResult) {
@@ -7734,11 +9295,11 @@ function classifyWorkerHealth(input) {
7734
9295
  }
7735
9296
 
7736
9297
  // src/monitor/monitor.store.ts
7737
- import { existsSync as existsSync26, mkdirSync as mkdirSync6, readdirSync as readdirSync9, unlinkSync as unlinkSync2 } from "node:fs";
7738
- import path44 from "node:path";
9298
+ import { existsSync as existsSync34, mkdirSync as mkdirSync6, readdirSync as readdirSync11, unlinkSync as unlinkSync2 } from "node:fs";
9299
+ import path52 from "node:path";
7739
9300
  function monitorsDir() {
7740
9301
  const { harnessRoot } = getHarnessPaths();
7741
- const dir = path44.join(harnessRoot, "monitors");
9302
+ const dir = path52.join(harnessRoot, "monitors");
7742
9303
  mkdirSync6(dir, { recursive: true });
7743
9304
  return dir;
7744
9305
  }
@@ -7746,7 +9307,7 @@ function monitorIdFor(runId, workerName) {
7746
9307
  return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
7747
9308
  }
7748
9309
  function monitorPath(monitorId) {
7749
- return path44.join(monitorsDir(), `${monitorId}.json`);
9310
+ return path52.join(monitorsDir(), `${monitorId}.json`);
7750
9311
  }
7751
9312
  function loadMonitorSession(monitorId) {
7752
9313
  return readJson(monitorPath(monitorId), void 0);
@@ -7756,18 +9317,18 @@ function saveMonitorSession(session) {
7756
9317
  }
7757
9318
  function deleteMonitorSession(monitorId) {
7758
9319
  const file = monitorPath(monitorId);
7759
- if (!existsSync26(file)) return false;
9320
+ if (!existsSync34(file)) return false;
7760
9321
  unlinkSync2(file);
7761
9322
  return true;
7762
9323
  }
7763
9324
  function listMonitorSessions() {
7764
9325
  const dir = monitorsDir();
7765
- if (!existsSync26(dir)) return [];
9326
+ if (!existsSync34(dir)) return [];
7766
9327
  const entries = [];
7767
- for (const name of readdirSync9(dir)) {
9328
+ for (const name of readdirSync11(dir)) {
7768
9329
  if (!name.endsWith(".json")) continue;
7769
9330
  const session = readJson(
7770
- path44.join(dir, name),
9331
+ path52.join(dir, name),
7771
9332
  void 0
7772
9333
  );
7773
9334
  if (!session?.monitorId) continue;
@@ -7858,7 +9419,7 @@ async function fetchTaskLeasesForWorkers(input) {
7858
9419
  // src/monitor/monitor.service.ts
7859
9420
  function workerRecord2(runId, name) {
7860
9421
  return readJson(
7861
- path45.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
9422
+ path53.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
7862
9423
  void 0
7863
9424
  );
7864
9425
  }
@@ -8063,22 +9624,22 @@ async function runMonitorLoop(args) {
8063
9624
  }
8064
9625
 
8065
9626
  // src/monitor/monitor-spawn.ts
8066
- import { spawn as spawn4 } from "node:child_process";
8067
- import { closeSync as closeSync4, existsSync as existsSync27, openSync as openSync4 } from "node:fs";
8068
- import path46 from "node:path";
9627
+ import { spawn as spawn6 } from "node:child_process";
9628
+ import { closeSync as closeSync6, existsSync as existsSync35, openSync as openSync6 } from "node:fs";
9629
+ import path54 from "node:path";
8069
9630
  import { fileURLToPath as fileURLToPath3 } from "node:url";
8070
9631
  function resolveDefaultCliPath2() {
8071
- return path46.join(fileURLToPath3(new URL(".", import.meta.url)), "cli.js");
9632
+ return path54.join(fileURLToPath3(new URL(".", import.meta.url)), "cli.js");
8072
9633
  }
8073
9634
  function spawnMonitorSidecar(opts) {
8074
9635
  const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
8075
- if (!existsSync27(cliPath)) return void 0;
9636
+ if (!existsSync35(cliPath)) return void 0;
8076
9637
  const monitorId = monitorIdFor(opts.runId, opts.workerName);
8077
9638
  const { harnessRoot } = getHarnessPaths();
8078
- const logPath = path46.join(harnessRoot, "monitors", `${monitorId}.log`);
9639
+ const logPath = path54.join(harnessRoot, "monitors", `${monitorId}.log`);
8079
9640
  let logFd;
8080
9641
  try {
8081
- logFd = openSync4(logPath, "a");
9642
+ logFd = openSync6(logPath, "a");
8082
9643
  } catch {
8083
9644
  logFd = void 0;
8084
9645
  }
@@ -8109,7 +9670,7 @@ function spawnMonitorSidecar(opts) {
8109
9670
  logFd ?? "ignore"
8110
9671
  ];
8111
9672
  try {
8112
- const child = spawn4(
9673
+ const child = spawn6(
8113
9674
  nodeExecutable,
8114
9675
  args,
8115
9676
  hiddenSpawnOptions({
@@ -8118,7 +9679,7 @@ function spawnMonitorSidecar(opts) {
8118
9679
  env: process.env
8119
9680
  })
8120
9681
  );
8121
- if (logFd !== void 0) closeSync4(logFd);
9682
+ if (logFd !== void 0) closeSync6(logFd);
8122
9683
  child.unref();
8123
9684
  const session = {
8124
9685
  monitorId,
@@ -8135,7 +9696,7 @@ function spawnMonitorSidecar(opts) {
8135
9696
  } catch {
8136
9697
  if (logFd !== void 0) {
8137
9698
  try {
8138
- closeSync4(logFd);
9699
+ closeSync6(logFd);
8139
9700
  } catch {
8140
9701
  }
8141
9702
  }
@@ -8195,13 +9756,13 @@ async function monitorTickCli(args) {
8195
9756
  }
8196
9757
 
8197
9758
  // src/package-version.ts
8198
- import { existsSync as existsSync28, readFileSync as readFileSync11 } from "node:fs";
9759
+ import { existsSync as existsSync36, readFileSync as readFileSync12 } from "node:fs";
8199
9760
  import { dirname, join } from "node:path";
8200
9761
  import { fileURLToPath as fileURLToPath4 } from "node:url";
8201
9762
  function resolvePackageRoot(moduleUrl) {
8202
9763
  let dir = dirname(fileURLToPath4(moduleUrl));
8203
9764
  for (let depth = 0; depth < 6; depth += 1) {
8204
- if (existsSync28(join(dir, "package.json"))) return dir;
9765
+ if (existsSync36(join(dir, "package.json"))) return dir;
8205
9766
  const parent = dirname(dir);
8206
9767
  if (parent === dir) break;
8207
9768
  dir = parent;
@@ -8210,7 +9771,7 @@ function resolvePackageRoot(moduleUrl) {
8210
9771
  }
8211
9772
  function readOwnPackageVersion(moduleUrl = import.meta.url) {
8212
9773
  const pkgPath = join(resolvePackageRoot(moduleUrl), "package.json");
8213
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf8"));
9774
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
8214
9775
  if (typeof pkg.version !== "string" || !pkg.version.trim()) {
8215
9776
  throw new Error(`Missing package.json version at ${pkgPath}`);
8216
9777
  }
@@ -8230,17 +9791,142 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
8230
9791
  return true;
8231
9792
  }
8232
9793
 
9794
+ // src/post-restart-unblock.ts
9795
+ import path55 from "node:path";
9796
+ function skip(runId, worker, taskId, agentOsId, leaseOwner, reason) {
9797
+ return { runId, worker, taskId, agentOsId, leaseOwner, action: "skipped", reason };
9798
+ }
9799
+ async function postRestartUnblock(args) {
9800
+ const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
9801
+ const agentOsIdFilter = args.agentOsId ? String(args.agentOsId).trim() : null;
9802
+ const dryRun = args.dryRun === true || args.dryRun === "true";
9803
+ const released = [];
9804
+ const skipped = [];
9805
+ const errors = [];
9806
+ for (const run of listRunRecords()) {
9807
+ for (const name of Object.keys(run.workers ?? {})) {
9808
+ const workerPath = path55.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
9809
+ const worker = readJson(workerPath, void 0);
9810
+ if (!worker) {
9811
+ skipped.push(skip(run.id, name, "", "", "", "worker.json missing"));
9812
+ continue;
9813
+ }
9814
+ const taskId = worker.taskId ?? "";
9815
+ const agentOsId = worker.agentOsId ?? "";
9816
+ const leaseOwner = worker.leaseOwner ?? "";
9817
+ if (!worker.dispatched || !taskId || !agentOsId || !leaseOwner) {
9818
+ skipped.push(
9819
+ skip(run.id, name, taskId, agentOsId, leaseOwner, "not a fully-leased dispatched worker")
9820
+ );
9821
+ continue;
9822
+ }
9823
+ if (agentOsIdFilter && agentOsId !== agentOsIdFilter) continue;
9824
+ if (worker.completionReportedAt) {
9825
+ skipped.push(
9826
+ skip(run.id, name, taskId, agentOsId, leaseOwner, "completion already reported")
9827
+ );
9828
+ continue;
9829
+ }
9830
+ const status = computeWorkerStatus(worker);
9831
+ if (status.finalResult) {
9832
+ skipped.push(
9833
+ skip(run.id, name, taskId, agentOsId, leaseOwner, "has final result \u2014 let pipeline tick complete it")
9834
+ );
9835
+ continue;
9836
+ }
9837
+ if (status.alive) {
9838
+ skipped.push(
9839
+ skip(run.id, name, taskId, agentOsId, leaseOwner, "worker process is still alive")
9840
+ );
9841
+ continue;
9842
+ }
9843
+ if (dryRun) {
9844
+ released.push({
9845
+ runId: run.id,
9846
+ worker: name,
9847
+ taskId,
9848
+ agentOsId,
9849
+ leaseOwner,
9850
+ action: "released",
9851
+ reason: "dry-run: would release dead dispatched worker lease"
9852
+ });
9853
+ continue;
9854
+ }
9855
+ try {
9856
+ const secret = await resolveCallbackSecretWithMint(
9857
+ args.secret ? String(args.secret) : void 0,
9858
+ agentOsId,
9859
+ { baseUrl: base }
9860
+ );
9861
+ const releaseUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/${encodeURIComponent(taskId)}/release`;
9862
+ const release = await postJsonWithCredentialRefresh(
9863
+ releaseUrl,
9864
+ secret,
9865
+ { agentOsId, leaseOwner },
9866
+ { agentOsId, baseUrl: base }
9867
+ );
9868
+ const releaseOk = release.ok === true || release.response?.ok === true;
9869
+ if (releaseOk) {
9870
+ released.push({
9871
+ runId: run.id,
9872
+ worker: name,
9873
+ taskId,
9874
+ agentOsId,
9875
+ leaseOwner,
9876
+ action: "released",
9877
+ reason: "dead dispatched worker lease released; task requeued"
9878
+ });
9879
+ } else {
9880
+ skipped.push({
9881
+ runId: run.id,
9882
+ worker: name,
9883
+ taskId,
9884
+ agentOsId,
9885
+ leaseOwner,
9886
+ action: "skipped",
9887
+ reason: `release returned ok:false (likely owner mismatch or already released): HTTP ${release.status}`
9888
+ });
9889
+ }
9890
+ } catch (err) {
9891
+ errors.push({
9892
+ runId: run.id,
9893
+ worker: name,
9894
+ taskId,
9895
+ agentOsId,
9896
+ leaseOwner,
9897
+ action: "error",
9898
+ reason: err.message
9899
+ });
9900
+ }
9901
+ }
9902
+ }
9903
+ return { dryRun, released, skipped, errors };
9904
+ }
9905
+ async function postRestartUnblockCli(args) {
9906
+ const result = await postRestartUnblock(args);
9907
+ const summary = {
9908
+ ok: result.errors.length === 0,
9909
+ dryRun: result.dryRun,
9910
+ released: result.released.length,
9911
+ skipped: result.skipped.length,
9912
+ errors: result.errors.length,
9913
+ details: result
9914
+ };
9915
+ console.log(JSON.stringify(summary, null, 2));
9916
+ if (result.errors.length > 0) process.exit(1);
9917
+ }
9918
+
8233
9919
  // src/doctor/runtime-takeover.ts
8234
- import path48 from "node:path";
9920
+ import path57 from "node:path";
8235
9921
 
8236
9922
  // src/doctor/runtime-takeover.probes.ts
8237
- import { accessSync, constants, existsSync as existsSync29, readFileSync as readFileSync12 } from "node:fs";
8238
- import { homedir as homedir8 } from "node:os";
8239
- import path47 from "node:path";
8240
- import { spawnSync as spawnSync6 } from "node:child_process";
9923
+ import { accessSync, constants, existsSync as existsSync37, readFileSync as readFileSync13 } from "node:fs";
9924
+ import { homedir as homedir13 } from "node:os";
9925
+ import path56 from "node:path";
9926
+ import { spawnSync as spawnSync7 } from "node:child_process";
8241
9927
  function captureCommand(bin, args) {
8242
9928
  try {
8243
- const res = spawnSync6(bin, args, { encoding: "utf8" });
9929
+ const res = spawnSync7(bin, args, { encoding: "utf8" });
8244
9930
  const stdout = (res.stdout || "").trim();
8245
9931
  const stderr = (res.stderr || "").trim();
8246
9932
  const ok = res.status === 0;
@@ -8265,7 +9951,7 @@ function tokenPrefix(token) {
8265
9951
  return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
8266
9952
  }
8267
9953
  function isWritable(target) {
8268
- if (!existsSync29(target)) return false;
9954
+ if (!existsSync37(target)) return false;
8269
9955
  try {
8270
9956
  accessSync(target, constants.W_OK);
8271
9957
  return true;
@@ -8278,15 +9964,15 @@ var defaultRuntimeTakeoverProbes = {
8278
9964
  commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
8279
9965
  kynverVersion: (bin) => captureCommand(bin, ["--version"]),
8280
9966
  loadConfig: () => loadUserConfig(),
8281
- configFilePath: () => path47.join(homedir8(), ".kynver", "config.json"),
8282
- credentialsFilePath: () => path47.join(homedir8(), ".kynver", "credentials"),
9967
+ configFilePath: () => path56.join(homedir13(), ".kynver", "config.json"),
9968
+ credentialsFilePath: () => path56.join(homedir13(), ".kynver", "credentials"),
8283
9969
  readCredentials: () => {
8284
- const credPath = path47.join(homedir8(), ".kynver", "credentials");
8285
- if (!existsSync29(credPath)) {
9970
+ const credPath = path56.join(homedir13(), ".kynver", "credentials");
9971
+ if (!existsSync37(credPath)) {
8286
9972
  return { hasApiKey: false };
8287
9973
  }
8288
9974
  try {
8289
- const parsed = JSON.parse(readFileSync12(credPath, "utf8"));
9975
+ const parsed = JSON.parse(readFileSync13(credPath, "utf8"));
8290
9976
  return {
8291
9977
  hasApiKey: Boolean(parsed.apiKey?.trim()),
8292
9978
  runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
@@ -8313,8 +9999,8 @@ var defaultRuntimeTakeoverProbes = {
8313
9999
  })()
8314
10000
  }),
8315
10001
  harnessRoot: () => resolveHarnessRoot(),
8316
- legacyOpenclawHarnessRoot: () => path47.join(homedir8(), ".openclaw", "harness"),
8317
- pathExists: (target) => existsSync29(target),
10002
+ legacyOpenclawHarnessRoot: () => path56.join(homedir13(), ".openclaw", "harness"),
10003
+ pathExists: (target) => existsSync37(target),
8318
10004
  pathWritable: (target) => isWritable(target),
8319
10005
  vercelVersion: () => captureCommand("vercel", ["--version"]),
8320
10006
  vercelWhoami: () => captureCommand("vercel", ["whoami"])
@@ -8642,8 +10328,8 @@ function assessVercelCli(probes) {
8642
10328
  }
8643
10329
  function assessHarnessDirs(probes) {
8644
10330
  const harnessRoot = probes.harnessRoot();
8645
- const runsDir = path48.join(harnessRoot, "runs");
8646
- const worktreesDir = path48.join(harnessRoot, "worktrees");
10331
+ const runsDir = path57.join(harnessRoot, "runs");
10332
+ const worktreesDir = path57.join(harnessRoot, "worktrees");
8647
10333
  const displayHarnessRoot = redactHomePath(harnessRoot);
8648
10334
  const displayRunsDir = redactHomePath(runsDir);
8649
10335
  const displayWorktreesDir = redactHomePath(worktreesDir);
@@ -8879,9 +10565,9 @@ function applySchedulerCutoverAttestation(config) {
8879
10565
  }
8880
10566
 
8881
10567
  // src/scheduler-cutover-cli.ts
8882
- import path49 from "node:path";
8883
- import { homedir as homedir9 } from "node:os";
8884
- var CONFIG_FILE2 = path49.join(homedir9(), ".kynver", "config.json");
10568
+ import path58 from "node:path";
10569
+ import { homedir as homedir14 } from "node:os";
10570
+ var CONFIG_FILE2 = path58.join(homedir14(), ".kynver", "config.json");
8885
10571
  function runSchedulerCutoverCheckCli(json = false) {
8886
10572
  const config = loadUserConfig();
8887
10573
  const report = assessSchedulerCutover(config);
@@ -8977,7 +10663,7 @@ function usage(code = 0) {
8977
10663
  " kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
8978
10664
  " kynver run list",
8979
10665
  " kynver run status --run RUN_ID",
8980
- " kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--target-task-id TASK_ID] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-8] [--disk-path /]",
10666
+ " kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--target-task-id TASK_ID] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-8] [--disk-path /] [--reconcile-stale-blockers]",
8981
10667
  " kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
8982
10668
  ' kynver worker start --run RUN_ID --name worker --task "..." [--owned path[,path]] [--model MODEL] [--provider claude|cursor] [--agent-os-id AOS_ID] [--task-id TASK_ID] [--wait]',
8983
10669
  " kynver worker status --run RUN_ID --name worker",
@@ -8987,6 +10673,7 @@ function usage(code = 0) {
8987
10673
  " kynver worker discard-disposable --run RUN_ID --name worker --path scripts/helper.mjs[,other]",
8988
10674
  " 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]",
8989
10675
  " kynver run reconcile",
10676
+ " kynver run unblock [--agent-os-id AOS_ID] [--base-url URL] [--secret SECRET] [--dry-run]",
8990
10677
  " 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]",
8991
10678
  " kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override] [--local]",
8992
10679
  " kynver harness verify --worktree PATH [--command CMD] [--json] [--wait-for-admission-ms MS] [--timeout-ms MS]",
@@ -9058,6 +10745,7 @@ async function main(argv = process.argv.slice(2)) {
9058
10745
  if (scope === "run" && action === "dispatch") return void await dispatchRun(args);
9059
10746
  if (scope === "run" && action === "sweep") return void await sweepRun(args);
9060
10747
  if (scope === "run" && action === "reconcile") return reconcileRunsCli();
10748
+ if (scope === "run" && action === "unblock") return void await postRestartUnblockCli(args);
9061
10749
  if (scope === "worker" && action === "start") return void await startWorker(args);
9062
10750
  if (scope === "worker" && action === "status") return workerStatus(args);
9063
10751
  if (scope === "worker" && action === "tail") return tailWorker(args);