@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.
- package/dist/cleanup-completion-blocker.d.ts +10 -0
- package/dist/cleanup-guards.d.ts +1 -6
- package/dist/cleanup-worktree-salvage.d.ts +7 -0
- package/dist/cli.js +166 -59
- package/dist/cli.js.map +4 -4
- package/dist/index.js +166 -59
- package/dist/index.js.map +4 -4
- package/dist/server/cleanup.d.ts +3 -0
- package/dist/server/cleanup.js +3511 -0
- package/dist/server/cleanup.js.map +7 -0
- package/dist/server/default-repo.d.ts +1 -0
- package/dist/server/default-repo.js +228 -0
- package/dist/server/default-repo.js.map +7 -0
- package/dist/server/harness-notice.d.ts +2 -0
- package/dist/server/harness-notice.js +287 -0
- package/dist/server/harness-notice.js.map +7 -0
- package/dist/server/heavy-verification.d.ts +2 -0
- package/dist/server/heavy-verification.js +223 -0
- package/dist/server/heavy-verification.js.map +7 -0
- package/dist/server/landing.d.ts +1 -0
- package/dist/server/landing.js +44 -0
- package/dist/server/landing.js.map +7 -0
- package/dist/server/memory-cost-enforce.d.ts +1 -0
- package/dist/server/memory-cost-enforce.js +470 -0
- package/dist/server/memory-cost-enforce.js.map +7 -0
- package/dist/server/memory-cost.d.ts +1 -0
- package/dist/server/memory-cost.js +184 -0
- package/dist/server/memory-cost.js.map +7 -0
- package/dist/server/monitor.d.ts +3 -0
- package/dist/server/monitor.js +1577 -0
- package/dist/server/monitor.js.map +7 -0
- package/dist/server/orchestration.d.ts +10 -0
- package/dist/server/orchestration.js +444 -0
- package/dist/server/orchestration.js.map +7 -0
- package/dist/server/pr-evidence.d.ts +2 -0
- package/dist/server/pr-evidence.js +163 -0
- package/dist/server/pr-evidence.js.map +7 -0
- package/dist/server/repo-search.d.ts +1 -0
- package/dist/server/repo-search.js +224 -0
- package/dist/server/repo-search.js.map +7 -0
- package/dist/server/worker-policy.d.ts +2 -0
- package/dist/server/worker-policy.js +177 -0
- package/dist/server/worker-policy.js.map +7 -0
- package/dist/status.d.ts +4 -0
- 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;
|
package/dist/cleanup-guards.d.ts
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
4993
|
+
return commitDirtyToExistingPr({
|
|
4994
|
+
snapshot,
|
|
4927
4995
|
prUrl: existing,
|
|
4928
|
-
|
|
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
|
|
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
|
|
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;
|
|
@@ -9364,30 +9441,7 @@ function indexedWorktreeHasMaterialChanges(entry) {
|
|
|
9364
9441
|
return materialWorktreeChanges2(gitStatusShort(entry.worktreePath)).length > 0;
|
|
9365
9442
|
}
|
|
9366
9443
|
|
|
9367
|
-
// src/cleanup-
|
|
9368
|
-
function deriveRunTerminal(indexed, ctx) {
|
|
9369
|
-
if (ctx) return ctx.runTerminalCache.derive(indexed.run);
|
|
9370
|
-
return deriveTerminalRunStatus(indexed.run);
|
|
9371
|
-
}
|
|
9372
|
-
function isWorkerProcessLive(indexed) {
|
|
9373
|
-
if (isPidAlive(indexed.worker.pid)) return true;
|
|
9374
|
-
if (!indexed.worker.pid) return indexedWorktreeStatus(indexed).alive;
|
|
9375
|
-
return false;
|
|
9376
|
-
}
|
|
9377
|
-
function isRunStaleActive(indexed, ctx) {
|
|
9378
|
-
if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
|
|
9379
|
-
return deriveRunTerminal(indexed, ctx) !== null;
|
|
9380
|
-
}
|
|
9381
|
-
function runBlocksWorktreeRemoval(indexed, ctx) {
|
|
9382
|
-
if (isWorkerProcessLive(indexed)) return true;
|
|
9383
|
-
if (indexed.worker.completionBlocker) return true;
|
|
9384
|
-
if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
|
|
9385
|
-
if (isRunStaleActive(indexed, ctx)) return false;
|
|
9386
|
-
if (!isFinishedWorkerStatus(indexedWorktreeStatus(indexed))) return true;
|
|
9387
|
-
return deriveRunTerminal(indexed, ctx) === null;
|
|
9388
|
-
}
|
|
9389
|
-
|
|
9390
|
-
// src/cleanup-guards.ts
|
|
9444
|
+
// src/cleanup-worktree-salvage.ts
|
|
9391
9445
|
function prUrlFromFinalResult(finalResult) {
|
|
9392
9446
|
if (typeof finalResult === "string") {
|
|
9393
9447
|
const match = finalResult.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/i);
|
|
@@ -9412,12 +9466,63 @@ function isPrOrUnmergedWork(status) {
|
|
|
9412
9466
|
if (status.changedFiles.length > 0 && status.finalResult) return true;
|
|
9413
9467
|
return false;
|
|
9414
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
|
|
9415
9514
|
function effectiveWorktreeAgeMs(input) {
|
|
9416
9515
|
const { indexed, includeOrphans, worktreesAgeMs, terminalWorktreesAgeMs } = input;
|
|
9417
9516
|
if (!indexed) return includeOrphans ? terminalWorktreesAgeMs : worktreesAgeMs;
|
|
9418
9517
|
if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) {
|
|
9419
9518
|
return terminalWorktreesAgeMs;
|
|
9420
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
|
+
}
|
|
9421
9526
|
return worktreesAgeMs;
|
|
9422
9527
|
}
|
|
9423
9528
|
function skipWorktreeRemoval(input) {
|
|
@@ -9431,7 +9536,7 @@ function skipWorktreeRemoval(input) {
|
|
|
9431
9536
|
if (ageThresholdMs > 0 && ageMs < ageThresholdMs) return "below_age_threshold";
|
|
9432
9537
|
const status = indexedWorktreeStatus(indexed);
|
|
9433
9538
|
if (isWorkerProcessLive(indexed)) return "active_worker";
|
|
9434
|
-
if (indexed
|
|
9539
|
+
if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return "completion_blocked";
|
|
9435
9540
|
if (runBlocksWorktreeRemoval(indexed, input.liveness)) return "run_still_active";
|
|
9436
9541
|
if (!isFinishedWorkerStatus(status)) return "run_still_active";
|
|
9437
9542
|
if (isPrOrUnmergedWork(status)) return "pr_or_unmerged_commits";
|
|
@@ -9587,7 +9692,9 @@ function skipRunDirectoryRemoval(input) {
|
|
|
9587
9692
|
}
|
|
9588
9693
|
if (!runDirectoryIsEmpty(runPath)) return "run_still_active";
|
|
9589
9694
|
const run = loadRunStatus(harnessRoot, runId);
|
|
9590
|
-
if (run && !TERMINAL_RUN_STATUSES.has(run.status))
|
|
9695
|
+
if (run && !TERMINAL_RUN_STATUSES.has(run.status)) {
|
|
9696
|
+
if (!deriveTerminalRunStatus(run)) return "run_still_active";
|
|
9697
|
+
}
|
|
9591
9698
|
if (runDirectoriesAgeMs > 0 && ageMs < runDirectoriesAgeMs) return "below_age_threshold";
|
|
9592
9699
|
return null;
|
|
9593
9700
|
}
|
|
@@ -10677,14 +10784,14 @@ function runHarnessCleanup(options = {}) {
|
|
|
10677
10784
|
const paths = resolvePaths(options);
|
|
10678
10785
|
emitCleanupProgress("scan", `${paths.scanRoots.length} harness root(s)`);
|
|
10679
10786
|
const activeGuards = collectActiveWorktreeGuards(paths.scanRoots, paths.now);
|
|
10680
|
-
emitCleanupProgress("index", "building worktree index");
|
|
10681
|
-
const index = mergeWorktreeIndexes(paths.scanRoots);
|
|
10682
|
-
emitCleanupProgress("index", `${index.size} indexed worktree(s)`);
|
|
10683
|
-
const liveness = { runTerminalCache: new CleanupRunTerminalCache() };
|
|
10684
10787
|
const finalizedRuns = retention.finalizeStaleRuns ? finalizeStaleRuns().map((f) => ({ runId: f.runId, from: f.from, to: f.to })) : [];
|
|
10685
10788
|
if (finalizedRuns.length > 0) {
|
|
10686
10789
|
emitCleanupProgress("finalize", `${finalizedRuns.length} stale run(s) marked terminal`);
|
|
10687
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() };
|
|
10688
10795
|
const skips = [];
|
|
10689
10796
|
const actions = [];
|
|
10690
10797
|
const processedPaths = /* @__PURE__ */ new Set();
|