@kynver-app/runtime 0.1.76 → 0.1.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -793,23 +793,23 @@ function isWslHost() {
793
793
  function observeWslHostDisk(options = {}) {
794
794
  const wsl = options.forceWsl === void 0 ? isWslHost() : options.forceWsl;
795
795
  if (!wsl) return null;
796
- const path60 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
796
+ const path63 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
797
797
  const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;
798
798
  const criticalBelowBytes = options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;
799
799
  const statfs = options.statfs ?? statfsSync;
800
800
  let stats;
801
801
  try {
802
- stats = statfs(path60);
802
+ stats = statfs(path63);
803
803
  } catch (error) {
804
804
  return {
805
805
  ok: false,
806
- path: path60,
806
+ path: path63,
807
807
  freeBytes: 0,
808
808
  totalBytes: 0,
809
809
  usedPercent: 100,
810
810
  warnBelowBytes,
811
811
  criticalBelowBytes,
812
- reason: `Windows host disk probe failed at ${path60}: ${error.message}`,
812
+ reason: `Windows host disk probe failed at ${path63}: ${error.message}`,
813
813
  probeError: error.message
814
814
  };
815
815
  }
@@ -823,11 +823,11 @@ function observeWslHostDisk(options = {}) {
823
823
  let reason = null;
824
824
  if (!ok) {
825
825
  const tag = criticalFree ? "critical" : "warning";
826
- reason = `Windows host disk ${path60} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
826
+ reason = `Windows host disk ${path63} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
827
827
  }
828
828
  return {
829
829
  ok,
830
- path: path60,
830
+ path: path63,
831
831
  freeBytes,
832
832
  totalBytes,
833
833
  usedPercent,
@@ -847,12 +847,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
847
847
  var DEFAULT_MAX_USED_PERCENT = 80;
848
848
  var DEFAULT_HARD_MAX_USED_PERCENT = 90;
849
849
  function observeRunnerDiskGate(input = {}) {
850
- const path60 = input.diskPath?.trim() || "/";
850
+ const path63 = input.diskPath?.trim() || "/";
851
851
  const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
852
852
  const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
853
853
  const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
854
854
  const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
855
- const stats = statfsSync2(path60);
855
+ const stats = statfsSync2(path63);
856
856
  const freeBytes = Number(stats.bavail) * Number(stats.bsize);
857
857
  const totalBytes = Number(stats.blocks) * Number(stats.bsize);
858
858
  const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
@@ -875,7 +875,7 @@ function observeRunnerDiskGate(input = {}) {
875
875
  }
876
876
  return {
877
877
  ok,
878
- path: path60,
878
+ path: path63,
879
879
  freeBytes,
880
880
  totalBytes,
881
881
  usedPercent,
@@ -1863,7 +1863,9 @@ function computeWorkerStatus(worker, options = {}) {
1863
1863
  finalResult,
1864
1864
  error,
1865
1865
  changedFiles,
1866
- gitAncestry
1866
+ gitAncestry,
1867
+ instructionPolicyFingerprint: worker.instructionPolicyFingerprint ?? null,
1868
+ instructionPolicyEvidence: worker.instructionPolicyEvidence ?? null
1867
1869
  };
1868
1870
  }
1869
1871
  function isFinishedWorkerStatus(status) {
@@ -2648,7 +2650,9 @@ function resolveOrchestrationRouting(input) {
2648
2650
  const risk = classifyOrchestrationRisk(task);
2649
2651
  const inventory = resolveInventory({
2650
2652
  inventory: input.inventory,
2651
- codexBinding: input.codexBinding ?? resolveCodexOrchestrationAdapter()
2653
+ // When callers pass a pre-built inventory (tests, CC snapshots), do not probe live
2654
+ // Hermes/Codex bindings — that would overwrite mocked oauth_local with subscription_hermes.
2655
+ codexBinding: input.codexBinding ?? (input.inventory ? null : resolveCodexOrchestrationAdapter())
2652
2656
  });
2653
2657
  const explicit = input.preferLowCost === false ? null : input.explicitProvider?.trim().toLowerCase();
2654
2658
  const explicitModel = input.explicitModel?.trim() || void 0;
@@ -3462,6 +3466,7 @@ function buildPrompt(input) {
3462
3466
  "",
3463
3467
  ...input.personaMarkdown?.trim() ? [input.personaMarkdown.trim(), ""] : [],
3464
3468
  ...input.instructionPolicyMarkdown?.trim() ? ["Operating rules (Lane A \u2014 from AgentOS memory policy):", input.instructionPolicyMarkdown.trim(), ""] : [],
3469
+ ...input.memoryQualityMarkdown?.trim() ? [input.memoryQualityMarkdown.trim(), ""] : [],
3465
3470
  ...input.repairTargetPrUrl ? [
3466
3471
  ...repairTargetPromptLines({
3467
3472
  targetPrUrl: input.repairTargetPrUrl,
@@ -3670,6 +3675,21 @@ function persistCompletionAck(worker, runId, fields) {
3670
3675
  saveWorker(runId, worker);
3671
3676
  }
3672
3677
 
3678
+ // src/completion-replay.ts
3679
+ function trimBlocker(value) {
3680
+ if (typeof value !== "string") return null;
3681
+ const trimmed = value.trim();
3682
+ return trimmed.length ? trimmed : null;
3683
+ }
3684
+ function shouldReplayHarnessCompletion(worker) {
3685
+ if (trimBlocker(worker.completionBlocker)) return true;
3686
+ if (worker.completionOutcome === "rejected") return true;
3687
+ return false;
3688
+ }
3689
+ function hasTerminalCompletionAck(worker) {
3690
+ return hasCompletionAck(worker) && !shouldReplayHarnessCompletion(worker);
3691
+ }
3692
+
3673
3693
  // src/worker-ops.ts
3674
3694
  import path17 from "node:path";
3675
3695
 
@@ -4241,8 +4261,8 @@ function dirtyPathsCoveredByDisposableRemoval(changedFiles, removed) {
4241
4261
  if (removed.length === 0) return false;
4242
4262
  const removedSet = new Set(removed.map((p) => normalizeRelativePath(p)));
4243
4263
  return material.every((line) => {
4244
- const path60 = normalizeRelativePath(pathFromGitStatusLine(line));
4245
- return removedSet.has(path60);
4264
+ const path63 = normalizeRelativePath(pathFromGitStatusLine(line));
4265
+ return removedSet.has(path63);
4246
4266
  });
4247
4267
  }
4248
4268
 
@@ -4488,7 +4508,7 @@ async function tryCompleteWorker(args) {
4488
4508
  return { ok: true, skipped: true, reason: "worker-not-finished" };
4489
4509
  }
4490
4510
  const forceReplay = args.force === true || args.force === "true";
4491
- if (!forceReplay && hasCompletionAck(worker)) {
4511
+ if (!forceReplay && hasTerminalCompletionAck(worker)) {
4492
4512
  return {
4493
4513
  ok: true,
4494
4514
  skipped: true,
@@ -4664,7 +4684,9 @@ async function completeWorker(args) {
4664
4684
  }
4665
4685
  }
4666
4686
  function workerStatus(args) {
4667
- const worker = loadWorker(String(args.run), String(args.name));
4687
+ const runId = required(typeof args.run === "string" ? args.run : "", "--run");
4688
+ const name = required(typeof args.name === "string" ? args.name : "", "--name");
4689
+ const worker = loadWorker(runId, name);
4668
4690
  const run = loadRun(worker.runId);
4669
4691
  const status = computeWorkerStatus(worker, workerStatusOptions(run));
4670
4692
  writeJson(path17.join(worker.workerDir, "last-status.json"), status);
@@ -4880,7 +4902,7 @@ async function autoCompleteWorker(raw) {
4880
4902
  reason: "worker has no agentOsId/taskId \u2014 nothing to attribute completion to"
4881
4903
  };
4882
4904
  }
4883
- if (hasCompletionAck(worker)) {
4905
+ if (hasTerminalCompletionAck(worker)) {
4884
4906
  return {
4885
4907
  worker: worker.name,
4886
4908
  runId: worker.runId,
@@ -4893,7 +4915,7 @@ async function autoCompleteWorker(raw) {
4893
4915
  const startMs = Date.now();
4894
4916
  while (true) {
4895
4917
  worker = loadWorker(args.run, args.name);
4896
- if (hasCompletionAck(worker)) {
4918
+ if (hasTerminalCompletionAck(worker)) {
4897
4919
  return {
4898
4920
  worker: worker.name,
4899
4921
  runId: worker.runId,
@@ -5099,6 +5121,7 @@ function spawnWorkerProcess(run, opts) {
5099
5121
  planId: opts.planId,
5100
5122
  taskId: opts.taskId,
5101
5123
  instructionPolicyMarkdown: opts.instructionPolicyMarkdown,
5124
+ memoryQualityMarkdown: opts.memoryQualityPromptMarkdown ?? opts.memoryQualityCapture?.promptMarkdown ?? null,
5102
5125
  personaMarkdown: opts.personaMarkdown,
5103
5126
  model: launchModel,
5104
5127
  repairTargetPrUrl: opts.repairTargetPrUrl,
@@ -5877,11 +5900,13 @@ function readHarnessWorkerContext(decision) {
5877
5900
  const personaEvidence = ctx.personaEvidence && typeof ctx.personaEvidence === "object" ? ctx.personaEvidence : null;
5878
5901
  const personaInjectionReady = ctx.personaInjectionReady === true;
5879
5902
  const memoryQualityCapture = ctx.memoryQualityCapture && typeof ctx.memoryQualityCapture === "object" ? ctx.memoryQualityCapture : null;
5903
+ const memoryQualityPromptMarkdown = typeof ctx.memoryQualityPromptMarkdown === "string" ? ctx.memoryQualityPromptMarkdown : typeof memoryQualityCapture?.promptMarkdown === "string" ? memoryQualityCapture.promptMarkdown : null;
5880
5904
  return {
5881
5905
  instructionPolicyMarkdown: markdown,
5882
5906
  instructionPolicyFingerprint: fingerprint,
5883
5907
  instructionPolicyEvidence: evidence,
5884
5908
  memoryQualityCapture,
5909
+ memoryQualityPromptMarkdown,
5885
5910
  personaMarkdown,
5886
5911
  personaSlug,
5887
5912
  personaEvidence,
@@ -6138,6 +6163,7 @@ async function dispatchRun(args) {
6138
6163
  instructionPolicyFingerprint: harnessContext?.instructionPolicyFingerprint ?? null,
6139
6164
  instructionPolicyEvidence: harnessContext?.instructionPolicyEvidence ?? null,
6140
6165
  memoryQualityCapture: harnessContext?.memoryQualityCapture ?? null,
6166
+ memoryQualityPromptMarkdown: harnessContext?.memoryQualityPromptMarkdown ?? null,
6141
6167
  personaMarkdown: harnessContext?.personaMarkdown ?? null,
6142
6168
  personaSlug: harnessContext?.personaSlug ?? expectedPersona,
6143
6169
  personaEvidence: harnessContext?.personaEvidence ?? null,
@@ -6192,7 +6218,23 @@ async function dispatchRun(args) {
6192
6218
  for (const decision of result.started) {
6193
6219
  shouldContinueDispatch = await spawnClaimed(decision) && shouldContinueDispatch;
6194
6220
  }
6195
- skipped.push(...result.skipped ?? []);
6221
+ skipped.push(
6222
+ ...result.skipped ?? []
6223
+ );
6224
+ if (exactTargetMode) {
6225
+ for (const skipDecision of skipped) {
6226
+ const taskId = String(skipDecision.task.id);
6227
+ if (!exactTargetIds.has(taskId)) continue;
6228
+ outcomes.push({
6229
+ taskId,
6230
+ started: false,
6231
+ error: `exact_target_not_started:${skipDecision.skipReason}`,
6232
+ skipReason: skipDecision.skipReason,
6233
+ detail: skipDecision.reason ?? null,
6234
+ requestedTargetTaskIds: [...exactTargetIds]
6235
+ });
6236
+ }
6237
+ }
6196
6238
  if (exactTargetMode) shouldContinueDispatch = false;
6197
6239
  while (shouldContinueDispatch && outcomes.length < cappedStarts) {
6198
6240
  if (exactTargetMode) break;
@@ -6708,15 +6750,15 @@ function validateTailLines(lines) {
6708
6750
  }
6709
6751
 
6710
6752
  // src/worktree.ts
6711
- import { existsSync as existsSync25, mkdirSync as mkdirSync5 } from "node:fs";
6712
- import path32 from "node:path";
6753
+ import { existsSync as existsSync26, mkdirSync as mkdirSync5 } from "node:fs";
6754
+ import path33 from "node:path";
6713
6755
 
6714
6756
  // src/run-list.ts
6715
- import { existsSync as existsSync24, readFileSync as readFileSync12 } from "node:fs";
6716
- import path30 from "node:path";
6757
+ import { existsSync as existsSync25, readFileSync as readFileSync12 } from "node:fs";
6758
+ import path32 from "node:path";
6717
6759
 
6718
6760
  // src/stale-reconcile.ts
6719
- import path29 from "node:path";
6761
+ import path31 from "node:path";
6720
6762
 
6721
6763
  // src/finalize.ts
6722
6764
  import path26 from "node:path";
@@ -6777,8 +6819,8 @@ function finalizeStaleRuns() {
6777
6819
  }
6778
6820
 
6779
6821
  // src/worker-metadata-reconcile.ts
6780
- import { existsSync as existsSync23, lstatSync, readdirSync as readdirSync5, readlinkSync, renameSync as renameSync2, rmSync } from "node:fs";
6781
- import path28 from "node:path";
6822
+ import { existsSync as existsSync24, lstatSync, readdirSync as readdirSync6, readlinkSync, renameSync as renameSync2, rmSync } from "node:fs";
6823
+ import path30 from "node:path";
6782
6824
 
6783
6825
  // src/worker-metadata-paths.ts
6784
6826
  import path27 from "node:path";
@@ -6833,6 +6875,219 @@ function resolveWorkerJsonPath(input) {
6833
6875
  return canonical;
6834
6876
  }
6835
6877
 
6878
+ // src/run-metadata-retention.ts
6879
+ import { existsSync as existsSync23, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
6880
+ import path29 from "node:path";
6881
+
6882
+ // src/default-repo.ts
6883
+ import path28 from "node:path";
6884
+ function expandConfiguredRepo(value) {
6885
+ return path28.resolve(resolveUserPath(value.trim()));
6886
+ }
6887
+ function fromConfigured(value, source, persistedInConfig) {
6888
+ const trimmed = value?.trim();
6889
+ if (!trimmed) return null;
6890
+ return {
6891
+ repo: expandConfiguredRepo(trimmed),
6892
+ source,
6893
+ persistedInConfig
6894
+ };
6895
+ }
6896
+ function resolveDefaultRepo(opts = {}) {
6897
+ const env = opts.env ?? process.env;
6898
+ const config = opts.config ?? loadUserConfig();
6899
+ const fromConfig = fromConfigured(config.defaultRepo, "config", true);
6900
+ if (fromConfig) return fromConfig;
6901
+ const fromDefaultEnv = fromConfigured(env.KYNVER_DEFAULT_REPO, "env_default_repo", false);
6902
+ if (fromDefaultEnv) return fromDefaultEnv;
6903
+ const fromHarnessEnv = fromConfigured(env.KYNVER_HARNESS_REPO, "env_harness_repo", false);
6904
+ if (fromHarnessEnv) return fromHarnessEnv;
6905
+ const discovered = discoverDefaultRepo({
6906
+ cwd: opts.cwd,
6907
+ runtimeModuleUrl: opts.runtimeModuleUrl
6908
+ });
6909
+ if (!discovered) return null;
6910
+ return {
6911
+ repo: discovered.repo,
6912
+ source: discovered.source,
6913
+ persistedInConfig: false
6914
+ };
6915
+ }
6916
+ function persistDefaultRepo(repo, existing) {
6917
+ const config = {
6918
+ ...existing ?? loadUserConfig(),
6919
+ defaultRepo: redactHomePath(path28.resolve(repo))
6920
+ };
6921
+ saveUserConfig(config);
6922
+ return config;
6923
+ }
6924
+ function remediateDefaultRepo(opts) {
6925
+ const existing = opts?.config ?? loadUserConfig();
6926
+ const resolved = resolveDefaultRepo({ ...opts, config: existing });
6927
+ if (!resolved) {
6928
+ return {
6929
+ ok: false,
6930
+ reason: "No Kynver git checkout found. Clone the repo, cd into it, then run `kynver setup --repo /path/to/Kynver` (or export KYNVER_DEFAULT_REPO)."
6931
+ };
6932
+ }
6933
+ if (resolved.persistedInConfig) {
6934
+ return { ok: true, resolved, config: existing };
6935
+ }
6936
+ const config = persistDefaultRepo(resolved.repo, existing);
6937
+ return {
6938
+ ok: true,
6939
+ resolved: { ...resolved, persistedInConfig: true, source: "config" },
6940
+ config
6941
+ };
6942
+ }
6943
+ function formatResolvedDefaultRepo(resolved) {
6944
+ return {
6945
+ defaultRepo: displayUserPath(resolved.repo),
6946
+ source: resolved.source,
6947
+ persistedInConfig: resolved.persistedInConfig
6948
+ };
6949
+ }
6950
+
6951
+ // src/run-metadata-retention.ts
6952
+ var RUN_METADATA_ACTIVE_SIGNAL_MS = 15 * 60 * 1e3;
6953
+ function isHarnessRunMetadataPath(targetPath, harnessRoot) {
6954
+ const resolved = path29.resolve(targetPath);
6955
+ const runsDir = path29.resolve(harnessRunsDir(harnessRoot));
6956
+ const rel = path29.relative(runsDir, resolved);
6957
+ return rel !== ".." && !rel.startsWith("..") && !path29.isAbsolute(rel);
6958
+ }
6959
+ function listRunDirIds(runsDir) {
6960
+ if (!existsSync23(runsDir)) return [];
6961
+ try {
6962
+ return readdirSync5(runsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name !== "runs").map((entry) => entry.name);
6963
+ } catch {
6964
+ return [];
6965
+ }
6966
+ }
6967
+ function listWorkerNamesOnDisk(runDir2) {
6968
+ const workersDir = path29.join(runDir2, "workers");
6969
+ if (!existsSync23(workersDir)) return [];
6970
+ try {
6971
+ return readdirSync5(workersDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
6972
+ } catch {
6973
+ return [];
6974
+ }
6975
+ }
6976
+ function pathRecentlyTouched(target, now, windowMs) {
6977
+ if (!existsSync23(target)) return false;
6978
+ try {
6979
+ const age = now - statSync4(target).mtimeMs;
6980
+ return Number.isFinite(age) && age >= 0 && age < windowMs;
6981
+ } catch {
6982
+ return false;
6983
+ }
6984
+ }
6985
+ function workerDirHasActiveRetentionSignals(workerDir, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
6986
+ if (!existsSync23(workerDir)) return false;
6987
+ const artifacts = workerArtifactPaths(workerDir);
6988
+ const worker = readJson(
6989
+ artifacts.workerJsonPath,
6990
+ void 0
6991
+ );
6992
+ if (worker?.status === "running" && isPidAlive(worker.pid)) return true;
6993
+ const heartbeat = parseHeartbeat(artifacts.heartbeatPath);
6994
+ if (heartbeat.lastHeartbeatAt) {
6995
+ const age = now - Date.parse(heartbeat.lastHeartbeatAt);
6996
+ if (Number.isFinite(age) && age >= 0 && age < windowMs) return true;
6997
+ }
6998
+ if (pathRecentlyTouched(artifacts.stdoutPath, now, windowMs)) return true;
6999
+ if (pathRecentlyTouched(artifacts.heartbeatPath, now, windowMs)) return true;
7000
+ return false;
7001
+ }
7002
+ function runDirHasActiveRetentionSignals(runDir2, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
7003
+ for (const name of listWorkerNamesOnDisk(runDir2)) {
7004
+ if (workerDirHasActiveRetentionSignals(path29.join(runDir2, "workers", name), now, windowMs)) {
7005
+ return true;
7006
+ }
7007
+ }
7008
+ return false;
7009
+ }
7010
+ function inferRepoFields() {
7011
+ const resolved = resolveDefaultRepo();
7012
+ return {
7013
+ repo: resolved?.repo ?? "",
7014
+ base: "origin/main",
7015
+ baseCommit: "unknown"
7016
+ };
7017
+ }
7018
+ function synthesizeRunFromDisk(harnessRoot, runId) {
7019
+ const runDir2 = path29.join(harnessRunsDir(harnessRoot), safeSlug(runId));
7020
+ if (!existsSync23(runDir2)) return null;
7021
+ const workerNames = listWorkerNamesOnDisk(runDir2);
7022
+ if (workerNames.length === 0) return null;
7023
+ let createdAt;
7024
+ try {
7025
+ createdAt = statSync4(runDir2).birthtime.toISOString();
7026
+ } catch {
7027
+ createdAt = (/* @__PURE__ */ new Date()).toISOString();
7028
+ }
7029
+ const workers = {};
7030
+ for (const name of workerNames) {
7031
+ const canonicalDir = canonicalWorkerDir(harnessRoot, runId, name);
7032
+ workers[name] = {
7033
+ workerDir: canonicalDir,
7034
+ statusPath: path29.join(canonicalDir, "worker.json")
7035
+ };
7036
+ }
7037
+ const hasActive = runDirHasActiveRetentionSignals(runDir2);
7038
+ return {
7039
+ id: runId,
7040
+ name: runId,
7041
+ ...inferRepoFields(),
7042
+ status: hasActive ? "running" : "needs_attention",
7043
+ createdAt,
7044
+ workers
7045
+ };
7046
+ }
7047
+ function repairMissingRunMetadata(harnessRootInput) {
7048
+ const harnessRoot = normalizeHarnessRoot(harnessRootInput ?? resolveHarnessRoot());
7049
+ const runsDir = harnessRunsDir(harnessRoot);
7050
+ const outcomes = [];
7051
+ for (const runId of listRunDirIds(runsDir)) {
7052
+ const runJsonPath = path29.join(runsDir, runId, "run.json");
7053
+ if (existsSync23(runJsonPath)) {
7054
+ outcomes.push({
7055
+ runId,
7056
+ action: "skipped",
7057
+ reason: "run.json present"
7058
+ });
7059
+ continue;
7060
+ }
7061
+ const synthesized = synthesizeRunFromDisk(harnessRoot, runId);
7062
+ if (!synthesized) {
7063
+ outcomes.push({
7064
+ runId,
7065
+ action: "skipped",
7066
+ reason: "no worker dirs on disk"
7067
+ });
7068
+ continue;
7069
+ }
7070
+ saveRun(synthesized);
7071
+ outcomes.push({
7072
+ runId,
7073
+ action: "repaired_run_json",
7074
+ reason: runDirHasActiveRetentionSignals(path29.join(runsDir, runId)) ? "synthesized run.json while worker artifacts still active" : "synthesized run.json from orphan worker metadata dirs"
7075
+ });
7076
+ }
7077
+ return { runs: outcomes };
7078
+ }
7079
+ function collectFilesystemLiveRunKeys(harnessRoot, now = Date.now()) {
7080
+ const keys = /* @__PURE__ */ new Set();
7081
+ const runsDir = harnessRunsDir(harnessRoot);
7082
+ for (const runId of listRunDirIds(runsDir)) {
7083
+ const runDir2 = path29.join(runsDir, runId);
7084
+ if (runDirHasActiveRetentionSignals(runDir2, now)) {
7085
+ keys.add(`${harnessRoot}\0${runId}`);
7086
+ }
7087
+ }
7088
+ return keys;
7089
+ }
7090
+
6836
7091
  // src/worker-metadata-reconcile.ts
6837
7092
  function materializeSymlinkedRunDir(harnessRoot, runId) {
6838
7093
  const canonical = canonicalRunDir(harnessRoot, runId);
@@ -6843,7 +7098,10 @@ function materializeSymlinkedRunDir(harnessRoot, runId) {
6843
7098
  return null;
6844
7099
  }
6845
7100
  if (!stat.isSymbolicLink()) return null;
6846
- const linkedTarget = path28.resolve(path28.dirname(canonical), readlinkSync(canonical));
7101
+ const linkedTarget = path30.resolve(path30.dirname(canonical), readlinkSync(canonical));
7102
+ if (runDirHasActiveRetentionSignals(linkedTarget) || runDirHasActiveRetentionSignals(canonical)) {
7103
+ return null;
7104
+ }
6847
7105
  const staging = `${canonical}.materialize-${Date.now()}`;
6848
7106
  renameSync2(linkedTarget, staging);
6849
7107
  rmSync(canonical);
@@ -6853,9 +7111,9 @@ function materializeSymlinkedRunDir(harnessRoot, runId) {
6853
7111
  function relocateArtifacts(input) {
6854
7112
  const moved = [];
6855
7113
  for (const fileName of workerArtifactFileNames()) {
6856
- const from = path28.join(input.fromDir, fileName);
6857
- const to = path28.join(input.toDir, fileName);
6858
- if (!existsSync23(from) || existsSync23(to)) continue;
7114
+ const from = path30.join(input.fromDir, fileName);
7115
+ const to = path30.join(input.toDir, fileName);
7116
+ if (!existsSync24(from) || existsSync24(to)) continue;
6859
7117
  renameSync2(from, to);
6860
7118
  moved.push(fileName);
6861
7119
  }
@@ -6894,8 +7152,8 @@ function deriveSynthesizedStatus(input) {
6894
7152
  function synthesizeWorkerFromArtifacts(input) {
6895
7153
  const artifacts = workerArtifactPaths(input.canonicalDir);
6896
7154
  const lastStatus = readJson(artifacts.lastStatusPath, void 0);
6897
- const stdoutExists = existsSync23(artifacts.stdoutPath);
6898
- const heartbeatExists = existsSync23(artifacts.heartbeatPath);
7155
+ const stdoutExists = existsSync24(artifacts.stdoutPath);
7156
+ const heartbeatExists = existsSync24(artifacts.heartbeatPath);
6899
7157
  const hasArtifacts = stdoutExists || heartbeatExists || lastStatus != null;
6900
7158
  if (!hasArtifacts) return null;
6901
7159
  const parsedStdout = stdoutExists ? parseHarnessStream(artifacts.stdoutPath) : { finalResult: null };
@@ -6911,7 +7169,7 @@ function synthesizeWorkerFromArtifacts(input) {
6911
7169
  runId: input.run.id,
6912
7170
  status,
6913
7171
  branch: typeof lastStatus?.branch === "string" ? lastStatus.branch : `agent/${input.run.id}/${input.workerName}`,
6914
- worktreePath: typeof lastStatus?.worktreePath === "string" ? lastStatus.worktreePath : path28.join(getPaths().worktreesDir, input.run.id, input.workerName),
7172
+ worktreePath: typeof lastStatus?.worktreePath === "string" ? lastStatus.worktreePath : path30.join(getPaths().worktreesDir, input.run.id, input.workerName),
6915
7173
  workerDir: input.canonicalDir,
6916
7174
  stdoutPath: artifacts.stdoutPath,
6917
7175
  stderrPath: artifacts.stderrPath,
@@ -6938,7 +7196,7 @@ function repairRunWorkerIndex(run, harnessRoot) {
6938
7196
  const nextWorkers = { ...run.workers || {} };
6939
7197
  for (const [name, ref] of Object.entries(nextWorkers)) {
6940
7198
  const canonicalDir = canonicalWorkerDir(harnessRoot, run.id, name);
6941
- const canonicalStatus = path28.join(canonicalDir, "worker.json");
7199
+ const canonicalStatus = path30.join(canonicalDir, "worker.json");
6942
7200
  const nested = hasNestedRunsSegment(ref.workerDir) || hasNestedRunsSegment(ref.statusPath) || ref.workerDir !== canonicalDir || ref.statusPath !== canonicalStatus;
6943
7201
  if (!nested) continue;
6944
7202
  nextWorkers[name] = { workerDir: canonicalDir, statusPath: canonicalStatus };
@@ -6971,7 +7229,7 @@ function reconcileOneWorker(input) {
6971
7229
  statusPath: input.statusPath
6972
7230
  });
6973
7231
  let worker = readJson(workerJsonPath, void 0);
6974
- if (!worker && existsSync23(canonicalArtifacts.workerJsonPath)) {
7232
+ if (!worker && existsSync24(canonicalArtifacts.workerJsonPath)) {
6975
7233
  worker = readJson(canonicalArtifacts.workerJsonPath, void 0);
6976
7234
  }
6977
7235
  if (!worker) {
@@ -6991,15 +7249,15 @@ function reconcileOneWorker(input) {
6991
7249
  });
6992
7250
  return outcomes;
6993
7251
  }
6994
- const dirExists = existsSync23(canonicalDir);
6995
- const hasAnyArtifact = workerArtifactFileNames().some((f) => existsSync23(path28.join(canonicalDir, f)));
7252
+ const dirExists = existsSync24(canonicalDir);
7253
+ const hasAnyArtifact = workerArtifactFileNames().some((f) => existsSync24(path30.join(canonicalDir, f)));
6996
7254
  if (dirExists && !hasAnyArtifact) {
6997
7255
  const abandoned = {
6998
7256
  name: input.workerName,
6999
7257
  runId: input.run.id,
7000
7258
  status: "exited",
7001
7259
  branch: `agent/${input.run.id}/${input.workerName}`,
7002
- worktreePath: path28.join(getPaths().worktreesDir, input.run.id, input.workerName),
7260
+ worktreePath: path30.join(getPaths().worktreesDir, input.run.id, input.workerName),
7003
7261
  workerDir: canonicalDir,
7004
7262
  stdoutPath: canonicalArtifacts.stdoutPath,
7005
7263
  stderrPath: canonicalArtifacts.stderrPath,
@@ -7046,11 +7304,11 @@ function reconcileOneWorker(input) {
7046
7304
  return outcomes;
7047
7305
  }
7048
7306
  function listOrphanWorkerNames(run, harnessRoot) {
7049
- const workersDir = path28.join(runDirectory(run.id), "workers");
7050
- if (!existsSync23(workersDir)) return [];
7307
+ const workersDir = path30.join(runDirectory(run.id), "workers");
7308
+ if (!existsSync24(workersDir)) return [];
7051
7309
  const indexed = new Set(Object.keys(run.workers || {}));
7052
7310
  const orphans = [];
7053
- for (const entry of readdirSync5(workersDir, { withFileTypes: true })) {
7311
+ for (const entry of readdirSync6(workersDir, { withFileTypes: true })) {
7054
7312
  if (!entry.isDirectory()) continue;
7055
7313
  if (indexed.has(entry.name)) continue;
7056
7314
  orphans.push(entry.name);
@@ -7059,6 +7317,7 @@ function listOrphanWorkerNames(run, harnessRoot) {
7059
7317
  }
7060
7318
  function reconcileWorkerMetadata() {
7061
7319
  const { harnessRoot } = getPaths();
7320
+ const runMetadataRetention = repairMissingRunMetadata(harnessRoot);
7062
7321
  const outcomes = [];
7063
7322
  for (const run of listRunRecords()) {
7064
7323
  const materializedFrom = materializeSymlinkedRunDir(harnessRoot, run.id);
@@ -7085,7 +7344,7 @@ function reconcileWorkerMetadata() {
7085
7344
  ...run.workers || {},
7086
7345
  [orphan]: {
7087
7346
  workerDir: canonicalWorkerDir(harnessRoot, run.id, orphan),
7088
- statusPath: path28.join(canonicalWorkerDir(harnessRoot, run.id, orphan), "worker.json")
7347
+ statusPath: path30.join(canonicalWorkerDir(harnessRoot, run.id, orphan), "worker.json")
7089
7348
  }
7090
7349
  };
7091
7350
  saveRun(run);
@@ -7108,7 +7367,7 @@ function reconcileWorkerMetadata() {
7108
7367
  );
7109
7368
  }
7110
7369
  }
7111
- return { workers: outcomes };
7370
+ return { workers: outcomes, runMetadataRetention };
7112
7371
  }
7113
7372
  function reconcileWorkerMetadataCli() {
7114
7373
  const result = reconcileWorkerMetadata();
@@ -7116,7 +7375,22 @@ function reconcileWorkerMetadataCli() {
7116
7375
  acc[row.action] = (acc[row.action] ?? 0) + 1;
7117
7376
  return acc;
7118
7377
  }, {});
7119
- console.log(JSON.stringify({ ok: true, totals: byAction, details: result.workers }, null, 2));
7378
+ const runRetentionTotals = result.runMetadataRetention.runs.reduce((acc, row) => {
7379
+ acc[row.action] = (acc[row.action] ?? 0) + 1;
7380
+ return acc;
7381
+ }, {});
7382
+ console.log(
7383
+ JSON.stringify(
7384
+ {
7385
+ ok: true,
7386
+ totals: byAction,
7387
+ runMetadataRetention: { totals: runRetentionTotals, details: result.runMetadataRetention.runs },
7388
+ details: result.workers
7389
+ },
7390
+ null,
7391
+ 2
7392
+ )
7393
+ );
7120
7394
  }
7121
7395
 
7122
7396
  // src/stale-reconcile.ts
@@ -7133,7 +7407,7 @@ function reconcileStaleWorkers() {
7133
7407
  const now = Date.now();
7134
7408
  for (const run of listRunRecords()) {
7135
7409
  for (const name of Object.keys(run.workers || {})) {
7136
- const workerPath = path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
7410
+ const workerPath = path31.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
7137
7411
  const worker = readJson(workerPath, void 0);
7138
7412
  if (!worker || worker.status !== "running") {
7139
7413
  outcomes.push({
@@ -7216,16 +7490,28 @@ function reconcileRunsCli() {
7216
7490
  acc[row.action] = (acc[row.action] ?? 0) + 1;
7217
7491
  return acc;
7218
7492
  }, {});
7493
+ const runRetentionTotals = result.metadataReconcile.runMetadataRetention.runs.reduce((acc, row) => {
7494
+ acc[row.action] = (acc[row.action] ?? 0) + 1;
7495
+ return acc;
7496
+ }, {});
7219
7497
  console.log(
7220
7498
  JSON.stringify(
7221
7499
  {
7222
7500
  ok: true,
7223
7501
  workers: { markedExited, killedStale, skipped, total: result.workers.length },
7224
- metadataReconcile: { totals: metadataTotals, total: result.metadataReconcile.workers.length },
7502
+ metadataReconcile: {
7503
+ totals: metadataTotals,
7504
+ total: result.metadataReconcile.workers.length,
7505
+ runMetadataRetention: {
7506
+ totals: runRetentionTotals,
7507
+ total: result.metadataReconcile.runMetadataRetention.runs.length
7508
+ }
7509
+ },
7225
7510
  finalizedRuns: result.finalizedRuns.length,
7226
7511
  details: {
7227
7512
  workers: result.workers,
7228
7513
  metadataReconcile: result.metadataReconcile.workers,
7514
+ runMetadataRetention: result.metadataReconcile.runMetadataRetention.runs,
7229
7515
  finalizedRuns: result.finalizedRuns
7230
7516
  }
7231
7517
  },
@@ -7237,7 +7523,7 @@ function reconcileRunsCli() {
7237
7523
 
7238
7524
  // src/run-list.ts
7239
7525
  function heartbeatByteLength(heartbeatPath) {
7240
- if (!heartbeatPath || !existsSync24(heartbeatPath)) return 0;
7526
+ if (!heartbeatPath || !existsSync25(heartbeatPath)) return 0;
7241
7527
  try {
7242
7528
  return readFileSync12(heartbeatPath, "utf8").trim().length;
7243
7529
  } catch {
@@ -7245,7 +7531,7 @@ function heartbeatByteLength(heartbeatPath) {
7245
7531
  }
7246
7532
  }
7247
7533
  function workerEvidence(run, workerName) {
7248
- const workerPath = path30.join(runDirectory(run.id), "workers", safeSlug(workerName), "worker.json");
7534
+ const workerPath = path32.join(runDirectory(run.id), "workers", safeSlug(workerName), "worker.json");
7249
7535
  const worker = readJson(workerPath, void 0);
7250
7536
  if (!worker) {
7251
7537
  return {
@@ -7302,7 +7588,7 @@ function aggregateRunAttention(workers) {
7302
7588
  function countOpenWorkers(run) {
7303
7589
  let open = 0;
7304
7590
  for (const name of Object.keys(run.workers || {})) {
7305
- const workerPath = path30.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
7591
+ const workerPath = path32.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
7306
7592
  const worker = readJson(workerPath, void 0);
7307
7593
  if (!worker) continue;
7308
7594
  const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
@@ -7350,75 +7636,6 @@ function listRunsCli() {
7350
7636
  console.log(JSON.stringify(buildRunListRows(), null, 2));
7351
7637
  }
7352
7638
 
7353
- // src/default-repo.ts
7354
- import path31 from "node:path";
7355
- function expandConfiguredRepo(value) {
7356
- return path31.resolve(resolveUserPath(value.trim()));
7357
- }
7358
- function fromConfigured(value, source, persistedInConfig) {
7359
- const trimmed = value?.trim();
7360
- if (!trimmed) return null;
7361
- return {
7362
- repo: expandConfiguredRepo(trimmed),
7363
- source,
7364
- persistedInConfig
7365
- };
7366
- }
7367
- function resolveDefaultRepo(opts = {}) {
7368
- const env = opts.env ?? process.env;
7369
- const config = opts.config ?? loadUserConfig();
7370
- const fromConfig = fromConfigured(config.defaultRepo, "config", true);
7371
- if (fromConfig) return fromConfig;
7372
- const fromDefaultEnv = fromConfigured(env.KYNVER_DEFAULT_REPO, "env_default_repo", false);
7373
- if (fromDefaultEnv) return fromDefaultEnv;
7374
- const fromHarnessEnv = fromConfigured(env.KYNVER_HARNESS_REPO, "env_harness_repo", false);
7375
- if (fromHarnessEnv) return fromHarnessEnv;
7376
- const discovered = discoverDefaultRepo({
7377
- cwd: opts.cwd,
7378
- runtimeModuleUrl: opts.runtimeModuleUrl
7379
- });
7380
- if (!discovered) return null;
7381
- return {
7382
- repo: discovered.repo,
7383
- source: discovered.source,
7384
- persistedInConfig: false
7385
- };
7386
- }
7387
- function persistDefaultRepo(repo, existing) {
7388
- const config = {
7389
- ...existing ?? loadUserConfig(),
7390
- defaultRepo: redactHomePath(path31.resolve(repo))
7391
- };
7392
- saveUserConfig(config);
7393
- return config;
7394
- }
7395
- function remediateDefaultRepo(opts) {
7396
- const existing = opts?.config ?? loadUserConfig();
7397
- const resolved = resolveDefaultRepo({ ...opts, config: existing });
7398
- if (!resolved) {
7399
- return {
7400
- ok: false,
7401
- reason: "No Kynver git checkout found. Clone the repo, cd into it, then run `kynver setup --repo /path/to/Kynver` (or export KYNVER_DEFAULT_REPO)."
7402
- };
7403
- }
7404
- if (resolved.persistedInConfig) {
7405
- return { ok: true, resolved, config: existing };
7406
- }
7407
- const config = persistDefaultRepo(resolved.repo, existing);
7408
- return {
7409
- ok: true,
7410
- resolved: { ...resolved, persistedInConfig: true, source: "config" },
7411
- config
7412
- };
7413
- }
7414
- function formatResolvedDefaultRepo(resolved) {
7415
- return {
7416
- defaultRepo: displayUserPath(resolved.repo),
7417
- source: resolved.source,
7418
- persistedInConfig: resolved.persistedInConfig
7419
- };
7420
- }
7421
-
7422
7639
  // src/worktree.ts
7423
7640
  function resolveCreateRunRepo(args) {
7424
7641
  const explicit = typeof args.repo === "string" ? args.repo.trim() : "";
@@ -7433,7 +7650,7 @@ function createRun(args) {
7433
7650
  ensureGitRepo(repo);
7434
7651
  const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
7435
7652
  const dir = runDirectory(id);
7436
- if (existsSync25(dir)) failExists(`run already exists: ${id}`);
7653
+ if (existsSync26(dir)) failExists(`run already exists: ${id}`);
7437
7654
  mkdirSync5(dir, { recursive: true });
7438
7655
  const base = String(args.base || "origin/main");
7439
7656
  const baseCommit = git(repo, ["rev-parse", base]).trim();
@@ -7447,7 +7664,7 @@ function createRun(args) {
7447
7664
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7448
7665
  workers: {}
7449
7666
  };
7450
- writeJson(path32.join(dir, "run.json"), run);
7667
+ writeJson(path33.join(dir, "run.json"), run);
7451
7668
  console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
7452
7669
  }
7453
7670
  function listRuns() {
@@ -7459,7 +7676,7 @@ function failExists(message) {
7459
7676
  }
7460
7677
 
7461
7678
  // src/sweep.ts
7462
- import path33 from "node:path";
7679
+ import path34 from "node:path";
7463
7680
  async function sweepRun(args) {
7464
7681
  const pipeline = args.pipeline === true || args.pipeline === "true";
7465
7682
  try {
@@ -7472,7 +7689,7 @@ async function sweepRun(args) {
7472
7689
  const releasedLocalOrphans = [];
7473
7690
  for (const name of Object.keys(run.workers || {})) {
7474
7691
  const worker = readJson(
7475
- path33.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
7692
+ path34.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
7476
7693
  void 0
7477
7694
  );
7478
7695
  if (!worker || !worker.dispatched || !worker.taskId) continue;
@@ -7520,14 +7737,14 @@ async function sweepRun(args) {
7520
7737
  }
7521
7738
 
7522
7739
  // src/harness-storage-snapshot.ts
7523
- import { existsSync as existsSync27, readdirSync as readdirSync7, statSync as statSync5 } from "node:fs";
7524
- import path35 from "node:path";
7740
+ import { existsSync as existsSync28, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
7741
+ import path36 from "node:path";
7525
7742
 
7526
7743
  // src/cleanup-dir-size.ts
7527
- import { existsSync as existsSync26, readdirSync as readdirSync6, statSync as statSync4 } from "node:fs";
7528
- import path34 from "node:path";
7744
+ import { existsSync as existsSync27, readdirSync as readdirSync7, statSync as statSync5 } from "node:fs";
7745
+ import path35 from "node:path";
7529
7746
  function directorySizeBytes(root, maxEntries = 5e4) {
7530
- if (!existsSync26(root)) return 0;
7747
+ if (!existsSync27(root)) return 0;
7531
7748
  let total = 0;
7532
7749
  let seen = 0;
7533
7750
  const stack = [root];
@@ -7535,16 +7752,16 @@ function directorySizeBytes(root, maxEntries = 5e4) {
7535
7752
  const current = stack.pop();
7536
7753
  let entries;
7537
7754
  try {
7538
- entries = readdirSync6(current);
7755
+ entries = readdirSync7(current);
7539
7756
  } catch {
7540
7757
  continue;
7541
7758
  }
7542
7759
  for (const name of entries) {
7543
7760
  if (seen++ > maxEntries) return null;
7544
- const full = path34.join(current, name);
7761
+ const full = path35.join(current, name);
7545
7762
  let st;
7546
7763
  try {
7547
- st = statSync4(full);
7764
+ st = statSync5(full);
7548
7765
  } catch {
7549
7766
  continue;
7550
7767
  }
@@ -7561,7 +7778,7 @@ function harnessStorageSnapshot(opts = {}) {
7561
7778
  const worktreesDir = harnessWorktreesDir(harnessRoot);
7562
7779
  const now = opts.now ?? Date.now();
7563
7780
  const scannedAt = new Date(now).toISOString();
7564
- if (!existsSync27(worktreesDir)) {
7781
+ if (!existsSync28(worktreesDir)) {
7565
7782
  return {
7566
7783
  harnessRoot,
7567
7784
  worktreesDir,
@@ -7578,7 +7795,7 @@ function harnessStorageSnapshot(opts = {}) {
7578
7795
  let oldestMs = null;
7579
7796
  let entries;
7580
7797
  try {
7581
- entries = readdirSync7(worktreesDir, { withFileTypes: true });
7798
+ entries = readdirSync8(worktreesDir, { withFileTypes: true });
7582
7799
  } catch {
7583
7800
  return {
7584
7801
  harnessRoot,
@@ -7593,14 +7810,14 @@ function harnessStorageSnapshot(opts = {}) {
7593
7810
  for (const runEntry of entries) {
7594
7811
  if (!runEntry.isDirectory()) continue;
7595
7812
  runCount += 1;
7596
- const runPath = path35.join(worktreesDir, runEntry.name);
7813
+ const runPath = path36.join(worktreesDir, runEntry.name);
7597
7814
  try {
7598
- const st = statSync5(runPath);
7815
+ const st = statSync6(runPath);
7599
7816
  oldestMs = oldestMs === null ? st.mtimeMs : Math.min(oldestMs, st.mtimeMs);
7600
7817
  } catch {
7601
7818
  }
7602
7819
  try {
7603
- for (const workerEntry of readdirSync7(runPath, { withFileTypes: true })) {
7820
+ for (const workerEntry of readdirSync8(runPath, { withFileTypes: true })) {
7604
7821
  if (workerEntry.isDirectory()) workerCount += 1;
7605
7822
  }
7606
7823
  } catch {
@@ -7628,10 +7845,10 @@ function harnessStorageSnapshot(opts = {}) {
7628
7845
  }
7629
7846
 
7630
7847
  // src/cleanup.ts
7631
- import path46 from "node:path";
7848
+ import path47 from "node:path";
7632
7849
 
7633
7850
  // src/cleanup-guards.ts
7634
- import path36 from "node:path";
7851
+ import path37 from "node:path";
7635
7852
 
7636
7853
  // src/cleanup-run-liveness.ts
7637
7854
  function isWorkerProcessLive(indexed) {
@@ -7755,7 +7972,7 @@ function skipWorktreeRemoval(input) {
7755
7972
  function skipDependencyCacheRemoval(input) {
7756
7973
  const { indexed, nodeModulesAgeMs, ageMs, worktreePath, activeWorktreePaths, diskPressure } = input;
7757
7974
  if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
7758
- if (activeWorktreePaths.has(path36.resolve(worktreePath))) return "active_worker";
7975
+ if (activeWorktreePaths.has(path37.resolve(worktreePath))) return "active_worker";
7759
7976
  if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
7760
7977
  if (indexed && hasUnrestorableWorktreeChanges(indexed.status)) return "dirty_worktree";
7761
7978
  return null;
@@ -7782,11 +7999,11 @@ var LIVE_SKIP_REASONS = /* @__PURE__ */ new Set([
7782
7999
  function collectPreservedLivePaths(actions, skips) {
7783
8000
  const out = [];
7784
8001
  const seen = /* @__PURE__ */ new Set();
7785
- const push = (path60, reason, detail) => {
7786
- const key = `${path60}\0${reason}`;
8002
+ const push = (path63, reason, detail) => {
8003
+ const key = `${path63}\0${reason}`;
7787
8004
  if (seen.has(key) || out.length >= MAX_PRESERVED_LIVE_PATH_SAMPLES) return;
7788
8005
  seen.add(key);
7789
- out.push({ path: path60, reason, ...detail ? { detail } : {} });
8006
+ out.push({ path: path63, reason, ...detail ? { detail } : {} });
7790
8007
  };
7791
8008
  for (const skip2 of skips) {
7792
8009
  if (!LIVE_SKIP_REASONS.has(skip2.reason)) continue;
@@ -7801,11 +8018,11 @@ function collectPreservedLivePaths(actions, skips) {
7801
8018
  }
7802
8019
 
7803
8020
  // src/cleanup-run-directory.ts
7804
- import { existsSync as existsSync28, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
7805
- import path38 from "node:path";
8021
+ import { existsSync as existsSync29, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
8022
+ import path39 from "node:path";
7806
8023
 
7807
8024
  // src/cleanup-active-worktrees.ts
7808
- import path37 from "node:path";
8025
+ import path38 from "node:path";
7809
8026
  function isActiveHarnessWorker2(worker, runBase, runBaseCommit) {
7810
8027
  const status = computeWorkerStatus(worker, { base: runBase, baseCommit: runBaseCommit });
7811
8028
  return status.alive && !status.finalResult && status.attention.state !== "done";
@@ -7818,17 +8035,20 @@ function collectActiveWorktreeGuards(harnessRoots) {
7818
8035
  let runHasLive = false;
7819
8036
  for (const name of Object.keys(run.workers || {})) {
7820
8037
  const worker = readJson(
7821
- path37.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
8038
+ path38.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
7822
8039
  void 0
7823
8040
  );
7824
8041
  if (!worker?.worktreePath) continue;
7825
- const worktreePath = path37.resolve(worker.worktreePath);
8042
+ const worktreePath = path38.resolve(worker.worktreePath);
7826
8043
  if (!isActiveHarnessWorker2(worker, run.base, run.baseCommit)) continue;
7827
8044
  runHasLive = true;
7828
8045
  activeWorktreePaths.add(worktreePath);
7829
8046
  }
7830
8047
  if (runHasLive) liveRunKeys.add(`${harnessRoot}\0${run.id}`);
7831
8048
  }
8049
+ for (const key of collectFilesystemLiveRunKeys(harnessRoot)) {
8050
+ liveRunKeys.add(key);
8051
+ }
7832
8052
  }
7833
8053
  return { activeWorktreePaths, liveRunKeys };
7834
8054
  }
@@ -7840,20 +8060,20 @@ function isWorktreeOnLiveRun(worktreePath, harnessRoot, runId, liveRunKeys) {
7840
8060
  // src/cleanup-run-directory.ts
7841
8061
  function pathAgeMs(target, now) {
7842
8062
  try {
7843
- const mtime = statSync6(target).mtimeMs;
8063
+ const mtime = statSync7(target).mtimeMs;
7844
8064
  return Math.max(0, now - mtime);
7845
8065
  } catch {
7846
8066
  return 0;
7847
8067
  }
7848
8068
  }
7849
8069
  function loadRunStatus(harnessRoot, runId) {
7850
- const runPath = path38.join(harnessRoot, "runs", runId, "run.json");
7851
- if (!existsSync28(runPath)) return null;
8070
+ const runPath = path39.join(harnessRoot, "runs", runId, "run.json");
8071
+ if (!existsSync29(runPath)) return null;
7852
8072
  return readJson(runPath, null);
7853
8073
  }
7854
8074
  function runDirectoryIsEmpty(runPath) {
7855
8075
  try {
7856
- const entries = readdirSync8(runPath);
8076
+ const entries = readdirSync9(runPath);
7857
8077
  return entries.length === 0;
7858
8078
  } catch {
7859
8079
  return false;
@@ -7871,11 +8091,11 @@ function skipRunDirectoryRemoval(input) {
7871
8091
  return null;
7872
8092
  }
7873
8093
  function scanStaleRunDirectoryCandidates(opts) {
7874
- if (!existsSync28(opts.worktreesDir)) return [];
8094
+ if (!existsSync29(opts.worktreesDir)) return [];
7875
8095
  const candidates = [];
7876
8096
  let entries;
7877
8097
  try {
7878
- entries = readdirSync8(opts.worktreesDir, { withFileTypes: true });
8098
+ entries = readdirSync9(opts.worktreesDir, { withFileTypes: true });
7879
8099
  } catch {
7880
8100
  return [];
7881
8101
  }
@@ -7883,7 +8103,7 @@ function scanStaleRunDirectoryCandidates(opts) {
7883
8103
  if (!runEntry.isDirectory()) continue;
7884
8104
  const runId = runEntry.name;
7885
8105
  if (opts.runIdFilter && runId !== opts.runIdFilter) continue;
7886
- const runPath = path38.join(opts.worktreesDir, runId);
8106
+ const runPath = path39.join(opts.worktreesDir, runId);
7887
8107
  if (!runDirectoryIsEmpty(runPath)) continue;
7888
8108
  candidates.push({
7889
8109
  kind: "remove_run_directory",
@@ -7898,10 +8118,22 @@ function scanStaleRunDirectoryCandidates(opts) {
7898
8118
  }
7899
8119
 
7900
8120
  // src/cleanup-execute.ts
7901
- import { existsSync as existsSync29, rmSync as rmSync2 } from "node:fs";
7902
- import path39 from "node:path";
8121
+ import { existsSync as existsSync30, rmSync as rmSync2 } from "node:fs";
8122
+ import path40 from "node:path";
8123
+ function skipRunMetadataRemoval(candidate) {
8124
+ const harnessRoot = candidate.harnessRoot;
8125
+ if (!harnessRoot || !isHarnessRunMetadataPath(candidate.path, harnessRoot)) return null;
8126
+ return {
8127
+ ...candidate,
8128
+ executed: false,
8129
+ skipped: true,
8130
+ skipReason: "run_metadata_protected"
8131
+ };
8132
+ }
7903
8133
  function removeDependencyCache(candidate, execute) {
7904
- if (!existsSync29(candidate.path)) {
8134
+ const metadataSkip = skipRunMetadataRemoval(candidate);
8135
+ if (metadataSkip) return metadataSkip;
8136
+ if (!existsSync30(candidate.path)) {
7905
8137
  return {
7906
8138
  ...candidate,
7907
8139
  executed: false,
@@ -7941,7 +8173,9 @@ function removeBuildCache(candidate, execute) {
7941
8173
  return removeDependencyCache(candidate, execute);
7942
8174
  }
7943
8175
  function removeRunDirectory(candidate, execute) {
7944
- if (!existsSync29(candidate.path)) {
8176
+ const metadataSkip = skipRunMetadataRemoval(candidate);
8177
+ if (metadataSkip) return metadataSkip;
8178
+ if (!existsSync30(candidate.path)) {
7945
8179
  return {
7946
8180
  ...candidate,
7947
8181
  executed: false,
@@ -7972,7 +8206,9 @@ function removeRunDirectory(candidate, execute) {
7972
8206
  }
7973
8207
  }
7974
8208
  function removeWorktree(candidate, execute) {
7975
- if (!existsSync29(candidate.path)) {
8209
+ const metadataSkip = skipRunMetadataRemoval(candidate);
8210
+ if (metadataSkip) return metadataSkip;
8211
+ if (!existsSync30(candidate.path)) {
7976
8212
  return {
7977
8213
  ...candidate,
7978
8214
  executed: false,
@@ -7989,7 +8225,7 @@ function removeWorktree(candidate, execute) {
7989
8225
  if (repo) {
7990
8226
  git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
7991
8227
  }
7992
- if (existsSync29(candidate.path)) {
8228
+ if (existsSync30(candidate.path)) {
7993
8229
  rmSync2(candidate.path, { recursive: true, force: true });
7994
8230
  }
7995
8231
  return {
@@ -8009,15 +8245,15 @@ function removeWorktree(candidate, execute) {
8009
8245
  }
8010
8246
  }
8011
8247
  function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
8012
- const resolved = path39.resolve(targetPath);
8013
- const suffix = `${path39.sep}${cacheDirName}`;
8248
+ const resolved = path40.resolve(targetPath);
8249
+ const suffix = `${path40.sep}${cacheDirName}`;
8014
8250
  const cachePath = resolved.endsWith(suffix) ? resolved : null;
8015
8251
  if (!cachePath) return "path_outside_harness";
8016
- const rel = path39.relative(worktreesDir, cachePath);
8017
- if (rel.startsWith("..") || path39.isAbsolute(rel)) return "path_outside_harness";
8018
- const parts = rel.split(path39.sep);
8252
+ const rel = path40.relative(worktreesDir, cachePath);
8253
+ if (rel.startsWith("..") || path40.isAbsolute(rel)) return "path_outside_harness";
8254
+ const parts = rel.split(path40.sep);
8019
8255
  if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
8020
- if (!resolved.startsWith(path39.resolve(harnessRoot))) return "path_outside_harness";
8256
+ if (!resolved.startsWith(path40.resolve(harnessRoot))) return "path_outside_harness";
8021
8257
  return null;
8022
8258
  }
8023
8259
  function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
@@ -8027,37 +8263,37 @@ function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
8027
8263
  return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
8028
8264
  }
8029
8265
  function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
8030
- const resolved = path39.resolve(targetPath);
8031
- const relToWt = path39.relative(worktreesDir, resolved);
8032
- if (relToWt.startsWith("..") || path39.isAbsolute(relToWt)) return "path_outside_harness";
8033
- const parts = relToWt.split(path39.sep);
8266
+ const resolved = path40.resolve(targetPath);
8267
+ const relToWt = path40.relative(worktreesDir, resolved);
8268
+ if (relToWt.startsWith("..") || path40.isAbsolute(relToWt)) return "path_outside_harness";
8269
+ const parts = relToWt.split(path40.sep);
8034
8270
  if (parts.length < 3) return "path_outside_harness";
8035
- if (!resolved.startsWith(path39.resolve(harnessRoot))) return "path_outside_harness";
8271
+ if (!resolved.startsWith(path40.resolve(harnessRoot))) return "path_outside_harness";
8036
8272
  return null;
8037
8273
  }
8038
8274
 
8039
8275
  // src/cleanup-scan.ts
8040
- import { existsSync as existsSync30, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
8041
- import path40 from "node:path";
8276
+ import { existsSync as existsSync31, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
8277
+ import path41 from "node:path";
8042
8278
  function pathAgeMs2(target, now) {
8043
8279
  try {
8044
- const mtime = statSync7(target).mtimeMs;
8280
+ const mtime = statSync8(target).mtimeMs;
8045
8281
  return Math.max(0, now - mtime);
8046
8282
  } catch {
8047
8283
  return 0;
8048
8284
  }
8049
8285
  }
8050
8286
  function isPathInside(child, parent) {
8051
- const rel = path40.relative(parent, child);
8052
- return rel === "" || !rel.startsWith("..") && !path40.isAbsolute(rel);
8287
+ const rel = path41.relative(parent, child);
8288
+ return rel === "" || !rel.startsWith("..") && !path41.isAbsolute(rel);
8053
8289
  }
8054
8290
  function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
8055
8291
  const out = [];
8056
8292
  for (const rel of HARNESS_BUILD_CACHE_RELATIVE_PATHS) {
8057
8293
  if (rel === ".next") continue;
8058
- const target = path40.join(worktreePath, rel);
8059
- if (!existsSync30(target)) continue;
8060
- const resolved = path40.resolve(target);
8294
+ const target = path41.join(worktreePath, rel);
8295
+ if (!existsSync31(target)) continue;
8296
+ const resolved = path41.resolve(target);
8061
8297
  if (seen.has(resolved)) continue;
8062
8298
  if (!isPathInside(resolved, opts.harnessRoot)) continue;
8063
8299
  seen.add(resolved);
@@ -8086,13 +8322,13 @@ function scanBuildCacheCandidates(opts) {
8086
8322
  })
8087
8323
  );
8088
8324
  }
8089
- if (!opts.includeOrphans || !existsSync30(opts.worktreesDir)) return candidates;
8090
- for (const runEntry of readdirSync9(opts.worktreesDir, { withFileTypes: true })) {
8325
+ if (!opts.includeOrphans || !existsSync31(opts.worktreesDir)) return candidates;
8326
+ for (const runEntry of readdirSync10(opts.worktreesDir, { withFileTypes: true })) {
8091
8327
  if (!runEntry.isDirectory()) continue;
8092
- const runPath = path40.join(opts.worktreesDir, runEntry.name);
8093
- for (const workerEntry of readdirSync9(runPath, { withFileTypes: true })) {
8328
+ const runPath = path41.join(opts.worktreesDir, runEntry.name);
8329
+ for (const workerEntry of readdirSync10(runPath, { withFileTypes: true })) {
8094
8330
  if (!workerEntry.isDirectory()) continue;
8095
- const worktreePath = path40.join(runPath, workerEntry.name);
8331
+ const worktreePath = path41.join(runPath, workerEntry.name);
8096
8332
  candidates.push(
8097
8333
  ...collectBuildCacheForWorktree(worktreePath, opts, seen, {
8098
8334
  runId: runEntry.name,
@@ -8113,7 +8349,7 @@ function scanWorktreeCandidates(opts) {
8113
8349
  for (const entry of opts.index.values()) {
8114
8350
  if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
8115
8351
  const resolved = entry.worktreePath;
8116
- if (!existsSync30(resolved)) continue;
8352
+ if (!existsSync31(resolved)) continue;
8117
8353
  if (seen.has(resolved)) continue;
8118
8354
  seen.add(resolved);
8119
8355
  candidates.push({
@@ -8127,24 +8363,24 @@ function scanWorktreeCandidates(opts) {
8127
8363
  });
8128
8364
  }
8129
8365
  }
8130
- if (!orphanEnabled || !existsSync30(opts.worktreesDir)) return candidates;
8366
+ if (!orphanEnabled || !existsSync31(opts.worktreesDir)) return candidates;
8131
8367
  const indexedPaths = /* @__PURE__ */ new Set();
8132
8368
  for (const entry of opts.index.values()) {
8133
- indexedPaths.add(path40.resolve(entry.worktreePath));
8369
+ indexedPaths.add(path41.resolve(entry.worktreePath));
8134
8370
  }
8135
- for (const runEntry of readdirSync9(opts.worktreesDir, { withFileTypes: true })) {
8371
+ for (const runEntry of readdirSync10(opts.worktreesDir, { withFileTypes: true })) {
8136
8372
  if (!runEntry.isDirectory()) continue;
8137
8373
  if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
8138
- const runPath = path40.join(opts.worktreesDir, runEntry.name);
8374
+ const runPath = path41.join(opts.worktreesDir, runEntry.name);
8139
8375
  let workerEntries;
8140
8376
  try {
8141
- workerEntries = readdirSync9(runPath, { withFileTypes: true });
8377
+ workerEntries = readdirSync10(runPath, { withFileTypes: true });
8142
8378
  } catch {
8143
8379
  continue;
8144
8380
  }
8145
8381
  for (const workerEntry of workerEntries) {
8146
8382
  if (!workerEntry.isDirectory()) continue;
8147
- const worktreePath = path40.resolve(path40.join(runPath, workerEntry.name));
8383
+ const worktreePath = path41.resolve(path41.join(runPath, workerEntry.name));
8148
8384
  if (seen.has(worktreePath)) continue;
8149
8385
  if (indexedPaths.has(worktreePath)) continue;
8150
8386
  if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
@@ -8163,27 +8399,27 @@ function scanWorktreeCandidates(opts) {
8163
8399
  }
8164
8400
 
8165
8401
  // src/cleanup-dependency-scan.ts
8166
- import { existsSync as existsSync31, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
8167
- import path41 from "node:path";
8402
+ import { existsSync as existsSync32, readdirSync as readdirSync11, statSync as statSync9 } from "node:fs";
8403
+ import path42 from "node:path";
8168
8404
  var DEPENDENCY_CACHE_DIRS = [
8169
8405
  { dirName: "node_modules", kind: "remove_node_modules" },
8170
8406
  { dirName: ".next", kind: "remove_next_cache" }
8171
8407
  ];
8172
8408
  function pathAgeMs3(target, now) {
8173
8409
  try {
8174
- const mtime = statSync8(target).mtimeMs;
8410
+ const mtime = statSync9(target).mtimeMs;
8175
8411
  return Math.max(0, now - mtime);
8176
8412
  } catch {
8177
8413
  return 0;
8178
8414
  }
8179
8415
  }
8180
8416
  function isPathInside2(child, parent) {
8181
- const rel = path41.relative(parent, child);
8182
- return rel === "" || !rel.startsWith("..") && !path41.isAbsolute(rel);
8417
+ const rel = path42.relative(parent, child);
8418
+ return rel === "" || !rel.startsWith("..") && !path42.isAbsolute(rel);
8183
8419
  }
8184
8420
  function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
8185
- if (!existsSync31(targetPath)) return;
8186
- const resolved = path41.resolve(targetPath);
8421
+ if (!existsSync32(targetPath)) return;
8422
+ const resolved = path42.resolve(targetPath);
8187
8423
  if (seen.has(resolved)) return;
8188
8424
  if (!isPathInside2(resolved, opts.harnessRoot)) return;
8189
8425
  seen.add(resolved);
@@ -8200,7 +8436,7 @@ function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
8200
8436
  }
8201
8437
  function scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, meta) {
8202
8438
  for (const entry of DEPENDENCY_CACHE_DIRS) {
8203
- pushCandidate2(candidates, seen, opts, path41.join(worktreePath, entry.dirName), entry.kind, meta);
8439
+ pushCandidate2(candidates, seen, opts, path42.join(worktreePath, entry.dirName), entry.kind, meta);
8204
8440
  }
8205
8441
  }
8206
8442
  function scanDependencyCacheCandidates(opts) {
@@ -8214,20 +8450,20 @@ function scanDependencyCacheCandidates(opts) {
8214
8450
  repo: entry.run.repo
8215
8451
  });
8216
8452
  }
8217
- if (!existsSync31(opts.worktreesDir)) return candidates;
8218
- for (const runEntry of readdirSync10(opts.worktreesDir, { withFileTypes: true })) {
8453
+ if (!existsSync32(opts.worktreesDir)) return candidates;
8454
+ for (const runEntry of readdirSync11(opts.worktreesDir, { withFileTypes: true })) {
8219
8455
  if (!runEntry.isDirectory()) continue;
8220
8456
  if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
8221
- const runPath = path41.join(opts.worktreesDir, runEntry.name);
8457
+ const runPath = path42.join(opts.worktreesDir, runEntry.name);
8222
8458
  let workerEntries;
8223
8459
  try {
8224
- workerEntries = readdirSync10(runPath, { withFileTypes: true });
8460
+ workerEntries = readdirSync11(runPath, { withFileTypes: true });
8225
8461
  } catch {
8226
8462
  continue;
8227
8463
  }
8228
8464
  for (const workerEntry of workerEntries) {
8229
8465
  if (!workerEntry.isDirectory()) continue;
8230
- const worktreePath = path41.join(runPath, workerEntry.name);
8466
+ const worktreePath = path42.join(runPath, workerEntry.name);
8231
8467
  scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, {
8232
8468
  runId: runEntry.name,
8233
8469
  worker: workerEntry.name
@@ -8238,11 +8474,11 @@ function scanDependencyCacheCandidates(opts) {
8238
8474
  }
8239
8475
 
8240
8476
  // src/cleanup-duplicate-worktrees.ts
8241
- import { existsSync as existsSync32, statSync as statSync9 } from "node:fs";
8242
- import path42 from "node:path";
8477
+ import { existsSync as existsSync33, statSync as statSync10 } from "node:fs";
8478
+ import path43 from "node:path";
8243
8479
  function pathAgeMs4(target, now) {
8244
8480
  try {
8245
- const mtime = statSync9(target).mtimeMs;
8481
+ const mtime = statSync10(target).mtimeMs;
8246
8482
  return Math.max(0, now - mtime);
8247
8483
  } catch {
8248
8484
  return 0;
@@ -8269,8 +8505,8 @@ function parseWorktreePorcelain(output) {
8269
8505
  return records;
8270
8506
  }
8271
8507
  function isUnderWorktreesDir(worktreePath, worktreesDir) {
8272
- const rel = path42.relative(path42.resolve(worktreesDir), path42.resolve(worktreePath));
8273
- return rel !== "" && !rel.startsWith("..") && !path42.isAbsolute(rel);
8508
+ const rel = path43.relative(path43.resolve(worktreesDir), path43.resolve(worktreePath));
8509
+ return rel !== "" && !rel.startsWith("..") && !path43.isAbsolute(rel);
8274
8510
  }
8275
8511
  function isCleanWorktree(worktreePath, repoRoot) {
8276
8512
  try {
@@ -8283,14 +8519,14 @@ function isCleanWorktree(worktreePath, repoRoot) {
8283
8519
  }
8284
8520
  }
8285
8521
  function scanDuplicateWorktreeCandidates(opts) {
8286
- if (!opts.includeOrphans || !existsSync32(opts.worktreesDir)) return [];
8522
+ if (!opts.includeOrphans || !existsSync33(opts.worktreesDir)) return [];
8287
8523
  const repos = /* @__PURE__ */ new Set();
8288
8524
  for (const entry of opts.index.values()) {
8289
- if (entry.run.repo) repos.add(path42.resolve(entry.run.repo));
8525
+ if (entry.run.repo) repos.add(path43.resolve(entry.run.repo));
8290
8526
  }
8291
8527
  const indexedPaths = /* @__PURE__ */ new Set();
8292
8528
  for (const entry of opts.index.values()) {
8293
- indexedPaths.add(path42.resolve(entry.worktreePath));
8529
+ indexedPaths.add(path43.resolve(entry.worktreePath));
8294
8530
  }
8295
8531
  const candidates = [];
8296
8532
  const seen = /* @__PURE__ */ new Set();
@@ -8303,15 +8539,15 @@ function scanDuplicateWorktreeCandidates(opts) {
8303
8539
  }
8304
8540
  const worktrees = parseWorktreePorcelain(porcelain);
8305
8541
  for (const wt of worktrees) {
8306
- const resolved = path42.resolve(wt.path);
8307
- if (resolved === path42.resolve(repoRoot)) continue;
8542
+ const resolved = path43.resolve(wt.path);
8543
+ if (resolved === path43.resolve(repoRoot)) continue;
8308
8544
  if (!isUnderWorktreesDir(resolved, opts.worktreesDir)) continue;
8309
8545
  if (indexedPaths.has(resolved)) continue;
8310
8546
  if (seen.has(resolved)) continue;
8311
- if (!existsSync32(resolved)) continue;
8547
+ if (!existsSync33(resolved)) continue;
8312
8548
  if (!isCleanWorktree(resolved, repoRoot)) continue;
8313
- const rel = path42.relative(opts.worktreesDir, resolved);
8314
- const parts = rel.split(path42.sep);
8549
+ const rel = path43.relative(opts.worktreesDir, resolved);
8550
+ const parts = rel.split(path43.sep);
8315
8551
  const runId = parts[0];
8316
8552
  const worker = parts[1] ?? "unknown";
8317
8553
  seen.add(resolved);
@@ -8330,12 +8566,12 @@ function scanDuplicateWorktreeCandidates(opts) {
8330
8566
  }
8331
8567
 
8332
8568
  // src/cleanup-worktree-index.ts
8333
- import path43 from "node:path";
8569
+ import path44 from "node:path";
8334
8570
  function buildWorktreeIndexAt(harnessRoot) {
8335
8571
  const index = /* @__PURE__ */ new Map();
8336
8572
  for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
8337
8573
  for (const name of Object.keys(run.workers || {})) {
8338
- const workerPath = path43.join(
8574
+ const workerPath = path44.join(
8339
8575
  runDirectoryAt(harnessRoot, run.id),
8340
8576
  "workers",
8341
8577
  safeSlug(name),
@@ -8344,9 +8580,9 @@ function buildWorktreeIndexAt(harnessRoot) {
8344
8580
  const worker = readJson(workerPath, void 0);
8345
8581
  if (!worker?.worktreePath) continue;
8346
8582
  const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
8347
- index.set(path43.resolve(worker.worktreePath), {
8583
+ index.set(path44.resolve(worker.worktreePath), {
8348
8584
  harnessRoot,
8349
- worktreePath: path43.resolve(worker.worktreePath),
8585
+ worktreePath: path44.resolve(worker.worktreePath),
8350
8586
  runId: run.id,
8351
8587
  workerName: name,
8352
8588
  run,
@@ -8410,15 +8646,15 @@ function resolvePipelineHarnessRetention(runId) {
8410
8646
  }
8411
8647
 
8412
8648
  // src/cleanup-orphan-safety.ts
8413
- import { existsSync as existsSync33, statSync as statSync10 } from "node:fs";
8414
- import path44 from "node:path";
8649
+ import { existsSync as existsSync34, statSync as statSync11 } from "node:fs";
8650
+ import path45 from "node:path";
8415
8651
  var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
8416
8652
  function assessOrphanWorktreeSafety(input) {
8417
8653
  const now = input.now ?? Date.now();
8418
8654
  const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
8419
- if (!existsSync33(input.worktreePath)) return null;
8655
+ if (!existsSync34(input.worktreePath)) return null;
8420
8656
  if (input.runId && input.workerName) {
8421
- const heartbeatPath = path44.join(
8657
+ const heartbeatPath = path45.join(
8422
8658
  input.harnessRoot,
8423
8659
  "runs",
8424
8660
  input.runId,
@@ -8427,13 +8663,13 @@ function assessOrphanWorktreeSafety(input) {
8427
8663
  "heartbeat.jsonl"
8428
8664
  );
8429
8665
  try {
8430
- const mtime = statSync10(heartbeatPath).mtimeMs;
8666
+ const mtime = statSync11(heartbeatPath).mtimeMs;
8431
8667
  if (now - mtime < heartbeatFreshMs) return "active_worker";
8432
8668
  } catch {
8433
8669
  }
8434
8670
  }
8435
- const gitDir = path44.join(input.worktreePath, ".git");
8436
- if (!existsSync33(gitDir)) return null;
8671
+ const gitDir = path45.join(input.worktreePath, ".git");
8672
+ if (!existsSync34(gitDir)) return null;
8437
8673
  const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
8438
8674
  if (porcelain.status !== 0) return "pr_or_unmerged_commits";
8439
8675
  const dirtyLines = porcelain.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
@@ -8462,12 +8698,12 @@ function assessOrphanWorktreeSafety(input) {
8462
8698
  }
8463
8699
 
8464
8700
  // src/cleanup-harness-roots.ts
8465
- import { existsSync as existsSync34 } from "node:fs";
8701
+ import { existsSync as existsSync35 } from "node:fs";
8466
8702
  import { homedir as homedir11 } from "node:os";
8467
- import path45 from "node:path";
8703
+ import path46 from "node:path";
8468
8704
  var WELL_KNOWN_HARNESS_SCAN_ROOTS = [
8469
8705
  "/var/tmp/kynver-harness",
8470
- path45.join(homedir11(), ".openclaw", "harness")
8706
+ path46.join(homedir11(), ".openclaw", "harness")
8471
8707
  ];
8472
8708
  function addRoot(seen, roots, candidate) {
8473
8709
  if (!candidate?.trim()) return;
@@ -8489,8 +8725,8 @@ function resolveHarnessScanRoots(options = {}) {
8489
8725
  for (const candidate of extra ?? []) addRoot(seen, roots, candidate);
8490
8726
  if (shouldScanWellKnownRoots(options)) {
8491
8727
  for (const candidate of WELL_KNOWN_HARNESS_SCAN_ROOTS) {
8492
- const resolved = path45.resolve(candidate);
8493
- if (!seen.has(resolved) && existsSync34(resolved)) addRoot(seen, roots, resolved);
8728
+ const resolved = path46.resolve(candidate);
8729
+ if (!seen.has(resolved) && existsSync35(resolved)) addRoot(seen, roots, resolved);
8494
8730
  }
8495
8731
  }
8496
8732
  return roots;
@@ -8579,9 +8815,9 @@ function mergeWorktreeIndexes(scanRoots) {
8579
8815
  }
8580
8816
  function worktreePathForCandidate(candidate, worktreesDir) {
8581
8817
  if (candidate.runId && candidate.worker) {
8582
- return path46.join(worktreesDir, candidate.runId, candidate.worker);
8818
+ return path47.join(worktreesDir, candidate.runId, candidate.worker);
8583
8819
  }
8584
- return path46.resolve(candidate.path, "..");
8820
+ return path47.resolve(candidate.path, "..");
8585
8821
  }
8586
8822
  function runHarnessCleanup(options = {}) {
8587
8823
  let retention = resolveHarnessRetention(options);
@@ -8598,7 +8834,7 @@ function runHarnessCleanup(options = {}) {
8598
8834
  const atSweepCap = () => actions.length >= maxActions;
8599
8835
  for (const harnessRoot of paths.scanRoots) {
8600
8836
  if (atSweepCap()) break;
8601
- const worktreesDir = path46.join(harnessRoot, "worktrees");
8837
+ const worktreesDir = path47.join(harnessRoot, "worktrees");
8602
8838
  const scanOpts = {
8603
8839
  harnessRoot,
8604
8840
  worktreesDir,
@@ -8612,7 +8848,7 @@ function runHarnessCleanup(options = {}) {
8612
8848
  for (const raw of scanDependencyCacheCandidates(scanOpts)) {
8613
8849
  if (atSweepCap()) break;
8614
8850
  const candidate = attachCandidateBytes(raw, retention.accountBytes);
8615
- const resolved = path46.resolve(candidate.path);
8851
+ const resolved = path47.resolve(candidate.path);
8616
8852
  if (processedPaths.has(resolved)) continue;
8617
8853
  processedPaths.add(resolved);
8618
8854
  const pathSkip = pathGuardForDependencyCache(candidate, harnessRoot, worktreesDir);
@@ -8622,7 +8858,7 @@ function runHarnessCleanup(options = {}) {
8622
8858
  continue;
8623
8859
  }
8624
8860
  const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
8625
- const indexed = index.get(path46.resolve(worktreePath)) ?? null;
8861
+ const indexed = index.get(path47.resolve(worktreePath)) ?? null;
8626
8862
  const guardReason = skipDependencyCacheRemoval({
8627
8863
  indexed,
8628
8864
  includeOrphans: true,
@@ -8642,7 +8878,7 @@ function runHarnessCleanup(options = {}) {
8642
8878
  for (const raw of scanBuildCacheCandidates(scanOpts)) {
8643
8879
  if (atSweepCap()) break;
8644
8880
  const candidate = attachCandidateBytes(raw, retention.accountBytes);
8645
- const resolved = path46.resolve(candidate.path);
8881
+ const resolved = path47.resolve(candidate.path);
8646
8882
  if (processedPaths.has(resolved)) continue;
8647
8883
  processedPaths.add(resolved);
8648
8884
  const pathSkip = isHarnessBuildCachePath(candidate.path, harnessRoot, worktreesDir);
@@ -8652,7 +8888,7 @@ function runHarnessCleanup(options = {}) {
8652
8888
  continue;
8653
8889
  }
8654
8890
  const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
8655
- const indexed = index.get(path46.resolve(worktreePath)) ?? null;
8891
+ const indexed = index.get(path47.resolve(worktreePath)) ?? null;
8656
8892
  const guardReason = skipBuildCacheRemoval({
8657
8893
  indexed,
8658
8894
  includeOrphans: true,
@@ -8676,11 +8912,11 @@ function runHarnessCleanup(options = {}) {
8676
8912
  const worktreeSeen = /* @__PURE__ */ new Set();
8677
8913
  for (const raw of worktreeCandidates) {
8678
8914
  if (atSweepCap()) break;
8679
- const resolved = path46.resolve(raw.path);
8915
+ const resolved = path47.resolve(raw.path);
8680
8916
  if (worktreeSeen.has(resolved)) continue;
8681
8917
  worktreeSeen.add(resolved);
8682
8918
  const candidate = attachCandidateBytes({ ...raw, path: resolved }, retention.accountBytes);
8683
- const indexed = index.get(path46.resolve(candidate.path)) ?? null;
8919
+ const indexed = index.get(path47.resolve(candidate.path)) ?? null;
8684
8920
  const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
8685
8921
  worktreePath: candidate.path,
8686
8922
  harnessRoot,
@@ -8690,7 +8926,7 @@ function runHarnessCleanup(options = {}) {
8690
8926
  });
8691
8927
  const guardSkip = skipWorktreeRemoval({
8692
8928
  indexed,
8693
- worktreePath: path46.resolve(candidate.path),
8929
+ worktreePath: path47.resolve(candidate.path),
8694
8930
  includeOrphans: retention.includeOrphans,
8695
8931
  worktreesAgeMs: retention.worktreesAgeMs,
8696
8932
  terminalWorktreesAgeMs: retention.terminalWorktreesAgeMs,
@@ -8717,10 +8953,10 @@ function runHarnessCleanup(options = {}) {
8717
8953
  })) {
8718
8954
  if (atSweepCap()) break;
8719
8955
  const candidate = attachCandidateBytes(raw, retention.accountBytes);
8720
- const resolved = path46.resolve(candidate.path);
8956
+ const resolved = path47.resolve(candidate.path);
8721
8957
  if (processedPaths.has(resolved)) continue;
8722
8958
  processedPaths.add(resolved);
8723
- const runId = candidate.runId ?? path46.basename(resolved);
8959
+ const runId = candidate.runId ?? path47.basename(resolved);
8724
8960
  const dirSkip = skipRunDirectoryRemoval({
8725
8961
  harnessRoot,
8726
8962
  runId,
@@ -8812,8 +9048,8 @@ import { mkdirSync as mkdirSync7, realpathSync } from "node:fs";
8812
9048
  import { fileURLToPath as fileURLToPath5 } from "node:url";
8813
9049
 
8814
9050
  // src/discard-disposable.ts
8815
- import { existsSync as existsSync35, rmSync as rmSync3 } from "node:fs";
8816
- import path47 from "node:path";
9051
+ import { existsSync as existsSync36, rmSync as rmSync3 } from "node:fs";
9052
+ import path48 from "node:path";
8817
9053
  function normalizeRelativePath2(value) {
8818
9054
  const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
8819
9055
  if (!normalized || normalized.startsWith("/") || normalized.includes("..")) {
@@ -8834,15 +9070,15 @@ function discardDisposableArtifacts(args) {
8834
9070
  if (paths.length === 0) {
8835
9071
  return { ok: false, removed: [], reason: "requires at least one --path" };
8836
9072
  }
8837
- const worktreeRoot = path47.resolve(worker.worktreePath);
9073
+ const worktreeRoot = path48.resolve(worker.worktreePath);
8838
9074
  const removed = [];
8839
9075
  for (const raw of paths) {
8840
9076
  const rel = normalizeRelativePath2(raw);
8841
- const abs = path47.resolve(worktreeRoot, rel);
8842
- if (!abs.startsWith(worktreeRoot + path47.sep) && abs !== worktreeRoot) {
9077
+ const abs = path48.resolve(worktreeRoot, rel);
9078
+ if (!abs.startsWith(worktreeRoot + path48.sep) && abs !== worktreeRoot) {
8843
9079
  return { ok: false, removed, reason: `path escapes worktree: ${raw}` };
8844
9080
  }
8845
- if (!existsSync35(abs)) {
9081
+ if (!existsSync36(abs)) {
8846
9082
  return { ok: false, removed, reason: `path not found: ${raw}` };
8847
9083
  }
8848
9084
  rmSync3(abs, { recursive: true, force: true });
@@ -8864,8 +9100,472 @@ function discardDisposableCli(args) {
8864
9100
  if (!result.ok) process.exit(1);
8865
9101
  }
8866
9102
 
8867
- // src/pipeline-tick.ts
9103
+ // src/cron/cron-env.ts
9104
+ import { existsSync as existsSync37 } from "node:fs";
9105
+ import { homedir as homedir12 } from "node:os";
9106
+ import path49 from "node:path";
9107
+ function envFlag3(name, defaultValue) {
9108
+ const raw = process.env[name]?.trim().toLowerCase();
9109
+ if (!raw) return defaultValue;
9110
+ if (raw === "0" || raw === "false" || raw === "no" || raw === "off") return false;
9111
+ if (raw === "1" || raw === "true" || raw === "yes" || raw === "on") return true;
9112
+ return defaultValue;
9113
+ }
9114
+ function envInt(name, fallback, min = 1) {
9115
+ const n = Number(process.env[name]);
9116
+ if (!Number.isFinite(n) || n < min) return fallback;
9117
+ return Math.floor(n);
9118
+ }
9119
+ function defaultKynverCronStorePath() {
9120
+ const explicit = process.env.KYNVER_CRON_STORE_PATH?.trim() || process.env.OPENCLAW_CRON_STORE_PATH?.trim();
9121
+ if (explicit) return explicit;
9122
+ return path49.join(homedir12(), ".kynver", "agent-os-cron.json");
9123
+ }
9124
+ function defaultKynverCronStatePath(storePath = defaultKynverCronStorePath()) {
9125
+ const explicit = process.env.KYNVER_CRON_TICK_STATE_PATH?.trim();
9126
+ if (explicit) return explicit;
9127
+ return `${storePath.replace(/\.json$/i, "")}.tick-state.json`;
9128
+ }
9129
+ function resolveKynverCronFireBaseUrl() {
9130
+ const config = loadUserConfig();
9131
+ return process.env.KYNVER_API_URL?.trim() || config.apiBaseUrl?.trim() || process.env.KYNVER_CRON_FIRE_BASE_URL?.trim() || process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || null;
9132
+ }
9133
+ function resolveKynverCronSecret() {
9134
+ return process.env.KYNVER_CRON_SECRET?.trim() || process.env.OPENCLAW_CRON_SECRET?.trim() || process.env.KYNVER_RUNTIME_SECRET?.trim() || null;
9135
+ }
9136
+ function resolveKynverCronEnv() {
9137
+ const storePath = defaultKynverCronStorePath();
9138
+ const statePath = defaultKynverCronStatePath(storePath);
9139
+ const fireBaseUrl = resolveKynverCronFireBaseUrl();
9140
+ const secret = resolveKynverCronSecret();
9141
+ const credsReady = Boolean(fireBaseUrl && secret);
9142
+ const storeExists = existsSync37(storePath);
9143
+ const defaultEnabled = credsReady && (storeExists || envFlag3("KYNVER_CRON_TICK_FORCE", false));
9144
+ return {
9145
+ storePath,
9146
+ statePath,
9147
+ lockPath: `${statePath}.lock`,
9148
+ fireBaseUrl,
9149
+ secret,
9150
+ tickEnabled: envFlag3("KYNVER_CRON_TICK_ENABLED", defaultEnabled),
9151
+ tickIntervalMs: envInt("KYNVER_CRON_TICK_INTERVAL_MS", 6e4, 5e3),
9152
+ missedRunPolicy: process.env.KYNVER_CRON_MISSED_RUN_POLICY?.trim().toLowerCase() === "skip" ? "skip" : "catch_up",
9153
+ maxCatchUpPerTick: envInt("KYNVER_CRON_MAX_CATCH_UP_PER_TICK", 3, 0),
9154
+ maxRetries: envInt("KYNVER_CRON_MAX_RETRIES", 3, 0),
9155
+ retryBackoffMs: envInt("KYNVER_CRON_RETRY_BACKOFF_MS", 6e4, 1e3),
9156
+ inflightLeaseMs: envInt("KYNVER_CRON_INFLIGHT_LEASE_MS", 12e4, 5e3)
9157
+ };
9158
+ }
9159
+ function isKynverCronDaemonPrimary(env = resolveKynverCronEnv()) {
9160
+ return env.tickEnabled && Boolean(env.fireBaseUrl && env.secret);
9161
+ }
9162
+
9163
+ // src/cron/cron-fire.ts
9164
+ function trimTrailingSlash2(url) {
9165
+ return url.replace(/\/+$/, "");
9166
+ }
9167
+ async function fireKynverCronJob(input) {
9168
+ const doFetch = input.fetchFn ?? fetch;
9169
+ const callbackPath = input.entry.spec.callbackPath.startsWith("/") ? input.entry.spec.callbackPath : `/${input.entry.spec.callbackPath}`;
9170
+ const url = `${trimTrailingSlash2(input.baseUrl)}${callbackPath}`;
9171
+ const jobId = input.jobId ?? input.entry.spec.dedupeKey ?? null;
9172
+ const body = {
9173
+ source: "kynver-cron",
9174
+ jobId,
9175
+ agentOsId: input.entry.spec.target.agentOsId,
9176
+ kind: input.entry.spec.kind,
9177
+ target: input.entry.spec.target,
9178
+ ...input.entry.spec.payload !== void 0 && { payload: input.entry.spec.payload }
9179
+ };
9180
+ const res = await doFetch(url, {
9181
+ method: "POST",
9182
+ headers: buildHarnessCallbackHeaders(input.secret),
9183
+ body: JSON.stringify(body)
9184
+ });
9185
+ let parsed = null;
9186
+ try {
9187
+ parsed = await res.json();
9188
+ } catch {
9189
+ parsed = null;
9190
+ }
9191
+ return { ok: res.ok, status: res.status, body: parsed };
9192
+ }
9193
+
9194
+ // src/cron/cron-lock.ts
9195
+ import { closeSync as closeSync6, existsSync as existsSync38, openSync as openSync6, readFileSync as readFileSync13, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
9196
+ var STALE_LOCK_MS = 10 * 6e4;
9197
+ function readLockInfo(lockPath) {
9198
+ if (!existsSync38(lockPath)) return null;
9199
+ try {
9200
+ const parsed = JSON.parse(readFileSync13(lockPath, "utf8"));
9201
+ if (typeof parsed.pid === "number" && typeof parsed.at === "string") return parsed;
9202
+ } catch {
9203
+ return null;
9204
+ }
9205
+ return null;
9206
+ }
9207
+ function lockIsStale(lockPath) {
9208
+ const info = readLockInfo(lockPath);
9209
+ if (!info) return true;
9210
+ if (!isPidAlive(info.pid)) return true;
9211
+ const atMs = Date.parse(info.at);
9212
+ if (Number.isNaN(atMs)) return true;
9213
+ return Date.now() - atMs > STALE_LOCK_MS;
9214
+ }
9215
+ function tryAcquireCronTickLock(lockPath) {
9216
+ if (existsSync38(lockPath) && !lockIsStale(lockPath)) {
9217
+ const info = readLockInfo(lockPath);
9218
+ return {
9219
+ acquired: false,
9220
+ reason: info ? `held by pid ${info.pid}` : "held by another process"
9221
+ };
9222
+ }
9223
+ if (existsSync38(lockPath)) {
9224
+ try {
9225
+ unlinkSync2(lockPath);
9226
+ } catch {
9227
+ }
9228
+ }
9229
+ try {
9230
+ const fd = openSync6(lockPath, "wx");
9231
+ writeFileSync4(
9232
+ fd,
9233
+ JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() }),
9234
+ "utf8"
9235
+ );
9236
+ closeSync6(fd);
9237
+ return { acquired: true };
9238
+ } catch (err) {
9239
+ if (err.code === "EEXIST") {
9240
+ return { acquired: false, reason: "concurrent acquire" };
9241
+ }
9242
+ throw err;
9243
+ }
9244
+ }
9245
+ function releaseCronTickLock(lockPath) {
9246
+ try {
9247
+ unlinkSync2(lockPath);
9248
+ } catch {
9249
+ }
9250
+ }
9251
+
9252
+ // src/cron/cron-match.ts
9253
+ var CRON_RE = /^[\d*/,\-?LW#]+\s+[\d*/,\-?LW#]+\s+[\d*/,\-?LW#]+\s+[\d*/,\-?LW#]+\s+[\d*/,\-?LW#]+$/;
9254
+ function isCronExpression(value) {
9255
+ return CRON_RE.test(value.trim());
9256
+ }
9257
+ function parseList(field, min, max) {
9258
+ const out = /* @__PURE__ */ new Set();
9259
+ for (const part of field.split(",")) {
9260
+ const token = part.trim();
9261
+ if (!token) continue;
9262
+ if (token === "*") {
9263
+ for (let i = min; i <= max; i++) out.add(i);
9264
+ continue;
9265
+ }
9266
+ const stepMatch = /^(.+)\/(\d+)$/.exec(token);
9267
+ const base = stepMatch ? stepMatch[1] : token;
9268
+ const step = stepMatch ? Math.max(1, Number(stepMatch[2])) : 1;
9269
+ if (base === "*") {
9270
+ for (let i = min; i <= max; i += step) out.add(i);
9271
+ continue;
9272
+ }
9273
+ const rangeMatch = /^(\d+)-(\d+)$/.exec(base);
9274
+ if (rangeMatch) {
9275
+ const start = Math.max(min, Number(rangeMatch[1]));
9276
+ const end = Math.min(max, Number(rangeMatch[2]));
9277
+ for (let i = start; i <= end; i += step) out.add(i);
9278
+ continue;
9279
+ }
9280
+ const n = Number(base);
9281
+ if (Number.isInteger(n) && n >= min && n <= max) out.add(n);
9282
+ }
9283
+ return out;
9284
+ }
9285
+ function fieldMatches(field, value, min, max) {
9286
+ const trimmed = field.trim();
9287
+ if (trimmed === "*") return true;
9288
+ return parseList(trimmed, min, max).has(value);
9289
+ }
9290
+ function cronMatchesUtc(expr, at) {
9291
+ const parts = expr.trim().split(/\s+/);
9292
+ if (parts.length !== 5) return false;
9293
+ const [minF, hourF, domF, monF, dowF] = parts;
9294
+ return fieldMatches(minF, at.getUTCMinutes(), 0, 59) && fieldMatches(hourF, at.getUTCHours(), 0, 23) && fieldMatches(domF, at.getUTCDate(), 1, 31) && fieldMatches(monF, at.getUTCMonth() + 1, 1, 12) && fieldMatches(dowF, at.getUTCDay(), 0, 6);
9295
+ }
9296
+ var MAX_LOOKAHEAD_MINUTES = 366 * 24 * 60;
9297
+ function truncateToUtcMinute(at) {
9298
+ return new Date(
9299
+ Date.UTC(
9300
+ at.getUTCFullYear(),
9301
+ at.getUTCMonth(),
9302
+ at.getUTCDate(),
9303
+ at.getUTCHours(),
9304
+ at.getUTCMinutes(),
9305
+ 0,
9306
+ 0
9307
+ )
9308
+ );
9309
+ }
9310
+ function computeNextCronFireUtc(expr, after) {
9311
+ if (!isCronExpression(expr)) return null;
9312
+ let cursor = truncateToUtcMinute(after);
9313
+ cursor = new Date(cursor.getTime() + 6e4);
9314
+ for (let i = 0; i < MAX_LOOKAHEAD_MINUTES; i++) {
9315
+ if (cronMatchesUtc(expr, cursor)) return cursor;
9316
+ cursor = new Date(cursor.getTime() + 6e4);
9317
+ }
9318
+ return null;
9319
+ }
9320
+ function computeInitialNextFire(spec, now) {
9321
+ if (spec.scheduleKind === "runAt" && spec.runAt) {
9322
+ const ms = Date.parse(spec.runAt);
9323
+ return Number.isNaN(ms) ? null : new Date(ms).toISOString();
9324
+ }
9325
+ if (spec.scheduleKind === "cron" && spec.cron) {
9326
+ const next = computeNextCronFireUtc(spec.cron.trim(), now);
9327
+ return next ? next.toISOString() : null;
9328
+ }
9329
+ return null;
9330
+ }
9331
+ function advanceRecurringNextFire(spec, fromInclusive) {
9332
+ if (spec.scheduleKind !== "cron" || !spec.cron?.trim()) return null;
9333
+ const next = computeNextCronFireUtc(spec.cron.trim(), fromInclusive);
9334
+ return next ? next.toISOString() : null;
9335
+ }
9336
+
9337
+ // src/cron/cron-store.ts
9338
+ import { promises as fs2 } from "node:fs";
9339
+ async function readFileIfExists(filePath) {
9340
+ try {
9341
+ return await fs2.readFile(filePath, "utf8");
9342
+ } catch (err) {
9343
+ if (err.code === "ENOENT") return null;
9344
+ throw err;
9345
+ }
9346
+ }
9347
+ function parseCronStore(raw) {
9348
+ if (!raw) return [];
9349
+ try {
9350
+ const parsed = JSON.parse(raw);
9351
+ return Array.isArray(parsed.entries) ? parsed.entries : [];
9352
+ } catch {
9353
+ return [];
9354
+ }
9355
+ }
9356
+ async function loadCronJobs(storePath = defaultKynverCronStorePath()) {
9357
+ const raw = await readFileIfExists(storePath);
9358
+ return parseCronStore(raw);
9359
+ }
9360
+
9361
+ // src/cron/cron-tick-state.ts
9362
+ import { randomBytes } from "node:crypto";
9363
+ import { promises as fs3 } from "node:fs";
8868
9364
  import path50 from "node:path";
9365
+ var EMPTY = { version: 1, jobs: {} };
9366
+ async function readFileIfExists2(filePath) {
9367
+ try {
9368
+ return await fs3.readFile(filePath, "utf8");
9369
+ } catch (err) {
9370
+ if (err.code === "ENOENT") return null;
9371
+ throw err;
9372
+ }
9373
+ }
9374
+ function parseCronTickState(raw) {
9375
+ if (!raw) return { ...EMPTY, jobs: { ...EMPTY.jobs } };
9376
+ try {
9377
+ const parsed = JSON.parse(raw);
9378
+ if (parsed?.version !== 1 || typeof parsed.jobs !== "object" || !parsed.jobs) {
9379
+ return { ...EMPTY, jobs: {} };
9380
+ }
9381
+ return parsed;
9382
+ } catch {
9383
+ return { ...EMPTY, jobs: {} };
9384
+ }
9385
+ }
9386
+ async function loadCronTickState(statePath) {
9387
+ const raw = await readFileIfExists2(statePath);
9388
+ return parseCronTickState(raw);
9389
+ }
9390
+ async function writeStateAtomic(statePath, state) {
9391
+ await fs3.mkdir(path50.dirname(statePath), { recursive: true });
9392
+ const suffix = randomBytes(6).toString("hex");
9393
+ const tmp = `${statePath}.tmp-${process.pid}-${Date.now()}-${suffix}`;
9394
+ await fs3.writeFile(tmp, `${JSON.stringify(state, null, 2)}
9395
+ `, "utf8");
9396
+ try {
9397
+ await fs3.rename(tmp, statePath);
9398
+ } catch (err) {
9399
+ const code = err.code;
9400
+ if (code !== "EPERM" && code !== "EEXIST" && code !== "EACCES") throw err;
9401
+ await fs3.unlink(tmp).catch(() => {
9402
+ });
9403
+ }
9404
+ }
9405
+ async function saveCronTickState(statePath, state) {
9406
+ await writeStateAtomic(statePath, state);
9407
+ }
9408
+ function getOrCreateJobState(state, providerScheduleId) {
9409
+ const existing = state.jobs[providerScheduleId];
9410
+ if (existing) return existing;
9411
+ const created = {
9412
+ providerScheduleId,
9413
+ nextFireAt: null,
9414
+ lastFiredAt: null,
9415
+ lastAttemptAt: null,
9416
+ consecutiveFailures: 0,
9417
+ completedAt: null,
9418
+ inflightUntil: null
9419
+ };
9420
+ state.jobs[providerScheduleId] = created;
9421
+ return created;
9422
+ }
9423
+
9424
+ // src/cron/cron-tick.ts
9425
+ function isInflight(job, nowMs) {
9426
+ if (!job.inflightUntil) return false;
9427
+ const until = Date.parse(job.inflightUntil);
9428
+ return !Number.isNaN(until) && until > nowMs;
9429
+ }
9430
+ function isCompleted(job) {
9431
+ return Boolean(job.completedAt);
9432
+ }
9433
+ function backoffReady(job, env, nowMs) {
9434
+ if (job.consecutiveFailures === 0) return true;
9435
+ if (job.consecutiveFailures > env.maxRetries) return false;
9436
+ if (!job.lastAttemptAt) return true;
9437
+ const last = Date.parse(job.lastAttemptAt);
9438
+ if (Number.isNaN(last)) return true;
9439
+ return nowMs - last >= env.retryBackoffMs;
9440
+ }
9441
+ function ensureNextFire(entry, job, now) {
9442
+ if (job.nextFireAt) return job.nextFireAt;
9443
+ const initial = computeInitialNextFire(entry.spec, now);
9444
+ job.nextFireAt = initial;
9445
+ return initial;
9446
+ }
9447
+ function isDue(entry, job, nowMs, env) {
9448
+ if (entry.paused || isCompleted(job) || isInflight(job, nowMs)) return false;
9449
+ if (!backoffReady(job, env, nowMs)) return false;
9450
+ const nextMs = job.nextFireAt ? Date.parse(job.nextFireAt) : NaN;
9451
+ if (Number.isNaN(nextMs)) return false;
9452
+ return nowMs >= nextMs;
9453
+ }
9454
+ function advanceRecurringBeforeFire(entry, job, now) {
9455
+ if (entry.spec.scheduleKind !== "cron") return;
9456
+ job.nextFireAt = advanceRecurringNextFire(entry.spec, now);
9457
+ }
9458
+ async function runKynverCronTick(opts = {}) {
9459
+ const env = opts.env ?? resolveKynverCronEnv();
9460
+ const now = opts.now ?? /* @__PURE__ */ new Date();
9461
+ const nowMs = now.getTime();
9462
+ if (!env.tickEnabled) {
9463
+ return { enabled: false, skipped: "tick_disabled", scanned: 0, due: 0, fired: 0, skippedJobs: 0, errors: 0 };
9464
+ }
9465
+ if (!env.fireBaseUrl || !env.secret) {
9466
+ return {
9467
+ enabled: true,
9468
+ skipped: "missing_fire_credentials",
9469
+ scanned: 0,
9470
+ due: 0,
9471
+ fired: 0,
9472
+ skippedJobs: 0,
9473
+ errors: 0
9474
+ };
9475
+ }
9476
+ const lock = tryAcquireCronTickLock(env.lockPath);
9477
+ if (!lock.acquired) {
9478
+ return {
9479
+ enabled: true,
9480
+ skipped: lock.reason ?? "lock_not_acquired",
9481
+ scanned: 0,
9482
+ due: 0,
9483
+ fired: 0,
9484
+ skippedJobs: 0,
9485
+ errors: 0,
9486
+ lockHeld: true
9487
+ };
9488
+ }
9489
+ try {
9490
+ const entries = await loadCronJobs(env.storePath);
9491
+ const filtered = opts.agentOsIdFilter ? entries.filter((e) => e.spec.target.agentOsId === opts.agentOsIdFilter) : entries;
9492
+ const state = await loadCronTickState(env.statePath);
9493
+ const dueEntries = [];
9494
+ for (const entry of filtered) {
9495
+ const job = getOrCreateJobState(state, entry.providerScheduleId);
9496
+ ensureNextFire(entry, job, now);
9497
+ if (isDue(entry, job, nowMs, env)) {
9498
+ dueEntries.push({ entry, job });
9499
+ }
9500
+ }
9501
+ dueEntries.sort((a, b) => {
9502
+ const aMs = Date.parse(a.job.nextFireAt ?? "") || 0;
9503
+ const bMs = Date.parse(b.job.nextFireAt ?? "") || 0;
9504
+ return aMs - bMs;
9505
+ });
9506
+ let fired = 0;
9507
+ let errors = 0;
9508
+ let skippedJobs = 0;
9509
+ let catchUpBudget = env.maxCatchUpPerTick;
9510
+ for (const { entry, job } of dueEntries) {
9511
+ if (env.missedRunPolicy === "skip" && entry.spec.scheduleKind === "cron") {
9512
+ const next = Date.parse(job.nextFireAt ?? "");
9513
+ if (!Number.isNaN(next) && next < nowMs - env.tickIntervalMs * 2) {
9514
+ job.nextFireAt = advanceRecurringNextFire(entry.spec, now);
9515
+ skippedJobs++;
9516
+ continue;
9517
+ }
9518
+ }
9519
+ if (catchUpBudget <= 0) {
9520
+ skippedJobs++;
9521
+ continue;
9522
+ }
9523
+ job.inflightUntil = new Date(nowMs + env.inflightLeaseMs).toISOString();
9524
+ job.lastAttemptAt = now.toISOString();
9525
+ advanceRecurringBeforeFire(entry, job, now);
9526
+ try {
9527
+ const result = await fireKynverCronJob({
9528
+ entry,
9529
+ baseUrl: env.fireBaseUrl,
9530
+ secret: env.secret,
9531
+ fetchFn: opts.fetchFn
9532
+ });
9533
+ job.inflightUntil = null;
9534
+ if (result.ok) {
9535
+ job.lastFiredAt = now.toISOString();
9536
+ job.consecutiveFailures = 0;
9537
+ if (entry.spec.scheduleKind === "runAt") {
9538
+ job.completedAt = now.toISOString();
9539
+ job.nextFireAt = null;
9540
+ }
9541
+ fired++;
9542
+ catchUpBudget--;
9543
+ } else {
9544
+ job.consecutiveFailures += 1;
9545
+ errors++;
9546
+ }
9547
+ } catch {
9548
+ job.inflightUntil = null;
9549
+ job.consecutiveFailures += 1;
9550
+ errors++;
9551
+ }
9552
+ }
9553
+ await saveCronTickState(env.statePath, state);
9554
+ return {
9555
+ enabled: true,
9556
+ scanned: filtered.length,
9557
+ due: dueEntries.length,
9558
+ fired,
9559
+ skippedJobs,
9560
+ errors
9561
+ };
9562
+ } finally {
9563
+ releaseCronTickLock(env.lockPath);
9564
+ }
9565
+ }
9566
+
9567
+ // src/pipeline-tick.ts
9568
+ import path53 from "node:path";
8869
9569
 
8870
9570
  // src/pipeline-dispatch.ts
8871
9571
  var RESERVED_REVIEW_STARTS = 1;
@@ -8931,35 +9631,72 @@ async function runPipelineDispatch(args, slots) {
8931
9631
  };
8932
9632
  }
8933
9633
 
9634
+ // src/pipeline-exact-targets.ts
9635
+ function operatorExactTargetTaskIds(operatorTick) {
9636
+ if (!operatorTick || typeof operatorTick !== "object") return [];
9637
+ const body = operatorTick;
9638
+ const raw = body.response?.dispatch?.exactTargetTaskIds;
9639
+ if (!Array.isArray(raw)) return [];
9640
+ const seen = /* @__PURE__ */ new Set();
9641
+ const out = [];
9642
+ for (const value of raw) {
9643
+ if (typeof value !== "string") continue;
9644
+ const id = value.trim();
9645
+ if (!id || seen.has(id)) continue;
9646
+ seen.add(id);
9647
+ out.push(id);
9648
+ }
9649
+ return out;
9650
+ }
9651
+
8934
9652
  // src/pipeline-max-starts.ts
8935
9653
  function operatorDispatchFromTick(operatorTick) {
8936
9654
  const body = operatorTick;
8937
9655
  const dispatch = body.response?.dispatch;
8938
9656
  return dispatch && typeof dispatch === "object" ? dispatch : null;
8939
9657
  }
9658
+ function nonNegativeInt(value) {
9659
+ if (typeof value !== "number" || !Number.isFinite(value)) return null;
9660
+ return Math.max(0, Math.floor(value));
9661
+ }
8940
9662
  function resolvePipelineMaxStarts(resourceGate, operatorTick) {
8941
9663
  const dispatch = operatorDispatchFromTick(operatorTick);
8942
- const advised = typeof dispatch?.recommendedMaxStarts === "number" ? Math.max(0, dispatch.recommendedMaxStarts) : null;
9664
+ const advised = nonNegativeInt(dispatch?.recommendedMaxStarts);
9665
+ const actionableReady = nonNegativeInt(dispatch?.actionableReady);
9666
+ const queuedTasks = nonNegativeInt(dispatch?.queuedTasks);
9667
+ const boardAdvancedThisTick = nonNegativeInt(dispatch?.boardAdvancedThisTick) ?? 0;
9668
+ const leaseReapedThisTick = nonNegativeInt(dispatch?.leaseReapedThisTick) ?? 0;
9669
+ const hygieneAdvanced = boardAdvancedThisTick + leaseReapedThisTick;
9670
+ const readyFloor = actionableReady ?? queuedTasks;
8943
9671
  let maxStarts = resourceGate.slotsAvailable;
8944
- if (advised !== null) {
9672
+ if (readyFloor !== null) {
9673
+ maxStarts = Math.min(maxStarts, readyFloor);
9674
+ } else if (advised !== null) {
8945
9675
  maxStarts = Math.min(maxStarts, advised);
8946
9676
  }
8947
- const underutilized = dispatch?.underutilized === true;
8948
- const boardAdvancedThisTick = typeof dispatch?.boardAdvancedThisTick === "number" ? dispatch.boardAdvancedThisTick : 0;
8949
- if (underutilized && resourceGate.slotsAvailable > 0 && maxStarts === 0) {
8950
- const ready = dispatch?.actionableReady ?? dispatch?.queuedTasks ?? (boardAdvancedThisTick > 0 ? boardAdvancedThisTick : 1);
9677
+ if (readyFloor === null && advised !== null) {
9678
+ maxStarts = Math.max(maxStarts, Math.min(resourceGate.slotsAvailable, advised));
9679
+ }
9680
+ const underutilized = dispatch?.underutilized === true || (readyFloor ?? 0) > 0 && resourceGate.slotsAvailable > 0 && resourceGate.maxConcurrentWorkers > 0 && resourceGate.activeWorkers < resourceGate.maxConcurrentWorkers;
9681
+ if (resourceGate.slotsAvailable > 0 && maxStarts === 0 && (underutilized || hygieneAdvanced > 0)) {
9682
+ const ready = readyFloor ?? (hygieneAdvanced > 0 ? hygieneAdvanced : 1);
8951
9683
  maxStarts = Math.min(resourceGate.slotsAvailable, Math.max(1, ready));
8952
9684
  }
9685
+ const nonDispatchableReady = queuedTasks !== null && actionableReady !== null ? Math.max(0, queuedTasks - actionableReady) : null;
8953
9686
  return {
8954
9687
  maxStarts: Math.max(0, maxStarts),
8955
9688
  underutilized,
8956
9689
  advisedStarts: advised,
8957
- boardAdvancedThisTick
9690
+ actionableReady,
9691
+ queuedTasks,
9692
+ nonDispatchableReady,
9693
+ boardAdvancedThisTick,
9694
+ leaseReapedThisTick
8958
9695
  };
8959
9696
  }
8960
9697
 
8961
9698
  // src/plan-progress-daemon-sync.ts
8962
- import path48 from "node:path";
9699
+ import path51 from "node:path";
8963
9700
 
8964
9701
  // src/plan-progress-sync.ts
8965
9702
  async function syncPlanProgress(args) {
@@ -8983,7 +9720,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
8983
9720
  const outcomes = [];
8984
9721
  for (const name of Object.keys(run.workers || {})) {
8985
9722
  const worker = readJson(
8986
- path48.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
9723
+ path51.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
8987
9724
  void 0
8988
9725
  );
8989
9726
  if (!worker?.dispatched || !worker.taskId) continue;
@@ -9033,8 +9770,8 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
9033
9770
 
9034
9771
  // src/installed-package-versions.ts
9035
9772
  import { readFile } from "node:fs/promises";
9036
- import { homedir as homedir12 } from "node:os";
9037
- import path49 from "node:path";
9773
+ import { homedir as homedir13 } from "node:os";
9774
+ import path52 from "node:path";
9038
9775
  var MANAGED_PACKAGES = [
9039
9776
  "@kynver-app/runtime",
9040
9777
  "@kynver-app/openclaw-agent-os",
@@ -9048,13 +9785,13 @@ function unique(values) {
9048
9785
  return [...new Set(values.filter((value) => Boolean(value)))];
9049
9786
  }
9050
9787
  function moduleRoots() {
9051
- const home = homedir12();
9052
- const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path49.join(home, ".openclaw", "npm");
9053
- const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path49.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path49.join(home, ".npm-global", "lib", "node_modules"));
9788
+ const home = homedir13();
9789
+ const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path52.join(home, ".openclaw", "npm");
9790
+ const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path52.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path52.join(home, ".npm-global", "lib", "node_modules"));
9054
9791
  return unique([
9055
- path49.join(openClawPrefix, "lib", "node_modules"),
9056
- path49.join(openClawPrefix, "node_modules"),
9057
- npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path49.join(npmGlobalRoot, "lib", "node_modules")
9792
+ path52.join(openClawPrefix, "lib", "node_modules"),
9793
+ path52.join(openClawPrefix, "node_modules"),
9794
+ npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path52.join(npmGlobalRoot, "lib", "node_modules")
9058
9795
  ]);
9059
9796
  }
9060
9797
  async function readVersion(packageJsonPath) {
@@ -9070,7 +9807,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
9070
9807
  const out = {};
9071
9808
  for (const packageName of MANAGED_PACKAGES) {
9072
9809
  for (const root of roots) {
9073
- const packageJsonPath = path49.join(root, packageName, "package.json");
9810
+ const packageJsonPath = path52.join(root, packageName, "package.json");
9074
9811
  const version = await readVersion(packageJsonPath);
9075
9812
  if (!version) continue;
9076
9813
  out[packageName] = { version, observedAt, path: packageJsonPath };
@@ -9086,11 +9823,11 @@ async function completeFinishedWorkers(runId, args) {
9086
9823
  const outcomes = [];
9087
9824
  for (const name of Object.keys(run.workers || {})) {
9088
9825
  const worker = readJson(
9089
- path50.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
9826
+ path53.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
9090
9827
  void 0
9091
9828
  );
9092
9829
  if (!worker?.taskId || worker.localOnly) continue;
9093
- if (hasCompletionAck(worker)) {
9830
+ if (hasTerminalCompletionAck(worker)) {
9094
9831
  outcomes.push({ worker: name, ok: true, taskId: worker.taskId ?? null, skipped: true });
9095
9832
  continue;
9096
9833
  }
@@ -9159,24 +9896,51 @@ async function runPipelineTick(args) {
9159
9896
  let maxStarts = maxStartsAdvice.maxStarts;
9160
9897
  const sweep = await sweepRun({ run: runId, agentOsId, pipeline: true, ...args });
9161
9898
  let dispatch = null;
9162
- if (execute && maxStarts > 0) {
9163
- dispatch = await runPipelineDispatch(
9899
+ let startedCount = 0;
9900
+ const exactTargetTaskIds = operatorExactTargetTaskIds(operatorTick);
9901
+ let remainingStarts = maxStarts;
9902
+ if (execute && remainingStarts > 0 && exactTargetTaskIds.length > 0) {
9903
+ const exactBudget = Math.min(remainingStarts, exactTargetTaskIds.length);
9904
+ const exact = await runPipelineDispatch(
9905
+ {
9906
+ ...args,
9907
+ run: runId,
9908
+ agentOsId,
9909
+ targetTaskIds: exactTargetTaskIds.join(",")
9910
+ },
9911
+ exactBudget
9912
+ );
9913
+ const exactStarted = countDispatchStarts(exact);
9914
+ startedCount += exactStarted;
9915
+ remainingStarts = Math.max(0, remainingStarts - exactStarted);
9916
+ dispatch = { exactTargetTaskIds, exact, startedCount };
9917
+ }
9918
+ if (execute && remainingStarts > 0) {
9919
+ const broad = await runPipelineDispatch(
9164
9920
  {
9165
9921
  ...args,
9166
9922
  run: runId,
9167
9923
  agentOsId
9168
9924
  },
9169
- maxStarts
9925
+ remainingStarts
9170
9926
  );
9171
- } else {
9172
- dispatch = {
9173
- ok: true,
9174
- skipped: true,
9175
- reason: execute ? dispatchResourceGate.reason ?? "no slots or queued work" : "execute disabled",
9176
- maxStarts: 0
9177
- };
9927
+ const broadStarted = countDispatchStarts(broad);
9928
+ startedCount += broadStarted;
9929
+ dispatch = dispatch && typeof dispatch === "object" ? { ...dispatch, broad, startedCount } : broad;
9930
+ } else if (!execute || maxStarts <= 0) {
9931
+ if (!dispatch) {
9932
+ dispatch = {
9933
+ ok: true,
9934
+ skipped: true,
9935
+ reason: execute ? dispatchResourceGate.reason ?? "no slots or queued work" : "execute disabled",
9936
+ maxStarts: 0,
9937
+ dispatchAdvice: maxStartsAdvice,
9938
+ ...exactTargetTaskIds.length ? { exactTargetTaskIds, exactOnly: true } : {}
9939
+ };
9940
+ }
9941
+ } else if (dispatch && typeof dispatch === "object") {
9942
+ dispatch = { ...dispatch, broadSkipped: true, startedCount };
9178
9943
  }
9179
- const startedCount = dispatch?.startedCount ?? 0;
9180
9944
  const idle = !maxStartsAdvice.underutilized && maxStarts === 0 && completedWorkers.length === 0 && startedCount === 0;
9181
9945
  return {
9182
9946
  runId,
@@ -9192,6 +9956,7 @@ async function runPipelineTick(args) {
9192
9956
  completionAckSync,
9193
9957
  operatorTick,
9194
9958
  sweep,
9959
+ dispatchAdvice: maxStartsAdvice,
9195
9960
  dispatch,
9196
9961
  idle
9197
9962
  };
@@ -9215,8 +9980,18 @@ async function runDaemon(args) {
9215
9980
  stopping = true;
9216
9981
  });
9217
9982
  console.error(JSON.stringify({ event: "daemon_start", runId, agentOsId, execute, intervalMs }));
9983
+ const cronEnv = resolveKynverCronEnv();
9218
9984
  while (!stopping) {
9219
9985
  try {
9986
+ if (cronEnv.tickEnabled) {
9987
+ const cronTick = await runKynverCronTick({
9988
+ env: cronEnv,
9989
+ agentOsIdFilter: agentOsId
9990
+ });
9991
+ if (cronTick.enabled && (cronTick.fired > 0 || cronTick.errors > 0)) {
9992
+ console.error(JSON.stringify({ event: "daemon_cron_tick", ...cronTick }));
9993
+ }
9994
+ }
9220
9995
  const tick = await runPipelineTick({ run: runId, agentOsId, execute, ...args });
9221
9996
  console.error(JSON.stringify({ event: "daemon_tick", ...tick }));
9222
9997
  if (tick.idle) {
@@ -9235,7 +10010,7 @@ async function runDaemon(args) {
9235
10010
  }
9236
10011
 
9237
10012
  // src/plan-progress.ts
9238
- import path51 from "node:path";
10013
+ import path54 from "node:path";
9239
10014
 
9240
10015
  // src/bounded-build/constants.ts
9241
10016
  var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
@@ -9522,7 +10297,7 @@ async function emitPlanProgress(args) {
9522
10297
  }
9523
10298
  function verifyPlanLocal(args) {
9524
10299
  const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
9525
- const cwd = path51.resolve(worktree);
10300
+ const cwd = path54.resolve(worktree);
9526
10301
  const summary = runHarnessVerifyCommands(cwd);
9527
10302
  const emitJson = args.json === true || args.json === "true";
9528
10303
  const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
@@ -9571,9 +10346,9 @@ async function verifyPlan(args) {
9571
10346
  }
9572
10347
 
9573
10348
  // src/harness-verify-cli.ts
9574
- import path52 from "node:path";
10349
+ import path55 from "node:path";
9575
10350
  function runHarnessVerifyCli(args) {
9576
- const cwd = path52.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
10351
+ const cwd = path55.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
9577
10352
  const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
9578
10353
  const commands = [];
9579
10354
  const rawCmd = args.command;
@@ -9617,7 +10392,7 @@ function runHarnessVerifyCli(args) {
9617
10392
  }
9618
10393
 
9619
10394
  // src/plan-persist-cli.ts
9620
- import { readFileSync as readFileSync13 } from "node:fs";
10395
+ import { readFileSync as readFileSync14 } from "node:fs";
9621
10396
  var OPERATIONS = ["create", "add_version", "update_metadata"];
9622
10397
  var FAILURE_KINDS = [
9623
10398
  "approval_guard",
@@ -9629,7 +10404,7 @@ var FAILURE_KINDS = [
9629
10404
  function readBodyArg(args) {
9630
10405
  const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
9631
10406
  if (bodyFile) {
9632
- return { body: readFileSync13(bodyFile, "utf8"), bodyPathHint: bodyFile };
10407
+ return { body: readFileSync14(bodyFile, "utf8"), bodyPathHint: bodyFile };
9633
10408
  }
9634
10409
  const inline = args.body ? String(args.body) : void 0;
9635
10410
  if (inline) return { body: inline };
@@ -10002,7 +10777,7 @@ ${text.slice(0, 800)}`,
10002
10777
  }
10003
10778
 
10004
10779
  // src/monitor/monitor.service.ts
10005
- import path54 from "node:path";
10780
+ import path57 from "node:path";
10006
10781
 
10007
10782
  // src/monitor/monitor.classify.ts
10008
10783
  function classifyWorkerHealth(input) {
@@ -10054,11 +10829,11 @@ function classifyWorkerHealth(input) {
10054
10829
  }
10055
10830
 
10056
10831
  // src/monitor/monitor.store.ts
10057
- import { existsSync as existsSync36, mkdirSync as mkdirSync6, readdirSync as readdirSync11, unlinkSync as unlinkSync2 } from "node:fs";
10058
- import path53 from "node:path";
10832
+ import { existsSync as existsSync39, mkdirSync as mkdirSync6, readdirSync as readdirSync12, unlinkSync as unlinkSync3 } from "node:fs";
10833
+ import path56 from "node:path";
10059
10834
  function monitorsDir() {
10060
10835
  const { harnessRoot } = getHarnessPaths();
10061
- const dir = path53.join(harnessRoot, "monitors");
10836
+ const dir = path56.join(harnessRoot, "monitors");
10062
10837
  mkdirSync6(dir, { recursive: true });
10063
10838
  return dir;
10064
10839
  }
@@ -10066,7 +10841,7 @@ function monitorIdFor(runId, workerName) {
10066
10841
  return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
10067
10842
  }
10068
10843
  function monitorPath(monitorId) {
10069
- return path53.join(monitorsDir(), `${monitorId}.json`);
10844
+ return path56.join(monitorsDir(), `${monitorId}.json`);
10070
10845
  }
10071
10846
  function loadMonitorSession(monitorId) {
10072
10847
  return readJson(monitorPath(monitorId), void 0);
@@ -10076,18 +10851,18 @@ function saveMonitorSession(session) {
10076
10851
  }
10077
10852
  function deleteMonitorSession(monitorId) {
10078
10853
  const file = monitorPath(monitorId);
10079
- if (!existsSync36(file)) return false;
10080
- unlinkSync2(file);
10854
+ if (!existsSync39(file)) return false;
10855
+ unlinkSync3(file);
10081
10856
  return true;
10082
10857
  }
10083
10858
  function listMonitorSessions() {
10084
10859
  const dir = monitorsDir();
10085
- if (!existsSync36(dir)) return [];
10860
+ if (!existsSync39(dir)) return [];
10086
10861
  const entries = [];
10087
- for (const name of readdirSync11(dir)) {
10862
+ for (const name of readdirSync12(dir)) {
10088
10863
  if (!name.endsWith(".json")) continue;
10089
10864
  const session = readJson(
10090
- path53.join(dir, name),
10865
+ path56.join(dir, name),
10091
10866
  void 0
10092
10867
  );
10093
10868
  if (!session?.monitorId) continue;
@@ -10178,7 +10953,7 @@ async function fetchTaskLeasesForWorkers(input) {
10178
10953
  // src/monitor/monitor.service.ts
10179
10954
  function workerRecord2(runId, name) {
10180
10955
  return readJson(
10181
- path54.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
10956
+ path57.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
10182
10957
  void 0
10183
10958
  );
10184
10959
  }
@@ -10384,21 +11159,21 @@ async function runMonitorLoop(args) {
10384
11159
 
10385
11160
  // src/monitor/monitor-spawn.ts
10386
11161
  import { spawn as spawn6 } from "node:child_process";
10387
- import { closeSync as closeSync6, existsSync as existsSync37, openSync as openSync6 } from "node:fs";
10388
- import path55 from "node:path";
11162
+ import { closeSync as closeSync7, existsSync as existsSync40, openSync as openSync7 } from "node:fs";
11163
+ import path58 from "node:path";
10389
11164
  import { fileURLToPath as fileURLToPath4 } from "node:url";
10390
11165
  function resolveDefaultCliPath2() {
10391
- return path55.join(fileURLToPath4(new URL(".", import.meta.url)), "cli.js");
11166
+ return path58.join(fileURLToPath4(new URL(".", import.meta.url)), "cli.js");
10392
11167
  }
10393
11168
  function spawnMonitorSidecar(opts) {
10394
11169
  const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
10395
- if (!existsSync37(cliPath)) return void 0;
11170
+ if (!existsSync40(cliPath)) return void 0;
10396
11171
  const monitorId = monitorIdFor(opts.runId, opts.workerName);
10397
11172
  const { harnessRoot } = getHarnessPaths();
10398
- const logPath = path55.join(harnessRoot, "monitors", `${monitorId}.log`);
11173
+ const logPath = path58.join(harnessRoot, "monitors", `${monitorId}.log`);
10399
11174
  let logFd;
10400
11175
  try {
10401
- logFd = openSync6(logPath, "a");
11176
+ logFd = openSync7(logPath, "a");
10402
11177
  } catch {
10403
11178
  logFd = void 0;
10404
11179
  }
@@ -10438,7 +11213,7 @@ function spawnMonitorSidecar(opts) {
10438
11213
  env: process.env
10439
11214
  })
10440
11215
  );
10441
- if (logFd !== void 0) closeSync6(logFd);
11216
+ if (logFd !== void 0) closeSync7(logFd);
10442
11217
  child.unref();
10443
11218
  const session = {
10444
11219
  monitorId,
@@ -10455,7 +11230,7 @@ function spawnMonitorSidecar(opts) {
10455
11230
  } catch {
10456
11231
  if (logFd !== void 0) {
10457
11232
  try {
10458
- closeSync6(logFd);
11233
+ closeSync7(logFd);
10459
11234
  } catch {
10460
11235
  }
10461
11236
  }
@@ -10515,7 +11290,7 @@ async function monitorTickCli(args) {
10515
11290
  }
10516
11291
 
10517
11292
  // src/post-restart-unblock.ts
10518
- import path56 from "node:path";
11293
+ import path59 from "node:path";
10519
11294
  function skip(runId, worker, taskId, agentOsId, leaseOwner, reason) {
10520
11295
  return { runId, worker, taskId, agentOsId, leaseOwner, action: "skipped", reason };
10521
11296
  }
@@ -10528,7 +11303,7 @@ async function postRestartUnblock(args) {
10528
11303
  const errors = [];
10529
11304
  for (const run of listRunRecords()) {
10530
11305
  for (const name of Object.keys(run.workers ?? {})) {
10531
- const workerPath = path56.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
11306
+ const workerPath = path59.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
10532
11307
  const worker = readJson(workerPath, void 0);
10533
11308
  if (!worker) {
10534
11309
  skipped.push(skip(run.id, name, "", "", "", "worker.json missing"));
@@ -10640,12 +11415,12 @@ async function postRestartUnblockCli(args) {
10640
11415
  }
10641
11416
 
10642
11417
  // src/doctor/runtime-takeover.ts
10643
- import path58 from "node:path";
11418
+ import path61 from "node:path";
10644
11419
 
10645
11420
  // src/doctor/runtime-takeover.probes.ts
10646
- import { accessSync, constants, existsSync as existsSync38, readFileSync as readFileSync14 } from "node:fs";
10647
- import { homedir as homedir13 } from "node:os";
10648
- import path57 from "node:path";
11421
+ import { accessSync, constants, existsSync as existsSync41, readFileSync as readFileSync15 } from "node:fs";
11422
+ import { homedir as homedir14 } from "node:os";
11423
+ import path60 from "node:path";
10649
11424
  import { spawnSync as spawnSync8 } from "node:child_process";
10650
11425
  function captureCommand(bin, args) {
10651
11426
  try {
@@ -10674,7 +11449,7 @@ function tokenPrefix(token) {
10674
11449
  return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
10675
11450
  }
10676
11451
  function isWritable(target) {
10677
- if (!existsSync38(target)) return false;
11452
+ if (!existsSync41(target)) return false;
10678
11453
  try {
10679
11454
  accessSync(target, constants.W_OK);
10680
11455
  return true;
@@ -10687,15 +11462,15 @@ var defaultRuntimeTakeoverProbes = {
10687
11462
  commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
10688
11463
  kynverVersion: (bin) => captureCommand(bin, ["--version"]),
10689
11464
  loadConfig: () => loadUserConfig(),
10690
- configFilePath: () => path57.join(homedir13(), ".kynver", "config.json"),
10691
- credentialsFilePath: () => path57.join(homedir13(), ".kynver", "credentials"),
11465
+ configFilePath: () => path60.join(homedir14(), ".kynver", "config.json"),
11466
+ credentialsFilePath: () => path60.join(homedir14(), ".kynver", "credentials"),
10692
11467
  readCredentials: () => {
10693
- const credPath = path57.join(homedir13(), ".kynver", "credentials");
10694
- if (!existsSync38(credPath)) {
11468
+ const credPath = path60.join(homedir14(), ".kynver", "credentials");
11469
+ if (!existsSync41(credPath)) {
10695
11470
  return { hasApiKey: false };
10696
11471
  }
10697
11472
  try {
10698
- const parsed = JSON.parse(readFileSync14(credPath, "utf8"));
11473
+ const parsed = JSON.parse(readFileSync15(credPath, "utf8"));
10699
11474
  return {
10700
11475
  hasApiKey: Boolean(parsed.apiKey?.trim()),
10701
11476
  runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
@@ -10714,7 +11489,10 @@ var defaultRuntimeTakeoverProbes = {
10714
11489
  kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
10715
11490
  opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
10716
11491
  kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0,
10717
- openclawCronStorePath: Boolean(process.env.OPENCLAW_CRON_STORE_PATH?.trim()),
11492
+ openclawCronStorePath: Boolean(
11493
+ process.env.KYNVER_CRON_STORE_PATH?.trim() || process.env.OPENCLAW_CRON_STORE_PATH?.trim()
11494
+ ),
11495
+ kynverCronDaemonPrimary: isKynverCronDaemonPrimary(),
10718
11496
  qstashTokenPresent: Boolean(process.env.QSTASH_TOKEN?.trim()),
10719
11497
  kynverHostedDeployment: (() => {
10720
11498
  const v = process.env.KYNVER_HOSTED_DEPLOYMENT?.trim().toLowerCase();
@@ -10722,8 +11500,8 @@ var defaultRuntimeTakeoverProbes = {
10722
11500
  })()
10723
11501
  }),
10724
11502
  harnessRoot: () => resolveHarnessRoot(),
10725
- legacyOpenclawHarnessRoot: () => path57.join(homedir13(), ".openclaw", "harness"),
10726
- pathExists: (target) => existsSync38(target),
11503
+ legacyOpenclawHarnessRoot: () => path60.join(homedir14(), ".openclaw", "harness"),
11504
+ pathExists: (target) => existsSync41(target),
10727
11505
  pathWritable: (target) => isWritable(target),
10728
11506
  vercelVersion: () => captureCommand("vercel", ["--version"]),
10729
11507
  vercelWhoami: () => captureCommand("vercel", ["whoami"])
@@ -10743,8 +11521,10 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
10743
11521
  const schedulerDetails = {
10744
11522
  schedulerProvider: env.kynverSchedulerProvider ?? null,
10745
11523
  deploymentSchedulerProvider: ctx.deploymentSchedulerProvider ?? null,
10746
- openclawCronStorePath: Boolean(env.openclawCronStorePath)
11524
+ openclawCronStorePath: Boolean(env.openclawCronStorePath),
11525
+ kynverCronDaemonPrimary: Boolean(env.kynverCronDaemonPrimary)
10747
11526
  };
11527
+ const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
10748
11528
  if (hasQstashCutover(env, ctx) && !hasLocalOpenClawDependency(env, ctx)) {
10749
11529
  const source = env.kynverSchedulerProvider === "qstash" ? "KYNVER_SCHEDULER_PROVIDER=qstash on this host" : "deploymentSchedulerProvider=qstash in ~/.kynver/config.json";
10750
11530
  return check({
@@ -10756,6 +11536,19 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
10756
11536
  });
10757
11537
  }
10758
11538
  if (hasLocalOpenClawDependency(env, ctx)) {
11539
+ if (env.kynverCronDaemonPrimary && daemonDispatchReady) {
11540
+ return check({
11541
+ id: "hotspot_openclaw_scheduler",
11542
+ label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
11543
+ status: "pass",
11544
+ summary: "Kynver Cron local store present; `kynver daemon` owns schedule fires (kynver-cron tick loop)",
11545
+ details: {
11546
+ ...schedulerDetails,
11547
+ dispatchPath: "kynver-daemon-cron-tick",
11548
+ kynverCronDaemonPrimary: true
11549
+ }
11550
+ });
11551
+ }
10759
11552
  const parts = [];
10760
11553
  if (env.kynverSchedulerProvider === "openclaw-cron") {
10761
11554
  parts.push("KYNVER_SCHEDULER_PROVIDER=openclaw-cron");
@@ -10764,21 +11557,20 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
10764
11557
  parts.push("deploymentSchedulerProvider=openclaw-cron in config");
10765
11558
  }
10766
11559
  if (env.openclawCronStorePath) {
10767
- parts.push("OPENCLAW_CRON_STORE_PATH set (local cron bridge)");
11560
+ parts.push("KYNVER_CRON_STORE_PATH or OPENCLAW_CRON_STORE_PATH set (local cron store)");
10768
11561
  }
10769
11562
  return check({
10770
11563
  id: "hotspot_openclaw_scheduler",
10771
11564
  label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
10772
11565
  status: "warn",
10773
- summary: `OpenClaw local cron still active (${parts.join("; ")})`,
10774
- remediation: "On the Kynver deployment: set KYNVER_SCHEDULER_PROVIDER=qstash with QSTASH_TOKEN configured. On user runners: unset KYNVER_SCHEDULER_PROVIDER and OPENCLAW_CRON_STORE_PATH; after Vercel env is updated run `kynver scheduler attest-cutover`.",
11566
+ summary: `Local cron store without daemon tick (${parts.join("; ")})`,
11567
+ remediation: "Run `kynver daemon` with KYNVER_CRON_SECRET + KYNVER_API_URL (or KYNVER_CRON_FIRE_BASE_URL) so the daemon-owned cron tick fires schedules. On hosted deploys use QStash (KYNVER_SCHEDULER_PROVIDER=qstash). Legacy OpenClaw cron env aliases still work during cutover.",
10775
11568
  details: schedulerDetails
10776
11569
  });
10777
11570
  }
10778
11571
  const runnerOpenclaw = env.kynverSchedulerProvider === "openclaw-cron";
10779
11572
  const runnerQstash = env.kynverSchedulerProvider === "qstash";
10780
11573
  const hostedDeployment = Boolean(env.qstashTokenPresent) || Boolean(env.kynverHostedDeployment);
10781
- const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
10782
11574
  const hostedSchedulerProcess = hostedDeployment && !daemonDispatchReady;
10783
11575
  const deploymentNeedsQstash = hostedSchedulerProcess && !env.qstashTokenPresent && env.kynverSchedulerProvider !== "qstash";
10784
11576
  const deploymentOpenclaw = hostedSchedulerProcess && env.kynverSchedulerProvider === "openclaw-cron";
@@ -11051,8 +11843,8 @@ function assessVercelCli(probes) {
11051
11843
  }
11052
11844
  function assessHarnessDirs(probes) {
11053
11845
  const harnessRoot = probes.harnessRoot();
11054
- const runsDir = path58.join(harnessRoot, "runs");
11055
- const worktreesDir = path58.join(harnessRoot, "worktrees");
11846
+ const runsDir = path61.join(harnessRoot, "runs");
11847
+ const worktreesDir = path61.join(harnessRoot, "worktrees");
11056
11848
  const displayHarnessRoot = redactHomePath(harnessRoot);
11057
11849
  const displayRunsDir = redactHomePath(runsDir);
11058
11850
  const displayWorktreesDir = redactHomePath(worktreesDir);
@@ -11255,6 +12047,11 @@ var RUNNER_SCHEDULER_CUTOVER_STEPS = [
11255
12047
  function readSchedulerCutoverEnv(env = process.env) {
11256
12048
  return {
11257
12049
  kynverSchedulerProvider: env.KYNVER_SCHEDULER_PROVIDER?.trim() || null,
12050
+ kynverCronStorePath: env.KYNVER_CRON_STORE_PATH?.trim() || env.OPENCLAW_CRON_STORE_PATH?.trim() || null,
12051
+ kynverCronSecret: Boolean(
12052
+ env.KYNVER_CRON_SECRET?.trim() || env.OPENCLAW_CRON_SECRET?.trim()
12053
+ ),
12054
+ kynverCronFireBaseUrl: env.KYNVER_CRON_FIRE_BASE_URL?.trim() || env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || null,
11258
12055
  openclawCronStorePath: env.OPENCLAW_CRON_STORE_PATH?.trim() || null,
11259
12056
  openclawCronSecret: Boolean(env.OPENCLAW_CRON_SECRET?.trim()),
11260
12057
  openclawCronFireBaseUrl: env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || null
@@ -11265,8 +12062,13 @@ function assessSchedulerCutover(config, env = readSchedulerCutoverEnv()) {
11265
12062
  if (env.kynverSchedulerProvider === "openclaw-cron") {
11266
12063
  blockers.push("Runner still has KYNVER_SCHEDULER_PROVIDER=openclaw-cron");
11267
12064
  }
11268
- if (env.openclawCronStorePath) {
11269
- blockers.push("Runner still has OPENCLAW_CRON_STORE_PATH");
12065
+ if (env.kynverCronStorePath && env.kynverSchedulerProvider !== "kynver-cron") {
12066
+ blockers.push(
12067
+ "Runner has KYNVER_CRON_STORE_PATH but KYNVER_SCHEDULER_PROVIDER is not kynver-cron \u2014 use `kynver daemon` cron tick or unset the store for QStash-only runners"
12068
+ );
12069
+ }
12070
+ if (env.openclawCronStorePath && !env.kynverCronStorePath) {
12071
+ blockers.push("Runner still has legacy OPENCLAW_CRON_STORE_PATH (prefer KYNVER_CRON_STORE_PATH)");
11270
12072
  }
11271
12073
  if (config.deploymentSchedulerProvider === "openclaw-cron") {
11272
12074
  blockers.push("~/.kynver/config.json deploymentSchedulerProvider is still openclaw-cron");
@@ -11288,9 +12090,9 @@ function applySchedulerCutoverAttestation(config) {
11288
12090
  }
11289
12091
 
11290
12092
  // src/scheduler-cutover-cli.ts
11291
- import path59 from "node:path";
11292
- import { homedir as homedir14 } from "node:os";
11293
- var CONFIG_FILE2 = path59.join(homedir14(), ".kynver", "config.json");
12093
+ import path62 from "node:path";
12094
+ import { homedir as homedir15 } from "node:os";
12095
+ var CONFIG_FILE2 = path62.join(homedir15(), ".kynver", "config.json");
11294
12096
  function runSchedulerCutoverCheckCli(json = false) {
11295
12097
  const config = loadUserConfig();
11296
12098
  const report = assessSchedulerCutover(config);
@@ -11318,7 +12120,10 @@ function runSchedulerCutoverCheckCli(json = false) {
11318
12120
  ` KYNVER_SCHEDULER_PROVIDER: ${report.runnerEnv.kynverSchedulerProvider ?? "(unset)"}`
11319
12121
  );
11320
12122
  console.log(
11321
- ` OPENCLAW_CRON_STORE_PATH: ${report.runnerEnv.openclawCronStorePath ?? "(unset)"}`
12123
+ ` KYNVER_CRON_STORE_PATH: ${report.runnerEnv.kynverCronStorePath ?? "(unset)"}`
12124
+ );
12125
+ console.log(
12126
+ ` OPENCLAW_CRON_STORE_PATH (legacy): ${report.runnerEnv.openclawCronStorePath ?? "(unset)"}`
11322
12127
  );
11323
12128
  if (report.blockers.length) {
11324
12129
  console.log("\nBlockers:");
@@ -11365,6 +12170,65 @@ function runSchedulerAttestCutoverCli(json = false) {
11365
12170
  console.log(` config: ${payload.configPath}`);
11366
12171
  }
11367
12172
 
12173
+ // src/cron/cron-status.ts
12174
+ async function buildKynverCronStatusReport(env = resolveKynverCronEnv()) {
12175
+ const jobs = await loadCronJobs(env.storePath).catch(() => []);
12176
+ const state = await loadCronTickState(env.statePath).catch(() => ({ version: 1, jobs: {} }));
12177
+ const credentialsReady = Boolean(env.fireBaseUrl && env.secret);
12178
+ const daemonPrimary = isKynverCronDaemonPrimary(env);
12179
+ let primary = "disabled";
12180
+ if (daemonPrimary) primary = "kynver-cron-daemon";
12181
+ else if (process.env.QSTASH_TOKEN?.trim()) primary = "qstash";
12182
+ return {
12183
+ primary,
12184
+ env: {
12185
+ storePath: env.storePath,
12186
+ statePath: env.statePath,
12187
+ tickEnabled: env.tickEnabled,
12188
+ fireBaseUrl: env.fireBaseUrl,
12189
+ missedRunPolicy: env.missedRunPolicy,
12190
+ maxCatchUpPerTick: env.maxCatchUpPerTick,
12191
+ maxRetries: env.maxRetries
12192
+ },
12193
+ jobCount: jobs.length,
12194
+ stateJobCount: Object.keys(state.jobs).length,
12195
+ credentialsReady,
12196
+ daemonPrimary
12197
+ };
12198
+ }
12199
+
12200
+ // src/cron/cron-tick-cli.ts
12201
+ async function runCronStatusCli(json) {
12202
+ const report = await buildKynverCronStatusReport();
12203
+ if (json) {
12204
+ console.log(JSON.stringify(report, null, 2));
12205
+ return;
12206
+ }
12207
+ console.log(`Kynver Cron primary: ${report.primary}`);
12208
+ console.log(` store: ${report.env.storePath} (${report.jobCount} jobs)`);
12209
+ console.log(` tick state: ${report.env.statePath} (${report.stateJobCount} tracked)`);
12210
+ console.log(` tick enabled: ${report.env.tickEnabled}`);
12211
+ console.log(` fire base URL: ${report.env.fireBaseUrl ?? "(unset)"}`);
12212
+ console.log(` credentials ready: ${report.credentialsReady}`);
12213
+ console.log(` daemon-owned primary: ${report.daemonPrimary}`);
12214
+ }
12215
+ async function runCronTickCli(args) {
12216
+ const agentOsId = typeof args.agentOsId === "string" ? args.agentOsId : void 0;
12217
+ const result = await runKynverCronTick({
12218
+ agentOsIdFilter: agentOsId ?? null
12219
+ });
12220
+ if (args.json === true) {
12221
+ console.log(JSON.stringify(result, null, 2));
12222
+ return;
12223
+ }
12224
+ console.log(
12225
+ JSON.stringify({
12226
+ event: "kynver_cron_tick",
12227
+ ...result
12228
+ })
12229
+ );
12230
+ }
12231
+
11368
12232
  // src/cli.ts
11369
12233
  function isHelpFlag(arg) {
11370
12234
  return arg === "help" || arg === "--help" || arg === "-h";
@@ -11415,6 +12279,8 @@ function usage(code = 0) {
11415
12279
  " kynver doctor runtime-takeover",
11416
12280
  " kynver scheduler cutover-check [--json]",
11417
12281
  " kynver scheduler attest-cutover [--json]",
12282
+ " kynver cron status [--json]",
12283
+ " kynver cron tick [--agent-os-id AOS_ID] [--json]",
11418
12284
  " kynver board contract [--agent-os-id ID] [--base-url URL] [--since ISO] [--limit N]"
11419
12285
  ].join("\n")
11420
12286
  );
@@ -11426,7 +12292,7 @@ async function main(argv = process.argv.slice(2)) {
11426
12292
  const scope = argv.shift();
11427
12293
  let action;
11428
12294
  let rest;
11429
- if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "harness" || scope === "monitor" || scope === "doctor" || scope === "scheduler" || scope === "board") {
12295
+ if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "harness" || scope === "monitor" || scope === "doctor" || scope === "scheduler" || scope === "cron" || scope === "board") {
11430
12296
  action = argv.shift();
11431
12297
  rest = argv;
11432
12298
  } else {
@@ -11459,6 +12325,12 @@ async function main(argv = process.argv.slice(2)) {
11459
12325
  if (scope === "scheduler" && action === "attest-cutover") {
11460
12326
  return runSchedulerAttestCutoverCli(args.json === true);
11461
12327
  }
12328
+ if (scope === "cron" && action === "status") {
12329
+ return void await runCronStatusCli(args.json === true);
12330
+ }
12331
+ if (scope === "cron" && action === "tick") {
12332
+ return void await runCronTickCli(args);
12333
+ }
11462
12334
  if (scope === "board" && action === "contract") {
11463
12335
  return void await runCommandCenterContractCli(args);
11464
12336
  }
@@ -11826,6 +12698,7 @@ export {
11826
12698
  HERMES_OPENAI_CODEX_DEFAULT_MODEL,
11827
12699
  OPENAI_CODEX_PROVIDER,
11828
12700
  PACKAGE_VERSION,
12701
+ RUN_METADATA_ACTIVE_SIGNAL_MS,
11829
12702
  TRANSIENT_OPENAI_CODEX_ERROR_CLASSES,
11830
12703
  applyProductionDatabaseToProcess,
11831
12704
  assessAutoCompleteEligibility,
@@ -11854,6 +12727,7 @@ export {
11854
12727
  classifyVercelUrl,
11855
12728
  classifyWorkerHealth,
11856
12729
  codexProvider,
12730
+ collectFilesystemLiveRunKeys,
11857
12731
  collectVercelEvidence,
11858
12732
  compareProviderCandidates,
11859
12733
  completeWorker,
@@ -11897,6 +12771,7 @@ export {
11897
12771
  isFinishedWorkerStatus,
11898
12772
  isForbiddenWorkerEnvKey,
11899
12773
  isGeneratedHarnessPath,
12774
+ isHarnessRunMetadataPath,
11900
12775
  isInspectableVercelTarget,
11901
12776
  isKynverMonorepoRoot,
11902
12777
  isLandingBlockedWorkerStatus,
@@ -11949,6 +12824,7 @@ export {
11949
12824
  redactHarness,
11950
12825
  redactProviderErrorText,
11951
12826
  remediateDefaultRepo,
12827
+ repairMissingRunMetadata,
11952
12828
  repairNestedRunsPath,
11953
12829
  resolveBaseUrl,
11954
12830
  resolveBoxKindFromEnv,
@@ -11965,6 +12841,7 @@ export {
11965
12841
  resolveWorkerJsonPath,
11966
12842
  runBoundedBuildCheck,
11967
12843
  runDaemon,
12844
+ runDirHasActiveRetentionSignals,
11968
12845
  runHarnessCleanup,
11969
12846
  runHarnessVerifyCommands,
11970
12847
  runMonitorTick,
@@ -11995,6 +12872,7 @@ export {
11995
12872
  validateRunId,
11996
12873
  validateTailLines,
11997
12874
  validateWorkerName,
12875
+ workerDirHasActiveRetentionSignals,
11998
12876
  workerStatus
11999
12877
  };
12000
12878
  //# sourceMappingURL=index.js.map