@kynver-app/runtime 0.1.56 → 0.1.59
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/callbacks.d.ts +17 -0
- package/dist/cleanup-guards-helpers.d.ts +2 -0
- package/dist/cleanup-guards.d.ts +15 -4
- package/dist/cleanup-orphan-safety.d.ts +17 -0
- package/dist/cleanup-types.d.ts +28 -1
- package/dist/cli.js +712 -183
- package/dist/cli.js.map +4 -4
- package/dist/config.d.ts +5 -0
- package/dist/db-credential-resolver.d.ts +37 -0
- package/dist/db-url-hint.d.ts +4 -0
- package/dist/discard-disposable.d.ts +6 -0
- package/dist/disk-gate.d.ts +5 -0
- package/dist/dispatch.d.ts +2 -1
- package/dist/disposable-artifacts.d.ts +2 -0
- package/dist/doctor/runtime-takeover-scheduler.d.ts +5 -0
- package/dist/doctor/runtime-takeover.probes.d.ts +2 -0
- package/dist/harness-storage-snapshot.d.ts +26 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.js +1299 -588
- package/dist/index.js.map +4 -4
- package/dist/material-worktree-changes.d.ts +2 -0
- package/dist/paths.d.ts +1 -0
- package/dist/status.d.ts +13 -0
- package/dist/supervisor.d.ts +2 -1
- package/dist/worker-env.d.ts +1 -1
- package/dist/worktree-completion-handoff.d.ts +17 -0
- package/dist/wsl-host.d.ts +71 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -136,12 +136,14 @@ var FORBIDDEN_WORKER_ENV_KEYS = [
|
|
|
136
136
|
"NEXTAUTH_SECRET",
|
|
137
137
|
"DATABASE_URL",
|
|
138
138
|
"PRODUCTION_DATABASE_URL",
|
|
139
|
+
"KYNVER_PRODUCTION_DATABASE_URL",
|
|
139
140
|
"REDIS_URL",
|
|
140
141
|
"GOOGLE_CLIENT_SECRET",
|
|
141
142
|
"GITHUB_CLIENT_SECRET",
|
|
142
143
|
"KYNVER_API_KEY",
|
|
143
144
|
"KYNVER_SERVICE_SECRET",
|
|
144
145
|
"KYNVER_RUNTIME_SECRET",
|
|
146
|
+
"KYNVER_CRON_SECRET",
|
|
145
147
|
"OPENCLAW_CRON_SECRET",
|
|
146
148
|
"QSTASH_TOKEN",
|
|
147
149
|
"QSTASH_CURRENT_SIGNING_KEY",
|
|
@@ -412,7 +414,7 @@ function presentUserConfig(config) {
|
|
|
412
414
|
}
|
|
413
415
|
function inferSetupFields(existing, args) {
|
|
414
416
|
const creds = loadCredentialsFile();
|
|
415
|
-
const apiBaseUrl = (typeof args.apiBaseUrl === "string" ? args.apiBaseUrl : void 0) || existing.apiBaseUrl?.trim() || process.env.KYNVER_API_URL?.trim() || process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim();
|
|
417
|
+
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();
|
|
416
418
|
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);
|
|
417
419
|
const explicitRepo = typeof args.repo === "string" ? args.repo : args.discoverRepo === true || args.discoverRepo === "true" ? discoverDefaultRepo()?.repo : void 0;
|
|
418
420
|
const defaultRepo = explicitRepo || existing.defaultRepo?.trim() || process.env.KYNVER_DEFAULT_REPO?.trim() || process.env.KYNVER_HARNESS_REPO?.trim() || discoverDefaultRepo()?.repo;
|
|
@@ -464,17 +466,17 @@ function saveRunnerToken(agentOsId, token) {
|
|
|
464
466
|
}
|
|
465
467
|
function resolveBaseUrl(argsBaseUrl) {
|
|
466
468
|
const baseUrl = resolveConfiguredBaseUrl(argsBaseUrl);
|
|
467
|
-
if (!baseUrl) failConfig("requires --base-url, KYNVER_API_URL,
|
|
469
|
+
if (!baseUrl) failConfig("requires --base-url, KYNVER_API_URL, KYNVER_CRON_FIRE_BASE_URL, or ~/.kynver/config.json apiBaseUrl");
|
|
468
470
|
return baseUrl;
|
|
469
471
|
}
|
|
470
472
|
function resolveConfiguredBaseUrl(argsBaseUrl) {
|
|
471
|
-
const baseUrl = argsBaseUrl || process.env.KYNVER_API_URL || process.env.OPENCLAW_CRON_FIRE_BASE_URL || loadUserConfig().apiBaseUrl;
|
|
473
|
+
const baseUrl = argsBaseUrl || process.env.KYNVER_API_URL || process.env.KYNVER_CRON_FIRE_BASE_URL || process.env.OPENCLAW_CRON_FIRE_BASE_URL || loadUserConfig().apiBaseUrl;
|
|
472
474
|
return baseUrl ? trimTrailingSlash(String(baseUrl)) : void 0;
|
|
473
475
|
}
|
|
474
476
|
function resolveConfiguredCallbackSecret(argsSecret, agentOsId) {
|
|
475
477
|
const scoped = argsSecret || loadRunnerToken(agentOsId) || (agentOsId ? void 0 : loadRunnerToken(loadUserConfig().agentOsId));
|
|
476
478
|
if (scoped) return String(scoped);
|
|
477
|
-
const globalSecret = process.env.KYNVER_RUNTIME_SECRET || process.env.OPENCLAW_CRON_SECRET;
|
|
479
|
+
const globalSecret = process.env.KYNVER_RUNTIME_SECRET || process.env.KYNVER_CRON_SECRET || process.env.OPENCLAW_CRON_SECRET;
|
|
478
480
|
if (globalSecret) {
|
|
479
481
|
console.warn(
|
|
480
482
|
"[kynver] using deployment-level callback secret; run `kynver runner credential --agent-os-id <id>` for a scoped token"
|
|
@@ -498,7 +500,7 @@ async function resolveCallbackSecretWithMint(argsSecret, agentOsId, opts) {
|
|
|
498
500
|
}
|
|
499
501
|
}
|
|
500
502
|
failConfig(
|
|
501
|
-
"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 / OPENCLAW_CRON_SECRET"
|
|
503
|
+
"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"
|
|
502
504
|
);
|
|
503
505
|
}
|
|
504
506
|
async function refreshRunnerToken(agentOsId, opts) {
|
|
@@ -661,6 +663,11 @@ function buildHarnessCallbackHeaders(secret) {
|
|
|
661
663
|
}
|
|
662
664
|
return {
|
|
663
665
|
"Content-Type": "application/json",
|
|
666
|
+
// Canonical header. We keep sending the legacy `X-OpenClaw-Cron-Secret`
|
|
667
|
+
// (and `X-Kynver-Runtime-Secret`) so an un-upgraded Kynver server that
|
|
668
|
+
// only reads the old header still authenticates this runner during the
|
|
669
|
+
// rename compat window.
|
|
670
|
+
"X-Kynver-Cron-Secret": trimmed,
|
|
664
671
|
"X-OpenClaw-Cron-Secret": trimmed,
|
|
665
672
|
"X-Kynver-Runtime-Secret": trimmed
|
|
666
673
|
};
|
|
@@ -704,18 +711,88 @@ async function getJson(url, secret) {
|
|
|
704
711
|
}
|
|
705
712
|
|
|
706
713
|
// src/disk-gate.ts
|
|
707
|
-
import { statfsSync } from "node:fs";
|
|
714
|
+
import { statfsSync as statfsSync2 } from "node:fs";
|
|
715
|
+
|
|
716
|
+
// src/wsl-host.ts
|
|
717
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, statfsSync } from "node:fs";
|
|
718
|
+
var DEFAULT_WSL_HOST_WARN_FREE_BYTES = 25 * 1024 * 1024 * 1024;
|
|
719
|
+
var DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES = 12 * 1024 * 1024 * 1024;
|
|
720
|
+
var DEFAULT_WSL_HOST_MOUNT = "/mnt/c";
|
|
721
|
+
function isWslHost() {
|
|
722
|
+
if (process.platform !== "linux") return false;
|
|
723
|
+
for (const probe of ["/proc/sys/kernel/osrelease", "/proc/version"]) {
|
|
724
|
+
try {
|
|
725
|
+
if (!existsSync4(probe)) continue;
|
|
726
|
+
const text = readFileSync4(probe, "utf8");
|
|
727
|
+
if (/microsoft|wsl/i.test(text)) return true;
|
|
728
|
+
} catch {
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
function observeWslHostDisk(options = {}) {
|
|
734
|
+
const wsl = options.forceWsl === void 0 ? isWslHost() : options.forceWsl;
|
|
735
|
+
if (!wsl) return null;
|
|
736
|
+
const path43 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
|
|
737
|
+
const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;
|
|
738
|
+
const criticalBelowBytes = options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;
|
|
739
|
+
const statfs = options.statfs ?? statfsSync;
|
|
740
|
+
let stats;
|
|
741
|
+
try {
|
|
742
|
+
stats = statfs(path43);
|
|
743
|
+
} catch (error) {
|
|
744
|
+
return {
|
|
745
|
+
ok: false,
|
|
746
|
+
path: path43,
|
|
747
|
+
freeBytes: 0,
|
|
748
|
+
totalBytes: 0,
|
|
749
|
+
usedPercent: 100,
|
|
750
|
+
warnBelowBytes,
|
|
751
|
+
criticalBelowBytes,
|
|
752
|
+
reason: `Windows host disk probe failed at ${path43}: ${error.message}`,
|
|
753
|
+
probeError: error.message
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
757
|
+
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
758
|
+
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
759
|
+
const lowFree = freeBytes < warnBelowBytes;
|
|
760
|
+
const criticalFree = freeBytes < criticalBelowBytes;
|
|
761
|
+
const ok = !lowFree && !criticalFree;
|
|
762
|
+
const freeGiB = (freeBytes / (1024 * 1024 * 1024)).toFixed(1);
|
|
763
|
+
let reason = null;
|
|
764
|
+
if (!ok) {
|
|
765
|
+
const tag = criticalFree ? "critical" : "warning";
|
|
766
|
+
reason = `Windows host disk ${path43} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
|
|
767
|
+
}
|
|
768
|
+
return {
|
|
769
|
+
ok,
|
|
770
|
+
path: path43,
|
|
771
|
+
freeBytes,
|
|
772
|
+
totalBytes,
|
|
773
|
+
usedPercent,
|
|
774
|
+
warnBelowBytes,
|
|
775
|
+
criticalBelowBytes,
|
|
776
|
+
reason,
|
|
777
|
+
probeError: null
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
function summarizeWslRecoverySteps() {
|
|
781
|
+
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.";
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// src/disk-gate.ts
|
|
708
785
|
var DEFAULT_WARN_FREE_BYTES = 30 * 1024 * 1024 * 1024;
|
|
709
786
|
var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
710
787
|
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
711
788
|
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
712
789
|
function observeRunnerDiskGate(input = {}) {
|
|
713
|
-
const
|
|
790
|
+
const path43 = input.diskPath?.trim() || "/";
|
|
714
791
|
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
715
792
|
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
716
793
|
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
717
794
|
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
718
|
-
const stats =
|
|
795
|
+
const stats = statfsSync2(path43);
|
|
719
796
|
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
720
797
|
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
721
798
|
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
@@ -723,19 +800,22 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
723
800
|
const criticalFree = freeBytes < criticalBelowBytes;
|
|
724
801
|
const highUse = usedPercent > maxUsedPercent;
|
|
725
802
|
const hardHighUse = usedPercent > hardMaxUsedPercent;
|
|
726
|
-
const
|
|
803
|
+
const localOk = !lowFree && !criticalFree && !highUse && !hardHighUse;
|
|
804
|
+
const wslHost = input.skipWslHostCheck ? null : observeWslHostDisk(input.wslHost);
|
|
805
|
+
const ok = localOk && (wslHost ? wslHost.ok : true);
|
|
727
806
|
let reason = null;
|
|
728
807
|
if (!ok) {
|
|
729
808
|
reason = [
|
|
730
809
|
criticalFree ? `free space below critical ${criticalBelowBytes} bytes` : null,
|
|
731
810
|
lowFree ? `free space below warning ${warnBelowBytes} bytes` : null,
|
|
732
811
|
hardHighUse ? `used percent above hard cap ${hardMaxUsedPercent}%` : null,
|
|
733
|
-
highUse ? `used percent above cap ${maxUsedPercent}%` : null
|
|
812
|
+
highUse ? `used percent above cap ${maxUsedPercent}%` : null,
|
|
813
|
+
wslHost && !wslHost.ok ? wslHost.reason : null
|
|
734
814
|
].filter(Boolean).join("; ");
|
|
735
815
|
}
|
|
736
816
|
return {
|
|
737
817
|
ok,
|
|
738
|
-
path:
|
|
818
|
+
path: path43,
|
|
739
819
|
freeBytes,
|
|
740
820
|
totalBytes,
|
|
741
821
|
usedPercent,
|
|
@@ -743,7 +823,8 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
743
823
|
criticalBelowBytes,
|
|
744
824
|
maxUsedPercent,
|
|
745
825
|
hardMaxUsedPercent,
|
|
746
|
-
reason
|
|
826
|
+
reason,
|
|
827
|
+
wslHost
|
|
747
828
|
};
|
|
748
829
|
}
|
|
749
830
|
|
|
@@ -751,7 +832,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
751
832
|
import os2 from "node:os";
|
|
752
833
|
|
|
753
834
|
// src/bounded-build/meminfo.ts
|
|
754
|
-
import { readFileSync as
|
|
835
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
755
836
|
import os from "node:os";
|
|
756
837
|
function readMemAvailableBytes(meminfoText) {
|
|
757
838
|
if (meminfoText !== void 0) {
|
|
@@ -761,7 +842,7 @@ function readMemAvailableBytes(meminfoText) {
|
|
|
761
842
|
}
|
|
762
843
|
if (process.platform === "linux") {
|
|
763
844
|
try {
|
|
764
|
-
const meminfo =
|
|
845
|
+
const meminfo = readFileSync5("/proc/meminfo", "utf8");
|
|
765
846
|
const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m);
|
|
766
847
|
if (match) return Number(match[1]) * 1024;
|
|
767
848
|
} catch {
|
|
@@ -774,11 +855,11 @@ function readMemAvailableBytes(meminfoText) {
|
|
|
774
855
|
import path7 from "node:path";
|
|
775
856
|
|
|
776
857
|
// src/run-store.ts
|
|
777
|
-
import { existsSync as
|
|
858
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2 } from "node:fs";
|
|
778
859
|
import path6 from "node:path";
|
|
779
860
|
|
|
780
861
|
// src/paths.ts
|
|
781
|
-
import { existsSync as
|
|
862
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
782
863
|
import { homedir as homedir4 } from "node:os";
|
|
783
864
|
import path5 from "node:path";
|
|
784
865
|
var LEGACY_ROOT = path5.join(homedir4(), ".openclaw", "harness");
|
|
@@ -788,8 +869,8 @@ function resolveHarnessRoot() {
|
|
|
788
869
|
const configured = loadUserConfig().harnessRoot?.trim();
|
|
789
870
|
if (configured) return resolveUserPath(configured);
|
|
790
871
|
const kynverRoot = path5.join(homedir4(), ".kynver", "harness");
|
|
791
|
-
if (
|
|
792
|
-
if (
|
|
872
|
+
if (existsSync5(kynverRoot)) return kynverRoot;
|
|
873
|
+
if (existsSync5(LEGACY_ROOT)) return LEGACY_ROOT;
|
|
793
874
|
return kynverRoot;
|
|
794
875
|
}
|
|
795
876
|
function getHarnessPaths() {
|
|
@@ -814,7 +895,7 @@ function loadRun(id) {
|
|
|
814
895
|
}
|
|
815
896
|
function listRunRecords() {
|
|
816
897
|
const { runsDir } = getPaths();
|
|
817
|
-
if (!
|
|
898
|
+
if (!existsSync6(runsDir)) return [];
|
|
818
899
|
const runs = [];
|
|
819
900
|
for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
|
|
820
901
|
if (!entry.isDirectory()) continue;
|
|
@@ -846,7 +927,7 @@ function runDirectory(id) {
|
|
|
846
927
|
}
|
|
847
928
|
|
|
848
929
|
// src/heartbeat.ts
|
|
849
|
-
import { existsSync as
|
|
930
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
|
|
850
931
|
var HEARTBEAT_FUTURE_SKEW_MS = 6e4;
|
|
851
932
|
function isTerminalHeartbeatPhase(phase) {
|
|
852
933
|
return phase === "complete";
|
|
@@ -865,10 +946,10 @@ function parseHeartbeat(file) {
|
|
|
865
946
|
heartbeatBlocker: null,
|
|
866
947
|
timestampAnomalies: []
|
|
867
948
|
};
|
|
868
|
-
if (!
|
|
949
|
+
if (!existsSync7(file)) return result;
|
|
869
950
|
const maxFutureMs = Date.now() + HEARTBEAT_FUTURE_SKEW_MS;
|
|
870
951
|
const clampedTo = new Date(maxFutureMs).toISOString();
|
|
871
|
-
const lines =
|
|
952
|
+
const lines = readFileSync6(file, "utf8").split("\n").filter(Boolean);
|
|
872
953
|
for (const line of lines) {
|
|
873
954
|
const entry = safeJson(line);
|
|
874
955
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
@@ -895,7 +976,7 @@ function parseHeartbeat(file) {
|
|
|
895
976
|
}
|
|
896
977
|
|
|
897
978
|
// src/stream.ts
|
|
898
|
-
import { existsSync as
|
|
979
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
|
|
899
980
|
|
|
900
981
|
// src/shell-command-outcome.ts
|
|
901
982
|
var NPM_AUDIT_RE = /\bnpm\s+audit\b/i;
|
|
@@ -1099,8 +1180,8 @@ function parseHarnessStream(file) {
|
|
|
1099
1180
|
error: null,
|
|
1100
1181
|
lastShellOutcome: null
|
|
1101
1182
|
};
|
|
1102
|
-
if (!
|
|
1103
|
-
const lines =
|
|
1183
|
+
if (!existsSync8(file)) return result;
|
|
1184
|
+
const lines = readFileSync7(file, "utf8").split("\n").filter(Boolean);
|
|
1104
1185
|
for (const line of lines) {
|
|
1105
1186
|
const event = safeJson(line);
|
|
1106
1187
|
if (!event) continue;
|
|
@@ -2123,7 +2204,7 @@ function hasLiveWorkerForTask(runId, taskId) {
|
|
|
2123
2204
|
}
|
|
2124
2205
|
|
|
2125
2206
|
// src/supervisor.ts
|
|
2126
|
-
import { existsSync as
|
|
2207
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync3 } from "node:fs";
|
|
2127
2208
|
import path14 from "node:path";
|
|
2128
2209
|
|
|
2129
2210
|
// src/prompt.ts
|
|
@@ -2144,7 +2225,7 @@ function buildPrompt(input) {
|
|
|
2144
2225
|
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."
|
|
2145
2226
|
];
|
|
2146
2227
|
const mergeGateLines = compact ? [
|
|
2147
|
-
"Merge-gate cost control: run `node scripts/verify-pr-local.mjs --
|
|
2228
|
+
"Merge-gate cost control: run `node scripts/agent-os-pr-merge-gate.mjs --pr <url> --agent-os-id <id>` (or `verify-pr-local.mjs --from-pr` + `collect-pr-vercel-evidence.mjs` + 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)."
|
|
2148
2229
|
] : [
|
|
2149
2230
|
"GitHub Actions merge-gate cost control (Kynver/Hermes PRs):",
|
|
2150
2231
|
"- Prefer local cached package verification (`node scripts/verify-pr-local.mjs --emit-json`) and Vercel preview evidence before GitHub Actions.",
|
|
@@ -2171,7 +2252,7 @@ function buildPrompt(input) {
|
|
|
2171
2252
|
"After each major step, append one JSON line to the heartbeat file with fields: ts, phase, summary, changedFiles, blocker.",
|
|
2172
2253
|
"Final response must include files changed, verification commands, and unresolved risks.",
|
|
2173
2254
|
"Structured final result (recommended): record completion as JSON with summary, laneExpertise { whatChanged, why, files, prUrls, verification, risks, blockers, lessonsLearned, laneGuidance }, and targetPrReconciliation [{ prUrl, outcome: merged|skipped|blocked, mergeCommit?, reason? }] for every target PR on landing-only tasks.",
|
|
2174
|
-
"Completion handoff (required): before you stop, ensure the harness records a final result \u2014 summarize outcome in your last message and append a heartbeat line with phase `complete`. If you leave uncommitted changes or committed work without a PR, the orchestrator blocks completion until a GitHub PR exists (or you discard/commit cleanly). Exiting with only dirty files and no PR routes to salvage review, not production review.",
|
|
2255
|
+
"Completion handoff (required): before you stop, ensure the harness records a final result \u2014 summarize outcome in your last message and append a heartbeat line with phase `complete`. If you leave uncommitted changes or committed work without a PR, the orchestrator blocks completion until a GitHub PR exists (or you discard/commit cleanly). One-off helper scripts must be removed (`kynver worker discard-disposable --path <file>`) or committed before completion \u2014 maintenance/board-drain workers are not exempt. Exiting with only dirty files and no PR routes to salvage review, not production review.",
|
|
2175
2256
|
"PR-ready handoff: for substantial implementation work, commit, push, and open a GitHub PR (draft OK) on your branch before finishing \u2014 or rely on the harness to run `gh pr create` at completion when `gh` is authenticated.",
|
|
2176
2257
|
"Expert review / production-review workers (Dalton/Lorentz, plan-review-task, scheduledJob reviewer children): do NOT open new implementation PRs \u2014 review the parent task's existing PR and record reviewVerdict in finalResult; landing-contract targetPrReconciliation does not apply.",
|
|
2177
2258
|
"Worker resource guard: do not run full monorepo verification (`npm run typecheck`, `npm run build`, or equivalent) from this worker lane unless an operator explicitly requests it. Use targeted checks for touched paths and rely on CI/operator lanes for heavy gates.",
|
|
@@ -2193,12 +2274,12 @@ function buildPrompt(input) {
|
|
|
2193
2274
|
}
|
|
2194
2275
|
|
|
2195
2276
|
// src/providers/cursor.ts
|
|
2196
|
-
import { closeSync as closeSync2, existsSync as
|
|
2277
|
+
import { closeSync as closeSync2, existsSync as existsSync10, openSync as openSync2 } from "node:fs";
|
|
2197
2278
|
import { spawn as spawn2 } from "node:child_process";
|
|
2198
2279
|
import path10 from "node:path";
|
|
2199
2280
|
|
|
2200
2281
|
// src/providers/cursor-windows.ts
|
|
2201
|
-
import { existsSync as
|
|
2282
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3 } from "node:fs";
|
|
2202
2283
|
import path9 from "node:path";
|
|
2203
2284
|
var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
|
|
2204
2285
|
function parseCursorVersionSortKey(versionName) {
|
|
@@ -2211,7 +2292,7 @@ function parseCursorVersionSortKey(versionName) {
|
|
|
2211
2292
|
}
|
|
2212
2293
|
function pickLatestCursorVersionDir(agentRoot) {
|
|
2213
2294
|
const versionsRoot = path9.join(agentRoot, "versions");
|
|
2214
|
-
if (!
|
|
2295
|
+
if (!existsSync9(versionsRoot)) return null;
|
|
2215
2296
|
let bestDir = null;
|
|
2216
2297
|
let bestKey = -1;
|
|
2217
2298
|
for (const entry of readdirSync3(versionsRoot, { withFileTypes: true })) {
|
|
@@ -2227,14 +2308,14 @@ function resolveWindowsCursorBundled(agentRoot) {
|
|
|
2227
2308
|
const root = agentRoot?.trim() || path9.join(process.env.LOCALAPPDATA || "", "cursor-agent");
|
|
2228
2309
|
const directNode = path9.join(root, "node.exe");
|
|
2229
2310
|
const directIndex = path9.join(root, "index.js");
|
|
2230
|
-
if (
|
|
2311
|
+
if (existsSync9(directNode) && existsSync9(directIndex)) {
|
|
2231
2312
|
return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
|
|
2232
2313
|
}
|
|
2233
2314
|
const versionDir = pickLatestCursorVersionDir(root);
|
|
2234
2315
|
if (!versionDir) return null;
|
|
2235
2316
|
const nodeExe = path9.join(versionDir, "node.exe");
|
|
2236
2317
|
const indexJs = path9.join(versionDir, "index.js");
|
|
2237
|
-
if (!
|
|
2318
|
+
if (!existsSync9(nodeExe) || !existsSync9(indexJs)) return null;
|
|
2238
2319
|
return { nodeExe, indexJs, versionDir };
|
|
2239
2320
|
}
|
|
2240
2321
|
|
|
@@ -2252,7 +2333,7 @@ function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
|
|
|
2252
2333
|
function resolveCursorSpawn(agentBin) {
|
|
2253
2334
|
if (process.platform === "win32") {
|
|
2254
2335
|
const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
|
|
2255
|
-
const isBundledNode = /node\.exe$/i.test(agentBin) &&
|
|
2336
|
+
const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync10(path10.join(path10.dirname(agentBin), "index.js"));
|
|
2256
2337
|
const isDefaultShim = agentBin === "agent";
|
|
2257
2338
|
if (isCursorWrapper || isBundledNode || isDefaultShim) {
|
|
2258
2339
|
const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path10.dirname(agentBin)) : isBundledNode ? {
|
|
@@ -2279,7 +2360,7 @@ function resolveAgentBin() {
|
|
|
2279
2360
|
);
|
|
2280
2361
|
if (bundled) return bundled.nodeExe;
|
|
2281
2362
|
const localAgent = path10.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
|
|
2282
|
-
if (
|
|
2363
|
+
if (existsSync10(localAgent)) return localAgent;
|
|
2283
2364
|
}
|
|
2284
2365
|
return "agent";
|
|
2285
2366
|
}
|
|
@@ -2362,7 +2443,7 @@ function resolveWorkerProvider(name) {
|
|
|
2362
2443
|
|
|
2363
2444
|
// src/auto-complete.ts
|
|
2364
2445
|
import { spawn as spawn3 } from "node:child_process";
|
|
2365
|
-
import { existsSync as
|
|
2446
|
+
import { existsSync as existsSync11, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
|
|
2366
2447
|
import path13 from "node:path";
|
|
2367
2448
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2368
2449
|
|
|
@@ -2861,6 +2942,133 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
|
|
|
2861
2942
|
};
|
|
2862
2943
|
}
|
|
2863
2944
|
|
|
2945
|
+
// src/material-worktree-changes.ts
|
|
2946
|
+
function materialWorktreeChanges(changedFiles) {
|
|
2947
|
+
return changedFiles.filter((line) => {
|
|
2948
|
+
const trimmed = line.trim();
|
|
2949
|
+
const pathPart = trimmed.startsWith("??") ? trimmed.slice(2).trim() : trimmed.length > 3 ? trimmed.slice(3).trim() : trimmed;
|
|
2950
|
+
return pathPart !== "node_modules" && !pathPart.startsWith("node_modules/");
|
|
2951
|
+
});
|
|
2952
|
+
}
|
|
2953
|
+
function pathFromGitStatusLine(line) {
|
|
2954
|
+
const trimmed = line.trim();
|
|
2955
|
+
if (trimmed.startsWith("??")) return trimmed.slice(2).trim();
|
|
2956
|
+
if (trimmed.length > 3 && /^[ MADRCU?!]{2} /.test(trimmed.slice(0, 3))) {
|
|
2957
|
+
return trimmed.slice(3).trim();
|
|
2958
|
+
}
|
|
2959
|
+
return trimmed;
|
|
2960
|
+
}
|
|
2961
|
+
|
|
2962
|
+
// src/disposable-artifacts.ts
|
|
2963
|
+
function stringList(value) {
|
|
2964
|
+
if (!Array.isArray(value)) return [];
|
|
2965
|
+
return value.map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
|
|
2966
|
+
}
|
|
2967
|
+
function asRecord2(value) {
|
|
2968
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2969
|
+
}
|
|
2970
|
+
function extractDisposableArtifactsRemoved(finalResult) {
|
|
2971
|
+
const record = asRecord2(finalResult);
|
|
2972
|
+
if (!record) return [];
|
|
2973
|
+
const nested = asRecord2(record.worktreeHandoff);
|
|
2974
|
+
const fromNested = stringList(nested?.disposableArtifactsRemoved);
|
|
2975
|
+
if (fromNested.length) return fromNested;
|
|
2976
|
+
return stringList(record.disposableArtifactsRemoved);
|
|
2977
|
+
}
|
|
2978
|
+
function normalizeRelativePath(value) {
|
|
2979
|
+
return value.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
|
|
2980
|
+
}
|
|
2981
|
+
function dirtyPathsCoveredByDisposableRemoval(changedFiles, removed) {
|
|
2982
|
+
const material = materialWorktreeChanges(changedFiles);
|
|
2983
|
+
if (material.length === 0) return true;
|
|
2984
|
+
if (removed.length === 0) return false;
|
|
2985
|
+
const removedSet = new Set(removed.map((p) => normalizeRelativePath(p)));
|
|
2986
|
+
return material.every((line) => {
|
|
2987
|
+
const path43 = normalizeRelativePath(pathFromGitStatusLine(line));
|
|
2988
|
+
return removedSet.has(path43);
|
|
2989
|
+
});
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
// src/worktree-completion-handoff.ts
|
|
2993
|
+
function trimOrNull5(value) {
|
|
2994
|
+
if (typeof value !== "string") return null;
|
|
2995
|
+
const t = value.trim();
|
|
2996
|
+
return t.length ? t : null;
|
|
2997
|
+
}
|
|
2998
|
+
function stringList2(value) {
|
|
2999
|
+
if (!Array.isArray(value)) return [];
|
|
3000
|
+
return value.map((item) => typeof item === "string" ? item.trim() : "").filter(Boolean);
|
|
3001
|
+
}
|
|
3002
|
+
function hasFinalResult4(value) {
|
|
3003
|
+
if (value === void 0 || value === null) return false;
|
|
3004
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
3005
|
+
if (typeof value === "boolean") return value;
|
|
3006
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
3007
|
+
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
3008
|
+
return true;
|
|
3009
|
+
}
|
|
3010
|
+
function mergedDisposableRemoved(input) {
|
|
3011
|
+
const fromWorker = stringList2(input.disposableArtifactsRemoved);
|
|
3012
|
+
const fromResult = extractDisposableArtifactsRemoved(input.finalResult);
|
|
3013
|
+
return [.../* @__PURE__ */ new Set([...fromWorker, ...fromResult])];
|
|
3014
|
+
}
|
|
3015
|
+
function assessWorktreeCompletionHandoff(input) {
|
|
3016
|
+
const rawDirty = stringList2(input.changedFiles);
|
|
3017
|
+
const materialDirty = materialWorktreeChanges(rawDirty);
|
|
3018
|
+
const removed = mergedDisposableRemoved(input);
|
|
3019
|
+
const effectivelyClean = materialDirty.length === 0 || dirtyPathsCoveredByDisposableRemoval(rawDirty, removed);
|
|
3020
|
+
if (trimOrNull5(input.prUrl)) {
|
|
3021
|
+
if (!effectivelyClean) {
|
|
3022
|
+
return {
|
|
3023
|
+
allowed: false,
|
|
3024
|
+
state: "dirty_worktree",
|
|
3025
|
+
materialDirtyCount: materialDirty.length,
|
|
3026
|
+
detail: `Worktree has ${materialDirty.length} uncommitted change(s) with a PR attached; commit or discard before completing`
|
|
3027
|
+
};
|
|
3028
|
+
}
|
|
3029
|
+
return { allowed: true, state: "pr_handoff", materialDirtyCount: 0 };
|
|
3030
|
+
}
|
|
3031
|
+
if (trimOrNull5(input.headCommit)) {
|
|
3032
|
+
if (!effectivelyClean) {
|
|
3033
|
+
return {
|
|
3034
|
+
allowed: false,
|
|
3035
|
+
state: "dirty_worktree",
|
|
3036
|
+
materialDirtyCount: materialDirty.length,
|
|
3037
|
+
detail: `Worktree has ${materialDirty.length} uncommitted change(s) on top of a commit; commit or discard before completing`
|
|
3038
|
+
};
|
|
3039
|
+
}
|
|
3040
|
+
return { allowed: true, state: "commit_handoff", materialDirtyCount: 0 };
|
|
3041
|
+
}
|
|
3042
|
+
if (trimOrNull5(input.artifactBundlePath) || trimOrNull5(input.patchPath)) {
|
|
3043
|
+
if (!effectivelyClean) {
|
|
3044
|
+
return {
|
|
3045
|
+
allowed: false,
|
|
3046
|
+
state: "dirty_worktree",
|
|
3047
|
+
materialDirtyCount: materialDirty.length,
|
|
3048
|
+
detail: `Worktree has ${materialDirty.length} uncommitted change(s) with a patch/bundle handoff; clean the tree before completing`
|
|
3049
|
+
};
|
|
3050
|
+
}
|
|
3051
|
+
return { allowed: true, state: "commit_handoff", materialDirtyCount: 0 };
|
|
3052
|
+
}
|
|
3053
|
+
if (effectivelyClean) {
|
|
3054
|
+
return { allowed: true, state: "clean", materialDirtyCount: 0 };
|
|
3055
|
+
}
|
|
3056
|
+
if (hasFinalResult4(input.finalResult)) {
|
|
3057
|
+
return {
|
|
3058
|
+
allowed: false,
|
|
3059
|
+
state: "dirty_worktree_no_pr",
|
|
3060
|
+
materialDirtyCount: materialDirty.length,
|
|
3061
|
+
detail: `Worktree has ${materialDirty.length} uncommitted change(s) with no commit or PR; commit, open a PR, discard, or remove one-off files via \`kynver worker discard-disposable\` before completing`
|
|
3062
|
+
};
|
|
3063
|
+
}
|
|
3064
|
+
return {
|
|
3065
|
+
allowed: false,
|
|
3066
|
+
state: "dirty_worktree",
|
|
3067
|
+
materialDirtyCount: materialDirty.length,
|
|
3068
|
+
detail: `Worktree has ${materialDirty.length} uncommitted change(s) with no final result`
|
|
3069
|
+
};
|
|
3070
|
+
}
|
|
3071
|
+
|
|
2864
3072
|
// src/worker-lifecycle.ts
|
|
2865
3073
|
import path11 from "node:path";
|
|
2866
3074
|
var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
|
|
@@ -2957,7 +3165,7 @@ function completionErrorText(parsed) {
|
|
|
2957
3165
|
}
|
|
2958
3166
|
return void 0;
|
|
2959
3167
|
}
|
|
2960
|
-
function
|
|
3168
|
+
function asRecord3(value) {
|
|
2961
3169
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2962
3170
|
}
|
|
2963
3171
|
function asString2(value) {
|
|
@@ -3034,26 +3242,48 @@ async function tryCompleteWorker(args) {
|
|
|
3034
3242
|
if (worker.localOnly) {
|
|
3035
3243
|
return { ok: true, skipped: true, reason: "local-only-worker" };
|
|
3036
3244
|
}
|
|
3245
|
+
const headCommit = status.gitAncestry.headIsAncestorOfBase === false && status.gitAncestry.head ? status.gitAncestry.head : status.headCommit;
|
|
3246
|
+
const handoff = assessWorktreeCompletionHandoff({
|
|
3247
|
+
changedFiles: status.changedFiles,
|
|
3248
|
+
finalResult: status.finalResult,
|
|
3249
|
+
prUrl: status.prUrl,
|
|
3250
|
+
headCommit,
|
|
3251
|
+
disposableArtifactsRemoved: worker.disposableArtifactsRemoved
|
|
3252
|
+
});
|
|
3253
|
+
if (!handoff.allowed) {
|
|
3254
|
+
const reason2 = handoff.detail ?? `worktree completion blocked (${handoff.state})`;
|
|
3255
|
+
persistCompletionBlocker(worker, reason2);
|
|
3256
|
+
return {
|
|
3257
|
+
ok: false,
|
|
3258
|
+
reason: reason2,
|
|
3259
|
+
nextAction: "Clean the worktree (commit, open a PR, or `kynver worker discard-disposable --path <file>`), then rerun `kynver worker complete`.",
|
|
3260
|
+
completionBlocked: true
|
|
3261
|
+
};
|
|
3262
|
+
}
|
|
3037
3263
|
const skipPrHandoff = args.skipPrHandoff === true || args.skipPrHandoff === "true";
|
|
3038
3264
|
if (!skipPrHandoff && worker.dispatched && taskId) {
|
|
3039
|
-
const
|
|
3040
|
-
if (!
|
|
3041
|
-
persistCompletionBlocker(worker,
|
|
3265
|
+
const handoff2 = ensurePrReadyHandoff({ worker, run, status });
|
|
3266
|
+
if (!handoff2.ok) {
|
|
3267
|
+
persistCompletionBlocker(worker, handoff2.reason);
|
|
3042
3268
|
return {
|
|
3043
3269
|
ok: false,
|
|
3044
|
-
reason:
|
|
3045
|
-
nextAction:
|
|
3270
|
+
reason: handoff2.reason,
|
|
3271
|
+
nextAction: handoff2.nextAction,
|
|
3046
3272
|
completionBlocked: true
|
|
3047
3273
|
};
|
|
3048
3274
|
}
|
|
3049
|
-
if (
|
|
3050
|
-
status = applyPrHandoffToStatus(status,
|
|
3275
|
+
if (handoff2.prUrl || handoff2.headCommit) {
|
|
3276
|
+
status = applyPrHandoffToStatus(status, handoff2);
|
|
3051
3277
|
}
|
|
3052
3278
|
}
|
|
3053
3279
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
3054
3280
|
const explicitSecret = args.secret ? String(args.secret) : void 0;
|
|
3055
3281
|
let secret = await resolveCallbackSecretWithMint(explicitSecret, agentOsId, { baseUrl: base });
|
|
3056
3282
|
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/harness/completion`;
|
|
3283
|
+
const statusPayload = { ...status };
|
|
3284
|
+
if (worker.disposableArtifactsRemoved?.length) {
|
|
3285
|
+
statusPayload.disposableArtifactsRemoved = worker.disposableArtifactsRemoved;
|
|
3286
|
+
}
|
|
3057
3287
|
const body = {
|
|
3058
3288
|
source: "kynver-harness",
|
|
3059
3289
|
agentOsId,
|
|
@@ -3062,10 +3292,11 @@ async function tryCompleteWorker(args) {
|
|
|
3062
3292
|
taskId,
|
|
3063
3293
|
startedAt: worker.startedAt,
|
|
3064
3294
|
finishedAt: status.lastActivityAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
3065
|
-
status,
|
|
3295
|
+
status: statusPayload,
|
|
3066
3296
|
workerInjection: {
|
|
3067
3297
|
instructionPolicyFingerprint: worker.instructionPolicyFingerprint ?? null,
|
|
3068
3298
|
instructionPolicyEvidence: worker.instructionPolicyEvidence ?? null,
|
|
3299
|
+
memoryQualityCapture: worker.memoryQualityCapture ?? null,
|
|
3069
3300
|
policyAt: worker.instructionPolicyEvidence && typeof worker.instructionPolicyEvidence === "object" && "policyAt" in worker.instructionPolicyEvidence && typeof worker.instructionPolicyEvidence.policyAt === "string" ? worker.instructionPolicyEvidence.policyAt : null,
|
|
3070
3301
|
personaSlug: worker.personaSlug ?? null,
|
|
3071
3302
|
personaEvidence: worker.personaEvidence ?? null
|
|
@@ -3206,8 +3437,8 @@ function buildRunBoard(runId) {
|
|
|
3206
3437
|
const completionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
|
|
3207
3438
|
const boardStatus = completionBlocker ? "blocked" : status.status;
|
|
3208
3439
|
const boardAttention = completionBlocker ? "blocked" : status.attention.state;
|
|
3209
|
-
const completionResponse =
|
|
3210
|
-
const completionTask =
|
|
3440
|
+
const completionResponse = asRecord3(worker.completionResponse);
|
|
3441
|
+
const completionTask = asRecord3(completionResponse?.task);
|
|
3211
3442
|
const completionOutcome = asString2(completionResponse?.outcome);
|
|
3212
3443
|
const completionRouteStatus = asString2(completionResponse?.status);
|
|
3213
3444
|
const completionWarnings = Array.isArray(completionResponse?.warnings) ? completionResponse.warnings.filter((w) => typeof w === "string" && w.trim().length > 0) : [];
|
|
@@ -3488,7 +3719,7 @@ function resolveDefaultCliPath() {
|
|
|
3488
3719
|
}
|
|
3489
3720
|
function spawnCompletionSidecar(opts) {
|
|
3490
3721
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath();
|
|
3491
|
-
if (!
|
|
3722
|
+
if (!existsSync11(cliPath)) return void 0;
|
|
3492
3723
|
const logPath = path13.join(opts.workerDir, "auto-complete.log");
|
|
3493
3724
|
let logFd;
|
|
3494
3725
|
try {
|
|
@@ -3575,7 +3806,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3575
3806
|
mkdirSync3(workerDir, { recursive: true });
|
|
3576
3807
|
const worktreePath = path14.join(worktreesDir, run.id, name);
|
|
3577
3808
|
const branch = opts.branch || `agent/${run.id}/${name}`;
|
|
3578
|
-
if (
|
|
3809
|
+
if (existsSync12(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
|
|
3579
3810
|
git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
|
|
3580
3811
|
git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
|
|
3581
3812
|
const stdoutPath = path14.join(workerDir, "stdout.jsonl");
|
|
@@ -3631,6 +3862,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3631
3862
|
...opts.planId ? { planId: String(opts.planId) } : {},
|
|
3632
3863
|
...opts.instructionPolicyFingerprint ? { instructionPolicyFingerprint: String(opts.instructionPolicyFingerprint) } : {},
|
|
3633
3864
|
...opts.instructionPolicyEvidence ? { instructionPolicyEvidence: opts.instructionPolicyEvidence } : {},
|
|
3865
|
+
...opts.memoryQualityCapture ? { memoryQualityCapture: opts.memoryQualityCapture } : {},
|
|
3634
3866
|
...opts.personaSlug ? { personaSlug: String(opts.personaSlug) } : {},
|
|
3635
3867
|
...opts.personaEvidence ? { personaEvidence: opts.personaEvidence } : {},
|
|
3636
3868
|
...opts.leaseOwner ? { leaseOwner: String(opts.leaseOwner) } : {},
|
|
@@ -3965,8 +4197,8 @@ function isTmpOnlyPath(filePath) {
|
|
|
3965
4197
|
|
|
3966
4198
|
// src/plan-persist/outbox-store.ts
|
|
3967
4199
|
import {
|
|
3968
|
-
existsSync as
|
|
3969
|
-
readFileSync as
|
|
4200
|
+
existsSync as existsSync14,
|
|
4201
|
+
readFileSync as readFileSync8,
|
|
3970
4202
|
renameSync,
|
|
3971
4203
|
readdirSync as readdirSync4,
|
|
3972
4204
|
writeFileSync as writeFileSync3,
|
|
@@ -3992,9 +4224,9 @@ function findOutboxByIdempotencyKey(key) {
|
|
|
3992
4224
|
return null;
|
|
3993
4225
|
}
|
|
3994
4226
|
function readOutboxItem(jsonPath) {
|
|
3995
|
-
if (!
|
|
4227
|
+
if (!existsSync14(jsonPath)) return null;
|
|
3996
4228
|
try {
|
|
3997
|
-
return JSON.parse(
|
|
4229
|
+
return JSON.parse(readFileSync8(jsonPath, "utf8"));
|
|
3998
4230
|
} catch {
|
|
3999
4231
|
return null;
|
|
4000
4232
|
}
|
|
@@ -4002,7 +4234,7 @@ function readOutboxItem(jsonPath) {
|
|
|
4002
4234
|
function readOutboxBody(item) {
|
|
4003
4235
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
4004
4236
|
const bodyFile = path16.join(outboxDir, item.bodyPath);
|
|
4005
|
-
return
|
|
4237
|
+
return readFileSync8(bodyFile, "utf8");
|
|
4006
4238
|
}
|
|
4007
4239
|
function writeOutboxItem(input, opts) {
|
|
4008
4240
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
@@ -4056,8 +4288,8 @@ function archiveOutboxItem(item) {
|
|
|
4056
4288
|
const bodySrc = path16.join(outboxDir, item.bodyPath);
|
|
4057
4289
|
const jsonDst = path16.join(archiveDir, `${item.id}.json`);
|
|
4058
4290
|
const bodyDst = path16.join(archiveDir, item.bodyPath);
|
|
4059
|
-
if (
|
|
4060
|
-
if (
|
|
4291
|
+
if (existsSync14(jsonSrc)) renameSync(jsonSrc, jsonDst);
|
|
4292
|
+
if (existsSync14(bodySrc)) renameSync(bodySrc, bodyDst);
|
|
4061
4293
|
}
|
|
4062
4294
|
function outboxItemPaths(item) {
|
|
4063
4295
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
@@ -4326,10 +4558,12 @@ function readHarnessWorkerContext(decision) {
|
|
|
4326
4558
|
const personaSlug = typeof ctx.personaEvidence === "object" && ctx.personaEvidence && typeof ctx.personaEvidence.injectedPersonaSlug === "string" ? ctx.personaEvidence.injectedPersonaSlug : null;
|
|
4327
4559
|
const personaEvidence = ctx.personaEvidence && typeof ctx.personaEvidence === "object" ? ctx.personaEvidence : null;
|
|
4328
4560
|
const personaInjectionReady = ctx.personaInjectionReady === true;
|
|
4561
|
+
const memoryQualityCapture = ctx.memoryQualityCapture && typeof ctx.memoryQualityCapture === "object" ? ctx.memoryQualityCapture : null;
|
|
4329
4562
|
return {
|
|
4330
4563
|
instructionPolicyMarkdown: markdown,
|
|
4331
4564
|
instructionPolicyFingerprint: fingerprint,
|
|
4332
4565
|
instructionPolicyEvidence: evidence,
|
|
4566
|
+
memoryQualityCapture,
|
|
4333
4567
|
personaMarkdown,
|
|
4334
4568
|
personaSlug,
|
|
4335
4569
|
personaEvidence,
|
|
@@ -4518,6 +4752,7 @@ async function dispatchRun(args) {
|
|
|
4518
4752
|
instructionPolicyMarkdown: harnessContext?.instructionPolicyMarkdown ?? null,
|
|
4519
4753
|
instructionPolicyFingerprint: harnessContext?.instructionPolicyFingerprint ?? null,
|
|
4520
4754
|
instructionPolicyEvidence: harnessContext?.instructionPolicyEvidence ?? null,
|
|
4755
|
+
memoryQualityCapture: harnessContext?.memoryQualityCapture ?? null,
|
|
4521
4756
|
personaMarkdown: harnessContext?.personaMarkdown ?? null,
|
|
4522
4757
|
personaSlug: harnessContext?.personaSlug ?? expectedPersona,
|
|
4523
4758
|
personaEvidence: harnessContext?.personaEvidence ?? null,
|
|
@@ -4648,7 +4883,7 @@ async function sweepRun(args) {
|
|
|
4648
4883
|
}
|
|
4649
4884
|
|
|
4650
4885
|
// src/worktree.ts
|
|
4651
|
-
import { existsSync as
|
|
4886
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5 } from "node:fs";
|
|
4652
4887
|
import path22 from "node:path";
|
|
4653
4888
|
|
|
4654
4889
|
// src/default-repo.ts
|
|
@@ -4721,7 +4956,7 @@ function createRun(args) {
|
|
|
4721
4956
|
ensureGitRepo(repo);
|
|
4722
4957
|
const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
|
|
4723
4958
|
const dir = runDirectory(id);
|
|
4724
|
-
if (
|
|
4959
|
+
if (existsSync15(dir)) failExists(`run already exists: ${id}`);
|
|
4725
4960
|
mkdirSync5(dir, { recursive: true });
|
|
4726
4961
|
const base = String(args.base || "origin/main");
|
|
4727
4962
|
const baseCommit = git(repo, ["rev-parse", base]).trim();
|
|
@@ -4754,8 +4989,61 @@ function failExists(message) {
|
|
|
4754
4989
|
process.exit(1);
|
|
4755
4990
|
}
|
|
4756
4991
|
|
|
4992
|
+
// src/discard-disposable.ts
|
|
4993
|
+
import { existsSync as existsSync16, rmSync } from "node:fs";
|
|
4994
|
+
import path23 from "node:path";
|
|
4995
|
+
function normalizeRelativePath2(value) {
|
|
4996
|
+
const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
4997
|
+
if (!normalized || normalized.startsWith("/") || normalized.includes("..")) {
|
|
4998
|
+
throw new Error(`unsafe path: ${value}`);
|
|
4999
|
+
}
|
|
5000
|
+
return normalized;
|
|
5001
|
+
}
|
|
5002
|
+
function parsePathsArg(raw) {
|
|
5003
|
+
if (typeof raw !== "string" || !raw.trim()) return [];
|
|
5004
|
+
return raw.split(",").map((p) => p.trim()).filter(Boolean);
|
|
5005
|
+
}
|
|
5006
|
+
function discardDisposableArtifacts(args) {
|
|
5007
|
+
const worker = loadWorker(String(args.run), String(args.name));
|
|
5008
|
+
const paths = [
|
|
5009
|
+
...parsePathsArg(args.path),
|
|
5010
|
+
...Array.isArray(args.paths) ? args.paths : []
|
|
5011
|
+
];
|
|
5012
|
+
if (paths.length === 0) {
|
|
5013
|
+
return { ok: false, removed: [], reason: "requires at least one --path" };
|
|
5014
|
+
}
|
|
5015
|
+
const worktreeRoot = path23.resolve(worker.worktreePath);
|
|
5016
|
+
const removed = [];
|
|
5017
|
+
for (const raw of paths) {
|
|
5018
|
+
const rel = normalizeRelativePath2(raw);
|
|
5019
|
+
const abs = path23.resolve(worktreeRoot, rel);
|
|
5020
|
+
if (!abs.startsWith(worktreeRoot + path23.sep) && abs !== worktreeRoot) {
|
|
5021
|
+
return { ok: false, removed, reason: `path escapes worktree: ${raw}` };
|
|
5022
|
+
}
|
|
5023
|
+
if (!existsSync16(abs)) {
|
|
5024
|
+
return { ok: false, removed, reason: `path not found: ${raw}` };
|
|
5025
|
+
}
|
|
5026
|
+
rmSync(abs, { recursive: true, force: true });
|
|
5027
|
+
removed.push(rel);
|
|
5028
|
+
}
|
|
5029
|
+
const prior = Array.isArray(worker.disposableArtifactsRemoved) ? worker.disposableArtifactsRemoved.filter((p) => typeof p === "string") : [];
|
|
5030
|
+
worker.disposableArtifactsRemoved = [.../* @__PURE__ */ new Set([...prior, ...removed])];
|
|
5031
|
+
saveWorker(worker.runId, worker);
|
|
5032
|
+
const status = computeWorkerStatus(worker);
|
|
5033
|
+
return {
|
|
5034
|
+
ok: true,
|
|
5035
|
+
removed,
|
|
5036
|
+
...status.changedFiles.length ? { reason: "worktree still has other changes" } : {}
|
|
5037
|
+
};
|
|
5038
|
+
}
|
|
5039
|
+
function discardDisposableCli(args) {
|
|
5040
|
+
const result = discardDisposableArtifacts(args);
|
|
5041
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5042
|
+
if (!result.ok) process.exit(1);
|
|
5043
|
+
}
|
|
5044
|
+
|
|
4757
5045
|
// src/pipeline-tick.ts
|
|
4758
|
-
import
|
|
5046
|
+
import path35 from "node:path";
|
|
4759
5047
|
|
|
4760
5048
|
// src/pipeline-dispatch.ts
|
|
4761
5049
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -4809,10 +5097,10 @@ async function runPipelineDispatch(args, slots) {
|
|
|
4809
5097
|
}
|
|
4810
5098
|
|
|
4811
5099
|
// src/stale-reconcile.ts
|
|
4812
|
-
import
|
|
5100
|
+
import path25 from "node:path";
|
|
4813
5101
|
|
|
4814
5102
|
// src/finalize.ts
|
|
4815
|
-
import
|
|
5103
|
+
import path24 from "node:path";
|
|
4816
5104
|
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set([
|
|
4817
5105
|
"running",
|
|
4818
5106
|
"dispatching",
|
|
@@ -4830,7 +5118,7 @@ function deriveTerminalRunStatus(run) {
|
|
|
4830
5118
|
let anyLandingBlocked = false;
|
|
4831
5119
|
for (const name of names) {
|
|
4832
5120
|
const worker = readJson(
|
|
4833
|
-
|
|
5121
|
+
path24.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4834
5122
|
void 0
|
|
4835
5123
|
);
|
|
4836
5124
|
if (!worker) continue;
|
|
@@ -4882,7 +5170,7 @@ function reconcileStaleWorkers() {
|
|
|
4882
5170
|
const now = Date.now();
|
|
4883
5171
|
for (const run of listRunRecords()) {
|
|
4884
5172
|
for (const name of Object.keys(run.workers || {})) {
|
|
4885
|
-
const workerPath =
|
|
5173
|
+
const workerPath = path25.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
4886
5174
|
const worker = readJson(workerPath, void 0);
|
|
4887
5175
|
if (!worker || worker.status !== "running") {
|
|
4888
5176
|
outcomes.push({
|
|
@@ -4976,7 +5264,7 @@ function reconcileRunsCli() {
|
|
|
4976
5264
|
}
|
|
4977
5265
|
|
|
4978
5266
|
// src/plan-progress-daemon-sync.ts
|
|
4979
|
-
import
|
|
5267
|
+
import path26 from "node:path";
|
|
4980
5268
|
|
|
4981
5269
|
// src/plan-progress-sync.ts
|
|
4982
5270
|
async function syncPlanProgress(args) {
|
|
@@ -5000,7 +5288,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
5000
5288
|
const outcomes = [];
|
|
5001
5289
|
for (const name of Object.keys(run.workers || {})) {
|
|
5002
5290
|
const worker = readJson(
|
|
5003
|
-
|
|
5291
|
+
path26.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
5004
5292
|
void 0
|
|
5005
5293
|
);
|
|
5006
5294
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -5049,7 +5337,7 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
5049
5337
|
}
|
|
5050
5338
|
|
|
5051
5339
|
// src/cleanup.ts
|
|
5052
|
-
import
|
|
5340
|
+
import path33 from "node:path";
|
|
5053
5341
|
|
|
5054
5342
|
// src/cleanup-run-liveness.ts
|
|
5055
5343
|
function isWorkerProcessLive(indexed) {
|
|
@@ -5070,6 +5358,15 @@ function runBlocksWorktreeRemoval(indexed) {
|
|
|
5070
5358
|
return deriveTerminalRunStatus(indexed.run) === null;
|
|
5071
5359
|
}
|
|
5072
5360
|
|
|
5361
|
+
// src/cleanup-guards-helpers.ts
|
|
5362
|
+
function materialWorktreeChanges2(changedFiles) {
|
|
5363
|
+
return changedFiles.filter((line) => {
|
|
5364
|
+
const trimmed = line.trim();
|
|
5365
|
+
const pathPart = trimmed.startsWith("??") ? trimmed.slice(2).trim() : trimmed.length > 3 ? trimmed.slice(3).trim() : trimmed;
|
|
5366
|
+
return pathPart !== "node_modules" && !pathPart.startsWith("node_modules/");
|
|
5367
|
+
});
|
|
5368
|
+
}
|
|
5369
|
+
|
|
5073
5370
|
// src/cleanup-guards.ts
|
|
5074
5371
|
function prUrlFromFinalResult(finalResult) {
|
|
5075
5372
|
if (typeof finalResult === "string") {
|
|
@@ -5092,29 +5389,25 @@ function isPrOrUnmergedWork(status) {
|
|
|
5092
5389
|
if (status.changedFiles.length > 0 && status.finalResult) return true;
|
|
5093
5390
|
return false;
|
|
5094
5391
|
}
|
|
5095
|
-
function materialWorktreeChanges(changedFiles) {
|
|
5096
|
-
return changedFiles.filter((line) => {
|
|
5097
|
-
const trimmed = line.trim();
|
|
5098
|
-
const pathPart = trimmed.startsWith("??") ? trimmed.slice(2).trim() : trimmed.length > 3 ? trimmed.slice(3).trim() : trimmed;
|
|
5099
|
-
return pathPart !== "node_modules" && !pathPart.startsWith("node_modules/");
|
|
5100
|
-
});
|
|
5101
|
-
}
|
|
5102
5392
|
function hasUnrestorableWorktreeChanges(status) {
|
|
5103
|
-
if (
|
|
5393
|
+
if (materialWorktreeChanges2(status.changedFiles).length > 0) return true;
|
|
5104
5394
|
if (status.gitAncestry?.relation === "diverged") return true;
|
|
5105
5395
|
return false;
|
|
5106
5396
|
}
|
|
5107
5397
|
function skipWorktreeRemoval(input) {
|
|
5108
|
-
const { indexed, includeOrphans, worktreesAgeMs, ageMs } = input;
|
|
5109
|
-
if (
|
|
5110
|
-
|
|
5111
|
-
|
|
5398
|
+
const { indexed, includeOrphans, worktreesAgeMs, ageMs, orphanSafety, worktreeRemovalGuard } = input;
|
|
5399
|
+
if (!indexed) {
|
|
5400
|
+
if (!includeOrphans) return "orphan_without_flag";
|
|
5401
|
+
return orphanSafety ?? null;
|
|
5402
|
+
}
|
|
5403
|
+
if (worktreesAgeMs <= 0 && !includeOrphans) return "worktrees_disabled";
|
|
5404
|
+
if (worktreesAgeMs > 0 && ageMs < worktreesAgeMs) return "below_age_threshold";
|
|
5112
5405
|
if (isWorkerProcessLive(indexed)) return "active_worker";
|
|
5113
5406
|
if (indexed.worker.completionBlocker) return "completion_blocked";
|
|
5114
5407
|
if (runBlocksWorktreeRemoval(indexed)) return "run_still_active";
|
|
5115
5408
|
if (!isFinishedWorkerStatus(indexed.status)) return "run_still_active";
|
|
5116
5409
|
if (isPrOrUnmergedWork(indexed.status)) return "pr_or_unmerged_commits";
|
|
5117
|
-
if (
|
|
5410
|
+
if (materialWorktreeChanges2(indexed.status.changedFiles).length > 0) return "dirty_worktree";
|
|
5118
5411
|
const landing = assessWorkerLanding({
|
|
5119
5412
|
finalResult: indexed.status.finalResult,
|
|
5120
5413
|
changedFiles: indexed.status.changedFiles,
|
|
@@ -5122,6 +5415,17 @@ function skipWorktreeRemoval(input) {
|
|
|
5122
5415
|
prUrl: prUrlFromFinalResult(indexed.status.finalResult)
|
|
5123
5416
|
});
|
|
5124
5417
|
if (landing.blocked) return "landing_blocked";
|
|
5418
|
+
if (worktreeRemovalGuard && input.worktreePath) {
|
|
5419
|
+
const overlay = worktreeRemovalGuard({
|
|
5420
|
+
worktreePath: input.worktreePath,
|
|
5421
|
+
indexed: Boolean(indexed),
|
|
5422
|
+
runId: indexed?.runId,
|
|
5423
|
+
worker: indexed?.workerName
|
|
5424
|
+
});
|
|
5425
|
+
if (overlay) {
|
|
5426
|
+
return overlay.detail ? { reason: overlay.reason, detail: overlay.detail } : overlay.reason;
|
|
5427
|
+
}
|
|
5428
|
+
}
|
|
5125
5429
|
return null;
|
|
5126
5430
|
}
|
|
5127
5431
|
function skipNodeModulesRemoval(input) {
|
|
@@ -5138,21 +5442,21 @@ function skipNodeModulesRemoval(input) {
|
|
|
5138
5442
|
gitAncestry: indexed.status.gitAncestry,
|
|
5139
5443
|
prUrl: prUrlFromFinalResult(indexed.status.finalResult)
|
|
5140
5444
|
});
|
|
5141
|
-
if (landing.blocked &&
|
|
5445
|
+
if (landing.blocked && materialWorktreeChanges2(indexed.status.changedFiles).length > 0) {
|
|
5142
5446
|
return "landing_blocked";
|
|
5143
5447
|
}
|
|
5144
5448
|
return null;
|
|
5145
5449
|
}
|
|
5146
5450
|
|
|
5147
5451
|
// src/cleanup-execute.ts
|
|
5148
|
-
import { existsSync as
|
|
5149
|
-
import
|
|
5452
|
+
import { existsSync as existsSync18, rmSync as rmSync2 } from "node:fs";
|
|
5453
|
+
import path28 from "node:path";
|
|
5150
5454
|
|
|
5151
5455
|
// src/cleanup-dir-size.ts
|
|
5152
|
-
import { existsSync as
|
|
5153
|
-
import
|
|
5456
|
+
import { existsSync as existsSync17, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
|
|
5457
|
+
import path27 from "node:path";
|
|
5154
5458
|
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
5155
|
-
if (!
|
|
5459
|
+
if (!existsSync17(root)) return 0;
|
|
5156
5460
|
let total = 0;
|
|
5157
5461
|
let seen = 0;
|
|
5158
5462
|
const stack = [root];
|
|
@@ -5166,7 +5470,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
5166
5470
|
}
|
|
5167
5471
|
for (const name of entries) {
|
|
5168
5472
|
if (seen++ > maxEntries) return null;
|
|
5169
|
-
const full =
|
|
5473
|
+
const full = path27.join(current, name);
|
|
5170
5474
|
let st;
|
|
5171
5475
|
try {
|
|
5172
5476
|
st = statSync2(full);
|
|
@@ -5182,7 +5486,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
5182
5486
|
|
|
5183
5487
|
// src/cleanup-execute.ts
|
|
5184
5488
|
function removeNodeModules(candidate, execute) {
|
|
5185
|
-
if (!
|
|
5489
|
+
if (!existsSync18(candidate.path)) {
|
|
5186
5490
|
return {
|
|
5187
5491
|
...candidate,
|
|
5188
5492
|
executed: false,
|
|
@@ -5195,7 +5499,7 @@ function removeNodeModules(candidate, execute) {
|
|
|
5195
5499
|
}
|
|
5196
5500
|
try {
|
|
5197
5501
|
const bytesBefore = candidate.bytes ?? directorySizeBytes(candidate.path);
|
|
5198
|
-
|
|
5502
|
+
rmSync2(candidate.path, { recursive: true, force: true });
|
|
5199
5503
|
return {
|
|
5200
5504
|
...candidate,
|
|
5201
5505
|
bytes: bytesBefore,
|
|
@@ -5213,7 +5517,7 @@ function removeNodeModules(candidate, execute) {
|
|
|
5213
5517
|
}
|
|
5214
5518
|
}
|
|
5215
5519
|
function removeWorktree(candidate, execute) {
|
|
5216
|
-
if (!
|
|
5520
|
+
if (!existsSync18(candidate.path)) {
|
|
5217
5521
|
return {
|
|
5218
5522
|
...candidate,
|
|
5219
5523
|
executed: false,
|
|
@@ -5230,8 +5534,8 @@ function removeWorktree(candidate, execute) {
|
|
|
5230
5534
|
if (repo) {
|
|
5231
5535
|
git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
|
|
5232
5536
|
}
|
|
5233
|
-
if (
|
|
5234
|
-
|
|
5537
|
+
if (existsSync18(candidate.path)) {
|
|
5538
|
+
rmSync2(candidate.path, { recursive: true, force: true });
|
|
5235
5539
|
}
|
|
5236
5540
|
return {
|
|
5237
5541
|
...candidate,
|
|
@@ -5250,20 +5554,20 @@ function removeWorktree(candidate, execute) {
|
|
|
5250
5554
|
}
|
|
5251
5555
|
}
|
|
5252
5556
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
5253
|
-
const resolved =
|
|
5254
|
-
const nm = resolved.endsWith(`${
|
|
5557
|
+
const resolved = path28.resolve(targetPath);
|
|
5558
|
+
const nm = resolved.endsWith(`${path28.sep}node_modules`) ? resolved : null;
|
|
5255
5559
|
if (!nm) return "path_outside_harness";
|
|
5256
|
-
const rel =
|
|
5257
|
-
if (rel.startsWith("..") ||
|
|
5258
|
-
const parts = rel.split(
|
|
5560
|
+
const rel = path28.relative(worktreesDir, nm);
|
|
5561
|
+
if (rel.startsWith("..") || path28.isAbsolute(rel)) return "path_outside_harness";
|
|
5562
|
+
const parts = rel.split(path28.sep);
|
|
5259
5563
|
if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
|
|
5260
|
-
if (!resolved.startsWith(
|
|
5564
|
+
if (!resolved.startsWith(path28.resolve(harnessRoot))) return "path_outside_harness";
|
|
5261
5565
|
return null;
|
|
5262
5566
|
}
|
|
5263
5567
|
|
|
5264
5568
|
// src/cleanup-scan.ts
|
|
5265
|
-
import { existsSync as
|
|
5266
|
-
import
|
|
5569
|
+
import { existsSync as existsSync19, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
5570
|
+
import path29 from "node:path";
|
|
5267
5571
|
function pathAgeMs(target, now) {
|
|
5268
5572
|
try {
|
|
5269
5573
|
const mtime = statSync3(target).mtimeMs;
|
|
@@ -5273,17 +5577,17 @@ function pathAgeMs(target, now) {
|
|
|
5273
5577
|
}
|
|
5274
5578
|
}
|
|
5275
5579
|
function isPathInside(child, parent) {
|
|
5276
|
-
const rel =
|
|
5277
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
5580
|
+
const rel = path29.relative(parent, child);
|
|
5581
|
+
return rel === "" || !rel.startsWith("..") && !path29.isAbsolute(rel);
|
|
5278
5582
|
}
|
|
5279
5583
|
function scanNodeModulesCandidates(opts) {
|
|
5280
5584
|
const candidates = [];
|
|
5281
5585
|
const seen = /* @__PURE__ */ new Set();
|
|
5282
5586
|
for (const entry of opts.index.values()) {
|
|
5283
5587
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
5284
|
-
const nm =
|
|
5285
|
-
if (!
|
|
5286
|
-
const resolved =
|
|
5588
|
+
const nm = path29.join(entry.worktreePath, "node_modules");
|
|
5589
|
+
if (!existsSync19(nm)) continue;
|
|
5590
|
+
const resolved = path29.resolve(nm);
|
|
5287
5591
|
if (seen.has(resolved)) continue;
|
|
5288
5592
|
seen.add(resolved);
|
|
5289
5593
|
candidates.push({
|
|
@@ -5296,16 +5600,16 @@ function scanNodeModulesCandidates(opts) {
|
|
|
5296
5600
|
ageMs: pathAgeMs(resolved, opts.now)
|
|
5297
5601
|
});
|
|
5298
5602
|
}
|
|
5299
|
-
if (!opts.includeOrphans || !
|
|
5603
|
+
if (!opts.includeOrphans || !existsSync19(opts.worktreesDir)) return candidates;
|
|
5300
5604
|
for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
|
|
5301
5605
|
if (!runEntry.isDirectory()) continue;
|
|
5302
|
-
const runPath =
|
|
5606
|
+
const runPath = path29.join(opts.worktreesDir, runEntry.name);
|
|
5303
5607
|
for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
|
|
5304
5608
|
if (!workerEntry.isDirectory()) continue;
|
|
5305
|
-
const worktreePath =
|
|
5306
|
-
const nm =
|
|
5307
|
-
if (!
|
|
5308
|
-
const resolved =
|
|
5609
|
+
const worktreePath = path29.join(runPath, workerEntry.name);
|
|
5610
|
+
const nm = path29.join(worktreePath, "node_modules");
|
|
5611
|
+
if (!existsSync19(nm)) continue;
|
|
5612
|
+
const resolved = path29.resolve(nm);
|
|
5309
5613
|
if (seen.has(resolved)) continue;
|
|
5310
5614
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
5311
5615
|
seen.add(resolved);
|
|
@@ -5322,40 +5626,76 @@ function scanNodeModulesCandidates(opts) {
|
|
|
5322
5626
|
return candidates;
|
|
5323
5627
|
}
|
|
5324
5628
|
function scanWorktreeCandidates(opts) {
|
|
5325
|
-
|
|
5629
|
+
const indexedEnabled = opts.worktreesAgeMs > 0 || opts.includeOrphans;
|
|
5630
|
+
const orphanEnabled = opts.includeOrphans;
|
|
5631
|
+
if (!indexedEnabled && !orphanEnabled) return [];
|
|
5326
5632
|
const candidates = [];
|
|
5327
5633
|
const seen = /* @__PURE__ */ new Set();
|
|
5634
|
+
if (indexedEnabled) {
|
|
5635
|
+
for (const entry of opts.index.values()) {
|
|
5636
|
+
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
5637
|
+
const resolved = entry.worktreePath;
|
|
5638
|
+
if (!existsSync19(resolved)) continue;
|
|
5639
|
+
if (seen.has(resolved)) continue;
|
|
5640
|
+
seen.add(resolved);
|
|
5641
|
+
candidates.push({
|
|
5642
|
+
kind: "remove_worktree",
|
|
5643
|
+
path: resolved,
|
|
5644
|
+
bytes: null,
|
|
5645
|
+
runId: entry.runId,
|
|
5646
|
+
worker: entry.workerName,
|
|
5647
|
+
repo: entry.run.repo,
|
|
5648
|
+
ageMs: pathAgeMs(resolved, opts.now)
|
|
5649
|
+
});
|
|
5650
|
+
}
|
|
5651
|
+
}
|
|
5652
|
+
if (!orphanEnabled || !existsSync19(opts.worktreesDir)) return candidates;
|
|
5653
|
+
const indexedPaths = /* @__PURE__ */ new Set();
|
|
5328
5654
|
for (const entry of opts.index.values()) {
|
|
5329
|
-
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
if (
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5655
|
+
indexedPaths.add(path29.resolve(entry.worktreePath));
|
|
5656
|
+
}
|
|
5657
|
+
for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
|
|
5658
|
+
if (!runEntry.isDirectory()) continue;
|
|
5659
|
+
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
5660
|
+
const runPath = path29.join(opts.worktreesDir, runEntry.name);
|
|
5661
|
+
let workerEntries;
|
|
5662
|
+
try {
|
|
5663
|
+
workerEntries = readdirSync6(runPath, { withFileTypes: true });
|
|
5664
|
+
} catch {
|
|
5665
|
+
continue;
|
|
5666
|
+
}
|
|
5667
|
+
for (const workerEntry of workerEntries) {
|
|
5668
|
+
if (!workerEntry.isDirectory()) continue;
|
|
5669
|
+
const worktreePath = path29.resolve(path29.join(runPath, workerEntry.name));
|
|
5670
|
+
if (seen.has(worktreePath)) continue;
|
|
5671
|
+
if (indexedPaths.has(worktreePath)) continue;
|
|
5672
|
+
if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
|
|
5673
|
+
seen.add(worktreePath);
|
|
5674
|
+
candidates.push({
|
|
5675
|
+
kind: "remove_worktree",
|
|
5676
|
+
path: worktreePath,
|
|
5677
|
+
bytes: null,
|
|
5678
|
+
runId: runEntry.name,
|
|
5679
|
+
worker: workerEntry.name,
|
|
5680
|
+
ageMs: pathAgeMs(worktreePath, opts.now)
|
|
5681
|
+
});
|
|
5682
|
+
}
|
|
5343
5683
|
}
|
|
5344
5684
|
return candidates;
|
|
5345
5685
|
}
|
|
5346
5686
|
|
|
5347
5687
|
// src/cleanup-worktree-index.ts
|
|
5348
|
-
import
|
|
5688
|
+
import path30 from "node:path";
|
|
5349
5689
|
function buildWorktreeIndex() {
|
|
5350
5690
|
const index = /* @__PURE__ */ new Map();
|
|
5351
5691
|
for (const run of listRunRecords()) {
|
|
5352
5692
|
for (const name of Object.keys(run.workers || {})) {
|
|
5353
|
-
const workerPath =
|
|
5693
|
+
const workerPath = path30.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
5354
5694
|
const worker = readJson(workerPath, void 0);
|
|
5355
5695
|
if (!worker?.worktreePath) continue;
|
|
5356
5696
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
5357
|
-
index.set(
|
|
5358
|
-
worktreePath:
|
|
5697
|
+
index.set(path30.resolve(worker.worktreePath), {
|
|
5698
|
+
worktreePath: path30.resolve(worker.worktreePath),
|
|
5359
5699
|
runId: run.id,
|
|
5360
5700
|
workerName: name,
|
|
5361
5701
|
run,
|
|
@@ -5413,13 +5753,143 @@ function resolvePipelineHarnessRetention(runId) {
|
|
|
5413
5753
|
});
|
|
5414
5754
|
}
|
|
5415
5755
|
|
|
5756
|
+
// src/cleanup-orphan-safety.ts
|
|
5757
|
+
import { existsSync as existsSync20, statSync as statSync4 } from "node:fs";
|
|
5758
|
+
import path31 from "node:path";
|
|
5759
|
+
var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
|
|
5760
|
+
function assessOrphanWorktreeSafety(input) {
|
|
5761
|
+
const now = input.now ?? Date.now();
|
|
5762
|
+
const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
|
|
5763
|
+
if (!existsSync20(input.worktreePath)) return null;
|
|
5764
|
+
if (input.runId && input.workerName) {
|
|
5765
|
+
const heartbeatPath = path31.join(
|
|
5766
|
+
input.harnessRoot,
|
|
5767
|
+
"runs",
|
|
5768
|
+
input.runId,
|
|
5769
|
+
"workers",
|
|
5770
|
+
input.workerName,
|
|
5771
|
+
"heartbeat.jsonl"
|
|
5772
|
+
);
|
|
5773
|
+
try {
|
|
5774
|
+
const mtime = statSync4(heartbeatPath).mtimeMs;
|
|
5775
|
+
if (now - mtime < heartbeatFreshMs) return "active_worker";
|
|
5776
|
+
} catch {
|
|
5777
|
+
}
|
|
5778
|
+
}
|
|
5779
|
+
const gitDir = path31.join(input.worktreePath, ".git");
|
|
5780
|
+
if (!existsSync20(gitDir)) return null;
|
|
5781
|
+
const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
|
|
5782
|
+
if (porcelain.status !== 0) return "pr_or_unmerged_commits";
|
|
5783
|
+
const dirtyLines = porcelain.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
5784
|
+
if (materialWorktreeChanges2(dirtyLines).length > 0) return "dirty_worktree";
|
|
5785
|
+
const upstreamAhead = gitCapture(input.worktreePath, [
|
|
5786
|
+
"rev-list",
|
|
5787
|
+
"--count",
|
|
5788
|
+
"@{u}..HEAD"
|
|
5789
|
+
]);
|
|
5790
|
+
if (upstreamAhead.status === 0) {
|
|
5791
|
+
const count = Number(upstreamAhead.stdout.trim());
|
|
5792
|
+
if (Number.isFinite(count) && count > 0) return "pr_or_unmerged_commits";
|
|
5793
|
+
}
|
|
5794
|
+
const mainAhead = gitCapture(input.worktreePath, [
|
|
5795
|
+
"rev-list",
|
|
5796
|
+
"--count",
|
|
5797
|
+
"origin/main..HEAD"
|
|
5798
|
+
]);
|
|
5799
|
+
if (mainAhead.status !== 0) {
|
|
5800
|
+
if (upstreamAhead.status !== 0) return "pr_or_unmerged_commits";
|
|
5801
|
+
return null;
|
|
5802
|
+
}
|
|
5803
|
+
const mainCount = Number(mainAhead.stdout.trim());
|
|
5804
|
+
if (Number.isFinite(mainCount) && mainCount > 0) return "pr_or_unmerged_commits";
|
|
5805
|
+
return null;
|
|
5806
|
+
}
|
|
5807
|
+
|
|
5808
|
+
// src/harness-storage-snapshot.ts
|
|
5809
|
+
import { existsSync as existsSync21, readdirSync as readdirSync7, statSync as statSync5 } from "node:fs";
|
|
5810
|
+
import path32 from "node:path";
|
|
5811
|
+
function harnessStorageSnapshot(opts = {}) {
|
|
5812
|
+
const harnessRoot = opts.harnessRoot ?? resolveHarnessRoot();
|
|
5813
|
+
const worktreesDir = path32.join(harnessRoot, "worktrees");
|
|
5814
|
+
const now = opts.now ?? Date.now();
|
|
5815
|
+
const scannedAt = new Date(now).toISOString();
|
|
5816
|
+
if (!existsSync21(worktreesDir)) {
|
|
5817
|
+
return {
|
|
5818
|
+
harnessRoot,
|
|
5819
|
+
worktreesDir,
|
|
5820
|
+
worktreesBytes: 0,
|
|
5821
|
+
runCount: 0,
|
|
5822
|
+
workerCount: 0,
|
|
5823
|
+
oldestRunAt: null,
|
|
5824
|
+
scannedAt
|
|
5825
|
+
};
|
|
5826
|
+
}
|
|
5827
|
+
let totalBytes = 0;
|
|
5828
|
+
let runCount = 0;
|
|
5829
|
+
let workerCount = 0;
|
|
5830
|
+
let oldestMs = null;
|
|
5831
|
+
let entries;
|
|
5832
|
+
try {
|
|
5833
|
+
entries = readdirSync7(worktreesDir, { withFileTypes: true });
|
|
5834
|
+
} catch {
|
|
5835
|
+
return {
|
|
5836
|
+
harnessRoot,
|
|
5837
|
+
worktreesDir,
|
|
5838
|
+
worktreesBytes: null,
|
|
5839
|
+
runCount: 0,
|
|
5840
|
+
workerCount: 0,
|
|
5841
|
+
oldestRunAt: null,
|
|
5842
|
+
scannedAt
|
|
5843
|
+
};
|
|
5844
|
+
}
|
|
5845
|
+
for (const runEntry of entries) {
|
|
5846
|
+
if (!runEntry.isDirectory()) continue;
|
|
5847
|
+
runCount += 1;
|
|
5848
|
+
const runPath = path32.join(worktreesDir, runEntry.name);
|
|
5849
|
+
try {
|
|
5850
|
+
const st = statSync5(runPath);
|
|
5851
|
+
oldestMs = oldestMs === null ? st.mtimeMs : Math.min(oldestMs, st.mtimeMs);
|
|
5852
|
+
} catch {
|
|
5853
|
+
}
|
|
5854
|
+
try {
|
|
5855
|
+
for (const workerEntry of readdirSync7(runPath, { withFileTypes: true })) {
|
|
5856
|
+
if (workerEntry.isDirectory()) workerCount += 1;
|
|
5857
|
+
}
|
|
5858
|
+
} catch {
|
|
5859
|
+
}
|
|
5860
|
+
if (totalBytes !== null && opts.perRunEntryCap !== null) {
|
|
5861
|
+
const cap = opts.perRunEntryCap ?? 5e4;
|
|
5862
|
+
if (cap <= 0) {
|
|
5863
|
+
totalBytes = null;
|
|
5864
|
+
} else {
|
|
5865
|
+
const runBytes = directorySizeBytes(runPath, cap);
|
|
5866
|
+
if (runBytes === null) totalBytes = null;
|
|
5867
|
+
else totalBytes += runBytes;
|
|
5868
|
+
}
|
|
5869
|
+
}
|
|
5870
|
+
}
|
|
5871
|
+
return {
|
|
5872
|
+
harnessRoot,
|
|
5873
|
+
worktreesDir,
|
|
5874
|
+
worktreesBytes: totalBytes,
|
|
5875
|
+
runCount,
|
|
5876
|
+
workerCount,
|
|
5877
|
+
oldestRunAt: oldestMs === null ? null : new Date(oldestMs).toISOString(),
|
|
5878
|
+
scannedAt
|
|
5879
|
+
};
|
|
5880
|
+
}
|
|
5881
|
+
|
|
5416
5882
|
// src/cleanup.ts
|
|
5417
5883
|
function resolvePaths(options = {}) {
|
|
5418
5884
|
const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
|
|
5419
|
-
const { worktreesDir } = options.harnessRoot ? { worktreesDir:
|
|
5885
|
+
const { worktreesDir } = options.harnessRoot ? { worktreesDir: path33.join(harnessRoot, "worktrees") } : getHarnessPaths();
|
|
5420
5886
|
const now = options.now ?? Date.now();
|
|
5421
5887
|
return { harnessRoot, worktreesDir, now };
|
|
5422
5888
|
}
|
|
5889
|
+
function normalizeGuardSkip(skip) {
|
|
5890
|
+
if (typeof skip === "string") return { reason: skip };
|
|
5891
|
+
return skip;
|
|
5892
|
+
}
|
|
5423
5893
|
function recordSkip(skips, pathValue, reason, detail) {
|
|
5424
5894
|
skips.push({ path: pathValue, reason, ...detail ? { detail } : {} });
|
|
5425
5895
|
}
|
|
@@ -5464,7 +5934,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
5464
5934
|
actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
|
|
5465
5935
|
continue;
|
|
5466
5936
|
}
|
|
5467
|
-
const worktreePath =
|
|
5937
|
+
const worktreePath = path33.resolve(candidate.path, "..");
|
|
5468
5938
|
const indexed = index.get(worktreePath) ?? null;
|
|
5469
5939
|
const guardReason = skipNodeModulesRemoval({
|
|
5470
5940
|
indexed,
|
|
@@ -5481,15 +5951,26 @@ function runHarnessCleanup(options = {}) {
|
|
|
5481
5951
|
}
|
|
5482
5952
|
for (const raw of scanWorktreeCandidates(scanOpts)) {
|
|
5483
5953
|
const candidate = attachCandidateBytes(raw, retention.accountBytes);
|
|
5484
|
-
const indexed = index.get(
|
|
5485
|
-
const
|
|
5954
|
+
const indexed = index.get(path33.resolve(candidate.path)) ?? null;
|
|
5955
|
+
const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
|
|
5956
|
+
worktreePath: candidate.path,
|
|
5957
|
+
harnessRoot: paths.harnessRoot,
|
|
5958
|
+
runId: candidate.runId,
|
|
5959
|
+
workerName: candidate.worker,
|
|
5960
|
+
now: paths.now
|
|
5961
|
+
});
|
|
5962
|
+
const guardSkip = skipWorktreeRemoval({
|
|
5486
5963
|
indexed,
|
|
5964
|
+
worktreePath: path33.resolve(candidate.path),
|
|
5487
5965
|
includeOrphans: retention.includeOrphans,
|
|
5488
5966
|
worktreesAgeMs: retention.worktreesAgeMs,
|
|
5489
|
-
ageMs: candidate.ageMs
|
|
5967
|
+
ageMs: candidate.ageMs,
|
|
5968
|
+
orphanSafety,
|
|
5969
|
+
worktreeRemovalGuard: options.worktreeRemovalGuard
|
|
5490
5970
|
});
|
|
5491
|
-
if (
|
|
5492
|
-
|
|
5971
|
+
if (guardSkip) {
|
|
5972
|
+
const { reason: guardReason, detail: guardDetail } = normalizeGuardSkip(guardSkip);
|
|
5973
|
+
recordSkip(skips, candidate.path, guardReason, guardDetail);
|
|
5493
5974
|
actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
|
|
5494
5975
|
continue;
|
|
5495
5976
|
}
|
|
@@ -5511,6 +5992,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
5511
5992
|
if (action.skipReason === "dry_run" && action.bytes) reclaimableBytes += action.bytes;
|
|
5512
5993
|
}
|
|
5513
5994
|
}
|
|
5995
|
+
const storage = retention.accountBytes ? harnessStorageSnapshot({ harnessRoot: paths.harnessRoot, now: paths.now }) : void 0;
|
|
5514
5996
|
return {
|
|
5515
5997
|
harnessRoot: paths.harnessRoot,
|
|
5516
5998
|
dryRun: !retention.execute,
|
|
@@ -5529,7 +6011,8 @@ function runHarnessCleanup(options = {}) {
|
|
|
5529
6011
|
removedPaths,
|
|
5530
6012
|
skippedPaths,
|
|
5531
6013
|
skipReasons: tallySkipReasons(actions, skips)
|
|
5532
|
-
}
|
|
6014
|
+
},
|
|
6015
|
+
...storage ? { storage } : {}
|
|
5533
6016
|
};
|
|
5534
6017
|
}
|
|
5535
6018
|
function runPipelineHarnessCleanup(runId) {
|
|
@@ -5551,7 +6034,7 @@ function isPipelineCleanupEnabled() {
|
|
|
5551
6034
|
// src/installed-package-versions.ts
|
|
5552
6035
|
import { readFile } from "node:fs/promises";
|
|
5553
6036
|
import { homedir as homedir6 } from "node:os";
|
|
5554
|
-
import
|
|
6037
|
+
import path34 from "node:path";
|
|
5555
6038
|
var MANAGED_PACKAGES = [
|
|
5556
6039
|
"@kynver-app/runtime",
|
|
5557
6040
|
"@kynver-app/openclaw-agent-os",
|
|
@@ -5566,12 +6049,12 @@ function unique(values) {
|
|
|
5566
6049
|
}
|
|
5567
6050
|
function moduleRoots() {
|
|
5568
6051
|
const home = homedir6();
|
|
5569
|
-
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ??
|
|
5570
|
-
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ?
|
|
6052
|
+
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path34.join(home, ".openclaw", "npm");
|
|
6053
|
+
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path34.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path34.join(home, ".npm-global", "lib", "node_modules"));
|
|
5571
6054
|
return unique([
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot :
|
|
6055
|
+
path34.join(openClawPrefix, "lib", "node_modules"),
|
|
6056
|
+
path34.join(openClawPrefix, "node_modules"),
|
|
6057
|
+
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path34.join(npmGlobalRoot, "lib", "node_modules")
|
|
5575
6058
|
]);
|
|
5576
6059
|
}
|
|
5577
6060
|
async function readVersion(packageJsonPath) {
|
|
@@ -5587,7 +6070,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
|
|
|
5587
6070
|
const out = {};
|
|
5588
6071
|
for (const packageName of MANAGED_PACKAGES) {
|
|
5589
6072
|
for (const root of roots) {
|
|
5590
|
-
const packageJsonPath =
|
|
6073
|
+
const packageJsonPath = path34.join(root, packageName, "package.json");
|
|
5591
6074
|
const version = await readVersion(packageJsonPath);
|
|
5592
6075
|
if (!version) continue;
|
|
5593
6076
|
out[packageName] = { version, observedAt, path: packageJsonPath };
|
|
@@ -5603,7 +6086,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
5603
6086
|
const outcomes = [];
|
|
5604
6087
|
for (const name of Object.keys(run.workers || {})) {
|
|
5605
6088
|
const worker = readJson(
|
|
5606
|
-
|
|
6089
|
+
path35.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
5607
6090
|
void 0
|
|
5608
6091
|
);
|
|
5609
6092
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -5745,7 +6228,7 @@ async function runDaemon(args) {
|
|
|
5745
6228
|
}
|
|
5746
6229
|
|
|
5747
6230
|
// src/plan-progress.ts
|
|
5748
|
-
import
|
|
6231
|
+
import path36 from "node:path";
|
|
5749
6232
|
|
|
5750
6233
|
// src/bounded-build/constants.ts
|
|
5751
6234
|
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
@@ -6031,7 +6514,7 @@ async function emitPlanProgress(args) {
|
|
|
6031
6514
|
}
|
|
6032
6515
|
function verifyPlanLocal(args) {
|
|
6033
6516
|
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
6034
|
-
const cwd =
|
|
6517
|
+
const cwd = path36.resolve(worktree);
|
|
6035
6518
|
const summary = runHarnessVerifyCommands(cwd);
|
|
6036
6519
|
const emitJson = args.json === true || args.json === "true";
|
|
6037
6520
|
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
@@ -6080,9 +6563,9 @@ async function verifyPlan(args) {
|
|
|
6080
6563
|
}
|
|
6081
6564
|
|
|
6082
6565
|
// src/harness-verify-cli.ts
|
|
6083
|
-
import
|
|
6566
|
+
import path37 from "node:path";
|
|
6084
6567
|
function runHarnessVerifyCli(args) {
|
|
6085
|
-
const cwd =
|
|
6568
|
+
const cwd = path37.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
6086
6569
|
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
6087
6570
|
const commands = [];
|
|
6088
6571
|
const rawCmd = args.command;
|
|
@@ -6126,7 +6609,7 @@ function runHarnessVerifyCli(args) {
|
|
|
6126
6609
|
}
|
|
6127
6610
|
|
|
6128
6611
|
// src/plan-persist-cli.ts
|
|
6129
|
-
import { readFileSync as
|
|
6612
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
6130
6613
|
var OPERATIONS = ["create", "add_version", "update_metadata"];
|
|
6131
6614
|
var FAILURE_KINDS = [
|
|
6132
6615
|
"approval_guard",
|
|
@@ -6138,7 +6621,7 @@ var FAILURE_KINDS = [
|
|
|
6138
6621
|
function readBodyArg(args) {
|
|
6139
6622
|
const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
|
|
6140
6623
|
if (bodyFile) {
|
|
6141
|
-
return { body:
|
|
6624
|
+
return { body: readFileSync9(bodyFile, "utf8"), bodyPathHint: bodyFile };
|
|
6142
6625
|
}
|
|
6143
6626
|
const inline = args.body ? String(args.body) : void 0;
|
|
6144
6627
|
if (inline) return { body: inline };
|
|
@@ -6228,7 +6711,7 @@ function runCleanupCli(args) {
|
|
|
6228
6711
|
}
|
|
6229
6712
|
|
|
6230
6713
|
// src/monitor/monitor.service.ts
|
|
6231
|
-
import
|
|
6714
|
+
import path39 from "node:path";
|
|
6232
6715
|
|
|
6233
6716
|
// src/monitor/monitor.classify.ts
|
|
6234
6717
|
function expectedLeaseOwner(runId) {
|
|
@@ -6284,11 +6767,11 @@ function classifyWorkerHealth(input) {
|
|
|
6284
6767
|
}
|
|
6285
6768
|
|
|
6286
6769
|
// src/monitor/monitor.store.ts
|
|
6287
|
-
import { existsSync as
|
|
6288
|
-
import
|
|
6770
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync6, readdirSync as readdirSync8, unlinkSync as unlinkSync2 } from "node:fs";
|
|
6771
|
+
import path38 from "node:path";
|
|
6289
6772
|
function monitorsDir() {
|
|
6290
6773
|
const { harnessRoot } = getHarnessPaths();
|
|
6291
|
-
const dir =
|
|
6774
|
+
const dir = path38.join(harnessRoot, "monitors");
|
|
6292
6775
|
mkdirSync6(dir, { recursive: true });
|
|
6293
6776
|
return dir;
|
|
6294
6777
|
}
|
|
@@ -6296,7 +6779,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
6296
6779
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
6297
6780
|
}
|
|
6298
6781
|
function monitorPath(monitorId) {
|
|
6299
|
-
return
|
|
6782
|
+
return path38.join(monitorsDir(), `${monitorId}.json`);
|
|
6300
6783
|
}
|
|
6301
6784
|
function loadMonitorSession(monitorId) {
|
|
6302
6785
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -6306,18 +6789,18 @@ function saveMonitorSession(session) {
|
|
|
6306
6789
|
}
|
|
6307
6790
|
function deleteMonitorSession(monitorId) {
|
|
6308
6791
|
const file = monitorPath(monitorId);
|
|
6309
|
-
if (!
|
|
6792
|
+
if (!existsSync22(file)) return false;
|
|
6310
6793
|
unlinkSync2(file);
|
|
6311
6794
|
return true;
|
|
6312
6795
|
}
|
|
6313
6796
|
function listMonitorSessions() {
|
|
6314
6797
|
const dir = monitorsDir();
|
|
6315
|
-
if (!
|
|
6798
|
+
if (!existsSync22(dir)) return [];
|
|
6316
6799
|
const entries = [];
|
|
6317
|
-
for (const name of
|
|
6800
|
+
for (const name of readdirSync8(dir)) {
|
|
6318
6801
|
if (!name.endsWith(".json")) continue;
|
|
6319
6802
|
const session = readJson(
|
|
6320
|
-
|
|
6803
|
+
path38.join(dir, name),
|
|
6321
6804
|
void 0
|
|
6322
6805
|
);
|
|
6323
6806
|
if (!session?.monitorId) continue;
|
|
@@ -6408,7 +6891,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
6408
6891
|
// src/monitor/monitor.service.ts
|
|
6409
6892
|
function workerRecord2(runId, name) {
|
|
6410
6893
|
return readJson(
|
|
6411
|
-
|
|
6894
|
+
path39.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
6412
6895
|
void 0
|
|
6413
6896
|
);
|
|
6414
6897
|
}
|
|
@@ -6610,18 +7093,18 @@ async function runMonitorLoop(args) {
|
|
|
6610
7093
|
|
|
6611
7094
|
// src/monitor/monitor-spawn.ts
|
|
6612
7095
|
import { spawn as spawn4 } from "node:child_process";
|
|
6613
|
-
import { closeSync as closeSync4, existsSync as
|
|
6614
|
-
import
|
|
7096
|
+
import { closeSync as closeSync4, existsSync as existsSync23, openSync as openSync4 } from "node:fs";
|
|
7097
|
+
import path40 from "node:path";
|
|
6615
7098
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
6616
7099
|
function resolveDefaultCliPath2() {
|
|
6617
|
-
return
|
|
7100
|
+
return path40.join(fileURLToPath3(new URL(".", import.meta.url)), "cli.js");
|
|
6618
7101
|
}
|
|
6619
7102
|
function spawnMonitorSidecar(opts) {
|
|
6620
7103
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
6621
|
-
if (!
|
|
7104
|
+
if (!existsSync23(cliPath)) return void 0;
|
|
6622
7105
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
6623
7106
|
const { harnessRoot } = getHarnessPaths();
|
|
6624
|
-
const logPath =
|
|
7107
|
+
const logPath = path40.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
6625
7108
|
let logFd;
|
|
6626
7109
|
try {
|
|
6627
7110
|
logFd = openSync4(logPath, "a");
|
|
@@ -6741,13 +7224,13 @@ async function monitorTickCli(args) {
|
|
|
6741
7224
|
}
|
|
6742
7225
|
|
|
6743
7226
|
// src/package-version.ts
|
|
6744
|
-
import { existsSync as
|
|
7227
|
+
import { existsSync as existsSync24, readFileSync as readFileSync10 } from "node:fs";
|
|
6745
7228
|
import { dirname, join } from "node:path";
|
|
6746
7229
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
6747
7230
|
function resolvePackageRoot(moduleUrl) {
|
|
6748
7231
|
let dir = dirname(fileURLToPath4(moduleUrl));
|
|
6749
7232
|
for (let depth = 0; depth < 6; depth += 1) {
|
|
6750
|
-
if (
|
|
7233
|
+
if (existsSync24(join(dir, "package.json"))) return dir;
|
|
6751
7234
|
const parent = dirname(dir);
|
|
6752
7235
|
if (parent === dir) break;
|
|
6753
7236
|
dir = parent;
|
|
@@ -6756,7 +7239,7 @@ function resolvePackageRoot(moduleUrl) {
|
|
|
6756
7239
|
}
|
|
6757
7240
|
function readOwnPackageVersion(moduleUrl = import.meta.url) {
|
|
6758
7241
|
const pkgPath = join(resolvePackageRoot(moduleUrl), "package.json");
|
|
6759
|
-
const pkg = JSON.parse(
|
|
7242
|
+
const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
|
|
6760
7243
|
if (typeof pkg.version !== "string" || !pkg.version.trim()) {
|
|
6761
7244
|
throw new Error(`Missing package.json version at ${pkgPath}`);
|
|
6762
7245
|
}
|
|
@@ -6777,12 +7260,12 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
|
|
|
6777
7260
|
}
|
|
6778
7261
|
|
|
6779
7262
|
// src/doctor/runtime-takeover.ts
|
|
6780
|
-
import
|
|
7263
|
+
import path42 from "node:path";
|
|
6781
7264
|
|
|
6782
7265
|
// src/doctor/runtime-takeover.probes.ts
|
|
6783
|
-
import { accessSync, constants, existsSync as
|
|
7266
|
+
import { accessSync, constants, existsSync as existsSync25, readFileSync as readFileSync11 } from "node:fs";
|
|
6784
7267
|
import { homedir as homedir7 } from "node:os";
|
|
6785
|
-
import
|
|
7268
|
+
import path41 from "node:path";
|
|
6786
7269
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
6787
7270
|
function captureCommand(bin, args) {
|
|
6788
7271
|
try {
|
|
@@ -6811,7 +7294,7 @@ function tokenPrefix(token) {
|
|
|
6811
7294
|
return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
|
|
6812
7295
|
}
|
|
6813
7296
|
function isWritable(target) {
|
|
6814
|
-
if (!
|
|
7297
|
+
if (!existsSync25(target)) return false;
|
|
6815
7298
|
try {
|
|
6816
7299
|
accessSync(target, constants.W_OK);
|
|
6817
7300
|
return true;
|
|
@@ -6824,15 +7307,15 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
6824
7307
|
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
6825
7308
|
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
6826
7309
|
loadConfig: () => loadUserConfig(),
|
|
6827
|
-
configFilePath: () =>
|
|
6828
|
-
credentialsFilePath: () =>
|
|
7310
|
+
configFilePath: () => path41.join(homedir7(), ".kynver", "config.json"),
|
|
7311
|
+
credentialsFilePath: () => path41.join(homedir7(), ".kynver", "credentials"),
|
|
6829
7312
|
readCredentials: () => {
|
|
6830
|
-
const credPath =
|
|
6831
|
-
if (!
|
|
7313
|
+
const credPath = path41.join(homedir7(), ".kynver", "credentials");
|
|
7314
|
+
if (!existsSync25(credPath)) {
|
|
6832
7315
|
return { hasApiKey: false };
|
|
6833
7316
|
}
|
|
6834
7317
|
try {
|
|
6835
|
-
const parsed = JSON.parse(
|
|
7318
|
+
const parsed = JSON.parse(readFileSync11(credPath, "utf8"));
|
|
6836
7319
|
return {
|
|
6837
7320
|
hasApiKey: Boolean(parsed.apiKey?.trim()),
|
|
6838
7321
|
runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
|
|
@@ -6851,6 +7334,7 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
6851
7334
|
kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
|
|
6852
7335
|
opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
|
|
6853
7336
|
kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0,
|
|
7337
|
+
openclawCronStorePath: Boolean(process.env.OPENCLAW_CRON_STORE_PATH?.trim()),
|
|
6854
7338
|
qstashTokenPresent: Boolean(process.env.QSTASH_TOKEN?.trim()),
|
|
6855
7339
|
kynverHostedDeployment: (() => {
|
|
6856
7340
|
const v = process.env.KYNVER_HOSTED_DEPLOYMENT?.trim().toLowerCase();
|
|
@@ -6858,18 +7342,59 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
6858
7342
|
})()
|
|
6859
7343
|
}),
|
|
6860
7344
|
harnessRoot: () => resolveHarnessRoot(),
|
|
6861
|
-
legacyOpenclawHarnessRoot: () =>
|
|
6862
|
-
pathExists: (target) =>
|
|
7345
|
+
legacyOpenclawHarnessRoot: () => path41.join(homedir7(), ".openclaw", "harness"),
|
|
7346
|
+
pathExists: (target) => existsSync25(target),
|
|
6863
7347
|
pathWritable: (target) => isWritable(target),
|
|
6864
7348
|
vercelVersion: () => captureCommand("vercel", ["--version"]),
|
|
6865
7349
|
vercelWhoami: () => captureCommand("vercel", ["whoami"])
|
|
6866
7350
|
};
|
|
6867
7351
|
|
|
6868
7352
|
// src/doctor/runtime-takeover-scheduler.ts
|
|
7353
|
+
function hasLocalOpenClawDependency(env, ctx) {
|
|
7354
|
+
return env.kynverSchedulerProvider === "kynver-cron" || env.kynverSchedulerProvider === "openclaw-cron" || ctx.deploymentSchedulerProvider === "kynver-cron" || ctx.deploymentSchedulerProvider === "openclaw-cron" || Boolean(env.openclawCronStorePath);
|
|
7355
|
+
}
|
|
7356
|
+
function hasQstashCutover(env, ctx) {
|
|
7357
|
+
return env.kynverSchedulerProvider === "qstash" || ctx.deploymentSchedulerProvider === "qstash";
|
|
7358
|
+
}
|
|
6869
7359
|
function check(partial) {
|
|
6870
7360
|
return partial;
|
|
6871
7361
|
}
|
|
6872
7362
|
function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
7363
|
+
const schedulerDetails = {
|
|
7364
|
+
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
7365
|
+
deploymentSchedulerProvider: ctx.deploymentSchedulerProvider ?? null,
|
|
7366
|
+
openclawCronStorePath: Boolean(env.openclawCronStorePath)
|
|
7367
|
+
};
|
|
7368
|
+
if (hasQstashCutover(env, ctx) && !hasLocalOpenClawDependency(env, ctx)) {
|
|
7369
|
+
const source = env.kynverSchedulerProvider === "qstash" ? "KYNVER_SCHEDULER_PROVIDER=qstash on this host" : "deploymentSchedulerProvider=qstash in ~/.kynver/config.json";
|
|
7370
|
+
return check({
|
|
7371
|
+
id: "hotspot_openclaw_scheduler",
|
|
7372
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
7373
|
+
status: "pass",
|
|
7374
|
+
summary: `AgentOS scheduler cut over to QStash (${source})`,
|
|
7375
|
+
details: schedulerDetails
|
|
7376
|
+
});
|
|
7377
|
+
}
|
|
7378
|
+
if (hasLocalOpenClawDependency(env, ctx)) {
|
|
7379
|
+
const parts = [];
|
|
7380
|
+
if (env.kynverSchedulerProvider === "openclaw-cron") {
|
|
7381
|
+
parts.push("KYNVER_SCHEDULER_PROVIDER=openclaw-cron");
|
|
7382
|
+
}
|
|
7383
|
+
if (ctx.deploymentSchedulerProvider === "openclaw-cron") {
|
|
7384
|
+
parts.push("deploymentSchedulerProvider=openclaw-cron in config");
|
|
7385
|
+
}
|
|
7386
|
+
if (env.openclawCronStorePath) {
|
|
7387
|
+
parts.push("OPENCLAW_CRON_STORE_PATH set (local cron bridge)");
|
|
7388
|
+
}
|
|
7389
|
+
return check({
|
|
7390
|
+
id: "hotspot_openclaw_scheduler",
|
|
7391
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
7392
|
+
status: "warn",
|
|
7393
|
+
summary: `OpenClaw local cron still active (${parts.join("; ")})`,
|
|
7394
|
+
remediation: "On the Kynver deployment: set KYNVER_SCHEDULER_PROVIDER=qstash with QSTASH_TOKEN configured. On user runners: unset KYNVER_SCHEDULER_PROVIDER and OPENCLAW_CRON_STORE_PATH; set deploymentSchedulerProvider to qstash in ~/.kynver/config.json after Vercel env is updated.",
|
|
7395
|
+
details: schedulerDetails
|
|
7396
|
+
});
|
|
7397
|
+
}
|
|
6873
7398
|
const runnerOpenclaw = env.kynverSchedulerProvider === "openclaw-cron";
|
|
6874
7399
|
const runnerQstash = env.kynverSchedulerProvider === "qstash";
|
|
6875
7400
|
const hostedDeployment = Boolean(env.qstashTokenPresent) || Boolean(env.kynverHostedDeployment);
|
|
@@ -7146,8 +7671,8 @@ function assessVercelCli(probes) {
|
|
|
7146
7671
|
}
|
|
7147
7672
|
function assessHarnessDirs(probes) {
|
|
7148
7673
|
const harnessRoot = probes.harnessRoot();
|
|
7149
|
-
const runsDir =
|
|
7150
|
-
const worktreesDir =
|
|
7674
|
+
const runsDir = path42.join(harnessRoot, "runs");
|
|
7675
|
+
const worktreesDir = path42.join(harnessRoot, "worktrees");
|
|
7151
7676
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
7152
7677
|
const displayRunsDir = redactHomePath(runsDir);
|
|
7153
7678
|
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
@@ -7256,7 +7781,8 @@ function assessOpenclawHotspots(probes) {
|
|
|
7256
7781
|
assessRuntimeTakeoverScheduler(env, {
|
|
7257
7782
|
agentOsId: targetAgentOsId ?? null,
|
|
7258
7783
|
apiBaseUrl: config.apiBaseUrl?.trim() ?? env.kynverApiUrl ?? null,
|
|
7259
|
-
hasScopedRunnerToken
|
|
7784
|
+
hasScopedRunnerToken,
|
|
7785
|
+
deploymentSchedulerProvider: config.deploymentSchedulerProvider === "qstash" || config.deploymentSchedulerProvider === "kynver-cron" || config.deploymentSchedulerProvider === "openclaw-cron" ? config.deploymentSchedulerProvider : void 0
|
|
7260
7786
|
}),
|
|
7261
7787
|
check2({
|
|
7262
7788
|
id: "hotspot_lease_source_names",
|
|
@@ -7362,6 +7888,7 @@ function usage(code = 0) {
|
|
|
7362
7888
|
" kynver worker tail --run RUN_ID --name worker [--lines 40] [--raw]",
|
|
7363
7889
|
" kynver worker stop --run RUN_ID --name worker",
|
|
7364
7890
|
" kynver worker complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--task-id TASK_ID] [--base-url URL] [--secret SECRET]",
|
|
7891
|
+
" kynver worker discard-disposable --run RUN_ID --name worker --path scripts/helper.mjs[,other]",
|
|
7365
7892
|
" kynver worker auto-complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--poll-ms 5000] [--max-total-ms 21600000] [--complete-attempts 3] [--complete-backoff-ms 5000] [--base-url URL] [--secret SECRET]",
|
|
7366
7893
|
" kynver run reconcile",
|
|
7367
7894
|
" kynver plan progress --plan PLAN_ID --row ROW_KEY --role ROLE --status STATUS [--task TASK_ID] [--note NOTE] [--evidence type:value] [--agent-os-id AOS_ID]",
|
|
@@ -7371,6 +7898,7 @@ function usage(code = 0) {
|
|
|
7371
7898
|
" kynver plan outbox list",
|
|
7372
7899
|
" kynver plan outbox drain [--max N] [--id OUTBOX_ID]",
|
|
7373
7900
|
" kynver cleanup [--execute] [--node-modules-age-ms MS] [--worktrees-age-ms MS] [--harness-root PATH] [--include-orphans] [--skip-finalize]",
|
|
7901
|
+
" --include-orphans also scans whole worktree directories (<harnessRoot>/worktrees/<runId>/<workerId>/) that no run/worker.json references; orphans pass salvage gates (recent heartbeat, dirty git, ahead of origin/main) before removal.",
|
|
7374
7902
|
" kynver monitor start --run RUN_ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS]",
|
|
7375
7903
|
" kynver monitor status [--run RUN_ID] [--name worker] [--tick]",
|
|
7376
7904
|
" kynver monitor stop --run RUN_ID [--name worker]",
|
|
@@ -7431,6 +7959,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
7431
7959
|
if (scope === "worker" && action === "tail") return tailWorker(args);
|
|
7432
7960
|
if (scope === "worker" && action === "stop") return stopWorker(args);
|
|
7433
7961
|
if (scope === "worker" && action === "complete") return void await completeWorker(args);
|
|
7962
|
+
if (scope === "worker" && action === "discard-disposable") return discardDisposableCli(args);
|
|
7434
7963
|
if (scope === "worker" && action === "auto-complete") return void await autoCompleteWorkerCli(args);
|
|
7435
7964
|
if (scope === "monitor" && action === "start") {
|
|
7436
7965
|
const result = await startMonitorCli(args);
|