@kynver-app/runtime 0.1.76 → 0.1.79

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -746,23 +746,23 @@ function isWslHost() {
746
746
  function observeWslHostDisk(options = {}) {
747
747
  const wsl = options.forceWsl === void 0 ? isWslHost() : options.forceWsl;
748
748
  if (!wsl) return null;
749
- const path59 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
749
+ const path62 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
750
750
  const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;
751
751
  const criticalBelowBytes = options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;
752
752
  const statfs = options.statfs ?? statfsSync;
753
753
  let stats;
754
754
  try {
755
- stats = statfs(path59);
755
+ stats = statfs(path62);
756
756
  } catch (error) {
757
757
  return {
758
758
  ok: false,
759
- path: path59,
759
+ path: path62,
760
760
  freeBytes: 0,
761
761
  totalBytes: 0,
762
762
  usedPercent: 100,
763
763
  warnBelowBytes,
764
764
  criticalBelowBytes,
765
- reason: `Windows host disk probe failed at ${path59}: ${error.message}`,
765
+ reason: `Windows host disk probe failed at ${path62}: ${error.message}`,
766
766
  probeError: error.message
767
767
  };
768
768
  }
@@ -776,11 +776,11 @@ function observeWslHostDisk(options = {}) {
776
776
  let reason = null;
777
777
  if (!ok) {
778
778
  const tag = criticalFree ? "critical" : "warning";
779
- reason = `Windows host disk ${path59} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
779
+ reason = `Windows host disk ${path62} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
780
780
  }
781
781
  return {
782
782
  ok,
783
- path: path59,
783
+ path: path62,
784
784
  freeBytes,
785
785
  totalBytes,
786
786
  usedPercent,
@@ -800,12 +800,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
800
800
  var DEFAULT_MAX_USED_PERCENT = 80;
801
801
  var DEFAULT_HARD_MAX_USED_PERCENT = 90;
802
802
  function observeRunnerDiskGate(input = {}) {
803
- const path59 = input.diskPath?.trim() || "/";
803
+ const path62 = input.diskPath?.trim() || "/";
804
804
  const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
805
805
  const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
806
806
  const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
807
807
  const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
808
- const stats = statfsSync2(path59);
808
+ const stats = statfsSync2(path62);
809
809
  const freeBytes = Number(stats.bavail) * Number(stats.bsize);
810
810
  const totalBytes = Number(stats.blocks) * Number(stats.bsize);
811
811
  const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
@@ -828,7 +828,7 @@ function observeRunnerDiskGate(input = {}) {
828
828
  }
829
829
  return {
830
830
  ok,
831
- path: path59,
831
+ path: path62,
832
832
  freeBytes,
833
833
  totalBytes,
834
834
  usedPercent,
@@ -1813,7 +1813,9 @@ function computeWorkerStatus(worker, options = {}) {
1813
1813
  finalResult,
1814
1814
  error,
1815
1815
  changedFiles,
1816
- gitAncestry
1816
+ gitAncestry,
1817
+ instructionPolicyFingerprint: worker.instructionPolicyFingerprint ?? null,
1818
+ instructionPolicyEvidence: worker.instructionPolicyEvidence ?? null
1817
1819
  };
1818
1820
  }
1819
1821
  function isFinishedWorkerStatus(status) {
@@ -2578,7 +2580,9 @@ function resolveOrchestrationRouting(input) {
2578
2580
  const risk = classifyOrchestrationRisk(task);
2579
2581
  const inventory = resolveInventory({
2580
2582
  inventory: input.inventory,
2581
- codexBinding: input.codexBinding ?? resolveCodexOrchestrationAdapter()
2583
+ // When callers pass a pre-built inventory (tests, CC snapshots), do not probe live
2584
+ // Hermes/Codex bindings — that would overwrite mocked oauth_local with subscription_hermes.
2585
+ codexBinding: input.codexBinding ?? (input.inventory ? null : resolveCodexOrchestrationAdapter())
2582
2586
  });
2583
2587
  const explicit = input.preferLowCost === false ? null : input.explicitProvider?.trim().toLowerCase();
2584
2588
  const explicitModel = input.explicitModel?.trim() || void 0;
@@ -3392,6 +3396,7 @@ function buildPrompt(input) {
3392
3396
  "",
3393
3397
  ...input.personaMarkdown?.trim() ? [input.personaMarkdown.trim(), ""] : [],
3394
3398
  ...input.instructionPolicyMarkdown?.trim() ? ["Operating rules (Lane A \u2014 from AgentOS memory policy):", input.instructionPolicyMarkdown.trim(), ""] : [],
3399
+ ...input.memoryQualityMarkdown?.trim() ? [input.memoryQualityMarkdown.trim(), ""] : [],
3395
3400
  ...input.repairTargetPrUrl ? [
3396
3401
  ...repairTargetPromptLines({
3397
3402
  targetPrUrl: input.repairTargetPrUrl,
@@ -3600,6 +3605,21 @@ function persistCompletionAck(worker, runId, fields) {
3600
3605
  saveWorker(runId, worker);
3601
3606
  }
3602
3607
 
3608
+ // src/completion-replay.ts
3609
+ function trimBlocker(value) {
3610
+ if (typeof value !== "string") return null;
3611
+ const trimmed = value.trim();
3612
+ return trimmed.length ? trimmed : null;
3613
+ }
3614
+ function shouldReplayHarnessCompletion(worker) {
3615
+ if (trimBlocker(worker.completionBlocker)) return true;
3616
+ if (worker.completionOutcome === "rejected") return true;
3617
+ return false;
3618
+ }
3619
+ function hasTerminalCompletionAck(worker) {
3620
+ return hasCompletionAck(worker) && !shouldReplayHarnessCompletion(worker);
3621
+ }
3622
+
3603
3623
  // src/worker-ops.ts
3604
3624
  import path17 from "node:path";
3605
3625
 
@@ -4171,8 +4191,8 @@ function dirtyPathsCoveredByDisposableRemoval(changedFiles, removed) {
4171
4191
  if (removed.length === 0) return false;
4172
4192
  const removedSet = new Set(removed.map((p) => normalizeRelativePath(p)));
4173
4193
  return material.every((line) => {
4174
- const path59 = normalizeRelativePath(pathFromGitStatusLine(line));
4175
- return removedSet.has(path59);
4194
+ const path62 = normalizeRelativePath(pathFromGitStatusLine(line));
4195
+ return removedSet.has(path62);
4176
4196
  });
4177
4197
  }
4178
4198
 
@@ -4418,7 +4438,7 @@ async function tryCompleteWorker(args) {
4418
4438
  return { ok: true, skipped: true, reason: "worker-not-finished" };
4419
4439
  }
4420
4440
  const forceReplay = args.force === true || args.force === "true";
4421
- if (!forceReplay && hasCompletionAck(worker)) {
4441
+ if (!forceReplay && hasTerminalCompletionAck(worker)) {
4422
4442
  return {
4423
4443
  ok: true,
4424
4444
  skipped: true,
@@ -4594,7 +4614,9 @@ async function completeWorker(args) {
4594
4614
  }
4595
4615
  }
4596
4616
  function workerStatus(args) {
4597
- const worker = loadWorker(String(args.run), String(args.name));
4617
+ const runId = required(typeof args.run === "string" ? args.run : "", "--run");
4618
+ const name = required(typeof args.name === "string" ? args.name : "", "--name");
4619
+ const worker = loadWorker(runId, name);
4598
4620
  const run = loadRun(worker.runId);
4599
4621
  const status = computeWorkerStatus(worker, workerStatusOptions(run));
4600
4622
  writeJson(path17.join(worker.workerDir, "last-status.json"), status);
@@ -4810,7 +4832,7 @@ async function autoCompleteWorker(raw) {
4810
4832
  reason: "worker has no agentOsId/taskId \u2014 nothing to attribute completion to"
4811
4833
  };
4812
4834
  }
4813
- if (hasCompletionAck(worker)) {
4835
+ if (hasTerminalCompletionAck(worker)) {
4814
4836
  return {
4815
4837
  worker: worker.name,
4816
4838
  runId: worker.runId,
@@ -4823,7 +4845,7 @@ async function autoCompleteWorker(raw) {
4823
4845
  const startMs = Date.now();
4824
4846
  while (true) {
4825
4847
  worker = loadWorker(args.run, args.name);
4826
- if (hasCompletionAck(worker)) {
4848
+ if (hasTerminalCompletionAck(worker)) {
4827
4849
  return {
4828
4850
  worker: worker.name,
4829
4851
  runId: worker.runId,
@@ -5029,6 +5051,7 @@ function spawnWorkerProcess(run, opts) {
5029
5051
  planId: opts.planId,
5030
5052
  taskId: opts.taskId,
5031
5053
  instructionPolicyMarkdown: opts.instructionPolicyMarkdown,
5054
+ memoryQualityMarkdown: opts.memoryQualityPromptMarkdown ?? opts.memoryQualityCapture?.promptMarkdown ?? null,
5032
5055
  personaMarkdown: opts.personaMarkdown,
5033
5056
  model: launchModel,
5034
5057
  repairTargetPrUrl: opts.repairTargetPrUrl,
@@ -5807,11 +5830,13 @@ function readHarnessWorkerContext(decision) {
5807
5830
  const personaEvidence = ctx.personaEvidence && typeof ctx.personaEvidence === "object" ? ctx.personaEvidence : null;
5808
5831
  const personaInjectionReady = ctx.personaInjectionReady === true;
5809
5832
  const memoryQualityCapture = ctx.memoryQualityCapture && typeof ctx.memoryQualityCapture === "object" ? ctx.memoryQualityCapture : null;
5833
+ const memoryQualityPromptMarkdown = typeof ctx.memoryQualityPromptMarkdown === "string" ? ctx.memoryQualityPromptMarkdown : typeof memoryQualityCapture?.promptMarkdown === "string" ? memoryQualityCapture.promptMarkdown : null;
5810
5834
  return {
5811
5835
  instructionPolicyMarkdown: markdown,
5812
5836
  instructionPolicyFingerprint: fingerprint,
5813
5837
  instructionPolicyEvidence: evidence,
5814
5838
  memoryQualityCapture,
5839
+ memoryQualityPromptMarkdown,
5815
5840
  personaMarkdown,
5816
5841
  personaSlug,
5817
5842
  personaEvidence,
@@ -6068,6 +6093,7 @@ async function dispatchRun(args) {
6068
6093
  instructionPolicyFingerprint: harnessContext?.instructionPolicyFingerprint ?? null,
6069
6094
  instructionPolicyEvidence: harnessContext?.instructionPolicyEvidence ?? null,
6070
6095
  memoryQualityCapture: harnessContext?.memoryQualityCapture ?? null,
6096
+ memoryQualityPromptMarkdown: harnessContext?.memoryQualityPromptMarkdown ?? null,
6071
6097
  personaMarkdown: harnessContext?.personaMarkdown ?? null,
6072
6098
  personaSlug: harnessContext?.personaSlug ?? expectedPersona,
6073
6099
  personaEvidence: harnessContext?.personaEvidence ?? null,
@@ -6122,7 +6148,23 @@ async function dispatchRun(args) {
6122
6148
  for (const decision of result.started) {
6123
6149
  shouldContinueDispatch = await spawnClaimed(decision) && shouldContinueDispatch;
6124
6150
  }
6125
- skipped.push(...result.skipped ?? []);
6151
+ skipped.push(
6152
+ ...result.skipped ?? []
6153
+ );
6154
+ if (exactTargetMode) {
6155
+ for (const skipDecision of skipped) {
6156
+ const taskId = String(skipDecision.task.id);
6157
+ if (!exactTargetIds.has(taskId)) continue;
6158
+ outcomes.push({
6159
+ taskId,
6160
+ started: false,
6161
+ error: `exact_target_not_started:${skipDecision.skipReason}`,
6162
+ skipReason: skipDecision.skipReason,
6163
+ detail: skipDecision.reason ?? null,
6164
+ requestedTargetTaskIds: [...exactTargetIds]
6165
+ });
6166
+ }
6167
+ }
6126
6168
  if (exactTargetMode) shouldContinueDispatch = false;
6127
6169
  while (shouldContinueDispatch && outcomes.length < cappedStarts) {
6128
6170
  if (exactTargetMode) break;
@@ -6235,15 +6277,15 @@ async function sweepRun(args) {
6235
6277
  }
6236
6278
 
6237
6279
  // src/worktree.ts
6238
- import { existsSync as existsSync23, mkdirSync as mkdirSync5 } from "node:fs";
6239
- import path32 from "node:path";
6280
+ import { existsSync as existsSync24, mkdirSync as mkdirSync5 } from "node:fs";
6281
+ import path33 from "node:path";
6240
6282
 
6241
6283
  // src/run-list.ts
6242
- import { existsSync as existsSync22, readFileSync as readFileSync10 } from "node:fs";
6243
- import path29 from "node:path";
6284
+ import { existsSync as existsSync23, readFileSync as readFileSync10 } from "node:fs";
6285
+ import path31 from "node:path";
6244
6286
 
6245
6287
  // src/stale-reconcile.ts
6246
- import path28 from "node:path";
6288
+ import path30 from "node:path";
6247
6289
 
6248
6290
  // src/finalize.ts
6249
6291
  import path25 from "node:path";
@@ -6304,8 +6346,8 @@ function finalizeStaleRuns() {
6304
6346
  }
6305
6347
 
6306
6348
  // src/worker-metadata-reconcile.ts
6307
- import { existsSync as existsSync21, lstatSync, readdirSync as readdirSync5, readlinkSync, renameSync as renameSync2, rmSync } from "node:fs";
6308
- import path27 from "node:path";
6349
+ import { existsSync as existsSync22, lstatSync, readdirSync as readdirSync6, readlinkSync, renameSync as renameSync2, rmSync } from "node:fs";
6350
+ import path29 from "node:path";
6309
6351
 
6310
6352
  // src/worker-metadata-paths.ts
6311
6353
  import path26 from "node:path";
@@ -6350,6 +6392,192 @@ function resolveWorkerJsonPath(input) {
6350
6392
  return canonical;
6351
6393
  }
6352
6394
 
6395
+ // src/run-metadata-retention.ts
6396
+ import { existsSync as existsSync21, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
6397
+ import path28 from "node:path";
6398
+
6399
+ // src/default-repo.ts
6400
+ import path27 from "node:path";
6401
+ function expandConfiguredRepo(value) {
6402
+ return path27.resolve(resolveUserPath(value.trim()));
6403
+ }
6404
+ function fromConfigured(value, source, persistedInConfig) {
6405
+ const trimmed = value?.trim();
6406
+ if (!trimmed) return null;
6407
+ return {
6408
+ repo: expandConfiguredRepo(trimmed),
6409
+ source,
6410
+ persistedInConfig
6411
+ };
6412
+ }
6413
+ function resolveDefaultRepo(opts = {}) {
6414
+ const env = opts.env ?? process.env;
6415
+ const config = opts.config ?? loadUserConfig();
6416
+ const fromConfig = fromConfigured(config.defaultRepo, "config", true);
6417
+ if (fromConfig) return fromConfig;
6418
+ const fromDefaultEnv = fromConfigured(env.KYNVER_DEFAULT_REPO, "env_default_repo", false);
6419
+ if (fromDefaultEnv) return fromDefaultEnv;
6420
+ const fromHarnessEnv = fromConfigured(env.KYNVER_HARNESS_REPO, "env_harness_repo", false);
6421
+ if (fromHarnessEnv) return fromHarnessEnv;
6422
+ const discovered = discoverDefaultRepo({
6423
+ cwd: opts.cwd,
6424
+ runtimeModuleUrl: opts.runtimeModuleUrl
6425
+ });
6426
+ if (!discovered) return null;
6427
+ return {
6428
+ repo: discovered.repo,
6429
+ source: discovered.source,
6430
+ persistedInConfig: false
6431
+ };
6432
+ }
6433
+ function formatResolvedDefaultRepo(resolved) {
6434
+ return {
6435
+ defaultRepo: displayUserPath(resolved.repo),
6436
+ source: resolved.source,
6437
+ persistedInConfig: resolved.persistedInConfig
6438
+ };
6439
+ }
6440
+
6441
+ // src/run-metadata-retention.ts
6442
+ var RUN_METADATA_ACTIVE_SIGNAL_MS = 15 * 60 * 1e3;
6443
+ function isHarnessRunMetadataPath(targetPath, harnessRoot) {
6444
+ const resolved = path28.resolve(targetPath);
6445
+ const runsDir = path28.resolve(harnessRunsDir(harnessRoot));
6446
+ const rel = path28.relative(runsDir, resolved);
6447
+ return rel !== ".." && !rel.startsWith("..") && !path28.isAbsolute(rel);
6448
+ }
6449
+ function listRunDirIds(runsDir) {
6450
+ if (!existsSync21(runsDir)) return [];
6451
+ try {
6452
+ return readdirSync5(runsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name !== "runs").map((entry) => entry.name);
6453
+ } catch {
6454
+ return [];
6455
+ }
6456
+ }
6457
+ function listWorkerNamesOnDisk(runDir2) {
6458
+ const workersDir = path28.join(runDir2, "workers");
6459
+ if (!existsSync21(workersDir)) return [];
6460
+ try {
6461
+ return readdirSync5(workersDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
6462
+ } catch {
6463
+ return [];
6464
+ }
6465
+ }
6466
+ function pathRecentlyTouched(target, now, windowMs) {
6467
+ if (!existsSync21(target)) return false;
6468
+ try {
6469
+ const age = now - statSync4(target).mtimeMs;
6470
+ return Number.isFinite(age) && age >= 0 && age < windowMs;
6471
+ } catch {
6472
+ return false;
6473
+ }
6474
+ }
6475
+ function workerDirHasActiveRetentionSignals(workerDir, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
6476
+ if (!existsSync21(workerDir)) return false;
6477
+ const artifacts = workerArtifactPaths(workerDir);
6478
+ const worker = readJson(
6479
+ artifacts.workerJsonPath,
6480
+ void 0
6481
+ );
6482
+ if (worker?.status === "running" && isPidAlive(worker.pid)) return true;
6483
+ const heartbeat = parseHeartbeat(artifacts.heartbeatPath);
6484
+ if (heartbeat.lastHeartbeatAt) {
6485
+ const age = now - Date.parse(heartbeat.lastHeartbeatAt);
6486
+ if (Number.isFinite(age) && age >= 0 && age < windowMs) return true;
6487
+ }
6488
+ if (pathRecentlyTouched(artifacts.stdoutPath, now, windowMs)) return true;
6489
+ if (pathRecentlyTouched(artifacts.heartbeatPath, now, windowMs)) return true;
6490
+ return false;
6491
+ }
6492
+ function runDirHasActiveRetentionSignals(runDir2, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
6493
+ for (const name of listWorkerNamesOnDisk(runDir2)) {
6494
+ if (workerDirHasActiveRetentionSignals(path28.join(runDir2, "workers", name), now, windowMs)) {
6495
+ return true;
6496
+ }
6497
+ }
6498
+ return false;
6499
+ }
6500
+ function inferRepoFields() {
6501
+ const resolved = resolveDefaultRepo();
6502
+ return {
6503
+ repo: resolved?.repo ?? "",
6504
+ base: "origin/main",
6505
+ baseCommit: "unknown"
6506
+ };
6507
+ }
6508
+ function synthesizeRunFromDisk(harnessRoot, runId) {
6509
+ const runDir2 = path28.join(harnessRunsDir(harnessRoot), safeSlug(runId));
6510
+ if (!existsSync21(runDir2)) return null;
6511
+ const workerNames = listWorkerNamesOnDisk(runDir2);
6512
+ if (workerNames.length === 0) return null;
6513
+ let createdAt;
6514
+ try {
6515
+ createdAt = statSync4(runDir2).birthtime.toISOString();
6516
+ } catch {
6517
+ createdAt = (/* @__PURE__ */ new Date()).toISOString();
6518
+ }
6519
+ const workers = {};
6520
+ for (const name of workerNames) {
6521
+ const canonicalDir = canonicalWorkerDir(harnessRoot, runId, name);
6522
+ workers[name] = {
6523
+ workerDir: canonicalDir,
6524
+ statusPath: path28.join(canonicalDir, "worker.json")
6525
+ };
6526
+ }
6527
+ const hasActive = runDirHasActiveRetentionSignals(runDir2);
6528
+ return {
6529
+ id: runId,
6530
+ name: runId,
6531
+ ...inferRepoFields(),
6532
+ status: hasActive ? "running" : "needs_attention",
6533
+ createdAt,
6534
+ workers
6535
+ };
6536
+ }
6537
+ function repairMissingRunMetadata(harnessRootInput) {
6538
+ const harnessRoot = normalizeHarnessRoot(harnessRootInput ?? resolveHarnessRoot());
6539
+ const runsDir = harnessRunsDir(harnessRoot);
6540
+ const outcomes = [];
6541
+ for (const runId of listRunDirIds(runsDir)) {
6542
+ const runJsonPath = path28.join(runsDir, runId, "run.json");
6543
+ if (existsSync21(runJsonPath)) {
6544
+ outcomes.push({
6545
+ runId,
6546
+ action: "skipped",
6547
+ reason: "run.json present"
6548
+ });
6549
+ continue;
6550
+ }
6551
+ const synthesized = synthesizeRunFromDisk(harnessRoot, runId);
6552
+ if (!synthesized) {
6553
+ outcomes.push({
6554
+ runId,
6555
+ action: "skipped",
6556
+ reason: "no worker dirs on disk"
6557
+ });
6558
+ continue;
6559
+ }
6560
+ saveRun(synthesized);
6561
+ outcomes.push({
6562
+ runId,
6563
+ action: "repaired_run_json",
6564
+ reason: runDirHasActiveRetentionSignals(path28.join(runsDir, runId)) ? "synthesized run.json while worker artifacts still active" : "synthesized run.json from orphan worker metadata dirs"
6565
+ });
6566
+ }
6567
+ return { runs: outcomes };
6568
+ }
6569
+ function collectFilesystemLiveRunKeys(harnessRoot, now = Date.now()) {
6570
+ const keys = /* @__PURE__ */ new Set();
6571
+ const runsDir = harnessRunsDir(harnessRoot);
6572
+ for (const runId of listRunDirIds(runsDir)) {
6573
+ const runDir2 = path28.join(runsDir, runId);
6574
+ if (runDirHasActiveRetentionSignals(runDir2, now)) {
6575
+ keys.add(`${harnessRoot}\0${runId}`);
6576
+ }
6577
+ }
6578
+ return keys;
6579
+ }
6580
+
6353
6581
  // src/worker-metadata-reconcile.ts
6354
6582
  function materializeSymlinkedRunDir(harnessRoot, runId) {
6355
6583
  const canonical = canonicalRunDir(harnessRoot, runId);
@@ -6360,7 +6588,10 @@ function materializeSymlinkedRunDir(harnessRoot, runId) {
6360
6588
  return null;
6361
6589
  }
6362
6590
  if (!stat.isSymbolicLink()) return null;
6363
- const linkedTarget = path27.resolve(path27.dirname(canonical), readlinkSync(canonical));
6591
+ const linkedTarget = path29.resolve(path29.dirname(canonical), readlinkSync(canonical));
6592
+ if (runDirHasActiveRetentionSignals(linkedTarget) || runDirHasActiveRetentionSignals(canonical)) {
6593
+ return null;
6594
+ }
6364
6595
  const staging = `${canonical}.materialize-${Date.now()}`;
6365
6596
  renameSync2(linkedTarget, staging);
6366
6597
  rmSync(canonical);
@@ -6370,9 +6601,9 @@ function materializeSymlinkedRunDir(harnessRoot, runId) {
6370
6601
  function relocateArtifacts(input) {
6371
6602
  const moved = [];
6372
6603
  for (const fileName of workerArtifactFileNames()) {
6373
- const from = path27.join(input.fromDir, fileName);
6374
- const to = path27.join(input.toDir, fileName);
6375
- if (!existsSync21(from) || existsSync21(to)) continue;
6604
+ const from = path29.join(input.fromDir, fileName);
6605
+ const to = path29.join(input.toDir, fileName);
6606
+ if (!existsSync22(from) || existsSync22(to)) continue;
6376
6607
  renameSync2(from, to);
6377
6608
  moved.push(fileName);
6378
6609
  }
@@ -6411,8 +6642,8 @@ function deriveSynthesizedStatus(input) {
6411
6642
  function synthesizeWorkerFromArtifacts(input) {
6412
6643
  const artifacts = workerArtifactPaths(input.canonicalDir);
6413
6644
  const lastStatus = readJson(artifacts.lastStatusPath, void 0);
6414
- const stdoutExists = existsSync21(artifacts.stdoutPath);
6415
- const heartbeatExists = existsSync21(artifacts.heartbeatPath);
6645
+ const stdoutExists = existsSync22(artifacts.stdoutPath);
6646
+ const heartbeatExists = existsSync22(artifacts.heartbeatPath);
6416
6647
  const hasArtifacts = stdoutExists || heartbeatExists || lastStatus != null;
6417
6648
  if (!hasArtifacts) return null;
6418
6649
  const parsedStdout = stdoutExists ? parseHarnessStream(artifacts.stdoutPath) : { finalResult: null };
@@ -6428,7 +6659,7 @@ function synthesizeWorkerFromArtifacts(input) {
6428
6659
  runId: input.run.id,
6429
6660
  status,
6430
6661
  branch: typeof lastStatus?.branch === "string" ? lastStatus.branch : `agent/${input.run.id}/${input.workerName}`,
6431
- worktreePath: typeof lastStatus?.worktreePath === "string" ? lastStatus.worktreePath : path27.join(getPaths().worktreesDir, input.run.id, input.workerName),
6662
+ worktreePath: typeof lastStatus?.worktreePath === "string" ? lastStatus.worktreePath : path29.join(getPaths().worktreesDir, input.run.id, input.workerName),
6432
6663
  workerDir: input.canonicalDir,
6433
6664
  stdoutPath: artifacts.stdoutPath,
6434
6665
  stderrPath: artifacts.stderrPath,
@@ -6455,7 +6686,7 @@ function repairRunWorkerIndex(run, harnessRoot) {
6455
6686
  const nextWorkers = { ...run.workers || {} };
6456
6687
  for (const [name, ref] of Object.entries(nextWorkers)) {
6457
6688
  const canonicalDir = canonicalWorkerDir(harnessRoot, run.id, name);
6458
- const canonicalStatus = path27.join(canonicalDir, "worker.json");
6689
+ const canonicalStatus = path29.join(canonicalDir, "worker.json");
6459
6690
  const nested = hasNestedRunsSegment(ref.workerDir) || hasNestedRunsSegment(ref.statusPath) || ref.workerDir !== canonicalDir || ref.statusPath !== canonicalStatus;
6460
6691
  if (!nested) continue;
6461
6692
  nextWorkers[name] = { workerDir: canonicalDir, statusPath: canonicalStatus };
@@ -6488,7 +6719,7 @@ function reconcileOneWorker(input) {
6488
6719
  statusPath: input.statusPath
6489
6720
  });
6490
6721
  let worker = readJson(workerJsonPath, void 0);
6491
- if (!worker && existsSync21(canonicalArtifacts.workerJsonPath)) {
6722
+ if (!worker && existsSync22(canonicalArtifacts.workerJsonPath)) {
6492
6723
  worker = readJson(canonicalArtifacts.workerJsonPath, void 0);
6493
6724
  }
6494
6725
  if (!worker) {
@@ -6508,15 +6739,15 @@ function reconcileOneWorker(input) {
6508
6739
  });
6509
6740
  return outcomes;
6510
6741
  }
6511
- const dirExists = existsSync21(canonicalDir);
6512
- const hasAnyArtifact = workerArtifactFileNames().some((f) => existsSync21(path27.join(canonicalDir, f)));
6742
+ const dirExists = existsSync22(canonicalDir);
6743
+ const hasAnyArtifact = workerArtifactFileNames().some((f) => existsSync22(path29.join(canonicalDir, f)));
6513
6744
  if (dirExists && !hasAnyArtifact) {
6514
6745
  const abandoned = {
6515
6746
  name: input.workerName,
6516
6747
  runId: input.run.id,
6517
6748
  status: "exited",
6518
6749
  branch: `agent/${input.run.id}/${input.workerName}`,
6519
- worktreePath: path27.join(getPaths().worktreesDir, input.run.id, input.workerName),
6750
+ worktreePath: path29.join(getPaths().worktreesDir, input.run.id, input.workerName),
6520
6751
  workerDir: canonicalDir,
6521
6752
  stdoutPath: canonicalArtifacts.stdoutPath,
6522
6753
  stderrPath: canonicalArtifacts.stderrPath,
@@ -6563,11 +6794,11 @@ function reconcileOneWorker(input) {
6563
6794
  return outcomes;
6564
6795
  }
6565
6796
  function listOrphanWorkerNames(run, harnessRoot) {
6566
- const workersDir = path27.join(runDirectory(run.id), "workers");
6567
- if (!existsSync21(workersDir)) return [];
6797
+ const workersDir = path29.join(runDirectory(run.id), "workers");
6798
+ if (!existsSync22(workersDir)) return [];
6568
6799
  const indexed = new Set(Object.keys(run.workers || {}));
6569
6800
  const orphans = [];
6570
- for (const entry of readdirSync5(workersDir, { withFileTypes: true })) {
6801
+ for (const entry of readdirSync6(workersDir, { withFileTypes: true })) {
6571
6802
  if (!entry.isDirectory()) continue;
6572
6803
  if (indexed.has(entry.name)) continue;
6573
6804
  orphans.push(entry.name);
@@ -6576,6 +6807,7 @@ function listOrphanWorkerNames(run, harnessRoot) {
6576
6807
  }
6577
6808
  function reconcileWorkerMetadata() {
6578
6809
  const { harnessRoot } = getPaths();
6810
+ const runMetadataRetention = repairMissingRunMetadata(harnessRoot);
6579
6811
  const outcomes = [];
6580
6812
  for (const run of listRunRecords()) {
6581
6813
  const materializedFrom = materializeSymlinkedRunDir(harnessRoot, run.id);
@@ -6602,7 +6834,7 @@ function reconcileWorkerMetadata() {
6602
6834
  ...run.workers || {},
6603
6835
  [orphan]: {
6604
6836
  workerDir: canonicalWorkerDir(harnessRoot, run.id, orphan),
6605
- statusPath: path27.join(canonicalWorkerDir(harnessRoot, run.id, orphan), "worker.json")
6837
+ statusPath: path29.join(canonicalWorkerDir(harnessRoot, run.id, orphan), "worker.json")
6606
6838
  }
6607
6839
  };
6608
6840
  saveRun(run);
@@ -6625,7 +6857,7 @@ function reconcileWorkerMetadata() {
6625
6857
  );
6626
6858
  }
6627
6859
  }
6628
- return { workers: outcomes };
6860
+ return { workers: outcomes, runMetadataRetention };
6629
6861
  }
6630
6862
 
6631
6863
  // src/stale-reconcile.ts
@@ -6642,7 +6874,7 @@ function reconcileStaleWorkers() {
6642
6874
  const now = Date.now();
6643
6875
  for (const run of listRunRecords()) {
6644
6876
  for (const name of Object.keys(run.workers || {})) {
6645
- const workerPath = path28.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
6877
+ const workerPath = path30.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
6646
6878
  const worker = readJson(workerPath, void 0);
6647
6879
  if (!worker || worker.status !== "running") {
6648
6880
  outcomes.push({
@@ -6725,16 +6957,28 @@ function reconcileRunsCli() {
6725
6957
  acc[row.action] = (acc[row.action] ?? 0) + 1;
6726
6958
  return acc;
6727
6959
  }, {});
6960
+ const runRetentionTotals = result.metadataReconcile.runMetadataRetention.runs.reduce((acc, row) => {
6961
+ acc[row.action] = (acc[row.action] ?? 0) + 1;
6962
+ return acc;
6963
+ }, {});
6728
6964
  console.log(
6729
6965
  JSON.stringify(
6730
6966
  {
6731
6967
  ok: true,
6732
6968
  workers: { markedExited, killedStale, skipped, total: result.workers.length },
6733
- metadataReconcile: { totals: metadataTotals, total: result.metadataReconcile.workers.length },
6969
+ metadataReconcile: {
6970
+ totals: metadataTotals,
6971
+ total: result.metadataReconcile.workers.length,
6972
+ runMetadataRetention: {
6973
+ totals: runRetentionTotals,
6974
+ total: result.metadataReconcile.runMetadataRetention.runs.length
6975
+ }
6976
+ },
6734
6977
  finalizedRuns: result.finalizedRuns.length,
6735
6978
  details: {
6736
6979
  workers: result.workers,
6737
6980
  metadataReconcile: result.metadataReconcile.workers,
6981
+ runMetadataRetention: result.metadataReconcile.runMetadataRetention.runs,
6738
6982
  finalizedRuns: result.finalizedRuns
6739
6983
  }
6740
6984
  },
@@ -6746,7 +6990,7 @@ function reconcileRunsCli() {
6746
6990
 
6747
6991
  // src/run-list.ts
6748
6992
  function heartbeatByteLength(heartbeatPath) {
6749
- if (!heartbeatPath || !existsSync22(heartbeatPath)) return 0;
6993
+ if (!heartbeatPath || !existsSync23(heartbeatPath)) return 0;
6750
6994
  try {
6751
6995
  return readFileSync10(heartbeatPath, "utf8").trim().length;
6752
6996
  } catch {
@@ -6754,7 +6998,7 @@ function heartbeatByteLength(heartbeatPath) {
6754
6998
  }
6755
6999
  }
6756
7000
  function workerEvidence(run, workerName) {
6757
- const workerPath = path29.join(runDirectory(run.id), "workers", safeSlug(workerName), "worker.json");
7001
+ const workerPath = path31.join(runDirectory(run.id), "workers", safeSlug(workerName), "worker.json");
6758
7002
  const worker = readJson(workerPath, void 0);
6759
7003
  if (!worker) {
6760
7004
  return {
@@ -6811,7 +7055,7 @@ function aggregateRunAttention(workers) {
6811
7055
  function countOpenWorkers(run) {
6812
7056
  let open = 0;
6813
7057
  for (const name of Object.keys(run.workers || {})) {
6814
- const workerPath = path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
7058
+ const workerPath = path31.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
6815
7059
  const worker = readJson(workerPath, void 0);
6816
7060
  if (!worker) continue;
6817
7061
  const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
@@ -6859,50 +7103,8 @@ function listRunsCli() {
6859
7103
  console.log(JSON.stringify(buildRunListRows(), null, 2));
6860
7104
  }
6861
7105
 
6862
- // src/default-repo.ts
6863
- import path30 from "node:path";
6864
- function expandConfiguredRepo(value) {
6865
- return path30.resolve(resolveUserPath(value.trim()));
6866
- }
6867
- function fromConfigured(value, source, persistedInConfig) {
6868
- const trimmed = value?.trim();
6869
- if (!trimmed) return null;
6870
- return {
6871
- repo: expandConfiguredRepo(trimmed),
6872
- source,
6873
- persistedInConfig
6874
- };
6875
- }
6876
- function resolveDefaultRepo(opts = {}) {
6877
- const env = opts.env ?? process.env;
6878
- const config = opts.config ?? loadUserConfig();
6879
- const fromConfig = fromConfigured(config.defaultRepo, "config", true);
6880
- if (fromConfig) return fromConfig;
6881
- const fromDefaultEnv = fromConfigured(env.KYNVER_DEFAULT_REPO, "env_default_repo", false);
6882
- if (fromDefaultEnv) return fromDefaultEnv;
6883
- const fromHarnessEnv = fromConfigured(env.KYNVER_HARNESS_REPO, "env_harness_repo", false);
6884
- if (fromHarnessEnv) return fromHarnessEnv;
6885
- const discovered = discoverDefaultRepo({
6886
- cwd: opts.cwd,
6887
- runtimeModuleUrl: opts.runtimeModuleUrl
6888
- });
6889
- if (!discovered) return null;
6890
- return {
6891
- repo: discovered.repo,
6892
- source: discovered.source,
6893
- persistedInConfig: false
6894
- };
6895
- }
6896
- function formatResolvedDefaultRepo(resolved) {
6897
- return {
6898
- defaultRepo: displayUserPath(resolved.repo),
6899
- source: resolved.source,
6900
- persistedInConfig: resolved.persistedInConfig
6901
- };
6902
- }
6903
-
6904
7106
  // src/validate.ts
6905
- import path31 from "node:path";
7107
+ import path32 from "node:path";
6906
7108
  var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
6907
7109
  function validateRunId(runId) {
6908
7110
  const trimmed = runId.trim();
@@ -6910,7 +7112,7 @@ function validateRunId(runId) {
6910
7112
  return trimmed;
6911
7113
  }
6912
7114
  function validateRepo(repo) {
6913
- const resolved = path31.resolve(repo);
7115
+ const resolved = path32.resolve(repo);
6914
7116
  if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
6915
7117
  return resolved;
6916
7118
  }
@@ -6929,7 +7131,7 @@ function createRun(args) {
6929
7131
  ensureGitRepo(repo);
6930
7132
  const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
6931
7133
  const dir = runDirectory(id);
6932
- if (existsSync23(dir)) failExists(`run already exists: ${id}`);
7134
+ if (existsSync24(dir)) failExists(`run already exists: ${id}`);
6933
7135
  mkdirSync5(dir, { recursive: true });
6934
7136
  const base = String(args.base || "origin/main");
6935
7137
  const baseCommit = git(repo, ["rev-parse", base]).trim();
@@ -6943,7 +7145,7 @@ function createRun(args) {
6943
7145
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6944
7146
  workers: {}
6945
7147
  };
6946
- writeJson(path32.join(dir, "run.json"), run);
7148
+ writeJson(path33.join(dir, "run.json"), run);
6947
7149
  console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
6948
7150
  }
6949
7151
  function listRuns() {
@@ -6955,8 +7157,8 @@ function failExists(message) {
6955
7157
  }
6956
7158
 
6957
7159
  // src/discard-disposable.ts
6958
- import { existsSync as existsSync24, rmSync as rmSync2 } from "node:fs";
6959
- import path33 from "node:path";
7160
+ import { existsSync as existsSync25, rmSync as rmSync2 } from "node:fs";
7161
+ import path34 from "node:path";
6960
7162
  function normalizeRelativePath2(value) {
6961
7163
  const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
6962
7164
  if (!normalized || normalized.startsWith("/") || normalized.includes("..")) {
@@ -6977,15 +7179,15 @@ function discardDisposableArtifacts(args) {
6977
7179
  if (paths.length === 0) {
6978
7180
  return { ok: false, removed: [], reason: "requires at least one --path" };
6979
7181
  }
6980
- const worktreeRoot = path33.resolve(worker.worktreePath);
7182
+ const worktreeRoot = path34.resolve(worker.worktreePath);
6981
7183
  const removed = [];
6982
7184
  for (const raw of paths) {
6983
7185
  const rel = normalizeRelativePath2(raw);
6984
- const abs = path33.resolve(worktreeRoot, rel);
6985
- if (!abs.startsWith(worktreeRoot + path33.sep) && abs !== worktreeRoot) {
7186
+ const abs = path34.resolve(worktreeRoot, rel);
7187
+ if (!abs.startsWith(worktreeRoot + path34.sep) && abs !== worktreeRoot) {
6986
7188
  return { ok: false, removed, reason: `path escapes worktree: ${raw}` };
6987
7189
  }
6988
- if (!existsSync24(abs)) {
7190
+ if (!existsSync25(abs)) {
6989
7191
  return { ok: false, removed, reason: `path not found: ${raw}` };
6990
7192
  }
6991
7193
  rmSync2(abs, { recursive: true, force: true });
@@ -7007,8 +7209,472 @@ function discardDisposableCli(args) {
7007
7209
  if (!result.ok) process.exit(1);
7008
7210
  }
7009
7211
 
7212
+ // src/cron/cron-env.ts
7213
+ import { existsSync as existsSync26 } from "node:fs";
7214
+ import { homedir as homedir11 } from "node:os";
7215
+ import path35 from "node:path";
7216
+ function envFlag(name, defaultValue) {
7217
+ const raw = process.env[name]?.trim().toLowerCase();
7218
+ if (!raw) return defaultValue;
7219
+ if (raw === "0" || raw === "false" || raw === "no" || raw === "off") return false;
7220
+ if (raw === "1" || raw === "true" || raw === "yes" || raw === "on") return true;
7221
+ return defaultValue;
7222
+ }
7223
+ function envInt(name, fallback, min = 1) {
7224
+ const n = Number(process.env[name]);
7225
+ if (!Number.isFinite(n) || n < min) return fallback;
7226
+ return Math.floor(n);
7227
+ }
7228
+ function defaultKynverCronStorePath() {
7229
+ const explicit = process.env.KYNVER_CRON_STORE_PATH?.trim() || process.env.OPENCLAW_CRON_STORE_PATH?.trim();
7230
+ if (explicit) return explicit;
7231
+ return path35.join(homedir11(), ".kynver", "agent-os-cron.json");
7232
+ }
7233
+ function defaultKynverCronStatePath(storePath = defaultKynverCronStorePath()) {
7234
+ const explicit = process.env.KYNVER_CRON_TICK_STATE_PATH?.trim();
7235
+ if (explicit) return explicit;
7236
+ return `${storePath.replace(/\.json$/i, "")}.tick-state.json`;
7237
+ }
7238
+ function resolveKynverCronFireBaseUrl() {
7239
+ const config = loadUserConfig();
7240
+ 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;
7241
+ }
7242
+ function resolveKynverCronSecret() {
7243
+ return process.env.KYNVER_CRON_SECRET?.trim() || process.env.OPENCLAW_CRON_SECRET?.trim() || process.env.KYNVER_RUNTIME_SECRET?.trim() || null;
7244
+ }
7245
+ function resolveKynverCronEnv() {
7246
+ const storePath = defaultKynverCronStorePath();
7247
+ const statePath = defaultKynverCronStatePath(storePath);
7248
+ const fireBaseUrl = resolveKynverCronFireBaseUrl();
7249
+ const secret = resolveKynverCronSecret();
7250
+ const credsReady = Boolean(fireBaseUrl && secret);
7251
+ const storeExists = existsSync26(storePath);
7252
+ const defaultEnabled = credsReady && (storeExists || envFlag("KYNVER_CRON_TICK_FORCE", false));
7253
+ return {
7254
+ storePath,
7255
+ statePath,
7256
+ lockPath: `${statePath}.lock`,
7257
+ fireBaseUrl,
7258
+ secret,
7259
+ tickEnabled: envFlag("KYNVER_CRON_TICK_ENABLED", defaultEnabled),
7260
+ tickIntervalMs: envInt("KYNVER_CRON_TICK_INTERVAL_MS", 6e4, 5e3),
7261
+ missedRunPolicy: process.env.KYNVER_CRON_MISSED_RUN_POLICY?.trim().toLowerCase() === "skip" ? "skip" : "catch_up",
7262
+ maxCatchUpPerTick: envInt("KYNVER_CRON_MAX_CATCH_UP_PER_TICK", 3, 0),
7263
+ maxRetries: envInt("KYNVER_CRON_MAX_RETRIES", 3, 0),
7264
+ retryBackoffMs: envInt("KYNVER_CRON_RETRY_BACKOFF_MS", 6e4, 1e3),
7265
+ inflightLeaseMs: envInt("KYNVER_CRON_INFLIGHT_LEASE_MS", 12e4, 5e3)
7266
+ };
7267
+ }
7268
+ function isKynverCronDaemonPrimary(env = resolveKynverCronEnv()) {
7269
+ return env.tickEnabled && Boolean(env.fireBaseUrl && env.secret);
7270
+ }
7271
+
7272
+ // src/cron/cron-fire.ts
7273
+ function trimTrailingSlash2(url) {
7274
+ return url.replace(/\/+$/, "");
7275
+ }
7276
+ async function fireKynverCronJob(input) {
7277
+ const doFetch = input.fetchFn ?? fetch;
7278
+ const callbackPath = input.entry.spec.callbackPath.startsWith("/") ? input.entry.spec.callbackPath : `/${input.entry.spec.callbackPath}`;
7279
+ const url = `${trimTrailingSlash2(input.baseUrl)}${callbackPath}`;
7280
+ const jobId = input.jobId ?? input.entry.spec.dedupeKey ?? null;
7281
+ const body = {
7282
+ source: "kynver-cron",
7283
+ jobId,
7284
+ agentOsId: input.entry.spec.target.agentOsId,
7285
+ kind: input.entry.spec.kind,
7286
+ target: input.entry.spec.target,
7287
+ ...input.entry.spec.payload !== void 0 && { payload: input.entry.spec.payload }
7288
+ };
7289
+ const res = await doFetch(url, {
7290
+ method: "POST",
7291
+ headers: buildHarnessCallbackHeaders(input.secret),
7292
+ body: JSON.stringify(body)
7293
+ });
7294
+ let parsed = null;
7295
+ try {
7296
+ parsed = await res.json();
7297
+ } catch {
7298
+ parsed = null;
7299
+ }
7300
+ return { ok: res.ok, status: res.status, body: parsed };
7301
+ }
7302
+
7303
+ // src/cron/cron-lock.ts
7304
+ import { closeSync as closeSync6, existsSync as existsSync27, openSync as openSync6, readFileSync as readFileSync11, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
7305
+ var STALE_LOCK_MS = 10 * 6e4;
7306
+ function readLockInfo(lockPath) {
7307
+ if (!existsSync27(lockPath)) return null;
7308
+ try {
7309
+ const parsed = JSON.parse(readFileSync11(lockPath, "utf8"));
7310
+ if (typeof parsed.pid === "number" && typeof parsed.at === "string") return parsed;
7311
+ } catch {
7312
+ return null;
7313
+ }
7314
+ return null;
7315
+ }
7316
+ function lockIsStale(lockPath) {
7317
+ const info = readLockInfo(lockPath);
7318
+ if (!info) return true;
7319
+ if (!isPidAlive(info.pid)) return true;
7320
+ const atMs = Date.parse(info.at);
7321
+ if (Number.isNaN(atMs)) return true;
7322
+ return Date.now() - atMs > STALE_LOCK_MS;
7323
+ }
7324
+ function tryAcquireCronTickLock(lockPath) {
7325
+ if (existsSync27(lockPath) && !lockIsStale(lockPath)) {
7326
+ const info = readLockInfo(lockPath);
7327
+ return {
7328
+ acquired: false,
7329
+ reason: info ? `held by pid ${info.pid}` : "held by another process"
7330
+ };
7331
+ }
7332
+ if (existsSync27(lockPath)) {
7333
+ try {
7334
+ unlinkSync2(lockPath);
7335
+ } catch {
7336
+ }
7337
+ }
7338
+ try {
7339
+ const fd = openSync6(lockPath, "wx");
7340
+ writeFileSync4(
7341
+ fd,
7342
+ JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() }),
7343
+ "utf8"
7344
+ );
7345
+ closeSync6(fd);
7346
+ return { acquired: true };
7347
+ } catch (err) {
7348
+ if (err.code === "EEXIST") {
7349
+ return { acquired: false, reason: "concurrent acquire" };
7350
+ }
7351
+ throw err;
7352
+ }
7353
+ }
7354
+ function releaseCronTickLock(lockPath) {
7355
+ try {
7356
+ unlinkSync2(lockPath);
7357
+ } catch {
7358
+ }
7359
+ }
7360
+
7361
+ // src/cron/cron-match.ts
7362
+ var CRON_RE = /^[\d*/,\-?LW#]+\s+[\d*/,\-?LW#]+\s+[\d*/,\-?LW#]+\s+[\d*/,\-?LW#]+\s+[\d*/,\-?LW#]+$/;
7363
+ function isCronExpression(value) {
7364
+ return CRON_RE.test(value.trim());
7365
+ }
7366
+ function parseList(field, min, max) {
7367
+ const out = /* @__PURE__ */ new Set();
7368
+ for (const part of field.split(",")) {
7369
+ const token = part.trim();
7370
+ if (!token) continue;
7371
+ if (token === "*") {
7372
+ for (let i = min; i <= max; i++) out.add(i);
7373
+ continue;
7374
+ }
7375
+ const stepMatch = /^(.+)\/(\d+)$/.exec(token);
7376
+ const base = stepMatch ? stepMatch[1] : token;
7377
+ const step = stepMatch ? Math.max(1, Number(stepMatch[2])) : 1;
7378
+ if (base === "*") {
7379
+ for (let i = min; i <= max; i += step) out.add(i);
7380
+ continue;
7381
+ }
7382
+ const rangeMatch = /^(\d+)-(\d+)$/.exec(base);
7383
+ if (rangeMatch) {
7384
+ const start = Math.max(min, Number(rangeMatch[1]));
7385
+ const end = Math.min(max, Number(rangeMatch[2]));
7386
+ for (let i = start; i <= end; i += step) out.add(i);
7387
+ continue;
7388
+ }
7389
+ const n = Number(base);
7390
+ if (Number.isInteger(n) && n >= min && n <= max) out.add(n);
7391
+ }
7392
+ return out;
7393
+ }
7394
+ function fieldMatches(field, value, min, max) {
7395
+ const trimmed = field.trim();
7396
+ if (trimmed === "*") return true;
7397
+ return parseList(trimmed, min, max).has(value);
7398
+ }
7399
+ function cronMatchesUtc(expr, at) {
7400
+ const parts = expr.trim().split(/\s+/);
7401
+ if (parts.length !== 5) return false;
7402
+ const [minF, hourF, domF, monF, dowF] = parts;
7403
+ 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);
7404
+ }
7405
+ var MAX_LOOKAHEAD_MINUTES = 366 * 24 * 60;
7406
+ function truncateToUtcMinute(at) {
7407
+ return new Date(
7408
+ Date.UTC(
7409
+ at.getUTCFullYear(),
7410
+ at.getUTCMonth(),
7411
+ at.getUTCDate(),
7412
+ at.getUTCHours(),
7413
+ at.getUTCMinutes(),
7414
+ 0,
7415
+ 0
7416
+ )
7417
+ );
7418
+ }
7419
+ function computeNextCronFireUtc(expr, after) {
7420
+ if (!isCronExpression(expr)) return null;
7421
+ let cursor = truncateToUtcMinute(after);
7422
+ cursor = new Date(cursor.getTime() + 6e4);
7423
+ for (let i = 0; i < MAX_LOOKAHEAD_MINUTES; i++) {
7424
+ if (cronMatchesUtc(expr, cursor)) return cursor;
7425
+ cursor = new Date(cursor.getTime() + 6e4);
7426
+ }
7427
+ return null;
7428
+ }
7429
+ function computeInitialNextFire(spec, now) {
7430
+ if (spec.scheduleKind === "runAt" && spec.runAt) {
7431
+ const ms = Date.parse(spec.runAt);
7432
+ return Number.isNaN(ms) ? null : new Date(ms).toISOString();
7433
+ }
7434
+ if (spec.scheduleKind === "cron" && spec.cron) {
7435
+ const next = computeNextCronFireUtc(spec.cron.trim(), now);
7436
+ return next ? next.toISOString() : null;
7437
+ }
7438
+ return null;
7439
+ }
7440
+ function advanceRecurringNextFire(spec, fromInclusive) {
7441
+ if (spec.scheduleKind !== "cron" || !spec.cron?.trim()) return null;
7442
+ const next = computeNextCronFireUtc(spec.cron.trim(), fromInclusive);
7443
+ return next ? next.toISOString() : null;
7444
+ }
7445
+
7446
+ // src/cron/cron-store.ts
7447
+ import { promises as fs } from "node:fs";
7448
+ async function readFileIfExists(filePath) {
7449
+ try {
7450
+ return await fs.readFile(filePath, "utf8");
7451
+ } catch (err) {
7452
+ if (err.code === "ENOENT") return null;
7453
+ throw err;
7454
+ }
7455
+ }
7456
+ function parseCronStore(raw) {
7457
+ if (!raw) return [];
7458
+ try {
7459
+ const parsed = JSON.parse(raw);
7460
+ return Array.isArray(parsed.entries) ? parsed.entries : [];
7461
+ } catch {
7462
+ return [];
7463
+ }
7464
+ }
7465
+ async function loadCronJobs(storePath = defaultKynverCronStorePath()) {
7466
+ const raw = await readFileIfExists(storePath);
7467
+ return parseCronStore(raw);
7468
+ }
7469
+
7470
+ // src/cron/cron-tick-state.ts
7471
+ import { randomBytes } from "node:crypto";
7472
+ import { promises as fs2 } from "node:fs";
7473
+ import path36 from "node:path";
7474
+ var EMPTY = { version: 1, jobs: {} };
7475
+ async function readFileIfExists2(filePath) {
7476
+ try {
7477
+ return await fs2.readFile(filePath, "utf8");
7478
+ } catch (err) {
7479
+ if (err.code === "ENOENT") return null;
7480
+ throw err;
7481
+ }
7482
+ }
7483
+ function parseCronTickState(raw) {
7484
+ if (!raw) return { ...EMPTY, jobs: { ...EMPTY.jobs } };
7485
+ try {
7486
+ const parsed = JSON.parse(raw);
7487
+ if (parsed?.version !== 1 || typeof parsed.jobs !== "object" || !parsed.jobs) {
7488
+ return { ...EMPTY, jobs: {} };
7489
+ }
7490
+ return parsed;
7491
+ } catch {
7492
+ return { ...EMPTY, jobs: {} };
7493
+ }
7494
+ }
7495
+ async function loadCronTickState(statePath) {
7496
+ const raw = await readFileIfExists2(statePath);
7497
+ return parseCronTickState(raw);
7498
+ }
7499
+ async function writeStateAtomic(statePath, state) {
7500
+ await fs2.mkdir(path36.dirname(statePath), { recursive: true });
7501
+ const suffix = randomBytes(6).toString("hex");
7502
+ const tmp = `${statePath}.tmp-${process.pid}-${Date.now()}-${suffix}`;
7503
+ await fs2.writeFile(tmp, `${JSON.stringify(state, null, 2)}
7504
+ `, "utf8");
7505
+ try {
7506
+ await fs2.rename(tmp, statePath);
7507
+ } catch (err) {
7508
+ const code = err.code;
7509
+ if (code !== "EPERM" && code !== "EEXIST" && code !== "EACCES") throw err;
7510
+ await fs2.unlink(tmp).catch(() => {
7511
+ });
7512
+ }
7513
+ }
7514
+ async function saveCronTickState(statePath, state) {
7515
+ await writeStateAtomic(statePath, state);
7516
+ }
7517
+ function getOrCreateJobState(state, providerScheduleId) {
7518
+ const existing = state.jobs[providerScheduleId];
7519
+ if (existing) return existing;
7520
+ const created = {
7521
+ providerScheduleId,
7522
+ nextFireAt: null,
7523
+ lastFiredAt: null,
7524
+ lastAttemptAt: null,
7525
+ consecutiveFailures: 0,
7526
+ completedAt: null,
7527
+ inflightUntil: null
7528
+ };
7529
+ state.jobs[providerScheduleId] = created;
7530
+ return created;
7531
+ }
7532
+
7533
+ // src/cron/cron-tick.ts
7534
+ function isInflight(job, nowMs) {
7535
+ if (!job.inflightUntil) return false;
7536
+ const until = Date.parse(job.inflightUntil);
7537
+ return !Number.isNaN(until) && until > nowMs;
7538
+ }
7539
+ function isCompleted(job) {
7540
+ return Boolean(job.completedAt);
7541
+ }
7542
+ function backoffReady(job, env, nowMs) {
7543
+ if (job.consecutiveFailures === 0) return true;
7544
+ if (job.consecutiveFailures > env.maxRetries) return false;
7545
+ if (!job.lastAttemptAt) return true;
7546
+ const last = Date.parse(job.lastAttemptAt);
7547
+ if (Number.isNaN(last)) return true;
7548
+ return nowMs - last >= env.retryBackoffMs;
7549
+ }
7550
+ function ensureNextFire(entry, job, now) {
7551
+ if (job.nextFireAt) return job.nextFireAt;
7552
+ const initial = computeInitialNextFire(entry.spec, now);
7553
+ job.nextFireAt = initial;
7554
+ return initial;
7555
+ }
7556
+ function isDue(entry, job, nowMs, env) {
7557
+ if (entry.paused || isCompleted(job) || isInflight(job, nowMs)) return false;
7558
+ if (!backoffReady(job, env, nowMs)) return false;
7559
+ const nextMs = job.nextFireAt ? Date.parse(job.nextFireAt) : NaN;
7560
+ if (Number.isNaN(nextMs)) return false;
7561
+ return nowMs >= nextMs;
7562
+ }
7563
+ function advanceRecurringBeforeFire(entry, job, now) {
7564
+ if (entry.spec.scheduleKind !== "cron") return;
7565
+ job.nextFireAt = advanceRecurringNextFire(entry.spec, now);
7566
+ }
7567
+ async function runKynverCronTick(opts = {}) {
7568
+ const env = opts.env ?? resolveKynverCronEnv();
7569
+ const now = opts.now ?? /* @__PURE__ */ new Date();
7570
+ const nowMs = now.getTime();
7571
+ if (!env.tickEnabled) {
7572
+ return { enabled: false, skipped: "tick_disabled", scanned: 0, due: 0, fired: 0, skippedJobs: 0, errors: 0 };
7573
+ }
7574
+ if (!env.fireBaseUrl || !env.secret) {
7575
+ return {
7576
+ enabled: true,
7577
+ skipped: "missing_fire_credentials",
7578
+ scanned: 0,
7579
+ due: 0,
7580
+ fired: 0,
7581
+ skippedJobs: 0,
7582
+ errors: 0
7583
+ };
7584
+ }
7585
+ const lock = tryAcquireCronTickLock(env.lockPath);
7586
+ if (!lock.acquired) {
7587
+ return {
7588
+ enabled: true,
7589
+ skipped: lock.reason ?? "lock_not_acquired",
7590
+ scanned: 0,
7591
+ due: 0,
7592
+ fired: 0,
7593
+ skippedJobs: 0,
7594
+ errors: 0,
7595
+ lockHeld: true
7596
+ };
7597
+ }
7598
+ try {
7599
+ const entries = await loadCronJobs(env.storePath);
7600
+ const filtered = opts.agentOsIdFilter ? entries.filter((e) => e.spec.target.agentOsId === opts.agentOsIdFilter) : entries;
7601
+ const state = await loadCronTickState(env.statePath);
7602
+ const dueEntries = [];
7603
+ for (const entry of filtered) {
7604
+ const job = getOrCreateJobState(state, entry.providerScheduleId);
7605
+ ensureNextFire(entry, job, now);
7606
+ if (isDue(entry, job, nowMs, env)) {
7607
+ dueEntries.push({ entry, job });
7608
+ }
7609
+ }
7610
+ dueEntries.sort((a, b) => {
7611
+ const aMs = Date.parse(a.job.nextFireAt ?? "") || 0;
7612
+ const bMs = Date.parse(b.job.nextFireAt ?? "") || 0;
7613
+ return aMs - bMs;
7614
+ });
7615
+ let fired = 0;
7616
+ let errors = 0;
7617
+ let skippedJobs = 0;
7618
+ let catchUpBudget = env.maxCatchUpPerTick;
7619
+ for (const { entry, job } of dueEntries) {
7620
+ if (env.missedRunPolicy === "skip" && entry.spec.scheduleKind === "cron") {
7621
+ const next = Date.parse(job.nextFireAt ?? "");
7622
+ if (!Number.isNaN(next) && next < nowMs - env.tickIntervalMs * 2) {
7623
+ job.nextFireAt = advanceRecurringNextFire(entry.spec, now);
7624
+ skippedJobs++;
7625
+ continue;
7626
+ }
7627
+ }
7628
+ if (catchUpBudget <= 0) {
7629
+ skippedJobs++;
7630
+ continue;
7631
+ }
7632
+ job.inflightUntil = new Date(nowMs + env.inflightLeaseMs).toISOString();
7633
+ job.lastAttemptAt = now.toISOString();
7634
+ advanceRecurringBeforeFire(entry, job, now);
7635
+ try {
7636
+ const result = await fireKynverCronJob({
7637
+ entry,
7638
+ baseUrl: env.fireBaseUrl,
7639
+ secret: env.secret,
7640
+ fetchFn: opts.fetchFn
7641
+ });
7642
+ job.inflightUntil = null;
7643
+ if (result.ok) {
7644
+ job.lastFiredAt = now.toISOString();
7645
+ job.consecutiveFailures = 0;
7646
+ if (entry.spec.scheduleKind === "runAt") {
7647
+ job.completedAt = now.toISOString();
7648
+ job.nextFireAt = null;
7649
+ }
7650
+ fired++;
7651
+ catchUpBudget--;
7652
+ } else {
7653
+ job.consecutiveFailures += 1;
7654
+ errors++;
7655
+ }
7656
+ } catch {
7657
+ job.inflightUntil = null;
7658
+ job.consecutiveFailures += 1;
7659
+ errors++;
7660
+ }
7661
+ }
7662
+ await saveCronTickState(env.statePath, state);
7663
+ return {
7664
+ enabled: true,
7665
+ scanned: filtered.length,
7666
+ due: dueEntries.length,
7667
+ fired,
7668
+ skippedJobs,
7669
+ errors
7670
+ };
7671
+ } finally {
7672
+ releaseCronTickLock(env.lockPath);
7673
+ }
7674
+ }
7675
+
7010
7676
  // src/pipeline-tick.ts
7011
- import path49 from "node:path";
7677
+ import path52 from "node:path";
7012
7678
 
7013
7679
  // src/pipeline-dispatch.ts
7014
7680
  var RESERVED_REVIEW_STARTS = 1;
@@ -7074,30 +7740,67 @@ async function runPipelineDispatch(args, slots) {
7074
7740
  };
7075
7741
  }
7076
7742
 
7743
+ // src/pipeline-exact-targets.ts
7744
+ function operatorExactTargetTaskIds(operatorTick) {
7745
+ if (!operatorTick || typeof operatorTick !== "object") return [];
7746
+ const body = operatorTick;
7747
+ const raw = body.response?.dispatch?.exactTargetTaskIds;
7748
+ if (!Array.isArray(raw)) return [];
7749
+ const seen = /* @__PURE__ */ new Set();
7750
+ const out = [];
7751
+ for (const value of raw) {
7752
+ if (typeof value !== "string") continue;
7753
+ const id = value.trim();
7754
+ if (!id || seen.has(id)) continue;
7755
+ seen.add(id);
7756
+ out.push(id);
7757
+ }
7758
+ return out;
7759
+ }
7760
+
7077
7761
  // src/pipeline-max-starts.ts
7078
7762
  function operatorDispatchFromTick(operatorTick) {
7079
7763
  const body = operatorTick;
7080
7764
  const dispatch = body.response?.dispatch;
7081
7765
  return dispatch && typeof dispatch === "object" ? dispatch : null;
7082
7766
  }
7767
+ function nonNegativeInt(value) {
7768
+ if (typeof value !== "number" || !Number.isFinite(value)) return null;
7769
+ return Math.max(0, Math.floor(value));
7770
+ }
7083
7771
  function resolvePipelineMaxStarts(resourceGate, operatorTick) {
7084
7772
  const dispatch = operatorDispatchFromTick(operatorTick);
7085
- const advised = typeof dispatch?.recommendedMaxStarts === "number" ? Math.max(0, dispatch.recommendedMaxStarts) : null;
7773
+ const advised = nonNegativeInt(dispatch?.recommendedMaxStarts);
7774
+ const actionableReady = nonNegativeInt(dispatch?.actionableReady);
7775
+ const queuedTasks = nonNegativeInt(dispatch?.queuedTasks);
7776
+ const boardAdvancedThisTick = nonNegativeInt(dispatch?.boardAdvancedThisTick) ?? 0;
7777
+ const leaseReapedThisTick = nonNegativeInt(dispatch?.leaseReapedThisTick) ?? 0;
7778
+ const hygieneAdvanced = boardAdvancedThisTick + leaseReapedThisTick;
7779
+ const readyFloor = actionableReady ?? queuedTasks;
7086
7780
  let maxStarts = resourceGate.slotsAvailable;
7087
- if (advised !== null) {
7781
+ if (readyFloor !== null) {
7782
+ maxStarts = Math.min(maxStarts, readyFloor);
7783
+ } else if (advised !== null) {
7088
7784
  maxStarts = Math.min(maxStarts, advised);
7089
7785
  }
7090
- const underutilized = dispatch?.underutilized === true;
7091
- const boardAdvancedThisTick = typeof dispatch?.boardAdvancedThisTick === "number" ? dispatch.boardAdvancedThisTick : 0;
7092
- if (underutilized && resourceGate.slotsAvailable > 0 && maxStarts === 0) {
7093
- const ready = dispatch?.actionableReady ?? dispatch?.queuedTasks ?? (boardAdvancedThisTick > 0 ? boardAdvancedThisTick : 1);
7786
+ if (readyFloor === null && advised !== null) {
7787
+ maxStarts = Math.max(maxStarts, Math.min(resourceGate.slotsAvailable, advised));
7788
+ }
7789
+ const underutilized = dispatch?.underutilized === true || (readyFloor ?? 0) > 0 && resourceGate.slotsAvailable > 0 && resourceGate.maxConcurrentWorkers > 0 && resourceGate.activeWorkers < resourceGate.maxConcurrentWorkers;
7790
+ if (resourceGate.slotsAvailable > 0 && maxStarts === 0 && (underutilized || hygieneAdvanced > 0)) {
7791
+ const ready = readyFloor ?? (hygieneAdvanced > 0 ? hygieneAdvanced : 1);
7094
7792
  maxStarts = Math.min(resourceGate.slotsAvailable, Math.max(1, ready));
7095
7793
  }
7794
+ const nonDispatchableReady = queuedTasks !== null && actionableReady !== null ? Math.max(0, queuedTasks - actionableReady) : null;
7096
7795
  return {
7097
7796
  maxStarts: Math.max(0, maxStarts),
7098
7797
  underutilized,
7099
7798
  advisedStarts: advised,
7100
- boardAdvancedThisTick
7799
+ actionableReady,
7800
+ queuedTasks,
7801
+ nonDispatchableReady,
7802
+ boardAdvancedThisTick,
7803
+ leaseReapedThisTick
7101
7804
  };
7102
7805
  }
7103
7806
 
@@ -7141,7 +7844,7 @@ function buildBoxResourceSnapshotFromGate(gate, input = {}) {
7141
7844
  }
7142
7845
 
7143
7846
  // src/plan-progress-daemon-sync.ts
7144
- import path34 from "node:path";
7847
+ import path37 from "node:path";
7145
7848
 
7146
7849
  // src/plan-progress-sync.ts
7147
7850
  async function syncPlanProgress(args) {
@@ -7165,7 +7868,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
7165
7868
  const outcomes = [];
7166
7869
  for (const name of Object.keys(run.workers || {})) {
7167
7870
  const worker = readJson(
7168
- path34.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
7871
+ path37.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
7169
7872
  void 0
7170
7873
  );
7171
7874
  if (!worker?.dispatched || !worker.taskId) continue;
@@ -7214,10 +7917,10 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
7214
7917
  }
7215
7918
 
7216
7919
  // src/cleanup.ts
7217
- import path47 from "node:path";
7920
+ import path50 from "node:path";
7218
7921
 
7219
7922
  // src/cleanup-guards.ts
7220
- import path35 from "node:path";
7923
+ import path38 from "node:path";
7221
7924
 
7222
7925
  // src/cleanup-run-liveness.ts
7223
7926
  function isWorkerProcessLive(indexed) {
@@ -7341,7 +8044,7 @@ function skipWorktreeRemoval(input) {
7341
8044
  function skipDependencyCacheRemoval(input) {
7342
8045
  const { indexed, nodeModulesAgeMs, ageMs, worktreePath, activeWorktreePaths, diskPressure } = input;
7343
8046
  if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
7344
- if (activeWorktreePaths.has(path35.resolve(worktreePath))) return "active_worker";
8047
+ if (activeWorktreePaths.has(path38.resolve(worktreePath))) return "active_worker";
7345
8048
  if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
7346
8049
  if (indexed && hasUnrestorableWorktreeChanges(indexed.status)) return "dirty_worktree";
7347
8050
  return null;
@@ -7368,11 +8071,11 @@ var LIVE_SKIP_REASONS = /* @__PURE__ */ new Set([
7368
8071
  function collectPreservedLivePaths(actions, skips) {
7369
8072
  const out = [];
7370
8073
  const seen = /* @__PURE__ */ new Set();
7371
- const push = (path59, reason, detail) => {
7372
- const key = `${path59}\0${reason}`;
8074
+ const push = (path62, reason, detail) => {
8075
+ const key = `${path62}\0${reason}`;
7373
8076
  if (seen.has(key) || out.length >= MAX_PRESERVED_LIVE_PATH_SAMPLES) return;
7374
8077
  seen.add(key);
7375
- out.push({ path: path59, reason, ...detail ? { detail } : {} });
8078
+ out.push({ path: path62, reason, ...detail ? { detail } : {} });
7376
8079
  };
7377
8080
  for (const skip2 of skips) {
7378
8081
  if (!LIVE_SKIP_REASONS.has(skip2.reason)) continue;
@@ -7387,11 +8090,11 @@ function collectPreservedLivePaths(actions, skips) {
7387
8090
  }
7388
8091
 
7389
8092
  // src/cleanup-run-directory.ts
7390
- import { existsSync as existsSync25, readdirSync as readdirSync6, statSync as statSync4 } from "node:fs";
7391
- import path37 from "node:path";
8093
+ import { existsSync as existsSync28, readdirSync as readdirSync7, statSync as statSync5 } from "node:fs";
8094
+ import path40 from "node:path";
7392
8095
 
7393
8096
  // src/cleanup-active-worktrees.ts
7394
- import path36 from "node:path";
8097
+ import path39 from "node:path";
7395
8098
  function isActiveHarnessWorker2(worker, runBase, runBaseCommit) {
7396
8099
  const status = computeWorkerStatus(worker, { base: runBase, baseCommit: runBaseCommit });
7397
8100
  return status.alive && !status.finalResult && status.attention.state !== "done";
@@ -7404,17 +8107,20 @@ function collectActiveWorktreeGuards(harnessRoots) {
7404
8107
  let runHasLive = false;
7405
8108
  for (const name of Object.keys(run.workers || {})) {
7406
8109
  const worker = readJson(
7407
- path36.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
8110
+ path39.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
7408
8111
  void 0
7409
8112
  );
7410
8113
  if (!worker?.worktreePath) continue;
7411
- const worktreePath = path36.resolve(worker.worktreePath);
8114
+ const worktreePath = path39.resolve(worker.worktreePath);
7412
8115
  if (!isActiveHarnessWorker2(worker, run.base, run.baseCommit)) continue;
7413
8116
  runHasLive = true;
7414
8117
  activeWorktreePaths.add(worktreePath);
7415
8118
  }
7416
8119
  if (runHasLive) liveRunKeys.add(`${harnessRoot}\0${run.id}`);
7417
8120
  }
8121
+ for (const key of collectFilesystemLiveRunKeys(harnessRoot)) {
8122
+ liveRunKeys.add(key);
8123
+ }
7418
8124
  }
7419
8125
  return { activeWorktreePaths, liveRunKeys };
7420
8126
  }
@@ -7426,20 +8132,20 @@ function isWorktreeOnLiveRun(worktreePath, harnessRoot, runId, liveRunKeys) {
7426
8132
  // src/cleanup-run-directory.ts
7427
8133
  function pathAgeMs(target, now) {
7428
8134
  try {
7429
- const mtime = statSync4(target).mtimeMs;
8135
+ const mtime = statSync5(target).mtimeMs;
7430
8136
  return Math.max(0, now - mtime);
7431
8137
  } catch {
7432
8138
  return 0;
7433
8139
  }
7434
8140
  }
7435
8141
  function loadRunStatus(harnessRoot, runId) {
7436
- const runPath = path37.join(harnessRoot, "runs", runId, "run.json");
7437
- if (!existsSync25(runPath)) return null;
8142
+ const runPath = path40.join(harnessRoot, "runs", runId, "run.json");
8143
+ if (!existsSync28(runPath)) return null;
7438
8144
  return readJson(runPath, null);
7439
8145
  }
7440
8146
  function runDirectoryIsEmpty(runPath) {
7441
8147
  try {
7442
- const entries = readdirSync6(runPath);
8148
+ const entries = readdirSync7(runPath);
7443
8149
  return entries.length === 0;
7444
8150
  } catch {
7445
8151
  return false;
@@ -7457,11 +8163,11 @@ function skipRunDirectoryRemoval(input) {
7457
8163
  return null;
7458
8164
  }
7459
8165
  function scanStaleRunDirectoryCandidates(opts) {
7460
- if (!existsSync25(opts.worktreesDir)) return [];
8166
+ if (!existsSync28(opts.worktreesDir)) return [];
7461
8167
  const candidates = [];
7462
8168
  let entries;
7463
8169
  try {
7464
- entries = readdirSync6(opts.worktreesDir, { withFileTypes: true });
8170
+ entries = readdirSync7(opts.worktreesDir, { withFileTypes: true });
7465
8171
  } catch {
7466
8172
  return [];
7467
8173
  }
@@ -7469,7 +8175,7 @@ function scanStaleRunDirectoryCandidates(opts) {
7469
8175
  if (!runEntry.isDirectory()) continue;
7470
8176
  const runId = runEntry.name;
7471
8177
  if (opts.runIdFilter && runId !== opts.runIdFilter) continue;
7472
- const runPath = path37.join(opts.worktreesDir, runId);
8178
+ const runPath = path40.join(opts.worktreesDir, runId);
7473
8179
  if (!runDirectoryIsEmpty(runPath)) continue;
7474
8180
  candidates.push({
7475
8181
  kind: "remove_run_directory",
@@ -7484,14 +8190,14 @@ function scanStaleRunDirectoryCandidates(opts) {
7484
8190
  }
7485
8191
 
7486
8192
  // src/cleanup-execute.ts
7487
- import { existsSync as existsSync27, rmSync as rmSync3 } from "node:fs";
7488
- import path39 from "node:path";
8193
+ import { existsSync as existsSync30, rmSync as rmSync3 } from "node:fs";
8194
+ import path42 from "node:path";
7489
8195
 
7490
8196
  // src/cleanup-dir-size.ts
7491
- import { existsSync as existsSync26, readdirSync as readdirSync7, statSync as statSync5 } from "node:fs";
7492
- import path38 from "node:path";
8197
+ import { existsSync as existsSync29, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
8198
+ import path41 from "node:path";
7493
8199
  function directorySizeBytes(root, maxEntries = 5e4) {
7494
- if (!existsSync26(root)) return 0;
8200
+ if (!existsSync29(root)) return 0;
7495
8201
  let total = 0;
7496
8202
  let seen = 0;
7497
8203
  const stack = [root];
@@ -7499,16 +8205,16 @@ function directorySizeBytes(root, maxEntries = 5e4) {
7499
8205
  const current = stack.pop();
7500
8206
  let entries;
7501
8207
  try {
7502
- entries = readdirSync7(current);
8208
+ entries = readdirSync8(current);
7503
8209
  } catch {
7504
8210
  continue;
7505
8211
  }
7506
8212
  for (const name of entries) {
7507
8213
  if (seen++ > maxEntries) return null;
7508
- const full = path38.join(current, name);
8214
+ const full = path41.join(current, name);
7509
8215
  let st;
7510
8216
  try {
7511
- st = statSync5(full);
8217
+ st = statSync6(full);
7512
8218
  } catch {
7513
8219
  continue;
7514
8220
  }
@@ -7520,8 +8226,20 @@ function directorySizeBytes(root, maxEntries = 5e4) {
7520
8226
  }
7521
8227
 
7522
8228
  // src/cleanup-execute.ts
8229
+ function skipRunMetadataRemoval(candidate) {
8230
+ const harnessRoot = candidate.harnessRoot;
8231
+ if (!harnessRoot || !isHarnessRunMetadataPath(candidate.path, harnessRoot)) return null;
8232
+ return {
8233
+ ...candidate,
8234
+ executed: false,
8235
+ skipped: true,
8236
+ skipReason: "run_metadata_protected"
8237
+ };
8238
+ }
7523
8239
  function removeDependencyCache(candidate, execute) {
7524
- if (!existsSync27(candidate.path)) {
8240
+ const metadataSkip = skipRunMetadataRemoval(candidate);
8241
+ if (metadataSkip) return metadataSkip;
8242
+ if (!existsSync30(candidate.path)) {
7525
8243
  return {
7526
8244
  ...candidate,
7527
8245
  executed: false,
@@ -7561,7 +8279,9 @@ function removeBuildCache(candidate, execute) {
7561
8279
  return removeDependencyCache(candidate, execute);
7562
8280
  }
7563
8281
  function removeRunDirectory(candidate, execute) {
7564
- if (!existsSync27(candidate.path)) {
8282
+ const metadataSkip = skipRunMetadataRemoval(candidate);
8283
+ if (metadataSkip) return metadataSkip;
8284
+ if (!existsSync30(candidate.path)) {
7565
8285
  return {
7566
8286
  ...candidate,
7567
8287
  executed: false,
@@ -7592,7 +8312,9 @@ function removeRunDirectory(candidate, execute) {
7592
8312
  }
7593
8313
  }
7594
8314
  function removeWorktree(candidate, execute) {
7595
- if (!existsSync27(candidate.path)) {
8315
+ const metadataSkip = skipRunMetadataRemoval(candidate);
8316
+ if (metadataSkip) return metadataSkip;
8317
+ if (!existsSync30(candidate.path)) {
7596
8318
  return {
7597
8319
  ...candidate,
7598
8320
  executed: false,
@@ -7609,7 +8331,7 @@ function removeWorktree(candidate, execute) {
7609
8331
  if (repo) {
7610
8332
  git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
7611
8333
  }
7612
- if (existsSync27(candidate.path)) {
8334
+ if (existsSync30(candidate.path)) {
7613
8335
  rmSync3(candidate.path, { recursive: true, force: true });
7614
8336
  }
7615
8337
  return {
@@ -7629,15 +8351,15 @@ function removeWorktree(candidate, execute) {
7629
8351
  }
7630
8352
  }
7631
8353
  function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
7632
- const resolved = path39.resolve(targetPath);
7633
- const suffix = `${path39.sep}${cacheDirName}`;
8354
+ const resolved = path42.resolve(targetPath);
8355
+ const suffix = `${path42.sep}${cacheDirName}`;
7634
8356
  const cachePath = resolved.endsWith(suffix) ? resolved : null;
7635
8357
  if (!cachePath) return "path_outside_harness";
7636
- const rel = path39.relative(worktreesDir, cachePath);
7637
- if (rel.startsWith("..") || path39.isAbsolute(rel)) return "path_outside_harness";
7638
- const parts = rel.split(path39.sep);
8358
+ const rel = path42.relative(worktreesDir, cachePath);
8359
+ if (rel.startsWith("..") || path42.isAbsolute(rel)) return "path_outside_harness";
8360
+ const parts = rel.split(path42.sep);
7639
8361
  if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
7640
- if (!resolved.startsWith(path39.resolve(harnessRoot))) return "path_outside_harness";
8362
+ if (!resolved.startsWith(path42.resolve(harnessRoot))) return "path_outside_harness";
7641
8363
  return null;
7642
8364
  }
7643
8365
  function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
@@ -7647,37 +8369,37 @@ function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
7647
8369
  return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
7648
8370
  }
7649
8371
  function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
7650
- const resolved = path39.resolve(targetPath);
7651
- const relToWt = path39.relative(worktreesDir, resolved);
7652
- if (relToWt.startsWith("..") || path39.isAbsolute(relToWt)) return "path_outside_harness";
7653
- const parts = relToWt.split(path39.sep);
8372
+ const resolved = path42.resolve(targetPath);
8373
+ const relToWt = path42.relative(worktreesDir, resolved);
8374
+ if (relToWt.startsWith("..") || path42.isAbsolute(relToWt)) return "path_outside_harness";
8375
+ const parts = relToWt.split(path42.sep);
7654
8376
  if (parts.length < 3) return "path_outside_harness";
7655
- if (!resolved.startsWith(path39.resolve(harnessRoot))) return "path_outside_harness";
8377
+ if (!resolved.startsWith(path42.resolve(harnessRoot))) return "path_outside_harness";
7656
8378
  return null;
7657
8379
  }
7658
8380
 
7659
8381
  // src/cleanup-scan.ts
7660
- import { existsSync as existsSync28, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
7661
- import path40 from "node:path";
8382
+ import { existsSync as existsSync31, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
8383
+ import path43 from "node:path";
7662
8384
  function pathAgeMs2(target, now) {
7663
8385
  try {
7664
- const mtime = statSync6(target).mtimeMs;
8386
+ const mtime = statSync7(target).mtimeMs;
7665
8387
  return Math.max(0, now - mtime);
7666
8388
  } catch {
7667
8389
  return 0;
7668
8390
  }
7669
8391
  }
7670
8392
  function isPathInside(child, parent) {
7671
- const rel = path40.relative(parent, child);
7672
- return rel === "" || !rel.startsWith("..") && !path40.isAbsolute(rel);
8393
+ const rel = path43.relative(parent, child);
8394
+ return rel === "" || !rel.startsWith("..") && !path43.isAbsolute(rel);
7673
8395
  }
7674
8396
  function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
7675
8397
  const out = [];
7676
8398
  for (const rel of HARNESS_BUILD_CACHE_RELATIVE_PATHS) {
7677
8399
  if (rel === ".next") continue;
7678
- const target = path40.join(worktreePath, rel);
7679
- if (!existsSync28(target)) continue;
7680
- const resolved = path40.resolve(target);
8400
+ const target = path43.join(worktreePath, rel);
8401
+ if (!existsSync31(target)) continue;
8402
+ const resolved = path43.resolve(target);
7681
8403
  if (seen.has(resolved)) continue;
7682
8404
  if (!isPathInside(resolved, opts.harnessRoot)) continue;
7683
8405
  seen.add(resolved);
@@ -7706,13 +8428,13 @@ function scanBuildCacheCandidates(opts) {
7706
8428
  })
7707
8429
  );
7708
8430
  }
7709
- if (!opts.includeOrphans || !existsSync28(opts.worktreesDir)) return candidates;
7710
- for (const runEntry of readdirSync8(opts.worktreesDir, { withFileTypes: true })) {
8431
+ if (!opts.includeOrphans || !existsSync31(opts.worktreesDir)) return candidates;
8432
+ for (const runEntry of readdirSync9(opts.worktreesDir, { withFileTypes: true })) {
7711
8433
  if (!runEntry.isDirectory()) continue;
7712
- const runPath = path40.join(opts.worktreesDir, runEntry.name);
7713
- for (const workerEntry of readdirSync8(runPath, { withFileTypes: true })) {
8434
+ const runPath = path43.join(opts.worktreesDir, runEntry.name);
8435
+ for (const workerEntry of readdirSync9(runPath, { withFileTypes: true })) {
7714
8436
  if (!workerEntry.isDirectory()) continue;
7715
- const worktreePath = path40.join(runPath, workerEntry.name);
8437
+ const worktreePath = path43.join(runPath, workerEntry.name);
7716
8438
  candidates.push(
7717
8439
  ...collectBuildCacheForWorktree(worktreePath, opts, seen, {
7718
8440
  runId: runEntry.name,
@@ -7733,7 +8455,7 @@ function scanWorktreeCandidates(opts) {
7733
8455
  for (const entry of opts.index.values()) {
7734
8456
  if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
7735
8457
  const resolved = entry.worktreePath;
7736
- if (!existsSync28(resolved)) continue;
8458
+ if (!existsSync31(resolved)) continue;
7737
8459
  if (seen.has(resolved)) continue;
7738
8460
  seen.add(resolved);
7739
8461
  candidates.push({
@@ -7747,24 +8469,24 @@ function scanWorktreeCandidates(opts) {
7747
8469
  });
7748
8470
  }
7749
8471
  }
7750
- if (!orphanEnabled || !existsSync28(opts.worktreesDir)) return candidates;
8472
+ if (!orphanEnabled || !existsSync31(opts.worktreesDir)) return candidates;
7751
8473
  const indexedPaths = /* @__PURE__ */ new Set();
7752
8474
  for (const entry of opts.index.values()) {
7753
- indexedPaths.add(path40.resolve(entry.worktreePath));
8475
+ indexedPaths.add(path43.resolve(entry.worktreePath));
7754
8476
  }
7755
- for (const runEntry of readdirSync8(opts.worktreesDir, { withFileTypes: true })) {
8477
+ for (const runEntry of readdirSync9(opts.worktreesDir, { withFileTypes: true })) {
7756
8478
  if (!runEntry.isDirectory()) continue;
7757
8479
  if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
7758
- const runPath = path40.join(opts.worktreesDir, runEntry.name);
8480
+ const runPath = path43.join(opts.worktreesDir, runEntry.name);
7759
8481
  let workerEntries;
7760
8482
  try {
7761
- workerEntries = readdirSync8(runPath, { withFileTypes: true });
8483
+ workerEntries = readdirSync9(runPath, { withFileTypes: true });
7762
8484
  } catch {
7763
8485
  continue;
7764
8486
  }
7765
8487
  for (const workerEntry of workerEntries) {
7766
8488
  if (!workerEntry.isDirectory()) continue;
7767
- const worktreePath = path40.resolve(path40.join(runPath, workerEntry.name));
8489
+ const worktreePath = path43.resolve(path43.join(runPath, workerEntry.name));
7768
8490
  if (seen.has(worktreePath)) continue;
7769
8491
  if (indexedPaths.has(worktreePath)) continue;
7770
8492
  if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
@@ -7783,27 +8505,27 @@ function scanWorktreeCandidates(opts) {
7783
8505
  }
7784
8506
 
7785
8507
  // src/cleanup-dependency-scan.ts
7786
- import { existsSync as existsSync29, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
7787
- import path41 from "node:path";
8508
+ import { existsSync as existsSync32, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
8509
+ import path44 from "node:path";
7788
8510
  var DEPENDENCY_CACHE_DIRS = [
7789
8511
  { dirName: "node_modules", kind: "remove_node_modules" },
7790
8512
  { dirName: ".next", kind: "remove_next_cache" }
7791
8513
  ];
7792
8514
  function pathAgeMs3(target, now) {
7793
8515
  try {
7794
- const mtime = statSync7(target).mtimeMs;
8516
+ const mtime = statSync8(target).mtimeMs;
7795
8517
  return Math.max(0, now - mtime);
7796
8518
  } catch {
7797
8519
  return 0;
7798
8520
  }
7799
8521
  }
7800
8522
  function isPathInside2(child, parent) {
7801
- const rel = path41.relative(parent, child);
7802
- return rel === "" || !rel.startsWith("..") && !path41.isAbsolute(rel);
8523
+ const rel = path44.relative(parent, child);
8524
+ return rel === "" || !rel.startsWith("..") && !path44.isAbsolute(rel);
7803
8525
  }
7804
8526
  function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
7805
- if (!existsSync29(targetPath)) return;
7806
- const resolved = path41.resolve(targetPath);
8527
+ if (!existsSync32(targetPath)) return;
8528
+ const resolved = path44.resolve(targetPath);
7807
8529
  if (seen.has(resolved)) return;
7808
8530
  if (!isPathInside2(resolved, opts.harnessRoot)) return;
7809
8531
  seen.add(resolved);
@@ -7820,7 +8542,7 @@ function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
7820
8542
  }
7821
8543
  function scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, meta) {
7822
8544
  for (const entry of DEPENDENCY_CACHE_DIRS) {
7823
- pushCandidate2(candidates, seen, opts, path41.join(worktreePath, entry.dirName), entry.kind, meta);
8545
+ pushCandidate2(candidates, seen, opts, path44.join(worktreePath, entry.dirName), entry.kind, meta);
7824
8546
  }
7825
8547
  }
7826
8548
  function scanDependencyCacheCandidates(opts) {
@@ -7834,20 +8556,20 @@ function scanDependencyCacheCandidates(opts) {
7834
8556
  repo: entry.run.repo
7835
8557
  });
7836
8558
  }
7837
- if (!existsSync29(opts.worktreesDir)) return candidates;
7838
- for (const runEntry of readdirSync9(opts.worktreesDir, { withFileTypes: true })) {
8559
+ if (!existsSync32(opts.worktreesDir)) return candidates;
8560
+ for (const runEntry of readdirSync10(opts.worktreesDir, { withFileTypes: true })) {
7839
8561
  if (!runEntry.isDirectory()) continue;
7840
8562
  if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
7841
- const runPath = path41.join(opts.worktreesDir, runEntry.name);
8563
+ const runPath = path44.join(opts.worktreesDir, runEntry.name);
7842
8564
  let workerEntries;
7843
8565
  try {
7844
- workerEntries = readdirSync9(runPath, { withFileTypes: true });
8566
+ workerEntries = readdirSync10(runPath, { withFileTypes: true });
7845
8567
  } catch {
7846
8568
  continue;
7847
8569
  }
7848
8570
  for (const workerEntry of workerEntries) {
7849
8571
  if (!workerEntry.isDirectory()) continue;
7850
- const worktreePath = path41.join(runPath, workerEntry.name);
8572
+ const worktreePath = path44.join(runPath, workerEntry.name);
7851
8573
  scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, {
7852
8574
  runId: runEntry.name,
7853
8575
  worker: workerEntry.name
@@ -7858,11 +8580,11 @@ function scanDependencyCacheCandidates(opts) {
7858
8580
  }
7859
8581
 
7860
8582
  // src/cleanup-duplicate-worktrees.ts
7861
- import { existsSync as existsSync30, statSync as statSync8 } from "node:fs";
7862
- import path42 from "node:path";
8583
+ import { existsSync as existsSync33, statSync as statSync9 } from "node:fs";
8584
+ import path45 from "node:path";
7863
8585
  function pathAgeMs4(target, now) {
7864
8586
  try {
7865
- const mtime = statSync8(target).mtimeMs;
8587
+ const mtime = statSync9(target).mtimeMs;
7866
8588
  return Math.max(0, now - mtime);
7867
8589
  } catch {
7868
8590
  return 0;
@@ -7889,8 +8611,8 @@ function parseWorktreePorcelain(output) {
7889
8611
  return records;
7890
8612
  }
7891
8613
  function isUnderWorktreesDir(worktreePath, worktreesDir) {
7892
- const rel = path42.relative(path42.resolve(worktreesDir), path42.resolve(worktreePath));
7893
- return rel !== "" && !rel.startsWith("..") && !path42.isAbsolute(rel);
8614
+ const rel = path45.relative(path45.resolve(worktreesDir), path45.resolve(worktreePath));
8615
+ return rel !== "" && !rel.startsWith("..") && !path45.isAbsolute(rel);
7894
8616
  }
7895
8617
  function isCleanWorktree(worktreePath, repoRoot) {
7896
8618
  try {
@@ -7903,14 +8625,14 @@ function isCleanWorktree(worktreePath, repoRoot) {
7903
8625
  }
7904
8626
  }
7905
8627
  function scanDuplicateWorktreeCandidates(opts) {
7906
- if (!opts.includeOrphans || !existsSync30(opts.worktreesDir)) return [];
8628
+ if (!opts.includeOrphans || !existsSync33(opts.worktreesDir)) return [];
7907
8629
  const repos = /* @__PURE__ */ new Set();
7908
8630
  for (const entry of opts.index.values()) {
7909
- if (entry.run.repo) repos.add(path42.resolve(entry.run.repo));
8631
+ if (entry.run.repo) repos.add(path45.resolve(entry.run.repo));
7910
8632
  }
7911
8633
  const indexedPaths = /* @__PURE__ */ new Set();
7912
8634
  for (const entry of opts.index.values()) {
7913
- indexedPaths.add(path42.resolve(entry.worktreePath));
8635
+ indexedPaths.add(path45.resolve(entry.worktreePath));
7914
8636
  }
7915
8637
  const candidates = [];
7916
8638
  const seen = /* @__PURE__ */ new Set();
@@ -7923,15 +8645,15 @@ function scanDuplicateWorktreeCandidates(opts) {
7923
8645
  }
7924
8646
  const worktrees = parseWorktreePorcelain(porcelain);
7925
8647
  for (const wt of worktrees) {
7926
- const resolved = path42.resolve(wt.path);
7927
- if (resolved === path42.resolve(repoRoot)) continue;
8648
+ const resolved = path45.resolve(wt.path);
8649
+ if (resolved === path45.resolve(repoRoot)) continue;
7928
8650
  if (!isUnderWorktreesDir(resolved, opts.worktreesDir)) continue;
7929
8651
  if (indexedPaths.has(resolved)) continue;
7930
8652
  if (seen.has(resolved)) continue;
7931
- if (!existsSync30(resolved)) continue;
8653
+ if (!existsSync33(resolved)) continue;
7932
8654
  if (!isCleanWorktree(resolved, repoRoot)) continue;
7933
- const rel = path42.relative(opts.worktreesDir, resolved);
7934
- const parts = rel.split(path42.sep);
8655
+ const rel = path45.relative(opts.worktreesDir, resolved);
8656
+ const parts = rel.split(path45.sep);
7935
8657
  const runId = parts[0];
7936
8658
  const worker = parts[1] ?? "unknown";
7937
8659
  seen.add(resolved);
@@ -7950,12 +8672,12 @@ function scanDuplicateWorktreeCandidates(opts) {
7950
8672
  }
7951
8673
 
7952
8674
  // src/cleanup-worktree-index.ts
7953
- import path43 from "node:path";
8675
+ import path46 from "node:path";
7954
8676
  function buildWorktreeIndexAt(harnessRoot) {
7955
8677
  const index = /* @__PURE__ */ new Map();
7956
8678
  for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
7957
8679
  for (const name of Object.keys(run.workers || {})) {
7958
- const workerPath = path43.join(
8680
+ const workerPath = path46.join(
7959
8681
  runDirectoryAt(harnessRoot, run.id),
7960
8682
  "workers",
7961
8683
  safeSlug(name),
@@ -7964,9 +8686,9 @@ function buildWorktreeIndexAt(harnessRoot) {
7964
8686
  const worker = readJson(workerPath, void 0);
7965
8687
  if (!worker?.worktreePath) continue;
7966
8688
  const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
7967
- index.set(path43.resolve(worker.worktreePath), {
8689
+ index.set(path46.resolve(worker.worktreePath), {
7968
8690
  harnessRoot,
7969
- worktreePath: path43.resolve(worker.worktreePath),
8691
+ worktreePath: path46.resolve(worker.worktreePath),
7970
8692
  runId: run.id,
7971
8693
  workerName: name,
7972
8694
  run,
@@ -7979,7 +8701,7 @@ function buildWorktreeIndexAt(harnessRoot) {
7979
8701
  }
7980
8702
 
7981
8703
  // src/cleanup-retention-config.ts
7982
- function envFlag(name) {
8704
+ function envFlag2(name) {
7983
8705
  const v = process.env[name];
7984
8706
  return v === "1" || v === "true" || v === "yes";
7985
8707
  }
@@ -7990,17 +8712,17 @@ function envMs(name, fallback) {
7990
8712
  return Number.isFinite(n) && n >= 0 ? n : fallback;
7991
8713
  }
7992
8714
  function resolveHarnessRetention(options = {}) {
7993
- const execute = options.execute === true || options.execute !== false && envFlag("KYNVER_CLEANUP_EXECUTE");
7994
- const finalizeStaleRuns2 = options.finalizeStaleRuns !== false && !envFlag("KYNVER_CLEANUP_SKIP_FINALIZE");
8715
+ const execute = options.execute === true || options.execute !== false && envFlag2("KYNVER_CLEANUP_EXECUTE");
8716
+ const finalizeStaleRuns2 = options.finalizeStaleRuns !== false && !envFlag2("KYNVER_CLEANUP_SKIP_FINALIZE");
7995
8717
  const nodeModulesAgeMs = options.nodeModulesAgeMs ?? envMs("KYNVER_CLEANUP_NODE_MODULES_AGE_MS", DEFAULT_NODE_MODULES_AGE_MS);
7996
8718
  const worktreesAgeMs = options.worktreesAgeMs ?? envMs("KYNVER_CLEANUP_WORKTREES_AGE_MS", 0);
7997
8719
  const terminalWorktreesAgeMs = options.terminalWorktreesAgeMs ?? envMs("KYNVER_CLEANUP_TERMINAL_WORKTREES_AGE_MS", DEFAULT_TERMINAL_WORKTREES_AGE_MS);
7998
8720
  const runDirectoriesAgeMs = options.runDirectoriesAgeMs ?? envMs("KYNVER_CLEANUP_RUN_DIRECTORIES_AGE_MS", DEFAULT_RUN_DIRECTORIES_AGE_MS);
7999
8721
  const maxActionsPerSweep = options.maxActionsPerSweep ?? envMs("KYNVER_CLEANUP_MAX_ACTIONS_PER_SWEEP", DEFAULT_MAX_ACTIONS_PER_SWEEP);
8000
- const includeOrphans = options.includeOrphans === true || envFlag("KYNVER_CLEANUP_INCLUDE_ORPHANS");
8001
- const scopeAll = envFlag("KYNVER_CLEANUP_SCOPE_ALL") || process.env.KYNVER_CLEANUP_SCOPE === "all";
8722
+ const includeOrphans = options.includeOrphans === true || envFlag2("KYNVER_CLEANUP_INCLUDE_ORPHANS");
8723
+ const scopeAll = envFlag2("KYNVER_CLEANUP_SCOPE_ALL") || process.env.KYNVER_CLEANUP_SCOPE === "all";
8002
8724
  const runIdFilter = scopeAll ? options.runIdFilter : options.runIdFilter ?? (process.env.KYNVER_CLEANUP_RUN_ID || void 0);
8003
- const accountBytes = options.accountBytes !== false && !envFlag("KYNVER_CLEANUP_SKIP_BYTE_ACCOUNTING");
8725
+ const accountBytes = options.accountBytes !== false && !envFlag2("KYNVER_CLEANUP_SKIP_BYTE_ACCOUNTING");
8004
8726
  return {
8005
8727
  execute,
8006
8728
  finalizeStaleRuns: finalizeStaleRuns2,
@@ -8030,15 +8752,15 @@ function resolvePipelineHarnessRetention(runId) {
8030
8752
  }
8031
8753
 
8032
8754
  // src/cleanup-orphan-safety.ts
8033
- import { existsSync as existsSync31, statSync as statSync9 } from "node:fs";
8034
- import path44 from "node:path";
8755
+ import { existsSync as existsSync34, statSync as statSync10 } from "node:fs";
8756
+ import path47 from "node:path";
8035
8757
  var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
8036
8758
  function assessOrphanWorktreeSafety(input) {
8037
8759
  const now = input.now ?? Date.now();
8038
8760
  const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
8039
- if (!existsSync31(input.worktreePath)) return null;
8761
+ if (!existsSync34(input.worktreePath)) return null;
8040
8762
  if (input.runId && input.workerName) {
8041
- const heartbeatPath = path44.join(
8763
+ const heartbeatPath = path47.join(
8042
8764
  input.harnessRoot,
8043
8765
  "runs",
8044
8766
  input.runId,
@@ -8047,13 +8769,13 @@ function assessOrphanWorktreeSafety(input) {
8047
8769
  "heartbeat.jsonl"
8048
8770
  );
8049
8771
  try {
8050
- const mtime = statSync9(heartbeatPath).mtimeMs;
8772
+ const mtime = statSync10(heartbeatPath).mtimeMs;
8051
8773
  if (now - mtime < heartbeatFreshMs) return "active_worker";
8052
8774
  } catch {
8053
8775
  }
8054
8776
  }
8055
- const gitDir = path44.join(input.worktreePath, ".git");
8056
- if (!existsSync31(gitDir)) return null;
8777
+ const gitDir = path47.join(input.worktreePath, ".git");
8778
+ if (!existsSync34(gitDir)) return null;
8057
8779
  const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
8058
8780
  if (porcelain.status !== 0) return "pr_or_unmerged_commits";
8059
8781
  const dirtyLines = porcelain.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
@@ -8082,14 +8804,14 @@ function assessOrphanWorktreeSafety(input) {
8082
8804
  }
8083
8805
 
8084
8806
  // src/harness-storage-snapshot.ts
8085
- import { existsSync as existsSync32, readdirSync as readdirSync10, statSync as statSync10 } from "node:fs";
8086
- import path45 from "node:path";
8807
+ import { existsSync as existsSync35, readdirSync as readdirSync11, statSync as statSync11 } from "node:fs";
8808
+ import path48 from "node:path";
8087
8809
  function harnessStorageSnapshot(opts = {}) {
8088
8810
  const harnessRoot = normalizeHarnessRoot(opts.harnessRoot ?? resolveHarnessRoot());
8089
8811
  const worktreesDir = harnessWorktreesDir(harnessRoot);
8090
8812
  const now = opts.now ?? Date.now();
8091
8813
  const scannedAt = new Date(now).toISOString();
8092
- if (!existsSync32(worktreesDir)) {
8814
+ if (!existsSync35(worktreesDir)) {
8093
8815
  return {
8094
8816
  harnessRoot,
8095
8817
  worktreesDir,
@@ -8106,7 +8828,7 @@ function harnessStorageSnapshot(opts = {}) {
8106
8828
  let oldestMs = null;
8107
8829
  let entries;
8108
8830
  try {
8109
- entries = readdirSync10(worktreesDir, { withFileTypes: true });
8831
+ entries = readdirSync11(worktreesDir, { withFileTypes: true });
8110
8832
  } catch {
8111
8833
  return {
8112
8834
  harnessRoot,
@@ -8121,14 +8843,14 @@ function harnessStorageSnapshot(opts = {}) {
8121
8843
  for (const runEntry of entries) {
8122
8844
  if (!runEntry.isDirectory()) continue;
8123
8845
  runCount += 1;
8124
- const runPath = path45.join(worktreesDir, runEntry.name);
8846
+ const runPath = path48.join(worktreesDir, runEntry.name);
8125
8847
  try {
8126
- const st = statSync10(runPath);
8848
+ const st = statSync11(runPath);
8127
8849
  oldestMs = oldestMs === null ? st.mtimeMs : Math.min(oldestMs, st.mtimeMs);
8128
8850
  } catch {
8129
8851
  }
8130
8852
  try {
8131
- for (const workerEntry of readdirSync10(runPath, { withFileTypes: true })) {
8853
+ for (const workerEntry of readdirSync11(runPath, { withFileTypes: true })) {
8132
8854
  if (workerEntry.isDirectory()) workerCount += 1;
8133
8855
  }
8134
8856
  } catch {
@@ -8156,12 +8878,12 @@ function harnessStorageSnapshot(opts = {}) {
8156
8878
  }
8157
8879
 
8158
8880
  // src/cleanup-harness-roots.ts
8159
- import { existsSync as existsSync33 } from "node:fs";
8160
- import { homedir as homedir11 } from "node:os";
8161
- import path46 from "node:path";
8881
+ import { existsSync as existsSync36 } from "node:fs";
8882
+ import { homedir as homedir12 } from "node:os";
8883
+ import path49 from "node:path";
8162
8884
  var WELL_KNOWN_HARNESS_SCAN_ROOTS = [
8163
8885
  "/var/tmp/kynver-harness",
8164
- path46.join(homedir11(), ".openclaw", "harness")
8886
+ path49.join(homedir12(), ".openclaw", "harness")
8165
8887
  ];
8166
8888
  function addRoot(seen, roots, candidate) {
8167
8889
  if (!candidate?.trim()) return;
@@ -8183,15 +8905,15 @@ function resolveHarnessScanRoots(options = {}) {
8183
8905
  for (const candidate of extra ?? []) addRoot(seen, roots, candidate);
8184
8906
  if (shouldScanWellKnownRoots(options)) {
8185
8907
  for (const candidate of WELL_KNOWN_HARNESS_SCAN_ROOTS) {
8186
- const resolved = path46.resolve(candidate);
8187
- if (!seen.has(resolved) && existsSync33(resolved)) addRoot(seen, roots, resolved);
8908
+ const resolved = path49.resolve(candidate);
8909
+ if (!seen.has(resolved) && existsSync36(resolved)) addRoot(seen, roots, resolved);
8188
8910
  }
8189
8911
  }
8190
8912
  return roots;
8191
8913
  }
8192
8914
 
8193
8915
  // src/cleanup-disk-pressure.ts
8194
- function envFlag2(name) {
8916
+ function envFlag3(name) {
8195
8917
  const v = process.env[name];
8196
8918
  return v === "1" || v === "true" || v === "yes";
8197
8919
  }
@@ -8214,7 +8936,7 @@ function observeCleanupDiskPressure(input = {}) {
8214
8936
  }
8215
8937
  function applyDiskPressureToRetention(retention, pressure) {
8216
8938
  if (!pressure.pressured) return retention;
8217
- const executeOnPressure = retention.execute || envFlag2("KYNVER_CLEANUP_EXECUTE_ON_PRESSURE");
8939
+ const executeOnPressure = retention.execute || envFlag3("KYNVER_CLEANUP_EXECUTE_ON_PRESSURE");
8218
8940
  return {
8219
8941
  ...retention,
8220
8942
  execute: executeOnPressure,
@@ -8273,9 +8995,9 @@ function mergeWorktreeIndexes(scanRoots) {
8273
8995
  }
8274
8996
  function worktreePathForCandidate(candidate, worktreesDir) {
8275
8997
  if (candidate.runId && candidate.worker) {
8276
- return path47.join(worktreesDir, candidate.runId, candidate.worker);
8998
+ return path50.join(worktreesDir, candidate.runId, candidate.worker);
8277
8999
  }
8278
- return path47.resolve(candidate.path, "..");
9000
+ return path50.resolve(candidate.path, "..");
8279
9001
  }
8280
9002
  function runHarnessCleanup(options = {}) {
8281
9003
  let retention = resolveHarnessRetention(options);
@@ -8292,7 +9014,7 @@ function runHarnessCleanup(options = {}) {
8292
9014
  const atSweepCap = () => actions.length >= maxActions;
8293
9015
  for (const harnessRoot of paths.scanRoots) {
8294
9016
  if (atSweepCap()) break;
8295
- const worktreesDir = path47.join(harnessRoot, "worktrees");
9017
+ const worktreesDir = path50.join(harnessRoot, "worktrees");
8296
9018
  const scanOpts = {
8297
9019
  harnessRoot,
8298
9020
  worktreesDir,
@@ -8306,7 +9028,7 @@ function runHarnessCleanup(options = {}) {
8306
9028
  for (const raw of scanDependencyCacheCandidates(scanOpts)) {
8307
9029
  if (atSweepCap()) break;
8308
9030
  const candidate = attachCandidateBytes(raw, retention.accountBytes);
8309
- const resolved = path47.resolve(candidate.path);
9031
+ const resolved = path50.resolve(candidate.path);
8310
9032
  if (processedPaths.has(resolved)) continue;
8311
9033
  processedPaths.add(resolved);
8312
9034
  const pathSkip = pathGuardForDependencyCache(candidate, harnessRoot, worktreesDir);
@@ -8316,7 +9038,7 @@ function runHarnessCleanup(options = {}) {
8316
9038
  continue;
8317
9039
  }
8318
9040
  const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
8319
- const indexed = index.get(path47.resolve(worktreePath)) ?? null;
9041
+ const indexed = index.get(path50.resolve(worktreePath)) ?? null;
8320
9042
  const guardReason = skipDependencyCacheRemoval({
8321
9043
  indexed,
8322
9044
  includeOrphans: true,
@@ -8336,7 +9058,7 @@ function runHarnessCleanup(options = {}) {
8336
9058
  for (const raw of scanBuildCacheCandidates(scanOpts)) {
8337
9059
  if (atSweepCap()) break;
8338
9060
  const candidate = attachCandidateBytes(raw, retention.accountBytes);
8339
- const resolved = path47.resolve(candidate.path);
9061
+ const resolved = path50.resolve(candidate.path);
8340
9062
  if (processedPaths.has(resolved)) continue;
8341
9063
  processedPaths.add(resolved);
8342
9064
  const pathSkip = isHarnessBuildCachePath(candidate.path, harnessRoot, worktreesDir);
@@ -8346,7 +9068,7 @@ function runHarnessCleanup(options = {}) {
8346
9068
  continue;
8347
9069
  }
8348
9070
  const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
8349
- const indexed = index.get(path47.resolve(worktreePath)) ?? null;
9071
+ const indexed = index.get(path50.resolve(worktreePath)) ?? null;
8350
9072
  const guardReason = skipBuildCacheRemoval({
8351
9073
  indexed,
8352
9074
  includeOrphans: true,
@@ -8370,11 +9092,11 @@ function runHarnessCleanup(options = {}) {
8370
9092
  const worktreeSeen = /* @__PURE__ */ new Set();
8371
9093
  for (const raw of worktreeCandidates) {
8372
9094
  if (atSweepCap()) break;
8373
- const resolved = path47.resolve(raw.path);
9095
+ const resolved = path50.resolve(raw.path);
8374
9096
  if (worktreeSeen.has(resolved)) continue;
8375
9097
  worktreeSeen.add(resolved);
8376
9098
  const candidate = attachCandidateBytes({ ...raw, path: resolved }, retention.accountBytes);
8377
- const indexed = index.get(path47.resolve(candidate.path)) ?? null;
9099
+ const indexed = index.get(path50.resolve(candidate.path)) ?? null;
8378
9100
  const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
8379
9101
  worktreePath: candidate.path,
8380
9102
  harnessRoot,
@@ -8384,7 +9106,7 @@ function runHarnessCleanup(options = {}) {
8384
9106
  });
8385
9107
  const guardSkip = skipWorktreeRemoval({
8386
9108
  indexed,
8387
- worktreePath: path47.resolve(candidate.path),
9109
+ worktreePath: path50.resolve(candidate.path),
8388
9110
  includeOrphans: retention.includeOrphans,
8389
9111
  worktreesAgeMs: retention.worktreesAgeMs,
8390
9112
  terminalWorktreesAgeMs: retention.terminalWorktreesAgeMs,
@@ -8411,10 +9133,10 @@ function runHarnessCleanup(options = {}) {
8411
9133
  })) {
8412
9134
  if (atSweepCap()) break;
8413
9135
  const candidate = attachCandidateBytes(raw, retention.accountBytes);
8414
- const resolved = path47.resolve(candidate.path);
9136
+ const resolved = path50.resolve(candidate.path);
8415
9137
  if (processedPaths.has(resolved)) continue;
8416
9138
  processedPaths.add(resolved);
8417
- const runId = candidate.runId ?? path47.basename(resolved);
9139
+ const runId = candidate.runId ?? path50.basename(resolved);
8418
9140
  const dirSkip = skipRunDirectoryRemoval({
8419
9141
  harnessRoot,
8420
9142
  runId,
@@ -8503,8 +9225,8 @@ function isPipelineCleanupEnabled() {
8503
9225
 
8504
9226
  // src/installed-package-versions.ts
8505
9227
  import { readFile } from "node:fs/promises";
8506
- import { homedir as homedir12 } from "node:os";
8507
- import path48 from "node:path";
9228
+ import { homedir as homedir13 } from "node:os";
9229
+ import path51 from "node:path";
8508
9230
  var MANAGED_PACKAGES = [
8509
9231
  "@kynver-app/runtime",
8510
9232
  "@kynver-app/openclaw-agent-os",
@@ -8518,13 +9240,13 @@ function unique(values) {
8518
9240
  return [...new Set(values.filter((value) => Boolean(value)))];
8519
9241
  }
8520
9242
  function moduleRoots() {
8521
- const home = homedir12();
8522
- const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path48.join(home, ".openclaw", "npm");
8523
- const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path48.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path48.join(home, ".npm-global", "lib", "node_modules"));
9243
+ const home = homedir13();
9244
+ const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path51.join(home, ".openclaw", "npm");
9245
+ const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path51.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path51.join(home, ".npm-global", "lib", "node_modules"));
8524
9246
  return unique([
8525
- path48.join(openClawPrefix, "lib", "node_modules"),
8526
- path48.join(openClawPrefix, "node_modules"),
8527
- npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path48.join(npmGlobalRoot, "lib", "node_modules")
9247
+ path51.join(openClawPrefix, "lib", "node_modules"),
9248
+ path51.join(openClawPrefix, "node_modules"),
9249
+ npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path51.join(npmGlobalRoot, "lib", "node_modules")
8528
9250
  ]);
8529
9251
  }
8530
9252
  async function readVersion(packageJsonPath) {
@@ -8540,7 +9262,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
8540
9262
  const out = {};
8541
9263
  for (const packageName of MANAGED_PACKAGES) {
8542
9264
  for (const root of roots) {
8543
- const packageJsonPath = path48.join(root, packageName, "package.json");
9265
+ const packageJsonPath = path51.join(root, packageName, "package.json");
8544
9266
  const version = await readVersion(packageJsonPath);
8545
9267
  if (!version) continue;
8546
9268
  out[packageName] = { version, observedAt, path: packageJsonPath };
@@ -8556,11 +9278,11 @@ async function completeFinishedWorkers(runId, args) {
8556
9278
  const outcomes = [];
8557
9279
  for (const name of Object.keys(run.workers || {})) {
8558
9280
  const worker = readJson(
8559
- path49.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
9281
+ path52.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
8560
9282
  void 0
8561
9283
  );
8562
9284
  if (!worker?.taskId || worker.localOnly) continue;
8563
- if (hasCompletionAck(worker)) {
9285
+ if (hasTerminalCompletionAck(worker)) {
8564
9286
  outcomes.push({ worker: name, ok: true, taskId: worker.taskId ?? null, skipped: true });
8565
9287
  continue;
8566
9288
  }
@@ -8629,24 +9351,51 @@ async function runPipelineTick(args) {
8629
9351
  let maxStarts = maxStartsAdvice.maxStarts;
8630
9352
  const sweep = await sweepRun({ run: runId, agentOsId, pipeline: true, ...args });
8631
9353
  let dispatch = null;
8632
- if (execute && maxStarts > 0) {
8633
- dispatch = await runPipelineDispatch(
9354
+ let startedCount = 0;
9355
+ const exactTargetTaskIds = operatorExactTargetTaskIds(operatorTick);
9356
+ let remainingStarts = maxStarts;
9357
+ if (execute && remainingStarts > 0 && exactTargetTaskIds.length > 0) {
9358
+ const exactBudget = Math.min(remainingStarts, exactTargetTaskIds.length);
9359
+ const exact = await runPipelineDispatch(
9360
+ {
9361
+ ...args,
9362
+ run: runId,
9363
+ agentOsId,
9364
+ targetTaskIds: exactTargetTaskIds.join(",")
9365
+ },
9366
+ exactBudget
9367
+ );
9368
+ const exactStarted = countDispatchStarts(exact);
9369
+ startedCount += exactStarted;
9370
+ remainingStarts = Math.max(0, remainingStarts - exactStarted);
9371
+ dispatch = { exactTargetTaskIds, exact, startedCount };
9372
+ }
9373
+ if (execute && remainingStarts > 0) {
9374
+ const broad = await runPipelineDispatch(
8634
9375
  {
8635
9376
  ...args,
8636
9377
  run: runId,
8637
9378
  agentOsId
8638
9379
  },
8639
- maxStarts
9380
+ remainingStarts
8640
9381
  );
8641
- } else {
8642
- dispatch = {
8643
- ok: true,
8644
- skipped: true,
8645
- reason: execute ? dispatchResourceGate.reason ?? "no slots or queued work" : "execute disabled",
8646
- maxStarts: 0
8647
- };
9382
+ const broadStarted = countDispatchStarts(broad);
9383
+ startedCount += broadStarted;
9384
+ dispatch = dispatch && typeof dispatch === "object" ? { ...dispatch, broad, startedCount } : broad;
9385
+ } else if (!execute || maxStarts <= 0) {
9386
+ if (!dispatch) {
9387
+ dispatch = {
9388
+ ok: true,
9389
+ skipped: true,
9390
+ reason: execute ? dispatchResourceGate.reason ?? "no slots or queued work" : "execute disabled",
9391
+ maxStarts: 0,
9392
+ dispatchAdvice: maxStartsAdvice,
9393
+ ...exactTargetTaskIds.length ? { exactTargetTaskIds, exactOnly: true } : {}
9394
+ };
9395
+ }
9396
+ } else if (dispatch && typeof dispatch === "object") {
9397
+ dispatch = { ...dispatch, broadSkipped: true, startedCount };
8648
9398
  }
8649
- const startedCount = dispatch?.startedCount ?? 0;
8650
9399
  const idle = !maxStartsAdvice.underutilized && maxStarts === 0 && completedWorkers.length === 0 && startedCount === 0;
8651
9400
  return {
8652
9401
  runId,
@@ -8662,6 +9411,7 @@ async function runPipelineTick(args) {
8662
9411
  completionAckSync,
8663
9412
  operatorTick,
8664
9413
  sweep,
9414
+ dispatchAdvice: maxStartsAdvice,
8665
9415
  dispatch,
8666
9416
  idle
8667
9417
  };
@@ -8685,8 +9435,18 @@ async function runDaemon(args) {
8685
9435
  stopping = true;
8686
9436
  });
8687
9437
  console.error(JSON.stringify({ event: "daemon_start", runId, agentOsId, execute, intervalMs }));
9438
+ const cronEnv = resolveKynverCronEnv();
8688
9439
  while (!stopping) {
8689
9440
  try {
9441
+ if (cronEnv.tickEnabled) {
9442
+ const cronTick = await runKynverCronTick({
9443
+ env: cronEnv,
9444
+ agentOsIdFilter: agentOsId
9445
+ });
9446
+ if (cronTick.enabled && (cronTick.fired > 0 || cronTick.errors > 0)) {
9447
+ console.error(JSON.stringify({ event: "daemon_cron_tick", ...cronTick }));
9448
+ }
9449
+ }
8690
9450
  const tick = await runPipelineTick({ run: runId, agentOsId, execute, ...args });
8691
9451
  console.error(JSON.stringify({ event: "daemon_tick", ...tick }));
8692
9452
  if (tick.idle) {
@@ -8705,7 +9465,7 @@ async function runDaemon(args) {
8705
9465
  }
8706
9466
 
8707
9467
  // src/plan-progress.ts
8708
- import path50 from "node:path";
9468
+ import path53 from "node:path";
8709
9469
 
8710
9470
  // src/bounded-build/constants.ts
8711
9471
  var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
@@ -8992,7 +9752,7 @@ async function emitPlanProgress(args) {
8992
9752
  }
8993
9753
  function verifyPlanLocal(args) {
8994
9754
  const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
8995
- const cwd = path50.resolve(worktree);
9755
+ const cwd = path53.resolve(worktree);
8996
9756
  const summary = runHarnessVerifyCommands(cwd);
8997
9757
  const emitJson = args.json === true || args.json === "true";
8998
9758
  const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
@@ -9041,9 +9801,9 @@ async function verifyPlan(args) {
9041
9801
  }
9042
9802
 
9043
9803
  // src/harness-verify-cli.ts
9044
- import path51 from "node:path";
9804
+ import path54 from "node:path";
9045
9805
  function runHarnessVerifyCli(args) {
9046
- const cwd = path51.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
9806
+ const cwd = path54.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
9047
9807
  const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
9048
9808
  const commands = [];
9049
9809
  const rawCmd = args.command;
@@ -9087,7 +9847,7 @@ function runHarnessVerifyCli(args) {
9087
9847
  }
9088
9848
 
9089
9849
  // src/plan-persist-cli.ts
9090
- import { readFileSync as readFileSync11 } from "node:fs";
9850
+ import { readFileSync as readFileSync12 } from "node:fs";
9091
9851
  var OPERATIONS = ["create", "add_version", "update_metadata"];
9092
9852
  var FAILURE_KINDS = [
9093
9853
  "approval_guard",
@@ -9099,7 +9859,7 @@ var FAILURE_KINDS = [
9099
9859
  function readBodyArg(args) {
9100
9860
  const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
9101
9861
  if (bodyFile) {
9102
- return { body: readFileSync11(bodyFile, "utf8"), bodyPathHint: bodyFile };
9862
+ return { body: readFileSync12(bodyFile, "utf8"), bodyPathHint: bodyFile };
9103
9863
  }
9104
9864
  const inline = args.body ? String(args.body) : void 0;
9105
9865
  if (inline) return { body: inline };
@@ -9243,7 +10003,7 @@ function formatMonitorTickNotice(tick) {
9243
10003
  }
9244
10004
 
9245
10005
  // src/monitor/monitor.service.ts
9246
- import path53 from "node:path";
10006
+ import path56 from "node:path";
9247
10007
 
9248
10008
  // src/monitor/monitor.classify.ts
9249
10009
  function classifyWorkerHealth(input) {
@@ -9295,11 +10055,11 @@ function classifyWorkerHealth(input) {
9295
10055
  }
9296
10056
 
9297
10057
  // src/monitor/monitor.store.ts
9298
- import { existsSync as existsSync34, mkdirSync as mkdirSync6, readdirSync as readdirSync11, unlinkSync as unlinkSync2 } from "node:fs";
9299
- import path52 from "node:path";
10058
+ import { existsSync as existsSync37, mkdirSync as mkdirSync6, readdirSync as readdirSync12, unlinkSync as unlinkSync3 } from "node:fs";
10059
+ import path55 from "node:path";
9300
10060
  function monitorsDir() {
9301
10061
  const { harnessRoot } = getHarnessPaths();
9302
- const dir = path52.join(harnessRoot, "monitors");
10062
+ const dir = path55.join(harnessRoot, "monitors");
9303
10063
  mkdirSync6(dir, { recursive: true });
9304
10064
  return dir;
9305
10065
  }
@@ -9307,7 +10067,7 @@ function monitorIdFor(runId, workerName) {
9307
10067
  return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
9308
10068
  }
9309
10069
  function monitorPath(monitorId) {
9310
- return path52.join(monitorsDir(), `${monitorId}.json`);
10070
+ return path55.join(monitorsDir(), `${monitorId}.json`);
9311
10071
  }
9312
10072
  function loadMonitorSession(monitorId) {
9313
10073
  return readJson(monitorPath(monitorId), void 0);
@@ -9317,18 +10077,18 @@ function saveMonitorSession(session) {
9317
10077
  }
9318
10078
  function deleteMonitorSession(monitorId) {
9319
10079
  const file = monitorPath(monitorId);
9320
- if (!existsSync34(file)) return false;
9321
- unlinkSync2(file);
10080
+ if (!existsSync37(file)) return false;
10081
+ unlinkSync3(file);
9322
10082
  return true;
9323
10083
  }
9324
10084
  function listMonitorSessions() {
9325
10085
  const dir = monitorsDir();
9326
- if (!existsSync34(dir)) return [];
10086
+ if (!existsSync37(dir)) return [];
9327
10087
  const entries = [];
9328
- for (const name of readdirSync11(dir)) {
10088
+ for (const name of readdirSync12(dir)) {
9329
10089
  if (!name.endsWith(".json")) continue;
9330
10090
  const session = readJson(
9331
- path52.join(dir, name),
10091
+ path55.join(dir, name),
9332
10092
  void 0
9333
10093
  );
9334
10094
  if (!session?.monitorId) continue;
@@ -9419,7 +10179,7 @@ async function fetchTaskLeasesForWorkers(input) {
9419
10179
  // src/monitor/monitor.service.ts
9420
10180
  function workerRecord2(runId, name) {
9421
10181
  return readJson(
9422
- path53.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
10182
+ path56.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
9423
10183
  void 0
9424
10184
  );
9425
10185
  }
@@ -9625,21 +10385,21 @@ async function runMonitorLoop(args) {
9625
10385
 
9626
10386
  // src/monitor/monitor-spawn.ts
9627
10387
  import { spawn as spawn6 } from "node:child_process";
9628
- import { closeSync as closeSync6, existsSync as existsSync35, openSync as openSync6 } from "node:fs";
9629
- import path54 from "node:path";
10388
+ import { closeSync as closeSync7, existsSync as existsSync38, openSync as openSync7 } from "node:fs";
10389
+ import path57 from "node:path";
9630
10390
  import { fileURLToPath as fileURLToPath3 } from "node:url";
9631
10391
  function resolveDefaultCliPath2() {
9632
- return path54.join(fileURLToPath3(new URL(".", import.meta.url)), "cli.js");
10392
+ return path57.join(fileURLToPath3(new URL(".", import.meta.url)), "cli.js");
9633
10393
  }
9634
10394
  function spawnMonitorSidecar(opts) {
9635
10395
  const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
9636
- if (!existsSync35(cliPath)) return void 0;
10396
+ if (!existsSync38(cliPath)) return void 0;
9637
10397
  const monitorId = monitorIdFor(opts.runId, opts.workerName);
9638
10398
  const { harnessRoot } = getHarnessPaths();
9639
- const logPath = path54.join(harnessRoot, "monitors", `${monitorId}.log`);
10399
+ const logPath = path57.join(harnessRoot, "monitors", `${monitorId}.log`);
9640
10400
  let logFd;
9641
10401
  try {
9642
- logFd = openSync6(logPath, "a");
10402
+ logFd = openSync7(logPath, "a");
9643
10403
  } catch {
9644
10404
  logFd = void 0;
9645
10405
  }
@@ -9679,7 +10439,7 @@ function spawnMonitorSidecar(opts) {
9679
10439
  env: process.env
9680
10440
  })
9681
10441
  );
9682
- if (logFd !== void 0) closeSync6(logFd);
10442
+ if (logFd !== void 0) closeSync7(logFd);
9683
10443
  child.unref();
9684
10444
  const session = {
9685
10445
  monitorId,
@@ -9696,7 +10456,7 @@ function spawnMonitorSidecar(opts) {
9696
10456
  } catch {
9697
10457
  if (logFd !== void 0) {
9698
10458
  try {
9699
- closeSync6(logFd);
10459
+ closeSync7(logFd);
9700
10460
  } catch {
9701
10461
  }
9702
10462
  }
@@ -9756,13 +10516,13 @@ async function monitorTickCli(args) {
9756
10516
  }
9757
10517
 
9758
10518
  // src/package-version.ts
9759
- import { existsSync as existsSync36, readFileSync as readFileSync12 } from "node:fs";
10519
+ import { existsSync as existsSync39, readFileSync as readFileSync13 } from "node:fs";
9760
10520
  import { dirname, join } from "node:path";
9761
10521
  import { fileURLToPath as fileURLToPath4 } from "node:url";
9762
10522
  function resolvePackageRoot(moduleUrl) {
9763
10523
  let dir = dirname(fileURLToPath4(moduleUrl));
9764
10524
  for (let depth = 0; depth < 6; depth += 1) {
9765
- if (existsSync36(join(dir, "package.json"))) return dir;
10525
+ if (existsSync39(join(dir, "package.json"))) return dir;
9766
10526
  const parent = dirname(dir);
9767
10527
  if (parent === dir) break;
9768
10528
  dir = parent;
@@ -9771,7 +10531,7 @@ function resolvePackageRoot(moduleUrl) {
9771
10531
  }
9772
10532
  function readOwnPackageVersion(moduleUrl = import.meta.url) {
9773
10533
  const pkgPath = join(resolvePackageRoot(moduleUrl), "package.json");
9774
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
10534
+ const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
9775
10535
  if (typeof pkg.version !== "string" || !pkg.version.trim()) {
9776
10536
  throw new Error(`Missing package.json version at ${pkgPath}`);
9777
10537
  }
@@ -9792,7 +10552,7 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
9792
10552
  }
9793
10553
 
9794
10554
  // src/post-restart-unblock.ts
9795
- import path55 from "node:path";
10555
+ import path58 from "node:path";
9796
10556
  function skip(runId, worker, taskId, agentOsId, leaseOwner, reason) {
9797
10557
  return { runId, worker, taskId, agentOsId, leaseOwner, action: "skipped", reason };
9798
10558
  }
@@ -9805,7 +10565,7 @@ async function postRestartUnblock(args) {
9805
10565
  const errors = [];
9806
10566
  for (const run of listRunRecords()) {
9807
10567
  for (const name of Object.keys(run.workers ?? {})) {
9808
- const workerPath = path55.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
10568
+ const workerPath = path58.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
9809
10569
  const worker = readJson(workerPath, void 0);
9810
10570
  if (!worker) {
9811
10571
  skipped.push(skip(run.id, name, "", "", "", "worker.json missing"));
@@ -9917,12 +10677,12 @@ async function postRestartUnblockCli(args) {
9917
10677
  }
9918
10678
 
9919
10679
  // src/doctor/runtime-takeover.ts
9920
- import path57 from "node:path";
10680
+ import path60 from "node:path";
9921
10681
 
9922
10682
  // src/doctor/runtime-takeover.probes.ts
9923
- import { accessSync, constants, existsSync as existsSync37, readFileSync as readFileSync13 } from "node:fs";
9924
- import { homedir as homedir13 } from "node:os";
9925
- import path56 from "node:path";
10683
+ import { accessSync, constants, existsSync as existsSync40, readFileSync as readFileSync14 } from "node:fs";
10684
+ import { homedir as homedir14 } from "node:os";
10685
+ import path59 from "node:path";
9926
10686
  import { spawnSync as spawnSync7 } from "node:child_process";
9927
10687
  function captureCommand(bin, args) {
9928
10688
  try {
@@ -9951,7 +10711,7 @@ function tokenPrefix(token) {
9951
10711
  return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
9952
10712
  }
9953
10713
  function isWritable(target) {
9954
- if (!existsSync37(target)) return false;
10714
+ if (!existsSync40(target)) return false;
9955
10715
  try {
9956
10716
  accessSync(target, constants.W_OK);
9957
10717
  return true;
@@ -9964,15 +10724,15 @@ var defaultRuntimeTakeoverProbes = {
9964
10724
  commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
9965
10725
  kynverVersion: (bin) => captureCommand(bin, ["--version"]),
9966
10726
  loadConfig: () => loadUserConfig(),
9967
- configFilePath: () => path56.join(homedir13(), ".kynver", "config.json"),
9968
- credentialsFilePath: () => path56.join(homedir13(), ".kynver", "credentials"),
10727
+ configFilePath: () => path59.join(homedir14(), ".kynver", "config.json"),
10728
+ credentialsFilePath: () => path59.join(homedir14(), ".kynver", "credentials"),
9969
10729
  readCredentials: () => {
9970
- const credPath = path56.join(homedir13(), ".kynver", "credentials");
9971
- if (!existsSync37(credPath)) {
10730
+ const credPath = path59.join(homedir14(), ".kynver", "credentials");
10731
+ if (!existsSync40(credPath)) {
9972
10732
  return { hasApiKey: false };
9973
10733
  }
9974
10734
  try {
9975
- const parsed = JSON.parse(readFileSync13(credPath, "utf8"));
10735
+ const parsed = JSON.parse(readFileSync14(credPath, "utf8"));
9976
10736
  return {
9977
10737
  hasApiKey: Boolean(parsed.apiKey?.trim()),
9978
10738
  runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
@@ -9991,7 +10751,10 @@ var defaultRuntimeTakeoverProbes = {
9991
10751
  kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
9992
10752
  opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
9993
10753
  kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0,
9994
- openclawCronStorePath: Boolean(process.env.OPENCLAW_CRON_STORE_PATH?.trim()),
10754
+ openclawCronStorePath: Boolean(
10755
+ process.env.KYNVER_CRON_STORE_PATH?.trim() || process.env.OPENCLAW_CRON_STORE_PATH?.trim()
10756
+ ),
10757
+ kynverCronDaemonPrimary: isKynverCronDaemonPrimary(),
9995
10758
  qstashTokenPresent: Boolean(process.env.QSTASH_TOKEN?.trim()),
9996
10759
  kynverHostedDeployment: (() => {
9997
10760
  const v = process.env.KYNVER_HOSTED_DEPLOYMENT?.trim().toLowerCase();
@@ -9999,8 +10762,8 @@ var defaultRuntimeTakeoverProbes = {
9999
10762
  })()
10000
10763
  }),
10001
10764
  harnessRoot: () => resolveHarnessRoot(),
10002
- legacyOpenclawHarnessRoot: () => path56.join(homedir13(), ".openclaw", "harness"),
10003
- pathExists: (target) => existsSync37(target),
10765
+ legacyOpenclawHarnessRoot: () => path59.join(homedir14(), ".openclaw", "harness"),
10766
+ pathExists: (target) => existsSync40(target),
10004
10767
  pathWritable: (target) => isWritable(target),
10005
10768
  vercelVersion: () => captureCommand("vercel", ["--version"]),
10006
10769
  vercelWhoami: () => captureCommand("vercel", ["whoami"])
@@ -10020,8 +10783,10 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
10020
10783
  const schedulerDetails = {
10021
10784
  schedulerProvider: env.kynverSchedulerProvider ?? null,
10022
10785
  deploymentSchedulerProvider: ctx.deploymentSchedulerProvider ?? null,
10023
- openclawCronStorePath: Boolean(env.openclawCronStorePath)
10786
+ openclawCronStorePath: Boolean(env.openclawCronStorePath),
10787
+ kynverCronDaemonPrimary: Boolean(env.kynverCronDaemonPrimary)
10024
10788
  };
10789
+ const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
10025
10790
  if (hasQstashCutover(env, ctx) && !hasLocalOpenClawDependency(env, ctx)) {
10026
10791
  const source = env.kynverSchedulerProvider === "qstash" ? "KYNVER_SCHEDULER_PROVIDER=qstash on this host" : "deploymentSchedulerProvider=qstash in ~/.kynver/config.json";
10027
10792
  return check({
@@ -10033,6 +10798,19 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
10033
10798
  });
10034
10799
  }
10035
10800
  if (hasLocalOpenClawDependency(env, ctx)) {
10801
+ if (env.kynverCronDaemonPrimary && daemonDispatchReady) {
10802
+ return check({
10803
+ id: "hotspot_openclaw_scheduler",
10804
+ label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
10805
+ status: "pass",
10806
+ summary: "Kynver Cron local store present; `kynver daemon` owns schedule fires (kynver-cron tick loop)",
10807
+ details: {
10808
+ ...schedulerDetails,
10809
+ dispatchPath: "kynver-daemon-cron-tick",
10810
+ kynverCronDaemonPrimary: true
10811
+ }
10812
+ });
10813
+ }
10036
10814
  const parts = [];
10037
10815
  if (env.kynverSchedulerProvider === "openclaw-cron") {
10038
10816
  parts.push("KYNVER_SCHEDULER_PROVIDER=openclaw-cron");
@@ -10041,21 +10819,20 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
10041
10819
  parts.push("deploymentSchedulerProvider=openclaw-cron in config");
10042
10820
  }
10043
10821
  if (env.openclawCronStorePath) {
10044
- parts.push("OPENCLAW_CRON_STORE_PATH set (local cron bridge)");
10822
+ parts.push("KYNVER_CRON_STORE_PATH or OPENCLAW_CRON_STORE_PATH set (local cron store)");
10045
10823
  }
10046
10824
  return check({
10047
10825
  id: "hotspot_openclaw_scheduler",
10048
10826
  label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
10049
10827
  status: "warn",
10050
- summary: `OpenClaw local cron still active (${parts.join("; ")})`,
10051
- 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`.",
10828
+ summary: `Local cron store without daemon tick (${parts.join("; ")})`,
10829
+ 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.",
10052
10830
  details: schedulerDetails
10053
10831
  });
10054
10832
  }
10055
10833
  const runnerOpenclaw = env.kynverSchedulerProvider === "openclaw-cron";
10056
10834
  const runnerQstash = env.kynverSchedulerProvider === "qstash";
10057
10835
  const hostedDeployment = Boolean(env.qstashTokenPresent) || Boolean(env.kynverHostedDeployment);
10058
- const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
10059
10836
  const hostedSchedulerProcess = hostedDeployment && !daemonDispatchReady;
10060
10837
  const deploymentNeedsQstash = hostedSchedulerProcess && !env.qstashTokenPresent && env.kynverSchedulerProvider !== "qstash";
10061
10838
  const deploymentOpenclaw = hostedSchedulerProcess && env.kynverSchedulerProvider === "openclaw-cron";
@@ -10328,8 +11105,8 @@ function assessVercelCli(probes) {
10328
11105
  }
10329
11106
  function assessHarnessDirs(probes) {
10330
11107
  const harnessRoot = probes.harnessRoot();
10331
- const runsDir = path57.join(harnessRoot, "runs");
10332
- const worktreesDir = path57.join(harnessRoot, "worktrees");
11108
+ const runsDir = path60.join(harnessRoot, "runs");
11109
+ const worktreesDir = path60.join(harnessRoot, "worktrees");
10333
11110
  const displayHarnessRoot = redactHomePath(harnessRoot);
10334
11111
  const displayRunsDir = redactHomePath(runsDir);
10335
11112
  const displayWorktreesDir = redactHomePath(worktreesDir);
@@ -10532,6 +11309,11 @@ var RUNNER_SCHEDULER_CUTOVER_STEPS = [
10532
11309
  function readSchedulerCutoverEnv(env = process.env) {
10533
11310
  return {
10534
11311
  kynverSchedulerProvider: env.KYNVER_SCHEDULER_PROVIDER?.trim() || null,
11312
+ kynverCronStorePath: env.KYNVER_CRON_STORE_PATH?.trim() || env.OPENCLAW_CRON_STORE_PATH?.trim() || null,
11313
+ kynverCronSecret: Boolean(
11314
+ env.KYNVER_CRON_SECRET?.trim() || env.OPENCLAW_CRON_SECRET?.trim()
11315
+ ),
11316
+ kynverCronFireBaseUrl: env.KYNVER_CRON_FIRE_BASE_URL?.trim() || env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || null,
10535
11317
  openclawCronStorePath: env.OPENCLAW_CRON_STORE_PATH?.trim() || null,
10536
11318
  openclawCronSecret: Boolean(env.OPENCLAW_CRON_SECRET?.trim()),
10537
11319
  openclawCronFireBaseUrl: env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || null
@@ -10542,8 +11324,13 @@ function assessSchedulerCutover(config, env = readSchedulerCutoverEnv()) {
10542
11324
  if (env.kynverSchedulerProvider === "openclaw-cron") {
10543
11325
  blockers.push("Runner still has KYNVER_SCHEDULER_PROVIDER=openclaw-cron");
10544
11326
  }
10545
- if (env.openclawCronStorePath) {
10546
- blockers.push("Runner still has OPENCLAW_CRON_STORE_PATH");
11327
+ if (env.kynverCronStorePath && env.kynverSchedulerProvider !== "kynver-cron") {
11328
+ blockers.push(
11329
+ "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"
11330
+ );
11331
+ }
11332
+ if (env.openclawCronStorePath && !env.kynverCronStorePath) {
11333
+ blockers.push("Runner still has legacy OPENCLAW_CRON_STORE_PATH (prefer KYNVER_CRON_STORE_PATH)");
10547
11334
  }
10548
11335
  if (config.deploymentSchedulerProvider === "openclaw-cron") {
10549
11336
  blockers.push("~/.kynver/config.json deploymentSchedulerProvider is still openclaw-cron");
@@ -10565,9 +11352,9 @@ function applySchedulerCutoverAttestation(config) {
10565
11352
  }
10566
11353
 
10567
11354
  // src/scheduler-cutover-cli.ts
10568
- import path58 from "node:path";
10569
- import { homedir as homedir14 } from "node:os";
10570
- var CONFIG_FILE2 = path58.join(homedir14(), ".kynver", "config.json");
11355
+ import path61 from "node:path";
11356
+ import { homedir as homedir15 } from "node:os";
11357
+ var CONFIG_FILE2 = path61.join(homedir15(), ".kynver", "config.json");
10571
11358
  function runSchedulerCutoverCheckCli(json = false) {
10572
11359
  const config = loadUserConfig();
10573
11360
  const report = assessSchedulerCutover(config);
@@ -10595,7 +11382,10 @@ function runSchedulerCutoverCheckCli(json = false) {
10595
11382
  ` KYNVER_SCHEDULER_PROVIDER: ${report.runnerEnv.kynverSchedulerProvider ?? "(unset)"}`
10596
11383
  );
10597
11384
  console.log(
10598
- ` OPENCLAW_CRON_STORE_PATH: ${report.runnerEnv.openclawCronStorePath ?? "(unset)"}`
11385
+ ` KYNVER_CRON_STORE_PATH: ${report.runnerEnv.kynverCronStorePath ?? "(unset)"}`
11386
+ );
11387
+ console.log(
11388
+ ` OPENCLAW_CRON_STORE_PATH (legacy): ${report.runnerEnv.openclawCronStorePath ?? "(unset)"}`
10599
11389
  );
10600
11390
  if (report.blockers.length) {
10601
11391
  console.log("\nBlockers:");
@@ -10642,6 +11432,65 @@ function runSchedulerAttestCutoverCli(json = false) {
10642
11432
  console.log(` config: ${payload.configPath}`);
10643
11433
  }
10644
11434
 
11435
+ // src/cron/cron-status.ts
11436
+ async function buildKynverCronStatusReport(env = resolveKynverCronEnv()) {
11437
+ const jobs = await loadCronJobs(env.storePath).catch(() => []);
11438
+ const state = await loadCronTickState(env.statePath).catch(() => ({ version: 1, jobs: {} }));
11439
+ const credentialsReady = Boolean(env.fireBaseUrl && env.secret);
11440
+ const daemonPrimary = isKynverCronDaemonPrimary(env);
11441
+ let primary = "disabled";
11442
+ if (daemonPrimary) primary = "kynver-cron-daemon";
11443
+ else if (process.env.QSTASH_TOKEN?.trim()) primary = "qstash";
11444
+ return {
11445
+ primary,
11446
+ env: {
11447
+ storePath: env.storePath,
11448
+ statePath: env.statePath,
11449
+ tickEnabled: env.tickEnabled,
11450
+ fireBaseUrl: env.fireBaseUrl,
11451
+ missedRunPolicy: env.missedRunPolicy,
11452
+ maxCatchUpPerTick: env.maxCatchUpPerTick,
11453
+ maxRetries: env.maxRetries
11454
+ },
11455
+ jobCount: jobs.length,
11456
+ stateJobCount: Object.keys(state.jobs).length,
11457
+ credentialsReady,
11458
+ daemonPrimary
11459
+ };
11460
+ }
11461
+
11462
+ // src/cron/cron-tick-cli.ts
11463
+ async function runCronStatusCli(json) {
11464
+ const report = await buildKynverCronStatusReport();
11465
+ if (json) {
11466
+ console.log(JSON.stringify(report, null, 2));
11467
+ return;
11468
+ }
11469
+ console.log(`Kynver Cron primary: ${report.primary}`);
11470
+ console.log(` store: ${report.env.storePath} (${report.jobCount} jobs)`);
11471
+ console.log(` tick state: ${report.env.statePath} (${report.stateJobCount} tracked)`);
11472
+ console.log(` tick enabled: ${report.env.tickEnabled}`);
11473
+ console.log(` fire base URL: ${report.env.fireBaseUrl ?? "(unset)"}`);
11474
+ console.log(` credentials ready: ${report.credentialsReady}`);
11475
+ console.log(` daemon-owned primary: ${report.daemonPrimary}`);
11476
+ }
11477
+ async function runCronTickCli(args) {
11478
+ const agentOsId = typeof args.agentOsId === "string" ? args.agentOsId : void 0;
11479
+ const result = await runKynverCronTick({
11480
+ agentOsIdFilter: agentOsId ?? null
11481
+ });
11482
+ if (args.json === true) {
11483
+ console.log(JSON.stringify(result, null, 2));
11484
+ return;
11485
+ }
11486
+ console.log(
11487
+ JSON.stringify({
11488
+ event: "kynver_cron_tick",
11489
+ ...result
11490
+ })
11491
+ );
11492
+ }
11493
+
10645
11494
  // src/cli.ts
10646
11495
  function isHelpFlag(arg) {
10647
11496
  return arg === "help" || arg === "--help" || arg === "-h";
@@ -10692,6 +11541,8 @@ function usage(code = 0) {
10692
11541
  " kynver doctor runtime-takeover",
10693
11542
  " kynver scheduler cutover-check [--json]",
10694
11543
  " kynver scheduler attest-cutover [--json]",
11544
+ " kynver cron status [--json]",
11545
+ " kynver cron tick [--agent-os-id AOS_ID] [--json]",
10695
11546
  " kynver board contract [--agent-os-id ID] [--base-url URL] [--since ISO] [--limit N]"
10696
11547
  ].join("\n")
10697
11548
  );
@@ -10703,7 +11554,7 @@ async function main(argv = process.argv.slice(2)) {
10703
11554
  const scope = argv.shift();
10704
11555
  let action;
10705
11556
  let rest;
10706
- if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "harness" || scope === "monitor" || scope === "doctor" || scope === "scheduler" || scope === "board") {
11557
+ if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "harness" || scope === "monitor" || scope === "doctor" || scope === "scheduler" || scope === "cron" || scope === "board") {
10707
11558
  action = argv.shift();
10708
11559
  rest = argv;
10709
11560
  } else {
@@ -10736,6 +11587,12 @@ async function main(argv = process.argv.slice(2)) {
10736
11587
  if (scope === "scheduler" && action === "attest-cutover") {
10737
11588
  return runSchedulerAttestCutoverCli(args.json === true);
10738
11589
  }
11590
+ if (scope === "cron" && action === "status") {
11591
+ return void await runCronStatusCli(args.json === true);
11592
+ }
11593
+ if (scope === "cron" && action === "tick") {
11594
+ return void await runCronTickCli(args);
11595
+ }
10739
11596
  if (scope === "board" && action === "contract") {
10740
11597
  return void await runCommandCenterContractCli(args);
10741
11598
  }