@kynver-app/runtime 0.1.103 → 0.1.105

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/cleanup-completion-blocker.d.ts +10 -0
  2. package/dist/cleanup-guards.d.ts +1 -6
  3. package/dist/cleanup-worktree-salvage.d.ts +7 -0
  4. package/dist/cli.js +166 -59
  5. package/dist/cli.js.map +4 -4
  6. package/dist/index.js +166 -59
  7. package/dist/index.js.map +4 -4
  8. package/dist/server/cleanup.d.ts +3 -0
  9. package/dist/server/cleanup.js +3511 -0
  10. package/dist/server/cleanup.js.map +7 -0
  11. package/dist/server/default-repo.d.ts +1 -0
  12. package/dist/server/default-repo.js +228 -0
  13. package/dist/server/default-repo.js.map +7 -0
  14. package/dist/server/harness-notice.d.ts +2 -0
  15. package/dist/server/harness-notice.js +287 -0
  16. package/dist/server/harness-notice.js.map +7 -0
  17. package/dist/server/heavy-verification.d.ts +2 -0
  18. package/dist/server/heavy-verification.js +223 -0
  19. package/dist/server/heavy-verification.js.map +7 -0
  20. package/dist/server/landing.d.ts +1 -0
  21. package/dist/server/landing.js +44 -0
  22. package/dist/server/landing.js.map +7 -0
  23. package/dist/server/memory-cost-enforce.d.ts +1 -0
  24. package/dist/server/memory-cost-enforce.js +470 -0
  25. package/dist/server/memory-cost-enforce.js.map +7 -0
  26. package/dist/server/memory-cost.d.ts +1 -0
  27. package/dist/server/memory-cost.js +184 -0
  28. package/dist/server/memory-cost.js.map +7 -0
  29. package/dist/server/monitor.d.ts +3 -0
  30. package/dist/server/monitor.js +1577 -0
  31. package/dist/server/monitor.js.map +7 -0
  32. package/dist/server/orchestration.d.ts +10 -0
  33. package/dist/server/orchestration.js +444 -0
  34. package/dist/server/orchestration.js.map +7 -0
  35. package/dist/server/pr-evidence.d.ts +2 -0
  36. package/dist/server/pr-evidence.js +163 -0
  37. package/dist/server/pr-evidence.js.map +7 -0
  38. package/dist/server/repo-search.d.ts +1 -0
  39. package/dist/server/repo-search.js +224 -0
  40. package/dist/server/repo-search.js.map +7 -0
  41. package/dist/server/worker-policy.d.ts +2 -0
  42. package/dist/server/worker-policy.js +177 -0
  43. package/dist/server/worker-policy.js.map +7 -0
  44. package/dist/status.d.ts +4 -0
  45. package/package.json +63 -3
package/dist/index.js CHANGED
@@ -2128,7 +2128,7 @@ var NO_START_MS = 18e4;
2128
2128
  var STALE_MS = 6e5;
