@kynver-app/runtime 0.1.102 → 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 +181 -64
  5. package/dist/cli.js.map +4 -4
  6. package/dist/index.js +181 -64
  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
@@ -0,0 +1,10 @@
1
+ import type { IndexedWorktree } from "./cleanup-worktree-index.js";
2
+ import type { RawHarnessWorkerStatus } from "./status.js";
3
+ /**
4
+ * Whether a persisted `completionBlocker` should still block whole-worktree removal.
5
+ *
6
+ * Dead workers with landed/clean work may keep replay metadata on disk after the
7
+ * board advanced externally — those blockers are stale for GC and must not pin
8
+ * worktrees indefinitely.
9
+ */
10
+ export declare function completionBlockerBlocksWorktreeRemoval(indexed: IndexedWorktree, status?: RawHarnessWorkerStatus): boolean;
@@ -1,13 +1,8 @@
1
1
  import type { CleanupSkipReason, WorktreeRemovalGuardHook } from "./cleanup-types.js";
2
2
  import type { IndexedWorktree } from "./cleanup-worktree-index.js";
3
3
  import type { CleanupRunLivenessContext } from "./cleanup-run-liveness.js";
4
- import type { GitAncestry } from "./git.js";
5
- import type { RawHarnessWorkerStatus } from "./status.js";
6
4
  export { materialWorktreeChanges } from "./cleanup-guards-helpers.js";
