@kynver-app/runtime 0.1.99 → 0.1.103
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 +628 -198
- 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 +623 -189
- 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,
|
|
@@ -7341,6 +7367,11 @@ function readAdmissionExhaustion(result) {
|
|
|
7341
7367
|
if (!raw || typeof raw !== "object") return null;
|
|
7342
7368
|
return raw;
|
|
7343
7369
|
}
|
|
7370
|
+
function readDispatchSkipDrain(result) {
|
|
7371
|
+
const raw = result.dispatchSkipDrain;
|
|
7372
|
+
if (!raw || typeof raw !== "object") return null;
|
|
7373
|
+
return raw;
|
|
7374
|
+
}
|
|
7344
7375
|
function readHarnessWorkerContext(decision) {
|
|
7345
7376
|
const raw = decision.harnessWorkerContext;
|
|
7346
7377
|
if (!raw || typeof raw !== "object") return null;
|
|
@@ -7504,6 +7535,7 @@ async function dispatchRun(args) {
|
|
|
7504
7535
|
const result = first.result;
|
|
7505
7536
|
if (dryRun) {
|
|
7506
7537
|
const admissionExhaustion2 = readAdmissionExhaustion(result);
|
|
7538
|
+
const dispatchSkipDrain2 = readDispatchSkipDrain(result);
|
|
7507
7539
|
const summary2 = {
|
|
7508
7540
|
runId: run.id,
|
|
7509
7541
|
agentOsId,
|
|
@@ -7522,7 +7554,8 @@ async function dispatchRun(args) {
|
|
|
7522
7554
|
pagesScanned: result.pagesScanned ?? null,
|
|
7523
7555
|
candidatesExhausted: result.candidatesExhausted ?? null,
|
|
7524
7556
|
capacityIdle: admissionExhaustion2?.capacityIdle === true,
|
|
7525
|
-
admissionExhaustion: admissionExhaustion2
|
|
7557
|
+
admissionExhaustion: admissionExhaustion2,
|
|
7558
|
+
dispatchSkipDrain: dispatchSkipDrain2
|
|
7526
7559
|
};
|
|
7527
7560
|
if (pipeline) return { ok: true, ...summary2 };
|
|
7528
7561
|
console.log(JSON.stringify(summary2, null, 2));
|
|
@@ -7578,7 +7611,7 @@ async function dispatchRun(args) {
|
|
|
7578
7611
|
);
|
|
7579
7612
|
}
|
|
7580
7613
|
const attempt = Number(task.attempt) || 1;
|
|
7581
|
-
if (attempt
|
|
7614
|
+
if (attempt > retryLimits.maxTaskAttempts) {
|
|
7582
7615
|
return abortClaimedSpawn(
|
|
7583
7616
|
task,
|
|
7584
7617
|
`task attempt ${attempt} exceeds KYNVER_MAX_TASK_ATTEMPTS (${retryLimits.maxTaskAttempts})`
|
|
@@ -7712,9 +7745,16 @@ async function dispatchRun(args) {
|
|
|
7712
7745
|
}
|
|
7713
7746
|
const startedCount = outcomes.filter((o) => o.started).length;
|
|
7714
7747
|
const admissionExhaustion = readAdmissionExhaustion(result);
|
|
7715
|
-
const
|
|
7748
|
+
const dispatchSkipDrain = readDispatchSkipDrain(result);
|
|
7749
|
+
const capacityIdle = startedCount === 0 && (admissionExhaustion?.capacityIdle === true || Number(result.resourceGate?.slotsAvailable) > 0);
|
|
7716
7750
|
if (capacityIdle && admissionExhaustion?.summary) {
|
|
7717
|
-
|
|
7751
|
+
const retryCeiling = admissionExhaustion.skipReasonCounts?.retry_ceiling_exceeded ?? 0;
|
|
7752
|
+
const recovery = result.overAttemptIdleRecovery ?? admissionExhaustion.overAttemptIdleRecovery;
|
|
7753
|
+
const recoveryNote = recovery?.attempted === true ? `; over_attempt_recovery minted=${recovery.minted ?? 0} started=${recovery.started ?? 0}` : retryCeiling > 0 ? "; over_attempt_recovery not attempted" : "";
|
|
7754
|
+
const drainNote = dispatchSkipDrain ? `; dispatch_skip_drain scanned=${dispatchSkipDrain.scanned ?? 0} advanced=${dispatchSkipDrain.advanced ?? 0}` : "";
|
|
7755
|
+
console.error(
|
|
7756
|
+
`[dispatch] ${admissionExhaustion.summary}${retryCeiling > 0 ? `; retry_ceiling_exceeded=${retryCeiling}` : ""}${recoveryNote}${drainNote}`
|
|
7757
|
+
);
|
|
7718
7758
|
}
|
|
7719
7759
|
const summary = {
|
|
7720
7760
|
runId: run.id,
|
|
@@ -7724,6 +7764,7 @@ async function dispatchRun(args) {
|
|
|
7724
7764
|
startedCount,
|
|
7725
7765
|
capacityIdle,
|
|
7726
7766
|
admissionExhaustion,
|
|
7767
|
+
dispatchSkipDrain,
|
|
7727
7768
|
outcomes,
|
|
7728
7769
|
skipped: skipped.map((d) => ({
|
|
7729
7770
|
taskId: d.task.id,
|
|
@@ -8299,14 +8340,14 @@ function applyProductionDatabaseToProcess(options = {}) {
|
|
|
8299
8340
|
|
|
8300
8341
|
// src/worktree.ts
|
|
8301
8342
|
import { existsSync as existsSync29, mkdirSync as mkdirSync6 } from "node:fs";
|
|
8302
|
-
import
|
|
8343
|
+
import path38 from "node:path";
|
|
8303
8344
|
|
|
8304
8345
|
// src/run-list.ts
|
|
8305
8346
|
import { existsSync as existsSync28, readFileSync as readFileSync15 } from "node:fs";
|
|
8306
|
-
import
|
|
8347
|
+
import path37 from "node:path";
|
|
8307
8348
|
|
|
8308
8349
|
// src/stale-reconcile.ts
|
|
8309
|
-
import
|
|
8350
|
+
import path36 from "node:path";
|
|
8310
8351
|
|
|
8311
8352
|
// src/finalize.ts
|
|
8312
8353
|
import path30 from "node:path";
|
|
@@ -8941,6 +8982,193 @@ function reconcileWorkerMetadataCli() {
|
|
|
8941
8982
|
);
|
|
8942
8983
|
}
|
|
8943
8984
|
|
|
8985
|
+
// src/local-pr-attention-reconcile.ts
|
|
8986
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
8987
|
+
import path35 from "node:path";
|
|
8988
|
+
function normalizePrUrl3(url) {
|
|
8989
|
+
const m = url.trim().match(/github\.com\/([^/]+\/[^/]+)\/(?:pull|pulls)\/(\d+)/i);
|
|
8990
|
+
if (!m) return null;
|
|
8991
|
+
return `https://github.com/${m[1]}/pull/${m[2]}`;
|
|
8992
|
+
}
|
|
8993
|
+
function prNumberFromUrl(url) {
|
|
8994
|
+
const m = normalizePrUrl3(url)?.match(/\/pull\/(\d+)$/);
|
|
8995
|
+
if (!m) return null;
|
|
8996
|
+
const n = Number.parseInt(m[1], 10);
|
|
8997
|
+
return Number.isFinite(n) ? n : null;
|
|
8998
|
+
}
|
|
8999
|
+
function extractText(value) {
|
|
9000
|
+
if (value == null) return "";
|
|
9001
|
+
if (typeof value === "string") return value;
|
|
9002
|
+
try {
|
|
9003
|
+
return JSON.stringify(value);
|
|
9004
|
+
} catch {
|
|
9005
|
+
return "";
|
|
9006
|
+
}
|
|
9007
|
+
}
|
|
9008
|
+
function extractPrNumbersFromText(text) {
|
|
9009
|
+
const out = /* @__PURE__ */ new Set();
|
|
9010
|
+
for (const match of text.matchAll(/github\.com\/[^/\s)>"']+\/[^/\s)>"']+\/(?:pull|pulls)\/(\d+)/gi)) {
|
|
9011
|
+
const n = Number.parseInt(match[1], 10);
|
|
9012
|
+
if (Number.isFinite(n)) out.add(n);
|
|
9013
|
+
}
|
|
9014
|
+
for (const match of text.matchAll(/\bPR\s*#?\s*(\d{2,})\b/gi)) {
|
|
9015
|
+
const n = Number.parseInt(match[1], 10);
|
|
9016
|
+
if (Number.isFinite(n)) out.add(n);
|
|
9017
|
+
}
|
|
9018
|
+
for (const match of text.matchAll(/\bpr[-_]?(\d{2,})\b/gi)) {
|
|
9019
|
+
const n = Number.parseInt(match[1], 10);
|
|
9020
|
+
if (Number.isFinite(n)) out.add(n);
|
|
9021
|
+
}
|
|
9022
|
+
return [...out];
|
|
9023
|
+
}
|
|
9024
|
+
function extractTargetPrReconciliation(value) {
|
|
9025
|
+
let record3 = null;
|
|
9026
|
+
if (typeof value === "string") record3 = extractEmbeddedWorkerFinalResultRecord(value);
|
|
9027
|
+
else if (value && typeof value === "object" && !Array.isArray(value)) record3 = value;
|
|
9028
|
+
if (!record3) return [];
|
|
9029
|
+
const raw = record3.targetPrReconciliation ?? record3.target_pr_reconciliation;
|
|
9030
|
+
if (!Array.isArray(raw)) return [];
|
|
9031
|
+
const out = [];
|
|
9032
|
+
for (const item of raw) {
|
|
9033
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
9034
|
+
const row = item;
|
|
9035
|
+
const prUrl = normalizePrUrl3(String(row.prUrl ?? row.pr_url ?? ""));
|
|
9036
|
+
const outcome = String(row.outcome ?? "").trim();
|
|
9037
|
+
if (!prUrl || outcome !== "merged") continue;
|
|
9038
|
+
out.push({
|
|
9039
|
+
prUrl,
|
|
9040
|
+
mergeCommit: typeof row.mergeCommit === "string" ? row.mergeCommit : typeof row.merge_commit === "string" ? row.merge_commit : null,
|
|
9041
|
+
reason: typeof row.reason === "string" ? row.reason : null
|
|
9042
|
+
});
|
|
9043
|
+
}
|
|
9044
|
+
return out;
|
|
9045
|
+
}
|
|
9046
|
+
function defaultLookupPr(input) {
|
|
9047
|
+
try {
|
|
9048
|
+
const repo = execFileSync2("git", ["config", "--get", "remote.origin.url"], {
|
|
9049
|
+
cwd: input.repoDir,
|
|
9050
|
+
encoding: "utf8",
|
|
9051
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
9052
|
+
}).trim();
|
|
9053
|
+
const repoMatch = repo.match(/github\.com[:/]([^/]+\/[^/.]+)(?:\.git)?$/i);
|
|
9054
|
+
const repoSlug = repoMatch?.[1];
|
|
9055
|
+
if (!repoSlug) return null;
|
|
9056
|
+
const raw = execFileSync2(
|
|
9057
|
+
"gh",
|
|
9058
|
+
["pr", "view", String(input.prNumber), "--repo", repoSlug, "--json", "state,mergedAt,mergeCommit,url"],
|
|
9059
|
+
{ cwd: input.repoDir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
9060
|
+
);
|
|
9061
|
+
const parsed = JSON.parse(raw);
|
|
9062
|
+
return {
|
|
9063
|
+
prUrl: normalizePrUrl3(parsed.url ?? `https://github.com/${repoSlug}/pull/${input.prNumber}`) ?? `https://github.com/${repoSlug}/pull/${input.prNumber}`,
|
|
9064
|
+
state: parsed.state ?? "",
|
|
9065
|
+
mergedAt: parsed.mergedAt ?? null,
|
|
9066
|
+
mergeCommit: parsed.mergeCommit?.oid ?? null
|
|
9067
|
+
};
|
|
9068
|
+
} catch {
|
|
9069
|
+
return null;
|
|
9070
|
+
}
|
|
9071
|
+
}
|
|
9072
|
+
function finalResultForWorker(worker) {
|
|
9073
|
+
const heartbeat = parseHeartbeat(worker.heartbeatPath);
|
|
9074
|
+
return worker.completionSnapshot?.finalResult ?? terminalFinalResultFromHeartbeat(heartbeat);
|
|
9075
|
+
}
|
|
9076
|
+
function collectMergedEvidenceByPr(run) {
|
|
9077
|
+
const byPr = /* @__PURE__ */ new Map();
|
|
9078
|
+
const runDir2 = runDirectory(run.id);
|
|
9079
|
+
for (const name of listRunWorkerNames(run)) {
|
|
9080
|
+
const worker = readJson(
|
|
9081
|
+
path35.join(runDir2, "workers", safeSlug(name), "worker.json"),
|
|
9082
|
+
void 0
|
|
9083
|
+
);
|
|
9084
|
+
if (!worker) continue;
|
|
9085
|
+
for (const evidence of extractTargetPrReconciliation(finalResultForWorker(worker))) {
|
|
9086
|
+
const n = prNumberFromUrl(evidence.prUrl);
|
|
9087
|
+
if (n != null && !byPr.has(n)) byPr.set(n, evidence);
|
|
9088
|
+
}
|
|
9089
|
+
}
|
|
9090
|
+
return byPr;
|
|
9091
|
+
}
|
|
9092
|
+
function candidatePrNumbers(worker, statusFinalResult) {
|
|
9093
|
+
const heartbeat = parseHeartbeat(worker.heartbeatPath);
|
|
9094
|
+
const text = [
|
|
9095
|
+
worker.name,
|
|
9096
|
+
worker.repairTargetPrUrl,
|
|
9097
|
+
worker.taskPrUrl,
|
|
9098
|
+
worker.branch,
|
|
9099
|
+
heartbeat.lastHeartbeatSummary,
|
|
9100
|
+
extractText(statusFinalResult)
|
|
9101
|
+
].filter(Boolean).join("\n");
|
|
9102
|
+
return extractPrNumbersFromText(text);
|
|
9103
|
+
}
|
|
9104
|
+
function isLocalOnlyAttentionNoise(worker) {
|
|
9105
|
+
return worker.localOnly === true && !worker.taskId && !worker.agentOsId;
|
|
9106
|
+
}
|
|
9107
|
+
function reconcileLocalOnlyMergedPrAttention(options = {}) {
|
|
9108
|
+
const lookupPr = options.lookupPr ?? defaultLookupPr;
|
|
9109
|
+
const outcomes = [];
|
|
9110
|
+
const liveLookupCache = /* @__PURE__ */ new Map();
|
|
9111
|
+
for (const run of listRunRecords()) {
|
|
9112
|
+
const mergedByPr = collectMergedEvidenceByPr(run);
|
|
9113
|
+
const runDir2 = runDirectory(run.id);
|
|
9114
|
+
for (const name of listRunWorkerNames(run)) {
|
|
9115
|
+
const workerPath = path35.join(runDir2, "workers", safeSlug(name), "worker.json");
|
|
9116
|
+
const worker = readJson(workerPath, void 0);
|
|
9117
|
+
if (!worker || !isLocalOnlyAttentionNoise(worker)) continue;
|
|
9118
|
+
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
9119
|
+
if (status.attention.state !== "needs_attention") continue;
|
|
9120
|
+
let merged = null;
|
|
9121
|
+
for (const prNumber of candidatePrNumbers(worker, status.finalResult)) {
|
|
9122
|
+
merged = mergedByPr.get(prNumber) ?? null;
|
|
9123
|
+
if (!merged) {
|
|
9124
|
+
const repoDir = run.repo || worker.worktreePath;
|
|
9125
|
+
const cacheKey = `${repoDir}#${prNumber}`;
|
|
9126
|
+
const live = liveLookupCache.has(cacheKey) ? liveLookupCache.get(cacheKey) ?? null : lookupPr({ repoDir, prNumber });
|
|
9127
|
+
if (!liveLookupCache.has(cacheKey)) liveLookupCache.set(cacheKey, live);
|
|
9128
|
+
if (live && (live.state === "MERGED" || live.mergedAt)) {
|
|
9129
|
+
merged = { prUrl: live.prUrl, mergeCommit: live.mergeCommit ?? null, reason: "GitHub reports PR merged" };
|
|
9130
|
+
}
|
|
9131
|
+
}
|
|
9132
|
+
if (merged) break;
|
|
9133
|
+
}
|
|
9134
|
+
if (!merged) {
|
|
9135
|
+
outcomes.push({ runId: run.id, worker: name, action: "skipped", reason: "no merged PR evidence" });
|
|
9136
|
+
continue;
|
|
9137
|
+
}
|
|
9138
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9139
|
+
worker.status = "done";
|
|
9140
|
+
worker.completionSnapshot = {
|
|
9141
|
+
prUrl: merged.prUrl,
|
|
9142
|
+
summary: `Local-only worker superseded by merged PR ${merged.prUrl}`,
|
|
9143
|
+
finalResult: {
|
|
9144
|
+
summary: `Local-only repair/salvage worker superseded by merged PR ${merged.prUrl}`,
|
|
9145
|
+
targetPrReconciliation: [
|
|
9146
|
+
{
|
|
9147
|
+
prUrl: merged.prUrl,
|
|
9148
|
+
outcome: "merged",
|
|
9149
|
+
mergeCommit: merged.mergeCommit ?? null,
|
|
9150
|
+
reason: merged.reason ?? "PR already merged; local-only worker no longer requires attention"
|
|
9151
|
+
}
|
|
9152
|
+
]
|
|
9153
|
+
}
|
|
9154
|
+
};
|
|
9155
|
+
worker.completionAckSource = "local-pr-merged-reconcile";
|
|
9156
|
+
worker.reconciledAt = now;
|
|
9157
|
+
worker.reconcileReason = "local-only needs_attention superseded by merged PR";
|
|
9158
|
+
saveWorker(run.id, worker);
|
|
9159
|
+
outcomes.push({
|
|
9160
|
+
runId: run.id,
|
|
9161
|
+
worker: name,
|
|
9162
|
+
action: "marked_done",
|
|
9163
|
+
reason: worker.reconcileReason,
|
|
9164
|
+
prUrl: merged.prUrl,
|
|
9165
|
+
mergeCommit: merged.mergeCommit ?? null
|
|
9166
|
+
});
|
|
9167
|
+
}
|
|
9168
|
+
}
|
|
9169
|
+
return { workers: outcomes };
|
|
9170
|
+
}
|
|
9171
|
+
|
|
8944
9172
|
// src/stale-reconcile.ts
|
|
8945
9173
|
var STALE_RECONCILE_HEARTBEAT_MS = 15 * 60 * 1e3;
|
|
8946
9174
|
function staleReconcileDisabled() {
|
|
@@ -8949,13 +9177,14 @@ function staleReconcileDisabled() {
|
|
|
8949
9177
|
function reconcileStaleWorkers() {
|
|
8950
9178
|
const metadataReconcile = reconcileWorkerMetadata();
|
|
8951
9179
|
if (staleReconcileDisabled()) {
|
|
8952
|
-
|
|
9180
|
+
const localPrAttentionReconcile2 = reconcileLocalOnlyMergedPrAttention();
|
|
9181
|
+
return { workers: [], finalizedRuns: finalizeStaleRuns(), metadataReconcile, localPrAttentionReconcile: localPrAttentionReconcile2 };
|
|
8953
9182
|
}
|
|
8954
9183
|
const outcomes = [];
|
|
8955
9184
|
const now = Date.now();
|
|
8956
9185
|
for (const run of listRunRecords()) {
|
|
8957
9186
|
for (const name of listRunWorkerNames(run)) {
|
|
8958
|
-
const workerPath =
|
|
9187
|
+
const workerPath = path36.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
8959
9188
|
const worker = readJson(workerPath, void 0);
|
|
8960
9189
|
if (!worker || worker.status !== "running") {
|
|
8961
9190
|
outcomes.push({
|
|
@@ -9027,7 +9256,8 @@ function reconcileStaleWorkers() {
|
|
|
9027
9256
|
});
|
|
9028
9257
|
}
|
|
9029
9258
|
}
|
|
9030
|
-
|
|
9259
|
+
const localPrAttentionReconcile = reconcileLocalOnlyMergedPrAttention();
|
|
9260
|
+
return { workers: outcomes, finalizedRuns: finalizeStaleRuns(), metadataReconcile, localPrAttentionReconcile };
|
|
9031
9261
|
}
|
|
9032
9262
|
function reconcileRunsCli() {
|
|
9033
9263
|
const result = reconcileStaleWorkers();
|
|
@@ -9038,6 +9268,10 @@ function reconcileRunsCli() {
|
|
|
9038
9268
|
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
9039
9269
|
return acc;
|
|
9040
9270
|
}, {});
|
|
9271
|
+
const localPrAttentionTotals = result.localPrAttentionReconcile.workers.reduce((acc, row) => {
|
|
9272
|
+
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
9273
|
+
return acc;
|
|
9274
|
+
}, {});
|
|
9041
9275
|
const runRetentionTotals = result.metadataReconcile.runMetadataRetention.runs.reduce((acc, row) => {
|
|
9042
9276
|
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
9043
9277
|
return acc;
|
|
@@ -9055,10 +9289,15 @@ function reconcileRunsCli() {
|
|
|
9055
9289
|
total: result.metadataReconcile.runMetadataRetention.runs.length
|
|
9056
9290
|
}
|
|
9057
9291
|
},
|
|
9292
|
+
localPrAttentionReconcile: {
|
|
9293
|
+
totals: localPrAttentionTotals,
|
|
9294
|
+
total: result.localPrAttentionReconcile.workers.length
|
|
9295
|
+
},
|
|
9058
9296
|
finalizedRuns: result.finalizedRuns.length,
|
|
9059
9297
|
details: {
|
|
9060
9298
|
workers: result.workers,
|
|
9061
9299
|
metadataReconcile: result.metadataReconcile.workers,
|
|
9300
|
+
localPrAttentionReconcile: result.localPrAttentionReconcile.workers,
|
|
9062
9301
|
runMetadataRetention: result.metadataReconcile.runMetadataRetention.runs,
|
|
9063
9302
|
finalizedRuns: result.finalizedRuns
|
|
9064
9303
|
}
|
|
@@ -9079,7 +9318,7 @@ function heartbeatByteLength(heartbeatPath) {
|
|
|
9079
9318
|
}
|
|
9080
9319
|
}
|
|
9081
9320
|
function workerEvidence(run, workerName) {
|
|
9082
|
-
const workerPath =
|
|
9321
|
+
const workerPath = path37.join(runDirectory(run.id), "workers", safeSlug(workerName), "worker.json");
|
|
9083
9322
|
const worker = readJson(workerPath, void 0);
|
|
9084
9323
|
if (!worker) {
|
|
9085
9324
|
return {
|
|
@@ -9136,7 +9375,7 @@ function aggregateRunAttention(workers) {
|
|
|
9136
9375
|
function countOpenWorkers(run) {
|
|
9137
9376
|
let open = 0;
|
|
9138
9377
|
for (const name of listRunWorkerNames(run)) {
|
|
9139
|
-
const workerPath =
|
|
9378
|
+
const workerPath = path37.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
9140
9379
|
const worker = readJson(workerPath, void 0);
|
|
9141
9380
|
if (!worker) continue;
|
|
9142
9381
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
@@ -9212,7 +9451,7 @@ function createRun(args) {
|
|
|
9212
9451
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9213
9452
|
workers: {}
|
|
9214
9453
|
};
|
|
9215
|
-
writeJson(
|
|
9454
|
+
writeJson(path38.join(dir, "run.json"), run);
|
|
9216
9455
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
9217
9456
|
}
|
|
9218
9457
|
function listRuns() {
|
|
@@ -9223,8 +9462,32 @@ function failExists(message) {
|
|
|
9223
9462
|
process.exit(1);
|
|
9224
9463
|
}
|
|
9225
9464
|
|
|
9465
|
+
// src/run-resolve.ts
|
|
9466
|
+
function resolveHarnessRunByName(runName) {
|
|
9467
|
+
const name = runName.trim();
|
|
9468
|
+
if (!name) return null;
|
|
9469
|
+
const rows = buildRunListRows();
|
|
9470
|
+
return rows.find((row) => row.name === name && row.status !== "completed") ?? null;
|
|
9471
|
+
}
|
|
9472
|
+
function resolveHarnessRunCli(args) {
|
|
9473
|
+
const name = String(required(String(args.name || ""), "--name"));
|
|
9474
|
+
const hit = resolveHarnessRunByName(name);
|
|
9475
|
+
console.log(
|
|
9476
|
+
JSON.stringify(
|
|
9477
|
+
{
|
|
9478
|
+
runId: hit?.id ?? null,
|
|
9479
|
+
name,
|
|
9480
|
+
status: hit?.status ?? null,
|
|
9481
|
+
effectiveStatus: hit?.effectiveStatus ?? null
|
|
9482
|
+
},
|
|
9483
|
+
null,
|
|
9484
|
+
2
|
|
9485
|
+
)
|
|
9486
|
+
);
|
|
9487
|
+
}
|
|
9488
|
+
|
|
9226
9489
|
// src/sweep.ts
|
|
9227
|
-
import
|
|
9490
|
+
import path39 from "node:path";
|
|
9228
9491
|
async function sweepRun(args) {
|
|
9229
9492
|
const pipeline = args.pipeline === true || args.pipeline === "true";
|
|
9230
9493
|
try {
|
|
@@ -9237,7 +9500,7 @@ async function sweepRun(args) {
|
|
|
9237
9500
|
const releasedLocalOrphans = [];
|
|
9238
9501
|
for (const name of Object.keys(run.workers || {})) {
|
|
9239
9502
|
const worker = readJson(
|
|
9240
|
-
|
|
9503
|
+
path39.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
9241
9504
|
void 0
|
|
9242
9505
|
);
|
|
9243
9506
|
if (!worker || !worker.dispatched || !worker.taskId) continue;
|
|
@@ -9286,17 +9549,17 @@ async function sweepRun(args) {
|
|
|
9286
9549
|
|
|
9287
9550
|
// src/harness-storage-snapshot.ts
|
|
9288
9551
|
import { existsSync as existsSync31, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
|
|
9289
|
-
import
|
|
9552
|
+
import path41 from "node:path";
|
|
9290
9553
|
|
|
9291
9554
|
// src/cleanup-dir-size.ts
|
|
9292
|
-
import { execFileSync as
|
|
9555
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
9293
9556
|
import { existsSync as existsSync30, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
|
|
9294
|
-
import
|
|
9557
|
+
import path40 from "node:path";
|
|
9295
9558
|
var DEFAULT_DU_TIMEOUT_MS = 2500;
|
|
9296
9559
|
function directorySizeBytesDu(root, timeoutMs = DEFAULT_DU_TIMEOUT_MS) {
|
|
9297
9560
|
if (!existsSync30(root)) return 0;
|
|
9298
9561
|
try {
|
|
9299
|
-
const out =
|
|
9562
|
+
const out = execFileSync3("du", ["-sb", root], {
|
|
9300
9563
|
encoding: "utf8",
|
|
9301
9564
|
timeout: timeoutMs,
|
|
9302
9565
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -9325,7 +9588,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
9325
9588
|
}
|
|
9326
9589
|
for (const name of entries) {
|
|
9327
9590
|
if (seen++ > maxEntries) return null;
|
|
9328
|
-
const full =
|
|
9591
|
+
const full = path40.join(current, name);
|
|
9329
9592
|
let st;
|
|
9330
9593
|
try {
|
|
9331
9594
|
st = statSync6(full);
|
|
@@ -9377,7 +9640,7 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
9377
9640
|
for (const runEntry of entries) {
|
|
9378
9641
|
if (!runEntry.isDirectory()) continue;
|
|
9379
9642
|
runCount += 1;
|
|
9380
|
-
const runPath =
|
|
9643
|
+
const runPath = path41.join(worktreesDir, runEntry.name);
|
|
9381
9644
|
try {
|
|
9382
9645
|
const st = statSync7(runPath);
|
|
9383
9646
|
oldestMs = oldestMs === null ? st.mtimeMs : Math.min(oldestMs, st.mtimeMs);
|
|
@@ -9412,10 +9675,10 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
9412
9675
|
}
|
|
9413
9676
|
|
|
9414
9677
|
// src/cleanup.ts
|
|
9415
|
-
import
|
|
9678
|
+
import path53 from "node:path";
|
|
9416
9679
|
|
|
9417
9680
|
// src/cleanup-guards.ts
|
|
9418
|
-
import
|
|
9681
|
+
import path42 from "node:path";
|
|
9419
9682
|
|
|
9420
9683
|
// src/cleanup-build-cache-paths.ts
|
|
9421
9684
|
var HARNESS_BUILD_CACHE_RELATIVE_PATHS = [
|
|
@@ -9556,7 +9819,7 @@ function skipWorktreeRemoval(input) {
|
|
|
9556
9819
|
function skipDependencyCacheRemoval(input) {
|
|
9557
9820
|
const { indexed, nodeModulesAgeMs, ageMs, worktreePath, activeWorktreePaths, diskPressure } = input;
|
|
9558
9821
|
if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
|
|
9559
|
-
if (activeWorktreePaths.has(
|
|
9822
|
+
if (activeWorktreePaths.has(path42.resolve(worktreePath))) return "active_worker";
|
|
9560
9823
|
if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
|
|
9561
9824
|
if (indexed && indexedWorktreeHasMaterialChanges(indexed)) return "dirty_worktree";
|
|
9562
9825
|
return null;
|
|
@@ -9583,11 +9846,11 @@ var LIVE_SKIP_REASONS = /* @__PURE__ */ new Set([
|
|
|
9583
9846
|
function collectPreservedLivePaths(actions, skips) {
|
|
9584
9847
|
const out = [];
|
|
9585
9848
|
const seen = /* @__PURE__ */ new Set();
|
|
9586
|
-
const push = (
|
|
9587
|
-
const key = `${
|
|
9849
|
+
const push = (path73, reason, detail) => {
|
|
9850
|
+
const key = `${path73}\0${reason}`;
|
|
9588
9851
|
if (seen.has(key) || out.length >= MAX_PRESERVED_LIVE_PATH_SAMPLES) return;
|
|
9589
9852
|
seen.add(key);
|
|
9590
|
-
out.push({ path:
|
|
9853
|
+
out.push({ path: path73, reason, ...detail ? { detail } : {} });
|
|
9591
9854
|
};
|
|
9592
9855
|
for (const skip2 of skips) {
|
|
9593
9856
|
if (!LIVE_SKIP_REASONS.has(skip2.reason)) continue;
|
|
@@ -9603,11 +9866,11 @@ function collectPreservedLivePaths(actions, skips) {
|
|
|
9603
9866
|
|
|
9604
9867
|
// src/cleanup-run-directory.ts
|
|
9605
9868
|
import { existsSync as existsSync33, readdirSync as readdirSync11, statSync as statSync9 } from "node:fs";
|
|
9606
|
-
import
|
|
9869
|
+
import path44 from "node:path";
|
|
9607
9870
|
|
|
9608
9871
|
// src/cleanup-active-worktrees.ts
|
|
9609
9872
|
import { existsSync as existsSync32, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
|
|
9610
|
-
import
|
|
9873
|
+
import path43 from "node:path";
|
|
9611
9874
|
function workerHasRecentHarnessActivity(worker, now) {
|
|
9612
9875
|
const paths = [worker.heartbeatPath, worker.stdoutPath, worker.stderrPath];
|
|
9613
9876
|
for (const target of paths) {
|
|
@@ -9633,11 +9896,11 @@ function collectActiveWorktreeGuards(harnessRoots, now = Date.now()) {
|
|
|
9633
9896
|
let runHasLive = false;
|
|
9634
9897
|
for (const name of Object.keys(run.workers || {})) {
|
|
9635
9898
|
const worker = readJson(
|
|
9636
|
-
|
|
9899
|
+
path43.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
|
|
9637
9900
|
void 0
|
|
9638
9901
|
);
|
|
9639
9902
|
if (!worker?.worktreePath) continue;
|
|
9640
|
-
const worktreePath =
|
|
9903
|
+
const worktreePath = path43.resolve(worker.worktreePath);
|
|
9641
9904
|
if (!isActiveHarnessWorker2(worker, now)) continue;
|
|
9642
9905
|
runHasLive = true;
|
|
9643
9906
|
activeWorktreePaths.add(worktreePath);
|
|
@@ -9665,7 +9928,7 @@ function pathAgeMs(target, now) {
|
|
|
9665
9928
|
}
|
|
9666
9929
|
}
|
|
9667
9930
|
function loadRunStatus(harnessRoot, runId) {
|
|
9668
|
-
const runPath =
|
|
9931
|
+
const runPath = path44.join(harnessRoot, "runs", runId, "run.json");
|
|
9669
9932
|
if (!existsSync33(runPath)) return null;
|
|
9670
9933
|
return readJson(runPath, null);
|
|
9671
9934
|
}
|
|
@@ -9701,7 +9964,7 @@ function scanStaleRunDirectoryCandidates(opts) {
|
|
|
9701
9964
|
if (!runEntry.isDirectory()) continue;
|
|
9702
9965
|
const runId = runEntry.name;
|
|
9703
9966
|
if (opts.runIdFilter && runId !== opts.runIdFilter) continue;
|
|
9704
|
-
const runPath =
|
|
9967
|
+
const runPath = path44.join(opts.worktreesDir, runId);
|
|
9705
9968
|
if (!runDirectoryIsEmpty(runPath)) continue;
|
|
9706
9969
|
candidates.push({
|
|
9707
9970
|
kind: "remove_run_directory",
|
|
@@ -9755,20 +10018,20 @@ function pathHasForeignOwnedEntry(targetPath, maxEntries = 32) {
|
|
|
9755
10018
|
|
|
9756
10019
|
// src/cleanup-privileged-remove.ts
|
|
9757
10020
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
9758
|
-
import
|
|
10021
|
+
import path46 from "node:path";
|
|
9759
10022
|
|
|
9760
10023
|
// src/cleanup-harness-path-validate.ts
|
|
9761
|
-
import
|
|
10024
|
+
import path45 from "node:path";
|
|
9762
10025
|
function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
|
|
9763
|
-
const resolved =
|
|
9764
|
-
const suffix = `${
|
|
10026
|
+
const resolved = path45.resolve(targetPath);
|
|
10027
|
+
const suffix = `${path45.sep}${cacheDirName}`;
|
|
9765
10028
|
const cachePath = resolved.endsWith(suffix) ? resolved : null;
|
|
9766
10029
|
if (!cachePath) return "path_outside_harness";
|
|
9767
|
-
const rel =
|
|
9768
|
-
if (rel.startsWith("..") ||
|
|
9769
|
-
const parts = rel.split(
|
|
10030
|
+
const rel = path45.relative(worktreesDir, cachePath);
|
|
10031
|
+
if (rel.startsWith("..") || path45.isAbsolute(rel)) return "path_outside_harness";
|
|
10032
|
+
const parts = rel.split(path45.sep);
|
|
9770
10033
|
if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
|
|
9771
|
-
if (!resolved.startsWith(
|
|
10034
|
+
if (!resolved.startsWith(path45.resolve(harnessRoot))) return "path_outside_harness";
|
|
9772
10035
|
return null;
|
|
9773
10036
|
}
|
|
9774
10037
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
@@ -9778,16 +10041,16 @@ function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
|
9778
10041
|
return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
|
|
9779
10042
|
}
|
|
9780
10043
|
function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
9781
|
-
const resolved =
|
|
9782
|
-
const relToWt =
|
|
9783
|
-
if (relToWt.startsWith("..") ||
|
|
9784
|
-
const parts = relToWt.split(
|
|
10044
|
+
const resolved = path45.resolve(targetPath);
|
|
10045
|
+
const relToWt = path45.relative(worktreesDir, resolved);
|
|
10046
|
+
if (relToWt.startsWith("..") || path45.isAbsolute(relToWt)) return "path_outside_harness";
|
|
10047
|
+
const parts = relToWt.split(path45.sep);
|
|
9785
10048
|
if (parts.length < 3) return "path_outside_harness";
|
|
9786
|
-
if (!resolved.startsWith(
|
|
10049
|
+
if (!resolved.startsWith(path45.resolve(harnessRoot))) return "path_outside_harness";
|
|
9787
10050
|
return null;
|
|
9788
10051
|
}
|
|
9789
10052
|
function isHarnessGeneratedCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
9790
|
-
const resolved =
|
|
10053
|
+
const resolved = path45.resolve(targetPath);
|
|
9791
10054
|
return isHarnessNodeModulesPath(resolved, harnessRoot, worktreesDir) === null || isHarnessNextCachePath(resolved, harnessRoot, worktreesDir) === null || isHarnessBuildCachePath(resolved, harnessRoot, worktreesDir) === null;
|
|
9792
10055
|
}
|
|
9793
10056
|
|
|
@@ -9821,12 +10084,12 @@ function tryPrivilegedReclaimHarnessCache(targetPath, harnessRoot, worktreesDir)
|
|
|
9821
10084
|
"chown",
|
|
9822
10085
|
"-R",
|
|
9823
10086
|
`${effectiveUid}:${effectiveGid}`,
|
|
9824
|
-
|
|
10087
|
+
path46.resolve(targetPath)
|
|
9825
10088
|
]);
|
|
9826
10089
|
if (chown.ok) {
|
|
9827
10090
|
return { ok: true, method: "chown_then_rm" };
|
|
9828
10091
|
}
|
|
9829
|
-
const rm = runSudoNonInteractive(["rm", "-rf",
|
|
10092
|
+
const rm = runSudoNonInteractive(["rm", "-rf", path46.resolve(targetPath)]);
|
|
9830
10093
|
if (rm.ok) {
|
|
9831
10094
|
return { ok: true, method: "sudo_rm" };
|
|
9832
10095
|
}
|
|
@@ -10028,7 +10291,7 @@ function removeWorktree(candidate, execute) {
|
|
|
10028
10291
|
|
|
10029
10292
|
// src/cleanup-scan.ts
|
|
10030
10293
|
import { existsSync as existsSync36, readdirSync as readdirSync13, statSync as statSync10 } from "node:fs";
|
|
10031
|
-
import
|
|
10294
|
+
import path47 from "node:path";
|
|
10032
10295
|
function pathAgeMs2(target, now) {
|
|
10033
10296
|
try {
|
|
10034
10297
|
const mtime = statSync10(target).mtimeMs;
|
|
@@ -10038,16 +10301,16 @@ function pathAgeMs2(target, now) {
|
|
|
10038
10301
|
}
|
|
10039
10302
|
}
|
|
10040
10303
|
function isPathInside(child, parent) {
|
|
10041
|
-
const rel =
|
|
10042
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
10304
|
+
const rel = path47.relative(parent, child);
|
|
10305
|
+
return rel === "" || !rel.startsWith("..") && !path47.isAbsolute(rel);
|
|
10043
10306
|
}
|
|
10044
10307
|
function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
|
|
10045
10308
|
const out = [];
|
|
10046
10309
|
for (const rel of HARNESS_BUILD_CACHE_RELATIVE_PATHS) {
|
|
10047
10310
|
if (rel === ".next") continue;
|
|
10048
|
-
const target =
|
|
10311
|
+
const target = path47.join(worktreePath, rel);
|
|
10049
10312
|
if (!existsSync36(target)) continue;
|
|
10050
|
-
const resolved =
|
|
10313
|
+
const resolved = path47.resolve(target);
|
|
10051
10314
|
if (seen.has(resolved)) continue;
|
|
10052
10315
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
10053
10316
|
seen.add(resolved);
|
|
@@ -10079,10 +10342,10 @@ function scanBuildCacheCandidates(opts) {
|
|
|
10079
10342
|
if (!opts.includeOrphans || !existsSync36(opts.worktreesDir)) return candidates;
|
|
10080
10343
|
for (const runEntry of readdirSync13(opts.worktreesDir, { withFileTypes: true })) {
|
|
10081
10344
|
if (!runEntry.isDirectory()) continue;
|
|
10082
|
-
const runPath =
|
|
10345
|
+
const runPath = path47.join(opts.worktreesDir, runEntry.name);
|
|
10083
10346
|
for (const workerEntry of readdirSync13(runPath, { withFileTypes: true })) {
|
|
10084
10347
|
if (!workerEntry.isDirectory()) continue;
|
|
10085
|
-
const worktreePath =
|
|
10348
|
+
const worktreePath = path47.join(runPath, workerEntry.name);
|
|
10086
10349
|
candidates.push(
|
|
10087
10350
|
...collectBuildCacheForWorktree(worktreePath, opts, seen, {
|
|
10088
10351
|
runId: runEntry.name,
|
|
@@ -10120,12 +10383,12 @@ function scanWorktreeCandidates(opts) {
|
|
|
10120
10383
|
if (!orphanEnabled || !existsSync36(opts.worktreesDir)) return candidates;
|
|
10121
10384
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
10122
10385
|
for (const entry of opts.index.values()) {
|
|
10123
|
-
indexedPaths.add(
|
|
10386
|
+
indexedPaths.add(path47.resolve(entry.worktreePath));
|
|
10124
10387
|
}
|
|
10125
10388
|
for (const runEntry of readdirSync13(opts.worktreesDir, { withFileTypes: true })) {
|
|
10126
10389
|
if (!runEntry.isDirectory()) continue;
|
|
10127
10390
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
10128
|
-
const runPath =
|
|
10391
|
+
const runPath = path47.join(opts.worktreesDir, runEntry.name);
|
|
10129
10392
|
let workerEntries;
|
|
10130
10393
|
try {
|
|
10131
10394
|
workerEntries = readdirSync13(runPath, { withFileTypes: true });
|
|
@@ -10134,7 +10397,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
10134
10397
|
}
|
|
10135
10398
|
for (const workerEntry of workerEntries) {
|
|
10136
10399
|
if (!workerEntry.isDirectory()) continue;
|
|
10137
|
-
const worktreePath =
|
|
10400
|
+
const worktreePath = path47.resolve(path47.join(runPath, workerEntry.name));
|
|
10138
10401
|
if (seen.has(worktreePath)) continue;
|
|
10139
10402
|
if (indexedPaths.has(worktreePath)) continue;
|
|
10140
10403
|
if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
|
|
@@ -10154,7 +10417,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
10154
10417
|
|
|
10155
10418
|
// src/cleanup-dependency-scan.ts
|
|
10156
10419
|
import { existsSync as existsSync37, readdirSync as readdirSync14, statSync as statSync11 } from "node:fs";
|
|
10157
|
-
import
|
|
10420
|
+
import path48 from "node:path";
|
|
10158
10421
|
var DEPENDENCY_CACHE_DIRS = [
|
|
10159
10422
|
{ dirName: "node_modules", kind: "remove_node_modules" },
|
|
10160
10423
|
{ dirName: ".next", kind: "remove_next_cache" }
|
|
@@ -10168,12 +10431,12 @@ function pathAgeMs3(target, now) {
|
|
|
10168
10431
|
}
|
|
10169
10432
|
}
|
|
10170
10433
|
function isPathInside2(child, parent) {
|
|
10171
|
-
const rel =
|
|
10172
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
10434
|
+
const rel = path48.relative(parent, child);
|
|
10435
|
+
return rel === "" || !rel.startsWith("..") && !path48.isAbsolute(rel);
|
|
10173
10436
|
}
|
|
10174
10437
|
function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
10175
10438
|
if (!existsSync37(targetPath)) return;
|
|
10176
|
-
const resolved =
|
|
10439
|
+
const resolved = path48.resolve(targetPath);
|
|
10177
10440
|
if (seen.has(resolved)) return;
|
|
10178
10441
|
if (!isPathInside2(resolved, opts.harnessRoot)) return;
|
|
10179
10442
|
seen.add(resolved);
|
|
@@ -10190,7 +10453,7 @@ function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
|
10190
10453
|
}
|
|
10191
10454
|
function scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, meta) {
|
|
10192
10455
|
for (const entry of DEPENDENCY_CACHE_DIRS) {
|
|
10193
|
-
pushCandidate2(candidates, seen, opts,
|
|
10456
|
+
pushCandidate2(candidates, seen, opts, path48.join(worktreePath, entry.dirName), entry.kind, meta);
|
|
10194
10457
|
}
|
|
10195
10458
|
}
|
|
10196
10459
|
function scanDependencyCacheCandidates(opts) {
|
|
@@ -10208,7 +10471,7 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
10208
10471
|
for (const runEntry of readdirSync14(opts.worktreesDir, { withFileTypes: true })) {
|
|
10209
10472
|
if (!runEntry.isDirectory()) continue;
|
|
10210
10473
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
10211
|
-
const runPath =
|
|
10474
|
+
const runPath = path48.join(opts.worktreesDir, runEntry.name);
|
|
10212
10475
|
let workerEntries;
|
|
10213
10476
|
try {
|
|
10214
10477
|
workerEntries = readdirSync14(runPath, { withFileTypes: true });
|
|
@@ -10217,7 +10480,7 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
10217
10480
|
}
|
|
10218
10481
|
for (const workerEntry of workerEntries) {
|
|
10219
10482
|
if (!workerEntry.isDirectory()) continue;
|
|
10220
|
-
const worktreePath =
|
|
10483
|
+
const worktreePath = path48.join(runPath, workerEntry.name);
|
|
10221
10484
|
scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, {
|
|
10222
10485
|
runId: runEntry.name,
|
|
10223
10486
|
worker: workerEntry.name
|
|
@@ -10229,7 +10492,7 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
10229
10492
|
|
|
10230
10493
|
// src/cleanup-duplicate-worktrees.ts
|
|
10231
10494
|
import { existsSync as existsSync38, statSync as statSync12 } from "node:fs";
|
|
10232
|
-
import
|
|
10495
|
+
import path49 from "node:path";
|
|
10233
10496
|
function pathAgeMs4(target, now) {
|
|
10234
10497
|
try {
|
|
10235
10498
|
const mtime = statSync12(target).mtimeMs;
|
|
@@ -10259,8 +10522,8 @@ function parseWorktreePorcelain(output) {
|
|
|
10259
10522
|
return records;
|
|
10260
10523
|
}
|
|
10261
10524
|
function isUnderWorktreesDir(worktreePath, worktreesDir) {
|
|
10262
|
-
const rel =
|
|
10263
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
10525
|
+
const rel = path49.relative(path49.resolve(worktreesDir), path49.resolve(worktreePath));
|
|
10526
|
+
return rel !== "" && !rel.startsWith("..") && !path49.isAbsolute(rel);
|
|
10264
10527
|
}
|
|
10265
10528
|
function isCleanWorktree(worktreePath, repoRoot) {
|
|
10266
10529
|
try {
|
|
@@ -10276,11 +10539,11 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
10276
10539
|
if (!opts.includeOrphans || !existsSync38(opts.worktreesDir)) return [];
|
|
10277
10540
|
const repos = /* @__PURE__ */ new Set();
|
|
10278
10541
|
for (const entry of opts.index.values()) {
|
|
10279
|
-
if (entry.run.repo) repos.add(
|
|
10542
|
+
if (entry.run.repo) repos.add(path49.resolve(entry.run.repo));
|
|
10280
10543
|
}
|
|
10281
10544
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
10282
10545
|
for (const entry of opts.index.values()) {
|
|
10283
|
-
indexedPaths.add(
|
|
10546
|
+
indexedPaths.add(path49.resolve(entry.worktreePath));
|
|
10284
10547
|
}
|
|
10285
10548
|
const candidates = [];
|
|
10286
10549
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -10293,15 +10556,15 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
10293
10556
|
}
|
|
10294
10557
|
const worktrees = parseWorktreePorcelain(porcelain);
|
|
10295
10558
|
for (const wt of worktrees) {
|
|
10296
|
-
const resolved =
|
|
10297
|
-
if (resolved ===
|
|
10559
|
+
const resolved = path49.resolve(wt.path);
|
|
10560
|
+
if (resolved === path49.resolve(repoRoot)) continue;
|
|
10298
10561
|
if (!isUnderWorktreesDir(resolved, opts.worktreesDir)) continue;
|
|
10299
10562
|
if (indexedPaths.has(resolved)) continue;
|
|
10300
10563
|
if (seen.has(resolved)) continue;
|
|
10301
10564
|
if (!existsSync38(resolved)) continue;
|
|
10302
10565
|
if (!isCleanWorktree(resolved, repoRoot)) continue;
|
|
10303
|
-
const rel =
|
|
10304
|
-
const parts = rel.split(
|
|
10566
|
+
const rel = path49.relative(opts.worktreesDir, resolved);
|
|
10567
|
+
const parts = rel.split(path49.sep);
|
|
10305
10568
|
const runId = parts[0];
|
|
10306
10569
|
const worker = parts[1] ?? "unknown";
|
|
10307
10570
|
seen.add(resolved);
|
|
@@ -10320,12 +10583,12 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
10320
10583
|
}
|
|
10321
10584
|
|
|
10322
10585
|
// src/cleanup-worktree-index.ts
|
|
10323
|
-
import
|
|
10586
|
+
import path50 from "node:path";
|
|
10324
10587
|
function buildWorktreeIndexAt(harnessRoot) {
|
|
10325
10588
|
const index = /* @__PURE__ */ new Map();
|
|
10326
10589
|
for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
|
|
10327
10590
|
for (const name of Object.keys(run.workers || {})) {
|
|
10328
|
-
const workerPath =
|
|
10591
|
+
const workerPath = path50.join(
|
|
10329
10592
|
runDirectoryAt(harnessRoot, run.id),
|
|
10330
10593
|
"workers",
|
|
10331
10594
|
safeSlug(name),
|
|
@@ -10333,9 +10596,9 @@ function buildWorktreeIndexAt(harnessRoot) {
|
|
|
10333
10596
|
);
|
|
10334
10597
|
const worker = readJson(workerPath, void 0);
|
|
10335
10598
|
if (!worker?.worktreePath) continue;
|
|
10336
|
-
index.set(
|
|
10599
|
+
index.set(path50.resolve(worker.worktreePath), {
|
|
10337
10600
|
harnessRoot,
|
|
10338
|
-
worktreePath:
|
|
10601
|
+
worktreePath: path50.resolve(worker.worktreePath),
|
|
10339
10602
|
runId: run.id,
|
|
10340
10603
|
workerName: name,
|
|
10341
10604
|
run,
|
|
@@ -10405,14 +10668,14 @@ function resolvePipelineHarnessRetention(runId) {
|
|
|
10405
10668
|
|
|
10406
10669
|
// src/cleanup-orphan-safety.ts
|
|
10407
10670
|
import { existsSync as existsSync39, statSync as statSync13 } from "node:fs";
|
|
10408
|
-
import
|
|
10671
|
+
import path51 from "node:path";
|
|
10409
10672
|
var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
|
|
10410
10673
|
function assessOrphanWorktreeSafety(input) {
|
|
10411
10674
|
const now = input.now ?? Date.now();
|
|
10412
10675
|
const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
|
|
10413
10676
|
if (!existsSync39(input.worktreePath)) return null;
|
|
10414
10677
|
if (input.runId && input.workerName) {
|
|
10415
|
-
const heartbeatPath =
|
|
10678
|
+
const heartbeatPath = path51.join(
|
|
10416
10679
|
input.harnessRoot,
|
|
10417
10680
|
"runs",
|
|
10418
10681
|
input.runId,
|
|
@@ -10426,7 +10689,7 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
10426
10689
|
} catch {
|
|
10427
10690
|
}
|
|
10428
10691
|
}
|
|
10429
|
-
const gitDir =
|
|
10692
|
+
const gitDir = path51.join(input.worktreePath, ".git");
|
|
10430
10693
|
if (!existsSync39(gitDir)) return null;
|
|
10431
10694
|
const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
|
|
10432
10695
|
if (porcelain.status !== 0) return "pr_or_unmerged_commits";
|
|
@@ -10458,10 +10721,10 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
10458
10721
|
// src/cleanup-harness-roots.ts
|
|
10459
10722
|
import { existsSync as existsSync40 } from "node:fs";
|
|
10460
10723
|
import { homedir as homedir13 } from "node:os";
|
|
10461
|
-
import
|
|
10724
|
+
import path52 from "node:path";
|
|
10462
10725
|
var WELL_KNOWN_HARNESS_SCAN_ROOTS = [
|
|
10463
10726
|
"/var/tmp/kynver-harness",
|
|
10464
|
-
|
|
10727
|
+
path52.join(homedir13(), ".openclaw", "harness")
|
|
10465
10728
|
];
|
|
10466
10729
|
function addRoot(seen, roots, candidate) {
|
|
10467
10730
|
if (!candidate?.trim()) return;
|
|
@@ -10483,7 +10746,7 @@ function resolveHarnessScanRoots(options = {}) {
|
|
|
10483
10746
|
for (const candidate of extra ?? []) addRoot(seen, roots, candidate);
|
|
10484
10747
|
if (shouldScanWellKnownRoots(options)) {
|
|
10485
10748
|
for (const candidate of WELL_KNOWN_HARNESS_SCAN_ROOTS) {
|
|
10486
|
-
const resolved =
|
|
10749
|
+
const resolved = path52.resolve(candidate);
|
|
10487
10750
|
if (!seen.has(resolved) && existsSync40(resolved)) addRoot(seen, roots, resolved);
|
|
10488
10751
|
}
|
|
10489
10752
|
}
|
|
@@ -10638,9 +10901,9 @@ function mergeWorktreeIndexes(scanRoots) {
|
|
|
10638
10901
|
}
|
|
10639
10902
|
function worktreePathForCandidate(candidate, worktreesDir) {
|
|
10640
10903
|
if (candidate.runId && candidate.worker) {
|
|
10641
|
-
return
|
|
10904
|
+
return path53.join(worktreesDir, candidate.runId, candidate.worker);
|
|
10642
10905
|
}
|
|
10643
|
-
return
|
|
10906
|
+
return path53.resolve(candidate.path, "..");
|
|
10644
10907
|
}
|
|
10645
10908
|
function runHarnessCleanup(options = {}) {
|
|
10646
10909
|
let retention = resolveHarnessRetention(options);
|
|
@@ -10665,7 +10928,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10665
10928
|
for (const harnessRoot of paths.scanRoots) {
|
|
10666
10929
|
if (atSweepCap()) break;
|
|
10667
10930
|
emitCleanupProgress("root", harnessRoot);
|
|
10668
|
-
const worktreesDir =
|
|
10931
|
+
const worktreesDir = path53.join(harnessRoot, "worktrees");
|
|
10669
10932
|
const scanOpts = {
|
|
10670
10933
|
harnessRoot,
|
|
10671
10934
|
worktreesDir,
|
|
@@ -10678,7 +10941,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10678
10941
|
};
|
|
10679
10942
|
for (const raw of scanDependencyCacheCandidates(scanOpts)) {
|
|
10680
10943
|
if (atSweepCap()) break;
|
|
10681
|
-
const resolved =
|
|
10944
|
+
const resolved = path53.resolve(raw.path);
|
|
10682
10945
|
if (processedPaths.has(resolved)) continue;
|
|
10683
10946
|
processedPaths.add(resolved);
|
|
10684
10947
|
const candidate = { ...raw, path: resolved };
|
|
@@ -10689,7 +10952,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10689
10952
|
continue;
|
|
10690
10953
|
}
|
|
10691
10954
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
10692
|
-
const indexed = index.get(
|
|
10955
|
+
const indexed = index.get(path53.resolve(worktreePath)) ?? null;
|
|
10693
10956
|
const guardReason = skipDependencyCacheRemoval({
|
|
10694
10957
|
indexed,
|
|
10695
10958
|
includeOrphans: true,
|
|
@@ -10713,7 +10976,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10713
10976
|
}
|
|
10714
10977
|
for (const raw of scanBuildCacheCandidates(scanOpts)) {
|
|
10715
10978
|
if (atSweepCap()) break;
|
|
10716
|
-
const resolved =
|
|
10979
|
+
const resolved = path53.resolve(raw.path);
|
|
10717
10980
|
if (processedPaths.has(resolved)) continue;
|
|
10718
10981
|
processedPaths.add(resolved);
|
|
10719
10982
|
const candidate = { ...raw, path: resolved };
|
|
@@ -10724,7 +10987,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10724
10987
|
continue;
|
|
10725
10988
|
}
|
|
10726
10989
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
10727
|
-
const indexed = index.get(
|
|
10990
|
+
const indexed = index.get(path53.resolve(worktreePath)) ?? null;
|
|
10728
10991
|
const guardReason = skipBuildCacheRemoval({
|
|
10729
10992
|
indexed,
|
|
10730
10993
|
includeOrphans: true,
|
|
@@ -10754,11 +11017,11 @@ function runHarnessCleanup(options = {}) {
|
|
|
10754
11017
|
const worktreeSeen = /* @__PURE__ */ new Set();
|
|
10755
11018
|
for (const raw of worktreeCandidates) {
|
|
10756
11019
|
if (atSweepCap()) break;
|
|
10757
|
-
const resolved =
|
|
11020
|
+
const resolved = path53.resolve(raw.path);
|
|
10758
11021
|
if (worktreeSeen.has(resolved)) continue;
|
|
10759
11022
|
worktreeSeen.add(resolved);
|
|
10760
11023
|
const candidate = { ...raw, path: resolved };
|
|
10761
|
-
const indexed = index.get(
|
|
11024
|
+
const indexed = index.get(path53.resolve(candidate.path)) ?? null;
|
|
10762
11025
|
const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
|
|
10763
11026
|
worktreePath: candidate.path,
|
|
10764
11027
|
harnessRoot,
|
|
@@ -10768,7 +11031,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10768
11031
|
});
|
|
10769
11032
|
const guardSkip = skipWorktreeRemoval({
|
|
10770
11033
|
indexed,
|
|
10771
|
-
worktreePath:
|
|
11034
|
+
worktreePath: path53.resolve(candidate.path),
|
|
10772
11035
|
includeOrphans: retention.includeOrphans,
|
|
10773
11036
|
worktreesAgeMs: retention.worktreesAgeMs,
|
|
10774
11037
|
terminalWorktreesAgeMs: retention.terminalWorktreesAgeMs,
|
|
@@ -10800,11 +11063,11 @@ function runHarnessCleanup(options = {}) {
|
|
|
10800
11063
|
now: paths.now
|
|
10801
11064
|
})) {
|
|
10802
11065
|
if (atSweepCap()) break;
|
|
10803
|
-
const resolved =
|
|
11066
|
+
const resolved = path53.resolve(raw.path);
|
|
10804
11067
|
if (processedPaths.has(resolved)) continue;
|
|
10805
11068
|
processedPaths.add(resolved);
|
|
10806
11069
|
const candidate = { ...raw, path: resolved };
|
|
10807
|
-
const runId = candidate.runId ??
|
|
11070
|
+
const runId = candidate.runId ?? path53.basename(resolved);
|
|
10808
11071
|
const dirSkip = skipRunDirectoryRemoval({
|
|
10809
11072
|
harnessRoot,
|
|
10810
11073
|
runId,
|
|
@@ -10943,7 +11206,7 @@ import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
|
10943
11206
|
|
|
10944
11207
|
// src/discard-disposable.ts
|
|
10945
11208
|
import { existsSync as existsSync41, rmSync as rmSync4 } from "node:fs";
|
|
10946
|
-
import
|
|
11209
|
+
import path54 from "node:path";
|
|
10947
11210
|
function normalizeRelativePath2(value) {
|
|
10948
11211
|
const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
10949
11212
|
if (!normalized || normalized.startsWith("/") || normalized.includes("..")) {
|
|
@@ -10965,12 +11228,12 @@ function discardDisposableArtifacts(args) {
|
|
|
10965
11228
|
if (paths.length === 0) {
|
|
10966
11229
|
return { ok: false, removed: [], reason: "requires at least one --path" };
|
|
10967
11230
|
}
|
|
10968
|
-
const worktreeRoot =
|
|
11231
|
+
const worktreeRoot = path54.resolve(worker.worktreePath);
|
|
10969
11232
|
const removed = [];
|
|
10970
11233
|
for (const raw of paths) {
|
|
10971
11234
|
const rel = normalizeRelativePath2(raw);
|
|
10972
|
-
const abs =
|
|
10973
|
-
if (!abs.startsWith(worktreeRoot +
|
|
11235
|
+
const abs = path54.resolve(worktreeRoot, rel);
|
|
11236
|
+
if (!abs.startsWith(worktreeRoot + path54.sep) && abs !== worktreeRoot) {
|
|
10974
11237
|
return { ok: false, removed, reason: `path escapes worktree: ${raw}` };
|
|
10975
11238
|
}
|
|
10976
11239
|
if (!existsSync41(abs)) {
|
|
@@ -11037,7 +11300,7 @@ function validateDaemonInstallIdentity(config = loadUserConfig(), env = process.
|
|
|
11037
11300
|
// src/cron/cron-env.ts
|
|
11038
11301
|
import { existsSync as existsSync42 } from "node:fs";
|
|
11039
11302
|
import { homedir as homedir14 } from "node:os";
|
|
11040
|
-
import
|
|
11303
|
+
import path55 from "node:path";
|
|
11041
11304
|
function envFlag3(name, defaultValue) {
|
|
11042
11305
|
const raw = process.env[name]?.trim().toLowerCase();
|
|
11043
11306
|
if (!raw) return defaultValue;
|
|
@@ -11053,7 +11316,7 @@ function envInt(name, fallback, min = 1) {
|
|
|
11053
11316
|
function defaultKynverCronStorePath() {
|
|
11054
11317
|
const explicit = process.env.KYNVER_CRON_STORE_PATH?.trim() || process.env.OPENCLAW_CRON_STORE_PATH?.trim();
|
|
11055
11318
|
if (explicit) return explicit;
|
|
11056
|
-
return
|
|
11319
|
+
return path55.join(homedir14(), ".kynver", "agent-os-cron.json");
|
|
11057
11320
|
}
|
|
11058
11321
|
function defaultKynverCronStatePath(storePath = defaultKynverCronStorePath()) {
|
|
11059
11322
|
const explicit = process.env.KYNVER_CRON_TICK_STATE_PATH?.trim();
|
|
@@ -11295,7 +11558,7 @@ async function loadCronJobs(storePath = defaultKynverCronStorePath()) {
|
|
|
11295
11558
|
// src/cron/cron-tick-state.ts
|
|
11296
11559
|
import { randomBytes } from "node:crypto";
|
|
11297
11560
|
import { promises as fs4 } from "node:fs";
|
|
11298
|
-
import
|
|
11561
|
+
import path56 from "node:path";
|
|
11299
11562
|
var EMPTY = { version: 1, jobs: {} };
|
|
11300
11563
|
async function readFileIfExists2(filePath) {
|
|
11301
11564
|
try {
|
|
@@ -11322,7 +11585,7 @@ async function loadCronTickState(statePath) {
|
|
|
11322
11585
|
return parseCronTickState(raw);
|
|
11323
11586
|
}
|
|
11324
11587
|
async function writeStateAtomic(statePath, state) {
|
|
11325
|
-
await fs4.mkdir(
|
|
11588
|
+
await fs4.mkdir(path56.dirname(statePath), { recursive: true });
|
|
11326
11589
|
const suffix = randomBytes(6).toString("hex");
|
|
11327
11590
|
const tmp = `${statePath}.tmp-${process.pid}-${Date.now()}-${suffix}`;
|
|
11328
11591
|
await fs4.writeFile(tmp, `${JSON.stringify(state, null, 2)}
|
|
@@ -11499,7 +11762,7 @@ async function runKynverCronTick(opts = {}) {
|
|
|
11499
11762
|
}
|
|
11500
11763
|
|
|
11501
11764
|
// src/pipeline-tick.ts
|
|
11502
|
-
import
|
|
11765
|
+
import path58 from "node:path";
|
|
11503
11766
|
|
|
11504
11767
|
// src/pipeline-dispatch.ts
|
|
11505
11768
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -11630,7 +11893,7 @@ function resolvePipelineMaxStarts(resourceGate, operatorTick) {
|
|
|
11630
11893
|
}
|
|
11631
11894
|
|
|
11632
11895
|
// src/plan-progress-daemon-sync.ts
|
|
11633
|
-
import
|
|
11896
|
+
import path57 from "node:path";
|
|
11634
11897
|
|
|
11635
11898
|
// src/plan-progress-sync.ts
|
|
11636
11899
|
async function syncPlanProgress(args) {
|
|
@@ -11654,7 +11917,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
11654
11917
|
const outcomes = [];
|
|
11655
11918
|
for (const name of Object.keys(run.workers || {})) {
|
|
11656
11919
|
const worker = readJson(
|
|
11657
|
-
|
|
11920
|
+
path57.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
11658
11921
|
void 0
|
|
11659
11922
|
);
|
|
11660
11923
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -11716,7 +11979,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
11716
11979
|
const outcomes = [];
|
|
11717
11980
|
for (const name of Object.keys(run.workers || {})) {
|
|
11718
11981
|
const worker = readJson(
|
|
11719
|
-
|
|
11982
|
+
path58.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
11720
11983
|
void 0
|
|
11721
11984
|
);
|
|
11722
11985
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -11863,6 +12126,15 @@ async function runPipelineTick(args) {
|
|
|
11863
12126
|
var DEFAULT_INTERVAL_MS = 6e4;
|
|
11864
12127
|
var IDLE_INTERVAL_MS = 5 * 6e4;
|
|
11865
12128
|
var MAX_IDLE_STREAK = 10;
|
|
12129
|
+
var SLEEP_POLL_MS = 250;
|
|
12130
|
+
async function awaitDaemonBackoff(ms, isStopping) {
|
|
12131
|
+
let remaining = ms;
|
|
12132
|
+
while (remaining > 0 && !isStopping()) {
|
|
12133
|
+
const step = Math.min(SLEEP_POLL_MS, remaining);
|
|
12134
|
+
await sleepMsAsync(step);
|
|
12135
|
+
remaining -= step;
|
|
12136
|
+
}
|
|
12137
|
+
}
|
|
11866
12138
|
async function runDaemon(args) {
|
|
11867
12139
|
const runId = String(required(String(args.run || ""), "--run"));
|
|
11868
12140
|
const agentOsId = String(required(String(args.agentOsId || loadUserConfig().agentOsId || ""), "--agent-os-id"));
|
|
@@ -11920,17 +12192,17 @@ async function runDaemon(args) {
|
|
|
11920
12192
|
idleStreak = 0;
|
|
11921
12193
|
}
|
|
11922
12194
|
const backoff = idleStreak >= MAX_IDLE_STREAK ? IDLE_INTERVAL_MS : intervalMs;
|
|
11923
|
-
await
|
|
12195
|
+
await awaitDaemonBackoff(backoff, () => stopping);
|
|
11924
12196
|
} catch (error) {
|
|
11925
12197
|
console.error(JSON.stringify({ event: "daemon_tick_error", error: error.message }));
|
|
11926
|
-
await
|
|
12198
|
+
await awaitDaemonBackoff(intervalMs, () => stopping);
|
|
11927
12199
|
}
|
|
11928
12200
|
}
|
|
11929
12201
|
console.error(JSON.stringify({ event: "daemon_stop", runId, agentOsId }));
|
|
11930
12202
|
}
|
|
11931
12203
|
|
|
11932
12204
|
// src/plan-progress.ts
|
|
11933
|
-
import
|
|
12205
|
+
import path62 from "node:path";
|
|
11934
12206
|
|
|
11935
12207
|
// src/bounded-build/constants.ts
|
|
11936
12208
|
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
@@ -12073,16 +12345,16 @@ import {
|
|
|
12073
12345
|
unlinkSync as unlinkSync4,
|
|
12074
12346
|
writeFileSync as writeFileSync5
|
|
12075
12347
|
} from "node:fs";
|
|
12076
|
-
import
|
|
12348
|
+
import path60 from "node:path";
|
|
12077
12349
|
|
|
12078
12350
|
// src/heavy-verification/paths.ts
|
|
12079
12351
|
import { mkdirSync as mkdirSync7 } from "node:fs";
|
|
12080
|
-
import
|
|
12352
|
+
import path59 from "node:path";
|
|
12081
12353
|
function resolveHeavyVerificationRoot() {
|
|
12082
|
-
return
|
|
12354
|
+
return path59.join(resolveKynverStateRoot(), "heavy-verification");
|
|
12083
12355
|
}
|
|
12084
12356
|
function heavyVerificationSlotsDir() {
|
|
12085
|
-
return
|
|
12357
|
+
return path59.join(resolveHeavyVerificationRoot(), "slots");
|
|
12086
12358
|
}
|
|
12087
12359
|
function ensureHeavyVerificationDirs() {
|
|
12088
12360
|
const dir = heavyVerificationSlotsDir();
|
|
@@ -12111,7 +12383,7 @@ function indexedSlotId(index) {
|
|
|
12111
12383
|
return `slot-${index}`;
|
|
12112
12384
|
}
|
|
12113
12385
|
function slotFilePath(slotId, slotsDir = heavyVerificationSlotsDir()) {
|
|
12114
|
-
return
|
|
12386
|
+
return path60.join(slotsDir, `${slotId}.json`);
|
|
12115
12387
|
}
|
|
12116
12388
|
function readSlotRecord(filePath) {
|
|
12117
12389
|
if (!existsSync44(filePath)) return null;
|
|
@@ -12150,7 +12422,7 @@ function reclaimStaleHeavyVerificationSlots(opts = {}) {
|
|
|
12150
12422
|
let reclaimed = 0;
|
|
12151
12423
|
for (const name of readdirSync15(slotsDir)) {
|
|
12152
12424
|
if (!name.endsWith(".json")) continue;
|
|
12153
|
-
const filePath =
|
|
12425
|
+
const filePath = path60.join(slotsDir, name);
|
|
12154
12426
|
const before = existsSync44(filePath);
|
|
12155
12427
|
reclaimStaleSlot(filePath, staleMs);
|
|
12156
12428
|
if (before && !existsSync44(filePath)) reclaimed += 1;
|
|
@@ -12164,7 +12436,7 @@ function listActiveHeavyVerificationSlots(opts = {}) {
|
|
|
12164
12436
|
const active = [];
|
|
12165
12437
|
for (const name of readdirSync15(slotsDir)) {
|
|
12166
12438
|
if (!name.endsWith(".json")) continue;
|
|
12167
|
-
const record3 = readSlotRecord(
|
|
12439
|
+
const record3 = readSlotRecord(path60.join(slotsDir, name));
|
|
12168
12440
|
if (record3 && !slotIsStale(record3, staleMs)) active.push(record3);
|
|
12169
12441
|
}
|
|
12170
12442
|
return active;
|
|
@@ -12284,11 +12556,11 @@ function waitForHeavyVerificationSlot(command, timeoutMs, pollMs = 2e3, opts = {
|
|
|
12284
12556
|
}
|
|
12285
12557
|
|
|
12286
12558
|
// src/harness-worktree-build-guard.ts
|
|
12287
|
-
import
|
|
12559
|
+
import path61 from "node:path";
|
|
12288
12560
|
function isPathUnderHarnessWorktree(cwd) {
|
|
12289
12561
|
const worktreesDir = harnessWorktreesDir(resolveHarnessRoot());
|
|
12290
|
-
const rel =
|
|
12291
|
-
return rel.length > 0 && !rel.startsWith("..") && !
|
|
12562
|
+
const rel = path61.relative(worktreesDir, path61.resolve(cwd));
|
|
12563
|
+
return rel.length > 0 && !rel.startsWith("..") && !path61.isAbsolute(rel);
|
|
12292
12564
|
}
|
|
12293
12565
|
function assessHarnessWorktreeBuildGuard(cwd) {
|
|
12294
12566
|
if (!isPathUnderHarnessWorktree(cwd)) return { ok: true };
|
|
@@ -12500,7 +12772,7 @@ async function emitPlanProgress(args) {
|
|
|
12500
12772
|
}
|
|
12501
12773
|
function verifyPlanLocal(args) {
|
|
12502
12774
|
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
12503
|
-
const cwd =
|
|
12775
|
+
const cwd = path62.resolve(worktree);
|
|
12504
12776
|
const summary = runHarnessVerifyCommands(cwd);
|
|
12505
12777
|
const emitJson = args.json === true || args.json === "true";
|
|
12506
12778
|
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
@@ -12549,9 +12821,9 @@ async function verifyPlan(args) {
|
|
|
12549
12821
|
}
|
|
12550
12822
|
|
|
12551
12823
|
// src/harness-verify-cli.ts
|
|
12552
|
-
import
|
|
12824
|
+
import path63 from "node:path";
|
|
12553
12825
|
function runHarnessVerifyCli(args) {
|
|
12554
|
-
const cwd =
|
|
12826
|
+
const cwd = path63.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
12555
12827
|
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
12556
12828
|
const commands = [];
|
|
12557
12829
|
const rawCmd = args.command;
|
|
@@ -12988,7 +13260,7 @@ ${text.slice(0, 800)}`,
|
|
|
12988
13260
|
}
|
|
12989
13261
|
|
|
12990
13262
|
// src/monitor/monitor.service.ts
|
|
12991
|
-
import
|
|
13263
|
+
import path65 from "node:path";
|
|
12992
13264
|
|
|
12993
13265
|
// src/monitor/monitor.classify.ts
|
|
12994
13266
|
function classifyWorkerHealth(input) {
|
|
@@ -13041,10 +13313,10 @@ function classifyWorkerHealth(input) {
|
|
|
13041
13313
|
|
|
13042
13314
|
// src/monitor/monitor.store.ts
|
|
13043
13315
|
import { existsSync as existsSync45, mkdirSync as mkdirSync9, readdirSync as readdirSync16, unlinkSync as unlinkSync5 } from "node:fs";
|
|
13044
|
-
import
|
|
13316
|
+
import path64 from "node:path";
|
|
13045
13317
|
function monitorsDir() {
|
|
13046
13318
|
const { harnessRoot } = getHarnessPaths();
|
|
13047
|
-
const dir =
|
|
13319
|
+
const dir = path64.join(harnessRoot, "monitors");
|
|
13048
13320
|
mkdirSync9(dir, { recursive: true });
|
|
13049
13321
|
return dir;
|
|
13050
13322
|
}
|
|
@@ -13052,7 +13324,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
13052
13324
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
13053
13325
|
}
|
|
13054
13326
|
function monitorPath(monitorId) {
|
|
13055
|
-
return
|
|
13327
|
+
return path64.join(monitorsDir(), `${monitorId}.json`);
|
|
13056
13328
|
}
|
|
13057
13329
|
function loadMonitorSession(monitorId) {
|
|
13058
13330
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -13073,7 +13345,7 @@ function listMonitorSessions() {
|
|
|
13073
13345
|
for (const name of readdirSync16(dir)) {
|
|
13074
13346
|
if (!name.endsWith(".json")) continue;
|
|
13075
13347
|
const session = readJson(
|
|
13076
|
-
|
|
13348
|
+
path64.join(dir, name),
|
|
13077
13349
|
void 0
|
|
13078
13350
|
);
|
|
13079
13351
|
if (!session?.monitorId) continue;
|
|
@@ -13164,7 +13436,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
13164
13436
|
// src/monitor/monitor.service.ts
|
|
13165
13437
|
function workerRecord2(runId, name) {
|
|
13166
13438
|
return readJson(
|
|
13167
|
-
|
|
13439
|
+
path65.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
13168
13440
|
void 0
|
|
13169
13441
|
);
|
|
13170
13442
|
}
|
|
@@ -13371,17 +13643,17 @@ async function runMonitorLoop(args) {
|
|
|
13371
13643
|
// src/monitor/monitor-spawn.ts
|
|
13372
13644
|
import { spawn as spawn6 } from "node:child_process";
|
|
13373
13645
|
import { closeSync as closeSync8, existsSync as existsSync46, openSync as openSync8 } from "node:fs";
|
|
13374
|
-
import
|
|
13646
|
+
import path66 from "node:path";
|
|
13375
13647
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
13376
13648
|
function resolveDefaultCliPath2() {
|
|
13377
|
-
return
|
|
13649
|
+
return path66.join(fileURLToPath4(new URL(".", import.meta.url)), "cli.js");
|
|
13378
13650
|
}
|
|
13379
13651
|
function spawnMonitorSidecar(opts) {
|
|
13380
13652
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
13381
13653
|
if (!existsSync46(cliPath)) return void 0;
|
|
13382
13654
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
13383
13655
|
const { harnessRoot } = getHarnessPaths();
|
|
13384
|
-
const logPath =
|
|
13656
|
+
const logPath = path66.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
13385
13657
|
let logFd;
|
|
13386
13658
|
try {
|
|
13387
13659
|
logFd = openSync8(logPath, "a");
|
|
@@ -13501,7 +13773,7 @@ async function monitorTickCli(args) {
|
|
|
13501
13773
|
}
|
|
13502
13774
|
|
|
13503
13775
|
// src/post-restart-unblock.ts
|
|
13504
|
-
import
|
|
13776
|
+
import path67 from "node:path";
|
|
13505
13777
|
function skip(runId, worker, taskId, agentOsId, leaseOwner, reason) {
|
|
13506
13778
|
return { runId, worker, taskId, agentOsId, leaseOwner, action: "skipped", reason };
|
|
13507
13779
|
}
|
|
@@ -13514,7 +13786,7 @@ async function postRestartUnblock(args) {
|
|
|
13514
13786
|
const errors = [];
|
|
13515
13787
|
for (const run of listRunRecords()) {
|
|
13516
13788
|
for (const name of Object.keys(run.workers ?? {})) {
|
|
13517
|
-
const workerPath =
|
|
13789
|
+
const workerPath = path67.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
13518
13790
|
const worker = readJson(workerPath, void 0);
|
|
13519
13791
|
if (!worker) {
|
|
13520
13792
|
skipped.push(skip(run.id, name, "", "", "", "worker.json missing"));
|
|
@@ -13626,9 +13898,9 @@ async function postRestartUnblockCli(args) {
|
|
|
13626
13898
|
}
|
|
13627
13899
|
|
|
13628
13900
|
// src/default-repo-cli.ts
|
|
13629
|
-
import
|
|
13901
|
+
import path68 from "node:path";
|
|
13630
13902
|
import { homedir as homedir15 } from "node:os";
|
|
13631
|
-
var CONFIG_FILE2 =
|
|
13903
|
+
var CONFIG_FILE2 = path68.join(homedir15(), ".kynver", "config.json");
|
|
13632
13904
|
function ensureDefaultRepo(opts) {
|
|
13633
13905
|
const existing = loadUserConfig();
|
|
13634
13906
|
const resolved = resolveDefaultRepo({ ...opts, config: existing });
|
|
@@ -13709,12 +13981,12 @@ function summarizeResolvedDefaultRepo(resolved) {
|
|
|
13709
13981
|
}
|
|
13710
13982
|
|
|
13711
13983
|
// src/doctor/runtime-takeover.ts
|
|
13712
|
-
import
|
|
13984
|
+
import path70 from "node:path";
|
|
13713
13985
|
|
|
13714
13986
|
// src/doctor/runtime-takeover.probes.ts
|
|
13715
13987
|
import { accessSync, constants, existsSync as existsSync47, readFileSync as readFileSync19 } from "node:fs";
|
|
13716
13988
|
import { homedir as homedir16 } from "node:os";
|
|
13717
|
-
import
|
|
13989
|
+
import path69 from "node:path";
|
|
13718
13990
|
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
13719
13991
|
function captureCommand(bin, args) {
|
|
13720
13992
|
try {
|
|
@@ -13756,10 +14028,10 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
13756
14028
|
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
13757
14029
|
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
13758
14030
|
loadConfig: () => loadUserConfig(),
|
|
13759
|
-
configFilePath: () =>
|
|
13760
|
-
credentialsFilePath: () =>
|
|
14031
|
+
configFilePath: () => path69.join(homedir16(), ".kynver", "config.json"),
|
|
14032
|
+
credentialsFilePath: () => path69.join(homedir16(), ".kynver", "credentials"),
|
|
13761
14033
|
readCredentials: () => {
|
|
13762
|
-
const credPath =
|
|
14034
|
+
const credPath = path69.join(homedir16(), ".kynver", "credentials");
|
|
13763
14035
|
if (!existsSync47(credPath)) {
|
|
13764
14036
|
return { hasApiKey: false };
|
|
13765
14037
|
}
|
|
@@ -13794,7 +14066,7 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
13794
14066
|
})()
|
|
13795
14067
|
}),
|
|
13796
14068
|
harnessRoot: () => resolveHarnessRoot(),
|
|
13797
|
-
legacyOpenclawHarnessRoot: () =>
|
|
14069
|
+
legacyOpenclawHarnessRoot: () => path69.join(homedir16(), ".openclaw", "harness"),
|
|
13798
14070
|
pathExists: (target) => existsSync47(target),
|
|
13799
14071
|
pathWritable: (target) => isWritable(target)
|
|
13800
14072
|
};
|
|
@@ -14201,8 +14473,8 @@ function assessVercelDeployEvidence(probes) {
|
|
|
14201
14473
|
}
|
|
14202
14474
|
function assessHarnessDirs(probes) {
|
|
14203
14475
|
const harnessRoot = probes.harnessRoot();
|
|
14204
|
-
const runsDir =
|
|
14205
|
-
const worktreesDir =
|
|
14476
|
+
const runsDir = path70.join(harnessRoot, "runs");
|
|
14477
|
+
const worktreesDir = path70.join(harnessRoot, "worktrees");
|
|
14206
14478
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
14207
14479
|
const displayRunsDir = redactHomePath(runsDir);
|
|
14208
14480
|
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
@@ -14466,9 +14738,9 @@ function applySchedulerCutoverAttestation(config) {
|
|
|
14466
14738
|
}
|
|
14467
14739
|
|
|
14468
14740
|
// src/scheduler-cutover-cli.ts
|
|
14469
|
-
import
|
|
14741
|
+
import path71 from "node:path";
|
|
14470
14742
|
import { homedir as homedir17 } from "node:os";
|
|
14471
|
-
var CONFIG_FILE3 =
|
|
14743
|
+
var CONFIG_FILE3 = path71.join(homedir17(), ".kynver", "config.json");
|
|
14472
14744
|
function runSchedulerCutoverCheckCli(json = false) {
|
|
14473
14745
|
const config = loadUserConfig();
|
|
14474
14746
|
const report = assessSchedulerCutover(config);
|
|
@@ -14605,6 +14877,157 @@ async function runCronTickCli(args) {
|
|
|
14605
14877
|
);
|
|
14606
14878
|
}
|
|
14607
14879
|
|
|
14880
|
+
// src/lane/landing-maintainer-tick.ts
|
|
14881
|
+
import os9 from "node:os";
|
|
14882
|
+
|
|
14883
|
+
// src/lane/lane-spec.ts
|
|
14884
|
+
var LANDING_MAINTAINER_LANE_SPEC = {
|
|
14885
|
+
slug: "landing-maintainer",
|
|
14886
|
+
originCron: "maintain-8-blocker-and-pr-landing-workers",
|
|
14887
|
+
defaultRepo: "Totalsolutionsync/Kynver",
|
|
14888
|
+
landScript: "scripts/agent-os-land-pr.mjs",
|
|
14889
|
+
landScriptArgs: ["--skip-not-ready"]
|
|
14890
|
+
};
|
|
14891
|
+
|
|
14892
|
+
// src/lane/landing-maintainer-local.ts
|
|
14893
|
+
import { spawnSync as spawnSync11 } from "node:child_process";
|
|
14894
|
+
import path72 from "node:path";
|
|
14895
|
+
function runLandingWrapper(prNumber, repoRoot, execute) {
|
|
14896
|
+
const script = path72.join(repoRoot, LANDING_MAINTAINER_LANE_SPEC.landScript);
|
|
14897
|
+
const args = [script, String(prNumber), ...LANDING_MAINTAINER_LANE_SPEC.landScriptArgs];
|
|
14898
|
+
if (!execute) {
|
|
14899
|
+
return {
|
|
14900
|
+
action: { kind: "land_pr", prNumber, reason: "dry-run" },
|
|
14901
|
+
executed: false,
|
|
14902
|
+
exitCode: 0,
|
|
14903
|
+
stdout: `dry-run: node ${args.join(" ")}`,
|
|
14904
|
+
stderr: ""
|
|
14905
|
+
};
|
|
14906
|
+
}
|
|
14907
|
+
const result = spawnSync11("node", args, {
|
|
14908
|
+
cwd: repoRoot,
|
|
14909
|
+
encoding: "utf8",
|
|
14910
|
+
timeout: 10 * 60 * 1e3
|
|
14911
|
+
});
|
|
14912
|
+
return {
|
|
14913
|
+
action: { kind: "land_pr", prNumber, reason: "landing wrapper invoked" },
|
|
14914
|
+
executed: true,
|
|
14915
|
+
exitCode: result.status,
|
|
14916
|
+
stdout: result.stdout ?? "",
|
|
14917
|
+
stderr: result.stderr ?? ""
|
|
14918
|
+
};
|
|
14919
|
+
}
|
|
14920
|
+
function resolveLandingMaintainerRepoRoot(args) {
|
|
14921
|
+
const explicit = args.repoPath ? String(args.repoPath).trim() : "";
|
|
14922
|
+
if (explicit) return path72.resolve(explicit);
|
|
14923
|
+
const resolved = resolveDefaultRepo();
|
|
14924
|
+
return resolved?.repo ?? process.cwd();
|
|
14925
|
+
}
|
|
14926
|
+
|
|
14927
|
+
// src/lane/landing-maintainer-tick.ts
|
|
14928
|
+
async function runLandingMaintainerLaneTick(args) {
|
|
14929
|
+
const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
|
|
14930
|
+
const repoSlug = String(args.repo || LANDING_MAINTAINER_LANE_SPEC.defaultRepo).trim();
|
|
14931
|
+
const fleet = args.fleet === true || args.fleet === "true";
|
|
14932
|
+
const execute = args.execute === true || args.execute === "true";
|
|
14933
|
+
const runId = args.run ? String(args.run) : void 0;
|
|
14934
|
+
const resourceGate = observeRunnerResourceGate({
|
|
14935
|
+
runId: runId ?? "fleet-lane-tick"
|
|
14936
|
+
});
|
|
14937
|
+
const boxCapacity = {
|
|
14938
|
+
...buildBoxResourceSnapshotFromGate(resourceGate, {
|
|
14939
|
+
harnessRunId: runId,
|
|
14940
|
+
boxKind: resolveBoxKindFromConfig(loadUserConfig()),
|
|
14941
|
+
hostLabel: os9.hostname()
|
|
14942
|
+
}),
|
|
14943
|
+
providerHealthy: resourceGate.ok,
|
|
14944
|
+
authorizedForRepair: resourceGate.ok,
|
|
14945
|
+
authorizedForLanding: resourceGate.ok,
|
|
14946
|
+
systemHealthBlockers: resourceGate.ok ? [] : [resourceGate.reason ?? "resource_gate_blocked"],
|
|
14947
|
+
actionableWorkers: resourceGate.activeWorkers
|
|
14948
|
+
};
|
|
14949
|
+
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
14950
|
+
const secret = await resolveCallbackSecretWithMint(
|
|
14951
|
+
args.secret ? String(args.secret) : void 0,
|
|
14952
|
+
agentOsId,
|
|
14953
|
+
{ baseUrl: base }
|
|
14954
|
+
);
|
|
14955
|
+
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/fleet-landing-maintainer/tick`;
|
|
14956
|
+
const res = await postJson(url, secret, {
|
|
14957
|
+
repo: repoSlug,
|
|
14958
|
+
fleet,
|
|
14959
|
+
execute,
|
|
14960
|
+
runId,
|
|
14961
|
+
boxCapacity
|
|
14962
|
+
});
|
|
14963
|
+
const coordinator = res.response;
|
|
14964
|
+
const localOutcomes = [];
|
|
14965
|
+
const repoRoot = resolveLandingMaintainerRepoRoot(args);
|
|
14966
|
+
const actions = Array.isArray(coordinator?.localActions) ? coordinator.localActions : [];
|
|
14967
|
+
const holderBoxId = boxCapacity.boxId;
|
|
14968
|
+
for (const action of actions) {
|
|
14969
|
+
if (action.kind === "land_pr" && typeof action.prNumber === "number") {
|
|
14970
|
+
const outcome = runLandingWrapper(action.prNumber, repoRoot, execute);
|
|
14971
|
+
localOutcomes.push({
|
|
14972
|
+
action: outcome.action,
|
|
14973
|
+
executed: outcome.executed,
|
|
14974
|
+
exitCode: outcome.exitCode
|
|
14975
|
+
});
|
|
14976
|
+
if (execute && outcome.executed) {
|
|
14977
|
+
const merged = outcome.exitCode === 0;
|
|
14978
|
+
const prUrl = typeof action.prUrl === "string" && action.prUrl.trim() ? action.prUrl.trim() : `https://github.com/${repoSlug}/pull/${action.prNumber}`;
|
|
14979
|
+
try {
|
|
14980
|
+
await postJson(
|
|
14981
|
+
`${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/fleet-landing-maintainer/outcome`,
|
|
14982
|
+
secret,
|
|
14983
|
+
{ repo: repoSlug, prUrl, holderBoxId, merged }
|
|
14984
|
+
);
|
|
14985
|
+
} catch {
|
|
14986
|
+
}
|
|
14987
|
+
}
|
|
14988
|
+
continue;
|
|
14989
|
+
}
|
|
14990
|
+
localOutcomes.push({
|
|
14991
|
+
action,
|
|
14992
|
+
executed: false,
|
|
14993
|
+
exitCode: null
|
|
14994
|
+
});
|
|
14995
|
+
}
|
|
14996
|
+
return {
|
|
14997
|
+
repo: repoSlug,
|
|
14998
|
+
fleet,
|
|
14999
|
+
execute,
|
|
15000
|
+
coordinator,
|
|
15001
|
+
localOutcomes
|
|
15002
|
+
};
|
|
15003
|
+
}
|
|
15004
|
+
|
|
15005
|
+
// src/lane/lane-tick-cli.ts
|
|
15006
|
+
async function runLaneTickCli(args, laneFromArgv) {
|
|
15007
|
+
const lane = String(laneFromArgv ?? args.lane ?? "").trim();
|
|
15008
|
+
if (lane !== "landing-maintainer") {
|
|
15009
|
+
console.error(`unknown lane: ${lane || "(none)"}`);
|
|
15010
|
+
process.exit(1);
|
|
15011
|
+
}
|
|
15012
|
+
const result = await runLandingMaintainerLaneTick(args);
|
|
15013
|
+
if (args.json === true || args.json === "true") {
|
|
15014
|
+
console.log(JSON.stringify(result, null, 2));
|
|
15015
|
+
return;
|
|
15016
|
+
}
|
|
15017
|
+
console.log(
|
|
15018
|
+
[
|
|
15019
|
+
`fleet landing-maintainer tick`,
|
|
15020
|
+
`repo=${result.repo}`,
|
|
15021
|
+
`fleet=${result.fleet}`,
|
|
15022
|
+
`execute=${result.execute}`,
|
|
15023
|
+
`localOutcomes=${result.localOutcomes.length}`
|
|
15024
|
+
].join(" ")
|
|
15025
|
+
);
|
|
15026
|
+
for (const row of result.localOutcomes) {
|
|
15027
|
+
console.log(` ${row.action.kind} pr=${row.action.prNumber ?? "-"} executed=${row.executed}`);
|
|
15028
|
+
}
|
|
15029
|
+
}
|
|
15030
|
+
|
|
14608
15031
|
// src/cli.ts
|
|
14609
15032
|
function isHelpFlag(arg) {
|
|
14610
15033
|
return arg === "help" || arg === "--help" || arg === "-h";
|
|
@@ -14625,6 +15048,7 @@ function usage(code = 0) {
|
|
|
14625
15048
|
" kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS]",
|
|
14626
15049
|
" kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
|
|
14627
15050
|
" kynver run list",
|
|
15051
|
+
" kynver run resolve --name RUN_NAME",
|
|
14628
15052
|
" kynver run status --run RUN_ID [--json] [--compact]",
|
|
14629
15053
|
" 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
15054
|
" kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
|
|
@@ -14660,6 +15084,7 @@ function usage(code = 0) {
|
|
|
14660
15084
|
" kynver scheduler attest-cutover [--json]",
|
|
14661
15085
|
" kynver cron status [--json]",
|
|
14662
15086
|
" kynver cron tick [--agent-os-id AOS_ID] [--json]",
|
|
15087
|
+
" kynver lane tick landing-maintainer [--fleet] [--repo OWNER/NAME] [--agent-os-id AOS_ID] [--execute] [--json]",
|
|
14663
15088
|
" kynver board contract [--agent-os-id ID] [--base-url URL] [--since ISO] [--limit N]"
|
|
14664
15089
|
].join("\n")
|
|
14665
15090
|
);
|
|
@@ -14671,7 +15096,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
14671
15096
|
const scope = argv.shift();
|
|
14672
15097
|
let action;
|
|
14673
15098
|
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") {
|
|
15099
|
+
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
15100
|
action = argv.shift();
|
|
14676
15101
|
rest = argv;
|
|
14677
15102
|
} else {
|
|
@@ -14728,11 +15153,16 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
14728
15153
|
if (scope === "cron" && action === "tick") {
|
|
14729
15154
|
return void await runCronTickCli(args);
|
|
14730
15155
|
}
|
|
15156
|
+
if (scope === "lane" && action === "tick") {
|
|
15157
|
+
const laneName = rest.shift();
|
|
15158
|
+
return void await runLaneTickCli(parseArgs(rest), laneName);
|
|
15159
|
+
}
|
|
14731
15160
|
if (scope === "board" && action === "contract") {
|
|
14732
15161
|
return void await runCommandCenterContractCli(args);
|
|
14733
15162
|
}
|
|
14734
15163
|
if (scope === "run" && action === "create") return createRun(args);
|
|
14735
15164
|
if (scope === "run" && action === "list") return listRuns();
|
|
15165
|
+
if (scope === "run" && action === "resolve") return resolveHarnessRunCli(args);
|
|
14736
15166
|
if (scope === "run" && action === "status") return runStatus(args);
|
|
14737
15167
|
if (scope === "run" && action === "dispatch") return void await dispatchRun(args);
|
|
14738
15168
|
if (scope === "run" && action === "sweep") return void await sweepRun(args);
|
|
@@ -15226,6 +15656,7 @@ export {
|
|
|
15226
15656
|
hasNestedRunsSegment,
|
|
15227
15657
|
hashPlanBody,
|
|
15228
15658
|
hermesCodexProvider,
|
|
15659
|
+
isActiveHarnessWorker,
|
|
15229
15660
|
isClaudeFamilyProvider,
|
|
15230
15661
|
isDashboardVercelUrl,
|
|
15231
15662
|
isEngagementRequiredSkip,
|
|
@@ -15289,6 +15720,7 @@ export {
|
|
|
15289
15720
|
readMemAvailableBytes,
|
|
15290
15721
|
readProductionDbKeysFromEnvFile,
|
|
15291
15722
|
reclaimStaleHeavyVerificationSlots,
|
|
15723
|
+
reconcileLocalOnlyMergedPrAttention,
|
|
15292
15724
|
reconcileRunsCli,
|
|
15293
15725
|
reconcileStaleWorkers,
|
|
15294
15726
|
reconcileWorkerMetadata,
|
|
@@ -15307,6 +15739,8 @@ export {
|
|
|
15307
15739
|
resolveConfiguredWorkerProvider,
|
|
15308
15740
|
resolveDefaultRepo,
|
|
15309
15741
|
resolveHarnessRoot,
|
|
15742
|
+
resolveHarnessRunByName,
|
|
15743
|
+
resolveHarnessRunCli,
|
|
15310
15744
|
resolveHeavyVerificationMaxConcurrent,
|
|
15311
15745
|
resolveOpenAiCodexRetryBudget,
|
|
15312
15746
|
resolveOrchestrationPolicyMode,
|