@kynver-app/runtime 0.1.103 → 0.1.106
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 +171 -62
- package/dist/cli.js.map +4 -4
- package/dist/index.js +172 -63
- 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/dist/worker-persona-catalog.js +2 -2
- package/dist/worker-persona-catalog.js.map +2 -2
- 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
|
@@ -311,7 +311,9 @@ function redactHomePath(value) {
|
|
|
311
311
|
if (resolved.startsWith(`${home}${path2.sep}`)) {
|
|
312
312
|
return `~/${path2.relative(home, resolved).split(path2.sep).join("/")}`;
|
|
313
313
|
}
|
|
314
|
-
|
|
314
|
+
const posix = resolved.replace(/\\/g, "/");
|
|
315
|
+
const redacted = posix.replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^\/Users\/[^/]+(?=\/|$)/, "~").replace(/^[A-Za-z]:\/home\/[^/]+(?=\/|$)/i, "~").replace(/^[A-Za-z]:\/Users\/[^/]+(?=\/|$)/i, "~");
|
|
316
|
+
return redacted;
|
|
315
317
|
}
|
|
316
318
|
function displayUserPath(value) {
|
|
317
319
|
return redactHomePath(value);
|
|
@@ -1783,7 +1785,7 @@ var NO_START_MS = 18e4;
|
|
|
1783
1785
|
var STALE_MS = 6e5;
|
|
1784
1786
|
function computeAttention(input) {
|
|
1785
1787
|
const now = Date.now();
|
|
1786
|
-
if (input.completionBlocker) {
|
|
1788
|
+
if (input.completionBlocker && !isSkippedTerminalCompletionBlocker(input.completionBlocker)) {
|
|
1787
1789
|
return { state: "blocked", reason: input.completionBlocker };
|
|
1788
1790
|
}
|
|
1789
1791
|
if (input.finalResult) {
|
|
@@ -1821,6 +1823,9 @@ function computeAttention(input) {
|
|
|
1821
1823
|
return { state: "done", reason: "final result recorded" };
|
|
1822
1824
|
}
|
|
1823
1825
|
if (!input.alive) {
|
|
1826
|
+
if (isAbandonedEmptyWorker(input)) {
|
|
1827
|
+
return { state: "done", reason: "empty abandoned worker record" };
|
|
1828
|
+
}
|
|
1824
1829
|
const classified = classifyExitFailure(input.error);
|
|
1825
1830
|
if (classified) return { state: "blocked", reason: classified.reason };
|
|
1826
1831
|
const salvage = assessExitedWorkerSalvage({
|
|
@@ -1855,6 +1860,19 @@ function computeAttention(input) {
|
|
|
1855
1860
|
}
|
|
1856
1861
|
return { state: "ok", reason: "recent activity" };
|
|
1857
1862
|
}
|
|
1863
|
+
function isSkippedTerminalCompletionBlocker(reason) {
|
|
1864
|
+
const text = reason?.trim();
|
|
1865
|
+
if (!text) return false;
|
|
1866
|
+
return /completion acknowledged but board not advanced/i.test(text) && /task already terminal/i.test(text);
|
|
1867
|
+
}
|
|
1868
|
+
function isAbandonedEmptyWorker(input) {
|
|
1869
|
+
if (input.finalResult) return false;
|
|
1870
|
+
if (input.taskId || input.agentOsId) return false;
|
|
1871
|
+
if (input.stdoutBytes > 0 || (input.stderrBytes ?? 0) > 0 || input.heartbeatBytes > 0) return false;
|
|
1872
|
+
if (input.error?.trim()) return false;
|
|
1873
|
+
if ((input.changedFiles ?? []).some((line) => line.trim())) return false;
|
|
1874
|
+
return /empty worker dir|marked abandoned/i.test(input.reconcileReason ?? "");
|
|
1875
|
+
}
|
|
1858
1876
|
function hasMergedTargetPrReconciliation(value) {
|
|
1859
1877
|
let record = null;
|
|
1860
1878
|
if (typeof value === "string") record = extractEmbeddedWorkerFinalResultRecord(value);
|
|
@@ -1899,6 +1917,7 @@ function computeWorkerStatus(worker, options = {}) {
|
|
|
1899
1917
|
]);
|
|
1900
1918
|
const error = parsed.error || (!alive && !finalResult ? tailFile(worker.stderrPath, 10).trim() || void 0 : void 0);
|
|
1901
1919
|
const completionBlocker = typeof worker.completionBlocker === "string" && worker.completionBlocker.trim() ? worker.completionBlocker.trim() : null;
|
|
1920
|
+
const effectiveCompletionBlocker = isSkippedTerminalCompletionBlocker(completionBlocker) ? null : completionBlocker;
|
|
1902
1921
|
const landingContract = worker.repairTargetPrUrl ? {
|
|
1903
1922
|
landingOnly: false,
|
|
1904
1923
|
targetPrUrls: [worker.repairTargetPrUrl],
|
|
@@ -1910,6 +1929,7 @@ function computeWorkerStatus(worker, options = {}) {
|
|
|
1910
1929
|
finalResult,
|
|
1911
1930
|
firstEventAt: parsed.firstEventAt,
|
|
1912
1931
|
stdoutBytes,
|
|
1932
|
+
stderrBytes,
|
|
1913
1933
|
heartbeatBytes,
|
|
1914
1934
|
lastActivityAt,
|
|
1915
1935
|
heartbeatBlocker: heartbeat.heartbeatBlocker,
|
|
@@ -1917,12 +1937,15 @@ function computeWorkerStatus(worker, options = {}) {
|
|
|
1917
1937
|
error,
|
|
1918
1938
|
changedFiles,
|
|
1919
1939
|
gitAncestry,
|
|
1920
|
-
completionBlocker,
|
|
1940
|
+
completionBlocker: effectiveCompletionBlocker,
|
|
1921
1941
|
landingContract,
|
|
1922
1942
|
prUrl: worker.repairTargetPrUrl ?? worker.taskPrUrl ?? null,
|
|
1923
|
-
localOnly: worker.localOnly === true
|
|
1943
|
+
localOnly: worker.localOnly === true,
|
|
1944
|
+
taskId: worker.taskId ?? null,
|
|
1945
|
+
agentOsId: worker.agentOsId ?? null,
|
|
1946
|
+
reconcileReason: worker.reconcileReason ?? null
|
|
1924
1947
|
});
|
|
1925
|
-
const workerStatusLabel =
|
|
1948
|
+
const workerStatusLabel = effectiveCompletionBlocker || attention.state === "blocked" ? "blocked" : completionAcknowledged || attention.state === "done" ? "done" : finalResult ? "exited" : alive ? "running" : "exited";
|
|
1926
1949
|
return {
|
|
1927
1950
|
runId: worker.runId,
|
|
1928
1951
|
worker: worker.name,
|
|
@@ -2675,14 +2698,14 @@ var WORKER_PERSONA_CATALOG = [
|
|
|
2675
2698
|
{
|
|
2676
2699
|
slug: "lorentz",
|
|
2677
2700
|
displayName: "Lorentz",
|
|
2678
|
-
description: "
|
|
2701
|
+
description: "Deep/adversarial review lane expert for risk, correctness, and safety gates. Run adversarial review and validation gating.",
|
|
2679
2702
|
dispatchLane: "review",
|
|
2680
2703
|
defaultRoleLane: "report_reviewer"
|
|
2681
2704
|
},
|
|
2682
2705
|
{
|
|
2683
2706
|
slug: "dalton",
|
|
2684
2707
|
displayName: "Dalton",
|
|
2685
|
-
description: "Landing
|
|
2708
|
+
description: "Landing-only \u2014 merge-ready handoff and final verification evidence; no implementation ownership.",
|
|
2686
2709
|
dispatchLane: "landing",
|
|
2687
2710
|
defaultRoleLane: "implementer"
|
|
2688
2711
|
}
|
|
@@ -2839,6 +2862,9 @@ function enforceCursorWorkerProvider(input) {
|
|
|
2839
2862
|
if (taskAllowsClaudeWorker(task)) {
|
|
2840
2863
|
return routing;
|
|
2841
2864
|
}
|
|
2865
|
+
if (routing.rule === "explicit:cli" && isClaudeFamilyProvider(routing.provider)) {
|
|
2866
|
+
return routing;
|
|
2867
|
+
}
|
|
2842
2868
|
if (!isClaudeFamilyProvider(routing.provider)) {
|
|
2843
2869
|
return routing;
|
|
2844
2870
|
}
|
|
@@ -3872,7 +3898,7 @@ function resolveWorkerLaunch(input) {
|
|
|
3872
3898
|
if (!input.task || Object.keys(input.task).length === 0) {
|
|
3873
3899
|
return afterCursorPolicy;
|
|
3874
3900
|
}
|
|
3875
|
-
if (afterCursorPolicy.rule === "explicit:model_provider_alias" || afterCursorPolicy.rule === "explicit:model_provider_alias_overrode_provider") {
|
|
3901
|
+
if (afterCursorPolicy.rule === "explicit:model_provider_alias" || afterCursorPolicy.rule === "explicit:model_provider_alias_overrode_provider" || afterCursorPolicy.rule === "explicit:cli") {
|
|
3876
3902
|
return afterCursorPolicy;
|
|
3877
3903
|
}
|
|
3878
3904
|
if (isClaudeFamilyProvider(afterCursorPolicy.provider) && (input.explicitProviderIsOperatorOverride || taskAllowsClaudeWorker(input.task))) {
|
|
@@ -4486,6 +4512,7 @@ function asString(value) {
|
|
|
4486
4512
|
var ADVANCED_OUTCOMES = /* @__PURE__ */ new Set([
|
|
4487
4513
|
"review_scheduled",
|
|
4488
4514
|
"review_already_scheduled",
|
|
4515
|
+
"skipped_terminal_task",
|
|
4489
4516
|
"closed",
|
|
4490
4517
|
"dispatched",
|
|
4491
4518
|
"dispatch_already_done"
|
|
@@ -4833,6 +4860,36 @@ function defaultPrBody(taskId, workerName, runId) {
|
|
|
4833
4860
|
"Opened by orchestrator completion enforcement so production review receives a reviewable artifact."
|
|
4834
4861
|
].filter(Boolean).join("\n");
|
|
4835
4862
|
}
|
|
4863
|
+
function commitDirtyToExistingPr(input) {
|
|
4864
|
+
if (input.snapshot.changedFiles.length === 0) {
|
|
4865
|
+
return {
|
|
4866
|
+
ok: true,
|
|
4867
|
+
prUrl: input.prUrl,
|
|
4868
|
+
headCommit: input.snapshot.headCommit ?? resolveHeadCommit(input.snapshot.worktreePath, input.exec) ?? void 0
|
|
4869
|
+
};
|
|
4870
|
+
}
|
|
4871
|
+
const pushResult = commitAndPushBranch({
|
|
4872
|
+
worktreePath: input.snapshot.worktreePath,
|
|
4873
|
+
branch: input.snapshot.branch,
|
|
4874
|
+
commitMessage: input.commitMessage,
|
|
4875
|
+
hasDirtyFiles: true,
|
|
4876
|
+
exec: input.exec
|
|
4877
|
+
});
|
|
4878
|
+
if (!pushResult.ok) {
|
|
4879
|
+
return {
|
|
4880
|
+
ok: false,
|
|
4881
|
+
reason: `PR-ready handoff blocked: ${pushResult.detail ?? "git commit/push failed"}`,
|
|
4882
|
+
nextAction: "Commit and push the dirty worker changes to the existing PR branch, then rerun `kynver worker complete`."
|
|
4883
|
+
};
|
|
4884
|
+
}
|
|
4885
|
+
return {
|
|
4886
|
+
ok: true,
|
|
4887
|
+
prUrl: input.prUrl,
|
|
4888
|
+
headCommit: pushResult.headCommit ?? input.snapshot.headCommit ?? void 0,
|
|
4889
|
+
committed: pushResult.committed,
|
|
4890
|
+
pushed: pushResult.pushed
|
|
4891
|
+
};
|
|
4892
|
+
}
|
|
4836
4893
|
function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
|
|
4837
4894
|
const prUrlHint = input.prUrlHint ?? extractPrUrlFromText(input.status.finalResult) ?? null;
|
|
4838
4895
|
const snapshot = buildPrHandoffSnapshotFromStatus(input.status, {
|
|
@@ -4856,10 +4913,23 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
|
|
|
4856
4913
|
snapshot
|
|
4857
4914
|
});
|
|
4858
4915
|
if (!requirement.required) {
|
|
4859
|
-
|
|
4916
|
+
if (prUrlHint) {
|
|
4917
|
+
return commitDirtyToExistingPr({
|
|
4918
|
+
snapshot,
|
|
4919
|
+
prUrl: prUrlHint,
|
|
4920
|
+
commitMessage: `chore(harness): update PR handoff for ${input.worker.name}`,
|
|
4921
|
+
exec
|
|
4922
|
+
});
|
|
4923
|
+
}
|
|
4924
|
+
return { ok: true };
|
|
4860
4925
|
}
|
|
4861
4926
|
if (prUrlHint) {
|
|
4862
|
-
return {
|
|
4927
|
+
return commitDirtyToExistingPr({
|
|
4928
|
+
snapshot,
|
|
4929
|
+
prUrl: prUrlHint,
|
|
4930
|
+
commitMessage: `chore(harness): update PR handoff for ${input.worker.name}`,
|
|
4931
|
+
exec
|
|
4932
|
+
});
|
|
4863
4933
|
}
|
|
4864
4934
|
if (!ghAvailable(exec)) {
|
|
4865
4935
|
const dirty = snapshot.changedFiles.length;
|
|
@@ -4922,11 +4992,12 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
|
|
|
4922
4992
|
}
|
|
4923
4993
|
const existing = findOpenPrUrl(snapshot.worktreePath, repo, snapshot.branch, exec);
|
|
4924
4994
|
if (existing) {
|
|
4925
|
-
return {
|
|
4926
|
-
|
|
4995
|
+
return commitDirtyToExistingPr({
|
|
4996
|
+
snapshot,
|
|
4927
4997
|
prUrl: existing,
|
|
4928
|
-
|
|
4929
|
-
|
|
4998
|
+
commitMessage: `chore(harness): update PR handoff for ${input.worker.name}`,
|
|
4999
|
+
exec
|
|
5000
|
+
});
|
|
4930
5001
|
}
|
|
4931
5002
|
const hasDirty = snapshot.changedFiles.length > 0;
|
|
4932
5003
|
let committed = false;
|
|
@@ -5322,6 +5393,11 @@ function deriveNextAction(input) {
|
|
|
5322
5393
|
}
|
|
5323
5394
|
return null;
|
|
5324
5395
|
}
|
|
5396
|
+
function isSkippedTerminalCompletionBlocker2(reason) {
|
|
5397
|
+
const text = reason?.trim();
|
|
5398
|
+
if (!text) return false;
|
|
5399
|
+
return /completion acknowledged but board not advanced/i.test(text) && /task already terminal/i.test(text);
|
|
5400
|
+
}
|
|
5325
5401
|
function deriveHandoffState(input) {
|
|
5326
5402
|
if (input.prUrl) return "pr_handoff";
|
|
5327
5403
|
if (input.headCommit) return "commit_handoff";
|
|
@@ -5378,6 +5454,23 @@ async function tryCompleteWorker(args) {
|
|
|
5378
5454
|
if (!forceReplay && shouldReplayHarnessCompletion(worker)) {
|
|
5379
5455
|
clearCompletionBlockerForReplay(worker);
|
|
5380
5456
|
}
|
|
5457
|
+
const skipPrHandoff = args.skipPrHandoff === true || args.skipPrHandoff === "true";
|
|
5458
|
+
if (!skipPrHandoff && worker.dispatched && taskId) {
|
|
5459
|
+
const handoff = ensurePrReadyHandoff({ worker, run, status });
|
|
5460
|
+
if (!handoff.ok) {
|
|
5461
|
+
persistCompletionBlocker(worker, handoff.reason);
|
|
5462
|
+
return {
|
|
5463
|
+
ok: false,
|
|
5464
|
+
reason: handoff.reason,
|
|
5465
|
+
nextAction: handoff.nextAction,
|
|
5466
|
+
completionBlocked: true
|
|
5467
|
+
};
|
|
5468
|
+
}
|
|
5469
|
+
if (handoff.prUrl || handoff.headCommit) {
|
|
5470
|
+
status = computeWorkerStatus(worker, workerStatusOptions(run));
|
|
5471
|
+
status = applyPrHandoffToStatus(status, handoff);
|
|
5472
|
+
}
|
|
5473
|
+
}
|
|
5381
5474
|
const headCommit = status.gitAncestry.headIsAncestorOfBase === false && status.gitAncestry.head ? status.gitAncestry.head : status.headCommit;
|
|
5382
5475
|
if (worker.dispatched) {
|
|
5383
5476
|
const handoff = assessWorktreeCompletionHandoff({
|
|
@@ -5398,22 +5491,6 @@ async function tryCompleteWorker(args) {
|
|
|
5398
5491
|
};
|
|
5399
5492
|
}
|
|
5400
5493
|
}
|
|
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
5494
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
5418
5495
|
const explicitSecret = args.secret ? String(args.secret) : void 0;
|
|
5419
5496
|
let secret = await resolveCallbackSecretWithMint(explicitSecret, agentOsId, { baseUrl: base });
|
|
@@ -5619,7 +5696,8 @@ function buildWorkerBoardEntry(input) {
|
|
|
5619
5696
|
headCommit
|
|
5620
5697
|
});
|
|
5621
5698
|
const rawBlocker = worker.completionBlocker;
|
|
5622
|
-
const
|
|
5699
|
+
const rawCompletionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
|
|
5700
|
+
const completionBlocker = isSkippedTerminalCompletionBlocker2(rawCompletionBlocker) ? void 0 : rawCompletionBlocker;
|
|
5623
5701
|
const boardStatus = completionBlocker ? "blocked" : status.status;
|
|
5624
5702
|
const boardAttention = completionBlocker ? "blocked" : status.attention.state;
|
|
5625
5703
|
const completionResponse = asRecord3(worker.completionResponse);
|
|
@@ -5704,7 +5782,8 @@ function buildWorkerBoardEntry(input) {
|
|
|
5704
5782
|
}
|
|
5705
5783
|
function isMetadataTerminalDone(worker) {
|
|
5706
5784
|
const status = typeof worker.status === "string" ? worker.status : "";
|
|
5707
|
-
const
|
|
5785
|
+
const rawCompletionBlocker = typeof worker.completionBlocker === "string" && worker.completionBlocker.trim().length > 0 ? worker.completionBlocker : void 0;
|
|
5786
|
+
const completionBlocker = rawCompletionBlocker && !isSkippedTerminalCompletionBlocker2(rawCompletionBlocker);
|
|
5708
5787
|
if (completionBlocker) return false;
|
|
5709
5788
|
if (typeof worker.completionReportedAt === "string" && worker.completionReportedAt.trim()) return true;
|
|
5710
5789
|
if (worker.completionOutcome === "acknowledged") return true;
|
|
@@ -9364,30 +9443,7 @@ function indexedWorktreeHasMaterialChanges(entry) {
|
|
|
9364
9443
|
return materialWorktreeChanges2(gitStatusShort(entry.worktreePath)).length > 0;
|
|
9365
9444
|
}
|
|
9366
9445
|
|
|
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
|
|
9446
|
+
// src/cleanup-worktree-salvage.ts
|
|
9391
9447
|
function prUrlFromFinalResult(finalResult) {
|
|
9392
9448
|
if (typeof finalResult === "string") {
|
|
9393
9449
|
const match = finalResult.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/i);
|
|
@@ -9412,12 +9468,63 @@ function isPrOrUnmergedWork(status) {
|
|
|
9412
9468
|
if (status.changedFiles.length > 0 && status.finalResult) return true;
|
|
9413
9469
|
return false;
|
|
9414
9470
|
}
|
|
9471
|
+
|
|
9472
|
+
// src/cleanup-completion-blocker.ts
|
|
9473
|
+
function completionBlockerBlocksWorktreeRemoval(indexed, status) {
|
|
9474
|
+
const blocker = typeof indexed.worker.completionBlocker === "string" ? indexed.worker.completionBlocker.trim() : "";
|
|
9475
|
+
if (!blocker) return false;
|
|
9476
|
+
if (isWorkerProcessLive(indexed)) return true;
|
|
9477
|
+
const resolved = status ?? indexedWorktreeStatus(indexed);
|
|
9478
|
+
if (!isFinishedWorkerStatus(resolved)) return true;
|
|
9479
|
+
if (isPrOrUnmergedWork(resolved)) return true;
|
|
9480
|
+
if (materialWorktreeChanges2(resolved.changedFiles).length > 0) return true;
|
|
9481
|
+
const landing = assessWorkerLanding({
|
|
9482
|
+
finalResult: resolved.finalResult,
|
|
9483
|
+
changedFiles: resolved.changedFiles,
|
|
9484
|
+
gitAncestry: resolved.gitAncestry,
|
|
9485
|
+
prUrl: prUrlFromFinalResult(resolved.finalResult)
|
|
9486
|
+
});
|
|
9487
|
+
if (landing.blocked) return true;
|
|
9488
|
+
return false;
|
|
9489
|
+
}
|
|
9490
|
+
|
|
9491
|
+
// src/cleanup-run-liveness.ts
|
|
9492
|
+
function deriveRunTerminal(indexed, ctx) {
|
|
9493
|
+
if (ctx) return ctx.runTerminalCache.derive(indexed.run);
|
|
9494
|
+
return deriveTerminalRunStatus(indexed.run);
|
|
9495
|
+
}
|
|
9496
|
+
function isWorkerProcessLive(indexed) {
|
|
9497
|
+
if (isPidAlive(indexed.worker.pid)) return true;
|
|
9498
|
+
if (!indexed.worker.pid) return indexedWorktreeStatus(indexed).alive;
|
|
9499
|
+
return false;
|
|
9500
|
+
}
|
|
9501
|
+
function isRunStaleActive(indexed, ctx) {
|
|
9502
|
+
if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
|
|
9503
|
+
return deriveRunTerminal(indexed, ctx) !== null;
|
|
9504
|
+
}
|
|
9505
|
+
function runBlocksWorktreeRemoval(indexed, ctx) {
|
|
9506
|
+
if (isWorkerProcessLive(indexed)) return true;
|
|
9507
|
+
const status = indexedWorktreeStatus(indexed);
|
|
9508
|
+
if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return true;
|
|
9509
|
+
if (isFinishedWorkerStatus(status)) return false;
|
|
9510
|
+
if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
|
|
9511
|
+
if (isRunStaleActive(indexed, ctx)) return false;
|
|
9512
|
+
return deriveRunTerminal(indexed, ctx) === null;
|
|
9513
|
+
}
|
|
9514
|
+
|
|
9515
|
+
// src/cleanup-guards.ts
|
|
9415
9516
|
function effectiveWorktreeAgeMs(input) {
|
|
9416
9517
|
const { indexed, includeOrphans, worktreesAgeMs, terminalWorktreesAgeMs } = input;
|
|
9417
9518
|
if (!indexed) return includeOrphans ? terminalWorktreesAgeMs : worktreesAgeMs;
|
|
9418
9519
|
if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) {
|
|
9419
9520
|
return terminalWorktreesAgeMs;
|
|
9420
9521
|
}
|
|
9522
|
+
if (input.liveness && isRunStaleActive(indexed, input.liveness)) {
|
|
9523
|
+
return terminalWorktreesAgeMs;
|
|
9524
|
+
}
|
|
9525
|
+
if (input.liveness && isFinishedWorkerStatus(indexedWorktreeStatus(indexed)) && !isWorkerProcessLive(indexed)) {
|
|
9526
|
+
return terminalWorktreesAgeMs;
|
|
9527
|
+
}
|
|
9421
9528
|
return worktreesAgeMs;
|
|
9422
9529
|
}
|
|
9423
9530
|
function skipWorktreeRemoval(input) {
|
|
@@ -9431,7 +9538,7 @@ function skipWorktreeRemoval(input) {
|
|
|
9431
9538
|
if (ageThresholdMs > 0 && ageMs < ageThresholdMs) return "below_age_threshold";
|
|
9432
9539
|
const status = indexedWorktreeStatus(indexed);
|
|
9433
9540
|
if (isWorkerProcessLive(indexed)) return "active_worker";
|
|
9434
|
-
if (indexed
|
|
9541
|
+
if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return "completion_blocked";
|
|
9435
9542
|
if (runBlocksWorktreeRemoval(indexed, input.liveness)) return "run_still_active";
|
|
9436
9543
|
if (!isFinishedWorkerStatus(status)) return "run_still_active";
|
|
9437
9544
|
if (isPrOrUnmergedWork(status)) return "pr_or_unmerged_commits";
|
|
@@ -9587,7 +9694,9 @@ function skipRunDirectoryRemoval(input) {
|
|
|
9587
9694
|
}
|
|
9588
9695
|
if (!runDirectoryIsEmpty(runPath)) return "run_still_active";
|
|
9589
9696
|
const run = loadRunStatus(harnessRoot, runId);
|
|
9590
|
-
if (run && !TERMINAL_RUN_STATUSES.has(run.status))
|
|
9697
|
+
if (run && !TERMINAL_RUN_STATUSES.has(run.status)) {
|
|
9698
|
+
if (!deriveTerminalRunStatus(run)) return "run_still_active";
|
|
9699
|
+
}
|
|
9591
9700
|
if (runDirectoriesAgeMs > 0 && ageMs < runDirectoriesAgeMs) return "below_age_threshold";
|
|
9592
9701
|
return null;
|
|
9593
9702
|
}
|
|
@@ -10677,14 +10786,14 @@ function runHarnessCleanup(options = {}) {
|
|
|
10677
10786
|
const paths = resolvePaths(options);
|
|
10678
10787
|
emitCleanupProgress("scan", `${paths.scanRoots.length} harness root(s)`);
|
|
10679
10788
|
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
10789
|
const finalizedRuns = retention.finalizeStaleRuns ? finalizeStaleRuns().map((f) => ({ runId: f.runId, from: f.from, to: f.to })) : [];
|
|
10685
10790
|
if (finalizedRuns.length > 0) {
|
|
10686
10791
|
emitCleanupProgress("finalize", `${finalizedRuns.length} stale run(s) marked terminal`);
|
|
10687
10792
|
}
|
|
10793
|
+
emitCleanupProgress("index", "building worktree index");
|
|
10794
|
+
const index = mergeWorktreeIndexes(paths.scanRoots);
|
|
10795
|
+
emitCleanupProgress("index", `${index.size} indexed worktree(s)`);
|
|
10796
|
+
const liveness = { runTerminalCache: new CleanupRunTerminalCache() };
|
|
10688
10797
|
const skips = [];
|
|
10689
10798
|
const actions = [];
|
|
10690
10799
|
const processedPaths = /* @__PURE__ */ new Set();
|