7
- /** True when git ancestry shows the worker branch is fully landed on the run base. */
8
- export declare function isLandedGitAncestry(ancestry: GitAncestry | null | undefined): boolean;
9
- /** Blocks whole-worktree removal when commits are not landed or tree is dirty. */
10
- export declare function isPrOrUnmergedWork(status: RawHarnessWorkerStatus): boolean;
5
+ export { isLandedGitAncestry, isPrOrUnmergedWork } from "./cleanup-worktree-salvage.js";
11
6
  export interface WorktreeGuardInput {
12
7
  indexed: IndexedWorktree | null;
13
8
  /** Resolved worktree directory (required for overlay guards on orphans). */
@@ -0,0 +1,7 @@
1
+ import type { GitAncestry } from "./git.js";
2
+ import type { RawHarnessWorkerStatus } from "./status.js";
3
+ export declare function prUrlFromFinalResult(finalResult: unknown): string | null;
4
+ /** True when git ancestry shows the worker branch is fully landed on the run base. */
5
+ export declare function isLandedGitAncestry(ancestry: GitAncestry | null | undefined): boolean;
6
+ /** Blocks whole-worktree removal when commits are not landed or tree is dirty. */
7
+ export declare function isPrOrUnmergedWork(status: RawHarnessWorkerStatus): boolean;
package/dist/cli.js CHANGED
@@ -1783,7 +1783,7 @@ var NO_START_MS = 18e4;
1783
1783
  var STALE_MS = 6e5;
1784
1784
  function computeAttention(input) {
1785
1785
  const now = Date.now();
1786
- if (input.completionBlocker) {
1786
+ if (input.completionBlocker && !isSkippedTerminalCompletionBlocker(input.completionBlocker)) {
1787
1787
  return { state: "blocked", reason: input.completionBlocker };
1788
1788
  }
1789
1789
  if (input.finalResult) {
@@ -1821,6 +1821,9 @@ function computeAttention(input) {
1821
1821
  return { state: "done", reason: "final result recorded" };
1822
1822
  }
1823
1823
  if (!input.alive) {
1824
+ if (isAbandonedEmptyWorker(input)) {
1825
+ return { state: "done", reason: "empty abandoned worker record" };
1826
+ }
1824
1827
  const classified = classifyExitFailure(input.error);
1825
1828
  if (classified) return { state: "blocked", reason: classified.reason };
1826
1829
  const salvage = assessExitedWorkerSalvage({
@@ -1855,6 +1858,19 @@ function computeAttention(input) {
1855
1858
  }
1856
1859
  return { state: "ok", reason: "recent activity" };
1857
1860
  }
1861
+ function isSkippedTerminalCompletionBlocker(reason) {
1862
+ const text = reason?.trim();
1863
+ if (!text) return false;
1864
+ return /completion acknowledged but board not advanced/i.test(text) && /task already terminal/i.test(text);
1865
+ }
1866
+ function isAbandonedEmptyWorker(input) {
1867
+ if (input.finalResult) return false;
1868
+ if (input.taskId || input.agentOsId) return false;
1869
+ if (input.stdoutBytes > 0 || (input.stderrBytes ?? 0) > 0 || input.heartbeatBytes > 0) return false;
1870
+ if (input.error?.trim()) return false;
1871
+ if ((input.changedFiles ?? []).some((line) => line.trim())) return false;
1872
+ return /empty worker dir|marked abandoned/i.test(input.reconcileReason ?? "");
1873
+ }
1858
1874
  function hasMergedTargetPrReconciliation(value) {
1859
1875
  let record = null;
1860
1876
  if (typeof value === "string") record = extractEmbeddedWorkerFinalResultRecord(value);
@@ -1899,6 +1915,7 @@ function computeWorkerStatus(worker, options = {}) {
1899
1915
  ]);
1900
1916
  const error = parsed.error || (!alive && !finalResult ? tailFile(worker.stderrPath, 10).trim() || void 0 : void 0);
1901
1917
  const completionBlocker = typeof worker.completionBlocker === "string" && worker.completionBlocker.trim() ? worker.completionBlocker.trim() : null;
1918
+ const effectiveCompletionBlocker = isSkippedTerminalCompletionBlocker(completionBlocker) ? null : completionBlocker;
1902
1919
  const landingContract = worker.repairTargetPrUrl ? {
1903
1920
  landingOnly: false,
1904
1921
  targetPrUrls: [worker.repairTargetPrUrl],
@@ -1910,6 +1927,7 @@ function computeWorkerStatus(worker, options = {}) {
1910
1927
  finalResult,
1911
1928
  firstEventAt: parsed.firstEventAt,
1912
1929
  stdoutBytes,
1930
+ stderrBytes,
1913
1931
  heartbeatBytes,
1914
1932
  lastActivityAt,
1915
1933
  heartbeatBlocker: heartbeat.heartbeatBlocker,
@@ -1917,12 +1935,15 @@ function computeWorkerStatus(worker, options = {}) {
1917
1935
  error,
1918
1936
  changedFiles,
1919
1937
  gitAncestry,
1920
- completionBlocker,
1938
+ completionBlocker: effectiveCompletionBlocker,
1921
1939
  landingContract,
1922
1940
  prUrl: worker.repairTargetPrUrl ?? worker.taskPrUrl ?? null,
1923
- localOnly: worker.localOnly === true
1941
+ localOnly: worker.localOnly === true,
1942
+ taskId: worker.taskId ?? null,
1943
+ agentOsId: worker.agentOsId ?? null,
1944
+ reconcileReason: worker.reconcileReason ?? null
1924
1945
  });
1925
- const workerStatusLabel = completionBlocker || attention.state === "blocked" ? "blocked" : completionAcknowledged || attention.state === "done" ? "done" : finalResult ? "exited" : alive ? "running" : "exited";
1946
+ const workerStatusLabel = effectiveCompletionBlocker || attention.state === "blocked" ? "blocked" : completionAcknowledged || attention.state === "done" ? "done" : finalResult ? "exited" : alive ? "running" : "exited";
1926
1947
  return {
1927
1948
  runId: worker.runId,
1928
1949
  worker: worker.name,
@@ -2839,6 +2860,9 @@ function enforceCursorWorkerProvider(input) {
2839
2860
  if (taskAllowsClaudeWorker(task)) {
2840
2861
  return routing;
2841
2862
  }
2863
+ if (routing.rule === "explicit:cli" && isClaudeFamilyProvider(routing.provider)) {
2864
+ return routing;
2865
+ }
2842
2866
  if (!isClaudeFamilyProvider(routing.provider)) {
2843
2867
  return routing;
2844
2868
  }
@@ -3872,7 +3896,7 @@ function resolveWorkerLaunch(input) {
3872
3896
  if (!input.task || Object.keys(input.task).length === 0) {
3873
3897
  return afterCursorPolicy;
3874
3898
  }
3875
- if (afterCursorPolicy.rule === "explicit:model_provider_alias" || afterCursorPolicy.rule === "explicit:model_provider_alias_overrode_provider") {
3899
+ if (afterCursorPolicy.rule === "explicit:model_provider_alias" || afterCursorPolicy.rule === "explicit:model_provider_alias_overrode_provider" || afterCursorPolicy.rule === "explicit:cli") {
3876
3900
  return afterCursorPolicy;
3877
3901
  }
3878
3902
  if (isClaudeFamilyProvider(afterCursorPolicy.provider) && (input.explicitProviderIsOperatorOverride || taskAllowsClaudeWorker(input.task))) {
@@ -4486,6 +4510,7 @@ function asString(value) {
4486
4510
  var ADVANCED_OUTCOMES = /* @__PURE__ */ new Set([
4487
4511
  "review_scheduled",
4488
4512
  "review_already_scheduled",
4513
+ "skipped_terminal_task",
4489
4514
  "closed",
4490
4515
  "dispatched",
4491
4516
  "dispatch_already_done"
@@ -4833,6 +4858,36 @@ function defaultPrBody(taskId, workerName, runId) {
4833
4858
  "Opened by orchestrator completion enforcement so production review receives a reviewable artifact."
4834
4859
  ].filter(Boolean).join("\n");
4835
4860
  }
4861
+ function commitDirtyToExistingPr(input) {
4862
+ if (input.snapshot.changedFiles.length === 0) {
4863
+ return {
4864
+ ok: true,
4865
+ prUrl: input.prUrl,
4866
+ headCommit: input.snapshot.headCommit ?? resolveHeadCommit(input.snapshot.worktreePath, input.exec) ?? void 0
4867
+ };
4868
+ }
4869
+ const pushResult = commitAndPushBranch({
4870
+ worktreePath: input.snapshot.worktreePath,
4871
+ branch: input.snapshot.branch,
4872
+ commitMessage: input.commitMessage,
4873
+ hasDirtyFiles: true,
4874
+ exec: input.exec
4875
+ });
4876
+ if (!pushResult.ok) {
4877
+ return {
4878
+ ok: false,
4879
+ reason: `PR-ready handoff blocked: ${pushResult.detail ?? "git commit/push failed"}`,
4880
+ nextAction: "Commit and push the dirty worker changes to the existing PR branch, then rerun `kynver worker complete`."
4881
+ };
4882
+ }
4883
+ return {
4884
+ ok: true,
4885
+ prUrl: input.prUrl,
4886
+ headCommit: pushResult.headCommit ?? input.snapshot.headCommit ?? void 0,
4887
+ committed: pushResult.committed,
4888
+ pushed: pushResult.pushed
4889
+ };
4890
+ }
4836
4891
  function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
4837
4892
  const prUrlHint = input.prUrlHint ?? extractPrUrlFromText(input.status.finalResult) ?? null;
4838
4893
  const snapshot = buildPrHandoffSnapshotFromStatus(input.status, {
@@ -4856,10 +4911,23 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
4856
4911
  snapshot
4857
4912
  });
4858
4913
  if (!requirement.required) {
4859
- return { ok: true, prUrl: prUrlHint ?? void 0 };
4914
+ if (prUrlHint) {
4915
+ return commitDirtyToExistingPr({
4916
+ snapshot,
4917
+ prUrl: prUrlHint,
4918
+ commitMessage: `chore(harness): update PR handoff for ${input.worker.name}`,
4919
+ exec
4920
+ });
4921
+ }
4922
+ return { ok: true };
4860
4923
  }
4861
4924
  if (prUrlHint) {
4862
- return { ok: true, prUrl: prUrlHint };
4925
+ return commitDirtyToExistingPr({
4926
+ snapshot,
4927
+ prUrl: prUrlHint,
4928
+ commitMessage: `chore(harness): update PR handoff for ${input.worker.name}`,
4929
+ exec
4930
+ });
4863
4931
  }
4864
4932
  if (!ghAvailable(exec)) {
4865
4933
  const dirty = snapshot.changedFiles.length;
@@ -4922,11 +4990,12 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
4922
4990
  }
4923
4991
  const existing = findOpenPrUrl(snapshot.worktreePath, repo, snapshot.branch, exec);
4924
4992
  if (existing) {
4925
- return {
4926
- ok: true,
4993
+ return commitDirtyToExistingPr({
4994
+ snapshot,
4927
4995
  prUrl: existing,
4928
- headCommit: snapshot.headCommit ?? resolveHeadCommit(snapshot.worktreePath, exec) ?? void 0
4929
- };
4996
+ commitMessage: `chore(harness): update PR handoff for ${input.worker.name}`,
4997
+ exec
4998
+ });
4930
4999
  }
4931
5000
  const hasDirty = snapshot.changedFiles.length > 0;
4932
5001
  let committed = false;
@@ -5322,6 +5391,11 @@ function deriveNextAction(input) {
5322
5391
  }
5323
5392
  return null;
5324
5393
  }
5394
+ function isSkippedTerminalCompletionBlocker2(reason) {
5395
+ const text = reason?.trim();
5396
+ if (!text) return false;
5397
+ return /completion acknowledged but board not advanced/i.test(text) && /task already terminal/i.test(text);
5398
+ }
5325
5399
  function deriveHandoffState(input) {
5326
5400
  if (input.prUrl) return "pr_handoff";
5327
5401
  if (input.headCommit) return "commit_handoff";
@@ -5378,6 +5452,23 @@ async function tryCompleteWorker(args) {
5378
5452
  if (!forceReplay && shouldReplayHarnessCompletion(worker)) {
5379
5453
  clearCompletionBlockerForReplay(worker);
5380
5454
  }
5455
+ const skipPrHandoff = args.skipPrHandoff === true || args.skipPrHandoff === "true";
5456
+ if (!skipPrHandoff && worker.dispatched && taskId) {
5457
+ const handoff = ensurePrReadyHandoff({ worker, run, status });
5458
+ if (!handoff.ok) {
5459
+ persistCompletionBlocker(worker, handoff.reason);
5460
+ return {
5461
+ ok: false,
5462
+ reason: handoff.reason,
5463
+ nextAction: handoff.nextAction,
5464
+ completionBlocked: true
5465
+ };
5466
+ }
5467
+ if (handoff.prUrl || handoff.headCommit) {
5468
+ status = computeWorkerStatus(worker, workerStatusOptions(run));
5469
+ status = applyPrHandoffToStatus(status, handoff);
5470
+ }
5471
+ }
5381
5472
  const headCommit = status.gitAncestry.headIsAncestorOfBase === false && status.gitAncestry.head ? status.gitAncestry.head : status.headCommit;
5382
5473
  if (worker.dispatched) {
5383
5474
  const handoff = assessWorktreeCompletionHandoff({
@@ -5398,22 +5489,6 @@ async function tryCompleteWorker(args) {
5398
5489
  };
5399
5490
  }
5400
5491
  }
5401
- const skipPrHandoff = args.skipPrHandoff === true || args.skipPrHandoff === "true";
5402
- if (!skipPrHandoff && worker.dispatched && taskId) {
5403
- const handoff = ensurePrReadyHandoff({ worker, run, status });
5404
- if (!handoff.ok) {
5405
- persistCompletionBlocker(worker, handoff.reason);
5406
- return {
5407
- ok: false,
5408
- reason: handoff.reason,
5409
- nextAction: handoff.nextAction,
5410
- completionBlocked: true
5411
- };
5412
- }
5413
- if (handoff.prUrl || handoff.headCommit) {
5414
- status = applyPrHandoffToStatus(status, handoff);
5415
- }
5416
- }
5417
5492
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
5418
5493
  const explicitSecret = args.secret ? String(args.secret) : void 0;
5419
5494
  let secret = await resolveCallbackSecretWithMint(explicitSecret, agentOsId, { baseUrl: base });
@@ -5619,7 +5694,8 @@ function buildWorkerBoardEntry(input) {
5619
5694
  headCommit
5620
5695
  });
5621
5696
  const rawBlocker = worker.completionBlocker;
5622
- const completionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
5697
+ const rawCompletionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
5698
+ const completionBlocker = isSkippedTerminalCompletionBlocker2(rawCompletionBlocker) ? void 0 : rawCompletionBlocker;
5623
5699
  const boardStatus = completionBlocker ? "blocked" : status.status;
5624
5700
  const boardAttention = completionBlocker ? "blocked" : status.attention.state;
5625
5701
  const completionResponse = asRecord3(worker.completionResponse);
@@ -5704,7 +5780,8 @@ function buildWorkerBoardEntry(input) {
5704
5780
  }
5705
5781
  function isMetadataTerminalDone(worker) {
5706
5782
  const status = typeof worker.status === "string" ? worker.status : "";
5707
- const completionBlocker = typeof worker.completionBlocker === "string" && worker.completionBlocker.trim().length > 0;
5783
+ const rawCompletionBlocker = typeof worker.completionBlocker === "string" && worker.completionBlocker.trim().length > 0 ? worker.completionBlocker : void 0;
5784
+ const completionBlocker = rawCompletionBlocker && !isSkippedTerminalCompletionBlocker2(rawCompletionBlocker);
5708
5785
  if (completionBlocker) return false;
5709
5786
  if (typeof worker.completionReportedAt === "string" && worker.completionReportedAt.trim()) return true;
5710
5787
  if (worker.completionOutcome === "acknowledged") return true;
@@ -6966,6 +7043,11 @@ function readAdmissionExhaustion(result) {
6966
7043
  if (!raw || typeof raw !== "object") return null;
6967
7044
  return raw;
6968
7045
  }
7046
+ function readDispatchSkipDrain(result) {
7047
+ const raw = result.dispatchSkipDrain;
7048
+ if (!raw || typeof raw !== "object") return null;
7049
+ return raw;
7050
+ }
6969
7051
  function readHarnessWorkerContext(decision) {
6970
7052
  const raw = decision.harnessWorkerContext;
6971
7053
  if (!raw || typeof raw !== "object") return null;
@@ -7129,6 +7211,7 @@ async function dispatchRun(args) {
7129
7211
  const result = first.result;
7130
7212
  if (dryRun) {
7131
7213
  const admissionExhaustion2 = readAdmissionExhaustion(result);
7214
+ const dispatchSkipDrain2 = readDispatchSkipDrain(result);
7132
7215
  const summary2 = {
7133
7216
  runId: run.id,
7134
7217
  agentOsId,
@@ -7147,7 +7230,8 @@ async function dispatchRun(args) {
7147
7230
  pagesScanned: result.pagesScanned ?? null,
7148
7231
  candidatesExhausted: result.candidatesExhausted ?? null,
7149
7232
  capacityIdle: admissionExhaustion2?.capacityIdle === true,
7150
- admissionExhaustion: admissionExhaustion2
7233
+ admissionExhaustion: admissionExhaustion2,
7234
+ dispatchSkipDrain: dispatchSkipDrain2
7151
7235
  };
7152
7236
  if (pipeline) return { ok: true, ...summary2 };
7153
7237
  console.log(JSON.stringify(summary2, null, 2));
@@ -7337,13 +7421,15 @@ async function dispatchRun(args) {
7337
7421
  }
7338
7422
  const startedCount = outcomes.filter((o) => o.started).length;
7339
7423
  const admissionExhaustion = readAdmissionExhaustion(result);
7340
- const capacityIdle = admissionExhaustion?.capacityIdle === true || startedCount === 0 && Number(result.resourceGate?.slotsAvailable) > 0;
7424
+ const dispatchSkipDrain = readDispatchSkipDrain(result);
7425
+ const capacityIdle = startedCount === 0 && (admissionExhaustion?.capacityIdle === true || Number(result.resourceGate?.slotsAvailable) > 0);
7341
7426
  if (capacityIdle && admissionExhaustion?.summary) {
7342
7427
  const retryCeiling = admissionExhaustion.skipReasonCounts?.retry_ceiling_exceeded ?? 0;
7343
- const recovery = admissionExhaustion.overAttemptIdleRecovery;
7428
+ const recovery = result.overAttemptIdleRecovery ?? admissionExhaustion.overAttemptIdleRecovery;
7344
7429
  const recoveryNote = recovery?.attempted === true ? `; over_attempt_recovery minted=${recovery.minted ?? 0} started=${recovery.started ?? 0}` : retryCeiling > 0 ? "; over_attempt_recovery not attempted" : "";
7430
+ const drainNote = dispatchSkipDrain ? `; dispatch_skip_drain scanned=${dispatchSkipDrain.scanned ?? 0} advanced=${dispatchSkipDrain.advanced ?? 0}` : "";
7345
7431
  console.error(
7346
- `[dispatch] ${admissionExhaustion.summary}${retryCeiling > 0 ? `; retry_ceiling_exceeded=${retryCeiling}` : ""}${recoveryNote}`
7432
+ `[dispatch] ${admissionExhaustion.summary}${retryCeiling > 0 ? `; retry_ceiling_exceeded=${retryCeiling}` : ""}${recoveryNote}${drainNote}`
7347
7433
  );
7348
7434
  }
7349
7435
  const summary = {
@@ -7354,6 +7440,7 @@ async function dispatchRun(args) {
7354
7440
  startedCount,
7355
7441
  capacityIdle,
7356
7442
  admissionExhaustion,
7443
+ dispatchSkipDrain,
7357
7444
  outcomes,
7358
7445
  skipped: skipped.map((d) => ({
7359
7446
  taskId: d.task.id,
@@ -9354,30 +9441,7 @@ function indexedWorktreeHasMaterialChanges(entry) {
9354
9441
  return materialWorktreeChanges2(gitStatusShort(entry.worktreePath)).length > 0;
9355
9442
  }
9356
9443
 
9357
- // src/cleanup-run-liveness.ts
9358
- function deriveRunTerminal(indexed, ctx) {
9359
- if (ctx) return ctx.runTerminalCache.derive(indexed.run);
9360
- return deriveTerminalRunStatus(indexed.run);
9361
- }
9362
- function isWorkerProcessLive(indexed) {
9363
- if (isPidAlive(indexed.worker.pid)) return true;
9364
- if (!indexed.worker.pid) return indexedWorktreeStatus(indexed).alive;
9365
- return false;
9366
- }
9367
- function isRunStaleActive(indexed, ctx) {
9368
- if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
9369
- return deriveRunTerminal(indexed, ctx) !== null;
9370
- }
9371
- function runBlocksWorktreeRemoval(indexed, ctx) {
9372
- if (isWorkerProcessLive(indexed)) return true;
9373
- if (indexed.worker.completionBlocker) return true;
9374
- if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
9375
- if (isRunStaleActive(indexed, ctx)) return false;
9376
- if (!isFinishedWorkerStatus(indexedWorktreeStatus(indexed))) return true;
9377
- return deriveRunTerminal(indexed, ctx) === null;
9378
- }
9379
-
9380
- // src/cleanup-guards.ts
9444
+ // src/cleanup-worktree-salvage.ts
9381
9445
  function prUrlFromFinalResult(finalResult) {
9382
9446
  if (typeof finalResult === "string") {
9383
9447
  const match = finalResult.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/i);
@@ -9402,12 +9466,63 @@ function isPrOrUnmergedWork(status) {
9402
9466
  if (status.changedFiles.length > 0 && status.finalResult) return true;
9403
9467
  return false;
9404
9468
  }
9469
+
9470
+ // src/cleanup-completion-blocker.ts
9471
+ function completionBlockerBlocksWorktreeRemoval(indexed, status) {
9472
+ const blocker = typeof indexed.worker.completionBlocker === "string" ? indexed.worker.completionBlocker.trim() : "";
9473
+ if (!blocker) return false;
9474
+ if (isWorkerProcessLive(indexed)) return true;
9475
+ const resolved = status ?? indexedWorktreeStatus(indexed);
9476
+ if (!isFinishedWorkerStatus(resolved)) return true;
9477
+ if (isPrOrUnmergedWork(resolved)) return true;
9478
+ if (materialWorktreeChanges2(resolved.changedFiles).length > 0) return true;
9479
+ const landing = assessWorkerLanding({
9480
+ finalResult: resolved.finalResult,
9481
+ changedFiles: resolved.changedFiles,
9482
+ gitAncestry: resolved.gitAncestry,
9483
+ prUrl: prUrlFromFinalResult(resolved.finalResult)
9484
+ });
9485
+ if (landing.blocked) return true;
9486
+ return false;
9487
+ }
9488
+
9489
+ // src/cleanup-run-liveness.ts
9490
+ function deriveRunTerminal(indexed, ctx) {
9491
+ if (ctx) return ctx.runTerminalCache.derive(indexed.run);
9492
+ return deriveTerminalRunStatus(indexed.run);
9493
+ }
9494
+ function isWorkerProcessLive(indexed) {
9495
+ if (isPidAlive(indexed.worker.pid)) return true;
9496
+ if (!indexed.worker.pid) return indexedWorktreeStatus(indexed).alive;
9497
+ return false;
9498
+ }
9499
+ function isRunStaleActive(indexed, ctx) {
9500
+ if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
9501
+ return deriveRunTerminal(indexed, ctx) !== null;
9502
+ }
9503
+ function runBlocksWorktreeRemoval(indexed, ctx) {
9504
+ if (isWorkerProcessLive(indexed)) return true;
9505
+ const status = indexedWorktreeStatus(indexed);
9506
+ if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return true;
9507
+ if (isFinishedWorkerStatus(status)) return false;
9508
+ if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
9509
+ if (isRunStaleActive(indexed, ctx)) return false;
9510
+ return deriveRunTerminal(indexed, ctx) === null;
9511
+ }
9512
+
9513
+ // src/cleanup-guards.ts
9405
9514
  function effectiveWorktreeAgeMs(input) {
9406
9515
  const { indexed, includeOrphans, worktreesAgeMs, terminalWorktreesAgeMs } = input;
9407
9516
  if (!indexed) return includeOrphans ? terminalWorktreesAgeMs : worktreesAgeMs;
9408
9517
  if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) {
9409
9518
  return terminalWorktreesAgeMs;
9410
9519
  }
9520
+ if (input.liveness && isRunStaleActive(indexed, input.liveness)) {
9521
+ return terminalWorktreesAgeMs;
9522
+ }
9523
+ if (input.liveness && isFinishedWorkerStatus(indexedWorktreeStatus(indexed)) && !isWorkerProcessLive(indexed)) {
9524
+ return terminalWorktreesAgeMs;
9525
+ }
9411
9526
  return worktreesAgeMs;
9412
9527
  }
9413
9528
  function skipWorktreeRemoval(input) {
@@ -9421,7 +9536,7 @@ function skipWorktreeRemoval(input) {
9421
9536
  if (ageThresholdMs > 0 && ageMs < ageThresholdMs) return "below_age_threshold";
9422
9537
  const status = indexedWorktreeStatus(indexed);
9423
9538
  if (isWorkerProcessLive(indexed)) return "active_worker";
9424
- if (indexed.worker.completionBlocker) return "completion_blocked";
9539
+ if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return "completion_blocked";
9425
9540
  if (runBlocksWorktreeRemoval(indexed, input.liveness)) return "run_still_active";
9426
9541
  if (!isFinishedWorkerStatus(status)) return "run_still_active";
9427
9542
  if (isPrOrUnmergedWork(status)) return "pr_or_unmerged_commits";
@@ -9577,7 +9692,9 @@ function skipRunDirectoryRemoval(input) {
9577
9692
  }
9578
9693
  if (!runDirectoryIsEmpty(runPath)) return "run_still_active";
9579
9694
  const run = loadRunStatus(harnessRoot, runId);
9580
- if (run && !TERMINAL_RUN_STATUSES.has(run.status)) return "run_still_active";
9695
+ if (run && !TERMINAL_RUN_STATUSES.has(run.status)) {
9696
+ if (!deriveTerminalRunStatus(run)) return "run_still_active";
9697
+ }
9581
9698
  if (runDirectoriesAgeMs > 0 && ageMs < runDirectoriesAgeMs) return "below_age_threshold";
9582
9699
  return null;
9583
9700
  }
@@ -10667,14 +10784,14 @@ function runHarnessCleanup(options = {}) {
10667
10784
  const paths = resolvePaths(options);
10668
10785
  emitCleanupProgress("scan", `${paths.scanRoots.length} harness root(s)`);
10669
10786
  const activeGuards = collectActiveWorktreeGuards(paths.scanRoots, paths.now);
10670
- emitCleanupProgress("index", "building worktree index");
10671
- const index = mergeWorktreeIndexes(paths.scanRoots);
10672
- emitCleanupProgress("index", `${index.size} indexed worktree(s)`);
10673
- const liveness = { runTerminalCache: new CleanupRunTerminalCache() };
10674
10787
  const finalizedRuns = retention.finalizeStaleRuns ? finalizeStaleRuns().map((f) => ({ runId: f.runId, from: f.from, to: f.to })) : [];
10675
10788
  if (finalizedRuns.length > 0) {
10676
10789
  emitCleanupProgress("finalize", `${finalizedRuns.length} stale run(s) marked terminal`);
10677
10790
  }
10791
+ emitCleanupProgress("index", "building worktree index");
10792
+ const index = mergeWorktreeIndexes(paths.scanRoots);
10793
+ emitCleanupProgress("index", `${index.size} indexed worktree(s)`);
10794
+ const liveness = { runTerminalCache: new CleanupRunTerminalCache() };
10678
10795
  const skips = [];
10679
10796
  const actions = [];
10680
10797
  const processedPaths = /* @__PURE__ */ new Set();
@@ -14037,7 +14154,7 @@ async function runLandingMaintainerLaneTick(args) {
14037
14154
  const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
14038
14155
  const repoSlug = String(args.repo || LANDING_MAINTAINER_LANE_SPEC.defaultRepo).trim();
14039
14156
  const fleet = args.fleet === true || args.fleet === "true";
14040
- const execute = args.execute !== false && args.execute !== "false";
14157
+ const execute = args.execute === true || args.execute === "true";
14041
14158
  const runId = args.run ? String(args.run) : void 0;
14042
14159
  const resourceGate = observeRunnerResourceGate({
14043
14160
  runId: runId ?? "fleet-lane-tick"