@kynver-app/runtime 0.1.91 → 0.1.92
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/box-identity.d.ts +19 -0
- package/dist/box-resource-snapshot-shared.d.ts +2 -1
- package/dist/cleanup-execute.d.ts +2 -4
- package/dist/cleanup-harness-path-validate.d.ts +5 -0
- package/dist/cleanup-path-ownership.d.ts +9 -0
- package/dist/cleanup-privileged-remove.d.ts +13 -0
- package/dist/cleanup-remove-path.d.ts +17 -0
- package/dist/cleanup-types.d.ts +2 -0
- package/dist/cli.js +1535 -1096
- package/dist/cli.js.map +4 -4
- package/dist/config.d.ts +6 -0
- package/dist/daemon-box-identity.d.ts +16 -0
- package/dist/harness-worktree-build-guard.d.ts +12 -0
- package/dist/index.js +1724 -1280
- package/dist/index.js.map +4 -4
- package/dist/resource-gate.d.ts +5 -0
- package/dist/worker-cap-source.d.ts +29 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -319,9 +319,9 @@ function shouldEnforceMemoryCostPackageGuardCli(scope, action) {
|
|
|
319
319
|
}
|
|
320
320
|
|
|
321
321
|
// src/config.ts
|
|
322
|
-
import { existsSync as
|
|
323
|
-
import { homedir as
|
|
324
|
-
import
|
|
322
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync2 } from "node:fs";
|
|
323
|
+
import { homedir as homedir5, totalmem } from "node:os";
|
|
324
|
+
import path10 from "node:path";
|
|
325
325
|
|
|
326
326
|
// src/default-repo-discovery.ts
|
|
327
327
|
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
@@ -705,775 +705,440 @@ function discoverDefaultRepo(opts) {
|
|
|
705
705
|
return discoverDefaultRepoCandidates(opts)[0] ?? null;
|
|
706
706
|
}
|
|
707
707
|
|
|
708
|
-
// src/
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
if (
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
708
|
+
// src/box-identity.ts
|
|
709
|
+
function normalizeWorkerPoolBoxKind(raw) {
|
|
710
|
+
const kind = (raw ?? "").trim().toLowerCase();
|
|
711
|
+
if (kind === "ghost" || kind === "forge") return kind;
|
|
712
|
+
if (kind.includes("forge")) return "forge";
|
|
713
|
+
if (kind.includes("ghost") || kind.includes("openclaw")) return "ghost";
|
|
714
|
+
return "forge";
|
|
715
|
+
}
|
|
716
|
+
function trimEnv(env, key) {
|
|
717
|
+
const value = env[key]?.trim();
|
|
718
|
+
return value || null;
|
|
719
|
+
}
|
|
720
|
+
function resolveBoxIdentity(env = process.env, config = {}) {
|
|
721
|
+
const warnings = [];
|
|
722
|
+
const configKind = config.boxKind?.trim();
|
|
723
|
+
if (configKind) {
|
|
724
|
+
return {
|
|
725
|
+
boxKind: normalizeWorkerPoolBoxKind(configKind),
|
|
726
|
+
source: "config",
|
|
727
|
+
slugInferenceBlocked: false,
|
|
728
|
+
warnings
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
const envKind = trimEnv(env, "KYNVER_BOX_KIND");
|
|
732
|
+
if (envKind) {
|
|
733
|
+
return {
|
|
734
|
+
boxKind: normalizeWorkerPoolBoxKind(envKind),
|
|
735
|
+
source: "env",
|
|
736
|
+
slugInferenceBlocked: false,
|
|
737
|
+
warnings
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
const agentOsSlug = trimEnv(env, "KYNVER_AGENT_OS_SLUG");
|
|
741
|
+
if (agentOsSlug) {
|
|
742
|
+
warnings.push(
|
|
743
|
+
`KYNVER_AGENT_OS_SLUG=${agentOsSlug} is a workspace slug, not box identity \u2014 set boxKind via \`kynver setup --box-kind forge|ghost\` or KYNVER_BOX_KIND (defaulting box kind to forge)`
|
|
744
|
+
);
|
|
718
745
|
}
|
|
746
|
+
return {
|
|
747
|
+
boxKind: "forge",
|
|
748
|
+
source: "default",
|
|
749
|
+
slugInferenceBlocked: Boolean(agentOsSlug),
|
|
750
|
+
warnings
|
|
751
|
+
};
|
|
719
752
|
}
|
|
720
|
-
function
|
|
721
|
-
|
|
722
|
-
writeFileSync2(CONFIG_FILE, `${JSON.stringify(normalizeConfigPaths(config), null, 2)}
|
|
723
|
-
`, { mode: 384 });
|
|
753
|
+
function resolveBoxKindFromConfig(config = {}, env = process.env) {
|
|
754
|
+
return resolveBoxIdentity(env, config).boxKind;
|
|
724
755
|
}
|
|
725
|
-
|
|
756
|
+
|
|
757
|
+
// src/resource-gate.ts
|
|
758
|
+
import os2 from "node:os";
|
|
759
|
+
|
|
760
|
+
// src/bounded-build/meminfo.ts
|
|
761
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
762
|
+
import os from "node:os";
|
|
763
|
+
function readMemAvailableBytes(meminfoText) {
|
|
764
|
+
if (meminfoText !== void 0) {
|
|
765
|
+
const match = meminfoText.match(/^MemAvailable:\s+(\d+)\s*kB/m);
|
|
766
|
+
if (match) return Number(match[1]) * 1024;
|
|
767
|
+
return os.freemem();
|
|
768
|
+
}
|
|
769
|
+
if (process.platform === "linux") {
|
|
770
|
+
try {
|
|
771
|
+
const meminfo = readFileSync5("/proc/meminfo", "utf8");
|
|
772
|
+
const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m);
|
|
773
|
+
if (match) return Number(match[1]) * 1024;
|
|
774
|
+
} catch {
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return os.freemem();
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// src/resource-gate.ts
|
|
781
|
+
import path9 from "node:path";
|
|
782
|
+
|
|
783
|
+
// src/disk-gate.ts
|
|
784
|
+
import { statfsSync as statfsSync2 } from "node:fs";
|
|
785
|
+
|
|
786
|
+
// src/wsl-host.ts
|
|
787
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6, statfsSync } from "node:fs";
|
|
788
|
+
var DEFAULT_WSL_HOST_WARN_FREE_BYTES = 25 * 1024 * 1024 * 1024;
|
|
789
|
+
var DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES = 12 * 1024 * 1024 * 1024;
|
|
790
|
+
var DEFAULT_WSL_HOST_MOUNT = "/mnt/c";
|
|
791
|
+
function isWslHost() {
|
|
792
|
+
if (process.platform !== "linux") return false;
|
|
793
|
+
for (const probe of ["/proc/sys/kernel/osrelease", "/proc/version"]) {
|
|
794
|
+
try {
|
|
795
|
+
if (!existsSync5(probe)) continue;
|
|
796
|
+
const text = readFileSync6(probe, "utf8");
|
|
797
|
+
if (/microsoft|wsl/i.test(text)) return true;
|
|
798
|
+
} catch {
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
function observeWslHostDisk(options = {}) {
|
|
804
|
+
const wsl = options.forceWsl === void 0 ? isWslHost() : options.forceWsl;
|
|
805
|
+
if (!wsl) return null;
|
|
806
|
+
const path69 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
|
|
807
|
+
const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;
|
|
808
|
+
const criticalBelowBytes = options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;
|
|
809
|
+
const statfs = options.statfs ?? statfsSync;
|
|
810
|
+
let stats;
|
|
811
|
+
try {
|
|
812
|
+
stats = statfs(path69);
|
|
813
|
+
} catch (error) {
|
|
814
|
+
return {
|
|
815
|
+
ok: false,
|
|
816
|
+
path: path69,
|
|
817
|
+
freeBytes: 0,
|
|
818
|
+
totalBytes: 0,
|
|
819
|
+
usedPercent: 100,
|
|
820
|
+
warnBelowBytes,
|
|
821
|
+
criticalBelowBytes,
|
|
822
|
+
reason: `Windows host disk probe failed at ${path69}: ${error.message}`,
|
|
823
|
+
probeError: error.message
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
827
|
+
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
828
|
+
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
829
|
+
const lowFree = freeBytes < warnBelowBytes;
|
|
830
|
+
const criticalFree = freeBytes < criticalBelowBytes;
|
|
831
|
+
const ok = !lowFree && !criticalFree;
|
|
832
|
+
const freeGiB = (freeBytes / (1024 * 1024 * 1024)).toFixed(1);
|
|
833
|
+
let reason = null;
|
|
834
|
+
if (!ok) {
|
|
835
|
+
const tag = criticalFree ? "critical" : "warning";
|
|
836
|
+
reason = `Windows host disk ${path69} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
|
|
837
|
+
}
|
|
726
838
|
return {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
839
|
+
ok,
|
|
840
|
+
path: path69,
|
|
841
|
+
freeBytes,
|
|
842
|
+
totalBytes,
|
|
843
|
+
usedPercent,
|
|
844
|
+
warnBelowBytes,
|
|
845
|
+
criticalBelowBytes,
|
|
846
|
+
reason,
|
|
847
|
+
probeError: null
|
|
730
848
|
};
|
|
731
849
|
}
|
|
732
|
-
function
|
|
733
|
-
return
|
|
850
|
+
function summarizeWslRecoverySteps() {
|
|
851
|
+
return "Recovery: 1) free Windows C: (empty Recycle Bin / Storage Sense / clear %TEMP%); 2) shut down WSL (`wsl --shutdown`) then compact the VHDX (`Optimize-VHD` or `diskpart compact vdisk`); 3) clear local node_modules / .next / harness worktrees before restarting workers. Full runbook: docs/runbooks/wsl-disk-pressure.md.";
|
|
734
852
|
}
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
853
|
+
|
|
854
|
+
// src/disk-gate.ts
|
|
855
|
+
var DEFAULT_WARN_FREE_BYTES = 30 * 1024 * 1024 * 1024;
|
|
856
|
+
var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
857
|
+
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
858
|
+
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
859
|
+
function observeRunnerDiskGate(input = {}) {
|
|
860
|
+
const path69 = input.diskPath?.trim() || "/";
|
|
861
|
+
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
862
|
+
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
863
|
+
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
864
|
+
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
865
|
+
const stats = statfsSync2(path69);
|
|
866
|
+
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
867
|
+
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
868
|
+
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
869
|
+
const lowFree = freeBytes < warnBelowBytes;
|
|
870
|
+
const criticalFree = freeBytes < criticalBelowBytes;
|
|
871
|
+
const highUse = usedPercent > maxUsedPercent;
|
|
872
|
+
const hardHighUse = usedPercent > hardMaxUsedPercent;
|
|
873
|
+
const localOk = !lowFree && !criticalFree && !highUse && !hardHighUse;
|
|
874
|
+
const wslHost = input.skipWslHostCheck ? null : observeWslHostDisk(input.wslHost);
|
|
875
|
+
const ok = localOk && (wslHost ? wslHost.ok : true);
|
|
876
|
+
let reason = null;
|
|
877
|
+
if (!ok) {
|
|
878
|
+
reason = [
|
|
879
|
+
criticalFree ? `free space below critical ${criticalBelowBytes} bytes` : null,
|
|
880
|
+
lowFree ? `free space below warning ${warnBelowBytes} bytes` : null,
|
|
881
|
+
hardHighUse ? `used percent above hard cap ${hardMaxUsedPercent}%` : null,
|
|
882
|
+
highUse ? `used percent above cap ${maxUsedPercent}%` : null,
|
|
883
|
+
wslHost && !wslHost.ok ? wslHost.reason : null
|
|
884
|
+
].filter(Boolean).join("; ");
|
|
885
|
+
}
|
|
742
886
|
return {
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
887
|
+
ok,
|
|
888
|
+
path: path69,
|
|
889
|
+
freeBytes,
|
|
890
|
+
totalBytes,
|
|
891
|
+
usedPercent,
|
|
892
|
+
warnBelowBytes,
|
|
893
|
+
criticalBelowBytes,
|
|
894
|
+
maxUsedPercent,
|
|
895
|
+
hardMaxUsedPercent,
|
|
896
|
+
reason,
|
|
897
|
+
wslHost
|
|
748
898
|
};
|
|
749
899
|
}
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
900
|
+
|
|
901
|
+
// src/run-store.ts
|
|
902
|
+
import { existsSync as existsSync7, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
903
|
+
import path7 from "node:path";
|
|
904
|
+
|
|
905
|
+
// src/paths.ts
|
|
906
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
907
|
+
import { homedir as homedir4 } from "node:os";
|
|
908
|
+
import path6 from "node:path";
|
|
909
|
+
var LEGACY_ROOT = path6.join(homedir4(), ".openclaw", "harness");
|
|
910
|
+
var HARNESS_LAYOUT_DIR_NAMES = /* @__PURE__ */ new Set(["runs", "worktrees"]);
|
|
911
|
+
function normalizeHarnessRoot(root) {
|
|
912
|
+
let resolved = path6.resolve(resolveUserPath(root.trim()));
|
|
913
|
+
while (HARNESS_LAYOUT_DIR_NAMES.has(path6.basename(resolved))) {
|
|
914
|
+
resolved = path6.dirname(resolved);
|
|
756
915
|
}
|
|
916
|
+
return resolved;
|
|
757
917
|
}
|
|
758
|
-
function
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
918
|
+
function resolveHarnessRoot() {
|
|
919
|
+
const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
|
|
920
|
+
if (env) return normalizeHarnessRoot(env);
|
|
921
|
+
const configured = loadUserConfig().harnessRoot?.trim();
|
|
922
|
+
if (configured) return normalizeHarnessRoot(configured);
|
|
923
|
+
const kynverRoot = path6.join(homedir4(), ".kynver", "harness");
|
|
924
|
+
if (existsSync6(kynverRoot)) return kynverRoot;
|
|
925
|
+
if (existsSync6(LEGACY_ROOT)) return LEGACY_ROOT;
|
|
926
|
+
return kynverRoot;
|
|
762
927
|
}
|
|
763
|
-
function
|
|
764
|
-
|
|
765
|
-
return loadCredentialsFile().apiKey;
|
|
928
|
+
function harnessRunsDir(harnessRoot) {
|
|
929
|
+
return path6.join(normalizeHarnessRoot(harnessRoot), "runs");
|
|
766
930
|
}
|
|
767
|
-
function
|
|
768
|
-
|
|
931
|
+
function harnessWorktreesDir(harnessRoot) {
|
|
932
|
+
return path6.join(normalizeHarnessRoot(harnessRoot), "worktrees");
|
|
769
933
|
}
|
|
770
|
-
function
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
}
|
|
778
|
-
return creds.runnerToken;
|
|
934
|
+
function getHarnessPaths() {
|
|
935
|
+
const harnessRoot = resolveHarnessRoot();
|
|
936
|
+
return {
|
|
937
|
+
harnessRoot,
|
|
938
|
+
runsDir: harnessRunsDir(harnessRoot),
|
|
939
|
+
worktreesDir: harnessWorktreesDir(harnessRoot)
|
|
940
|
+
};
|
|
779
941
|
}
|
|
780
|
-
function
|
|
781
|
-
|
|
782
|
-
...loadCredentialsFile(),
|
|
783
|
-
runnerToken: token,
|
|
784
|
-
runnerTokenAgentOsId: agentOsId
|
|
785
|
-
});
|
|
942
|
+
function runDir(runsDir, id) {
|
|
943
|
+
return path6.join(runsDir, safeSlug(id));
|
|
786
944
|
}
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
return
|
|
945
|
+
|
|
946
|
+
// src/run-store.ts
|
|
947
|
+
function getPaths() {
|
|
948
|
+
return getHarnessPaths();
|
|
791
949
|
}
|
|
792
|
-
function
|
|
793
|
-
const
|
|
794
|
-
return
|
|
950
|
+
function loadRun(id) {
|
|
951
|
+
const { runsDir } = getPaths();
|
|
952
|
+
return readJson(path7.join(runDir(runsDir, safeSlug(id)), "run.json"));
|
|
795
953
|
}
|
|
796
|
-
function
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
const globalSecret = process.env.KYNVER_RUNTIME_SECRET || process.env.KYNVER_CRON_SECRET || process.env.OPENCLAW_CRON_SECRET;
|
|
800
|
-
if (globalSecret) {
|
|
801
|
-
console.warn(
|
|
802
|
-
"[kynver] using deployment-level callback secret; run `kynver runner credential --agent-os-id <id>` for a scoped token"
|
|
803
|
-
);
|
|
804
|
-
return String(globalSecret);
|
|
805
|
-
}
|
|
806
|
-
return void 0;
|
|
954
|
+
function listRunRecords() {
|
|
955
|
+
const { runsDir } = getPaths();
|
|
956
|
+
return listRunRecordsAt(runsDir);
|
|
807
957
|
}
|
|
808
|
-
function
|
|
809
|
-
|
|
810
|
-
if (configured) return configured;
|
|
811
|
-
failConfig(
|
|
812
|
-
"requires --secret, KYNVER_RUNNER_TOKEN, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, KYNVER_API_KEY with an API base URL to mint one, or (legacy) KYNVER_RUNTIME_SECRET / KYNVER_CRON_SECRET / OPENCLAW_CRON_SECRET"
|
|
813
|
-
);
|
|
814
|
-
}
|
|
815
|
-
async function resolveCallbackSecretWithMint(argsSecret, agentOsId, opts) {
|
|
816
|
-
const configured = resolveConfiguredCallbackSecret(argsSecret, agentOsId);
|
|
817
|
-
if (configured) return configured;
|
|
818
|
-
const apiKey = loadApiKey();
|
|
819
|
-
const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);
|
|
820
|
-
if (apiKey && agentOsId && baseUrl) {
|
|
821
|
-
try {
|
|
822
|
-
const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });
|
|
823
|
-
saveRunnerToken(agentOsId, token);
|
|
824
|
-
return token;
|
|
825
|
-
} catch (error) {
|
|
826
|
-
failConfig(`runner credential mint failed: ${error.message}`);
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
failConfig(
|
|
830
|
-
"requires --secret, KYNVER_RUNNER_TOKEN, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, KYNVER_API_KEY with an API base URL to mint one, or (legacy) KYNVER_RUNTIME_SECRET / KYNVER_CRON_SECRET / OPENCLAW_CRON_SECRET"
|
|
831
|
-
);
|
|
958
|
+
function listRunRecordsForHarnessRoot(harnessRoot) {
|
|
959
|
+
return listRunRecordsAt(harnessRunsDir(harnessRoot));
|
|
832
960
|
}
|
|
833
|
-
|
|
834
|
-
const apiKey = loadApiKey();
|
|
835
|
-
const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);
|
|
836
|
-
if (!apiKey || !agentOsId || !baseUrl) return null;
|
|
961
|
+
function isRunDirectoryEntry(runDirPath) {
|
|
837
962
|
try {
|
|
838
|
-
|
|
839
|
-
saveRunnerToken(agentOsId, token);
|
|
840
|
-
return token;
|
|
963
|
+
return statSync2(runDirPath).isDirectory();
|
|
841
964
|
} catch {
|
|
842
|
-
return
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
async function refreshRunnerTokenForAuthFailure(rejectedSecret, agentOsId, opts) {
|
|
846
|
-
const apiKey = loadApiKey();
|
|
847
|
-
const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);
|
|
848
|
-
if (!apiKey) return { ok: false, reason: "KYNVER_API_KEY is required to refresh a rejected runner token" };
|
|
849
|
-
if (!agentOsId) return { ok: false, reason: "agentOsId is required to refresh a rejected runner token" };
|
|
850
|
-
if (!baseUrl) return { ok: false, reason: "KYNVER_API_URL or --base-url is required to refresh a rejected runner token" };
|
|
851
|
-
try {
|
|
852
|
-
const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });
|
|
853
|
-
if (token && token !== rejectedSecret) {
|
|
854
|
-
saveRunnerToken(agentOsId, token);
|
|
855
|
-
return { ok: true, token };
|
|
856
|
-
}
|
|
857
|
-
return { ok: false, reason: "runner credential refresh returned the rejected token" };
|
|
858
|
-
} catch (error) {
|
|
859
|
-
return { ok: false, reason: error.message };
|
|
965
|
+
return false;
|
|
860
966
|
}
|
|
861
967
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
const
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
},
|
|
873
|
-
body: JSON.stringify({})
|
|
874
|
-
});
|
|
875
|
-
const text = await res.text();
|
|
876
|
-
let parsed = null;
|
|
877
|
-
try {
|
|
878
|
-
parsed = JSON.parse(text);
|
|
879
|
-
} catch {
|
|
880
|
-
parsed = null;
|
|
881
|
-
}
|
|
882
|
-
if (!res.ok || !parsed?.token) {
|
|
883
|
-
throw new Error(
|
|
884
|
-
`runner credential mint failed (${res.status}): ${parsed?.error ?? text.slice(0, 200)}`
|
|
968
|
+
function listRunRecordsAt(runsDir) {
|
|
969
|
+
if (!existsSync7(runsDir)) return [];
|
|
970
|
+
const runs = [];
|
|
971
|
+
for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
|
|
972
|
+
if (entry.name === "runs") continue;
|
|
973
|
+
const runDir2 = path7.join(runsDir, entry.name);
|
|
974
|
+
if (!isRunDirectoryEntry(runDir2)) continue;
|
|
975
|
+
const run = readJson(
|
|
976
|
+
path7.join(runDir2, "run.json"),
|
|
977
|
+
void 0
|
|
885
978
|
);
|
|
979
|
+
if (run?.id) runs.push(run);
|
|
886
980
|
}
|
|
887
|
-
return
|
|
981
|
+
return runs;
|
|
888
982
|
}
|
|
889
|
-
|
|
890
|
-
const
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
baseUrl: args.baseUrl ? String(args.baseUrl) : void 0
|
|
895
|
-
});
|
|
896
|
-
saveRunnerToken(agentOsId, token);
|
|
897
|
-
console.log(
|
|
898
|
-
JSON.stringify(
|
|
899
|
-
{
|
|
900
|
-
ok: true,
|
|
901
|
-
agentOsId,
|
|
902
|
-
credentialsPath: displayUserPath(CREDENTIALS_FILE),
|
|
903
|
-
tokenPrefix: `${token.slice(0, 12)}\u2026`,
|
|
904
|
-
note: "Scoped runner token saved; callbacks use X-Kynver-Runner-Token."
|
|
905
|
-
},
|
|
906
|
-
null,
|
|
907
|
-
2
|
|
908
|
-
)
|
|
909
|
-
);
|
|
910
|
-
} catch (err) {
|
|
911
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
912
|
-
process.exit(1);
|
|
913
|
-
}
|
|
983
|
+
function loadWorker(runId, name) {
|
|
984
|
+
const { runsDir } = getPaths();
|
|
985
|
+
return readJson(
|
|
986
|
+
path7.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
|
|
987
|
+
);
|
|
914
988
|
}
|
|
915
|
-
function
|
|
916
|
-
|
|
917
|
-
|
|
989
|
+
function saveRun(run) {
|
|
990
|
+
const { runsDir } = getPaths();
|
|
991
|
+
writeJson(path7.join(runDir(runsDir, run.id), "run.json"), run);
|
|
918
992
|
}
|
|
919
|
-
function
|
|
920
|
-
const
|
|
921
|
-
|
|
922
|
-
const item = argv[i];
|
|
923
|
-
if (!item.startsWith("--")) continue;
|
|
924
|
-
const key = item.slice(2).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
925
|
-
const next = argv[i + 1];
|
|
926
|
-
if (!next || next.startsWith("--")) args[key] = true;
|
|
927
|
-
else {
|
|
928
|
-
args[key] = next;
|
|
929
|
-
i++;
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
return args;
|
|
993
|
+
function saveWorker(runId, worker) {
|
|
994
|
+
const { runsDir } = getPaths();
|
|
995
|
+
writeJson(path7.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
|
|
933
996
|
}
|
|
934
|
-
|
|
935
|
-
const
|
|
936
|
-
|
|
937
|
-
const config = normalizeConfigPaths({
|
|
938
|
-
...existing,
|
|
939
|
-
...inferSetupFields(existing, args),
|
|
940
|
-
...maxWorkersRaw ? { maxConcurrentWorkers: Math.max(1, Math.floor(Number(maxWorkersRaw))) } : {},
|
|
941
|
-
workerProvider: typeof args.provider === "string" ? args.provider : existing.workerProvider || "cursor"
|
|
942
|
-
});
|
|
943
|
-
saveUserConfig(config);
|
|
944
|
-
let runnerCredentialNote;
|
|
945
|
-
const apiKey = loadApiKey();
|
|
946
|
-
const agentOsId = config.agentOsId;
|
|
947
|
-
if (apiKey && agentOsId) {
|
|
948
|
-
try {
|
|
949
|
-
const token = await fetchRunnerCredential(agentOsId, {
|
|
950
|
-
baseUrl: typeof args.apiBaseUrl === "string" ? args.apiBaseUrl : config.apiBaseUrl,
|
|
951
|
-
apiKey
|
|
952
|
-
});
|
|
953
|
-
saveRunnerToken(agentOsId, token);
|
|
954
|
-
runnerCredentialNote = "Scoped runner token minted and saved to ~/.kynver/credentials.";
|
|
955
|
-
} catch {
|
|
956
|
-
runnerCredentialNote = "Runner token not minted (server offline or master secret unset). Run `kynver runner credential` after deploy.";
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
console.log(
|
|
960
|
-
JSON.stringify(
|
|
961
|
-
{
|
|
962
|
-
ok: true,
|
|
963
|
-
configPath: displayUserPath(CONFIG_FILE),
|
|
964
|
-
config: presentUserConfig(config),
|
|
965
|
-
note: runnerCredentialNote ?? "Set worker limit once with --max-workers N (or omit to auto-size from RAM). Run `kynver login` + `kynver runner credential` for scoped callbacks."
|
|
966
|
-
},
|
|
967
|
-
null,
|
|
968
|
-
2
|
|
969
|
-
)
|
|
970
|
-
);
|
|
997
|
+
function runDirectory(id) {
|
|
998
|
+
const { harnessRoot } = getPaths();
|
|
999
|
+
return runDirectoryAt(harnessRoot, id);
|
|
971
1000
|
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
if (!apiKey) failConfig("kynver login requires --api-key or KYNVER_API_KEY");
|
|
975
|
-
saveApiKey(apiKey);
|
|
976
|
-
console.log(JSON.stringify({ ok: true, credentialsPath: displayUserPath(CREDENTIALS_FILE) }, null, 2));
|
|
1001
|
+
function runDirectoryAt(harnessRoot, id) {
|
|
1002
|
+
return runDir(harnessRunsDir(harnessRoot), safeSlug(id));
|
|
977
1003
|
}
|
|
978
1004
|
|
|
979
|
-
// src/
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
};
|
|
1005
|
+
// src/run-worker-index.ts
|
|
1006
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3 } from "node:fs";
|
|
1007
|
+
import path8 from "node:path";
|
|
1008
|
+
function listRunWorkerNames(run) {
|
|
1009
|
+
const names = /* @__PURE__ */ new Set();
|
|
1010
|
+
for (const name of Object.keys(run.workers || {})) {
|
|
1011
|
+
names.add(safeSlug(name));
|
|
987
1012
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
// rename compat window.
|
|
994
|
-
"X-Kynver-Cron-Secret": trimmed,
|
|
995
|
-
"X-OpenClaw-Cron-Secret": trimmed,
|
|
996
|
-
"X-Kynver-Runtime-Secret": trimmed
|
|
997
|
-
};
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
// src/callbacks.ts
|
|
1001
|
-
function callbackTimeoutMs() {
|
|
1002
|
-
const parsed = Number(process.env.KYNVER_CALLBACK_TIMEOUT_MS);
|
|
1003
|
-
if (Number.isFinite(parsed) && parsed > 0) return Math.floor(parsed);
|
|
1004
|
-
return 3e4;
|
|
1005
|
-
}
|
|
1006
|
-
async function withTimeout(fn) {
|
|
1007
|
-
const controller = new AbortController();
|
|
1008
|
-
const timeout = setTimeout(() => controller.abort(), callbackTimeoutMs());
|
|
1009
|
-
try {
|
|
1010
|
-
return await fn(controller.signal);
|
|
1011
|
-
} finally {
|
|
1012
|
-
clearTimeout(timeout);
|
|
1013
|
+
const workersDir = path8.join(runDirectory(run.id), "workers");
|
|
1014
|
+
if (!existsSync8(workersDir)) return [...names];
|
|
1015
|
+
for (const entry of readdirSync3(workersDir, { withFileTypes: true })) {
|
|
1016
|
+
if (!entry.isDirectory()) continue;
|
|
1017
|
+
names.add(safeSlug(entry.name));
|
|
1013
1018
|
}
|
|
1019
|
+
return [...names];
|
|
1014
1020
|
}
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
);
|
|
1024
|
-
let response = null;
|
|
1021
|
+
|
|
1022
|
+
// src/heartbeat.ts
|
|
1023
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
|
|
1024
|
+
|
|
1025
|
+
// src/heartbeat-final-result.ts
|
|
1026
|
+
function tryParseJsonObject(text) {
|
|
1027
|
+
const trimmed = text.trim();
|
|
1028
|
+
if (!trimmed.startsWith("{")) return null;
|
|
1025
1029
|
try {
|
|
1026
|
-
|
|
1030
|
+
const parsed = JSON.parse(trimmed);
|
|
1031
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1032
|
+
return parsed;
|
|
1033
|
+
}
|
|
1027
1034
|
} catch {
|
|
1028
|
-
|
|
1035
|
+
return null;
|
|
1029
1036
|
}
|
|
1030
|
-
return
|
|
1031
|
-
}
|
|
1032
|
-
async function postJsonWithCredentialRefresh(url, secret, body, opts) {
|
|
1033
|
-
const first = await postJson(url, secret, body);
|
|
1034
|
-
if (first.ok || first.status !== 401) return first;
|
|
1035
|
-
const refreshed = await refreshRunnerTokenForAuthFailure(secret, opts.agentOsId, { baseUrl: opts.baseUrl });
|
|
1036
|
-
if (!refreshed.ok) return { ...first, authRefreshFailure: refreshed.reason };
|
|
1037
|
-
const retry = await postJson(url, refreshed.token, body);
|
|
1038
|
-
return { ...retry, refreshedAuth: true };
|
|
1037
|
+
return null;
|
|
1039
1038
|
}
|
|
1040
|
-
|
|
1041
|
-
const
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
} catch {
|
|
1052
|
-
response = null;
|
|
1039
|
+
function embeddedRecordFromProse(text) {
|
|
1040
|
+
const trimmed = text.trim();
|
|
1041
|
+
if (!trimmed) return null;
|
|
1042
|
+
const direct = tryParseJsonObject(trimmed);
|
|
1043
|
+
if (direct) return direct;
|
|
1044
|
+
const candidates = [];
|
|
1045
|
+
const fenceRe = /```(?:json)?\s*([\s\S]*?)```/gi;
|
|
1046
|
+
let fenceMatch;
|
|
1047
|
+
while ((fenceMatch = fenceRe.exec(trimmed)) !== null) {
|
|
1048
|
+
const fromFence = tryParseJsonObject(fenceMatch[1] ?? "");
|
|
1049
|
+
if (fromFence) candidates.push(fromFence);
|
|
1053
1050
|
}
|
|
1054
|
-
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
return (value ?? "").trim().toLowerCase();
|
|
1060
|
-
}
|
|
1061
|
-
function roleLaneToDispatchLane(roleLane) {
|
|
1062
|
-
switch (trimLower(roleLane)) {
|
|
1063
|
-
case "implementer":
|
|
1064
|
-
case "repair_implementer":
|
|
1065
|
-
case "plan_author":
|
|
1066
|
-
case "runtime_verifier":
|
|
1067
|
-
return "implementation";
|
|
1068
|
-
case "plan_reviewer":
|
|
1069
|
-
case "report_reviewer":
|
|
1070
|
-
case "deep_reviewer":
|
|
1071
|
-
return "review";
|
|
1072
|
-
default:
|
|
1073
|
-
return null;
|
|
1051
|
+
const firstBrace = trimmed.indexOf("{");
|
|
1052
|
+
const lastBrace = trimmed.lastIndexOf("}");
|
|
1053
|
+
if (firstBrace >= 0 && lastBrace > firstBrace) {
|
|
1054
|
+
const slice = tryParseJsonObject(trimmed.slice(firstBrace, lastBrace + 1));
|
|
1055
|
+
if (slice) candidates.push(slice);
|
|
1074
1056
|
}
|
|
1057
|
+
return candidates.length > 0 ? candidates[candidates.length - 1] : null;
|
|
1075
1058
|
}
|
|
1076
|
-
function
|
|
1077
|
-
const
|
|
1078
|
-
if (
|
|
1079
|
-
|
|
1080
|
-
|
|
1059
|
+
function terminalFinalResultFromHeartbeatRow(row) {
|
|
1060
|
+
const explicit = row.finalResult ?? row.final_result;
|
|
1061
|
+
if (explicit !== void 0 && explicit !== null) {
|
|
1062
|
+
if (typeof explicit === "string") {
|
|
1063
|
+
const embedded2 = embeddedRecordFromProse(explicit);
|
|
1064
|
+
return embedded2 ?? (explicit.trim() || null);
|
|
1065
|
+
}
|
|
1066
|
+
return explicit;
|
|
1081
1067
|
}
|
|
1082
|
-
const
|
|
1083
|
-
if (
|
|
1084
|
-
|
|
1085
|
-
if (
|
|
1086
|
-
return
|
|
1087
|
-
}
|
|
1088
|
-
function resolveDispatchNextLaneFilter(raw) {
|
|
1089
|
-
return normalizeDispatchNextLaneFilter(raw) ?? "any";
|
|
1068
|
+
const summary = typeof row.summary === "string" ? row.summary.trim() : "";
|
|
1069
|
+
if (!summary) return null;
|
|
1070
|
+
const embedded = embeddedRecordFromProse(summary);
|
|
1071
|
+
if (embedded) return embedded;
|
|
1072
|
+
return summary;
|
|
1090
1073
|
}
|
|
1091
1074
|
|
|
1092
|
-
// src/
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
import { existsSync as existsSync6, readFileSync as readFileSync6, statfsSync } from "node:fs";
|
|
1097
|
-
var DEFAULT_WSL_HOST_WARN_FREE_BYTES = 25 * 1024 * 1024 * 1024;
|
|
1098
|
-
var DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES = 12 * 1024 * 1024 * 1024;
|
|
1099
|
-
var DEFAULT_WSL_HOST_MOUNT = "/mnt/c";
|
|
1100
|
-
function isWslHost() {
|
|
1101
|
-
if (process.platform !== "linux") return false;
|
|
1102
|
-
for (const probe of ["/proc/sys/kernel/osrelease", "/proc/version"]) {
|
|
1103
|
-
try {
|
|
1104
|
-
if (!existsSync6(probe)) continue;
|
|
1105
|
-
const text = readFileSync6(probe, "utf8");
|
|
1106
|
-
if (/microsoft|wsl/i.test(text)) return true;
|
|
1107
|
-
} catch {
|
|
1108
|
-
}
|
|
1109
|
-
}
|
|
1110
|
-
return false;
|
|
1075
|
+
// src/heartbeat.ts
|
|
1076
|
+
var HEARTBEAT_FUTURE_SKEW_MS = 6e4;
|
|
1077
|
+
function isTerminalHeartbeatPhase(phase) {
|
|
1078
|
+
return phase === "complete";
|
|
1111
1079
|
}
|
|
1112
|
-
function
|
|
1113
|
-
|
|
1114
|
-
if (
|
|
1115
|
-
|
|
1116
|
-
const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;
|
|
1117
|
-
const criticalBelowBytes = options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;
|
|
1118
|
-
const statfs = options.statfs ?? statfsSync;
|
|
1119
|
-
let stats;
|
|
1120
|
-
try {
|
|
1121
|
-
stats = statfs(path67);
|
|
1122
|
-
} catch (error) {
|
|
1123
|
-
return {
|
|
1124
|
-
ok: false,
|
|
1125
|
-
path: path67,
|
|
1126
|
-
freeBytes: 0,
|
|
1127
|
-
totalBytes: 0,
|
|
1128
|
-
usedPercent: 100,
|
|
1129
|
-
warnBelowBytes,
|
|
1130
|
-
criticalBelowBytes,
|
|
1131
|
-
reason: `Windows host disk probe failed at ${path67}: ${error.message}`,
|
|
1132
|
-
probeError: error.message
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
|
-
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
1136
|
-
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
1137
|
-
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
1138
|
-
const lowFree = freeBytes < warnBelowBytes;
|
|
1139
|
-
const criticalFree = freeBytes < criticalBelowBytes;
|
|
1140
|
-
const ok = !lowFree && !criticalFree;
|
|
1141
|
-
const freeGiB = (freeBytes / (1024 * 1024 * 1024)).toFixed(1);
|
|
1142
|
-
let reason = null;
|
|
1143
|
-
if (!ok) {
|
|
1144
|
-
const tag = criticalFree ? "critical" : "warning";
|
|
1145
|
-
reason = `Windows host disk ${path67} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
|
|
1080
|
+
function terminalFinalResultFromHeartbeat(heartbeat) {
|
|
1081
|
+
if (!isTerminalHeartbeatPhase(heartbeat.lastHeartbeatPhase)) return null;
|
|
1082
|
+
if (heartbeat.terminalFinalResult !== void 0 && heartbeat.terminalFinalResult !== null) {
|
|
1083
|
+
return heartbeat.terminalFinalResult;
|
|
1146
1084
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
path: path67,
|
|
1150
|
-
freeBytes,
|
|
1151
|
-
totalBytes,
|
|
1152
|
-
usedPercent,
|
|
1153
|
-
warnBelowBytes,
|
|
1154
|
-
criticalBelowBytes,
|
|
1155
|
-
reason,
|
|
1156
|
-
probeError: null
|
|
1157
|
-
};
|
|
1158
|
-
}
|
|
1159
|
-
function summarizeWslRecoverySteps() {
|
|
1160
|
-
return "Recovery: 1) free Windows C: (empty Recycle Bin / Storage Sense / clear %TEMP%); 2) shut down WSL (`wsl --shutdown`) then compact the VHDX (`Optimize-VHD` or `diskpart compact vdisk`); 3) clear local node_modules / .next / harness worktrees before restarting workers. Full runbook: docs/runbooks/wsl-disk-pressure.md.";
|
|
1085
|
+
const summary = heartbeat.lastHeartbeatSummary?.trim();
|
|
1086
|
+
return summary || "completed";
|
|
1161
1087
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
1174
|
-
const stats = statfsSync2(path67);
|
|
1175
|
-
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
1176
|
-
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
1177
|
-
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
1178
|
-
const lowFree = freeBytes < warnBelowBytes;
|
|
1179
|
-
const criticalFree = freeBytes < criticalBelowBytes;
|
|
1180
|
-
const highUse = usedPercent > maxUsedPercent;
|
|
1181
|
-
const hardHighUse = usedPercent > hardMaxUsedPercent;
|
|
1182
|
-
const localOk = !lowFree && !criticalFree && !highUse && !hardHighUse;
|
|
1183
|
-
const wslHost = input.skipWslHostCheck ? null : observeWslHostDisk(input.wslHost);
|
|
1184
|
-
const ok = localOk && (wslHost ? wslHost.ok : true);
|
|
1185
|
-
let reason = null;
|
|
1186
|
-
if (!ok) {
|
|
1187
|
-
reason = [
|
|
1188
|
-
criticalFree ? `free space below critical ${criticalBelowBytes} bytes` : null,
|
|
1189
|
-
lowFree ? `free space below warning ${warnBelowBytes} bytes` : null,
|
|
1190
|
-
hardHighUse ? `used percent above hard cap ${hardMaxUsedPercent}%` : null,
|
|
1191
|
-
highUse ? `used percent above cap ${maxUsedPercent}%` : null,
|
|
1192
|
-
wslHost && !wslHost.ok ? wslHost.reason : null
|
|
1193
|
-
].filter(Boolean).join("; ");
|
|
1194
|
-
}
|
|
1195
|
-
return {
|
|
1196
|
-
ok,
|
|
1197
|
-
path: path67,
|
|
1198
|
-
freeBytes,
|
|
1199
|
-
totalBytes,
|
|
1200
|
-
usedPercent,
|
|
1201
|
-
warnBelowBytes,
|
|
1202
|
-
criticalBelowBytes,
|
|
1203
|
-
maxUsedPercent,
|
|
1204
|
-
hardMaxUsedPercent,
|
|
1205
|
-
reason,
|
|
1206
|
-
wslHost
|
|
1088
|
+
function parseHeartbeat(file) {
|
|
1089
|
+
const result = {
|
|
1090
|
+
heartbeatCount: 0,
|
|
1091
|
+
lastHeartbeatAt: null,
|
|
1092
|
+
lastHeartbeatPhase: null,
|
|
1093
|
+
lastHeartbeatSummary: null,
|
|
1094
|
+
terminalFinalResult: null,
|
|
1095
|
+
heartbeatBlocker: null,
|
|
1096
|
+
timestampAnomalies: [],
|
|
1097
|
+
lastBoxResourceSnapshot: null,
|
|
1098
|
+
lastPrEvidence: []
|
|
1207
1099
|
};
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1100
|
+
if (!existsSync9(file)) return result;
|
|
1101
|
+
const maxFutureMs = Date.now() + HEARTBEAT_FUTURE_SKEW_MS;
|
|
1102
|
+
const clampedTo = new Date(maxFutureMs).toISOString();
|
|
1103
|
+
const lines = readFileSync7(file, "utf8").split("\n").filter(Boolean);
|
|
1104
|
+
for (const line of lines) {
|
|
1105
|
+
const entry = safeJson(line);
|
|
1106
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
1107
|
+
const row = entry;
|
|
1108
|
+
result.heartbeatCount++;
|
|
1109
|
+
if (row.ts) {
|
|
1110
|
+
const ts = String(row.ts);
|
|
1111
|
+
const tsMs = Date.parse(ts);
|
|
1112
|
+
if (Number.isFinite(tsMs) && tsMs > maxFutureMs) {
|
|
1113
|
+
result.timestampAnomalies.push({
|
|
1114
|
+
kind: "future_heartbeat_timestamp",
|
|
1115
|
+
observedAt: ts,
|
|
1116
|
+
clampedTo
|
|
1117
|
+
});
|
|
1118
|
+
} else {
|
|
1119
|
+
result.lastHeartbeatAt = ts;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
if (row.phase !== void 0 && row.phase !== null) result.lastHeartbeatPhase = String(row.phase);
|
|
1123
|
+
if (row.summary !== void 0 && row.summary !== null) result.lastHeartbeatSummary = String(row.summary);
|
|
1124
|
+
if (isTerminalHeartbeatPhase(result.lastHeartbeatPhase)) {
|
|
1125
|
+
result.terminalFinalResult = terminalFinalResultFromHeartbeatRow(row);
|
|
1126
|
+
}
|
|
1127
|
+
result.heartbeatBlocker = row.blocker ? String(row.blocker) : null;
|
|
1128
|
+
if (row.boxResourceSnapshot && typeof row.boxResourceSnapshot === "object" && !Array.isArray(row.boxResourceSnapshot)) {
|
|
1129
|
+
result.lastBoxResourceSnapshot = row.boxResourceSnapshot;
|
|
1130
|
+
}
|
|
1131
|
+
if (Array.isArray(row.prEvidence)) {
|
|
1132
|
+
result.lastPrEvidence = row.prEvidence.filter(
|
|
1133
|
+
(entry2) => !!entry2 && typeof entry2 === "object" && typeof entry2.prUrl === "string"
|
|
1134
|
+
);
|
|
1228
1135
|
}
|
|
1229
1136
|
}
|
|
1230
|
-
return
|
|
1137
|
+
return result;
|
|
1231
1138
|
}
|
|
1232
1139
|
|
|
1233
|
-
// src/
|
|
1234
|
-
import
|
|
1235
|
-
|
|
1236
|
-
// src/run-store.ts
|
|
1237
|
-
import { existsSync as existsSync8, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
1238
|
-
import path8 from "node:path";
|
|
1239
|
-
|
|
1240
|
-
// src/paths.ts
|
|
1241
|
-
import { existsSync as existsSync7 } from "node:fs";
|
|
1242
|
-
import { homedir as homedir5 } from "node:os";
|
|
1243
|
-
import path7 from "node:path";
|
|
1244
|
-
var LEGACY_ROOT = path7.join(homedir5(), ".openclaw", "harness");
|
|
1245
|
-
var HARNESS_LAYOUT_DIR_NAMES = /* @__PURE__ */ new Set(["runs", "worktrees"]);
|
|
1246
|
-
function normalizeHarnessRoot(root) {
|
|
1247
|
-
let resolved = path7.resolve(resolveUserPath(root.trim()));
|
|
1248
|
-
while (HARNESS_LAYOUT_DIR_NAMES.has(path7.basename(resolved))) {
|
|
1249
|
-
resolved = path7.dirname(resolved);
|
|
1250
|
-
}
|
|
1251
|
-
return resolved;
|
|
1252
|
-
}
|
|
1253
|
-
function resolveHarnessRoot() {
|
|
1254
|
-
const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
|
|
1255
|
-
if (env) return normalizeHarnessRoot(env);
|
|
1256
|
-
const configured = loadUserConfig().harnessRoot?.trim();
|
|
1257
|
-
if (configured) return normalizeHarnessRoot(configured);
|
|
1258
|
-
const kynverRoot = path7.join(homedir5(), ".kynver", "harness");
|
|
1259
|
-
if (existsSync7(kynverRoot)) return kynverRoot;
|
|
1260
|
-
if (existsSync7(LEGACY_ROOT)) return LEGACY_ROOT;
|
|
1261
|
-
return kynverRoot;
|
|
1262
|
-
}
|
|
1263
|
-
function harnessRunsDir(harnessRoot) {
|
|
1264
|
-
return path7.join(normalizeHarnessRoot(harnessRoot), "runs");
|
|
1265
|
-
}
|
|
1266
|
-
function harnessWorktreesDir(harnessRoot) {
|
|
1267
|
-
return path7.join(normalizeHarnessRoot(harnessRoot), "worktrees");
|
|
1268
|
-
}
|
|
1269
|
-
function getHarnessPaths() {
|
|
1270
|
-
const harnessRoot = resolveHarnessRoot();
|
|
1271
|
-
return {
|
|
1272
|
-
harnessRoot,
|
|
1273
|
-
runsDir: harnessRunsDir(harnessRoot),
|
|
1274
|
-
worktreesDir: harnessWorktreesDir(harnessRoot)
|
|
1275
|
-
};
|
|
1276
|
-
}
|
|
1277
|
-
function runDir(runsDir, id) {
|
|
1278
|
-
return path7.join(runsDir, safeSlug(id));
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
// src/run-store.ts
|
|
1282
|
-
function getPaths() {
|
|
1283
|
-
return getHarnessPaths();
|
|
1284
|
-
}
|
|
1285
|
-
function loadRun(id) {
|
|
1286
|
-
const { runsDir } = getPaths();
|
|
1287
|
-
return readJson(path8.join(runDir(runsDir, safeSlug(id)), "run.json"));
|
|
1288
|
-
}
|
|
1289
|
-
function listRunRecords() {
|
|
1290
|
-
const { runsDir } = getPaths();
|
|
1291
|
-
return listRunRecordsAt(runsDir);
|
|
1292
|
-
}
|
|
1293
|
-
function listRunRecordsForHarnessRoot(harnessRoot) {
|
|
1294
|
-
return listRunRecordsAt(harnessRunsDir(harnessRoot));
|
|
1295
|
-
}
|
|
1296
|
-
function isRunDirectoryEntry(runDirPath) {
|
|
1297
|
-
try {
|
|
1298
|
-
return statSync2(runDirPath).isDirectory();
|
|
1299
|
-
} catch {
|
|
1300
|
-
return false;
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
function listRunRecordsAt(runsDir) {
|
|
1304
|
-
if (!existsSync8(runsDir)) return [];
|
|
1305
|
-
const runs = [];
|
|
1306
|
-
for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
|
|
1307
|
-
if (entry.name === "runs") continue;
|
|
1308
|
-
const runDir2 = path8.join(runsDir, entry.name);
|
|
1309
|
-
if (!isRunDirectoryEntry(runDir2)) continue;
|
|
1310
|
-
const run = readJson(
|
|
1311
|
-
path8.join(runDir2, "run.json"),
|
|
1312
|
-
void 0
|
|
1313
|
-
);
|
|
1314
|
-
if (run?.id) runs.push(run);
|
|
1315
|
-
}
|
|
1316
|
-
return runs;
|
|
1317
|
-
}
|
|
1318
|
-
function loadWorker(runId, name) {
|
|
1319
|
-
const { runsDir } = getPaths();
|
|
1320
|
-
return readJson(
|
|
1321
|
-
path8.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
|
|
1322
|
-
);
|
|
1323
|
-
}
|
|
1324
|
-
function saveRun(run) {
|
|
1325
|
-
const { runsDir } = getPaths();
|
|
1326
|
-
writeJson(path8.join(runDir(runsDir, run.id), "run.json"), run);
|
|
1327
|
-
}
|
|
1328
|
-
function saveWorker(runId, worker) {
|
|
1329
|
-
const { runsDir } = getPaths();
|
|
1330
|
-
writeJson(path8.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
|
|
1331
|
-
}
|
|
1332
|
-
function runDirectory(id) {
|
|
1333
|
-
const { harnessRoot } = getPaths();
|
|
1334
|
-
return runDirectoryAt(harnessRoot, id);
|
|
1335
|
-
}
|
|
1336
|
-
function runDirectoryAt(harnessRoot, id) {
|
|
1337
|
-
return runDir(harnessRunsDir(harnessRoot), safeSlug(id));
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
// src/run-worker-index.ts
|
|
1341
|
-
import { existsSync as existsSync9, readdirSync as readdirSync3 } from "node:fs";
|
|
1342
|
-
import path9 from "node:path";
|
|
1343
|
-
function listRunWorkerNames(run) {
|
|
1344
|
-
const names = /* @__PURE__ */ new Set();
|
|
1345
|
-
for (const name of Object.keys(run.workers || {})) {
|
|
1346
|
-
names.add(safeSlug(name));
|
|
1347
|
-
}
|
|
1348
|
-
const workersDir = path9.join(runDirectory(run.id), "workers");
|
|
1349
|
-
if (!existsSync9(workersDir)) return [...names];
|
|
1350
|
-
for (const entry of readdirSync3(workersDir, { withFileTypes: true })) {
|
|
1351
|
-
if (!entry.isDirectory()) continue;
|
|
1352
|
-
names.add(safeSlug(entry.name));
|
|
1353
|
-
}
|
|
1354
|
-
return [...names];
|
|
1355
|
-
}
|
|
1356
|
-
|
|
1357
|
-
// src/heartbeat.ts
|
|
1358
|
-
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "node:fs";
|
|
1359
|
-
|
|
1360
|
-
// src/heartbeat-final-result.ts
|
|
1361
|
-
function tryParseJsonObject(text) {
|
|
1362
|
-
const trimmed = text.trim();
|
|
1363
|
-
if (!trimmed.startsWith("{")) return null;
|
|
1364
|
-
try {
|
|
1365
|
-
const parsed = JSON.parse(trimmed);
|
|
1366
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1367
|
-
return parsed;
|
|
1368
|
-
}
|
|
1369
|
-
} catch {
|
|
1370
|
-
return null;
|
|
1371
|
-
}
|
|
1372
|
-
return null;
|
|
1373
|
-
}
|
|
1374
|
-
function embeddedRecordFromProse(text) {
|
|
1375
|
-
const trimmed = text.trim();
|
|
1376
|
-
if (!trimmed) return null;
|
|
1377
|
-
const direct = tryParseJsonObject(trimmed);
|
|
1378
|
-
if (direct) return direct;
|
|
1379
|
-
const candidates = [];
|
|
1380
|
-
const fenceRe = /```(?:json)?\s*([\s\S]*?)```/gi;
|
|
1381
|
-
let fenceMatch;
|
|
1382
|
-
while ((fenceMatch = fenceRe.exec(trimmed)) !== null) {
|
|
1383
|
-
const fromFence = tryParseJsonObject(fenceMatch[1] ?? "");
|
|
1384
|
-
if (fromFence) candidates.push(fromFence);
|
|
1385
|
-
}
|
|
1386
|
-
const firstBrace = trimmed.indexOf("{");
|
|
1387
|
-
const lastBrace = trimmed.lastIndexOf("}");
|
|
1388
|
-
if (firstBrace >= 0 && lastBrace > firstBrace) {
|
|
1389
|
-
const slice = tryParseJsonObject(trimmed.slice(firstBrace, lastBrace + 1));
|
|
1390
|
-
if (slice) candidates.push(slice);
|
|
1391
|
-
}
|
|
1392
|
-
return candidates.length > 0 ? candidates[candidates.length - 1] : null;
|
|
1393
|
-
}
|
|
1394
|
-
function terminalFinalResultFromHeartbeatRow(row) {
|
|
1395
|
-
const explicit = row.finalResult ?? row.final_result;
|
|
1396
|
-
if (explicit !== void 0 && explicit !== null) {
|
|
1397
|
-
if (typeof explicit === "string") {
|
|
1398
|
-
const embedded2 = embeddedRecordFromProse(explicit);
|
|
1399
|
-
return embedded2 ?? (explicit.trim() || null);
|
|
1400
|
-
}
|
|
1401
|
-
return explicit;
|
|
1402
|
-
}
|
|
1403
|
-
const summary = typeof row.summary === "string" ? row.summary.trim() : "";
|
|
1404
|
-
if (!summary) return null;
|
|
1405
|
-
const embedded = embeddedRecordFromProse(summary);
|
|
1406
|
-
if (embedded) return embedded;
|
|
1407
|
-
return summary;
|
|
1408
|
-
}
|
|
1409
|
-
|
|
1410
|
-
// src/heartbeat.ts
|
|
1411
|
-
var HEARTBEAT_FUTURE_SKEW_MS = 6e4;
|
|
1412
|
-
function isTerminalHeartbeatPhase(phase) {
|
|
1413
|
-
return phase === "complete";
|
|
1414
|
-
}
|
|
1415
|
-
function terminalFinalResultFromHeartbeat(heartbeat) {
|
|
1416
|
-
if (!isTerminalHeartbeatPhase(heartbeat.lastHeartbeatPhase)) return null;
|
|
1417
|
-
if (heartbeat.terminalFinalResult !== void 0 && heartbeat.terminalFinalResult !== null) {
|
|
1418
|
-
return heartbeat.terminalFinalResult;
|
|
1419
|
-
}
|
|
1420
|
-
const summary = heartbeat.lastHeartbeatSummary?.trim();
|
|
1421
|
-
return summary || "completed";
|
|
1422
|
-
}
|
|
1423
|
-
function parseHeartbeat(file) {
|
|
1424
|
-
const result = {
|
|
1425
|
-
heartbeatCount: 0,
|
|
1426
|
-
lastHeartbeatAt: null,
|
|
1427
|
-
lastHeartbeatPhase: null,
|
|
1428
|
-
lastHeartbeatSummary: null,
|
|
1429
|
-
terminalFinalResult: null,
|
|
1430
|
-
heartbeatBlocker: null,
|
|
1431
|
-
timestampAnomalies: [],
|
|
1432
|
-
lastBoxResourceSnapshot: null,
|
|
1433
|
-
lastPrEvidence: []
|
|
1434
|
-
};
|
|
1435
|
-
if (!existsSync10(file)) return result;
|
|
1436
|
-
const maxFutureMs = Date.now() + HEARTBEAT_FUTURE_SKEW_MS;
|
|
1437
|
-
const clampedTo = new Date(maxFutureMs).toISOString();
|
|
1438
|
-
const lines = readFileSync8(file, "utf8").split("\n").filter(Boolean);
|
|
1439
|
-
for (const line of lines) {
|
|
1440
|
-
const entry = safeJson(line);
|
|
1441
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
1442
|
-
const row = entry;
|
|
1443
|
-
result.heartbeatCount++;
|
|
1444
|
-
if (row.ts) {
|
|
1445
|
-
const ts = String(row.ts);
|
|
1446
|
-
const tsMs = Date.parse(ts);
|
|
1447
|
-
if (Number.isFinite(tsMs) && tsMs > maxFutureMs) {
|
|
1448
|
-
result.timestampAnomalies.push({
|
|
1449
|
-
kind: "future_heartbeat_timestamp",
|
|
1450
|
-
observedAt: ts,
|
|
1451
|
-
clampedTo
|
|
1452
|
-
});
|
|
1453
|
-
} else {
|
|
1454
|
-
result.lastHeartbeatAt = ts;
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
if (row.phase !== void 0 && row.phase !== null) result.lastHeartbeatPhase = String(row.phase);
|
|
1458
|
-
if (row.summary !== void 0 && row.summary !== null) result.lastHeartbeatSummary = String(row.summary);
|
|
1459
|
-
if (isTerminalHeartbeatPhase(result.lastHeartbeatPhase)) {
|
|
1460
|
-
result.terminalFinalResult = terminalFinalResultFromHeartbeatRow(row);
|
|
1461
|
-
}
|
|
1462
|
-
result.heartbeatBlocker = row.blocker ? String(row.blocker) : null;
|
|
1463
|
-
if (row.boxResourceSnapshot && typeof row.boxResourceSnapshot === "object" && !Array.isArray(row.boxResourceSnapshot)) {
|
|
1464
|
-
result.lastBoxResourceSnapshot = row.boxResourceSnapshot;
|
|
1465
|
-
}
|
|
1466
|
-
if (Array.isArray(row.prEvidence)) {
|
|
1467
|
-
result.lastPrEvidence = row.prEvidence.filter(
|
|
1468
|
-
(entry2) => !!entry2 && typeof entry2 === "object" && typeof entry2.prUrl === "string"
|
|
1469
|
-
);
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
return result;
|
|
1473
|
-
}
|
|
1474
|
-
|
|
1475
|
-
// src/stream.ts
|
|
1476
|
-
import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
|
|
1140
|
+
// src/stream.ts
|
|
1141
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "node:fs";
|
|
1477
1142
|
|
|
1478
1143
|
// src/repo-search.ts
|
|
1479
1144
|
var RG_BINARIES = /* @__PURE__ */ new Set(["rg", "ripgrep", "grep"]);
|
|
@@ -1957,8 +1622,8 @@ function parseHarnessStream(file) {
|
|
|
1957
1622
|
error: null,
|
|
1958
1623
|
lastShellOutcome: null
|
|
1959
1624
|
};
|
|
1960
|
-
if (!
|
|
1961
|
-
const lines =
|
|
1625
|
+
if (!existsSync10(file)) return result;
|
|
1626
|
+
const lines = readFileSync8(file, "utf8").split("\n").filter(Boolean);
|
|
1962
1627
|
for (const line of lines) {
|
|
1963
1628
|
const event = safeJson(line);
|
|
1964
1629
|
if (!event) continue;
|
|
@@ -2414,302 +2079,823 @@ function assessWorkerLandingContract(input) {
|
|
|
2414
2079
|
missing.push(key);
|
|
2415
2080
|
}
|
|
2416
2081
|
}
|
|
2417
|
-
if (missing.length > 0) {
|
|
2418
|
-
return {
|
|
2419
|
-
blocked: true,
|
|
2420
|
-
reason: missing.every((u) => byUrl.has(u)) ? "incomplete_target_pr_landing" : "missing_target_pr_reconciliation",
|
|
2421
|
-
detail: `Target PR reconciliation incomplete: ${missing.join(", ")}`
|
|
2422
|
-
};
|
|
2423
|
-
}
|
|
2424
|
-
return { blocked: false };
|
|
2082
|
+
if (missing.length > 0) {
|
|
2083
|
+
return {
|
|
2084
|
+
blocked: true,
|
|
2085
|
+
reason: missing.every((u) => byUrl.has(u)) ? "incomplete_target_pr_landing" : "missing_target_pr_reconciliation",
|
|
2086
|
+
detail: `Target PR reconciliation incomplete: ${missing.join(", ")}`
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
return { blocked: false };
|
|
2090
|
+
}
|
|
2091
|
+
function landingContractAttentionReason(verdict) {
|
|
2092
|
+
if (!verdict.blocked) return void 0;
|
|
2093
|
+
return verdict.detail ?? verdict.reason;
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
// src/status.ts
|
|
2097
|
+
var NO_START_MS = 18e4;
|
|
2098
|
+
var STALE_MS = 6e5;
|
|
2099
|
+
function computeAttention(input) {
|
|
2100
|
+
const now = Date.now();
|
|
2101
|
+
if (input.completionBlocker) {
|
|
2102
|
+
return { state: "blocked", reason: input.completionBlocker };
|
|
2103
|
+
}
|
|
2104
|
+
if (input.finalResult) {
|
|
2105
|
+
const landingSnapshot = {
|
|
2106
|
+
finalResult: input.finalResult,
|
|
2107
|
+
changedFiles: input.changedFiles ?? [],
|
|
2108
|
+
gitAncestry: input.gitAncestry ?? null,
|
|
2109
|
+
prUrl: input.prUrl ?? null
|
|
2110
|
+
};
|
|
2111
|
+
const landing = assessWorkerLanding(landingSnapshot);
|
|
2112
|
+
if (landing.blocked) {
|
|
2113
|
+
const detail = landingAttentionReason(landing);
|
|
2114
|
+
return {
|
|
2115
|
+
state: "needs_attention",
|
|
2116
|
+
reason: landing.reason ? `landing blocked (${landing.reason}): ${detail}` : `landing blocked: ${detail}`
|
|
2117
|
+
};
|
|
2118
|
+
}
|
|
2119
|
+
if (input.landingContract) {
|
|
2120
|
+
const contractVerdict = assessWorkerLandingContract({
|
|
2121
|
+
contract: input.landingContract,
|
|
2122
|
+
snapshot: landingSnapshot,
|
|
2123
|
+
finalResult: input.finalResult
|
|
2124
|
+
});
|
|
2125
|
+
const contractDetail = landingContractAttentionReason(contractVerdict);
|
|
2126
|
+
if (contractDetail) {
|
|
2127
|
+
return {
|
|
2128
|
+
state: "needs_attention",
|
|
2129
|
+
reason: contractVerdict.reason ? `landing contract (${contractVerdict.reason}): ${contractDetail}` : `landing contract: ${contractDetail}`
|
|
2130
|
+
};
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
return { state: "done", reason: "final result recorded" };
|
|
2134
|
+
}
|
|
2135
|
+
if (!input.alive) {
|
|
2136
|
+
const classified = classifyExitFailure(input.error);
|
|
2137
|
+
if (classified) return { state: "blocked", reason: classified.reason };
|
|
2138
|
+
const salvage = assessExitedWorkerSalvage({
|
|
2139
|
+
alive: false,
|
|
2140
|
+
finalResult: null,
|
|
2141
|
+
changedFiles: input.changedFiles,
|
|
2142
|
+
gitAncestry: input.gitAncestry
|
|
2143
|
+
});
|
|
2144
|
+
if (salvage?.salvageable) {
|
|
2145
|
+
const tail2 = input.error?.trim();
|
|
2146
|
+
return {
|
|
2147
|
+
state: "needs_attention",
|
|
2148
|
+
reason: tail2 ? `${salvage.attentionReason} (${tail2})` : salvage.attentionReason
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
const tail = input.error?.trim();
|
|
2152
|
+
return {
|
|
2153
|
+
state: "needs_attention",
|
|
2154
|
+
reason: tail ? `process exited without a final result: ${tail}` : salvage?.attentionReason ?? "process exited without a final result"
|
|
2155
|
+
};
|
|
2156
|
+
}
|
|
2157
|
+
if (input.heartbeatBlocker) {
|
|
2158
|
+
return { state: "blocked", reason: `worker heartbeat reported blocker: ${input.heartbeatBlocker}` };
|
|
2159
|
+
}
|
|
2160
|
+
const startMs = input.startedAt ? Date.parse(input.startedAt) : NaN;
|
|
2161
|
+
if (!input.firstEventAt && input.stdoutBytes === 0 && input.heartbeatBytes === 0 && Number.isFinite(startMs) && now - startMs > NO_START_MS) {
|
|
2162
|
+
return { state: "needs_attention", reason: `no first stream event ${secsAgo(startMs)}s after start` };
|
|
2163
|
+
}
|
|
2164
|
+
const actMs = input.lastActivityAt ? Date.parse(input.lastActivityAt) : NaN;
|
|
2165
|
+
if (Number.isFinite(actMs) && now - actMs > STALE_MS) {
|
|
2166
|
+
return { state: "stale", reason: `no log/event/heartbeat activity for ${secsAgo(actMs)}s` };
|
|
2167
|
+
}
|
|
2168
|
+
return { state: "ok", reason: "recent activity" };
|
|
2169
|
+
}
|
|
2170
|
+
function resolveFinalResult(worker, parsedFinalResult, heartbeat) {
|
|
2171
|
+
if (parsedFinalResult) return parsedFinalResult;
|
|
2172
|
+
const ackSnapshot = worker.completionSnapshot?.finalResult;
|
|
2173
|
+
if (ackSnapshot !== void 0 && ackSnapshot !== null) return ackSnapshot;
|
|
2174
|
+
return terminalFinalResultFromHeartbeat(heartbeat);
|
|
2175
|
+
}
|
|
2176
|
+
function computeWorkerStatus(worker, options = {}) {
|
|
2177
|
+
const parsed = parseHarnessStream(worker.stdoutPath);
|
|
2178
|
+
const heartbeat = parseHeartbeat(worker.heartbeatPath);
|
|
2179
|
+
const completionAcknowledged = typeof worker.completionReportedAt === "string" && worker.completionReportedAt.trim().length > 0;
|
|
2180
|
+
const finalResult = resolveFinalResult(worker, parsed.finalResult, heartbeat);
|
|
2181
|
+
const alive = completionAcknowledged ? false : isPidAlive(worker.pid);
|
|
2182
|
+
const stdoutBytes = fileSize(worker.stdoutPath);
|
|
2183
|
+
const stderrBytes = fileSize(worker.stderrPath);
|
|
2184
|
+
const heartbeatBytes = fileSize(worker.heartbeatPath);
|
|
2185
|
+
const changedFiles = gitStatusShort(worker.worktreePath);
|
|
2186
|
+
const gitAncestry = computeGitAncestry(worker.worktreePath, {
|
|
2187
|
+
base: options.base,
|
|
2188
|
+
baseCommit: options.baseCommit
|
|
2189
|
+
});
|
|
2190
|
+
const lastActivityAt = latestIso([
|
|
2191
|
+
parsed.lastEventAt,
|
|
2192
|
+
heartbeat.lastHeartbeatAt,
|
|
2193
|
+
fileMtime(worker.stdoutPath),
|
|
2194
|
+
fileMtime(worker.stderrPath),
|
|
2195
|
+
fileMtime(worker.heartbeatPath)
|
|
2196
|
+
]);
|
|
2197
|
+
const error = parsed.error || (!alive && !finalResult ? tailFile(worker.stderrPath, 10).trim() || void 0 : void 0);
|
|
2198
|
+
const completionBlocker = typeof worker.completionBlocker === "string" && worker.completionBlocker.trim() ? worker.completionBlocker.trim() : null;
|
|
2199
|
+
const landingContract = worker.repairTargetPrUrl ? {
|
|
2200
|
+
landingOnly: false,
|
|
2201
|
+
targetPrUrls: [worker.repairTargetPrUrl],
|
|
2202
|
+
targetPrUrl: worker.repairTargetPrUrl,
|
|
2203
|
+
repairEnforceOriginalPr: true
|
|
2204
|
+
} : null;
|
|
2205
|
+
const attention = computeAttention({
|
|
2206
|
+
alive,
|
|
2207
|
+
finalResult,
|
|
2208
|
+
firstEventAt: parsed.firstEventAt,
|
|
2209
|
+
stdoutBytes,
|
|
2210
|
+
heartbeatBytes,
|
|
2211
|
+
lastActivityAt,
|
|
2212
|
+
heartbeatBlocker: heartbeat.heartbeatBlocker,
|
|
2213
|
+
startedAt: worker.startedAt,
|
|
2214
|
+
error,
|
|
2215
|
+
changedFiles,
|
|
2216
|
+
gitAncestry,
|
|
2217
|
+
completionBlocker,
|
|
2218
|
+
landingContract,
|
|
2219
|
+
prUrl: worker.repairTargetPrUrl ?? worker.taskPrUrl ?? null
|
|
2220
|
+
});
|
|
2221
|
+
const workerStatusLabel = completionBlocker || attention.state === "blocked" ? "blocked" : completionAcknowledged || attention.state === "done" ? "done" : finalResult ? "exited" : alive ? "running" : "exited";
|
|
2222
|
+
return {
|
|
2223
|
+
runId: worker.runId,
|
|
2224
|
+
worker: worker.name,
|
|
2225
|
+
pid: worker.pid,
|
|
2226
|
+
alive,
|
|
2227
|
+
status: workerStatusLabel,
|
|
2228
|
+
attention,
|
|
2229
|
+
branch: worker.branch,
|
|
2230
|
+
worktreePath: worker.worktreePath,
|
|
2231
|
+
ownedPaths: worker.ownedPaths,
|
|
2232
|
+
stdoutBytes,
|
|
2233
|
+
stderrBytes,
|
|
2234
|
+
heartbeatBytes,
|
|
2235
|
+
firstEventAt: parsed.firstEventAt,
|
|
2236
|
+
lastEventAt: parsed.lastEventAt,
|
|
2237
|
+
lastActivityAt,
|
|
2238
|
+
currentTool: completionAcknowledged ? null : parsed.currentTool,
|
|
2239
|
+
heartbeatCount: heartbeat.heartbeatCount,
|
|
2240
|
+
lastHeartbeatAt: heartbeat.lastHeartbeatAt,
|
|
2241
|
+
lastHeartbeatPhase: heartbeat.lastHeartbeatPhase,
|
|
2242
|
+
lastHeartbeatSummary: heartbeat.lastHeartbeatSummary,
|
|
2243
|
+
heartbeatBlocker: heartbeat.heartbeatBlocker,
|
|
2244
|
+
timestampAnomalies: heartbeat.timestampAnomalies,
|
|
2245
|
+
finalResult,
|
|
2246
|
+
error,
|
|
2247
|
+
changedFiles,
|
|
2248
|
+
gitAncestry,
|
|
2249
|
+
instructionPolicyFingerprint: worker.instructionPolicyFingerprint ?? null,
|
|
2250
|
+
instructionPolicyEvidence: worker.instructionPolicyEvidence ?? null
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
2253
|
+
function isFinishedWorkerStatus(status) {
|
|
2254
|
+
if (status.finalResult) return true;
|
|
2255
|
+
if (status.alive === false) return true;
|
|
2256
|
+
if (status.status === "exited" || status.status === "done") return true;
|
|
2257
|
+
return false;
|
|
2258
|
+
}
|
|
2259
|
+
function isLandingBlockedWorkerStatus(status) {
|
|
2260
|
+
if (!status.finalResult) return false;
|
|
2261
|
+
return status.attention.state === "needs_attention" || status.attention.state === "blocked";
|
|
2262
|
+
}
|
|
2263
|
+
function deriveRunStatus(fallback, workers) {
|
|
2264
|
+
if (workers.length === 0) return fallback;
|
|
2265
|
+
if (workers.some((w) => w.attention === "needs_attention" || w.attention === "stale" || w.attention === "blocked")) {
|
|
2266
|
+
return "needs_attention";
|
|
2267
|
+
}
|
|
2268
|
+
if (workers.every((w) => w.status === "done")) return "done";
|
|
2269
|
+
if (workers.some((w) => w.status === "running")) return "running";
|
|
2270
|
+
return fallback;
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
// src/resource-gate.ts
|
|
2274
|
+
var DEFAULT_PER_WORKER_MEM_BYTES = 500 * 1024 * 1024;
|
|
2275
|
+
var DEFAULT_MEM_RESERVE_BYTES = 4 * 1024 * 1024 * 1024;
|
|
2276
|
+
var DEFAULT_MEM_UTILIZATION = 0.85;
|
|
2277
|
+
var AUTO_MAX_WORKERS_CEILING = 64;
|
|
2278
|
+
function positiveInt(value, fallback) {
|
|
2279
|
+
const n = Number(value);
|
|
2280
|
+
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
2281
|
+
return Math.floor(n);
|
|
2282
|
+
}
|
|
2283
|
+
function resolveResourceConfig(config = loadUserConfig(), configuredMaxWorkersOverride, totalMemBytes) {
|
|
2284
|
+
const perWorkerMemBytes = positiveInt(config.perWorkerMemBytes, DEFAULT_PER_WORKER_MEM_BYTES);
|
|
2285
|
+
const memReserveBytes = positiveInt(config.memReserveBytes, DEFAULT_MEM_RESERVE_BYTES);
|
|
2286
|
+
const memUtilization = Math.min(
|
|
2287
|
+
1,
|
|
2288
|
+
Math.max(0.1, Number(config.memUtilization) > 0 ? Number(config.memUtilization) : DEFAULT_MEM_UTILIZATION)
|
|
2289
|
+
);
|
|
2290
|
+
const cap = resolveWorkerCap({
|
|
2291
|
+
config,
|
|
2292
|
+
configuredMaxWorkersOverride,
|
|
2293
|
+
totalMemBytes: totalMemBytes ?? os2.totalmem()
|
|
2294
|
+
});
|
|
2295
|
+
return {
|
|
2296
|
+
perWorkerMemBytes,
|
|
2297
|
+
memReserveBytes,
|
|
2298
|
+
memUtilization,
|
|
2299
|
+
configuredMaxWorkers: cap.configuredMaxWorkers,
|
|
2300
|
+
autoCap: cap.autoCap,
|
|
2301
|
+
workerCapSource: cap.workerCapSource
|
|
2302
|
+
};
|
|
2303
|
+
}
|
|
2304
|
+
function computeAutoMaxWorkers(totalMemBytes, opts = {}) {
|
|
2305
|
+
const perWorkerMemBytes = opts.perWorkerMemBytes ?? DEFAULT_PER_WORKER_MEM_BYTES;
|
|
2306
|
+
const memReserveBytes = opts.memReserveBytes ?? DEFAULT_MEM_RESERVE_BYTES;
|
|
2307
|
+
const memUtilization = opts.memUtilization ?? DEFAULT_MEM_UTILIZATION;
|
|
2308
|
+
const budgetBytes = Math.max(0, Math.floor(totalMemBytes * memUtilization) - memReserveBytes);
|
|
2309
|
+
const raw = Math.max(1, Math.floor(budgetBytes / perWorkerMemBytes));
|
|
2310
|
+
return Math.min(raw, AUTO_MAX_WORKERS_CEILING);
|
|
2311
|
+
}
|
|
2312
|
+
function readAvailableMemBytes() {
|
|
2313
|
+
return readMemAvailableBytes();
|
|
2314
|
+
}
|
|
2315
|
+
function isActiveHarnessWorker(worker) {
|
|
2316
|
+
if (typeof worker.completionBlocker === "string" && worker.completionBlocker.trim()) {
|
|
2317
|
+
return false;
|
|
2318
|
+
}
|
|
2319
|
+
const status = computeWorkerStatus(worker);
|
|
2320
|
+
return status.alive && !status.finalResult && status.attention.state !== "done";
|
|
2321
|
+
}
|
|
2322
|
+
function countActiveWorkersForRun(run) {
|
|
2323
|
+
let active = 0;
|
|
2324
|
+
for (const name of listRunWorkerNames(run)) {
|
|
2325
|
+
const worker = readJson(
|
|
2326
|
+
path9.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2327
|
+
void 0
|
|
2328
|
+
);
|
|
2329
|
+
if (!worker || !isActiveHarnessWorker(worker)) continue;
|
|
2330
|
+
active++;
|
|
2331
|
+
}
|
|
2332
|
+
return active;
|
|
2333
|
+
}
|
|
2334
|
+
function countActiveWorkersGlobal() {
|
|
2335
|
+
let active = 0;
|
|
2336
|
+
for (const run of listRunRecords()) active += countActiveWorkersForRun(run);
|
|
2337
|
+
return active;
|
|
2338
|
+
}
|
|
2339
|
+
function observeRunnerResourceGate(input) {
|
|
2340
|
+
const config = input.config ?? loadUserConfig();
|
|
2341
|
+
const totalMemBytes = input.totalMemBytes ?? os2.totalmem();
|
|
2342
|
+
const { perWorkerMemBytes, memReserveBytes, memUtilization, configuredMaxWorkers, autoCap: resolvedAutoCap, workerCapSource } = resolveResourceConfig(config, input.configuredMaxWorkersOverride, totalMemBytes);
|
|
2343
|
+
const boxKind = resolveBoxKindFromConfig(config);
|
|
2344
|
+
const freeMemBytes = input.freeMemBytes ?? readAvailableMemBytes();
|
|
2345
|
+
const activeWorkers = input.activeWorkers ?? countActiveWorkersGlobal();
|
|
2346
|
+
const budgetBytes = Math.max(0, Math.floor(totalMemBytes * memUtilization) - memReserveBytes);
|
|
2347
|
+
const capacityFromTotal = Math.max(0, Math.floor(budgetBytes / perWorkerMemBytes));
|
|
2348
|
+
const capacityFromFree = Math.max(0, Math.floor(Math.max(0, freeMemBytes - memReserveBytes) / perWorkerMemBytes));
|
|
2349
|
+
const autoCap = resolvedAutoCap;
|
|
2350
|
+
const targetCap = configuredMaxWorkers ?? autoCap;
|
|
2351
|
+
const maxConcurrentWorkers = Math.max(0, Math.min(targetCap, capacityFromTotal));
|
|
2352
|
+
const slotsByCapacity = Math.max(0, maxConcurrentWorkers - activeWorkers);
|
|
2353
|
+
const slotsByFreeMem = capacityFromFree;
|
|
2354
|
+
let slotsAvailable = Math.min(slotsByCapacity, slotsByFreeMem);
|
|
2355
|
+
const skipDisk = input.skipDiskGate || process.env.KYNVER_RESOURCE_GATE_SKIP_DISK === "1";
|
|
2356
|
+
const diskGate = skipDisk ? void 0 : observeRunnerDiskGate({
|
|
2357
|
+
diskPath: input.diskPath?.trim() || process.env.KYNVER_DISK_GUARD_PATH?.trim() || "/"
|
|
2358
|
+
});
|
|
2359
|
+
if (diskGate && !diskGate.ok) slotsAvailable = 0;
|
|
2360
|
+
let reason = null;
|
|
2361
|
+
if (slotsAvailable <= 0) {
|
|
2362
|
+
if (diskGate && !diskGate.ok) {
|
|
2363
|
+
reason = diskGate.reason ?? "disk gate blocked worker admission";
|
|
2364
|
+
} else if (activeWorkers >= maxConcurrentWorkers) {
|
|
2365
|
+
reason = `at worker limit (${activeWorkers}/${maxConcurrentWorkers} running)`;
|
|
2366
|
+
} else if (capacityFromFree <= 0) {
|
|
2367
|
+
reason = "insufficient free memory \u2014 waiting for workers to finish";
|
|
2368
|
+
} else {
|
|
2369
|
+
reason = "no worker slots available";
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
return {
|
|
2373
|
+
ok: slotsAvailable > 0,
|
|
2374
|
+
totalMemBytes,
|
|
2375
|
+
freeMemBytes,
|
|
2376
|
+
memReserveBytes,
|
|
2377
|
+
perWorkerMemBytes,
|
|
2378
|
+
configuredMaxWorkers,
|
|
2379
|
+
workerCapSource,
|
|
2380
|
+
boxKind,
|
|
2381
|
+
autoCap,
|
|
2382
|
+
capacityWorkers: capacityFromTotal,
|
|
2383
|
+
maxConcurrentWorkers,
|
|
2384
|
+
activeWorkers,
|
|
2385
|
+
slotsAvailable,
|
|
2386
|
+
reason,
|
|
2387
|
+
...diskGate ? { diskGate } : {}
|
|
2388
|
+
};
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
// src/worker-cap-source.ts
|
|
2392
|
+
function positiveInt2(value, fallback) {
|
|
2393
|
+
const n = Number(value);
|
|
2394
|
+
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
2395
|
+
return Math.floor(n);
|
|
2396
|
+
}
|
|
2397
|
+
function resolveWorkerCap(input) {
|
|
2398
|
+
const config = input.config ?? {};
|
|
2399
|
+
const env = input.env ?? process.env;
|
|
2400
|
+
const perWorkerMemBytes = positiveInt2(config.perWorkerMemBytes, DEFAULT_PER_WORKER_MEM_BYTES);
|
|
2401
|
+
const memReserveBytes = positiveInt2(config.memReserveBytes, DEFAULT_MEM_RESERVE_BYTES);
|
|
2402
|
+
const memUtilization = Math.min(
|
|
2403
|
+
1,
|
|
2404
|
+
Math.max(0.1, Number(config.memUtilization) > 0 ? Number(config.memUtilization) : DEFAULT_MEM_UTILIZATION)
|
|
2405
|
+
);
|
|
2406
|
+
const autoCap = computeAutoMaxWorkers(input.totalMemBytes, {
|
|
2407
|
+
perWorkerMemBytes,
|
|
2408
|
+
memReserveBytes,
|
|
2409
|
+
memUtilization
|
|
2410
|
+
});
|
|
2411
|
+
if (input.configuredMaxWorkersOverride !== void 0 && input.configuredMaxWorkersOverride !== null) {
|
|
2412
|
+
return {
|
|
2413
|
+
configuredMaxWorkers: positiveInt2(input.configuredMaxWorkersOverride, autoCap),
|
|
2414
|
+
autoCap,
|
|
2415
|
+
workerCapSource: "workspace_override"
|
|
2416
|
+
};
|
|
2417
|
+
}
|
|
2418
|
+
if (config.maxConcurrentWorkers !== void 0 && config.maxConcurrentWorkers !== null) {
|
|
2419
|
+
const configured = positiveInt2(config.maxConcurrentWorkers, 0) || null;
|
|
2420
|
+
if (configured) {
|
|
2421
|
+
return {
|
|
2422
|
+
configuredMaxWorkers: Math.min(configured, AUTO_MAX_WORKERS_CEILING),
|
|
2423
|
+
autoCap,
|
|
2424
|
+
workerCapSource: "config"
|
|
2425
|
+
};
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
const envIntentional = env.KYNVER_MAX_WORKERS_INTENTIONAL === "1" || env.KYNVER_MAX_WORKERS_INTENTIONAL === "true";
|
|
2429
|
+
const envCap = envIntentional && env.KYNVER_MAX_WORKERS ? positiveInt2(env.KYNVER_MAX_WORKERS, 0) || null : null;
|
|
2430
|
+
if (envCap) {
|
|
2431
|
+
return {
|
|
2432
|
+
configuredMaxWorkers: Math.min(envCap, AUTO_MAX_WORKERS_CEILING),
|
|
2433
|
+
autoCap,
|
|
2434
|
+
workerCapSource: "env"
|
|
2435
|
+
};
|
|
2436
|
+
}
|
|
2437
|
+
return {
|
|
2438
|
+
configuredMaxWorkers: null,
|
|
2439
|
+
autoCap,
|
|
2440
|
+
workerCapSource: "auto"
|
|
2441
|
+
};
|
|
2442
|
+
}
|
|
2443
|
+
function recommendSetupWorkerCap(input = {}) {
|
|
2444
|
+
const totalMemBytes = input.totalMemBytes ?? 0;
|
|
2445
|
+
const autoCap = computeAutoMaxWorkers(totalMemBytes, {
|
|
2446
|
+
perWorkerMemBytes: positiveInt2(input.config?.perWorkerMemBytes, DEFAULT_PER_WORKER_MEM_BYTES),
|
|
2447
|
+
memReserveBytes: positiveInt2(input.config?.memReserveBytes, DEFAULT_MEM_RESERVE_BYTES),
|
|
2448
|
+
memUtilization: input.config?.memUtilization && Number(input.config.memUtilization) > 0 ? Number(input.config.memUtilization) : DEFAULT_MEM_UTILIZATION
|
|
2449
|
+
});
|
|
2450
|
+
const diskGateOk = input.diskGateOk ?? true;
|
|
2451
|
+
const recommendedMaxWorkers = diskGateOk ? autoCap : Math.max(1, Math.min(autoCap, 4));
|
|
2452
|
+
return {
|
|
2453
|
+
totalMemBytes,
|
|
2454
|
+
autoCap,
|
|
2455
|
+
recommendedMaxWorkers,
|
|
2456
|
+
diskPath: input.diskPath ?? "/",
|
|
2457
|
+
diskGateOk,
|
|
2458
|
+
diskFreeBytes: input.diskFreeBytes ?? null
|
|
2459
|
+
};
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
// src/config.ts
|
|
2463
|
+
import os3 from "node:os";
|
|
2464
|
+
var CONFIG_DIR = path10.join(homedir5(), ".kynver");
|
|
2465
|
+
var CONFIG_FILE = path10.join(CONFIG_DIR, "config.json");
|
|
2466
|
+
var CREDENTIALS_FILE = path10.join(CONFIG_DIR, "credentials");
|
|
2467
|
+
function loadUserConfig() {
|
|
2468
|
+
if (!existsSync11(CONFIG_FILE)) return {};
|
|
2469
|
+
try {
|
|
2470
|
+
return JSON.parse(readFileSync9(CONFIG_FILE, "utf8"));
|
|
2471
|
+
} catch {
|
|
2472
|
+
return {};
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
function saveUserConfig(config) {
|
|
2476
|
+
mkdirSync2(CONFIG_DIR, { recursive: true });
|
|
2477
|
+
writeFileSync2(CONFIG_FILE, `${JSON.stringify(normalizeConfigPaths(config), null, 2)}
|
|
2478
|
+
`, { mode: 384 });
|
|
2479
|
+
}
|
|
2480
|
+
function normalizeConfigPaths(config) {
|
|
2481
|
+
return {
|
|
2482
|
+
...config,
|
|
2483
|
+
...config.harnessRoot?.trim() ? { harnessRoot: redactHomePath(config.harnessRoot.trim()) } : {},
|
|
2484
|
+
...config.defaultRepo?.trim() ? { defaultRepo: redactHomePath(config.defaultRepo.trim()) } : {}
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
function presentUserConfig(config) {
|
|
2488
|
+
return normalizeConfigPaths(config);
|
|
2489
|
+
}
|
|
2490
|
+
function inferSetupFields(existing, args) {
|
|
2491
|
+
const creds = loadCredentialsFile();
|
|
2492
|
+
const apiBaseUrl = (typeof args.apiBaseUrl === "string" ? args.apiBaseUrl : void 0) || existing.apiBaseUrl?.trim() || process.env.KYNVER_API_URL?.trim() || process.env.KYNVER_CRON_FIRE_BASE_URL?.trim() || process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim();
|
|
2493
|
+
const agentOsId = (typeof args.agentOsId === "string" ? args.agentOsId : void 0) || existing.agentOsId?.trim() || process.env.KYNVER_AGENT_OS_ID?.trim() || (creds.runnerToken?.trim().startsWith("krc1.") ? creds.runnerTokenAgentOsId?.trim() : void 0);
|
|
2494
|
+
const explicitRepo = typeof args.repo === "string" ? args.repo : args.discoverRepo === true || args.discoverRepo === "true" ? discoverDefaultRepo()?.repo : void 0;
|
|
2495
|
+
const defaultRepo = explicitRepo || existing.defaultRepo?.trim() || process.env.KYNVER_DEFAULT_REPO?.trim() || process.env.KYNVER_HARNESS_REPO?.trim() || discoverDefaultRepo()?.repo;
|
|
2496
|
+
const harnessRoot = (typeof args.harnessRoot === "string" ? args.harnessRoot : void 0) || existing.harnessRoot?.trim() || process.env.KYNVER_HARNESS_ROOT?.trim() || process.env.OPUS_HARNESS_ROOT?.trim();
|
|
2497
|
+
return {
|
|
2498
|
+
...apiBaseUrl ? { apiBaseUrl: trimTrailingSlash(apiBaseUrl) } : {},
|
|
2499
|
+
...agentOsId ? { agentOsId } : {},
|
|
2500
|
+
...defaultRepo ? { defaultRepo } : {},
|
|
2501
|
+
...harnessRoot ? { harnessRoot } : {},
|
|
2502
|
+
...typeof args.agentOsSlug === "string" ? { agentOsSlug: args.agentOsSlug } : existing.agentOsSlug ? { agentOsSlug: existing.agentOsSlug } : {}
|
|
2503
|
+
};
|
|
2504
|
+
}
|
|
2505
|
+
var SETUP_PER_WORKER_MEM_BYTES = 500 * 1024 * 1024;
|
|
2506
|
+
var SETUP_MEM_RESERVE_BYTES = 4 * 1024 * 1024 * 1024;
|
|
2507
|
+
function resolveSetupWorkerConfig(existing, args, totalMemBytes = totalmem()) {
|
|
2508
|
+
const maxWorkersRaw = typeof args.maxWorkers === "string" ? args.maxWorkers : typeof args.maxConcurrentWorkers === "string" ? args.maxConcurrentWorkers : void 0;
|
|
2509
|
+
const explicitBoxKindArg = typeof args.boxKind === "string" ? args.boxKind : typeof args["box-kind"] === "string" ? String(args["box-kind"]) : void 0;
|
|
2510
|
+
const boxKind = resolveBoxIdentity(process.env, {
|
|
2511
|
+
...existing,
|
|
2512
|
+
...explicitBoxKindArg ? { boxKind: normalizeWorkerPoolBoxKind(explicitBoxKindArg) } : {}
|
|
2513
|
+
}).boxKind;
|
|
2514
|
+
const diskGate = observeRunnerDiskGate({
|
|
2515
|
+
diskPath: typeof args.diskPath === "string" ? args.diskPath : "/"
|
|
2516
|
+
});
|
|
2517
|
+
const capRecommendation = recommendSetupWorkerCap({
|
|
2518
|
+
totalMemBytes,
|
|
2519
|
+
diskPath: diskGate.path,
|
|
2520
|
+
diskGateOk: diskGate.ok,
|
|
2521
|
+
diskFreeBytes: diskGate.freeBytes,
|
|
2522
|
+
config: existing
|
|
2523
|
+
});
|
|
2524
|
+
if (maxWorkersRaw) {
|
|
2525
|
+
return {
|
|
2526
|
+
maxConcurrentWorkers: Math.max(1, Math.floor(Number(maxWorkersRaw))),
|
|
2527
|
+
maxConcurrentWorkersSource: "setup-flag",
|
|
2528
|
+
boxKind
|
|
2529
|
+
};
|
|
2530
|
+
}
|
|
2531
|
+
if (existing.maxConcurrentWorkers !== void 0 && existing.maxConcurrentWorkers !== null) {
|
|
2532
|
+
return {
|
|
2533
|
+
maxConcurrentWorkers: Math.max(1, Math.floor(Number(existing.maxConcurrentWorkers))),
|
|
2534
|
+
maxConcurrentWorkersSource: existing.maxConcurrentWorkersSource ?? "operator",
|
|
2535
|
+
boxKind
|
|
2536
|
+
};
|
|
2537
|
+
}
|
|
2538
|
+
return {
|
|
2539
|
+
maxConcurrentWorkers: capRecommendation.recommendedMaxWorkers,
|
|
2540
|
+
maxConcurrentWorkersSource: "setup-auto",
|
|
2541
|
+
boxKind
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
function loadCredentialsFile() {
|
|
2545
|
+
if (!existsSync11(CREDENTIALS_FILE)) return {};
|
|
2546
|
+
try {
|
|
2547
|
+
return JSON.parse(readFileSync9(CREDENTIALS_FILE, "utf8"));
|
|
2548
|
+
} catch {
|
|
2549
|
+
return {};
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
function saveCredentialsFile(parsed) {
|
|
2553
|
+
mkdirSync2(CONFIG_DIR, { recursive: true });
|
|
2554
|
+
writeFileSync2(CREDENTIALS_FILE, `${JSON.stringify(parsed, null, 2)}
|
|
2555
|
+
`, { mode: 384 });
|
|
2556
|
+
}
|
|
2557
|
+
function loadApiKey() {
|
|
2558
|
+
if (process.env.KYNVER_API_KEY) return process.env.KYNVER_API_KEY;
|
|
2559
|
+
return loadCredentialsFile().apiKey;
|
|
2560
|
+
}
|
|
2561
|
+
function saveApiKey(apiKey) {
|
|
2562
|
+
saveCredentialsFile({ ...loadCredentialsFile(), apiKey });
|
|
2563
|
+
}
|
|
2564
|
+
function loadRunnerToken(agentOsId) {
|
|
2565
|
+
const envToken = process.env.KYNVER_RUNNER_TOKEN?.trim();
|
|
2566
|
+
if (envToken) return envToken;
|
|
2567
|
+
const creds = loadCredentialsFile();
|
|
2568
|
+
if (!creds.runnerToken) return void 0;
|
|
2569
|
+
if (agentOsId && creds.runnerTokenAgentOsId && creds.runnerTokenAgentOsId !== agentOsId) {
|
|
2570
|
+
return void 0;
|
|
2571
|
+
}
|
|
2572
|
+
return creds.runnerToken;
|
|
2573
|
+
}
|
|
2574
|
+
function saveRunnerToken(agentOsId, token) {
|
|
2575
|
+
saveCredentialsFile({
|
|
2576
|
+
...loadCredentialsFile(),
|
|
2577
|
+
runnerToken: token,
|
|
2578
|
+
runnerTokenAgentOsId: agentOsId
|
|
2579
|
+
});
|
|
2580
|
+
}
|
|
2581
|
+
function resolveBaseUrl(argsBaseUrl) {
|
|
2582
|
+
const baseUrl = resolveConfiguredBaseUrl(argsBaseUrl);
|
|
2583
|
+
if (!baseUrl) failConfig("requires --base-url, KYNVER_API_URL, KYNVER_CRON_FIRE_BASE_URL, or ~/.kynver/config.json apiBaseUrl");
|
|
2584
|
+
return baseUrl;
|
|
2585
|
+
}
|
|
2586
|
+
function resolveConfiguredBaseUrl(argsBaseUrl) {
|
|
2587
|
+
const baseUrl = argsBaseUrl || process.env.KYNVER_API_URL || process.env.KYNVER_CRON_FIRE_BASE_URL || process.env.OPENCLAW_CRON_FIRE_BASE_URL || loadUserConfig().apiBaseUrl;
|
|
2588
|
+
return baseUrl ? trimTrailingSlash(String(baseUrl)) : void 0;
|
|
2589
|
+
}
|
|
2590
|
+
function resolveConfiguredCallbackSecret(argsSecret, agentOsId) {
|
|
2591
|
+
const scoped = argsSecret || loadRunnerToken(agentOsId) || (agentOsId ? void 0 : loadRunnerToken(loadUserConfig().agentOsId));
|
|
2592
|
+
if (scoped) return String(scoped);
|
|
2593
|
+
const globalSecret = process.env.KYNVER_RUNTIME_SECRET || process.env.KYNVER_CRON_SECRET || process.env.OPENCLAW_CRON_SECRET;
|
|
2594
|
+
if (globalSecret) {
|
|
2595
|
+
console.warn(
|
|
2596
|
+
"[kynver] using deployment-level callback secret; run `kynver runner credential --agent-os-id <id>` for a scoped token"
|
|
2597
|
+
);
|
|
2598
|
+
return String(globalSecret);
|
|
2599
|
+
}
|
|
2600
|
+
return void 0;
|
|
2601
|
+
}
|
|
2602
|
+
function resolveCallbackSecret(argsSecret, agentOsId) {
|
|
2603
|
+
const configured = resolveConfiguredCallbackSecret(argsSecret, agentOsId);
|
|
2604
|
+
if (configured) return configured;
|
|
2605
|
+
failConfig(
|
|
2606
|
+
"requires --secret, KYNVER_RUNNER_TOKEN, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, KYNVER_API_KEY with an API base URL to mint one, or (legacy) KYNVER_RUNTIME_SECRET / KYNVER_CRON_SECRET / OPENCLAW_CRON_SECRET"
|
|
2607
|
+
);
|
|
2608
|
+
}
|
|
2609
|
+
async function resolveCallbackSecretWithMint(argsSecret, agentOsId, opts) {
|
|
2610
|
+
const configured = resolveConfiguredCallbackSecret(argsSecret, agentOsId);
|
|
2611
|
+
if (configured) return configured;
|
|
2612
|
+
const apiKey = loadApiKey();
|
|
2613
|
+
const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);
|
|
2614
|
+
if (apiKey && agentOsId && baseUrl) {
|
|
2615
|
+
try {
|
|
2616
|
+
const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });
|
|
2617
|
+
saveRunnerToken(agentOsId, token);
|
|
2618
|
+
return token;
|
|
2619
|
+
} catch (error) {
|
|
2620
|
+
failConfig(`runner credential mint failed: ${error.message}`);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
failConfig(
|
|
2624
|
+
"requires --secret, KYNVER_RUNNER_TOKEN, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, KYNVER_API_KEY with an API base URL to mint one, or (legacy) KYNVER_RUNTIME_SECRET / KYNVER_CRON_SECRET / OPENCLAW_CRON_SECRET"
|
|
2625
|
+
);
|
|
2626
|
+
}
|
|
2627
|
+
async function refreshRunnerToken(agentOsId, opts) {
|
|
2628
|
+
const apiKey = loadApiKey();
|
|
2629
|
+
const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);
|
|
2630
|
+
if (!apiKey || !agentOsId || !baseUrl) return null;
|
|
2631
|
+
try {
|
|
2632
|
+
const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });
|
|
2633
|
+
saveRunnerToken(agentOsId, token);
|
|
2634
|
+
return token;
|
|
2635
|
+
} catch {
|
|
2636
|
+
return null;
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
async function refreshRunnerTokenForAuthFailure(rejectedSecret, agentOsId, opts) {
|
|
2640
|
+
const apiKey = loadApiKey();
|
|
2641
|
+
const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);
|
|
2642
|
+
if (!apiKey) return { ok: false, reason: "KYNVER_API_KEY is required to refresh a rejected runner token" };
|
|
2643
|
+
if (!agentOsId) return { ok: false, reason: "agentOsId is required to refresh a rejected runner token" };
|
|
2644
|
+
if (!baseUrl) return { ok: false, reason: "KYNVER_API_URL or --base-url is required to refresh a rejected runner token" };
|
|
2645
|
+
try {
|
|
2646
|
+
const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });
|
|
2647
|
+
if (token && token !== rejectedSecret) {
|
|
2648
|
+
saveRunnerToken(agentOsId, token);
|
|
2649
|
+
return { ok: true, token };
|
|
2650
|
+
}
|
|
2651
|
+
return { ok: false, reason: "runner credential refresh returned the rejected token" };
|
|
2652
|
+
} catch (error) {
|
|
2653
|
+
return { ok: false, reason: error.message };
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
async function fetchRunnerCredential(agentOsId, opts) {
|
|
2657
|
+
const apiKey = opts?.apiKey || loadApiKey();
|
|
2658
|
+
if (!apiKey) throw new Error("API key required \u2014 run `kynver login` first");
|
|
2659
|
+
const base = resolveBaseUrl(opts?.baseUrl);
|
|
2660
|
+
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/runner-credentials`;
|
|
2661
|
+
const res = await fetch(url, {
|
|
2662
|
+
method: "POST",
|
|
2663
|
+
headers: {
|
|
2664
|
+
"Content-Type": "application/json",
|
|
2665
|
+
Authorization: `Bearer ${apiKey}`
|
|
2666
|
+
},
|
|
2667
|
+
body: JSON.stringify({})
|
|
2668
|
+
});
|
|
2669
|
+
const text = await res.text();
|
|
2670
|
+
let parsed = null;
|
|
2671
|
+
try {
|
|
2672
|
+
parsed = JSON.parse(text);
|
|
2673
|
+
} catch {
|
|
2674
|
+
parsed = null;
|
|
2675
|
+
}
|
|
2676
|
+
if (!res.ok || !parsed?.token) {
|
|
2677
|
+
throw new Error(
|
|
2678
|
+
`runner credential mint failed (${res.status}): ${parsed?.error ?? text.slice(0, 200)}`
|
|
2679
|
+
);
|
|
2680
|
+
}
|
|
2681
|
+
return parsed.token;
|
|
2682
|
+
}
|
|
2683
|
+
async function mintRunnerCredential(args) {
|
|
2684
|
+
const agentOsId = (args.agentOsId ? String(args.agentOsId) : loadUserConfig().agentOsId) || "";
|
|
2685
|
+
if (!agentOsId) failConfig("runner credential requires --agent-os-id or agentOsId in ~/.kynver/config.json");
|
|
2686
|
+
try {
|
|
2687
|
+
const token = await fetchRunnerCredential(agentOsId, {
|
|
2688
|
+
baseUrl: args.baseUrl ? String(args.baseUrl) : void 0
|
|
2689
|
+
});
|
|
2690
|
+
saveRunnerToken(agentOsId, token);
|
|
2691
|
+
console.log(
|
|
2692
|
+
JSON.stringify(
|
|
2693
|
+
{
|
|
2694
|
+
ok: true,
|
|
2695
|
+
agentOsId,
|
|
2696
|
+
credentialsPath: displayUserPath(CREDENTIALS_FILE),
|
|
2697
|
+
tokenPrefix: `${token.slice(0, 12)}\u2026`,
|
|
2698
|
+
note: "Scoped runner token saved; callbacks use X-Kynver-Runner-Token."
|
|
2699
|
+
},
|
|
2700
|
+
null,
|
|
2701
|
+
2
|
|
2702
|
+
)
|
|
2703
|
+
);
|
|
2704
|
+
} catch (err) {
|
|
2705
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
2706
|
+
process.exit(1);
|
|
2707
|
+
}
|
|
2708
|
+
}
|
|
2709
|
+
function failConfig(message) {
|
|
2710
|
+
console.error(message);
|
|
2711
|
+
process.exit(1);
|
|
2712
|
+
}
|
|
2713
|
+
function parseArgs(argv) {
|
|
2714
|
+
const args = {};
|
|
2715
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2716
|
+
const item = argv[i];
|
|
2717
|
+
if (!item.startsWith("--")) continue;
|
|
2718
|
+
const key = item.slice(2).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
2719
|
+
const next = argv[i + 1];
|
|
2720
|
+
if (!next || next.startsWith("--")) args[key] = true;
|
|
2721
|
+
else {
|
|
2722
|
+
args[key] = next;
|
|
2723
|
+
i++;
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
return args;
|
|
2727
|
+
}
|
|
2728
|
+
async function runSetup(args) {
|
|
2729
|
+
const existing = loadUserConfig();
|
|
2730
|
+
const diskGate = observeRunnerDiskGate({
|
|
2731
|
+
diskPath: typeof args.diskPath === "string" ? args.diskPath : "/"
|
|
2732
|
+
});
|
|
2733
|
+
const capRecommendation = recommendSetupWorkerCap({
|
|
2734
|
+
totalMemBytes: os3.totalmem(),
|
|
2735
|
+
diskPath: diskGate.path,
|
|
2736
|
+
diskGateOk: diskGate.ok,
|
|
2737
|
+
diskFreeBytes: diskGate.freeBytes,
|
|
2738
|
+
config: existing
|
|
2739
|
+
});
|
|
2740
|
+
const workerConfig = resolveSetupWorkerConfig(existing, args);
|
|
2741
|
+
const config = normalizeConfigPaths({
|
|
2742
|
+
...existing,
|
|
2743
|
+
...inferSetupFields(existing, args),
|
|
2744
|
+
...workerConfig,
|
|
2745
|
+
workerProvider: typeof args.provider === "string" ? args.provider : existing.workerProvider || "cursor"
|
|
2746
|
+
});
|
|
2747
|
+
saveUserConfig(config);
|
|
2748
|
+
const boxIdentity = resolveBoxIdentity(process.env, config);
|
|
2749
|
+
let runnerCredentialNote;
|
|
2750
|
+
const apiKey = loadApiKey();
|
|
2751
|
+
const agentOsId = config.agentOsId;
|
|
2752
|
+
if (apiKey && agentOsId) {
|
|
2753
|
+
try {
|
|
2754
|
+
const token = await fetchRunnerCredential(agentOsId, {
|
|
2755
|
+
baseUrl: typeof args.apiBaseUrl === "string" ? args.apiBaseUrl : config.apiBaseUrl,
|
|
2756
|
+
apiKey
|
|
2757
|
+
});
|
|
2758
|
+
saveRunnerToken(agentOsId, token);
|
|
2759
|
+
runnerCredentialNote = "Scoped runner token minted and saved to ~/.kynver/credentials.";
|
|
2760
|
+
} catch {
|
|
2761
|
+
runnerCredentialNote = "Runner token not minted (server offline or master secret unset). Run `kynver runner credential` after deploy.";
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
console.log(
|
|
2765
|
+
JSON.stringify(
|
|
2766
|
+
{
|
|
2767
|
+
ok: true,
|
|
2768
|
+
configPath: displayUserPath(CONFIG_FILE),
|
|
2769
|
+
config: presentUserConfig(config),
|
|
2770
|
+
boxKind: config.boxKind,
|
|
2771
|
+
boxKindSource: boxIdentity.source,
|
|
2772
|
+
workerCapRecommendation: capRecommendation,
|
|
2773
|
+
...boxIdentity.warnings.length ? { boxIdentityWarnings: boxIdentity.warnings } : {},
|
|
2774
|
+
note: runnerCredentialNote ?? "boxKind and maxConcurrentWorkers persisted; override with --box-kind and --max-workers. Run `kynver login` + `kynver runner credential` for scoped callbacks."
|
|
2775
|
+
},
|
|
2776
|
+
null,
|
|
2777
|
+
2
|
|
2778
|
+
)
|
|
2779
|
+
);
|
|
2425
2780
|
}
|
|
2426
|
-
function
|
|
2427
|
-
|
|
2428
|
-
|
|
2781
|
+
async function runLogin(args) {
|
|
2782
|
+
const apiKey = typeof args.apiKey === "string" ? args.apiKey : process.env.KYNVER_API_KEY;
|
|
2783
|
+
if (!apiKey) failConfig("kynver login requires --api-key or KYNVER_API_KEY");
|
|
2784
|
+
saveApiKey(apiKey);
|
|
2785
|
+
console.log(JSON.stringify({ ok: true, credentialsPath: displayUserPath(CREDENTIALS_FILE) }, null, 2));
|
|
2429
2786
|
}
|
|
2430
2787
|
|
|
2431
|
-
// src/
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
const now = Date.now();
|
|
2436
|
-
if (input.completionBlocker) {
|
|
2437
|
-
return { state: "blocked", reason: input.completionBlocker };
|
|
2438
|
-
}
|
|
2439
|
-
if (input.finalResult) {
|
|
2440
|
-
const landingSnapshot = {
|
|
2441
|
-
finalResult: input.finalResult,
|
|
2442
|
-
changedFiles: input.changedFiles ?? [],
|
|
2443
|
-
gitAncestry: input.gitAncestry ?? null,
|
|
2444
|
-
prUrl: input.prUrl ?? null
|
|
2445
|
-
};
|
|
2446
|
-
const landing = assessWorkerLanding(landingSnapshot);
|
|
2447
|
-
if (landing.blocked) {
|
|
2448
|
-
const detail = landingAttentionReason(landing);
|
|
2449
|
-
return {
|
|
2450
|
-
state: "needs_attention",
|
|
2451
|
-
reason: landing.reason ? `landing blocked (${landing.reason}): ${detail}` : `landing blocked: ${detail}`
|
|
2452
|
-
};
|
|
2453
|
-
}
|
|
2454
|
-
if (input.landingContract) {
|
|
2455
|
-
const contractVerdict = assessWorkerLandingContract({
|
|
2456
|
-
contract: input.landingContract,
|
|
2457
|
-
snapshot: landingSnapshot,
|
|
2458
|
-
finalResult: input.finalResult
|
|
2459
|
-
});
|
|
2460
|
-
const contractDetail = landingContractAttentionReason(contractVerdict);
|
|
2461
|
-
if (contractDetail) {
|
|
2462
|
-
return {
|
|
2463
|
-
state: "needs_attention",
|
|
2464
|
-
reason: contractVerdict.reason ? `landing contract (${contractVerdict.reason}): ${contractDetail}` : `landing contract: ${contractDetail}`
|
|
2465
|
-
};
|
|
2466
|
-
}
|
|
2467
|
-
}
|
|
2468
|
-
return { state: "done", reason: "final result recorded" };
|
|
2469
|
-
}
|
|
2470
|
-
if (!input.alive) {
|
|
2471
|
-
const classified = classifyExitFailure(input.error);
|
|
2472
|
-
if (classified) return { state: "blocked", reason: classified.reason };
|
|
2473
|
-
const salvage = assessExitedWorkerSalvage({
|
|
2474
|
-
alive: false,
|
|
2475
|
-
finalResult: null,
|
|
2476
|
-
changedFiles: input.changedFiles,
|
|
2477
|
-
gitAncestry: input.gitAncestry
|
|
2478
|
-
});
|
|
2479
|
-
if (salvage?.salvageable) {
|
|
2480
|
-
const tail2 = input.error?.trim();
|
|
2481
|
-
return {
|
|
2482
|
-
state: "needs_attention",
|
|
2483
|
-
reason: tail2 ? `${salvage.attentionReason} (${tail2})` : salvage.attentionReason
|
|
2484
|
-
};
|
|
2485
|
-
}
|
|
2486
|
-
const tail = input.error?.trim();
|
|
2788
|
+
// src/callback-headers.ts
|
|
2789
|
+
function buildHarnessCallbackHeaders(secret) {
|
|
2790
|
+
const trimmed = String(secret).trim();
|
|
2791
|
+
if (trimmed.startsWith("krc1.")) {
|
|
2487
2792
|
return {
|
|
2488
|
-
|
|
2489
|
-
|
|
2793
|
+
"Content-Type": "application/json",
|
|
2794
|
+
"X-Kynver-Runner-Token": trimmed
|
|
2490
2795
|
};
|
|
2491
2796
|
}
|
|
2492
|
-
if (input.heartbeatBlocker) {
|
|
2493
|
-
return { state: "blocked", reason: `worker heartbeat reported blocker: ${input.heartbeatBlocker}` };
|
|
2494
|
-
}
|
|
2495
|
-
const startMs = input.startedAt ? Date.parse(input.startedAt) : NaN;
|
|
2496
|
-
if (!input.firstEventAt && input.stdoutBytes === 0 && input.heartbeatBytes === 0 && Number.isFinite(startMs) && now - startMs > NO_START_MS) {
|
|
2497
|
-
return { state: "needs_attention", reason: `no first stream event ${secsAgo(startMs)}s after start` };
|
|
2498
|
-
}
|
|
2499
|
-
const actMs = input.lastActivityAt ? Date.parse(input.lastActivityAt) : NaN;
|
|
2500
|
-
if (Number.isFinite(actMs) && now - actMs > STALE_MS) {
|
|
2501
|
-
return { state: "stale", reason: `no log/event/heartbeat activity for ${secsAgo(actMs)}s` };
|
|
2502
|
-
}
|
|
2503
|
-
return { state: "ok", reason: "recent activity" };
|
|
2504
|
-
}
|
|
2505
|
-
function resolveFinalResult(worker, parsedFinalResult, heartbeat) {
|
|
2506
|
-
if (parsedFinalResult) return parsedFinalResult;
|
|
2507
|
-
const ackSnapshot = worker.completionSnapshot?.finalResult;
|
|
2508
|
-
if (ackSnapshot !== void 0 && ackSnapshot !== null) return ackSnapshot;
|
|
2509
|
-
return terminalFinalResultFromHeartbeat(heartbeat);
|
|
2510
|
-
}
|
|
2511
|
-
function computeWorkerStatus(worker, options = {}) {
|
|
2512
|
-
const parsed = parseHarnessStream(worker.stdoutPath);
|
|
2513
|
-
const heartbeat = parseHeartbeat(worker.heartbeatPath);
|
|
2514
|
-
const completionAcknowledged = typeof worker.completionReportedAt === "string" && worker.completionReportedAt.trim().length > 0;
|
|
2515
|
-
const finalResult = resolveFinalResult(worker, parsed.finalResult, heartbeat);
|
|
2516
|
-
const alive = completionAcknowledged ? false : isPidAlive(worker.pid);
|
|
2517
|
-
const stdoutBytes = fileSize(worker.stdoutPath);
|
|
2518
|
-
const stderrBytes = fileSize(worker.stderrPath);
|
|
2519
|
-
const heartbeatBytes = fileSize(worker.heartbeatPath);
|
|
2520
|
-
const changedFiles = gitStatusShort(worker.worktreePath);
|
|
2521
|
-
const gitAncestry = computeGitAncestry(worker.worktreePath, {
|
|
2522
|
-
base: options.base,
|
|
2523
|
-
baseCommit: options.baseCommit
|
|
2524
|
-
});
|
|
2525
|
-
const lastActivityAt = latestIso([
|
|
2526
|
-
parsed.lastEventAt,
|
|
2527
|
-
heartbeat.lastHeartbeatAt,
|
|
2528
|
-
fileMtime(worker.stdoutPath),
|
|
2529
|
-
fileMtime(worker.stderrPath),
|
|
2530
|
-
fileMtime(worker.heartbeatPath)
|
|
2531
|
-
]);
|
|
2532
|
-
const error = parsed.error || (!alive && !finalResult ? tailFile(worker.stderrPath, 10).trim() || void 0 : void 0);
|
|
2533
|
-
const completionBlocker = typeof worker.completionBlocker === "string" && worker.completionBlocker.trim() ? worker.completionBlocker.trim() : null;
|
|
2534
|
-
const landingContract = worker.repairTargetPrUrl ? {
|
|
2535
|
-
landingOnly: false,
|
|
2536
|
-
targetPrUrls: [worker.repairTargetPrUrl],
|
|
2537
|
-
targetPrUrl: worker.repairTargetPrUrl,
|
|
2538
|
-
repairEnforceOriginalPr: true
|
|
2539
|
-
} : null;
|
|
2540
|
-
const attention = computeAttention({
|
|
2541
|
-
alive,
|
|
2542
|
-
finalResult,
|
|
2543
|
-
firstEventAt: parsed.firstEventAt,
|
|
2544
|
-
stdoutBytes,
|
|
2545
|
-
heartbeatBytes,
|
|
2546
|
-
lastActivityAt,
|
|
2547
|
-
heartbeatBlocker: heartbeat.heartbeatBlocker,
|
|
2548
|
-
startedAt: worker.startedAt,
|
|
2549
|
-
error,
|
|
2550
|
-
changedFiles,
|
|
2551
|
-
gitAncestry,
|
|
2552
|
-
completionBlocker,
|
|
2553
|
-
landingContract,
|
|
2554
|
-
prUrl: worker.repairTargetPrUrl ?? worker.taskPrUrl ?? null
|
|
2555
|
-
});
|
|
2556
|
-
const workerStatusLabel = completionBlocker || attention.state === "blocked" ? "blocked" : completionAcknowledged || attention.state === "done" ? "done" : finalResult ? "exited" : alive ? "running" : "exited";
|
|
2557
2797
|
return {
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
ownedPaths: worker.ownedPaths,
|
|
2567
|
-
stdoutBytes,
|
|
2568
|
-
stderrBytes,
|
|
2569
|
-
heartbeatBytes,
|
|
2570
|
-
firstEventAt: parsed.firstEventAt,
|
|
2571
|
-
lastEventAt: parsed.lastEventAt,
|
|
2572
|
-
lastActivityAt,
|
|
2573
|
-
currentTool: completionAcknowledged ? null : parsed.currentTool,
|
|
2574
|
-
heartbeatCount: heartbeat.heartbeatCount,
|
|
2575
|
-
lastHeartbeatAt: heartbeat.lastHeartbeatAt,
|
|
2576
|
-
lastHeartbeatPhase: heartbeat.lastHeartbeatPhase,
|
|
2577
|
-
lastHeartbeatSummary: heartbeat.lastHeartbeatSummary,
|
|
2578
|
-
heartbeatBlocker: heartbeat.heartbeatBlocker,
|
|
2579
|
-
timestampAnomalies: heartbeat.timestampAnomalies,
|
|
2580
|
-
finalResult,
|
|
2581
|
-
error,
|
|
2582
|
-
changedFiles,
|
|
2583
|
-
gitAncestry,
|
|
2584
|
-
instructionPolicyFingerprint: worker.instructionPolicyFingerprint ?? null,
|
|
2585
|
-
instructionPolicyEvidence: worker.instructionPolicyEvidence ?? null
|
|
2798
|
+
"Content-Type": "application/json",
|
|
2799
|
+
// Canonical header. We keep sending the legacy `X-OpenClaw-Cron-Secret`
|
|
2800
|
+
// (and `X-Kynver-Runtime-Secret`) so an un-upgraded Kynver server that
|
|
2801
|
+
// only reads the old header still authenticates this runner during the
|
|
2802
|
+
// rename compat window.
|
|
2803
|
+
"X-Kynver-Cron-Secret": trimmed,
|
|
2804
|
+
"X-OpenClaw-Cron-Secret": trimmed,
|
|
2805
|
+
"X-Kynver-Runtime-Secret": trimmed
|
|
2586
2806
|
};
|
|
2587
2807
|
}
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
return
|
|
2808
|
+
|
|
2809
|
+
// src/callbacks.ts
|
|
2810
|
+
function callbackTimeoutMs() {
|
|
2811
|
+
const parsed = Number(process.env.KYNVER_CALLBACK_TIMEOUT_MS);
|
|
2812
|
+
if (Number.isFinite(parsed) && parsed > 0) return Math.floor(parsed);
|
|
2813
|
+
return 3e4;
|
|
2593
2814
|
}
|
|
2594
|
-
function
|
|
2595
|
-
|
|
2596
|
-
|
|
2815
|
+
async function withTimeout(fn) {
|
|
2816
|
+
const controller = new AbortController();
|
|
2817
|
+
const timeout = setTimeout(() => controller.abort(), callbackTimeoutMs());
|
|
2818
|
+
try {
|
|
2819
|
+
return await fn(controller.signal);
|
|
2820
|
+
} finally {
|
|
2821
|
+
clearTimeout(timeout);
|
|
2822
|
+
}
|
|
2597
2823
|
}
|
|
2598
|
-
function
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2824
|
+
async function postJson(url, secret, body) {
|
|
2825
|
+
const res = await withTimeout(
|
|
2826
|
+
(signal) => fetch(url, {
|
|
2827
|
+
method: "POST",
|
|
2828
|
+
headers: buildHarnessCallbackHeaders(secret),
|
|
2829
|
+
body: JSON.stringify(body),
|
|
2830
|
+
signal
|
|
2831
|
+
})
|
|
2832
|
+
);
|
|
2833
|
+
let response = null;
|
|
2834
|
+
try {
|
|
2835
|
+
response = await res.json();
|
|
2836
|
+
} catch {
|
|
2837
|
+
response = null;
|
|
2602
2838
|
}
|
|
2603
|
-
|
|
2604
|
-
if (workers.some((w) => w.status === "running")) return "running";
|
|
2605
|
-
return fallback;
|
|
2839
|
+
return { ok: res.ok, status: res.status, response };
|
|
2606
2840
|
}
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
const n = Number(value);
|
|
2615
|
-
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
2616
|
-
return Math.floor(n);
|
|
2841
|
+
async function postJsonWithCredentialRefresh(url, secret, body, opts) {
|
|
2842
|
+
const first = await postJson(url, secret, body);
|
|
2843
|
+
if (first.ok || first.status !== 401) return first;
|
|
2844
|
+
const refreshed = await refreshRunnerTokenForAuthFailure(secret, opts.agentOsId, { baseUrl: opts.baseUrl });
|
|
2845
|
+
if (!refreshed.ok) return { ...first, authRefreshFailure: refreshed.reason };
|
|
2846
|
+
const retry = await postJson(url, refreshed.token, body);
|
|
2847
|
+
return { ...retry, refreshedAuth: true };
|
|
2617
2848
|
}
|
|
2618
|
-
function
|
|
2619
|
-
const
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2849
|
+
async function getJson(url, secret) {
|
|
2850
|
+
const res = await withTimeout(
|
|
2851
|
+
(signal) => fetch(url, {
|
|
2852
|
+
method: "GET",
|
|
2853
|
+
headers: buildHarnessCallbackHeaders(secret),
|
|
2854
|
+
signal
|
|
2855
|
+
})
|
|
2624
2856
|
);
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
}
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
const memUtilization = opts.memUtilization ?? DEFAULT_MEM_UTILIZATION;
|
|
2633
|
-
const budgetBytes = Math.max(0, Math.floor(totalMemBytes * memUtilization) - memReserveBytes);
|
|
2634
|
-
const raw = Math.max(1, Math.floor(budgetBytes / perWorkerMemBytes));
|
|
2635
|
-
return Math.min(raw, AUTO_MAX_WORKERS_CEILING);
|
|
2857
|
+
let response = null;
|
|
2858
|
+
try {
|
|
2859
|
+
response = await res.json();
|
|
2860
|
+
} catch {
|
|
2861
|
+
response = null;
|
|
2862
|
+
}
|
|
2863
|
+
return { ok: res.ok, status: res.status, response };
|
|
2636
2864
|
}
|
|
2637
|
-
|
|
2638
|
-
|
|
2865
|
+
|
|
2866
|
+
// src/dispatch-lane-normalization.ts
|
|
2867
|
+
function trimLower(value) {
|
|
2868
|
+
return (value ?? "").trim().toLowerCase();
|
|
2639
2869
|
}
|
|
2640
|
-
function
|
|
2641
|
-
|
|
2642
|
-
|
|
2870
|
+
function roleLaneToDispatchLane(roleLane) {
|
|
2871
|
+
switch (trimLower(roleLane)) {
|
|
2872
|
+
case "implementer":
|
|
2873
|
+
case "repair_implementer":
|
|
2874
|
+
case "plan_author":
|
|
2875
|
+
case "runtime_verifier":
|
|
2876
|
+
return "implementation";
|
|
2877
|
+
case "plan_reviewer":
|
|
2878
|
+
case "report_reviewer":
|
|
2879
|
+
case "deep_reviewer":
|
|
2880
|
+
return "review";
|
|
2881
|
+
default:
|
|
2882
|
+
return null;
|
|
2643
2883
|
}
|
|
2644
|
-
const status = computeWorkerStatus(worker);
|
|
2645
|
-
return status.alive && !status.finalResult && status.attention.state !== "done";
|
|
2646
2884
|
}
|
|
2647
|
-
function
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
void 0
|
|
2653
|
-
);
|
|
2654
|
-
if (!worker || !isActiveHarnessWorker(worker)) continue;
|
|
2655
|
-
active++;
|
|
2885
|
+
function normalizeDispatchNextLaneFilter(raw) {
|
|
2886
|
+
const key = trimLower(raw);
|
|
2887
|
+
if (!key) return void 0;
|
|
2888
|
+
if (key === "implementation" || key === "review" || key === "landing" || key === "any") {
|
|
2889
|
+
return key;
|
|
2656
2890
|
}
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
return active;
|
|
2891
|
+
const mapped = roleLaneToDispatchLane(key);
|
|
2892
|
+
if (mapped) return mapped;
|
|
2893
|
+
if (key === "implement" || key === "repair" || key === "coding") return "implementation";
|
|
2894
|
+
if (key === "land" || key === "merge") return "landing";
|
|
2895
|
+
return void 0;
|
|
2663
2896
|
}
|
|
2664
|
-
function
|
|
2665
|
-
|
|
2666
|
-
input.config,
|
|
2667
|
-
input.configuredMaxWorkersOverride
|
|
2668
|
-
);
|
|
2669
|
-
const totalMemBytes = input.totalMemBytes ?? os2.totalmem();
|
|
2670
|
-
const freeMemBytes = input.freeMemBytes ?? readAvailableMemBytes();
|
|
2671
|
-
const activeWorkers = input.activeWorkers ?? countActiveWorkersGlobal();
|
|
2672
|
-
const budgetBytes = Math.max(0, Math.floor(totalMemBytes * memUtilization) - memReserveBytes);
|
|
2673
|
-
const capacityFromTotal = Math.max(0, Math.floor(budgetBytes / perWorkerMemBytes));
|
|
2674
|
-
const capacityFromFree = Math.max(0, Math.floor(Math.max(0, freeMemBytes - memReserveBytes) / perWorkerMemBytes));
|
|
2675
|
-
const autoCap = computeAutoMaxWorkers(totalMemBytes, { perWorkerMemBytes, memReserveBytes, memUtilization });
|
|
2676
|
-
const targetCap = configuredMaxWorkers ?? autoCap;
|
|
2677
|
-
const maxConcurrentWorkers = Math.max(0, Math.min(targetCap, capacityFromTotal));
|
|
2678
|
-
const slotsByCapacity = Math.max(0, maxConcurrentWorkers - activeWorkers);
|
|
2679
|
-
const slotsByFreeMem = capacityFromFree;
|
|
2680
|
-
let slotsAvailable = Math.min(slotsByCapacity, slotsByFreeMem);
|
|
2681
|
-
const skipDisk = input.skipDiskGate || process.env.KYNVER_RESOURCE_GATE_SKIP_DISK === "1";
|
|
2682
|
-
const diskGate = skipDisk ? void 0 : observeRunnerDiskGate({
|
|
2683
|
-
diskPath: input.diskPath?.trim() || process.env.KYNVER_DISK_GUARD_PATH?.trim() || "/"
|
|
2684
|
-
});
|
|
2685
|
-
if (diskGate && !diskGate.ok) slotsAvailable = 0;
|
|
2686
|
-
let reason = null;
|
|
2687
|
-
if (slotsAvailable <= 0) {
|
|
2688
|
-
if (diskGate && !diskGate.ok) {
|
|
2689
|
-
reason = diskGate.reason ?? "disk gate blocked worker admission";
|
|
2690
|
-
} else if (activeWorkers >= maxConcurrentWorkers) {
|
|
2691
|
-
reason = `at worker limit (${activeWorkers}/${maxConcurrentWorkers} running)`;
|
|
2692
|
-
} else if (capacityFromFree <= 0) {
|
|
2693
|
-
reason = "insufficient free memory \u2014 waiting for workers to finish";
|
|
2694
|
-
} else {
|
|
2695
|
-
reason = "no worker slots available";
|
|
2696
|
-
}
|
|
2697
|
-
}
|
|
2698
|
-
return {
|
|
2699
|
-
ok: slotsAvailable > 0,
|
|
2700
|
-
totalMemBytes,
|
|
2701
|
-
freeMemBytes,
|
|
2702
|
-
memReserveBytes,
|
|
2703
|
-
perWorkerMemBytes,
|
|
2704
|
-
configuredMaxWorkers,
|
|
2705
|
-
autoCap,
|
|
2706
|
-
capacityWorkers: capacityFromTotal,
|
|
2707
|
-
maxConcurrentWorkers,
|
|
2708
|
-
activeWorkers,
|
|
2709
|
-
slotsAvailable,
|
|
2710
|
-
reason,
|
|
2711
|
-
...diskGate ? { diskGate } : {}
|
|
2712
|
-
};
|
|
2897
|
+
function resolveDispatchNextLaneFilter(raw) {
|
|
2898
|
+
return normalizeDispatchNextLaneFilter(raw) ?? "any";
|
|
2713
2899
|
}
|
|
2714
2900
|
|
|
2715
2901
|
// src/worker-persona-catalog.ts
|
|
@@ -4005,15 +4191,15 @@ function resolveModelFallback(startedModel, launchModel, providerDefault) {
|
|
|
4005
4191
|
}
|
|
4006
4192
|
|
|
4007
4193
|
// src/retry-limits.ts
|
|
4008
|
-
function
|
|
4194
|
+
function positiveInt3(value, fallback) {
|
|
4009
4195
|
const n = Number(value);
|
|
4010
4196
|
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
4011
4197
|
return Math.floor(n);
|
|
4012
4198
|
}
|
|
4013
4199
|
function readHarnessRetryLimits() {
|
|
4014
4200
|
return {
|
|
4015
|
-
maxTaskAttempts:
|
|
4016
|
-
dispatchCooldownMs:
|
|
4201
|
+
maxTaskAttempts: positiveInt3(process.env.KYNVER_MAX_TASK_ATTEMPTS, 4),
|
|
4202
|
+
dispatchCooldownMs: positiveInt3(process.env.KYNVER_DISPATCH_COOLDOWN_MS, 5e3)
|
|
4017
4203
|
};
|
|
4018
4204
|
}
|
|
4019
4205
|
|
|
@@ -4054,22 +4240,15 @@ function resolveHarnessLeaseOwnerForRenewal(input) {
|
|
|
4054
4240
|
}
|
|
4055
4241
|
|
|
4056
4242
|
// src/runner-identity.ts
|
|
4057
|
-
import
|
|
4243
|
+
import os5 from "node:os";
|
|
4058
4244
|
|
|
4059
4245
|
// src/box-resource-snapshot-shared.ts
|
|
4060
|
-
import
|
|
4061
|
-
function normalizeWorkerPoolBoxKind(raw) {
|
|
4062
|
-
const kind = (raw ?? "").trim().toLowerCase();
|
|
4063
|
-
if (kind === "ghost" || kind === "forge") return kind;
|
|
4064
|
-
if (kind.includes("forge")) return "forge";
|
|
4065
|
-
if (kind.includes("ghost") || kind.includes("openclaw")) return "ghost";
|
|
4066
|
-
return "forge";
|
|
4067
|
-
}
|
|
4246
|
+
import os4 from "node:os";
|
|
4068
4247
|
function resolveBoxKindFromEnv(env = process.env) {
|
|
4069
|
-
return
|
|
4248
|
+
return resolveBoxIdentity(env).boxKind;
|
|
4070
4249
|
}
|
|
4071
4250
|
function defaultBoxId(boxKind, hostLabel) {
|
|
4072
|
-
const host = (hostLabel ??
|
|
4251
|
+
const host = (hostLabel ?? os4.hostname()).trim().toLowerCase().replace(/\s+/g, "-") || "unknown-host";
|
|
4073
4252
|
return `${boxKind}:${host}`;
|
|
4074
4253
|
}
|
|
4075
4254
|
|
|
@@ -4080,10 +4259,10 @@ function trimOrNull5(value) {
|
|
|
4080
4259
|
}
|
|
4081
4260
|
function resolveRunnerPresencePayload(input = {}) {
|
|
4082
4261
|
const env = input.env ?? process.env;
|
|
4083
|
-
const runnerId = trimOrNull5(env.KYNVER_RUNTIME_ID) ?? trimOrNull5(env.OPENCLAW_RUNTIME_ID) ?? trimOrNull5(env.HOSTNAME) ??
|
|
4262
|
+
const runnerId = trimOrNull5(env.KYNVER_RUNTIME_ID) ?? trimOrNull5(env.OPENCLAW_RUNTIME_ID) ?? trimOrNull5(env.HOSTNAME) ?? os5.hostname();
|
|
4084
4263
|
return {
|
|
4085
4264
|
runnerId,
|
|
4086
|
-
hostname: trimOrNull5(env.HOSTNAME) ??
|
|
4265
|
+
hostname: trimOrNull5(env.HOSTNAME) ?? os5.hostname(),
|
|
4087
4266
|
profile: trimOrNull5(env.KYNVER_RUNNER_PROFILE) ?? trimOrNull5(env.OPENCLAW_RUNNER_PROFILE),
|
|
4088
4267
|
harnessRepo: trimOrNull5(env.KYNVER_HARNESS_REPO) ?? trimOrNull5(env.KYNVER_DEFAULT_REPO),
|
|
4089
4268
|
runId: input.runId ?? null
|
|
@@ -4259,13 +4438,13 @@ function buildPrompt(input) {
|
|
|
4259
4438
|
input.planId ? `Active planId: ${input.planId}${input.taskId ? ` \xB7 taskId: ${input.taskId}` : ""}` : "No planId on this worker \u2014 still emit progress when you touch plan-scoped work."
|
|
4260
4439
|
];
|
|
4261
4440
|
const mergeGateLines = compact ? [
|
|
4262
|
-
"Merge-gate cost control:
|
|
4441
|
+
"Merge-gate cost control: do not use Vercel previews/builds for PR verification. Run `node scripts/agent-os-pr-merge-gate.mjs --pr <url> --agent-os-id <id>` (or `verify-pr-local.mjs --from-pr` + POST pr-merge-gate/refresh) before any GitHub Actions run; request merge-gate only via refresh then POST pr-merge-gate/request-run (one Actions run per PR head unless human approves extra)."
|
|
4263
4442
|
] : [
|
|
4264
4443
|
"GitHub Actions merge-gate cost control (Kynver/Hermes PRs):",
|
|
4265
|
-
"- Prefer local cached package verification (`node scripts/verify-pr-local.mjs --emit-json`)
|
|
4444
|
+
"- Prefer local cached package verification (`node scripts/verify-pr-local.mjs --emit-json`) before GitHub Actions. Do not use Vercel previews/builds as PR evidence.",
|
|
4266
4445
|
"- Do not push empty commits to re-trigger CI. One budgeted merge-gate Actions run per PR candidate (head SHA) unless a human approves extra.",
|
|
4267
|
-
"- Record evidence: POST `/api/agent-os/by-id/<agentOsId>/pr-merge-gate/refresh` with prUrl + local
|
|
4268
|
-
"- Request the final Actions run only when local
|
|
4446
|
+
"- Record evidence: POST `/api/agent-os/by-id/<agentOsId>/pr-merge-gate/refresh` with prUrl + local payloads.",
|
|
4447
|
+
"- Request the final Actions run only when local verification is green: POST `.../pr-merge-gate/request-run` (applies `merge-gate` label).",
|
|
4269
4448
|
"- Empty failed Actions jobs (no runner/steps/logs) are infra/quota \u2014 do not enter repair loops; escalate to operator."
|
|
4270
4449
|
];
|
|
4271
4450
|
const planArtifactLines = compact ? [
|
|
@@ -4319,7 +4498,7 @@ function buildPrompt(input) {
|
|
|
4319
4498
|
// src/providers/cursor.ts
|
|
4320
4499
|
import { closeSync as closeSync4, existsSync as existsSync19, mkdirSync as mkdirSync3, openSync as openSync4, statSync as statSync4, unlinkSync } from "node:fs";
|
|
4321
4500
|
import { spawn as spawn4 } from "node:child_process";
|
|
4322
|
-
import
|
|
4501
|
+
import os6 from "node:os";
|
|
4323
4502
|
import path18 from "node:path";
|
|
4324
4503
|
|
|
4325
4504
|
// src/providers/cursor-windows.ts
|
|
@@ -4430,7 +4609,7 @@ function positiveIntEnv(name, fallback) {
|
|
|
4430
4609
|
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : fallback;
|
|
4431
4610
|
}
|
|
4432
4611
|
function cursorStartLockPath() {
|
|
4433
|
-
const root = process.env.KYNVER_CURSOR_START_LOCK_DIR?.trim() || path18.join(
|
|
4612
|
+
const root = process.env.KYNVER_CURSOR_START_LOCK_DIR?.trim() || path18.join(os6.homedir(), ".kynver", "locks");
|
|
4434
4613
|
mkdirSync3(root, { recursive: true });
|
|
4435
4614
|
return path18.join(root, "cursor-agent-start.lock");
|
|
4436
4615
|
}
|
|
@@ -5165,8 +5344,8 @@ function dirtyPathsCoveredByDisposableRemoval(changedFiles, removed) {
|
|
|
5165
5344
|
if (removed.length === 0) return false;
|
|
5166
5345
|
const removedSet = new Set(removed.map((p) => normalizeRelativePath(p)));
|
|
5167
5346
|
return material.every((line) => {
|
|
5168
|
-
const
|
|
5169
|
-
return removedSet.has(
|
|
5347
|
+
const path69 = normalizeRelativePath(pathFromGitStatusLine(line));
|
|
5348
|
+
return removedSet.has(path69);
|
|
5170
5349
|
});
|
|
5171
5350
|
}
|
|
5172
5351
|
|
|
@@ -7505,10 +7684,10 @@ async function dispatchRun(args) {
|
|
|
7505
7684
|
}
|
|
7506
7685
|
|
|
7507
7686
|
// src/box-resource-snapshot.ts
|
|
7508
|
-
import
|
|
7687
|
+
import os7 from "node:os";
|
|
7509
7688
|
function buildBoxResourceSnapshotFromGate(gate, input = {}) {
|
|
7510
|
-
const boxKind = (input.boxKind ??
|
|
7511
|
-
const hostLabel = input.hostLabel ??
|
|
7689
|
+
const boxKind = (input.boxKind ?? resolveBoxKindFromConfig(loadUserConfig())).trim().toLowerCase() || "forge";
|
|
7690
|
+
const hostLabel = input.hostLabel ?? os7.hostname();
|
|
7512
7691
|
const boxId = input.boxId ?? defaultBoxId(boxKind, hostLabel);
|
|
7513
7692
|
return {
|
|
7514
7693
|
boxId,
|
|
@@ -9169,7 +9348,7 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
9169
9348
|
}
|
|
9170
9349
|
|
|
9171
9350
|
// src/cleanup.ts
|
|
9172
|
-
import
|
|
9351
|
+
import path52 from "node:path";
|
|
9173
9352
|
|
|
9174
9353
|
// src/cleanup-guards.ts
|
|
9175
9354
|
import path41 from "node:path";
|
|
@@ -9340,11 +9519,11 @@ var LIVE_SKIP_REASONS = /* @__PURE__ */ new Set([
|
|
|
9340
9519
|
function collectPreservedLivePaths(actions, skips) {
|
|
9341
9520
|
const out = [];
|
|
9342
9521
|
const seen = /* @__PURE__ */ new Set();
|
|
9343
|
-
const push = (
|
|
9344
|
-
const key = `${
|
|
9522
|
+
const push = (path69, reason, detail) => {
|
|
9523
|
+
const key = `${path69}\0${reason}`;
|
|
9345
9524
|
if (seen.has(key) || out.length >= MAX_PRESERVED_LIVE_PATH_SAMPLES) return;
|
|
9346
9525
|
seen.add(key);
|
|
9347
|
-
out.push({ path:
|
|
9526
|
+
out.push({ path: path69, reason, ...detail ? { detail } : {} });
|
|
9348
9527
|
};
|
|
9349
9528
|
for (const skip2 of skips) {
|
|
9350
9529
|
if (!LIVE_SKIP_REASONS.has(skip2.reason)) continue;
|
|
@@ -9421,60 +9600,263 @@ function pathAgeMs(target, now) {
|
|
|
9421
9600
|
return 0;
|
|
9422
9601
|
}
|
|
9423
9602
|
}
|
|
9424
|
-
function loadRunStatus(harnessRoot, runId) {
|
|
9425
|
-
const runPath = path43.join(harnessRoot, "runs", runId, "run.json");
|
|
9426
|
-
if (!existsSync33(runPath)) return null;
|
|
9427
|
-
return readJson(runPath, null);
|
|
9603
|
+
function loadRunStatus(harnessRoot, runId) {
|
|
9604
|
+
const runPath = path43.join(harnessRoot, "runs", runId, "run.json");
|
|
9605
|
+
if (!existsSync33(runPath)) return null;
|
|
9606
|
+
return readJson(runPath, null);
|
|
9607
|
+
}
|
|
9608
|
+
function runDirectoryIsEmpty(runPath) {
|
|
9609
|
+
try {
|
|
9610
|
+
const entries = readdirSync11(runPath);
|
|
9611
|
+
return entries.length === 0;
|
|
9612
|
+
} catch {
|
|
9613
|
+
return false;
|
|
9614
|
+
}
|
|
9615
|
+
}
|
|
9616
|
+
function skipRunDirectoryRemoval(input) {
|
|
9617
|
+
const { harnessRoot, runId, runPath, ageMs, runDirectoriesAgeMs, activeGuards } = input;
|
|
9618
|
+
if (isWorktreeOnLiveRun(runPath, harnessRoot, runId, activeGuards.liveRunKeys)) {
|
|
9619
|
+
return "run_still_active";
|
|
9620
|
+
}
|
|
9621
|
+
if (!runDirectoryIsEmpty(runPath)) return "run_still_active";
|
|
9622
|
+
const run = loadRunStatus(harnessRoot, runId);
|
|
9623
|
+
if (run && !TERMINAL_RUN_STATUSES.has(run.status)) return "run_still_active";
|
|
9624
|
+
if (runDirectoriesAgeMs > 0 && ageMs < runDirectoriesAgeMs) return "below_age_threshold";
|
|
9625
|
+
return null;
|
|
9626
|
+
}
|
|
9627
|
+
function scanStaleRunDirectoryCandidates(opts) {
|
|
9628
|
+
if (!existsSync33(opts.worktreesDir)) return [];
|
|
9629
|
+
const candidates = [];
|
|
9630
|
+
let entries;
|
|
9631
|
+
try {
|
|
9632
|
+
entries = readdirSync11(opts.worktreesDir, { withFileTypes: true });
|
|
9633
|
+
} catch {
|
|
9634
|
+
return [];
|
|
9635
|
+
}
|
|
9636
|
+
for (const runEntry of entries) {
|
|
9637
|
+
if (!runEntry.isDirectory()) continue;
|
|
9638
|
+
const runId = runEntry.name;
|
|
9639
|
+
if (opts.runIdFilter && runId !== opts.runIdFilter) continue;
|
|
9640
|
+
const runPath = path43.join(opts.worktreesDir, runId);
|
|
9641
|
+
if (!runDirectoryIsEmpty(runPath)) continue;
|
|
9642
|
+
candidates.push({
|
|
9643
|
+
kind: "remove_run_directory",
|
|
9644
|
+
path: runPath,
|
|
9645
|
+
bytes: null,
|
|
9646
|
+
harnessRoot: opts.harnessRoot,
|
|
9647
|
+
runId,
|
|
9648
|
+
ageMs: pathAgeMs(runPath, opts.now)
|
|
9649
|
+
});
|
|
9650
|
+
}
|
|
9651
|
+
return candidates;
|
|
9652
|
+
}
|
|
9653
|
+
|
|
9654
|
+
// src/cleanup-execute.ts
|
|
9655
|
+
import { existsSync as existsSync35, rmSync as rmSync3 } from "node:fs";
|
|
9656
|
+
|
|
9657
|
+
// src/cleanup-remove-path.ts
|
|
9658
|
+
import { existsSync as existsSync34, rmSync as rmSync2 } from "node:fs";
|
|
9659
|
+
|
|
9660
|
+
// src/cleanup-path-ownership.ts
|
|
9661
|
+
import { lstatSync as lstatSync2, readdirSync as readdirSync12 } from "node:fs";
|
|
9662
|
+
function readPathOwnership(targetPath) {
|
|
9663
|
+
try {
|
|
9664
|
+
const st = lstatSync2(targetPath);
|
|
9665
|
+
const effectiveUid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
9666
|
+
const effectiveGid = typeof process.getgid === "function" ? process.getgid() : null;
|
|
9667
|
+
const foreign = effectiveUid !== null && (st.uid !== effectiveUid || effectiveGid !== null && st.gid !== effectiveGid);
|
|
9668
|
+
return { uid: st.uid, gid: st.gid, foreign };
|
|
9669
|
+
} catch {
|
|
9670
|
+
return null;
|
|
9671
|
+
}
|
|
9672
|
+
}
|
|
9673
|
+
function pathHasForeignOwnedEntry(targetPath, maxEntries = 32) {
|
|
9674
|
+
const root = readPathOwnership(targetPath);
|
|
9675
|
+
if (!root) return false;
|
|
9676
|
+
if (root.foreign) return true;
|
|
9677
|
+
try {
|
|
9678
|
+
const names = readdirSync12(targetPath);
|
|
9679
|
+
let checked = 0;
|
|
9680
|
+
for (const name of names) {
|
|
9681
|
+
if (checked >= maxEntries) break;
|
|
9682
|
+
const child = `${targetPath.replace(/\/$/, "")}/${name}`;
|
|
9683
|
+
const info = readPathOwnership(child);
|
|
9684
|
+
checked += 1;
|
|
9685
|
+
if (info?.foreign) return true;
|
|
9686
|
+
}
|
|
9687
|
+
} catch {
|
|
9688
|
+
}
|
|
9689
|
+
return false;
|
|
9690
|
+
}
|
|
9691
|
+
|
|
9692
|
+
// src/cleanup-privileged-remove.ts
|
|
9693
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
9694
|
+
import path45 from "node:path";
|
|
9695
|
+
|
|
9696
|
+
// src/cleanup-harness-path-validate.ts
|
|
9697
|
+
import path44 from "node:path";
|
|
9698
|
+
function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
|
|
9699
|
+
const resolved = path44.resolve(targetPath);
|
|
9700
|
+
const suffix = `${path44.sep}${cacheDirName}`;
|
|
9701
|
+
const cachePath = resolved.endsWith(suffix) ? resolved : null;
|
|
9702
|
+
if (!cachePath) return "path_outside_harness";
|
|
9703
|
+
const rel = path44.relative(worktreesDir, cachePath);
|
|
9704
|
+
if (rel.startsWith("..") || path44.isAbsolute(rel)) return "path_outside_harness";
|
|
9705
|
+
const parts = rel.split(path44.sep);
|
|
9706
|
+
if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
|
|
9707
|
+
if (!resolved.startsWith(path44.resolve(harnessRoot))) return "path_outside_harness";
|
|
9708
|
+
return null;
|
|
9709
|
+
}
|
|
9710
|
+
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
9711
|
+
return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, "node_modules");
|
|
9712
|
+
}
|
|
9713
|
+
function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
9714
|
+
return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
|
|
9715
|
+
}
|
|
9716
|
+
function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
9717
|
+
const resolved = path44.resolve(targetPath);
|
|
9718
|
+
const relToWt = path44.relative(worktreesDir, resolved);
|
|
9719
|
+
if (relToWt.startsWith("..") || path44.isAbsolute(relToWt)) return "path_outside_harness";
|
|
9720
|
+
const parts = relToWt.split(path44.sep);
|
|
9721
|
+
if (parts.length < 3) return "path_outside_harness";
|
|
9722
|
+
if (!resolved.startsWith(path44.resolve(harnessRoot))) return "path_outside_harness";
|
|
9723
|
+
return null;
|
|
9724
|
+
}
|
|
9725
|
+
function isHarnessGeneratedCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
9726
|
+
const resolved = path44.resolve(targetPath);
|
|
9727
|
+
return isHarnessNodeModulesPath(resolved, harnessRoot, worktreesDir) === null || isHarnessNextCachePath(resolved, harnessRoot, worktreesDir) === null || isHarnessBuildCachePath(resolved, harnessRoot, worktreesDir) === null;
|
|
9728
|
+
}
|
|
9729
|
+
|
|
9730
|
+
// src/cleanup-privileged-remove.ts
|
|
9731
|
+
function resolvePrivilegedCleanupMode() {
|
|
9732
|
+
const raw = (process.env.KYNVER_CLEANUP_PRIVILEGED ?? "auto").trim().toLowerCase();
|
|
9733
|
+
if (raw === "0" || raw === "false" || raw === "off" || raw === "no") return "off";
|
|
9734
|
+
if (raw === "1" || raw === "true" || raw === "force" || raw === "yes") return "force";
|
|
9735
|
+
return "auto";
|
|
9736
|
+
}
|
|
9737
|
+
function runSudoNonInteractive(argv) {
|
|
9738
|
+
const res = spawnSync5("sudo", ["-n", ...argv], {
|
|
9739
|
+
encoding: "utf8",
|
|
9740
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
9741
|
+
});
|
|
9742
|
+
return {
|
|
9743
|
+
ok: res.status === 0,
|
|
9744
|
+
stderr: `${res.stderr ?? ""}${res.stdout ?? ""}`.trim()
|
|
9745
|
+
};
|
|
9746
|
+
}
|
|
9747
|
+
function tryPrivilegedReclaimHarnessCache(targetPath, harnessRoot, worktreesDir) {
|
|
9748
|
+
if (!isHarnessGeneratedCachePath(targetPath, harnessRoot, worktreesDir)) {
|
|
9749
|
+
return { ok: false, error: "path is not an allowed harness generated cache" };
|
|
9750
|
+
}
|
|
9751
|
+
const effectiveUid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
9752
|
+
const effectiveGid = typeof process.getgid === "function" ? process.getgid() : null;
|
|
9753
|
+
if (effectiveUid === null || effectiveGid === null) {
|
|
9754
|
+
return { ok: false, error: "privileged reclaim requires POSIX uid/gid" };
|
|
9755
|
+
}
|
|
9756
|
+
const chown = runSudoNonInteractive([
|
|
9757
|
+
"chown",
|
|
9758
|
+
"-R",
|
|
9759
|
+
`${effectiveUid}:${effectiveGid}`,
|
|
9760
|
+
path45.resolve(targetPath)
|
|
9761
|
+
]);
|
|
9762
|
+
if (chown.ok) {
|
|
9763
|
+
return { ok: true, method: "chown_then_rm" };
|
|
9764
|
+
}
|
|
9765
|
+
const rm = runSudoNonInteractive(["rm", "-rf", path45.resolve(targetPath)]);
|
|
9766
|
+
if (rm.ok) {
|
|
9767
|
+
return { ok: true, method: "sudo_rm" };
|
|
9768
|
+
}
|
|
9769
|
+
const detail = chown.stderr || rm.stderr || "sudo -n failed (password required or not permitted)";
|
|
9770
|
+
return {
|
|
9771
|
+
ok: false,
|
|
9772
|
+
error: detail
|
|
9773
|
+
};
|
|
9774
|
+
}
|
|
9775
|
+
var HARNESS_ROOT_OWNED_CACHE_RUNBOOK = "Root-owned harness caches require operator reclaim: configure passwordless sudo for chown/rm under ~/.kynver/harness/worktrees, or run `node scripts/reclaim-harness-root-owned-cache.mjs --execute` as the harness owner with sudo available. See docs/runbooks/harness-root-owned-cache-reclaim.md.";
|
|
9776
|
+
|
|
9777
|
+
// src/cleanup-remove-path.ts
|
|
9778
|
+
function permissionFailure(error) {
|
|
9779
|
+
const code = error?.code;
|
|
9780
|
+
return code === "EACCES" || code === "EPERM";
|
|
9428
9781
|
}
|
|
9429
|
-
function
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
return entries.length === 0;
|
|
9433
|
-
} catch {
|
|
9434
|
-
return false;
|
|
9782
|
+
function removeHarnessGeneratedPath(candidate, execute, deps = {}) {
|
|
9783
|
+
if (!existsSync34(candidate.path)) {
|
|
9784
|
+
return { executed: false, skipped: true, skipReason: "missing_worktree" };
|
|
9435
9785
|
}
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
const { harnessRoot, runId, runPath, ageMs, runDirectoriesAgeMs, activeGuards } = input;
|
|
9439
|
-
if (isWorktreeOnLiveRun(runPath, harnessRoot, runId, activeGuards.liveRunKeys)) {
|
|
9440
|
-
return "run_still_active";
|
|
9786
|
+
if (!execute) {
|
|
9787
|
+
return { executed: false, skipped: true, skipReason: "dry_run" };
|
|
9441
9788
|
}
|
|
9442
|
-
|
|
9443
|
-
const
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
function scanStaleRunDirectoryCandidates(opts) {
|
|
9449
|
-
if (!existsSync33(opts.worktreesDir)) return [];
|
|
9450
|
-
const candidates = [];
|
|
9451
|
-
let entries;
|
|
9789
|
+
const harnessRoot = candidate.harnessRoot;
|
|
9790
|
+
const worktreesDir = harnessRoot ? harnessWorktreesDir(harnessRoot) : null;
|
|
9791
|
+
const bytesBefore = candidate.bytes ?? directorySizeBytes(candidate.path);
|
|
9792
|
+
const bytesReported = bytesBefore ?? void 0;
|
|
9793
|
+
const removePath = deps.removePath ?? rmSync2;
|
|
9794
|
+
const hasForeignOwnedEntry = deps.hasForeignOwnedEntry ?? pathHasForeignOwnedEntry;
|
|
9452
9795
|
try {
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
|
|
9469
|
-
|
|
9470
|
-
|
|
9796
|
+
removePath(candidate.path, { recursive: true, force: true });
|
|
9797
|
+
return { executed: true, skipped: false, bytes: bytesReported };
|
|
9798
|
+
} catch (error) {
|
|
9799
|
+
if (!permissionFailure(error) || !harnessRoot || !worktreesDir) {
|
|
9800
|
+
return {
|
|
9801
|
+
executed: false,
|
|
9802
|
+
skipped: true,
|
|
9803
|
+
skipReason: "remove_failed",
|
|
9804
|
+
error: error.message
|
|
9805
|
+
};
|
|
9806
|
+
}
|
|
9807
|
+
const foreign = hasForeignOwnedEntry(candidate.path);
|
|
9808
|
+
const mode = resolvePrivilegedCleanupMode();
|
|
9809
|
+
const shouldTryPrivileged = mode === "force" || mode === "auto" && foreign;
|
|
9810
|
+
if (!shouldTryPrivileged) {
|
|
9811
|
+
return foreign ? {
|
|
9812
|
+
executed: false,
|
|
9813
|
+
skipped: true,
|
|
9814
|
+
skipReason: "foreign_owner",
|
|
9815
|
+
error: `${error.message}; ${HARNESS_ROOT_OWNED_CACHE_RUNBOOK}`
|
|
9816
|
+
} : {
|
|
9817
|
+
executed: false,
|
|
9818
|
+
skipped: true,
|
|
9819
|
+
skipReason: "remove_failed",
|
|
9820
|
+
error: error.message
|
|
9821
|
+
};
|
|
9822
|
+
}
|
|
9823
|
+
const reclaim = tryPrivilegedReclaimHarnessCache(candidate.path, harnessRoot, worktreesDir);
|
|
9824
|
+
if (reclaim.ok && reclaim.method === "sudo_rm") {
|
|
9825
|
+
return {
|
|
9826
|
+
executed: true,
|
|
9827
|
+
skipped: false,
|
|
9828
|
+
bytes: bytesReported,
|
|
9829
|
+
privilegedReclaim: true
|
|
9830
|
+
};
|
|
9831
|
+
}
|
|
9832
|
+
if (reclaim.ok) {
|
|
9833
|
+
try {
|
|
9834
|
+
removePath(candidate.path, { recursive: true, force: true });
|
|
9835
|
+
return {
|
|
9836
|
+
executed: true,
|
|
9837
|
+
skipped: false,
|
|
9838
|
+
bytes: bytesReported,
|
|
9839
|
+
privilegedReclaim: true
|
|
9840
|
+
};
|
|
9841
|
+
} catch (retryError) {
|
|
9842
|
+
return {
|
|
9843
|
+
executed: false,
|
|
9844
|
+
skipped: true,
|
|
9845
|
+
skipReason: "foreign_owner",
|
|
9846
|
+
error: `${retryError.message}; privileged chown succeeded but rm still failed`
|
|
9847
|
+
};
|
|
9848
|
+
}
|
|
9849
|
+
}
|
|
9850
|
+
return {
|
|
9851
|
+
executed: false,
|
|
9852
|
+
skipped: true,
|
|
9853
|
+
skipReason: "foreign_owner",
|
|
9854
|
+
error: `${error.message}; privileged reclaim failed: ${reclaim.error}; ${HARNESS_ROOT_OWNED_CACHE_RUNBOOK}`
|
|
9855
|
+
};
|
|
9471
9856
|
}
|
|
9472
|
-
return candidates;
|
|
9473
9857
|
}
|
|
9474
9858
|
|
|
9475
9859
|
// src/cleanup-execute.ts
|
|
9476
|
-
import { existsSync as existsSync34, rmSync as rmSync2 } from "node:fs";
|
|
9477
|
-
import path44 from "node:path";
|
|
9478
9860
|
function skipRunMetadataRemoval(candidate) {
|
|
9479
9861
|
const harnessRoot = candidate.harnessRoot;
|
|
9480
9862
|
if (!harnessRoot || !isHarnessRunMetadataPath(candidate.path, harnessRoot)) return null;
|
|
@@ -9488,35 +9870,15 @@ function skipRunMetadataRemoval(candidate) {
|
|
|
9488
9870
|
function removeDependencyCache(candidate, execute) {
|
|
9489
9871
|
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
9490
9872
|
if (metadataSkip) return metadataSkip;
|
|
9491
|
-
|
|
9492
|
-
|
|
9493
|
-
|
|
9494
|
-
|
|
9495
|
-
|
|
9496
|
-
|
|
9497
|
-
|
|
9498
|
-
|
|
9499
|
-
|
|
9500
|
-
return { ...candidate, executed: false, skipped: true, skipReason: "dry_run" };
|
|
9501
|
-
}
|
|
9502
|
-
try {
|
|
9503
|
-
const bytesBefore = candidate.bytes ?? directorySizeBytes(candidate.path);
|
|
9504
|
-
rmSync2(candidate.path, { recursive: true, force: true });
|
|
9505
|
-
return {
|
|
9506
|
-
...candidate,
|
|
9507
|
-
bytes: bytesBefore,
|
|
9508
|
-
executed: true,
|
|
9509
|
-
skipped: false
|
|
9510
|
-
};
|
|
9511
|
-
} catch (error) {
|
|
9512
|
-
return {
|
|
9513
|
-
...candidate,
|
|
9514
|
-
executed: false,
|
|
9515
|
-
skipped: true,
|
|
9516
|
-
skipReason: "remove_failed",
|
|
9517
|
-
error: error.message
|
|
9518
|
-
};
|
|
9519
|
-
}
|
|
9873
|
+
const outcome = removeHarnessGeneratedPath(candidate, execute);
|
|
9874
|
+
return {
|
|
9875
|
+
...candidate,
|
|
9876
|
+
bytes: outcome.bytes ?? candidate.bytes,
|
|
9877
|
+
executed: outcome.executed,
|
|
9878
|
+
skipped: outcome.skipped,
|
|
9879
|
+
skipReason: outcome.skipReason,
|
|
9880
|
+
error: outcome.error
|
|
9881
|
+
};
|
|
9520
9882
|
}
|
|
9521
9883
|
function removeNodeModules(candidate, execute) {
|
|
9522
9884
|
return removeDependencyCache(candidate, execute);
|
|
@@ -9530,7 +9892,7 @@ function removeBuildCache(candidate, execute) {
|
|
|
9530
9892
|
function removeRunDirectory(candidate, execute) {
|
|
9531
9893
|
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
9532
9894
|
if (metadataSkip) return metadataSkip;
|
|
9533
|
-
if (!
|
|
9895
|
+
if (!existsSync35(candidate.path)) {
|
|
9534
9896
|
return {
|
|
9535
9897
|
...candidate,
|
|
9536
9898
|
executed: false,
|
|
@@ -9543,7 +9905,7 @@ function removeRunDirectory(candidate, execute) {
|
|
|
9543
9905
|
}
|
|
9544
9906
|
try {
|
|
9545
9907
|
const bytesBefore = candidate.bytes ?? directorySizeBytes(candidate.path);
|
|
9546
|
-
|
|
9908
|
+
rmSync3(candidate.path, { recursive: true, force: true });
|
|
9547
9909
|
return {
|
|
9548
9910
|
...candidate,
|
|
9549
9911
|
bytes: bytesBefore,
|
|
@@ -9563,7 +9925,7 @@ function removeRunDirectory(candidate, execute) {
|
|
|
9563
9925
|
function removeWorktree(candidate, execute) {
|
|
9564
9926
|
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
9565
9927
|
if (metadataSkip) return metadataSkip;
|
|
9566
|
-
if (!
|
|
9928
|
+
if (!existsSync35(candidate.path)) {
|
|
9567
9929
|
return {
|
|
9568
9930
|
...candidate,
|
|
9569
9931
|
executed: false,
|
|
@@ -9580,8 +9942,8 @@ function removeWorktree(candidate, execute) {
|
|
|
9580
9942
|
if (repo) {
|
|
9581
9943
|
git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
|
|
9582
9944
|
}
|
|
9583
|
-
if (
|
|
9584
|
-
|
|
9945
|
+
if (existsSync35(candidate.path)) {
|
|
9946
|
+
rmSync3(candidate.path, { recursive: true, force: true });
|
|
9585
9947
|
}
|
|
9586
9948
|
return {
|
|
9587
9949
|
...candidate,
|
|
@@ -9599,37 +9961,10 @@ function removeWorktree(candidate, execute) {
|
|
|
9599
9961
|
};
|
|
9600
9962
|
}
|
|
9601
9963
|
}
|
|
9602
|
-
function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
|
|
9603
|
-
const resolved = path44.resolve(targetPath);
|
|
9604
|
-
const suffix = `${path44.sep}${cacheDirName}`;
|
|
9605
|
-
const cachePath = resolved.endsWith(suffix) ? resolved : null;
|
|
9606
|
-
if (!cachePath) return "path_outside_harness";
|
|
9607
|
-
const rel = path44.relative(worktreesDir, cachePath);
|
|
9608
|
-
if (rel.startsWith("..") || path44.isAbsolute(rel)) return "path_outside_harness";
|
|
9609
|
-
const parts = rel.split(path44.sep);
|
|
9610
|
-
if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
|
|
9611
|
-
if (!resolved.startsWith(path44.resolve(harnessRoot))) return "path_outside_harness";
|
|
9612
|
-
return null;
|
|
9613
|
-
}
|
|
9614
|
-
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
9615
|
-
return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, "node_modules");
|
|
9616
|
-
}
|
|
9617
|
-
function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
9618
|
-
return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
|
|
9619
|
-
}
|
|
9620
|
-
function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
9621
|
-
const resolved = path44.resolve(targetPath);
|
|
9622
|
-
const relToWt = path44.relative(worktreesDir, resolved);
|
|
9623
|
-
if (relToWt.startsWith("..") || path44.isAbsolute(relToWt)) return "path_outside_harness";
|
|
9624
|
-
const parts = relToWt.split(path44.sep);
|
|
9625
|
-
if (parts.length < 3) return "path_outside_harness";
|
|
9626
|
-
if (!resolved.startsWith(path44.resolve(harnessRoot))) return "path_outside_harness";
|
|
9627
|
-
return null;
|
|
9628
|
-
}
|
|
9629
9964
|
|
|
9630
9965
|
// src/cleanup-scan.ts
|
|
9631
|
-
import { existsSync as
|
|
9632
|
-
import
|
|
9966
|
+
import { existsSync as existsSync36, readdirSync as readdirSync13, statSync as statSync10 } from "node:fs";
|
|
9967
|
+
import path46 from "node:path";
|
|
9633
9968
|
function pathAgeMs2(target, now) {
|
|
9634
9969
|
try {
|
|
9635
9970
|
const mtime = statSync10(target).mtimeMs;
|
|
@@ -9639,16 +9974,16 @@ function pathAgeMs2(target, now) {
|
|
|
9639
9974
|
}
|
|
9640
9975
|
}
|
|
9641
9976
|
function isPathInside(child, parent) {
|
|
9642
|
-
const rel =
|
|
9643
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
9977
|
+
const rel = path46.relative(parent, child);
|
|
9978
|
+
return rel === "" || !rel.startsWith("..") && !path46.isAbsolute(rel);
|
|
9644
9979
|
}
|
|
9645
9980
|
function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
|
|
9646
9981
|
const out = [];
|
|
9647
9982
|
for (const rel of HARNESS_BUILD_CACHE_RELATIVE_PATHS) {
|
|
9648
9983
|
if (rel === ".next") continue;
|
|
9649
|
-
const target =
|
|
9650
|
-
if (!
|
|
9651
|
-
const resolved =
|
|
9984
|
+
const target = path46.join(worktreePath, rel);
|
|
9985
|
+
if (!existsSync36(target)) continue;
|
|
9986
|
+
const resolved = path46.resolve(target);
|
|
9652
9987
|
if (seen.has(resolved)) continue;
|
|
9653
9988
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
9654
9989
|
seen.add(resolved);
|
|
@@ -9677,13 +10012,13 @@ function scanBuildCacheCandidates(opts) {
|
|
|
9677
10012
|
})
|
|
9678
10013
|
);
|
|
9679
10014
|
}
|
|
9680
|
-
if (!opts.includeOrphans || !
|
|
9681
|
-
for (const runEntry of
|
|
10015
|
+
if (!opts.includeOrphans || !existsSync36(opts.worktreesDir)) return candidates;
|
|
10016
|
+
for (const runEntry of readdirSync13(opts.worktreesDir, { withFileTypes: true })) {
|
|
9682
10017
|
if (!runEntry.isDirectory()) continue;
|
|
9683
|
-
const runPath =
|
|
9684
|
-
for (const workerEntry of
|
|
10018
|
+
const runPath = path46.join(opts.worktreesDir, runEntry.name);
|
|
10019
|
+
for (const workerEntry of readdirSync13(runPath, { withFileTypes: true })) {
|
|
9685
10020
|
if (!workerEntry.isDirectory()) continue;
|
|
9686
|
-
const worktreePath =
|
|
10021
|
+
const worktreePath = path46.join(runPath, workerEntry.name);
|
|
9687
10022
|
candidates.push(
|
|
9688
10023
|
...collectBuildCacheForWorktree(worktreePath, opts, seen, {
|
|
9689
10024
|
runId: runEntry.name,
|
|
@@ -9704,7 +10039,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
9704
10039
|
for (const entry of opts.index.values()) {
|
|
9705
10040
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
9706
10041
|
const resolved = entry.worktreePath;
|
|
9707
|
-
if (!
|
|
10042
|
+
if (!existsSync36(resolved)) continue;
|
|
9708
10043
|
if (seen.has(resolved)) continue;
|
|
9709
10044
|
seen.add(resolved);
|
|
9710
10045
|
candidates.push({
|
|
@@ -9718,24 +10053,24 @@ function scanWorktreeCandidates(opts) {
|
|
|
9718
10053
|
});
|
|
9719
10054
|
}
|
|
9720
10055
|
}
|
|
9721
|
-
if (!orphanEnabled || !
|
|
10056
|
+
if (!orphanEnabled || !existsSync36(opts.worktreesDir)) return candidates;
|
|
9722
10057
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
9723
10058
|
for (const entry of opts.index.values()) {
|
|
9724
|
-
indexedPaths.add(
|
|
10059
|
+
indexedPaths.add(path46.resolve(entry.worktreePath));
|
|
9725
10060
|
}
|
|
9726
|
-
for (const runEntry of
|
|
10061
|
+
for (const runEntry of readdirSync13(opts.worktreesDir, { withFileTypes: true })) {
|
|
9727
10062
|
if (!runEntry.isDirectory()) continue;
|
|
9728
10063
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
9729
|
-
const runPath =
|
|
10064
|
+
const runPath = path46.join(opts.worktreesDir, runEntry.name);
|
|
9730
10065
|
let workerEntries;
|
|
9731
10066
|
try {
|
|
9732
|
-
workerEntries =
|
|
10067
|
+
workerEntries = readdirSync13(runPath, { withFileTypes: true });
|
|
9733
10068
|
} catch {
|
|
9734
10069
|
continue;
|
|
9735
10070
|
}
|
|
9736
10071
|
for (const workerEntry of workerEntries) {
|
|
9737
10072
|
if (!workerEntry.isDirectory()) continue;
|
|
9738
|
-
const worktreePath =
|
|
10073
|
+
const worktreePath = path46.resolve(path46.join(runPath, workerEntry.name));
|
|
9739
10074
|
if (seen.has(worktreePath)) continue;
|
|
9740
10075
|
if (indexedPaths.has(worktreePath)) continue;
|
|
9741
10076
|
if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
|
|
@@ -9754,8 +10089,8 @@ function scanWorktreeCandidates(opts) {
|
|
|
9754
10089
|
}
|
|
9755
10090
|
|
|
9756
10091
|
// src/cleanup-dependency-scan.ts
|
|
9757
|
-
import { existsSync as
|
|
9758
|
-
import
|
|
10092
|
+
import { existsSync as existsSync37, readdirSync as readdirSync14, statSync as statSync11 } from "node:fs";
|
|
10093
|
+
import path47 from "node:path";
|
|
9759
10094
|
var DEPENDENCY_CACHE_DIRS = [
|
|
9760
10095
|
{ dirName: "node_modules", kind: "remove_node_modules" },
|
|
9761
10096
|
{ dirName: ".next", kind: "remove_next_cache" }
|
|
@@ -9769,12 +10104,12 @@ function pathAgeMs3(target, now) {
|
|
|
9769
10104
|
}
|
|
9770
10105
|
}
|
|
9771
10106
|
function isPathInside2(child, parent) {
|
|
9772
|
-
const rel =
|
|
9773
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
10107
|
+
const rel = path47.relative(parent, child);
|
|
10108
|
+
return rel === "" || !rel.startsWith("..") && !path47.isAbsolute(rel);
|
|
9774
10109
|
}
|
|
9775
10110
|
function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
9776
|
-
if (!
|
|
9777
|
-
const resolved =
|
|
10111
|
+
if (!existsSync37(targetPath)) return;
|
|
10112
|
+
const resolved = path47.resolve(targetPath);
|
|
9778
10113
|
if (seen.has(resolved)) return;
|
|
9779
10114
|
if (!isPathInside2(resolved, opts.harnessRoot)) return;
|
|
9780
10115
|
seen.add(resolved);
|
|
@@ -9791,7 +10126,7 @@ function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
|
9791
10126
|
}
|
|
9792
10127
|
function scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, meta) {
|
|
9793
10128
|
for (const entry of DEPENDENCY_CACHE_DIRS) {
|
|
9794
|
-
pushCandidate2(candidates, seen, opts,
|
|
10129
|
+
pushCandidate2(candidates, seen, opts, path47.join(worktreePath, entry.dirName), entry.kind, meta);
|
|
9795
10130
|
}
|
|
9796
10131
|
}
|
|
9797
10132
|
function scanDependencyCacheCandidates(opts) {
|
|
@@ -9805,20 +10140,20 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
9805
10140
|
repo: entry.run.repo
|
|
9806
10141
|
});
|
|
9807
10142
|
}
|
|
9808
|
-
if (!opts.includeOrphans || !
|
|
9809
|
-
for (const runEntry of
|
|
10143
|
+
if (!opts.includeOrphans || !existsSync37(opts.worktreesDir)) return candidates;
|
|
10144
|
+
for (const runEntry of readdirSync14(opts.worktreesDir, { withFileTypes: true })) {
|
|
9810
10145
|
if (!runEntry.isDirectory()) continue;
|
|
9811
10146
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
9812
|
-
const runPath =
|
|
10147
|
+
const runPath = path47.join(opts.worktreesDir, runEntry.name);
|
|
9813
10148
|
let workerEntries;
|
|
9814
10149
|
try {
|
|
9815
|
-
workerEntries =
|
|
10150
|
+
workerEntries = readdirSync14(runPath, { withFileTypes: true });
|
|
9816
10151
|
} catch {
|
|
9817
10152
|
continue;
|
|
9818
10153
|
}
|
|
9819
10154
|
for (const workerEntry of workerEntries) {
|
|
9820
10155
|
if (!workerEntry.isDirectory()) continue;
|
|
9821
|
-
const worktreePath =
|
|
10156
|
+
const worktreePath = path47.join(runPath, workerEntry.name);
|
|
9822
10157
|
scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, {
|
|
9823
10158
|
runId: runEntry.name,
|
|
9824
10159
|
worker: workerEntry.name
|
|
@@ -9829,8 +10164,8 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
9829
10164
|
}
|
|
9830
10165
|
|
|
9831
10166
|
// src/cleanup-duplicate-worktrees.ts
|
|
9832
|
-
import { existsSync as
|
|
9833
|
-
import
|
|
10167
|
+
import { existsSync as existsSync38, statSync as statSync12 } from "node:fs";
|
|
10168
|
+
import path48 from "node:path";
|
|
9834
10169
|
function pathAgeMs4(target, now) {
|
|
9835
10170
|
try {
|
|
9836
10171
|
const mtime = statSync12(target).mtimeMs;
|
|
@@ -9860,8 +10195,8 @@ function parseWorktreePorcelain(output) {
|
|
|
9860
10195
|
return records;
|
|
9861
10196
|
}
|
|
9862
10197
|
function isUnderWorktreesDir(worktreePath, worktreesDir) {
|
|
9863
|
-
const rel =
|
|
9864
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
10198
|
+
const rel = path48.relative(path48.resolve(worktreesDir), path48.resolve(worktreePath));
|
|
10199
|
+
return rel !== "" && !rel.startsWith("..") && !path48.isAbsolute(rel);
|
|
9865
10200
|
}
|
|
9866
10201
|
function isCleanWorktree(worktreePath, repoRoot) {
|
|
9867
10202
|
try {
|
|
@@ -9874,14 +10209,14 @@ function isCleanWorktree(worktreePath, repoRoot) {
|
|
|
9874
10209
|
}
|
|
9875
10210
|
}
|
|
9876
10211
|
function scanDuplicateWorktreeCandidates(opts) {
|
|
9877
|
-
if (!opts.includeOrphans || !
|
|
10212
|
+
if (!opts.includeOrphans || !existsSync38(opts.worktreesDir)) return [];
|
|
9878
10213
|
const repos = /* @__PURE__ */ new Set();
|
|
9879
10214
|
for (const entry of opts.index.values()) {
|
|
9880
|
-
if (entry.run.repo) repos.add(
|
|
10215
|
+
if (entry.run.repo) repos.add(path48.resolve(entry.run.repo));
|
|
9881
10216
|
}
|
|
9882
10217
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
9883
10218
|
for (const entry of opts.index.values()) {
|
|
9884
|
-
indexedPaths.add(
|
|
10219
|
+
indexedPaths.add(path48.resolve(entry.worktreePath));
|
|
9885
10220
|
}
|
|
9886
10221
|
const candidates = [];
|
|
9887
10222
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -9894,15 +10229,15 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
9894
10229
|
}
|
|
9895
10230
|
const worktrees = parseWorktreePorcelain(porcelain);
|
|
9896
10231
|
for (const wt of worktrees) {
|
|
9897
|
-
const resolved =
|
|
9898
|
-
if (resolved ===
|
|
10232
|
+
const resolved = path48.resolve(wt.path);
|
|
10233
|
+
if (resolved === path48.resolve(repoRoot)) continue;
|
|
9899
10234
|
if (!isUnderWorktreesDir(resolved, opts.worktreesDir)) continue;
|
|
9900
10235
|
if (indexedPaths.has(resolved)) continue;
|
|
9901
10236
|
if (seen.has(resolved)) continue;
|
|
9902
|
-
if (!
|
|
10237
|
+
if (!existsSync38(resolved)) continue;
|
|
9903
10238
|
if (!isCleanWorktree(resolved, repoRoot)) continue;
|
|
9904
|
-
const rel =
|
|
9905
|
-
const parts = rel.split(
|
|
10239
|
+
const rel = path48.relative(opts.worktreesDir, resolved);
|
|
10240
|
+
const parts = rel.split(path48.sep);
|
|
9906
10241
|
const runId = parts[0];
|
|
9907
10242
|
const worker = parts[1] ?? "unknown";
|
|
9908
10243
|
seen.add(resolved);
|
|
@@ -9921,12 +10256,12 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
9921
10256
|
}
|
|
9922
10257
|
|
|
9923
10258
|
// src/cleanup-worktree-index.ts
|
|
9924
|
-
import
|
|
10259
|
+
import path49 from "node:path";
|
|
9925
10260
|
function buildWorktreeIndexAt(harnessRoot) {
|
|
9926
10261
|
const index = /* @__PURE__ */ new Map();
|
|
9927
10262
|
for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
|
|
9928
10263
|
for (const name of Object.keys(run.workers || {})) {
|
|
9929
|
-
const workerPath =
|
|
10264
|
+
const workerPath = path49.join(
|
|
9930
10265
|
runDirectoryAt(harnessRoot, run.id),
|
|
9931
10266
|
"workers",
|
|
9932
10267
|
safeSlug(name),
|
|
@@ -9934,9 +10269,9 @@ function buildWorktreeIndexAt(harnessRoot) {
|
|
|
9934
10269
|
);
|
|
9935
10270
|
const worker = readJson(workerPath, void 0);
|
|
9936
10271
|
if (!worker?.worktreePath) continue;
|
|
9937
|
-
index.set(
|
|
10272
|
+
index.set(path49.resolve(worker.worktreePath), {
|
|
9938
10273
|
harnessRoot,
|
|
9939
|
-
worktreePath:
|
|
10274
|
+
worktreePath: path49.resolve(worker.worktreePath),
|
|
9940
10275
|
runId: run.id,
|
|
9941
10276
|
workerName: name,
|
|
9942
10277
|
run,
|
|
@@ -10005,15 +10340,15 @@ function resolvePipelineHarnessRetention(runId) {
|
|
|
10005
10340
|
}
|
|
10006
10341
|
|
|
10007
10342
|
// src/cleanup-orphan-safety.ts
|
|
10008
|
-
import { existsSync as
|
|
10009
|
-
import
|
|
10343
|
+
import { existsSync as existsSync39, statSync as statSync13 } from "node:fs";
|
|
10344
|
+
import path50 from "node:path";
|
|
10010
10345
|
var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
|
|
10011
10346
|
function assessOrphanWorktreeSafety(input) {
|
|
10012
10347
|
const now = input.now ?? Date.now();
|
|
10013
10348
|
const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
|
|
10014
|
-
if (!
|
|
10349
|
+
if (!existsSync39(input.worktreePath)) return null;
|
|
10015
10350
|
if (input.runId && input.workerName) {
|
|
10016
|
-
const heartbeatPath =
|
|
10351
|
+
const heartbeatPath = path50.join(
|
|
10017
10352
|
input.harnessRoot,
|
|
10018
10353
|
"runs",
|
|
10019
10354
|
input.runId,
|
|
@@ -10027,8 +10362,8 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
10027
10362
|
} catch {
|
|
10028
10363
|
}
|
|
10029
10364
|
}
|
|
10030
|
-
const gitDir =
|
|
10031
|
-
if (!
|
|
10365
|
+
const gitDir = path50.join(input.worktreePath, ".git");
|
|
10366
|
+
if (!existsSync39(gitDir)) return null;
|
|
10032
10367
|
const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
|
|
10033
10368
|
if (porcelain.status !== 0) return "pr_or_unmerged_commits";
|
|
10034
10369
|
const dirtyLines = porcelain.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
@@ -10057,12 +10392,12 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
10057
10392
|
}
|
|
10058
10393
|
|
|
10059
10394
|
// src/cleanup-harness-roots.ts
|
|
10060
|
-
import { existsSync as
|
|
10395
|
+
import { existsSync as existsSync40 } from "node:fs";
|
|
10061
10396
|
import { homedir as homedir13 } from "node:os";
|
|
10062
|
-
import
|
|
10397
|
+
import path51 from "node:path";
|
|
10063
10398
|
var WELL_KNOWN_HARNESS_SCAN_ROOTS = [
|
|
10064
10399
|
"/var/tmp/kynver-harness",
|
|
10065
|
-
|
|
10400
|
+
path51.join(homedir13(), ".openclaw", "harness")
|
|
10066
10401
|
];
|
|
10067
10402
|
function addRoot(seen, roots, candidate) {
|
|
10068
10403
|
if (!candidate?.trim()) return;
|
|
@@ -10084,8 +10419,8 @@ function resolveHarnessScanRoots(options = {}) {
|
|
|
10084
10419
|
for (const candidate of extra ?? []) addRoot(seen, roots, candidate);
|
|
10085
10420
|
if (shouldScanWellKnownRoots(options)) {
|
|
10086
10421
|
for (const candidate of WELL_KNOWN_HARNESS_SCAN_ROOTS) {
|
|
10087
|
-
const resolved =
|
|
10088
|
-
if (!seen.has(resolved) &&
|
|
10422
|
+
const resolved = path51.resolve(candidate);
|
|
10423
|
+
if (!seen.has(resolved) && existsSync40(resolved)) addRoot(seen, roots, resolved);
|
|
10089
10424
|
}
|
|
10090
10425
|
}
|
|
10091
10426
|
return roots;
|
|
@@ -10115,11 +10450,19 @@ function observeCleanupDiskPressure(input = {}) {
|
|
|
10115
10450
|
}
|
|
10116
10451
|
function applyDiskPressureToRetention(retention, pressure) {
|
|
10117
10452
|
if (!pressure.pressured) return retention;
|
|
10118
|
-
const executeOnPressure = retention.execute || envFlag2("
|
|
10453
|
+
const executeOnPressure = retention.execute || !envFlag2("KYNVER_CLEANUP_DRY_RUN_ON_PRESSURE");
|
|
10119
10454
|
return {
|
|
10120
10455
|
...retention,
|
|
10121
10456
|
execute: executeOnPressure,
|
|
10122
10457
|
nodeModulesAgeMs: 0,
|
|
10458
|
+
// Disk pressure means the current-run-only cleanup scope has already fallen
|
|
10459
|
+
// behind. Expand the sweep to every known harness root/run, but keep the
|
|
10460
|
+
// worktree salvage/PR/dirty/live-worker guards in the cleanup pipeline.
|
|
10461
|
+
runIdFilter: void 0,
|
|
10462
|
+
includeOrphans: true,
|
|
10463
|
+
terminalWorktreesAgeMs: 0,
|
|
10464
|
+
runDirectoriesAgeMs: 0,
|
|
10465
|
+
worktreesAgeMs: retention.worktreesAgeMs > 0 ? retention.worktreesAgeMs : DEFAULT_WORKTREES_AGE_MS,
|
|
10123
10466
|
diskPressure: true,
|
|
10124
10467
|
diskGate: pressure.diskGate
|
|
10125
10468
|
};
|
|
@@ -10231,9 +10574,9 @@ function mergeWorktreeIndexes(scanRoots) {
|
|
|
10231
10574
|
}
|
|
10232
10575
|
function worktreePathForCandidate(candidate, worktreesDir) {
|
|
10233
10576
|
if (candidate.runId && candidate.worker) {
|
|
10234
|
-
return
|
|
10577
|
+
return path52.join(worktreesDir, candidate.runId, candidate.worker);
|
|
10235
10578
|
}
|
|
10236
|
-
return
|
|
10579
|
+
return path52.resolve(candidate.path, "..");
|
|
10237
10580
|
}
|
|
10238
10581
|
function runHarnessCleanup(options = {}) {
|
|
10239
10582
|
let retention = resolveHarnessRetention(options);
|
|
@@ -10258,7 +10601,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10258
10601
|
for (const harnessRoot of paths.scanRoots) {
|
|
10259
10602
|
if (atSweepCap()) break;
|
|
10260
10603
|
emitCleanupProgress("root", harnessRoot);
|
|
10261
|
-
const worktreesDir =
|
|
10604
|
+
const worktreesDir = path52.join(harnessRoot, "worktrees");
|
|
10262
10605
|
const scanOpts = {
|
|
10263
10606
|
harnessRoot,
|
|
10264
10607
|
worktreesDir,
|
|
@@ -10271,7 +10614,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10271
10614
|
};
|
|
10272
10615
|
for (const raw of scanDependencyCacheCandidates(scanOpts)) {
|
|
10273
10616
|
if (atSweepCap()) break;
|
|
10274
|
-
const resolved =
|
|
10617
|
+
const resolved = path52.resolve(raw.path);
|
|
10275
10618
|
if (processedPaths.has(resolved)) continue;
|
|
10276
10619
|
processedPaths.add(resolved);
|
|
10277
10620
|
const candidate = { ...raw, path: resolved };
|
|
@@ -10282,7 +10625,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10282
10625
|
continue;
|
|
10283
10626
|
}
|
|
10284
10627
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
10285
|
-
const indexed = index.get(
|
|
10628
|
+
const indexed = index.get(path52.resolve(worktreePath)) ?? null;
|
|
10286
10629
|
const guardReason = skipDependencyCacheRemoval({
|
|
10287
10630
|
indexed,
|
|
10288
10631
|
includeOrphans: true,
|
|
@@ -10306,7 +10649,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10306
10649
|
}
|
|
10307
10650
|
for (const raw of scanBuildCacheCandidates(scanOpts)) {
|
|
10308
10651
|
if (atSweepCap()) break;
|
|
10309
|
-
const resolved =
|
|
10652
|
+
const resolved = path52.resolve(raw.path);
|
|
10310
10653
|
if (processedPaths.has(resolved)) continue;
|
|
10311
10654
|
processedPaths.add(resolved);
|
|
10312
10655
|
const candidate = { ...raw, path: resolved };
|
|
@@ -10317,7 +10660,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10317
10660
|
continue;
|
|
10318
10661
|
}
|
|
10319
10662
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
10320
|
-
const indexed = index.get(
|
|
10663
|
+
const indexed = index.get(path52.resolve(worktreePath)) ?? null;
|
|
10321
10664
|
const guardReason = skipBuildCacheRemoval({
|
|
10322
10665
|
indexed,
|
|
10323
10666
|
includeOrphans: true,
|
|
@@ -10347,11 +10690,11 @@ function runHarnessCleanup(options = {}) {
|
|
|
10347
10690
|
const worktreeSeen = /* @__PURE__ */ new Set();
|
|
10348
10691
|
for (const raw of worktreeCandidates) {
|
|
10349
10692
|
if (atSweepCap()) break;
|
|
10350
|
-
const resolved =
|
|
10693
|
+
const resolved = path52.resolve(raw.path);
|
|
10351
10694
|
if (worktreeSeen.has(resolved)) continue;
|
|
10352
10695
|
worktreeSeen.add(resolved);
|
|
10353
10696
|
const candidate = { ...raw, path: resolved };
|
|
10354
|
-
const indexed = index.get(
|
|
10697
|
+
const indexed = index.get(path52.resolve(candidate.path)) ?? null;
|
|
10355
10698
|
const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
|
|
10356
10699
|
worktreePath: candidate.path,
|
|
10357
10700
|
harnessRoot,
|
|
@@ -10361,7 +10704,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10361
10704
|
});
|
|
10362
10705
|
const guardSkip = skipWorktreeRemoval({
|
|
10363
10706
|
indexed,
|
|
10364
|
-
worktreePath:
|
|
10707
|
+
worktreePath: path52.resolve(candidate.path),
|
|
10365
10708
|
includeOrphans: retention.includeOrphans,
|
|
10366
10709
|
worktreesAgeMs: retention.worktreesAgeMs,
|
|
10367
10710
|
terminalWorktreesAgeMs: retention.terminalWorktreesAgeMs,
|
|
@@ -10393,11 +10736,11 @@ function runHarnessCleanup(options = {}) {
|
|
|
10393
10736
|
now: paths.now
|
|
10394
10737
|
})) {
|
|
10395
10738
|
if (atSweepCap()) break;
|
|
10396
|
-
const resolved =
|
|
10739
|
+
const resolved = path52.resolve(raw.path);
|
|
10397
10740
|
if (processedPaths.has(resolved)) continue;
|
|
10398
10741
|
processedPaths.add(resolved);
|
|
10399
10742
|
const candidate = { ...raw, path: resolved };
|
|
10400
|
-
const runId = candidate.runId ??
|
|
10743
|
+
const runId = candidate.runId ?? path52.basename(resolved);
|
|
10401
10744
|
const dirSkip = skipRunDirectoryRemoval({
|
|
10402
10745
|
harnessRoot,
|
|
10403
10746
|
runId,
|
|
@@ -10535,8 +10878,8 @@ import { mkdirSync as mkdirSync8, realpathSync } from "node:fs";
|
|
|
10535
10878
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
10536
10879
|
|
|
10537
10880
|
// src/discard-disposable.ts
|
|
10538
|
-
import { existsSync as
|
|
10539
|
-
import
|
|
10881
|
+
import { existsSync as existsSync41, rmSync as rmSync4 } from "node:fs";
|
|
10882
|
+
import path53 from "node:path";
|
|
10540
10883
|
function normalizeRelativePath2(value) {
|
|
10541
10884
|
const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
10542
10885
|
if (!normalized || normalized.startsWith("/") || normalized.includes("..")) {
|
|
@@ -10558,18 +10901,18 @@ function discardDisposableArtifacts(args) {
|
|
|
10558
10901
|
if (paths.length === 0) {
|
|
10559
10902
|
return { ok: false, removed: [], reason: "requires at least one --path" };
|
|
10560
10903
|
}
|
|
10561
|
-
const worktreeRoot =
|
|
10904
|
+
const worktreeRoot = path53.resolve(worker.worktreePath);
|
|
10562
10905
|
const removed = [];
|
|
10563
10906
|
for (const raw of paths) {
|
|
10564
10907
|
const rel = normalizeRelativePath2(raw);
|
|
10565
|
-
const abs =
|
|
10566
|
-
if (!abs.startsWith(worktreeRoot +
|
|
10908
|
+
const abs = path53.resolve(worktreeRoot, rel);
|
|
10909
|
+
if (!abs.startsWith(worktreeRoot + path53.sep) && abs !== worktreeRoot) {
|
|
10567
10910
|
return { ok: false, removed, reason: `path escapes worktree: ${raw}` };
|
|
10568
10911
|
}
|
|
10569
|
-
if (!
|
|
10912
|
+
if (!existsSync41(abs)) {
|
|
10570
10913
|
return { ok: false, removed, reason: `path not found: ${raw}` };
|
|
10571
10914
|
}
|
|
10572
|
-
|
|
10915
|
+
rmSync4(abs, { recursive: true, force: true });
|
|
10573
10916
|
removed.push(rel);
|
|
10574
10917
|
}
|
|
10575
10918
|
const prior = Array.isArray(worker.disposableArtifactsRemoved) ? worker.disposableArtifactsRemoved.filter((p) => typeof p === "string") : [];
|
|
@@ -10588,10 +10931,49 @@ function discardDisposableCli(args) {
|
|
|
10588
10931
|
if (!result.ok) process.exit(1);
|
|
10589
10932
|
}
|
|
10590
10933
|
|
|
10934
|
+
// src/daemon-box-identity.ts
|
|
10935
|
+
import os8 from "node:os";
|
|
10936
|
+
function emitDaemonIdentityMessage(level, message) {
|
|
10937
|
+
console.error(JSON.stringify({ event: "daemon_identity", level, message }));
|
|
10938
|
+
}
|
|
10939
|
+
function validateDaemonInstallIdentity(config = loadUserConfig(), env = process.env) {
|
|
10940
|
+
const box = resolveBoxIdentity(env, config);
|
|
10941
|
+
const cap = resolveWorkerCap({
|
|
10942
|
+
config,
|
|
10943
|
+
totalMemBytes: os8.totalmem(),
|
|
10944
|
+
env
|
|
10945
|
+
});
|
|
10946
|
+
const warnings = [...box.warnings];
|
|
10947
|
+
const errors = [];
|
|
10948
|
+
if (!config.boxKind?.trim() && !env.KYNVER_BOX_KIND?.trim()) {
|
|
10949
|
+
warnings.push(
|
|
10950
|
+
"boxKind is not persisted in ~/.kynver/config.json \u2014 run `kynver setup --box-kind forge|ghost` so Command Center attributes snapshots to the correct pool"
|
|
10951
|
+
);
|
|
10952
|
+
}
|
|
10953
|
+
if (box.slugInferenceBlocked) {
|
|
10954
|
+
const strict = env.KYNVER_DAEMON_STRICT_IDENTITY === "1" || env.KYNVER_DAEMON_STRICT_IDENTITY === "true";
|
|
10955
|
+
const msg = "ambiguous box identity: KYNVER_AGENT_OS_SLUG is set without KYNVER_BOX_KIND or config.boxKind; treating this host as forge";
|
|
10956
|
+
if (strict) errors.push(msg);
|
|
10957
|
+
else warnings.push(msg);
|
|
10958
|
+
}
|
|
10959
|
+
const ok = errors.length === 0;
|
|
10960
|
+
for (const warning of warnings) emitDaemonIdentityMessage("warn", warning);
|
|
10961
|
+
for (const error of errors) emitDaemonIdentityMessage("error", error);
|
|
10962
|
+
return {
|
|
10963
|
+
ok,
|
|
10964
|
+
box,
|
|
10965
|
+
workerCapSource: cap.workerCapSource,
|
|
10966
|
+
maxConcurrentWorkers: cap.configuredMaxWorkers ?? cap.autoCap,
|
|
10967
|
+
autoCap: cap.autoCap,
|
|
10968
|
+
warnings,
|
|
10969
|
+
errors
|
|
10970
|
+
};
|
|
10971
|
+
}
|
|
10972
|
+
|
|
10591
10973
|
// src/cron/cron-env.ts
|
|
10592
|
-
import { existsSync as
|
|
10974
|
+
import { existsSync as existsSync42 } from "node:fs";
|
|
10593
10975
|
import { homedir as homedir14 } from "node:os";
|
|
10594
|
-
import
|
|
10976
|
+
import path54 from "node:path";
|
|
10595
10977
|
function envFlag3(name, defaultValue) {
|
|
10596
10978
|
const raw = process.env[name]?.trim().toLowerCase();
|
|
10597
10979
|
if (!raw) return defaultValue;
|
|
@@ -10607,7 +10989,7 @@ function envInt(name, fallback, min = 1) {
|
|
|
10607
10989
|
function defaultKynverCronStorePath() {
|
|
10608
10990
|
const explicit = process.env.KYNVER_CRON_STORE_PATH?.trim() || process.env.OPENCLAW_CRON_STORE_PATH?.trim();
|
|
10609
10991
|
if (explicit) return explicit;
|
|
10610
|
-
return
|
|
10992
|
+
return path54.join(homedir14(), ".kynver", "agent-os-cron.json");
|
|
10611
10993
|
}
|
|
10612
10994
|
function defaultKynverCronStatePath(storePath = defaultKynverCronStorePath()) {
|
|
10613
10995
|
const explicit = process.env.KYNVER_CRON_TICK_STATE_PATH?.trim();
|
|
@@ -10627,7 +11009,7 @@ function resolveKynverCronEnv() {
|
|
|
10627
11009
|
const fireBaseUrl = resolveKynverCronFireBaseUrl();
|
|
10628
11010
|
const secret = resolveKynverCronSecret();
|
|
10629
11011
|
const credsReady = Boolean(fireBaseUrl && secret);
|
|
10630
|
-
const storeExists =
|
|
11012
|
+
const storeExists = existsSync42(storePath);
|
|
10631
11013
|
const defaultEnabled = credsReady && (storeExists || envFlag3("KYNVER_CRON_TICK_FORCE", false));
|
|
10632
11014
|
return {
|
|
10633
11015
|
storePath,
|
|
@@ -10680,10 +11062,10 @@ async function fireKynverCronJob(input) {
|
|
|
10680
11062
|
}
|
|
10681
11063
|
|
|
10682
11064
|
// src/cron/cron-lock.ts
|
|
10683
|
-
import { closeSync as closeSync6, existsSync as
|
|
11065
|
+
import { closeSync as closeSync6, existsSync as existsSync43, openSync as openSync6, readFileSync as readFileSync15, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
10684
11066
|
var STALE_LOCK_MS = 10 * 6e4;
|
|
10685
11067
|
function readLockInfo(lockPath) {
|
|
10686
|
-
if (!
|
|
11068
|
+
if (!existsSync43(lockPath)) return null;
|
|
10687
11069
|
try {
|
|
10688
11070
|
const parsed = JSON.parse(readFileSync15(lockPath, "utf8"));
|
|
10689
11071
|
if (typeof parsed.pid === "number" && typeof parsed.at === "string") return parsed;
|
|
@@ -10701,14 +11083,14 @@ function lockIsStale(lockPath) {
|
|
|
10701
11083
|
return Date.now() - atMs > STALE_LOCK_MS;
|
|
10702
11084
|
}
|
|
10703
11085
|
function tryAcquireCronTickLock(lockPath) {
|
|
10704
|
-
if (
|
|
11086
|
+
if (existsSync43(lockPath) && !lockIsStale(lockPath)) {
|
|
10705
11087
|
const info = readLockInfo(lockPath);
|
|
10706
11088
|
return {
|
|
10707
11089
|
acquired: false,
|
|
10708
11090
|
reason: info ? `held by pid ${info.pid}` : "held by another process"
|
|
10709
11091
|
};
|
|
10710
11092
|
}
|
|
10711
|
-
if (
|
|
11093
|
+
if (existsSync43(lockPath)) {
|
|
10712
11094
|
try {
|
|
10713
11095
|
unlinkSync3(lockPath);
|
|
10714
11096
|
} catch {
|
|
@@ -10849,7 +11231,7 @@ async function loadCronJobs(storePath = defaultKynverCronStorePath()) {
|
|
|
10849
11231
|
// src/cron/cron-tick-state.ts
|
|
10850
11232
|
import { randomBytes } from "node:crypto";
|
|
10851
11233
|
import { promises as fs4 } from "node:fs";
|
|
10852
|
-
import
|
|
11234
|
+
import path55 from "node:path";
|
|
10853
11235
|
var EMPTY = { version: 1, jobs: {} };
|
|
10854
11236
|
async function readFileIfExists2(filePath) {
|
|
10855
11237
|
try {
|
|
@@ -10876,7 +11258,7 @@ async function loadCronTickState(statePath) {
|
|
|
10876
11258
|
return parseCronTickState(raw);
|
|
10877
11259
|
}
|
|
10878
11260
|
async function writeStateAtomic(statePath, state) {
|
|
10879
|
-
await fs4.mkdir(
|
|
11261
|
+
await fs4.mkdir(path55.dirname(statePath), { recursive: true });
|
|
10880
11262
|
const suffix = randomBytes(6).toString("hex");
|
|
10881
11263
|
const tmp = `${statePath}.tmp-${process.pid}-${Date.now()}-${suffix}`;
|
|
10882
11264
|
await fs4.writeFile(tmp, `${JSON.stringify(state, null, 2)}
|
|
@@ -11053,7 +11435,7 @@ async function runKynverCronTick(opts = {}) {
|
|
|
11053
11435
|
}
|
|
11054
11436
|
|
|
11055
11437
|
// src/pipeline-tick.ts
|
|
11056
|
-
import
|
|
11438
|
+
import path57 from "node:path";
|
|
11057
11439
|
|
|
11058
11440
|
// src/pipeline-dispatch.ts
|
|
11059
11441
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -11184,7 +11566,7 @@ function resolvePipelineMaxStarts(resourceGate, operatorTick) {
|
|
|
11184
11566
|
}
|
|
11185
11567
|
|
|
11186
11568
|
// src/plan-progress-daemon-sync.ts
|
|
11187
|
-
import
|
|
11569
|
+
import path56 from "node:path";
|
|
11188
11570
|
|
|
11189
11571
|
// src/plan-progress-sync.ts
|
|
11190
11572
|
async function syncPlanProgress(args) {
|
|
@@ -11208,7 +11590,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
11208
11590
|
const outcomes = [];
|
|
11209
11591
|
for (const name of Object.keys(run.workers || {})) {
|
|
11210
11592
|
const worker = readJson(
|
|
11211
|
-
|
|
11593
|
+
path56.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
11212
11594
|
void 0
|
|
11213
11595
|
);
|
|
11214
11596
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -11237,7 +11619,8 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
11237
11619
|
|
|
11238
11620
|
// src/workspace-runtime-config.ts
|
|
11239
11621
|
function shouldApplyWorkspaceRuntimePreferences(env = process.env) {
|
|
11240
|
-
|
|
11622
|
+
const config = loadUserConfig();
|
|
11623
|
+
return resolveBoxKindFromConfig(config, env) !== "forge";
|
|
11241
11624
|
}
|
|
11242
11625
|
function configuredMaxWorkersOverrideForBox(preferences, env = process.env) {
|
|
11243
11626
|
if (!shouldApplyWorkspaceRuntimePreferences(env)) return void 0;
|
|
@@ -11269,7 +11652,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
11269
11652
|
const outcomes = [];
|
|
11270
11653
|
for (const name of Object.keys(run.workers || {})) {
|
|
11271
11654
|
const worker = readJson(
|
|
11272
|
-
|
|
11655
|
+
path57.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
11273
11656
|
void 0
|
|
11274
11657
|
);
|
|
11275
11658
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -11308,7 +11691,10 @@ async function postOperatorTick(agentOsId, runId, resourceGate, args, harnessCle
|
|
|
11308
11691
|
ingestHarness: true,
|
|
11309
11692
|
harnessBoardSnapshot: buildRunBoard(runId),
|
|
11310
11693
|
resourceGate,
|
|
11311
|
-
boxResourceSnapshot: buildBoxResourceSnapshotFromGate(resourceGate, {
|
|
11694
|
+
boxResourceSnapshot: buildBoxResourceSnapshotFromGate(resourceGate, {
|
|
11695
|
+
harnessRunId: runId,
|
|
11696
|
+
boxKind: resolveBoxKindFromConfig(loadUserConfig())
|
|
11697
|
+
}),
|
|
11312
11698
|
packageVersions,
|
|
11313
11699
|
...harnessCleanup ? { harnessCleanup } : {},
|
|
11314
11700
|
runnerPresence: resolveRunnerPresencePayload({ runId }),
|
|
@@ -11426,7 +11812,30 @@ async function runDaemon(args) {
|
|
|
11426
11812
|
process.on("SIGTERM", () => {
|
|
11427
11813
|
stopping = true;
|
|
11428
11814
|
});
|
|
11429
|
-
|
|
11815
|
+
const identity = validateDaemonInstallIdentity(loadUserConfig());
|
|
11816
|
+
if (!identity.ok) {
|
|
11817
|
+
console.error(
|
|
11818
|
+
JSON.stringify({
|
|
11819
|
+
event: "daemon_start_blocked",
|
|
11820
|
+
runId,
|
|
11821
|
+
agentOsId,
|
|
11822
|
+
errors: identity.errors
|
|
11823
|
+
})
|
|
11824
|
+
);
|
|
11825
|
+
process.exit(1);
|
|
11826
|
+
}
|
|
11827
|
+
console.error(
|
|
11828
|
+
JSON.stringify({
|
|
11829
|
+
event: "daemon_start",
|
|
11830
|
+
runId,
|
|
11831
|
+
agentOsId,
|
|
11832
|
+
execute,
|
|
11833
|
+
intervalMs,
|
|
11834
|
+
boxKind: identity.box.boxKind,
|
|
11835
|
+
workerCapSource: identity.workerCapSource,
|
|
11836
|
+
maxConcurrentWorkers: identity.maxConcurrentWorkers
|
|
11837
|
+
})
|
|
11838
|
+
);
|
|
11430
11839
|
const cronEnv = resolveKynverCronEnv();
|
|
11431
11840
|
while (!stopping) {
|
|
11432
11841
|
try {
|
|
@@ -11457,7 +11866,7 @@ async function runDaemon(args) {
|
|
|
11457
11866
|
}
|
|
11458
11867
|
|
|
11459
11868
|
// src/plan-progress.ts
|
|
11460
|
-
import
|
|
11869
|
+
import path59 from "node:path";
|
|
11461
11870
|
|
|
11462
11871
|
// src/bounded-build/constants.ts
|
|
11463
11872
|
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
@@ -11495,7 +11904,7 @@ function formatNodeOptionsFlag(mb = resolveNodeOldSpaceSizeMb()) {
|
|
|
11495
11904
|
}
|
|
11496
11905
|
|
|
11497
11906
|
// src/bounded-build/systemd-wrap.ts
|
|
11498
|
-
import { spawnSync as
|
|
11907
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
11499
11908
|
var systemdAvailableCache;
|
|
11500
11909
|
function isSystemdRunAvailable() {
|
|
11501
11910
|
if (process.env.KYNVER_BUILD_SKIP_SYSTEMD === "1" || process.env.KYNVER_BUILD_SKIP_SYSTEMD === "true") {
|
|
@@ -11506,7 +11915,7 @@ function isSystemdRunAvailable() {
|
|
|
11506
11915
|
systemdAvailableCache = false;
|
|
11507
11916
|
return false;
|
|
11508
11917
|
}
|
|
11509
|
-
const res =
|
|
11918
|
+
const res = spawnSync6("systemd-run", ["--version"], { encoding: "utf8", stdio: ["ignore", "ignore", "pipe"] });
|
|
11510
11919
|
systemdAvailableCache = res.status === 0;
|
|
11511
11920
|
return systemdAvailableCache;
|
|
11512
11921
|
}
|
|
@@ -11530,18 +11939,18 @@ function buildSystemdRunArgv(opts) {
|
|
|
11530
11939
|
}
|
|
11531
11940
|
|
|
11532
11941
|
// src/bounded-build/admission.ts
|
|
11533
|
-
import { spawnSync as
|
|
11534
|
-
function
|
|
11942
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
11943
|
+
function positiveInt4(value, fallback) {
|
|
11535
11944
|
const n = Number(value);
|
|
11536
11945
|
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
11537
11946
|
return Math.floor(n);
|
|
11538
11947
|
}
|
|
11539
11948
|
function resolveBuildAdmissionConfig(config = loadUserConfig()) {
|
|
11540
|
-
const envBudget = process.env.KYNVER_BUILD_MEM_BUDGET_BYTES ?
|
|
11541
|
-
const envReserve = process.env.KYNVER_BUILD_MEM_RESERVE_BYTES ?
|
|
11949
|
+
const envBudget = process.env.KYNVER_BUILD_MEM_BUDGET_BYTES ? positiveInt4(process.env.KYNVER_BUILD_MEM_BUDGET_BYTES, DEFAULT_BUILD_MEM_BUDGET_BYTES) : void 0;
|
|
11950
|
+
const envReserve = process.env.KYNVER_BUILD_MEM_RESERVE_BYTES ? positiveInt4(process.env.KYNVER_BUILD_MEM_RESERVE_BYTES, DEFAULT_BUILD_MEM_RESERVE_BYTES) : void 0;
|
|
11542
11951
|
return {
|
|
11543
|
-
perBuildBudgetBytes: envBudget ??
|
|
11544
|
-
reserveBytes: envReserve ??
|
|
11952
|
+
perBuildBudgetBytes: envBudget ?? positiveInt4(config.perWorkerMemBytes, DEFAULT_BUILD_MEM_BUDGET_BYTES),
|
|
11953
|
+
reserveBytes: envReserve ?? positiveInt4(config.memReserveBytes, DEFAULT_BUILD_MEM_RESERVE_BYTES)
|
|
11545
11954
|
};
|
|
11546
11955
|
}
|
|
11547
11956
|
var activeBuilds = 0;
|
|
@@ -11566,7 +11975,7 @@ function assessBuildAdmission(opts = {}) {
|
|
|
11566
11975
|
}
|
|
11567
11976
|
function sleepMs2(ms) {
|
|
11568
11977
|
if (ms <= 0) return;
|
|
11569
|
-
|
|
11978
|
+
spawnSync7(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
|
|
11570
11979
|
stdio: "ignore"
|
|
11571
11980
|
});
|
|
11572
11981
|
}
|
|
@@ -11587,7 +11996,28 @@ function waitForBuildAdmission(timeoutMs, pollMs = 2e3, opts = {}) {
|
|
|
11587
11996
|
}
|
|
11588
11997
|
|
|
11589
11998
|
// src/bounded-build/exec.ts
|
|
11590
|
-
import { spawnSync as
|
|
11999
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
12000
|
+
|
|
12001
|
+
// src/harness-worktree-build-guard.ts
|
|
12002
|
+
import path58 from "node:path";
|
|
12003
|
+
function isPathUnderHarnessWorktree(cwd) {
|
|
12004
|
+
const worktreesDir = harnessWorktreesDir(resolveHarnessRoot());
|
|
12005
|
+
const rel = path58.relative(worktreesDir, path58.resolve(cwd));
|
|
12006
|
+
return rel.length > 0 && !rel.startsWith("..") && !path58.isAbsolute(rel);
|
|
12007
|
+
}
|
|
12008
|
+
function assessHarnessWorktreeBuildGuard(cwd) {
|
|
12009
|
+
if (!isPathUnderHarnessWorktree(cwd)) return { ok: true };
|
|
12010
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
12011
|
+
if (uid === 0) {
|
|
12012
|
+
return {
|
|
12013
|
+
ok: false,
|
|
12014
|
+
reason: "Refusing build/install as root inside a harness worktree \u2014 generated caches become root-owned and block daemon cleanup. Run kynver daemon and workers as the harness owner (never sudo kynver)."
|
|
12015
|
+
};
|
|
12016
|
+
}
|
|
12017
|
+
return { ok: true };
|
|
12018
|
+
}
|
|
12019
|
+
|
|
12020
|
+
// src/bounded-build/exec.ts
|
|
11591
12021
|
function envArgv(env) {
|
|
11592
12022
|
const out = [];
|
|
11593
12023
|
for (const [key, value] of Object.entries(env)) {
|
|
@@ -11597,7 +12027,7 @@ function envArgv(env) {
|
|
|
11597
12027
|
return out;
|
|
11598
12028
|
}
|
|
11599
12029
|
function runSpawn(argv, opts) {
|
|
11600
|
-
const res =
|
|
12030
|
+
const res = spawnSync8(argv[0], argv.slice(1), {
|
|
11601
12031
|
cwd: opts.cwd,
|
|
11602
12032
|
env: opts.env,
|
|
11603
12033
|
encoding: "utf8",
|
|
@@ -11627,6 +12057,20 @@ function runBoundedBuildCheck(input) {
|
|
|
11627
12057
|
command: input.command
|
|
11628
12058
|
};
|
|
11629
12059
|
}
|
|
12060
|
+
const worktreeGuard = assessHarnessWorktreeBuildGuard(input.cwd);
|
|
12061
|
+
if (!worktreeGuard.ok) {
|
|
12062
|
+
return {
|
|
12063
|
+
ok: false,
|
|
12064
|
+
exitCode: 1,
|
|
12065
|
+
stdout: "",
|
|
12066
|
+
stderr: worktreeGuard.reason,
|
|
12067
|
+
admitted: true,
|
|
12068
|
+
wrappedWithSystemd: false,
|
|
12069
|
+
nodeOptionsFlag: formatNodeOptionsFlag(),
|
|
12070
|
+
admission,
|
|
12071
|
+
command: input.command
|
|
12072
|
+
};
|
|
12073
|
+
}
|
|
11630
12074
|
const env = mergeNodeOptionsForBuildCheck({ ...process.env, ...input.env });
|
|
11631
12075
|
const nodeOptionsFlag = formatNodeOptionsFlag();
|
|
11632
12076
|
const useSystemd = isSystemdRunAvailable();
|
|
@@ -11744,7 +12188,7 @@ async function emitPlanProgress(args) {
|
|
|
11744
12188
|
}
|
|
11745
12189
|
function verifyPlanLocal(args) {
|
|
11746
12190
|
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
11747
|
-
const cwd =
|
|
12191
|
+
const cwd = path59.resolve(worktree);
|
|
11748
12192
|
const summary = runHarnessVerifyCommands(cwd);
|
|
11749
12193
|
const emitJson = args.json === true || args.json === "true";
|
|
11750
12194
|
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
@@ -11793,9 +12237,9 @@ async function verifyPlan(args) {
|
|
|
11793
12237
|
}
|
|
11794
12238
|
|
|
11795
12239
|
// src/harness-verify-cli.ts
|
|
11796
|
-
import
|
|
12240
|
+
import path60 from "node:path";
|
|
11797
12241
|
function runHarnessVerifyCli(args) {
|
|
11798
|
-
const cwd =
|
|
12242
|
+
const cwd = path60.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
11799
12243
|
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
11800
12244
|
const commands = [];
|
|
11801
12245
|
const rawCmd = args.command;
|
|
@@ -12231,7 +12675,7 @@ ${text.slice(0, 800)}`,
|
|
|
12231
12675
|
}
|
|
12232
12676
|
|
|
12233
12677
|
// src/monitor/monitor.service.ts
|
|
12234
|
-
import
|
|
12678
|
+
import path62 from "node:path";
|
|
12235
12679
|
|
|
12236
12680
|
// src/monitor/monitor.classify.ts
|
|
12237
12681
|
function classifyWorkerHealth(input) {
|
|
@@ -12283,11 +12727,11 @@ function classifyWorkerHealth(input) {
|
|
|
12283
12727
|
}
|
|
12284
12728
|
|
|
12285
12729
|
// src/monitor/monitor.store.ts
|
|
12286
|
-
import { existsSync as
|
|
12287
|
-
import
|
|
12730
|
+
import { existsSync as existsSync44, mkdirSync as mkdirSync7, readdirSync as readdirSync15, unlinkSync as unlinkSync4 } from "node:fs";
|
|
12731
|
+
import path61 from "node:path";
|
|
12288
12732
|
function monitorsDir() {
|
|
12289
12733
|
const { harnessRoot } = getHarnessPaths();
|
|
12290
|
-
const dir =
|
|
12734
|
+
const dir = path61.join(harnessRoot, "monitors");
|
|
12291
12735
|
mkdirSync7(dir, { recursive: true });
|
|
12292
12736
|
return dir;
|
|
12293
12737
|
}
|
|
@@ -12295,7 +12739,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
12295
12739
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
12296
12740
|
}
|
|
12297
12741
|
function monitorPath(monitorId) {
|
|
12298
|
-
return
|
|
12742
|
+
return path61.join(monitorsDir(), `${monitorId}.json`);
|
|
12299
12743
|
}
|
|
12300
12744
|
function loadMonitorSession(monitorId) {
|
|
12301
12745
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -12305,18 +12749,18 @@ function saveMonitorSession(session) {
|
|
|
12305
12749
|
}
|
|
12306
12750
|
function deleteMonitorSession(monitorId) {
|
|
12307
12751
|
const file = monitorPath(monitorId);
|
|
12308
|
-
if (!
|
|
12752
|
+
if (!existsSync44(file)) return false;
|
|
12309
12753
|
unlinkSync4(file);
|
|
12310
12754
|
return true;
|
|
12311
12755
|
}
|
|
12312
12756
|
function listMonitorSessions() {
|
|
12313
12757
|
const dir = monitorsDir();
|
|
12314
|
-
if (!
|
|
12758
|
+
if (!existsSync44(dir)) return [];
|
|
12315
12759
|
const entries = [];
|
|
12316
|
-
for (const name of
|
|
12760
|
+
for (const name of readdirSync15(dir)) {
|
|
12317
12761
|
if (!name.endsWith(".json")) continue;
|
|
12318
12762
|
const session = readJson(
|
|
12319
|
-
|
|
12763
|
+
path61.join(dir, name),
|
|
12320
12764
|
void 0
|
|
12321
12765
|
);
|
|
12322
12766
|
if (!session?.monitorId) continue;
|
|
@@ -12407,7 +12851,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
12407
12851
|
// src/monitor/monitor.service.ts
|
|
12408
12852
|
function workerRecord2(runId, name) {
|
|
12409
12853
|
return readJson(
|
|
12410
|
-
|
|
12854
|
+
path62.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
12411
12855
|
void 0
|
|
12412
12856
|
);
|
|
12413
12857
|
}
|
|
@@ -12613,18 +13057,18 @@ async function runMonitorLoop(args) {
|
|
|
12613
13057
|
|
|
12614
13058
|
// src/monitor/monitor-spawn.ts
|
|
12615
13059
|
import { spawn as spawn6 } from "node:child_process";
|
|
12616
|
-
import { closeSync as closeSync7, existsSync as
|
|
12617
|
-
import
|
|
13060
|
+
import { closeSync as closeSync7, existsSync as existsSync45, openSync as openSync7 } from "node:fs";
|
|
13061
|
+
import path63 from "node:path";
|
|
12618
13062
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
12619
13063
|
function resolveDefaultCliPath2() {
|
|
12620
|
-
return
|
|
13064
|
+
return path63.join(fileURLToPath4(new URL(".", import.meta.url)), "cli.js");
|
|
12621
13065
|
}
|
|
12622
13066
|
function spawnMonitorSidecar(opts) {
|
|
12623
13067
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
12624
|
-
if (!
|
|
13068
|
+
if (!existsSync45(cliPath)) return void 0;
|
|
12625
13069
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
12626
13070
|
const { harnessRoot } = getHarnessPaths();
|
|
12627
|
-
const logPath =
|
|
13071
|
+
const logPath = path63.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
12628
13072
|
let logFd;
|
|
12629
13073
|
try {
|
|
12630
13074
|
logFd = openSync7(logPath, "a");
|
|
@@ -12744,7 +13188,7 @@ async function monitorTickCli(args) {
|
|
|
12744
13188
|
}
|
|
12745
13189
|
|
|
12746
13190
|
// src/post-restart-unblock.ts
|
|
12747
|
-
import
|
|
13191
|
+
import path64 from "node:path";
|
|
12748
13192
|
function skip(runId, worker, taskId, agentOsId, leaseOwner, reason) {
|
|
12749
13193
|
return { runId, worker, taskId, agentOsId, leaseOwner, action: "skipped", reason };
|
|
12750
13194
|
}
|
|
@@ -12757,7 +13201,7 @@ async function postRestartUnblock(args) {
|
|
|
12757
13201
|
const errors = [];
|
|
12758
13202
|
for (const run of listRunRecords()) {
|
|
12759
13203
|
for (const name of Object.keys(run.workers ?? {})) {
|
|
12760
|
-
const workerPath =
|
|
13204
|
+
const workerPath = path64.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
12761
13205
|
const worker = readJson(workerPath, void 0);
|
|
12762
13206
|
if (!worker) {
|
|
12763
13207
|
skipped.push(skip(run.id, name, "", "", "", "worker.json missing"));
|
|
@@ -12869,9 +13313,9 @@ async function postRestartUnblockCli(args) {
|
|
|
12869
13313
|
}
|
|
12870
13314
|
|
|
12871
13315
|
// src/default-repo-cli.ts
|
|
12872
|
-
import
|
|
13316
|
+
import path65 from "node:path";
|
|
12873
13317
|
import { homedir as homedir15 } from "node:os";
|
|
12874
|
-
var CONFIG_FILE2 =
|
|
13318
|
+
var CONFIG_FILE2 = path65.join(homedir15(), ".kynver", "config.json");
|
|
12875
13319
|
function ensureDefaultRepo(opts) {
|
|
12876
13320
|
const existing = loadUserConfig();
|
|
12877
13321
|
const resolved = resolveDefaultRepo({ ...opts, config: existing });
|
|
@@ -12952,16 +13396,16 @@ function summarizeResolvedDefaultRepo(resolved) {
|
|
|
12952
13396
|
}
|
|
12953
13397
|
|
|
12954
13398
|
// src/doctor/runtime-takeover.ts
|
|
12955
|
-
import
|
|
13399
|
+
import path67 from "node:path";
|
|
12956
13400
|
|
|
12957
13401
|
// src/doctor/runtime-takeover.probes.ts
|
|
12958
|
-
import { accessSync, constants, existsSync as
|
|
13402
|
+
import { accessSync, constants, existsSync as existsSync46, readFileSync as readFileSync17 } from "node:fs";
|
|
12959
13403
|
import { homedir as homedir16 } from "node:os";
|
|
12960
|
-
import
|
|
12961
|
-
import { spawnSync as
|
|
13404
|
+
import path66 from "node:path";
|
|
13405
|
+
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
12962
13406
|
function captureCommand(bin, args) {
|
|
12963
13407
|
try {
|
|
12964
|
-
const res =
|
|
13408
|
+
const res = spawnSync9(bin, args, { encoding: "utf8" });
|
|
12965
13409
|
const stdout = (res.stdout || "").trim();
|
|
12966
13410
|
const stderr = (res.stderr || "").trim();
|
|
12967
13411
|
const ok = res.status === 0;
|
|
@@ -12986,7 +13430,7 @@ function tokenPrefix(token) {
|
|
|
12986
13430
|
return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
|
|
12987
13431
|
}
|
|
12988
13432
|
function isWritable(target) {
|
|
12989
|
-
if (!
|
|
13433
|
+
if (!existsSync46(target)) return false;
|
|
12990
13434
|
try {
|
|
12991
13435
|
accessSync(target, constants.W_OK);
|
|
12992
13436
|
return true;
|
|
@@ -12999,11 +13443,11 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
12999
13443
|
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
13000
13444
|
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
13001
13445
|
loadConfig: () => loadUserConfig(),
|
|
13002
|
-
configFilePath: () =>
|
|
13003
|
-
credentialsFilePath: () =>
|
|
13446
|
+
configFilePath: () => path66.join(homedir16(), ".kynver", "config.json"),
|
|
13447
|
+
credentialsFilePath: () => path66.join(homedir16(), ".kynver", "credentials"),
|
|
13004
13448
|
readCredentials: () => {
|
|
13005
|
-
const credPath =
|
|
13006
|
-
if (!
|
|
13449
|
+
const credPath = path66.join(homedir16(), ".kynver", "credentials");
|
|
13450
|
+
if (!existsSync46(credPath)) {
|
|
13007
13451
|
return { hasApiKey: false };
|
|
13008
13452
|
}
|
|
13009
13453
|
try {
|
|
@@ -13037,8 +13481,8 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
13037
13481
|
})()
|
|
13038
13482
|
}),
|
|
13039
13483
|
harnessRoot: () => resolveHarnessRoot(),
|
|
13040
|
-
legacyOpenclawHarnessRoot: () =>
|
|
13041
|
-
pathExists: (target) =>
|
|
13484
|
+
legacyOpenclawHarnessRoot: () => path66.join(homedir16(), ".openclaw", "harness"),
|
|
13485
|
+
pathExists: (target) => existsSync46(target),
|
|
13042
13486
|
pathWritable: (target) => isWritable(target),
|
|
13043
13487
|
vercelVersion: () => captureCommand("vercel", ["--version"]),
|
|
13044
13488
|
vercelWhoami: () => captureCommand("vercel", ["whoami"])
|
|
@@ -13437,8 +13881,8 @@ function assessVercelCli(probes) {
|
|
|
13437
13881
|
}
|
|
13438
13882
|
function assessHarnessDirs(probes) {
|
|
13439
13883
|
const harnessRoot = probes.harnessRoot();
|
|
13440
|
-
const runsDir =
|
|
13441
|
-
const worktreesDir =
|
|
13884
|
+
const runsDir = path67.join(harnessRoot, "runs");
|
|
13885
|
+
const worktreesDir = path67.join(harnessRoot, "worktrees");
|
|
13442
13886
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
13443
13887
|
const displayRunsDir = redactHomePath(runsDir);
|
|
13444
13888
|
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
@@ -13702,9 +14146,9 @@ function applySchedulerCutoverAttestation(config) {
|
|
|
13702
14146
|
}
|
|
13703
14147
|
|
|
13704
14148
|
// src/scheduler-cutover-cli.ts
|
|
13705
|
-
import
|
|
14149
|
+
import path68 from "node:path";
|
|
13706
14150
|
import { homedir as homedir17 } from "node:os";
|
|
13707
|
-
var CONFIG_FILE3 =
|
|
14151
|
+
var CONFIG_FILE3 = path68.join(homedir17(), ".kynver", "config.json");
|
|
13708
14152
|
function runSchedulerCutoverCheckCli(json = false) {
|
|
13709
14153
|
const config = loadUserConfig();
|
|
13710
14154
|
const report = assessSchedulerCutover(config);
|
|
@@ -13857,7 +14301,7 @@ function usage(code = 0) {
|
|
|
13857
14301
|
"Usage:",
|
|
13858
14302
|
" kynver login --api-key KEY",
|
|
13859
14303
|
" kynver runner credential [--agent-os-id ID] [--base-url URL]",
|
|
13860
|
-
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--discover-repo] [--max-workers N] [--provider claude|cursor]",
|
|
14304
|
+
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--box-kind forge|ghost] [--repo PATH] [--discover-repo] [--max-workers N] [--provider claude|cursor]",
|
|
13861
14305
|
" kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS]",
|
|
13862
14306
|
" kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
|
|
13863
14307
|
" kynver run list",
|
|
@@ -14121,7 +14565,7 @@ function pickVercelStatusContext(statuses) {
|
|
|
14121
14565
|
}
|
|
14122
14566
|
|
|
14123
14567
|
// src/vercel/vercel-evidence.ts
|
|
14124
|
-
import { spawnSync as
|
|
14568
|
+
import { spawnSync as spawnSync10 } from "node:child_process";
|
|
14125
14569
|
var DEFAULT_INSPECT_WAIT_SECONDS = 120;
|
|
14126
14570
|
function mapGitHubStateToEvidence(state) {
|
|
14127
14571
|
if (state === "success") return "ready";
|
|
@@ -14196,7 +14640,7 @@ function defaultRunVercelInspect(target, waitSeconds) {
|
|
|
14196
14640
|
};
|
|
14197
14641
|
}
|
|
14198
14642
|
const args = ["inspect", target, "--wait", String(waitSeconds)];
|
|
14199
|
-
const result =
|
|
14643
|
+
const result = spawnSync10("vercel", args, {
|
|
14200
14644
|
encoding: "utf8",
|
|
14201
14645
|
stdio: ["ignore", "pipe", "pipe"]
|
|
14202
14646
|
});
|