@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/cli.js
CHANGED
|
@@ -90,6 +90,9 @@ function readMaybeFile(file) {
|
|
|
90
90
|
function sleepMs(ms) {
|
|
91
91
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
92
92
|
}
|
|
93
|
+
function sleepMsAsync(ms) {
|
|
94
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
95
|
+
}
|
|
93
96
|
function isPidAlive(pid) {
|
|
94
97
|
if (!pid) return false;
|
|
95
98
|
try {
|
|
@@ -461,7 +464,6 @@ function readMemAvailableBytes(meminfoText) {
|
|
|
461
464
|
|
|
462
465
|
// src/resource-gate.ts
|
|
463
466
|
import path7 from "node:path";
|
|
464
|
-
import { readFileSync as readFileSync7 } from "node:fs";
|
|
465
467
|
|
|
466
468
|
// src/disk-gate.ts
|
|
467
469
|
import { statfsSync as statfsSync2 } from "node:fs";
|
|
@@ -486,23 +488,23 @@ function isWslHost() {
|
|
|
486
488
|
function observeWslHostDisk(options = {}) {
|
|
487
489
|
const wsl = options.forceWsl === void 0 ? isWslHost() : options.forceWsl;
|
|
488
490
|
if (!wsl) return null;
|
|
489
|
-
const
|
|
491
|
+
const path71 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
|
|
490
492
|
const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;
|
|
491
493
|
const criticalBelowBytes = options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;
|
|
492
494
|
const statfs = options.statfs ?? statfsSync;
|
|
493
495
|
let stats;
|
|
494
496
|
try {
|
|
495
|
-
stats = statfs(
|
|
497
|
+
stats = statfs(path71);
|
|
496
498
|
} catch (error) {
|
|
497
499
|
return {
|
|
498
500
|
ok: false,
|
|
499
|
-
path:
|
|
501
|
+
path: path71,
|
|
500
502
|
freeBytes: 0,
|
|
501
503
|
totalBytes: 0,
|
|
502
504
|
usedPercent: 100,
|
|
503
505
|
warnBelowBytes,
|
|
504
506
|
criticalBelowBytes,
|
|
505
|
-
reason: `Windows host disk probe failed at ${
|
|
507
|
+
reason: `Windows host disk probe failed at ${path71}: ${error.message}`,
|
|
506
508
|
probeError: error.message
|
|
507
509
|
};
|
|
508
510
|
}
|
|
@@ -516,11 +518,11 @@ function observeWslHostDisk(options = {}) {
|
|
|
516
518
|
let reason = null;
|
|
517
519
|
if (!ok) {
|
|
518
520
|
const tag = criticalFree ? "critical" : "warning";
|
|
519
|
-
reason = `Windows host disk ${
|
|
521
|
+
reason = `Windows host disk ${path71} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
|
|
520
522
|
}
|
|
521
523
|
return {
|
|
522
524
|
ok,
|
|
523
|
-
path:
|
|
525
|
+
path: path71,
|
|
524
526
|
freeBytes,
|
|
525
527
|
totalBytes,
|
|
526
528
|
usedPercent,
|
|
@@ -540,12 +542,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
|
540
542
|
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
541
543
|
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
542
544
|
function observeRunnerDiskGate(input = {}) {
|
|
543
|
-
const
|
|
545
|
+
const path71 = input.diskPath?.trim() || "/";
|
|
544
546
|
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
545
547
|
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
546
548
|
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
547
549
|
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
548
|
-
const stats = statfsSync2(
|
|
550
|
+
const stats = statfsSync2(path71);
|
|
549
551
|
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
550
552
|
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
551
553
|
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
@@ -568,7 +570,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
568
570
|
}
|
|
569
571
|
return {
|
|
570
572
|
ok,
|
|
571
|
-
path:
|
|
573
|
+
path: path71,
|
|
572
574
|
freeBytes,
|
|
573
575
|
totalBytes,
|
|
574
576
|
usedPercent,
|
|
@@ -702,6 +704,9 @@ function listRunWorkerNames(run) {
|
|
|
702
704
|
return [...names];
|
|
703
705
|
}
|
|
704
706
|
|
|
707
|
+
// src/harness-worker-active.ts
|
|
708
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
709
|
+
|
|
705
710
|
// src/heartbeat.ts
|
|
706
711
|
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
707
712
|
|
|
@@ -1782,6 +1787,9 @@ function computeAttention(input) {
|
|
|
1782
1787
|
return { state: "blocked", reason: input.completionBlocker };
|
|
1783
1788
|
}
|
|
1784
1789
|
if (input.finalResult) {
|
|
1790
|
+
if (input.localOnly && hasMergedTargetPrReconciliation(input.finalResult)) {
|
|
1791
|
+
return { state: "done", reason: "local-only worker superseded by merged PR" };
|
|
1792
|
+
}
|
|
1785
1793
|
const landingSnapshot = {
|
|
1786
1794
|
finalResult: input.finalResult,
|
|
1787
1795
|
changedFiles: input.changedFiles ?? [],
|
|
@@ -1847,9 +1855,24 @@ function computeAttention(input) {
|
|
|
1847
1855
|
}
|
|
1848
1856
|
return { state: "ok", reason: "recent activity" };
|
|
1849
1857
|
}
|
|
1858
|
+
function hasMergedTargetPrReconciliation(value) {
|
|
1859
|
+
let record = null;
|
|
1860
|
+
if (typeof value === "string") record = extractEmbeddedWorkerFinalResultRecord(value);
|
|
1861
|
+
else if (value && typeof value === "object" && !Array.isArray(value)) record = value;
|
|
1862
|
+
if (!record) return false;
|
|
1863
|
+
const raw = record.targetPrReconciliation ?? record.target_pr_reconciliation;
|
|
1864
|
+
if (!Array.isArray(raw)) return false;
|
|
1865
|
+
return raw.some((item) => {
|
|
1866
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return false;
|
|
1867
|
+
return String(item.outcome ?? "").trim() === "merged";
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1850
1870
|
function resolveFinalResult(worker, parsedFinalResult, heartbeat) {
|
|
1851
|
-
if (parsedFinalResult) return parsedFinalResult;
|
|
1852
1871
|
const ackSnapshot = worker.completionSnapshot?.finalResult;
|
|
1872
|
+
if (worker.completionAckSource === "local-pr-merged-reconcile" && ackSnapshot !== void 0 && ackSnapshot !== null) {
|
|
1873
|
+
return ackSnapshot;
|
|
1874
|
+
}
|
|
1875
|
+
if (parsedFinalResult) return parsedFinalResult;
|
|
1853
1876
|
if (ackSnapshot !== void 0 && ackSnapshot !== null) return ackSnapshot;
|
|
1854
1877
|
return terminalFinalResultFromHeartbeat(heartbeat);
|
|
1855
1878
|
}
|
|
@@ -1896,7 +1919,8 @@ function computeWorkerStatus(worker, options = {}) {
|
|
|
1896
1919
|
gitAncestry,
|
|
1897
1920
|
completionBlocker,
|
|
1898
1921
|
landingContract,
|
|
1899
|
-
prUrl: worker.repairTargetPrUrl ?? worker.taskPrUrl ?? null
|
|
1922
|
+
prUrl: worker.repairTargetPrUrl ?? worker.taskPrUrl ?? null,
|
|
1923
|
+
localOnly: worker.localOnly === true
|
|
1900
1924
|
});
|
|
1901
1925
|
const workerStatusLabel = completionBlocker || attention.state === "blocked" ? "blocked" : completionAcknowledged || attention.state === "done" ? "done" : finalResult ? "exited" : alive ? "running" : "exited";
|
|
1902
1926
|
return {
|
|
@@ -1950,6 +1974,33 @@ function deriveRunStatus(fallback, workers) {
|
|
|
1950
1974
|
return fallback;
|
|
1951
1975
|
}
|
|
1952
1976
|
|
|
1977
|
+
// src/harness-worker-active.ts
|
|
1978
|
+
function pidCommandLine(pid) {
|
|
1979
|
+
if (!pid || process.platform !== "linux") return null;
|
|
1980
|
+
try {
|
|
1981
|
+
return readFileSync7(`/proc/${pid}/cmdline`, "utf8").replace(/\0/g, " ");
|
|
1982
|
+
} catch {
|
|
1983
|
+
return null;
|
|
1984
|
+
}
|
|
1985
|
+
}
|
|
1986
|
+
function workerProcessMatchesRecord(worker) {
|
|
1987
|
+
if (!worker.pid || process.platform !== "linux") return true;
|
|
1988
|
+
const cmdline = pidCommandLine(worker.pid);
|
|
1989
|
+
if (!cmdline) return false;
|
|
1990
|
+
const probes = [worker.worktreePath, worker.workerDir, worker.heartbeatPath].filter(
|
|
1991
|
+
(value) => typeof value === "string" && value.trim().length > 0
|
|
1992
|
+
);
|
|
1993
|
+
return probes.some((probe) => cmdline.includes(probe));
|
|
1994
|
+
}
|
|
1995
|
+
function isActiveHarnessWorker(worker) {
|
|
1996
|
+
if (typeof worker.completionBlocker === "string" && worker.completionBlocker.trim()) {
|
|
1997
|
+
return false;
|
|
1998
|
+
}
|
|
1999
|
+
const status = computeWorkerStatus(worker);
|
|
2000
|
+
if (status.alive && !workerProcessMatchesRecord(worker)) return false;
|
|
2001
|
+
return status.alive && !status.finalResult && status.attention.state !== "done";
|
|
2002
|
+
}
|
|
2003
|
+
|
|
1953
2004
|
// src/resource-gate.ts
|
|
1954
2005
|
var DEFAULT_PER_WORKER_MEM_BYTES = 500 * 1024 * 1024;
|
|
1955
2006
|
var DEFAULT_MEM_RESERVE_BYTES = 4 * 1024 * 1024 * 1024;
|
|
@@ -1992,31 +2043,6 @@ function computeAutoMaxWorkers(totalMemBytes, opts = {}) {
|
|
|
1992
2043
|
function readAvailableMemBytes() {
|
|
1993
2044
|
return readMemAvailableBytes();
|
|
1994
2045
|
}
|
|
1995
|
-
function pidCommandLine(pid) {
|
|
1996
|
-
if (!pid || process.platform !== "linux") return null;
|
|
1997
|
-
try {
|
|
1998
|
-
return readFileSync7(`/proc/${pid}/cmdline`, "utf8").replace(/\0/g, " ");
|
|
1999
|
-
} catch {
|
|
2000
|
-
return null;
|
|
2001
|
-
}
|
|
2002
|
-
}
|
|
2003
|
-
function workerProcessMatchesRecord(worker) {
|
|
2004
|
-
if (!worker.pid || process.platform !== "linux") return true;
|
|
2005
|
-
const cmdline = pidCommandLine(worker.pid);
|
|
2006
|
-
if (!cmdline) return false;
|
|
2007
|
-
const probes = [worker.worktreePath, worker.workerDir, worker.heartbeatPath].filter(
|
|
2008
|
-
(value) => typeof value === "string" && value.trim().length > 0
|
|
2009
|
-
);
|
|
2010
|
-
return probes.some((probe) => cmdline.includes(probe));
|
|
2011
|
-
}
|
|
2012
|
-
function isActiveHarnessWorker(worker) {
|
|
2013
|
-
if (typeof worker.completionBlocker === "string" && worker.completionBlocker.trim()) {
|
|
2014
|
-
return false;
|
|
2015
|
-
}
|
|
2016
|
-
const status = computeWorkerStatus(worker);
|
|
2017
|
-
if (status.alive && !workerProcessMatchesRecord(worker)) return false;
|
|
2018
|
-
return status.alive && !status.finalResult && status.attention.state !== "done";
|
|
2019
|
-
}
|
|
2020
2046
|
function countActiveWorkersForRun(run) {
|
|
2021
2047
|
let active = 0;
|
|
2022
2048
|
for (const name of listRunWorkerNames(run)) {
|
|
@@ -5021,8 +5047,8 @@ function dirtyPathsCoveredByDisposableRemoval(changedFiles, removed) {
|
|
|
5021
5047
|
if (removed.length === 0) return false;
|
|
5022
5048
|
const removedSet = new Set(removed.map((p) => normalizeRelativePath(p)));
|
|
5023
5049
|
return material.every((line) => {
|
|
5024
|
-
const
|
|
5025
|
-
return removedSet.has(
|
|
5050
|
+
const path71 = normalizeRelativePath(pathFromGitStatusLine(line));
|
|
5051
|
+
return removedSet.has(path71);
|
|
5026
5052
|
});
|
|
5027
5053
|
}
|
|
5028
5054
|
|
|
@@ -6316,7 +6342,7 @@ function collectRunActiveHarnessWorkers(runId) {
|
|
|
6316
6342
|
path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
6317
6343
|
void 0
|
|
6318
6344
|
);
|
|
6319
|
-
if (!worker?.taskId || !
|
|
6345
|
+
if (!worker?.taskId || !isActiveHarnessWorker(worker)) continue;
|
|
6320
6346
|
out.push({
|
|
6321
6347
|
runId: run.id,
|
|
6322
6348
|
workerName: name,
|
|
@@ -7177,7 +7203,7 @@ async function dispatchRun(args) {
|
|
|
7177
7203
|
);
|
|
7178
7204
|
}
|
|
7179
7205
|
const attempt = Number(task.attempt) || 1;
|
|
7180
|
-
if (attempt
|
|
7206
|
+
if (attempt > retryLimits.maxTaskAttempts) {
|
|
7181
7207
|
return abortClaimedSpawn(
|
|
7182
7208
|
task,
|
|
7183
7209
|
`task attempt ${attempt} exceeds KYNVER_MAX_TASK_ATTEMPTS (${retryLimits.maxTaskAttempts})`
|
|
@@ -7313,7 +7339,12 @@ async function dispatchRun(args) {
|
|
|
7313
7339
|
const admissionExhaustion = readAdmissionExhaustion(result);
|
|
7314
7340
|
const capacityIdle = admissionExhaustion?.capacityIdle === true || startedCount === 0 && Number(result.resourceGate?.slotsAvailable) > 0;
|
|
7315
7341
|
if (capacityIdle && admissionExhaustion?.summary) {
|
|
7316
|
-
|
|
7342
|
+
const retryCeiling = admissionExhaustion.skipReasonCounts?.retry_ceiling_exceeded ?? 0;
|
|
7343
|
+
const recovery = admissionExhaustion.overAttemptIdleRecovery;
|
|
7344
|
+
const recoveryNote = recovery?.attempted === true ? `; over_attempt_recovery minted=${recovery.minted ?? 0} started=${recovery.started ?? 0}` : retryCeiling > 0 ? "; over_attempt_recovery not attempted" : "";
|
|
7345
|
+
console.error(
|
|
7346
|
+
`[dispatch] ${admissionExhaustion.summary}${retryCeiling > 0 ? `; retry_ceiling_exceeded=${retryCeiling}` : ""}${recoveryNote}`
|
|
7347
|
+
);
|
|
7317
7348
|
}
|
|
7318
7349
|
const summary = {
|
|
7319
7350
|
runId: run.id,
|
|
@@ -7409,14 +7440,14 @@ async function sweepRun(args) {
|
|
|
7409
7440
|
|
|
7410
7441
|
// src/worktree.ts
|
|
7411
7442
|
import { existsSync as existsSync25, mkdirSync as mkdirSync6 } from "node:fs";
|
|
7412
|
-
import
|
|
7443
|
+
import path35 from "node:path";
|
|
7413
7444
|
|
|
7414
7445
|
// src/run-list.ts
|
|
7415
7446
|
import { existsSync as existsSync24, readFileSync as readFileSync11 } from "node:fs";
|
|
7416
|
-
import
|
|
7447
|
+
import path34 from "node:path";
|
|
7417
7448
|
|
|
7418
7449
|
// src/stale-reconcile.ts
|
|
7419
|
-
import
|
|
7450
|
+
import path33 from "node:path";
|
|
7420
7451
|
|
|
7421
7452
|
// src/finalize.ts
|
|
7422
7453
|
import path27 from "node:path";
|
|
@@ -7999,6 +8030,193 @@ function reconcileWorkerMetadata() {
|
|
|
7999
8030
|
return { workers: outcomes, runMetadataRetention };
|
|
8000
8031
|
}
|
|
8001
8032
|
|
|
8033
|
+
// src/local-pr-attention-reconcile.ts
|
|
8034
|
+
import { execFileSync } from "node:child_process";
|
|
8035
|
+
import path32 from "node:path";
|
|
8036
|
+
function normalizePrUrl3(url) {
|
|
8037
|
+
const m = url.trim().match(/github\.com\/([^/]+\/[^/]+)\/(?:pull|pulls)\/(\d+)/i);
|
|
8038
|
+
if (!m) return null;
|
|
8039
|
+
return `https://github.com/${m[1]}/pull/${m[2]}`;
|
|
8040
|
+
}
|
|
8041
|
+
function prNumberFromUrl(url) {
|
|
8042
|
+
const m = normalizePrUrl3(url)?.match(/\/pull\/(\d+)$/);
|
|
8043
|
+
if (!m) return null;
|
|
8044
|
+
const n = Number.parseInt(m[1], 10);
|
|
8045
|
+
return Number.isFinite(n) ? n : null;
|
|
8046
|
+
}
|
|
8047
|
+
function extractText(value) {
|
|
8048
|
+
if (value == null) return "";
|
|
8049
|
+
if (typeof value === "string") return value;
|
|
8050
|
+
try {
|
|
8051
|
+
return JSON.stringify(value);
|
|
8052
|
+
} catch {
|
|
8053
|
+
return "";
|
|
8054
|
+
}
|
|
8055
|
+
}
|
|
8056
|
+
function extractPrNumbersFromText(text) {
|
|
8057
|
+
const out = /* @__PURE__ */ new Set();
|
|
8058
|
+
for (const match of text.matchAll(/github\.com\/[^/\s)>"']+\/[^/\s)>"']+\/(?:pull|pulls)\/(\d+)/gi)) {
|
|
8059
|
+
const n = Number.parseInt(match[1], 10);
|
|
8060
|
+
if (Number.isFinite(n)) out.add(n);
|
|
8061
|
+
}
|
|
8062
|
+
for (const match of text.matchAll(/\bPR\s*#?\s*(\d{2,})\b/gi)) {
|
|
8063
|
+
const n = Number.parseInt(match[1], 10);
|
|
8064
|
+
if (Number.isFinite(n)) out.add(n);
|
|
8065
|
+
}
|
|
8066
|
+
for (const match of text.matchAll(/\bpr[-_]?(\d{2,})\b/gi)) {
|
|
8067
|
+
const n = Number.parseInt(match[1], 10);
|
|
8068
|
+
if (Number.isFinite(n)) out.add(n);
|
|
8069
|
+
}
|
|
8070
|
+
return [...out];
|
|
8071
|
+
}
|
|
8072
|
+
function extractTargetPrReconciliation(value) {
|
|
8073
|
+
let record = null;
|
|
8074
|
+
if (typeof value === "string") record = extractEmbeddedWorkerFinalResultRecord(value);
|
|
8075
|
+
else if (value && typeof value === "object" && !Array.isArray(value)) record = value;
|
|
8076
|
+
if (!record) return [];
|
|
8077
|
+
const raw = record.targetPrReconciliation ?? record.target_pr_reconciliation;
|
|
8078
|
+
if (!Array.isArray(raw)) return [];
|
|
8079
|
+
const out = [];
|
|
8080
|
+
for (const item of raw) {
|
|
8081
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
8082
|
+
const row = item;
|
|
8083
|
+
const prUrl = normalizePrUrl3(String(row.prUrl ?? row.pr_url ?? ""));
|
|
8084
|
+
const outcome = String(row.outcome ?? "").trim();
|
|
8085
|
+
if (!prUrl || outcome !== "merged") continue;
|
|
8086
|
+
out.push({
|
|
8087
|
+
prUrl,
|
|
8088
|
+
mergeCommit: typeof row.mergeCommit === "string" ? row.mergeCommit : typeof row.merge_commit === "string" ? row.merge_commit : null,
|
|
8089
|
+
reason: typeof row.reason === "string" ? row.reason : null
|
|
8090
|
+
});
|
|
8091
|
+
}
|
|
8092
|
+
return out;
|
|
8093
|
+
}
|
|
8094
|
+
function defaultLookupPr(input) {
|
|
8095
|
+
try {
|
|
8096
|
+
const repo = execFileSync("git", ["config", "--get", "remote.origin.url"], {
|
|
8097
|
+
cwd: input.repoDir,
|
|
8098
|
+
encoding: "utf8",
|
|
8099
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
8100
|
+
}).trim();
|
|
8101
|
+
const repoMatch = repo.match(/github\.com[:/]([^/]+\/[^/.]+)(?:\.git)?$/i);
|
|
8102
|
+
const repoSlug = repoMatch?.[1];
|
|
8103
|
+
if (!repoSlug) return null;
|
|
8104
|
+
const raw = execFileSync(
|
|
8105
|
+
"gh",
|
|
8106
|
+
["pr", "view", String(input.prNumber), "--repo", repoSlug, "--json", "state,mergedAt,mergeCommit,url"],
|
|
8107
|
+
{ cwd: input.repoDir, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
8108
|
+
);
|
|
8109
|
+
const parsed = JSON.parse(raw);
|
|
8110
|
+
return {
|
|
8111
|
+
prUrl: normalizePrUrl3(parsed.url ?? `https://github.com/${repoSlug}/pull/${input.prNumber}`) ?? `https://github.com/${repoSlug}/pull/${input.prNumber}`,
|
|
8112
|
+
state: parsed.state ?? "",
|
|
8113
|
+
mergedAt: parsed.mergedAt ?? null,
|
|
8114
|
+
mergeCommit: parsed.mergeCommit?.oid ?? null
|
|
8115
|
+
};
|
|
8116
|
+
} catch {
|
|
8117
|
+
return null;
|
|
8118
|
+
}
|
|
8119
|
+
}
|
|
8120
|
+
function finalResultForWorker(worker) {
|
|
8121
|
+
const heartbeat = parseHeartbeat(worker.heartbeatPath);
|
|
8122
|
+
return worker.completionSnapshot?.finalResult ?? terminalFinalResultFromHeartbeat(heartbeat);
|
|
8123
|
+
}
|
|
8124
|
+
function collectMergedEvidenceByPr(run) {
|
|
8125
|
+
const byPr = /* @__PURE__ */ new Map();
|
|
8126
|
+
const runDir2 = runDirectory(run.id);
|
|
8127
|
+
for (const name of listRunWorkerNames(run)) {
|
|
8128
|
+
const worker = readJson(
|
|
8129
|
+
path32.join(runDir2, "workers", safeSlug(name), "worker.json"),
|
|
8130
|
+
void 0
|
|
8131
|
+
);
|
|
8132
|
+
if (!worker) continue;
|
|
8133
|
+
for (const evidence of extractTargetPrReconciliation(finalResultForWorker(worker))) {
|
|
8134
|
+
const n = prNumberFromUrl(evidence.prUrl);
|
|
8135
|
+
if (n != null && !byPr.has(n)) byPr.set(n, evidence);
|
|
8136
|
+
}
|
|
8137
|
+
}
|
|
8138
|
+
return byPr;
|
|
8139
|
+
}
|
|
8140
|
+
function candidatePrNumbers(worker, statusFinalResult) {
|
|
8141
|
+
const heartbeat = parseHeartbeat(worker.heartbeatPath);
|
|
8142
|
+
const text = [
|
|
8143
|
+
worker.name,
|
|
8144
|
+
worker.repairTargetPrUrl,
|
|
8145
|
+
worker.taskPrUrl,
|
|
8146
|
+
worker.branch,
|
|
8147
|
+
heartbeat.lastHeartbeatSummary,
|
|
8148
|
+
extractText(statusFinalResult)
|
|
8149
|
+
].filter(Boolean).join("\n");
|
|
8150
|
+
return extractPrNumbersFromText(text);
|
|
8151
|
+
}
|
|
8152
|
+
function isLocalOnlyAttentionNoise(worker) {
|
|
8153
|
+
return worker.localOnly === true && !worker.taskId && !worker.agentOsId;
|
|
8154
|
+
}
|
|
8155
|
+
function reconcileLocalOnlyMergedPrAttention(options = {}) {
|
|
8156
|
+
const lookupPr = options.lookupPr ?? defaultLookupPr;
|
|
8157
|
+
const outcomes = [];
|
|
8158
|
+
const liveLookupCache = /* @__PURE__ */ new Map();
|
|
8159
|
+
for (const run of listRunRecords()) {
|
|
8160
|
+
const mergedByPr = collectMergedEvidenceByPr(run);
|
|
8161
|
+
const runDir2 = runDirectory(run.id);
|
|
8162
|
+
for (const name of listRunWorkerNames(run)) {
|
|
8163
|
+
const workerPath = path32.join(runDir2, "workers", safeSlug(name), "worker.json");
|
|
8164
|
+
const worker = readJson(workerPath, void 0);
|
|
8165
|
+
if (!worker || !isLocalOnlyAttentionNoise(worker)) continue;
|
|
8166
|
+
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
8167
|
+
if (status.attention.state !== "needs_attention") continue;
|
|
8168
|
+
let merged = null;
|
|
8169
|
+
for (const prNumber of candidatePrNumbers(worker, status.finalResult)) {
|
|
8170
|
+
merged = mergedByPr.get(prNumber) ?? null;
|
|
8171
|
+
if (!merged) {
|
|
8172
|
+
const repoDir = run.repo || worker.worktreePath;
|
|
8173
|
+
const cacheKey = `${repoDir}#${prNumber}`;
|
|
8174
|
+
const live = liveLookupCache.has(cacheKey) ? liveLookupCache.get(cacheKey) ?? null : lookupPr({ repoDir, prNumber });
|
|
8175
|
+
if (!liveLookupCache.has(cacheKey)) liveLookupCache.set(cacheKey, live);
|
|
8176
|
+
if (live && (live.state === "MERGED" || live.mergedAt)) {
|
|
8177
|
+
merged = { prUrl: live.prUrl, mergeCommit: live.mergeCommit ?? null, reason: "GitHub reports PR merged" };
|
|
8178
|
+
}
|
|
8179
|
+
}
|
|
8180
|
+
if (merged) break;
|
|
8181
|
+
}
|
|
8182
|
+
if (!merged) {
|
|
8183
|
+
outcomes.push({ runId: run.id, worker: name, action: "skipped", reason: "no merged PR evidence" });
|
|
8184
|
+
continue;
|
|
8185
|
+
}
|
|
8186
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8187
|
+
worker.status = "done";
|
|
8188
|
+
worker.completionSnapshot = {
|
|
8189
|
+
prUrl: merged.prUrl,
|
|
8190
|
+
summary: `Local-only worker superseded by merged PR ${merged.prUrl}`,
|
|
8191
|
+
finalResult: {
|
|
8192
|
+
summary: `Local-only repair/salvage worker superseded by merged PR ${merged.prUrl}`,
|
|
8193
|
+
targetPrReconciliation: [
|
|
8194
|
+
{
|
|
8195
|
+
prUrl: merged.prUrl,
|
|
8196
|
+
outcome: "merged",
|
|
8197
|
+
mergeCommit: merged.mergeCommit ?? null,
|
|
8198
|
+
reason: merged.reason ?? "PR already merged; local-only worker no longer requires attention"
|
|
8199
|
+
}
|
|
8200
|
+
]
|
|
8201
|
+
}
|
|
8202
|
+
};
|
|
8203
|
+
worker.completionAckSource = "local-pr-merged-reconcile";
|
|
8204
|
+
worker.reconciledAt = now;
|
|
8205
|
+
worker.reconcileReason = "local-only needs_attention superseded by merged PR";
|
|
8206
|
+
saveWorker(run.id, worker);
|
|
8207
|
+
outcomes.push({
|
|
8208
|
+
runId: run.id,
|
|
8209
|
+
worker: name,
|
|
8210
|
+
action: "marked_done",
|
|
8211
|
+
reason: worker.reconcileReason,
|
|
8212
|
+
prUrl: merged.prUrl,
|
|
8213
|
+
mergeCommit: merged.mergeCommit ?? null
|
|
8214
|
+
});
|
|
8215
|
+
}
|
|
8216
|
+
}
|
|
8217
|
+
return { workers: outcomes };
|
|
8218
|
+
}
|
|
8219
|
+
|
|
8002
8220
|
// src/stale-reconcile.ts
|
|
8003
8221
|
var STALE_RECONCILE_HEARTBEAT_MS = 15 * 60 * 1e3;
|
|
8004
8222
|
function staleReconcileDisabled() {
|
|
@@ -8007,13 +8225,14 @@ function staleReconcileDisabled() {
|
|
|
8007
8225
|
function reconcileStaleWorkers() {
|
|
8008
8226
|
const metadataReconcile = reconcileWorkerMetadata();
|
|
8009
8227
|
if (staleReconcileDisabled()) {
|
|
8010
|
-
|
|
8228
|
+
const localPrAttentionReconcile2 = reconcileLocalOnlyMergedPrAttention();
|
|
8229
|
+
return { workers: [], finalizedRuns: finalizeStaleRuns(), metadataReconcile, localPrAttentionReconcile: localPrAttentionReconcile2 };
|
|
8011
8230
|
}
|
|
8012
8231
|
const outcomes = [];
|
|
8013
8232
|
const now = Date.now();
|
|
8014
8233
|
for (const run of listRunRecords()) {
|
|
8015
8234
|
for (const name of listRunWorkerNames(run)) {
|
|
8016
|
-
const workerPath =
|
|
8235
|
+
const workerPath = path33.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
8017
8236
|
const worker = readJson(workerPath, void 0);
|
|
8018
8237
|
if (!worker || worker.status !== "running") {
|
|
8019
8238
|
outcomes.push({
|
|
@@ -8085,7 +8304,8 @@ function reconcileStaleWorkers() {
|
|
|
8085
8304
|
});
|
|
8086
8305
|
}
|
|
8087
8306
|
}
|
|
8088
|
-
|
|
8307
|
+
const localPrAttentionReconcile = reconcileLocalOnlyMergedPrAttention();
|
|
8308
|
+
return { workers: outcomes, finalizedRuns: finalizeStaleRuns(), metadataReconcile, localPrAttentionReconcile };
|
|
8089
8309
|
}
|
|
8090
8310
|
function reconcileRunsCli() {
|
|
8091
8311
|
const result = reconcileStaleWorkers();
|
|
@@ -8096,6 +8316,10 @@ function reconcileRunsCli() {
|
|
|
8096
8316
|
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
8097
8317
|
return acc;
|
|
8098
8318
|
}, {});
|
|
8319
|
+
const localPrAttentionTotals = result.localPrAttentionReconcile.workers.reduce((acc, row) => {
|
|
8320
|
+
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
8321
|
+
return acc;
|
|
8322
|
+
}, {});
|
|
8099
8323
|
const runRetentionTotals = result.metadataReconcile.runMetadataRetention.runs.reduce((acc, row) => {
|
|
8100
8324
|
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
8101
8325
|
return acc;
|
|
@@ -8113,10 +8337,15 @@ function reconcileRunsCli() {
|
|
|
8113
8337
|
total: result.metadataReconcile.runMetadataRetention.runs.length
|
|
8114
8338
|
}
|
|
8115
8339
|
},
|
|
8340
|
+
localPrAttentionReconcile: {
|
|
8341
|
+
totals: localPrAttentionTotals,
|
|
8342
|
+
total: result.localPrAttentionReconcile.workers.length
|
|
8343
|
+
},
|
|
8116
8344
|
finalizedRuns: result.finalizedRuns.length,
|
|
8117
8345
|
details: {
|
|
8118
8346
|
workers: result.workers,
|
|
8119
8347
|
metadataReconcile: result.metadataReconcile.workers,
|
|
8348
|
+
localPrAttentionReconcile: result.localPrAttentionReconcile.workers,
|
|
8120
8349
|
runMetadataRetention: result.metadataReconcile.runMetadataRetention.runs,
|
|
8121
8350
|
finalizedRuns: result.finalizedRuns
|
|
8122
8351
|
}
|
|
@@ -8137,7 +8366,7 @@ function heartbeatByteLength(heartbeatPath) {
|
|
|
8137
8366
|
}
|
|
8138
8367
|
}
|
|
8139
8368
|
function workerEvidence(run, workerName) {
|
|
8140
|
-
const workerPath =
|
|
8369
|
+
const workerPath = path34.join(runDirectory(run.id), "workers", safeSlug(workerName), "worker.json");
|
|
8141
8370
|
const worker = readJson(workerPath, void 0);
|
|
8142
8371
|
if (!worker) {
|
|
8143
8372
|
return {
|
|
@@ -8194,7 +8423,7 @@ function aggregateRunAttention(workers) {
|
|
|
8194
8423
|
function countOpenWorkers(run) {
|
|
8195
8424
|
let open = 0;
|
|
8196
8425
|
for (const name of listRunWorkerNames(run)) {
|
|
8197
|
-
const workerPath =
|
|
8426
|
+
const workerPath = path34.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
8198
8427
|
const worker = readJson(workerPath, void 0);
|
|
8199
8428
|
if (!worker) continue;
|
|
8200
8429
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
@@ -8270,7 +8499,7 @@ function createRun(args) {
|
|
|
8270
8499
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8271
8500
|
workers: {}
|
|
8272
8501
|
};
|
|
8273
|
-
writeJson(
|
|
8502
|
+
writeJson(path35.join(dir, "run.json"), run);
|
|
8274
8503
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
8275
8504
|
}
|
|
8276
8505
|
function listRuns() {
|
|
@@ -8283,7 +8512,7 @@ function failExists(message) {
|
|
|
8283
8512
|
|
|
8284
8513
|
// src/discard-disposable.ts
|
|
8285
8514
|
import { existsSync as existsSync26, rmSync as rmSync2 } from "node:fs";
|
|
8286
|
-
import
|
|
8515
|
+
import path36 from "node:path";
|
|
8287
8516
|
function normalizeRelativePath2(value) {
|
|
8288
8517
|
const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
8289
8518
|
if (!normalized || normalized.startsWith("/") || normalized.includes("..")) {
|
|
@@ -8305,12 +8534,12 @@ function discardDisposableArtifacts(args) {
|
|
|
8305
8534
|
if (paths.length === 0) {
|
|
8306
8535
|
return { ok: false, removed: [], reason: "requires at least one --path" };
|
|
8307
8536
|
}
|
|
8308
|
-
const worktreeRoot =
|
|
8537
|
+
const worktreeRoot = path36.resolve(worker.worktreePath);
|
|
8309
8538
|
const removed = [];
|
|
8310
8539
|
for (const raw of paths) {
|
|
8311
8540
|
const rel = normalizeRelativePath2(raw);
|
|
8312
|
-
const abs =
|
|
8313
|
-
if (!abs.startsWith(worktreeRoot +
|
|
8541
|
+
const abs = path36.resolve(worktreeRoot, rel);
|
|
8542
|
+
if (!abs.startsWith(worktreeRoot + path36.sep) && abs !== worktreeRoot) {
|
|
8314
8543
|
return { ok: false, removed, reason: `path escapes worktree: ${raw}` };
|
|
8315
8544
|
}
|
|
8316
8545
|
if (!existsSync26(abs)) {
|
|
@@ -8377,7 +8606,7 @@ function validateDaemonInstallIdentity(config = loadUserConfig(), env = process.
|
|
|
8377
8606
|
// src/cron/cron-env.ts
|
|
8378
8607
|
import { existsSync as existsSync27 } from "node:fs";
|
|
8379
8608
|
import { homedir as homedir11 } from "node:os";
|
|
8380
|
-
import
|
|
8609
|
+
import path37 from "node:path";
|
|
8381
8610
|
function envFlag(name, defaultValue) {
|
|
8382
8611
|
const raw = process.env[name]?.trim().toLowerCase();
|
|
8383
8612
|
if (!raw) return defaultValue;
|
|
@@ -8393,7 +8622,7 @@ function envInt(name, fallback, min = 1) {
|
|
|
8393
8622
|
function defaultKynverCronStorePath() {
|
|
8394
8623
|
const explicit = process.env.KYNVER_CRON_STORE_PATH?.trim() || process.env.OPENCLAW_CRON_STORE_PATH?.trim();
|
|
8395
8624
|
if (explicit) return explicit;
|
|
8396
|
-
return
|
|
8625
|
+
return path37.join(homedir11(), ".kynver", "agent-os-cron.json");
|
|
8397
8626
|
}
|
|
8398
8627
|
function defaultKynverCronStatePath(storePath = defaultKynverCronStorePath()) {
|
|
8399
8628
|
const explicit = process.env.KYNVER_CRON_TICK_STATE_PATH?.trim();
|
|
@@ -8635,7 +8864,7 @@ async function loadCronJobs(storePath = defaultKynverCronStorePath()) {
|
|
|
8635
8864
|
// src/cron/cron-tick-state.ts
|
|
8636
8865
|
import { randomBytes } from "node:crypto";
|
|
8637
8866
|
import { promises as fs2 } from "node:fs";
|
|
8638
|
-
import
|
|
8867
|
+
import path38 from "node:path";
|
|
8639
8868
|
var EMPTY = { version: 1, jobs: {} };
|
|
8640
8869
|
async function readFileIfExists2(filePath) {
|
|
8641
8870
|
try {
|
|
@@ -8662,7 +8891,7 @@ async function loadCronTickState(statePath) {
|
|
|
8662
8891
|
return parseCronTickState(raw);
|
|
8663
8892
|
}
|
|
8664
8893
|
async function writeStateAtomic(statePath, state) {
|
|
8665
|
-
await fs2.mkdir(
|
|
8894
|
+
await fs2.mkdir(path38.dirname(statePath), { recursive: true });
|
|
8666
8895
|
const suffix = randomBytes(6).toString("hex");
|
|
8667
8896
|
const tmp = `${statePath}.tmp-${process.pid}-${Date.now()}-${suffix}`;
|
|
8668
8897
|
await fs2.writeFile(tmp, `${JSON.stringify(state, null, 2)}
|
|
@@ -8839,7 +9068,7 @@ async function runKynverCronTick(opts = {}) {
|
|
|
8839
9068
|
}
|
|
8840
9069
|
|
|
8841
9070
|
// src/pipeline-tick.ts
|
|
8842
|
-
import
|
|
9071
|
+
import path56 from "node:path";
|
|
8843
9072
|
|
|
8844
9073
|
// src/pipeline-dispatch.ts
|
|
8845
9074
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -8995,7 +9224,7 @@ function buildBoxResourceSnapshotFromGate(gate, input = {}) {
|
|
|
8995
9224
|
}
|
|
8996
9225
|
|
|
8997
9226
|
// src/plan-progress-daemon-sync.ts
|
|
8998
|
-
import
|
|
9227
|
+
import path39 from "node:path";
|
|
8999
9228
|
|
|
9000
9229
|
// src/plan-progress-sync.ts
|
|
9001
9230
|
async function syncPlanProgress(args) {
|
|
@@ -9019,7 +9248,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
9019
9248
|
const outcomes = [];
|
|
9020
9249
|
for (const name of Object.keys(run.workers || {})) {
|
|
9021
9250
|
const worker = readJson(
|
|
9022
|
-
|
|
9251
|
+
path39.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
9023
9252
|
void 0
|
|
9024
9253
|
);
|
|
9025
9254
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -9076,10 +9305,10 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
9076
9305
|
}
|
|
9077
9306
|
|
|
9078
9307
|
// src/cleanup.ts
|
|
9079
|
-
import
|
|
9308
|
+
import path53 from "node:path";
|
|
9080
9309
|
|
|
9081
9310
|
// src/cleanup-guards.ts
|
|
9082
|
-
import
|
|
9311
|
+
import path40 from "node:path";
|
|
9083
9312
|
|
|
9084
9313
|
// src/cleanup-build-cache-paths.ts
|
|
9085
9314
|
var HARNESS_BUILD_CACHE_RELATIVE_PATHS = [
|
|
@@ -9220,7 +9449,7 @@ function skipWorktreeRemoval(input) {
|
|
|
9220
9449
|
function skipDependencyCacheRemoval(input) {
|
|
9221
9450
|
const { indexed, nodeModulesAgeMs, ageMs, worktreePath, activeWorktreePaths, diskPressure } = input;
|
|
9222
9451
|
if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
|
|
9223
|
-
if (activeWorktreePaths.has(
|
|
9452
|
+
if (activeWorktreePaths.has(path40.resolve(worktreePath))) return "active_worker";
|
|
9224
9453
|
if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
|
|
9225
9454
|
if (indexed && indexedWorktreeHasMaterialChanges(indexed)) return "dirty_worktree";
|
|
9226
9455
|
return null;
|
|
@@ -9247,11 +9476,11 @@ var LIVE_SKIP_REASONS = /* @__PURE__ */ new Set([
|
|
|
9247
9476
|
function collectPreservedLivePaths(actions, skips) {
|
|
9248
9477
|
const out = [];
|
|
9249
9478
|
const seen = /* @__PURE__ */ new Set();
|
|
9250
|
-
const push = (
|
|
9251
|
-
const key = `${
|
|
9479
|
+
const push = (path71, reason, detail) => {
|
|
9480
|
+
const key = `${path71}\0${reason}`;
|
|
9252
9481
|
if (seen.has(key) || out.length >= MAX_PRESERVED_LIVE_PATH_SAMPLES) return;
|
|
9253
9482
|
seen.add(key);
|
|
9254
|
-
out.push({ path:
|
|
9483
|
+
out.push({ path: path71, reason, ...detail ? { detail } : {} });
|
|
9255
9484
|
};
|
|
9256
9485
|
for (const skip2 of skips) {
|
|
9257
9486
|
if (!LIVE_SKIP_REASONS.has(skip2.reason)) continue;
|
|
@@ -9267,11 +9496,11 @@ function collectPreservedLivePaths(actions, skips) {
|
|
|
9267
9496
|
|
|
9268
9497
|
// src/cleanup-run-directory.ts
|
|
9269
9498
|
import { existsSync as existsSync30, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
|
|
9270
|
-
import
|
|
9499
|
+
import path42 from "node:path";
|
|
9271
9500
|
|
|
9272
9501
|
// src/cleanup-active-worktrees.ts
|
|
9273
9502
|
import { existsSync as existsSync29, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
|
|
9274
|
-
import
|
|
9503
|
+
import path41 from "node:path";
|
|
9275
9504
|
function workerHasRecentHarnessActivity(worker, now) {
|
|
9276
9505
|
const paths = [worker.heartbeatPath, worker.stdoutPath, worker.stderrPath];
|
|
9277
9506
|
for (const target of paths) {
|
|
@@ -9297,11 +9526,11 @@ function collectActiveWorktreeGuards(harnessRoots, now = Date.now()) {
|
|
|
9297
9526
|
let runHasLive = false;
|
|
9298
9527
|
for (const name of Object.keys(run.workers || {})) {
|
|
9299
9528
|
const worker = readJson(
|
|
9300
|
-
|
|
9529
|
+
path41.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
|
|
9301
9530
|
void 0
|
|
9302
9531
|
);
|
|
9303
9532
|
if (!worker?.worktreePath) continue;
|
|
9304
|
-
const worktreePath =
|
|
9533
|
+
const worktreePath = path41.resolve(worker.worktreePath);
|
|
9305
9534
|
if (!isActiveHarnessWorker2(worker, now)) continue;
|
|
9306
9535
|
runHasLive = true;
|
|
9307
9536
|
activeWorktreePaths.add(worktreePath);
|
|
@@ -9329,7 +9558,7 @@ function pathAgeMs(target, now) {
|
|
|
9329
9558
|
}
|
|
9330
9559
|
}
|
|
9331
9560
|
function loadRunStatus(harnessRoot, runId) {
|
|
9332
|
-
const runPath =
|
|
9561
|
+
const runPath = path42.join(harnessRoot, "runs", runId, "run.json");
|
|
9333
9562
|
if (!existsSync30(runPath)) return null;
|
|
9334
9563
|
return readJson(runPath, null);
|
|
9335
9564
|
}
|
|
@@ -9365,7 +9594,7 @@ function scanStaleRunDirectoryCandidates(opts) {
|
|
|
9365
9594
|
if (!runEntry.isDirectory()) continue;
|
|
9366
9595
|
const runId = runEntry.name;
|
|
9367
9596
|
if (opts.runIdFilter && runId !== opts.runIdFilter) continue;
|
|
9368
|
-
const runPath =
|
|
9597
|
+
const runPath = path42.join(opts.worktreesDir, runId);
|
|
9369
9598
|
if (!runDirectoryIsEmpty(runPath)) continue;
|
|
9370
9599
|
candidates.push({
|
|
9371
9600
|
kind: "remove_run_directory",
|
|
@@ -9383,14 +9612,14 @@ function scanStaleRunDirectoryCandidates(opts) {
|
|
|
9383
9612
|
import { existsSync as existsSync33, rmSync as rmSync4 } from "node:fs";
|
|
9384
9613
|
|
|
9385
9614
|
// src/cleanup-dir-size.ts
|
|
9386
|
-
import { execFileSync } from "node:child_process";
|
|
9615
|
+
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
9387
9616
|
import { existsSync as existsSync31, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
|
|
9388
|
-
import
|
|
9617
|
+
import path43 from "node:path";
|
|
9389
9618
|
var DEFAULT_DU_TIMEOUT_MS = 2500;
|
|
9390
9619
|
function directorySizeBytesDu(root, timeoutMs = DEFAULT_DU_TIMEOUT_MS) {
|
|
9391
9620
|
if (!existsSync31(root)) return 0;
|
|
9392
9621
|
try {
|
|
9393
|
-
const out =
|
|
9622
|
+
const out = execFileSync2("du", ["-sb", root], {
|
|
9394
9623
|
encoding: "utf8",
|
|
9395
9624
|
timeout: timeoutMs,
|
|
9396
9625
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -9419,7 +9648,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
9419
9648
|
}
|
|
9420
9649
|
for (const name of entries) {
|
|
9421
9650
|
if (seen++ > maxEntries) return null;
|
|
9422
|
-
const full =
|
|
9651
|
+
const full = path43.join(current, name);
|
|
9423
9652
|
let st;
|
|
9424
9653
|
try {
|
|
9425
9654
|
st = statSync8(full);
|
|
@@ -9470,20 +9699,20 @@ function pathHasForeignOwnedEntry(targetPath, maxEntries = 32) {
|
|
|
9470
9699
|
|
|
9471
9700
|
// src/cleanup-privileged-remove.ts
|
|
9472
9701
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
9473
|
-
import
|
|
9702
|
+
import path45 from "node:path";
|
|
9474
9703
|
|
|
9475
9704
|
// src/cleanup-harness-path-validate.ts
|
|
9476
|
-
import
|
|
9705
|
+
import path44 from "node:path";
|
|
9477
9706
|
function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
|
|
9478
|
-
const resolved =
|
|
9479
|
-
const suffix = `${
|
|
9707
|
+
const resolved = path44.resolve(targetPath);
|
|
9708
|
+
const suffix = `${path44.sep}${cacheDirName}`;
|
|
9480
9709
|
const cachePath = resolved.endsWith(suffix) ? resolved : null;
|
|
9481
9710
|
if (!cachePath) return "path_outside_harness";
|
|
9482
|
-
const rel =
|
|
9483
|
-
if (rel.startsWith("..") ||
|
|
9484
|
-
const parts = rel.split(
|
|
9711
|
+
const rel = path44.relative(worktreesDir, cachePath);
|
|
9712
|
+
if (rel.startsWith("..") || path44.isAbsolute(rel)) return "path_outside_harness";
|
|
9713
|
+
const parts = rel.split(path44.sep);
|
|
9485
9714
|
if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
|
|
9486
|
-
if (!resolved.startsWith(
|
|
9715
|
+
if (!resolved.startsWith(path44.resolve(harnessRoot))) return "path_outside_harness";
|
|
9487
9716
|
return null;
|
|
9488
9717
|
}
|
|
9489
9718
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
@@ -9493,16 +9722,16 @@ function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
|
9493
9722
|
return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
|
|
9494
9723
|
}
|
|
9495
9724
|
function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
9496
|
-
const resolved =
|
|
9497
|
-
const relToWt =
|
|
9498
|
-
if (relToWt.startsWith("..") ||
|
|
9499
|
-
const parts = relToWt.split(
|
|
9725
|
+
const resolved = path44.resolve(targetPath);
|
|
9726
|
+
const relToWt = path44.relative(worktreesDir, resolved);
|
|
9727
|
+
if (relToWt.startsWith("..") || path44.isAbsolute(relToWt)) return "path_outside_harness";
|
|
9728
|
+
const parts = relToWt.split(path44.sep);
|
|
9500
9729
|
if (parts.length < 3) return "path_outside_harness";
|
|
9501
|
-
if (!resolved.startsWith(
|
|
9730
|
+
if (!resolved.startsWith(path44.resolve(harnessRoot))) return "path_outside_harness";
|
|
9502
9731
|
return null;
|
|
9503
9732
|
}
|
|
9504
9733
|
function isHarnessGeneratedCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
9505
|
-
const resolved =
|
|
9734
|
+
const resolved = path44.resolve(targetPath);
|
|
9506
9735
|
return isHarnessNodeModulesPath(resolved, harnessRoot, worktreesDir) === null || isHarnessNextCachePath(resolved, harnessRoot, worktreesDir) === null || isHarnessBuildCachePath(resolved, harnessRoot, worktreesDir) === null;
|
|
9507
9736
|
}
|
|
9508
9737
|
|
|
@@ -9536,12 +9765,12 @@ function tryPrivilegedReclaimHarnessCache(targetPath, harnessRoot, worktreesDir)
|
|
|
9536
9765
|
"chown",
|
|
9537
9766
|
"-R",
|
|
9538
9767
|
`${effectiveUid}:${effectiveGid}`,
|
|
9539
|
-
|
|
9768
|
+
path45.resolve(targetPath)
|
|
9540
9769
|
]);
|
|
9541
9770
|
if (chown.ok) {
|
|
9542
9771
|
return { ok: true, method: "chown_then_rm" };
|
|
9543
9772
|
}
|
|
9544
|
-
const rm = runSudoNonInteractive(["rm", "-rf",
|
|
9773
|
+
const rm = runSudoNonInteractive(["rm", "-rf", path45.resolve(targetPath)]);
|
|
9545
9774
|
if (rm.ok) {
|
|
9546
9775
|
return { ok: true, method: "sudo_rm" };
|
|
9547
9776
|
}
|
|
@@ -9743,7 +9972,7 @@ function removeWorktree(candidate, execute) {
|
|
|
9743
9972
|
|
|
9744
9973
|
// src/cleanup-scan.ts
|
|
9745
9974
|
import { existsSync as existsSync34, readdirSync as readdirSync12, statSync as statSync9 } from "node:fs";
|
|
9746
|
-
import
|
|
9975
|
+
import path46 from "node:path";
|
|
9747
9976
|
function pathAgeMs2(target, now) {
|
|
9748
9977
|
try {
|
|
9749
9978
|
const mtime = statSync9(target).mtimeMs;
|
|
@@ -9753,16 +9982,16 @@ function pathAgeMs2(target, now) {
|
|
|
9753
9982
|
}
|
|
9754
9983
|
}
|
|
9755
9984
|
function isPathInside(child, parent) {
|
|
9756
|
-
const rel =
|
|
9757
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
9985
|
+
const rel = path46.relative(parent, child);
|
|
9986
|
+
return rel === "" || !rel.startsWith("..") && !path46.isAbsolute(rel);
|
|
9758
9987
|
}
|
|
9759
9988
|
function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
|
|
9760
9989
|
const out = [];
|
|
9761
9990
|
for (const rel of HARNESS_BUILD_CACHE_RELATIVE_PATHS) {
|
|
9762
9991
|
if (rel === ".next") continue;
|
|
9763
|
-
const target =
|
|
9992
|
+
const target = path46.join(worktreePath, rel);
|
|
9764
9993
|
if (!existsSync34(target)) continue;
|
|
9765
|
-
const resolved =
|
|
9994
|
+
const resolved = path46.resolve(target);
|
|
9766
9995
|
if (seen.has(resolved)) continue;
|
|
9767
9996
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
9768
9997
|
seen.add(resolved);
|
|
@@ -9794,10 +10023,10 @@ function scanBuildCacheCandidates(opts) {
|
|
|
9794
10023
|
if (!opts.includeOrphans || !existsSync34(opts.worktreesDir)) return candidates;
|
|
9795
10024
|
for (const runEntry of readdirSync12(opts.worktreesDir, { withFileTypes: true })) {
|
|
9796
10025
|
if (!runEntry.isDirectory()) continue;
|
|
9797
|
-
const runPath =
|
|
10026
|
+
const runPath = path46.join(opts.worktreesDir, runEntry.name);
|
|
9798
10027
|
for (const workerEntry of readdirSync12(runPath, { withFileTypes: true })) {
|
|
9799
10028
|
if (!workerEntry.isDirectory()) continue;
|
|
9800
|
-
const worktreePath =
|
|
10029
|
+
const worktreePath = path46.join(runPath, workerEntry.name);
|
|
9801
10030
|
candidates.push(
|
|
9802
10031
|
...collectBuildCacheForWorktree(worktreePath, opts, seen, {
|
|
9803
10032
|
runId: runEntry.name,
|
|
@@ -9835,12 +10064,12 @@ function scanWorktreeCandidates(opts) {
|
|
|
9835
10064
|
if (!orphanEnabled || !existsSync34(opts.worktreesDir)) return candidates;
|
|
9836
10065
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
9837
10066
|
for (const entry of opts.index.values()) {
|
|
9838
|
-
indexedPaths.add(
|
|
10067
|
+
indexedPaths.add(path46.resolve(entry.worktreePath));
|
|
9839
10068
|
}
|
|
9840
10069
|
for (const runEntry of readdirSync12(opts.worktreesDir, { withFileTypes: true })) {
|
|
9841
10070
|
if (!runEntry.isDirectory()) continue;
|
|
9842
10071
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
9843
|
-
const runPath =
|
|
10072
|
+
const runPath = path46.join(opts.worktreesDir, runEntry.name);
|
|
9844
10073
|
let workerEntries;
|
|
9845
10074
|
try {
|
|
9846
10075
|
workerEntries = readdirSync12(runPath, { withFileTypes: true });
|
|
@@ -9849,7 +10078,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
9849
10078
|
}
|
|
9850
10079
|
for (const workerEntry of workerEntries) {
|
|
9851
10080
|
if (!workerEntry.isDirectory()) continue;
|
|
9852
|
-
const worktreePath =
|
|
10081
|
+
const worktreePath = path46.resolve(path46.join(runPath, workerEntry.name));
|
|
9853
10082
|
if (seen.has(worktreePath)) continue;
|
|
9854
10083
|
if (indexedPaths.has(worktreePath)) continue;
|
|
9855
10084
|
if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
|
|
@@ -9869,7 +10098,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
9869
10098
|
|
|
9870
10099
|
// src/cleanup-dependency-scan.ts
|
|
9871
10100
|
import { existsSync as existsSync35, readdirSync as readdirSync13, statSync as statSync10 } from "node:fs";
|
|
9872
|
-
import
|
|
10101
|
+
import path47 from "node:path";
|
|
9873
10102
|
var DEPENDENCY_CACHE_DIRS = [
|
|
9874
10103
|
{ dirName: "node_modules", kind: "remove_node_modules" },
|
|
9875
10104
|
{ dirName: ".next", kind: "remove_next_cache" }
|
|
@@ -9883,12 +10112,12 @@ function pathAgeMs3(target, now) {
|
|
|
9883
10112
|
}
|
|
9884
10113
|
}
|
|
9885
10114
|
function isPathInside2(child, parent) {
|
|
9886
|
-
const rel =
|
|
9887
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
10115
|
+
const rel = path47.relative(parent, child);
|
|
10116
|
+
return rel === "" || !rel.startsWith("..") && !path47.isAbsolute(rel);
|
|
9888
10117
|
}
|
|
9889
10118
|
function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
9890
10119
|
if (!existsSync35(targetPath)) return;
|
|
9891
|
-
const resolved =
|
|
10120
|
+
const resolved = path47.resolve(targetPath);
|
|
9892
10121
|
if (seen.has(resolved)) return;
|
|
9893
10122
|
if (!isPathInside2(resolved, opts.harnessRoot)) return;
|
|
9894
10123
|
seen.add(resolved);
|
|
@@ -9905,7 +10134,7 @@ function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
|
9905
10134
|
}
|
|
9906
10135
|
function scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, meta) {
|
|
9907
10136
|
for (const entry of DEPENDENCY_CACHE_DIRS) {
|
|
9908
|
-
pushCandidate2(candidates, seen, opts,
|
|
10137
|
+
pushCandidate2(candidates, seen, opts, path47.join(worktreePath, entry.dirName), entry.kind, meta);
|
|
9909
10138
|
}
|
|
9910
10139
|
}
|
|
9911
10140
|
function scanDependencyCacheCandidates(opts) {
|
|
@@ -9923,7 +10152,7 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
9923
10152
|
for (const runEntry of readdirSync13(opts.worktreesDir, { withFileTypes: true })) {
|
|
9924
10153
|
if (!runEntry.isDirectory()) continue;
|
|
9925
10154
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
9926
|
-
const runPath =
|
|
10155
|
+
const runPath = path47.join(opts.worktreesDir, runEntry.name);
|
|
9927
10156
|
let workerEntries;
|
|
9928
10157
|
try {
|
|
9929
10158
|
workerEntries = readdirSync13(runPath, { withFileTypes: true });
|
|
@@ -9932,7 +10161,7 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
9932
10161
|
}
|
|
9933
10162
|
for (const workerEntry of workerEntries) {
|
|
9934
10163
|
if (!workerEntry.isDirectory()) continue;
|
|
9935
|
-
const worktreePath =
|
|
10164
|
+
const worktreePath = path47.join(runPath, workerEntry.name);
|
|
9936
10165
|
scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, {
|
|
9937
10166
|
runId: runEntry.name,
|
|
9938
10167
|
worker: workerEntry.name
|
|
@@ -9944,7 +10173,7 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
9944
10173
|
|
|
9945
10174
|
// src/cleanup-duplicate-worktrees.ts
|
|
9946
10175
|
import { existsSync as existsSync36, statSync as statSync11 } from "node:fs";
|
|
9947
|
-
import
|
|
10176
|
+
import path48 from "node:path";
|
|
9948
10177
|
function pathAgeMs4(target, now) {
|
|
9949
10178
|
try {
|
|
9950
10179
|
const mtime = statSync11(target).mtimeMs;
|
|
@@ -9974,8 +10203,8 @@ function parseWorktreePorcelain(output) {
|
|
|
9974
10203
|
return records;
|
|
9975
10204
|
}
|
|
9976
10205
|
function isUnderWorktreesDir(worktreePath, worktreesDir) {
|
|
9977
|
-
const rel =
|
|
9978
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
10206
|
+
const rel = path48.relative(path48.resolve(worktreesDir), path48.resolve(worktreePath));
|
|
10207
|
+
return rel !== "" && !rel.startsWith("..") && !path48.isAbsolute(rel);
|
|
9979
10208
|
}
|
|
9980
10209
|
function isCleanWorktree(worktreePath, repoRoot) {
|
|
9981
10210
|
try {
|
|
@@ -9991,11 +10220,11 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
9991
10220
|
if (!opts.includeOrphans || !existsSync36(opts.worktreesDir)) return [];
|
|
9992
10221
|
const repos = /* @__PURE__ */ new Set();
|
|
9993
10222
|
for (const entry of opts.index.values()) {
|
|
9994
|
-
if (entry.run.repo) repos.add(
|
|
10223
|
+
if (entry.run.repo) repos.add(path48.resolve(entry.run.repo));
|
|
9995
10224
|
}
|
|
9996
10225
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
9997
10226
|
for (const entry of opts.index.values()) {
|
|
9998
|
-
indexedPaths.add(
|
|
10227
|
+
indexedPaths.add(path48.resolve(entry.worktreePath));
|
|
9999
10228
|
}
|
|
10000
10229
|
const candidates = [];
|
|
10001
10230
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -10008,15 +10237,15 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
10008
10237
|
}
|
|
10009
10238
|
const worktrees = parseWorktreePorcelain(porcelain);
|
|
10010
10239
|
for (const wt of worktrees) {
|
|
10011
|
-
const resolved =
|
|
10012
|
-
if (resolved ===
|
|
10240
|
+
const resolved = path48.resolve(wt.path);
|
|
10241
|
+
if (resolved === path48.resolve(repoRoot)) continue;
|
|
10013
10242
|
if (!isUnderWorktreesDir(resolved, opts.worktreesDir)) continue;
|
|
10014
10243
|
if (indexedPaths.has(resolved)) continue;
|
|
10015
10244
|
if (seen.has(resolved)) continue;
|
|
10016
10245
|
if (!existsSync36(resolved)) continue;
|
|
10017
10246
|
if (!isCleanWorktree(resolved, repoRoot)) continue;
|
|
10018
|
-
const rel =
|
|
10019
|
-
const parts = rel.split(
|
|
10247
|
+
const rel = path48.relative(opts.worktreesDir, resolved);
|
|
10248
|
+
const parts = rel.split(path48.sep);
|
|
10020
10249
|
const runId = parts[0];
|
|
10021
10250
|
const worker = parts[1] ?? "unknown";
|
|
10022
10251
|
seen.add(resolved);
|
|
@@ -10035,12 +10264,12 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
10035
10264
|
}
|
|
10036
10265
|
|
|
10037
10266
|
// src/cleanup-worktree-index.ts
|
|
10038
|
-
import
|
|
10267
|
+
import path49 from "node:path";
|
|
10039
10268
|
function buildWorktreeIndexAt(harnessRoot) {
|
|
10040
10269
|
const index = /* @__PURE__ */ new Map();
|
|
10041
10270
|
for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
|
|
10042
10271
|
for (const name of Object.keys(run.workers || {})) {
|
|
10043
|
-
const workerPath =
|
|
10272
|
+
const workerPath = path49.join(
|
|
10044
10273
|
runDirectoryAt(harnessRoot, run.id),
|
|
10045
10274
|
"workers",
|
|
10046
10275
|
safeSlug(name),
|
|
@@ -10048,9 +10277,9 @@ function buildWorktreeIndexAt(harnessRoot) {
|
|
|
10048
10277
|
);
|
|
10049
10278
|
const worker = readJson(workerPath, void 0);
|
|
10050
10279
|
if (!worker?.worktreePath) continue;
|
|
10051
|
-
index.set(
|
|
10280
|
+
index.set(path49.resolve(worker.worktreePath), {
|
|
10052
10281
|
harnessRoot,
|
|
10053
|
-
worktreePath:
|
|
10282
|
+
worktreePath: path49.resolve(worker.worktreePath),
|
|
10054
10283
|
runId: run.id,
|
|
10055
10284
|
workerName: name,
|
|
10056
10285
|
run,
|
|
@@ -10120,14 +10349,14 @@ function resolvePipelineHarnessRetention(runId) {
|
|
|
10120
10349
|
|
|
10121
10350
|
// src/cleanup-orphan-safety.ts
|
|
10122
10351
|
import { existsSync as existsSync37, statSync as statSync12 } from "node:fs";
|
|
10123
|
-
import
|
|
10352
|
+
import path50 from "node:path";
|
|
10124
10353
|
var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
|
|
10125
10354
|
function assessOrphanWorktreeSafety(input) {
|
|
10126
10355
|
const now = input.now ?? Date.now();
|
|
10127
10356
|
const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
|
|
10128
10357
|
if (!existsSync37(input.worktreePath)) return null;
|
|
10129
10358
|
if (input.runId && input.workerName) {
|
|
10130
|
-
const heartbeatPath =
|
|
10359
|
+
const heartbeatPath = path50.join(
|
|
10131
10360
|
input.harnessRoot,
|
|
10132
10361
|
"runs",
|
|
10133
10362
|
input.runId,
|
|
@@ -10141,7 +10370,7 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
10141
10370
|
} catch {
|
|
10142
10371
|
}
|
|
10143
10372
|
}
|
|
10144
|
-
const gitDir =
|
|
10373
|
+
const gitDir = path50.join(input.worktreePath, ".git");
|
|
10145
10374
|
if (!existsSync37(gitDir)) return null;
|
|
10146
10375
|
const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
|
|
10147
10376
|
if (porcelain.status !== 0) return "pr_or_unmerged_commits";
|
|
@@ -10172,7 +10401,7 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
10172
10401
|
|
|
10173
10402
|
// src/harness-storage-snapshot.ts
|
|
10174
10403
|
import { existsSync as existsSync38, readdirSync as readdirSync14, statSync as statSync13 } from "node:fs";
|
|
10175
|
-
import
|
|
10404
|
+
import path51 from "node:path";
|
|
10176
10405
|
function harnessStorageSnapshot(opts = {}) {
|
|
10177
10406
|
const harnessRoot = normalizeHarnessRoot(opts.harnessRoot ?? resolveHarnessRoot());
|
|
10178
10407
|
const worktreesDir = harnessWorktreesDir(harnessRoot);
|
|
@@ -10210,7 +10439,7 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
10210
10439
|
for (const runEntry of entries) {
|
|
10211
10440
|
if (!runEntry.isDirectory()) continue;
|
|
10212
10441
|
runCount += 1;
|
|
10213
|
-
const runPath =
|
|
10442
|
+
const runPath = path51.join(worktreesDir, runEntry.name);
|
|
10214
10443
|
try {
|
|
10215
10444
|
const st = statSync13(runPath);
|
|
10216
10445
|
oldestMs = oldestMs === null ? st.mtimeMs : Math.min(oldestMs, st.mtimeMs);
|
|
@@ -10247,10 +10476,10 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
10247
10476
|
// src/cleanup-harness-roots.ts
|
|
10248
10477
|
import { existsSync as existsSync39 } from "node:fs";
|
|
10249
10478
|
import { homedir as homedir12 } from "node:os";
|
|
10250
|
-
import
|
|
10479
|
+
import path52 from "node:path";
|
|
10251
10480
|
var WELL_KNOWN_HARNESS_SCAN_ROOTS = [
|
|
10252
10481
|
"/var/tmp/kynver-harness",
|
|
10253
|
-
|
|
10482
|
+
path52.join(homedir12(), ".openclaw", "harness")
|
|
10254
10483
|
];
|
|
10255
10484
|
function addRoot(seen, roots, candidate) {
|
|
10256
10485
|
if (!candidate?.trim()) return;
|
|
@@ -10272,7 +10501,7 @@ function resolveHarnessScanRoots(options = {}) {
|
|
|
10272
10501
|
for (const candidate of extra ?? []) addRoot(seen, roots, candidate);
|
|
10273
10502
|
if (shouldScanWellKnownRoots(options)) {
|
|
10274
10503
|
for (const candidate of WELL_KNOWN_HARNESS_SCAN_ROOTS) {
|
|
10275
|
-
const resolved =
|
|
10504
|
+
const resolved = path52.resolve(candidate);
|
|
10276
10505
|
if (!seen.has(resolved) && existsSync39(resolved)) addRoot(seen, roots, resolved);
|
|
10277
10506
|
}
|
|
10278
10507
|
}
|
|
@@ -10427,9 +10656,9 @@ function mergeWorktreeIndexes(scanRoots) {
|
|
|
10427
10656
|
}
|
|
10428
10657
|
function worktreePathForCandidate(candidate, worktreesDir) {
|
|
10429
10658
|
if (candidate.runId && candidate.worker) {
|
|
10430
|
-
return
|
|
10659
|
+
return path53.join(worktreesDir, candidate.runId, candidate.worker);
|
|
10431
10660
|
}
|
|
10432
|
-
return
|
|
10661
|
+
return path53.resolve(candidate.path, "..");
|
|
10433
10662
|
}
|
|
10434
10663
|
function runHarnessCleanup(options = {}) {
|
|
10435
10664
|
let retention = resolveHarnessRetention(options);
|
|
@@ -10454,7 +10683,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10454
10683
|
for (const harnessRoot of paths.scanRoots) {
|
|
10455
10684
|
if (atSweepCap()) break;
|
|
10456
10685
|
emitCleanupProgress("root", harnessRoot);
|
|
10457
|
-
const worktreesDir =
|
|
10686
|
+
const worktreesDir = path53.join(harnessRoot, "worktrees");
|
|
10458
10687
|
const scanOpts = {
|
|
10459
10688
|
harnessRoot,
|
|
10460
10689
|
worktreesDir,
|
|
@@ -10467,7 +10696,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10467
10696
|
};
|
|
10468
10697
|
for (const raw of scanDependencyCacheCandidates(scanOpts)) {
|
|
10469
10698
|
if (atSweepCap()) break;
|
|
10470
|
-
const resolved =
|
|
10699
|
+
const resolved = path53.resolve(raw.path);
|
|
10471
10700
|
if (processedPaths.has(resolved)) continue;
|
|
10472
10701
|
processedPaths.add(resolved);
|
|
10473
10702
|
const candidate = { ...raw, path: resolved };
|
|
@@ -10478,7 +10707,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10478
10707
|
continue;
|
|
10479
10708
|
}
|
|
10480
10709
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
10481
|
-
const indexed = index.get(
|
|
10710
|
+
const indexed = index.get(path53.resolve(worktreePath)) ?? null;
|
|
10482
10711
|
const guardReason = skipDependencyCacheRemoval({
|
|
10483
10712
|
indexed,
|
|
10484
10713
|
includeOrphans: true,
|
|
@@ -10502,7 +10731,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10502
10731
|
}
|
|
10503
10732
|
for (const raw of scanBuildCacheCandidates(scanOpts)) {
|
|
10504
10733
|
if (atSweepCap()) break;
|
|
10505
|
-
const resolved =
|
|
10734
|
+
const resolved = path53.resolve(raw.path);
|
|
10506
10735
|
if (processedPaths.has(resolved)) continue;
|
|
10507
10736
|
processedPaths.add(resolved);
|
|
10508
10737
|
const candidate = { ...raw, path: resolved };
|
|
@@ -10513,7 +10742,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10513
10742
|
continue;
|
|
10514
10743
|
}
|
|
10515
10744
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
10516
|
-
const indexed = index.get(
|
|
10745
|
+
const indexed = index.get(path53.resolve(worktreePath)) ?? null;
|
|
10517
10746
|
const guardReason = skipBuildCacheRemoval({
|
|
10518
10747
|
indexed,
|
|
10519
10748
|
includeOrphans: true,
|
|
@@ -10543,11 +10772,11 @@ function runHarnessCleanup(options = {}) {
|
|
|
10543
10772
|
const worktreeSeen = /* @__PURE__ */ new Set();
|
|
10544
10773
|
for (const raw of worktreeCandidates) {
|
|
10545
10774
|
if (atSweepCap()) break;
|
|
10546
|
-
const resolved =
|
|
10775
|
+
const resolved = path53.resolve(raw.path);
|
|
10547
10776
|
if (worktreeSeen.has(resolved)) continue;
|
|
10548
10777
|
worktreeSeen.add(resolved);
|
|
10549
10778
|
const candidate = { ...raw, path: resolved };
|
|
10550
|
-
const indexed = index.get(
|
|
10779
|
+
const indexed = index.get(path53.resolve(candidate.path)) ?? null;
|
|
10551
10780
|
const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
|
|
10552
10781
|
worktreePath: candidate.path,
|
|
10553
10782
|
harnessRoot,
|
|
@@ -10557,7 +10786,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10557
10786
|
});
|
|
10558
10787
|
const guardSkip = skipWorktreeRemoval({
|
|
10559
10788
|
indexed,
|
|
10560
|
-
worktreePath:
|
|
10789
|
+
worktreePath: path53.resolve(candidate.path),
|
|
10561
10790
|
includeOrphans: retention.includeOrphans,
|
|
10562
10791
|
worktreesAgeMs: retention.worktreesAgeMs,
|
|
10563
10792
|
terminalWorktreesAgeMs: retention.terminalWorktreesAgeMs,
|
|
@@ -10589,11 +10818,11 @@ function runHarnessCleanup(options = {}) {
|
|
|
10589
10818
|
now: paths.now
|
|
10590
10819
|
})) {
|
|
10591
10820
|
if (atSweepCap()) break;
|
|
10592
|
-
const resolved =
|
|
10821
|
+
const resolved = path53.resolve(raw.path);
|
|
10593
10822
|
if (processedPaths.has(resolved)) continue;
|
|
10594
10823
|
processedPaths.add(resolved);
|
|
10595
10824
|
const candidate = { ...raw, path: resolved };
|
|
10596
|
-
const runId = candidate.runId ??
|
|
10825
|
+
const runId = candidate.runId ?? path53.basename(resolved);
|
|
10597
10826
|
const dirSkip = skipRunDirectoryRemoval({
|
|
10598
10827
|
harnessRoot,
|
|
10599
10828
|
runId,
|
|
@@ -10729,11 +10958,11 @@ function isPipelineCleanupEnabled() {
|
|
|
10729
10958
|
// src/installed-package-versions.ts
|
|
10730
10959
|
import { readFile } from "node:fs/promises";
|
|
10731
10960
|
import { homedir as homedir13 } from "node:os";
|
|
10732
|
-
import
|
|
10961
|
+
import path55 from "node:path";
|
|
10733
10962
|
|
|
10734
10963
|
// src/memory-cost-package-version-guard.ts
|
|
10735
10964
|
import { existsSync as existsSync40, readFileSync as readFileSync13 } from "node:fs";
|
|
10736
|
-
import
|
|
10965
|
+
import path54 from "node:path";
|
|
10737
10966
|
var MEMORY_COST_PACKAGE_MIN_VERSIONS = {
|
|
10738
10967
|
"@kynver-app/runtime": "0.1.83",
|
|
10739
10968
|
"@kynver-app/openclaw-agent-os": "0.1.43",
|
|
@@ -10795,8 +11024,8 @@ function resolveRepoRoot(cwd, explicitRepoRoot) {
|
|
|
10795
11024
|
(value) => Boolean(value?.trim())
|
|
10796
11025
|
);
|
|
10797
11026
|
for (const candidate of candidates) {
|
|
10798
|
-
const resolved =
|
|
10799
|
-
if (existsSync40(
|
|
11027
|
+
const resolved = path54.resolve(candidate);
|
|
11028
|
+
if (existsSync40(path54.join(resolved, "packages/kynver-runtime/package.json")) && existsSync40(path54.join(resolved, "package.json"))) {
|
|
10800
11029
|
return resolved;
|
|
10801
11030
|
}
|
|
10802
11031
|
}
|
|
@@ -10809,7 +11038,7 @@ function probeRepoPackageVersions(input = {}) {
|
|
|
10809
11038
|
if (!repoRoot) return {};
|
|
10810
11039
|
const out = {};
|
|
10811
11040
|
for (const packageName of MEMORY_COST_MANAGED_PACKAGES) {
|
|
10812
|
-
const packageJsonPath =
|
|
11041
|
+
const packageJsonPath = path54.join(repoRoot, REPO_PACKAGE_JSON_RELATIVE[packageName]);
|
|
10813
11042
|
const version = readPackageJsonVersion(packageJsonPath);
|
|
10814
11043
|
if (!version) continue;
|
|
10815
11044
|
out[packageName] = { version, source: "repo", path: packageJsonPath };
|
|
@@ -10929,12 +11158,12 @@ function unique(values) {
|
|
|
10929
11158
|
}
|
|
10930
11159
|
function moduleRoots() {
|
|
10931
11160
|
const home = homedir13();
|
|
10932
|
-
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ??
|
|
10933
|
-
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ?
|
|
11161
|
+
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path55.join(home, ".openclaw", "npm");
|
|
11162
|
+
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path55.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path55.join(home, ".npm-global", "lib", "node_modules"));
|
|
10934
11163
|
return unique([
|
|
10935
|
-
|
|
10936
|
-
|
|
10937
|
-
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot :
|
|
11164
|
+
path55.join(openClawPrefix, "lib", "node_modules"),
|
|
11165
|
+
path55.join(openClawPrefix, "node_modules"),
|
|
11166
|
+
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path55.join(npmGlobalRoot, "lib", "node_modules")
|
|
10938
11167
|
]);
|
|
10939
11168
|
}
|
|
10940
11169
|
async function readVersion(packageJsonPath) {
|
|
@@ -10950,7 +11179,7 @@ function installedPackageJsonCandidates(packageName) {
|
|
|
10950
11179
|
const seen = /* @__PURE__ */ new Set();
|
|
10951
11180
|
const out = [];
|
|
10952
11181
|
for (const root of roots) {
|
|
10953
|
-
const candidate =
|
|
11182
|
+
const candidate = path55.join(root, packageName, "package.json");
|
|
10954
11183
|
if (seen.has(candidate)) continue;
|
|
10955
11184
|
seen.add(candidate);
|
|
10956
11185
|
out.push(candidate);
|
|
@@ -10981,7 +11210,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
10981
11210
|
const outcomes = [];
|
|
10982
11211
|
for (const name of Object.keys(run.workers || {})) {
|
|
10983
11212
|
const worker = readJson(
|
|
10984
|
-
|
|
11213
|
+
path56.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
10985
11214
|
void 0
|
|
10986
11215
|
);
|
|
10987
11216
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -11128,6 +11357,15 @@ async function runPipelineTick(args) {
|
|
|
11128
11357
|
var DEFAULT_INTERVAL_MS = 6e4;
|
|
11129
11358
|
var IDLE_INTERVAL_MS = 5 * 6e4;
|
|
11130
11359
|
var MAX_IDLE_STREAK = 10;
|
|
11360
|
+
var SLEEP_POLL_MS = 250;
|
|
11361
|
+
async function awaitDaemonBackoff(ms, isStopping) {
|
|
11362
|
+
let remaining = ms;
|
|
11363
|
+
while (remaining > 0 && !isStopping()) {
|
|
11364
|
+
const step = Math.min(SLEEP_POLL_MS, remaining);
|
|
11365
|
+
await sleepMsAsync(step);
|
|
11366
|
+
remaining -= step;
|
|
11367
|
+
}
|
|
11368
|
+
}
|
|
11131
11369
|
async function runDaemon(args) {
|
|
11132
11370
|
const runId = String(required(String(args.run || ""), "--run"));
|
|
11133
11371
|
const agentOsId = String(required(String(args.agentOsId || loadUserConfig().agentOsId || ""), "--agent-os-id"));
|
|
@@ -11185,17 +11423,17 @@ async function runDaemon(args) {
|
|
|
11185
11423
|
idleStreak = 0;
|
|
11186
11424
|
}
|
|
11187
11425
|
const backoff = idleStreak >= MAX_IDLE_STREAK ? IDLE_INTERVAL_MS : intervalMs;
|
|
11188
|
-
await
|
|
11426
|
+
await awaitDaemonBackoff(backoff, () => stopping);
|
|
11189
11427
|
} catch (error) {
|
|
11190
11428
|
console.error(JSON.stringify({ event: "daemon_tick_error", error: error.message }));
|
|
11191
|
-
await
|
|
11429
|
+
await awaitDaemonBackoff(intervalMs, () => stopping);
|
|
11192
11430
|
}
|
|
11193
11431
|
}
|
|
11194
11432
|
console.error(JSON.stringify({ event: "daemon_stop", runId, agentOsId }));
|
|
11195
11433
|
}
|
|
11196
11434
|
|
|
11197
11435
|
// src/plan-progress.ts
|
|
11198
|
-
import
|
|
11436
|
+
import path60 from "node:path";
|
|
11199
11437
|
|
|
11200
11438
|
// src/bounded-build/constants.ts
|
|
11201
11439
|
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
@@ -11338,16 +11576,16 @@ import {
|
|
|
11338
11576
|
unlinkSync as unlinkSync4,
|
|
11339
11577
|
writeFileSync as writeFileSync5
|
|
11340
11578
|
} from "node:fs";
|
|
11341
|
-
import
|
|
11579
|
+
import path58 from "node:path";
|
|
11342
11580
|
|
|
11343
11581
|
// src/heavy-verification/paths.ts
|
|
11344
11582
|
import { mkdirSync as mkdirSync7 } from "node:fs";
|
|
11345
|
-
import
|
|
11583
|
+
import path57 from "node:path";
|
|
11346
11584
|
function resolveHeavyVerificationRoot() {
|
|
11347
|
-
return
|
|
11585
|
+
return path57.join(resolveKynverStateRoot(), "heavy-verification");
|
|
11348
11586
|
}
|
|
11349
11587
|
function heavyVerificationSlotsDir() {
|
|
11350
|
-
return
|
|
11588
|
+
return path57.join(resolveHeavyVerificationRoot(), "slots");
|
|
11351
11589
|
}
|
|
11352
11590
|
function ensureHeavyVerificationDirs() {
|
|
11353
11591
|
const dir = heavyVerificationSlotsDir();
|
|
@@ -11376,7 +11614,7 @@ function indexedSlotId(index) {
|
|
|
11376
11614
|
return `slot-${index}`;
|
|
11377
11615
|
}
|
|
11378
11616
|
function slotFilePath(slotId, slotsDir = heavyVerificationSlotsDir()) {
|
|
11379
|
-
return
|
|
11617
|
+
return path58.join(slotsDir, `${slotId}.json`);
|
|
11380
11618
|
}
|
|
11381
11619
|
function readSlotRecord(filePath) {
|
|
11382
11620
|
if (!existsSync41(filePath)) return null;
|
|
@@ -11415,7 +11653,7 @@ function reclaimStaleHeavyVerificationSlots(opts = {}) {
|
|
|
11415
11653
|
let reclaimed = 0;
|
|
11416
11654
|
for (const name of readdirSync15(slotsDir)) {
|
|
11417
11655
|
if (!name.endsWith(".json")) continue;
|
|
11418
|
-
const filePath =
|
|
11656
|
+
const filePath = path58.join(slotsDir, name);
|
|
11419
11657
|
const before = existsSync41(filePath);
|
|
11420
11658
|
reclaimStaleSlot(filePath, staleMs);
|
|
11421
11659
|
if (before && !existsSync41(filePath)) reclaimed += 1;
|
|
@@ -11429,7 +11667,7 @@ function listActiveHeavyVerificationSlots(opts = {}) {
|
|
|
11429
11667
|
const active = [];
|
|
11430
11668
|
for (const name of readdirSync15(slotsDir)) {
|
|
11431
11669
|
if (!name.endsWith(".json")) continue;
|
|
11432
|
-
const record = readSlotRecord(
|
|
11670
|
+
const record = readSlotRecord(path58.join(slotsDir, name));
|
|
11433
11671
|
if (record && !slotIsStale(record, staleMs)) active.push(record);
|
|
11434
11672
|
}
|
|
11435
11673
|
return active;
|
|
@@ -11549,11 +11787,11 @@ function waitForHeavyVerificationSlot(command, timeoutMs, pollMs = 2e3, opts = {
|
|
|
11549
11787
|
}
|
|
11550
11788
|
|
|
11551
11789
|
// src/harness-worktree-build-guard.ts
|
|
11552
|
-
import
|
|
11790
|
+
import path59 from "node:path";
|
|
11553
11791
|
function isPathUnderHarnessWorktree(cwd) {
|
|
11554
11792
|
const worktreesDir = harnessWorktreesDir(resolveHarnessRoot());
|
|
11555
|
-
const rel =
|
|
11556
|
-
return rel.length > 0 && !rel.startsWith("..") && !
|
|
11793
|
+
const rel = path59.relative(worktreesDir, path59.resolve(cwd));
|
|
11794
|
+
return rel.length > 0 && !rel.startsWith("..") && !path59.isAbsolute(rel);
|
|
11557
11795
|
}
|
|
11558
11796
|
function assessHarnessWorktreeBuildGuard(cwd) {
|
|
11559
11797
|
if (!isPathUnderHarnessWorktree(cwd)) return { ok: true };
|
|
@@ -11765,7 +12003,7 @@ async function emitPlanProgress(args) {
|
|
|
11765
12003
|
}
|
|
11766
12004
|
function verifyPlanLocal(args) {
|
|
11767
12005
|
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
11768
|
-
const cwd =
|
|
12006
|
+
const cwd = path60.resolve(worktree);
|
|
11769
12007
|
const summary = runHarnessVerifyCommands(cwd);
|
|
11770
12008
|
const emitJson = args.json === true || args.json === "true";
|
|
11771
12009
|
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
@@ -11814,9 +12052,9 @@ async function verifyPlan(args) {
|
|
|
11814
12052
|
}
|
|
11815
12053
|
|
|
11816
12054
|
// src/harness-verify-cli.ts
|
|
11817
|
-
import
|
|
12055
|
+
import path61 from "node:path";
|
|
11818
12056
|
function runHarnessVerifyCli(args) {
|
|
11819
|
-
const cwd =
|
|
12057
|
+
const cwd = path61.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
11820
12058
|
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
11821
12059
|
const commands = [];
|
|
11822
12060
|
const rawCmd = args.command;
|
|
@@ -12024,7 +12262,7 @@ function formatMonitorTickNotice(tick) {
|
|
|
12024
12262
|
}
|
|
12025
12263
|
|
|
12026
12264
|
// src/monitor/monitor.service.ts
|
|
12027
|
-
import
|
|
12265
|
+
import path63 from "node:path";
|
|
12028
12266
|
|
|
12029
12267
|
// src/monitor/monitor.classify.ts
|
|
12030
12268
|
function classifyWorkerHealth(input) {
|
|
@@ -12077,10 +12315,10 @@ function classifyWorkerHealth(input) {
|
|
|
12077
12315
|
|
|
12078
12316
|
// src/monitor/monitor.store.ts
|
|
12079
12317
|
import { existsSync as existsSync42, mkdirSync as mkdirSync9, readdirSync as readdirSync16, unlinkSync as unlinkSync5 } from "node:fs";
|
|
12080
|
-
import
|
|
12318
|
+
import path62 from "node:path";
|
|
12081
12319
|
function monitorsDir() {
|
|
12082
12320
|
const { harnessRoot } = getHarnessPaths();
|
|
12083
|
-
const dir =
|
|
12321
|
+
const dir = path62.join(harnessRoot, "monitors");
|
|
12084
12322
|
mkdirSync9(dir, { recursive: true });
|
|
12085
12323
|
return dir;
|
|
12086
12324
|
}
|
|
@@ -12088,7 +12326,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
12088
12326
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
12089
12327
|
}
|
|
12090
12328
|
function monitorPath(monitorId) {
|
|
12091
|
-
return
|
|
12329
|
+
return path62.join(monitorsDir(), `${monitorId}.json`);
|
|
12092
12330
|
}
|
|
12093
12331
|
function loadMonitorSession(monitorId) {
|
|
12094
12332
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -12109,7 +12347,7 @@ function listMonitorSessions() {
|
|
|
12109
12347
|
for (const name of readdirSync16(dir)) {
|
|
12110
12348
|
if (!name.endsWith(".json")) continue;
|
|
12111
12349
|
const session = readJson(
|
|
12112
|
-
|
|
12350
|
+
path62.join(dir, name),
|
|
12113
12351
|
void 0
|
|
12114
12352
|
);
|
|
12115
12353
|
if (!session?.monitorId) continue;
|
|
@@ -12200,7 +12438,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
12200
12438
|
// src/monitor/monitor.service.ts
|
|
12201
12439
|
function workerRecord2(runId, name) {
|
|
12202
12440
|
return readJson(
|
|
12203
|
-
|
|
12441
|
+
path63.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
12204
12442
|
void 0
|
|
12205
12443
|
);
|
|
12206
12444
|
}
|
|
@@ -12407,17 +12645,17 @@ async function runMonitorLoop(args) {
|
|
|
12407
12645
|
// src/monitor/monitor-spawn.ts
|
|
12408
12646
|
import { spawn as spawn6 } from "node:child_process";
|
|
12409
12647
|
import { closeSync as closeSync8, existsSync as existsSync43, openSync as openSync8 } from "node:fs";
|
|
12410
|
-
import
|
|
12648
|
+
import path64 from "node:path";
|
|
12411
12649
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
12412
12650
|
function resolveDefaultCliPath2() {
|
|
12413
|
-
return
|
|
12651
|
+
return path64.join(fileURLToPath3(new URL(".", import.meta.url)), "cli.js");
|
|
12414
12652
|
}
|
|
12415
12653
|
function spawnMonitorSidecar(opts) {
|
|
12416
12654
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
12417
12655
|
if (!existsSync43(cliPath)) return void 0;
|
|
12418
12656
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
12419
12657
|
const { harnessRoot } = getHarnessPaths();
|
|
12420
|
-
const logPath =
|
|
12658
|
+
const logPath = path64.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
12421
12659
|
let logFd;
|
|
12422
12660
|
try {
|
|
12423
12661
|
logFd = openSync8(logPath, "a");
|
|
@@ -12618,8 +12856,32 @@ function shouldEnforceMemoryCostPackageGuardCli(scope, action) {
|
|
|
12618
12856
|
return false;
|
|
12619
12857
|
}
|
|
12620
12858
|
|
|
12859
|
+
// src/run-resolve.ts
|
|
12860
|
+
function resolveHarnessRunByName(runName) {
|
|
12861
|
+
const name = runName.trim();
|
|
12862
|
+
if (!name) return null;
|
|
12863
|
+
const rows = buildRunListRows();
|
|
12864
|
+
return rows.find((row) => row.name === name && row.status !== "completed") ?? null;
|
|
12865
|
+
}
|
|
12866
|
+
function resolveHarnessRunCli(args) {
|
|
12867
|
+
const name = String(required(String(args.name || ""), "--name"));
|
|
12868
|
+
const hit = resolveHarnessRunByName(name);
|
|
12869
|
+
console.log(
|
|
12870
|
+
JSON.stringify(
|
|
12871
|
+
{
|
|
12872
|
+
runId: hit?.id ?? null,
|
|
12873
|
+
name,
|
|
12874
|
+
status: hit?.status ?? null,
|
|
12875
|
+
effectiveStatus: hit?.effectiveStatus ?? null
|
|
12876
|
+
},
|
|
12877
|
+
null,
|
|
12878
|
+
2
|
|
12879
|
+
)
|
|
12880
|
+
);
|
|
12881
|
+
}
|
|
12882
|
+
|
|
12621
12883
|
// src/post-restart-unblock.ts
|
|
12622
|
-
import
|
|
12884
|
+
import path65 from "node:path";
|
|
12623
12885
|
function skip(runId, worker, taskId, agentOsId, leaseOwner, reason) {
|
|
12624
12886
|
return { runId, worker, taskId, agentOsId, leaseOwner, action: "skipped", reason };
|
|
12625
12887
|
}
|
|
@@ -12632,7 +12894,7 @@ async function postRestartUnblock(args) {
|
|
|
12632
12894
|
const errors = [];
|
|
12633
12895
|
for (const run of listRunRecords()) {
|
|
12634
12896
|
for (const name of Object.keys(run.workers ?? {})) {
|
|
12635
|
-
const workerPath =
|
|
12897
|
+
const workerPath = path65.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
12636
12898
|
const worker = readJson(workerPath, void 0);
|
|
12637
12899
|
if (!worker) {
|
|
12638
12900
|
skipped.push(skip(run.id, name, "", "", "", "worker.json missing"));
|
|
@@ -12744,9 +13006,9 @@ async function postRestartUnblockCli(args) {
|
|
|
12744
13006
|
}
|
|
12745
13007
|
|
|
12746
13008
|
// src/default-repo-cli.ts
|
|
12747
|
-
import
|
|
13009
|
+
import path66 from "node:path";
|
|
12748
13010
|
import { homedir as homedir14 } from "node:os";
|
|
12749
|
-
var CONFIG_FILE2 =
|
|
13011
|
+
var CONFIG_FILE2 = path66.join(homedir14(), ".kynver", "config.json");
|
|
12750
13012
|
function ensureDefaultRepo(opts) {
|
|
12751
13013
|
const existing = loadUserConfig();
|
|
12752
13014
|
const resolved = resolveDefaultRepo({ ...opts, config: existing });
|
|
@@ -12827,12 +13089,12 @@ function summarizeResolvedDefaultRepo(resolved) {
|
|
|
12827
13089
|
}
|
|
12828
13090
|
|
|
12829
13091
|
// src/doctor/runtime-takeover.ts
|
|
12830
|
-
import
|
|
13092
|
+
import path68 from "node:path";
|
|
12831
13093
|
|
|
12832
13094
|
// src/doctor/runtime-takeover.probes.ts
|
|
12833
13095
|
import { accessSync, constants, existsSync as existsSync45, readFileSync as readFileSync17 } from "node:fs";
|
|
12834
13096
|
import { homedir as homedir15 } from "node:os";
|
|
12835
|
-
import
|
|
13097
|
+
import path67 from "node:path";
|
|
12836
13098
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
12837
13099
|
function captureCommand(bin, args) {
|
|
12838
13100
|
try {
|
|
@@ -12874,10 +13136,10 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
12874
13136
|
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
12875
13137
|
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
12876
13138
|
loadConfig: () => loadUserConfig(),
|
|
12877
|
-
configFilePath: () =>
|
|
12878
|
-
credentialsFilePath: () =>
|
|
13139
|
+
configFilePath: () => path67.join(homedir15(), ".kynver", "config.json"),
|
|
13140
|
+
credentialsFilePath: () => path67.join(homedir15(), ".kynver", "credentials"),
|
|
12879
13141
|
readCredentials: () => {
|
|
12880
|
-
const credPath =
|
|
13142
|
+
const credPath = path67.join(homedir15(), ".kynver", "credentials");
|
|
12881
13143
|
if (!existsSync45(credPath)) {
|
|
12882
13144
|
return { hasApiKey: false };
|
|
12883
13145
|
}
|
|
@@ -12912,7 +13174,7 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
12912
13174
|
})()
|
|
12913
13175
|
}),
|
|
12914
13176
|
harnessRoot: () => resolveHarnessRoot(),
|
|
12915
|
-
legacyOpenclawHarnessRoot: () =>
|
|
13177
|
+
legacyOpenclawHarnessRoot: () => path67.join(homedir15(), ".openclaw", "harness"),
|
|
12916
13178
|
pathExists: (target) => existsSync45(target),
|
|
12917
13179
|
pathWritable: (target) => isWritable(target)
|
|
12918
13180
|
};
|
|
@@ -13319,8 +13581,8 @@ function assessVercelDeployEvidence(probes) {
|
|
|
13319
13581
|
}
|
|
13320
13582
|
function assessHarnessDirs(probes) {
|
|
13321
13583
|
const harnessRoot = probes.harnessRoot();
|
|
13322
|
-
const runsDir =
|
|
13323
|
-
const worktreesDir =
|
|
13584
|
+
const runsDir = path68.join(harnessRoot, "runs");
|
|
13585
|
+
const worktreesDir = path68.join(harnessRoot, "worktrees");
|
|
13324
13586
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
13325
13587
|
const displayRunsDir = redactHomePath(runsDir);
|
|
13326
13588
|
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
@@ -13584,9 +13846,9 @@ function applySchedulerCutoverAttestation(config) {
|
|
|
13584
13846
|
}
|
|
13585
13847
|
|
|
13586
13848
|
// src/scheduler-cutover-cli.ts
|
|
13587
|
-
import
|
|
13849
|
+
import path69 from "node:path";
|
|
13588
13850
|
import { homedir as homedir16 } from "node:os";
|
|
13589
|
-
var CONFIG_FILE3 =
|
|
13851
|
+
var CONFIG_FILE3 = path69.join(homedir16(), ".kynver", "config.json");
|
|
13590
13852
|
function runSchedulerCutoverCheckCli(json = false) {
|
|
13591
13853
|
const config = loadUserConfig();
|
|
13592
13854
|
const report = assessSchedulerCutover(config);
|
|
@@ -13723,6 +13985,157 @@ async function runCronTickCli(args) {
|
|
|
13723
13985
|
);
|
|
13724
13986
|
}
|
|
13725
13987
|
|
|
13988
|
+
// src/lane/landing-maintainer-tick.ts
|
|
13989
|
+
import os9 from "node:os";
|
|
13990
|
+
|
|
13991
|
+
// src/lane/lane-spec.ts
|
|
13992
|
+
var LANDING_MAINTAINER_LANE_SPEC = {
|
|
13993
|
+
slug: "landing-maintainer",
|
|
13994
|
+
originCron: "maintain-8-blocker-and-pr-landing-workers",
|
|
13995
|
+
defaultRepo: "Totalsolutionsync/Kynver",
|
|
13996
|
+
landScript: "scripts/agent-os-land-pr.mjs",
|
|
13997
|
+
landScriptArgs: ["--skip-not-ready"]
|
|
13998
|
+
};
|
|
13999
|
+
|
|
14000
|
+
// src/lane/landing-maintainer-local.ts
|
|
14001
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
14002
|
+
import path70 from "node:path";
|
|
14003
|
+
function runLandingWrapper(prNumber, repoRoot, execute) {
|
|
14004
|
+
const script = path70.join(repoRoot, LANDING_MAINTAINER_LANE_SPEC.landScript);
|
|
14005
|
+
const args = [script, String(prNumber), ...LANDING_MAINTAINER_LANE_SPEC.landScriptArgs];
|
|
14006
|
+
if (!execute) {
|
|
14007
|
+
return {
|
|
14008
|
+
action: { kind: "land_pr", prNumber, reason: "dry-run" },
|
|
14009
|
+
executed: false,
|
|
14010
|
+
exitCode: 0,
|
|
14011
|
+
stdout: `dry-run: node ${args.join(" ")}`,
|
|
14012
|
+
stderr: ""
|
|
14013
|
+
};
|
|
14014
|
+
}
|
|
14015
|
+
const result = spawnSync10("node", args, {
|
|
14016
|
+
cwd: repoRoot,
|
|
14017
|
+
encoding: "utf8",
|
|
14018
|
+
timeout: 10 * 60 * 1e3
|
|
14019
|
+
});
|
|
14020
|
+
return {
|
|
14021
|
+
action: { kind: "land_pr", prNumber, reason: "landing wrapper invoked" },
|
|
14022
|
+
executed: true,
|
|
14023
|
+
exitCode: result.status,
|
|
14024
|
+
stdout: result.stdout ?? "",
|
|
14025
|
+
stderr: result.stderr ?? ""
|
|
14026
|
+
};
|
|
14027
|
+
}
|
|
14028
|
+
function resolveLandingMaintainerRepoRoot(args) {
|
|
14029
|
+
const explicit = args.repoPath ? String(args.repoPath).trim() : "";
|
|
14030
|
+
if (explicit) return path70.resolve(explicit);
|
|
14031
|
+
const resolved = resolveDefaultRepo();
|
|
14032
|
+
return resolved?.repo ?? process.cwd();
|
|
14033
|
+
}
|
|
14034
|
+
|
|
14035
|
+
// src/lane/landing-maintainer-tick.ts
|
|
14036
|
+
async function runLandingMaintainerLaneTick(args) {
|
|
14037
|
+
const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
|
|
14038
|
+
const repoSlug = String(args.repo || LANDING_MAINTAINER_LANE_SPEC.defaultRepo).trim();
|
|
14039
|
+
const fleet = args.fleet === true || args.fleet === "true";
|
|
14040
|
+
const execute = args.execute !== false && args.execute !== "false";
|
|
14041
|
+
const runId = args.run ? String(args.run) : void 0;
|
|
14042
|
+
const resourceGate = observeRunnerResourceGate({
|
|
14043
|
+
runId: runId ?? "fleet-lane-tick"
|
|
14044
|
+
});
|
|
14045
|
+
const boxCapacity = {
|
|
14046
|
+
...buildBoxResourceSnapshotFromGate(resourceGate, {
|
|
14047
|
+
harnessRunId: runId,
|
|
14048
|
+
boxKind: resolveBoxKindFromConfig(loadUserConfig()),
|
|
14049
|
+
hostLabel: os9.hostname()
|
|
14050
|
+
}),
|
|
14051
|
+
providerHealthy: resourceGate.ok,
|
|
14052
|
+
authorizedForRepair: resourceGate.ok,
|
|
14053
|
+
authorizedForLanding: resourceGate.ok,
|
|
14054
|
+
systemHealthBlockers: resourceGate.ok ? [] : [resourceGate.reason ?? "resource_gate_blocked"],
|
|
14055
|
+
actionableWorkers: resourceGate.activeWorkers
|
|
14056
|
+
};
|
|
14057
|
+
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
14058
|
+
const secret = await resolveCallbackSecretWithMint(
|
|
14059
|
+
args.secret ? String(args.secret) : void 0,
|
|
14060
|
+
agentOsId,
|
|
14061
|
+
{ baseUrl: base }
|
|
14062
|
+
);
|
|
14063
|
+
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/fleet-landing-maintainer/tick`;
|
|
14064
|
+
const res = await postJson(url, secret, {
|
|
14065
|
+
repo: repoSlug,
|
|
14066
|
+
fleet,
|
|
14067
|
+
execute,
|
|
14068
|
+
runId,
|
|
14069
|
+
boxCapacity
|
|
14070
|
+
});
|
|
14071
|
+
const coordinator = res.response;
|
|
14072
|
+
const localOutcomes = [];
|
|
14073
|
+
const repoRoot = resolveLandingMaintainerRepoRoot(args);
|
|
14074
|
+
const actions = Array.isArray(coordinator?.localActions) ? coordinator.localActions : [];
|
|
14075
|
+
const holderBoxId = boxCapacity.boxId;
|
|
14076
|
+
for (const action of actions) {
|
|
14077
|
+
if (action.kind === "land_pr" && typeof action.prNumber === "number") {
|
|
14078
|
+
const outcome = runLandingWrapper(action.prNumber, repoRoot, execute);
|
|
14079
|
+
localOutcomes.push({
|
|
14080
|
+
action: outcome.action,
|
|
14081
|
+
executed: outcome.executed,
|
|
14082
|
+
exitCode: outcome.exitCode
|
|
14083
|
+
});
|
|
14084
|
+
if (execute && outcome.executed) {
|
|
14085
|
+
const merged = outcome.exitCode === 0;
|
|
14086
|
+
const prUrl = typeof action.prUrl === "string" && action.prUrl.trim() ? action.prUrl.trim() : `https://github.com/${repoSlug}/pull/${action.prNumber}`;
|
|
14087
|
+
try {
|
|
14088
|
+
await postJson(
|
|
14089
|
+
`${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/fleet-landing-maintainer/outcome`,
|
|
14090
|
+
secret,
|
|
14091
|
+
{ repo: repoSlug, prUrl, holderBoxId, merged }
|
|
14092
|
+
);
|
|
14093
|
+
} catch {
|
|
14094
|
+
}
|
|
14095
|
+
}
|
|
14096
|
+
continue;
|
|
14097
|
+
}
|
|
14098
|
+
localOutcomes.push({
|
|
14099
|
+
action,
|
|
14100
|
+
executed: false,
|
|
14101
|
+
exitCode: null
|
|
14102
|
+
});
|
|
14103
|
+
}
|
|
14104
|
+
return {
|
|
14105
|
+
repo: repoSlug,
|
|
14106
|
+
fleet,
|
|
14107
|
+
execute,
|
|
14108
|
+
coordinator,
|
|
14109
|
+
localOutcomes
|
|
14110
|
+
};
|
|
14111
|
+
}
|
|
14112
|
+
|
|
14113
|
+
// src/lane/lane-tick-cli.ts
|
|
14114
|
+
async function runLaneTickCli(args, laneFromArgv) {
|
|
14115
|
+
const lane = String(laneFromArgv ?? args.lane ?? "").trim();
|
|
14116
|
+
if (lane !== "landing-maintainer") {
|
|
14117
|
+
console.error(`unknown lane: ${lane || "(none)"}`);
|
|
14118
|
+
process.exit(1);
|
|
14119
|
+
}
|
|
14120
|
+
const result = await runLandingMaintainerLaneTick(args);
|
|
14121
|
+
if (args.json === true || args.json === "true") {
|
|
14122
|
+
console.log(JSON.stringify(result, null, 2));
|
|
14123
|
+
return;
|
|
14124
|
+
}
|
|
14125
|
+
console.log(
|
|
14126
|
+
[
|
|
14127
|
+
`fleet landing-maintainer tick`,
|
|
14128
|
+
`repo=${result.repo}`,
|
|
14129
|
+
`fleet=${result.fleet}`,
|
|
14130
|
+
`execute=${result.execute}`,
|
|
14131
|
+
`localOutcomes=${result.localOutcomes.length}`
|
|
14132
|
+
].join(" ")
|
|
14133
|
+
);
|
|
14134
|
+
for (const row of result.localOutcomes) {
|
|
14135
|
+
console.log(` ${row.action.kind} pr=${row.action.prNumber ?? "-"} executed=${row.executed}`);
|
|
14136
|
+
}
|
|
14137
|
+
}
|
|
14138
|
+
|
|
13726
14139
|
// src/cli.ts
|
|
13727
14140
|
function isHelpFlag(arg) {
|
|
13728
14141
|
return arg === "help" || arg === "--help" || arg === "-h";
|
|
@@ -13743,6 +14156,7 @@ function usage(code = 0) {
|
|
|
13743
14156
|
" kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS]",
|
|
13744
14157
|
" kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
|
|
13745
14158
|
" kynver run list",
|
|
14159
|
+
" kynver run resolve --name RUN_NAME",
|
|
13746
14160
|
" kynver run status --run RUN_ID [--json] [--compact]",
|
|
13747
14161
|
" 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]",
|
|
13748
14162
|
" kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
|
|
@@ -13778,6 +14192,7 @@ function usage(code = 0) {
|
|
|
13778
14192
|
" kynver scheduler attest-cutover [--json]",
|
|
13779
14193
|
" kynver cron status [--json]",
|
|
13780
14194
|
" kynver cron tick [--agent-os-id AOS_ID] [--json]",
|
|
14195
|
+
" kynver lane tick landing-maintainer [--fleet] [--repo OWNER/NAME] [--agent-os-id AOS_ID] [--execute] [--json]",
|
|
13781
14196
|
" kynver board contract [--agent-os-id ID] [--base-url URL] [--since ISO] [--limit N]"
|
|
13782
14197
|
].join("\n")
|
|
13783
14198
|
);
|
|
@@ -13789,7 +14204,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
13789
14204
|
const scope = argv.shift();
|
|
13790
14205
|
let action;
|
|
13791
14206
|
let rest;
|
|
13792
|
-
if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "harness" || scope === "monitor" || scope === "doctor" || scope === "config" || scope === "scheduler" || scope === "cron" || scope === "board") {
|
|
14207
|
+
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") {
|
|
13793
14208
|
action = argv.shift();
|
|
13794
14209
|
rest = argv;
|
|
13795
14210
|
} else {
|
|
@@ -13846,11 +14261,16 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
13846
14261
|
if (scope === "cron" && action === "tick") {
|
|
13847
14262
|
return void await runCronTickCli(args);
|
|
13848
14263
|
}
|
|
14264
|
+
if (scope === "lane" && action === "tick") {
|
|
14265
|
+
const laneName = rest.shift();
|
|
14266
|
+
return void await runLaneTickCli(parseArgs(rest), laneName);
|
|
14267
|
+
}
|
|
13849
14268
|
if (scope === "board" && action === "contract") {
|
|
13850
14269
|
return void await runCommandCenterContractCli(args);
|
|
13851
14270
|
}
|
|
13852
14271
|
if (scope === "run" && action === "create") return createRun(args);
|
|
13853
14272
|
if (scope === "run" && action === "list") return listRuns();
|
|
14273
|
+
if (scope === "run" && action === "resolve") return resolveHarnessRunCli(args);
|
|
13854
14274
|
if (scope === "run" && action === "status") return runStatus(args);
|
|
13855
14275
|
if (scope === "run" && action === "dispatch") return void await dispatchRun(args);
|
|
13856
14276
|
if (scope === "run" && action === "sweep") return void await sweepRun(args);
|