@kynver-app/runtime 0.1.23 → 0.1.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +570 -23
- package/dist/cli.js.map +4 -4
- package/dist/index.js +571 -23
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -422,12 +422,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
|
422
422
|
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
423
423
|
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
424
424
|
function observeRunnerDiskGate(input = {}) {
|
|
425
|
-
const
|
|
425
|
+
const path23 = input.diskPath?.trim() || "/";
|
|
426
426
|
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
427
427
|
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
428
428
|
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
429
429
|
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
430
|
-
const stats = statfsSync(
|
|
430
|
+
const stats = statfsSync(path23);
|
|
431
431
|
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
432
432
|
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
433
433
|
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
@@ -447,7 +447,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
447
447
|
}
|
|
448
448
|
return {
|
|
449
449
|
ok,
|
|
450
|
-
path:
|
|
450
|
+
path: path23,
|
|
451
451
|
freeBytes,
|
|
452
452
|
totalBytes,
|
|
453
453
|
usedPercent,
|
|
@@ -680,6 +680,64 @@ function classifyExitFailure(errorText) {
|
|
|
680
680
|
return null;
|
|
681
681
|
}
|
|
682
682
|
|
|
683
|
+
// src/exited-salvage.ts
|
|
684
|
+
function trimOrNull(value) {
|
|
685
|
+
if (typeof value !== "string") return null;
|
|
686
|
+
const trimmed = value.trim();
|
|
687
|
+
return trimmed.length ? trimmed : null;
|
|
688
|
+
}
|
|
689
|
+
function hasFinalResult(value) {
|
|
690
|
+
if (value === void 0 || value === null) return false;
|
|
691
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
692
|
+
if (typeof value === "boolean") return value;
|
|
693
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
694
|
+
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
697
|
+
function committedHeadFromAncestry(ancestry) {
|
|
698
|
+
if (!ancestry?.checked) return null;
|
|
699
|
+
if (ancestry.headIsAncestorOfBase !== false) return null;
|
|
700
|
+
return trimOrNull(ancestry.head);
|
|
701
|
+
}
|
|
702
|
+
function buildAttentionReason(kind, uncommittedCount, headCommit) {
|
|
703
|
+
const parts = ["exited_with_changes_salvage"];
|
|
704
|
+
if (kind === "uncommitted" || kind === "both") {
|
|
705
|
+
parts.push(
|
|
706
|
+
`${uncommittedCount} uncommitted change${uncommittedCount === 1 ? "" : "s"} with no final result`
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
if ((kind === "committed_ahead" || kind === "both") && headCommit) {
|
|
710
|
+
const sha = headCommit.length > 12 ? headCommit.slice(0, 12) : headCommit;
|
|
711
|
+
parts.push(`commit ${sha} ahead of base with no final result`);
|
|
712
|
+
}
|
|
713
|
+
parts.push("review worktree \u2014 commit, open a PR, or run a salvage worker before discarding");
|
|
714
|
+
return parts.join(": ");
|
|
715
|
+
}
|
|
716
|
+
function assessExitedWorkerSalvage(input) {
|
|
717
|
+
if (input.alive || hasFinalResult(input.finalResult)) return null;
|
|
718
|
+
const uncommittedCount = (input.changedFiles ?? []).filter((line) => line.trim()).length;
|
|
719
|
+
const headCommit = trimOrNull(input.headCommit) ?? committedHeadFromAncestry(input.gitAncestry);
|
|
720
|
+
const hasUncommitted = uncommittedCount > 0;
|
|
721
|
+
const hasCommittedAhead = Boolean(headCommit);
|
|
722
|
+
if (!hasUncommitted && !hasCommittedAhead) {
|
|
723
|
+
return {
|
|
724
|
+
kind: "none",
|
|
725
|
+
salvageable: false,
|
|
726
|
+
uncommittedCount: 0,
|
|
727
|
+
headCommit: null,
|
|
728
|
+
attentionReason: "process exited without a final result"
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
const kind = hasUncommitted && hasCommittedAhead ? "both" : hasUncommitted ? "uncommitted" : "committed_ahead";
|
|
732
|
+
return {
|
|
733
|
+
kind,
|
|
734
|
+
salvageable: true,
|
|
735
|
+
uncommittedCount,
|
|
736
|
+
headCommit,
|
|
737
|
+
attentionReason: buildAttentionReason(kind, uncommittedCount, headCommit)
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
|
|
683
741
|
// src/git.ts
|
|
684
742
|
import { spawnSync } from "node:child_process";
|
|
685
743
|
function git(cwd, args, options = {}) {
|
|
@@ -804,12 +862,12 @@ function scrubClaudeEnv(env) {
|
|
|
804
862
|
}
|
|
805
863
|
|
|
806
864
|
// src/landing-gate.ts
|
|
807
|
-
function
|
|
865
|
+
function trimOrNull2(value) {
|
|
808
866
|
if (typeof value !== "string") return null;
|
|
809
867
|
const trimmed = value.trim();
|
|
810
868
|
return trimmed.length ? trimmed : null;
|
|
811
869
|
}
|
|
812
|
-
function
|
|
870
|
+
function hasFinalResult2(value) {
|
|
813
871
|
if (value === void 0 || value === null) return false;
|
|
814
872
|
if (typeof value === "string") return value.trim().length > 0;
|
|
815
873
|
if (typeof value === "boolean") return value;
|
|
@@ -818,18 +876,18 @@ function hasFinalResult(value) {
|
|
|
818
876
|
return true;
|
|
819
877
|
}
|
|
820
878
|
function hasCommittedLandingRef(snapshot) {
|
|
821
|
-
if (
|
|
822
|
-
if (
|
|
823
|
-
if (
|
|
824
|
-
if (
|
|
879
|
+
if (trimOrNull2(snapshot.headCommit)) return true;
|
|
880
|
+
if (trimOrNull2(snapshot.prUrl)) return true;
|
|
881
|
+
if (trimOrNull2(snapshot.artifactBundlePath)) return true;
|
|
882
|
+
if (trimOrNull2(snapshot.patchPath)) return true;
|
|
825
883
|
const ancestry = snapshot.gitAncestry;
|
|
826
|
-
if (ancestry?.checked && ancestry.headIsAncestorOfBase === false &&
|
|
884
|
+
if (ancestry?.checked && ancestry.headIsAncestorOfBase === false && trimOrNull2(ancestry.head)) {
|
|
827
885
|
return true;
|
|
828
886
|
}
|
|
829
887
|
return false;
|
|
830
888
|
}
|
|
831
889
|
function assessWorkerLanding(snapshot) {
|
|
832
|
-
if (!
|
|
890
|
+
if (!hasFinalResult2(snapshot.finalResult)) return { blocked: false };
|
|
833
891
|
if (snapshot.changedFiles.length === 0) return { blocked: false };
|
|
834
892
|
if (!hasCommittedLandingRef(snapshot)) {
|
|
835
893
|
return {
|
|
@@ -874,10 +932,23 @@ function computeAttention(input) {
|
|
|
874
932
|
if (!input.alive) {
|
|
875
933
|
const classified = classifyExitFailure(input.error);
|
|
876
934
|
if (classified) return { state: "blocked", reason: classified.reason };
|
|
935
|
+
const salvage = assessExitedWorkerSalvage({
|
|
936
|
+
alive: false,
|
|
937
|
+
finalResult: null,
|
|
938
|
+
changedFiles: input.changedFiles,
|
|
939
|
+
gitAncestry: input.gitAncestry
|
|
940
|
+
});
|
|
941
|
+
if (salvage?.salvageable) {
|
|
942
|
+
const tail2 = input.error?.trim();
|
|
943
|
+
return {
|
|
944
|
+
state: "needs_attention",
|
|
945
|
+
reason: tail2 ? `${salvage.attentionReason} (${tail2})` : salvage.attentionReason
|
|
946
|
+
};
|
|
947
|
+
}
|
|
877
948
|
const tail = input.error?.trim();
|
|
878
949
|
return {
|
|
879
950
|
state: "needs_attention",
|
|
880
|
-
reason: tail ? `process exited without a final result: ${tail}` : "process exited without a final result"
|
|
951
|
+
reason: tail ? `process exited without a final result: ${tail}` : salvage?.attentionReason ?? "process exited without a final result"
|
|
881
952
|
};
|
|
882
953
|
}
|
|
883
954
|
if (input.heartbeatBlocker) {
|
|
@@ -1394,6 +1465,8 @@ function buildPrompt(input) {
|
|
|
1394
1465
|
`Progress heartbeat file: ${input.heartbeatPath}`,
|
|
1395
1466
|
"After each major step, append one JSON line to the heartbeat file with fields: ts, phase, summary, changedFiles, blocker.",
|
|
1396
1467
|
"Final response must include files changed, verification commands, and unresolved risks.",
|
|
1468
|
+
"Completion handoff (required): before you stop, ensure the harness records a final result \u2014 summarize outcome in your last message and append a heartbeat line with phase `complete`. If you leave uncommitted changes, commit or open a PR; exiting with only dirty files and no final result routes to salvage review, not production review.",
|
|
1469
|
+
"Long-running commands: prefer bounded verification (targeted tests/typecheck for touched paths). If a full build is required, note it in heartbeat phase `verify` so a silent exit is not mistaken for success.",
|
|
1397
1470
|
"",
|
|
1398
1471
|
...progressLines,
|
|
1399
1472
|
"",
|
|
@@ -1627,7 +1700,7 @@ async function tryCompleteWorker(args) {
|
|
|
1627
1700
|
let secret = await resolveCallbackSecretWithMint(explicitSecret, agentOsId, { baseUrl: base });
|
|
1628
1701
|
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/harness/completion`;
|
|
1629
1702
|
const body = {
|
|
1630
|
-
source: "
|
|
1703
|
+
source: "kynver-harness",
|
|
1631
1704
|
agentOsId,
|
|
1632
1705
|
runId: worker.runId,
|
|
1633
1706
|
workerName: worker.name,
|
|
@@ -1726,6 +1799,13 @@ function buildRunBoard(runId) {
|
|
|
1726
1799
|
baseCommit: run.baseCommit
|
|
1727
1800
|
});
|
|
1728
1801
|
const headCommit = status.gitAncestry.headIsAncestorOfBase === false && status.gitAncestry.head ? status.gitAncestry.head : void 0;
|
|
1802
|
+
const exitedSalvage = assessExitedWorkerSalvage({
|
|
1803
|
+
alive: status.alive,
|
|
1804
|
+
finalResult: status.finalResult,
|
|
1805
|
+
changedFiles: status.changedFiles,
|
|
1806
|
+
gitAncestry: status.gitAncestry,
|
|
1807
|
+
headCommit
|
|
1808
|
+
});
|
|
1729
1809
|
const rawBlocker = worker.completionBlocker;
|
|
1730
1810
|
const completionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
|
|
1731
1811
|
const boardStatus = completionBlocker ? "blocked" : status.status;
|
|
@@ -1736,6 +1816,9 @@ function buildRunBoard(runId) {
|
|
|
1736
1816
|
attention: boardAttention,
|
|
1737
1817
|
attentionReason: completionBlocker ?? status.attention.reason,
|
|
1738
1818
|
landingBlocked: status.finalResult ? boardAttention === "needs_attention" || boardAttention === "blocked" : false,
|
|
1819
|
+
exitedWithoutFinalResult: !status.finalResult && !status.alive,
|
|
1820
|
+
salvageState: exitedSalvage?.salvageable ? "review_needed" : "none",
|
|
1821
|
+
salvageReason: exitedSalvage?.salvageable ? exitedSalvage.attentionReason : void 0,
|
|
1739
1822
|
pid: status.pid,
|
|
1740
1823
|
alive: status.alive,
|
|
1741
1824
|
currentTool: status.currentTool,
|
|
@@ -2107,7 +2190,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
2107
2190
|
}
|
|
2108
2191
|
return worker;
|
|
2109
2192
|
}
|
|
2110
|
-
function startWorker(args) {
|
|
2193
|
+
async function startWorker(args) {
|
|
2111
2194
|
const run = loadRun(String(args.run));
|
|
2112
2195
|
const name = typeof args.name === "string" ? args.name.trim() : "";
|
|
2113
2196
|
if (!name) {
|
|
@@ -2119,8 +2202,10 @@ function startWorker(args) {
|
|
|
2119
2202
|
console.error("missing --task or --task-file");
|
|
2120
2203
|
process.exit(1);
|
|
2121
2204
|
}
|
|
2205
|
+
const wait = args.wait === true || args.wait === "true";
|
|
2206
|
+
let worker;
|
|
2122
2207
|
try {
|
|
2123
|
-
|
|
2208
|
+
worker = spawnWorkerProcess(run, {
|
|
2124
2209
|
name,
|
|
2125
2210
|
task,
|
|
2126
2211
|
ownedPaths: args.owned ? String(args.owned).split(",").map((s) => s.trim()).filter(Boolean) : [],
|
|
@@ -2148,6 +2233,18 @@ function startWorker(args) {
|
|
|
2148
2233
|
console.error(`worker start failed: ${error.message}`);
|
|
2149
2234
|
process.exit(1);
|
|
2150
2235
|
}
|
|
2236
|
+
if (!wait || !worker) return;
|
|
2237
|
+
const outcome = await autoCompleteWorker({
|
|
2238
|
+
run: String(args.run),
|
|
2239
|
+
name: worker.name,
|
|
2240
|
+
...worker.agentOsId ? { agentOsId: worker.agentOsId } : {},
|
|
2241
|
+
...args.baseUrl ? { baseUrl: String(args.baseUrl) } : {},
|
|
2242
|
+
...args.secret ? { secret: String(args.secret) } : {}
|
|
2243
|
+
});
|
|
2244
|
+
console.error(JSON.stringify({ event: "start_wait_outcome", ...outcome }));
|
|
2245
|
+
if (outcome.outcome === "timed_out") {
|
|
2246
|
+
process.exitCode = 1;
|
|
2247
|
+
}
|
|
2151
2248
|
}
|
|
2152
2249
|
|
|
2153
2250
|
// src/dispatch.ts
|
|
@@ -2171,7 +2268,7 @@ async function dispatchRun(args) {
|
|
|
2171
2268
|
const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, { baseUrl: base });
|
|
2172
2269
|
const execute = args.execute === true || args.execute === "true";
|
|
2173
2270
|
const dryRun = !execute;
|
|
2174
|
-
const leaseOwner = `
|
|
2271
|
+
const leaseOwner = `kynver-harness:${run.id}`;
|
|
2175
2272
|
const runnerDiskGate = args.diskPath ? observeRunnerDiskGate({ diskPath: String(args.diskPath) }) : observeRunnerDiskGate({ diskPath: run.repo });
|
|
2176
2273
|
const runnerResourceGate = observeRunnerResourceGate({ runId: run.id });
|
|
2177
2274
|
const requestedStarts = Number(args.maxStarts) > 0 ? Math.floor(Number(args.maxStarts)) : 1;
|
|
@@ -2420,7 +2517,7 @@ async function sweepRun(args) {
|
|
|
2420
2517
|
const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
|
|
2421
2518
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
2422
2519
|
const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, { baseUrl: base });
|
|
2423
|
-
const leaseOwner = `
|
|
2520
|
+
const leaseOwner = `kynver-harness:${run.id}`;
|
|
2424
2521
|
const snapshotPublished = await publishHarnessBoardSnapshot({ run: run.id, agentOsId, ...args }, "run_sweep");
|
|
2425
2522
|
const releasedLocalOrphans = [];
|
|
2426
2523
|
for (const name of Object.keys(run.workers || {})) {
|
|
@@ -2472,7 +2569,7 @@ import { mkdirSync as mkdirSync5, realpathSync } from "node:fs";
|
|
|
2472
2569
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2473
2570
|
|
|
2474
2571
|
// src/pipeline-tick.ts
|
|
2475
|
-
import
|
|
2572
|
+
import path22 from "node:path";
|
|
2476
2573
|
|
|
2477
2574
|
// src/stale-reconcile.ts
|
|
2478
2575
|
import path15 from "node:path";
|
|
@@ -2675,19 +2772,445 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
2675
2772
|
}
|
|
2676
2773
|
}
|
|
2677
2774
|
|
|
2775
|
+
// src/cleanup.ts
|
|
2776
|
+
import path21 from "node:path";
|
|
2777
|
+
|
|
2778
|
+
// src/cleanup-types.ts
|
|
2779
|
+
var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
2780
|
+
var DEFAULT_WORKTREES_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
2781
|
+
|
|
2782
|
+
// src/cleanup-guards.ts
|
|
2783
|
+
var ACTIVE_RUN_STATUSES2 = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued", "needs_attention"]);
|
|
2784
|
+
var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
2785
|
+
function prUrlFromFinalResult(finalResult) {
|
|
2786
|
+
if (typeof finalResult === "string") {
|
|
2787
|
+
const match = finalResult.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/i);
|
|
2788
|
+
return match?.[0] ?? null;
|
|
2789
|
+
}
|
|
2790
|
+
if (finalResult && typeof finalResult === "object") {
|
|
2791
|
+
const obj = finalResult;
|
|
2792
|
+
for (const key of ["prUrl", "pr_url", "pullRequestUrl"]) {
|
|
2793
|
+
const value = obj[key];
|
|
2794
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
return null;
|
|
2798
|
+
}
|
|
2799
|
+
function isPrOrUnmergedWork(status) {
|
|
2800
|
+
if (prUrlFromFinalResult(status.finalResult)) return true;
|
|
2801
|
+
const relation = status.gitAncestry?.relation;
|
|
2802
|
+
if (relation === "ahead" || relation === "diverged") return true;
|
|
2803
|
+
if (status.changedFiles.length > 0 && status.finalResult) return true;
|
|
2804
|
+
return false;
|
|
2805
|
+
}
|
|
2806
|
+
function skipWorktreeRemoval(input) {
|
|
2807
|
+
const { indexed, includeOrphans, worktreesAgeMs, ageMs } = input;
|
|
2808
|
+
if (worktreesAgeMs <= 0) return "worktrees_disabled";
|
|
2809
|
+
if (ageMs < worktreesAgeMs) return "below_age_threshold";
|
|
2810
|
+
if (!indexed) return includeOrphans ? null : "orphan_without_flag";
|
|
2811
|
+
if (ACTIVE_RUN_STATUSES2.has(indexed.run.status)) return "run_still_active";
|
|
2812
|
+
if (!TERMINAL_RUN_STATUSES.has(indexed.run.status)) return "run_still_active";
|
|
2813
|
+
if (indexed.status.alive) return "active_worker";
|
|
2814
|
+
if (indexed.worker.status === "running") return "active_worker";
|
|
2815
|
+
if (indexed.worker.completionBlocker) return "completion_blocked";
|
|
2816
|
+
if (isPrOrUnmergedWork(indexed.status)) return "pr_or_unmerged_commits";
|
|
2817
|
+
if (indexed.status.changedFiles.length > 0) return "dirty_worktree";
|
|
2818
|
+
const landing = assessWorkerLanding({
|
|
2819
|
+
finalResult: indexed.status.finalResult,
|
|
2820
|
+
changedFiles: indexed.status.changedFiles,
|
|
2821
|
+
gitAncestry: indexed.status.gitAncestry,
|
|
2822
|
+
prUrl: prUrlFromFinalResult(indexed.status.finalResult)
|
|
2823
|
+
});
|
|
2824
|
+
if (landing.blocked) return "landing_blocked";
|
|
2825
|
+
return null;
|
|
2826
|
+
}
|
|
2827
|
+
function skipNodeModulesRemoval(input) {
|
|
2828
|
+
const { indexed, includeOrphans, nodeModulesAgeMs, ageMs } = input;
|
|
2829
|
+
if (ageMs < nodeModulesAgeMs) return "below_age_threshold";
|
|
2830
|
+
if (!indexed) return includeOrphans ? null : "orphan_without_flag";
|
|
2831
|
+
if (indexed.status.alive) return "active_worker";
|
|
2832
|
+
if (indexed.worker.status === "running") return "active_worker";
|
|
2833
|
+
if (indexed.worker.completionBlocker) return "completion_blocked";
|
|
2834
|
+
if (isPrOrUnmergedWork(indexed.status)) return "pr_or_unmerged_commits";
|
|
2835
|
+
if (indexed.status.changedFiles.length > 0) return "dirty_worktree";
|
|
2836
|
+
const landing = assessWorkerLanding({
|
|
2837
|
+
finalResult: indexed.status.finalResult,
|
|
2838
|
+
changedFiles: indexed.status.changedFiles,
|
|
2839
|
+
gitAncestry: indexed.status.gitAncestry,
|
|
2840
|
+
prUrl: prUrlFromFinalResult(indexed.status.finalResult)
|
|
2841
|
+
});
|
|
2842
|
+
if (landing.blocked) return "landing_blocked";
|
|
2843
|
+
return null;
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
// src/cleanup-execute.ts
|
|
2847
|
+
import { existsSync as existsSync13, rmSync } from "node:fs";
|
|
2848
|
+
import path18 from "node:path";
|
|
2849
|
+
|
|
2850
|
+
// src/cleanup-dir-size.ts
|
|
2851
|
+
import { existsSync as existsSync12, readdirSync as readdirSync4, statSync as statSync2 } from "node:fs";
|
|
2852
|
+
import path17 from "node:path";
|
|
2853
|
+
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
2854
|
+
if (!existsSync12(root)) return 0;
|
|
2855
|
+
let total = 0;
|
|
2856
|
+
let seen = 0;
|
|
2857
|
+
const stack = [root];
|
|
2858
|
+
while (stack.length > 0) {
|
|
2859
|
+
const current = stack.pop();
|
|
2860
|
+
let entries;
|
|
2861
|
+
try {
|
|
2862
|
+
entries = readdirSync4(current);
|
|
2863
|
+
} catch {
|
|
2864
|
+
continue;
|
|
2865
|
+
}
|
|
2866
|
+
for (const name of entries) {
|
|
2867
|
+
if (seen++ > maxEntries) return null;
|
|
2868
|
+
const full = path17.join(current, name);
|
|
2869
|
+
let st;
|
|
2870
|
+
try {
|
|
2871
|
+
st = statSync2(full);
|
|
2872
|
+
} catch {
|
|
2873
|
+
continue;
|
|
2874
|
+
}
|
|
2875
|
+
if (st.isDirectory()) stack.push(full);
|
|
2876
|
+
else total += st.size;
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
return total;
|
|
2880
|
+
}
|
|
2881
|
+
|
|
2882
|
+
// src/cleanup-execute.ts
|
|
2883
|
+
function removeNodeModules(candidate, execute) {
|
|
2884
|
+
if (!existsSync13(candidate.path)) {
|
|
2885
|
+
return {
|
|
2886
|
+
...candidate,
|
|
2887
|
+
executed: false,
|
|
2888
|
+
skipped: true,
|
|
2889
|
+
skipReason: "missing_worktree"
|
|
2890
|
+
};
|
|
2891
|
+
}
|
|
2892
|
+
if (!execute) {
|
|
2893
|
+
return { ...candidate, executed: false, skipped: true, skipReason: "dry_run" };
|
|
2894
|
+
}
|
|
2895
|
+
try {
|
|
2896
|
+
const bytesBefore = candidate.bytes ?? directorySizeBytes(candidate.path);
|
|
2897
|
+
rmSync(candidate.path, { recursive: true, force: true });
|
|
2898
|
+
return {
|
|
2899
|
+
...candidate,
|
|
2900
|
+
bytes: bytesBefore,
|
|
2901
|
+
executed: true,
|
|
2902
|
+
skipped: false
|
|
2903
|
+
};
|
|
2904
|
+
} catch (error) {
|
|
2905
|
+
return {
|
|
2906
|
+
...candidate,
|
|
2907
|
+
executed: false,
|
|
2908
|
+
skipped: true,
|
|
2909
|
+
skipReason: "remove_failed",
|
|
2910
|
+
error: error.message
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
function removeWorktree(candidate, execute) {
|
|
2915
|
+
if (!existsSync13(candidate.path)) {
|
|
2916
|
+
return {
|
|
2917
|
+
...candidate,
|
|
2918
|
+
executed: false,
|
|
2919
|
+
skipped: true,
|
|
2920
|
+
skipReason: "missing_worktree"
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
if (!execute) {
|
|
2924
|
+
return { ...candidate, executed: false, skipped: true, skipReason: "dry_run" };
|
|
2925
|
+
}
|
|
2926
|
+
const repo = candidate.repo;
|
|
2927
|
+
try {
|
|
2928
|
+
const bytesBefore = candidate.bytes ?? directorySizeBytes(candidate.path);
|
|
2929
|
+
if (repo) {
|
|
2930
|
+
git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
|
|
2931
|
+
}
|
|
2932
|
+
if (existsSync13(candidate.path)) {
|
|
2933
|
+
rmSync(candidate.path, { recursive: true, force: true });
|
|
2934
|
+
}
|
|
2935
|
+
return {
|
|
2936
|
+
...candidate,
|
|
2937
|
+
bytes: bytesBefore,
|
|
2938
|
+
executed: true,
|
|
2939
|
+
skipped: false
|
|
2940
|
+
};
|
|
2941
|
+
} catch (error) {
|
|
2942
|
+
return {
|
|
2943
|
+
...candidate,
|
|
2944
|
+
executed: false,
|
|
2945
|
+
skipped: true,
|
|
2946
|
+
skipReason: "remove_failed",
|
|
2947
|
+
error: error.message
|
|
2948
|
+
};
|
|
2949
|
+
}
|
|
2950
|
+
}
|
|
2951
|
+
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
2952
|
+
const resolved = path18.resolve(targetPath);
|
|
2953
|
+
const nm = resolved.endsWith(`${path18.sep}node_modules`) ? resolved : null;
|
|
2954
|
+
if (!nm) return "path_outside_harness";
|
|
2955
|
+
const rel = path18.relative(worktreesDir, nm);
|
|
2956
|
+
if (rel.startsWith("..") || path18.isAbsolute(rel)) return "path_outside_harness";
|
|
2957
|
+
const parts = rel.split(path18.sep);
|
|
2958
|
+
if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
|
|
2959
|
+
if (!resolved.startsWith(path18.resolve(harnessRoot))) return "path_outside_harness";
|
|
2960
|
+
return null;
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
// src/cleanup-scan.ts
|
|
2964
|
+
import { existsSync as existsSync14, readdirSync as readdirSync5, statSync as statSync3 } from "node:fs";
|
|
2965
|
+
import path19 from "node:path";
|
|
2966
|
+
function pathAgeMs(target, now) {
|
|
2967
|
+
try {
|
|
2968
|
+
const mtime = statSync3(target).mtimeMs;
|
|
2969
|
+
return Math.max(0, now - mtime);
|
|
2970
|
+
} catch {
|
|
2971
|
+
return 0;
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
function isPathInside(child, parent) {
|
|
2975
|
+
const rel = path19.relative(parent, child);
|
|
2976
|
+
return rel === "" || !rel.startsWith("..") && !path19.isAbsolute(rel);
|
|
2977
|
+
}
|
|
2978
|
+
function scanNodeModulesCandidates(opts) {
|
|
2979
|
+
const candidates = [];
|
|
2980
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2981
|
+
for (const entry of opts.index.values()) {
|
|
2982
|
+
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
2983
|
+
const nm = path19.join(entry.worktreePath, "node_modules");
|
|
2984
|
+
if (!existsSync14(nm)) continue;
|
|
2985
|
+
const resolved = path19.resolve(nm);
|
|
2986
|
+
if (seen.has(resolved)) continue;
|
|
2987
|
+
seen.add(resolved);
|
|
2988
|
+
candidates.push({
|
|
2989
|
+
kind: "remove_node_modules",
|
|
2990
|
+
path: resolved,
|
|
2991
|
+
bytes: null,
|
|
2992
|
+
runId: entry.runId,
|
|
2993
|
+
worker: entry.workerName,
|
|
2994
|
+
repo: entry.run.repo,
|
|
2995
|
+
ageMs: pathAgeMs(resolved, opts.now)
|
|
2996
|
+
});
|
|
2997
|
+
}
|
|
2998
|
+
if (!opts.includeOrphans || !existsSync14(opts.worktreesDir)) return candidates;
|
|
2999
|
+
for (const runEntry of readdirSync5(opts.worktreesDir, { withFileTypes: true })) {
|
|
3000
|
+
if (!runEntry.isDirectory()) continue;
|
|
3001
|
+
const runPath = path19.join(opts.worktreesDir, runEntry.name);
|
|
3002
|
+
for (const workerEntry of readdirSync5(runPath, { withFileTypes: true })) {
|
|
3003
|
+
if (!workerEntry.isDirectory()) continue;
|
|
3004
|
+
const worktreePath = path19.join(runPath, workerEntry.name);
|
|
3005
|
+
const nm = path19.join(worktreePath, "node_modules");
|
|
3006
|
+
if (!existsSync14(nm)) continue;
|
|
3007
|
+
const resolved = path19.resolve(nm);
|
|
3008
|
+
if (seen.has(resolved)) continue;
|
|
3009
|
+
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
3010
|
+
seen.add(resolved);
|
|
3011
|
+
candidates.push({
|
|
3012
|
+
kind: "remove_node_modules",
|
|
3013
|
+
path: resolved,
|
|
3014
|
+
bytes: null,
|
|
3015
|
+
runId: runEntry.name,
|
|
3016
|
+
worker: workerEntry.name,
|
|
3017
|
+
ageMs: pathAgeMs(resolved, opts.now)
|
|
3018
|
+
});
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
return candidates;
|
|
3022
|
+
}
|
|
3023
|
+
function scanWorktreeCandidates(opts) {
|
|
3024
|
+
if (opts.worktreesAgeMs <= 0) return [];
|
|
3025
|
+
const candidates = [];
|
|
3026
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3027
|
+
for (const entry of opts.index.values()) {
|
|
3028
|
+
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
3029
|
+
const resolved = entry.worktreePath;
|
|
3030
|
+
if (!existsSync14(resolved)) continue;
|
|
3031
|
+
if (seen.has(resolved)) continue;
|
|
3032
|
+
seen.add(resolved);
|
|
3033
|
+
candidates.push({
|
|
3034
|
+
kind: "remove_worktree",
|
|
3035
|
+
path: resolved,
|
|
3036
|
+
bytes: null,
|
|
3037
|
+
runId: entry.runId,
|
|
3038
|
+
worker: entry.workerName,
|
|
3039
|
+
repo: entry.run.repo,
|
|
3040
|
+
ageMs: pathAgeMs(resolved, opts.now)
|
|
3041
|
+
});
|
|
3042
|
+
}
|
|
3043
|
+
return candidates;
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
// src/cleanup-worktree-index.ts
|
|
3047
|
+
import path20 from "node:path";
|
|
3048
|
+
function buildWorktreeIndex() {
|
|
3049
|
+
const index = /* @__PURE__ */ new Map();
|
|
3050
|
+
for (const run of listRunRecords()) {
|
|
3051
|
+
for (const name of Object.keys(run.workers || {})) {
|
|
3052
|
+
const workerPath = path20.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
3053
|
+
const worker = readJson(workerPath, void 0);
|
|
3054
|
+
if (!worker?.worktreePath) continue;
|
|
3055
|
+
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
3056
|
+
index.set(path20.resolve(worker.worktreePath), {
|
|
3057
|
+
worktreePath: path20.resolve(worker.worktreePath),
|
|
3058
|
+
runId: run.id,
|
|
3059
|
+
workerName: name,
|
|
3060
|
+
run,
|
|
3061
|
+
worker,
|
|
3062
|
+
status
|
|
3063
|
+
});
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
3066
|
+
return index;
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
// src/cleanup.ts
|
|
3070
|
+
function resolveOptions(options = {}) {
|
|
3071
|
+
const harnessRoot = options.harnessRoot ? path21.resolve(options.harnessRoot) : resolveHarnessRoot();
|
|
3072
|
+
const { worktreesDir } = options.harnessRoot ? { worktreesDir: path21.join(harnessRoot, "worktrees") } : getHarnessPaths();
|
|
3073
|
+
const execute = options.execute === true;
|
|
3074
|
+
const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
|
|
3075
|
+
const worktreesAgeMs = options.worktreesAgeMs ?? 0;
|
|
3076
|
+
const includeOrphans = options.includeOrphans === true;
|
|
3077
|
+
const runIdFilter = options.runIdFilter ? String(options.runIdFilter) : void 0;
|
|
3078
|
+
const now = options.now ?? Date.now();
|
|
3079
|
+
return {
|
|
3080
|
+
harnessRoot,
|
|
3081
|
+
worktreesDir,
|
|
3082
|
+
execute,
|
|
3083
|
+
dryRun: !execute,
|
|
3084
|
+
nodeModulesAgeMs,
|
|
3085
|
+
worktreesAgeMs,
|
|
3086
|
+
includeOrphans,
|
|
3087
|
+
runIdFilter,
|
|
3088
|
+
now
|
|
3089
|
+
};
|
|
3090
|
+
}
|
|
3091
|
+
function recordSkip(skips, pathValue, reason, detail) {
|
|
3092
|
+
skips.push({ path: pathValue, reason, ...detail ? { detail } : {} });
|
|
3093
|
+
}
|
|
3094
|
+
function runHarnessCleanup(options = {}) {
|
|
3095
|
+
const resolved = resolveOptions(options);
|
|
3096
|
+
const index = buildWorktreeIndex();
|
|
3097
|
+
const scanOpts = {
|
|
3098
|
+
harnessRoot: resolved.harnessRoot,
|
|
3099
|
+
worktreesDir: resolved.worktreesDir,
|
|
3100
|
+
nodeModulesAgeMs: resolved.nodeModulesAgeMs,
|
|
3101
|
+
worktreesAgeMs: resolved.worktreesAgeMs,
|
|
3102
|
+
includeOrphans: resolved.includeOrphans,
|
|
3103
|
+
runIdFilter: resolved.runIdFilter,
|
|
3104
|
+
index,
|
|
3105
|
+
now: resolved.now
|
|
3106
|
+
};
|
|
3107
|
+
const skips = [];
|
|
3108
|
+
const actions = [];
|
|
3109
|
+
for (const candidate of scanNodeModulesCandidates(scanOpts)) {
|
|
3110
|
+
const pathSkip = isHarnessNodeModulesPath(candidate.path, resolved.harnessRoot, resolved.worktreesDir);
|
|
3111
|
+
if (pathSkip) {
|
|
3112
|
+
recordSkip(skips, candidate.path, pathSkip);
|
|
3113
|
+
actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
|
|
3114
|
+
continue;
|
|
3115
|
+
}
|
|
3116
|
+
const worktreePath = path21.resolve(candidate.path, "..");
|
|
3117
|
+
const indexed = index.get(worktreePath) ?? null;
|
|
3118
|
+
const guardReason = skipNodeModulesRemoval({
|
|
3119
|
+
indexed,
|
|
3120
|
+
includeOrphans: resolved.includeOrphans,
|
|
3121
|
+
nodeModulesAgeMs: resolved.nodeModulesAgeMs,
|
|
3122
|
+
ageMs: candidate.ageMs
|
|
3123
|
+
});
|
|
3124
|
+
if (guardReason) {
|
|
3125
|
+
recordSkip(skips, candidate.path, guardReason);
|
|
3126
|
+
actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
|
|
3127
|
+
continue;
|
|
3128
|
+
}
|
|
3129
|
+
actions.push(removeNodeModules(candidate, resolved.execute));
|
|
3130
|
+
}
|
|
3131
|
+
for (const candidate of scanWorktreeCandidates(scanOpts)) {
|
|
3132
|
+
const indexed = index.get(path21.resolve(candidate.path)) ?? null;
|
|
3133
|
+
const guardReason = skipWorktreeRemoval({
|
|
3134
|
+
indexed,
|
|
3135
|
+
includeOrphans: resolved.includeOrphans,
|
|
3136
|
+
worktreesAgeMs: resolved.worktreesAgeMs,
|
|
3137
|
+
ageMs: candidate.ageMs
|
|
3138
|
+
});
|
|
3139
|
+
if (guardReason) {
|
|
3140
|
+
recordSkip(skips, candidate.path, guardReason);
|
|
3141
|
+
actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
|
|
3142
|
+
continue;
|
|
3143
|
+
}
|
|
3144
|
+
actions.push(removeWorktree(candidate, resolved.execute));
|
|
3145
|
+
}
|
|
3146
|
+
let candidateBytes = 0;
|
|
3147
|
+
let removedBytes = 0;
|
|
3148
|
+
let removedPaths = 0;
|
|
3149
|
+
let skippedPaths = 0;
|
|
3150
|
+
for (const action of actions) {
|
|
3151
|
+
if (action.bytes) candidateBytes += action.bytes;
|
|
3152
|
+
if (action.executed) {
|
|
3153
|
+
removedPaths += 1;
|
|
3154
|
+
removedBytes += action.bytes ?? 0;
|
|
3155
|
+
} else if (action.skipped) {
|
|
3156
|
+
skippedPaths += 1;
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3159
|
+
return {
|
|
3160
|
+
harnessRoot: resolved.harnessRoot,
|
|
3161
|
+
dryRun: resolved.dryRun,
|
|
3162
|
+
execute: resolved.execute,
|
|
3163
|
+
nodeModulesAgeMs: resolved.nodeModulesAgeMs,
|
|
3164
|
+
worktreesAgeMs: resolved.worktreesAgeMs,
|
|
3165
|
+
includeOrphans: resolved.includeOrphans,
|
|
3166
|
+
scannedAt: new Date(resolved.now).toISOString(),
|
|
3167
|
+
actions,
|
|
3168
|
+
skips,
|
|
3169
|
+
totals: {
|
|
3170
|
+
candidateBytes,
|
|
3171
|
+
removedBytes,
|
|
3172
|
+
removedPaths,
|
|
3173
|
+
skippedPaths
|
|
3174
|
+
}
|
|
3175
|
+
};
|
|
3176
|
+
}
|
|
3177
|
+
function runPipelineHarnessCleanup(runId) {
|
|
3178
|
+
const nodeModulesAgeMs = Number(process.env.KYNVER_CLEANUP_NODE_MODULES_AGE_MS) || DEFAULT_NODE_MODULES_AGE_MS;
|
|
3179
|
+
const worktreesAgeMs = Number(process.env.KYNVER_CLEANUP_WORKTREES_AGE_MS) || 0;
|
|
3180
|
+
const execute = process.env.KYNVER_CLEANUP_EXECUTE === "1";
|
|
3181
|
+
const includeOrphans = process.env.KYNVER_CLEANUP_INCLUDE_ORPHANS === "1";
|
|
3182
|
+
const scopeAll = process.env.KYNVER_CLEANUP_SCOPE === "all";
|
|
3183
|
+
return runHarnessCleanup({
|
|
3184
|
+
execute,
|
|
3185
|
+
nodeModulesAgeMs,
|
|
3186
|
+
worktreesAgeMs,
|
|
3187
|
+
includeOrphans,
|
|
3188
|
+
runIdFilter: scopeAll ? void 0 : runId
|
|
3189
|
+
});
|
|
3190
|
+
}
|
|
3191
|
+
function isPipelineCleanupEnabled() {
|
|
3192
|
+
return process.env.KYNVER_PIPELINE_CLEANUP !== "0";
|
|
3193
|
+
}
|
|
3194
|
+
|
|
2678
3195
|
// src/pipeline-tick.ts
|
|
2679
3196
|
async function completeFinishedWorkers(runId, args) {
|
|
2680
3197
|
const run = loadRun(runId);
|
|
2681
3198
|
const outcomes = [];
|
|
2682
3199
|
for (const name of Object.keys(run.workers || {})) {
|
|
2683
3200
|
const worker = readJson(
|
|
2684
|
-
|
|
3201
|
+
path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2685
3202
|
void 0
|
|
2686
3203
|
);
|
|
2687
3204
|
if (!worker?.taskId) continue;
|
|
2688
3205
|
const status = computeWorkerStatus(worker);
|
|
2689
3206
|
if (!isFinishedWorkerStatus(status)) continue;
|
|
2690
|
-
|
|
3207
|
+
const exitedSalvage = assessExitedWorkerSalvage({
|
|
3208
|
+
alive: status.alive,
|
|
3209
|
+
finalResult: status.finalResult,
|
|
3210
|
+
changedFiles: status.changedFiles,
|
|
3211
|
+
gitAncestry: status.gitAncestry
|
|
3212
|
+
});
|
|
3213
|
+
if (!worker.dispatched && !status.finalResult && !exitedSalvage?.salvageable) continue;
|
|
2691
3214
|
const result = await tryCompleteWorker({
|
|
2692
3215
|
run: runId,
|
|
2693
3216
|
name,
|
|
@@ -2724,6 +3247,7 @@ async function runPipelineTick(args) {
|
|
|
2724
3247
|
const operatorTick = await postOperatorTick(agentOsId, runId, resourceGate, args);
|
|
2725
3248
|
const completedWorkers = await completeFinishedWorkers(runId, args);
|
|
2726
3249
|
const staleReconcile = reconcileStaleWorkers();
|
|
3250
|
+
const harnessCleanup = isPipelineCleanupEnabled() ? runPipelineHarnessCleanup(runId) : void 0;
|
|
2727
3251
|
const planProgressSync = await syncActiveWorkerPlanProgress(runId, args);
|
|
2728
3252
|
let maxStarts = resourceGate.slotsAvailable;
|
|
2729
3253
|
const tickBody = operatorTick;
|
|
@@ -2759,6 +3283,7 @@ async function runPipelineTick(args) {
|
|
|
2759
3283
|
resourceGate,
|
|
2760
3284
|
completedWorkers,
|
|
2761
3285
|
staleReconcile,
|
|
3286
|
+
harnessCleanup,
|
|
2762
3287
|
planProgressSync,
|
|
2763
3288
|
operatorTick,
|
|
2764
3289
|
sweep,
|
|
@@ -2897,6 +3422,26 @@ async function verifyPlan(args) {
|
|
|
2897
3422
|
console.log(JSON.stringify(parsed, null, 2));
|
|
2898
3423
|
}
|
|
2899
3424
|
|
|
3425
|
+
// src/cleanup-cli.ts
|
|
3426
|
+
function runCleanupCli(args) {
|
|
3427
|
+
const execute = args.execute === true || args.execute === "true";
|
|
3428
|
+
const nodeModulesAgeMs = args.nodeModulesAgeMs ? Number(args.nodeModulesAgeMs) : DEFAULT_NODE_MODULES_AGE_MS;
|
|
3429
|
+
const worktreesAgeMs = args.worktreesAgeMs ? Number(args.worktreesAgeMs) : 0;
|
|
3430
|
+
const includeOrphans = args.includeOrphans === true || args.includeOrphans === "true";
|
|
3431
|
+
const harnessRoot = args.harnessRoot ? String(args.harnessRoot) : void 0;
|
|
3432
|
+
const summary = runHarnessCleanup({
|
|
3433
|
+
execute,
|
|
3434
|
+
nodeModulesAgeMs: Number.isFinite(nodeModulesAgeMs) ? nodeModulesAgeMs : DEFAULT_NODE_MODULES_AGE_MS,
|
|
3435
|
+
worktreesAgeMs: Number.isFinite(worktreesAgeMs) ? worktreesAgeMs : 0,
|
|
3436
|
+
includeOrphans,
|
|
3437
|
+
harnessRoot
|
|
3438
|
+
});
|
|
3439
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
3440
|
+
if (execute && summary.totals.removedPaths === 0 && summary.actions.length === 0) {
|
|
3441
|
+
process.exitCode = 0;
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
|
|
2900
3445
|
// src/cli.ts
|
|
2901
3446
|
function isHelpFlag(arg) {
|
|
2902
3447
|
return arg === "help" || arg === "--help" || arg === "-h";
|
|
@@ -2920,14 +3465,15 @@ function usage(code = 0) {
|
|
|
2920
3465
|
" kynver run status --run RUN_ID",
|
|
2921
3466
|
" kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-7] [--disk-path /]",
|
|
2922
3467
|
" kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
|
|
2923
|
-
' kynver worker start --run RUN_ID --name worker --task "..." [--owned path[,path]] [--model MODEL] [--provider claude|cursor] [--agent-os-id AOS_ID] [--task-id TASK_ID]',
|
|
3468
|
+
' kynver worker start --run RUN_ID --name worker --task "..." [--owned path[,path]] [--model MODEL] [--provider claude|cursor] [--agent-os-id AOS_ID] [--task-id TASK_ID] [--wait]',
|
|
2924
3469
|
" kynver worker status --run RUN_ID --name worker",
|
|
2925
3470
|
" kynver worker tail --run RUN_ID --name worker [--lines 40] [--raw]",
|
|
2926
3471
|
" kynver worker stop --run RUN_ID --name worker",
|
|
2927
3472
|
" kynver worker complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--task-id TASK_ID] [--base-url URL] [--secret SECRET]",
|
|
2928
3473
|
" kynver worker auto-complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--poll-ms 5000] [--max-total-ms 21600000] [--complete-attempts 3] [--complete-backoff-ms 5000] [--base-url URL] [--secret SECRET]",
|
|
2929
3474
|
" kynver plan progress --plan PLAN_ID --row ROW_KEY --role ROLE --status STATUS [--task TASK_ID] [--note NOTE] [--evidence type:value] [--agent-os-id AOS_ID]",
|
|
2930
|
-
" kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override]"
|
|
3475
|
+
" kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override]",
|
|
3476
|
+
" kynver cleanup [--execute] [--node-modules-age-ms MS] [--worktrees-age-ms MS] [--harness-root PATH] [--include-orphans]"
|
|
2931
3477
|
].join("\n")
|
|
2932
3478
|
);
|
|
2933
3479
|
process.exit(code);
|
|
@@ -2954,12 +3500,13 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2954
3500
|
if (scope === "daemon") return void await runDaemon(args);
|
|
2955
3501
|
if (scope === "plan" && action === "progress") return void await emitPlanProgress(args);
|
|
2956
3502
|
if (scope === "plan" && action === "verify") return void await verifyPlan(args);
|
|
3503
|
+
if (scope === "cleanup") return runCleanupCli(args);
|
|
2957
3504
|
if (scope === "run" && action === "create") return createRun(args);
|
|
2958
3505
|
if (scope === "run" && action === "list") return listRuns();
|
|
2959
3506
|
if (scope === "run" && action === "status") return runStatus(args);
|
|
2960
3507
|
if (scope === "run" && action === "dispatch") return void await dispatchRun(args);
|
|
2961
3508
|
if (scope === "run" && action === "sweep") return void await sweepRun(args);
|
|
2962
|
-
if (scope === "worker" && action === "start") return startWorker(args);
|
|
3509
|
+
if (scope === "worker" && action === "start") return void await startWorker(args);
|
|
2963
3510
|
if (scope === "worker" && action === "status") return workerStatus(args);
|
|
2964
3511
|
if (scope === "worker" && action === "tail") return tailWorker(args);
|
|
2965
3512
|
if (scope === "worker" && action === "stop") return stopWorker(args);
|
|
@@ -2976,6 +3523,7 @@ if (isCliEntry) {
|
|
|
2976
3523
|
}
|
|
2977
3524
|
export {
|
|
2978
3525
|
DEFAULT_DISPATCH_LEASE_MS,
|
|
3526
|
+
assessExitedWorkerSalvage,
|
|
2979
3527
|
assessWorkerLanding,
|
|
2980
3528
|
autoCompleteWorker,
|
|
2981
3529
|
autoCompleteWorkerCli,
|