@kynver-app/runtime 0.1.74 → 0.1.76
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/active-harness-workers.d.ts +11 -0
- package/dist/cleanup-evidence.d.ts +10 -0
- package/dist/cleanup-execute.d.ts +1 -0
- package/dist/cleanup-guards.d.ts +2 -0
- package/dist/cleanup-retention-config.d.ts +3 -0
- package/dist/cleanup-run-directory.d.ts +20 -0
- package/dist/cleanup-types.d.ts +25 -1
- package/dist/cli.js +2439 -751
- package/dist/cli.js.map +4 -4
- package/dist/cron/hermes-provider-notice.d.ts +1 -0
- package/dist/harness-lease-owner.d.ts +13 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.js +2715 -760
- package/dist/index.js.map +4 -4
- package/dist/landing-contract-gate.d.ts +1 -0
- package/dist/model-routing.d.ts +4 -1
- package/dist/orchestration-providers/audit.d.ts +3 -0
- package/dist/orchestration-providers/capabilities.d.ts +3 -0
- package/dist/orchestration-providers/claude-oauth-binding.d.ts +6 -0
- package/dist/orchestration-providers/codex-oauth-binding.d.ts +5 -0
- package/dist/orchestration-providers/codex-orchestration-adapter.d.ts +13 -0
- package/dist/orchestration-providers/cost-router.d.ts +24 -0
- package/dist/orchestration-providers/cursor-oauth-binding.d.ts +5 -0
- package/dist/orchestration-providers/hermes-cli-adapter.d.ts +15 -0
- package/dist/orchestration-providers/hermes-openai-codex-binding.d.ts +15 -0
- package/dist/orchestration-providers/index.d.ts +13 -0
- package/dist/orchestration-providers/inventory.d.ts +31 -0
- package/dist/orchestration-providers/oauth-binding-utils.d.ts +21 -0
- package/dist/orchestration-providers/routing.d.ts +16 -0
- package/dist/orchestration-providers/types.d.ts +45 -0
- package/dist/paths.d.ts +9 -1
- package/dist/pipeline-tick.d.ts +3 -0
- package/dist/post-restart-unblock.d.ts +24 -0
- package/dist/providers/codex.d.ts +9 -0
- package/dist/providers/hermes-codex.d.ts +9 -0
- package/dist/providers/model-preflight.d.ts +2 -0
- package/dist/providers/openai-codex-resilience.d.ts +33 -0
- package/dist/retry-limits.d.ts +1 -1
- package/dist/stale-reconcile.d.ts +2 -0
- package/dist/status.d.ts +6 -0
- package/dist/supervisor.d.ts +1 -0
- package/dist/worker-metadata-paths.d.ts +26 -0
- package/dist/worker-metadata-reconcile.d.ts +19 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -645,9 +645,6 @@ async function runLogin(args) {
|
|
|
645
645
|
console.log(JSON.stringify({ ok: true, credentialsPath: displayUserPath(CREDENTIALS_FILE) }, null, 2));
|
|
646
646
|
}
|
|
647
647
|
|
|
648
|
-
// src/dispatch.ts
|
|
649
|
-
import path18 from "node:path";
|
|
650
|
-
|
|
651
648
|
// src/callback-headers.ts
|
|
652
649
|
function buildHarnessCallbackHeaders(secret) {
|
|
653
650
|
const trimmed = String(secret).trim();
|
|
@@ -670,12 +667,29 @@ function buildHarnessCallbackHeaders(secret) {
|
|
|
670
667
|
}
|
|
671
668
|
|
|
672
669
|
// src/callbacks.ts
|
|
670
|
+
function callbackTimeoutMs() {
|
|
671
|
+
const parsed = Number(process.env.KYNVER_CALLBACK_TIMEOUT_MS);
|
|
672
|
+
if (Number.isFinite(parsed) && parsed > 0) return Math.floor(parsed);
|
|
673
|
+
return 3e4;
|
|
674
|
+
}
|
|
675
|
+
async function withTimeout(fn) {
|
|
676
|
+
const controller = new AbortController();
|
|
677
|
+
const timeout = setTimeout(() => controller.abort(), callbackTimeoutMs());
|
|
678
|
+
try {
|
|
679
|
+
return await fn(controller.signal);
|
|
680
|
+
} finally {
|
|
681
|
+
clearTimeout(timeout);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
673
684
|
async function postJson(url, secret, body) {
|
|
674
|
-
const res = await
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
685
|
+
const res = await withTimeout(
|
|
686
|
+
(signal) => fetch(url, {
|
|
687
|
+
method: "POST",
|
|
688
|
+
headers: buildHarnessCallbackHeaders(secret),
|
|
689
|
+
body: JSON.stringify(body),
|
|
690
|
+
signal
|
|
691
|
+
})
|
|
692
|
+
);
|
|
679
693
|
let response = null;
|
|
680
694
|
try {
|
|
681
695
|
response = await res.json();
|
|
@@ -693,10 +707,13 @@ async function postJsonWithCredentialRefresh(url, secret, body, opts) {
|
|
|
693
707
|
return { ...retry, refreshedAuth: true };
|
|
694
708
|
}
|
|
695
709
|
async function getJson(url, secret) {
|
|
696
|
-
const res = await
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
710
|
+
const res = await withTimeout(
|
|
711
|
+
(signal) => fetch(url, {
|
|
712
|
+
method: "GET",
|
|
713
|
+
headers: buildHarnessCallbackHeaders(secret),
|
|
714
|
+
signal
|
|
715
|
+
})
|
|
716
|
+
);
|
|
700
717
|
let response = null;
|
|
701
718
|
try {
|
|
702
719
|
response = await res.json();
|
|
@@ -729,23 +746,23 @@ function isWslHost() {
|
|
|
729
746
|
function observeWslHostDisk(options = {}) {
|
|
730
747
|
const wsl = options.forceWsl === void 0 ? isWslHost() : options.forceWsl;
|
|
731
748
|
if (!wsl) return null;
|
|
732
|
-
const
|
|
749
|
+
const path59 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
|
|
733
750
|
const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;
|
|
734
751
|
const criticalBelowBytes = options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;
|
|
735
752
|
const statfs = options.statfs ?? statfsSync;
|
|
736
753
|
let stats;
|
|
737
754
|
try {
|
|
738
|
-
stats = statfs(
|
|
755
|
+
stats = statfs(path59);
|
|
739
756
|
} catch (error) {
|
|
740
757
|
return {
|
|
741
758
|
ok: false,
|
|
742
|
-
path:
|
|
759
|
+
path: path59,
|
|
743
760
|
freeBytes: 0,
|
|
744
761
|
totalBytes: 0,
|
|
745
762
|
usedPercent: 100,
|
|
746
763
|
warnBelowBytes,
|
|
747
764
|
criticalBelowBytes,
|
|
748
|
-
reason: `Windows host disk probe failed at ${
|
|
765
|
+
reason: `Windows host disk probe failed at ${path59}: ${error.message}`,
|
|
749
766
|
probeError: error.message
|
|
750
767
|
};
|
|
751
768
|
}
|
|
@@ -759,11 +776,11 @@ function observeWslHostDisk(options = {}) {
|
|
|
759
776
|
let reason = null;
|
|
760
777
|
if (!ok) {
|
|
761
778
|
const tag = criticalFree ? "critical" : "warning";
|
|
762
|
-
reason = `Windows host disk ${
|
|
779
|
+
reason = `Windows host disk ${path59} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
|
|
763
780
|
}
|
|
764
781
|
return {
|
|
765
782
|
ok,
|
|
766
|
-
path:
|
|
783
|
+
path: path59,
|
|
767
784
|
freeBytes,
|
|
768
785
|
totalBytes,
|
|
769
786
|
usedPercent,
|
|
@@ -783,12 +800,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
|
783
800
|
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
784
801
|
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
785
802
|
function observeRunnerDiskGate(input = {}) {
|
|
786
|
-
const
|
|
803
|
+
const path59 = input.diskPath?.trim() || "/";
|
|
787
804
|
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
788
805
|
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
789
806
|
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
790
807
|
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
791
|
-
const stats = statfsSync2(
|
|
808
|
+
const stats = statfsSync2(path59);
|
|
792
809
|
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
793
810
|
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
794
811
|
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
@@ -811,7 +828,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
811
828
|
}
|
|
812
829
|
return {
|
|
813
830
|
ok,
|
|
814
|
-
path:
|
|
831
|
+
path: path59,
|
|
815
832
|
freeBytes,
|
|
816
833
|
totalBytes,
|
|
817
834
|
usedPercent,
|
|
@@ -851,7 +868,7 @@ function readMemAvailableBytes(meminfoText) {
|
|
|
851
868
|
import path7 from "node:path";
|
|
852
869
|
|
|
853
870
|
// src/run-store.ts
|
|
854
|
-
import { existsSync as existsSync6, readdirSync as readdirSync2 } from "node:fs";
|
|
871
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
855
872
|
import path6 from "node:path";
|
|
856
873
|
|
|
857
874
|
// src/paths.ts
|
|
@@ -859,22 +876,36 @@ import { existsSync as existsSync5 } from "node:fs";
|
|
|
859
876
|
import { homedir as homedir4 } from "node:os";
|
|
860
877
|
import path5 from "node:path";
|
|
861
878
|
var LEGACY_ROOT = path5.join(homedir4(), ".openclaw", "harness");
|
|
879
|
+
var HARNESS_LAYOUT_DIR_NAMES = /* @__PURE__ */ new Set(["runs", "worktrees"]);
|
|
880
|
+
function normalizeHarnessRoot(root) {
|
|
881
|
+
let resolved = path5.resolve(resolveUserPath(root.trim()));
|
|
882
|
+
while (HARNESS_LAYOUT_DIR_NAMES.has(path5.basename(resolved))) {
|
|
883
|
+
resolved = path5.dirname(resolved);
|
|
884
|
+
}
|
|
885
|
+
return resolved;
|
|
886
|
+
}
|
|
862
887
|
function resolveHarnessRoot() {
|
|
863
888
|
const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
|
|
864
|
-
if (env) return
|
|
889
|
+
if (env) return normalizeHarnessRoot(env);
|
|
865
890
|
const configured = loadUserConfig().harnessRoot?.trim();
|
|
866
|
-
if (configured) return
|
|
891
|
+
if (configured) return normalizeHarnessRoot(configured);
|
|
867
892
|
const kynverRoot = path5.join(homedir4(), ".kynver", "harness");
|
|
868
893
|
if (existsSync5(kynverRoot)) return kynverRoot;
|
|
869
894
|
if (existsSync5(LEGACY_ROOT)) return LEGACY_ROOT;
|
|
870
895
|
return kynverRoot;
|
|
871
896
|
}
|
|
897
|
+
function harnessRunsDir(harnessRoot) {
|
|
898
|
+
return path5.join(normalizeHarnessRoot(harnessRoot), "runs");
|
|
899
|
+
}
|
|
900
|
+
function harnessWorktreesDir(harnessRoot) {
|
|
901
|
+
return path5.join(normalizeHarnessRoot(harnessRoot), "worktrees");
|
|
902
|
+
}
|
|
872
903
|
function getHarnessPaths() {
|
|
873
904
|
const harnessRoot = resolveHarnessRoot();
|
|
874
905
|
return {
|
|
875
906
|
harnessRoot,
|
|
876
|
-
runsDir:
|
|
877
|
-
worktreesDir:
|
|
907
|
+
runsDir: harnessRunsDir(harnessRoot),
|
|
908
|
+
worktreesDir: harnessWorktreesDir(harnessRoot)
|
|
878
909
|
};
|
|
879
910
|
}
|
|
880
911
|
function runDir(runsDir, id) {
|
|
@@ -894,15 +925,24 @@ function listRunRecords() {
|
|
|
894
925
|
return listRunRecordsAt(runsDir);
|
|
895
926
|
}
|
|
896
927
|
function listRunRecordsForHarnessRoot(harnessRoot) {
|
|
897
|
-
return listRunRecordsAt(
|
|
928
|
+
return listRunRecordsAt(harnessRunsDir(harnessRoot));
|
|
929
|
+
}
|
|
930
|
+
function isRunDirectoryEntry(runDirPath) {
|
|
931
|
+
try {
|
|
932
|
+
return statSync2(runDirPath).isDirectory();
|
|
933
|
+
} catch {
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
898
936
|
}
|
|
899
937
|
function listRunRecordsAt(runsDir) {
|
|
900
938
|
if (!existsSync6(runsDir)) return [];
|
|
901
939
|
const runs = [];
|
|
902
940
|
for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
|
|
903
|
-
if (
|
|
941
|
+
if (entry.name === "runs") continue;
|
|
942
|
+
const runDir2 = path6.join(runsDir, entry.name);
|
|
943
|
+
if (!isRunDirectoryEntry(runDir2)) continue;
|
|
904
944
|
const run = readJson(
|
|
905
|
-
path6.join(
|
|
945
|
+
path6.join(runDir2, "run.json"),
|
|
906
946
|
void 0
|
|
907
947
|
);
|
|
908
948
|
if (run?.id) runs.push(run);
|
|
@@ -924,11 +964,11 @@ function saveWorker(runId, worker) {
|
|
|
924
964
|
writeJson(path6.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
|
|
925
965
|
}
|
|
926
966
|
function runDirectory(id) {
|
|
927
|
-
const {
|
|
928
|
-
return runDirectoryAt(
|
|
967
|
+
const { harnessRoot } = getPaths();
|
|
968
|
+
return runDirectoryAt(harnessRoot, id);
|
|
929
969
|
}
|
|
930
970
|
function runDirectoryAt(harnessRoot, id) {
|
|
931
|
-
return runDir(
|
|
971
|
+
return runDir(harnessRunsDir(harnessRoot), safeSlug(id));
|
|
932
972
|
}
|
|
933
973
|
|
|
934
974
|
// src/heartbeat.ts
|
|
@@ -1490,13 +1530,29 @@ function parseReconciliation(finalResult) {
|
|
|
1490
1530
|
}
|
|
1491
1531
|
return out;
|
|
1492
1532
|
}
|
|
1533
|
+
function stringList(value) {
|
|
1534
|
+
if (!Array.isArray(value)) return [];
|
|
1535
|
+
return value.map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
|
|
1536
|
+
}
|
|
1493
1537
|
function workerPrUrls(snapshot, finalResult) {
|
|
1494
1538
|
const urls = [];
|
|
1495
1539
|
const fromSnapshot = normalizePrUrl(trimOrNull3(snapshot.prUrl) ?? "");
|
|
1496
1540
|
if (fromSnapshot) urls.push(fromSnapshot);
|
|
1497
1541
|
if (finalResult && typeof finalResult === "object" && !Array.isArray(finalResult)) {
|
|
1498
|
-
const
|
|
1542
|
+
const record = finalResult;
|
|
1543
|
+
const pr = normalizePrUrl(String(record.prUrl ?? ""));
|
|
1499
1544
|
if (pr) urls.push(pr);
|
|
1545
|
+
const lane = record.laneExpertise ?? record.lane_expertise;
|
|
1546
|
+
if (lane && typeof lane === "object" && !Array.isArray(lane)) {
|
|
1547
|
+
for (const u of stringList(lane.prUrls)) {
|
|
1548
|
+
const n = normalizePrUrl(u);
|
|
1549
|
+
if (n) urls.push(n);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
for (const u of stringList(record.prUrls ?? record.pr_urls)) {
|
|
1553
|
+
const n = normalizePrUrl(u);
|
|
1554
|
+
if (n) urls.push(n);
|
|
1555
|
+
}
|
|
1500
1556
|
}
|
|
1501
1557
|
return [...new Set(urls)];
|
|
1502
1558
|
}
|
|
@@ -1506,7 +1562,21 @@ function assessWorkerLandingContract(input) {
|
|
|
1506
1562
|
if (!contract.landingOnly && contract.targetPrUrls.length === 0 && !contract.repairEnforceOriginalPr) {
|
|
1507
1563
|
return { blocked: false };
|
|
1508
1564
|
}
|
|
1509
|
-
if (!hasFinalResult3(finalResult))
|
|
1565
|
+
if (!hasFinalResult3(finalResult)) {
|
|
1566
|
+
const requiresEarly = contract.requiresTargetPrReconciliation ?? (contract.landingOnly || Boolean(contract.repairEnforceOriginalPr) || contract.targetPrUrls.length > 0);
|
|
1567
|
+
if (requiresEarly && contract.targetPrUrls.length > 0) {
|
|
1568
|
+
return {
|
|
1569
|
+
blocked: true,
|
|
1570
|
+
reason: "missing_target_pr_reconciliation",
|
|
1571
|
+
detail: `Final result required to reconcile target PR(s): ${contract.targetPrUrls.join(", ")}`
|
|
1572
|
+
};
|
|
1573
|
+
}
|
|
1574
|
+
return { blocked: false };
|
|
1575
|
+
}
|
|
1576
|
+
const requiresTargetPrReconciliation = contract.requiresTargetPrReconciliation ?? (contract.landingOnly || Boolean(contract.repairEnforceOriginalPr) || contract.targetPrUrls.length > 0);
|
|
1577
|
+
if (!requiresTargetPrReconciliation && !contract.repairEnforceOriginalPr) {
|
|
1578
|
+
return { blocked: false };
|
|
1579
|
+
}
|
|
1510
1580
|
const repairTarget = contract.repairEnforceOriginalPr ? normalizePrUrl(trimOrNull3(contract.targetPrUrl) ?? "") ?? (contract.targetPrUrls.length === 1 ? normalizePrUrl(contract.targetPrUrls[0]) : null) : null;
|
|
1511
1581
|
if (repairTarget) {
|
|
1512
1582
|
const workerPrs2 = workerPrUrls(snapshot, finalResult);
|
|
@@ -1538,6 +1608,14 @@ function assessWorkerLandingContract(input) {
|
|
|
1538
1608
|
contract.targetPrUrls.map((u) => normalizePrUrl(u) ?? u).filter(Boolean)
|
|
1539
1609
|
);
|
|
1540
1610
|
const workerPrs = workerPrUrls(snapshot, finalResult);
|
|
1611
|
+
const unrelatedReconciled = reconciliation.map((entry) => entry.prUrl).filter((pr) => pr && !targetSet.has(pr));
|
|
1612
|
+
if (unrelatedReconciled.length > 0 && (contract.landingOnly || contract.repairEnforceOriginalPr)) {
|
|
1613
|
+
return {
|
|
1614
|
+
blocked: true,
|
|
1615
|
+
reason: "unrelated_implementation_pr",
|
|
1616
|
+
detail: `Reconciliation must only cite contract target PR(s); unrelated: ${unrelatedReconciled.join(", ")}`
|
|
1617
|
+
};
|
|
1618
|
+
}
|
|
1541
1619
|
if (contract.landingOnly) {
|
|
1542
1620
|
for (const pr of workerPrs) {
|
|
1543
1621
|
if (targetSet.size > 0 && !targetSet.has(pr)) {
|
|
@@ -1791,6 +1869,9 @@ function readAvailableMemBytes() {
|
|
|
1791
1869
|
return readMemAvailableBytes();
|
|
1792
1870
|
}
|
|
1793
1871
|
function isActiveHarnessWorker(worker) {
|
|
1872
|
+
if (typeof worker.completionBlocker === "string" && worker.completionBlocker.trim()) {
|
|
1873
|
+
return false;
|
|
1874
|
+
}
|
|
1794
1875
|
const status = computeWorkerStatus(worker);
|
|
1795
1876
|
return status.alive && !status.finalResult && status.attention.state !== "done";
|
|
1796
1877
|
}
|
|
@@ -1925,127 +2006,6 @@ function enrichTaskForModelRouting(task) {
|
|
|
1925
2006
|
return { ...task, roleLane };
|
|
1926
2007
|
}
|
|
1927
2008
|
|
|
1928
|
-
// src/providers/claude.ts
|
|
1929
|
-
import { closeSync, openSync } from "node:fs";
|
|
1930
|
-
import { spawn } from "node:child_process";
|
|
1931
|
-
|
|
1932
|
-
// src/providers/model-preflight.ts
|
|
1933
|
-
var REASONING_SUFFIX_RE = /-(?:thinking(?:-(?:high|medium|low|minimal|max|none))?|high|medium|low|minimal)$/i;
|
|
1934
|
-
function stripReasoningSuffix(model) {
|
|
1935
|
-
return model.replace(REASONING_SUFFIX_RE, "");
|
|
1936
|
-
}
|
|
1937
|
-
var FOREIGN_MODEL_RE = /^(?:gpt-|gpt5|o1|o3|o4|gemini-|grok-|composer|deepseek|llama|mistral|qwen|command-)/i;
|
|
1938
|
-
function looksLikeClaudeModel(model) {
|
|
1939
|
-
return /^claude[-_]/i.test(model) || /^(?:opus|sonnet|haiku)\b/i.test(model);
|
|
1940
|
-
}
|
|
1941
|
-
var CURSOR_MODEL_ALIASES = /* @__PURE__ */ new Set(["cursor"]);
|
|
1942
|
-
function normalizeCursorModelAlias(model) {
|
|
1943
|
-
const key = model.trim().toLowerCase();
|
|
1944
|
-
if (CURSOR_MODEL_ALIASES.has(key)) return "auto";
|
|
1945
|
-
return null;
|
|
1946
|
-
}
|
|
1947
|
-
function preflightClaudeModel(model, defaultModel) {
|
|
1948
|
-
const requested = (model ?? "").trim();
|
|
1949
|
-
if (!requested) {
|
|
1950
|
-
return { ok: true, model: defaultModel, normalized: false };
|
|
1951
|
-
}
|
|
1952
|
-
const stripped = stripReasoningSuffix(requested).trim();
|
|
1953
|
-
const launch = stripped || defaultModel;
|
|
1954
|
-
if (FOREIGN_MODEL_RE.test(launch) || !looksLikeClaudeModel(launch) && launch !== defaultModel) {
|
|
1955
|
-
return {
|
|
1956
|
-
ok: false,
|
|
1957
|
-
model: requested,
|
|
1958
|
-
normalized: false,
|
|
1959
|
-
requested,
|
|
1960
|
-
note: `model "${requested}" is not a Claude model \u2014 the "claude" provider drives the Claude CLI, which only accepts claude-* model ids (got "${launch}"). Pick a Claude model or switch the worker provider.`
|
|
1961
|
-
};
|
|
1962
|
-
}
|
|
1963
|
-
if (launch !== requested) {
|
|
1964
|
-
return {
|
|
1965
|
-
ok: true,
|
|
1966
|
-
model: launch,
|
|
1967
|
-
normalized: true,
|
|
1968
|
-
requested,
|
|
1969
|
-
note: `normalized model "${requested}" \u2192 "${launch}" (the Claude CLI rejects reasoning-effort suffixes)`
|
|
1970
|
-
};
|
|
1971
|
-
}
|
|
1972
|
-
return { ok: true, model: launch, normalized: false };
|
|
1973
|
-
}
|
|
1974
|
-
function preflightCursorModel(model, defaultModel) {
|
|
1975
|
-
const requested = (model ?? "").trim();
|
|
1976
|
-
if (!requested) {
|
|
1977
|
-
return { ok: true, model: defaultModel, normalized: false };
|
|
1978
|
-
}
|
|
1979
|
-
const aliasLaunch = normalizeCursorModelAlias(requested);
|
|
1980
|
-
if (aliasLaunch) {
|
|
1981
|
-
return {
|
|
1982
|
-
ok: true,
|
|
1983
|
-
model: aliasLaunch,
|
|
1984
|
-
normalized: true,
|
|
1985
|
-
requested,
|
|
1986
|
-
note: `normalized model "${requested}" \u2192 "${aliasLaunch}" (Cursor provider alias \u2014 use "auto" or a composer id, not "cursor")`
|
|
1987
|
-
};
|
|
1988
|
-
}
|
|
1989
|
-
if (looksLikeClaudeModel(requested)) {
|
|
1990
|
-
return {
|
|
1991
|
-
ok: false,
|
|
1992
|
-
model: requested,
|
|
1993
|
-
normalized: false,
|
|
1994
|
-
requested,
|
|
1995
|
-
note: `model "${requested}" is a Claude model but the worker provider is "cursor". Switch the provider to "claude" or pick a Cursor model.`
|
|
1996
|
-
};
|
|
1997
|
-
}
|
|
1998
|
-
return { ok: true, model: requested, normalized: false };
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
// src/providers/claude.ts
|
|
2002
|
-
var CLAUDE_DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
2003
|
-
var claudeProvider = {
|
|
2004
|
-
name: "claude",
|
|
2005
|
-
defaultModel: CLAUDE_DEFAULT_MODEL,
|
|
2006
|
-
preflightModel(model) {
|
|
2007
|
-
return preflightClaudeModel(model, CLAUDE_DEFAULT_MODEL);
|
|
2008
|
-
},
|
|
2009
|
-
start(opts) {
|
|
2010
|
-
const preflight = preflightClaudeModel(opts.model, CLAUDE_DEFAULT_MODEL);
|
|
2011
|
-
if (!preflight.ok) {
|
|
2012
|
-
throw new Error(`claude provider model preflight failed: ${preflight.note}`);
|
|
2013
|
-
}
|
|
2014
|
-
const model = preflight.model;
|
|
2015
|
-
const stdoutFd = openSync(opts.stdoutPath, "a");
|
|
2016
|
-
const stderrFd = openSync(opts.stderrPath, "a");
|
|
2017
|
-
const stdio = ["ignore", stdoutFd, stderrFd];
|
|
2018
|
-
const child = spawn(
|
|
2019
|
-
"claude",
|
|
2020
|
-
[
|
|
2021
|
-
"--model",
|
|
2022
|
-
model,
|
|
2023
|
-
"-p",
|
|
2024
|
-
"--verbose",
|
|
2025
|
-
"--permission-mode",
|
|
2026
|
-
"bypassPermissions",
|
|
2027
|
-
"--output-format",
|
|
2028
|
-
"stream-json",
|
|
2029
|
-
"--include-partial-messages",
|
|
2030
|
-
opts.prompt
|
|
2031
|
-
],
|
|
2032
|
-
hiddenSpawnOptions({
|
|
2033
|
-
cwd: opts.worktreePath,
|
|
2034
|
-
detached: true,
|
|
2035
|
-
stdio,
|
|
2036
|
-
env: scrubWorkerEnv(process.env)
|
|
2037
|
-
})
|
|
2038
|
-
);
|
|
2039
|
-
closeSync(stdoutFd);
|
|
2040
|
-
closeSync(stderrFd);
|
|
2041
|
-
if (!child.pid) {
|
|
2042
|
-
throw new Error("failed to spawn claude worker process (is the `claude` CLI on PATH?)");
|
|
2043
|
-
}
|
|
2044
|
-
child.unref();
|
|
2045
|
-
return { pid: child.pid, model };
|
|
2046
|
-
}
|
|
2047
|
-
};
|
|
2048
|
-
|
|
2049
2009
|
// src/worker-provider-policy.ts
|
|
2050
2010
|
var DEFAULT_WORKER_PROVIDER = "cursor";
|
|
2051
2011
|
var CLAUDE_FAMILY = /* @__PURE__ */ new Set(["claude", "opus", "anthropic"]);
|
|
@@ -2109,75 +2069,959 @@ function resolveConfiguredWorkerProvider(configured, fallback = DEFAULT_WORKER_P
|
|
|
2109
2069
|
const trimmed = configured?.trim();
|
|
2110
2070
|
if (!trimmed) return fallback;
|
|
2111
2071
|
if (isClaudeFamilyProvider(trimmed)) return DEFAULT_WORKER_PROVIDER;
|
|
2072
|
+
if (trimmed === "codex") return "codex";
|
|
2112
2073
|
return trimmed;
|
|
2113
2074
|
}
|
|
2114
2075
|
|
|
2115
|
-
// src/
|
|
2116
|
-
var
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2076
|
+
// src/orchestration-providers/capabilities.ts
|
|
2077
|
+
var CAPABILITIES = {
|
|
2078
|
+
codex: {
|
|
2079
|
+
id: "codex",
|
|
2080
|
+
displayName: "Codex (BYO OAuth / Hermes openai-codex)",
|
|
2081
|
+
costTier: "low",
|
|
2082
|
+
authSources: ["oauth_local", "api_key_env", "subscription_hermes"],
|
|
2083
|
+
supportsLowRiskOrchestration: true,
|
|
2084
|
+
supportsPrivilegedPlatformActions: false
|
|
2085
|
+
},
|
|
2086
|
+
cursor: {
|
|
2087
|
+
id: "cursor",
|
|
2088
|
+
displayName: "Cursor / Composer",
|
|
2089
|
+
costTier: "medium",
|
|
2090
|
+
authSources: ["oauth_local", "api_key_env"],
|
|
2091
|
+
supportsLowRiskOrchestration: true,
|
|
2092
|
+
supportsPrivilegedPlatformActions: true
|
|
2093
|
+
},
|
|
2094
|
+
claude: {
|
|
2095
|
+
id: "claude",
|
|
2096
|
+
displayName: "Claude Code",
|
|
2097
|
+
costTier: "high",
|
|
2098
|
+
authSources: ["oauth_local"],
|
|
2099
|
+
supportsLowRiskOrchestration: true,
|
|
2100
|
+
supportsPrivilegedPlatformActions: true
|
|
2101
|
+
}
|
|
2102
|
+
};
|
|
2103
|
+
function getOrchestrationProviderCapability(id) {
|
|
2104
|
+
return CAPABILITIES[id];
|
|
2124
2105
|
}
|
|
2125
|
-
function
|
|
2126
|
-
|
|
2127
|
-
if (fromConfig) return fromConfig;
|
|
2128
|
-
const fromEnv = process.env.KYNVER_DEFAULT_MODEL?.trim();
|
|
2129
|
-
if (fromEnv) return fromEnv;
|
|
2130
|
-
return GLOBAL_DEFAULT_MODEL;
|
|
2106
|
+
function listOrchestrationProviderCapabilities() {
|
|
2107
|
+
return Object.values(CAPABILITIES);
|
|
2131
2108
|
}
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2109
|
+
|
|
2110
|
+
// src/orchestration-providers/codex-oauth-binding.ts
|
|
2111
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
2112
|
+
import { homedir as homedir5 } from "node:os";
|
|
2113
|
+
import path8 from "node:path";
|
|
2114
|
+
|
|
2115
|
+
// src/orchestration-providers/oauth-binding-utils.ts
|
|
2116
|
+
import { createHash } from "node:crypto";
|
|
2117
|
+
import { statSync as statSync3 } from "node:fs";
|
|
2118
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2119
|
+
function fingerprintAuthStore(filePath) {
|
|
2120
|
+
try {
|
|
2121
|
+
const st = statSync3(filePath);
|
|
2122
|
+
const material = `${filePath}|${st.size}|${st.mtimeMs}`;
|
|
2123
|
+
return createHash("sha256").update(material).digest("hex").slice(0, 16);
|
|
2124
|
+
} catch {
|
|
2125
|
+
return void 0;
|
|
2137
2126
|
}
|
|
2138
|
-
|
|
2139
|
-
|
|
2127
|
+
}
|
|
2128
|
+
function envKeyFingerprint(envKey) {
|
|
2129
|
+
return createHash("sha256").update(`env:${envKey}`).digest("hex").slice(0, 16);
|
|
2130
|
+
}
|
|
2131
|
+
function resolveCliBin(defaultBin, envKeys) {
|
|
2132
|
+
for (const key of envKeys) {
|
|
2133
|
+
const value = process.env[key]?.trim();
|
|
2134
|
+
if (value) return value;
|
|
2140
2135
|
}
|
|
2141
|
-
return
|
|
2136
|
+
return defaultBin;
|
|
2142
2137
|
}
|
|
2143
|
-
function
|
|
2144
|
-
const
|
|
2145
|
-
|
|
2146
|
-
|
|
2138
|
+
function probeCliCommand(bin, args, options = {}) {
|
|
2139
|
+
const timeoutMs = options.timeoutMs ?? 8e3;
|
|
2140
|
+
try {
|
|
2141
|
+
const result = spawnSync2(bin, args, {
|
|
2142
|
+
encoding: "utf8",
|
|
2143
|
+
timeout: timeoutMs,
|
|
2144
|
+
env: { ...process.env, CI: "1", NO_COLOR: "1" }
|
|
2145
|
+
});
|
|
2146
|
+
if (result.status === 0) {
|
|
2147
|
+
return { ok: true, note: options.okNote ?? `${bin} ${args.join(" ")}: ok` };
|
|
2148
|
+
}
|
|
2149
|
+
const stderr = (result.stderr || result.stdout || "").trim();
|
|
2150
|
+
const prefix = options.failPrefix ?? `${bin} ${args.join(" ")} failed`;
|
|
2147
2151
|
return {
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
rule: provider && provider !== "cursor" ? "explicit:model_provider_alias_overrode_provider" : "explicit:model_provider_alias",
|
|
2151
|
-
requestedModel: model
|
|
2152
|
+
ok: false,
|
|
2153
|
+
note: stderr ? `${prefix}: ${stderr.slice(0, 200)}` : prefix
|
|
2152
2154
|
};
|
|
2153
|
-
}
|
|
2154
|
-
if (alias === "claude" || alias === "anthropic") {
|
|
2155
|
+
} catch (err) {
|
|
2155
2156
|
return {
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
rule: provider && provider !== "claude" ? "explicit:model_provider_alias_overrode_provider" : "explicit:model_provider_alias",
|
|
2159
|
-
requestedModel: model
|
|
2157
|
+
ok: false,
|
|
2158
|
+
note: err instanceof Error ? err.message : `${bin} probe failed`
|
|
2160
2159
|
};
|
|
2161
2160
|
}
|
|
2162
|
-
return null;
|
|
2163
2161
|
}
|
|
2164
|
-
function
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2162
|
+
function probeCliExitCodeOnly(bin, args, options = {}) {
|
|
2163
|
+
const timeoutMs = options.timeoutMs ?? 8e3;
|
|
2164
|
+
try {
|
|
2165
|
+
const result = spawnSync2(bin, args, {
|
|
2166
|
+
stdio: "ignore",
|
|
2167
|
+
timeout: timeoutMs,
|
|
2168
|
+
env: { ...process.env, CI: "1", NO_COLOR: "1" }
|
|
2169
|
+
});
|
|
2170
|
+
if (result.status === 0) {
|
|
2171
|
+
return { ok: true, note: options.okNote ?? `${bin} authenticated` };
|
|
2172
|
+
}
|
|
2173
|
+
return { ok: false, note: options.failNote ?? `${bin} not authenticated` };
|
|
2174
|
+
} catch (err) {
|
|
2175
|
+
return {
|
|
2176
|
+
ok: false,
|
|
2177
|
+
note: err instanceof Error ? err.message : `${bin} auth probe failed`
|
|
2178
|
+
};
|
|
2171
2179
|
}
|
|
2172
|
-
return false;
|
|
2173
2180
|
}
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
const
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
+
|
|
2182
|
+
// src/orchestration-providers/codex-oauth-binding.ts
|
|
2183
|
+
var DEFAULT_CODEX_BIN = "codex";
|
|
2184
|
+
function authStoreCandidates() {
|
|
2185
|
+
const home = homedir5();
|
|
2186
|
+
return [
|
|
2187
|
+
path8.join(home, ".codex", "auth.json"),
|
|
2188
|
+
path8.join(home, ".config", "codex", "auth.json")
|
|
2189
|
+
];
|
|
2190
|
+
}
|
|
2191
|
+
function probeCodexOAuthBinding(nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2192
|
+
const bin = resolveCliBin(DEFAULT_CODEX_BIN, ["KYNVER_CODEX_BIN", "CODEX_BIN"]);
|
|
2193
|
+
if (process.env.CODEX_API_KEY?.trim()) {
|
|
2194
|
+
return {
|
|
2195
|
+
providerId: "codex",
|
|
2196
|
+
ready: true,
|
|
2197
|
+
authSource: "api_key_env",
|
|
2198
|
+
sessionFingerprint: envKeyFingerprint("CODEX_API_KEY"),
|
|
2199
|
+
checkedAt: nowIso,
|
|
2200
|
+
note: "CODEX_API_KEY present (env binding \u2014 cloud credits path, no token stored in Kynver)"
|
|
2201
|
+
};
|
|
2202
|
+
}
|
|
2203
|
+
const authPath = authStoreCandidates().find((p) => existsSync9(p));
|
|
2204
|
+
const login = probeCliCommand(bin, ["login", "status"], {
|
|
2205
|
+
okNote: "codex login status: authenticated",
|
|
2206
|
+
failPrefix: "codex login status"
|
|
2207
|
+
});
|
|
2208
|
+
const ready = Boolean(authPath) && login.ok;
|
|
2209
|
+
return {
|
|
2210
|
+
providerId: "codex",
|
|
2211
|
+
ready,
|
|
2212
|
+
authSource: "oauth_local",
|
|
2213
|
+
sessionFingerprint: authPath ? fingerprintAuthStore(authPath) : void 0,
|
|
2214
|
+
checkedAt: nowIso,
|
|
2215
|
+
note: ready ? "Local Codex OAuth session bound on runner" : [login.note, authPath ? void 0 : "no ~/.codex/auth.json"].filter(Boolean).join("; ")
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
// src/orchestration-providers/hermes-openai-codex-binding.ts
|
|
2220
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
2221
|
+
import { homedir as homedir6 } from "node:os";
|
|
2222
|
+
import path9 from "node:path";
|
|
2223
|
+
function hermesAuthStorePath() {
|
|
2224
|
+
const configured = process.env.HERMES_HOME?.trim();
|
|
2225
|
+
const home = configured ? path9.resolve(configured) : path9.join(homedir6(), ".hermes");
|
|
2226
|
+
return path9.join(home, "auth.json");
|
|
2227
|
+
}
|
|
2228
|
+
function probeHermesOpenAiCodexBinding(nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2229
|
+
const bin = resolveCliBin("hermes", ["KYNVER_HERMES_BIN", "HERMES_BIN"]);
|
|
2230
|
+
const cli = probeCliCommand(bin, ["--version"], {
|
|
2231
|
+
okNote: `${bin} CLI available`,
|
|
2232
|
+
failPrefix: `${bin} CLI not available`
|
|
2233
|
+
});
|
|
2234
|
+
const authPath = hermesAuthStorePath();
|
|
2235
|
+
const authStorePresent = existsSync10(authPath);
|
|
2236
|
+
const auth = probeCliExitCodeOnly(bin, ["auth", "status", "openai-codex"], {
|
|
2237
|
+
okNote: "hermes openai-codex: logged in",
|
|
2238
|
+
failNote: "hermes openai-codex: not logged in"
|
|
2239
|
+
});
|
|
2240
|
+
const ready = cli.ok && auth.ok;
|
|
2241
|
+
const authSource = ready ? "subscription_hermes" : "none";
|
|
2242
|
+
return {
|
|
2243
|
+
path: "hermes_openai_codex",
|
|
2244
|
+
ready,
|
|
2245
|
+
authSource,
|
|
2246
|
+
sessionFingerprint: authStorePresent ? fingerprintAuthStore(authPath) : void 0,
|
|
2247
|
+
checkedAt: nowIso,
|
|
2248
|
+
note: ready ? "Hermes openai-codex subscription session bound (ChatGPT Codex OAuth)" : [cli.note, auth.note, authStorePresent ? void 0 : "no ~/.hermes/auth.json"].filter(Boolean).join("; ")
|
|
2249
|
+
};
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
// src/orchestration-providers/codex-orchestration-adapter.ts
|
|
2253
|
+
function resolveCodexOrchestrationAdapter(nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2254
|
+
const cliBinding = probeCodexOAuthBinding(nowIso);
|
|
2255
|
+
if (cliBinding.ready) {
|
|
2256
|
+
return { ...cliBinding, path: "codex_cli" };
|
|
2257
|
+
}
|
|
2258
|
+
const hermes = probeHermesOpenAiCodexBinding(nowIso);
|
|
2259
|
+
if (hermes.ready) {
|
|
2260
|
+
return {
|
|
2261
|
+
providerId: "codex",
|
|
2262
|
+
ready: true,
|
|
2263
|
+
authSource: hermes.authSource,
|
|
2264
|
+
sessionFingerprint: hermes.sessionFingerprint,
|
|
2265
|
+
checkedAt: hermes.checkedAt,
|
|
2266
|
+
note: hermes.note,
|
|
2267
|
+
path: "hermes_openai_codex",
|
|
2268
|
+
hermesOpenAiCodex: hermes
|
|
2269
|
+
};
|
|
2270
|
+
}
|
|
2271
|
+
return {
|
|
2272
|
+
providerId: "codex",
|
|
2273
|
+
ready: false,
|
|
2274
|
+
authSource: "none",
|
|
2275
|
+
checkedAt: nowIso,
|
|
2276
|
+
path: "none",
|
|
2277
|
+
hermesOpenAiCodex: hermes,
|
|
2278
|
+
note: [cliBinding.note, hermes.note].filter(Boolean).join("; ") || "Codex CLI and Hermes openai-codex unavailable"
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
// src/orchestration-providers/cost-router.ts
|
|
2283
|
+
var COST_TIER_ORDER = ["low", "medium", "high"];
|
|
2284
|
+
var AUTH_SOURCE_RANK = {
|
|
2285
|
+
oauth_local: 0,
|
|
2286
|
+
subscription_hermes: 0,
|
|
2287
|
+
runner_token: 1,
|
|
2288
|
+
api_key_env: 2,
|
|
2289
|
+
none: 3
|
|
2290
|
+
};
|
|
2291
|
+
function providerCapableForRisk(providerId, riskClass) {
|
|
2292
|
+
const cap = getOrchestrationProviderCapability(providerId);
|
|
2293
|
+
if (riskClass === "privileged") return cap.supportsPrivilegedPlatformActions;
|
|
2294
|
+
if (riskClass === "low") return cap.supportsLowRiskOrchestration;
|
|
2295
|
+
return true;
|
|
2296
|
+
}
|
|
2297
|
+
function compareProviderCandidates(a, b) {
|
|
2298
|
+
const capA = getOrchestrationProviderCapability(a.providerId);
|
|
2299
|
+
const capB = getOrchestrationProviderCapability(b.providerId);
|
|
2300
|
+
const tierDiff = COST_TIER_ORDER.indexOf(capA.costTier) - COST_TIER_ORDER.indexOf(capB.costTier);
|
|
2301
|
+
if (tierDiff !== 0) return tierDiff;
|
|
2302
|
+
const authDiff = AUTH_SOURCE_RANK[a.binding.authSource] - AUTH_SOURCE_RANK[b.binding.authSource];
|
|
2303
|
+
if (authDiff !== 0) return authDiff;
|
|
2304
|
+
return a.providerId.localeCompare(b.providerId);
|
|
2305
|
+
}
|
|
2306
|
+
function selectCheapestCapableProvider(input) {
|
|
2307
|
+
const candidates = listOrchestrationProviderCapabilities().map((cap) => ({
|
|
2308
|
+
providerId: cap.id,
|
|
2309
|
+
binding: input.inventory.bindings[cap.id]
|
|
2310
|
+
})).filter(
|
|
2311
|
+
(c) => c.binding.ready && providerCapableForRisk(c.providerId, input.riskClass)
|
|
2312
|
+
);
|
|
2313
|
+
if (candidates.length === 0) return null;
|
|
2314
|
+
const sorted = [...candidates].sort(compareProviderCandidates);
|
|
2315
|
+
const chosen = sorted[0];
|
|
2316
|
+
const lowTierProviderId = "codex";
|
|
2317
|
+
if (chosen.providerId === lowTierProviderId) {
|
|
2318
|
+
return { providerId: chosen.providerId, binding: chosen.binding };
|
|
2319
|
+
}
|
|
2320
|
+
const lowBinding = input.inventory.bindings[lowTierProviderId];
|
|
2321
|
+
let escalatedReason;
|
|
2322
|
+
if (!providerCapableForRisk(lowTierProviderId, input.riskClass)) {
|
|
2323
|
+
escalatedReason = `${input.riskClass} orchestration requires privileged-capable provider`;
|
|
2324
|
+
} else if (!lowBinding.ready) {
|
|
2325
|
+
escalatedReason = lowBinding.note ?? `${lowTierProviderId} not bound`;
|
|
2326
|
+
}
|
|
2327
|
+
return {
|
|
2328
|
+
providerId: chosen.providerId,
|
|
2329
|
+
binding: chosen.binding,
|
|
2330
|
+
escalatedFrom: lowTierProviderId,
|
|
2331
|
+
escalatedReason
|
|
2332
|
+
};
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
// src/orchestration-providers/claude-oauth-binding.ts
|
|
2336
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
2337
|
+
import { homedir as homedir7 } from "node:os";
|
|
2338
|
+
import path10 from "node:path";
|
|
2339
|
+
var DEFAULT_CLAUDE_BIN = "claude";
|
|
2340
|
+
function claudeAuthStoreCandidates() {
|
|
2341
|
+
const home = homedir7();
|
|
2342
|
+
return [path10.join(home, ".claude", ".credentials.json")];
|
|
2343
|
+
}
|
|
2344
|
+
function probeClaudeOAuthBinding(nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2345
|
+
const bin = resolveCliBin(DEFAULT_CLAUDE_BIN, ["KYNVER_CLAUDE_BIN", "CLAUDE_BIN"]);
|
|
2346
|
+
if (process.env.ANTHROPIC_API_KEY?.trim()) {
|
|
2347
|
+
return {
|
|
2348
|
+
providerId: "claude",
|
|
2349
|
+
ready: true,
|
|
2350
|
+
authSource: "api_key_env",
|
|
2351
|
+
sessionFingerprint: envKeyFingerprint("ANTHROPIC_API_KEY"),
|
|
2352
|
+
checkedAt: nowIso,
|
|
2353
|
+
note: "ANTHROPIC_API_KEY present (env binding \u2014 cloud credits path, no token stored in Kynver)"
|
|
2354
|
+
};
|
|
2355
|
+
}
|
|
2356
|
+
const cli = probeCliCommand(bin, ["--version"], {
|
|
2357
|
+
okNote: `${bin} CLI available`,
|
|
2358
|
+
failPrefix: `${bin} CLI not available`
|
|
2359
|
+
});
|
|
2360
|
+
const authPath = claudeAuthStoreCandidates().find((p) => existsSync11(p));
|
|
2361
|
+
const login = probeCliExitCodeOnly(bin, ["auth", "status"], {
|
|
2362
|
+
okNote: "claude auth status: authenticated",
|
|
2363
|
+
failNote: "claude auth status: not authenticated"
|
|
2364
|
+
});
|
|
2365
|
+
const ready = cli.ok && (login.ok || Boolean(authPath));
|
|
2366
|
+
return {
|
|
2367
|
+
providerId: "claude",
|
|
2368
|
+
ready,
|
|
2369
|
+
authSource: "oauth_local",
|
|
2370
|
+
sessionFingerprint: authPath ? fingerprintAuthStore(authPath) : void 0,
|
|
2371
|
+
checkedAt: nowIso,
|
|
2372
|
+
note: ready ? "Local Claude Code OAuth session bound on runner" : [cli.note, login.note, authPath ? void 0 : "no ~/.claude/.credentials.json"].filter(Boolean).join("; ")
|
|
2373
|
+
};
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
// src/orchestration-providers/cursor-oauth-binding.ts
|
|
2377
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
2378
|
+
import { homedir as homedir8 } from "node:os";
|
|
2379
|
+
import path11 from "node:path";
|
|
2380
|
+
var DEFAULT_AGENT_BIN = "agent";
|
|
2381
|
+
function cursorAuthStoreCandidates() {
|
|
2382
|
+
const home = homedir8();
|
|
2383
|
+
return [path11.join(home, ".config", "cursor", "auth.json")];
|
|
2384
|
+
}
|
|
2385
|
+
function probeCursorOAuthBinding(nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2386
|
+
const bin = resolveCliBin(DEFAULT_AGENT_BIN, [
|
|
2387
|
+
"KYNVER_CURSOR_AGENT_BIN",
|
|
2388
|
+
"CURSOR_AGENT_BIN"
|
|
2389
|
+
]);
|
|
2390
|
+
if (process.env.CURSOR_API_KEY?.trim()) {
|
|
2391
|
+
return {
|
|
2392
|
+
providerId: "cursor",
|
|
2393
|
+
ready: true,
|
|
2394
|
+
authSource: "api_key_env",
|
|
2395
|
+
sessionFingerprint: envKeyFingerprint("CURSOR_API_KEY"),
|
|
2396
|
+
checkedAt: nowIso,
|
|
2397
|
+
note: "CURSOR_API_KEY present (env binding \u2014 cloud credits path, no token stored in Kynver)"
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
const cli = probeCliCommand(bin, ["-v"], {
|
|
2401
|
+
okNote: `${bin} CLI available`,
|
|
2402
|
+
failPrefix: `${bin} CLI not available`
|
|
2403
|
+
});
|
|
2404
|
+
const authPath = cursorAuthStoreCandidates().find((p) => existsSync12(p));
|
|
2405
|
+
const ready = Boolean(authPath) && cli.ok;
|
|
2406
|
+
return {
|
|
2407
|
+
providerId: "cursor",
|
|
2408
|
+
ready,
|
|
2409
|
+
authSource: "oauth_local",
|
|
2410
|
+
sessionFingerprint: authPath ? fingerprintAuthStore(authPath) : void 0,
|
|
2411
|
+
checkedAt: nowIso,
|
|
2412
|
+
note: ready ? "Local Cursor/Composer OAuth session bound on runner" : [cli.note, authPath ? void 0 : "no ~/.config/cursor/auth.json"].filter(Boolean).join("; ")
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
// src/orchestration-providers/hermes-cli-adapter.ts
|
|
2417
|
+
import { existsSync as existsSync13, readFileSync as readFileSync8 } from "node:fs";
|
|
2418
|
+
import { homedir as homedir9 } from "node:os";
|
|
2419
|
+
import path12 from "node:path";
|
|
2420
|
+
var PROFILE_ENV_KEYS = [
|
|
2421
|
+
"OPENAI_API_KEY",
|
|
2422
|
+
"ANTHROPIC_API_KEY",
|
|
2423
|
+
"KYNVER_API_KEY",
|
|
2424
|
+
"KYNVER_BASE_URL"
|
|
2425
|
+
];
|
|
2426
|
+
function hermesProfileEnvPath() {
|
|
2427
|
+
const configured = process.env.HERMES_HOME?.trim();
|
|
2428
|
+
const home = configured ? path12.resolve(configured) : path12.join(homedir9(), ".hermes");
|
|
2429
|
+
return path12.join(home, ".env");
|
|
2430
|
+
}
|
|
2431
|
+
function profileEnvKeyPresence(envPath) {
|
|
2432
|
+
try {
|
|
2433
|
+
const text = readFileSync8(envPath, "utf8");
|
|
2434
|
+
const present = [];
|
|
2435
|
+
for (const key of PROFILE_ENV_KEYS) {
|
|
2436
|
+
const re = new RegExp(`^${key}=`, "m");
|
|
2437
|
+
if (re.test(text)) present.push(key);
|
|
2438
|
+
}
|
|
2439
|
+
return present;
|
|
2440
|
+
} catch {
|
|
2441
|
+
return [];
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
function probeHermesCliAdapter(nowIso = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2445
|
+
const bin = resolveCliBin("hermes", ["KYNVER_HERMES_BIN", "HERMES_BIN"]);
|
|
2446
|
+
const cli = probeCliCommand(bin, ["--version"], {
|
|
2447
|
+
okNote: `${bin} CLI available`,
|
|
2448
|
+
failPrefix: `${bin} CLI not available`
|
|
2449
|
+
});
|
|
2450
|
+
const profilePath = hermesProfileEnvPath();
|
|
2451
|
+
const profileBound = existsSync13(profilePath);
|
|
2452
|
+
const envKeys = profileBound ? profileEnvKeyPresence(profilePath) : [];
|
|
2453
|
+
const hasApiKeys = envKeys.some((k) => k.endsWith("_API_KEY"));
|
|
2454
|
+
const authSource = hasApiKeys ? "api_key_env" : profileBound ? "oauth_local" : "none";
|
|
2455
|
+
const ready = cli.ok && profileBound;
|
|
2456
|
+
return {
|
|
2457
|
+
adapterId: "hermes",
|
|
2458
|
+
cliAvailable: cli.ok,
|
|
2459
|
+
profileBound,
|
|
2460
|
+
authSource,
|
|
2461
|
+
sessionFingerprint: profileBound ? fingerprintAuthStore(profilePath) : void 0,
|
|
2462
|
+
checkedAt: nowIso,
|
|
2463
|
+
note: ready ? `Hermes profile bound (${envKeys.length ? `keys: ${envKeys.join(", ")}` : "no API keys detected"})` : [cli.note, profileBound ? void 0 : "no ~/.hermes/.env"].filter(Boolean).join("; ")
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
// src/orchestration-providers/inventory.ts
|
|
2468
|
+
function buildOrchestrationProviderInventory(options = {}) {
|
|
2469
|
+
const generatedAt = options.nowIso?.() ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
2470
|
+
const codexAdapter = resolveCodexOrchestrationAdapter(generatedAt);
|
|
2471
|
+
const bindings = {
|
|
2472
|
+
codex: codexAdapter,
|
|
2473
|
+
cursor: probeCursorOAuthBinding(generatedAt),
|
|
2474
|
+
claude: probeClaudeOAuthBinding(generatedAt)
|
|
2475
|
+
};
|
|
2476
|
+
const readyCount = Object.values(bindings).filter((b) => b.ready).length;
|
|
2477
|
+
return {
|
|
2478
|
+
generatedAt,
|
|
2479
|
+
bindings,
|
|
2480
|
+
cloudApiCredits: {
|
|
2481
|
+
anthropicApiKey: Boolean(process.env.ANTHROPIC_API_KEY?.trim()),
|
|
2482
|
+
openaiApiKey: Boolean(process.env.OPENAI_API_KEY?.trim()),
|
|
2483
|
+
codexApiKey: Boolean(process.env.CODEX_API_KEY?.trim())
|
|
2484
|
+
},
|
|
2485
|
+
hermes: probeHermesCliAdapter(generatedAt),
|
|
2486
|
+
hermesOpenAiCodex: probeHermesOpenAiCodexBinding(generatedAt),
|
|
2487
|
+
codexAdapterPath: codexAdapter.path,
|
|
2488
|
+
readyCount
|
|
2489
|
+
};
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
// src/orchestration-providers/routing.ts
|
|
2493
|
+
var PRIVILEGED_MARKERS = [
|
|
2494
|
+
/\bprivileged\b/i,
|
|
2495
|
+
/\bproduction\s+db\b/i,
|
|
2496
|
+
/\bdb:push\b/i,
|
|
2497
|
+
/\bmigration\b/i,
|
|
2498
|
+
/\bdeploy\b/i,
|
|
2499
|
+
/\bsecrets?\b/i,
|
|
2500
|
+
/\bstripe\b/i,
|
|
2501
|
+
/\[require-approval\]/i,
|
|
2502
|
+
/\[operator-only\]/i
|
|
2503
|
+
];
|
|
2504
|
+
var LOW_RISK_MARKERS = [
|
|
2505
|
+
/\borchestration\b/i,
|
|
2506
|
+
/\bplan[- ]?progress\b/i,
|
|
2507
|
+
/\bstatus\s+sync\b/i,
|
|
2508
|
+
/\bheartbeat\b/i,
|
|
2509
|
+
/\bboard[- ]?drain\b/i,
|
|
2510
|
+
/\bmaintenance\b/i,
|
|
2511
|
+
/\[orchestration:\s*low-risk\]/i
|
|
2512
|
+
];
|
|
2513
|
+
function taskString3(task, key) {
|
|
2514
|
+
const v = task[key];
|
|
2515
|
+
return typeof v === "string" ? v.trim() : "";
|
|
2516
|
+
}
|
|
2517
|
+
function classifyOrchestrationRisk(task) {
|
|
2518
|
+
const ref = taskString3(task, "executorRef").toLowerCase();
|
|
2519
|
+
const title = taskString3(task, "title").toLowerCase();
|
|
2520
|
+
const description = taskString3(task, "description").toLowerCase();
|
|
2521
|
+
const blob = `${ref}
|
|
2522
|
+
${title}
|
|
2523
|
+
${description}`;
|
|
2524
|
+
if (PRIVILEGED_MARKERS.some((re) => re.test(blob))) return "privileged";
|
|
2525
|
+
if (ref.includes("provider:claude") || ref.includes("deep_review") || ref.includes("security")) {
|
|
2526
|
+
return "elevated";
|
|
2527
|
+
}
|
|
2528
|
+
if (ref.includes("landing") || ref.includes("review") || title.startsWith("land:") || ref.includes("implementer") || ref.includes("repair") || title.includes("implement")) {
|
|
2529
|
+
return "elevated";
|
|
2530
|
+
}
|
|
2531
|
+
if (LOW_RISK_MARKERS.some((re) => re.test(blob))) {
|
|
2532
|
+
return "low";
|
|
2533
|
+
}
|
|
2534
|
+
return "elevated";
|
|
2535
|
+
}
|
|
2536
|
+
function buildAudit(input) {
|
|
2537
|
+
const cap = getOrchestrationProviderCapability(input.provider);
|
|
2538
|
+
const costRationale = input.escalatedFrom && input.escalatedReason ? `Escalated from ${input.escalatedFrom} (${input.escalatedReason}); using ${cap.displayName} (${cap.costTier} tier)` : `${cap.displayName} selected for ${input.riskClass} orchestration (${cap.costTier} tier, auth=${input.authSource})`;
|
|
2539
|
+
return {
|
|
2540
|
+
provider: input.provider,
|
|
2541
|
+
model: input.model,
|
|
2542
|
+
authSource: input.authSource,
|
|
2543
|
+
costTier: cap.costTier,
|
|
2544
|
+
riskClass: input.riskClass,
|
|
2545
|
+
costRationale,
|
|
2546
|
+
routingRule: input.routingRule,
|
|
2547
|
+
escalatedFrom: input.escalatedFrom,
|
|
2548
|
+
escalatedReason: input.escalatedReason
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
function resolveInventory(input) {
|
|
2552
|
+
const inventory = input.inventory ?? buildOrchestrationProviderInventory();
|
|
2553
|
+
if (!input.codexBinding) return inventory;
|
|
2554
|
+
return {
|
|
2555
|
+
...inventory,
|
|
2556
|
+
bindings: { ...inventory.bindings, codex: input.codexBinding }
|
|
2557
|
+
};
|
|
2558
|
+
}
|
|
2559
|
+
function decisionForProvider(input) {
|
|
2560
|
+
const audit = buildAudit({
|
|
2561
|
+
provider: input.providerId,
|
|
2562
|
+
model: input.model,
|
|
2563
|
+
authSource: input.binding.authSource,
|
|
2564
|
+
riskClass: input.riskClass,
|
|
2565
|
+
routingRule: input.routingRule,
|
|
2566
|
+
escalatedFrom: input.escalatedFrom,
|
|
2567
|
+
escalatedReason: input.escalatedReason
|
|
2568
|
+
});
|
|
2569
|
+
return {
|
|
2570
|
+
provider: input.providerId,
|
|
2571
|
+
model: input.model,
|
|
2572
|
+
rule: audit.routingRule,
|
|
2573
|
+
audit
|
|
2574
|
+
};
|
|
2575
|
+
}
|
|
2576
|
+
function resolveOrchestrationRouting(input) {
|
|
2577
|
+
const task = input.task ?? {};
|
|
2578
|
+
const risk = classifyOrchestrationRisk(task);
|
|
2579
|
+
const inventory = resolveInventory({
|
|
2580
|
+
inventory: input.inventory,
|
|
2581
|
+
codexBinding: input.codexBinding ?? resolveCodexOrchestrationAdapter()
|
|
2582
|
+
});
|
|
2583
|
+
const explicit = input.preferLowCost === false ? null : input.explicitProvider?.trim().toLowerCase();
|
|
2584
|
+
const explicitModel = input.explicitModel?.trim() || void 0;
|
|
2585
|
+
const fallbackProvider = DEFAULT_WORKER_PROVIDER;
|
|
2586
|
+
if (explicit === "codex" || explicit === "provider:codex") {
|
|
2587
|
+
const binding = inventory.bindings.codex;
|
|
2588
|
+
if (!binding.ready) {
|
|
2589
|
+
const selection2 = selectCheapestCapableProvider({ inventory, riskClass: risk });
|
|
2590
|
+
if (selection2) {
|
|
2591
|
+
return decisionForProvider({
|
|
2592
|
+
providerId: selection2.providerId,
|
|
2593
|
+
binding: selection2.binding,
|
|
2594
|
+
riskClass: risk,
|
|
2595
|
+
routingRule: "orchestration:explicit_codex_unavailable_cost_aware",
|
|
2596
|
+
model: explicitModel,
|
|
2597
|
+
escalatedFrom: "codex",
|
|
2598
|
+
escalatedReason: binding.note ?? "codex oauth not bound"
|
|
2599
|
+
});
|
|
2600
|
+
}
|
|
2601
|
+
return decisionForProvider({
|
|
2602
|
+
providerId: fallbackProvider,
|
|
2603
|
+
binding: inventory.bindings[fallbackProvider],
|
|
2604
|
+
riskClass: risk,
|
|
2605
|
+
routingRule: "orchestration:codex_unavailable_escalate_cursor",
|
|
2606
|
+
model: explicitModel,
|
|
2607
|
+
escalatedFrom: "codex",
|
|
2608
|
+
escalatedReason: binding.note ?? "codex oauth not bound"
|
|
2609
|
+
});
|
|
2610
|
+
}
|
|
2611
|
+
return decisionForProvider({
|
|
2612
|
+
providerId: "codex",
|
|
2613
|
+
binding,
|
|
2614
|
+
riskClass: risk,
|
|
2615
|
+
routingRule: "orchestration:explicit_codex",
|
|
2616
|
+
model: explicitModel
|
|
2617
|
+
});
|
|
2618
|
+
}
|
|
2619
|
+
if (explicit === "cursor" || explicit === "provider:cursor") {
|
|
2620
|
+
const binding = inventory.bindings.cursor;
|
|
2621
|
+
if (binding.ready) {
|
|
2622
|
+
return decisionForProvider({
|
|
2623
|
+
providerId: "cursor",
|
|
2624
|
+
binding,
|
|
2625
|
+
riskClass: risk,
|
|
2626
|
+
routingRule: "orchestration:explicit_cursor",
|
|
2627
|
+
model: explicitModel
|
|
2628
|
+
});
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
if (explicit === "claude" || explicit === "provider:claude") {
|
|
2632
|
+
const binding = inventory.bindings.claude;
|
|
2633
|
+
if (binding.ready) {
|
|
2634
|
+
return decisionForProvider({
|
|
2635
|
+
providerId: "claude",
|
|
2636
|
+
binding,
|
|
2637
|
+
riskClass: risk,
|
|
2638
|
+
routingRule: "orchestration:explicit_claude",
|
|
2639
|
+
model: explicitModel
|
|
2640
|
+
});
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
const selection = selectCheapestCapableProvider({ inventory, riskClass: risk });
|
|
2644
|
+
if (selection) {
|
|
2645
|
+
return decisionForProvider({
|
|
2646
|
+
providerId: selection.providerId,
|
|
2647
|
+
binding: selection.binding,
|
|
2648
|
+
riskClass: risk,
|
|
2649
|
+
routingRule: `orchestration:cost_aware_${selection.providerId}`,
|
|
2650
|
+
model: explicitModel,
|
|
2651
|
+
escalatedFrom: selection.escalatedFrom,
|
|
2652
|
+
escalatedReason: selection.escalatedReason
|
|
2653
|
+
});
|
|
2654
|
+
}
|
|
2655
|
+
const codexBinding = inventory.bindings.codex;
|
|
2656
|
+
return decisionForProvider({
|
|
2657
|
+
providerId: fallbackProvider,
|
|
2658
|
+
binding: inventory.bindings[fallbackProvider],
|
|
2659
|
+
riskClass: risk,
|
|
2660
|
+
routingRule: codexBinding.ready ? "orchestration:inventory_empty_default_cursor" : "orchestration:codex_unavailable_default_cursor",
|
|
2661
|
+
model: explicitModel,
|
|
2662
|
+
...codexBinding.ready ? {} : { escalatedFrom: "codex", escalatedReason: codexBinding.note ?? "codex oauth not bound" }
|
|
2663
|
+
});
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2666
|
+
// src/providers/claude.ts
|
|
2667
|
+
import { closeSync, openSync } from "node:fs";
|
|
2668
|
+
import { spawn } from "node:child_process";
|
|
2669
|
+
|
|
2670
|
+
// src/providers/model-preflight.ts
|
|
2671
|
+
var REASONING_SUFFIX_RE = /-(?:thinking(?:-(?:high|medium|low|minimal|max|none))?|high|medium|low|minimal)$/i;
|
|
2672
|
+
function stripReasoningSuffix(model) {
|
|
2673
|
+
return model.replace(REASONING_SUFFIX_RE, "");
|
|
2674
|
+
}
|
|
2675
|
+
var FOREIGN_MODEL_RE = /^(?:gpt-|gpt5|o1|o3|o4|gemini-|grok-|composer|deepseek|llama|mistral|qwen|command-)/i;
|
|
2676
|
+
function looksLikeClaudeModel(model) {
|
|
2677
|
+
return /^claude[-_]/i.test(model) || /^(?:opus|sonnet|haiku)\b/i.test(model);
|
|
2678
|
+
}
|
|
2679
|
+
var CURSOR_MODEL_ALIASES = /* @__PURE__ */ new Set(["cursor"]);
|
|
2680
|
+
function normalizeCursorModelAlias(model) {
|
|
2681
|
+
const key = model.trim().toLowerCase();
|
|
2682
|
+
if (CURSOR_MODEL_ALIASES.has(key)) return "auto";
|
|
2683
|
+
return null;
|
|
2684
|
+
}
|
|
2685
|
+
function preflightClaudeModel(model, defaultModel) {
|
|
2686
|
+
const requested = (model ?? "").trim();
|
|
2687
|
+
if (!requested) {
|
|
2688
|
+
return { ok: true, model: defaultModel, normalized: false };
|
|
2689
|
+
}
|
|
2690
|
+
const stripped = stripReasoningSuffix(requested).trim();
|
|
2691
|
+
const launch = stripped || defaultModel;
|
|
2692
|
+
if (FOREIGN_MODEL_RE.test(launch) || !looksLikeClaudeModel(launch) && launch !== defaultModel) {
|
|
2693
|
+
return {
|
|
2694
|
+
ok: false,
|
|
2695
|
+
model: requested,
|
|
2696
|
+
normalized: false,
|
|
2697
|
+
requested,
|
|
2698
|
+
note: `model "${requested}" is not a Claude model \u2014 the "claude" provider drives the Claude CLI, which only accepts claude-* model ids (got "${launch}"). Pick a Claude model or switch the worker provider.`
|
|
2699
|
+
};
|
|
2700
|
+
}
|
|
2701
|
+
if (launch !== requested) {
|
|
2702
|
+
return {
|
|
2703
|
+
ok: true,
|
|
2704
|
+
model: launch,
|
|
2705
|
+
normalized: true,
|
|
2706
|
+
requested,
|
|
2707
|
+
note: `normalized model "${requested}" \u2192 "${launch}" (the Claude CLI rejects reasoning-effort suffixes)`
|
|
2708
|
+
};
|
|
2709
|
+
}
|
|
2710
|
+
return { ok: true, model: launch, normalized: false };
|
|
2711
|
+
}
|
|
2712
|
+
function preflightCodexModel(model, defaultModel) {
|
|
2713
|
+
const requested = (model ?? "").trim();
|
|
2714
|
+
if (!requested) {
|
|
2715
|
+
return { ok: true, model: defaultModel, normalized: false };
|
|
2716
|
+
}
|
|
2717
|
+
if (looksLikeClaudeModel(requested)) {
|
|
2718
|
+
return {
|
|
2719
|
+
ok: false,
|
|
2720
|
+
model: requested,
|
|
2721
|
+
normalized: false,
|
|
2722
|
+
requested,
|
|
2723
|
+
note: `model "${requested}" is a Claude model but the worker provider is "codex". Switch the provider or pick a Codex/OpenAI model.`
|
|
2724
|
+
};
|
|
2725
|
+
}
|
|
2726
|
+
return { ok: true, model: requested, normalized: false };
|
|
2727
|
+
}
|
|
2728
|
+
function preflightCursorModel(model, defaultModel) {
|
|
2729
|
+
const requested = (model ?? "").trim();
|
|
2730
|
+
if (!requested) {
|
|
2731
|
+
return { ok: true, model: defaultModel, normalized: false };
|
|
2732
|
+
}
|
|
2733
|
+
const aliasLaunch = normalizeCursorModelAlias(requested);
|
|
2734
|
+
if (aliasLaunch) {
|
|
2735
|
+
return {
|
|
2736
|
+
ok: true,
|
|
2737
|
+
model: aliasLaunch,
|
|
2738
|
+
normalized: true,
|
|
2739
|
+
requested,
|
|
2740
|
+
note: `normalized model "${requested}" \u2192 "${aliasLaunch}" (Cursor provider alias \u2014 use "auto" or a composer id, not "cursor")`
|
|
2741
|
+
};
|
|
2742
|
+
}
|
|
2743
|
+
if (looksLikeClaudeModel(requested)) {
|
|
2744
|
+
return {
|
|
2745
|
+
ok: false,
|
|
2746
|
+
model: requested,
|
|
2747
|
+
normalized: false,
|
|
2748
|
+
requested,
|
|
2749
|
+
note: `model "${requested}" is a Claude model but the worker provider is "cursor". Switch the provider to "claude" or pick a Cursor model.`
|
|
2750
|
+
};
|
|
2751
|
+
}
|
|
2752
|
+
return { ok: true, model: requested, normalized: false };
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
// src/providers/claude.ts
|
|
2756
|
+
var CLAUDE_DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
2757
|
+
var claudeProvider = {
|
|
2758
|
+
name: "claude",
|
|
2759
|
+
defaultModel: CLAUDE_DEFAULT_MODEL,
|
|
2760
|
+
preflightModel(model) {
|
|
2761
|
+
return preflightClaudeModel(model, CLAUDE_DEFAULT_MODEL);
|
|
2762
|
+
},
|
|
2763
|
+
start(opts) {
|
|
2764
|
+
const preflight = preflightClaudeModel(opts.model, CLAUDE_DEFAULT_MODEL);
|
|
2765
|
+
if (!preflight.ok) {
|
|
2766
|
+
throw new Error(`claude provider model preflight failed: ${preflight.note}`);
|
|
2767
|
+
}
|
|
2768
|
+
const model = preflight.model;
|
|
2769
|
+
const stdoutFd = openSync(opts.stdoutPath, "a");
|
|
2770
|
+
const stderrFd = openSync(opts.stderrPath, "a");
|
|
2771
|
+
const stdio = ["ignore", stdoutFd, stderrFd];
|
|
2772
|
+
const child = spawn(
|
|
2773
|
+
"claude",
|
|
2774
|
+
[
|
|
2775
|
+
"--model",
|
|
2776
|
+
model,
|
|
2777
|
+
"-p",
|
|
2778
|
+
"--verbose",
|
|
2779
|
+
"--permission-mode",
|
|
2780
|
+
"bypassPermissions",
|
|
2781
|
+
"--output-format",
|
|
2782
|
+
"stream-json",
|
|
2783
|
+
"--include-partial-messages",
|
|
2784
|
+
opts.prompt
|
|
2785
|
+
],
|
|
2786
|
+
hiddenSpawnOptions({
|
|
2787
|
+
cwd: opts.worktreePath,
|
|
2788
|
+
detached: true,
|
|
2789
|
+
stdio,
|
|
2790
|
+
env: scrubWorkerEnv(process.env)
|
|
2791
|
+
})
|
|
2792
|
+
);
|
|
2793
|
+
closeSync(stdoutFd);
|
|
2794
|
+
closeSync(stderrFd);
|
|
2795
|
+
if (!child.pid) {
|
|
2796
|
+
throw new Error("failed to spawn claude worker process (is the `claude` CLI on PATH?)");
|
|
2797
|
+
}
|
|
2798
|
+
child.unref();
|
|
2799
|
+
return { pid: child.pid, model };
|
|
2800
|
+
}
|
|
2801
|
+
};
|
|
2802
|
+
|
|
2803
|
+
// src/providers/codex.ts
|
|
2804
|
+
import { closeSync as closeSync3, existsSync as existsSync14, openSync as openSync3 } from "node:fs";
|
|
2805
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
2806
|
+
|
|
2807
|
+
// src/providers/hermes-codex.ts
|
|
2808
|
+
import { closeSync as closeSync2, openSync as openSync2 } from "node:fs";
|
|
2809
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
2810
|
+
var HERMES_OPENAI_CODEX_DEFAULT_MODEL = "gpt-5.4";
|
|
2811
|
+
function hermesWorkerEnv() {
|
|
2812
|
+
return scrubWorkerEnv({
|
|
2813
|
+
...process.env,
|
|
2814
|
+
CI: "1",
|
|
2815
|
+
NO_COLOR: "1",
|
|
2816
|
+
HERMES_ACCEPT_HOOKS: process.env.HERMES_ACCEPT_HOOKS ?? "1"
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
function buildHermesOpenAiCodexChatArgv(model, prompt) {
|
|
2820
|
+
const maxTurns = process.env.KYNVER_HERMES_CODEX_MAX_TURNS?.trim() || "40";
|
|
2821
|
+
return [
|
|
2822
|
+
"chat",
|
|
2823
|
+
"-q",
|
|
2824
|
+
prompt,
|
|
2825
|
+
"--provider",
|
|
2826
|
+
"openai-codex",
|
|
2827
|
+
"-m",
|
|
2828
|
+
model,
|
|
2829
|
+
"-Q",
|
|
2830
|
+
"--accept-hooks",
|
|
2831
|
+
"--max-turns",
|
|
2832
|
+
maxTurns,
|
|
2833
|
+
"--toolsets",
|
|
2834
|
+
"hermes-cli"
|
|
2835
|
+
];
|
|
2836
|
+
}
|
|
2837
|
+
var hermesCodexProvider = {
|
|
2838
|
+
name: "hermes-codex",
|
|
2839
|
+
defaultModel: HERMES_OPENAI_CODEX_DEFAULT_MODEL,
|
|
2840
|
+
preflightModel(model) {
|
|
2841
|
+
return preflightCodexModel(model, HERMES_OPENAI_CODEX_DEFAULT_MODEL);
|
|
2842
|
+
},
|
|
2843
|
+
start(opts) {
|
|
2844
|
+
const preflight = preflightCodexModel(opts.model, HERMES_OPENAI_CODEX_DEFAULT_MODEL);
|
|
2845
|
+
if (!preflight.ok) {
|
|
2846
|
+
throw new Error(`hermes-codex provider model preflight failed: ${preflight.note}`);
|
|
2847
|
+
}
|
|
2848
|
+
const model = preflight.model;
|
|
2849
|
+
const stdoutFd = openSync2(opts.stdoutPath, "a");
|
|
2850
|
+
const stderrFd = openSync2(opts.stderrPath, "a");
|
|
2851
|
+
const stdio = ["ignore", stdoutFd, stderrFd];
|
|
2852
|
+
const bin = resolveCliBin("hermes", ["KYNVER_HERMES_BIN", "HERMES_BIN"]);
|
|
2853
|
+
const args = buildHermesOpenAiCodexChatArgv(model, opts.prompt);
|
|
2854
|
+
const child = spawn2(
|
|
2855
|
+
bin,
|
|
2856
|
+
args,
|
|
2857
|
+
hiddenSpawnOptions({
|
|
2858
|
+
cwd: opts.worktreePath,
|
|
2859
|
+
detached: true,
|
|
2860
|
+
shell: false,
|
|
2861
|
+
stdio,
|
|
2862
|
+
env: hermesWorkerEnv()
|
|
2863
|
+
})
|
|
2864
|
+
);
|
|
2865
|
+
closeSync2(stdoutFd);
|
|
2866
|
+
closeSync2(stderrFd);
|
|
2867
|
+
if (!child.pid) {
|
|
2868
|
+
throw new Error(
|
|
2869
|
+
`failed to spawn Hermes openai-codex worker (is \`${bin}\` on PATH? run \`hermes auth status openai-codex\`)`
|
|
2870
|
+
);
|
|
2871
|
+
}
|
|
2872
|
+
child.unref();
|
|
2873
|
+
return { pid: child.pid, model };
|
|
2874
|
+
}
|
|
2875
|
+
};
|
|
2876
|
+
|
|
2877
|
+
// src/providers/codex.ts
|
|
2878
|
+
var CODEX_DEFAULT_MODEL = "gpt-5.4";
|
|
2879
|
+
function resolveCodexBin() {
|
|
2880
|
+
return process.env.KYNVER_CODEX_BIN?.trim() || process.env.CODEX_BIN?.trim() || "codex";
|
|
2881
|
+
}
|
|
2882
|
+
function codexWorkerEnv() {
|
|
2883
|
+
return scrubWorkerEnv({
|
|
2884
|
+
...process.env,
|
|
2885
|
+
CI: "1",
|
|
2886
|
+
NO_COLOR: "1"
|
|
2887
|
+
});
|
|
2888
|
+
}
|
|
2889
|
+
function buildCodexExecArgv(model, prompt) {
|
|
2890
|
+
return [
|
|
2891
|
+
"exec",
|
|
2892
|
+
"--sandbox",
|
|
2893
|
+
"read-only",
|
|
2894
|
+
"--ask-for-approval",
|
|
2895
|
+
"never",
|
|
2896
|
+
"--model",
|
|
2897
|
+
model,
|
|
2898
|
+
prompt
|
|
2899
|
+
];
|
|
2900
|
+
}
|
|
2901
|
+
function spawnCodexProcess(bin, args, opts) {
|
|
2902
|
+
const useScript = process.platform !== "win32" && existsSync14("/usr/bin/script");
|
|
2903
|
+
if (!useScript) {
|
|
2904
|
+
return spawn3(bin, args, opts);
|
|
2905
|
+
}
|
|
2906
|
+
const quoted = [bin, ...args].map((a) => `'${a.replace(/'/g, `'\\''`)}'`).join(" ");
|
|
2907
|
+
return spawn3("script", ["-qfc", quoted, "/dev/null"], opts);
|
|
2908
|
+
}
|
|
2909
|
+
var codexProvider = {
|
|
2910
|
+
name: "codex",
|
|
2911
|
+
defaultModel: CODEX_DEFAULT_MODEL,
|
|
2912
|
+
preflightModel(model) {
|
|
2913
|
+
return preflightCodexModel(model, CODEX_DEFAULT_MODEL);
|
|
2914
|
+
},
|
|
2915
|
+
start(opts) {
|
|
2916
|
+
const adapter = resolveCodexOrchestrationAdapter();
|
|
2917
|
+
if (adapter.path === "hermes_openai_codex") {
|
|
2918
|
+
return hermesCodexProvider.start(opts);
|
|
2919
|
+
}
|
|
2920
|
+
const preflight = preflightCodexModel(opts.model, CODEX_DEFAULT_MODEL);
|
|
2921
|
+
if (!preflight.ok) {
|
|
2922
|
+
throw new Error(`codex provider model preflight failed: ${preflight.note}`);
|
|
2923
|
+
}
|
|
2924
|
+
const model = preflight.model;
|
|
2925
|
+
const stdoutFd = openSync3(opts.stdoutPath, "a");
|
|
2926
|
+
const stderrFd = openSync3(opts.stderrPath, "a");
|
|
2927
|
+
const stdio = ["ignore", stdoutFd, stderrFd];
|
|
2928
|
+
const bin = resolveCodexBin();
|
|
2929
|
+
const args = buildCodexExecArgv(model, opts.prompt);
|
|
2930
|
+
const child = spawnCodexProcess(
|
|
2931
|
+
bin,
|
|
2932
|
+
args,
|
|
2933
|
+
hiddenSpawnOptions({
|
|
2934
|
+
cwd: opts.worktreePath,
|
|
2935
|
+
detached: true,
|
|
2936
|
+
shell: false,
|
|
2937
|
+
stdio,
|
|
2938
|
+
env: codexWorkerEnv()
|
|
2939
|
+
})
|
|
2940
|
+
);
|
|
2941
|
+
closeSync3(stdoutFd);
|
|
2942
|
+
closeSync3(stderrFd);
|
|
2943
|
+
if (!child.pid) {
|
|
2944
|
+
throw new Error(
|
|
2945
|
+
`failed to spawn Codex worker (is \`${bin}\` on PATH? run \`codex login\` or set CODEX_API_KEY)`
|
|
2946
|
+
);
|
|
2947
|
+
}
|
|
2948
|
+
child.unref();
|
|
2949
|
+
return { pid: child.pid, model };
|
|
2950
|
+
}
|
|
2951
|
+
};
|
|
2952
|
+
|
|
2953
|
+
// src/model-routing.ts
|
|
2954
|
+
var GLOBAL_DEFAULT_MODEL = "claude-sonnet-4-6";
|
|
2955
|
+
var CURSOR_DEFAULT_MODEL = "composer-2.5";
|
|
2956
|
+
function taskString4(task, key) {
|
|
2957
|
+
const v = task[key];
|
|
2958
|
+
return typeof v === "string" ? v.trim() : "";
|
|
2959
|
+
}
|
|
2960
|
+
function normalizeRef(ref) {
|
|
2961
|
+
return ref.toLowerCase();
|
|
2962
|
+
}
|
|
2963
|
+
function resolveGlobalDefaultModel(config = loadUserConfig()) {
|
|
2964
|
+
const fromConfig = config.defaultModel?.trim();
|
|
2965
|
+
if (fromConfig) return fromConfig;
|
|
2966
|
+
const fromEnv = process.env.KYNVER_DEFAULT_MODEL?.trim();
|
|
2967
|
+
if (fromEnv) return fromEnv;
|
|
2968
|
+
return GLOBAL_DEFAULT_MODEL;
|
|
2969
|
+
}
|
|
2970
|
+
function inferProviderFromModel(model) {
|
|
2971
|
+
const m = (model ?? "").toLowerCase();
|
|
2972
|
+
if (!m) return "cursor";
|
|
2973
|
+
if (m.includes("composer") || m.includes("cursor") || m.includes("codex") || m.startsWith("gpt-") || m.startsWith("gpt5")) {
|
|
2974
|
+
return "cursor";
|
|
2975
|
+
}
|
|
2976
|
+
if (/^claude[-_]/i.test(m) || /^(?:opus|sonnet|haiku)\b/i.test(m)) {
|
|
2977
|
+
return "claude";
|
|
2978
|
+
}
|
|
2979
|
+
return "cursor";
|
|
2980
|
+
}
|
|
2981
|
+
function normalizeProviderAliasModel(model, explicitProvider) {
|
|
2982
|
+
const alias = model.trim().toLowerCase();
|
|
2983
|
+
const provider = explicitProvider?.trim();
|
|
2984
|
+
if (alias === "cursor") {
|
|
2985
|
+
return {
|
|
2986
|
+
model: CURSOR_DEFAULT_MODEL,
|
|
2987
|
+
provider: "cursor",
|
|
2988
|
+
rule: provider && provider !== "cursor" ? "explicit:model_provider_alias_overrode_provider" : "explicit:model_provider_alias",
|
|
2989
|
+
requestedModel: model
|
|
2990
|
+
};
|
|
2991
|
+
}
|
|
2992
|
+
if (alias === "claude" || alias === "anthropic") {
|
|
2993
|
+
return {
|
|
2994
|
+
model: CLAUDE_DEFAULT_MODEL,
|
|
2995
|
+
provider: "claude",
|
|
2996
|
+
rule: provider && provider !== "claude" ? "explicit:model_provider_alias_overrode_provider" : "explicit:model_provider_alias",
|
|
2997
|
+
requestedModel: model
|
|
2998
|
+
};
|
|
2999
|
+
}
|
|
3000
|
+
return null;
|
|
3001
|
+
}
|
|
3002
|
+
function isOpusLane(ref, title) {
|
|
3003
|
+
if (ref.includes("deep") && ref.includes("review")) return true;
|
|
3004
|
+
if (ref.includes("security")) return true;
|
|
3005
|
+
if (ref.includes("plan_author") || ref.includes("plan-author")) return true;
|
|
3006
|
+
if (title.includes("deep review") || title.includes("security review")) return true;
|
|
3007
|
+
if (ref.includes("plan") && !ref.includes("review") && (ref.includes("author") || ref.includes("strategy"))) {
|
|
3008
|
+
return true;
|
|
3009
|
+
}
|
|
3010
|
+
return false;
|
|
3011
|
+
}
|
|
3012
|
+
function inferModelRoutingFromTask(task) {
|
|
3013
|
+
const ref = normalizeRef(taskString4(task, "executorRef"));
|
|
3014
|
+
const title = taskString4(task, "title").toLowerCase();
|
|
3015
|
+
const priority = taskString4(task, "priority") || "normal";
|
|
3016
|
+
const roleLane = normalizeRef(taskString4(task, "roleLane"));
|
|
3017
|
+
if (ref.includes("provider:codex") || ref.startsWith("codex:")) {
|
|
3018
|
+
return { provider: "codex", model: CODEX_DEFAULT_MODEL, rule: "lane:codex_orchestration" };
|
|
3019
|
+
}
|
|
3020
|
+
if (ref.includes("cursor") || ref.includes("composer") || ref.includes("copilot") || roleLane === "implementer" || roleLane === "repair_implementer") {
|
|
3021
|
+
return { provider: "cursor", rule: "lane:implementation" };
|
|
3022
|
+
}
|
|
3023
|
+
if (ref.includes("codex")) {
|
|
3024
|
+
return { provider: "codex", model: CODEX_DEFAULT_MODEL, rule: "lane:codex_orchestration" };
|
|
2181
3025
|
}
|
|
2182
3026
|
if (ref.includes("landing") || title.startsWith("land:") || title.includes(" merge")) {
|
|
2183
3027
|
return { provider: "cursor", rule: "lane:landing" };
|
|
@@ -2237,12 +3081,33 @@ function resolveWorkerLaunch(input) {
|
|
|
2237
3081
|
requestedModel: model
|
|
2238
3082
|
};
|
|
2239
3083
|
}
|
|
2240
|
-
|
|
3084
|
+
const afterCursorPolicy = enforceCursorWorkerProvider({
|
|
2241
3085
|
routing: decision,
|
|
2242
3086
|
task: input.task,
|
|
2243
3087
|
explicitProvider: input.explicitProvider,
|
|
2244
3088
|
explicitProviderIsOperatorOverride: input.explicitProviderIsOperatorOverride
|
|
2245
3089
|
});
|
|
3090
|
+
if (!input.task || Object.keys(input.task).length === 0) {
|
|
3091
|
+
return afterCursorPolicy;
|
|
3092
|
+
}
|
|
3093
|
+
if (afterCursorPolicy.rule === "explicit:model_provider_alias" || afterCursorPolicy.rule === "explicit:model_provider_alias_overrode_provider") {
|
|
3094
|
+
return afterCursorPolicy;
|
|
3095
|
+
}
|
|
3096
|
+
if (isClaudeFamilyProvider(afterCursorPolicy.provider) && (input.explicitProviderIsOperatorOverride || taskAllowsClaudeWorker(input.task))) {
|
|
3097
|
+
return afterCursorPolicy;
|
|
3098
|
+
}
|
|
3099
|
+
const orchestration = resolveOrchestrationRouting({
|
|
3100
|
+
task: input.task,
|
|
3101
|
+
explicitProvider: input.explicitProvider ?? afterCursorPolicy.provider,
|
|
3102
|
+
explicitModel: afterCursorPolicy.model
|
|
3103
|
+
});
|
|
3104
|
+
return {
|
|
3105
|
+
provider: orchestration.provider,
|
|
3106
|
+
model: orchestration.provider === "codex" ? orchestration.model ?? afterCursorPolicy.model ?? CODEX_DEFAULT_MODEL : afterCursorPolicy.model,
|
|
3107
|
+
rule: orchestration.rule,
|
|
3108
|
+
requestedModel: afterCursorPolicy.requestedModel,
|
|
3109
|
+
orchestrationAudit: orchestration.audit
|
|
3110
|
+
};
|
|
2246
3111
|
}
|
|
2247
3112
|
function resolveModelFallback(startedModel, launchModel, providerDefault) {
|
|
2248
3113
|
return startedModel || launchModel || providerDefault || resolveGlobalDefaultModel() || CLAUDE_DEFAULT_MODEL;
|
|
@@ -2256,16 +3121,69 @@ function positiveInt2(value, fallback) {
|
|
|
2256
3121
|
}
|
|
2257
3122
|
function readHarnessRetryLimits() {
|
|
2258
3123
|
return {
|
|
2259
|
-
maxTaskAttempts: positiveInt2(process.env.KYNVER_MAX_TASK_ATTEMPTS,
|
|
3124
|
+
maxTaskAttempts: positiveInt2(process.env.KYNVER_MAX_TASK_ATTEMPTS, 4),
|
|
2260
3125
|
dispatchCooldownMs: positiveInt2(process.env.KYNVER_DISPATCH_COOLDOWN_MS, 5e3)
|
|
2261
3126
|
};
|
|
2262
3127
|
}
|
|
2263
3128
|
|
|
2264
3129
|
// src/lease-renewal.ts
|
|
2265
|
-
import
|
|
3130
|
+
import path13 from "node:path";
|
|
3131
|
+
|
|
3132
|
+
// src/harness-lease-owner.ts
|
|
3133
|
+
var HARNESS_LEASE_PREFIX = "kynver-harness:";
|
|
3134
|
+
var RUNNER_MARKER = "@runner:";
|
|
3135
|
+
function trimOrNull4(value) {
|
|
3136
|
+
if (!value?.trim()) return null;
|
|
3137
|
+
return value.trim();
|
|
3138
|
+
}
|
|
3139
|
+
function parseHarnessLeaseRunId(leaseOwner) {
|
|
3140
|
+
const owner = trimOrNull4(leaseOwner);
|
|
3141
|
+
if (!owner?.startsWith(HARNESS_LEASE_PREFIX)) return null;
|
|
3142
|
+
const body = owner.slice(HARNESS_LEASE_PREFIX.length);
|
|
3143
|
+
const atRunner = body.indexOf(RUNNER_MARKER);
|
|
3144
|
+
if (atRunner >= 0) return body.slice(0, atRunner).trim() || null;
|
|
3145
|
+
return body.trim() || null;
|
|
3146
|
+
}
|
|
3147
|
+
function formatHarnessLeaseOwner(runId, runnerId) {
|
|
3148
|
+
const run = runId.trim();
|
|
3149
|
+
const runner = runnerId.trim();
|
|
3150
|
+
return `${HARNESS_LEASE_PREFIX}${run}${RUNNER_MARKER}${runner}`;
|
|
3151
|
+
}
|
|
3152
|
+
function harnessLeaseOwnerMatchesRun(leaseOwner, runId) {
|
|
3153
|
+
const expectedRun = trimOrNull4(runId);
|
|
3154
|
+
if (!expectedRun) return false;
|
|
3155
|
+
return parseHarnessLeaseRunId(leaseOwner) === expectedRun;
|
|
3156
|
+
}
|
|
3157
|
+
function resolveHarnessLeaseOwnerForRenewal(input) {
|
|
3158
|
+
const stored = trimOrNull4(input.workerLeaseOwner);
|
|
3159
|
+
if (stored && harnessLeaseOwnerMatchesRun(stored, input.runId)) {
|
|
3160
|
+
return stored;
|
|
3161
|
+
}
|
|
3162
|
+
return formatHarnessLeaseOwner(input.runId, input.runnerId);
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
// src/runner-identity.ts
|
|
3166
|
+
import os3 from "node:os";
|
|
3167
|
+
function trimOrNull5(value) {
|
|
3168
|
+
if (!value?.trim()) return null;
|
|
3169
|
+
return value.trim();
|
|
3170
|
+
}
|
|
3171
|
+
function resolveRunnerPresencePayload(input = {}) {
|
|
3172
|
+
const env = input.env ?? process.env;
|
|
3173
|
+
const runnerId = trimOrNull5(env.KYNVER_RUNTIME_ID) ?? trimOrNull5(env.OPENCLAW_RUNTIME_ID) ?? trimOrNull5(env.HOSTNAME) ?? os3.hostname();
|
|
3174
|
+
return {
|
|
3175
|
+
runnerId,
|
|
3176
|
+
hostname: trimOrNull5(env.HOSTNAME) ?? os3.hostname(),
|
|
3177
|
+
profile: trimOrNull5(env.KYNVER_RUNNER_PROFILE) ?? trimOrNull5(env.OPENCLAW_RUNNER_PROFILE),
|
|
3178
|
+
harnessRepo: trimOrNull5(env.KYNVER_HARNESS_REPO) ?? trimOrNull5(env.KYNVER_DEFAULT_REPO),
|
|
3179
|
+
runId: input.runId ?? null
|
|
3180
|
+
};
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
// src/lease-renewal.ts
|
|
2266
3184
|
function workerRecord(runId, name) {
|
|
2267
3185
|
return readJson(
|
|
2268
|
-
|
|
3186
|
+
path13.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
2269
3187
|
void 0
|
|
2270
3188
|
);
|
|
2271
3189
|
}
|
|
@@ -2282,7 +3200,7 @@ async function renewActiveTaskLeases(runId, args) {
|
|
|
2282
3200
|
{ baseUrl: base }
|
|
2283
3201
|
);
|
|
2284
3202
|
const leaseDurationMs = Number(args.leaseMs) > 0 ? Math.floor(Number(args.leaseMs)) : DEFAULT_DISPATCH_LEASE_MS;
|
|
2285
|
-
const
|
|
3203
|
+
const runnerId = resolveRunnerPresencePayload({ runId }).runnerId;
|
|
2286
3204
|
const renewed = [];
|
|
2287
3205
|
const failed = [];
|
|
2288
3206
|
const skipped = [];
|
|
@@ -2301,11 +3219,20 @@ async function renewActiveTaskLeases(runId, args) {
|
|
|
2301
3219
|
skipped.push(name);
|
|
2302
3220
|
continue;
|
|
2303
3221
|
}
|
|
3222
|
+
const leaseOwner = resolveHarnessLeaseOwnerForRenewal({
|
|
3223
|
+
runId,
|
|
3224
|
+
workerLeaseOwner: worker.leaseOwner ?? null,
|
|
3225
|
+
runnerId
|
|
3226
|
+
});
|
|
2304
3227
|
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/${encodeURIComponent(worker.taskId)}/renew-lease`;
|
|
2305
3228
|
const res = await postJsonWithCredentialRefresh(
|
|
2306
3229
|
url,
|
|
2307
3230
|
secret,
|
|
2308
|
-
{
|
|
3231
|
+
{
|
|
3232
|
+
leaseOwner,
|
|
3233
|
+
leaseDurationMs,
|
|
3234
|
+
...worker.leaseToken ? { leaseToken: worker.leaseToken } : {}
|
|
3235
|
+
},
|
|
2309
3236
|
{ agentOsId, baseUrl: base }
|
|
2310
3237
|
);
|
|
2311
3238
|
if (res.ok) {
|
|
@@ -2331,20 +3258,20 @@ function hasLiveWorkerForTask(runId, taskId) {
|
|
|
2331
3258
|
}
|
|
2332
3259
|
|
|
2333
3260
|
// src/supervisor.ts
|
|
2334
|
-
import { existsSync as
|
|
2335
|
-
import
|
|
3261
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync3 } from "node:fs";
|
|
3262
|
+
import path19 from "node:path";
|
|
2336
3263
|
|
|
2337
3264
|
// src/harness-repair-target.ts
|
|
2338
3265
|
var HARNESS_CONTRACT_RE = /<!--\s*harness-contract:\s*(\{[\s\S]*?\})\s*-->/i;
|
|
2339
3266
|
var FIX_EXECUTOR_REF_PREFIX = "next-action-fix:";
|
|
2340
|
-
function
|
|
3267
|
+
function trimOrNull6(value) {
|
|
2341
3268
|
if (typeof value !== "string") return null;
|
|
2342
3269
|
const t = value.trim();
|
|
2343
3270
|
return t.length ? t : null;
|
|
2344
3271
|
}
|
|
2345
3272
|
function normalizePrUrl2(url) {
|
|
2346
3273
|
const m = url.trim().match(/github\.com\/([^/]+\/[^/]+)\/(?:pull|pulls)\/(\d+)/i);
|
|
2347
|
-
if (!m) return
|
|
3274
|
+
if (!m) return trimOrNull6(url);
|
|
2348
3275
|
return `https://github.com/${m[1]}/pull/${m[2]}`;
|
|
2349
3276
|
}
|
|
2350
3277
|
function isHarnessRepairTask(task) {
|
|
@@ -2366,10 +3293,10 @@ function parseRepairTargetContractFromDescription(description) {
|
|
|
2366
3293
|
if (!m?.[1]) return empty;
|
|
2367
3294
|
try {
|
|
2368
3295
|
const parsed = JSON.parse(m[1]);
|
|
2369
|
-
const url =
|
|
3296
|
+
const url = trimOrNull6(
|
|
2370
3297
|
String(parsed.targetPrUrl ?? parsed.target_pr_url ?? "")
|
|
2371
3298
|
);
|
|
2372
|
-
const branch =
|
|
3299
|
+
const branch = trimOrNull6(
|
|
2373
3300
|
String(parsed.targetPrBranch ?? parsed.target_pr_branch ?? "")
|
|
2374
3301
|
);
|
|
2375
3302
|
const enforce = parsed.repairEnforceOriginalPr === true || parsed.repair_enforce_original_pr === true;
|
|
@@ -2390,7 +3317,7 @@ function resolveHarnessRepairTargetFromTask(task) {
|
|
|
2390
3317
|
if (!targetPrUrl) return null;
|
|
2391
3318
|
return {
|
|
2392
3319
|
targetPrUrl,
|
|
2393
|
-
targetPrBranch: block.targetPrBranch ??
|
|
3320
|
+
targetPrBranch: block.targetPrBranch ?? trimOrNull6(task.branch)
|
|
2394
3321
|
};
|
|
2395
3322
|
}
|
|
2396
3323
|
function repairTargetPromptLines(target) {
|
|
@@ -2478,13 +3405,13 @@ function buildPrompt(input) {
|
|
|
2478
3405
|
}
|
|
2479
3406
|
|
|
2480
3407
|
// src/providers/cursor.ts
|
|
2481
|
-
import { closeSync as
|
|
2482
|
-
import { spawn as
|
|
2483
|
-
import
|
|
3408
|
+
import { closeSync as closeSync4, existsSync as existsSync16, openSync as openSync4 } from "node:fs";
|
|
3409
|
+
import { spawn as spawn4 } from "node:child_process";
|
|
3410
|
+
import path15 from "node:path";
|
|
2484
3411
|
|
|
2485
3412
|
// src/providers/cursor-windows.ts
|
|
2486
|
-
import { existsSync as
|
|
2487
|
-
import
|
|
3413
|
+
import { existsSync as existsSync15, readdirSync as readdirSync3 } from "node:fs";
|
|
3414
|
+
import path14 from "node:path";
|
|
2488
3415
|
var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
|
|
2489
3416
|
function parseCursorVersionSortKey(versionName) {
|
|
2490
3417
|
const datePart = versionName.split("-")[0];
|
|
@@ -2495,8 +3422,8 @@ function parseCursorVersionSortKey(versionName) {
|
|
|
2495
3422
|
return Number(`${year}${month.padStart(2, "0")}${day.padStart(2, "0")}`);
|
|
2496
3423
|
}
|
|
2497
3424
|
function pickLatestCursorVersionDir(agentRoot) {
|
|
2498
|
-
const versionsRoot =
|
|
2499
|
-
if (!
|
|
3425
|
+
const versionsRoot = path14.join(agentRoot, "versions");
|
|
3426
|
+
if (!existsSync15(versionsRoot)) return null;
|
|
2500
3427
|
let bestDir = null;
|
|
2501
3428
|
let bestKey = -1;
|
|
2502
3429
|
for (const entry of readdirSync3(versionsRoot, { withFileTypes: true })) {
|
|
@@ -2504,22 +3431,22 @@ function pickLatestCursorVersionDir(agentRoot) {
|
|
|
2504
3431
|
const key = parseCursorVersionSortKey(entry.name);
|
|
2505
3432
|
if (key == null || key <= bestKey) continue;
|
|
2506
3433
|
bestKey = key;
|
|
2507
|
-
bestDir =
|
|
3434
|
+
bestDir = path14.join(versionsRoot, entry.name);
|
|
2508
3435
|
}
|
|
2509
3436
|
return bestDir;
|
|
2510
3437
|
}
|
|
2511
3438
|
function resolveWindowsCursorBundled(agentRoot) {
|
|
2512
|
-
const root = agentRoot?.trim() ||
|
|
2513
|
-
const directNode =
|
|
2514
|
-
const directIndex =
|
|
2515
|
-
if (
|
|
3439
|
+
const root = agentRoot?.trim() || path14.join(process.env.LOCALAPPDATA || "", "cursor-agent");
|
|
3440
|
+
const directNode = path14.join(root, "node.exe");
|
|
3441
|
+
const directIndex = path14.join(root, "index.js");
|
|
3442
|
+
if (existsSync15(directNode) && existsSync15(directIndex)) {
|
|
2516
3443
|
return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
|
|
2517
3444
|
}
|
|
2518
3445
|
const versionDir = pickLatestCursorVersionDir(root);
|
|
2519
3446
|
if (!versionDir) return null;
|
|
2520
|
-
const nodeExe =
|
|
2521
|
-
const indexJs =
|
|
2522
|
-
if (!
|
|
3447
|
+
const nodeExe = path14.join(versionDir, "node.exe");
|
|
3448
|
+
const indexJs = path14.join(versionDir, "index.js");
|
|
3449
|
+
if (!existsSync15(nodeExe) || !existsSync15(indexJs)) return null;
|
|
2523
3450
|
return { nodeExe, indexJs, versionDir };
|
|
2524
3451
|
}
|
|
2525
3452
|
|
|
@@ -2537,13 +3464,13 @@ function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
|
|
|
2537
3464
|
function resolveCursorSpawn(agentBin) {
|
|
2538
3465
|
if (process.platform === "win32") {
|
|
2539
3466
|
const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
|
|
2540
|
-
const isBundledNode = /node\.exe$/i.test(agentBin) &&
|
|
3467
|
+
const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync16(path15.join(path15.dirname(agentBin), "index.js"));
|
|
2541
3468
|
const isDefaultShim = agentBin === "agent";
|
|
2542
3469
|
if (isCursorWrapper || isBundledNode || isDefaultShim) {
|
|
2543
|
-
const bundled = isCursorWrapper ? resolveWindowsCursorBundled(
|
|
3470
|
+
const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path15.dirname(agentBin)) : isBundledNode ? {
|
|
2544
3471
|
nodeExe: agentBin,
|
|
2545
|
-
indexJs:
|
|
2546
|
-
versionDir:
|
|
3472
|
+
indexJs: path15.join(path15.dirname(agentBin), "index.js"),
|
|
3473
|
+
versionDir: path15.dirname(agentBin)
|
|
2547
3474
|
} : resolveWindowsCursorBundled();
|
|
2548
3475
|
if (bundled) {
|
|
2549
3476
|
return bundledSpawnTarget(bundled.nodeExe, bundled.indexJs, bundled.versionDir);
|
|
@@ -2563,8 +3490,8 @@ function resolveAgentBin() {
|
|
|
2563
3490
|
process.env.KYNVER_CURSOR_AGENT_ROOT?.trim() || void 0
|
|
2564
3491
|
);
|
|
2565
3492
|
if (bundled) return bundled.nodeExe;
|
|
2566
|
-
const localAgent =
|
|
2567
|
-
if (
|
|
3493
|
+
const localAgent = path15.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
|
|
3494
|
+
if (existsSync16(localAgent)) return localAgent;
|
|
2568
3495
|
}
|
|
2569
3496
|
return "agent";
|
|
2570
3497
|
}
|
|
@@ -2573,7 +3500,7 @@ function cursorWorkerEnv(agentBin, spawnTarget) {
|
|
|
2573
3500
|
...process.env,
|
|
2574
3501
|
CI: "1",
|
|
2575
3502
|
NO_COLOR: "1",
|
|
2576
|
-
...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS:
|
|
3503
|
+
...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path15.basename(agentBin) || "agent.cmd" } : {}
|
|
2577
3504
|
});
|
|
2578
3505
|
}
|
|
2579
3506
|
var cursorProvider = {
|
|
@@ -2588,12 +3515,12 @@ var cursorProvider = {
|
|
|
2588
3515
|
throw new Error(`cursor provider model preflight failed: ${preflight.note}`);
|
|
2589
3516
|
}
|
|
2590
3517
|
const model = preflight.model;
|
|
2591
|
-
const stdoutFd =
|
|
2592
|
-
const stderrFd =
|
|
3518
|
+
const stdoutFd = openSync4(opts.stdoutPath, "a");
|
|
3519
|
+
const stderrFd = openSync4(opts.stderrPath, "a");
|
|
2593
3520
|
const stdio = ["ignore", stdoutFd, stderrFd];
|
|
2594
3521
|
const agentBin = resolveAgentBin();
|
|
2595
3522
|
const spawnTarget = resolveCursorSpawn(agentBin);
|
|
2596
|
-
const child =
|
|
3523
|
+
const child = spawn4(
|
|
2597
3524
|
spawnTarget.executable,
|
|
2598
3525
|
[
|
|
2599
3526
|
...spawnTarget.prefixArgs,
|
|
@@ -2617,8 +3544,8 @@ var cursorProvider = {
|
|
|
2617
3544
|
env: cursorWorkerEnv(agentBin, spawnTarget)
|
|
2618
3545
|
})
|
|
2619
3546
|
);
|
|
2620
|
-
|
|
2621
|
-
|
|
3547
|
+
closeSync4(stdoutFd);
|
|
3548
|
+
closeSync4(stderrFd);
|
|
2622
3549
|
if (!child.pid) {
|
|
2623
3550
|
throw new Error(
|
|
2624
3551
|
`failed to spawn Cursor agent worker (is \`${agentBin}\` on PATH? run \`agent login\` or set CURSOR_API_KEY)`
|
|
@@ -2632,6 +3559,7 @@ var cursorProvider = {
|
|
|
2632
3559
|
// src/providers/registry.ts
|
|
2633
3560
|
var BUILTIN = {
|
|
2634
3561
|
claude: claudeProvider,
|
|
3562
|
+
codex: codexProvider,
|
|
2635
3563
|
cursor: cursorProvider
|
|
2636
3564
|
};
|
|
2637
3565
|
var overrideProvider = null;
|
|
@@ -2654,9 +3582,9 @@ function resolveWorkerProvider(name) {
|
|
|
2654
3582
|
}
|
|
2655
3583
|
|
|
2656
3584
|
// src/auto-complete.ts
|
|
2657
|
-
import { spawn as
|
|
2658
|
-
import { existsSync as
|
|
2659
|
-
import
|
|
3585
|
+
import { spawn as spawn5 } from "node:child_process";
|
|
3586
|
+
import { existsSync as existsSync17, openSync as openSync5, closeSync as closeSync5 } from "node:fs";
|
|
3587
|
+
import path18 from "node:path";
|
|
2660
3588
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2661
3589
|
|
|
2662
3590
|
// src/completion-ack.ts
|
|
@@ -2673,7 +3601,7 @@ function persistCompletionAck(worker, runId, fields) {
|
|
|
2673
3601
|
}
|
|
2674
3602
|
|
|
2675
3603
|
// src/worker-ops.ts
|
|
2676
|
-
import
|
|
3604
|
+
import path17 from "node:path";
|
|
2677
3605
|
|
|
2678
3606
|
// src/completion-response.ts
|
|
2679
3607
|
function asRecord(value) {
|
|
@@ -2741,7 +3669,7 @@ var NO_PR_COMMITS_BETWEEN_RE = /no commits between/i;
|
|
|
2741
3669
|
function isGhNoCommitsBetweenError(detail) {
|
|
2742
3670
|
return Boolean(detail && NO_PR_COMMITS_BETWEEN_RE.test(detail));
|
|
2743
3671
|
}
|
|
2744
|
-
function
|
|
3672
|
+
function trimOrNull7(value) {
|
|
2745
3673
|
if (typeof value !== "string") return null;
|
|
2746
3674
|
const trimmed = value.trim();
|
|
2747
3675
|
return trimmed.length ? trimmed : null;
|
|
@@ -2749,7 +3677,7 @@ function trimOrNull5(value) {
|
|
|
2749
3677
|
function committedHead(ancestry) {
|
|
2750
3678
|
if (!ancestry?.checked) return null;
|
|
2751
3679
|
if (ancestry.headIsAncestorOfBase !== false) return null;
|
|
2752
|
-
return
|
|
3680
|
+
return trimOrNull7(ancestry.head);
|
|
2753
3681
|
}
|
|
2754
3682
|
function extractPrUrlFromText(value) {
|
|
2755
3683
|
if (value === void 0 || value === null) return null;
|
|
@@ -2757,7 +3685,7 @@ function extractPrUrlFromText(value) {
|
|
|
2757
3685
|
const m = text.match(
|
|
2758
3686
|
/https?:\/\/[^\s)>"]+\/(?:pull|pulls|merge_requests|pull-requests)\/\d+/i
|
|
2759
3687
|
);
|
|
2760
|
-
return m ?
|
|
3688
|
+
return m ? trimOrNull7(m[0]) : null;
|
|
2761
3689
|
}
|
|
2762
3690
|
function countCommitsAheadOfBase(worktreePath, baseRef, exec) {
|
|
2763
3691
|
const base = baseRef.trim();
|
|
@@ -2769,21 +3697,21 @@ function countCommitsAheadOfBase(worktreePath, baseRef, exec) {
|
|
|
2769
3697
|
}
|
|
2770
3698
|
function isReviewArtifactWorker(worker, snapshot) {
|
|
2771
3699
|
if (snapshot.changedFiles.length > 0) return false;
|
|
2772
|
-
const persona =
|
|
3700
|
+
const persona = trimOrNull7(worker.personaSlug)?.toLowerCase();
|
|
2773
3701
|
if (persona && REVIEW_PERSONA_SLUGS.has(persona)) return true;
|
|
2774
|
-
const rule =
|
|
3702
|
+
const rule = trimOrNull7(worker.routingRule) ?? "";
|
|
2775
3703
|
if (rule && REVIEW_LANE_RULE.test(rule)) return true;
|
|
2776
3704
|
return false;
|
|
2777
3705
|
}
|
|
2778
3706
|
function hasWorkProduct(snapshot, options) {
|
|
2779
3707
|
if (snapshot.changedFiles.length > 0) return true;
|
|
2780
|
-
const baseRef =
|
|
3708
|
+
const baseRef = trimOrNull7(options?.baseRef);
|
|
2781
3709
|
if (baseRef && options?.exec && options.worktreePath) {
|
|
2782
3710
|
const ahead = countCommitsAheadOfBase(options.worktreePath, baseRef, options.exec);
|
|
2783
3711
|
if (ahead === 0) return false;
|
|
2784
3712
|
if (ahead !== null && ahead > 0) return true;
|
|
2785
3713
|
}
|
|
2786
|
-
if (
|
|
3714
|
+
if (trimOrNull7(snapshot.headCommit)) return true;
|
|
2787
3715
|
if (committedHead(snapshot.gitAncestry)) return true;
|
|
2788
3716
|
return false;
|
|
2789
3717
|
}
|
|
@@ -2799,7 +3727,7 @@ function assessPrHandoffRequirement(input) {
|
|
|
2799
3727
|
})) {
|
|
2800
3728
|
return { required: false, reason: "expert_review_task" };
|
|
2801
3729
|
}
|
|
2802
|
-
const rule =
|
|
3730
|
+
const rule = trimOrNull7(input.routingRule) ?? "";
|
|
2803
3731
|
if (rule && REVIEW_LANE_RULE.test(rule)) {
|
|
2804
3732
|
return { required: false, reason: "review_lane" };
|
|
2805
3733
|
}
|
|
@@ -2810,14 +3738,14 @@ function assessPrHandoffRequirement(input) {
|
|
|
2810
3738
|
if (isReviewArtifactWorker(workerCtx, input.snapshot)) {
|
|
2811
3739
|
return { required: false, reason: "review_artifact" };
|
|
2812
3740
|
}
|
|
2813
|
-
if (
|
|
3741
|
+
if (trimOrNull7(input.patchPath) || trimOrNull7(input.artifactBundlePath)) {
|
|
2814
3742
|
return { required: false, reason: "patch_or_bundle" };
|
|
2815
3743
|
}
|
|
2816
|
-
const repairTarget =
|
|
3744
|
+
const repairTarget = trimOrNull7(input.repairTargetPrUrl);
|
|
2817
3745
|
if (repairTarget) {
|
|
2818
3746
|
return { required: false, reason: "repair_target_pr" };
|
|
2819
3747
|
}
|
|
2820
|
-
const prUrl =
|
|
3748
|
+
const prUrl = trimOrNull7(input.prUrl) ?? trimOrNull7(input.taskPrUrl) ?? trimOrNull7(input.snapshot.prUrl);
|
|
2821
3749
|
if (prUrl) {
|
|
2822
3750
|
return { required: false, reason: "already_has_pr" };
|
|
2823
3751
|
}
|
|
@@ -2838,13 +3766,13 @@ function buildPrHandoffSnapshotFromStatus(status, extras) {
|
|
|
2838
3766
|
worktreePath: status.worktreePath,
|
|
2839
3767
|
gitAncestry: status.gitAncestry,
|
|
2840
3768
|
finalResult: status.finalResult,
|
|
2841
|
-
headCommit:
|
|
2842
|
-
prUrl:
|
|
3769
|
+
headCommit: trimOrNull7(extras?.headCommit) ?? committedHead(status.gitAncestry),
|
|
3770
|
+
prUrl: trimOrNull7(extras?.prUrl) ?? null
|
|
2843
3771
|
};
|
|
2844
3772
|
}
|
|
2845
3773
|
|
|
2846
3774
|
// src/pr-handoff/pr-handoff-gh.ts
|
|
2847
|
-
import { spawnSync as
|
|
3775
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
2848
3776
|
|
|
2849
3777
|
// src/github-repo.ts
|
|
2850
3778
|
function parseGithubOwnerRepo(remoteUrl) {
|
|
@@ -2877,7 +3805,7 @@ function normalizeOwnerRepo(value) {
|
|
|
2877
3805
|
// src/pr-handoff/pr-handoff-gh.ts
|
|
2878
3806
|
function capture(bin, cwd, args) {
|
|
2879
3807
|
try {
|
|
2880
|
-
const res =
|
|
3808
|
+
const res = spawnSync3(bin, args, { cwd, encoding: "utf8" });
|
|
2881
3809
|
return {
|
|
2882
3810
|
status: res.status,
|
|
2883
3811
|
stdout: (res.stdout || "").trim(),
|
|
@@ -3219,7 +4147,7 @@ function pathFromGitStatusLine(line) {
|
|
|
3219
4147
|
}
|
|
3220
4148
|
|
|
3221
4149
|
// src/disposable-artifacts.ts
|
|
3222
|
-
function
|
|
4150
|
+
function stringList2(value) {
|
|
3223
4151
|
if (!Array.isArray(value)) return [];
|
|
3224
4152
|
return value.map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
|
|
3225
4153
|
}
|
|
@@ -3230,9 +4158,9 @@ function extractDisposableArtifactsRemoved(finalResult) {
|
|
|
3230
4158
|
const record = asRecord2(finalResult);
|
|
3231
4159
|
if (!record) return [];
|
|
3232
4160
|
const nested = asRecord2(record.worktreeHandoff);
|
|
3233
|
-
const fromNested =
|
|
4161
|
+
const fromNested = stringList2(nested?.disposableArtifactsRemoved);
|
|
3234
4162
|
if (fromNested.length) return fromNested;
|
|
3235
|
-
return
|
|
4163
|
+
return stringList2(record.disposableArtifactsRemoved);
|
|
3236
4164
|
}
|
|
3237
4165
|
function normalizeRelativePath(value) {
|
|
3238
4166
|
return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
@@ -3243,18 +4171,18 @@ function dirtyPathsCoveredByDisposableRemoval(changedFiles, removed) {
|
|
|
3243
4171
|
if (removed.length === 0) return false;
|
|
3244
4172
|
const removedSet = new Set(removed.map((p) => normalizeRelativePath(p)));
|
|
3245
4173
|
return material.every((line) => {
|
|
3246
|
-
const
|
|
3247
|
-
return removedSet.has(
|
|
4174
|
+
const path59 = normalizeRelativePath(pathFromGitStatusLine(line));
|
|
4175
|
+
return removedSet.has(path59);
|
|
3248
4176
|
});
|
|
3249
4177
|
}
|
|
3250
4178
|
|
|
3251
4179
|
// src/worktree-completion-handoff.ts
|
|
3252
|
-
function
|
|
4180
|
+
function trimOrNull8(value) {
|
|
3253
4181
|
if (typeof value !== "string") return null;
|
|
3254
4182
|
const t = value.trim();
|
|
3255
4183
|
return t.length ? t : null;
|
|
3256
4184
|
}
|
|
3257
|
-
function
|
|
4185
|
+
function stringList3(value) {
|
|
3258
4186
|
if (!Array.isArray(value)) return [];
|
|
3259
4187
|
return value.map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
|
|
3260
4188
|
}
|
|
@@ -3267,16 +4195,16 @@ function hasFinalResult4(value) {
|
|
|
3267
4195
|
return true;
|
|
3268
4196
|
}
|
|
3269
4197
|
function mergedDisposableRemoved(input) {
|
|
3270
|
-
const fromWorker =
|
|
4198
|
+
const fromWorker = stringList3(input.disposableArtifactsRemoved);
|
|
3271
4199
|
const fromResult = extractDisposableArtifactsRemoved(input.finalResult);
|
|
3272
4200
|
return [.../* @__PURE__ */ new Set([...fromWorker, ...fromResult])];
|
|
3273
4201
|
}
|
|
3274
4202
|
function assessWorktreeCompletionHandoff(input) {
|
|
3275
|
-
const rawDirty =
|
|
4203
|
+
const rawDirty = stringList3(input.changedFiles);
|
|
3276
4204
|
const materialDirty = materialWorktreeChanges(rawDirty);
|
|
3277
4205
|
const removed = mergedDisposableRemoved(input);
|
|
3278
4206
|
const effectivelyClean = materialDirty.length === 0 || dirtyPathsCoveredByDisposableRemoval(rawDirty, removed);
|
|
3279
|
-
if (
|
|
4207
|
+
if (trimOrNull8(input.prUrl)) {
|
|
3280
4208
|
if (!effectivelyClean) {
|
|
3281
4209
|
return {
|
|
3282
4210
|
allowed: false,
|
|
@@ -3287,7 +4215,7 @@ function assessWorktreeCompletionHandoff(input) {
|
|
|
3287
4215
|
}
|
|
3288
4216
|
return { allowed: true, state: "pr_handoff", materialDirtyCount: 0 };
|
|
3289
4217
|
}
|
|
3290
|
-
if (
|
|
4218
|
+
if (trimOrNull8(input.headCommit)) {
|
|
3291
4219
|
if (!effectivelyClean) {
|
|
3292
4220
|
return {
|
|
3293
4221
|
allowed: false,
|
|
@@ -3298,7 +4226,7 @@ function assessWorktreeCompletionHandoff(input) {
|
|
|
3298
4226
|
}
|
|
3299
4227
|
return { allowed: true, state: "commit_handoff", materialDirtyCount: 0 };
|
|
3300
4228
|
}
|
|
3301
|
-
if (
|
|
4229
|
+
if (trimOrNull8(input.artifactBundlePath) || trimOrNull8(input.patchPath)) {
|
|
3302
4230
|
if (!effectivelyClean) {
|
|
3303
4231
|
return {
|
|
3304
4232
|
allowed: false,
|
|
@@ -3329,7 +4257,7 @@ function assessWorktreeCompletionHandoff(input) {
|
|
|
3329
4257
|
}
|
|
3330
4258
|
|
|
3331
4259
|
// src/worker-lifecycle.ts
|
|
3332
|
-
import
|
|
4260
|
+
import path16 from "node:path";
|
|
3333
4261
|
var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
|
|
3334
4262
|
"awaiting_review",
|
|
3335
4263
|
"blocked",
|
|
@@ -3385,7 +4313,7 @@ function syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick) {
|
|
|
3385
4313
|
const synced = [];
|
|
3386
4314
|
for (const name of Object.keys(run.workers || {})) {
|
|
3387
4315
|
const worker = readJson(
|
|
3388
|
-
|
|
4316
|
+
path16.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
3389
4317
|
void 0
|
|
3390
4318
|
);
|
|
3391
4319
|
if (!worker?.taskId || isCompletionAcknowledged(worker)) continue;
|
|
@@ -3502,37 +4430,39 @@ async function tryCompleteWorker(args) {
|
|
|
3502
4430
|
return { ok: true, skipped: true, reason: "local-only-worker" };
|
|
3503
4431
|
}
|
|
3504
4432
|
const headCommit = status.gitAncestry.headIsAncestorOfBase === false && status.gitAncestry.head ? status.gitAncestry.head : status.headCommit;
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
4433
|
+
if (worker.dispatched) {
|
|
4434
|
+
const handoff = assessWorktreeCompletionHandoff({
|
|
4435
|
+
changedFiles: status.changedFiles,
|
|
4436
|
+
finalResult: status.finalResult,
|
|
4437
|
+
prUrl: status.prUrl,
|
|
4438
|
+
headCommit,
|
|
4439
|
+
disposableArtifactsRemoved: worker.disposableArtifactsRemoved
|
|
4440
|
+
});
|
|
4441
|
+
if (!handoff.allowed) {
|
|
4442
|
+
const reason2 = handoff.detail ?? `worktree completion blocked (${handoff.state})`;
|
|
4443
|
+
persistCompletionBlocker(worker, reason2);
|
|
4444
|
+
return {
|
|
4445
|
+
ok: false,
|
|
4446
|
+
reason: reason2,
|
|
4447
|
+
nextAction: "Clean the worktree (commit, open a PR, or `kynver worker discard-disposable --path <file>`), then rerun `kynver worker complete`.",
|
|
4448
|
+
completionBlocked: true
|
|
4449
|
+
};
|
|
4450
|
+
}
|
|
3521
4451
|
}
|
|
3522
4452
|
const skipPrHandoff = args.skipPrHandoff === true || args.skipPrHandoff === "true";
|
|
3523
4453
|
if (!skipPrHandoff && worker.dispatched && taskId) {
|
|
3524
|
-
const
|
|
3525
|
-
if (!
|
|
3526
|
-
persistCompletionBlocker(worker,
|
|
4454
|
+
const handoff = ensurePrReadyHandoff({ worker, run, status });
|
|
4455
|
+
if (!handoff.ok) {
|
|
4456
|
+
persistCompletionBlocker(worker, handoff.reason);
|
|
3527
4457
|
return {
|
|
3528
4458
|
ok: false,
|
|
3529
|
-
reason:
|
|
3530
|
-
nextAction:
|
|
4459
|
+
reason: handoff.reason,
|
|
4460
|
+
nextAction: handoff.nextAction,
|
|
3531
4461
|
completionBlocked: true
|
|
3532
4462
|
};
|
|
3533
4463
|
}
|
|
3534
|
-
if (
|
|
3535
|
-
status = applyPrHandoffToStatus(status,
|
|
4464
|
+
if (handoff.prUrl || handoff.headCommit) {
|
|
4465
|
+
status = applyPrHandoffToStatus(status, handoff);
|
|
3536
4466
|
}
|
|
3537
4467
|
}
|
|
3538
4468
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
@@ -3549,9 +4479,10 @@ async function tryCompleteWorker(args) {
|
|
|
3549
4479
|
runId: worker.runId,
|
|
3550
4480
|
workerName: worker.name,
|
|
3551
4481
|
taskId,
|
|
4482
|
+
leaseToken: worker.leaseToken ?? null,
|
|
3552
4483
|
startedAt: worker.startedAt,
|
|
3553
4484
|
finishedAt: status.lastActivityAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
3554
|
-
status: statusPayload,
|
|
4485
|
+
status: worker.leaseToken ? { ...statusPayload, leaseToken: worker.leaseToken } : statusPayload,
|
|
3555
4486
|
workerInjection: {
|
|
3556
4487
|
instructionPolicyFingerprint: worker.instructionPolicyFingerprint ?? null,
|
|
3557
4488
|
instructionPolicyEvidence: worker.instructionPolicyEvidence ?? null,
|
|
@@ -3666,7 +4597,7 @@ function workerStatus(args) {
|
|
|
3666
4597
|
const worker = loadWorker(String(args.run), String(args.name));
|
|
3667
4598
|
const run = loadRun(worker.runId);
|
|
3668
4599
|
const status = computeWorkerStatus(worker, workerStatusOptions(run));
|
|
3669
|
-
writeJson(
|
|
4600
|
+
writeJson(path17.join(worker.workerDir, "last-status.json"), status);
|
|
3670
4601
|
console.log(JSON.stringify(status, null, 2));
|
|
3671
4602
|
}
|
|
3672
4603
|
function buildRunBoard(runId) {
|
|
@@ -3674,7 +4605,7 @@ function buildRunBoard(runId) {
|
|
|
3674
4605
|
const names = Object.keys(run.workers || {});
|
|
3675
4606
|
const workers = names.map((name) => {
|
|
3676
4607
|
const worker = readJson(
|
|
3677
|
-
|
|
4608
|
+
path17.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
3678
4609
|
void 0
|
|
3679
4610
|
);
|
|
3680
4611
|
if (!worker) {
|
|
@@ -3785,7 +4716,7 @@ function buildRunBoard(runId) {
|
|
|
3785
4716
|
needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
|
|
3786
4717
|
workers
|
|
3787
4718
|
};
|
|
3788
|
-
writeJson(
|
|
4719
|
+
writeJson(path17.join(runDirectory(run.id), "last-board.json"), board);
|
|
3789
4720
|
return board;
|
|
3790
4721
|
}
|
|
3791
4722
|
async function publishHarnessBoardSnapshot(args, source) {
|
|
@@ -3974,15 +4905,15 @@ async function autoCompleteWorkerCli(raw) {
|
|
|
3974
4905
|
}
|
|
3975
4906
|
}
|
|
3976
4907
|
function resolveDefaultCliPath() {
|
|
3977
|
-
return
|
|
4908
|
+
return path18.join(fileURLToPath2(new URL(".", import.meta.url)), "cli.js");
|
|
3978
4909
|
}
|
|
3979
4910
|
function spawnCompletionSidecar(opts) {
|
|
3980
4911
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath();
|
|
3981
|
-
if (!
|
|
3982
|
-
const logPath =
|
|
4912
|
+
if (!existsSync17(cliPath)) return void 0;
|
|
4913
|
+
const logPath = path18.join(opts.workerDir, "auto-complete.log");
|
|
3983
4914
|
let logFd;
|
|
3984
4915
|
try {
|
|
3985
|
-
logFd =
|
|
4916
|
+
logFd = openSync5(logPath, "a");
|
|
3986
4917
|
} catch {
|
|
3987
4918
|
logFd = void 0;
|
|
3988
4919
|
}
|
|
@@ -4005,7 +4936,7 @@ function spawnCompletionSidecar(opts) {
|
|
|
4005
4936
|
if (opts.baseUrl) args.push("--base-url", opts.baseUrl);
|
|
4006
4937
|
if (opts.secret) args.push("--secret", opts.secret);
|
|
4007
4938
|
try {
|
|
4008
|
-
const child =
|
|
4939
|
+
const child = spawn5(
|
|
4009
4940
|
nodeExecutable,
|
|
4010
4941
|
args,
|
|
4011
4942
|
hiddenSpawnOptions({
|
|
@@ -4014,13 +4945,13 @@ function spawnCompletionSidecar(opts) {
|
|
|
4014
4945
|
env: process.env
|
|
4015
4946
|
})
|
|
4016
4947
|
);
|
|
4017
|
-
if (logFd !== void 0)
|
|
4948
|
+
if (logFd !== void 0) closeSync5(logFd);
|
|
4018
4949
|
child.unref();
|
|
4019
4950
|
return { pid: child.pid, logPath, cliPath };
|
|
4020
4951
|
} catch {
|
|
4021
4952
|
if (logFd !== void 0) {
|
|
4022
4953
|
try {
|
|
4023
|
-
|
|
4954
|
+
closeSync5(logFd);
|
|
4024
4955
|
} catch {
|
|
4025
4956
|
}
|
|
4026
4957
|
}
|
|
@@ -4075,21 +5006,21 @@ function spawnWorkerProcess(run, opts) {
|
|
|
4075
5006
|
launchModel = preflight.model;
|
|
4076
5007
|
}
|
|
4077
5008
|
const { worktreesDir } = getPaths();
|
|
4078
|
-
const workerDir =
|
|
5009
|
+
const workerDir = path19.join(runDirectory(run.id), "workers", name);
|
|
4079
5010
|
mkdirSync3(workerDir, { recursive: true });
|
|
4080
|
-
const worktreePath =
|
|
5011
|
+
const worktreePath = path19.join(worktreesDir, run.id, name);
|
|
4081
5012
|
const repairBranch = opts.repairTargetBranch?.trim() || void 0;
|
|
4082
5013
|
const branch = repairBranch || opts.branch || `agent/${run.id}/${name}`;
|
|
4083
|
-
if (
|
|
5014
|
+
if (existsSync18(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
|
|
4084
5015
|
git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
|
|
4085
5016
|
if (repairBranch) {
|
|
4086
5017
|
addWorktreeForRepairBranch(run.repo, worktreePath, repairBranch);
|
|
4087
5018
|
} else {
|
|
4088
5019
|
git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
|
|
4089
5020
|
}
|
|
4090
|
-
const stdoutPath =
|
|
4091
|
-
const stderrPath =
|
|
4092
|
-
const heartbeatPath =
|
|
5021
|
+
const stdoutPath = path19.join(workerDir, "stdout.jsonl");
|
|
5022
|
+
const stderrPath = path19.join(workerDir, "stderr.log");
|
|
5023
|
+
const heartbeatPath = path19.join(workerDir, "heartbeat.jsonl");
|
|
4093
5024
|
const prompt = buildPrompt({
|
|
4094
5025
|
task: opts.task,
|
|
4095
5026
|
ownedPaths: opts.ownedPaths || [],
|
|
@@ -4146,10 +5077,12 @@ function spawnWorkerProcess(run, opts) {
|
|
|
4146
5077
|
...opts.personaSlug ? { personaSlug: String(opts.personaSlug) } : {},
|
|
4147
5078
|
...opts.personaEvidence ? { personaEvidence: opts.personaEvidence } : {},
|
|
4148
5079
|
...opts.leaseOwner ? { leaseOwner: String(opts.leaseOwner) } : {},
|
|
5080
|
+
...opts.leaseToken ? { leaseToken: String(opts.leaseToken) } : {},
|
|
4149
5081
|
...opts.dispatched ? { dispatched: true } : {},
|
|
4150
5082
|
...!opts.agentOsId || !opts.taskId ? { localOnly: true } : {},
|
|
4151
5083
|
routingRule: routing.rule,
|
|
4152
5084
|
...routing.requestedModel ? { requestedModel: routing.requestedModel } : {},
|
|
5085
|
+
...routing.orchestrationAudit ? { orchestrationAudit: routing.orchestrationAudit } : {},
|
|
4153
5086
|
...opts.executorRef ? { executorRef: String(opts.executorRef) } : {},
|
|
4154
5087
|
...opts.parentTaskId ? { parentTaskId: String(opts.parentTaskId) } : {},
|
|
4155
5088
|
...opts.taskTitle ? { taskTitle: String(opts.taskTitle) } : {},
|
|
@@ -4159,7 +5092,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
4159
5092
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4160
5093
|
};
|
|
4161
5094
|
saveWorker(run.id, worker);
|
|
4162
|
-
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath:
|
|
5095
|
+
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path19.join(workerDir, "worker.json") } };
|
|
4163
5096
|
run.status = "running";
|
|
4164
5097
|
saveRun(run);
|
|
4165
5098
|
if (worker.agentOsId && worker.taskId) {
|
|
@@ -4253,17 +5186,50 @@ async function startWorker(args) {
|
|
|
4253
5186
|
}
|
|
4254
5187
|
}
|
|
4255
5188
|
|
|
5189
|
+
// src/active-harness-workers.ts
|
|
5190
|
+
import path20 from "node:path";
|
|
5191
|
+
function workerWriteSetFields(worker) {
|
|
5192
|
+
const ownedPaths = Array.isArray(worker.ownedPaths) ? worker.ownedPaths.filter((p) => typeof p === "string") : [];
|
|
5193
|
+
const writeSetPrefixes = Array.isArray(
|
|
5194
|
+
worker.writeSetPrefixes
|
|
5195
|
+
) ? (worker.writeSetPrefixes ?? []).filter((p) => typeof p === "string") : [];
|
|
5196
|
+
return {
|
|
5197
|
+
...ownedPaths.length ? { ownedPaths } : {},
|
|
5198
|
+
...writeSetPrefixes.length ? { writeSetPrefixes } : {},
|
|
5199
|
+
...worker.allowConcurrentHotspot ? { allowConcurrentHotspot: true } : {}
|
|
5200
|
+
};
|
|
5201
|
+
}
|
|
5202
|
+
function collectRunActiveHarnessWorkers(runId) {
|
|
5203
|
+
const run = loadRun(runId);
|
|
5204
|
+
const out = [];
|
|
5205
|
+
for (const name of Object.keys(run.workers || {})) {
|
|
5206
|
+
const worker = readJson(
|
|
5207
|
+
path20.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
5208
|
+
void 0
|
|
5209
|
+
);
|
|
5210
|
+
if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
|
|
5211
|
+
out.push({
|
|
5212
|
+
runId: run.id,
|
|
5213
|
+
workerName: name,
|
|
5214
|
+
taskId: worker.taskId,
|
|
5215
|
+
pid: worker.pid,
|
|
5216
|
+
...workerWriteSetFields(worker)
|
|
5217
|
+
});
|
|
5218
|
+
}
|
|
5219
|
+
return out;
|
|
5220
|
+
}
|
|
5221
|
+
|
|
4256
5222
|
// src/plan-persist/body-hash.ts
|
|
4257
|
-
import { createHash } from "node:crypto";
|
|
5223
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
4258
5224
|
function hashPlanBody(body) {
|
|
4259
5225
|
const normalized = body.replace(/\r\n/g, "\n").trimEnd();
|
|
4260
|
-
return
|
|
5226
|
+
return createHash2("sha256").update(normalized, "utf8").digest("hex");
|
|
4261
5227
|
}
|
|
4262
5228
|
function hashSummary(summary) {
|
|
4263
5229
|
if (summary == null) return null;
|
|
4264
5230
|
const trimmed = summary.trim();
|
|
4265
5231
|
if (!trimmed) return null;
|
|
4266
|
-
return
|
|
5232
|
+
return createHash2("sha256").update(trimmed, "utf8").digest("hex");
|
|
4267
5233
|
}
|
|
4268
5234
|
|
|
4269
5235
|
// src/plan-persist/errors.ts
|
|
@@ -4433,7 +5399,7 @@ function mergeSourceRefs(input) {
|
|
|
4433
5399
|
}
|
|
4434
5400
|
|
|
4435
5401
|
// src/plan-persist/idempotency.ts
|
|
4436
|
-
import { createHash as
|
|
5402
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
4437
5403
|
function buildPlanPersistIdempotencyKey(input) {
|
|
4438
5404
|
const payload = {
|
|
4439
5405
|
operation: input.operation,
|
|
@@ -4446,23 +5412,23 @@ function buildPlanPersistIdempotencyKey(input) {
|
|
|
4446
5412
|
changeSummary: input.changeSummary?.trim() ?? null,
|
|
4447
5413
|
markCurrent: input.markCurrent ?? true
|
|
4448
5414
|
};
|
|
4449
|
-
return
|
|
5415
|
+
return createHash3("sha256").update(JSON.stringify(payload), "utf8").digest("hex");
|
|
4450
5416
|
}
|
|
4451
5417
|
|
|
4452
5418
|
// src/plan-persist/paths.ts
|
|
4453
5419
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
4454
|
-
import { homedir as
|
|
4455
|
-
import
|
|
5420
|
+
import { homedir as homedir10 } from "node:os";
|
|
5421
|
+
import path21 from "node:path";
|
|
4456
5422
|
function resolveKynverStateRoot() {
|
|
4457
5423
|
const env = process.env.KYNVER_STATE_ROOT;
|
|
4458
|
-
if (env) return
|
|
4459
|
-
return
|
|
5424
|
+
if (env) return path21.resolve(env);
|
|
5425
|
+
return path21.join(homedir10(), ".kynver", "state");
|
|
4460
5426
|
}
|
|
4461
5427
|
function planOutboxDir() {
|
|
4462
|
-
return
|
|
5428
|
+
return path21.join(resolveKynverStateRoot(), "plan-outbox");
|
|
4463
5429
|
}
|
|
4464
5430
|
function planOutboxArchiveDir() {
|
|
4465
|
-
return
|
|
5431
|
+
return path21.join(resolveKynverStateRoot(), "plan-outbox-archive");
|
|
4466
5432
|
}
|
|
4467
5433
|
function ensurePlanOutboxDirs() {
|
|
4468
5434
|
const outboxDir = planOutboxDir();
|
|
@@ -4473,20 +5439,20 @@ function ensurePlanOutboxDirs() {
|
|
|
4473
5439
|
}
|
|
4474
5440
|
function isTmpOnlyPath(filePath) {
|
|
4475
5441
|
if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
|
|
4476
|
-
const resolved =
|
|
4477
|
-
return resolved.startsWith("/tmp/") || resolved.startsWith(
|
|
5442
|
+
const resolved = path21.resolve(filePath);
|
|
5443
|
+
return resolved.startsWith("/tmp/") || resolved.startsWith(path21.join("/var", "folders"));
|
|
4478
5444
|
}
|
|
4479
5445
|
|
|
4480
5446
|
// src/plan-persist/outbox-store.ts
|
|
4481
5447
|
import {
|
|
4482
|
-
existsSync as
|
|
4483
|
-
readFileSync as
|
|
5448
|
+
existsSync as existsSync20,
|
|
5449
|
+
readFileSync as readFileSync9,
|
|
4484
5450
|
renameSync,
|
|
4485
5451
|
readdirSync as readdirSync4,
|
|
4486
5452
|
writeFileSync as writeFileSync3,
|
|
4487
5453
|
unlinkSync
|
|
4488
5454
|
} from "node:fs";
|
|
4489
|
-
import
|
|
5455
|
+
import path22 from "node:path";
|
|
4490
5456
|
import { randomUUID } from "node:crypto";
|
|
4491
5457
|
var DEFAULT_MAX_RETRIES = 12;
|
|
4492
5458
|
function listOutboxItems() {
|
|
@@ -4494,7 +5460,7 @@ function listOutboxItems() {
|
|
|
4494
5460
|
const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
|
|
4495
5461
|
const items = [];
|
|
4496
5462
|
for (const file of files) {
|
|
4497
|
-
const item = readOutboxItem(
|
|
5463
|
+
const item = readOutboxItem(path22.join(outboxDir, file));
|
|
4498
5464
|
if (item && item.queueStatus === "queued") items.push(item);
|
|
4499
5465
|
}
|
|
4500
5466
|
return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
@@ -4506,25 +5472,25 @@ function findOutboxByIdempotencyKey(key) {
|
|
|
4506
5472
|
return null;
|
|
4507
5473
|
}
|
|
4508
5474
|
function readOutboxItem(jsonPath) {
|
|
4509
|
-
if (!
|
|
5475
|
+
if (!existsSync20(jsonPath)) return null;
|
|
4510
5476
|
try {
|
|
4511
|
-
return JSON.parse(
|
|
5477
|
+
return JSON.parse(readFileSync9(jsonPath, "utf8"));
|
|
4512
5478
|
} catch {
|
|
4513
5479
|
return null;
|
|
4514
5480
|
}
|
|
4515
5481
|
}
|
|
4516
5482
|
function readOutboxBody(item) {
|
|
4517
5483
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
4518
|
-
const bodyFile =
|
|
4519
|
-
return
|
|
5484
|
+
const bodyFile = path22.join(outboxDir, item.bodyPath);
|
|
5485
|
+
return readFileSync9(bodyFile, "utf8");
|
|
4520
5486
|
}
|
|
4521
5487
|
function writeOutboxItem(input, opts) {
|
|
4522
5488
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
4523
5489
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4524
5490
|
const id = opts.existing?.id ?? randomUUID();
|
|
4525
5491
|
const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
|
|
4526
|
-
const jsonPath =
|
|
4527
|
-
const bodyFile =
|
|
5492
|
+
const jsonPath = path22.join(outboxDir, `${id}.json`);
|
|
5493
|
+
const bodyFile = path22.join(outboxDir, bodyPath);
|
|
4528
5494
|
if (!opts.existing) {
|
|
4529
5495
|
writeFileSync3(bodyFile, input.body, "utf8");
|
|
4530
5496
|
}
|
|
@@ -4560,24 +5526,24 @@ function writeOutboxItem(input, opts) {
|
|
|
4560
5526
|
}
|
|
4561
5527
|
function saveOutboxItem(item) {
|
|
4562
5528
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
4563
|
-
const jsonPath =
|
|
5529
|
+
const jsonPath = path22.join(outboxDir, `${item.id}.json`);
|
|
4564
5530
|
writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
|
|
4565
5531
|
`, { mode: 384 });
|
|
4566
5532
|
}
|
|
4567
5533
|
function archiveOutboxItem(item) {
|
|
4568
5534
|
const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
|
|
4569
|
-
const jsonSrc =
|
|
4570
|
-
const bodySrc =
|
|
4571
|
-
const jsonDst =
|
|
4572
|
-
const bodyDst =
|
|
4573
|
-
if (
|
|
4574
|
-
if (
|
|
5535
|
+
const jsonSrc = path22.join(outboxDir, `${item.id}.json`);
|
|
5536
|
+
const bodySrc = path22.join(outboxDir, item.bodyPath);
|
|
5537
|
+
const jsonDst = path22.join(archiveDir, `${item.id}.json`);
|
|
5538
|
+
const bodyDst = path22.join(archiveDir, item.bodyPath);
|
|
5539
|
+
if (existsSync20(jsonSrc)) renameSync(jsonSrc, jsonDst);
|
|
5540
|
+
if (existsSync20(bodySrc)) renameSync(bodySrc, bodyDst);
|
|
4575
5541
|
}
|
|
4576
5542
|
function outboxItemPaths(item) {
|
|
4577
5543
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
4578
5544
|
return {
|
|
4579
|
-
jsonPath:
|
|
4580
|
-
bodyPath:
|
|
5545
|
+
jsonPath: path22.join(outboxDir, `${item.id}.json`),
|
|
5546
|
+
bodyPath: path22.join(outboxDir, item.bodyPath)
|
|
4581
5547
|
};
|
|
4582
5548
|
}
|
|
4583
5549
|
function outboxInputFromItem(item, body) {
|
|
@@ -4763,7 +5729,7 @@ function markOutboxFailed(item, message) {
|
|
|
4763
5729
|
}
|
|
4764
5730
|
|
|
4765
5731
|
// src/plan-persist/drain.ts
|
|
4766
|
-
import
|
|
5732
|
+
import path23 from "node:path";
|
|
4767
5733
|
async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
4768
5734
|
const items = listOutboxItems().filter(
|
|
4769
5735
|
(item) => opts.outboxId ? item.id === opts.outboxId : true
|
|
@@ -4797,7 +5763,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
|
4797
5763
|
return result;
|
|
4798
5764
|
}
|
|
4799
5765
|
function loadOutboxById(outboxId) {
|
|
4800
|
-
const jsonPath =
|
|
5766
|
+
const jsonPath = path23.join(planOutboxDir(), `${outboxId}.json`);
|
|
4801
5767
|
return readOutboxItem(jsonPath);
|
|
4802
5768
|
}
|
|
4803
5769
|
|
|
@@ -4827,24 +5793,6 @@ function extractPlanOutboxFromTask(task) {
|
|
|
4827
5793
|
};
|
|
4828
5794
|
}
|
|
4829
5795
|
|
|
4830
|
-
// src/runner-identity.ts
|
|
4831
|
-
import os3 from "node:os";
|
|
4832
|
-
function trimOrNull7(value) {
|
|
4833
|
-
if (!value?.trim()) return null;
|
|
4834
|
-
return value.trim();
|
|
4835
|
-
}
|
|
4836
|
-
function resolveRunnerPresencePayload(input = {}) {
|
|
4837
|
-
const env = input.env ?? process.env;
|
|
4838
|
-
const runnerId = trimOrNull7(env.KYNVER_RUNTIME_ID) ?? trimOrNull7(env.OPENCLAW_RUNTIME_ID) ?? trimOrNull7(env.HOSTNAME) ?? os3.hostname();
|
|
4839
|
-
return {
|
|
4840
|
-
runnerId,
|
|
4841
|
-
hostname: trimOrNull7(env.HOSTNAME) ?? os3.hostname(),
|
|
4842
|
-
profile: trimOrNull7(env.KYNVER_RUNNER_PROFILE) ?? trimOrNull7(env.OPENCLAW_RUNNER_PROFILE),
|
|
4843
|
-
harnessRepo: trimOrNull7(env.KYNVER_HARNESS_REPO) ?? trimOrNull7(env.KYNVER_DEFAULT_REPO),
|
|
4844
|
-
runId: input.runId ?? null
|
|
4845
|
-
};
|
|
4846
|
-
}
|
|
4847
|
-
|
|
4848
5796
|
// src/dispatch.ts
|
|
4849
5797
|
var DEFAULT_DISPATCH_LEASE_MS = 60 * 60 * 1e3;
|
|
4850
5798
|
function readHarnessWorkerContext(decision) {
|
|
@@ -4900,6 +5848,20 @@ function buildDispatchTaskText(task, agentOsId) {
|
|
|
4900
5848
|
}
|
|
4901
5849
|
return lines.join("\n");
|
|
4902
5850
|
}
|
|
5851
|
+
function requestedTargetTaskIds(args) {
|
|
5852
|
+
const out = /* @__PURE__ */ new Set();
|
|
5853
|
+
if (args.targetTaskId) {
|
|
5854
|
+
out.add(String(args.targetTaskId));
|
|
5855
|
+
return out;
|
|
5856
|
+
}
|
|
5857
|
+
if (args.targetTaskIds) {
|
|
5858
|
+
for (const value of String(args.targetTaskIds).split(",")) {
|
|
5859
|
+
const trimmed = value.trim();
|
|
5860
|
+
if (trimmed) out.add(trimmed);
|
|
5861
|
+
}
|
|
5862
|
+
}
|
|
5863
|
+
return out;
|
|
5864
|
+
}
|
|
4903
5865
|
async function dispatchRun(args) {
|
|
4904
5866
|
const pipeline = args.pipeline === true || args.pipeline === "true";
|
|
4905
5867
|
try {
|
|
@@ -4910,37 +5872,46 @@ async function dispatchRun(args) {
|
|
|
4910
5872
|
const execute = args.execute === true || args.execute === "true";
|
|
4911
5873
|
const dryRun = !execute;
|
|
4912
5874
|
const runnerPresence = resolveRunnerPresencePayload({ runId: run.id });
|
|
4913
|
-
const leaseOwner =
|
|
5875
|
+
const leaseOwner = formatHarnessLeaseOwner(run.id, runnerPresence.runnerId);
|
|
4914
5876
|
const runnerDiskGate = args.diskPath ? observeRunnerDiskGate({ diskPath: String(args.diskPath) }) : observeRunnerDiskGate({ diskPath: run.repo });
|
|
4915
|
-
const runnerResourceGate = observeRunnerResourceGate({ runId: run.id });
|
|
4916
|
-
const
|
|
4917
|
-
const
|
|
4918
|
-
const
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
);
|
|
4924
|
-
if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
|
|
4925
|
-
const ownedPaths = Array.isArray(worker.ownedPaths) ? worker.ownedPaths.filter((p) => typeof p === "string") : [];
|
|
4926
|
-
const writeSetPrefixes = Array.isArray(
|
|
4927
|
-
worker.writeSetPrefixes
|
|
4928
|
-
) ? (worker.writeSetPrefixes ?? []).filter((p) => typeof p === "string") : [];
|
|
4929
|
-
activeHarnessWorkers.push({
|
|
5877
|
+
const runnerResourceGate = observeRunnerResourceGate({ runId: run.id });
|
|
5878
|
+
const exactTargetIds = requestedTargetTaskIds(args);
|
|
5879
|
+
const exactTargetMode = exactTargetIds.size > 0;
|
|
5880
|
+
const requestedStarts = Number(args.maxStarts) > 0 ? Math.floor(Number(args.maxStarts)) : 1;
|
|
5881
|
+
const effectiveRequestedStarts = exactTargetMode ? Math.min(requestedStarts, exactTargetIds.size) : requestedStarts;
|
|
5882
|
+
const cappedStarts = dryRun ? effectiveRequestedStarts : Math.min(effectiveRequestedStarts, runnerResourceGate.slotsAvailable);
|
|
5883
|
+
if (!dryRun && cappedStarts <= 0) {
|
|
5884
|
+
const summary2 = {
|
|
4930
5885
|
runId: run.id,
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
}
|
|
5886
|
+
agentOsId,
|
|
5887
|
+
dryRun: false,
|
|
5888
|
+
skipped: true,
|
|
5889
|
+
reason: runnerResourceGate.reason ?? "no resource slots",
|
|
5890
|
+
resourceGate: runnerResourceGate
|
|
5891
|
+
};
|
|
5892
|
+
if (pipeline) return { ok: true, ...summary2 };
|
|
5893
|
+
console.log(JSON.stringify(summary2, null, 2));
|
|
5894
|
+
return;
|
|
5895
|
+
}
|
|
5896
|
+
const activeHarnessWorkers = collectRunActiveHarnessWorkers(run.id);
|
|
5897
|
+
if (args.reconcileStaleBlockers === true || args.reconcileStaleBlockers === "true") {
|
|
5898
|
+
const laneHygieneUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/lane-hygiene`;
|
|
5899
|
+
try {
|
|
5900
|
+
await postJsonWithCredentialRefresh(
|
|
5901
|
+
laneHygieneUrl,
|
|
5902
|
+
secret,
|
|
5903
|
+
{ agentOsId, dryRun: false, includeBoardReconcile: true },
|
|
5904
|
+
{ agentOsId, baseUrl: base }
|
|
5905
|
+
);
|
|
5906
|
+
} catch (err) {
|
|
5907
|
+
console.error(`[dispatch] reconcile-stale-blockers lane-hygiene call failed: ${err.message}`);
|
|
5908
|
+
}
|
|
4938
5909
|
}
|
|
4939
5910
|
const dispatchUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/dispatch-next`;
|
|
4940
|
-
const
|
|
5911
|
+
const buildBody = (maxStarts) => ({
|
|
4941
5912
|
agentOsId,
|
|
4942
5913
|
dryRun,
|
|
4943
|
-
maxStarts
|
|
5914
|
+
maxStarts,
|
|
4944
5915
|
leaseOwner,
|
|
4945
5916
|
leaseDurationMs: Number(args.leaseMs) > 0 ? Math.floor(Number(args.leaseMs)) : DEFAULT_DISPATCH_LEASE_MS,
|
|
4946
5917
|
runnerDiskGate,
|
|
@@ -4951,25 +5922,32 @@ async function dispatchRun(args) {
|
|
|
4951
5922
|
...args.lane ? { lane: String(args.lane) } : {},
|
|
4952
5923
|
executor: args.executor ? String(args.executor) : "harness",
|
|
4953
5924
|
...args.diskPath ? { diskPath: String(args.diskPath) } : {},
|
|
4954
|
-
...args.targetTaskId ? { targetTaskId: String(args.targetTaskId) } : {}
|
|
5925
|
+
...args.targetTaskId ? { targetTaskId: String(args.targetTaskId) } : {},
|
|
5926
|
+
...!args.targetTaskId && args.targetTaskIds ? {
|
|
5927
|
+
targetTaskIds: String(args.targetTaskIds).split(",").map((s) => s.trim()).filter(Boolean)
|
|
5928
|
+
} : {}
|
|
5929
|
+
});
|
|
5930
|
+
const requestDispatch = async (maxStarts) => {
|
|
5931
|
+
const dispatch = await postJsonWithCredentialRefresh(dispatchUrl, secret, buildBody(maxStarts), { agentOsId, baseUrl: base });
|
|
5932
|
+
const responseBody = dispatch.response;
|
|
5933
|
+
return { dispatch, result: responseBody?.result };
|
|
4955
5934
|
};
|
|
4956
|
-
const
|
|
4957
|
-
|
|
4958
|
-
if (!dispatch.ok || !responseBody?.result) {
|
|
5935
|
+
const first = await requestDispatch(exactTargetMode || dryRun ? cappedStarts : 1);
|
|
5936
|
+
if (!first.dispatch.ok || !first.result) {
|
|
4959
5937
|
const failure = {
|
|
4960
5938
|
runId: run.id,
|
|
4961
5939
|
agentOsId,
|
|
4962
5940
|
action: "dispatch",
|
|
4963
|
-
httpStatus: dispatch.status,
|
|
4964
|
-
response: dispatch.response,
|
|
4965
|
-
authRefreshed: dispatch.refreshedAuth === true,
|
|
4966
|
-
authRefreshFailure: dispatch.authRefreshFailure
|
|
5941
|
+
httpStatus: first.dispatch.status,
|
|
5942
|
+
response: first.dispatch.response,
|
|
5943
|
+
authRefreshed: first.dispatch.refreshedAuth === true,
|
|
5944
|
+
authRefreshFailure: first.dispatch.authRefreshFailure
|
|
4967
5945
|
};
|
|
4968
5946
|
if (pipeline) return { ok: false, ...failure };
|
|
4969
5947
|
console.log(JSON.stringify(failure, null, 2));
|
|
4970
5948
|
process.exit(1);
|
|
4971
5949
|
}
|
|
4972
|
-
const result =
|
|
5950
|
+
const result = first.result;
|
|
4973
5951
|
if (dryRun) {
|
|
4974
5952
|
const summary2 = {
|
|
4975
5953
|
runId: run.id,
|
|
@@ -4991,25 +5969,41 @@ async function dispatchRun(args) {
|
|
|
4991
5969
|
console.log(JSON.stringify(summary2, null, 2));
|
|
4992
5970
|
return;
|
|
4993
5971
|
}
|
|
4994
|
-
if (!dryRun && cappedStarts <= 0) {
|
|
4995
|
-
const summary2 = {
|
|
4996
|
-
runId: run.id,
|
|
4997
|
-
agentOsId,
|
|
4998
|
-
dryRun: false,
|
|
4999
|
-
skipped: true,
|
|
5000
|
-
reason: runnerResourceGate.reason ?? "no resource slots",
|
|
5001
|
-
resourceGate: runnerResourceGate
|
|
5002
|
-
};
|
|
5003
|
-
if (pipeline) return { ok: true, ...summary2 };
|
|
5004
|
-
console.log(JSON.stringify(summary2, null, 2));
|
|
5005
|
-
return;
|
|
5006
|
-
}
|
|
5007
5972
|
const retryLimits = readHarnessRetryLimits();
|
|
5008
5973
|
const outcomes = [];
|
|
5009
|
-
|
|
5974
|
+
const skipped = [];
|
|
5975
|
+
let inspected = Number(result.inspected) || 0;
|
|
5976
|
+
async function spawnClaimed(decision) {
|
|
5010
5977
|
const task = decision.task;
|
|
5011
5978
|
const harnessContext = readHarnessWorkerContext(decision);
|
|
5012
5979
|
const taskId = String(task.id);
|
|
5980
|
+
if (exactTargetMode && !exactTargetIds.has(taskId)) {
|
|
5981
|
+
const releaseUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/${encodeURIComponent(taskId)}/release`;
|
|
5982
|
+
let release;
|
|
5983
|
+
try {
|
|
5984
|
+
release = await postJsonWithCredentialRefresh(
|
|
5985
|
+
releaseUrl,
|
|
5986
|
+
secret,
|
|
5987
|
+
{
|
|
5988
|
+
agentOsId,
|
|
5989
|
+
leaseOwner,
|
|
5990
|
+
reason: `exact target dispatch mismatch: requested ${[...exactTargetIds].join(",")} but dispatch-next returned ${taskId}`
|
|
5991
|
+
},
|
|
5992
|
+
{ agentOsId, baseUrl: base }
|
|
5993
|
+
);
|
|
5994
|
+
} catch (relErr) {
|
|
5995
|
+
release = { ok: false, error: relErr.message };
|
|
5996
|
+
}
|
|
5997
|
+
outcomes.push({
|
|
5998
|
+
taskId,
|
|
5999
|
+
started: false,
|
|
6000
|
+
error: "exact_target_mismatch: dispatch-next returned a different task than requested",
|
|
6001
|
+
requestedTargetTaskIds: [...exactTargetIds],
|
|
6002
|
+
released: release.ok === true || release.response?.ok === true,
|
|
6003
|
+
releaseResponse: release.response ?? release
|
|
6004
|
+
});
|
|
6005
|
+
return false;
|
|
6006
|
+
}
|
|
5013
6007
|
const expectedPersona = normalizePersonaSlug2(task.personaSlug);
|
|
5014
6008
|
if (expectedPersona && (!harnessContext?.personaInjectionReady || !harnessContext.personaMarkdown)) {
|
|
5015
6009
|
outcomes.push({
|
|
@@ -5017,7 +6011,7 @@ async function dispatchRun(args) {
|
|
|
5017
6011
|
started: false,
|
|
5018
6012
|
error: `persona_injection_required: missing anchored context-envelope persona block for "${expectedPersona}"`
|
|
5019
6013
|
});
|
|
5020
|
-
|
|
6014
|
+
return false;
|
|
5021
6015
|
}
|
|
5022
6016
|
if (hasLiveWorkerForTask(run.id, taskId)) {
|
|
5023
6017
|
outcomes.push({
|
|
@@ -5025,16 +6019,16 @@ async function dispatchRun(args) {
|
|
|
5025
6019
|
started: false,
|
|
5026
6020
|
error: "duplicate_dispatch_prevented: live local worker already owns this task"
|
|
5027
6021
|
});
|
|
5028
|
-
|
|
6022
|
+
return false;
|
|
5029
6023
|
}
|
|
5030
6024
|
const attempt = Number(task.attempt) || 1;
|
|
5031
|
-
if (attempt
|
|
6025
|
+
if (attempt >= retryLimits.maxTaskAttempts) {
|
|
5032
6026
|
outcomes.push({
|
|
5033
6027
|
taskId: task.id,
|
|
5034
6028
|
started: false,
|
|
5035
6029
|
error: `task attempt ${attempt} exceeds KYNVER_MAX_TASK_ATTEMPTS (${retryLimits.maxTaskAttempts})`
|
|
5036
6030
|
});
|
|
5037
|
-
|
|
6031
|
+
return false;
|
|
5038
6032
|
}
|
|
5039
6033
|
const name = safeSlug(`t-${task.id}-a${task.attempt}`);
|
|
5040
6034
|
const routing = resolveWorkerLaunch({
|
|
@@ -5078,6 +6072,7 @@ async function dispatchRun(args) {
|
|
|
5078
6072
|
personaSlug: harnessContext?.personaSlug ?? expectedPersona,
|
|
5079
6073
|
personaEvidence: harnessContext?.personaEvidence ?? null,
|
|
5080
6074
|
leaseOwner,
|
|
6075
|
+
leaseToken: task.leaseToken ? String(task.leaseToken) : void 0,
|
|
5081
6076
|
dispatched: true
|
|
5082
6077
|
});
|
|
5083
6078
|
outcomes.push({
|
|
@@ -5104,6 +6099,7 @@ async function dispatchRun(args) {
|
|
|
5104
6099
|
`[dispatch] task ${taskId}: persona context injected slug=${harnessContext.personaSlug}`
|
|
5105
6100
|
);
|
|
5106
6101
|
}
|
|
6102
|
+
return true;
|
|
5107
6103
|
} catch (error) {
|
|
5108
6104
|
const releaseUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/${encodeURIComponent(String(task.id))}/release`;
|
|
5109
6105
|
let release;
|
|
@@ -5119,6 +6115,35 @@ async function dispatchRun(args) {
|
|
|
5119
6115
|
released: release.ok === true || release.response?.ok === true,
|
|
5120
6116
|
releaseResponse: release.response ?? release
|
|
5121
6117
|
});
|
|
6118
|
+
return false;
|
|
6119
|
+
}
|
|
6120
|
+
}
|
|
6121
|
+
let shouldContinueDispatch = true;
|
|
6122
|
+
for (const decision of result.started) {
|
|
6123
|
+
shouldContinueDispatch = await spawnClaimed(decision) && shouldContinueDispatch;
|
|
6124
|
+
}
|
|
6125
|
+
skipped.push(...result.skipped ?? []);
|
|
6126
|
+
if (exactTargetMode) shouldContinueDispatch = false;
|
|
6127
|
+
while (shouldContinueDispatch && outcomes.length < cappedStarts) {
|
|
6128
|
+
if (exactTargetMode) break;
|
|
6129
|
+
const next = await requestDispatch(1);
|
|
6130
|
+
if (!next.dispatch.ok || !next.result) {
|
|
6131
|
+
outcomes.push({
|
|
6132
|
+
started: false,
|
|
6133
|
+
error: "dispatch_next request failed during top-up",
|
|
6134
|
+
httpStatus: next.dispatch.status,
|
|
6135
|
+
response: next.dispatch.response
|
|
6136
|
+
});
|
|
6137
|
+
break;
|
|
6138
|
+
}
|
|
6139
|
+
inspected += Number(next.result.inspected) || 0;
|
|
6140
|
+
skipped.push(...next.result.skipped ?? []);
|
|
6141
|
+
const started = next.result.started ?? [];
|
|
6142
|
+
if (started.length === 0) break;
|
|
6143
|
+
for (const decision of started) {
|
|
6144
|
+
if (outcomes.length >= cappedStarts) break;
|
|
6145
|
+
shouldContinueDispatch = await spawnClaimed(decision) && shouldContinueDispatch;
|
|
6146
|
+
if (!shouldContinueDispatch) break;
|
|
5122
6147
|
}
|
|
5123
6148
|
}
|
|
5124
6149
|
const summary = {
|
|
@@ -5128,10 +6153,11 @@ async function dispatchRun(args) {
|
|
|
5128
6153
|
leaseOwner,
|
|
5129
6154
|
startedCount: outcomes.filter((o) => o.started).length,
|
|
5130
6155
|
outcomes,
|
|
5131
|
-
skipped:
|
|
6156
|
+
skipped: skipped.map((d) => ({
|
|
5132
6157
|
taskId: d.task.id,
|
|
5133
6158
|
skipReason: d.skipReason
|
|
5134
6159
|
})),
|
|
6160
|
+
inspected,
|
|
5135
6161
|
diskGate: result.diskGate,
|
|
5136
6162
|
resourceGate: result.resourceGate
|
|
5137
6163
|
};
|
|
@@ -5148,7 +6174,7 @@ async function dispatchRun(args) {
|
|
|
5148
6174
|
}
|
|
5149
6175
|
|
|
5150
6176
|
// src/sweep.ts
|
|
5151
|
-
import
|
|
6177
|
+
import path24 from "node:path";
|
|
5152
6178
|
async function sweepRun(args) {
|
|
5153
6179
|
const pipeline = args.pipeline === true || args.pipeline === "true";
|
|
5154
6180
|
try {
|
|
@@ -5156,18 +6182,23 @@ async function sweepRun(args) {
|
|
|
5156
6182
|
const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
|
|
5157
6183
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
5158
6184
|
const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, { baseUrl: base });
|
|
5159
|
-
const
|
|
6185
|
+
const runnerId = resolveRunnerPresencePayload({ runId: run.id }).runnerId;
|
|
5160
6186
|
const snapshotPublished = await publishHarnessBoardSnapshot({ run: run.id, agentOsId, ...args }, "run_sweep");
|
|
5161
6187
|
const releasedLocalOrphans = [];
|
|
5162
6188
|
for (const name of Object.keys(run.workers || {})) {
|
|
5163
6189
|
const worker = readJson(
|
|
5164
|
-
|
|
6190
|
+
path24.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
5165
6191
|
void 0
|
|
5166
6192
|
);
|
|
5167
6193
|
if (!worker || !worker.dispatched || !worker.taskId) continue;
|
|
5168
6194
|
const status = computeWorkerStatus(worker);
|
|
5169
6195
|
if (status.alive) continue;
|
|
5170
6196
|
if (status.finalResult || worker.completionReportedAt) continue;
|
|
6197
|
+
const leaseOwner = resolveHarnessLeaseOwnerForRenewal({
|
|
6198
|
+
runId: run.id,
|
|
6199
|
+
workerLeaseOwner: worker.leaseOwner ?? null,
|
|
6200
|
+
runnerId
|
|
6201
|
+
});
|
|
5171
6202
|
const releaseUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/${encodeURIComponent(String(worker.taskId))}/release`;
|
|
5172
6203
|
let release;
|
|
5173
6204
|
try {
|
|
@@ -5193,7 +6224,7 @@ async function sweepRun(args) {
|
|
|
5193
6224
|
} catch (reapErr) {
|
|
5194
6225
|
reap = { ok: false, error: reapErr.message };
|
|
5195
6226
|
}
|
|
5196
|
-
const summary = { runId: run.id, agentOsId,
|
|
6227
|
+
const summary = { runId: run.id, agentOsId, snapshotPublished, releasedLocalOrphans, reap: reap.response ?? reap };
|
|
5197
6228
|
if (pipeline) return { ok: true, ...summary };
|
|
5198
6229
|
console.log(JSON.stringify(summary, null, 2));
|
|
5199
6230
|
} catch (error) {
|
|
@@ -5204,18 +6235,18 @@ async function sweepRun(args) {
|
|
|
5204
6235
|
}
|
|
5205
6236
|
|
|
5206
6237
|
// src/worktree.ts
|
|
5207
|
-
import { existsSync as
|
|
5208
|
-
import
|
|
6238
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync5 } from "node:fs";
|
|
6239
|
+
import path32 from "node:path";
|
|
5209
6240
|
|
|
5210
6241
|
// src/run-list.ts
|
|
5211
|
-
import { existsSync as
|
|
5212
|
-
import
|
|
6242
|
+
import { existsSync as existsSync22, readFileSync as readFileSync10 } from "node:fs";
|
|
6243
|
+
import path29 from "node:path";
|
|
5213
6244
|
|
|
5214
6245
|
// src/stale-reconcile.ts
|
|
5215
|
-
import
|
|
6246
|
+
import path28 from "node:path";
|
|
5216
6247
|
|
|
5217
6248
|
// src/finalize.ts
|
|
5218
|
-
import
|
|
6249
|
+
import path25 from "node:path";
|
|
5219
6250
|
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set([
|
|
5220
6251
|
"running",
|
|
5221
6252
|
"dispatching",
|
|
@@ -5233,7 +6264,7 @@ function deriveTerminalRunStatus(run) {
|
|
|
5233
6264
|
let anyLandingBlocked = false;
|
|
5234
6265
|
for (const name of names) {
|
|
5235
6266
|
const worker = readJson(
|
|
5236
|
-
|
|
6267
|
+
path25.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
5237
6268
|
void 0
|
|
5238
6269
|
);
|
|
5239
6270
|
if (!worker) continue;
|
|
@@ -5272,20 +6303,346 @@ function finalizeStaleRuns() {
|
|
|
5272
6303
|
return finalized;
|
|
5273
6304
|
}
|
|
5274
6305
|
|
|
6306
|
+
// src/worker-metadata-reconcile.ts
|
|
6307
|
+
import { existsSync as existsSync21, lstatSync, readdirSync as readdirSync5, readlinkSync, renameSync as renameSync2, rmSync } from "node:fs";
|
|
6308
|
+
import path27 from "node:path";
|
|
6309
|
+
|
|
6310
|
+
// src/worker-metadata-paths.ts
|
|
6311
|
+
import path26 from "node:path";
|
|
6312
|
+
var NESTED_RUNS = `${path26.sep}runs${path26.sep}runs${path26.sep}`;
|
|
6313
|
+
function hasNestedRunsSegment(filePath) {
|
|
6314
|
+
return filePath.includes(NESTED_RUNS);
|
|
6315
|
+
}
|
|
6316
|
+
function canonicalRunDir(harnessRoot, runId) {
|
|
6317
|
+
return path26.join(harnessRunsDir(normalizeHarnessRoot(harnessRoot)), safeSlug(runId));
|
|
6318
|
+
}
|
|
6319
|
+
function canonicalWorkerDir(harnessRoot, runId, workerName) {
|
|
6320
|
+
return path26.join(canonicalRunDir(harnessRoot, runId), "workers", safeSlug(workerName));
|
|
6321
|
+
}
|
|
6322
|
+
function legacyNestedWorkerDir(harnessRoot, runId, workerName) {
|
|
6323
|
+
const root = normalizeHarnessRoot(harnessRoot);
|
|
6324
|
+
return path26.join(root, "runs", "runs", safeSlug(runId), "workers", safeSlug(workerName));
|
|
6325
|
+
}
|
|
6326
|
+
function workerArtifactFileNames() {
|
|
6327
|
+
return [
|
|
6328
|
+
"stdout.jsonl",
|
|
6329
|
+
"stderr.log",
|
|
6330
|
+
"heartbeat.jsonl",
|
|
6331
|
+
"last-status.json",
|
|
6332
|
+
"auto-complete.log",
|
|
6333
|
+
"worker.json"
|
|
6334
|
+
];
|
|
6335
|
+
}
|
|
6336
|
+
function workerArtifactPaths(workerDir) {
|
|
6337
|
+
return {
|
|
6338
|
+
workerJsonPath: path26.join(workerDir, "worker.json"),
|
|
6339
|
+
stdoutPath: path26.join(workerDir, "stdout.jsonl"),
|
|
6340
|
+
stderrPath: path26.join(workerDir, "stderr.log"),
|
|
6341
|
+
heartbeatPath: path26.join(workerDir, "heartbeat.jsonl"),
|
|
6342
|
+
lastStatusPath: path26.join(workerDir, "last-status.json")
|
|
6343
|
+
};
|
|
6344
|
+
}
|
|
6345
|
+
function resolveWorkerJsonPath(input) {
|
|
6346
|
+
const canonical = workerArtifactPaths(canonicalWorkerDir(input.harnessRoot, input.runId, input.workerName)).workerJsonPath;
|
|
6347
|
+
if (input.statusPath && !hasNestedRunsSegment(input.statusPath)) {
|
|
6348
|
+
return input.statusPath;
|
|
6349
|
+
}
|
|
6350
|
+
return canonical;
|
|
6351
|
+
}
|
|
6352
|
+
|
|
6353
|
+
// src/worker-metadata-reconcile.ts
|
|
6354
|
+
function materializeSymlinkedRunDir(harnessRoot, runId) {
|
|
6355
|
+
const canonical = canonicalRunDir(harnessRoot, runId);
|
|
6356
|
+
let stat;
|
|
6357
|
+
try {
|
|
6358
|
+
stat = lstatSync(canonical);
|
|
6359
|
+
} catch {
|
|
6360
|
+
return null;
|
|
6361
|
+
}
|
|
6362
|
+
if (!stat.isSymbolicLink()) return null;
|
|
6363
|
+
const linkedTarget = path27.resolve(path27.dirname(canonical), readlinkSync(canonical));
|
|
6364
|
+
const staging = `${canonical}.materialize-${Date.now()}`;
|
|
6365
|
+
renameSync2(linkedTarget, staging);
|
|
6366
|
+
rmSync(canonical);
|
|
6367
|
+
renameSync2(staging, canonical);
|
|
6368
|
+
return linkedTarget;
|
|
6369
|
+
}
|
|
6370
|
+
function relocateArtifacts(input) {
|
|
6371
|
+
const moved = [];
|
|
6372
|
+
for (const fileName of workerArtifactFileNames()) {
|
|
6373
|
+
const from = path27.join(input.fromDir, fileName);
|
|
6374
|
+
const to = path27.join(input.toDir, fileName);
|
|
6375
|
+
if (!existsSync21(from) || existsSync21(to)) continue;
|
|
6376
|
+
renameSync2(from, to);
|
|
6377
|
+
moved.push(fileName);
|
|
6378
|
+
}
|
|
6379
|
+
return moved;
|
|
6380
|
+
}
|
|
6381
|
+
function repairWorkerPathFields(worker, canonicalDir) {
|
|
6382
|
+
const artifacts = workerArtifactPaths(canonicalDir);
|
|
6383
|
+
let changed = false;
|
|
6384
|
+
if (worker.workerDir !== canonicalDir) {
|
|
6385
|
+
worker.workerDir = canonicalDir;
|
|
6386
|
+
changed = true;
|
|
6387
|
+
}
|
|
6388
|
+
if (worker.stdoutPath !== artifacts.stdoutPath) {
|
|
6389
|
+
worker.stdoutPath = artifacts.stdoutPath;
|
|
6390
|
+
changed = true;
|
|
6391
|
+
}
|
|
6392
|
+
if (worker.stderrPath !== artifacts.stderrPath) {
|
|
6393
|
+
worker.stderrPath = artifacts.stderrPath;
|
|
6394
|
+
changed = true;
|
|
6395
|
+
}
|
|
6396
|
+
if (worker.heartbeatPath !== artifacts.heartbeatPath) {
|
|
6397
|
+
worker.heartbeatPath = artifacts.heartbeatPath;
|
|
6398
|
+
changed = true;
|
|
6399
|
+
}
|
|
6400
|
+
return changed;
|
|
6401
|
+
}
|
|
6402
|
+
function deriveSynthesizedStatus(input) {
|
|
6403
|
+
const statusFromLast = typeof input.lastStatus?.status === "string" ? input.lastStatus.status : void 0;
|
|
6404
|
+
const attentionState = input.lastStatus?.attention && typeof input.lastStatus.attention === "object" ? input.lastStatus.attention.state : void 0;
|
|
6405
|
+
if (statusFromLast === "done" || attentionState === "done") return "done";
|
|
6406
|
+
if (input.stdoutFinalResult || input.heartbeatFinalResult) return "done";
|
|
6407
|
+
if (statusFromLast === "blocked") return "blocked";
|
|
6408
|
+
if (statusFromLast === "running") return "exited";
|
|
6409
|
+
return statusFromLast ?? "exited";
|
|
6410
|
+
}
|
|
6411
|
+
function synthesizeWorkerFromArtifacts(input) {
|
|
6412
|
+
const artifacts = workerArtifactPaths(input.canonicalDir);
|
|
6413
|
+
const lastStatus = readJson(artifacts.lastStatusPath, void 0);
|
|
6414
|
+
const stdoutExists = existsSync21(artifacts.stdoutPath);
|
|
6415
|
+
const heartbeatExists = existsSync21(artifacts.heartbeatPath);
|
|
6416
|
+
const hasArtifacts = stdoutExists || heartbeatExists || lastStatus != null;
|
|
6417
|
+
if (!hasArtifacts) return null;
|
|
6418
|
+
const parsedStdout = stdoutExists ? parseHarnessStream(artifacts.stdoutPath) : { finalResult: null };
|
|
6419
|
+
const heartbeat = heartbeatExists ? parseHeartbeat(artifacts.heartbeatPath) : parseHeartbeat("");
|
|
6420
|
+
const heartbeatFinalResult = terminalFinalResultFromHeartbeat(heartbeat);
|
|
6421
|
+
const status = deriveSynthesizedStatus({
|
|
6422
|
+
lastStatus,
|
|
6423
|
+
stdoutFinalResult: parsedStdout.finalResult,
|
|
6424
|
+
heartbeatFinalResult
|
|
6425
|
+
});
|
|
6426
|
+
const worker = {
|
|
6427
|
+
name: input.workerName,
|
|
6428
|
+
runId: input.run.id,
|
|
6429
|
+
status,
|
|
6430
|
+
branch: typeof lastStatus?.branch === "string" ? lastStatus.branch : `agent/${input.run.id}/${input.workerName}`,
|
|
6431
|
+
worktreePath: typeof lastStatus?.worktreePath === "string" ? lastStatus.worktreePath : path27.join(getPaths().worktreesDir, input.run.id, input.workerName),
|
|
6432
|
+
workerDir: input.canonicalDir,
|
|
6433
|
+
stdoutPath: artifacts.stdoutPath,
|
|
6434
|
+
stderrPath: artifacts.stderrPath,
|
|
6435
|
+
heartbeatPath: artifacts.heartbeatPath,
|
|
6436
|
+
startedAt: typeof lastStatus?.startedAt === "string" ? lastStatus.startedAt : input.run.createdAt,
|
|
6437
|
+
reconciledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6438
|
+
reconcileReason: "synthesized worker.json from on-disk artifacts after metadata repair"
|
|
6439
|
+
};
|
|
6440
|
+
if (typeof lastStatus?.taskId === "string") worker.taskId = lastStatus.taskId;
|
|
6441
|
+
if (typeof lastStatus?.agentOsId === "string") worker.agentOsId = lastStatus.agentOsId;
|
|
6442
|
+
if (typeof lastStatus?.planId === "string") worker.planId = lastStatus.planId;
|
|
6443
|
+
if (typeof lastStatus?.pid === "number") worker.pid = lastStatus.pid;
|
|
6444
|
+
if (parsedStdout.finalResult || heartbeatFinalResult) {
|
|
6445
|
+
worker.completionSnapshot = {
|
|
6446
|
+
finalResult: parsedStdout.finalResult ?? heartbeatFinalResult ?? "completed",
|
|
6447
|
+
summary: heartbeat.lastHeartbeatSummary
|
|
6448
|
+
};
|
|
6449
|
+
}
|
|
6450
|
+
repairWorkerPathFields(worker, input.canonicalDir);
|
|
6451
|
+
return worker;
|
|
6452
|
+
}
|
|
6453
|
+
function repairRunWorkerIndex(run, harnessRoot) {
|
|
6454
|
+
let changed = false;
|
|
6455
|
+
const nextWorkers = { ...run.workers || {} };
|
|
6456
|
+
for (const [name, ref] of Object.entries(nextWorkers)) {
|
|
6457
|
+
const canonicalDir = canonicalWorkerDir(harnessRoot, run.id, name);
|
|
6458
|
+
const canonicalStatus = path27.join(canonicalDir, "worker.json");
|
|
6459
|
+
const nested = hasNestedRunsSegment(ref.workerDir) || hasNestedRunsSegment(ref.statusPath) || ref.workerDir !== canonicalDir || ref.statusPath !== canonicalStatus;
|
|
6460
|
+
if (!nested) continue;
|
|
6461
|
+
nextWorkers[name] = { workerDir: canonicalDir, statusPath: canonicalStatus };
|
|
6462
|
+
changed = true;
|
|
6463
|
+
}
|
|
6464
|
+
if (changed) {
|
|
6465
|
+
run.workers = nextWorkers;
|
|
6466
|
+
saveRun(run);
|
|
6467
|
+
}
|
|
6468
|
+
return changed;
|
|
6469
|
+
}
|
|
6470
|
+
function reconcileOneWorker(input) {
|
|
6471
|
+
const outcomes = [];
|
|
6472
|
+
const canonicalDir = canonicalWorkerDir(input.harnessRoot, input.run.id, input.workerName);
|
|
6473
|
+
const legacyDir = legacyNestedWorkerDir(input.harnessRoot, input.run.id, input.workerName);
|
|
6474
|
+
const canonicalArtifacts = workerArtifactPaths(canonicalDir);
|
|
6475
|
+
const moved = relocateArtifacts({ fromDir: legacyDir, toDir: canonicalDir });
|
|
6476
|
+
if (moved.length > 0) {
|
|
6477
|
+
outcomes.push({
|
|
6478
|
+
runId: input.run.id,
|
|
6479
|
+
worker: input.workerName,
|
|
6480
|
+
action: "relocated_artifacts",
|
|
6481
|
+
reason: `moved ${moved.join(", ")} from nested runs/runs layout`
|
|
6482
|
+
});
|
|
6483
|
+
}
|
|
6484
|
+
const workerJsonPath = resolveWorkerJsonPath({
|
|
6485
|
+
harnessRoot: input.harnessRoot,
|
|
6486
|
+
runId: input.run.id,
|
|
6487
|
+
workerName: input.workerName,
|
|
6488
|
+
statusPath: input.statusPath
|
|
6489
|
+
});
|
|
6490
|
+
let worker = readJson(workerJsonPath, void 0);
|
|
6491
|
+
if (!worker && existsSync21(canonicalArtifacts.workerJsonPath)) {
|
|
6492
|
+
worker = readJson(canonicalArtifacts.workerJsonPath, void 0);
|
|
6493
|
+
}
|
|
6494
|
+
if (!worker) {
|
|
6495
|
+
const synthesized = synthesizeWorkerFromArtifacts({
|
|
6496
|
+
run: input.run,
|
|
6497
|
+
workerName: input.workerName,
|
|
6498
|
+
canonicalDir,
|
|
6499
|
+
harnessRoot: input.harnessRoot
|
|
6500
|
+
});
|
|
6501
|
+
if (synthesized) {
|
|
6502
|
+
saveWorker(input.run.id, synthesized);
|
|
6503
|
+
outcomes.push({
|
|
6504
|
+
runId: input.run.id,
|
|
6505
|
+
worker: input.workerName,
|
|
6506
|
+
action: "synthesized_worker_json",
|
|
6507
|
+
reason: synthesized.reconcileReason ?? "synthesized from artifacts"
|
|
6508
|
+
});
|
|
6509
|
+
return outcomes;
|
|
6510
|
+
}
|
|
6511
|
+
const dirExists = existsSync21(canonicalDir);
|
|
6512
|
+
const hasAnyArtifact = workerArtifactFileNames().some((f) => existsSync21(path27.join(canonicalDir, f)));
|
|
6513
|
+
if (dirExists && !hasAnyArtifact) {
|
|
6514
|
+
const abandoned = {
|
|
6515
|
+
name: input.workerName,
|
|
6516
|
+
runId: input.run.id,
|
|
6517
|
+
status: "exited",
|
|
6518
|
+
branch: `agent/${input.run.id}/${input.workerName}`,
|
|
6519
|
+
worktreePath: path27.join(getPaths().worktreesDir, input.run.id, input.workerName),
|
|
6520
|
+
workerDir: canonicalDir,
|
|
6521
|
+
stdoutPath: canonicalArtifacts.stdoutPath,
|
|
6522
|
+
stderrPath: canonicalArtifacts.stderrPath,
|
|
6523
|
+
heartbeatPath: canonicalArtifacts.heartbeatPath,
|
|
6524
|
+
startedAt: input.run.createdAt,
|
|
6525
|
+
reconciledAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6526
|
+
reconcileReason: "empty worker dir \u2014 marked abandoned during metadata reconcile"
|
|
6527
|
+
};
|
|
6528
|
+
saveWorker(input.run.id, abandoned);
|
|
6529
|
+
outcomes.push({
|
|
6530
|
+
runId: input.run.id,
|
|
6531
|
+
worker: input.workerName,
|
|
6532
|
+
action: "marked_abandoned",
|
|
6533
|
+
reason: abandoned.reconcileReason ?? "empty worker dir"
|
|
6534
|
+
});
|
|
6535
|
+
return outcomes;
|
|
6536
|
+
}
|
|
6537
|
+
outcomes.push({
|
|
6538
|
+
runId: input.run.id,
|
|
6539
|
+
worker: input.workerName,
|
|
6540
|
+
action: "skipped",
|
|
6541
|
+
reason: "worker.json missing and no recoverable artifacts"
|
|
6542
|
+
});
|
|
6543
|
+
return outcomes;
|
|
6544
|
+
}
|
|
6545
|
+
if (repairWorkerPathFields(worker, canonicalDir)) {
|
|
6546
|
+
worker.reconciledAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6547
|
+
worker.reconcileReason = worker.reconcileReason ?? "repaired nested runs/runs path fields";
|
|
6548
|
+
saveWorker(input.run.id, worker);
|
|
6549
|
+
outcomes.push({
|
|
6550
|
+
runId: input.run.id,
|
|
6551
|
+
worker: input.workerName,
|
|
6552
|
+
action: "repaired_worker_paths",
|
|
6553
|
+
reason: worker.reconcileReason
|
|
6554
|
+
});
|
|
6555
|
+
} else {
|
|
6556
|
+
outcomes.push({
|
|
6557
|
+
runId: input.run.id,
|
|
6558
|
+
worker: input.workerName,
|
|
6559
|
+
action: "skipped",
|
|
6560
|
+
reason: "worker metadata already canonical"
|
|
6561
|
+
});
|
|
6562
|
+
}
|
|
6563
|
+
return outcomes;
|
|
6564
|
+
}
|
|
6565
|
+
function listOrphanWorkerNames(run, harnessRoot) {
|
|
6566
|
+
const workersDir = path27.join(runDirectory(run.id), "workers");
|
|
6567
|
+
if (!existsSync21(workersDir)) return [];
|
|
6568
|
+
const indexed = new Set(Object.keys(run.workers || {}));
|
|
6569
|
+
const orphans = [];
|
|
6570
|
+
for (const entry of readdirSync5(workersDir, { withFileTypes: true })) {
|
|
6571
|
+
if (!entry.isDirectory()) continue;
|
|
6572
|
+
if (indexed.has(entry.name)) continue;
|
|
6573
|
+
orphans.push(entry.name);
|
|
6574
|
+
}
|
|
6575
|
+
return orphans;
|
|
6576
|
+
}
|
|
6577
|
+
function reconcileWorkerMetadata() {
|
|
6578
|
+
const { harnessRoot } = getPaths();
|
|
6579
|
+
const outcomes = [];
|
|
6580
|
+
for (const run of listRunRecords()) {
|
|
6581
|
+
const materializedFrom = materializeSymlinkedRunDir(harnessRoot, run.id);
|
|
6582
|
+
if (materializedFrom) {
|
|
6583
|
+
outcomes.push({
|
|
6584
|
+
runId: run.id,
|
|
6585
|
+
worker: "*",
|
|
6586
|
+
action: "materialized_run_symlink",
|
|
6587
|
+
reason: `replaced symlink with real run dir (was ${materializedFrom})`
|
|
6588
|
+
});
|
|
6589
|
+
}
|
|
6590
|
+
if (repairRunWorkerIndex(run, harnessRoot)) {
|
|
6591
|
+
outcomes.push({
|
|
6592
|
+
runId: run.id,
|
|
6593
|
+
worker: "*",
|
|
6594
|
+
action: "repaired_run_index",
|
|
6595
|
+
reason: "repaired nested runs/runs workerDir/statusPath entries in run.json"
|
|
6596
|
+
});
|
|
6597
|
+
}
|
|
6598
|
+
const workerNames = new Set(Object.keys(run.workers || {}));
|
|
6599
|
+
for (const orphan of listOrphanWorkerNames(run, harnessRoot)) {
|
|
6600
|
+
workerNames.add(orphan);
|
|
6601
|
+
run.workers = {
|
|
6602
|
+
...run.workers || {},
|
|
6603
|
+
[orphan]: {
|
|
6604
|
+
workerDir: canonicalWorkerDir(harnessRoot, run.id, orphan),
|
|
6605
|
+
statusPath: path27.join(canonicalWorkerDir(harnessRoot, run.id, orphan), "worker.json")
|
|
6606
|
+
}
|
|
6607
|
+
};
|
|
6608
|
+
saveRun(run);
|
|
6609
|
+
outcomes.push({
|
|
6610
|
+
runId: run.id,
|
|
6611
|
+
worker: orphan,
|
|
6612
|
+
action: "repaired_run_index",
|
|
6613
|
+
reason: "indexed orphan worker dir into run.json"
|
|
6614
|
+
});
|
|
6615
|
+
}
|
|
6616
|
+
for (const name of workerNames) {
|
|
6617
|
+
const ref = run.workers?.[name];
|
|
6618
|
+
outcomes.push(
|
|
6619
|
+
...reconcileOneWorker({
|
|
6620
|
+
run,
|
|
6621
|
+
workerName: name,
|
|
6622
|
+
harnessRoot,
|
|
6623
|
+
statusPath: ref?.statusPath
|
|
6624
|
+
})
|
|
6625
|
+
);
|
|
6626
|
+
}
|
|
6627
|
+
}
|
|
6628
|
+
return { workers: outcomes };
|
|
6629
|
+
}
|
|
6630
|
+
|
|
5275
6631
|
// src/stale-reconcile.ts
|
|
5276
6632
|
var STALE_RECONCILE_HEARTBEAT_MS = 15 * 60 * 1e3;
|
|
5277
6633
|
function staleReconcileDisabled() {
|
|
5278
6634
|
return process.env.KYNVER_NO_STALE_CLEANUP === "1";
|
|
5279
6635
|
}
|
|
5280
6636
|
function reconcileStaleWorkers() {
|
|
6637
|
+
const metadataReconcile = reconcileWorkerMetadata();
|
|
5281
6638
|
if (staleReconcileDisabled()) {
|
|
5282
|
-
return { workers: [], finalizedRuns: finalizeStaleRuns() };
|
|
6639
|
+
return { workers: [], finalizedRuns: finalizeStaleRuns(), metadataReconcile };
|
|
5283
6640
|
}
|
|
5284
6641
|
const outcomes = [];
|
|
5285
6642
|
const now = Date.now();
|
|
5286
6643
|
for (const run of listRunRecords()) {
|
|
5287
6644
|
for (const name of Object.keys(run.workers || {})) {
|
|
5288
|
-
const workerPath =
|
|
6645
|
+
const workerPath = path28.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
5289
6646
|
const worker = readJson(workerPath, void 0);
|
|
5290
6647
|
if (!worker || worker.status !== "running") {
|
|
5291
6648
|
outcomes.push({
|
|
@@ -5357,20 +6714,29 @@ function reconcileStaleWorkers() {
|
|
|
5357
6714
|
});
|
|
5358
6715
|
}
|
|
5359
6716
|
}
|
|
5360
|
-
return { workers: outcomes, finalizedRuns: finalizeStaleRuns() };
|
|
6717
|
+
return { workers: outcomes, finalizedRuns: finalizeStaleRuns(), metadataReconcile };
|
|
5361
6718
|
}
|
|
5362
6719
|
function reconcileRunsCli() {
|
|
5363
6720
|
const result = reconcileStaleWorkers();
|
|
5364
6721
|
const markedExited = result.workers.filter((w) => w.action === "marked_exited").length;
|
|
5365
6722
|
const killedStale = result.workers.filter((w) => w.action === "killed_stale").length;
|
|
5366
6723
|
const skipped = result.workers.filter((w) => w.action === "skipped").length;
|
|
6724
|
+
const metadataTotals = result.metadataReconcile.workers.reduce((acc, row) => {
|
|
6725
|
+
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
6726
|
+
return acc;
|
|
6727
|
+
}, {});
|
|
5367
6728
|
console.log(
|
|
5368
6729
|
JSON.stringify(
|
|
5369
6730
|
{
|
|
5370
6731
|
ok: true,
|
|
5371
6732
|
workers: { markedExited, killedStale, skipped, total: result.workers.length },
|
|
6733
|
+
metadataReconcile: { totals: metadataTotals, total: result.metadataReconcile.workers.length },
|
|
5372
6734
|
finalizedRuns: result.finalizedRuns.length,
|
|
5373
|
-
details: {
|
|
6735
|
+
details: {
|
|
6736
|
+
workers: result.workers,
|
|
6737
|
+
metadataReconcile: result.metadataReconcile.workers,
|
|
6738
|
+
finalizedRuns: result.finalizedRuns
|
|
6739
|
+
}
|
|
5374
6740
|
},
|
|
5375
6741
|
null,
|
|
5376
6742
|
2
|
|
@@ -5380,15 +6746,15 @@ function reconcileRunsCli() {
|
|
|
5380
6746
|
|
|
5381
6747
|
// src/run-list.ts
|
|
5382
6748
|
function heartbeatByteLength(heartbeatPath) {
|
|
5383
|
-
if (!heartbeatPath || !
|
|
6749
|
+
if (!heartbeatPath || !existsSync22(heartbeatPath)) return 0;
|
|
5384
6750
|
try {
|
|
5385
|
-
return
|
|
6751
|
+
return readFileSync10(heartbeatPath, "utf8").trim().length;
|
|
5386
6752
|
} catch {
|
|
5387
6753
|
return 0;
|
|
5388
6754
|
}
|
|
5389
6755
|
}
|
|
5390
6756
|
function workerEvidence(run, workerName) {
|
|
5391
|
-
const workerPath =
|
|
6757
|
+
const workerPath = path29.join(runDirectory(run.id), "workers", safeSlug(workerName), "worker.json");
|
|
5392
6758
|
const worker = readJson(workerPath, void 0);
|
|
5393
6759
|
if (!worker) {
|
|
5394
6760
|
return {
|
|
@@ -5445,7 +6811,7 @@ function aggregateRunAttention(workers) {
|
|
|
5445
6811
|
function countOpenWorkers(run) {
|
|
5446
6812
|
let open = 0;
|
|
5447
6813
|
for (const name of Object.keys(run.workers || {})) {
|
|
5448
|
-
const workerPath =
|
|
6814
|
+
const workerPath = path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
5449
6815
|
const worker = readJson(workerPath, void 0);
|
|
5450
6816
|
if (!worker) continue;
|
|
5451
6817
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
@@ -5494,9 +6860,9 @@ function listRunsCli() {
|
|
|
5494
6860
|
}
|
|
5495
6861
|
|
|
5496
6862
|
// src/default-repo.ts
|
|
5497
|
-
import
|
|
6863
|
+
import path30 from "node:path";
|
|
5498
6864
|
function expandConfiguredRepo(value) {
|
|
5499
|
-
return
|
|
6865
|
+
return path30.resolve(resolveUserPath(value.trim()));
|
|
5500
6866
|
}
|
|
5501
6867
|
function fromConfigured(value, source, persistedInConfig) {
|
|
5502
6868
|
const trimmed = value?.trim();
|
|
@@ -5536,7 +6902,7 @@ function formatResolvedDefaultRepo(resolved) {
|
|
|
5536
6902
|
}
|
|
5537
6903
|
|
|
5538
6904
|
// src/validate.ts
|
|
5539
|
-
import
|
|
6905
|
+
import path31 from "node:path";
|
|
5540
6906
|
var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
5541
6907
|
function validateRunId(runId) {
|
|
5542
6908
|
const trimmed = runId.trim();
|
|
@@ -5544,7 +6910,7 @@ function validateRunId(runId) {
|
|
|
5544
6910
|
return trimmed;
|
|
5545
6911
|
}
|
|
5546
6912
|
function validateRepo(repo) {
|
|
5547
|
-
const resolved =
|
|
6913
|
+
const resolved = path31.resolve(repo);
|
|
5548
6914
|
if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
|
|
5549
6915
|
return resolved;
|
|
5550
6916
|
}
|
|
@@ -5563,7 +6929,7 @@ function createRun(args) {
|
|
|
5563
6929
|
ensureGitRepo(repo);
|
|
5564
6930
|
const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
|
|
5565
6931
|
const dir = runDirectory(id);
|
|
5566
|
-
if (
|
|
6932
|
+
if (existsSync23(dir)) failExists(`run already exists: ${id}`);
|
|
5567
6933
|
mkdirSync5(dir, { recursive: true });
|
|
5568
6934
|
const base = String(args.base || "origin/main");
|
|
5569
6935
|
const baseCommit = git(repo, ["rev-parse", base]).trim();
|
|
@@ -5577,7 +6943,7 @@ function createRun(args) {
|
|
|
5577
6943
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5578
6944
|
workers: {}
|
|
5579
6945
|
};
|
|
5580
|
-
writeJson(
|
|
6946
|
+
writeJson(path32.join(dir, "run.json"), run);
|
|
5581
6947
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
5582
6948
|
}
|
|
5583
6949
|
function listRuns() {
|
|
@@ -5589,8 +6955,8 @@ function failExists(message) {
|
|
|
5589
6955
|
}
|
|
5590
6956
|
|
|
5591
6957
|
// src/discard-disposable.ts
|
|
5592
|
-
import { existsSync as
|
|
5593
|
-
import
|
|
6958
|
+
import { existsSync as existsSync24, rmSync as rmSync2 } from "node:fs";
|
|
6959
|
+
import path33 from "node:path";
|
|
5594
6960
|
function normalizeRelativePath2(value) {
|
|
5595
6961
|
const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
5596
6962
|
if (!normalized || normalized.startsWith("/") || normalized.includes("..")) {
|
|
@@ -5611,18 +6977,18 @@ function discardDisposableArtifacts(args) {
|
|
|
5611
6977
|
if (paths.length === 0) {
|
|
5612
6978
|
return { ok: false, removed: [], reason: "requires at least one --path" };
|
|
5613
6979
|
}
|
|
5614
|
-
const worktreeRoot =
|
|
6980
|
+
const worktreeRoot = path33.resolve(worker.worktreePath);
|
|
5615
6981
|
const removed = [];
|
|
5616
6982
|
for (const raw of paths) {
|
|
5617
6983
|
const rel = normalizeRelativePath2(raw);
|
|
5618
|
-
const abs =
|
|
5619
|
-
if (!abs.startsWith(worktreeRoot +
|
|
6984
|
+
const abs = path33.resolve(worktreeRoot, rel);
|
|
6985
|
+
if (!abs.startsWith(worktreeRoot + path33.sep) && abs !== worktreeRoot) {
|
|
5620
6986
|
return { ok: false, removed, reason: `path escapes worktree: ${raw}` };
|
|
5621
6987
|
}
|
|
5622
|
-
if (!
|
|
6988
|
+
if (!existsSync24(abs)) {
|
|
5623
6989
|
return { ok: false, removed, reason: `path not found: ${raw}` };
|
|
5624
6990
|
}
|
|
5625
|
-
|
|
6991
|
+
rmSync2(abs, { recursive: true, force: true });
|
|
5626
6992
|
removed.push(rel);
|
|
5627
6993
|
}
|
|
5628
6994
|
const prior = Array.isArray(worker.disposableArtifactsRemoved) ? worker.disposableArtifactsRemoved.filter((p) => typeof p === "string") : [];
|
|
@@ -5642,7 +7008,7 @@ function discardDisposableCli(args) {
|
|
|
5642
7008
|
}
|
|
5643
7009
|
|
|
5644
7010
|
// src/pipeline-tick.ts
|
|
5645
|
-
import
|
|
7011
|
+
import path49 from "node:path";
|
|
5646
7012
|
|
|
5647
7013
|
// src/pipeline-dispatch.ts
|
|
5648
7014
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -5663,6 +7029,19 @@ async function runPipelineDispatch(args, slots) {
|
|
|
5663
7029
|
return { ok: true, skipped: true, reason: "no slots", maxStarts: 0, startedCount: 0 };
|
|
5664
7030
|
}
|
|
5665
7031
|
const base = stripCliMaxStarts(args);
|
|
7032
|
+
if (base.targetTaskId || base.targetTaskIds) {
|
|
7033
|
+
const target = await dispatchRun({
|
|
7034
|
+
...base,
|
|
7035
|
+
execute: true,
|
|
7036
|
+
pipeline: true,
|
|
7037
|
+
maxStarts: String(slots)
|
|
7038
|
+
});
|
|
7039
|
+
return {
|
|
7040
|
+
...typeof target === "object" && target !== null ? target : {},
|
|
7041
|
+
passes: { target },
|
|
7042
|
+
startedCount: countDispatchStarts(target)
|
|
7043
|
+
};
|
|
7044
|
+
}
|
|
5666
7045
|
const reviewBudget = Math.min(slots, RESERVED_REVIEW_STARTS);
|
|
5667
7046
|
const workBudget = Math.max(0, slots - reviewBudget);
|
|
5668
7047
|
const review = await dispatchRun({
|
|
@@ -5762,7 +7141,7 @@ function buildBoxResourceSnapshotFromGate(gate, input = {}) {
|
|
|
5762
7141
|
}
|
|
5763
7142
|
|
|
5764
7143
|
// src/plan-progress-daemon-sync.ts
|
|
5765
|
-
import
|
|
7144
|
+
import path34 from "node:path";
|
|
5766
7145
|
|
|
5767
7146
|
// src/plan-progress-sync.ts
|
|
5768
7147
|
async function syncPlanProgress(args) {
|
|
@@ -5786,7 +7165,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
5786
7165
|
const outcomes = [];
|
|
5787
7166
|
for (const name of Object.keys(run.workers || {})) {
|
|
5788
7167
|
const worker = readJson(
|
|
5789
|
-
|
|
7168
|
+
path34.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
5790
7169
|
void 0
|
|
5791
7170
|
);
|
|
5792
7171
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -5835,10 +7214,10 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
5835
7214
|
}
|
|
5836
7215
|
|
|
5837
7216
|
// src/cleanup.ts
|
|
5838
|
-
import
|
|
7217
|
+
import path47 from "node:path";
|
|
5839
7218
|
|
|
5840
7219
|
// src/cleanup-guards.ts
|
|
5841
|
-
import
|
|
7220
|
+
import path35 from "node:path";
|
|
5842
7221
|
|
|
5843
7222
|
// src/cleanup-run-liveness.ts
|
|
5844
7223
|
function isWorkerProcessLive(indexed) {
|
|
@@ -5902,72 +7281,217 @@ function prUrlFromFinalResult(finalResult) {
|
|
|
5902
7281
|
return null;
|
|
5903
7282
|
}
|
|
5904
7283
|
function isPrOrUnmergedWork(status) {
|
|
5905
|
-
if (prUrlFromFinalResult(status.finalResult)) return true;
|
|
5906
7284
|
const relation = status.gitAncestry?.relation;
|
|
7285
|
+
if (relation === "merged" || relation === "synced") {
|
|
7286
|
+
return materialWorktreeChanges2(status.changedFiles).length > 0;
|
|
7287
|
+
}
|
|
7288
|
+
if (prUrlFromFinalResult(status.finalResult)) return true;
|
|
5907
7289
|
if (relation === "ahead" || relation === "diverged") return true;
|
|
5908
7290
|
if (status.changedFiles.length > 0 && status.finalResult) return true;
|
|
5909
7291
|
return false;
|
|
5910
7292
|
}
|
|
5911
|
-
function hasUnrestorableWorktreeChanges(status) {
|
|
5912
|
-
if (materialWorktreeChanges2(status.changedFiles).length > 0) return true;
|
|
5913
|
-
if (status.gitAncestry?.relation === "diverged") return true;
|
|
5914
|
-
return false;
|
|
7293
|
+
function hasUnrestorableWorktreeChanges(status) {
|
|
7294
|
+
if (materialWorktreeChanges2(status.changedFiles).length > 0) return true;
|
|
7295
|
+
if (status.gitAncestry?.relation === "diverged") return true;
|
|
7296
|
+
return false;
|
|
7297
|
+
}
|
|
7298
|
+
function effectiveWorktreeAgeMs(input) {
|
|
7299
|
+
const { indexed, includeOrphans, worktreesAgeMs, terminalWorktreesAgeMs } = input;
|
|
7300
|
+
if (!indexed) return includeOrphans ? terminalWorktreesAgeMs : worktreesAgeMs;
|
|
7301
|
+
if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) {
|
|
7302
|
+
return terminalWorktreesAgeMs;
|
|
7303
|
+
}
|
|
7304
|
+
return worktreesAgeMs;
|
|
7305
|
+
}
|
|
7306
|
+
function skipWorktreeRemoval(input) {
|
|
7307
|
+
const { indexed, includeOrphans, worktreesAgeMs, ageMs, orphanSafety, worktreeRemovalGuard } = input;
|
|
7308
|
+
if (!indexed) {
|
|
7309
|
+
if (!includeOrphans) return "orphan_without_flag";
|
|
7310
|
+
return orphanSafety ?? null;
|
|
7311
|
+
}
|
|
7312
|
+
const ageThresholdMs = effectiveWorktreeAgeMs(input);
|
|
7313
|
+
if (worktreesAgeMs <= 0 && !includeOrphans && ageThresholdMs <= 0) return "worktrees_disabled";
|
|
7314
|
+
if (ageThresholdMs > 0 && ageMs < ageThresholdMs) return "below_age_threshold";
|
|
7315
|
+
if (isWorkerProcessLive(indexed)) return "active_worker";
|
|
7316
|
+
if (indexed.worker.completionBlocker) return "completion_blocked";
|
|
7317
|
+
if (runBlocksWorktreeRemoval(indexed)) return "run_still_active";
|
|
7318
|
+
if (!isFinishedWorkerStatus(indexed.status)) return "run_still_active";
|
|
7319
|
+
if (isPrOrUnmergedWork(indexed.status)) return "pr_or_unmerged_commits";
|
|
7320
|
+
if (materialWorktreeChanges2(indexed.status.changedFiles).length > 0) return "dirty_worktree";
|
|
7321
|
+
const landing = assessWorkerLanding({
|
|
7322
|
+
finalResult: indexed.status.finalResult,
|
|
7323
|
+
changedFiles: indexed.status.changedFiles,
|
|
7324
|
+
gitAncestry: indexed.status.gitAncestry,
|
|
7325
|
+
prUrl: prUrlFromFinalResult(indexed.status.finalResult)
|
|
7326
|
+
});
|
|
7327
|
+
if (landing.blocked) return "landing_blocked";
|
|
7328
|
+
if (worktreeRemovalGuard && input.worktreePath) {
|
|
7329
|
+
const overlay = worktreeRemovalGuard({
|
|
7330
|
+
worktreePath: input.worktreePath,
|
|
7331
|
+
indexed: Boolean(indexed),
|
|
7332
|
+
runId: indexed?.runId,
|
|
7333
|
+
worker: indexed?.workerName
|
|
7334
|
+
});
|
|
7335
|
+
if (overlay) {
|
|
7336
|
+
return overlay.detail ? { reason: overlay.reason, detail: overlay.detail } : overlay.reason;
|
|
7337
|
+
}
|
|
7338
|
+
}
|
|
7339
|
+
return null;
|
|
7340
|
+
}
|
|
7341
|
+
function skipDependencyCacheRemoval(input) {
|
|
7342
|
+
const { indexed, nodeModulesAgeMs, ageMs, worktreePath, activeWorktreePaths, diskPressure } = input;
|
|
7343
|
+
if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
|
|
7344
|
+
if (activeWorktreePaths.has(path35.resolve(worktreePath))) return "active_worker";
|
|
7345
|
+
if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
|
|
7346
|
+
if (indexed && hasUnrestorableWorktreeChanges(indexed.status)) return "dirty_worktree";
|
|
7347
|
+
return null;
|
|
7348
|
+
}
|
|
7349
|
+
function skipBuildCacheRemoval(input) {
|
|
7350
|
+
return skipDependencyCacheRemoval(input);
|
|
7351
|
+
}
|
|
7352
|
+
|
|
7353
|
+
// src/cleanup-types.ts
|
|
7354
|
+
var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
7355
|
+
var DEFAULT_WORKTREES_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
7356
|
+
var DEFAULT_TERMINAL_WORKTREES_AGE_MS = DEFAULT_NODE_MODULES_AGE_MS;
|
|
7357
|
+
var DEFAULT_RUN_DIRECTORIES_AGE_MS = 60 * 60 * 1e3;
|
|
7358
|
+
var DEFAULT_MAX_ACTIONS_PER_SWEEP = 120;
|
|
7359
|
+
var MAX_PRESERVED_LIVE_PATH_SAMPLES = 24;
|
|
7360
|
+
|
|
7361
|
+
// src/cleanup-evidence.ts
|
|
7362
|
+
var LIVE_SKIP_REASONS = /* @__PURE__ */ new Set([
|
|
7363
|
+
"active_worker",
|
|
7364
|
+
"run_still_active",
|
|
7365
|
+
"completion_blocked",
|
|
7366
|
+
"landing_blocked"
|
|
7367
|
+
]);
|
|
7368
|
+
function collectPreservedLivePaths(actions, skips) {
|
|
7369
|
+
const out = [];
|
|
7370
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7371
|
+
const push = (path59, reason, detail) => {
|
|
7372
|
+
const key = `${path59}\0${reason}`;
|
|
7373
|
+
if (seen.has(key) || out.length >= MAX_PRESERVED_LIVE_PATH_SAMPLES) return;
|
|
7374
|
+
seen.add(key);
|
|
7375
|
+
out.push({ path: path59, reason, ...detail ? { detail } : {} });
|
|
7376
|
+
};
|
|
7377
|
+
for (const skip2 of skips) {
|
|
7378
|
+
if (!LIVE_SKIP_REASONS.has(skip2.reason)) continue;
|
|
7379
|
+
push(skip2.path, skip2.reason, skip2.detail);
|
|
7380
|
+
}
|
|
7381
|
+
for (const action of actions) {
|
|
7382
|
+
if (!action.skipped || !action.skipReason) continue;
|
|
7383
|
+
if (!LIVE_SKIP_REASONS.has(action.skipReason)) continue;
|
|
7384
|
+
push(action.path, action.skipReason);
|
|
7385
|
+
}
|
|
7386
|
+
return out;
|
|
7387
|
+
}
|
|
7388
|
+
|
|
7389
|
+
// src/cleanup-run-directory.ts
|
|
7390
|
+
import { existsSync as existsSync25, readdirSync as readdirSync6, statSync as statSync4 } from "node:fs";
|
|
7391
|
+
import path37 from "node:path";
|
|
7392
|
+
|
|
7393
|
+
// src/cleanup-active-worktrees.ts
|
|
7394
|
+
import path36 from "node:path";
|
|
7395
|
+
function isActiveHarnessWorker2(worker, runBase, runBaseCommit) {
|
|
7396
|
+
const status = computeWorkerStatus(worker, { base: runBase, baseCommit: runBaseCommit });
|
|
7397
|
+
return status.alive && !status.finalResult && status.attention.state !== "done";
|
|
7398
|
+
}
|
|
7399
|
+
function collectActiveWorktreeGuards(harnessRoots) {
|
|
7400
|
+
const activeWorktreePaths = /* @__PURE__ */ new Set();
|
|
7401
|
+
const liveRunKeys = /* @__PURE__ */ new Set();
|
|
7402
|
+
for (const harnessRoot of harnessRoots) {
|
|
7403
|
+
for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
|
|
7404
|
+
let runHasLive = false;
|
|
7405
|
+
for (const name of Object.keys(run.workers || {})) {
|
|
7406
|
+
const worker = readJson(
|
|
7407
|
+
path36.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
|
|
7408
|
+
void 0
|
|
7409
|
+
);
|
|
7410
|
+
if (!worker?.worktreePath) continue;
|
|
7411
|
+
const worktreePath = path36.resolve(worker.worktreePath);
|
|
7412
|
+
if (!isActiveHarnessWorker2(worker, run.base, run.baseCommit)) continue;
|
|
7413
|
+
runHasLive = true;
|
|
7414
|
+
activeWorktreePaths.add(worktreePath);
|
|
7415
|
+
}
|
|
7416
|
+
if (runHasLive) liveRunKeys.add(`${harnessRoot}\0${run.id}`);
|
|
7417
|
+
}
|
|
7418
|
+
}
|
|
7419
|
+
return { activeWorktreePaths, liveRunKeys };
|
|
7420
|
+
}
|
|
7421
|
+
function isWorktreeOnLiveRun(worktreePath, harnessRoot, runId, liveRunKeys) {
|
|
7422
|
+
if (!runId) return false;
|
|
7423
|
+
return liveRunKeys.has(`${harnessRoot}\0${runId}`);
|
|
5915
7424
|
}
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
7425
|
+
|
|
7426
|
+
// src/cleanup-run-directory.ts
|
|
7427
|
+
function pathAgeMs(target, now) {
|
|
7428
|
+
try {
|
|
7429
|
+
const mtime = statSync4(target).mtimeMs;
|
|
7430
|
+
return Math.max(0, now - mtime);
|
|
7431
|
+
} catch {
|
|
7432
|
+
return 0;
|
|
5921
7433
|
}
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
if (
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
prUrl: prUrlFromFinalResult(indexed.status.finalResult)
|
|
5935
|
-
});
|
|
5936
|
-
if (landing.blocked) return "landing_blocked";
|
|
5937
|
-
if (worktreeRemovalGuard && input.worktreePath) {
|
|
5938
|
-
const overlay = worktreeRemovalGuard({
|
|
5939
|
-
worktreePath: input.worktreePath,
|
|
5940
|
-
indexed: Boolean(indexed),
|
|
5941
|
-
runId: indexed?.runId,
|
|
5942
|
-
worker: indexed?.workerName
|
|
5943
|
-
});
|
|
5944
|
-
if (overlay) {
|
|
5945
|
-
return overlay.detail ? { reason: overlay.reason, detail: overlay.detail } : overlay.reason;
|
|
5946
|
-
}
|
|
7434
|
+
}
|
|
7435
|
+
function loadRunStatus(harnessRoot, runId) {
|
|
7436
|
+
const runPath = path37.join(harnessRoot, "runs", runId, "run.json");
|
|
7437
|
+
if (!existsSync25(runPath)) return null;
|
|
7438
|
+
return readJson(runPath, null);
|
|
7439
|
+
}
|
|
7440
|
+
function runDirectoryIsEmpty(runPath) {
|
|
7441
|
+
try {
|
|
7442
|
+
const entries = readdirSync6(runPath);
|
|
7443
|
+
return entries.length === 0;
|
|
7444
|
+
} catch {
|
|
7445
|
+
return false;
|
|
5947
7446
|
}
|
|
5948
|
-
return null;
|
|
5949
7447
|
}
|
|
5950
|
-
function
|
|
5951
|
-
const {
|
|
5952
|
-
if (
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
if (
|
|
7448
|
+
function skipRunDirectoryRemoval(input) {
|
|
7449
|
+
const { harnessRoot, runId, runPath, ageMs, runDirectoriesAgeMs, activeGuards } = input;
|
|
7450
|
+
if (isWorktreeOnLiveRun(runPath, harnessRoot, runId, activeGuards.liveRunKeys)) {
|
|
7451
|
+
return "run_still_active";
|
|
7452
|
+
}
|
|
7453
|
+
if (!runDirectoryIsEmpty(runPath)) return "run_still_active";
|
|
7454
|
+
const run = loadRunStatus(harnessRoot, runId);
|
|
7455
|
+
if (run && !TERMINAL_RUN_STATUSES.has(run.status)) return "run_still_active";
|
|
7456
|
+
if (runDirectoriesAgeMs > 0 && ageMs < runDirectoriesAgeMs) return "below_age_threshold";
|
|
5956
7457
|
return null;
|
|
5957
7458
|
}
|
|
5958
|
-
function
|
|
5959
|
-
|
|
7459
|
+
function scanStaleRunDirectoryCandidates(opts) {
|
|
7460
|
+
if (!existsSync25(opts.worktreesDir)) return [];
|
|
7461
|
+
const candidates = [];
|
|
7462
|
+
let entries;
|
|
7463
|
+
try {
|
|
7464
|
+
entries = readdirSync6(opts.worktreesDir, { withFileTypes: true });
|
|
7465
|
+
} catch {
|
|
7466
|
+
return [];
|
|
7467
|
+
}
|
|
7468
|
+
for (const runEntry of entries) {
|
|
7469
|
+
if (!runEntry.isDirectory()) continue;
|
|
7470
|
+
const runId = runEntry.name;
|
|
7471
|
+
if (opts.runIdFilter && runId !== opts.runIdFilter) continue;
|
|
7472
|
+
const runPath = path37.join(opts.worktreesDir, runId);
|
|
7473
|
+
if (!runDirectoryIsEmpty(runPath)) continue;
|
|
7474
|
+
candidates.push({
|
|
7475
|
+
kind: "remove_run_directory",
|
|
7476
|
+
path: runPath,
|
|
7477
|
+
bytes: null,
|
|
7478
|
+
harnessRoot: opts.harnessRoot,
|
|
7479
|
+
runId,
|
|
7480
|
+
ageMs: pathAgeMs(runPath, opts.now)
|
|
7481
|
+
});
|
|
7482
|
+
}
|
|
7483
|
+
return candidates;
|
|
5960
7484
|
}
|
|
5961
7485
|
|
|
5962
7486
|
// src/cleanup-execute.ts
|
|
5963
|
-
import { existsSync as
|
|
5964
|
-
import
|
|
7487
|
+
import { existsSync as existsSync27, rmSync as rmSync3 } from "node:fs";
|
|
7488
|
+
import path39 from "node:path";
|
|
5965
7489
|
|
|
5966
7490
|
// src/cleanup-dir-size.ts
|
|
5967
|
-
import { existsSync as
|
|
5968
|
-
import
|
|
7491
|
+
import { existsSync as existsSync26, readdirSync as readdirSync7, statSync as statSync5 } from "node:fs";
|
|
7492
|
+
import path38 from "node:path";
|
|
5969
7493
|
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
5970
|
-
if (!
|
|
7494
|
+
if (!existsSync26(root)) return 0;
|
|
5971
7495
|
let total = 0;
|
|
5972
7496
|
let seen = 0;
|
|
5973
7497
|
const stack = [root];
|
|
@@ -5975,16 +7499,16 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
5975
7499
|
const current = stack.pop();
|
|
5976
7500
|
let entries;
|
|
5977
7501
|
try {
|
|
5978
|
-
entries =
|
|
7502
|
+
entries = readdirSync7(current);
|
|
5979
7503
|
} catch {
|
|
5980
7504
|
continue;
|
|
5981
7505
|
}
|
|
5982
7506
|
for (const name of entries) {
|
|
5983
7507
|
if (seen++ > maxEntries) return null;
|
|
5984
|
-
const full =
|
|
7508
|
+
const full = path38.join(current, name);
|
|
5985
7509
|
let st;
|
|
5986
7510
|
try {
|
|
5987
|
-
st =
|
|
7511
|
+
st = statSync5(full);
|
|
5988
7512
|
} catch {
|
|
5989
7513
|
continue;
|
|
5990
7514
|
}
|
|
@@ -5997,7 +7521,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
5997
7521
|
|
|
5998
7522
|
// src/cleanup-execute.ts
|
|
5999
7523
|
function removeDependencyCache(candidate, execute) {
|
|
6000
|
-
if (!
|
|
7524
|
+
if (!existsSync27(candidate.path)) {
|
|
6001
7525
|
return {
|
|
6002
7526
|
...candidate,
|
|
6003
7527
|
executed: false,
|
|
@@ -6010,7 +7534,7 @@ function removeDependencyCache(candidate, execute) {
|
|
|
6010
7534
|
}
|
|
6011
7535
|
try {
|
|
6012
7536
|
const bytesBefore = candidate.bytes ?? directorySizeBytes(candidate.path);
|
|
6013
|
-
|
|
7537
|
+
rmSync3(candidate.path, { recursive: true, force: true });
|
|
6014
7538
|
return {
|
|
6015
7539
|
...candidate,
|
|
6016
7540
|
bytes: bytesBefore,
|
|
@@ -6036,8 +7560,39 @@ function removeNextCache(candidate, execute) {
|
|
|
6036
7560
|
function removeBuildCache(candidate, execute) {
|
|
6037
7561
|
return removeDependencyCache(candidate, execute);
|
|
6038
7562
|
}
|
|
7563
|
+
function removeRunDirectory(candidate, execute) {
|
|
7564
|
+
if (!existsSync27(candidate.path)) {
|
|
7565
|
+
return {
|
|
7566
|
+
...candidate,
|
|
7567
|
+
executed: false,
|
|
7568
|
+
skipped: true,
|
|
7569
|
+
skipReason: "missing_worktree"
|
|
7570
|
+
};
|
|
7571
|
+
}
|
|
7572
|
+
if (!execute) {
|
|
7573
|
+
return { ...candidate, executed: false, skipped: true, skipReason: "dry_run" };
|
|
7574
|
+
}
|
|
7575
|
+
try {
|
|
7576
|
+
const bytesBefore = candidate.bytes ?? directorySizeBytes(candidate.path);
|
|
7577
|
+
rmSync3(candidate.path, { recursive: true, force: true });
|
|
7578
|
+
return {
|
|
7579
|
+
...candidate,
|
|
7580
|
+
bytes: bytesBefore,
|
|
7581
|
+
executed: true,
|
|
7582
|
+
skipped: false
|
|
7583
|
+
};
|
|
7584
|
+
} catch (error) {
|
|
7585
|
+
return {
|
|
7586
|
+
...candidate,
|
|
7587
|
+
executed: false,
|
|
7588
|
+
skipped: true,
|
|
7589
|
+
skipReason: "remove_failed",
|
|
7590
|
+
error: error.message
|
|
7591
|
+
};
|
|
7592
|
+
}
|
|
7593
|
+
}
|
|
6039
7594
|
function removeWorktree(candidate, execute) {
|
|
6040
|
-
if (!
|
|
7595
|
+
if (!existsSync27(candidate.path)) {
|
|
6041
7596
|
return {
|
|
6042
7597
|
...candidate,
|
|
6043
7598
|
executed: false,
|
|
@@ -6054,8 +7609,8 @@ function removeWorktree(candidate, execute) {
|
|
|
6054
7609
|
if (repo) {
|
|
6055
7610
|
git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
|
|
6056
7611
|
}
|
|
6057
|
-
if (
|
|
6058
|
-
|
|
7612
|
+
if (existsSync27(candidate.path)) {
|
|
7613
|
+
rmSync3(candidate.path, { recursive: true, force: true });
|
|
6059
7614
|
}
|
|
6060
7615
|
return {
|
|
6061
7616
|
...candidate,
|
|
@@ -6074,15 +7629,15 @@ function removeWorktree(candidate, execute) {
|
|
|
6074
7629
|
}
|
|
6075
7630
|
}
|
|
6076
7631
|
function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
|
|
6077
|
-
const resolved =
|
|
6078
|
-
const suffix = `${
|
|
7632
|
+
const resolved = path39.resolve(targetPath);
|
|
7633
|
+
const suffix = `${path39.sep}${cacheDirName}`;
|
|
6079
7634
|
const cachePath = resolved.endsWith(suffix) ? resolved : null;
|
|
6080
7635
|
if (!cachePath) return "path_outside_harness";
|
|
6081
|
-
const rel =
|
|
6082
|
-
if (rel.startsWith("..") ||
|
|
6083
|
-
const parts = rel.split(
|
|
7636
|
+
const rel = path39.relative(worktreesDir, cachePath);
|
|
7637
|
+
if (rel.startsWith("..") || path39.isAbsolute(rel)) return "path_outside_harness";
|
|
7638
|
+
const parts = rel.split(path39.sep);
|
|
6084
7639
|
if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
|
|
6085
|
-
if (!resolved.startsWith(
|
|
7640
|
+
if (!resolved.startsWith(path39.resolve(harnessRoot))) return "path_outside_harness";
|
|
6086
7641
|
return null;
|
|
6087
7642
|
}
|
|
6088
7643
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
@@ -6092,37 +7647,37 @@ function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
|
6092
7647
|
return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
|
|
6093
7648
|
}
|
|
6094
7649
|
function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
6095
|
-
const resolved =
|
|
6096
|
-
const relToWt =
|
|
6097
|
-
if (relToWt.startsWith("..") ||
|
|
6098
|
-
const parts = relToWt.split(
|
|
7650
|
+
const resolved = path39.resolve(targetPath);
|
|
7651
|
+
const relToWt = path39.relative(worktreesDir, resolved);
|
|
7652
|
+
if (relToWt.startsWith("..") || path39.isAbsolute(relToWt)) return "path_outside_harness";
|
|
7653
|
+
const parts = relToWt.split(path39.sep);
|
|
6099
7654
|
if (parts.length < 3) return "path_outside_harness";
|
|
6100
|
-
if (!resolved.startsWith(
|
|
7655
|
+
if (!resolved.startsWith(path39.resolve(harnessRoot))) return "path_outside_harness";
|
|
6101
7656
|
return null;
|
|
6102
7657
|
}
|
|
6103
7658
|
|
|
6104
7659
|
// src/cleanup-scan.ts
|
|
6105
|
-
import { existsSync as
|
|
6106
|
-
import
|
|
6107
|
-
function
|
|
7660
|
+
import { existsSync as existsSync28, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
|
|
7661
|
+
import path40 from "node:path";
|
|
7662
|
+
function pathAgeMs2(target, now) {
|
|
6108
7663
|
try {
|
|
6109
|
-
const mtime =
|
|
7664
|
+
const mtime = statSync6(target).mtimeMs;
|
|
6110
7665
|
return Math.max(0, now - mtime);
|
|
6111
7666
|
} catch {
|
|
6112
7667
|
return 0;
|
|
6113
7668
|
}
|
|
6114
7669
|
}
|
|
6115
7670
|
function isPathInside(child, parent) {
|
|
6116
|
-
const rel =
|
|
6117
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
7671
|
+
const rel = path40.relative(parent, child);
|
|
7672
|
+
return rel === "" || !rel.startsWith("..") && !path40.isAbsolute(rel);
|
|
6118
7673
|
}
|
|
6119
7674
|
function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
|
|
6120
7675
|
const out = [];
|
|
6121
7676
|
for (const rel of HARNESS_BUILD_CACHE_RELATIVE_PATHS) {
|
|
6122
7677
|
if (rel === ".next") continue;
|
|
6123
|
-
const target =
|
|
6124
|
-
if (!
|
|
6125
|
-
const resolved =
|
|
7678
|
+
const target = path40.join(worktreePath, rel);
|
|
7679
|
+
if (!existsSync28(target)) continue;
|
|
7680
|
+
const resolved = path40.resolve(target);
|
|
6126
7681
|
if (seen.has(resolved)) continue;
|
|
6127
7682
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
6128
7683
|
seen.add(resolved);
|
|
@@ -6133,7 +7688,7 @@ function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
|
|
|
6133
7688
|
runId: meta.runId,
|
|
6134
7689
|
worker: meta.worker,
|
|
6135
7690
|
repo: meta.repo,
|
|
6136
|
-
ageMs:
|
|
7691
|
+
ageMs: pathAgeMs2(resolved, opts.now)
|
|
6137
7692
|
});
|
|
6138
7693
|
}
|
|
6139
7694
|
return out;
|
|
@@ -6151,13 +7706,13 @@ function scanBuildCacheCandidates(opts) {
|
|
|
6151
7706
|
})
|
|
6152
7707
|
);
|
|
6153
7708
|
}
|
|
6154
|
-
if (!opts.includeOrphans || !
|
|
6155
|
-
for (const runEntry of
|
|
7709
|
+
if (!opts.includeOrphans || !existsSync28(opts.worktreesDir)) return candidates;
|
|
7710
|
+
for (const runEntry of readdirSync8(opts.worktreesDir, { withFileTypes: true })) {
|
|
6156
7711
|
if (!runEntry.isDirectory()) continue;
|
|
6157
|
-
const runPath =
|
|
6158
|
-
for (const workerEntry of
|
|
7712
|
+
const runPath = path40.join(opts.worktreesDir, runEntry.name);
|
|
7713
|
+
for (const workerEntry of readdirSync8(runPath, { withFileTypes: true })) {
|
|
6159
7714
|
if (!workerEntry.isDirectory()) continue;
|
|
6160
|
-
const worktreePath =
|
|
7715
|
+
const worktreePath = path40.join(runPath, workerEntry.name);
|
|
6161
7716
|
candidates.push(
|
|
6162
7717
|
...collectBuildCacheForWorktree(worktreePath, opts, seen, {
|
|
6163
7718
|
runId: runEntry.name,
|
|
@@ -6178,7 +7733,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
6178
7733
|
for (const entry of opts.index.values()) {
|
|
6179
7734
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
6180
7735
|
const resolved = entry.worktreePath;
|
|
6181
|
-
if (!
|
|
7736
|
+
if (!existsSync28(resolved)) continue;
|
|
6182
7737
|
if (seen.has(resolved)) continue;
|
|
6183
7738
|
seen.add(resolved);
|
|
6184
7739
|
candidates.push({
|
|
@@ -6188,28 +7743,28 @@ function scanWorktreeCandidates(opts) {
|
|
|
6188
7743
|
runId: entry.runId,
|
|
6189
7744
|
worker: entry.workerName,
|
|
6190
7745
|
repo: entry.run.repo,
|
|
6191
|
-
ageMs:
|
|
7746
|
+
ageMs: pathAgeMs2(resolved, opts.now)
|
|
6192
7747
|
});
|
|
6193
7748
|
}
|
|
6194
7749
|
}
|
|
6195
|
-
if (!orphanEnabled || !
|
|
7750
|
+
if (!orphanEnabled || !existsSync28(opts.worktreesDir)) return candidates;
|
|
6196
7751
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
6197
7752
|
for (const entry of opts.index.values()) {
|
|
6198
|
-
indexedPaths.add(
|
|
7753
|
+
indexedPaths.add(path40.resolve(entry.worktreePath));
|
|
6199
7754
|
}
|
|
6200
|
-
for (const runEntry of
|
|
7755
|
+
for (const runEntry of readdirSync8(opts.worktreesDir, { withFileTypes: true })) {
|
|
6201
7756
|
if (!runEntry.isDirectory()) continue;
|
|
6202
7757
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
6203
|
-
const runPath =
|
|
7758
|
+
const runPath = path40.join(opts.worktreesDir, runEntry.name);
|
|
6204
7759
|
let workerEntries;
|
|
6205
7760
|
try {
|
|
6206
|
-
workerEntries =
|
|
7761
|
+
workerEntries = readdirSync8(runPath, { withFileTypes: true });
|
|
6207
7762
|
} catch {
|
|
6208
7763
|
continue;
|
|
6209
7764
|
}
|
|
6210
7765
|
for (const workerEntry of workerEntries) {
|
|
6211
7766
|
if (!workerEntry.isDirectory()) continue;
|
|
6212
|
-
const worktreePath =
|
|
7767
|
+
const worktreePath = path40.resolve(path40.join(runPath, workerEntry.name));
|
|
6213
7768
|
if (seen.has(worktreePath)) continue;
|
|
6214
7769
|
if (indexedPaths.has(worktreePath)) continue;
|
|
6215
7770
|
if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
|
|
@@ -6220,7 +7775,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
6220
7775
|
bytes: null,
|
|
6221
7776
|
runId: runEntry.name,
|
|
6222
7777
|
worker: workerEntry.name,
|
|
6223
|
-
ageMs:
|
|
7778
|
+
ageMs: pathAgeMs2(worktreePath, opts.now)
|
|
6224
7779
|
});
|
|
6225
7780
|
}
|
|
6226
7781
|
}
|
|
@@ -6228,27 +7783,27 @@ function scanWorktreeCandidates(opts) {
|
|
|
6228
7783
|
}
|
|
6229
7784
|
|
|
6230
7785
|
// src/cleanup-dependency-scan.ts
|
|
6231
|
-
import { existsSync as
|
|
6232
|
-
import
|
|
7786
|
+
import { existsSync as existsSync29, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
|
|
7787
|
+
import path41 from "node:path";
|
|
6233
7788
|
var DEPENDENCY_CACHE_DIRS = [
|
|
6234
7789
|
{ dirName: "node_modules", kind: "remove_node_modules" },
|
|
6235
7790
|
{ dirName: ".next", kind: "remove_next_cache" }
|
|
6236
7791
|
];
|
|
6237
|
-
function
|
|
7792
|
+
function pathAgeMs3(target, now) {
|
|
6238
7793
|
try {
|
|
6239
|
-
const mtime =
|
|
7794
|
+
const mtime = statSync7(target).mtimeMs;
|
|
6240
7795
|
return Math.max(0, now - mtime);
|
|
6241
7796
|
} catch {
|
|
6242
7797
|
return 0;
|
|
6243
7798
|
}
|
|
6244
7799
|
}
|
|
6245
7800
|
function isPathInside2(child, parent) {
|
|
6246
|
-
const rel =
|
|
6247
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
7801
|
+
const rel = path41.relative(parent, child);
|
|
7802
|
+
return rel === "" || !rel.startsWith("..") && !path41.isAbsolute(rel);
|
|
6248
7803
|
}
|
|
6249
7804
|
function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
6250
|
-
if (!
|
|
6251
|
-
const resolved =
|
|
7805
|
+
if (!existsSync29(targetPath)) return;
|
|
7806
|
+
const resolved = path41.resolve(targetPath);
|
|
6252
7807
|
if (seen.has(resolved)) return;
|
|
6253
7808
|
if (!isPathInside2(resolved, opts.harnessRoot)) return;
|
|
6254
7809
|
seen.add(resolved);
|
|
@@ -6260,12 +7815,12 @@ function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
|
6260
7815
|
runId: meta.runId,
|
|
6261
7816
|
worker: meta.worker,
|
|
6262
7817
|
repo: meta.repo,
|
|
6263
|
-
ageMs:
|
|
7818
|
+
ageMs: pathAgeMs3(resolved, opts.now)
|
|
6264
7819
|
});
|
|
6265
7820
|
}
|
|
6266
7821
|
function scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, meta) {
|
|
6267
7822
|
for (const entry of DEPENDENCY_CACHE_DIRS) {
|
|
6268
|
-
pushCandidate2(candidates, seen, opts,
|
|
7823
|
+
pushCandidate2(candidates, seen, opts, path41.join(worktreePath, entry.dirName), entry.kind, meta);
|
|
6269
7824
|
}
|
|
6270
7825
|
}
|
|
6271
7826
|
function scanDependencyCacheCandidates(opts) {
|
|
@@ -6279,20 +7834,20 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
6279
7834
|
repo: entry.run.repo
|
|
6280
7835
|
});
|
|
6281
7836
|
}
|
|
6282
|
-
if (!
|
|
6283
|
-
for (const runEntry of
|
|
7837
|
+
if (!existsSync29(opts.worktreesDir)) return candidates;
|
|
7838
|
+
for (const runEntry of readdirSync9(opts.worktreesDir, { withFileTypes: true })) {
|
|
6284
7839
|
if (!runEntry.isDirectory()) continue;
|
|
6285
7840
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
6286
|
-
const runPath =
|
|
7841
|
+
const runPath = path41.join(opts.worktreesDir, runEntry.name);
|
|
6287
7842
|
let workerEntries;
|
|
6288
7843
|
try {
|
|
6289
|
-
workerEntries =
|
|
7844
|
+
workerEntries = readdirSync9(runPath, { withFileTypes: true });
|
|
6290
7845
|
} catch {
|
|
6291
7846
|
continue;
|
|
6292
7847
|
}
|
|
6293
7848
|
for (const workerEntry of workerEntries) {
|
|
6294
7849
|
if (!workerEntry.isDirectory()) continue;
|
|
6295
|
-
const worktreePath =
|
|
7850
|
+
const worktreePath = path41.join(runPath, workerEntry.name);
|
|
6296
7851
|
scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, {
|
|
6297
7852
|
runId: runEntry.name,
|
|
6298
7853
|
worker: workerEntry.name
|
|
@@ -6303,11 +7858,11 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
6303
7858
|
}
|
|
6304
7859
|
|
|
6305
7860
|
// src/cleanup-duplicate-worktrees.ts
|
|
6306
|
-
import { existsSync as
|
|
6307
|
-
import
|
|
6308
|
-
function
|
|
7861
|
+
import { existsSync as existsSync30, statSync as statSync8 } from "node:fs";
|
|
7862
|
+
import path42 from "node:path";
|
|
7863
|
+
function pathAgeMs4(target, now) {
|
|
6309
7864
|
try {
|
|
6310
|
-
const mtime =
|
|
7865
|
+
const mtime = statSync8(target).mtimeMs;
|
|
6311
7866
|
return Math.max(0, now - mtime);
|
|
6312
7867
|
} catch {
|
|
6313
7868
|
return 0;
|
|
@@ -6334,8 +7889,8 @@ function parseWorktreePorcelain(output) {
|
|
|
6334
7889
|
return records;
|
|
6335
7890
|
}
|
|
6336
7891
|
function isUnderWorktreesDir(worktreePath, worktreesDir) {
|
|
6337
|
-
const rel =
|
|
6338
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
7892
|
+
const rel = path42.relative(path42.resolve(worktreesDir), path42.resolve(worktreePath));
|
|
7893
|
+
return rel !== "" && !rel.startsWith("..") && !path42.isAbsolute(rel);
|
|
6339
7894
|
}
|
|
6340
7895
|
function isCleanWorktree(worktreePath, repoRoot) {
|
|
6341
7896
|
try {
|
|
@@ -6348,14 +7903,14 @@ function isCleanWorktree(worktreePath, repoRoot) {
|
|
|
6348
7903
|
}
|
|
6349
7904
|
}
|
|
6350
7905
|
function scanDuplicateWorktreeCandidates(opts) {
|
|
6351
|
-
if (!opts.includeOrphans || !
|
|
7906
|
+
if (!opts.includeOrphans || !existsSync30(opts.worktreesDir)) return [];
|
|
6352
7907
|
const repos = /* @__PURE__ */ new Set();
|
|
6353
7908
|
for (const entry of opts.index.values()) {
|
|
6354
|
-
if (entry.run.repo) repos.add(
|
|
7909
|
+
if (entry.run.repo) repos.add(path42.resolve(entry.run.repo));
|
|
6355
7910
|
}
|
|
6356
7911
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
6357
7912
|
for (const entry of opts.index.values()) {
|
|
6358
|
-
indexedPaths.add(
|
|
7913
|
+
indexedPaths.add(path42.resolve(entry.worktreePath));
|
|
6359
7914
|
}
|
|
6360
7915
|
const candidates = [];
|
|
6361
7916
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -6368,15 +7923,15 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
6368
7923
|
}
|
|
6369
7924
|
const worktrees = parseWorktreePorcelain(porcelain);
|
|
6370
7925
|
for (const wt of worktrees) {
|
|
6371
|
-
const resolved =
|
|
6372
|
-
if (resolved ===
|
|
7926
|
+
const resolved = path42.resolve(wt.path);
|
|
7927
|
+
if (resolved === path42.resolve(repoRoot)) continue;
|
|
6373
7928
|
if (!isUnderWorktreesDir(resolved, opts.worktreesDir)) continue;
|
|
6374
7929
|
if (indexedPaths.has(resolved)) continue;
|
|
6375
7930
|
if (seen.has(resolved)) continue;
|
|
6376
|
-
if (!
|
|
7931
|
+
if (!existsSync30(resolved)) continue;
|
|
6377
7932
|
if (!isCleanWorktree(resolved, repoRoot)) continue;
|
|
6378
|
-
const rel =
|
|
6379
|
-
const parts = rel.split(
|
|
7933
|
+
const rel = path42.relative(opts.worktreesDir, resolved);
|
|
7934
|
+
const parts = rel.split(path42.sep);
|
|
6380
7935
|
const runId = parts[0];
|
|
6381
7936
|
const worker = parts[1] ?? "unknown";
|
|
6382
7937
|
seen.add(resolved);
|
|
@@ -6387,7 +7942,7 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
6387
7942
|
runId,
|
|
6388
7943
|
worker,
|
|
6389
7944
|
repo: repoRoot,
|
|
6390
|
-
ageMs:
|
|
7945
|
+
ageMs: pathAgeMs4(resolved, opts.now)
|
|
6391
7946
|
});
|
|
6392
7947
|
}
|
|
6393
7948
|
}
|
|
@@ -6395,12 +7950,12 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
6395
7950
|
}
|
|
6396
7951
|
|
|
6397
7952
|
// src/cleanup-worktree-index.ts
|
|
6398
|
-
import
|
|
7953
|
+
import path43 from "node:path";
|
|
6399
7954
|
function buildWorktreeIndexAt(harnessRoot) {
|
|
6400
7955
|
const index = /* @__PURE__ */ new Map();
|
|
6401
7956
|
for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
|
|
6402
7957
|
for (const name of Object.keys(run.workers || {})) {
|
|
6403
|
-
const workerPath =
|
|
7958
|
+
const workerPath = path43.join(
|
|
6404
7959
|
runDirectoryAt(harnessRoot, run.id),
|
|
6405
7960
|
"workers",
|
|
6406
7961
|
safeSlug(name),
|
|
@@ -6409,9 +7964,9 @@ function buildWorktreeIndexAt(harnessRoot) {
|
|
|
6409
7964
|
const worker = readJson(workerPath, void 0);
|
|
6410
7965
|
if (!worker?.worktreePath) continue;
|
|
6411
7966
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
6412
|
-
index.set(
|
|
7967
|
+
index.set(path43.resolve(worker.worktreePath), {
|
|
6413
7968
|
harnessRoot,
|
|
6414
|
-
worktreePath:
|
|
7969
|
+
worktreePath: path43.resolve(worker.worktreePath),
|
|
6415
7970
|
runId: run.id,
|
|
6416
7971
|
workerName: name,
|
|
6417
7972
|
run,
|
|
@@ -6423,10 +7978,6 @@ function buildWorktreeIndexAt(harnessRoot) {
|
|
|
6423
7978
|
return index;
|
|
6424
7979
|
}
|
|
6425
7980
|
|
|
6426
|
-
// src/cleanup-types.ts
|
|
6427
|
-
var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
6428
|
-
var DEFAULT_WORKTREES_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
6429
|
-
|
|
6430
7981
|
// src/cleanup-retention-config.ts
|
|
6431
7982
|
function envFlag(name) {
|
|
6432
7983
|
const v = process.env[name];
|
|
@@ -6443,6 +7994,9 @@ function resolveHarnessRetention(options = {}) {
|
|
|
6443
7994
|
const finalizeStaleRuns2 = options.finalizeStaleRuns !== false && !envFlag("KYNVER_CLEANUP_SKIP_FINALIZE");
|
|
6444
7995
|
const nodeModulesAgeMs = options.nodeModulesAgeMs ?? envMs("KYNVER_CLEANUP_NODE_MODULES_AGE_MS", DEFAULT_NODE_MODULES_AGE_MS);
|
|
6445
7996
|
const worktreesAgeMs = options.worktreesAgeMs ?? envMs("KYNVER_CLEANUP_WORKTREES_AGE_MS", 0);
|
|
7997
|
+
const terminalWorktreesAgeMs = options.terminalWorktreesAgeMs ?? envMs("KYNVER_CLEANUP_TERMINAL_WORKTREES_AGE_MS", DEFAULT_TERMINAL_WORKTREES_AGE_MS);
|
|
7998
|
+
const runDirectoriesAgeMs = options.runDirectoriesAgeMs ?? envMs("KYNVER_CLEANUP_RUN_DIRECTORIES_AGE_MS", DEFAULT_RUN_DIRECTORIES_AGE_MS);
|
|
7999
|
+
const maxActionsPerSweep = options.maxActionsPerSweep ?? envMs("KYNVER_CLEANUP_MAX_ACTIONS_PER_SWEEP", DEFAULT_MAX_ACTIONS_PER_SWEEP);
|
|
6446
8000
|
const includeOrphans = options.includeOrphans === true || envFlag("KYNVER_CLEANUP_INCLUDE_ORPHANS");
|
|
6447
8001
|
const scopeAll = envFlag("KYNVER_CLEANUP_SCOPE_ALL") || process.env.KYNVER_CLEANUP_SCOPE === "all";
|
|
6448
8002
|
const runIdFilter = scopeAll ? options.runIdFilter : options.runIdFilter ?? (process.env.KYNVER_CLEANUP_RUN_ID || void 0);
|
|
@@ -6452,6 +8006,9 @@ function resolveHarnessRetention(options = {}) {
|
|
|
6452
8006
|
finalizeStaleRuns: finalizeStaleRuns2,
|
|
6453
8007
|
nodeModulesAgeMs,
|
|
6454
8008
|
worktreesAgeMs: worktreesAgeMs > 0 ? worktreesAgeMs : 0,
|
|
8009
|
+
terminalWorktreesAgeMs: terminalWorktreesAgeMs >= 0 ? terminalWorktreesAgeMs : 0,
|
|
8010
|
+
runDirectoriesAgeMs: runDirectoriesAgeMs >= 0 ? runDirectoriesAgeMs : 0,
|
|
8011
|
+
maxActionsPerSweep: Number.isFinite(maxActionsPerSweep) && maxActionsPerSweep > 0 ? Math.floor(maxActionsPerSweep) : DEFAULT_MAX_ACTIONS_PER_SWEEP,
|
|
6455
8012
|
includeOrphans,
|
|
6456
8013
|
runIdFilter: runIdFilter ? String(runIdFilter) : void 0,
|
|
6457
8014
|
accountBytes
|
|
@@ -6464,21 +8021,24 @@ function resolvePipelineHarnessRetention(runId) {
|
|
|
6464
8021
|
return resolveHarnessRetention({
|
|
6465
8022
|
runIdFilter: scopeAll ? void 0 : runId,
|
|
6466
8023
|
worktreesAgeMs,
|
|
8024
|
+
terminalWorktreesAgeMs: scopeAll ? DEFAULT_TERMINAL_WORKTREES_AGE_MS : void 0,
|
|
8025
|
+
runDirectoriesAgeMs: scopeAll ? DEFAULT_RUN_DIRECTORIES_AGE_MS : void 0,
|
|
8026
|
+
includeOrphans: scopeAll ? true : void 0,
|
|
6467
8027
|
finalizeStaleRuns: true,
|
|
6468
8028
|
accountBytes: true
|
|
6469
8029
|
});
|
|
6470
8030
|
}
|
|
6471
8031
|
|
|
6472
8032
|
// src/cleanup-orphan-safety.ts
|
|
6473
|
-
import { existsSync as
|
|
6474
|
-
import
|
|
8033
|
+
import { existsSync as existsSync31, statSync as statSync9 } from "node:fs";
|
|
8034
|
+
import path44 from "node:path";
|
|
6475
8035
|
var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
|
|
6476
8036
|
function assessOrphanWorktreeSafety(input) {
|
|
6477
8037
|
const now = input.now ?? Date.now();
|
|
6478
8038
|
const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
|
|
6479
|
-
if (!
|
|
8039
|
+
if (!existsSync31(input.worktreePath)) return null;
|
|
6480
8040
|
if (input.runId && input.workerName) {
|
|
6481
|
-
const heartbeatPath =
|
|
8041
|
+
const heartbeatPath = path44.join(
|
|
6482
8042
|
input.harnessRoot,
|
|
6483
8043
|
"runs",
|
|
6484
8044
|
input.runId,
|
|
@@ -6487,13 +8047,13 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
6487
8047
|
"heartbeat.jsonl"
|
|
6488
8048
|
);
|
|
6489
8049
|
try {
|
|
6490
|
-
const mtime =
|
|
8050
|
+
const mtime = statSync9(heartbeatPath).mtimeMs;
|
|
6491
8051
|
if (now - mtime < heartbeatFreshMs) return "active_worker";
|
|
6492
8052
|
} catch {
|
|
6493
8053
|
}
|
|
6494
8054
|
}
|
|
6495
|
-
const gitDir =
|
|
6496
|
-
if (!
|
|
8055
|
+
const gitDir = path44.join(input.worktreePath, ".git");
|
|
8056
|
+
if (!existsSync31(gitDir)) return null;
|
|
6497
8057
|
const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
|
|
6498
8058
|
if (porcelain.status !== 0) return "pr_or_unmerged_commits";
|
|
6499
8059
|
const dirtyLines = porcelain.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
@@ -6522,14 +8082,14 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
6522
8082
|
}
|
|
6523
8083
|
|
|
6524
8084
|
// src/harness-storage-snapshot.ts
|
|
6525
|
-
import { existsSync as
|
|
6526
|
-
import
|
|
8085
|
+
import { existsSync as existsSync32, readdirSync as readdirSync10, statSync as statSync10 } from "node:fs";
|
|
8086
|
+
import path45 from "node:path";
|
|
6527
8087
|
function harnessStorageSnapshot(opts = {}) {
|
|
6528
|
-
const harnessRoot = opts.harnessRoot ?? resolveHarnessRoot();
|
|
6529
|
-
const worktreesDir =
|
|
8088
|
+
const harnessRoot = normalizeHarnessRoot(opts.harnessRoot ?? resolveHarnessRoot());
|
|
8089
|
+
const worktreesDir = harnessWorktreesDir(harnessRoot);
|
|
6530
8090
|
const now = opts.now ?? Date.now();
|
|
6531
8091
|
const scannedAt = new Date(now).toISOString();
|
|
6532
|
-
if (!
|
|
8092
|
+
if (!existsSync32(worktreesDir)) {
|
|
6533
8093
|
return {
|
|
6534
8094
|
harnessRoot,
|
|
6535
8095
|
worktreesDir,
|
|
@@ -6546,7 +8106,7 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
6546
8106
|
let oldestMs = null;
|
|
6547
8107
|
let entries;
|
|
6548
8108
|
try {
|
|
6549
|
-
entries =
|
|
8109
|
+
entries = readdirSync10(worktreesDir, { withFileTypes: true });
|
|
6550
8110
|
} catch {
|
|
6551
8111
|
return {
|
|
6552
8112
|
harnessRoot,
|
|
@@ -6561,14 +8121,14 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
6561
8121
|
for (const runEntry of entries) {
|
|
6562
8122
|
if (!runEntry.isDirectory()) continue;
|
|
6563
8123
|
runCount += 1;
|
|
6564
|
-
const runPath =
|
|
8124
|
+
const runPath = path45.join(worktreesDir, runEntry.name);
|
|
6565
8125
|
try {
|
|
6566
|
-
const st =
|
|
8126
|
+
const st = statSync10(runPath);
|
|
6567
8127
|
oldestMs = oldestMs === null ? st.mtimeMs : Math.min(oldestMs, st.mtimeMs);
|
|
6568
8128
|
} catch {
|
|
6569
8129
|
}
|
|
6570
8130
|
try {
|
|
6571
|
-
for (const workerEntry of
|
|
8131
|
+
for (const workerEntry of readdirSync10(runPath, { withFileTypes: true })) {
|
|
6572
8132
|
if (workerEntry.isDirectory()) workerCount += 1;
|
|
6573
8133
|
}
|
|
6574
8134
|
} catch {
|
|
@@ -6596,16 +8156,16 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
6596
8156
|
}
|
|
6597
8157
|
|
|
6598
8158
|
// src/cleanup-harness-roots.ts
|
|
6599
|
-
import { existsSync as
|
|
6600
|
-
import { homedir as
|
|
6601
|
-
import
|
|
8159
|
+
import { existsSync as existsSync33 } from "node:fs";
|
|
8160
|
+
import { homedir as homedir11 } from "node:os";
|
|
8161
|
+
import path46 from "node:path";
|
|
6602
8162
|
var WELL_KNOWN_HARNESS_SCAN_ROOTS = [
|
|
6603
8163
|
"/var/tmp/kynver-harness",
|
|
6604
|
-
|
|
8164
|
+
path46.join(homedir11(), ".openclaw", "harness")
|
|
6605
8165
|
];
|
|
6606
8166
|
function addRoot(seen, roots, candidate) {
|
|
6607
8167
|
if (!candidate?.trim()) return;
|
|
6608
|
-
const resolved =
|
|
8168
|
+
const resolved = normalizeHarnessRoot(candidate.trim());
|
|
6609
8169
|
if (seen.has(resolved)) return;
|
|
6610
8170
|
seen.add(resolved);
|
|
6611
8171
|
roots.push(resolved);
|
|
@@ -6623,42 +8183,13 @@ function resolveHarnessScanRoots(options = {}) {
|
|
|
6623
8183
|
for (const candidate of extra ?? []) addRoot(seen, roots, candidate);
|
|
6624
8184
|
if (shouldScanWellKnownRoots(options)) {
|
|
6625
8185
|
for (const candidate of WELL_KNOWN_HARNESS_SCAN_ROOTS) {
|
|
6626
|
-
const resolved =
|
|
6627
|
-
if (!seen.has(resolved) &&
|
|
8186
|
+
const resolved = path46.resolve(candidate);
|
|
8187
|
+
if (!seen.has(resolved) && existsSync33(resolved)) addRoot(seen, roots, resolved);
|
|
6628
8188
|
}
|
|
6629
8189
|
}
|
|
6630
8190
|
return roots;
|
|
6631
8191
|
}
|
|
6632
8192
|
|
|
6633
|
-
// src/cleanup-active-worktrees.ts
|
|
6634
|
-
import path38 from "node:path";
|
|
6635
|
-
function isActiveHarnessWorker2(worker, runBase, runBaseCommit) {
|
|
6636
|
-
const status = computeWorkerStatus(worker, { base: runBase, baseCommit: runBaseCommit });
|
|
6637
|
-
return status.alive && !status.finalResult && status.attention.state !== "done";
|
|
6638
|
-
}
|
|
6639
|
-
function collectActiveWorktreeGuards(harnessRoots) {
|
|
6640
|
-
const activeWorktreePaths = /* @__PURE__ */ new Set();
|
|
6641
|
-
const liveRunKeys = /* @__PURE__ */ new Set();
|
|
6642
|
-
for (const harnessRoot of harnessRoots) {
|
|
6643
|
-
for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
|
|
6644
|
-
let runHasLive = false;
|
|
6645
|
-
for (const name of Object.keys(run.workers || {})) {
|
|
6646
|
-
const worker = readJson(
|
|
6647
|
-
path38.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
|
|
6648
|
-
void 0
|
|
6649
|
-
);
|
|
6650
|
-
if (!worker?.worktreePath) continue;
|
|
6651
|
-
const worktreePath = path38.resolve(worker.worktreePath);
|
|
6652
|
-
if (!isActiveHarnessWorker2(worker, run.base, run.baseCommit)) continue;
|
|
6653
|
-
runHasLive = true;
|
|
6654
|
-
activeWorktreePaths.add(worktreePath);
|
|
6655
|
-
}
|
|
6656
|
-
if (runHasLive) liveRunKeys.add(`${harnessRoot}\0${run.id}`);
|
|
6657
|
-
}
|
|
6658
|
-
}
|
|
6659
|
-
return { activeWorktreePaths, liveRunKeys };
|
|
6660
|
-
}
|
|
6661
|
-
|
|
6662
8193
|
// src/cleanup-disk-pressure.ts
|
|
6663
8194
|
function envFlag2(name) {
|
|
6664
8195
|
const v = process.env[name];
|
|
@@ -6695,14 +8226,14 @@ function applyDiskPressureToRetention(retention, pressure) {
|
|
|
6695
8226
|
|
|
6696
8227
|
// src/cleanup.ts
|
|
6697
8228
|
function resolvePaths(options = {}) {
|
|
6698
|
-
const harnessRoot = options.harnessRoot ?
|
|
8229
|
+
const harnessRoot = options.harnessRoot ? normalizeHarnessRoot(options.harnessRoot) : resolveHarnessRoot();
|
|
6699
8230
|
const scanRoots = resolveHarnessScanRoots({ harnessRoot });
|
|
6700
8231
|
const now = options.now ?? Date.now();
|
|
6701
8232
|
return { harnessRoot, scanRoots, now };
|
|
6702
8233
|
}
|
|
6703
|
-
function normalizeGuardSkip(
|
|
6704
|
-
if (typeof
|
|
6705
|
-
return
|
|
8234
|
+
function normalizeGuardSkip(skip2) {
|
|
8235
|
+
if (typeof skip2 === "string") return { reason: skip2 };
|
|
8236
|
+
return skip2;
|
|
6706
8237
|
}
|
|
6707
8238
|
function recordSkip(skips, pathValue, reason, detail) {
|
|
6708
8239
|
skips.push({ path: pathValue, reason, ...detail ? { detail } : {} });
|
|
@@ -6713,8 +8244,8 @@ function attachCandidateBytes(candidate, accountBytes) {
|
|
|
6713
8244
|
}
|
|
6714
8245
|
function tallySkipReasons(actions, skips) {
|
|
6715
8246
|
const counts = {};
|
|
6716
|
-
for (const
|
|
6717
|
-
counts[
|
|
8247
|
+
for (const skip2 of skips) {
|
|
8248
|
+
counts[skip2.reason] = (counts[skip2.reason] ?? 0) + 1;
|
|
6718
8249
|
}
|
|
6719
8250
|
for (const action of actions) {
|
|
6720
8251
|
if (action.skipReason) {
|
|
@@ -6742,9 +8273,9 @@ function mergeWorktreeIndexes(scanRoots) {
|
|
|
6742
8273
|
}
|
|
6743
8274
|
function worktreePathForCandidate(candidate, worktreesDir) {
|
|
6744
8275
|
if (candidate.runId && candidate.worker) {
|
|
6745
|
-
return
|
|
8276
|
+
return path47.join(worktreesDir, candidate.runId, candidate.worker);
|
|
6746
8277
|
}
|
|
6747
|
-
return
|
|
8278
|
+
return path47.resolve(candidate.path, "..");
|
|
6748
8279
|
}
|
|
6749
8280
|
function runHarnessCleanup(options = {}) {
|
|
6750
8281
|
let retention = resolveHarnessRetention(options);
|
|
@@ -6757,8 +8288,11 @@ function runHarnessCleanup(options = {}) {
|
|
|
6757
8288
|
const skips = [];
|
|
6758
8289
|
const actions = [];
|
|
6759
8290
|
const processedPaths = /* @__PURE__ */ new Set();
|
|
8291
|
+
const maxActions = retention.maxActionsPerSweep;
|
|
8292
|
+
const atSweepCap = () => actions.length >= maxActions;
|
|
6760
8293
|
for (const harnessRoot of paths.scanRoots) {
|
|
6761
|
-
|
|
8294
|
+
if (atSweepCap()) break;
|
|
8295
|
+
const worktreesDir = path47.join(harnessRoot, "worktrees");
|
|
6762
8296
|
const scanOpts = {
|
|
6763
8297
|
harnessRoot,
|
|
6764
8298
|
worktreesDir,
|
|
@@ -6770,8 +8304,9 @@ function runHarnessCleanup(options = {}) {
|
|
|
6770
8304
|
now: paths.now
|
|
6771
8305
|
};
|
|
6772
8306
|
for (const raw of scanDependencyCacheCandidates(scanOpts)) {
|
|
8307
|
+
if (atSweepCap()) break;
|
|
6773
8308
|
const candidate = attachCandidateBytes(raw, retention.accountBytes);
|
|
6774
|
-
const resolved =
|
|
8309
|
+
const resolved = path47.resolve(candidate.path);
|
|
6775
8310
|
if (processedPaths.has(resolved)) continue;
|
|
6776
8311
|
processedPaths.add(resolved);
|
|
6777
8312
|
const pathSkip = pathGuardForDependencyCache(candidate, harnessRoot, worktreesDir);
|
|
@@ -6781,7 +8316,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
6781
8316
|
continue;
|
|
6782
8317
|
}
|
|
6783
8318
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
6784
|
-
const indexed = index.get(
|
|
8319
|
+
const indexed = index.get(path47.resolve(worktreePath)) ?? null;
|
|
6785
8320
|
const guardReason = skipDependencyCacheRemoval({
|
|
6786
8321
|
indexed,
|
|
6787
8322
|
includeOrphans: true,
|
|
@@ -6799,8 +8334,9 @@ function runHarnessCleanup(options = {}) {
|
|
|
6799
8334
|
actions.push(removeDependencyCacheAction(candidate, retention.execute));
|
|
6800
8335
|
}
|
|
6801
8336
|
for (const raw of scanBuildCacheCandidates(scanOpts)) {
|
|
8337
|
+
if (atSweepCap()) break;
|
|
6802
8338
|
const candidate = attachCandidateBytes(raw, retention.accountBytes);
|
|
6803
|
-
const resolved =
|
|
8339
|
+
const resolved = path47.resolve(candidate.path);
|
|
6804
8340
|
if (processedPaths.has(resolved)) continue;
|
|
6805
8341
|
processedPaths.add(resolved);
|
|
6806
8342
|
const pathSkip = isHarnessBuildCachePath(candidate.path, harnessRoot, worktreesDir);
|
|
@@ -6810,7 +8346,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
6810
8346
|
continue;
|
|
6811
8347
|
}
|
|
6812
8348
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
6813
|
-
const indexed = index.get(
|
|
8349
|
+
const indexed = index.get(path47.resolve(worktreePath)) ?? null;
|
|
6814
8350
|
const guardReason = skipBuildCacheRemoval({
|
|
6815
8351
|
indexed,
|
|
6816
8352
|
includeOrphans: true,
|
|
@@ -6833,11 +8369,12 @@ function runHarnessCleanup(options = {}) {
|
|
|
6833
8369
|
];
|
|
6834
8370
|
const worktreeSeen = /* @__PURE__ */ new Set();
|
|
6835
8371
|
for (const raw of worktreeCandidates) {
|
|
6836
|
-
|
|
8372
|
+
if (atSweepCap()) break;
|
|
8373
|
+
const resolved = path47.resolve(raw.path);
|
|
6837
8374
|
if (worktreeSeen.has(resolved)) continue;
|
|
6838
8375
|
worktreeSeen.add(resolved);
|
|
6839
8376
|
const candidate = attachCandidateBytes({ ...raw, path: resolved }, retention.accountBytes);
|
|
6840
|
-
const indexed = index.get(
|
|
8377
|
+
const indexed = index.get(path47.resolve(candidate.path)) ?? null;
|
|
6841
8378
|
const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
|
|
6842
8379
|
worktreePath: candidate.path,
|
|
6843
8380
|
harnessRoot,
|
|
@@ -6847,9 +8384,10 @@ function runHarnessCleanup(options = {}) {
|
|
|
6847
8384
|
});
|
|
6848
8385
|
const guardSkip = skipWorktreeRemoval({
|
|
6849
8386
|
indexed,
|
|
6850
|
-
worktreePath:
|
|
8387
|
+
worktreePath: path47.resolve(candidate.path),
|
|
6851
8388
|
includeOrphans: retention.includeOrphans,
|
|
6852
8389
|
worktreesAgeMs: retention.worktreesAgeMs,
|
|
8390
|
+
terminalWorktreesAgeMs: retention.terminalWorktreesAgeMs,
|
|
6853
8391
|
ageMs: candidate.ageMs,
|
|
6854
8392
|
orphanSafety,
|
|
6855
8393
|
worktreeRemovalGuard: options.worktreeRemovalGuard
|
|
@@ -6862,8 +8400,40 @@ function runHarnessCleanup(options = {}) {
|
|
|
6862
8400
|
}
|
|
6863
8401
|
actions.push(removeWorktree(candidate, retention.execute));
|
|
6864
8402
|
}
|
|
8403
|
+
if (!atSweepCap() && retention.runDirectoriesAgeMs >= 0) {
|
|
8404
|
+
for (const raw of scanStaleRunDirectoryCandidates({
|
|
8405
|
+
harnessRoot,
|
|
8406
|
+
worktreesDir,
|
|
8407
|
+
runDirectoriesAgeMs: retention.runDirectoriesAgeMs,
|
|
8408
|
+
runIdFilter: retention.runIdFilter,
|
|
8409
|
+
activeGuards,
|
|
8410
|
+
now: paths.now
|
|
8411
|
+
})) {
|
|
8412
|
+
if (atSweepCap()) break;
|
|
8413
|
+
const candidate = attachCandidateBytes(raw, retention.accountBytes);
|
|
8414
|
+
const resolved = path47.resolve(candidate.path);
|
|
8415
|
+
if (processedPaths.has(resolved)) continue;
|
|
8416
|
+
processedPaths.add(resolved);
|
|
8417
|
+
const runId = candidate.runId ?? path47.basename(resolved);
|
|
8418
|
+
const dirSkip = skipRunDirectoryRemoval({
|
|
8419
|
+
harnessRoot,
|
|
8420
|
+
runId,
|
|
8421
|
+
runPath: resolved,
|
|
8422
|
+
ageMs: candidate.ageMs,
|
|
8423
|
+
runDirectoriesAgeMs: retention.runDirectoriesAgeMs,
|
|
8424
|
+
activeGuards
|
|
8425
|
+
});
|
|
8426
|
+
if (dirSkip) {
|
|
8427
|
+
recordSkip(skips, candidate.path, dirSkip);
|
|
8428
|
+
actions.push({ ...candidate, executed: false, skipped: true, skipReason: dirSkip });
|
|
8429
|
+
continue;
|
|
8430
|
+
}
|
|
8431
|
+
actions.push(removeRunDirectory(candidate, retention.execute));
|
|
8432
|
+
}
|
|
8433
|
+
}
|
|
6865
8434
|
}
|
|
6866
8435
|
let candidateBytes = 0;
|
|
8436
|
+
let removedRunDirectories = 0;
|
|
6867
8437
|
let reclaimableBytes = 0;
|
|
6868
8438
|
let removedBytes = 0;
|
|
6869
8439
|
let removedPaths = 0;
|
|
@@ -6874,12 +8444,14 @@ function runHarnessCleanup(options = {}) {
|
|
|
6874
8444
|
if (action.executed) {
|
|
6875
8445
|
removedPaths += 1;
|
|
6876
8446
|
removedBytes += action.bytes ?? 0;
|
|
8447
|
+
if (action.kind === "remove_run_directory") removedRunDirectories += 1;
|
|
6877
8448
|
} else if (action.skipped) {
|
|
6878
8449
|
skippedPaths += 1;
|
|
6879
8450
|
if (action.skipReason === "dry_run" && action.bytes) reclaimableBytes += action.bytes;
|
|
6880
8451
|
}
|
|
6881
8452
|
}
|
|
6882
8453
|
const storage = retention.accountBytes ? harnessStorageSnapshot({ harnessRoot: paths.harnessRoot, now: paths.now }) : void 0;
|
|
8454
|
+
const preservedLivePaths = collectPreservedLivePaths(actions, skips);
|
|
6883
8455
|
return {
|
|
6884
8456
|
harnessRoot: paths.harnessRoot,
|
|
6885
8457
|
scanRoots: paths.scanRoots,
|
|
@@ -6908,7 +8480,9 @@ function runHarnessCleanup(options = {}) {
|
|
|
6908
8480
|
skippedPaths,
|
|
6909
8481
|
skipReasons: tallySkipReasons(actions, skips)
|
|
6910
8482
|
},
|
|
6911
|
-
...storage ? { storage } : {}
|
|
8483
|
+
...storage ? { storage } : {},
|
|
8484
|
+
...preservedLivePaths.length > 0 ? { preservedLivePaths } : {},
|
|
8485
|
+
...removedRunDirectories > 0 ? { removedRunDirectories } : {}
|
|
6912
8486
|
};
|
|
6913
8487
|
}
|
|
6914
8488
|
function runPipelineHarnessCleanup(runId) {
|
|
@@ -6929,8 +8503,8 @@ function isPipelineCleanupEnabled() {
|
|
|
6929
8503
|
|
|
6930
8504
|
// src/installed-package-versions.ts
|
|
6931
8505
|
import { readFile } from "node:fs/promises";
|
|
6932
|
-
import { homedir as
|
|
6933
|
-
import
|
|
8506
|
+
import { homedir as homedir12 } from "node:os";
|
|
8507
|
+
import path48 from "node:path";
|
|
6934
8508
|
var MANAGED_PACKAGES = [
|
|
6935
8509
|
"@kynver-app/runtime",
|
|
6936
8510
|
"@kynver-app/openclaw-agent-os",
|
|
@@ -6944,13 +8518,13 @@ function unique(values) {
|
|
|
6944
8518
|
return [...new Set(values.filter((value) => Boolean(value)))];
|
|
6945
8519
|
}
|
|
6946
8520
|
function moduleRoots() {
|
|
6947
|
-
const home =
|
|
6948
|
-
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ??
|
|
6949
|
-
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ?
|
|
8521
|
+
const home = homedir12();
|
|
8522
|
+
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path48.join(home, ".openclaw", "npm");
|
|
8523
|
+
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path48.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path48.join(home, ".npm-global", "lib", "node_modules"));
|
|
6950
8524
|
return unique([
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot :
|
|
8525
|
+
path48.join(openClawPrefix, "lib", "node_modules"),
|
|
8526
|
+
path48.join(openClawPrefix, "node_modules"),
|
|
8527
|
+
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path48.join(npmGlobalRoot, "lib", "node_modules")
|
|
6954
8528
|
]);
|
|
6955
8529
|
}
|
|
6956
8530
|
async function readVersion(packageJsonPath) {
|
|
@@ -6966,7 +8540,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
|
|
|
6966
8540
|
const out = {};
|
|
6967
8541
|
for (const packageName of MANAGED_PACKAGES) {
|
|
6968
8542
|
for (const root of roots) {
|
|
6969
|
-
const packageJsonPath =
|
|
8543
|
+
const packageJsonPath = path48.join(root, packageName, "package.json");
|
|
6970
8544
|
const version = await readVersion(packageJsonPath);
|
|
6971
8545
|
if (!version) continue;
|
|
6972
8546
|
out[packageName] = { version, observedAt, path: packageJsonPath };
|
|
@@ -6982,7 +8556,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
6982
8556
|
const outcomes = [];
|
|
6983
8557
|
for (const name of Object.keys(run.workers || {})) {
|
|
6984
8558
|
const worker = readJson(
|
|
6985
|
-
|
|
8559
|
+
path49.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
6986
8560
|
void 0
|
|
6987
8561
|
);
|
|
6988
8562
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -7014,21 +8588,7 @@ async function postOperatorTick(agentOsId, runId, resourceGate, args, harnessCle
|
|
|
7014
8588
|
const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, { baseUrl: base });
|
|
7015
8589
|
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/operator/tick`;
|
|
7016
8590
|
const packageVersions = await collectInstalledPackageVersions();
|
|
7017
|
-
const activeHarnessWorkers =
|
|
7018
|
-
const run = loadRun(runId);
|
|
7019
|
-
for (const name of Object.keys(run.workers || {})) {
|
|
7020
|
-
const worker = readJson(
|
|
7021
|
-
path41.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
7022
|
-
void 0
|
|
7023
|
-
);
|
|
7024
|
-
if (!worker?.taskId) continue;
|
|
7025
|
-
activeHarnessWorkers.push({
|
|
7026
|
-
runId: run.id,
|
|
7027
|
-
workerName: name,
|
|
7028
|
-
taskId: worker.taskId,
|
|
7029
|
-
pid: worker.pid
|
|
7030
|
-
});
|
|
7031
|
-
}
|
|
8591
|
+
const activeHarnessWorkers = collectRunActiveHarnessWorkers(runId);
|
|
7032
8592
|
const res = await postJson(url, secret, {
|
|
7033
8593
|
agentOsId,
|
|
7034
8594
|
runId,
|
|
@@ -7059,9 +8619,13 @@ async function runPipelineTick(args) {
|
|
|
7059
8619
|
const completionAckSync = syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick);
|
|
7060
8620
|
const leaseRenewal = await renewActiveTaskLeases(runId, args);
|
|
7061
8621
|
const completedWorkers = await completeFinishedWorkers(runId, args);
|
|
8622
|
+
const dispatchResourceGate = observeRunnerResourceGate({
|
|
8623
|
+
runId,
|
|
8624
|
+
configuredMaxWorkersOverride: workspacePrefs?.maxConcurrentWorkers
|
|
8625
|
+
});
|
|
7062
8626
|
const staleReconcile = reconcileStaleWorkers();
|
|
7063
8627
|
const planProgressSync = await syncActiveWorkerPlanProgress(runId, args);
|
|
7064
|
-
const maxStartsAdvice = resolvePipelineMaxStarts(
|
|
8628
|
+
const maxStartsAdvice = resolvePipelineMaxStarts(dispatchResourceGate, operatorTick);
|
|
7065
8629
|
let maxStarts = maxStartsAdvice.maxStarts;
|
|
7066
8630
|
const sweep = await sweepRun({ run: runId, agentOsId, pipeline: true, ...args });
|
|
7067
8631
|
let dispatch = null;
|
|
@@ -7078,7 +8642,7 @@ async function runPipelineTick(args) {
|
|
|
7078
8642
|
dispatch = {
|
|
7079
8643
|
ok: true,
|
|
7080
8644
|
skipped: true,
|
|
7081
|
-
reason: execute ?
|
|
8645
|
+
reason: execute ? dispatchResourceGate.reason ?? "no slots or queued work" : "execute disabled",
|
|
7082
8646
|
maxStarts: 0
|
|
7083
8647
|
};
|
|
7084
8648
|
}
|
|
@@ -7089,6 +8653,7 @@ async function runPipelineTick(args) {
|
|
|
7089
8653
|
agentOsId,
|
|
7090
8654
|
execute,
|
|
7091
8655
|
resourceGate,
|
|
8656
|
+
dispatchResourceGate,
|
|
7092
8657
|
leaseRenewal,
|
|
7093
8658
|
completedWorkers,
|
|
7094
8659
|
staleReconcile,
|
|
@@ -7140,7 +8705,7 @@ async function runDaemon(args) {
|
|
|
7140
8705
|
}
|
|
7141
8706
|
|
|
7142
8707
|
// src/plan-progress.ts
|
|
7143
|
-
import
|
|
8708
|
+
import path50 from "node:path";
|
|
7144
8709
|
|
|
7145
8710
|
// src/bounded-build/constants.ts
|
|
7146
8711
|
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
@@ -7178,7 +8743,7 @@ function formatNodeOptionsFlag(mb = resolveNodeOldSpaceSizeMb()) {
|
|
|
7178
8743
|
}
|
|
7179
8744
|
|
|
7180
8745
|
// src/bounded-build/systemd-wrap.ts
|
|
7181
|
-
import { spawnSync as
|
|
8746
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
7182
8747
|
var systemdAvailableCache;
|
|
7183
8748
|
function isSystemdRunAvailable() {
|
|
7184
8749
|
if (process.env.KYNVER_BUILD_SKIP_SYSTEMD === "1" || process.env.KYNVER_BUILD_SKIP_SYSTEMD === "true") {
|
|
@@ -7189,7 +8754,7 @@ function isSystemdRunAvailable() {
|
|
|
7189
8754
|
systemdAvailableCache = false;
|
|
7190
8755
|
return false;
|
|
7191
8756
|
}
|
|
7192
|
-
const res =
|
|
8757
|
+
const res = spawnSync4("systemd-run", ["--version"], { encoding: "utf8", stdio: ["ignore", "ignore", "pipe"] });
|
|
7193
8758
|
systemdAvailableCache = res.status === 0;
|
|
7194
8759
|
return systemdAvailableCache;
|
|
7195
8760
|
}
|
|
@@ -7213,7 +8778,7 @@ function buildSystemdRunArgv(opts) {
|
|
|
7213
8778
|
}
|
|
7214
8779
|
|
|
7215
8780
|
// src/bounded-build/admission.ts
|
|
7216
|
-
import { spawnSync as
|
|
8781
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
7217
8782
|
function positiveInt3(value, fallback) {
|
|
7218
8783
|
const n = Number(value);
|
|
7219
8784
|
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
@@ -7249,7 +8814,7 @@ function assessBuildAdmission(opts = {}) {
|
|
|
7249
8814
|
}
|
|
7250
8815
|
function sleepMs2(ms) {
|
|
7251
8816
|
if (ms <= 0) return;
|
|
7252
|
-
|
|
8817
|
+
spawnSync5(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
|
|
7253
8818
|
stdio: "ignore"
|
|
7254
8819
|
});
|
|
7255
8820
|
}
|
|
@@ -7270,7 +8835,7 @@ function waitForBuildAdmission(timeoutMs, pollMs = 2e3, opts = {}) {
|
|
|
7270
8835
|
}
|
|
7271
8836
|
|
|
7272
8837
|
// src/bounded-build/exec.ts
|
|
7273
|
-
import { spawnSync as
|
|
8838
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
7274
8839
|
function envArgv(env) {
|
|
7275
8840
|
const out = [];
|
|
7276
8841
|
for (const [key, value] of Object.entries(env)) {
|
|
@@ -7280,7 +8845,7 @@ function envArgv(env) {
|
|
|
7280
8845
|
return out;
|
|
7281
8846
|
}
|
|
7282
8847
|
function runSpawn(argv, opts) {
|
|
7283
|
-
const res =
|
|
8848
|
+
const res = spawnSync6(argv[0], argv.slice(1), {
|
|
7284
8849
|
cwd: opts.cwd,
|
|
7285
8850
|
env: opts.env,
|
|
7286
8851
|
encoding: "utf8",
|
|
@@ -7427,7 +8992,7 @@ async function emitPlanProgress(args) {
|
|
|
7427
8992
|
}
|
|
7428
8993
|
function verifyPlanLocal(args) {
|
|
7429
8994
|
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
7430
|
-
const cwd =
|
|
8995
|
+
const cwd = path50.resolve(worktree);
|
|
7431
8996
|
const summary = runHarnessVerifyCommands(cwd);
|
|
7432
8997
|
const emitJson = args.json === true || args.json === "true";
|
|
7433
8998
|
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
@@ -7476,9 +9041,9 @@ async function verifyPlan(args) {
|
|
|
7476
9041
|
}
|
|
7477
9042
|
|
|
7478
9043
|
// src/harness-verify-cli.ts
|
|
7479
|
-
import
|
|
9044
|
+
import path51 from "node:path";
|
|
7480
9045
|
function runHarnessVerifyCli(args) {
|
|
7481
|
-
const cwd =
|
|
9046
|
+
const cwd = path51.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
7482
9047
|
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
7483
9048
|
const commands = [];
|
|
7484
9049
|
const rawCmd = args.command;
|
|
@@ -7522,7 +9087,7 @@ function runHarnessVerifyCli(args) {
|
|
|
7522
9087
|
}
|
|
7523
9088
|
|
|
7524
9089
|
// src/plan-persist-cli.ts
|
|
7525
|
-
import { readFileSync as
|
|
9090
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
7526
9091
|
var OPERATIONS = ["create", "add_version", "update_metadata"];
|
|
7527
9092
|
var FAILURE_KINDS = [
|
|
7528
9093
|
"approval_guard",
|
|
@@ -7534,7 +9099,7 @@ var FAILURE_KINDS = [
|
|
|
7534
9099
|
function readBodyArg(args) {
|
|
7535
9100
|
const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
|
|
7536
9101
|
if (bodyFile) {
|
|
7537
|
-
return { body:
|
|
9102
|
+
return { body: readFileSync11(bodyFile, "utf8"), bodyPathHint: bodyFile };
|
|
7538
9103
|
}
|
|
7539
9104
|
const inline = args.body ? String(args.body) : void 0;
|
|
7540
9105
|
if (inline) return { body: inline };
|
|
@@ -7678,21 +9243,17 @@ function formatMonitorTickNotice(tick) {
|
|
|
7678
9243
|
}
|
|
7679
9244
|
|
|
7680
9245
|
// src/monitor/monitor.service.ts
|
|
7681
|
-
import
|
|
9246
|
+
import path53 from "node:path";
|
|
7682
9247
|
|
|
7683
9248
|
// src/monitor/monitor.classify.ts
|
|
7684
|
-
function expectedLeaseOwner(runId) {
|
|
7685
|
-
return `kynver-harness:${runId}`;
|
|
7686
|
-
}
|
|
7687
9249
|
function classifyWorkerHealth(input) {
|
|
7688
9250
|
const { worker, status, taskLease } = input;
|
|
7689
9251
|
const leaseOwner = taskLease?.leaseOwner ?? null;
|
|
7690
|
-
const expectedOwner = expectedLeaseOwner(worker.runId);
|
|
7691
9252
|
if (worker.dispatched && taskLease) {
|
|
7692
|
-
if (taskLease.status === "running" && leaseOwner && leaseOwner
|
|
9253
|
+
if (taskLease.status === "running" && leaseOwner && !harnessLeaseOwnerMatchesRun(leaseOwner, worker.runId)) {
|
|
7693
9254
|
return {
|
|
7694
9255
|
health: "orphaned",
|
|
7695
|
-
reason: `task lease held by ${leaseOwner}, expected ${
|
|
9256
|
+
reason: `task lease held by ${leaseOwner}, expected harness run ${worker.runId}`
|
|
7696
9257
|
};
|
|
7697
9258
|
}
|
|
7698
9259
|
if (taskLease.status === "running" && !status.alive && !status.finalResult) {
|
|
@@ -7734,11 +9295,11 @@ function classifyWorkerHealth(input) {
|
|
|
7734
9295
|
}
|
|
7735
9296
|
|
|
7736
9297
|
// src/monitor/monitor.store.ts
|
|
7737
|
-
import { existsSync as
|
|
7738
|
-
import
|
|
9298
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync6, readdirSync as readdirSync11, unlinkSync as unlinkSync2 } from "node:fs";
|
|
9299
|
+
import path52 from "node:path";
|
|
7739
9300
|
function monitorsDir() {
|
|
7740
9301
|
const { harnessRoot } = getHarnessPaths();
|
|
7741
|
-
const dir =
|
|
9302
|
+
const dir = path52.join(harnessRoot, "monitors");
|
|
7742
9303
|
mkdirSync6(dir, { recursive: true });
|
|
7743
9304
|
return dir;
|
|
7744
9305
|
}
|
|
@@ -7746,7 +9307,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
7746
9307
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
7747
9308
|
}
|
|
7748
9309
|
function monitorPath(monitorId) {
|
|
7749
|
-
return
|
|
9310
|
+
return path52.join(monitorsDir(), `${monitorId}.json`);
|
|
7750
9311
|
}
|
|
7751
9312
|
function loadMonitorSession(monitorId) {
|
|
7752
9313
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -7756,18 +9317,18 @@ function saveMonitorSession(session) {
|
|
|
7756
9317
|
}
|
|
7757
9318
|
function deleteMonitorSession(monitorId) {
|
|
7758
9319
|
const file = monitorPath(monitorId);
|
|
7759
|
-
if (!
|
|
9320
|
+
if (!existsSync34(file)) return false;
|
|
7760
9321
|
unlinkSync2(file);
|
|
7761
9322
|
return true;
|
|
7762
9323
|
}
|
|
7763
9324
|
function listMonitorSessions() {
|
|
7764
9325
|
const dir = monitorsDir();
|
|
7765
|
-
if (!
|
|
9326
|
+
if (!existsSync34(dir)) return [];
|
|
7766
9327
|
const entries = [];
|
|
7767
|
-
for (const name of
|
|
9328
|
+
for (const name of readdirSync11(dir)) {
|
|
7768
9329
|
if (!name.endsWith(".json")) continue;
|
|
7769
9330
|
const session = readJson(
|
|
7770
|
-
|
|
9331
|
+
path52.join(dir, name),
|
|
7771
9332
|
void 0
|
|
7772
9333
|
);
|
|
7773
9334
|
if (!session?.monitorId) continue;
|
|
@@ -7858,7 +9419,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
7858
9419
|
// src/monitor/monitor.service.ts
|
|
7859
9420
|
function workerRecord2(runId, name) {
|
|
7860
9421
|
return readJson(
|
|
7861
|
-
|
|
9422
|
+
path53.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
7862
9423
|
void 0
|
|
7863
9424
|
);
|
|
7864
9425
|
}
|
|
@@ -8063,22 +9624,22 @@ async function runMonitorLoop(args) {
|
|
|
8063
9624
|
}
|
|
8064
9625
|
|
|
8065
9626
|
// src/monitor/monitor-spawn.ts
|
|
8066
|
-
import { spawn as
|
|
8067
|
-
import { closeSync as
|
|
8068
|
-
import
|
|
9627
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
9628
|
+
import { closeSync as closeSync6, existsSync as existsSync35, openSync as openSync6 } from "node:fs";
|
|
9629
|
+
import path54 from "node:path";
|
|
8069
9630
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
8070
9631
|
function resolveDefaultCliPath2() {
|
|
8071
|
-
return
|
|
9632
|
+
return path54.join(fileURLToPath3(new URL(".", import.meta.url)), "cli.js");
|
|
8072
9633
|
}
|
|
8073
9634
|
function spawnMonitorSidecar(opts) {
|
|
8074
9635
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
8075
|
-
if (!
|
|
9636
|
+
if (!existsSync35(cliPath)) return void 0;
|
|
8076
9637
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
8077
9638
|
const { harnessRoot } = getHarnessPaths();
|
|
8078
|
-
const logPath =
|
|
9639
|
+
const logPath = path54.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
8079
9640
|
let logFd;
|
|
8080
9641
|
try {
|
|
8081
|
-
logFd =
|
|
9642
|
+
logFd = openSync6(logPath, "a");
|
|
8082
9643
|
} catch {
|
|
8083
9644
|
logFd = void 0;
|
|
8084
9645
|
}
|
|
@@ -8109,7 +9670,7 @@ function spawnMonitorSidecar(opts) {
|
|
|
8109
9670
|
logFd ?? "ignore"
|
|
8110
9671
|
];
|
|
8111
9672
|
try {
|
|
8112
|
-
const child =
|
|
9673
|
+
const child = spawn6(
|
|
8113
9674
|
nodeExecutable,
|
|
8114
9675
|
args,
|
|
8115
9676
|
hiddenSpawnOptions({
|
|
@@ -8118,7 +9679,7 @@ function spawnMonitorSidecar(opts) {
|
|
|
8118
9679
|
env: process.env
|
|
8119
9680
|
})
|
|
8120
9681
|
);
|
|
8121
|
-
if (logFd !== void 0)
|
|
9682
|
+
if (logFd !== void 0) closeSync6(logFd);
|
|
8122
9683
|
child.unref();
|
|
8123
9684
|
const session = {
|
|
8124
9685
|
monitorId,
|
|
@@ -8135,7 +9696,7 @@ function spawnMonitorSidecar(opts) {
|
|
|
8135
9696
|
} catch {
|
|
8136
9697
|
if (logFd !== void 0) {
|
|
8137
9698
|
try {
|
|
8138
|
-
|
|
9699
|
+
closeSync6(logFd);
|
|
8139
9700
|
} catch {
|
|
8140
9701
|
}
|
|
8141
9702
|
}
|
|
@@ -8195,13 +9756,13 @@ async function monitorTickCli(args) {
|
|
|
8195
9756
|
}
|
|
8196
9757
|
|
|
8197
9758
|
// src/package-version.ts
|
|
8198
|
-
import { existsSync as
|
|
9759
|
+
import { existsSync as existsSync36, readFileSync as readFileSync12 } from "node:fs";
|
|
8199
9760
|
import { dirname, join } from "node:path";
|
|
8200
9761
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
8201
9762
|
function resolvePackageRoot(moduleUrl) {
|
|
8202
9763
|
let dir = dirname(fileURLToPath4(moduleUrl));
|
|
8203
9764
|
for (let depth = 0; depth < 6; depth += 1) {
|
|
8204
|
-
if (
|
|
9765
|
+
if (existsSync36(join(dir, "package.json"))) return dir;
|
|
8205
9766
|
const parent = dirname(dir);
|
|
8206
9767
|
if (parent === dir) break;
|
|
8207
9768
|
dir = parent;
|
|
@@ -8210,7 +9771,7 @@ function resolvePackageRoot(moduleUrl) {
|
|
|
8210
9771
|
}
|
|
8211
9772
|
function readOwnPackageVersion(moduleUrl = import.meta.url) {
|
|
8212
9773
|
const pkgPath = join(resolvePackageRoot(moduleUrl), "package.json");
|
|
8213
|
-
const pkg = JSON.parse(
|
|
9774
|
+
const pkg = JSON.parse(readFileSync12(pkgPath, "utf8"));
|
|
8214
9775
|
if (typeof pkg.version !== "string" || !pkg.version.trim()) {
|
|
8215
9776
|
throw new Error(`Missing package.json version at ${pkgPath}`);
|
|
8216
9777
|
}
|
|
@@ -8230,17 +9791,142 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
|
|
|
8230
9791
|
return true;
|
|
8231
9792
|
}
|
|
8232
9793
|
|
|
9794
|
+
// src/post-restart-unblock.ts
|
|
9795
|
+
import path55 from "node:path";
|
|
9796
|
+
function skip(runId, worker, taskId, agentOsId, leaseOwner, reason) {
|
|
9797
|
+
return { runId, worker, taskId, agentOsId, leaseOwner, action: "skipped", reason };
|
|
9798
|
+
}
|
|
9799
|
+
async function postRestartUnblock(args) {
|
|
9800
|
+
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
9801
|
+
const agentOsIdFilter = args.agentOsId ? String(args.agentOsId).trim() : null;
|
|
9802
|
+
const dryRun = args.dryRun === true || args.dryRun === "true";
|
|
9803
|
+
const released = [];
|
|
9804
|
+
const skipped = [];
|
|
9805
|
+
const errors = [];
|
|
9806
|
+
for (const run of listRunRecords()) {
|
|
9807
|
+
for (const name of Object.keys(run.workers ?? {})) {
|
|
9808
|
+
const workerPath = path55.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
9809
|
+
const worker = readJson(workerPath, void 0);
|
|
9810
|
+
if (!worker) {
|
|
9811
|
+
skipped.push(skip(run.id, name, "", "", "", "worker.json missing"));
|
|
9812
|
+
continue;
|
|
9813
|
+
}
|
|
9814
|
+
const taskId = worker.taskId ?? "";
|
|
9815
|
+
const agentOsId = worker.agentOsId ?? "";
|
|
9816
|
+
const leaseOwner = worker.leaseOwner ?? "";
|
|
9817
|
+
if (!worker.dispatched || !taskId || !agentOsId || !leaseOwner) {
|
|
9818
|
+
skipped.push(
|
|
9819
|
+
skip(run.id, name, taskId, agentOsId, leaseOwner, "not a fully-leased dispatched worker")
|
|
9820
|
+
);
|
|
9821
|
+
continue;
|
|
9822
|
+
}
|
|
9823
|
+
if (agentOsIdFilter && agentOsId !== agentOsIdFilter) continue;
|
|
9824
|
+
if (worker.completionReportedAt) {
|
|
9825
|
+
skipped.push(
|
|
9826
|
+
skip(run.id, name, taskId, agentOsId, leaseOwner, "completion already reported")
|
|
9827
|
+
);
|
|
9828
|
+
continue;
|
|
9829
|
+
}
|
|
9830
|
+
const status = computeWorkerStatus(worker);
|
|
9831
|
+
if (status.finalResult) {
|
|
9832
|
+
skipped.push(
|
|
9833
|
+
skip(run.id, name, taskId, agentOsId, leaseOwner, "has final result \u2014 let pipeline tick complete it")
|
|
9834
|
+
);
|
|
9835
|
+
continue;
|
|
9836
|
+
}
|
|
9837
|
+
if (status.alive) {
|
|
9838
|
+
skipped.push(
|
|
9839
|
+
skip(run.id, name, taskId, agentOsId, leaseOwner, "worker process is still alive")
|
|
9840
|
+
);
|
|
9841
|
+
continue;
|
|
9842
|
+
}
|
|
9843
|
+
if (dryRun) {
|
|
9844
|
+
released.push({
|
|
9845
|
+
runId: run.id,
|
|
9846
|
+
worker: name,
|
|
9847
|
+
taskId,
|
|
9848
|
+
agentOsId,
|
|
9849
|
+
leaseOwner,
|
|
9850
|
+
action: "released",
|
|
9851
|
+
reason: "dry-run: would release dead dispatched worker lease"
|
|
9852
|
+
});
|
|
9853
|
+
continue;
|
|
9854
|
+
}
|
|
9855
|
+
try {
|
|
9856
|
+
const secret = await resolveCallbackSecretWithMint(
|
|
9857
|
+
args.secret ? String(args.secret) : void 0,
|
|
9858
|
+
agentOsId,
|
|
9859
|
+
{ baseUrl: base }
|
|
9860
|
+
);
|
|
9861
|
+
const releaseUrl = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/tasks/${encodeURIComponent(taskId)}/release`;
|
|
9862
|
+
const release = await postJsonWithCredentialRefresh(
|
|
9863
|
+
releaseUrl,
|
|
9864
|
+
secret,
|
|
9865
|
+
{ agentOsId, leaseOwner },
|
|
9866
|
+
{ agentOsId, baseUrl: base }
|
|
9867
|
+
);
|
|
9868
|
+
const releaseOk = release.ok === true || release.response?.ok === true;
|
|
9869
|
+
if (releaseOk) {
|
|
9870
|
+
released.push({
|
|
9871
|
+
runId: run.id,
|
|
9872
|
+
worker: name,
|
|
9873
|
+
taskId,
|
|
9874
|
+
agentOsId,
|
|
9875
|
+
leaseOwner,
|
|
9876
|
+
action: "released",
|
|
9877
|
+
reason: "dead dispatched worker lease released; task requeued"
|
|
9878
|
+
});
|
|
9879
|
+
} else {
|
|
9880
|
+
skipped.push({
|
|
9881
|
+
runId: run.id,
|
|
9882
|
+
worker: name,
|
|
9883
|
+
taskId,
|
|
9884
|
+
agentOsId,
|
|
9885
|
+
leaseOwner,
|
|
9886
|
+
action: "skipped",
|
|
9887
|
+
reason: `release returned ok:false (likely owner mismatch or already released): HTTP ${release.status}`
|
|
9888
|
+
});
|
|
9889
|
+
}
|
|
9890
|
+
} catch (err) {
|
|
9891
|
+
errors.push({
|
|
9892
|
+
runId: run.id,
|
|
9893
|
+
worker: name,
|
|
9894
|
+
taskId,
|
|
9895
|
+
agentOsId,
|
|
9896
|
+
leaseOwner,
|
|
9897
|
+
action: "error",
|
|
9898
|
+
reason: err.message
|
|
9899
|
+
});
|
|
9900
|
+
}
|
|
9901
|
+
}
|
|
9902
|
+
}
|
|
9903
|
+
return { dryRun, released, skipped, errors };
|
|
9904
|
+
}
|
|
9905
|
+
async function postRestartUnblockCli(args) {
|
|
9906
|
+
const result = await postRestartUnblock(args);
|
|
9907
|
+
const summary = {
|
|
9908
|
+
ok: result.errors.length === 0,
|
|
9909
|
+
dryRun: result.dryRun,
|
|
9910
|
+
released: result.released.length,
|
|
9911
|
+
skipped: result.skipped.length,
|
|
9912
|
+
errors: result.errors.length,
|
|
9913
|
+
details: result
|
|
9914
|
+
};
|
|
9915
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
9916
|
+
if (result.errors.length > 0) process.exit(1);
|
|
9917
|
+
}
|
|
9918
|
+
|
|
8233
9919
|
// src/doctor/runtime-takeover.ts
|
|
8234
|
-
import
|
|
9920
|
+
import path57 from "node:path";
|
|
8235
9921
|
|
|
8236
9922
|
// src/doctor/runtime-takeover.probes.ts
|
|
8237
|
-
import { accessSync, constants, existsSync as
|
|
8238
|
-
import { homedir as
|
|
8239
|
-
import
|
|
8240
|
-
import { spawnSync as
|
|
9923
|
+
import { accessSync, constants, existsSync as existsSync37, readFileSync as readFileSync13 } from "node:fs";
|
|
9924
|
+
import { homedir as homedir13 } from "node:os";
|
|
9925
|
+
import path56 from "node:path";
|
|
9926
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
8241
9927
|
function captureCommand(bin, args) {
|
|
8242
9928
|
try {
|
|
8243
|
-
const res =
|
|
9929
|
+
const res = spawnSync7(bin, args, { encoding: "utf8" });
|
|
8244
9930
|
const stdout = (res.stdout || "").trim();
|
|
8245
9931
|
const stderr = (res.stderr || "").trim();
|
|
8246
9932
|
const ok = res.status === 0;
|
|
@@ -8265,7 +9951,7 @@ function tokenPrefix(token) {
|
|
|
8265
9951
|
return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
|
|
8266
9952
|
}
|
|
8267
9953
|
function isWritable(target) {
|
|
8268
|
-
if (!
|
|
9954
|
+
if (!existsSync37(target)) return false;
|
|
8269
9955
|
try {
|
|
8270
9956
|
accessSync(target, constants.W_OK);
|
|
8271
9957
|
return true;
|
|
@@ -8278,15 +9964,15 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
8278
9964
|
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
8279
9965
|
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
8280
9966
|
loadConfig: () => loadUserConfig(),
|
|
8281
|
-
configFilePath: () =>
|
|
8282
|
-
credentialsFilePath: () =>
|
|
9967
|
+
configFilePath: () => path56.join(homedir13(), ".kynver", "config.json"),
|
|
9968
|
+
credentialsFilePath: () => path56.join(homedir13(), ".kynver", "credentials"),
|
|
8283
9969
|
readCredentials: () => {
|
|
8284
|
-
const credPath =
|
|
8285
|
-
if (!
|
|
9970
|
+
const credPath = path56.join(homedir13(), ".kynver", "credentials");
|
|
9971
|
+
if (!existsSync37(credPath)) {
|
|
8286
9972
|
return { hasApiKey: false };
|
|
8287
9973
|
}
|
|
8288
9974
|
try {
|
|
8289
|
-
const parsed = JSON.parse(
|
|
9975
|
+
const parsed = JSON.parse(readFileSync13(credPath, "utf8"));
|
|
8290
9976
|
return {
|
|
8291
9977
|
hasApiKey: Boolean(parsed.apiKey?.trim()),
|
|
8292
9978
|
runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
|
|
@@ -8313,8 +9999,8 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
8313
9999
|
})()
|
|
8314
10000
|
}),
|
|
8315
10001
|
harnessRoot: () => resolveHarnessRoot(),
|
|
8316
|
-
legacyOpenclawHarnessRoot: () =>
|
|
8317
|
-
pathExists: (target) =>
|
|
10002
|
+
legacyOpenclawHarnessRoot: () => path56.join(homedir13(), ".openclaw", "harness"),
|
|
10003
|
+
pathExists: (target) => existsSync37(target),
|
|
8318
10004
|
pathWritable: (target) => isWritable(target),
|
|
8319
10005
|
vercelVersion: () => captureCommand("vercel", ["--version"]),
|
|
8320
10006
|
vercelWhoami: () => captureCommand("vercel", ["whoami"])
|
|
@@ -8642,8 +10328,8 @@ function assessVercelCli(probes) {
|
|
|
8642
10328
|
}
|
|
8643
10329
|
function assessHarnessDirs(probes) {
|
|
8644
10330
|
const harnessRoot = probes.harnessRoot();
|
|
8645
|
-
const runsDir =
|
|
8646
|
-
const worktreesDir =
|
|
10331
|
+
const runsDir = path57.join(harnessRoot, "runs");
|
|
10332
|
+
const worktreesDir = path57.join(harnessRoot, "worktrees");
|
|
8647
10333
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
8648
10334
|
const displayRunsDir = redactHomePath(runsDir);
|
|
8649
10335
|
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
@@ -8879,9 +10565,9 @@ function applySchedulerCutoverAttestation(config) {
|
|
|
8879
10565
|
}
|
|
8880
10566
|
|
|
8881
10567
|
// src/scheduler-cutover-cli.ts
|
|
8882
|
-
import
|
|
8883
|
-
import { homedir as
|
|
8884
|
-
var CONFIG_FILE2 =
|
|
10568
|
+
import path58 from "node:path";
|
|
10569
|
+
import { homedir as homedir14 } from "node:os";
|
|
10570
|
+
var CONFIG_FILE2 = path58.join(homedir14(), ".kynver", "config.json");
|
|
8885
10571
|
function runSchedulerCutoverCheckCli(json = false) {
|
|
8886
10572
|
const config = loadUserConfig();
|
|
8887
10573
|
const report = assessSchedulerCutover(config);
|
|
@@ -8977,7 +10663,7 @@ function usage(code = 0) {
|
|
|
8977
10663
|
" kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
|
|
8978
10664
|
" kynver run list",
|
|
8979
10665
|
" kynver run status --run RUN_ID",
|
|
8980
|
-
" 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 /]",
|
|
10666
|
+
" 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]",
|
|
8981
10667
|
" kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
|
|
8982
10668
|
' kynver worker start --run RUN_ID --name worker --task "..." [--owned path[,path]] [--model MODEL] [--provider claude|cursor] [--agent-os-id AOS_ID] [--task-id TASK_ID] [--wait]',
|
|
8983
10669
|
" kynver worker status --run RUN_ID --name worker",
|
|
@@ -8987,6 +10673,7 @@ function usage(code = 0) {
|
|
|
8987
10673
|
" kynver worker discard-disposable --run RUN_ID --name worker --path scripts/helper.mjs[,other]",
|
|
8988
10674
|
" kynver worker auto-complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--poll-ms 5000] [--max-total-ms 21600000] [--complete-attempts 3] [--complete-backoff-ms 5000] [--base-url URL] [--secret SECRET]",
|
|
8989
10675
|
" kynver run reconcile",
|
|
10676
|
+
" kynver run unblock [--agent-os-id AOS_ID] [--base-url URL] [--secret SECRET] [--dry-run]",
|
|
8990
10677
|
" kynver plan progress --plan PLAN_ID --row ROW_KEY --role ROLE --status STATUS [--task TASK_ID] [--note NOTE] [--evidence type:value] [--agent-os-id AOS_ID]",
|
|
8991
10678
|
" kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override] [--local]",
|
|
8992
10679
|
" kynver harness verify --worktree PATH [--command CMD] [--json] [--wait-for-admission-ms MS] [--timeout-ms MS]",
|
|
@@ -9058,6 +10745,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
9058
10745
|
if (scope === "run" && action === "dispatch") return void await dispatchRun(args);
|
|
9059
10746
|
if (scope === "run" && action === "sweep") return void await sweepRun(args);
|
|
9060
10747
|
if (scope === "run" && action === "reconcile") return reconcileRunsCli();
|
|
10748
|
+
if (scope === "run" && action === "unblock") return void await postRestartUnblockCli(args);
|
|
9061
10749
|
if (scope === "worker" && action === "start") return void await startWorker(args);
|
|
9062
10750
|
if (scope === "worker" && action === "status") return workerStatus(args);
|
|
9063
10751
|
if (scope === "worker" && action === "tail") return tailWorker(args);
|