@kynver-app/runtime 0.1.77 → 0.1.80

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,
@@ -2650,7 +2650,9 @@ function resolveOrchestrationRouting(input) {
2650
2650
  const risk = classifyOrchestrationRisk(task);
2651
2651
  const inventory = resolveInventory({
2652
2652
  inventory: input.inventory,
2653
- 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())
2654
2656
  });
2655
2657
  const explicit = input.preferLowCost === false ? null : input.explicitProvider?.trim().toLowerCase();
2656
2658
  const explicitModel = input.explicitModel?.trim() || void 0;
@@ -3464,6 +3466,7 @@ function buildPrompt(input) {
3464
3466
  "",
3465
3467
  ...input.personaMarkdown?.trim() ? [input.personaMarkdown.trim(), ""] : [],
3466
3468
  ...input.instructionPolicyMarkdown?.trim() ? ["Operating rules (Lane A \u2014 from AgentOS memory policy):", input.instructionPolicyMarkdown.trim(), ""] : [],
3469
+ ...input.memoryQualityMarkdown?.trim() ? [input.memoryQualityMarkdown.trim(), ""] : [],
3467
3470
  ...input.repairTargetPrUrl ? [
3468
3471
  ...repairTargetPromptLines({
3469
3472
  targetPrUrl: input.repairTargetPrUrl,
@@ -4258,8 +4261,8 @@ function dirtyPathsCoveredByDisposableRemoval(changedFiles, removed) {
4258
4261
  if (removed.length === 0) return false;
4259
4262
  const removedSet = new Set(removed.map((p) => normalizeRelativePath(p)));
4260
4263
  return material.every((line) => {
4261
- const path60 = normalizeRelativePath(pathFromGitStatusLine(line));
4262
- return removedSet.has(path60);
4264
+ const path63 = normalizeRelativePath(pathFromGitStatusLine(line));
4265
+ return removedSet.has(path63);
4263
4266
  });
4264
4267
  }
4265
4268
 
@@ -4681,7 +4684,9 @@ async function completeWorker(args) {
4681
4684
  }
4682
4685
  }
4683
4686
  function workerStatus(args) {
4684
- 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);
4685
4690
  const run = loadRun(worker.runId);
4686
4691
  const status = computeWorkerStatus(worker, workerStatusOptions(run));
4687
4692
  writeJson(path17.join(worker.workerDir, "last-status.json"), status);
@@ -5116,6 +5121,7 @@ function spawnWorkerProcess(run, opts) {
5116
5121
  planId: opts.planId,
5117
5122
  taskId: opts.taskId,
5118
5123
  instructionPolicyMarkdown: opts.instructionPolicyMarkdown,
5124
+ memoryQualityMarkdown: opts.memoryQualityPromptMarkdown ?? opts.memoryQualityCapture?.promptMarkdown ?? null,
5119
5125
  personaMarkdown: opts.personaMarkdown,
5120
5126
  model: launchModel,
5121
5127
  repairTargetPrUrl: opts.repairTargetPrUrl,
@@ -5894,11 +5900,13 @@ function readHarnessWorkerContext(decision) {
5894
5900
  const personaEvidence = ctx.personaEvidence && typeof ctx.personaEvidence === "object" ? ctx.personaEvidence : null;
5895
5901
  const personaInjectionReady = ctx.personaInjectionReady === true;
5896
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;
5897
5904
  return {
5898
5905
  instructionPolicyMarkdown: markdown,
5899
5906
  instructionPolicyFingerprint: fingerprint,
5900
5907
  instructionPolicyEvidence: evidence,
5901
5908
  memoryQualityCapture,
5909
+ memoryQualityPromptMarkdown,
5902
5910
  personaMarkdown,
5903
5911
  personaSlug,
5904
5912
  personaEvidence,
@@ -6155,6 +6163,7 @@ async function dispatchRun(args) {
6155
6163
  instructionPolicyFingerprint: harnessContext?.instructionPolicyFingerprint ?? null,
6156
6164
  instructionPolicyEvidence: harnessContext?.instructionPolicyEvidence ?? null,
6157
6165
  memoryQualityCapture: harnessContext?.memoryQualityCapture ?? null,
6166
+ memoryQualityPromptMarkdown: harnessContext?.memoryQualityPromptMarkdown ?? null,
6158
6167
  personaMarkdown: harnessContext?.personaMarkdown ?? null,
6159
6168
  personaSlug: harnessContext?.personaSlug ?? expectedPersona,
6160
6169
  personaEvidence: harnessContext?.personaEvidence ?? null,
@@ -6741,15 +6750,15 @@ function validateTailLines(lines) {
6741
6750
  }
6742
6751
 
6743
6752
  // src/worktree.ts
6744
- import { existsSync as existsSync25, mkdirSync as mkdirSync5 } from "node:fs";
6745
- import path32 from "node:path";
6753
+ import { existsSync as existsSync26, mkdirSync as mkdirSync5 } from "node:fs";
6754
+ import path33 from "node:path";
6746
6755
 
6747
6756
  // src/run-list.ts
6748
- import { existsSync as existsSync24, readFileSync as readFileSync12 } from "node:fs";
6749
- import path30 from "node:path";
6757
+ import { existsSync as existsSync25, readFileSync as readFileSync12 } from "node:fs";
6758
+ import path32 from "node:path";
6750
6759
 
6751
6760
  // src/stale-reconcile.ts
6752
- import path29 from "node:path";
6761
+ import path31 from "node:path";
6753
6762
 
6754
6763
  // src/finalize.ts
6755
6764
  import path26 from "node:path";
@@ -6810,8 +6819,8 @@ function finalizeStaleRuns() {
6810
6819
  }
6811
6820
 
6812
6821
  // src/worker-metadata-reconcile.ts
6813
- import { existsSync as existsSync23, lstatSync, readdirSync as readdirSync5, readlinkSync, renameSync as renameSync2, rmSync } from "node:fs";
6814
- 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";
6815
6824
 
6816
6825
  // src/worker-metadata-paths.ts
6817
6826
  import path27 from "node:path";
@@ -6866,6 +6875,219 @@ function resolveWorkerJsonPath(input) {
6866
6875
  return canonical;
6867
6876
  }
6868
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
+
6869
7091
  // src/worker-metadata-reconcile.ts
6870
7092
  function materializeSymlinkedRunDir(harnessRoot, runId) {
6871
7093
  const canonical = canonicalRunDir(harnessRoot, runId);
@@ -6876,7 +7098,10 @@ function materializeSymlinkedRunDir(harnessRoot, runId) {
6876
7098
  return null;
6877
7099
  }
6878
7100
  if (!stat.isSymbolicLink()) return null;
6879
- 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
+ }
6880
7105
  const staging = `${canonical}.materialize-${Date.now()}`;
6881
7106
  renameSync2(linkedTarget, staging);
6882
7107
  rmSync(canonical);
@@ -6886,9 +7111,9 @@ function materializeSymlinkedRunDir(harnessRoot, runId) {
6886
7111
  function relocateArtifacts(input) {
6887
7112
  const moved = [];
6888
7113
  for (const fileName of workerArtifactFileNames()) {
6889
- const from = path28.join(input.fromDir, fileName);
6890
- const to = path28.join(input.toDir, fileName);
6891
- 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;
6892
7117
  renameSync2(from, to);
6893
7118
  moved.push(fileName);
6894
7119
  }
@@ -6927,8 +7152,8 @@ function deriveSynthesizedStatus(input) {
6927
7152
  function synthesizeWorkerFromArtifacts(input) {
6928
7153
  const artifacts = workerArtifactPaths(input.canonicalDir);
6929
7154
  const lastStatus = readJson(artifacts.lastStatusPath, void 0);
6930
- const stdoutExists = existsSync23(artifacts.stdoutPath);
6931
- const heartbeatExists = existsSync23(artifacts.heartbeatPath);
7155
+ const stdoutExists = existsSync24(artifacts.stdoutPath);
7156
+ const heartbeatExists = existsSync24(artifacts.heartbeatPath);
6932
7157
  const hasArtifacts = stdoutExists || heartbeatExists || lastStatus != null;
6933
7158
  if (!hasArtifacts) return null;
6934
7159
  const parsedStdout = stdoutExists ? parseHarnessStream(artifacts.stdoutPath) : { finalResult: null };
@@ -6944,7 +7169,7 @@ function synthesizeWorkerFromArtifacts(input) {
6944
7169
  runId: input.run.id,
6945
7170
  status,
6946
7171
  branch: typeof lastStatus?.branch === "string" ? lastStatus.branch : `agent/${input.run.id}/${input.workerName}`,
6947
- 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),
6948
7173
  workerDir: input.canonicalDir,
6949
7174
  stdoutPath: artifacts.stdoutPath,
6950
7175
  stderrPath: artifacts.stderrPath,
@@ -6971,7 +7196,7 @@ function repairRunWorkerIndex(run, harnessRoot) {
6971
7196
  const nextWorkers = { ...run.workers || {} };
6972
7197
  for (const [name, ref] of Object.entries(nextWorkers)) {
6973
7198
  const canonicalDir = canonicalWorkerDir(harnessRoot, run.id, name);
6974
- const canonicalStatus = path28.join(canonicalDir, "worker.json");
7199
+ const canonicalStatus = path30.join(canonicalDir, "worker.json");
6975
7200
  const nested = hasNestedRunsSegment(ref.workerDir) || hasNestedRunsSegment(ref.statusPath) || ref.workerDir !== canonicalDir || ref.statusPath !== canonicalStatus;
6976
7201
  if (!nested) continue;
6977
7202
  nextWorkers[name] = { workerDir: canonicalDir, statusPath: canonicalStatus };
@@ -7004,7 +7229,7 @@ function reconcileOneWorker(input) {
7004
7229
  statusPath: input.statusPath
7005
7230
  });
7006
7231
  let worker = readJson(workerJsonPath, void 0);
7007
- if (!worker && existsSync23(canonicalArtifacts.workerJsonPath)) {
7232
+ if (!worker && existsSync24(canonicalArtifacts.workerJsonPath)) {
7008
7233
  worker = readJson(canonicalArtifacts.workerJsonPath, void 0);
7009
7234
  }
7010
7235
  if (!worker) {
@@ -7024,15 +7249,15 @@ function reconcileOneWorker(input) {
7024
7249
  });
7025
7250
  return outcomes;
7026
7251
  }
7027
- const dirExists = existsSync23(canonicalDir);
7028
- 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)));
7029
7254
  if (dirExists && !hasAnyArtifact) {
7030
7255
  const abandoned = {
7031
7256
  name: input.workerName,
7032
7257
  runId: input.run.id,
7033
7258
  status: "exited",
7034
7259
  branch: `agent/${input.run.id}/${input.workerName}`,
7035
- worktreePath: path28.join(getPaths().worktreesDir, input.run.id, input.workerName),
7260
+ worktreePath: path30.join(getPaths().worktreesDir, input.run.id, input.workerName),
7036
7261
  workerDir: canonicalDir,
7037
7262
  stdoutPath: canonicalArtifacts.stdoutPath,
7038
7263
  stderrPath: canonicalArtifacts.stderrPath,
@@ -7079,11 +7304,11 @@ function reconcileOneWorker(input) {
7079
7304
  return outcomes;
7080
7305
  }
7081
7306
  function listOrphanWorkerNames(run, harnessRoot) {
7082
- const workersDir = path28.join(runDirectory(run.id), "workers");
7083
- if (!existsSync23(workersDir)) return [];
7307
+ const workersDir = path30.join(runDirectory(run.id), "workers");
7308
+ if (!existsSync24(workersDir)) return [];
7084
7309
  const indexed = new Set(Object.keys(run.workers || {}));
7085
7310
  const orphans = [];
7086
- for (const entry of readdirSync5(workersDir, { withFileTypes: true })) {
7311
+ for (const entry of readdirSync6(workersDir, { withFileTypes: true })) {
7087
7312
  if (!entry.isDirectory()) continue;
7088
7313
  if (indexed.has(entry.name)) continue;
7089
7314
  orphans.push(entry.name);
@@ -7092,6 +7317,7 @@ function listOrphanWorkerNames(run, harnessRoot) {
7092
7317
  }
7093
7318
  function reconcileWorkerMetadata() {
7094
7319
  const { harnessRoot } = getPaths();
7320
+ const runMetadataRetention = repairMissingRunMetadata(harnessRoot);
7095
7321
  const outcomes = [];
7096
7322
  for (const run of listRunRecords()) {
7097
7323
  const materializedFrom = materializeSymlinkedRunDir(harnessRoot, run.id);
@@ -7118,7 +7344,7 @@ function reconcileWorkerMetadata() {
7118
7344
  ...run.workers || {},
7119
7345
  [orphan]: {
7120
7346
  workerDir: canonicalWorkerDir(harnessRoot, run.id, orphan),
7121
- statusPath: path28.join(canonicalWorkerDir(harnessRoot, run.id, orphan), "worker.json")
7347
+ statusPath: path30.join(canonicalWorkerDir(harnessRoot, run.id, orphan), "worker.json")
7122
7348
  }
7123
7349
  };
7124
7350
  saveRun(run);
@@ -7141,7 +7367,7 @@ function reconcileWorkerMetadata() {
7141
7367
  );
7142
7368
  }
7143
7369
  }
7144
- return { workers: outcomes };
7370
+ return { workers: outcomes, runMetadataRetention };
7145
7371
  }
7146
7372
  function reconcileWorkerMetadataCli() {
7147
7373
  const result = reconcileWorkerMetadata();
@@ -7149,7 +7375,22 @@ function reconcileWorkerMetadataCli() {
7149
7375
  acc[row.action] = (acc[row.action] ?? 0) + 1;
7150
7376
  return acc;
7151
7377
  }, {});
7152
- 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
+ );
7153
7394
  }
7154
7395
 
7155
7396
  // src/stale-reconcile.ts
@@ -7166,7 +7407,7 @@ function reconcileStaleWorkers() {
7166
7407
  const now = Date.now();
7167
7408
  for (const run of listRunRecords()) {
7168
7409
  for (const name of Object.keys(run.workers || {})) {
7169
- 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");
7170
7411
  const worker = readJson(workerPath, void 0);
7171
7412
  if (!worker || worker.status !== "running") {
7172
7413
  outcomes.push({
@@ -7249,16 +7490,28 @@ function reconcileRunsCli() {
7249
7490
  acc[row.action] = (acc[row.action] ?? 0) + 1;
7250
7491
  return acc;
7251
7492
  }, {});
7493
+ const runRetentionTotals = result.metadataReconcile.runMetadataRetention.runs.reduce((acc, row) => {
7494
+ acc[row.action] = (acc[row.action] ?? 0) + 1;
7495
+ return acc;
7496
+ }, {});
7252
7497
  console.log(
7253
7498
  JSON.stringify(
7254
7499
  {
7255
7500
  ok: true,
7256
7501
  workers: { markedExited, killedStale, skipped, total: result.workers.length },
7257
- 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
+ },
7258
7510
  finalizedRuns: result.finalizedRuns.length,
7259
7511
  details: {
7260
7512
  workers: result.workers,
7261
7513
  metadataReconcile: result.metadataReconcile.workers,
7514
+ runMetadataRetention: result.metadataReconcile.runMetadataRetention.runs,
7262
7515
  finalizedRuns: result.finalizedRuns
7263
7516
  }
7264
7517
  },
@@ -7270,7 +7523,7 @@ function reconcileRunsCli() {
7270
7523
 
7271
7524
  // src/run-list.ts
7272
7525
  function heartbeatByteLength(heartbeatPath) {
7273
- if (!heartbeatPath || !existsSync24(heartbeatPath)) return 0;
7526
+ if (!heartbeatPath || !existsSync25(heartbeatPath)) return 0;
7274
7527
  try {
7275
7528
  return readFileSync12(heartbeatPath, "utf8").trim().length;
7276
7529
  } catch {
@@ -7278,7 +7531,7 @@ function heartbeatByteLength(heartbeatPath) {
7278
7531
  }
7279
7532
  }
7280
7533
  function workerEvidence(run, workerName) {
7281
- 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");
7282
7535
  const worker = readJson(workerPath, void 0);
7283
7536
  if (!worker) {
7284
7537
  return {
@@ -7335,7 +7588,7 @@ function aggregateRunAttention(workers) {
7335
7588
  function countOpenWorkers(run) {
7336
7589
  let open = 0;
7337
7590
  for (const name of Object.keys(run.workers || {})) {
7338
- 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");
7339
7592
  const worker = readJson(workerPath, void 0);
7340
7593
  if (!worker) continue;
7341
7594
  const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
@@ -7383,75 +7636,6 @@ function listRunsCli() {
7383
7636
  console.log(JSON.stringify(buildRunListRows(), null, 2));
7384
7637
  }
7385
7638
 
7386
- // src/default-repo.ts
7387
- import path31 from "node:path";
7388
- function expandConfiguredRepo(value) {
7389
- return path31.resolve(resolveUserPath(value.trim()));
7390
- }
7391
- function fromConfigured(value, source, persistedInConfig) {
7392
- const trimmed = value?.trim();
7393
- if (!trimmed) return null;
7394
- return {
7395
- repo: expandConfiguredRepo(trimmed),
7396
- source,
7397
- persistedInConfig
7398
- };
7399
- }
7400
- function resolveDefaultRepo(opts = {}) {
7401
- const env = opts.env ?? process.env;
7402
- const config = opts.config ?? loadUserConfig();
7403
- const fromConfig = fromConfigured(config.defaultRepo, "config", true);
7404
- if (fromConfig) return fromConfig;
7405
- const fromDefaultEnv = fromConfigured(env.KYNVER_DEFAULT_REPO, "env_default_repo", false);
7406
- if (fromDefaultEnv) return fromDefaultEnv;
7407
- const fromHarnessEnv = fromConfigured(env.KYNVER_HARNESS_REPO, "env_harness_repo", false);
7408
- if (fromHarnessEnv) return fromHarnessEnv;
7409
- const discovered = discoverDefaultRepo({
7410
- cwd: opts.cwd,
7411
- runtimeModuleUrl: opts.runtimeModuleUrl
7412
- });
7413
- if (!discovered) return null;
7414
- return {
7415
- repo: discovered.repo,
7416
- source: discovered.source,
7417
- persistedInConfig: false
7418
- };
7419
- }
7420
- function persistDefaultRepo(repo, existing) {
7421
- const config = {
7422
- ...existing ?? loadUserConfig(),
7423
- defaultRepo: redactHomePath(path31.resolve(repo))
7424
- };
7425
- saveUserConfig(config);
7426
- return config;
7427
- }
7428
- function remediateDefaultRepo(opts) {
7429
- const existing = opts?.config ?? loadUserConfig();
7430
- const resolved = resolveDefaultRepo({ ...opts, config: existing });
7431
- if (!resolved) {
7432
- return {
7433
- ok: false,
7434
- 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)."
7435
- };
7436
- }
7437
- if (resolved.persistedInConfig) {
7438
- return { ok: true, resolved, config: existing };
7439
- }
7440
- const config = persistDefaultRepo(resolved.repo, existing);
7441
- return {
7442
- ok: true,
7443
- resolved: { ...resolved, persistedInConfig: true, source: "config" },
7444
- config
7445
- };
7446
- }
7447
- function formatResolvedDefaultRepo(resolved) {
7448
- return {
7449
- defaultRepo: displayUserPath(resolved.repo),
7450
- source: resolved.source,
7451
- persistedInConfig: resolved.persistedInConfig
7452
- };
7453
- }
7454
-
7455
7639
  // src/worktree.ts
7456
7640
  function resolveCreateRunRepo(args) {
7457
7641
  const explicit = typeof args.repo === "string" ? args.repo.trim() : "";
@@ -7466,7 +7650,7 @@ function createRun(args) {
7466
7650
  ensureGitRepo(repo);
7467
7651
  const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
7468
7652
  const dir = runDirectory(id);
7469
- if (existsSync25(dir)) failExists(`run already exists: ${id}`);
7653
+ if (existsSync26(dir)) failExists(`run already exists: ${id}`);
7470
7654
  mkdirSync5(dir, { recursive: true });
7471
7655
  const base = String(args.base || "origin/main");
7472
7656
  const baseCommit = git(repo, ["rev-parse", base]).trim();
@@ -7480,7 +7664,7 @@ function createRun(args) {
7480
7664
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7481
7665
  workers: {}
7482
7666
  };
7483
- writeJson(path32.join(dir, "run.json"), run);
7667
+ writeJson(path33.join(dir, "run.json"), run);
7484
7668
  console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
7485
7669
  }
7486
7670
  function listRuns() {
@@ -7492,7 +7676,7 @@ function failExists(message) {
7492
7676
  }
7493
7677
 
7494
7678
  // src/sweep.ts
7495
- import path33 from "node:path";
7679
+ import path34 from "node:path";
7496
7680
  async function sweepRun(args) {
7497
7681
  const pipeline = args.pipeline === true || args.pipeline === "true";
7498
7682
  try {
@@ -7505,7 +7689,7 @@ async function sweepRun(args) {
7505
7689
  const releasedLocalOrphans = [];
7506
7690
  for (const name of Object.keys(run.workers || {})) {
7507
7691
  const worker = readJson(
7508
- path33.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
7692
+ path34.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
7509
7693
  void 0
7510
7694
  );
7511
7695
  if (!worker || !worker.dispatched || !worker.taskId) continue;
@@ -7553,14 +7737,14 @@ async function sweepRun(args) {
7553
7737
  }
7554
7738
 
7555
7739
  // src/harness-storage-snapshot.ts
7556
- import { existsSync as existsSync27, readdirSync as readdirSync7, statSync as statSync5 } from "node:fs";
7557
- 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";
7558
7742
 
7559
7743
  // src/cleanup-dir-size.ts
7560
- import { existsSync as existsSync26, readdirSync as readdirSync6, statSync as statSync4 } from "node:fs";
7561
- 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";
7562
7746
  function directorySizeBytes(root, maxEntries = 5e4) {
7563
- if (!existsSync26(root)) return 0;
7747
+ if (!existsSync27(root)) return 0;
7564
7748
  let total = 0;
7565
7749
  let seen = 0;
7566
7750
  const stack = [root];
@@ -7568,16 +7752,16 @@ function directorySizeBytes(root, maxEntries = 5e4) {
7568
7752
  const current = stack.pop();
7569
7753
  let entries;
7570
7754
  try {
7571
- entries = readdirSync6(current);
7755
+ entries = readdirSync7(current);
7572
7756
  } catch {
7573
7757
  continue;
7574
7758
  }
7575
7759
  for (const name of entries) {
7576
7760
  if (seen++ > maxEntries) return null;
7577
- const full = path34.join(current, name);
7761
+ const full = path35.join(current, name);
7578
7762
  let st;
7579
7763
  try {
7580
- st = statSync4(full);
7764
+ st = statSync5(full);
7581
7765
  } catch {
7582
7766
  continue;
7583
7767
  }
@@ -7594,7 +7778,7 @@ function harnessStorageSnapshot(opts = {}) {
7594
7778
  const worktreesDir = harnessWorktreesDir(harnessRoot);
7595
7779
  const now = opts.now ?? Date.now();
7596
7780
  const scannedAt = new Date(now).toISOString();
7597
- if (!existsSync27(worktreesDir)) {
7781
+ if (!existsSync28(worktreesDir)) {
7598
7782
  return {
7599
7783
  harnessRoot,
7600
7784
  worktreesDir,
@@ -7611,7 +7795,7 @@ function harnessStorageSnapshot(opts = {}) {
7611
7795
  let oldestMs = null;
7612
7796
  let entries;
7613
7797
  try {
7614
- entries = readdirSync7(worktreesDir, { withFileTypes: true });
7798
+ entries = readdirSync8(worktreesDir, { withFileTypes: true });
7615
7799
  } catch {
7616
7800
  return {
7617
7801
  harnessRoot,
@@ -7626,14 +7810,14 @@ function harnessStorageSnapshot(opts = {}) {
7626
7810
  for (const runEntry of entries) {
7627
7811
  if (!runEntry.isDirectory()) continue;
7628
7812
  runCount += 1;
7629
- const runPath = path35.join(worktreesDir, runEntry.name);
7813
+ const runPath = path36.join(worktreesDir, runEntry.name);
7630
7814
  try {
7631
- const st = statSync5(runPath);
7815
+ const st = statSync6(runPath);
7632
7816
  oldestMs = oldestMs === null ? st.mtimeMs : Math.min(oldestMs, st.mtimeMs);
7633
7817
  } catch {
7634
7818
  }
7635
7819
  try {
7636
- for (const workerEntry of readdirSync7(runPath, { withFileTypes: true })) {
7820
+ for (const workerEntry of readdirSync8(runPath, { withFileTypes: true })) {
7637
7821
  if (workerEntry.isDirectory()) workerCount += 1;
7638
7822
  }
7639
7823
  } catch {
@@ -7661,10 +7845,10 @@ function harnessStorageSnapshot(opts = {}) {
7661
7845
  }
7662
7846
 
7663
7847
  // src/cleanup.ts
7664
- import path46 from "node:path";
7848
+ import path47 from "node:path";
7665
7849
 
7666
7850
  // src/cleanup-guards.ts
7667
- import path36 from "node:path";
7851
+ import path37 from "node:path";
7668
7852
 
7669
7853
  // src/cleanup-run-liveness.ts
7670
7854
  function isWorkerProcessLive(indexed) {
@@ -7788,7 +7972,7 @@ function skipWorktreeRemoval(input) {
7788
7972
  function skipDependencyCacheRemoval(input) {
7789
7973
  const { indexed, nodeModulesAgeMs, ageMs, worktreePath, activeWorktreePaths, diskPressure } = input;
7790
7974
  if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
7791
- if (activeWorktreePaths.has(path36.resolve(worktreePath))) return "active_worker";
7975
+ if (activeWorktreePaths.has(path37.resolve(worktreePath))) return "active_worker";
7792
7976
  if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
7793
7977
  if (indexed && hasUnrestorableWorktreeChanges(indexed.status)) return "dirty_worktree";
7794
7978
  return null;
@@ -7815,11 +7999,11 @@ var LIVE_SKIP_REASONS = /* @__PURE__ */ new Set([
7815
7999
  function collectPreservedLivePaths(actions, skips) {
7816
8000
  const out = [];
7817
8001
  const seen = /* @__PURE__ */ new Set();
7818
- const push = (path60, reason, detail) => {
7819
- const key = `${path60}\0${reason}`;
8002
+ const push = (path63, reason, detail) => {
8003
+ const key = `${path63}\0${reason}`;
7820
8004
  if (seen.has(key) || out.length >= MAX_PRESERVED_LIVE_PATH_SAMPLES) return;
7821
8005
  seen.add(key);
7822
- out.push({ path: path60, reason, ...detail ? { detail } : {} });
8006
+ out.push({ path: path63, reason, ...detail ? { detail } : {} });
7823
8007
  };
7824
8008
  for (const skip2 of skips) {
7825
8009
  if (!LIVE_SKIP_REASONS.has(skip2.reason)) continue;
@@ -7834,11 +8018,11 @@ function collectPreservedLivePaths(actions, skips) {
7834
8018
  }
7835
8019
 
7836
8020
  // src/cleanup-run-directory.ts
7837
- import { existsSync as existsSync28, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
7838
- 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";
7839
8023
 
7840
8024
  // src/cleanup-active-worktrees.ts
7841
- import path37 from "node:path";
8025
+ import path38 from "node:path";
7842
8026
  function isActiveHarnessWorker2(worker, runBase, runBaseCommit) {
7843
8027
  const status = computeWorkerStatus(worker, { base: runBase, baseCommit: runBaseCommit });
7844
8028
  return status.alive && !status.finalResult && status.attention.state !== "done";
@@ -7851,17 +8035,20 @@ function collectActiveWorktreeGuards(harnessRoots) {
7851
8035
  let runHasLive = false;
7852
8036
  for (const name of Object.keys(run.workers || {})) {
7853
8037
  const worker = readJson(
7854
- path37.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
8038
+ path38.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
7855
8039
  void 0
7856
8040
  );
7857
8041
  if (!worker?.worktreePath) continue;
7858
- const worktreePath = path37.resolve(worker.worktreePath);
8042
+ const worktreePath = path38.resolve(worker.worktreePath);
7859
8043
  if (!isActiveHarnessWorker2(worker, run.base, run.baseCommit)) continue;
7860
8044
  runHasLive = true;
7861
8045
  activeWorktreePaths.add(worktreePath);
7862
8046
  }
7863
8047
  if (runHasLive) liveRunKeys.add(`${harnessRoot}\0${run.id}`);
7864
8048
  }
8049
+ for (const key of collectFilesystemLiveRunKeys(harnessRoot)) {
8050
+ liveRunKeys.add(key);
8051
+ }
7865
8052
  }
7866
8053
  return { activeWorktreePaths, liveRunKeys };
7867
8054
  }
@@ -7873,20 +8060,20 @@ function isWorktreeOnLiveRun(worktreePath, harnessRoot, runId, liveRunKeys) {
7873
8060
  // src/cleanup-run-directory.ts
7874
8061
  function pathAgeMs(target, now) {
7875
8062
  try {
7876
- const mtime = statSync6(target).mtimeMs;
8063
+ const mtime = statSync7(target).mtimeMs;
7877
8064
  return Math.max(0, now - mtime);
7878
8065
  } catch {
7879
8066
  return 0;
7880
8067
  }
7881
8068
  }
7882
8069
  function loadRunStatus(harnessRoot, runId) {
7883
- const runPath = path38.join(harnessRoot, "runs", runId, "run.json");
7884
- if (!existsSync28(runPath)) return null;
8070
+ const runPath = path39.join(harnessRoot, "runs", runId, "run.json");
8071
+ if (!existsSync29(runPath)) return null;
7885
8072
  return readJson(runPath, null);
7886
8073
  }
7887
8074
  function runDirectoryIsEmpty(runPath) {
7888
8075
  try {
7889
- const entries = readdirSync8(runPath);
8076
+ const entries = readdirSync9(runPath);
7890
8077
  return entries.length === 0;
7891
8078
  } catch {
7892
8079
  return false;
@@ -7904,11 +8091,11 @@ function skipRunDirectoryRemoval(input) {
7904
8091
  return null;
7905
8092
  }
7906
8093
  function scanStaleRunDirectoryCandidates(opts) {
7907
- if (!existsSync28(opts.worktreesDir)) return [];
8094
+ if (!existsSync29(opts.worktreesDir)) return [];
7908
8095
  const candidates = [];
7909
8096
  let entries;
7910
8097
  try {
7911
- entries = readdirSync8(opts.worktreesDir, { withFileTypes: true });
8098
+ entries = readdirSync9(opts.worktreesDir, { withFileTypes: true });
7912
8099
  } catch {
7913
8100
  return [];
7914
8101
  }
@@ -7916,7 +8103,7 @@ function scanStaleRunDirectoryCandidates(opts) {
7916
8103
  if (!runEntry.isDirectory()) continue;
7917
8104
  const runId = runEntry.name;
7918
8105
  if (opts.runIdFilter && runId !== opts.runIdFilter) continue;
7919
- const runPath = path38.join(opts.worktreesDir, runId);
8106
+ const runPath = path39.join(opts.worktreesDir, runId);
7920
8107
  if (!runDirectoryIsEmpty(runPath)) continue;
7921
8108
  candidates.push({
7922
8109
  kind: "remove_run_directory",
@@ -7931,10 +8118,22 @@ function scanStaleRunDirectoryCandidates(opts) {
7931
8118
  }
7932
8119
 
7933
8120
  // src/cleanup-execute.ts
7934
- import { existsSync as existsSync29, rmSync as rmSync2 } from "node:fs";
7935
- 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
+ }
7936
8133
  function removeDependencyCache(candidate, execute) {
7937
- if (!existsSync29(candidate.path)) {
8134
+ const metadataSkip = skipRunMetadataRemoval(candidate);
8135
+ if (metadataSkip) return metadataSkip;
8136
+ if (!existsSync30(candidate.path)) {
7938
8137
  return {
7939
8138
  ...candidate,
7940
8139
  executed: false,
@@ -7974,7 +8173,9 @@ function removeBuildCache(candidate, execute) {
7974
8173
  return removeDependencyCache(candidate, execute);
7975
8174
  }
7976
8175
  function removeRunDirectory(candidate, execute) {
7977
- if (!existsSync29(candidate.path)) {
8176
+ const metadataSkip = skipRunMetadataRemoval(candidate);
8177
+ if (metadataSkip) return metadataSkip;
8178
+ if (!existsSync30(candidate.path)) {
7978
8179
  return {
7979
8180
  ...candidate,
7980
8181
  executed: false,
@@ -8005,7 +8206,9 @@ function removeRunDirectory(candidate, execute) {
8005
8206
  }
8006
8207
  }
8007
8208
  function removeWorktree(candidate, execute) {
8008
- if (!existsSync29(candidate.path)) {
8209
+ const metadataSkip = skipRunMetadataRemoval(candidate);
8210
+ if (metadataSkip) return metadataSkip;
8211
+ if (!existsSync30(candidate.path)) {
8009
8212
  return {
8010
8213
  ...candidate,
8011
8214
  executed: false,
@@ -8022,7 +8225,7 @@ function removeWorktree(candidate, execute) {
8022
8225
  if (repo) {
8023
8226
  git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
8024
8227
  }
8025
- if (existsSync29(candidate.path)) {
8228
+ if (existsSync30(candidate.path)) {
8026
8229
  rmSync2(candidate.path, { recursive: true, force: true });
8027
8230
  }
8028
8231
  return {
@@ -8042,15 +8245,15 @@ function removeWorktree(candidate, execute) {
8042
8245
  }
8043
8246
  }
8044
8247
  function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
8045
- const resolved = path39.resolve(targetPath);
8046
- const suffix = `${path39.sep}${cacheDirName}`;
8248
+ const resolved = path40.resolve(targetPath);
8249
+ const suffix = `${path40.sep}${cacheDirName}`;
8047
8250
  const cachePath = resolved.endsWith(suffix) ? resolved : null;
8048
8251
  if (!cachePath) return "path_outside_harness";
8049
- const rel = path39.relative(worktreesDir, cachePath);
8050
- if (rel.startsWith("..") || path39.isAbsolute(rel)) return "path_outside_harness";
8051
- 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);
8052
8255
  if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
8053
- if (!resolved.startsWith(path39.resolve(harnessRoot))) return "path_outside_harness";
8256
+ if (!resolved.startsWith(path40.resolve(harnessRoot))) return "path_outside_harness";
8054
8257
  return null;
8055
8258
  }
8056
8259
  function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
@@ -8060,37 +8263,37 @@ function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
8060
8263
  return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
8061
8264
  }
8062
8265
  function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
8063
- const resolved = path39.resolve(targetPath);
8064
- const relToWt = path39.relative(worktreesDir, resolved);
8065
- if (relToWt.startsWith("..") || path39.isAbsolute(relToWt)) return "path_outside_harness";
8066
- 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);
8067
8270
  if (parts.length < 3) return "path_outside_harness";
8068
- if (!resolved.startsWith(path39.resolve(harnessRoot))) return "path_outside_harness";
8271
+ if (!resolved.startsWith(path40.resolve(harnessRoot))) return "path_outside_harness";
8069
8272
  return null;
8070
8273
  }
8071
8274
 
8072
8275
  // src/cleanup-scan.ts
8073
- import { existsSync as existsSync30, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
8074
- 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";
8075
8278
  function pathAgeMs2(target, now) {
8076
8279
  try {
8077
- const mtime = statSync7(target).mtimeMs;
8280
+ const mtime = statSync8(target).mtimeMs;
8078
8281
  return Math.max(0, now - mtime);
8079
8282
  } catch {
8080
8283
  return 0;
8081
8284
  }
8082
8285
  }
8083
8286
  function isPathInside(child, parent) {
8084
- const rel = path40.relative(parent, child);
8085
- return rel === "" || !rel.startsWith("..") && !path40.isAbsolute(rel);
8287
+ const rel = path41.relative(parent, child);
8288
+ return rel === "" || !rel.startsWith("..") && !path41.isAbsolute(rel);
8086
8289
  }
8087
8290
  function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
8088
8291
  const out = [];
8089
8292
  for (const rel of HARNESS_BUILD_CACHE_RELATIVE_PATHS) {
8090
8293
  if (rel === ".next") continue;
8091
- const target = path40.join(worktreePath, rel);
8092
- if (!existsSync30(target)) continue;
8093
- const resolved = path40.resolve(target);
8294
+ const target = path41.join(worktreePath, rel);
8295
+ if (!existsSync31(target)) continue;
8296
+ const resolved = path41.resolve(target);
8094
8297
  if (seen.has(resolved)) continue;
8095
8298
  if (!isPathInside(resolved, opts.harnessRoot)) continue;
8096
8299
  seen.add(resolved);
@@ -8119,13 +8322,13 @@ function scanBuildCacheCandidates(opts) {
8119
8322
  })
8120
8323
  );
8121
8324
  }
8122
- if (!opts.includeOrphans || !existsSync30(opts.worktreesDir)) return candidates;
8123
- 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 })) {
8124
8327
  if (!runEntry.isDirectory()) continue;
8125
- const runPath = path40.join(opts.worktreesDir, runEntry.name);
8126
- 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 })) {
8127
8330
  if (!workerEntry.isDirectory()) continue;
8128
- const worktreePath = path40.join(runPath, workerEntry.name);
8331
+ const worktreePath = path41.join(runPath, workerEntry.name);
8129
8332
  candidates.push(
8130
8333
  ...collectBuildCacheForWorktree(worktreePath, opts, seen, {
8131
8334
  runId: runEntry.name,
@@ -8146,7 +8349,7 @@ function scanWorktreeCandidates(opts) {
8146
8349
  for (const entry of opts.index.values()) {
8147
8350
  if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
8148
8351
  const resolved = entry.worktreePath;
8149
- if (!existsSync30(resolved)) continue;
8352
+ if (!existsSync31(resolved)) continue;
8150
8353
  if (seen.has(resolved)) continue;
8151
8354
  seen.add(resolved);
8152
8355
  candidates.push({
@@ -8160,24 +8363,24 @@ function scanWorktreeCandidates(opts) {
8160
8363
  });
8161
8364
  }
8162
8365
  }
8163
- if (!orphanEnabled || !existsSync30(opts.worktreesDir)) return candidates;
8366
+ if (!orphanEnabled || !existsSync31(opts.worktreesDir)) return candidates;
8164
8367
  const indexedPaths = /* @__PURE__ */ new Set();
8165
8368
  for (const entry of opts.index.values()) {
8166
- indexedPaths.add(path40.resolve(entry.worktreePath));
8369
+ indexedPaths.add(path41.resolve(entry.worktreePath));
8167
8370
  }
8168
- for (const runEntry of readdirSync9(opts.worktreesDir, { withFileTypes: true })) {
8371
+ for (const runEntry of readdirSync10(opts.worktreesDir, { withFileTypes: true })) {
8169
8372
  if (!runEntry.isDirectory()) continue;
8170
8373
  if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
8171
- const runPath = path40.join(opts.worktreesDir, runEntry.name);
8374
+ const runPath = path41.join(opts.worktreesDir, runEntry.name);
8172
8375
  let workerEntries;
8173
8376
  try {
8174
- workerEntries = readdirSync9(runPath, { withFileTypes: true });
8377
+ workerEntries = readdirSync10(runPath, { withFileTypes: true });
8175
8378
  } catch {
8176
8379
  continue;
8177
8380
  }
8178
8381
  for (const workerEntry of workerEntries) {
8179
8382
  if (!workerEntry.isDirectory()) continue;
8180
- const worktreePath = path40.resolve(path40.join(runPath, workerEntry.name));
8383
+ const worktreePath = path41.resolve(path41.join(runPath, workerEntry.name));
8181
8384
  if (seen.has(worktreePath)) continue;
8182
8385
  if (indexedPaths.has(worktreePath)) continue;
8183
8386
  if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
@@ -8196,27 +8399,27 @@ function scanWorktreeCandidates(opts) {
8196
8399
  }
8197
8400
 
8198
8401
  // src/cleanup-dependency-scan.ts
8199
- import { existsSync as existsSync31, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
8200
- 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";
8201
8404
  var DEPENDENCY_CACHE_DIRS = [
8202
8405
  { dirName: "node_modules", kind: "remove_node_modules" },
8203
8406
  { dirName: ".next", kind: "remove_next_cache" }
8204
8407
  ];
8205
8408
  function pathAgeMs3(target, now) {
8206
8409
  try {
8207
- const mtime = statSync8(target).mtimeMs;
8410
+ const mtime = statSync9(target).mtimeMs;
8208
8411
  return Math.max(0, now - mtime);
8209
8412
  } catch {
8210
8413
  return 0;
8211
8414
  }
8212
8415
  }
8213
8416
  function isPathInside2(child, parent) {
8214
- const rel = path41.relative(parent, child);
8215
- return rel === "" || !rel.startsWith("..") && !path41.isAbsolute(rel);
8417
+ const rel = path42.relative(parent, child);
8418
+ return rel === "" || !rel.startsWith("..") && !path42.isAbsolute(rel);
8216
8419
  }
8217
8420
  function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
8218
- if (!existsSync31(targetPath)) return;
8219
- const resolved = path41.resolve(targetPath);
8421
+ if (!existsSync32(targetPath)) return;
8422
+ const resolved = path42.resolve(targetPath);
8220
8423
  if (seen.has(resolved)) return;
8221
8424
  if (!isPathInside2(resolved, opts.harnessRoot)) return;
8222
8425
  seen.add(resolved);
@@ -8233,7 +8436,7 @@ function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
8233
8436
  }
8234
8437
  function scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, meta) {
8235
8438
  for (const entry of DEPENDENCY_CACHE_DIRS) {
8236
- 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);
8237
8440
  }
8238
8441
  }
8239
8442
  function scanDependencyCacheCandidates(opts) {
@@ -8247,20 +8450,20 @@ function scanDependencyCacheCandidates(opts) {
8247
8450
  repo: entry.run.repo
8248
8451
  });
8249
8452
  }
8250
- if (!existsSync31(opts.worktreesDir)) return candidates;
8251
- 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 })) {
8252
8455
  if (!runEntry.isDirectory()) continue;
8253
8456
  if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
8254
- const runPath = path41.join(opts.worktreesDir, runEntry.name);
8457
+ const runPath = path42.join(opts.worktreesDir, runEntry.name);
8255
8458
  let workerEntries;
8256
8459
  try {
8257
- workerEntries = readdirSync10(runPath, { withFileTypes: true });
8460
+ workerEntries = readdirSync11(runPath, { withFileTypes: true });
8258
8461
  } catch {
8259
8462
  continue;
8260
8463
  }
8261
8464
  for (const workerEntry of workerEntries) {
8262
8465
  if (!workerEntry.isDirectory()) continue;
8263
- const worktreePath = path41.join(runPath, workerEntry.name);
8466
+ const worktreePath = path42.join(runPath, workerEntry.name);
8264
8467
  scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, {
8265
8468
  runId: runEntry.name,
8266
8469
  worker: workerEntry.name
@@ -8271,11 +8474,11 @@ function scanDependencyCacheCandidates(opts) {
8271
8474
  }
8272
8475
 
8273
8476
  // src/cleanup-duplicate-worktrees.ts
8274
- import { existsSync as existsSync32, statSync as statSync9 } from "node:fs";
8275
- import path42 from "node:path";
8477
+ import { existsSync as existsSync33, statSync as statSync10 } from "node:fs";
8478
+ import path43 from "node:path";
8276
8479
  function pathAgeMs4(target, now) {
8277
8480
  try {
8278
- const mtime = statSync9(target).mtimeMs;
8481
+ const mtime = statSync10(target).mtimeMs;
8279
8482
  return Math.max(0, now - mtime);
8280
8483
  } catch {
8281
8484
  return 0;
@@ -8302,8 +8505,8 @@ function parseWorktreePorcelain(output) {
8302
8505
  return records;
8303
8506
  }
8304
8507
  function isUnderWorktreesDir(worktreePath, worktreesDir) {
8305
- const rel = path42.relative(path42.resolve(worktreesDir), path42.resolve(worktreePath));
8306
- 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);
8307
8510
  }
8308
8511
  function isCleanWorktree(worktreePath, repoRoot) {
8309
8512
  try {
@@ -8316,14 +8519,14 @@ function isCleanWorktree(worktreePath, repoRoot) {
8316
8519
  }
8317
8520
  }
8318
8521
  function scanDuplicateWorktreeCandidates(opts) {
8319
- if (!opts.includeOrphans || !existsSync32(opts.worktreesDir)) return [];
8522
+ if (!opts.includeOrphans || !existsSync33(opts.worktreesDir)) return [];
8320
8523
  const repos = /* @__PURE__ */ new Set();
8321
8524
  for (const entry of opts.index.values()) {
8322
- if (entry.run.repo) repos.add(path42.resolve(entry.run.repo));
8525
+ if (entry.run.repo) repos.add(path43.resolve(entry.run.repo));
8323
8526
  }
8324
8527
  const indexedPaths = /* @__PURE__ */ new Set();
8325
8528
  for (const entry of opts.index.values()) {
8326
- indexedPaths.add(path42.resolve(entry.worktreePath));
8529
+ indexedPaths.add(path43.resolve(entry.worktreePath));
8327
8530
  }
8328
8531
  const candidates = [];
8329
8532
  const seen = /* @__PURE__ */ new Set();
@@ -8336,15 +8539,15 @@ function scanDuplicateWorktreeCandidates(opts) {
8336
8539
  }
8337
8540
  const worktrees = parseWorktreePorcelain(porcelain);
8338
8541
  for (const wt of worktrees) {
8339
- const resolved = path42.resolve(wt.path);
8340
- if (resolved === path42.resolve(repoRoot)) continue;
8542
+ const resolved = path43.resolve(wt.path);
8543
+ if (resolved === path43.resolve(repoRoot)) continue;
8341
8544
  if (!isUnderWorktreesDir(resolved, opts.worktreesDir)) continue;
8342
8545
  if (indexedPaths.has(resolved)) continue;
8343
8546
  if (seen.has(resolved)) continue;
8344
- if (!existsSync32(resolved)) continue;
8547
+ if (!existsSync33(resolved)) continue;
8345
8548
  if (!isCleanWorktree(resolved, repoRoot)) continue;
8346
- const rel = path42.relative(opts.worktreesDir, resolved);
8347
- const parts = rel.split(path42.sep);
8549
+ const rel = path43.relative(opts.worktreesDir, resolved);
8550
+ const parts = rel.split(path43.sep);
8348
8551
  const runId = parts[0];
8349
8552
  const worker = parts[1] ?? "unknown";
8350
8553
  seen.add(resolved);
@@ -8363,12 +8566,12 @@ function scanDuplicateWorktreeCandidates(opts) {
8363
8566
  }
8364
8567
 
8365
8568
  // src/cleanup-worktree-index.ts
8366
- import path43 from "node:path";
8569
+ import path44 from "node:path";
8367
8570
  function buildWorktreeIndexAt(harnessRoot) {
8368
8571
  const index = /* @__PURE__ */ new Map();
8369
8572
  for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
8370
8573
  for (const name of Object.keys(run.workers || {})) {
8371
- const workerPath = path43.join(
8574
+ const workerPath = path44.join(
8372
8575
  runDirectoryAt(harnessRoot, run.id),
8373
8576
  "workers",
8374
8577
  safeSlug(name),
@@ -8377,9 +8580,9 @@ function buildWorktreeIndexAt(harnessRoot) {
8377
8580
  const worker = readJson(workerPath, void 0);
8378
8581
  if (!worker?.worktreePath) continue;
8379
8582
  const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
8380
- index.set(path43.resolve(worker.worktreePath), {
8583
+ index.set(path44.resolve(worker.worktreePath), {
8381
8584
  harnessRoot,
8382
- worktreePath: path43.resolve(worker.worktreePath),
8585
+ worktreePath: path44.resolve(worker.worktreePath),
8383
8586
  runId: run.id,
8384
8587
  workerName: name,
8385
8588
  run,
@@ -8443,15 +8646,15 @@ function resolvePipelineHarnessRetention(runId) {
8443
8646
  }
8444
8647
 
8445
8648
  // src/cleanup-orphan-safety.ts
8446
- import { existsSync as existsSync33, statSync as statSync10 } from "node:fs";
8447
- import path44 from "node:path";
8649
+ import { existsSync as existsSync34, statSync as statSync11 } from "node:fs";
8650
+ import path45 from "node:path";
8448
8651
  var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
8449
8652
  function assessOrphanWorktreeSafety(input) {
8450
8653
  const now = input.now ?? Date.now();
8451
8654
  const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
8452
- if (!existsSync33(input.worktreePath)) return null;
8655
+ if (!existsSync34(input.worktreePath)) return null;
8453
8656
  if (input.runId && input.workerName) {
8454
- const heartbeatPath = path44.join(
8657
+ const heartbeatPath = path45.join(
8455
8658
  input.harnessRoot,
8456
8659
  "runs",
8457
8660
  input.runId,
@@ -8460,13 +8663,13 @@ function assessOrphanWorktreeSafety(input) {
8460
8663
  "heartbeat.jsonl"
8461
8664
  );
8462
8665
  try {
8463
- const mtime = statSync10(heartbeatPath).mtimeMs;
8666
+ const mtime = statSync11(heartbeatPath).mtimeMs;
8464
8667
  if (now - mtime < heartbeatFreshMs) return "active_worker";
8465
8668
  } catch {
8466
8669
  }
8467
8670
  }
8468
- const gitDir = path44.join(input.worktreePath, ".git");
8469
- if (!existsSync33(gitDir)) return null;
8671
+ const gitDir = path45.join(input.worktreePath, ".git");
8672
+ if (!existsSync34(gitDir)) return null;
8470
8673
  const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
8471
8674
  if (porcelain.status !== 0) return "pr_or_unmerged_commits";
8472
8675
  const dirtyLines = porcelain.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
@@ -8495,12 +8698,12 @@ function assessOrphanWorktreeSafety(input) {
8495
8698
  }
8496
8699
 
8497
8700
  // src/cleanup-harness-roots.ts
8498
- import { existsSync as existsSync34 } from "node:fs";
8701
+ import { existsSync as existsSync35 } from "node:fs";
8499
8702
  import { homedir as homedir11 } from "node:os";
8500
- import path45 from "node:path";
8703
+ import path46 from "node:path";
8501
8704
  var WELL_KNOWN_HARNESS_SCAN_ROOTS = [
8502
8705
  "/var/tmp/kynver-harness",
8503
- path45.join(homedir11(), ".openclaw", "harness")
8706
+ path46.join(homedir11(), ".openclaw", "harness")
8504
8707
  ];
8505
8708
  function addRoot(seen, roots, candidate) {
8506
8709
  if (!candidate?.trim()) return;
@@ -8522,8 +8725,8 @@ function resolveHarnessScanRoots(options = {}) {
8522
8725
  for (const candidate of extra ?? []) addRoot(seen, roots, candidate);
8523
8726
  if (shouldScanWellKnownRoots(options)) {
8524
8727
  for (const candidate of WELL_KNOWN_HARNESS_SCAN_ROOTS) {
8525
- const resolved = path45.resolve(candidate);
8526
- 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);
8527
8730
  }
8528
8731
  }
8529
8732
  return roots;
@@ -8612,9 +8815,9 @@ function mergeWorktreeIndexes(scanRoots) {
8612
8815
  }
8613
8816
  function worktreePathForCandidate(candidate, worktreesDir) {
8614
8817
  if (candidate.runId && candidate.worker) {
8615
- return path46.join(worktreesDir, candidate.runId, candidate.worker);
8818
+ return path47.join(worktreesDir, candidate.runId, candidate.worker);
8616
8819
  }
8617
- return path46.resolve(candidate.path, "..");
8820
+ return path47.resolve(candidate.path, "..");
8618
8821
  }
8619
8822
  function runHarnessCleanup(options = {}) {
8620
8823
  let retention = resolveHarnessRetention(options);
@@ -8631,7 +8834,7 @@ function runHarnessCleanup(options = {}) {
8631
8834
  const atSweepCap = () => actions.length >= maxActions;
8632
8835
  for (const harnessRoot of paths.scanRoots) {
8633
8836
  if (atSweepCap()) break;
8634
- const worktreesDir = path46.join(harnessRoot, "worktrees");
8837
+ const worktreesDir = path47.join(harnessRoot, "worktrees");
8635
8838
  const scanOpts = {
8636
8839
  harnessRoot,
8637
8840
  worktreesDir,
@@ -8645,7 +8848,7 @@ function runHarnessCleanup(options = {}) {
8645
8848
  for (const raw of scanDependencyCacheCandidates(scanOpts)) {
8646
8849
  if (atSweepCap()) break;
8647
8850
  const candidate = attachCandidateBytes(raw, retention.accountBytes);
8648
- const resolved = path46.resolve(candidate.path);
8851
+ const resolved = path47.resolve(candidate.path);
8649
8852
  if (processedPaths.has(resolved)) continue;
8650
8853
  processedPaths.add(resolved);
8651
8854
  const pathSkip = pathGuardForDependencyCache(candidate, harnessRoot, worktreesDir);
@@ -8655,7 +8858,7 @@ function runHarnessCleanup(options = {}) {
8655
8858
  continue;
8656
8859
  }
8657
8860
  const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
8658
- const indexed = index.get(path46.resolve(worktreePath)) ?? null;
8861
+ const indexed = index.get(path47.resolve(worktreePath)) ?? null;
8659
8862
  const guardReason = skipDependencyCacheRemoval({
8660
8863
  indexed,
8661
8864
  includeOrphans: true,
@@ -8675,7 +8878,7 @@ function runHarnessCleanup(options = {}) {
8675
8878
  for (const raw of scanBuildCacheCandidates(scanOpts)) {
8676
8879
  if (atSweepCap()) break;
8677
8880
  const candidate = attachCandidateBytes(raw, retention.accountBytes);
8678
- const resolved = path46.resolve(candidate.path);
8881
+ const resolved = path47.resolve(candidate.path);
8679
8882
  if (processedPaths.has(resolved)) continue;
8680
8883
  processedPaths.add(resolved);
8681
8884
  const pathSkip = isHarnessBuildCachePath(candidate.path, harnessRoot, worktreesDir);
@@ -8685,7 +8888,7 @@ function runHarnessCleanup(options = {}) {
8685
8888
  continue;
8686
8889
  }
8687
8890
  const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
8688
- const indexed = index.get(path46.resolve(worktreePath)) ?? null;
8891
+ const indexed = index.get(path47.resolve(worktreePath)) ?? null;
8689
8892
  const guardReason = skipBuildCacheRemoval({
8690
8893
  indexed,
8691
8894
  includeOrphans: true,
@@ -8709,11 +8912,11 @@ function runHarnessCleanup(options = {}) {
8709
8912
  const worktreeSeen = /* @__PURE__ */ new Set();
8710
8913
  for (const raw of worktreeCandidates) {
8711
8914
  if (atSweepCap()) break;
8712
- const resolved = path46.resolve(raw.path);
8915
+ const resolved = path47.resolve(raw.path);
8713
8916
  if (worktreeSeen.has(resolved)) continue;
8714
8917
  worktreeSeen.add(resolved);
8715
8918
  const candidate = attachCandidateBytes({ ...raw, path: resolved }, retention.accountBytes);
8716
- const indexed = index.get(path46.resolve(candidate.path)) ?? null;
8919
+ const indexed = index.get(path47.resolve(candidate.path)) ?? null;
8717
8920
  const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
8718
8921
  worktreePath: candidate.path,
8719
8922
  harnessRoot,
@@ -8723,7 +8926,7 @@ function runHarnessCleanup(options = {}) {
8723
8926
  });
8724
8927
  const guardSkip = skipWorktreeRemoval({
8725
8928
  indexed,
8726
- worktreePath: path46.resolve(candidate.path),
8929
+ worktreePath: path47.resolve(candidate.path),
8727
8930
  includeOrphans: retention.includeOrphans,
8728
8931
  worktreesAgeMs: retention.worktreesAgeMs,
8729
8932
  terminalWorktreesAgeMs: retention.terminalWorktreesAgeMs,
@@ -8750,10 +8953,10 @@ function runHarnessCleanup(options = {}) {
8750
8953
  })) {
8751
8954
  if (atSweepCap()) break;
8752
8955
  const candidate = attachCandidateBytes(raw, retention.accountBytes);
8753
- const resolved = path46.resolve(candidate.path);
8956
+ const resolved = path47.resolve(candidate.path);
8754
8957
  if (processedPaths.has(resolved)) continue;
8755
8958
  processedPaths.add(resolved);
8756
- const runId = candidate.runId ?? path46.basename(resolved);
8959
+ const runId = candidate.runId ?? path47.basename(resolved);
8757
8960
  const dirSkip = skipRunDirectoryRemoval({
8758
8961
  harnessRoot,
8759
8962
  runId,
@@ -8845,8 +9048,8 @@ import { mkdirSync as mkdirSync7, realpathSync } from "node:fs";
8845
9048
  import { fileURLToPath as fileURLToPath5 } from "node:url";
8846
9049
 
8847
9050
  // src/discard-disposable.ts
8848
- import { existsSync as existsSync35, rmSync as rmSync3 } from "node:fs";
8849
- import path47 from "node:path";
9051
+ import { existsSync as existsSync36, rmSync as rmSync3 } from "node:fs";
9052
+ import path48 from "node:path";
8850
9053
  function normalizeRelativePath2(value) {
8851
9054
  const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
8852
9055
  if (!normalized || normalized.startsWith("/") || normalized.includes("..")) {
@@ -8867,15 +9070,15 @@ function discardDisposableArtifacts(args) {
8867
9070
  if (paths.length === 0) {
8868
9071
  return { ok: false, removed: [], reason: "requires at least one --path" };
8869
9072
  }
8870
- const worktreeRoot = path47.resolve(worker.worktreePath);
9073
+ const worktreeRoot = path48.resolve(worker.worktreePath);
8871
9074
  const removed = [];
8872
9075
  for (const raw of paths) {
8873
9076
  const rel = normalizeRelativePath2(raw);
8874
- const abs = path47.resolve(worktreeRoot, rel);
8875
- if (!abs.startsWith(worktreeRoot + path47.sep) && abs !== worktreeRoot) {
9077
+ const abs = path48.resolve(worktreeRoot, rel);
9078
+ if (!abs.startsWith(worktreeRoot + path48.sep) && abs !== worktreeRoot) {
8876
9079
  return { ok: false, removed, reason: `path escapes worktree: ${raw}` };
8877
9080
  }
8878
- if (!existsSync35(abs)) {
9081
+ if (!existsSync36(abs)) {
8879
9082
  return { ok: false, removed, reason: `path not found: ${raw}` };
8880
9083
  }
8881
9084
  rmSync3(abs, { recursive: true, force: true });
@@ -8897,8 +9100,472 @@ function discardDisposableCli(args) {
8897
9100
  if (!result.ok) process.exit(1);
8898
9101
  }
8899
9102
 
8900
- // 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";
8901
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";
8902
9569
 
8903
9570
  // src/pipeline-dispatch.ts
8904
9571
  var RESERVED_REVIEW_STARTS = 1;
@@ -8988,29 +9655,48 @@ function operatorDispatchFromTick(operatorTick) {
8988
9655
  const dispatch = body.response?.dispatch;
8989
9656
  return dispatch && typeof dispatch === "object" ? dispatch : null;
8990
9657
  }
9658
+ function nonNegativeInt(value) {
9659
+ if (typeof value !== "number" || !Number.isFinite(value)) return null;
9660
+ return Math.max(0, Math.floor(value));
9661
+ }
8991
9662
  function resolvePipelineMaxStarts(resourceGate, operatorTick) {
8992
9663
  const dispatch = operatorDispatchFromTick(operatorTick);
8993
- 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;
8994
9671
  let maxStarts = resourceGate.slotsAvailable;
8995
- if (advised !== null) {
9672
+ if (readyFloor !== null) {
9673
+ maxStarts = Math.min(maxStarts, readyFloor);
9674
+ } else if (advised !== null) {
8996
9675
  maxStarts = Math.min(maxStarts, advised);
8997
9676
  }
8998
- const underutilized = dispatch?.underutilized === true;
8999
- const boardAdvancedThisTick = typeof dispatch?.boardAdvancedThisTick === "number" ? dispatch.boardAdvancedThisTick : 0;
9000
- if (underutilized && resourceGate.slotsAvailable > 0 && maxStarts === 0) {
9001
- 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);
9002
9683
  maxStarts = Math.min(resourceGate.slotsAvailable, Math.max(1, ready));
9003
9684
  }
9685
+ const nonDispatchableReady = queuedTasks !== null && actionableReady !== null ? Math.max(0, queuedTasks - actionableReady) : null;
9004
9686
  return {
9005
9687
  maxStarts: Math.max(0, maxStarts),
9006
9688
  underutilized,
9007
9689
  advisedStarts: advised,
9008
- boardAdvancedThisTick
9690
+ actionableReady,
9691
+ queuedTasks,
9692
+ nonDispatchableReady,
9693
+ boardAdvancedThisTick,
9694
+ leaseReapedThisTick
9009
9695
  };
9010
9696
  }
9011
9697
 
9012
9698
  // src/plan-progress-daemon-sync.ts
9013
- import path48 from "node:path";
9699
+ import path51 from "node:path";
9014
9700
 
9015
9701
  // src/plan-progress-sync.ts
9016
9702
  async function syncPlanProgress(args) {
@@ -9034,7 +9720,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
9034
9720
  const outcomes = [];
9035
9721
  for (const name of Object.keys(run.workers || {})) {
9036
9722
  const worker = readJson(
9037
- path48.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
9723
+ path51.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
9038
9724
  void 0
9039
9725
  );
9040
9726
  if (!worker?.dispatched || !worker.taskId) continue;
@@ -9084,8 +9770,8 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
9084
9770
 
9085
9771
  // src/installed-package-versions.ts
9086
9772
  import { readFile } from "node:fs/promises";
9087
- import { homedir as homedir12 } from "node:os";
9088
- import path49 from "node:path";
9773
+ import { homedir as homedir13 } from "node:os";
9774
+ import path52 from "node:path";
9089
9775
  var MANAGED_PACKAGES = [
9090
9776
  "@kynver-app/runtime",
9091
9777
  "@kynver-app/openclaw-agent-os",
@@ -9099,13 +9785,13 @@ function unique(values) {
9099
9785
  return [...new Set(values.filter((value) => Boolean(value)))];
9100
9786
  }
9101
9787
  function moduleRoots() {
9102
- const home = homedir12();
9103
- const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path49.join(home, ".openclaw", "npm");
9104
- 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"));
9105
9791
  return unique([
9106
- path49.join(openClawPrefix, "lib", "node_modules"),
9107
- path49.join(openClawPrefix, "node_modules"),
9108
- 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")
9109
9795
  ]);
9110
9796
  }
9111
9797
  async function readVersion(packageJsonPath) {
@@ -9121,7 +9807,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
9121
9807
  const out = {};
9122
9808
  for (const packageName of MANAGED_PACKAGES) {
9123
9809
  for (const root of roots) {
9124
- const packageJsonPath = path49.join(root, packageName, "package.json");
9810
+ const packageJsonPath = path52.join(root, packageName, "package.json");
9125
9811
  const version = await readVersion(packageJsonPath);
9126
9812
  if (!version) continue;
9127
9813
  out[packageName] = { version, observedAt, path: packageJsonPath };
@@ -9137,7 +9823,7 @@ async function completeFinishedWorkers(runId, args) {
9137
9823
  const outcomes = [];
9138
9824
  for (const name of Object.keys(run.workers || {})) {
9139
9825
  const worker = readJson(
9140
- path50.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
9826
+ path53.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
9141
9827
  void 0
9142
9828
  );
9143
9829
  if (!worker?.taskId || worker.localOnly) continue;
@@ -9248,6 +9934,7 @@ async function runPipelineTick(args) {
9248
9934
  skipped: true,
9249
9935
  reason: execute ? dispatchResourceGate.reason ?? "no slots or queued work" : "execute disabled",
9250
9936
  maxStarts: 0,
9937
+ dispatchAdvice: maxStartsAdvice,
9251
9938
  ...exactTargetTaskIds.length ? { exactTargetTaskIds, exactOnly: true } : {}
9252
9939
  };
9253
9940
  }
@@ -9269,6 +9956,7 @@ async function runPipelineTick(args) {
9269
9956
  completionAckSync,
9270
9957
  operatorTick,
9271
9958
  sweep,
9959
+ dispatchAdvice: maxStartsAdvice,
9272
9960
  dispatch,
9273
9961
  idle
9274
9962
  };
@@ -9292,8 +9980,18 @@ async function runDaemon(args) {
9292
9980
  stopping = true;
9293
9981
  });
9294
9982
  console.error(JSON.stringify({ event: "daemon_start", runId, agentOsId, execute, intervalMs }));
9983
+ const cronEnv = resolveKynverCronEnv();
9295
9984
  while (!stopping) {
9296
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
+ }
9297
9995
  const tick = await runPipelineTick({ run: runId, agentOsId, execute, ...args });
9298
9996
  console.error(JSON.stringify({ event: "daemon_tick", ...tick }));
9299
9997
  if (tick.idle) {
@@ -9312,7 +10010,7 @@ async function runDaemon(args) {
9312
10010
  }
9313
10011
 
9314
10012
  // src/plan-progress.ts
9315
- import path51 from "node:path";
10013
+ import path54 from "node:path";
9316
10014
 
9317
10015
  // src/bounded-build/constants.ts
9318
10016
  var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
@@ -9599,7 +10297,7 @@ async function emitPlanProgress(args) {
9599
10297
  }
9600
10298
  function verifyPlanLocal(args) {
9601
10299
  const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
9602
- const cwd = path51.resolve(worktree);
10300
+ const cwd = path54.resolve(worktree);
9603
10301
  const summary = runHarnessVerifyCommands(cwd);
9604
10302
  const emitJson = args.json === true || args.json === "true";
9605
10303
  const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
@@ -9648,9 +10346,9 @@ async function verifyPlan(args) {
9648
10346
  }
9649
10347
 
9650
10348
  // src/harness-verify-cli.ts
9651
- import path52 from "node:path";
10349
+ import path55 from "node:path";
9652
10350
  function runHarnessVerifyCli(args) {
9653
- 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"));
9654
10352
  const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
9655
10353
  const commands = [];
9656
10354
  const rawCmd = args.command;
@@ -9694,7 +10392,7 @@ function runHarnessVerifyCli(args) {
9694
10392
  }
9695
10393
 
9696
10394
  // src/plan-persist-cli.ts
9697
- import { readFileSync as readFileSync13 } from "node:fs";
10395
+ import { readFileSync as readFileSync14 } from "node:fs";
9698
10396
  var OPERATIONS = ["create", "add_version", "update_metadata"];
9699
10397
  var FAILURE_KINDS = [
9700
10398
  "approval_guard",
@@ -9706,7 +10404,7 @@ var FAILURE_KINDS = [
9706
10404
  function readBodyArg(args) {
9707
10405
  const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
9708
10406
  if (bodyFile) {
9709
- return { body: readFileSync13(bodyFile, "utf8"), bodyPathHint: bodyFile };
10407
+ return { body: readFileSync14(bodyFile, "utf8"), bodyPathHint: bodyFile };
9710
10408
  }
9711
10409
  const inline = args.body ? String(args.body) : void 0;
9712
10410
  if (inline) return { body: inline };
@@ -10079,7 +10777,7 @@ ${text.slice(0, 800)}`,
10079
10777
  }
10080
10778
 
10081
10779
  // src/monitor/monitor.service.ts
10082
- import path54 from "node:path";
10780
+ import path57 from "node:path";
10083
10781
 
10084
10782
  // src/monitor/monitor.classify.ts
10085
10783
  function classifyWorkerHealth(input) {
@@ -10131,11 +10829,11 @@ function classifyWorkerHealth(input) {
10131
10829
  }
10132
10830
 
10133
10831
  // src/monitor/monitor.store.ts
10134
- import { existsSync as existsSync36, mkdirSync as mkdirSync6, readdirSync as readdirSync11, unlinkSync as unlinkSync2 } from "node:fs";
10135
- 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";
10136
10834
  function monitorsDir() {
10137
10835
  const { harnessRoot } = getHarnessPaths();
10138
- const dir = path53.join(harnessRoot, "monitors");
10836
+ const dir = path56.join(harnessRoot, "monitors");
10139
10837
  mkdirSync6(dir, { recursive: true });
10140
10838
  return dir;
10141
10839
  }
@@ -10143,7 +10841,7 @@ function monitorIdFor(runId, workerName) {
10143
10841
  return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
10144
10842
  }
10145
10843
  function monitorPath(monitorId) {
10146
- return path53.join(monitorsDir(), `${monitorId}.json`);
10844
+ return path56.join(monitorsDir(), `${monitorId}.json`);
10147
10845
  }
10148
10846
  function loadMonitorSession(monitorId) {
10149
10847
  return readJson(monitorPath(monitorId), void 0);
@@ -10153,18 +10851,18 @@ function saveMonitorSession(session) {
10153
10851
  }
10154
10852
  function deleteMonitorSession(monitorId) {
10155
10853
  const file = monitorPath(monitorId);
10156
- if (!existsSync36(file)) return false;
10157
- unlinkSync2(file);
10854
+ if (!existsSync39(file)) return false;
10855
+ unlinkSync3(file);
10158
10856
  return true;
10159
10857
  }
10160
10858
  function listMonitorSessions() {
10161
10859
  const dir = monitorsDir();
10162
- if (!existsSync36(dir)) return [];
10860
+ if (!existsSync39(dir)) return [];
10163
10861
  const entries = [];
10164
- for (const name of readdirSync11(dir)) {
10862
+ for (const name of readdirSync12(dir)) {
10165
10863
  if (!name.endsWith(".json")) continue;
10166
10864
  const session = readJson(
10167
- path53.join(dir, name),
10865
+ path56.join(dir, name),
10168
10866
  void 0
10169
10867
  );
10170
10868
  if (!session?.monitorId) continue;
@@ -10255,7 +10953,7 @@ async function fetchTaskLeasesForWorkers(input) {
10255
10953
  // src/monitor/monitor.service.ts
10256
10954
  function workerRecord2(runId, name) {
10257
10955
  return readJson(
10258
- path54.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
10956
+ path57.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
10259
10957
  void 0
10260
10958
  );
10261
10959
  }
@@ -10461,21 +11159,21 @@ async function runMonitorLoop(args) {
10461
11159
 
10462
11160
  // src/monitor/monitor-spawn.ts
10463
11161
  import { spawn as spawn6 } from "node:child_process";
10464
- import { closeSync as closeSync6, existsSync as existsSync37, openSync as openSync6 } from "node:fs";
10465
- 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";
10466
11164
  import { fileURLToPath as fileURLToPath4 } from "node:url";
10467
11165
  function resolveDefaultCliPath2() {
10468
- return path55.join(fileURLToPath4(new URL(".", import.meta.url)), "cli.js");
11166
+ return path58.join(fileURLToPath4(new URL(".", import.meta.url)), "cli.js");
10469
11167
  }
10470
11168
  function spawnMonitorSidecar(opts) {
10471
11169
  const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
10472
- if (!existsSync37(cliPath)) return void 0;
11170
+ if (!existsSync40(cliPath)) return void 0;
10473
11171
  const monitorId = monitorIdFor(opts.runId, opts.workerName);
10474
11172
  const { harnessRoot } = getHarnessPaths();
10475
- const logPath = path55.join(harnessRoot, "monitors", `${monitorId}.log`);
11173
+ const logPath = path58.join(harnessRoot, "monitors", `${monitorId}.log`);
10476
11174
  let logFd;
10477
11175
  try {
10478
- logFd = openSync6(logPath, "a");
11176
+ logFd = openSync7(logPath, "a");
10479
11177
  } catch {
10480
11178
  logFd = void 0;
10481
11179
  }
@@ -10515,7 +11213,7 @@ function spawnMonitorSidecar(opts) {
10515
11213
  env: process.env
10516
11214
  })
10517
11215
  );
10518
- if (logFd !== void 0) closeSync6(logFd);
11216
+ if (logFd !== void 0) closeSync7(logFd);
10519
11217
  child.unref();
10520
11218
  const session = {
10521
11219
  monitorId,
@@ -10532,7 +11230,7 @@ function spawnMonitorSidecar(opts) {
10532
11230
  } catch {
10533
11231
  if (logFd !== void 0) {
10534
11232
  try {
10535
- closeSync6(logFd);
11233
+ closeSync7(logFd);
10536
11234
  } catch {
10537
11235
  }
10538
11236
  }
@@ -10592,7 +11290,7 @@ async function monitorTickCli(args) {
10592
11290
  }
10593
11291
 
10594
11292
  // src/post-restart-unblock.ts
10595
- import path56 from "node:path";
11293
+ import path59 from "node:path";
10596
11294
  function skip(runId, worker, taskId, agentOsId, leaseOwner, reason) {
10597
11295
  return { runId, worker, taskId, agentOsId, leaseOwner, action: "skipped", reason };
10598
11296
  }
@@ -10605,7 +11303,7 @@ async function postRestartUnblock(args) {
10605
11303
  const errors = [];
10606
11304
  for (const run of listRunRecords()) {
10607
11305
  for (const name of Object.keys(run.workers ?? {})) {
10608
- 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");
10609
11307
  const worker = readJson(workerPath, void 0);
10610
11308
  if (!worker) {
10611
11309
  skipped.push(skip(run.id, name, "", "", "", "worker.json missing"));
@@ -10717,12 +11415,12 @@ async function postRestartUnblockCli(args) {
10717
11415
  }
10718
11416
 
10719
11417
  // src/doctor/runtime-takeover.ts
10720
- import path58 from "node:path";
11418
+ import path61 from "node:path";
10721
11419
 
10722
11420
  // src/doctor/runtime-takeover.probes.ts
10723
- import { accessSync, constants, existsSync as existsSync38, readFileSync as readFileSync14 } from "node:fs";
10724
- import { homedir as homedir13 } from "node:os";
10725
- 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";
10726
11424
  import { spawnSync as spawnSync8 } from "node:child_process";
10727
11425
  function captureCommand(bin, args) {
10728
11426
  try {
@@ -10751,7 +11449,7 @@ function tokenPrefix(token) {
10751
11449
  return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
10752
11450
  }
10753
11451
  function isWritable(target) {
10754
- if (!existsSync38(target)) return false;
11452
+ if (!existsSync41(target)) return false;
10755
11453
  try {
10756
11454
  accessSync(target, constants.W_OK);
10757
11455
  return true;
@@ -10764,15 +11462,15 @@ var defaultRuntimeTakeoverProbes = {
10764
11462
  commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
10765
11463
  kynverVersion: (bin) => captureCommand(bin, ["--version"]),
10766
11464
  loadConfig: () => loadUserConfig(),
10767
- configFilePath: () => path57.join(homedir13(), ".kynver", "config.json"),
10768
- credentialsFilePath: () => path57.join(homedir13(), ".kynver", "credentials"),
11465
+ configFilePath: () => path60.join(homedir14(), ".kynver", "config.json"),
11466
+ credentialsFilePath: () => path60.join(homedir14(), ".kynver", "credentials"),
10769
11467
  readCredentials: () => {
10770
- const credPath = path57.join(homedir13(), ".kynver", "credentials");
10771
- if (!existsSync38(credPath)) {
11468
+ const credPath = path60.join(homedir14(), ".kynver", "credentials");
11469
+ if (!existsSync41(credPath)) {
10772
11470
  return { hasApiKey: false };
10773
11471
  }
10774
11472
  try {
10775
- const parsed = JSON.parse(readFileSync14(credPath, "utf8"));
11473
+ const parsed = JSON.parse(readFileSync15(credPath, "utf8"));
10776
11474
  return {
10777
11475
  hasApiKey: Boolean(parsed.apiKey?.trim()),
10778
11476
  runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
@@ -10791,7 +11489,10 @@ var defaultRuntimeTakeoverProbes = {
10791
11489
  kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
10792
11490
  opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
10793
11491
  kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0,
10794
- 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(),
10795
11496
  qstashTokenPresent: Boolean(process.env.QSTASH_TOKEN?.trim()),
10796
11497
  kynverHostedDeployment: (() => {
10797
11498
  const v = process.env.KYNVER_HOSTED_DEPLOYMENT?.trim().toLowerCase();
@@ -10799,8 +11500,8 @@ var defaultRuntimeTakeoverProbes = {
10799
11500
  })()
10800
11501
  }),
10801
11502
  harnessRoot: () => resolveHarnessRoot(),
10802
- legacyOpenclawHarnessRoot: () => path57.join(homedir13(), ".openclaw", "harness"),
10803
- pathExists: (target) => existsSync38(target),
11503
+ legacyOpenclawHarnessRoot: () => path60.join(homedir14(), ".openclaw", "harness"),
11504
+ pathExists: (target) => existsSync41(target),
10804
11505
  pathWritable: (target) => isWritable(target),
10805
11506
  vercelVersion: () => captureCommand("vercel", ["--version"]),
10806
11507
  vercelWhoami: () => captureCommand("vercel", ["whoami"])
@@ -10820,8 +11521,10 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
10820
11521
  const schedulerDetails = {
10821
11522
  schedulerProvider: env.kynverSchedulerProvider ?? null,
10822
11523
  deploymentSchedulerProvider: ctx.deploymentSchedulerProvider ?? null,
10823
- openclawCronStorePath: Boolean(env.openclawCronStorePath)
11524
+ openclawCronStorePath: Boolean(env.openclawCronStorePath),
11525
+ kynverCronDaemonPrimary: Boolean(env.kynverCronDaemonPrimary)
10824
11526
  };
11527
+ const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
10825
11528
  if (hasQstashCutover(env, ctx) && !hasLocalOpenClawDependency(env, ctx)) {
10826
11529
  const source = env.kynverSchedulerProvider === "qstash" ? "KYNVER_SCHEDULER_PROVIDER=qstash on this host" : "deploymentSchedulerProvider=qstash in ~/.kynver/config.json";
10827
11530
  return check({
@@ -10833,6 +11536,19 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
10833
11536
  });
10834
11537
  }
10835
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
+ }
10836
11552
  const parts = [];
10837
11553
  if (env.kynverSchedulerProvider === "openclaw-cron") {
10838
11554
  parts.push("KYNVER_SCHEDULER_PROVIDER=openclaw-cron");
@@ -10841,21 +11557,20 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
10841
11557
  parts.push("deploymentSchedulerProvider=openclaw-cron in config");
10842
11558
  }
10843
11559
  if (env.openclawCronStorePath) {
10844
- 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)");
10845
11561
  }
10846
11562
  return check({
10847
11563
  id: "hotspot_openclaw_scheduler",
10848
11564
  label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
10849
11565
  status: "warn",
10850
- summary: `OpenClaw local cron still active (${parts.join("; ")})`,
10851
- 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.",
10852
11568
  details: schedulerDetails
10853
11569
  });
10854
11570
  }
10855
11571
  const runnerOpenclaw = env.kynverSchedulerProvider === "openclaw-cron";
10856
11572
  const runnerQstash = env.kynverSchedulerProvider === "qstash";
10857
11573
  const hostedDeployment = Boolean(env.qstashTokenPresent) || Boolean(env.kynverHostedDeployment);
10858
- const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
10859
11574
  const hostedSchedulerProcess = hostedDeployment && !daemonDispatchReady;
10860
11575
  const deploymentNeedsQstash = hostedSchedulerProcess && !env.qstashTokenPresent && env.kynverSchedulerProvider !== "qstash";
10861
11576
  const deploymentOpenclaw = hostedSchedulerProcess && env.kynverSchedulerProvider === "openclaw-cron";
@@ -11128,8 +11843,8 @@ function assessVercelCli(probes) {
11128
11843
  }
11129
11844
  function assessHarnessDirs(probes) {
11130
11845
  const harnessRoot = probes.harnessRoot();
11131
- const runsDir = path58.join(harnessRoot, "runs");
11132
- const worktreesDir = path58.join(harnessRoot, "worktrees");
11846
+ const runsDir = path61.join(harnessRoot, "runs");
11847
+ const worktreesDir = path61.join(harnessRoot, "worktrees");
11133
11848
  const displayHarnessRoot = redactHomePath(harnessRoot);
11134
11849
  const displayRunsDir = redactHomePath(runsDir);
11135
11850
  const displayWorktreesDir = redactHomePath(worktreesDir);
@@ -11332,6 +12047,11 @@ var RUNNER_SCHEDULER_CUTOVER_STEPS = [
11332
12047
  function readSchedulerCutoverEnv(env = process.env) {
11333
12048
  return {
11334
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,
11335
12055
  openclawCronStorePath: env.OPENCLAW_CRON_STORE_PATH?.trim() || null,
11336
12056
  openclawCronSecret: Boolean(env.OPENCLAW_CRON_SECRET?.trim()),
11337
12057
  openclawCronFireBaseUrl: env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || null
@@ -11342,8 +12062,13 @@ function assessSchedulerCutover(config, env = readSchedulerCutoverEnv()) {
11342
12062
  if (env.kynverSchedulerProvider === "openclaw-cron") {
11343
12063
  blockers.push("Runner still has KYNVER_SCHEDULER_PROVIDER=openclaw-cron");
11344
12064
  }
11345
- if (env.openclawCronStorePath) {
11346
- 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)");
11347
12072
  }
11348
12073
  if (config.deploymentSchedulerProvider === "openclaw-cron") {
11349
12074
  blockers.push("~/.kynver/config.json deploymentSchedulerProvider is still openclaw-cron");
@@ -11365,9 +12090,9 @@ function applySchedulerCutoverAttestation(config) {
11365
12090
  }
11366
12091
 
11367
12092
  // src/scheduler-cutover-cli.ts
11368
- import path59 from "node:path";
11369
- import { homedir as homedir14 } from "node:os";
11370
- 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");
11371
12096
  function runSchedulerCutoverCheckCli(json = false) {
11372
12097
  const config = loadUserConfig();
11373
12098
  const report = assessSchedulerCutover(config);
@@ -11395,7 +12120,10 @@ function runSchedulerCutoverCheckCli(json = false) {
11395
12120
  ` KYNVER_SCHEDULER_PROVIDER: ${report.runnerEnv.kynverSchedulerProvider ?? "(unset)"}`
11396
12121
  );
11397
12122
  console.log(
11398
- ` 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)"}`
11399
12127
  );
11400
12128
  if (report.blockers.length) {
11401
12129
  console.log("\nBlockers:");
@@ -11442,6 +12170,65 @@ function runSchedulerAttestCutoverCli(json = false) {
11442
12170
  console.log(` config: ${payload.configPath}`);
11443
12171
  }
11444
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
+
11445
12232
  // src/cli.ts
11446
12233
  function isHelpFlag(arg) {
11447
12234
  return arg === "help" || arg === "--help" || arg === "-h";
@@ -11492,6 +12279,8 @@ function usage(code = 0) {
11492
12279
  " kynver doctor runtime-takeover",
11493
12280
  " kynver scheduler cutover-check [--json]",
11494
12281
  " kynver scheduler attest-cutover [--json]",
12282
+ " kynver cron status [--json]",
12283
+ " kynver cron tick [--agent-os-id AOS_ID] [--json]",
11495
12284
  " kynver board contract [--agent-os-id ID] [--base-url URL] [--since ISO] [--limit N]"
11496
12285
  ].join("\n")
11497
12286
  );
@@ -11503,7 +12292,7 @@ async function main(argv = process.argv.slice(2)) {
11503
12292
  const scope = argv.shift();
11504
12293
  let action;
11505
12294
  let rest;
11506
- 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") {
11507
12296
  action = argv.shift();
11508
12297
  rest = argv;
11509
12298
  } else {
@@ -11536,6 +12325,12 @@ async function main(argv = process.argv.slice(2)) {
11536
12325
  if (scope === "scheduler" && action === "attest-cutover") {
11537
12326
  return runSchedulerAttestCutoverCli(args.json === true);
11538
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
+ }
11539
12334
  if (scope === "board" && action === "contract") {
11540
12335
  return void await runCommandCenterContractCli(args);
11541
12336
  }
@@ -11903,6 +12698,7 @@ export {
11903
12698
  HERMES_OPENAI_CODEX_DEFAULT_MODEL,
11904
12699
  OPENAI_CODEX_PROVIDER,
11905
12700
  PACKAGE_VERSION,
12701
+ RUN_METADATA_ACTIVE_SIGNAL_MS,
11906
12702
  TRANSIENT_OPENAI_CODEX_ERROR_CLASSES,
11907
12703
  applyProductionDatabaseToProcess,
11908
12704
  assessAutoCompleteEligibility,
@@ -11931,6 +12727,7 @@ export {
11931
12727
  classifyVercelUrl,
11932
12728
  classifyWorkerHealth,
11933
12729
  codexProvider,
12730
+ collectFilesystemLiveRunKeys,
11934
12731
  collectVercelEvidence,
11935
12732
  compareProviderCandidates,
11936
12733
  completeWorker,
@@ -11974,6 +12771,7 @@ export {
11974
12771
  isFinishedWorkerStatus,
11975
12772
  isForbiddenWorkerEnvKey,
11976
12773
  isGeneratedHarnessPath,
12774
+ isHarnessRunMetadataPath,
11977
12775
  isInspectableVercelTarget,
11978
12776
  isKynverMonorepoRoot,
11979
12777
  isLandingBlockedWorkerStatus,
@@ -12026,6 +12824,7 @@ export {
12026
12824
  redactHarness,
12027
12825
  redactProviderErrorText,
12028
12826
  remediateDefaultRepo,
12827
+ repairMissingRunMetadata,
12029
12828
  repairNestedRunsPath,
12030
12829
  resolveBaseUrl,
12031
12830
  resolveBoxKindFromEnv,
@@ -12042,6 +12841,7 @@ export {
12042
12841
  resolveWorkerJsonPath,
12043
12842
  runBoundedBuildCheck,
12044
12843
  runDaemon,
12844
+ runDirHasActiveRetentionSignals,
12045
12845
  runHarnessCleanup,
12046
12846
  runHarnessVerifyCommands,
12047
12847
  runMonitorTick,
@@ -12072,6 +12872,7 @@ export {
12072
12872
  validateRunId,
12073
12873
  validateTailLines,
12074
12874
  validateWorkerName,
12875
+ workerDirHasActiveRetentionSignals,
12075
12876
  workerStatus
12076
12877
  };
12077
12878
  //# sourceMappingURL=index.js.map