@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/cli.js
CHANGED
|
@@ -421,12 +421,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
|
421
421
|
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
422
422
|
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
423
423
|
function observeRunnerDiskGate(input = {}) {
|
|
424
|
-
const
|
|
424
|
+
const path23 = input.diskPath?.trim() || "/";
|
|
425
425
|
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
426
426
|
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
427
427
|
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
428
428
|
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
429
|
-
const stats = statfsSync(
|
|
429
|
+
const stats = statfsSync(path23);
|
|
430
430
|
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
431
431
|
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
432
432
|
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
@@ -446,7 +446,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
446
446
|
}
|
|
447
447
|
return {
|
|
448
448
|
ok,
|
|
449
|
-
path:
|
|
449
|
+
path: path23,
|
|
450
450
|
freeBytes,
|
|
451
451
|
totalBytes,
|
|
452
452
|
usedPercent,
|
|
@@ -679,6 +679,64 @@ function classifyExitFailure(errorText) {
|
|
|
679
679
|
return null;
|
|
680
680
|
}
|
|
681
681
|
|
|
682
|
+
// src/exited-salvage.ts
|
|
683
|
+
function trimOrNull(value) {
|
|
684
|
+
if (typeof value !== "string") return null;
|
|
685
|
+
const trimmed = value.trim();
|
|
686
|
+
return trimmed.length ? trimmed : null;
|
|
687
|
+
}
|
|
688
|
+
function hasFinalResult(value) {
|
|
689
|
+
if (value === void 0 || value === null) return false;
|
|
690
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
691
|
+
if (typeof value === "boolean") return value;
|
|
692
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
693
|
+
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
694
|
+
return true;
|
|
695
|
+
}
|
|
696
|
+
function committedHeadFromAncestry(ancestry) {
|
|
697
|
+
if (!ancestry?.checked) return null;
|
|
698
|
+
if (ancestry.headIsAncestorOfBase !== false) return null;
|
|
699
|
+
return trimOrNull(ancestry.head);
|
|
700
|
+
}
|
|
701
|
+
function buildAttentionReason(kind, uncommittedCount, headCommit) {
|
|
702
|
+
const parts = ["exited_with_changes_salvage"];
|
|
703
|
+
if (kind === "uncommitted" || kind === "both") {
|
|
704
|
+
parts.push(
|
|
705
|
+
`${uncommittedCount} uncommitted change${uncommittedCount === 1 ? "" : "s"} with no final result`
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
if ((kind === "committed_ahead" || kind === "both") && headCommit) {
|
|
709
|
+
const sha = headCommit.length > 12 ? headCommit.slice(0, 12) : headCommit;
|
|
710
|
+
parts.push(`commit ${sha} ahead of base with no final result`);
|
|
711
|
+
}
|
|
712
|
+
parts.push("review worktree \u2014 commit, open a PR, or run a salvage worker before discarding");
|
|
713
|
+
return parts.join(": ");
|
|
714
|
+
}
|
|
715
|
+
function assessExitedWorkerSalvage(input) {
|
|
716
|
+
if (input.alive || hasFinalResult(input.finalResult)) return null;
|
|
717
|
+
const uncommittedCount = (input.changedFiles ?? []).filter((line) => line.trim()).length;
|
|
718
|
+
const headCommit = trimOrNull(input.headCommit) ?? committedHeadFromAncestry(input.gitAncestry);
|
|
719
|
+
const hasUncommitted = uncommittedCount > 0;
|
|
720
|
+
const hasCommittedAhead = Boolean(headCommit);
|
|
721
|
+
if (!hasUncommitted && !hasCommittedAhead) {
|
|
722
|
+
return {
|
|
723
|
+
kind: "none",
|
|
724
|
+
salvageable: false,
|
|
725
|
+
uncommittedCount: 0,
|
|
726
|
+
headCommit: null,
|
|
727
|
+
attentionReason: "process exited without a final result"
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
const kind = hasUncommitted && hasCommittedAhead ? "both" : hasUncommitted ? "uncommitted" : "committed_ahead";
|
|
731
|
+
return {
|
|
732
|
+
kind,
|
|
733
|
+
salvageable: true,
|
|
734
|
+
uncommittedCount,
|
|
735
|
+
headCommit,
|
|
736
|
+
attentionReason: buildAttentionReason(kind, uncommittedCount, headCommit)
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
|
|
682
740
|
// src/git.ts
|
|
683
741
|
import { spawnSync } from "node:child_process";
|
|
684
742
|
function git(cwd, args, options = {}) {
|
|
@@ -803,12 +861,12 @@ function scrubClaudeEnv(env) {
|
|
|
803
861
|
}
|
|
804
862
|
|
|
805
863
|
// src/landing-gate.ts
|
|
806
|
-
function
|
|
864
|
+
function trimOrNull2(value) {
|
|
807
865
|
if (typeof value !== "string") return null;
|
|
808
866
|
const trimmed = value.trim();
|
|
809
867
|
return trimmed.length ? trimmed : null;
|
|
810
868
|
}
|
|
811
|
-
function
|
|
869
|
+
function hasFinalResult2(value) {
|
|
812
870
|
if (value === void 0 || value === null) return false;
|
|
813
871
|
if (typeof value === "string") return value.trim().length > 0;
|
|
814
872
|
if (typeof value === "boolean") return value;
|
|
@@ -817,18 +875,18 @@ function hasFinalResult(value) {
|
|
|
817
875
|
return true;
|
|
818
876
|
}
|
|
819
877
|
function hasCommittedLandingRef(snapshot) {
|
|
820
|
-
if (
|
|
821
|
-
if (
|
|
822
|
-
if (
|
|
823
|
-
if (
|
|
878
|
+
if (trimOrNull2(snapshot.headCommit)) return true;
|
|
879
|
+
if (trimOrNull2(snapshot.prUrl)) return true;
|
|
880
|
+
if (trimOrNull2(snapshot.artifactBundlePath)) return true;
|
|
881
|
+
if (trimOrNull2(snapshot.patchPath)) return true;
|
|
824
882
|
const ancestry = snapshot.gitAncestry;
|
|
825
|
-
if (ancestry?.checked && ancestry.headIsAncestorOfBase === false &&
|
|
883
|
+
if (ancestry?.checked && ancestry.headIsAncestorOfBase === false && trimOrNull2(ancestry.head)) {
|
|
826
884
|
return true;
|
|
827
885
|
}
|
|
828
886
|
return false;
|
|
829
887
|
}
|
|
830
888
|
function assessWorkerLanding(snapshot) {
|
|
831
|
-
if (!
|
|
889
|
+
if (!hasFinalResult2(snapshot.finalResult)) return { blocked: false };
|
|
832
890
|
if (snapshot.changedFiles.length === 0) return { blocked: false };
|
|
833
891
|
if (!hasCommittedLandingRef(snapshot)) {
|
|
834
892
|
return {
|
|
@@ -873,10 +931,23 @@ function computeAttention(input) {
|
|
|
873
931
|
if (!input.alive) {
|
|
874
932
|
const classified = classifyExitFailure(input.error);
|
|
875
933
|
if (classified) return { state: "blocked", reason: classified.reason };
|
|
934
|
+
const salvage = assessExitedWorkerSalvage({
|
|
935
|
+
alive: false,
|
|
936
|
+
finalResult: null,
|
|
937
|
+
changedFiles: input.changedFiles,
|
|
938
|
+
gitAncestry: input.gitAncestry
|
|
939
|
+
});
|
|
940
|
+
if (salvage?.salvageable) {
|
|
941
|
+
const tail2 = input.error?.trim();
|
|
942
|
+
return {
|
|
943
|
+
state: "needs_attention",
|
|
944
|
+
reason: tail2 ? `${salvage.attentionReason} (${tail2})` : salvage.attentionReason
|
|
945
|
+
};
|
|
946
|
+
}
|
|
876
947
|
const tail = input.error?.trim();
|
|
877
948
|
return {
|
|
878
949
|
state: "needs_attention",
|
|
879
|
-
reason: tail ? `process exited without a final result: ${tail}` : "process exited without a final result"
|
|
950
|
+
reason: tail ? `process exited without a final result: ${tail}` : salvage?.attentionReason ?? "process exited without a final result"
|
|
880
951
|
};
|
|
881
952
|
}
|
|
882
953
|
if (input.heartbeatBlocker) {
|
|
@@ -1393,6 +1464,8 @@ function buildPrompt(input) {
|
|
|
1393
1464
|
`Progress heartbeat file: ${input.heartbeatPath}`,
|
|
1394
1465
|
"After each major step, append one JSON line to the heartbeat file with fields: ts, phase, summary, changedFiles, blocker.",
|
|
1395
1466
|
"Final response must include files changed, verification commands, and unresolved risks.",
|
|
1467
|
+
"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.",
|
|
1468
|
+
"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.",
|
|
1396
1469
|
"",
|
|
1397
1470
|
...progressLines,
|
|
1398
1471
|
"",
|
|
@@ -1626,7 +1699,7 @@ async function tryCompleteWorker(args) {
|
|
|
1626
1699
|
let secret = await resolveCallbackSecretWithMint(explicitSecret, agentOsId, { baseUrl: base });
|
|
1627
1700
|
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/harness/completion`;
|
|
1628
1701
|
const body = {
|
|
1629
|
-
source: "
|
|
1702
|
+
source: "kynver-harness",
|
|
1630
1703
|
agentOsId,
|
|
1631
1704
|
runId: worker.runId,
|
|
1632
1705
|
workerName: worker.name,
|
|
@@ -1725,6 +1798,13 @@ function buildRunBoard(runId) {
|
|
|
1725
1798
|
baseCommit: run.baseCommit
|
|
1726
1799
|
});
|
|
1727
1800
|
const headCommit = status.gitAncestry.headIsAncestorOfBase === false && status.gitAncestry.head ? status.gitAncestry.head : void 0;
|
|
1801
|
+
const exitedSalvage = assessExitedWorkerSalvage({
|
|
1802
|
+
alive: status.alive,
|
|
1803
|
+
finalResult: status.finalResult,
|
|
1804
|
+
changedFiles: status.changedFiles,
|
|
1805
|
+
gitAncestry: status.gitAncestry,
|
|
1806
|
+
headCommit
|
|
1807
|
+
});
|
|
1728
1808
|
const rawBlocker = worker.completionBlocker;
|
|
1729
1809
|
const completionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
|
|
1730
1810
|
const boardStatus = completionBlocker ? "blocked" : status.status;
|
|
@@ -1735,6 +1815,9 @@ function buildRunBoard(runId) {
|
|
|
1735
1815
|
attention: boardAttention,
|
|
1736
1816
|
attentionReason: completionBlocker ?? status.attention.reason,
|
|
1737
1817
|
landingBlocked: status.finalResult ? boardAttention === "needs_attention" || boardAttention === "blocked" : false,
|
|
1818
|
+
exitedWithoutFinalResult: !status.finalResult && !status.alive,
|
|
1819
|
+
salvageState: exitedSalvage?.salvageable ? "review_needed" : "none",
|
|
1820
|
+
salvageReason: exitedSalvage?.salvageable ? exitedSalvage.attentionReason : void 0,
|
|
1738
1821
|
pid: status.pid,
|
|
1739
1822
|
alive: status.alive,
|
|
1740
1823
|
currentTool: status.currentTool,
|
|
@@ -2106,7 +2189,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
2106
2189
|
}
|
|
2107
2190
|
return worker;
|
|
2108
2191
|
}
|
|
2109
|
-
function startWorker(args) {
|
|
2192
|
+
async function startWorker(args) {
|
|
2110
2193
|
const run = loadRun(String(args.run));
|
|
2111
2194
|
const name = typeof args.name === "string" ? args.name.trim() : "";
|
|
2112
2195
|
if (!name) {
|
|
@@ -2118,8 +2201,10 @@ function startWorker(args) {
|
|
|
2118
2201
|
console.error("missing --task or --task-file");
|
|
2119
2202
|
process.exit(1);
|
|
2120
2203
|
}
|
|
2204
|
+
const wait = args.wait === true || args.wait === "true";
|
|
2205
|
+
let worker;
|
|
2121
2206
|
try {
|
|
2122
|
-
|
|
2207
|
+
worker = spawnWorkerProcess(run, {
|
|
2123
2208
|
name,
|
|
2124
2209
|
task,
|
|
2125
2210
|
ownedPaths: args.owned ? String(args.owned).split(",").map((s) => s.trim()).filter(Boolean) : [],
|
|
@@ -2147,6 +2232,18 @@ function startWorker(args) {
|
|
|
2147
2232
|
console.error(`worker start failed: ${error.message}`);
|
|
2148
2233
|
process.exit(1);
|
|
2149
2234
|
}
|
|
2235
|
+
if (!wait || !worker) return;
|
|
2236
|
+
const outcome = await autoCompleteWorker({
|
|
2237
|
+
run: String(args.run),
|
|
2238
|
+
name: worker.name,
|
|
2239
|
+
...worker.agentOsId ? { agentOsId: worker.agentOsId } : {},
|
|
2240
|
+
...args.baseUrl ? { baseUrl: String(args.baseUrl) } : {},
|
|
2241
|
+
...args.secret ? { secret: String(args.secret) } : {}
|
|
2242
|
+
});
|
|
2243
|
+
console.error(JSON.stringify({ event: "start_wait_outcome", ...outcome }));
|
|
2244
|
+
if (outcome.outcome === "timed_out") {
|
|
2245
|
+
process.exitCode = 1;
|
|
2246
|
+
}
|
|
2150
2247
|
}
|
|
2151
2248
|
|
|
2152
2249
|
// src/dispatch.ts
|
|
@@ -2170,7 +2267,7 @@ async function dispatchRun(args) {
|
|
|
2170
2267
|
const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, { baseUrl: base });
|
|
2171
2268
|
const execute = args.execute === true || args.execute === "true";
|
|
2172
2269
|
const dryRun = !execute;
|
|
2173
|
-
const leaseOwner = `
|
|
2270
|
+
const leaseOwner = `kynver-harness:${run.id}`;
|
|
2174
2271
|
const runnerDiskGate = args.diskPath ? observeRunnerDiskGate({ diskPath: String(args.diskPath) }) : observeRunnerDiskGate({ diskPath: run.repo });
|
|
2175
2272
|
const runnerResourceGate = observeRunnerResourceGate({ runId: run.id });
|
|
2176
2273
|
const requestedStarts = Number(args.maxStarts) > 0 ? Math.floor(Number(args.maxStarts)) : 1;
|
|
@@ -2335,7 +2432,7 @@ async function sweepRun(args) {
|
|
|
2335
2432
|
const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
|
|
2336
2433
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
2337
2434
|
const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, { baseUrl: base });
|
|
2338
|
-
const leaseOwner = `
|
|
2435
|
+
const leaseOwner = `kynver-harness:${run.id}`;
|
|
2339
2436
|
const snapshotPublished = await publishHarnessBoardSnapshot({ run: run.id, agentOsId, ...args }, "run_sweep");
|
|
2340
2437
|
const releasedLocalOrphans = [];
|
|
2341
2438
|
for (const name of Object.keys(run.workers || {})) {
|
|
@@ -2440,7 +2537,7 @@ function failExists(message) {
|
|
|
2440
2537
|
}
|
|
2441
2538
|
|
|
2442
2539
|
// src/pipeline-tick.ts
|
|
2443
|
-
import
|
|
2540
|
+
import path22 from "node:path";
|
|
2444
2541
|
|
|
2445
2542
|
// src/stale-reconcile.ts
|
|
2446
2543
|
import path15 from "node:path";
|
|
@@ -2643,19 +2740,445 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
2643
2740
|
}
|
|
2644
2741
|
}
|
|
2645
2742
|
|
|
2743
|
+
// src/cleanup.ts
|
|
2744
|
+
import path21 from "node:path";
|
|
2745
|
+
|
|
2746
|
+
// src/cleanup-types.ts
|
|
2747
|
+
var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
2748
|
+
var DEFAULT_WORKTREES_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
2749
|
+
|
|
2750
|
+
// src/cleanup-guards.ts
|
|
2751
|
+
var ACTIVE_RUN_STATUSES2 = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued", "needs_attention"]);
|
|
2752
|
+
var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
2753
|
+
function prUrlFromFinalResult(finalResult) {
|
|
2754
|
+
if (typeof finalResult === "string") {
|
|
2755
|
+
const match = finalResult.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/i);
|
|
2756
|
+
return match?.[0] ?? null;
|
|
2757
|
+
}
|
|
2758
|
+
if (finalResult && typeof finalResult === "object") {
|
|
2759
|
+
const obj = finalResult;
|
|
2760
|
+
for (const key of ["prUrl", "pr_url", "pullRequestUrl"]) {
|
|
2761
|
+
const value = obj[key];
|
|
2762
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
return null;
|
|
2766
|
+
}
|
|
2767
|
+
function isPrOrUnmergedWork(status) {
|
|
2768
|
+
if (prUrlFromFinalResult(status.finalResult)) return true;
|
|
2769
|
+
const relation = status.gitAncestry?.relation;
|
|
2770
|
+
if (relation === "ahead" || relation === "diverged") return true;
|
|
2771
|
+
if (status.changedFiles.length > 0 && status.finalResult) return true;
|
|
2772
|
+
return false;
|
|
2773
|
+
}
|
|
2774
|
+
function skipWorktreeRemoval(input) {
|
|
2775
|
+
const { indexed, includeOrphans, worktreesAgeMs, ageMs } = input;
|
|
2776
|
+
if (worktreesAgeMs <= 0) return "worktrees_disabled";
|
|
2777
|
+
if (ageMs < worktreesAgeMs) return "below_age_threshold";
|
|
2778
|
+
if (!indexed) return includeOrphans ? null : "orphan_without_flag";
|
|
2779
|
+
if (ACTIVE_RUN_STATUSES2.has(indexed.run.status)) return "run_still_active";
|
|
2780
|
+
if (!TERMINAL_RUN_STATUSES.has(indexed.run.status)) return "run_still_active";
|
|
2781
|
+
if (indexed.status.alive) return "active_worker";
|
|
2782
|
+
if (indexed.worker.status === "running") return "active_worker";
|
|
2783
|
+
if (indexed.worker.completionBlocker) return "completion_blocked";
|
|
2784
|
+
if (isPrOrUnmergedWork(indexed.status)) return "pr_or_unmerged_commits";
|
|
2785
|
+
if (indexed.status.changedFiles.length > 0) return "dirty_worktree";
|
|
2786
|
+
const landing = assessWorkerLanding({
|
|
2787
|
+
finalResult: indexed.status.finalResult,
|
|
2788
|
+
changedFiles: indexed.status.changedFiles,
|
|
2789
|
+
gitAncestry: indexed.status.gitAncestry,
|
|
2790
|
+
prUrl: prUrlFromFinalResult(indexed.status.finalResult)
|
|
2791
|
+
});
|
|
2792
|
+
if (landing.blocked) return "landing_blocked";
|
|
2793
|
+
return null;
|
|
2794
|
+
}
|
|
2795
|
+
function skipNodeModulesRemoval(input) {
|
|
2796
|
+
const { indexed, includeOrphans, nodeModulesAgeMs, ageMs } = input;
|
|
2797
|
+
if (ageMs < nodeModulesAgeMs) return "below_age_threshold";
|
|
2798
|
+
if (!indexed) return includeOrphans ? null : "orphan_without_flag";
|
|
2799
|
+
if (indexed.status.alive) return "active_worker";
|
|
2800
|
+
if (indexed.worker.status === "running") return "active_worker";
|
|
2801
|
+
if (indexed.worker.completionBlocker) return "completion_blocked";
|
|
2802
|
+
if (isPrOrUnmergedWork(indexed.status)) return "pr_or_unmerged_commits";
|
|
2803
|
+
if (indexed.status.changedFiles.length > 0) return "dirty_worktree";
|
|
2804
|
+
const landing = assessWorkerLanding({
|
|
2805
|
+
finalResult: indexed.status.finalResult,
|
|
2806
|
+
changedFiles: indexed.status.changedFiles,
|
|
2807
|
+
gitAncestry: indexed.status.gitAncestry,
|
|
2808
|
+
prUrl: prUrlFromFinalResult(indexed.status.finalResult)
|
|
2809
|
+
});
|
|
2810
|
+
if (landing.blocked) return "landing_blocked";
|
|
2811
|
+
return null;
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
// src/cleanup-execute.ts
|
|
2815
|
+
import { existsSync as existsSync13, rmSync } from "node:fs";
|
|
2816
|
+
import path18 from "node:path";
|
|
2817
|
+
|
|
2818
|
+
// src/cleanup-dir-size.ts
|
|
2819
|
+
import { existsSync as existsSync12, readdirSync as readdirSync4, statSync as statSync2 } from "node:fs";
|
|
2820
|
+
import path17 from "node:path";
|
|
2821
|
+
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
2822
|
+
if (!existsSync12(root)) return 0;
|
|
2823
|
+
let total = 0;
|
|
2824
|
+
let seen = 0;
|
|
2825
|
+
const stack = [root];
|
|
2826
|
+
while (stack.length > 0) {
|
|
2827
|
+
const current = stack.pop();
|
|
2828
|
+
let entries;
|
|
2829
|
+
try {
|
|
2830
|
+
entries = readdirSync4(current);
|
|
2831
|
+
} catch {
|
|
2832
|
+
continue;
|
|
2833
|
+
}
|
|
2834
|
+
for (const name of entries) {
|
|
2835
|
+
if (seen++ > maxEntries) return null;
|
|
2836
|
+
const full = path17.join(current, name);
|
|
2837
|
+
let st;
|
|
2838
|
+
try {
|
|
2839
|
+
st = statSync2(full);
|
|
2840
|
+
} catch {
|
|
2841
|
+
continue;
|
|
2842
|
+
}
|
|
2843
|
+
if (st.isDirectory()) stack.push(full);
|
|
2844
|
+
else total += st.size;
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2847
|
+
return total;
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
// src/cleanup-execute.ts
|
|
2851
|
+
function removeNodeModules(candidate, execute) {
|
|
2852
|
+
if (!existsSync13(candidate.path)) {
|
|
2853
|
+
return {
|
|
2854
|
+
...candidate,
|
|
2855
|
+
executed: false,
|
|
2856
|
+
skipped: true,
|
|
2857
|
+
skipReason: "missing_worktree"
|
|
2858
|
+
};
|
|
2859
|
+
}
|
|
2860
|
+
if (!execute) {
|
|
2861
|
+
return { ...candidate, executed: false, skipped: true, skipReason: "dry_run" };
|
|
2862
|
+
}
|
|
2863
|
+
try {
|
|
2864
|
+
const bytesBefore = candidate.bytes ?? directorySizeBytes(candidate.path);
|
|
2865
|
+
rmSync(candidate.path, { recursive: true, force: true });
|
|
2866
|
+
return {
|
|
2867
|
+
...candidate,
|
|
2868
|
+
bytes: bytesBefore,
|
|
2869
|
+
executed: true,
|
|
2870
|
+
skipped: false
|
|
2871
|
+
};
|
|
2872
|
+
} catch (error) {
|
|
2873
|
+
return {
|
|
2874
|
+
...candidate,
|
|
2875
|
+
executed: false,
|
|
2876
|
+
skipped: true,
|
|
2877
|
+
skipReason: "remove_failed",
|
|
2878
|
+
error: error.message
|
|
2879
|
+
};
|
|
2880
|
+
}
|
|
2881
|
+
}
|
|
2882
|
+
function removeWorktree(candidate, execute) {
|
|
2883
|
+
if (!existsSync13(candidate.path)) {
|
|
2884
|
+
return {
|
|
2885
|
+
...candidate,
|
|
2886
|
+
executed: false,
|
|
2887
|
+
skipped: true,
|
|
2888
|
+
skipReason: "missing_worktree"
|
|
2889
|
+
};
|
|
2890
|
+
}
|
|
2891
|
+
if (!execute) {
|
|
2892
|
+
return { ...candidate, executed: false, skipped: true, skipReason: "dry_run" };
|
|
2893
|
+
}
|
|
2894
|
+
const repo = candidate.repo;
|
|
2895
|
+
try {
|
|
2896
|
+
const bytesBefore = candidate.bytes ?? directorySizeBytes(candidate.path);
|
|
2897
|
+
if (repo) {
|
|
2898
|
+
git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
|
|
2899
|
+
}
|
|
2900
|
+
if (existsSync13(candidate.path)) {
|
|
2901
|
+
rmSync(candidate.path, { recursive: true, force: true });
|
|
2902
|
+
}
|
|
2903
|
+
return {
|
|
2904
|
+
...candidate,
|
|
2905
|
+
bytes: bytesBefore,
|
|
2906
|
+
executed: true,
|
|
2907
|
+
skipped: false
|
|
2908
|
+
};
|
|
2909
|
+
} catch (error) {
|
|
2910
|
+
return {
|
|
2911
|
+
...candidate,
|
|
2912
|
+
executed: false,
|
|
2913
|
+
skipped: true,
|
|
2914
|
+
skipReason: "remove_failed",
|
|
2915
|
+
error: error.message
|
|
2916
|
+
};
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
2920
|
+
const resolved = path18.resolve(targetPath);
|
|
2921
|
+
const nm = resolved.endsWith(`${path18.sep}node_modules`) ? resolved : null;
|
|
2922
|
+
if (!nm) return "path_outside_harness";
|
|
2923
|
+
const rel = path18.relative(worktreesDir, nm);
|
|
2924
|
+
if (rel.startsWith("..") || path18.isAbsolute(rel)) return "path_outside_harness";
|
|
2925
|
+
const parts = rel.split(path18.sep);
|
|
2926
|
+
if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
|
|
2927
|
+
if (!resolved.startsWith(path18.resolve(harnessRoot))) return "path_outside_harness";
|
|
2928
|
+
return null;
|
|
2929
|
+
}
|
|
2930
|
+
|
|
2931
|
+
// src/cleanup-scan.ts
|
|
2932
|
+
import { existsSync as existsSync14, readdirSync as readdirSync5, statSync as statSync3 } from "node:fs";
|
|
2933
|
+
import path19 from "node:path";
|
|
2934
|
+
function pathAgeMs(target, now) {
|
|
2935
|
+
try {
|
|
2936
|
+
const mtime = statSync3(target).mtimeMs;
|
|
2937
|
+
return Math.max(0, now - mtime);
|
|
2938
|
+
} catch {
|
|
2939
|
+
return 0;
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
function isPathInside(child, parent) {
|
|
2943
|
+
const rel = path19.relative(parent, child);
|
|
2944
|
+
return rel === "" || !rel.startsWith("..") && !path19.isAbsolute(rel);
|
|
2945
|
+
}
|
|
2946
|
+
function scanNodeModulesCandidates(opts) {
|
|
2947
|
+
const candidates = [];
|
|
2948
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2949
|
+
for (const entry of opts.index.values()) {
|
|
2950
|
+
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
2951
|
+
const nm = path19.join(entry.worktreePath, "node_modules");
|
|
2952
|
+
if (!existsSync14(nm)) continue;
|
|
2953
|
+
const resolved = path19.resolve(nm);
|
|
2954
|
+
if (seen.has(resolved)) continue;
|
|
2955
|
+
seen.add(resolved);
|
|
2956
|
+
candidates.push({
|
|
2957
|
+
kind: "remove_node_modules",
|
|
2958
|
+
path: resolved,
|
|
2959
|
+
bytes: null,
|
|
2960
|
+
runId: entry.runId,
|
|
2961
|
+
worker: entry.workerName,
|
|
2962
|
+
repo: entry.run.repo,
|
|
2963
|
+
ageMs: pathAgeMs(resolved, opts.now)
|
|
2964
|
+
});
|
|
2965
|
+
}
|
|
2966
|
+
if (!opts.includeOrphans || !existsSync14(opts.worktreesDir)) return candidates;
|
|
2967
|
+
for (const runEntry of readdirSync5(opts.worktreesDir, { withFileTypes: true })) {
|
|
2968
|
+
if (!runEntry.isDirectory()) continue;
|
|
2969
|
+
const runPath = path19.join(opts.worktreesDir, runEntry.name);
|
|
2970
|
+
for (const workerEntry of readdirSync5(runPath, { withFileTypes: true })) {
|
|
2971
|
+
if (!workerEntry.isDirectory()) continue;
|
|
2972
|
+
const worktreePath = path19.join(runPath, workerEntry.name);
|
|
2973
|
+
const nm = path19.join(worktreePath, "node_modules");
|
|
2974
|
+
if (!existsSync14(nm)) continue;
|
|
2975
|
+
const resolved = path19.resolve(nm);
|
|
2976
|
+
if (seen.has(resolved)) continue;
|
|
2977
|
+
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
2978
|
+
seen.add(resolved);
|
|
2979
|
+
candidates.push({
|
|
2980
|
+
kind: "remove_node_modules",
|
|
2981
|
+
path: resolved,
|
|
2982
|
+
bytes: null,
|
|
2983
|
+
runId: runEntry.name,
|
|
2984
|
+
worker: workerEntry.name,
|
|
2985
|
+
ageMs: pathAgeMs(resolved, opts.now)
|
|
2986
|
+
});
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
return candidates;
|
|
2990
|
+
}
|
|
2991
|
+
function scanWorktreeCandidates(opts) {
|
|
2992
|
+
if (opts.worktreesAgeMs <= 0) return [];
|
|
2993
|
+
const candidates = [];
|
|
2994
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2995
|
+
for (const entry of opts.index.values()) {
|
|
2996
|
+
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
2997
|
+
const resolved = entry.worktreePath;
|
|
2998
|
+
if (!existsSync14(resolved)) continue;
|
|
2999
|
+
if (seen.has(resolved)) continue;
|
|
3000
|
+
seen.add(resolved);
|
|
3001
|
+
candidates.push({
|
|
3002
|
+
kind: "remove_worktree",
|
|
3003
|
+
path: resolved,
|
|
3004
|
+
bytes: null,
|
|
3005
|
+
runId: entry.runId,
|
|
3006
|
+
worker: entry.workerName,
|
|
3007
|
+
repo: entry.run.repo,
|
|
3008
|
+
ageMs: pathAgeMs(resolved, opts.now)
|
|
3009
|
+
});
|
|
3010
|
+
}
|
|
3011
|
+
return candidates;
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
// src/cleanup-worktree-index.ts
|
|
3015
|
+
import path20 from "node:path";
|
|
3016
|
+
function buildWorktreeIndex() {
|
|
3017
|
+
const index = /* @__PURE__ */ new Map();
|
|
3018
|
+
for (const run of listRunRecords()) {
|
|
3019
|
+
for (const name of Object.keys(run.workers || {})) {
|
|
3020
|
+
const workerPath = path20.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
3021
|
+
const worker = readJson(workerPath, void 0);
|
|
3022
|
+
if (!worker?.worktreePath) continue;
|
|
3023
|
+
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
3024
|
+
index.set(path20.resolve(worker.worktreePath), {
|
|
3025
|
+
worktreePath: path20.resolve(worker.worktreePath),
|
|
3026
|
+
runId: run.id,
|
|
3027
|
+
workerName: name,
|
|
3028
|
+
run,
|
|
3029
|
+
worker,
|
|
3030
|
+
status
|
|
3031
|
+
});
|
|
3032
|
+
}
|
|
3033
|
+
}
|
|
3034
|
+
return index;
|
|
3035
|
+
}
|
|
3036
|
+
|
|
3037
|
+
// src/cleanup.ts
|
|
3038
|
+
function resolveOptions(options = {}) {
|
|
3039
|
+
const harnessRoot = options.harnessRoot ? path21.resolve(options.harnessRoot) : resolveHarnessRoot();
|
|
3040
|
+
const { worktreesDir } = options.harnessRoot ? { worktreesDir: path21.join(harnessRoot, "worktrees") } : getHarnessPaths();
|
|
3041
|
+
const execute = options.execute === true;
|
|
3042
|
+
const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
|
|
3043
|
+
const worktreesAgeMs = options.worktreesAgeMs ?? 0;
|
|
3044
|
+
const includeOrphans = options.includeOrphans === true;
|
|
3045
|
+
const runIdFilter = options.runIdFilter ? String(options.runIdFilter) : void 0;
|
|
3046
|
+
const now = options.now ?? Date.now();
|
|
3047
|
+
return {
|
|
3048
|
+
harnessRoot,
|
|
3049
|
+
worktreesDir,
|
|
3050
|
+
execute,
|
|
3051
|
+
dryRun: !execute,
|
|
3052
|
+
nodeModulesAgeMs,
|
|
3053
|
+
worktreesAgeMs,
|
|
3054
|
+
includeOrphans,
|
|
3055
|
+
runIdFilter,
|
|
3056
|
+
now
|
|
3057
|
+
};
|
|
3058
|
+
}
|
|
3059
|
+
function recordSkip(skips, pathValue, reason, detail) {
|
|
3060
|
+
skips.push({ path: pathValue, reason, ...detail ? { detail } : {} });
|
|
3061
|
+
}
|
|
3062
|
+
function runHarnessCleanup(options = {}) {
|
|
3063
|
+
const resolved = resolveOptions(options);
|
|
3064
|
+
const index = buildWorktreeIndex();
|
|
3065
|
+
const scanOpts = {
|
|
3066
|
+
harnessRoot: resolved.harnessRoot,
|
|
3067
|
+
worktreesDir: resolved.worktreesDir,
|
|
3068
|
+
nodeModulesAgeMs: resolved.nodeModulesAgeMs,
|
|
3069
|
+
worktreesAgeMs: resolved.worktreesAgeMs,
|
|
3070
|
+
includeOrphans: resolved.includeOrphans,
|
|
3071
|
+
runIdFilter: resolved.runIdFilter,
|
|
3072
|
+
index,
|
|
3073
|
+
now: resolved.now
|
|
3074
|
+
};
|
|
3075
|
+
const skips = [];
|
|
3076
|
+
const actions = [];
|
|
3077
|
+
for (const candidate of scanNodeModulesCandidates(scanOpts)) {
|
|
3078
|
+
const pathSkip = isHarnessNodeModulesPath(candidate.path, resolved.harnessRoot, resolved.worktreesDir);
|
|
3079
|
+
if (pathSkip) {
|
|
3080
|
+
recordSkip(skips, candidate.path, pathSkip);
|
|
3081
|
+
actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
|
|
3082
|
+
continue;
|
|
3083
|
+
}
|
|
3084
|
+
const worktreePath = path21.resolve(candidate.path, "..");
|
|
3085
|
+
const indexed = index.get(worktreePath) ?? null;
|
|
3086
|
+
const guardReason = skipNodeModulesRemoval({
|
|
3087
|
+
indexed,
|
|
3088
|
+
includeOrphans: resolved.includeOrphans,
|
|
3089
|
+
nodeModulesAgeMs: resolved.nodeModulesAgeMs,
|
|
3090
|
+
ageMs: candidate.ageMs
|
|
3091
|
+
});
|
|
3092
|
+
if (guardReason) {
|
|
3093
|
+
recordSkip(skips, candidate.path, guardReason);
|
|
3094
|
+
actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
|
|
3095
|
+
continue;
|
|
3096
|
+
}
|
|
3097
|
+
actions.push(removeNodeModules(candidate, resolved.execute));
|
|
3098
|
+
}
|
|
3099
|
+
for (const candidate of scanWorktreeCandidates(scanOpts)) {
|
|
3100
|
+
const indexed = index.get(path21.resolve(candidate.path)) ?? null;
|
|
3101
|
+
const guardReason = skipWorktreeRemoval({
|
|
3102
|
+
indexed,
|
|
3103
|
+
includeOrphans: resolved.includeOrphans,
|
|
3104
|
+
worktreesAgeMs: resolved.worktreesAgeMs,
|
|
3105
|
+
ageMs: candidate.ageMs
|
|
3106
|
+
});
|
|
3107
|
+
if (guardReason) {
|
|
3108
|
+
recordSkip(skips, candidate.path, guardReason);
|
|
3109
|
+
actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
|
|
3110
|
+
continue;
|
|
3111
|
+
}
|
|
3112
|
+
actions.push(removeWorktree(candidate, resolved.execute));
|
|
3113
|
+
}
|
|
3114
|
+
let candidateBytes = 0;
|
|
3115
|
+
let removedBytes = 0;
|
|
3116
|
+
let removedPaths = 0;
|
|
3117
|
+
let skippedPaths = 0;
|
|
3118
|
+
for (const action of actions) {
|
|
3119
|
+
if (action.bytes) candidateBytes += action.bytes;
|
|
3120
|
+
if (action.executed) {
|
|
3121
|
+
removedPaths += 1;
|
|
3122
|
+
removedBytes += action.bytes ?? 0;
|
|
3123
|
+
} else if (action.skipped) {
|
|
3124
|
+
skippedPaths += 1;
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
3127
|
+
return {
|
|
3128
|
+
harnessRoot: resolved.harnessRoot,
|
|
3129
|
+
dryRun: resolved.dryRun,
|
|
3130
|
+
execute: resolved.execute,
|
|
3131
|
+
nodeModulesAgeMs: resolved.nodeModulesAgeMs,
|
|
3132
|
+
worktreesAgeMs: resolved.worktreesAgeMs,
|
|
3133
|
+
includeOrphans: resolved.includeOrphans,
|
|
3134
|
+
scannedAt: new Date(resolved.now).toISOString(),
|
|
3135
|
+
actions,
|
|
3136
|
+
skips,
|
|
3137
|
+
totals: {
|
|
3138
|
+
candidateBytes,
|
|
3139
|
+
removedBytes,
|
|
3140
|
+
removedPaths,
|
|
3141
|
+
skippedPaths
|
|
3142
|
+
}
|
|
3143
|
+
};
|
|
3144
|
+
}
|
|
3145
|
+
function runPipelineHarnessCleanup(runId) {
|
|
3146
|
+
const nodeModulesAgeMs = Number(process.env.KYNVER_CLEANUP_NODE_MODULES_AGE_MS) || DEFAULT_NODE_MODULES_AGE_MS;
|
|
3147
|
+
const worktreesAgeMs = Number(process.env.KYNVER_CLEANUP_WORKTREES_AGE_MS) || 0;
|
|
3148
|
+
const execute = process.env.KYNVER_CLEANUP_EXECUTE === "1";
|
|
3149
|
+
const includeOrphans = process.env.KYNVER_CLEANUP_INCLUDE_ORPHANS === "1";
|
|
3150
|
+
const scopeAll = process.env.KYNVER_CLEANUP_SCOPE === "all";
|
|
3151
|
+
return runHarnessCleanup({
|
|
3152
|
+
execute,
|
|
3153
|
+
nodeModulesAgeMs,
|
|
3154
|
+
worktreesAgeMs,
|
|
3155
|
+
includeOrphans,
|
|
3156
|
+
runIdFilter: scopeAll ? void 0 : runId
|
|
3157
|
+
});
|
|
3158
|
+
}
|
|
3159
|
+
function isPipelineCleanupEnabled() {
|
|
3160
|
+
return process.env.KYNVER_PIPELINE_CLEANUP !== "0";
|
|
3161
|
+
}
|
|
3162
|
+
|
|
2646
3163
|
// src/pipeline-tick.ts
|
|
2647
3164
|
async function completeFinishedWorkers(runId, args) {
|
|
2648
3165
|
const run = loadRun(runId);
|
|
2649
3166
|
const outcomes = [];
|
|
2650
3167
|
for (const name of Object.keys(run.workers || {})) {
|
|
2651
3168
|
const worker = readJson(
|
|
2652
|
-
|
|
3169
|
+
path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2653
3170
|
void 0
|
|
2654
3171
|
);
|
|
2655
3172
|
if (!worker?.taskId) continue;
|
|
2656
3173
|
const status = computeWorkerStatus(worker);
|
|
2657
3174
|
if (!isFinishedWorkerStatus(status)) continue;
|
|
2658
|
-
|
|
3175
|
+
const exitedSalvage = assessExitedWorkerSalvage({
|
|
3176
|
+
alive: status.alive,
|
|
3177
|
+
finalResult: status.finalResult,
|
|
3178
|
+
changedFiles: status.changedFiles,
|
|
3179
|
+
gitAncestry: status.gitAncestry
|
|
3180
|
+
});
|
|
3181
|
+
if (!worker.dispatched && !status.finalResult && !exitedSalvage?.salvageable) continue;
|
|
2659
3182
|
const result = await tryCompleteWorker({
|
|
2660
3183
|
run: runId,
|
|
2661
3184
|
name,
|
|
@@ -2692,6 +3215,7 @@ async function runPipelineTick(args) {
|
|
|
2692
3215
|
const operatorTick = await postOperatorTick(agentOsId, runId, resourceGate, args);
|
|
2693
3216
|
const completedWorkers = await completeFinishedWorkers(runId, args);
|
|
2694
3217
|
const staleReconcile = reconcileStaleWorkers();
|
|
3218
|
+
const harnessCleanup = isPipelineCleanupEnabled() ? runPipelineHarnessCleanup(runId) : void 0;
|
|
2695
3219
|
const planProgressSync = await syncActiveWorkerPlanProgress(runId, args);
|
|
2696
3220
|
let maxStarts = resourceGate.slotsAvailable;
|
|
2697
3221
|
const tickBody = operatorTick;
|
|
@@ -2727,6 +3251,7 @@ async function runPipelineTick(args) {
|
|
|
2727
3251
|
resourceGate,
|
|
2728
3252
|
completedWorkers,
|
|
2729
3253
|
staleReconcile,
|
|
3254
|
+
harnessCleanup,
|
|
2730
3255
|
planProgressSync,
|
|
2731
3256
|
operatorTick,
|
|
2732
3257
|
sweep,
|
|
@@ -2865,6 +3390,26 @@ async function verifyPlan(args) {
|
|
|
2865
3390
|
console.log(JSON.stringify(parsed, null, 2));
|
|
2866
3391
|
}
|
|
2867
3392
|
|
|
3393
|
+
// src/cleanup-cli.ts
|
|
3394
|
+
function runCleanupCli(args) {
|
|
3395
|
+
const execute = args.execute === true || args.execute === "true";
|
|
3396
|
+
const nodeModulesAgeMs = args.nodeModulesAgeMs ? Number(args.nodeModulesAgeMs) : DEFAULT_NODE_MODULES_AGE_MS;
|
|
3397
|
+
const worktreesAgeMs = args.worktreesAgeMs ? Number(args.worktreesAgeMs) : 0;
|
|
3398
|
+
const includeOrphans = args.includeOrphans === true || args.includeOrphans === "true";
|
|
3399
|
+
const harnessRoot = args.harnessRoot ? String(args.harnessRoot) : void 0;
|
|
3400
|
+
const summary = runHarnessCleanup({
|
|
3401
|
+
execute,
|
|
3402
|
+
nodeModulesAgeMs: Number.isFinite(nodeModulesAgeMs) ? nodeModulesAgeMs : DEFAULT_NODE_MODULES_AGE_MS,
|
|
3403
|
+
worktreesAgeMs: Number.isFinite(worktreesAgeMs) ? worktreesAgeMs : 0,
|
|
3404
|
+
includeOrphans,
|
|
3405
|
+
harnessRoot
|
|
3406
|
+
});
|
|
3407
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
3408
|
+
if (execute && summary.totals.removedPaths === 0 && summary.actions.length === 0) {
|
|
3409
|
+
process.exitCode = 0;
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
|
|
2868
3413
|
// src/cli.ts
|
|
2869
3414
|
function isHelpFlag(arg) {
|
|
2870
3415
|
return arg === "help" || arg === "--help" || arg === "-h";
|
|
@@ -2888,14 +3433,15 @@ function usage(code = 0) {
|
|
|
2888
3433
|
" kynver run status --run RUN_ID",
|
|
2889
3434
|
" 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 /]",
|
|
2890
3435
|
" kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
|
|
2891
|
-
' 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]',
|
|
3436
|
+
' 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]',
|
|
2892
3437
|
" kynver worker status --run RUN_ID --name worker",
|
|
2893
3438
|
" kynver worker tail --run RUN_ID --name worker [--lines 40] [--raw]",
|
|
2894
3439
|
" kynver worker stop --run RUN_ID --name worker",
|
|
2895
3440
|
" kynver worker complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--task-id TASK_ID] [--base-url URL] [--secret SECRET]",
|
|
2896
3441
|
" 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]",
|
|
2897
3442
|
" 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]",
|
|
2898
|
-
" kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override]"
|
|
3443
|
+
" kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override]",
|
|
3444
|
+
" kynver cleanup [--execute] [--node-modules-age-ms MS] [--worktrees-age-ms MS] [--harness-root PATH] [--include-orphans]"
|
|
2899
3445
|
].join("\n")
|
|
2900
3446
|
);
|
|
2901
3447
|
process.exit(code);
|
|
@@ -2922,12 +3468,13 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
2922
3468
|
if (scope === "daemon") return void await runDaemon(args);
|
|
2923
3469
|
if (scope === "plan" && action === "progress") return void await emitPlanProgress(args);
|
|
2924
3470
|
if (scope === "plan" && action === "verify") return void await verifyPlan(args);
|
|
3471
|
+
if (scope === "cleanup") return runCleanupCli(args);
|
|
2925
3472
|
if (scope === "run" && action === "create") return createRun(args);
|
|
2926
3473
|
if (scope === "run" && action === "list") return listRuns();
|
|
2927
3474
|
if (scope === "run" && action === "status") return runStatus(args);
|
|
2928
3475
|
if (scope === "run" && action === "dispatch") return void await dispatchRun(args);
|
|
2929
3476
|
if (scope === "run" && action === "sweep") return void await sweepRun(args);
|
|
2930
|
-
if (scope === "worker" && action === "start") return startWorker(args);
|
|
3477
|
+
if (scope === "worker" && action === "start") return void await startWorker(args);
|
|
2931
3478
|
if (scope === "worker" && action === "status") return workerStatus(args);
|
|
2932
3479
|
if (scope === "worker" && action === "tail") return tailWorker(args);
|
|
2933
3480
|
if (scope === "worker" && action === "stop") return stopWorker(args);
|