2129
2129
  function computeAttention(input) {
2130
2130
  const now = Date.now();
2131
- if (input.completionBlocker) {
2131
+ if (input.completionBlocker && !isSkippedTerminalCompletionBlocker(input.completionBlocker)) {
2132
2132
  return { state: "blocked", reason: input.completionBlocker };
2133
2133
  }
2134
2134
  if (input.finalResult) {
@@ -2166,6 +2166,9 @@ function computeAttention(input) {
2166
2166
  return { state: "done", reason: "final result recorded" };
2167
2167
  }
2168
2168
  if (!input.alive) {
2169
+ if (isAbandonedEmptyWorker(input)) {
2170
+ return { state: "done", reason: "empty abandoned worker record" };
2171
+ }
2169
2172
  const classified = classifyExitFailure(input.error);
2170
2173
  if (classified) return { state: "blocked", reason: classified.reason };
2171
2174
  const salvage = assessExitedWorkerSalvage({
@@ -2200,6 +2203,19 @@ function computeAttention(input) {
2200
2203
  }
2201
2204
  return { state: "ok", reason: "recent activity" };
2202
2205
  }
2206
+ function isSkippedTerminalCompletionBlocker(reason) {
2207
+ const text = reason?.trim();
2208
+ if (!text) return false;
2209
+ return /completion acknowledged but board not advanced/i.test(text) && /task already terminal/i.test(text);
2210
+ }
2211
+ function isAbandonedEmptyWorker(input) {
2212
+ if (input.finalResult) return false;
2213
+ if (input.taskId || input.agentOsId) return false;
2214
+ if (input.stdoutBytes > 0 || (input.stderrBytes ?? 0) > 0 || input.heartbeatBytes > 0) return false;
2215
+ if (input.error?.trim()) return false;
2216
+ if ((input.changedFiles ?? []).some((line) => line.trim())) return false;
2217
+ return /empty worker dir|marked abandoned/i.test(input.reconcileReason ?? "");
2218
+ }
2203
2219
  function hasMergedTargetPrReconciliation(value) {
2204
2220
  let record3 = null;
2205
2221
  if (typeof value === "string") record3 = extractEmbeddedWorkerFinalResultRecord(value);
@@ -2244,6 +2260,7 @@ function computeWorkerStatus(worker, options = {}) {
2244
2260
  ]);
2245
2261
  const error = parsed.error || (!alive && !finalResult ? tailFile(worker.stderrPath, 10).trim() || void 0 : void 0);
2246
2262
  const completionBlocker = typeof worker.completionBlocker === "string" && worker.completionBlocker.trim() ? worker.completionBlocker.trim() : null;
2263
+ const effectiveCompletionBlocker = isSkippedTerminalCompletionBlocker(completionBlocker) ? null : completionBlocker;
2247
2264
  const landingContract = worker.repairTargetPrUrl ? {
2248
2265
  landingOnly: false,
2249
2266
  targetPrUrls: [worker.repairTargetPrUrl],
@@ -2255,6 +2272,7 @@ function computeWorkerStatus(worker, options = {}) {
2255
2272
  finalResult,
2256
2273
  firstEventAt: parsed.firstEventAt,
2257
2274
  stdoutBytes,
2275
+ stderrBytes,
2258
2276
  heartbeatBytes,
2259
2277
  lastActivityAt,
2260
2278
  heartbeatBlocker: heartbeat.heartbeatBlocker,
@@ -2262,12 +2280,15 @@ function computeWorkerStatus(worker, options = {}) {
2262
2280
  error,
2263
2281
  changedFiles,
2264
2282
  gitAncestry,
2265
- completionBlocker,
2283
+ completionBlocker: effectiveCompletionBlocker,
2266
2284
  landingContract,
2267
2285
  prUrl: worker.repairTargetPrUrl ?? worker.taskPrUrl ?? null,
2268
- localOnly: worker.localOnly === true
2286
+ localOnly: worker.localOnly === true,
2287
+ taskId: worker.taskId ?? null,
2288
+ agentOsId: worker.agentOsId ?? null,
2289
+ reconcileReason: worker.reconcileReason ?? null
2269
2290
  });
2270
- const workerStatusLabel = completionBlocker || attention.state === "blocked" ? "blocked" : completionAcknowledged || attention.state === "done" ? "done" : finalResult ? "exited" : alive ? "running" : "exited";
2291
+ const workerStatusLabel = effectiveCompletionBlocker || attention.state === "blocked" ? "blocked" : completionAcknowledged || attention.state === "done" ? "done" : finalResult ? "exited" : alive ? "running" : "exited";
2271
2292
  return {
2272
2293
  runId: worker.runId,
2273
2294
  worker: worker.name,
@@ -3203,6 +3224,9 @@ function enforceCursorWorkerProvider(input) {
3203
3224
  if (taskAllowsClaudeWorker(task)) {
3204
3225
  return routing;
3205
3226
  }
3227
+ if (routing.rule === "explicit:cli" && isClaudeFamilyProvider(routing.provider)) {
3228
+ return routing;
3229
+ }
3206
3230
  if (!isClaudeFamilyProvider(routing.provider)) {
3207
3231
  return routing;
3208
3232
  }
@@ -4256,7 +4280,7 @@ function resolveWorkerLaunch(input) {
4256
4280
  if (!input.task || Object.keys(input.task).length === 0) {
4257
4281
  return afterCursorPolicy;
4258
4282
  }
4259
- if (afterCursorPolicy.rule === "explicit:model_provider_alias" || afterCursorPolicy.rule === "explicit:model_provider_alias_overrode_provider") {
4283
+ if (afterCursorPolicy.rule === "explicit:model_provider_alias" || afterCursorPolicy.rule === "explicit:model_provider_alias_overrode_provider" || afterCursorPolicy.rule === "explicit:cli") {
4260
4284
  return afterCursorPolicy;
4261
4285
  }
4262
4286
  if (isClaudeFamilyProvider(afterCursorPolicy.provider) && (input.explicitProviderIsOperatorOverride || taskAllowsClaudeWorker(input.task))) {
@@ -4873,6 +4897,7 @@ function asString(value) {
4873
4897
  var ADVANCED_OUTCOMES = /* @__PURE__ */ new Set([
4874
4898
  "review_scheduled",
4875
4899
  "review_already_scheduled",
4900
+ "skipped_terminal_task",
4876
4901
  "closed",
4877
4902
  "dispatched",
4878
4903
  "dispatch_already_done"
@@ -5220,6 +5245,36 @@ function defaultPrBody(taskId, workerName, runId) {
5220
5245
  "Opened by orchestrator completion enforcement so production review receives a reviewable artifact."
5221
5246
  ].filter(Boolean).join("\n");
5222
5247
  }
5248
+ function commitDirtyToExistingPr(input) {
5249
+ if (input.snapshot.changedFiles.length === 0) {
5250
+ return {
5251
+ ok: true,
5252
+ prUrl: input.prUrl,
5253
+ headCommit: input.snapshot.headCommit ?? resolveHeadCommit(input.snapshot.worktreePath, input.exec) ?? void 0
5254
+ };
5255
+ }
5256
+ const pushResult = commitAndPushBranch({
5257
+ worktreePath: input.snapshot.worktreePath,
5258
+ branch: input.snapshot.branch,
5259
+ commitMessage: input.commitMessage,
5260
+ hasDirtyFiles: true,
5261
+ exec: input.exec
5262
+ });
5263
+ if (!pushResult.ok) {
5264
+ return {
5265
+ ok: false,
5266
+ reason: `PR-ready handoff blocked: ${pushResult.detail ?? "git commit/push failed"}`,
5267
+ nextAction: "Commit and push the dirty worker changes to the existing PR branch, then rerun `kynver worker complete`."
5268
+ };
5269
+ }
5270
+ return {
5271
+ ok: true,
5272
+ prUrl: input.prUrl,
5273
+ headCommit: pushResult.headCommit ?? input.snapshot.headCommit ?? void 0,
5274
+ committed: pushResult.committed,
5275
+ pushed: pushResult.pushed
5276
+ };
5277
+ }
5223
5278
  function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
5224
5279
  const prUrlHint = input.prUrlHint ?? extractPrUrlFromText(input.status.finalResult) ?? null;
5225
5280
  const snapshot = buildPrHandoffSnapshotFromStatus(input.status, {
@@ -5243,10 +5298,23 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
5243
5298
  snapshot
5244
5299
  });
5245
5300
  if (!requirement.required) {
5246
- return { ok: true, prUrl: prUrlHint ?? void 0 };
5301
+ if (prUrlHint) {
5302
+ return commitDirtyToExistingPr({
5303
+ snapshot,
5304
+ prUrl: prUrlHint,
5305
+ commitMessage: `chore(harness): update PR handoff for ${input.worker.name}`,
5306
+ exec
5307
+ });
5308
+ }
5309
+ return { ok: true };
5247
5310
  }
5248
5311
  if (prUrlHint) {
5249
- return { ok: true, prUrl: prUrlHint };
5312
+ return commitDirtyToExistingPr({
5313
+ snapshot,
5314
+ prUrl: prUrlHint,
5315
+ commitMessage: `chore(harness): update PR handoff for ${input.worker.name}`,
5316
+ exec
5317
+ });
5250
5318
  }
5251
5319
  if (!ghAvailable(exec)) {
5252
5320
  const dirty = snapshot.changedFiles.length;
@@ -5309,11 +5377,12 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
5309
5377
  }
5310
5378
  const existing = findOpenPrUrl(snapshot.worktreePath, repo, snapshot.branch, exec);
5311
5379
  if (existing) {
5312
- return {
5313
- ok: true,
5380
+ return commitDirtyToExistingPr({
5381
+ snapshot,
5314
5382
  prUrl: existing,
5315
- headCommit: snapshot.headCommit ?? resolveHeadCommit(snapshot.worktreePath, exec) ?? void 0
5316
- };
5383
+ commitMessage: `chore(harness): update PR handoff for ${input.worker.name}`,
5384
+ exec
5385
+ });
5317
5386
  }
5318
5387
  const hasDirty = snapshot.changedFiles.length > 0;
5319
5388
  let committed = false;
@@ -5723,6 +5792,11 @@ function deriveNextAction(input) {
5723
5792
  }
5724
5793
  return null;
5725
5794
  }
5795
+ function isSkippedTerminalCompletionBlocker2(reason) {
5796
+ const text = reason?.trim();
5797
+ if (!text) return false;
5798
+ return /completion acknowledged but board not advanced/i.test(text) && /task already terminal/i.test(text);
5799
+ }
5726
5800
  function deriveHandoffState(input) {
5727
5801
  if (input.prUrl) return "pr_handoff";
5728
5802
  if (input.headCommit) return "commit_handoff";
@@ -5779,6 +5853,23 @@ async function tryCompleteWorker(args) {
5779
5853
  if (!forceReplay && shouldReplayHarnessCompletion(worker)) {
5780
5854
  clearCompletionBlockerForReplay(worker);
5781
5855
  }
5856
+ const skipPrHandoff = args.skipPrHandoff === true || args.skipPrHandoff === "true";
5857
+ if (!skipPrHandoff && worker.dispatched && taskId) {
5858
+ const handoff = ensurePrReadyHandoff({ worker, run, status });
5859
+ if (!handoff.ok) {
5860
+ persistCompletionBlocker(worker, handoff.reason);
5861
+ return {
5862
+ ok: false,
5863
+ reason: handoff.reason,
5864
+ nextAction: handoff.nextAction,
5865
+ completionBlocked: true
5866
+ };
5867
+ }
5868
+ if (handoff.prUrl || handoff.headCommit) {
5869
+ status = computeWorkerStatus(worker, workerStatusOptions(run));
5870
+ status = applyPrHandoffToStatus(status, handoff);
5871
+ }
5872
+ }
5782
5873
  const headCommit = status.gitAncestry.headIsAncestorOfBase === false && status.gitAncestry.head ? status.gitAncestry.head : status.headCommit;
5783
5874
  if (worker.dispatched) {
5784
5875
  const handoff = assessWorktreeCompletionHandoff({
@@ -5799,22 +5890,6 @@ async function tryCompleteWorker(args) {
5799
5890
  };
5800
5891
  }
5801
5892
  }
5802
- const skipPrHandoff = args.skipPrHandoff === true || args.skipPrHandoff === "true";
5803
- if (!skipPrHandoff && worker.dispatched && taskId) {
5804
- const handoff = ensurePrReadyHandoff({ worker, run, status });
5805
- if (!handoff.ok) {
5806
- persistCompletionBlocker(worker, handoff.reason);
5807
- return {
5808
- ok: false,
5809
- reason: handoff.reason,
5810
- nextAction: handoff.nextAction,
5811
- completionBlocked: true
5812
- };
5813
- }
5814
- if (handoff.prUrl || handoff.headCommit) {
5815
- status = applyPrHandoffToStatus(status, handoff);
5816
- }
5817
- }
5818
5893
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
5819
5894
  const explicitSecret = args.secret ? String(args.secret) : void 0;
5820
5895
  let secret = await resolveCallbackSecretWithMint(explicitSecret, agentOsId, { baseUrl: base });
@@ -6020,7 +6095,8 @@ function buildWorkerBoardEntry(input) {
6020
6095
  headCommit
6021
6096
  });
6022
6097
  const rawBlocker = worker.completionBlocker;
6023
- const completionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
6098
+ const rawCompletionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
6099
+ const completionBlocker = isSkippedTerminalCompletionBlocker2(rawCompletionBlocker) ? void 0 : rawCompletionBlocker;
6024
6100
  const boardStatus = completionBlocker ? "blocked" : status.status;
6025
6101
  const boardAttention = completionBlocker ? "blocked" : status.attention.state;
6026
6102
  const completionResponse = asRecord3(worker.completionResponse);
@@ -6105,7 +6181,8 @@ function buildWorkerBoardEntry(input) {
6105
6181
  }
6106
6182
  function isMetadataTerminalDone(worker) {
6107
6183
  const status = typeof worker.status === "string" ? worker.status : "";
6108
- const completionBlocker = typeof worker.completionBlocker === "string" && worker.completionBlocker.trim().length > 0;
6184
+ const rawCompletionBlocker = typeof worker.completionBlocker === "string" && worker.completionBlocker.trim().length > 0 ? worker.completionBlocker : void 0;
6185
+ const completionBlocker = rawCompletionBlocker && !isSkippedTerminalCompletionBlocker2(rawCompletionBlocker);
6109
6186
  if (completionBlocker) return false;
6110
6187
  if (typeof worker.completionReportedAt === "string" && worker.completionReportedAt.trim()) return true;
6111
6188
  if (worker.completionOutcome === "acknowledged") return true;
@@ -9724,30 +9801,7 @@ function indexedWorktreeHasMaterialChanges(entry) {
9724
9801
  return materialWorktreeChanges2(gitStatusShort(entry.worktreePath)).length > 0;
9725
9802
  }
9726
9803
 
9727
- // src/cleanup-run-liveness.ts
9728
- function deriveRunTerminal(indexed, ctx) {
9729
- if (ctx) return ctx.runTerminalCache.derive(indexed.run);
9730
- return deriveTerminalRunStatus(indexed.run);
9731
- }
9732
- function isWorkerProcessLive(indexed) {
9733
- if (isPidAlive(indexed.worker.pid)) return true;
9734
- if (!indexed.worker.pid) return indexedWorktreeStatus(indexed).alive;
9735
- return false;
9736
- }
9737
- function isRunStaleActive(indexed, ctx) {
9738
- if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
9739
- return deriveRunTerminal(indexed, ctx) !== null;
9740
- }
9741
- function runBlocksWorktreeRemoval(indexed, ctx) {
9742
- if (isWorkerProcessLive(indexed)) return true;
9743
- if (indexed.worker.completionBlocker) return true;
9744
- if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
9745
- if (isRunStaleActive(indexed, ctx)) return false;
9746
- if (!isFinishedWorkerStatus(indexedWorktreeStatus(indexed))) return true;
9747
- return deriveRunTerminal(indexed, ctx) === null;
9748
- }
9749
-
9750
- // src/cleanup-guards.ts
9804
+ // src/cleanup-worktree-salvage.ts
9751
9805
  function prUrlFromFinalResult(finalResult) {
9752
9806
  if (typeof finalResult === "string") {
9753
9807
  const match = finalResult.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/i);
@@ -9772,12 +9826,63 @@ function isPrOrUnmergedWork(status) {
9772
9826
  if (status.changedFiles.length > 0 && status.finalResult) return true;
9773
9827
  return false;
9774
9828
  }
9829
+
9830
+ // src/cleanup-completion-blocker.ts
9831
+ function completionBlockerBlocksWorktreeRemoval(indexed, status) {
9832
+ const blocker = typeof indexed.worker.completionBlocker === "string" ? indexed.worker.completionBlocker.trim() : "";
9833
+ if (!blocker) return false;
9834
+ if (isWorkerProcessLive(indexed)) return true;
9835
+ const resolved = status ?? indexedWorktreeStatus(indexed);
9836
+ if (!isFinishedWorkerStatus(resolved)) return true;
9837
+ if (isPrOrUnmergedWork(resolved)) return true;
9838
+ if (materialWorktreeChanges2(resolved.changedFiles).length > 0) return true;
9839
+ const landing = assessWorkerLanding({
9840
+ finalResult: resolved.finalResult,
9841
+ changedFiles: resolved.changedFiles,
9842
+ gitAncestry: resolved.gitAncestry,
9843
+ prUrl: prUrlFromFinalResult(resolved.finalResult)
9844
+ });
9845
+ if (landing.blocked) return true;
9846
+ return false;
9847
+ }
9848
+
9849
+ // src/cleanup-run-liveness.ts
9850
+ function deriveRunTerminal(indexed, ctx) {
9851
+ if (ctx) return ctx.runTerminalCache.derive(indexed.run);
9852
+ return deriveTerminalRunStatus(indexed.run);
9853
+ }
9854
+ function isWorkerProcessLive(indexed) {
9855
+ if (isPidAlive(indexed.worker.pid)) return true;
9856
+ if (!indexed.worker.pid) return indexedWorktreeStatus(indexed).alive;
9857
+ return false;
9858
+ }
9859
+ function isRunStaleActive(indexed, ctx) {
9860
+ if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
9861
+ return deriveRunTerminal(indexed, ctx) !== null;
9862
+ }
9863
+ function runBlocksWorktreeRemoval(indexed, ctx) {
9864
+ if (isWorkerProcessLive(indexed)) return true;
9865
+ const status = indexedWorktreeStatus(indexed);
9866
+ if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return true;
9867
+ if (isFinishedWorkerStatus(status)) return false;
9868
+ if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
9869
+ if (isRunStaleActive(indexed, ctx)) return false;
9870
+ return deriveRunTerminal(indexed, ctx) === null;
9871
+ }
9872
+
9873
+ // src/cleanup-guards.ts
9775
9874
  function effectiveWorktreeAgeMs(input) {
9776
9875
  const { indexed, includeOrphans, worktreesAgeMs, terminalWorktreesAgeMs } = input;
9777
9876
  if (!indexed) return includeOrphans ? terminalWorktreesAgeMs : worktreesAgeMs;
9778
9877
  if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) {
9779
9878
  return terminalWorktreesAgeMs;
9780
9879
  }
9880
+ if (input.liveness && isRunStaleActive(indexed, input.liveness)) {
9881
+ return terminalWorktreesAgeMs;
9882
+ }
9883
+ if (input.liveness && isFinishedWorkerStatus(indexedWorktreeStatus(indexed)) && !isWorkerProcessLive(indexed)) {
9884
+ return terminalWorktreesAgeMs;
9885
+ }
9781
9886
  return worktreesAgeMs;
9782
9887
  }
9783
9888
  function skipWorktreeRemoval(input) {
@@ -9791,7 +9896,7 @@ function skipWorktreeRemoval(input) {
9791
9896
  if (ageThresholdMs > 0 && ageMs < ageThresholdMs) return "below_age_threshold";
9792
9897
  const status = indexedWorktreeStatus(indexed);
9793
9898
  if (isWorkerProcessLive(indexed)) return "active_worker";
9794
- if (indexed.worker.completionBlocker) return "completion_blocked";
9899
+ if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return "completion_blocked";
9795
9900
  if (runBlocksWorktreeRemoval(indexed, input.liveness)) return "run_still_active";
9796
9901
  if (!isFinishedWorkerStatus(status)) return "run_still_active";
9797
9902
  if (isPrOrUnmergedWork(status)) return "pr_or_unmerged_commits";
@@ -9947,7 +10052,9 @@ function skipRunDirectoryRemoval(input) {
9947
10052
  }
9948
10053
  if (!runDirectoryIsEmpty(runPath)) return "run_still_active";
9949
10054
  const run = loadRunStatus(harnessRoot, runId);
9950
- if (run && !TERMINAL_RUN_STATUSES.has(run.status)) return "run_still_active";
10055
+ if (run && !TERMINAL_RUN_STATUSES.has(run.status)) {
10056
+ if (!deriveTerminalRunStatus(run)) return "run_still_active";
10057
+ }
9951
10058
  if (runDirectoriesAgeMs > 0 && ageMs < runDirectoriesAgeMs) return "below_age_threshold";
9952
10059
  return null;
9953
10060
  }
@@ -10912,14 +11019,14 @@ function runHarnessCleanup(options = {}) {
10912
11019
  const paths = resolvePaths(options);
10913
11020
  emitCleanupProgress("scan", `${paths.scanRoots.length} harness root(s)`);
10914
11021
  const activeGuards = collectActiveWorktreeGuards(paths.scanRoots, paths.now);
10915
- emitCleanupProgress("index", "building worktree index");
10916
- const index = mergeWorktreeIndexes(paths.scanRoots);
10917
- emitCleanupProgress("index", `${index.size} indexed worktree(s)`);
10918
- const liveness = { runTerminalCache: new CleanupRunTerminalCache() };
10919
11022
  const finalizedRuns = retention.finalizeStaleRuns ? finalizeStaleRuns().map((f) => ({ runId: f.runId, from: f.from, to: f.to })) : [];
10920
11023
  if (finalizedRuns.length > 0) {
10921
11024
  emitCleanupProgress("finalize", `${finalizedRuns.length} stale run(s) marked terminal`);
10922
11025
  }
11026
+ emitCleanupProgress("index", "building worktree index");
11027
+ const index = mergeWorktreeIndexes(paths.scanRoots);
11028
+ emitCleanupProgress("index", `${index.size} indexed worktree(s)`);
11029
+ const liveness = { runTerminalCache: new CleanupRunTerminalCache() };
10923
11030
  const skips = [];
10924
11031
  const actions = [];
10925
11032
  const processedPaths = /* @__PURE__ */ new Set();