@kynver-app/runtime 0.1.99 → 0.1.102
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 +616 -196
- package/dist/cli.js.map +4 -4
- package/dist/harness-worker-active.d.ts +12 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +611 -187
- package/dist/index.js.map +4 -4
- package/dist/lane/landing-maintainer-local.d.ts +15 -0
- package/dist/lane/landing-maintainer-tick.d.ts +16 -0
- package/dist/lane/lane-spec.d.ts +8 -0
- package/dist/lane/lane-tick-cli.d.ts +1 -0
- package/dist/local-pr-attention-reconcile.d.ts +25 -0
- package/dist/resource-gate.d.ts +2 -7
- package/dist/run-resolve.d.ts +4 -0
- package/dist/stale-reconcile.d.ts +2 -0
- package/dist/status.d.ts +1 -0
- package/dist/util.d.ts +2 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -119,6 +119,9 @@ function readMaybeFile(file) {
|
|
|
119
119
|
function sleepMs(ms) {
|
|
120
120
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
121
121
|
}
|
|
122
|
+
function sleepMsAsync(ms) {
|
|
123
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
124
|
+
}
|
|
122
125
|
function isPidAlive(pid) {
|
|
123
126
|
if (!pid) return false;
|
|
124
127
|
try {
|
|
@@ -803,7 +806,6 @@ function readMemAvailableBytes(meminfoText) {
|
|
|
803
806
|
|
|
804
807
|
// src/resource-gate.ts
|
|
805
808
|
import path9 from "node:path";
|
|
806
|
-
import { readFileSync as readFileSync9 } from "node:fs";
|
|
807
809
|
|
|
808
810
|
// src/disk-gate.ts
|
|
809
811
|
import { statfsSync as statfsSync2 } from "node:fs";
|
|
@@ -828,23 +830,23 @@ function isWslHost() {
|
|
|
828
830
|
function observeWslHostDisk(options = {}) {
|
|
829
831
|
const wsl = options.forceWsl === void 0 ? isWslHost() : options.forceWsl;
|
|
830
832
|
if (!wsl) return null;
|
|
831
|
-
const
|
|
833
|
+
const path73 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
|
|
832
834
|
const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;
|
|
833
835
|
const criticalBelowBytes = options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;
|
|
834
836
|
const statfs = options.statfs ?? statfsSync;
|
|
835
837
|
let stats;
|
|
836
838
|
try {
|
|
837
|
-
stats = statfs(
|
|
839
|
+
stats = statfs(path73);
|
|
838
840
|
} catch (error) {
|
|
839
841
|
return {
|
|
840
842
|
ok: false,
|
|
841
|
-
path:
|
|
843
|
+
path: path73,
|
|
842
844
|
freeBytes: 0,
|
|
843
845
|
totalBytes: 0,
|
|
844
846
|
usedPercent: 100,
|
|
845
847
|
warnBelowBytes,
|
|
846
848
|
criticalBelowBytes,
|
|
847
|
-
reason: `Windows host disk probe failed at ${
|
|
849
|
+
reason: `Windows host disk probe failed at ${path73}: ${error.message}`,
|
|
848
850
|
probeError: error.message
|
|
849
851
|
};
|
|
850
852
|
}
|
|
@@ -858,11 +860,11 @@ function observeWslHostDisk(options = {}) {
|
|
|
858
860
|
let reason = null;
|
|
859
861
|
if (!ok) {
|
|
860
862
|
const tag = criticalFree ? "critical" : "warning";
|
|
861
|
-
reason = `Windows host disk ${
|
|
863
|
+
reason = `Windows host disk ${path73} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
|
|
862
864
|
}
|
|
863
865
|
return {
|
|
864
866
|
ok,
|
|
865
|
-
path:
|
|
867
|
+
path: path73,
|
|
866
868
|
freeBytes,
|
|
867
869
|
totalBytes,
|
|
868
870
|
usedPercent,
|
|
@@ -882,12 +884,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
|
882
884
|
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
883
885
|
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
884
886
|
function observeRunnerDiskGate(input = {}) {
|
|
885
|
-
const
|
|
887
|
+
const path73 = input.diskPath?.trim() || "/";
|
|
886
888
|
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
887
889
|
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
888
890
|
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
889
891
|
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
890
|
-
const stats = statfsSync2(
|
|
892
|
+
const stats = statfsSync2(path73);
|
|
891
893
|
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
892
894
|
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
893
895
|
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
@@ -910,7 +912,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
910
912
|
}
|
|
911
913
|
return {
|
|
912
914
|
ok,
|
|
913
|
-
path:
|
|
915
|
+
path: path73,
|
|
914
916
|
freeBytes,
|
|
915
917
|
totalBytes,
|
|
916
918
|
usedPercent,
|
|
@@ -1044,6 +1046,9 @@ function listRunWorkerNames(run) {
|
|
|
1044
1046
|
return [...names];
|
|
1045
1047
|
}
|
|
1046
1048
|
|
|
1049
|
+
// src/harness-worker-active.ts
|
|
1050
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
1051
|
+
|
|
1047
1052
|
// src/heartbeat.ts
|
|
1048
1053
|
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
|
|
1049
1054
|
|
|
@@ -2127,6 +2132,9 @@ function computeAttention(input) {
|
|
|
2127
2132
|
return { state: "blocked", reason: input.completionBlocker };
|
|
2128
2133
|
}
|
|
2129
2134
|
if (input.finalResult) {
|
|
2135
|
+
if (input.localOnly && hasMergedTargetPrReconciliation(input.finalResult)) {
|
|
2136
|
+
return { state: "done", reason: "local-only worker superseded by merged PR" };
|
|
2137
|
+
}
|
|
2130
2138
|
const landingSnapshot = {
|
|
2131
2139
|
finalResult: input.finalResult,
|
|
2132
2140
|
changedFiles: input.changedFiles ?? [],
|
|
@@ -2192,9 +2200,24 @@ function computeAttention(input) {
|
|
|
2192
2200
|
}
|
|
2193
2201
|
return { state: "ok", reason: "recent activity" };
|
|
2194
2202
|
}
|
|
2203
|
+
function hasMergedTargetPrReconciliation(value) {
|
|
2204
|
+
let record3 = null;
|
|
2205
|
+
if (typeof value === "string") record3 = extractEmbeddedWorkerFinalResultRecord(value);
|
|
2206
|
+
else if (value && typeof value === "object" && !Array.isArray(value)) record3 = value;
|
|
2207
|
+
if (!record3) return false;
|
|
2208
|
+
const raw = record3.targetPrReconciliation ?? record3.target_pr_reconciliation;
|
|
2209
|
+
if (!Array.isArray(raw)) return false;
|
|
2210
|
+
return raw.some((item) => {
|
|
2211
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return false;
|
|
2212
|
+
return String(item.outcome ?? "").trim() === "merged";
|
|
2213
|
+
});
|
|
2214
|
+
}
|
|
2195
2215
|
function resolveFinalResult(worker, parsedFinalResult, heartbeat) {
|
|
2196
|
-
if (parsedFinalResult) return parsedFinalResult;
|
|
2197
2216
|
const ackSnapshot = worker.completionSnapshot?.finalResult;
|
|
2217
|
+
if (worker.completionAckSource === "local-pr-merged-reconcile" && ackSnapshot !== void 0 && ackSnapshot !== null) {
|
|
2218
|
+
return ackSnapshot;
|
|
2219
|
+
}
|
|
2220
|
+
if (parsedFinalResult) return parsedFinalResult;
|
|
2198
2221
|
if (ackSnapshot !== void 0 && ackSnapshot !== null) return ackSnapshot;
|
|
2199
2222
|
return terminalFinalResultFromHeartbeat(heartbeat);
|
|
2200
2223
|
}
|
|
@@ -2241,7 +2264,8 @@ function computeWorkerStatus(worker, options = {}) {
|
|
|
2241
2264
|
gitAncestry,
|
|
2242
2265
|
completionBlocker,
|
|
2243
2266
|
landingContract,
|
|
2244
|
-
prUrl: worker.repairTargetPrUrl ?? worker.taskPrUrl ?? null
|
|
2267
|
+
prUrl: worker.repairTargetPrUrl ?? worker.taskPrUrl ?? null,
|
|
2268
|
+
localOnly: worker.localOnly === true
|
|
2245
2269
|
});
|
|
2246
2270
|
const workerStatusLabel = completionBlocker || attention.state === "blocked" ? "blocked" : completionAcknowledged || attention.state === "done" ? "done" : finalResult ? "exited" : alive ? "running" : "exited";
|
|
2247
2271
|
return {
|
|
@@ -2295,6 +2319,33 @@ function deriveRunStatus(fallback, workers) {
|
|
|
2295
2319
|
return fallback;
|
|
2296
2320
|
}
|
|
2297
2321
|
|
|
2322
|
+
// src/harness-worker-active.ts
|
|
2323
|
+
function pidCommandLine(pid) {
|
|
2324
|
+
if (!pid || process.platform !== "linux") return null;
|
|
2325
|
+
try {
|
|
2326
|
+
return readFileSync9(`/proc/${pid}/cmdline`, "utf8").replace(/\0/g, " ");
|
|
2327
|
+
} catch {
|
|
2328
|
+
return null;
|
|
2329
|
+
}
|
|
2330
|
+
}
|
|
2331
|
+
function workerProcessMatchesRecord(worker) {
|
|
2332
|
+
if (!worker.pid || process.platform !== "linux") return true;
|
|
2333
|
+
const cmdline = pidCommandLine(worker.pid);
|
|
2334
|
+
if (!cmdline) return false;
|
|
2335
|
+
const probes = [worker.worktreePath, worker.workerDir, worker.heartbeatPath].filter(
|
|
2336
|
+
(value) => typeof value === "string" && value.trim().length > 0
|
|
2337
|
+
);
|
|
2338
|
+
return probes.some((probe) => cmdline.includes(probe));
|
|
2339
|
+
}
|
|
2340
|
+
function isActiveHarnessWorker(worker) {
|
|
2341
|
+
if (typeof worker.completionBlocker === "string" && worker.completionBlocker.trim()) {
|
|
2342
|
+
return false;
|
|
2343
|
+
}
|
|
2344
|
+
const status = computeWorkerStatus(worker);
|
|
2345
|
+
if (status.alive && !workerProcessMatchesRecord(worker)) return false;
|
|
2346
|
+
return status.alive && !status.finalResult && status.attention.state !== "done";
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2298
2349
|
// src/resource-gate.ts
|
|
2299
2350
|
var DEFAULT_PER_WORKER_MEM_BYTES = 500 * 1024 * 1024;
|
|
2300
2351
|
var DEFAULT_MEM_RESERVE_BYTES = 4 * 1024 * 1024 * 1024;
|
|
@@ -2337,31 +2388,6 @@ function computeAutoMaxWorkers(totalMemBytes, opts = {}) {
|
|
|
2337
2388
|
function readAvailableMemBytes() {
|
|
2338
2389
|
return readMemAvailableBytes();
|
|
2339
2390
|
}
|
|
2340
|
-
function pidCommandLine(pid) {
|
|
2341
|
-
if (!pid || process.platform !== "linux") return null;
|
|
2342
|
-
try {
|
|
2343
|
-
return readFileSync9(`/proc/${pid}/cmdline`, "utf8").replace(/\0/g, " ");
|
|
2344
|
-
} catch {
|
|
2345
|
-
return null;
|
|
2346
|
-
}
|
|
2347
|
-
}
|
|
2348
|
-
function workerProcessMatchesRecord(worker) {
|
|
2349
|
-
if (!worker.pid || process.platform !== "linux") return true;
|
|
2350
|
-
const cmdline = pidCommandLine(worker.pid);
|
|
2351
|
-
if (!cmdline) return false;
|
|
2352
|
-
const probes = [worker.worktreePath, worker.workerDir, worker.heartbeatPath].filter(
|
|
2353
|
-
(value) => typeof value === "string" && value.trim().length > 0
|
|
2354
|
-
);
|
|
2355
|
-
return probes.some((probe) => cmdline.includes(probe));
|
|
2356
|
-
}
|
|
2357
|
-
function isActiveHarnessWorker(worker) {
|
|
2358
|
-
if (typeof worker.completionBlocker === "string" && worker.completionBlocker.trim()) {
|
|
2359
|
-
return false;
|
|
2360
|
-
}
|
|
2361
|
-
const status = computeWorkerStatus(worker);
|
|
2362
|
-
if (status.alive && !workerProcessMatchesRecord(worker)) return false;
|
|
2363
|
-
return status.alive && !status.finalResult && status.attention.state !== "done";
|
|
2364
|
-
}
|
|
2365
2391
|
function countActiveWorkersForRun(run) {
|
|
2366
2392
|
let active = 0;
|
|
2367
2393
|
for (const name of listRunWorkerNames(run)) {
|
|
@@ -5408,8 +5434,8 @@ function dirtyPathsCoveredByDisposableRemoval(changedFiles, removed) {
|
|
|
5408
5434
|
if (removed.length === 0) return false;
|
|
5409
5435
|
const removedSet = new Set(removed.map((p) => normalizeRelativePath(p)));
|
|
5410
5436
|
return material.every((line) => {
|
|
5411
|
-
const
|
|
5412
|
-
return removedSet.has(
|
|
5437
|
+
const path73 = normalizeRelativePath(pathFromGitStatusLine(line));
|
|
5438
|
+
return removedSet.has(path73);
|
|
5413
5439
|
});
|
|
5414
5440
|
}
|
|
5415
5441
|
|
|
@@ -6717,7 +6743,7 @@ function collectRunActiveHarnessWorkers(runId) {
|
|
|
6717
6743
|
path24.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
6718
6744
|
void 0
|
|
6719
6745
|
);
|
|
6720
|
-
if (!worker?.taskId || !
|
|
6746
|
+
if (!worker?.taskId || !isActiveHarnessWorker(worker)) continue;
|
|
6721
6747
|
out.push({
|
|
6722
6748
|
runId: run.id,
|
|
6723
6749
|
workerName: name,
|
|
@@ -7578,7 +7604,7 @@ async function dispatchRun(args) {
|
|
|
7578
7604
|
);
|
|
7579
7605
|
}
|
|
7580
7606
|
const attempt = Number(task.attempt) || 1;
|
|
7581
|
-
if (attempt
|
|
7607
|
+
if (attempt > retryLimits.maxTaskAttempts) {
|
|
7582
7608
|
return abortClaimedSpawn(
|
|
7583
7609
|
task,
|
|
7584
7610
|
`task attempt ${attempt} exceeds KYNVER_MAX_TASK_ATTEMPTS (${retryLimits.maxTaskAttempts})`
|
|
@@ -7714,7 +7740,12 @@ async function dispatchRun(args) {
|
|
|
7714
7740
|
const admissionExhaustion = readAdmissionExhaustion(result);
|
|
7715
7741
|
const capacityIdle = admissionExhaustion?.capacityIdle === true || startedCount === 0 && Number(result.resourceGate?.slotsAvailable) > 0;
|
|
7716
7742
|
if (capacityIdle && admissionExhaustion?.summary) {
|
|
7717
|
-
|
|
7743
|
+
const retryCeiling = admissionExhaustion.skipReasonCounts?.retry_ceiling_exceeded ?? 0;
|
|
7744
|
+
const recovery = admissionExhaustion.overAttemptIdleRecovery;
|
|
7745
|
+
const recoveryNote = recovery?.attempted === true ? `; over_attempt_recovery minted=${recovery.minted ?? 0} started=${recovery.started ?? 0}` : retryCeiling > 0 ? "; over_attempt_recovery not attempted" : "";
|
|
7746
|
+
console.error(
|
|
7747
|
+
`[dispatch] ${admissionExhaustion.summary}${retryCeiling > 0 ? `; retry_ceiling_exceeded=${retryCeiling}` : ""}${recoveryNote}`
|
|
7748
|
+
);
|
|
7718
7749
|
}
|
|
7719
7750
|
const summary = {
|
|
7720
7751
|
runId: run.id,
|
|
@@ -8299,14 +8330,14 @@ function applyProductionDatabaseToProcess(options = {}) {
|
|
|
8299
8330
|
|
|
8300
8331
|
// src/worktree.ts
|
|
8301
8332
|
import { existsSync as existsSync29, mkdirSync as mkdirSync6 } from "node:fs";
|
|
8302
|
-
import
|
|
8333
|
+
import path38 from "node:path";
|
|
8303
8334
|
|
|
8304
8335
|
// src/run-list.ts
|
|
8305
8336
|
import { existsSync as existsSync28, readFileSync as readFileSync15 } from "node:fs";
|
|
8306
|
-
import
|
|
8337
|
+
import path37 from "node:path";
|
|
8307
8338
|
|
|
8308
8339
|
// src/stale-reconcile.ts
|
|
8309
|
-
import
|
|
8340
|
+
import path36 from "node:path";
|
|
8310
8341
|
|
|
8311
8342
|
// src/finalize.ts
|
|
8312
8343
|
import path30 from "node:path";
|
|
@@ -8941,6 +8972,193 @@ function reconcileWorkerMetadataCli() {
|
|
|
8941
8972
|
);
|
|
8942
8973
|
}
|
|
8943
8974
|
|
|
8975
|
+
// src/local-pr-attention-reconcile.ts
|
|
8976
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
8977
|
+
import path35 from "node:path";
|
|
8978
|
+
function normalizePrUrl3(url) {
|
|
8979
|
+
const m = url.trim().match(/github\.com\/([^/]+\/[^/]+)\/(?:pull|pulls)\/(\d+)/i);
|
|
8980
|
+
if (!m) return null;
|
|
8981
|
+
return `https://github.com/${m[1]}/pull/${m[2]}`;
|
|
8982
|
+
}
|
|
8983
|
+
function prNumberFromUrl(url) {
|
|
8984
|
+
const m = normalizePrUrl3(url)?.match(/\/pull\/(\d+)$/);
|
|
8985
|
+
if (!m) return null;
|
|
8986
|
+
const n = Number.parseInt(m[1], 10);
|
|
8987
|
+
return Number.isFinite(n) ? n : null;
|
|
8988
|
+
}
|
|
8989
|
+
function extractText(value) {
|
|
8990
|
+
if (value == null) return "";
|
|
8991
|
+
if (typeof value === "string") return value;
|
|
8992
|
+
try {
|
|
8993
|
+
return JSON.stringify(value);
|
|
8994
|
+
} catch {
|
|
8995
|
+
return "";
|
|
8996
|
+
}
|
|
8997
|
+
}
|
|
8998
|
+
function extractPrNumbersFromText(text) {
|
|
8999
|
+
const out = /* @__PURE__ */ new Set();
|
|
9000
|
+
for (const match of text.matchAll(/github\.com\/[^/\s)>"']+\/[^/\s)>"']+\/(?:pull|pulls)\/(\d+)/gi)) {
|
|
9001
|
+
const n = Number.parseInt(match[1], 10);
|
|
9002
|
+
if (Number.isFinite(n)) out.add(n);
|
|
9003
|
+
}
|
|
9004
|
+
for (const match of text.matchAll(/\bPR\s*#?\s*(\d{2,})\b/gi)) {
|
|
9005
|
+
const n = Number.parseInt(match[1], 10);
|
|
9006
|
+
if (Number.isFinite(n)) out.add(n);
|
|
9007
|
+
}
|
|
9008
|
+
for (const match of text.matchAll(/\bpr[-_]?(\d{2,})\b/gi)) {
|
|
9009
|
+
const n = Number.parseInt(match[1], 10);
|
|
9010
|
+
if (Number.isFinite(n)) out.add(n);
|
|
9011
|
+
}
|
|
9012
|
+
return [...out];
|
|
9013
|
+
}
|
|
9014
|
+
function extractTargetPrReconciliation(value) {
|
|
9015
|
+
let record3 = null;
|
|
9016
|
+
if (typeof value === "string") record3 = extractEmbeddedWorkerFinalResultRecord(value);
|
|
9017
|
+
else if (value && typeof value === "object" && !Array.isArray(value)) record3 = value;
|
|
9018
|
+
if (!record3) return [];
|
|
9019
|
+
const raw = record3.targetPrReconciliation ?? record3.target_pr_reconciliation;
|
|
9020
|
+
if (!Array.isArray(raw)) return [];
|
|
9021
|
+
const out = [];
|
|
9022
|
+
for (const item of raw) {
|
|
9023
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
9024
|
+
const row = item;
|
|
9025
|
+
const prUrl = normalizePrUrl3(String(row.prUrl ?? row.pr_url ?? ""));
|
|
9026
|
+
const outcome = String(row.outcome ?? "").trim();
|
|
9027
|
+
if (!prUrl || outcome !== "merged") continue;
|
|
9028
|
+
out.push({
|
|
9029
|
+
prUrl,
|
|
9030
|
+
mergeCommit: typeof row.mergeCommit === "string" ? row.mergeCommit : typeof row.merge_commit === "string" ? row.merge_commit : null,
|
|
9031
|
+
reason: typeof row.reason === "string" ? row.reason : null
|
|
9032
|
+
});
|
|
9033
|
+
}
|
|
9034
|
+
return out;
|
|
9035
|
+
}
|
|
9036
|
+
function defaultLookupPr(input) {
|
|
9037
|
+
try {
|
|
9038
|
+
const repo = execFileSync2("git", ["config", "--get", "remote.origin.url"], {
|
|
9039
|
+
cwd: input.repoDir,
|
|
9040
|
+
encoding: "utf8",
|
|
9041
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
9042
|
+
}).trim();
|
|
9043
|
+
const repoMatch = repo.match(/github\.com[:/]([^/]+\/[^/.]+)(?:\.git)?$/i);
|
|
9044
|
+
const repoSlug = repoMatch?.[1];
|
|
9045
|
+
if (!repoSlug) return null;
|
|
9046
|
+
const raw = execFileSync2(
|
|
9047
|
+
"gh",
|
|
9048
|
+
["pr", "view", String(input.prNumber), "--repo", repoSlug, "--json", "state,mergedAt,mergeCommit,url"],
|
|
9049
|
+
{ cwd: input.repoDir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
9050
|
+
);
|
|
9051
|
+
const parsed = JSON.parse(raw);
|
|
9052
|
+
return {
|
|
9053
|
+
prUrl: normalizePrUrl3(parsed.url ?? `https://github.com/${repoSlug}/pull/${input.prNumber}`) ?? `https://github.com/${repoSlug}/pull/${input.prNumber}`,
|
|
9054
|
+
state: parsed.state ?? "",
|
|
9055
|
+
mergedAt: parsed.mergedAt ?? null,
|
|
9056
|
+
mergeCommit: parsed.mergeCommit?.oid ?? null
|
|
9057
|
+
};
|
|
9058
|
+
} catch {
|
|
9059
|
+
return null;
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9062
|
+
function finalResultForWorker(worker) {
|
|
9063
|
+
const heartbeat = parseHeartbeat(worker.heartbeatPath);
|
|
9064
|
+
return worker.completionSnapshot?.finalResult ?? terminalFinalResultFromHeartbeat(heartbeat);
|
|
9065
|
+
}
|
|
9066
|
+
function collectMergedEvidenceByPr(run) {
|
|
9067
|
+
const byPr = /* @__PURE__ */ new Map();
|
|
9068
|
+
const runDir2 = runDirectory(run.id);
|
|
9069
|
+
for (const name of listRunWorkerNames(run)) {
|
|
9070
|
+
const worker = readJson(
|
|
9071
|
+
path35.join(runDir2, "workers", safeSlug(name), "worker.json"),
|
|
9072
|
+
void 0
|
|
9073
|
+
);
|
|
9074
|
+
if (!worker) continue;
|
|
9075
|
+
for (const evidence of extractTargetPrReconciliation(finalResultForWorker(worker))) {
|
|
9076
|
+
const n = prNumberFromUrl(evidence.prUrl);
|
|
9077
|
+
if (n != null && !byPr.has(n)) byPr.set(n, evidence);
|
|
9078
|
+
}
|
|
9079
|
+
}
|
|
9080
|
+
return byPr;
|
|
9081
|
+
}
|
|
9082
|
+
function candidatePrNumbers(worker, statusFinalResult) {
|
|
9083
|
+
const heartbeat = parseHeartbeat(worker.heartbeatPath);
|
|
9084
|
+
const text = [
|
|
9085
|
+
worker.name,
|
|
9086
|
+
worker.repairTargetPrUrl,
|
|
9087
|
+
worker.taskPrUrl,
|
|
9088
|
+
worker.branch,
|
|
9089
|
+
heartbeat.lastHeartbeatSummary,
|
|
9090
|
+
extractText(statusFinalResult)
|
|
9091
|
+
].filter(Boolean).join("\n");
|
|
9092
|
+
return extractPrNumbersFromText(text);
|
|
9093
|
+
}
|
|
9094
|
+
function isLocalOnlyAttentionNoise(worker) {
|
|
9095
|
+
return worker.localOnly === true && !worker.taskId && !worker.agentOsId;
|
|
9096
|
+
}
|
|
9097
|
+
function reconcileLocalOnlyMergedPrAttention(options = {}) {
|
|
9098
|
+
const lookupPr = options.lookupPr ?? defaultLookupPr;
|
|
9099
|
+
const outcomes = [];
|
|
9100
|
+
const liveLookupCache = /* @__PURE__ */ new Map();
|
|
9101
|
+
for (const run of listRunRecords()) {
|
|
9102
|
+
const mergedByPr = collectMergedEvidenceByPr(run);
|
|
9103
|
+
const runDir2 = runDirectory(run.id);
|
|
9104
|
+
for (const name of listRunWorkerNames(run)) {
|
|
9105
|
+
const workerPath = path35.join(runDir2, "workers", safeSlug(name), "worker.json");
|
|
9106
|
+
const worker = readJson(workerPath, void 0);
|
|
9107
|
+
if (!worker || !isLocalOnlyAttentionNoise(worker)) continue;
|
|
9108
|
+
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
9109
|
+
if (status.attention.state !== "needs_attention") continue;
|
|
9110
|
+
let merged = null;
|
|
9111
|
+
for (const prNumber of candidatePrNumbers(worker, status.finalResult)) {
|
|
9112
|
+
merged = mergedByPr.get(prNumber) ?? null;
|
|
9113
|
+
if (!merged) {
|
|
9114
|
+
const repoDir = run.repo || worker.worktreePath;
|
|
9115
|
+
const cacheKey = `${repoDir}#${prNumber}`;
|
|
9116
|
+
const live = liveLookupCache.has(cacheKey) ? liveLookupCache.get(cacheKey) ?? null : lookupPr({ repoDir, prNumber });
|
|
9117
|
+
if (!liveLookupCache.has(cacheKey)) liveLookupCache.set(cacheKey, live);
|
|
9118
|
+
if (live && (live.state === "MERGED" || live.mergedAt)) {
|
|
9119
|
+
merged = { prUrl: live.prUrl, mergeCommit: live.mergeCommit ?? null, reason: "GitHub reports PR merged" };
|
|
9120
|
+
}
|
|
9121
|
+
}
|
|
9122
|
+
if (merged) break;
|
|
9123
|
+
}
|
|
9124
|
+
if (!merged) {
|
|
9125
|
+
outcomes.push({ runId: run.id, worker: name, action: "skipped", reason: "no merged PR evidence" });
|
|
9126
|
+
continue;
|
|
9127
|
+
}
|
|
9128
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9129
|
+
worker.status = "done";
|
|
9130
|
+
worker.completionSnapshot = {
|
|
9131
|
+
prUrl: merged.prUrl,
|
|
9132
|
+
summary: `Local-only worker superseded by merged PR ${merged.prUrl}`,
|
|
9133
|
+
finalResult: {
|
|
9134
|
+
summary: `Local-only repair/salvage worker superseded by merged PR ${merged.prUrl}`,
|
|
9135
|
+
targetPrReconciliation: [
|
|
9136
|
+
{
|
|
9137
|
+
prUrl: merged.prUrl,
|
|
9138
|
+
outcome: "merged",
|
|
9139
|
+
mergeCommit: merged.mergeCommit ?? null,
|
|
9140
|
+
reason: merged.reason ?? "PR already merged; local-only worker no longer requires attention"
|
|
9141
|
+
}
|
|
9142
|
+
]
|
|
9143
|
+
}
|
|
9144
|
+
};
|
|
9145
|
+
worker.completionAckSource = "local-pr-merged-reconcile";
|
|
9146
|
+
worker.reconciledAt = now;
|
|
9147
|
+
worker.reconcileReason = "local-only needs_attention superseded by merged PR";
|
|
9148
|
+
saveWorker(run.id, worker);
|
|
9149
|
+
outcomes.push({
|
|
9150
|
+
runId: run.id,
|
|
9151
|
+
worker: name,
|
|
9152
|
+
action: "marked_done",
|
|
9153
|
+
reason: worker.reconcileReason,
|
|
9154
|
+
prUrl: merged.prUrl,
|
|
9155
|
+
mergeCommit: merged.mergeCommit ?? null
|
|
9156
|
+
});
|
|
9157
|
+
}
|
|
9158
|
+
}
|
|
9159
|
+
return { workers: outcomes };
|
|
9160
|
+
}
|
|
9161
|
+
|
|
8944
9162
|
// src/stale-reconcile.ts
|
|
8945
9163
|
var STALE_RECONCILE_HEARTBEAT_MS = 15 * 60 * 1e3;
|
|
8946
9164
|
function staleReconcileDisabled() {
|
|
@@ -8949,13 +9167,14 @@ function staleReconcileDisabled() {
|
|
|
8949
9167
|
function reconcileStaleWorkers() {
|
|
8950
9168
|
const metadataReconcile = reconcileWorkerMetadata();
|
|
8951
9169
|
if (staleReconcileDisabled()) {
|
|
8952
|
-
|
|
9170
|
+
const localPrAttentionReconcile2 = reconcileLocalOnlyMergedPrAttention();
|
|
9171
|
+
return { workers: [], finalizedRuns: finalizeStaleRuns(), metadataReconcile, localPrAttentionReconcile: localPrAttentionReconcile2 };
|
|
8953
9172
|
}
|
|
8954
9173
|
const outcomes = [];
|
|
8955
9174
|
const now = Date.now();
|
|
8956
9175
|
for (const run of listRunRecords()) {
|
|
8957
9176
|
for (const name of listRunWorkerNames(run)) {
|
|
8958
|
-
const workerPath =
|
|
9177
|
+
const workerPath = path36.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
8959
9178
|
const worker = readJson(workerPath, void 0);
|
|
8960
9179
|
if (!worker || worker.status !== "running") {
|
|
8961
9180
|
outcomes.push({
|
|
@@ -9027,7 +9246,8 @@ function reconcileStaleWorkers() {
|
|
|
9027
9246
|
});
|
|
9028
9247
|
}
|
|
9029
9248
|
}
|
|
9030
|
-
|
|
9249
|
+
const localPrAttentionReconcile = reconcileLocalOnlyMergedPrAttention();
|
|
9250
|
+
return { workers: outcomes, finalizedRuns: finalizeStaleRuns(), metadataReconcile, localPrAttentionReconcile };
|
|
9031
9251
|
}
|
|
9032
9252
|
function reconcileRunsCli() {
|
|
9033
9253
|
const result = reconcileStaleWorkers();
|
|
@@ -9038,6 +9258,10 @@ function reconcileRunsCli() {
|
|
|
9038
9258
|
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
9039
9259
|
return acc;
|
|
9040
9260
|
}, {});
|
|
9261
|
+
const localPrAttentionTotals = result.localPrAttentionReconcile.workers.reduce((acc, row) => {
|
|
9262
|
+
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
9263
|
+
return acc;
|
|
9264
|
+
}, {});
|
|
9041
9265
|
const runRetentionTotals = result.metadataReconcile.runMetadataRetention.runs.reduce((acc, row) => {
|
|
9042
9266
|
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
9043
9267
|
return acc;
|
|
@@ -9055,10 +9279,15 @@ function reconcileRunsCli() {
|
|
|
9055
9279
|
total: result.metadataReconcile.runMetadataRetention.runs.length
|
|
9056
9280
|
}
|
|
9057
9281
|
},
|
|
9282
|
+
localPrAttentionReconcile: {
|
|
9283
|
+
totals: localPrAttentionTotals,
|
|
9284
|
+
total: result.localPrAttentionReconcile.workers.length
|
|
9285
|
+
},
|
|
9058
9286
|
finalizedRuns: result.finalizedRuns.length,
|
|
9059
9287
|
details: {
|
|
9060
9288
|
workers: result.workers,
|
|
9061
9289
|
metadataReconcile: result.metadataReconcile.workers,
|
|
9290
|
+
localPrAttentionReconcile: result.localPrAttentionReconcile.workers,
|
|
9062
9291
|
runMetadataRetention: result.metadataReconcile.runMetadataRetention.runs,
|
|
9063
9292
|
finalizedRuns: result.finalizedRuns
|
|
9064
9293
|
}
|
|
@@ -9079,7 +9308,7 @@ function heartbeatByteLength(heartbeatPath) {
|
|
|
9079
9308
|
}
|
|
9080
9309
|
}
|
|
9081
9310
|
function workerEvidence(run, workerName) {
|
|
9082
|
-
const workerPath =
|
|
9311
|
+
const workerPath = path37.join(runDirectory(run.id), "workers", safeSlug(workerName), "worker.json");
|
|
9083
9312
|
const worker = readJson(workerPath, void 0);
|
|
9084
9313
|
if (!worker) {
|
|
9085
9314
|
return {
|
|
@@ -9136,7 +9365,7 @@ function aggregateRunAttention(workers) {
|
|
|
9136
9365
|
function countOpenWorkers(run) {
|
|
9137
9366
|
let open = 0;
|
|
9138
9367
|
for (const name of listRunWorkerNames(run)) {
|
|
9139
|
-
const workerPath =
|
|
9368
|
+
const workerPath = path37.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
9140
9369
|
const worker = readJson(workerPath, void 0);
|
|
9141
9370
|
if (!worker) continue;
|
|
9142
9371
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
@@ -9212,7 +9441,7 @@ function createRun(args) {
|
|
|
9212
9441
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9213
9442
|
workers: {}
|
|
9214
9443
|
};
|
|
9215
|
-
writeJson(
|
|
9444
|
+
writeJson(path38.join(dir, "run.json"), run);
|
|
9216
9445
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
9217
9446
|
}
|
|
9218
9447
|
function listRuns() {
|
|
@@ -9223,8 +9452,32 @@ function failExists(message) {
|
|
|
9223
9452
|
process.exit(1);
|
|
9224
9453
|
}
|
|
9225
9454
|
|
|
9455
|
+
// src/run-resolve.ts
|
|
9456
|
+
function resolveHarnessRunByName(runName) {
|
|
9457
|
+
const name = runName.trim();
|
|
9458
|
+
if (!name) return null;
|
|
9459
|
+
const rows = buildRunListRows();
|
|
9460
|
+
return rows.find((row) => row.name === name && row.status !== "completed") ?? null;
|
|
9461
|
+
}
|
|
9462
|
+
function resolveHarnessRunCli(args) {
|
|
9463
|
+
const name = String(required(String(args.name || ""), "--name"));
|
|
9464
|
+
const hit = resolveHarnessRunByName(name);
|
|
9465
|
+
console.log(
|
|
9466
|
+
JSON.stringify(
|
|
9467
|
+
{
|
|
9468
|
+
runId: hit?.id ?? null,
|
|
9469
|
+
name,
|
|
9470
|
+
status: hit?.status ?? null,
|
|
9471
|
+
effectiveStatus: hit?.effectiveStatus ?? null
|
|
9472
|
+
},
|
|
9473
|
+
null,
|
|
9474
|
+
2
|
|
9475
|
+
)
|
|
9476
|
+
);
|
|
9477
|
+
}
|
|
9478
|
+
|
|
9226
9479
|
// src/sweep.ts
|
|
9227
|
-
import
|
|
9480
|
+
import path39 from "node:path";
|
|
9228
9481
|
async function sweepRun(args) {
|
|
9229
9482
|
const pipeline = args.pipeline === true || args.pipeline === "true";
|
|
9230
9483
|
try {
|
|
@@ -9237,7 +9490,7 @@ async function sweepRun(args) {
|
|
|
9237
9490
|
const releasedLocalOrphans = [];
|
|
9238
9491
|
for (const name of Object.keys(run.workers || {})) {
|
|
9239
9492
|
const worker = readJson(
|
|
9240
|
-
|
|
9493
|
+
path39.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
9241
9494
|
void 0
|
|
9242
9495
|
);
|
|
9243
9496
|
if (!worker || !worker.dispatched || !worker.taskId) continue;
|
|
@@ -9286,17 +9539,17 @@ async function sweepRun(args) {
|
|
|
9286
9539
|
|
|
9287
9540
|
// src/harness-storage-snapshot.ts
|
|
9288
9541
|
import { existsSync as existsSync31, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
|
|
9289
|
-
import
|
|
9542
|
+
import path41 from "node:path";
|
|
9290
9543
|
|
|
9291
9544
|
// src/cleanup-dir-size.ts
|
|
9292
|
-
import { execFileSync as
|
|
9545
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
9293
9546
|
import { existsSync as existsSync30, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
|
|
9294
|
-
import
|
|
9547
|
+
import path40 from "node:path";
|
|
9295
9548
|
var DEFAULT_DU_TIMEOUT_MS = 2500;
|
|
9296
9549
|
function directorySizeBytesDu(root, timeoutMs = DEFAULT_DU_TIMEOUT_MS) {
|
|
9297
9550
|
if (!existsSync30(root)) return 0;
|
|
9298
9551
|
try {
|
|
9299
|
-
const out =
|
|
9552
|
+
const out = execFileSync3("du", ["-sb", root], {
|
|
9300
9553
|
encoding: "utf8",
|
|
9301
9554
|
timeout: timeoutMs,
|
|
9302
9555
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -9325,7 +9578,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
9325
9578
|
}
|
|
9326
9579
|
for (const name of entries) {
|
|
9327
9580
|
if (seen++ > maxEntries) return null;
|
|
9328
|
-
const full =
|
|
9581
|
+
const full = path40.join(current, name);
|
|
9329
9582
|
let st;
|
|
9330
9583
|
try {
|
|
9331
9584
|
st = statSync6(full);
|
|
@@ -9377,7 +9630,7 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
9377
9630
|
for (const runEntry of entries) {
|
|
9378
9631
|
if (!runEntry.isDirectory()) continue;
|
|
9379
9632
|
runCount += 1;
|
|
9380
|
-
const runPath =
|
|
9633
|
+
const runPath = path41.join(worktreesDir, runEntry.name);
|
|
9381
9634
|
try {
|
|
9382
9635
|
const st = statSync7(runPath);
|
|
9383
9636
|
oldestMs = oldestMs === null ? st.mtimeMs : Math.min(oldestMs, st.mtimeMs);
|
|
@@ -9412,10 +9665,10 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
9412
9665
|
}
|
|
9413
9666
|
|
|
9414
9667
|
// src/cleanup.ts
|
|
9415
|
-
import
|
|
9668
|
+
import path53 from "node:path";
|
|
9416
9669
|
|
|
9417
9670
|
// src/cleanup-guards.ts
|
|
9418
|
-
import
|
|
9671
|
+
import path42 from "node:path";
|
|
9419
9672
|
|
|
9420
9673
|
// src/cleanup-build-cache-paths.ts
|
|
9421
9674
|
var HARNESS_BUILD_CACHE_RELATIVE_PATHS = [
|
|
@@ -9556,7 +9809,7 @@ function skipWorktreeRemoval(input) {
|
|
|
9556
9809
|
function skipDependencyCacheRemoval(input) {
|
|
9557
9810
|
const { indexed, nodeModulesAgeMs, ageMs, worktreePath, activeWorktreePaths, diskPressure } = input;
|
|
9558
9811
|
if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
|
|
9559
|
-
if (activeWorktreePaths.has(
|
|
9812
|
+
if (activeWorktreePaths.has(path42.resolve(worktreePath))) return "active_worker";
|
|
9560
9813
|
if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
|
|
9561
9814
|
if (indexed && indexedWorktreeHasMaterialChanges(indexed)) return "dirty_worktree";
|
|
9562
9815
|
return null;
|
|
@@ -9583,11 +9836,11 @@ var LIVE_SKIP_REASONS = /* @__PURE__ */ new Set([
|
|
|
9583
9836
|
function collectPreservedLivePaths(actions, skips) {
|
|
9584
9837
|
const out = [];
|
|
9585
9838
|
const seen = /* @__PURE__ */ new Set();
|
|
9586
|
-
const push = (
|
|
9587
|
-
const key = `${
|
|
9839
|
+
const push = (path73, reason, detail) => {
|
|
9840
|
+
const key = `${path73}\0${reason}`;
|
|
9588
9841
|
if (seen.has(key) || out.length >= MAX_PRESERVED_LIVE_PATH_SAMPLES) return;
|
|
9589
9842
|
seen.add(key);
|
|
9590
|
-
out.push({ path:
|
|
9843
|
+
out.push({ path: path73, reason, ...detail ? { detail } : {} });
|
|
9591
9844
|
};
|
|
9592
9845
|
for (const skip2 of skips) {
|
|
9593
9846
|
if (!LIVE_SKIP_REASONS.has(skip2.reason)) continue;
|
|
@@ -9603,11 +9856,11 @@ function collectPreservedLivePaths(actions, skips) {
|
|
|
9603
9856
|
|
|
9604
9857
|
// src/cleanup-run-directory.ts
|
|
9605
9858
|
import { existsSync as existsSync33, readdirSync as readdirSync11, statSync as statSync9 } from "node:fs";
|
|
9606
|
-
import
|
|
9859
|
+
import path44 from "node:path";
|
|
9607
9860
|
|
|
9608
9861
|
// src/cleanup-active-worktrees.ts
|
|
9609
9862
|
import { existsSync as existsSync32, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
|
|
9610
|
-
import
|
|
9863
|
+
import path43 from "node:path";
|
|
9611
9864
|
function workerHasRecentHarnessActivity(worker, now) {
|
|
9612
9865
|
const paths = [worker.heartbeatPath, worker.stdoutPath, worker.stderrPath];
|
|
9613
9866
|
for (const target of paths) {
|
|
@@ -9633,11 +9886,11 @@ function collectActiveWorktreeGuards(harnessRoots, now = Date.now()) {
|
|
|
9633
9886
|
let runHasLive = false;
|
|
9634
9887
|
for (const name of Object.keys(run.workers || {})) {
|
|
9635
9888
|
const worker = readJson(
|
|
9636
|
-
|
|
9889
|
+
path43.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
|
|
9637
9890
|
void 0
|
|
9638
9891
|
);
|
|
9639
9892
|
if (!worker?.worktreePath) continue;
|
|
9640
|
-
const worktreePath =
|
|
9893
|
+
const worktreePath = path43.resolve(worker.worktreePath);
|
|
9641
9894
|
if (!isActiveHarnessWorker2(worker, now)) continue;
|
|
9642
9895
|
runHasLive = true;
|
|
9643
9896
|
activeWorktreePaths.add(worktreePath);
|
|
@@ -9665,7 +9918,7 @@ function pathAgeMs(target, now) {
|
|
|
9665
9918
|
}
|
|
9666
9919
|
}
|
|
9667
9920
|
function loadRunStatus(harnessRoot, runId) {
|
|
9668
|
-
const runPath =
|
|
9921
|
+
const runPath = path44.join(harnessRoot, "runs", runId, "run.json");
|
|
9669
9922
|
if (!existsSync33(runPath)) return null;
|
|
9670
9923
|
return readJson(runPath, null);
|
|
9671
9924
|
}
|
|
@@ -9701,7 +9954,7 @@ function scanStaleRunDirectoryCandidates(opts) {
|
|
|
9701
9954
|
if (!runEntry.isDirectory()) continue;
|
|
9702
9955
|
const runId = runEntry.name;
|
|
9703
9956
|
if (opts.runIdFilter && runId !== opts.runIdFilter) continue;
|
|
9704
|
-
const runPath =
|
|
9957
|
+
const runPath = path44.join(opts.worktreesDir, runId);
|
|
9705
9958
|
if (!runDirectoryIsEmpty(runPath)) continue;
|
|
9706
9959
|
candidates.push({
|
|
9707
9960
|
kind: "remove_run_directory",
|
|
@@ -9755,20 +10008,20 @@ function pathHasForeignOwnedEntry(targetPath, maxEntries = 32) {
|
|
|
9755
10008
|
|
|
9756
10009
|
// src/cleanup-privileged-remove.ts
|
|
9757
10010
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
9758
|
-
import
|
|
10011
|
+
import path46 from "node:path";
|
|
9759
10012
|
|
|
9760
10013
|
// src/cleanup-harness-path-validate.ts
|
|
9761
|
-
import
|
|
10014
|
+
import path45 from "node:path";
|
|
9762
10015
|
function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
|
|
9763
|
-
const resolved =
|
|
9764
|
-
const suffix = `${
|
|
10016
|
+
const resolved = path45.resolve(targetPath);
|
|
10017
|
+
const suffix = `${path45.sep}${cacheDirName}`;
|
|
9765
10018
|
const cachePath = resolved.endsWith(suffix) ? resolved : null;
|
|
9766
10019
|
if (!cachePath) return "path_outside_harness";
|
|
9767
|
-
const rel =
|
|
9768
|
-
if (rel.startsWith("..") ||
|
|
9769
|
-
const parts = rel.split(
|
|
10020
|
+
const rel = path45.relative(worktreesDir, cachePath);
|
|
10021
|
+
if (rel.startsWith("..") || path45.isAbsolute(rel)) return "path_outside_harness";
|
|
10022
|
+
const parts = rel.split(path45.sep);
|
|
9770
10023
|
if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
|
|
9771
|
-
if (!resolved.startsWith(
|
|
10024
|
+
if (!resolved.startsWith(path45.resolve(harnessRoot))) return "path_outside_harness";
|
|
9772
10025
|
return null;
|
|
9773
10026
|
}
|
|
9774
10027
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
@@ -9778,16 +10031,16 @@ function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
|
9778
10031
|
return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
|
|
9779
10032
|
}
|
|
9780
10033
|
function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
9781
|
-
const resolved =
|
|
9782
|
-
const relToWt =
|
|
9783
|
-
if (relToWt.startsWith("..") ||
|
|
9784
|
-
const parts = relToWt.split(
|
|
10034
|
+
const resolved = path45.resolve(targetPath);
|
|
10035
|
+
const relToWt = path45.relative(worktreesDir, resolved);
|
|
10036
|
+
if (relToWt.startsWith("..") || path45.isAbsolute(relToWt)) return "path_outside_harness";
|
|
10037
|
+
const parts = relToWt.split(path45.sep);
|
|
9785
10038
|
if (parts.length < 3) return "path_outside_harness";
|
|
9786
|
-
if (!resolved.startsWith(
|
|
10039
|
+
if (!resolved.startsWith(path45.resolve(harnessRoot))) return "path_outside_harness";
|
|
9787
10040
|
return null;
|
|
9788
10041
|
}
|
|
9789
10042
|
function isHarnessGeneratedCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
9790
|
-
const resolved =
|
|
10043
|
+
const resolved = path45.resolve(targetPath);
|
|
9791
10044
|
return isHarnessNodeModulesPath(resolved, harnessRoot, worktreesDir) === null || isHarnessNextCachePath(resolved, harnessRoot, worktreesDir) === null || isHarnessBuildCachePath(resolved, harnessRoot, worktreesDir) === null;
|
|
9792
10045
|
}
|
|
9793
10046
|
|
|
@@ -9821,12 +10074,12 @@ function tryPrivilegedReclaimHarnessCache(targetPath, harnessRoot, worktreesDir)
|
|
|
9821
10074
|
"chown",
|
|
9822
10075
|
"-R",
|
|
9823
10076
|
`${effectiveUid}:${effectiveGid}`,
|
|
9824
|
-
|
|
10077
|
+
path46.resolve(targetPath)
|
|
9825
10078
|
]);
|
|
9826
10079
|
if (chown.ok) {
|
|
9827
10080
|
return { ok: true, method: "chown_then_rm" };
|
|
9828
10081
|
}
|
|
9829
|
-
const rm = runSudoNonInteractive(["rm", "-rf",
|
|
10082
|
+
const rm = runSudoNonInteractive(["rm", "-rf", path46.resolve(targetPath)]);
|
|
9830
10083
|
if (rm.ok) {
|
|
9831
10084
|
return { ok: true, method: "sudo_rm" };
|
|
9832
10085
|
}
|
|
@@ -10028,7 +10281,7 @@ function removeWorktree(candidate, execute) {
|
|
|
10028
10281
|
|
|
10029
10282
|
// src/cleanup-scan.ts
|
|
10030
10283
|
import { existsSync as existsSync36, readdirSync as readdirSync13, statSync as statSync10 } from "node:fs";
|
|
10031
|
-
import
|
|
10284
|
+
import path47 from "node:path";
|
|
10032
10285
|
function pathAgeMs2(target, now) {
|
|
10033
10286
|
try {
|
|
10034
10287
|
const mtime = statSync10(target).mtimeMs;
|
|
@@ -10038,16 +10291,16 @@ function pathAgeMs2(target, now) {
|
|
|
10038
10291
|
}
|
|
10039
10292
|
}
|
|
10040
10293
|
function isPathInside(child, parent) {
|
|
10041
|
-
const rel =
|
|
10042
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
10294
|
+
const rel = path47.relative(parent, child);
|
|
10295
|
+
return rel === "" || !rel.startsWith("..") && !path47.isAbsolute(rel);
|
|
10043
10296
|
}
|
|
10044
10297
|
function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
|
|
10045
10298
|
const out = [];
|
|
10046
10299
|
for (const rel of HARNESS_BUILD_CACHE_RELATIVE_PATHS) {
|
|
10047
10300
|
if (rel === ".next") continue;
|
|
10048
|
-
const target =
|
|
10301
|
+
const target = path47.join(worktreePath, rel);
|
|
10049
10302
|
if (!existsSync36(target)) continue;
|
|
10050
|
-
const resolved =
|
|
10303
|
+
const resolved = path47.resolve(target);
|
|
10051
10304
|
if (seen.has(resolved)) continue;
|
|
10052
10305
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
10053
10306
|
seen.add(resolved);
|
|
@@ -10079,10 +10332,10 @@ function scanBuildCacheCandidates(opts) {
|
|
|
10079
10332
|
if (!opts.includeOrphans || !existsSync36(opts.worktreesDir)) return candidates;
|
|
10080
10333
|
for (const runEntry of readdirSync13(opts.worktreesDir, { withFileTypes: true })) {
|
|
10081
10334
|
if (!runEntry.isDirectory()) continue;
|
|
10082
|
-
const runPath =
|
|
10335
|
+
const runPath = path47.join(opts.worktreesDir, runEntry.name);
|
|
10083
10336
|
for (const workerEntry of readdirSync13(runPath, { withFileTypes: true })) {
|
|
10084
10337
|
if (!workerEntry.isDirectory()) continue;
|
|
10085
|
-
const worktreePath =
|
|
10338
|
+
const worktreePath = path47.join(runPath, workerEntry.name);
|
|
10086
10339
|
candidates.push(
|
|
10087
10340
|
...collectBuildCacheForWorktree(worktreePath, opts, seen, {
|
|
10088
10341
|
runId: runEntry.name,
|
|
@@ -10120,12 +10373,12 @@ function scanWorktreeCandidates(opts) {
|
|
|
10120
10373
|
if (!orphanEnabled || !existsSync36(opts.worktreesDir)) return candidates;
|
|
10121
10374
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
10122
10375
|
for (const entry of opts.index.values()) {
|
|
10123
|
-
indexedPaths.add(
|
|
10376
|
+
indexedPaths.add(path47.resolve(entry.worktreePath));
|
|
10124
10377
|
}
|
|
10125
10378
|
for (const runEntry of readdirSync13(opts.worktreesDir, { withFileTypes: true })) {
|
|
10126
10379
|
if (!runEntry.isDirectory()) continue;
|
|
10127
10380
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
10128
|
-
const runPath =
|
|
10381
|
+
const runPath = path47.join(opts.worktreesDir, runEntry.name);
|
|
10129
10382
|
let workerEntries;
|
|
10130
10383
|
try {
|
|
10131
10384
|
workerEntries = readdirSync13(runPath, { withFileTypes: true });
|
|
@@ -10134,7 +10387,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
10134
10387
|
}
|
|
10135
10388
|
for (const workerEntry of workerEntries) {
|
|
10136
10389
|
if (!workerEntry.isDirectory()) continue;
|
|
10137
|
-
const worktreePath =
|
|
10390
|
+
const worktreePath = path47.resolve(path47.join(runPath, workerEntry.name));
|
|
10138
10391
|
if (seen.has(worktreePath)) continue;
|
|
10139
10392
|
if (indexedPaths.has(worktreePath)) continue;
|
|
10140
10393
|
if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
|
|
@@ -10154,7 +10407,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
10154
10407
|
|
|
10155
10408
|
// src/cleanup-dependency-scan.ts
|
|
10156
10409
|
import { existsSync as existsSync37, readdirSync as readdirSync14, statSync as statSync11 } from "node:fs";
|
|
10157
|
-
import
|
|
10410
|
+
import path48 from "node:path";
|
|
10158
10411
|
var DEPENDENCY_CACHE_DIRS = [
|
|
10159
10412
|
{ dirName: "node_modules", kind: "remove_node_modules" },
|
|
10160
10413
|
{ dirName: ".next", kind: "remove_next_cache" }
|
|
@@ -10168,12 +10421,12 @@ function pathAgeMs3(target, now) {
|
|
|
10168
10421
|
}
|
|
10169
10422
|
}
|
|
10170
10423
|
function isPathInside2(child, parent) {
|
|
10171
|
-
const rel =
|
|
10172
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
10424
|
+
const rel = path48.relative(parent, child);
|
|
10425
|
+
return rel === "" || !rel.startsWith("..") && !path48.isAbsolute(rel);
|
|
10173
10426
|
}
|
|
10174
10427
|
function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
10175
10428
|
if (!existsSync37(targetPath)) return;
|
|
10176
|
-
const resolved =
|
|
10429
|
+
const resolved = path48.resolve(targetPath);
|
|
10177
10430
|
if (seen.has(resolved)) return;
|
|
10178
10431
|
if (!isPathInside2(resolved, opts.harnessRoot)) return;
|
|
10179
10432
|
seen.add(resolved);
|
|
@@ -10190,7 +10443,7 @@ function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
|
10190
10443
|
}
|
|
10191
10444
|
function scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, meta) {
|
|
10192
10445
|
for (const entry of DEPENDENCY_CACHE_DIRS) {
|
|
10193
|
-
pushCandidate2(candidates, seen, opts,
|
|
10446
|
+
pushCandidate2(candidates, seen, opts, path48.join(worktreePath, entry.dirName), entry.kind, meta);
|
|
10194
10447
|
}
|
|
10195
10448
|
}
|
|
10196
10449
|
function scanDependencyCacheCandidates(opts) {
|
|
@@ -10208,7 +10461,7 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
10208
10461
|
for (const runEntry of readdirSync14(opts.worktreesDir, { withFileTypes: true })) {
|
|
10209
10462
|
if (!runEntry.isDirectory()) continue;
|
|
10210
10463
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
10211
|
-
const runPath =
|
|
10464
|
+
const runPath = path48.join(opts.worktreesDir, runEntry.name);
|
|
10212
10465
|
let workerEntries;
|
|
10213
10466
|
try {
|
|
10214
10467
|
workerEntries = readdirSync14(runPath, { withFileTypes: true });
|
|
@@ -10217,7 +10470,7 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
10217
10470
|
}
|
|
10218
10471
|
for (const workerEntry of workerEntries) {
|
|
10219
10472
|
if (!workerEntry.isDirectory()) continue;
|
|
10220
|
-
const worktreePath =
|
|
10473
|
+
const worktreePath = path48.join(runPath, workerEntry.name);
|
|
10221
10474
|
scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, {
|
|
10222
10475
|
runId: runEntry.name,
|
|
10223
10476
|
worker: workerEntry.name
|
|
@@ -10229,7 +10482,7 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
10229
10482
|
|
|
10230
10483
|
// src/cleanup-duplicate-worktrees.ts
|
|
10231
10484
|
import { existsSync as existsSync38, statSync as statSync12 } from "node:fs";
|
|
10232
|
-
import
|
|
10485
|
+
import path49 from "node:path";
|
|
10233
10486
|
function pathAgeMs4(target, now) {
|
|
10234
10487
|
try {
|
|
10235
10488
|
const mtime = statSync12(target).mtimeMs;
|
|
@@ -10259,8 +10512,8 @@ function parseWorktreePorcelain(output) {
|
|
|
10259
10512
|
return records;
|
|
10260
10513
|
}
|
|
10261
10514
|
function isUnderWorktreesDir(worktreePath, worktreesDir) {
|
|
10262
|
-
const rel =
|
|
10263
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
10515
|
+
const rel = path49.relative(path49.resolve(worktreesDir), path49.resolve(worktreePath));
|
|
10516
|
+
return rel !== "" && !rel.startsWith("..") && !path49.isAbsolute(rel);
|
|
10264
10517
|
}
|
|
10265
10518
|
function isCleanWorktree(worktreePath, repoRoot) {
|
|
10266
10519
|
try {
|
|
@@ -10276,11 +10529,11 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
10276
10529
|
if (!opts.includeOrphans || !existsSync38(opts.worktreesDir)) return [];
|
|
10277
10530
|
const repos = /* @__PURE__ */ new Set();
|
|
10278
10531
|
for (const entry of opts.index.values()) {
|
|
10279
|
-
if (entry.run.repo) repos.add(
|
|
10532
|
+
if (entry.run.repo) repos.add(path49.resolve(entry.run.repo));
|
|
10280
10533
|
}
|
|
10281
10534
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
10282
10535
|
for (const entry of opts.index.values()) {
|
|
10283
|
-
indexedPaths.add(
|
|
10536
|
+
indexedPaths.add(path49.resolve(entry.worktreePath));
|
|
10284
10537
|
}
|
|
10285
10538
|
const candidates = [];
|
|
10286
10539
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -10293,15 +10546,15 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
10293
10546
|
}
|
|
10294
10547
|
const worktrees = parseWorktreePorcelain(porcelain);
|
|
10295
10548
|
for (const wt of worktrees) {
|
|
10296
|
-
const resolved =
|
|
10297
|
-
if (resolved ===
|
|
10549
|
+
const resolved = path49.resolve(wt.path);
|
|
10550
|
+
if (resolved === path49.resolve(repoRoot)) continue;
|
|
10298
10551
|
if (!isUnderWorktreesDir(resolved, opts.worktreesDir)) continue;
|
|
10299
10552
|
if (indexedPaths.has(resolved)) continue;
|
|
10300
10553
|
if (seen.has(resolved)) continue;
|
|
10301
10554
|
if (!existsSync38(resolved)) continue;
|
|
10302
10555
|
if (!isCleanWorktree(resolved, repoRoot)) continue;
|
|
10303
|
-
const rel =
|
|
10304
|
-
const parts = rel.split(
|
|
10556
|
+
const rel = path49.relative(opts.worktreesDir, resolved);
|
|
10557
|
+
const parts = rel.split(path49.sep);
|
|
10305
10558
|
const runId = parts[0];
|
|
10306
10559
|
const worker = parts[1] ?? "unknown";
|
|
10307
10560
|
seen.add(resolved);
|
|
@@ -10320,12 +10573,12 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
10320
10573
|
}
|
|
10321
10574
|
|
|
10322
10575
|
// src/cleanup-worktree-index.ts
|
|
10323
|
-
import
|
|
10576
|
+
import path50 from "node:path";
|
|
10324
10577
|
function buildWorktreeIndexAt(harnessRoot) {
|
|
10325
10578
|
const index = /* @__PURE__ */ new Map();
|
|
10326
10579
|
for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
|
|
10327
10580
|
for (const name of Object.keys(run.workers || {})) {
|
|
10328
|
-
const workerPath =
|
|
10581
|
+
const workerPath = path50.join(
|
|
10329
10582
|
runDirectoryAt(harnessRoot, run.id),
|
|
10330
10583
|
"workers",
|
|
10331
10584
|
safeSlug(name),
|
|
@@ -10333,9 +10586,9 @@ function buildWorktreeIndexAt(harnessRoot) {
|
|
|
10333
10586
|
);
|
|
10334
10587
|
const worker = readJson(workerPath, void 0);
|
|
10335
10588
|
if (!worker?.worktreePath) continue;
|
|
10336
|
-
index.set(
|
|
10589
|
+
index.set(path50.resolve(worker.worktreePath), {
|
|
10337
10590
|
harnessRoot,
|
|
10338
|
-
worktreePath:
|
|
10591
|
+
worktreePath: path50.resolve(worker.worktreePath),
|
|
10339
10592
|
runId: run.id,
|
|
10340
10593
|
workerName: name,
|
|
10341
10594
|
run,
|
|
@@ -10405,14 +10658,14 @@ function resolvePipelineHarnessRetention(runId) {
|
|
|
10405
10658
|
|
|
10406
10659
|
// src/cleanup-orphan-safety.ts
|
|
10407
10660
|
import { existsSync as existsSync39, statSync as statSync13 } from "node:fs";
|
|
10408
|
-
import
|
|
10661
|
+
import path51 from "node:path";
|
|
10409
10662
|
var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
|
|
10410
10663
|
function assessOrphanWorktreeSafety(input) {
|
|
10411
10664
|
const now = input.now ?? Date.now();
|
|
10412
10665
|
const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
|
|
10413
10666
|
if (!existsSync39(input.worktreePath)) return null;
|
|
10414
10667
|
if (input.runId && input.workerName) {
|
|
10415
|
-
const heartbeatPath =
|
|
10668
|
+
const heartbeatPath = path51.join(
|
|
10416
10669
|
input.harnessRoot,
|
|
10417
10670
|
"runs",
|
|
10418
10671
|
input.runId,
|
|
@@ -10426,7 +10679,7 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
10426
10679
|
} catch {
|
|
10427
10680
|
}
|
|
10428
10681
|
}
|
|
10429
|
-
const gitDir =
|
|
10682
|
+
const gitDir = path51.join(input.worktreePath, ".git");
|
|
10430
10683
|
if (!existsSync39(gitDir)) return null;
|
|
10431
10684
|
const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
|
|
10432
10685
|
if (porcelain.status !== 0) return "pr_or_unmerged_commits";
|
|
@@ -10458,10 +10711,10 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
10458
10711
|
// src/cleanup-harness-roots.ts
|
|
10459
10712
|
import { existsSync as existsSync40 } from "node:fs";
|
|
10460
10713
|
import { homedir as homedir13 } from "node:os";
|
|
10461
|
-
import
|
|
10714
|
+
import path52 from "node:path";
|
|
10462
10715
|
var WELL_KNOWN_HARNESS_SCAN_ROOTS = [
|
|
10463
10716
|
"/var/tmp/kynver-harness",
|
|
10464
|
-
|
|
10717
|
+
path52.join(homedir13(), ".openclaw", "harness")
|
|
10465
10718
|
];
|
|
10466
10719
|
function addRoot(seen, roots, candidate) {
|
|
10467
10720
|
if (!candidate?.trim()) return;
|
|
@@ -10483,7 +10736,7 @@ function resolveHarnessScanRoots(options = {}) {
|
|
|
10483
10736
|
for (const candidate of extra ?? []) addRoot(seen, roots, candidate);
|
|
10484
10737
|
if (shouldScanWellKnownRoots(options)) {
|
|
10485
10738
|
for (const candidate of WELL_KNOWN_HARNESS_SCAN_ROOTS) {
|
|
10486
|
-
const resolved =
|
|
10739
|
+
const resolved = path52.resolve(candidate);
|
|
10487
10740
|
if (!seen.has(resolved) && existsSync40(resolved)) addRoot(seen, roots, resolved);
|
|
10488
10741
|
}
|
|
10489
10742
|
}
|
|
@@ -10638,9 +10891,9 @@ function mergeWorktreeIndexes(scanRoots) {
|
|
|
10638
10891
|
}
|
|
10639
10892
|
function worktreePathForCandidate(candidate, worktreesDir) {
|
|
10640
10893
|
if (candidate.runId && candidate.worker) {
|
|
10641
|
-
return
|
|
10894
|
+
return path53.join(worktreesDir, candidate.runId, candidate.worker);
|
|
10642
10895
|
}
|
|
10643
|
-
return
|
|
10896
|
+
return path53.resolve(candidate.path, "..");
|
|
10644
10897
|
}
|
|
10645
10898
|
function runHarnessCleanup(options = {}) {
|
|
10646
10899
|
let retention = resolveHarnessRetention(options);
|
|
@@ -10665,7 +10918,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10665
10918
|
for (const harnessRoot of paths.scanRoots) {
|
|
10666
10919
|
if (atSweepCap()) break;
|
|
10667
10920
|
emitCleanupProgress("root", harnessRoot);
|
|
10668
|
-
const worktreesDir =
|
|
10921
|
+
const worktreesDir = path53.join(harnessRoot, "worktrees");
|
|
10669
10922
|
const scanOpts = {
|
|
10670
10923
|
harnessRoot,
|
|
10671
10924
|
worktreesDir,
|
|
@@ -10678,7 +10931,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10678
10931
|
};
|
|
10679
10932
|
for (const raw of scanDependencyCacheCandidates(scanOpts)) {
|
|
10680
10933
|
if (atSweepCap()) break;
|
|
10681
|
-
const resolved =
|
|
10934
|
+
const resolved = path53.resolve(raw.path);
|
|
10682
10935
|
if (processedPaths.has(resolved)) continue;
|
|
10683
10936
|
processedPaths.add(resolved);
|
|
10684
10937
|
const candidate = { ...raw, path: resolved };
|
|
@@ -10689,7 +10942,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10689
10942
|
continue;
|
|
10690
10943
|
}
|
|
10691
10944
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
10692
|
-
const indexed = index.get(
|
|
10945
|
+
const indexed = index.get(path53.resolve(worktreePath)) ?? null;
|
|
10693
10946
|
const guardReason = skipDependencyCacheRemoval({
|
|
10694
10947
|
indexed,
|
|
10695
10948
|
includeOrphans: true,
|
|
@@ -10713,7 +10966,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10713
10966
|
}
|
|
10714
10967
|
for (const raw of scanBuildCacheCandidates(scanOpts)) {
|
|
10715
10968
|
if (atSweepCap()) break;
|
|
10716
|
-
const resolved =
|
|
10969
|
+
const resolved = path53.resolve(raw.path);
|
|
10717
10970
|
if (processedPaths.has(resolved)) continue;
|
|
10718
10971
|
processedPaths.add(resolved);
|
|
10719
10972
|
const candidate = { ...raw, path: resolved };
|
|
@@ -10724,7 +10977,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10724
10977
|
continue;
|
|
10725
10978
|
}
|
|
10726
10979
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
10727
|
-
const indexed = index.get(
|
|
10980
|
+
const indexed = index.get(path53.resolve(worktreePath)) ?? null;
|
|
10728
10981
|
const guardReason = skipBuildCacheRemoval({
|
|
10729
10982
|
indexed,
|
|
10730
10983
|
includeOrphans: true,
|
|
@@ -10754,11 +11007,11 @@ function runHarnessCleanup(options = {}) {
|
|
|
10754
11007
|
const worktreeSeen = /* @__PURE__ */ new Set();
|
|
10755
11008
|
for (const raw of worktreeCandidates) {
|
|
10756
11009
|
if (atSweepCap()) break;
|
|
10757
|
-
const resolved =
|
|
11010
|
+
const resolved = path53.resolve(raw.path);
|
|
10758
11011
|
if (worktreeSeen.has(resolved)) continue;
|
|
10759
11012
|
worktreeSeen.add(resolved);
|
|
10760
11013
|
const candidate = { ...raw, path: resolved };
|
|
10761
|
-
const indexed = index.get(
|
|
11014
|
+
const indexed = index.get(path53.resolve(candidate.path)) ?? null;
|
|
10762
11015
|
const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
|
|
10763
11016
|
worktreePath: candidate.path,
|
|
10764
11017
|
harnessRoot,
|
|
@@ -10768,7 +11021,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10768
11021
|
});
|
|
10769
11022
|
const guardSkip = skipWorktreeRemoval({
|
|
10770
11023
|
indexed,
|
|
10771
|
-
worktreePath:
|
|
11024
|
+
worktreePath: path53.resolve(candidate.path),
|
|
10772
11025
|
includeOrphans: retention.includeOrphans,
|
|
10773
11026
|
worktreesAgeMs: retention.worktreesAgeMs,
|
|
10774
11027
|
terminalWorktreesAgeMs: retention.terminalWorktreesAgeMs,
|
|
@@ -10800,11 +11053,11 @@ function runHarnessCleanup(options = {}) {
|
|
|
10800
11053
|
now: paths.now
|
|
10801
11054
|
})) {
|
|
10802
11055
|
if (atSweepCap()) break;
|
|
10803
|
-
const resolved =
|
|
11056
|
+
const resolved = path53.resolve(raw.path);
|
|
10804
11057
|
if (processedPaths.has(resolved)) continue;
|
|
10805
11058
|
processedPaths.add(resolved);
|
|
10806
11059
|
const candidate = { ...raw, path: resolved };
|
|
10807
|
-
const runId = candidate.runId ??
|
|
11060
|
+
const runId = candidate.runId ?? path53.basename(resolved);
|
|
10808
11061
|
const dirSkip = skipRunDirectoryRemoval({
|
|
10809
11062
|
harnessRoot,
|
|
10810
11063
|
runId,
|
|
@@ -10943,7 +11196,7 @@ import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
|
10943
11196
|
|
|
10944
11197
|
// src/discard-disposable.ts
|
|
10945
11198
|
import { existsSync as existsSync41, rmSync as rmSync4 } from "node:fs";
|
|
10946
|
-
import
|
|
11199
|
+
import path54 from "node:path";
|
|
10947
11200
|
function normalizeRelativePath2(value) {
|
|
10948
11201
|
const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
10949
11202
|
if (!normalized || normalized.startsWith("/") || normalized.includes("..")) {
|
|
@@ -10965,12 +11218,12 @@ function discardDisposableArtifacts(args) {
|
|
|
10965
11218
|
if (paths.length === 0) {
|
|
10966
11219
|
return { ok: false, removed: [], reason: "requires at least one --path" };
|
|
10967
11220
|
}
|
|
10968
|
-
const worktreeRoot =
|
|
11221
|
+
const worktreeRoot = path54.resolve(worker.worktreePath);
|
|
10969
11222
|
const removed = [];
|
|
10970
11223
|
for (const raw of paths) {
|
|
10971
11224
|
const rel = normalizeRelativePath2(raw);
|
|
10972
|
-
const abs =
|
|
10973
|
-
if (!abs.startsWith(worktreeRoot +
|
|
11225
|
+
const abs = path54.resolve(worktreeRoot, rel);
|
|
11226
|
+
if (!abs.startsWith(worktreeRoot + path54.sep) && abs !== worktreeRoot) {
|
|
10974
11227
|
return { ok: false, removed, reason: `path escapes worktree: ${raw}` };
|
|
10975
11228
|
}
|
|
10976
11229
|
if (!existsSync41(abs)) {
|
|
@@ -11037,7 +11290,7 @@ function validateDaemonInstallIdentity(config = loadUserConfig(), env = process.
|
|
|
11037
11290
|
// src/cron/cron-env.ts
|
|
11038
11291
|
import { existsSync as existsSync42 } from "node:fs";
|
|
11039
11292
|
import { homedir as homedir14 } from "node:os";
|
|
11040
|
-
import
|
|
11293
|
+
import path55 from "node:path";
|
|
11041
11294
|
function envFlag3(name, defaultValue) {
|
|
11042
11295
|
const raw = process.env[name]?.trim().toLowerCase();
|
|
11043
11296
|
if (!raw) return defaultValue;
|
|
@@ -11053,7 +11306,7 @@ function envInt(name, fallback, min = 1) {
|
|
|
11053
11306
|
function defaultKynverCronStorePath() {
|
|
11054
11307
|
const explicit = process.env.KYNVER_CRON_STORE_PATH?.trim() || process.env.OPENCLAW_CRON_STORE_PATH?.trim();
|
|
11055
11308
|
if (explicit) return explicit;
|
|
11056
|
-
return
|
|
11309
|
+
return path55.join(homedir14(), ".kynver", "agent-os-cron.json");
|
|
11057
11310
|
}
|
|
11058
11311
|
function defaultKynverCronStatePath(storePath = defaultKynverCronStorePath()) {
|
|
11059
11312
|
const explicit = process.env.KYNVER_CRON_TICK_STATE_PATH?.trim();
|
|
@@ -11295,7 +11548,7 @@ async function loadCronJobs(storePath = defaultKynverCronStorePath()) {
|
|
|
11295
11548
|
// src/cron/cron-tick-state.ts
|
|
11296
11549
|
import { randomBytes } from "node:crypto";
|
|
11297
11550
|
import { promises as fs4 } from "node:fs";
|
|
11298
|
-
import
|
|
11551
|
+
import path56 from "node:path";
|
|
11299
11552
|
var EMPTY = { version: 1, jobs: {} };
|
|
11300
11553
|
async function readFileIfExists2(filePath) {
|
|
11301
11554
|
try {
|
|
@@ -11322,7 +11575,7 @@ async function loadCronTickState(statePath) {
|
|
|
11322
11575
|
return parseCronTickState(raw);
|
|
11323
11576
|
}
|
|
11324
11577
|
async function writeStateAtomic(statePath, state) {
|
|
11325
|
-
await fs4.mkdir(
|
|
11578
|
+
await fs4.mkdir(path56.dirname(statePath), { recursive: true });
|
|
11326
11579
|
const suffix = randomBytes(6).toString("hex");
|
|
11327
11580
|
const tmp = `${statePath}.tmp-${process.pid}-${Date.now()}-${suffix}`;
|
|
11328
11581
|
await fs4.writeFile(tmp, `${JSON.stringify(state, null, 2)}
|
|
@@ -11499,7 +11752,7 @@ async function runKynverCronTick(opts = {}) {
|
|
|
11499
11752
|
}
|
|
11500
11753
|
|
|
11501
11754
|
// src/pipeline-tick.ts
|
|
11502
|
-
import
|
|
11755
|
+
import path58 from "node:path";
|
|
11503
11756
|
|
|
11504
11757
|
// src/pipeline-dispatch.ts
|
|
11505
11758
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -11630,7 +11883,7 @@ function resolvePipelineMaxStarts(resourceGate, operatorTick) {
|
|
|
11630
11883
|
}
|
|
11631
11884
|
|
|
11632
11885
|
// src/plan-progress-daemon-sync.ts
|
|
11633
|
-
import
|
|
11886
|
+
import path57 from "node:path";
|
|
11634
11887
|
|
|
11635
11888
|
// src/plan-progress-sync.ts
|
|
11636
11889
|
async function syncPlanProgress(args) {
|
|
@@ -11654,7 +11907,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
11654
11907
|
const outcomes = [];
|
|
11655
11908
|
for (const name of Object.keys(run.workers || {})) {
|
|
11656
11909
|
const worker = readJson(
|
|
11657
|
-
|
|
11910
|
+
path57.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
11658
11911
|
void 0
|
|
11659
11912
|
);
|
|
11660
11913
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -11716,7 +11969,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
11716
11969
|
const outcomes = [];
|
|
11717
11970
|
for (const name of Object.keys(run.workers || {})) {
|
|
11718
11971
|
const worker = readJson(
|
|
11719
|
-
|
|
11972
|
+
path58.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
11720
11973
|
void 0
|
|
11721
11974
|
);
|
|
11722
11975
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -11863,6 +12116,15 @@ async function runPipelineTick(args) {
|
|
|
11863
12116
|
var DEFAULT_INTERVAL_MS = 6e4;
|
|
11864
12117
|
var IDLE_INTERVAL_MS = 5 * 6e4;
|
|
11865
12118
|
var MAX_IDLE_STREAK = 10;
|
|
12119
|
+
var SLEEP_POLL_MS = 250;
|
|
12120
|
+
async function awaitDaemonBackoff(ms, isStopping) {
|
|
12121
|
+
let remaining = ms;
|
|
12122
|
+
while (remaining > 0 && !isStopping()) {
|
|
12123
|
+
const step = Math.min(SLEEP_POLL_MS, remaining);
|
|
12124
|
+
await sleepMsAsync(step);
|
|
12125
|
+
remaining -= step;
|
|
12126
|
+
}
|
|
12127
|
+
}
|
|
11866
12128
|
async function runDaemon(args) {
|
|
11867
12129
|
const runId = String(required(String(args.run || ""), "--run"));
|
|
11868
12130
|
const agentOsId = String(required(String(args.agentOsId || loadUserConfig().agentOsId || ""), "--agent-os-id"));
|
|
@@ -11920,17 +12182,17 @@ async function runDaemon(args) {
|
|
|
11920
12182
|
idleStreak = 0;
|
|
11921
12183
|
}
|
|
11922
12184
|
const backoff = idleStreak >= MAX_IDLE_STREAK ? IDLE_INTERVAL_MS : intervalMs;
|
|
11923
|
-
await
|
|
12185
|
+
await awaitDaemonBackoff(backoff, () => stopping);
|
|
11924
12186
|
} catch (error) {
|
|
11925
12187
|
console.error(JSON.stringify({ event: "daemon_tick_error", error: error.message }));
|
|
11926
|
-
await
|
|
12188
|
+
await awaitDaemonBackoff(intervalMs, () => stopping);
|
|
11927
12189
|
}
|
|
11928
12190
|
}
|
|
11929
12191
|
console.error(JSON.stringify({ event: "daemon_stop", runId, agentOsId }));
|
|
11930
12192
|
}
|
|
11931
12193
|
|
|
11932
12194
|
// src/plan-progress.ts
|
|
11933
|
-
import
|
|
12195
|
+
import path62 from "node:path";
|
|
11934
12196
|
|
|
11935
12197
|
// src/bounded-build/constants.ts
|
|
11936
12198
|
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
@@ -12073,16 +12335,16 @@ import {
|
|
|
12073
12335
|
unlinkSync as unlinkSync4,
|
|
12074
12336
|
writeFileSync as writeFileSync5
|
|
12075
12337
|
} from "node:fs";
|
|
12076
|
-
import
|
|
12338
|
+
import path60 from "node:path";
|
|
12077
12339
|
|
|
12078
12340
|
// src/heavy-verification/paths.ts
|
|
12079
12341
|
import { mkdirSync as mkdirSync7 } from "node:fs";
|
|
12080
|
-
import
|
|
12342
|
+
import path59 from "node:path";
|
|
12081
12343
|
function resolveHeavyVerificationRoot() {
|
|
12082
|
-
return
|
|
12344
|
+
return path59.join(resolveKynverStateRoot(), "heavy-verification");
|
|
12083
12345
|
}
|
|
12084
12346
|
function heavyVerificationSlotsDir() {
|
|
12085
|
-
return
|
|
12347
|
+
return path59.join(resolveHeavyVerificationRoot(), "slots");
|
|
12086
12348
|
}
|
|
12087
12349
|
function ensureHeavyVerificationDirs() {
|
|
12088
12350
|
const dir = heavyVerificationSlotsDir();
|
|
@@ -12111,7 +12373,7 @@ function indexedSlotId(index) {
|
|
|
12111
12373
|
return `slot-${index}`;
|
|
12112
12374
|
}
|
|
12113
12375
|
function slotFilePath(slotId, slotsDir = heavyVerificationSlotsDir()) {
|
|
12114
|
-
return
|
|
12376
|
+
return path60.join(slotsDir, `${slotId}.json`);
|
|
12115
12377
|
}
|
|
12116
12378
|
function readSlotRecord(filePath) {
|
|
12117
12379
|
if (!existsSync44(filePath)) return null;
|
|
@@ -12150,7 +12412,7 @@ function reclaimStaleHeavyVerificationSlots(opts = {}) {
|
|
|
12150
12412
|
let reclaimed = 0;
|
|
12151
12413
|
for (const name of readdirSync15(slotsDir)) {
|
|
12152
12414
|
if (!name.endsWith(".json")) continue;
|
|
12153
|
-
const filePath =
|
|
12415
|
+
const filePath = path60.join(slotsDir, name);
|
|
12154
12416
|
const before = existsSync44(filePath);
|
|
12155
12417
|
reclaimStaleSlot(filePath, staleMs);
|
|
12156
12418
|
if (before && !existsSync44(filePath)) reclaimed += 1;
|
|
@@ -12164,7 +12426,7 @@ function listActiveHeavyVerificationSlots(opts = {}) {
|
|
|
12164
12426
|
const active = [];
|
|
12165
12427
|
for (const name of readdirSync15(slotsDir)) {
|
|
12166
12428
|
if (!name.endsWith(".json")) continue;
|
|
12167
|
-
const record3 = readSlotRecord(
|
|
12429
|
+
const record3 = readSlotRecord(path60.join(slotsDir, name));
|
|
12168
12430
|
if (record3 && !slotIsStale(record3, staleMs)) active.push(record3);
|
|
12169
12431
|
}
|
|
12170
12432
|
return active;
|
|
@@ -12284,11 +12546,11 @@ function waitForHeavyVerificationSlot(command, timeoutMs, pollMs = 2e3, opts = {
|
|
|
12284
12546
|
}
|
|
12285
12547
|
|
|
12286
12548
|
// src/harness-worktree-build-guard.ts
|
|
12287
|
-
import
|
|
12549
|
+
import path61 from "node:path";
|
|
12288
12550
|
function isPathUnderHarnessWorktree(cwd) {
|
|
12289
12551
|
const worktreesDir = harnessWorktreesDir(resolveHarnessRoot());
|
|
12290
|
-
const rel =
|
|
12291
|
-
return rel.length > 0 && !rel.startsWith("..") && !
|
|
12552
|
+
const rel = path61.relative(worktreesDir, path61.resolve(cwd));
|
|
12553
|
+
return rel.length > 0 && !rel.startsWith("..") && !path61.isAbsolute(rel);
|
|
12292
12554
|
}
|
|
12293
12555
|
function assessHarnessWorktreeBuildGuard(cwd) {
|
|
12294
12556
|
if (!isPathUnderHarnessWorktree(cwd)) return { ok: true };
|
|
@@ -12500,7 +12762,7 @@ async function emitPlanProgress(args) {
|
|
|
12500
12762
|
}
|
|
12501
12763
|
function verifyPlanLocal(args) {
|
|
12502
12764
|
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
12503
|
-
const cwd =
|
|
12765
|
+
const cwd = path62.resolve(worktree);
|
|
12504
12766
|
const summary = runHarnessVerifyCommands(cwd);
|
|
12505
12767
|
const emitJson = args.json === true || args.json === "true";
|
|
12506
12768
|
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
@@ -12549,9 +12811,9 @@ async function verifyPlan(args) {
|
|
|
12549
12811
|
}
|
|
12550
12812
|
|
|
12551
12813
|
// src/harness-verify-cli.ts
|
|
12552
|
-
import
|
|
12814
|
+
import path63 from "node:path";
|
|
12553
12815
|
function runHarnessVerifyCli(args) {
|
|
12554
|
-
const cwd =
|
|
12816
|
+
const cwd = path63.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
12555
12817
|
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
12556
12818
|
const commands = [];
|
|
12557
12819
|
const rawCmd = args.command;
|
|
@@ -12988,7 +13250,7 @@ ${text.slice(0, 800)}`,
|
|
|
12988
13250
|
}
|
|
12989
13251
|
|
|
12990
13252
|
// src/monitor/monitor.service.ts
|
|
12991
|
-
import
|
|
13253
|
+
import path65 from "node:path";
|
|
12992
13254
|
|
|
12993
13255
|
// src/monitor/monitor.classify.ts
|
|
12994
13256
|
function classifyWorkerHealth(input) {
|
|
@@ -13041,10 +13303,10 @@ function classifyWorkerHealth(input) {
|
|
|
13041
13303
|
|
|
13042
13304
|
// src/monitor/monitor.store.ts
|
|
13043
13305
|
import { existsSync as existsSync45, mkdirSync as mkdirSync9, readdirSync as readdirSync16, unlinkSync as unlinkSync5 } from "node:fs";
|
|
13044
|
-
import
|
|
13306
|
+
import path64 from "node:path";
|
|
13045
13307
|
function monitorsDir() {
|
|
13046
13308
|
const { harnessRoot } = getHarnessPaths();
|
|
13047
|
-
const dir =
|
|
13309
|
+
const dir = path64.join(harnessRoot, "monitors");
|
|
13048
13310
|
mkdirSync9(dir, { recursive: true });
|
|
13049
13311
|
return dir;
|
|
13050
13312
|
}
|
|
@@ -13052,7 +13314,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
13052
13314
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
13053
13315
|
}
|
|
13054
13316
|
function monitorPath(monitorId) {
|
|
13055
|
-
return
|
|
13317
|
+
return path64.join(monitorsDir(), `${monitorId}.json`);
|
|
13056
13318
|
}
|
|
13057
13319
|
function loadMonitorSession(monitorId) {
|
|
13058
13320
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -13073,7 +13335,7 @@ function listMonitorSessions() {
|
|
|
13073
13335
|
for (const name of readdirSync16(dir)) {
|
|
13074
13336
|
if (!name.endsWith(".json")) continue;
|
|
13075
13337
|
const session = readJson(
|
|
13076
|
-
|
|
13338
|
+
path64.join(dir, name),
|
|
13077
13339
|
void 0
|
|
13078
13340
|
);
|
|
13079
13341
|
if (!session?.monitorId) continue;
|
|
@@ -13164,7 +13426,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
13164
13426
|
// src/monitor/monitor.service.ts
|
|
13165
13427
|
function workerRecord2(runId, name) {
|
|
13166
13428
|
return readJson(
|
|
13167
|
-
|
|
13429
|
+
path65.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
13168
13430
|
void 0
|
|
13169
13431
|
);
|
|
13170
13432
|
}
|
|
@@ -13371,17 +13633,17 @@ async function runMonitorLoop(args) {
|
|
|
13371
13633
|
// src/monitor/monitor-spawn.ts
|
|
13372
13634
|
import { spawn as spawn6 } from "node:child_process";
|
|
13373
13635
|
import { closeSync as closeSync8, existsSync as existsSync46, openSync as openSync8 } from "node:fs";
|
|
13374
|
-
import
|
|
13636
|
+
import path66 from "node:path";
|
|
13375
13637
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
13376
13638
|
function resolveDefaultCliPath2() {
|
|
13377
|
-
return
|
|
13639
|
+
return path66.join(fileURLToPath4(new URL(".", import.meta.url)), "cli.js");
|
|
13378
13640
|
}
|
|
13379
13641
|
function spawnMonitorSidecar(opts) {
|
|
13380
13642
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
13381
13643
|
if (!existsSync46(cliPath)) return void 0;
|
|
13382
13644
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
13383
13645
|
const { harnessRoot } = getHarnessPaths();
|
|
13384
|
-
const logPath =
|
|
13646
|
+
const logPath = path66.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
13385
13647
|
let logFd;
|
|
13386
13648
|
try {
|
|
13387
13649
|
logFd = openSync8(logPath, "a");
|
|
@@ -13501,7 +13763,7 @@ async function monitorTickCli(args) {
|
|
|
13501
13763
|
}
|
|
13502
13764
|
|
|
13503
13765
|
// src/post-restart-unblock.ts
|
|
13504
|
-
import
|
|
13766
|
+
import path67 from "node:path";
|
|
13505
13767
|
function skip(runId, worker, taskId, agentOsId, leaseOwner, reason) {
|
|
13506
13768
|
return { runId, worker, taskId, agentOsId, leaseOwner, action: "skipped", reason };
|
|
13507
13769
|
}
|
|
@@ -13514,7 +13776,7 @@ async function postRestartUnblock(args) {
|
|
|
13514
13776
|
const errors = [];
|
|
13515
13777
|
for (const run of listRunRecords()) {
|
|
13516
13778
|
for (const name of Object.keys(run.workers ?? {})) {
|
|
13517
|
-
const workerPath =
|
|
13779
|
+
const workerPath = path67.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
13518
13780
|
const worker = readJson(workerPath, void 0);
|
|
13519
13781
|
if (!worker) {
|
|
13520
13782
|
skipped.push(skip(run.id, name, "", "", "", "worker.json missing"));
|
|
@@ -13626,9 +13888,9 @@ async function postRestartUnblockCli(args) {
|
|
|
13626
13888
|
}
|
|
13627
13889
|
|
|
13628
13890
|
// src/default-repo-cli.ts
|
|
13629
|
-
import
|
|
13891
|
+
import path68 from "node:path";
|
|
13630
13892
|
import { homedir as homedir15 } from "node:os";
|
|
13631
|
-
var CONFIG_FILE2 =
|
|
13893
|
+
var CONFIG_FILE2 = path68.join(homedir15(), ".kynver", "config.json");
|
|
13632
13894
|
function ensureDefaultRepo(opts) {
|
|
13633
13895
|
const existing = loadUserConfig();
|
|
13634
13896
|
const resolved = resolveDefaultRepo({ ...opts, config: existing });
|
|
@@ -13709,12 +13971,12 @@ function summarizeResolvedDefaultRepo(resolved) {
|
|
|
13709
13971
|
}
|
|
13710
13972
|
|
|
13711
13973
|
// src/doctor/runtime-takeover.ts
|
|
13712
|
-
import
|
|
13974
|
+
import path70 from "node:path";
|
|
13713
13975
|
|
|
13714
13976
|
// src/doctor/runtime-takeover.probes.ts
|
|
13715
13977
|
import { accessSync, constants, existsSync as existsSync47, readFileSync as readFileSync19 } from "node:fs";
|
|
13716
13978
|
import { homedir as homedir16 } from "node:os";
|
|
13717
|
-
import
|
|
13979
|
+
import path69 from "node:path";
|
|
13718
13980
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
13719
13981
|
function captureCommand(bin, args) {
|
|
13720
13982
|
try {
|
|
@@ -13756,10 +14018,10 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
13756
14018
|
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
13757
14019
|
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
13758
14020
|
loadConfig: () => loadUserConfig(),
|
|
13759
|
-
configFilePath: () =>
|
|
13760
|
-
credentialsFilePath: () =>
|
|
14021
|
+
configFilePath: () => path69.join(homedir16(), ".kynver", "config.json"),
|
|
14022
|
+
credentialsFilePath: () => path69.join(homedir16(), ".kynver", "credentials"),
|
|
13761
14023
|
readCredentials: () => {
|
|
13762
|
-
const credPath =
|
|
14024
|
+
const credPath = path69.join(homedir16(), ".kynver", "credentials");
|
|
13763
14025
|
if (!existsSync47(credPath)) {
|
|
13764
14026
|
return { hasApiKey: false };
|
|
13765
14027
|
}
|
|
@@ -13794,7 +14056,7 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
13794
14056
|
})()
|
|
13795
14057
|
}),
|
|
13796
14058
|
harnessRoot: () => resolveHarnessRoot(),
|
|
13797
|
-
legacyOpenclawHarnessRoot: () =>
|
|
14059
|
+
legacyOpenclawHarnessRoot: () => path69.join(homedir16(), ".openclaw", "harness"),
|
|
13798
14060
|
pathExists: (target) => existsSync47(target),
|
|
13799
14061
|
pathWritable: (target) => isWritable(target)
|
|
13800
14062
|
};
|
|
@@ -14201,8 +14463,8 @@ function assessVercelDeployEvidence(probes) {
|
|
|
14201
14463
|
}
|
|
14202
14464
|
function assessHarnessDirs(probes) {
|
|
14203
14465
|
const harnessRoot = probes.harnessRoot();
|
|
14204
|
-
const runsDir =
|
|
14205
|
-
const worktreesDir =
|
|
14466
|
+
const runsDir = path70.join(harnessRoot, "runs");
|
|
14467
|
+
const worktreesDir = path70.join(harnessRoot, "worktrees");
|
|
14206
14468
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
14207
14469
|
const displayRunsDir = redactHomePath(runsDir);
|
|
14208
14470
|
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
@@ -14466,9 +14728,9 @@ function applySchedulerCutoverAttestation(config) {
|
|
|
14466
14728
|
}
|
|
14467
14729
|
|
|
14468
14730
|
// src/scheduler-cutover-cli.ts
|
|
14469
|
-
import
|
|
14731
|
+
import path71 from "node:path";
|
|
14470
14732
|
import { homedir as homedir17 } from "node:os";
|
|
14471
|
-
var CONFIG_FILE3 =
|
|
14733
|
+
var CONFIG_FILE3 = path71.join(homedir17(), ".kynver", "config.json");
|
|
14472
14734
|
function runSchedulerCutoverCheckCli(json = false) {
|
|
14473
14735
|
const config = loadUserConfig();
|
|
14474
14736
|
const report = assessSchedulerCutover(config);
|
|
@@ -14605,6 +14867,157 @@ async function runCronTickCli(args) {
|
|
|
14605
14867
|
);
|
|
14606
14868
|
}
|
|
14607
14869
|
|
|
14870
|
+
// src/lane/landing-maintainer-tick.ts
|
|
14871
|
+
import os9 from "node:os";
|
|
14872
|
+
|
|
14873
|
+
// src/lane/lane-spec.ts
|
|
14874
|
+
var LANDING_MAINTAINER_LANE_SPEC = {
|
|
14875
|
+
slug: "landing-maintainer",
|
|
14876
|
+
originCron: "maintain-8-blocker-and-pr-landing-workers",
|
|
14877
|
+
defaultRepo: "Totalsolutionsync/Kynver",
|
|
14878
|
+
landScript: "scripts/agent-os-land-pr.mjs",
|
|
14879
|
+
landScriptArgs: ["--skip-not-ready"]
|
|
14880
|
+
};
|
|
14881
|
+
|
|
14882
|
+
// src/lane/landing-maintainer-local.ts
|
|
14883
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
14884
|
+
import path72 from "node:path";
|
|
14885
|
+
function runLandingWrapper(prNumber, repoRoot, execute) {
|
|
14886
|
+
const script = path72.join(repoRoot, LANDING_MAINTAINER_LANE_SPEC.landScript);
|
|
14887
|
+
const args = [script, String(prNumber), ...LANDING_MAINTAINER_LANE_SPEC.landScriptArgs];
|
|
14888
|
+
if (!execute) {
|
|
14889
|
+
return {
|
|
14890
|
+
action: { kind: "land_pr", prNumber, reason: "dry-run" },
|
|
14891
|
+
executed: false,
|
|
14892
|
+
exitCode: 0,
|
|
14893
|
+
stdout: `dry-run: node ${args.join(" ")}`,
|
|
14894
|
+
stderr: ""
|
|
14895
|
+
};
|
|
14896
|
+
}
|
|
14897
|
+
const result = spawnSync11("node", args, {
|
|
14898
|
+
cwd: repoRoot,
|
|
14899
|
+
encoding: "utf8",
|
|
14900
|
+
timeout: 10 * 60 * 1e3
|
|
14901
|
+
});
|
|
14902
|
+
return {
|
|
14903
|
+
action: { kind: "land_pr", prNumber, reason: "landing wrapper invoked" },
|
|
14904
|
+
executed: true,
|
|
14905
|
+
exitCode: result.status,
|
|
14906
|
+
stdout: result.stdout ?? "",
|
|
14907
|
+
stderr: result.stderr ?? ""
|
|
14908
|
+
};
|
|
14909
|
+
}
|
|
14910
|
+
function resolveLandingMaintainerRepoRoot(args) {
|
|
14911
|
+
const explicit = args.repoPath ? String(args.repoPath).trim() : "";
|
|
14912
|
+
if (explicit) return path72.resolve(explicit);
|
|
14913
|
+
const resolved = resolveDefaultRepo();
|
|
14914
|
+
return resolved?.repo ?? process.cwd();
|
|
14915
|
+
}
|
|
14916
|
+
|
|
14917
|
+
// src/lane/landing-maintainer-tick.ts
|
|
14918
|
+
async function runLandingMaintainerLaneTick(args) {
|
|
14919
|
+
const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
|
|
14920
|
+
const repoSlug = String(args.repo || LANDING_MAINTAINER_LANE_SPEC.defaultRepo).trim();
|
|
14921
|
+
const fleet = args.fleet === true || args.fleet === "true";
|
|
14922
|
+
const execute = args.execute !== false && args.execute !== "false";
|
|
14923
|
+
const runId = args.run ? String(args.run) : void 0;
|
|
14924
|
+
const resourceGate = observeRunnerResourceGate({
|
|
14925
|
+
runId: runId ?? "fleet-lane-tick"
|
|
14926
|
+
});
|
|
14927
|
+
const boxCapacity = {
|
|
14928
|
+
...buildBoxResourceSnapshotFromGate(resourceGate, {
|
|
14929
|
+
harnessRunId: runId,
|
|
14930
|
+
boxKind: resolveBoxKindFromConfig(loadUserConfig()),
|
|
14931
|
+
hostLabel: os9.hostname()
|
|
14932
|
+
}),
|
|
14933
|
+
providerHealthy: resourceGate.ok,
|
|
14934
|
+
authorizedForRepair: resourceGate.ok,
|
|
14935
|
+
authorizedForLanding: resourceGate.ok,
|
|
14936
|
+
systemHealthBlockers: resourceGate.ok ? [] : [resourceGate.reason ?? "resource_gate_blocked"],
|
|
14937
|
+
actionableWorkers: resourceGate.activeWorkers
|
|
14938
|
+
};
|
|
14939
|
+
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
14940
|
+
const secret = await resolveCallbackSecretWithMint(
|
|
14941
|
+
args.secret ? String(args.secret) : void 0,
|
|
14942
|
+
agentOsId,
|
|
14943
|
+
{ baseUrl: base }
|
|
14944
|
+
);
|
|
14945
|
+
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/fleet-landing-maintainer/tick`;
|
|
14946
|
+
const res = await postJson(url, secret, {
|
|
14947
|
+
repo: repoSlug,
|
|
14948
|
+
fleet,
|
|
14949
|
+
execute,
|
|
14950
|
+
runId,
|
|
14951
|
+
boxCapacity
|
|
14952
|
+
});
|
|
14953
|
+
const coordinator = res.response;
|
|
14954
|
+
const localOutcomes = [];
|
|
14955
|
+
const repoRoot = resolveLandingMaintainerRepoRoot(args);
|
|
14956
|
+
const actions = Array.isArray(coordinator?.localActions) ? coordinator.localActions : [];
|
|
14957
|
+
const holderBoxId = boxCapacity.boxId;
|
|
14958
|
+
for (const action of actions) {
|
|
14959
|
+
if (action.kind === "land_pr" && typeof action.prNumber === "number") {
|
|
14960
|
+
const outcome = runLandingWrapper(action.prNumber, repoRoot, execute);
|
|
14961
|
+
localOutcomes.push({
|
|
14962
|
+
action: outcome.action,
|
|
14963
|
+
executed: outcome.executed,
|
|
14964
|
+
exitCode: outcome.exitCode
|
|
14965
|
+
});
|
|
14966
|
+
if (execute && outcome.executed) {
|
|
14967
|
+
const merged = outcome.exitCode === 0;
|
|
14968
|
+
const prUrl = typeof action.prUrl === "string" && action.prUrl.trim() ? action.prUrl.trim() : `https://github.com/${repoSlug}/pull/${action.prNumber}`;
|
|
14969
|
+
try {
|
|
14970
|
+
await postJson(
|
|
14971
|
+
`${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/fleet-landing-maintainer/outcome`,
|
|
14972
|
+
secret,
|
|
14973
|
+
{ repo: repoSlug, prUrl, holderBoxId, merged }
|
|
14974
|
+
);
|
|
14975
|
+
} catch {
|
|
14976
|
+
}
|
|
14977
|
+
}
|
|
14978
|
+
continue;
|
|
14979
|
+
}
|
|
14980
|
+
localOutcomes.push({
|
|
14981
|
+
action,
|
|
14982
|
+
executed: false,
|
|
14983
|
+
exitCode: null
|
|
14984
|
+
});
|
|
14985
|
+
}
|
|
14986
|
+
return {
|
|
14987
|
+
repo: repoSlug,
|
|
14988
|
+
fleet,
|
|
14989
|
+
execute,
|
|
14990
|
+
coordinator,
|
|
14991
|
+
localOutcomes
|
|
14992
|
+
};
|
|
14993
|
+
}
|
|
14994
|
+
|
|
14995
|
+
// src/lane/lane-tick-cli.ts
|
|
14996
|
+
async function runLaneTickCli(args, laneFromArgv) {
|
|
14997
|
+
const lane = String(laneFromArgv ?? args.lane ?? "").trim();
|
|
14998
|
+
if (lane !== "landing-maintainer") {
|
|
14999
|
+
console.error(`unknown lane: ${lane || "(none)"}`);
|
|
15000
|
+
process.exit(1);
|
|
15001
|
+
}
|
|
15002
|
+
const result = await runLandingMaintainerLaneTick(args);
|
|
15003
|
+
if (args.json === true || args.json === "true") {
|
|
15004
|
+
console.log(JSON.stringify(result, null, 2));
|
|
15005
|
+
return;
|
|
15006
|
+
}
|
|
15007
|
+
console.log(
|
|
15008
|
+
[
|
|
15009
|
+
`fleet landing-maintainer tick`,
|
|
15010
|
+
`repo=${result.repo}`,
|
|
15011
|
+
`fleet=${result.fleet}`,
|
|
15012
|
+
`execute=${result.execute}`,
|
|
15013
|
+
`localOutcomes=${result.localOutcomes.length}`
|
|
15014
|
+
].join(" ")
|
|
15015
|
+
);
|
|
15016
|
+
for (const row of result.localOutcomes) {
|
|
15017
|
+
console.log(` ${row.action.kind} pr=${row.action.prNumber ?? "-"} executed=${row.executed}`);
|
|
15018
|
+
}
|
|
15019
|
+
}
|
|
15020
|
+
|
|
14608
15021
|
// src/cli.ts
|
|
14609
15022
|
function isHelpFlag(arg) {
|
|
14610
15023
|
return arg === "help" || arg === "--help" || arg === "-h";
|
|
@@ -14625,6 +15038,7 @@ function usage(code = 0) {
|
|
|
14625
15038
|
" kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS]",
|
|
14626
15039
|
" kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
|
|
14627
15040
|
" kynver run list",
|
|
15041
|
+
" kynver run resolve --name RUN_NAME",
|
|
14628
15042
|
" kynver run status --run RUN_ID [--json] [--compact]",
|
|
14629
15043
|
" kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--target-task-id TASK_ID] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-8] [--disk-path /] [--reconcile-stale-blockers]",
|
|
14630
15044
|
" kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
|
|
@@ -14660,6 +15074,7 @@ function usage(code = 0) {
|
|
|
14660
15074
|
" kynver scheduler attest-cutover [--json]",
|
|
14661
15075
|
" kynver cron status [--json]",
|
|
14662
15076
|
" kynver cron tick [--agent-os-id AOS_ID] [--json]",
|
|
15077
|
+
" kynver lane tick landing-maintainer [--fleet] [--repo OWNER/NAME] [--agent-os-id AOS_ID] [--execute] [--json]",
|
|
14663
15078
|
" kynver board contract [--agent-os-id ID] [--base-url URL] [--since ISO] [--limit N]"
|
|
14664
15079
|
].join("\n")
|
|
14665
15080
|
);
|
|
@@ -14671,7 +15086,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
14671
15086
|
const scope = argv.shift();
|
|
14672
15087
|
let action;
|
|
14673
15088
|
let rest;
|
|
14674
|
-
if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "harness" || scope === "monitor" || scope === "doctor" || scope === "config" || scope === "scheduler" || scope === "cron" || scope === "board") {
|
|
15089
|
+
if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "harness" || scope === "monitor" || scope === "doctor" || scope === "config" || scope === "scheduler" || scope === "cron" || scope === "lane" || scope === "board") {
|
|
14675
15090
|
action = argv.shift();
|
|
14676
15091
|
rest = argv;
|
|
14677
15092
|
} else {
|
|
@@ -14728,11 +15143,16 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
14728
15143
|
if (scope === "cron" && action === "tick") {
|
|
14729
15144
|
return void await runCronTickCli(args);
|
|
14730
15145
|
}
|
|
15146
|
+
if (scope === "lane" && action === "tick") {
|
|
15147
|
+
const laneName = rest.shift();
|
|
15148
|
+
return void await runLaneTickCli(parseArgs(rest), laneName);
|
|
15149
|
+
}
|
|
14731
15150
|
if (scope === "board" && action === "contract") {
|
|
14732
15151
|
return void await runCommandCenterContractCli(args);
|
|
14733
15152
|
}
|
|
14734
15153
|
if (scope === "run" && action === "create") return createRun(args);
|
|
14735
15154
|
if (scope === "run" && action === "list") return listRuns();
|
|
15155
|
+
if (scope === "run" && action === "resolve") return resolveHarnessRunCli(args);
|
|
14736
15156
|
if (scope === "run" && action === "status") return runStatus(args);
|
|
14737
15157
|
if (scope === "run" && action === "dispatch") return void await dispatchRun(args);
|
|
14738
15158
|
if (scope === "run" && action === "sweep") return void await sweepRun(args);
|
|
@@ -15226,6 +15646,7 @@ export {
|
|
|
15226
15646
|
hasNestedRunsSegment,
|
|
15227
15647
|
hashPlanBody,
|
|
15228
15648
|
hermesCodexProvider,
|
|
15649
|
+
isActiveHarnessWorker,
|
|
15229
15650
|
isClaudeFamilyProvider,
|
|
15230
15651
|
isDashboardVercelUrl,
|
|
15231
15652
|
isEngagementRequiredSkip,
|
|
@@ -15289,6 +15710,7 @@ export {
|
|
|
15289
15710
|
readMemAvailableBytes,
|
|
15290
15711
|
readProductionDbKeysFromEnvFile,
|
|
15291
15712
|
reclaimStaleHeavyVerificationSlots,
|
|
15713
|
+
reconcileLocalOnlyMergedPrAttention,
|
|
15292
15714
|
reconcileRunsCli,
|
|
15293
15715
|
reconcileStaleWorkers,
|
|
15294
15716
|
reconcileWorkerMetadata,
|
|
@@ -15307,6 +15729,8 @@ export {
|
|
|
15307
15729
|
resolveConfiguredWorkerProvider,
|
|
15308
15730
|
resolveDefaultRepo,
|
|
15309
15731
|
resolveHarnessRoot,
|
|
15732
|
+
resolveHarnessRunByName,
|
|
15733
|
+
resolveHarnessRunCli,
|
|
15310
15734
|
resolveHeavyVerificationMaxConcurrent,
|
|
15311
15735
|
resolveOpenAiCodexRetryBudget,
|
|
15312
15736
|
resolveOrchestrationPolicyMode,
|