@kynver-app/runtime 0.1.76 → 0.1.79
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/cleanup-types.d.ts +2 -0
- package/dist/cli.js +1176 -319
- package/dist/cli.js.map +4 -4
- package/dist/completion-replay.d.ts +10 -0
- package/dist/cron/cron-env.d.ts +21 -0
- package/dist/cron/cron-fire.d.ts +8 -0
- package/dist/cron/cron-lock.d.ts +9 -0
- package/dist/cron/cron-match.d.ts +8 -0
- package/dist/cron/cron-status.d.ts +10 -0
- package/dist/cron/cron-store.d.ts +3 -0
- package/dist/cron/cron-tick-cli.d.ts +2 -0
- package/dist/cron/cron-tick-state.d.ts +5 -0
- package/dist/cron/cron-tick.d.ts +17 -0
- package/dist/cron/cron-types.d.ts +44 -0
- package/dist/dispatch.d.ts +1 -0
- package/dist/doctor/runtime-takeover-scheduler.d.ts +3 -1
- package/dist/doctor/runtime-takeover.probes.d.ts +2 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1213 -335
- package/dist/index.js.map +4 -4
- package/dist/pipeline-dispatch.d.ts +1 -0
- package/dist/pipeline-exact-targets.d.ts +2 -0
- package/dist/pipeline-max-starts.d.ts +8 -1
- package/dist/pipeline-tick.d.ts +2 -0
- package/dist/prompt.d.ts +2 -0
- package/dist/run-metadata-retention.d.ts +23 -0
- package/dist/scheduler-cutover.d.ts +4 -1
- package/dist/status.d.ts +5 -0
- package/dist/supervisor.d.ts +1 -0
- package/dist/worker-metadata-reconcile.d.ts +2 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -793,23 +793,23 @@ function isWslHost() {
|
|
|
793
793
|
function observeWslHostDisk(options = {}) {
|
|
794
794
|
const wsl = options.forceWsl === void 0 ? isWslHost() : options.forceWsl;
|
|
795
795
|
if (!wsl) return null;
|
|
796
|
-
const
|
|
796
|
+
const path63 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
|
|
797
797
|
const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;
|
|
798
798
|
const criticalBelowBytes = options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;
|
|
799
799
|
const statfs = options.statfs ?? statfsSync;
|
|
800
800
|
let stats;
|
|
801
801
|
try {
|
|
802
|
-
stats = statfs(
|
|
802
|
+
stats = statfs(path63);
|
|
803
803
|
} catch (error) {
|
|
804
804
|
return {
|
|
805
805
|
ok: false,
|
|
806
|
-
path:
|
|
806
|
+
path: path63,
|
|
807
807
|
freeBytes: 0,
|
|
808
808
|
totalBytes: 0,
|
|
809
809
|
usedPercent: 100,
|
|
810
810
|
warnBelowBytes,
|
|
811
811
|
criticalBelowBytes,
|
|
812
|
-
reason: `Windows host disk probe failed at ${
|
|
812
|
+
reason: `Windows host disk probe failed at ${path63}: ${error.message}`,
|
|
813
813
|
probeError: error.message
|
|
814
814
|
};
|
|
815
815
|
}
|
|
@@ -823,11 +823,11 @@ function observeWslHostDisk(options = {}) {
|
|
|
823
823
|
let reason = null;
|
|
824
824
|
if (!ok) {
|
|
825
825
|
const tag = criticalFree ? "critical" : "warning";
|
|
826
|
-
reason = `Windows host disk ${
|
|
826
|
+
reason = `Windows host disk ${path63} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
|
|
827
827
|
}
|
|
828
828
|
return {
|
|
829
829
|
ok,
|
|
830
|
-
path:
|
|
830
|
+
path: path63,
|
|
831
831
|
freeBytes,
|
|
832
832
|
totalBytes,
|
|
833
833
|
usedPercent,
|
|
@@ -847,12 +847,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
|
847
847
|
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
848
848
|
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
849
849
|
function observeRunnerDiskGate(input = {}) {
|
|
850
|
-
const
|
|
850
|
+
const path63 = input.diskPath?.trim() || "/";
|
|
851
851
|
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
852
852
|
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
853
853
|
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
854
854
|
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
855
|
-
const stats = statfsSync2(
|
|
855
|
+
const stats = statfsSync2(path63);
|
|
856
856
|
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
857
857
|
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
858
858
|
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
@@ -875,7 +875,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
875
875
|
}
|
|
876
876
|
return {
|
|
877
877
|
ok,
|
|
878
|
-
path:
|
|
878
|
+
path: path63,
|
|
879
879
|
freeBytes,
|
|
880
880
|
totalBytes,
|
|
881
881
|
usedPercent,
|
|
@@ -1863,7 +1863,9 @@ function computeWorkerStatus(worker, options = {}) {
|
|
|
1863
1863
|
finalResult,
|
|
1864
1864
|
error,
|
|
1865
1865
|
changedFiles,
|
|
1866
|
-
gitAncestry
|
|
1866
|
+
gitAncestry,
|
|
1867
|
+
instructionPolicyFingerprint: worker.instructionPolicyFingerprint ?? null,
|
|
1868
|
+
instructionPolicyEvidence: worker.instructionPolicyEvidence ?? null
|
|
1867
1869
|
};
|
|
1868
1870
|
}
|
|
1869
1871
|
function isFinishedWorkerStatus(status) {
|
|
@@ -2648,7 +2650,9 @@ function resolveOrchestrationRouting(input) {
|
|
|
2648
2650
|
const risk = classifyOrchestrationRisk(task);
|
|
2649
2651
|
const inventory = resolveInventory({
|
|
2650
2652
|
inventory: input.inventory,
|
|
2651
|
-
|
|
2653
|
+
// When callers pass a pre-built inventory (tests, CC snapshots), do not probe live
|
|
2654
|
+
// Hermes/Codex bindings — that would overwrite mocked oauth_local with subscription_hermes.
|
|
2655
|
+
codexBinding: input.codexBinding ?? (input.inventory ? null : resolveCodexOrchestrationAdapter())
|
|
2652
2656
|
});
|
|
2653
2657
|
const explicit = input.preferLowCost === false ? null : input.explicitProvider?.trim().toLowerCase();
|
|
2654
2658
|
const explicitModel = input.explicitModel?.trim() || void 0;
|
|
@@ -3462,6 +3466,7 @@ function buildPrompt(input) {
|
|
|
3462
3466
|
"",
|
|
3463
3467
|
...input.personaMarkdown?.trim() ? [input.personaMarkdown.trim(), ""] : [],
|
|
3464
3468
|
...input.instructionPolicyMarkdown?.trim() ? ["Operating rules (Lane A \u2014 from AgentOS memory policy):", input.instructionPolicyMarkdown.trim(), ""] : [],
|
|
3469
|
+
...input.memoryQualityMarkdown?.trim() ? [input.memoryQualityMarkdown.trim(), ""] : [],
|
|
3465
3470
|
...input.repairTargetPrUrl ? [
|
|
3466
3471
|
...repairTargetPromptLines({
|
|
3467
3472
|
targetPrUrl: input.repairTargetPrUrl,
|
|
@@ -3670,6 +3675,21 @@ function persistCompletionAck(worker, runId, fields) {
|
|
|
3670
3675
|
saveWorker(runId, worker);
|
|
3671
3676
|
}
|
|
3672
3677
|
|
|
3678
|
+
// src/completion-replay.ts
|
|
3679
|
+
function trimBlocker(value) {
|
|
3680
|
+
if (typeof value !== "string") return null;
|
|
3681
|
+
const trimmed = value.trim();
|
|
3682
|
+
return trimmed.length ? trimmed : null;
|
|
3683
|
+
}
|
|
3684
|
+
function shouldReplayHarnessCompletion(worker) {
|
|
3685
|
+
if (trimBlocker(worker.completionBlocker)) return true;
|
|
3686
|
+
if (worker.completionOutcome === "rejected") return true;
|
|
3687
|
+
return false;
|
|
3688
|
+
}
|
|
3689
|
+
function hasTerminalCompletionAck(worker) {
|
|
3690
|
+
return hasCompletionAck(worker) && !shouldReplayHarnessCompletion(worker);
|
|
3691
|
+
}
|
|
3692
|
+
|
|
3673
3693
|
// src/worker-ops.ts
|
|
3674
3694
|
import path17 from "node:path";
|
|
3675
3695
|
|
|
@@ -4241,8 +4261,8 @@ function dirtyPathsCoveredByDisposableRemoval(changedFiles, removed) {
|
|
|
4241
4261
|
if (removed.length === 0) return false;
|
|
4242
4262
|
const removedSet = new Set(removed.map((p) => normalizeRelativePath(p)));
|
|
4243
4263
|
return material.every((line) => {
|
|
4244
|
-
const
|
|
4245
|
-
return removedSet.has(
|
|
4264
|
+
const path63 = normalizeRelativePath(pathFromGitStatusLine(line));
|
|
4265
|
+
return removedSet.has(path63);
|
|
4246
4266
|
});
|
|
4247
4267
|
}
|
|
4248
4268
|
|
|
@@ -4488,7 +4508,7 @@ async function tryCompleteWorker(args) {
|
|
|
4488
4508
|
return { ok: true, skipped: true, reason: "worker-not-finished" };
|
|
4489
4509
|
}
|
|
4490
4510
|
const forceReplay = args.force === true || args.force === "true";
|
|
4491
|
-
if (!forceReplay &&
|
|
4511
|
+
if (!forceReplay && hasTerminalCompletionAck(worker)) {
|
|
4492
4512
|
return {
|
|
4493
4513
|
ok: true,
|
|
4494
4514
|
skipped: true,
|
|
@@ -4664,7 +4684,9 @@ async function completeWorker(args) {
|
|
|
4664
4684
|
}
|
|
4665
4685
|
}
|
|
4666
4686
|
function workerStatus(args) {
|
|
4667
|
-
const
|
|
4687
|
+
const runId = required(typeof args.run === "string" ? args.run : "", "--run");
|
|
4688
|
+
const name = required(typeof args.name === "string" ? args.name : "", "--name");
|
|
4689
|
+
const worker = loadWorker(runId, name);
|
|
4668
4690
|
const run = loadRun(worker.runId);
|
|
4669
4691
|
const status = computeWorkerStatus(worker, workerStatusOptions(run));
|
|
4670
4692
|
writeJson(path17.join(worker.workerDir, "last-status.json"), status);
|
|
@@ -4880,7 +4902,7 @@ async function autoCompleteWorker(raw) {
|
|
|
4880
4902
|
reason: "worker has no agentOsId/taskId \u2014 nothing to attribute completion to"
|
|
4881
4903
|
};
|
|
4882
4904
|
}
|
|
4883
|
-
if (
|
|
4905
|
+
if (hasTerminalCompletionAck(worker)) {
|
|
4884
4906
|
return {
|
|
4885
4907
|
worker: worker.name,
|
|
4886
4908
|
runId: worker.runId,
|
|
@@ -4893,7 +4915,7 @@ async function autoCompleteWorker(raw) {
|
|
|
4893
4915
|
const startMs = Date.now();
|
|
4894
4916
|
while (true) {
|
|
4895
4917
|
worker = loadWorker(args.run, args.name);
|
|
4896
|
-
if (
|
|
4918
|
+
if (hasTerminalCompletionAck(worker)) {
|
|
4897
4919
|
return {
|
|
4898
4920
|
worker: worker.name,
|
|
4899
4921
|
runId: worker.runId,
|
|
@@ -5099,6 +5121,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
5099
5121
|
planId: opts.planId,
|
|
5100
5122
|
taskId: opts.taskId,
|
|
5101
5123
|
instructionPolicyMarkdown: opts.instructionPolicyMarkdown,
|
|
5124
|
+
memoryQualityMarkdown: opts.memoryQualityPromptMarkdown ?? opts.memoryQualityCapture?.promptMarkdown ?? null,
|
|
5102
5125
|
personaMarkdown: opts.personaMarkdown,
|
|
5103
5126
|
model: launchModel,
|
|
5104
5127
|
repairTargetPrUrl: opts.repairTargetPrUrl,
|
|
@@ -5877,11 +5900,13 @@ function readHarnessWorkerContext(decision) {
|
|
|
5877
5900
|
const personaEvidence = ctx.personaEvidence && typeof ctx.personaEvidence === "object" ? ctx.personaEvidence : null;
|
|
5878
5901
|
const personaInjectionReady = ctx.personaInjectionReady === true;
|
|
5879
5902
|
const memoryQualityCapture = ctx.memoryQualityCapture && typeof ctx.memoryQualityCapture === "object" ? ctx.memoryQualityCapture : null;
|
|
5903
|
+
const memoryQualityPromptMarkdown = typeof ctx.memoryQualityPromptMarkdown === "string" ? ctx.memoryQualityPromptMarkdown : typeof memoryQualityCapture?.promptMarkdown === "string" ? memoryQualityCapture.promptMarkdown : null;
|
|
5880
5904
|
return {
|
|
5881
5905
|
instructionPolicyMarkdown: markdown,
|
|
5882
5906
|
instructionPolicyFingerprint: fingerprint,
|
|
5883
5907
|
instructionPolicyEvidence: evidence,
|
|
5884
5908
|
memoryQualityCapture,
|
|
5909
|
+
memoryQualityPromptMarkdown,
|
|
5885
5910
|
personaMarkdown,
|
|
5886
5911
|
personaSlug,
|
|
5887
5912
|
personaEvidence,
|
|
@@ -6138,6 +6163,7 @@ async function dispatchRun(args) {
|
|
|
6138
6163
|
instructionPolicyFingerprint: harnessContext?.instructionPolicyFingerprint ?? null,
|
|
6139
6164
|
instructionPolicyEvidence: harnessContext?.instructionPolicyEvidence ?? null,
|
|
6140
6165
|
memoryQualityCapture: harnessContext?.memoryQualityCapture ?? null,
|
|
6166
|
+
memoryQualityPromptMarkdown: harnessContext?.memoryQualityPromptMarkdown ?? null,
|
|
6141
6167
|
personaMarkdown: harnessContext?.personaMarkdown ?? null,
|
|
6142
6168
|
personaSlug: harnessContext?.personaSlug ?? expectedPersona,
|
|
6143
6169
|
personaEvidence: harnessContext?.personaEvidence ?? null,
|
|
@@ -6192,7 +6218,23 @@ async function dispatchRun(args) {
|
|
|
6192
6218
|
for (const decision of result.started) {
|
|
6193
6219
|
shouldContinueDispatch = await spawnClaimed(decision) && shouldContinueDispatch;
|
|
6194
6220
|
}
|
|
6195
|
-
skipped.push(
|
|
6221
|
+
skipped.push(
|
|
6222
|
+
...result.skipped ?? []
|
|
6223
|
+
);
|
|
6224
|
+
if (exactTargetMode) {
|
|
6225
|
+
for (const skipDecision of skipped) {
|
|
6226
|
+
const taskId = String(skipDecision.task.id);
|
|
6227
|
+
if (!exactTargetIds.has(taskId)) continue;
|
|
6228
|
+
outcomes.push({
|
|
6229
|
+
taskId,
|
|
6230
|
+
started: false,
|
|
6231
|
+
error: `exact_target_not_started:${skipDecision.skipReason}`,
|
|
6232
|
+
skipReason: skipDecision.skipReason,
|
|
6233
|
+
detail: skipDecision.reason ?? null,
|
|
6234
|
+
requestedTargetTaskIds: [...exactTargetIds]
|
|
6235
|
+
});
|
|
6236
|
+
}
|
|
6237
|
+
}
|
|
6196
6238
|
if (exactTargetMode) shouldContinueDispatch = false;
|
|
6197
6239
|
while (shouldContinueDispatch && outcomes.length < cappedStarts) {
|
|
6198
6240
|
if (exactTargetMode) break;
|
|
@@ -6708,15 +6750,15 @@ function validateTailLines(lines) {
|
|
|
6708
6750
|
}
|
|
6709
6751
|
|
|
6710
6752
|
// src/worktree.ts
|
|
6711
|
-
import { existsSync as
|
|
6712
|
-
import
|
|
6753
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync5 } from "node:fs";
|
|
6754
|
+
import path33 from "node:path";
|
|
6713
6755
|
|
|
6714
6756
|
// src/run-list.ts
|
|
6715
|
-
import { existsSync as
|
|
6716
|
-
import
|
|
6757
|
+
import { existsSync as existsSync25, readFileSync as readFileSync12 } from "node:fs";
|
|
6758
|
+
import path32 from "node:path";
|
|
6717
6759
|
|
|
6718
6760
|
// src/stale-reconcile.ts
|
|
6719
|
-
import
|
|
6761
|
+
import path31 from "node:path";
|
|
6720
6762
|
|
|
6721
6763
|
// src/finalize.ts
|
|
6722
6764
|
import path26 from "node:path";
|
|
@@ -6777,8 +6819,8 @@ function finalizeStaleRuns() {
|
|
|
6777
6819
|
}
|
|
6778
6820
|
|
|
6779
6821
|
// src/worker-metadata-reconcile.ts
|
|
6780
|
-
import { existsSync as
|
|
6781
|
-
import
|
|
6822
|
+
import { existsSync as existsSync24, lstatSync, readdirSync as readdirSync6, readlinkSync, renameSync as renameSync2, rmSync } from "node:fs";
|
|
6823
|
+
import path30 from "node:path";
|
|
6782
6824
|
|
|
6783
6825
|
// src/worker-metadata-paths.ts
|
|
6784
6826
|
import path27 from "node:path";
|
|
@@ -6833,6 +6875,219 @@ function resolveWorkerJsonPath(input) {
|
|
|
6833
6875
|
return canonical;
|
|
6834
6876
|
}
|
|
6835
6877
|
|
|
6878
|
+
// src/run-metadata-retention.ts
|
|
6879
|
+
import { existsSync as existsSync23, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
|
|
6880
|
+
import path29 from "node:path";
|
|
6881
|
+
|
|
6882
|
+
// src/default-repo.ts
|
|
6883
|
+
import path28 from "node:path";
|
|
6884
|
+
function expandConfiguredRepo(value) {
|
|
6885
|
+
return path28.resolve(resolveUserPath(value.trim()));
|
|
6886
|
+
}
|
|
6887
|
+
function fromConfigured(value, source, persistedInConfig) {
|
|
6888
|
+
const trimmed = value?.trim();
|
|
6889
|
+
if (!trimmed) return null;
|
|
6890
|
+
return {
|
|
6891
|
+
repo: expandConfiguredRepo(trimmed),
|
|
6892
|
+
source,
|
|
6893
|
+
persistedInConfig
|
|
6894
|
+
};
|
|
6895
|
+
}
|
|
6896
|
+
function resolveDefaultRepo(opts = {}) {
|
|
6897
|
+
const env = opts.env ?? process.env;
|
|
6898
|
+
const config = opts.config ?? loadUserConfig();
|
|
6899
|
+
const fromConfig = fromConfigured(config.defaultRepo, "config", true);
|
|
6900
|
+
if (fromConfig) return fromConfig;
|
|
6901
|
+
const fromDefaultEnv = fromConfigured(env.KYNVER_DEFAULT_REPO, "env_default_repo", false);
|
|
6902
|
+
if (fromDefaultEnv) return fromDefaultEnv;
|
|
6903
|
+
const fromHarnessEnv = fromConfigured(env.KYNVER_HARNESS_REPO, "env_harness_repo", false);
|
|
6904
|
+
if (fromHarnessEnv) return fromHarnessEnv;
|
|
6905
|
+
const discovered = discoverDefaultRepo({
|
|
6906
|
+
cwd: opts.cwd,
|
|
6907
|
+
runtimeModuleUrl: opts.runtimeModuleUrl
|
|
6908
|
+
});
|
|
6909
|
+
if (!discovered) return null;
|
|
6910
|
+
return {
|
|
6911
|
+
repo: discovered.repo,
|
|
6912
|
+
source: discovered.source,
|
|
6913
|
+
persistedInConfig: false
|
|
6914
|
+
};
|
|
6915
|
+
}
|
|
6916
|
+
function persistDefaultRepo(repo, existing) {
|
|
6917
|
+
const config = {
|
|
6918
|
+
...existing ?? loadUserConfig(),
|
|
6919
|
+
defaultRepo: redactHomePath(path28.resolve(repo))
|
|
6920
|
+
};
|
|
6921
|
+
saveUserConfig(config);
|
|
6922
|
+
return config;
|
|
6923
|
+
}
|
|
6924
|
+
function remediateDefaultRepo(opts) {
|
|
6925
|
+
const existing = opts?.config ?? loadUserConfig();
|
|
6926
|
+
const resolved = resolveDefaultRepo({ ...opts, config: existing });
|
|
6927
|
+
if (!resolved) {
|
|
6928
|
+
return {
|
|
6929
|
+
ok: false,
|
|
6930
|
+
reason: "No Kynver git checkout found. Clone the repo, cd into it, then run `kynver setup --repo /path/to/Kynver` (or export KYNVER_DEFAULT_REPO)."
|
|
6931
|
+
};
|
|
6932
|
+
}
|
|
6933
|
+
if (resolved.persistedInConfig) {
|
|
6934
|
+
return { ok: true, resolved, config: existing };
|
|
6935
|
+
}
|
|
6936
|
+
const config = persistDefaultRepo(resolved.repo, existing);
|
|
6937
|
+
return {
|
|
6938
|
+
ok: true,
|
|
6939
|
+
resolved: { ...resolved, persistedInConfig: true, source: "config" },
|
|
6940
|
+
config
|
|
6941
|
+
};
|
|
6942
|
+
}
|
|
6943
|
+
function formatResolvedDefaultRepo(resolved) {
|
|
6944
|
+
return {
|
|
6945
|
+
defaultRepo: displayUserPath(resolved.repo),
|
|
6946
|
+
source: resolved.source,
|
|
6947
|
+
persistedInConfig: resolved.persistedInConfig
|
|
6948
|
+
};
|
|
6949
|
+
}
|
|
6950
|
+
|
|
6951
|
+
// src/run-metadata-retention.ts
|
|
6952
|
+
var RUN_METADATA_ACTIVE_SIGNAL_MS = 15 * 60 * 1e3;
|
|
6953
|
+
function isHarnessRunMetadataPath(targetPath, harnessRoot) {
|
|
6954
|
+
const resolved = path29.resolve(targetPath);
|
|
6955
|
+
const runsDir = path29.resolve(harnessRunsDir(harnessRoot));
|
|
6956
|
+
const rel = path29.relative(runsDir, resolved);
|
|
6957
|
+
return rel !== ".." && !rel.startsWith("..") && !path29.isAbsolute(rel);
|
|
6958
|
+
}
|
|
6959
|
+
function listRunDirIds(runsDir) {
|
|
6960
|
+
if (!existsSync23(runsDir)) return [];
|
|
6961
|
+
try {
|
|
6962
|
+
return readdirSync5(runsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name !== "runs").map((entry) => entry.name);
|
|
6963
|
+
} catch {
|
|
6964
|
+
return [];
|
|
6965
|
+
}
|
|
6966
|
+
}
|
|
6967
|
+
function listWorkerNamesOnDisk(runDir2) {
|
|
6968
|
+
const workersDir = path29.join(runDir2, "workers");
|
|
6969
|
+
if (!existsSync23(workersDir)) return [];
|
|
6970
|
+
try {
|
|
6971
|
+
return readdirSync5(workersDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
6972
|
+
} catch {
|
|
6973
|
+
return [];
|
|
6974
|
+
}
|
|
6975
|
+
}
|
|
6976
|
+
function pathRecentlyTouched(target, now, windowMs) {
|
|
6977
|
+
if (!existsSync23(target)) return false;
|
|
6978
|
+
try {
|
|
6979
|
+
const age = now - statSync4(target).mtimeMs;
|
|
6980
|
+
return Number.isFinite(age) && age >= 0 && age < windowMs;
|
|
6981
|
+
} catch {
|
|
6982
|
+
return false;
|
|
6983
|
+
}
|
|
6984
|
+
}
|
|
6985
|
+
function workerDirHasActiveRetentionSignals(workerDir, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
|
|
6986
|
+
if (!existsSync23(workerDir)) return false;
|
|
6987
|
+
const artifacts = workerArtifactPaths(workerDir);
|
|
6988
|
+
const worker = readJson(
|
|
6989
|
+
artifacts.workerJsonPath,
|
|
6990
|
+
void 0
|
|
6991
|
+
);
|
|
6992
|
+
if (worker?.status === "running" && isPidAlive(worker.pid)) return true;
|
|
6993
|
+
const heartbeat = parseHeartbeat(artifacts.heartbeatPath);
|
|
6994
|
+
if (heartbeat.lastHeartbeatAt) {
|
|
6995
|
+
const age = now - Date.parse(heartbeat.lastHeartbeatAt);
|
|
6996
|
+
if (Number.isFinite(age) && age >= 0 && age < windowMs) return true;
|
|
6997
|
+
}
|
|
6998
|
+
if (pathRecentlyTouched(artifacts.stdoutPath, now, windowMs)) return true;
|
|
6999
|
+
if (pathRecentlyTouched(artifacts.heartbeatPath, now, windowMs)) return true;
|
|
7000
|
+
return false;
|
|
7001
|
+
}
|
|
7002
|
+
function runDirHasActiveRetentionSignals(runDir2, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
|
|
7003
|
+
for (const name of listWorkerNamesOnDisk(runDir2)) {
|
|
7004
|
+
if (workerDirHasActiveRetentionSignals(path29.join(runDir2, "workers", name), now, windowMs)) {
|
|
7005
|
+
return true;
|
|
7006
|
+
}
|
|
7007
|
+
}
|
|
7008
|
+
return false;
|
|
7009
|
+
}
|
|
7010
|
+
function inferRepoFields() {
|
|
7011
|
+
const resolved = resolveDefaultRepo();
|
|
7012
|
+
return {
|
|
7013
|
+
repo: resolved?.repo ?? "",
|
|
7014
|
+
base: "origin/main",
|
|
7015
|
+
baseCommit: "unknown"
|
|
7016
|
+
};
|
|
7017
|
+
}
|
|
7018
|
+
function synthesizeRunFromDisk(harnessRoot, runId) {
|
|
7019
|
+
const runDir2 = path29.join(harnessRunsDir(harnessRoot), safeSlug(runId));
|
|
7020
|
+
if (!existsSync23(runDir2)) return null;
|
|
7021
|
+
const workerNames = listWorkerNamesOnDisk(runDir2);
|
|
7022
|
+
if (workerNames.length === 0) return null;
|
|
7023
|
+
let createdAt;
|
|
7024
|
+
try {
|
|
7025
|
+
createdAt = statSync4(runDir2).birthtime.toISOString();
|
|
7026
|
+
} catch {
|
|
7027
|
+
createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7028
|
+
}
|
|
7029
|
+
const workers = {};
|
|
7030
|
+
for (const name of workerNames) {
|
|
7031
|
+
const canonicalDir = canonicalWorkerDir(harnessRoot, runId, name);
|
|
7032
|
+
workers[name] = {
|
|
7033
|
+
workerDir: canonicalDir,
|
|
7034
|
+
statusPath: path29.join(canonicalDir, "worker.json")
|
|
7035
|
+
};
|
|
7036
|
+
}
|
|
7037
|
+
const hasActive = runDirHasActiveRetentionSignals(runDir2);
|
|
7038
|
+
return {
|
|
7039
|
+
id: runId,
|
|
7040
|
+
name: runId,
|
|
7041
|
+
...inferRepoFields(),
|
|
7042
|
+
status: hasActive ? "running" : "needs_attention",
|
|
7043
|
+
createdAt,
|
|
7044
|
+
workers
|
|
7045
|
+
};
|
|
7046
|
+
}
|
|
7047
|
+
function repairMissingRunMetadata(harnessRootInput) {
|
|
7048
|
+
const harnessRoot = normalizeHarnessRoot(harnessRootInput ?? resolveHarnessRoot());
|
|
7049
|
+
const runsDir = harnessRunsDir(harnessRoot);
|
|
7050
|
+
const outcomes = [];
|
|
7051
|
+
for (const runId of listRunDirIds(runsDir)) {
|
|
7052
|
+
const runJsonPath = path29.join(runsDir, runId, "run.json");
|
|
7053
|
+
if (existsSync23(runJsonPath)) {
|
|
7054
|
+
outcomes.push({
|
|
7055
|
+
runId,
|
|
7056
|
+
action: "skipped",
|
|
7057
|
+
reason: "run.json present"
|
|
7058
|
+
});
|
|
7059
|
+
continue;
|
|
7060
|
+
}
|
|
7061
|
+
const synthesized = synthesizeRunFromDisk(harnessRoot, runId);
|
|
7062
|
+
if (!synthesized) {
|
|
7063
|
+
outcomes.push({
|
|
7064
|
+
runId,
|
|
7065
|
+
action: "skipped",
|
|
7066
|
+
reason: "no worker dirs on disk"
|
|
7067
|
+
});
|
|
7068
|
+
continue;
|
|
7069
|
+
}
|
|
7070
|
+
saveRun(synthesized);
|
|
7071
|
+
outcomes.push({
|
|
7072
|
+
runId,
|
|
7073
|
+
action: "repaired_run_json",
|
|
7074
|
+
reason: runDirHasActiveRetentionSignals(path29.join(runsDir, runId)) ? "synthesized run.json while worker artifacts still active" : "synthesized run.json from orphan worker metadata dirs"
|
|
7075
|
+
});
|
|
7076
|
+
}
|
|
7077
|
+
return { runs: outcomes };
|
|
7078
|
+
}
|
|
7079
|
+
function collectFilesystemLiveRunKeys(harnessRoot, now = Date.now()) {
|
|
7080
|
+
const keys = /* @__PURE__ */ new Set();
|
|
7081
|
+
const runsDir = harnessRunsDir(harnessRoot);
|
|
7082
|
+
for (const runId of listRunDirIds(runsDir)) {
|
|
7083
|
+
const runDir2 = path29.join(runsDir, runId);
|
|
7084
|
+
if (runDirHasActiveRetentionSignals(runDir2, now)) {
|
|
7085
|
+
keys.add(`${harnessRoot}\0${runId}`);
|
|
7086
|
+
}
|
|
7087
|
+
}
|
|
7088
|
+
return keys;
|
|
7089
|
+
}
|
|
7090
|
+
|
|
6836
7091
|
// src/worker-metadata-reconcile.ts
|
|
6837
7092
|
function materializeSymlinkedRunDir(harnessRoot, runId) {
|
|
6838
7093
|
const canonical = canonicalRunDir(harnessRoot, runId);
|
|
@@ -6843,7 +7098,10 @@ function materializeSymlinkedRunDir(harnessRoot, runId) {
|
|
|
6843
7098
|
return null;
|
|
6844
7099
|
}
|
|
6845
7100
|
if (!stat.isSymbolicLink()) return null;
|
|
6846
|
-
const linkedTarget =
|
|
7101
|
+
const linkedTarget = path30.resolve(path30.dirname(canonical), readlinkSync(canonical));
|
|
7102
|
+
if (runDirHasActiveRetentionSignals(linkedTarget) || runDirHasActiveRetentionSignals(canonical)) {
|
|
7103
|
+
return null;
|
|
7104
|
+
}
|
|
6847
7105
|
const staging = `${canonical}.materialize-${Date.now()}`;
|
|
6848
7106
|
renameSync2(linkedTarget, staging);
|
|
6849
7107
|
rmSync(canonical);
|
|
@@ -6853,9 +7111,9 @@ function materializeSymlinkedRunDir(harnessRoot, runId) {
|
|
|
6853
7111
|
function relocateArtifacts(input) {
|
|
6854
7112
|
const moved = [];
|
|
6855
7113
|
for (const fileName of workerArtifactFileNames()) {
|
|
6856
|
-
const from =
|
|
6857
|
-
const to =
|
|
6858
|
-
if (!
|
|
7114
|
+
const from = path30.join(input.fromDir, fileName);
|
|
7115
|
+
const to = path30.join(input.toDir, fileName);
|
|
7116
|
+
if (!existsSync24(from) || existsSync24(to)) continue;
|
|
6859
7117
|
renameSync2(from, to);
|
|
6860
7118
|
moved.push(fileName);
|
|
6861
7119
|
}
|
|
@@ -6894,8 +7152,8 @@ function deriveSynthesizedStatus(input) {
|
|
|
6894
7152
|
function synthesizeWorkerFromArtifacts(input) {
|
|
6895
7153
|
const artifacts = workerArtifactPaths(input.canonicalDir);
|
|
6896
7154
|
const lastStatus = readJson(artifacts.lastStatusPath, void 0);
|
|
6897
|
-
const stdoutExists =
|
|
6898
|
-
const heartbeatExists =
|
|
7155
|
+
const stdoutExists = existsSync24(artifacts.stdoutPath);
|
|
7156
|
+
const heartbeatExists = existsSync24(artifacts.heartbeatPath);
|
|
6899
7157
|
const hasArtifacts = stdoutExists || heartbeatExists || lastStatus != null;
|
|
6900
7158
|
if (!hasArtifacts) return null;
|
|
6901
7159
|
const parsedStdout = stdoutExists ? parseHarnessStream(artifacts.stdoutPath) : { finalResult: null };
|
|
@@ -6911,7 +7169,7 @@ function synthesizeWorkerFromArtifacts(input) {
|
|
|
6911
7169
|
runId: input.run.id,
|
|
6912
7170
|
status,
|
|
6913
7171
|
branch: typeof lastStatus?.branch === "string" ? lastStatus.branch : `agent/${input.run.id}/${input.workerName}`,
|
|
6914
|
-
worktreePath: typeof lastStatus?.worktreePath === "string" ? lastStatus.worktreePath :
|
|
7172
|
+
worktreePath: typeof lastStatus?.worktreePath === "string" ? lastStatus.worktreePath : path30.join(getPaths().worktreesDir, input.run.id, input.workerName),
|
|
6915
7173
|
workerDir: input.canonicalDir,
|
|
6916
7174
|
stdoutPath: artifacts.stdoutPath,
|
|
6917
7175
|
stderrPath: artifacts.stderrPath,
|
|
@@ -6938,7 +7196,7 @@ function repairRunWorkerIndex(run, harnessRoot) {
|
|
|
6938
7196
|
const nextWorkers = { ...run.workers || {} };
|
|
6939
7197
|
for (const [name, ref] of Object.entries(nextWorkers)) {
|
|
6940
7198
|
const canonicalDir = canonicalWorkerDir(harnessRoot, run.id, name);
|
|
6941
|
-
const canonicalStatus =
|
|
7199
|
+
const canonicalStatus = path30.join(canonicalDir, "worker.json");
|
|
6942
7200
|
const nested = hasNestedRunsSegment(ref.workerDir) || hasNestedRunsSegment(ref.statusPath) || ref.workerDir !== canonicalDir || ref.statusPath !== canonicalStatus;
|
|
6943
7201
|
if (!nested) continue;
|
|
6944
7202
|
nextWorkers[name] = { workerDir: canonicalDir, statusPath: canonicalStatus };
|
|
@@ -6971,7 +7229,7 @@ function reconcileOneWorker(input) {
|
|
|
6971
7229
|
statusPath: input.statusPath
|
|
6972
7230
|
});
|
|
6973
7231
|
let worker = readJson(workerJsonPath, void 0);
|
|
6974
|
-
if (!worker &&
|
|
7232
|
+
if (!worker && existsSync24(canonicalArtifacts.workerJsonPath)) {
|
|
6975
7233
|
worker = readJson(canonicalArtifacts.workerJsonPath, void 0);
|
|
6976
7234
|
}
|
|
6977
7235
|
if (!worker) {
|
|
@@ -6991,15 +7249,15 @@ function reconcileOneWorker(input) {
|
|
|
6991
7249
|
});
|
|
6992
7250
|
return outcomes;
|
|
6993
7251
|
}
|
|
6994
|
-
const dirExists =
|
|
6995
|
-
const hasAnyArtifact = workerArtifactFileNames().some((f) =>
|
|
7252
|
+
const dirExists = existsSync24(canonicalDir);
|
|
7253
|
+
const hasAnyArtifact = workerArtifactFileNames().some((f) => existsSync24(path30.join(canonicalDir, f)));
|
|
6996
7254
|
if (dirExists && !hasAnyArtifact) {
|
|
6997
7255
|
const abandoned = {
|
|
6998
7256
|
name: input.workerName,
|
|
6999
7257
|
runId: input.run.id,
|
|
7000
7258
|
status: "exited",
|
|
7001
7259
|
branch: `agent/${input.run.id}/${input.workerName}`,
|
|
7002
|
-
worktreePath:
|
|
7260
|
+
worktreePath: path30.join(getPaths().worktreesDir, input.run.id, input.workerName),
|
|
7003
7261
|
workerDir: canonicalDir,
|
|
7004
7262
|
stdoutPath: canonicalArtifacts.stdoutPath,
|
|
7005
7263
|
stderrPath: canonicalArtifacts.stderrPath,
|
|
@@ -7046,11 +7304,11 @@ function reconcileOneWorker(input) {
|
|
|
7046
7304
|
return outcomes;
|
|
7047
7305
|
}
|
|
7048
7306
|
function listOrphanWorkerNames(run, harnessRoot) {
|
|
7049
|
-
const workersDir =
|
|
7050
|
-
if (!
|
|
7307
|
+
const workersDir = path30.join(runDirectory(run.id), "workers");
|
|
7308
|
+
if (!existsSync24(workersDir)) return [];
|
|
7051
7309
|
const indexed = new Set(Object.keys(run.workers || {}));
|
|
7052
7310
|
const orphans = [];
|
|
7053
|
-
for (const entry of
|
|
7311
|
+
for (const entry of readdirSync6(workersDir, { withFileTypes: true })) {
|
|
7054
7312
|
if (!entry.isDirectory()) continue;
|
|
7055
7313
|
if (indexed.has(entry.name)) continue;
|
|
7056
7314
|
orphans.push(entry.name);
|
|
@@ -7059,6 +7317,7 @@ function listOrphanWorkerNames(run, harnessRoot) {
|
|
|
7059
7317
|
}
|
|
7060
7318
|
function reconcileWorkerMetadata() {
|
|
7061
7319
|
const { harnessRoot } = getPaths();
|
|
7320
|
+
const runMetadataRetention = repairMissingRunMetadata(harnessRoot);
|
|
7062
7321
|
const outcomes = [];
|
|
7063
7322
|
for (const run of listRunRecords()) {
|
|
7064
7323
|
const materializedFrom = materializeSymlinkedRunDir(harnessRoot, run.id);
|
|
@@ -7085,7 +7344,7 @@ function reconcileWorkerMetadata() {
|
|
|
7085
7344
|
...run.workers || {},
|
|
7086
7345
|
[orphan]: {
|
|
7087
7346
|
workerDir: canonicalWorkerDir(harnessRoot, run.id, orphan),
|
|
7088
|
-
statusPath:
|
|
7347
|
+
statusPath: path30.join(canonicalWorkerDir(harnessRoot, run.id, orphan), "worker.json")
|
|
7089
7348
|
}
|
|
7090
7349
|
};
|
|
7091
7350
|
saveRun(run);
|
|
@@ -7108,7 +7367,7 @@ function reconcileWorkerMetadata() {
|
|
|
7108
7367
|
);
|
|
7109
7368
|
}
|
|
7110
7369
|
}
|
|
7111
|
-
return { workers: outcomes };
|
|
7370
|
+
return { workers: outcomes, runMetadataRetention };
|
|
7112
7371
|
}
|
|
7113
7372
|
function reconcileWorkerMetadataCli() {
|
|
7114
7373
|
const result = reconcileWorkerMetadata();
|
|
@@ -7116,7 +7375,22 @@ function reconcileWorkerMetadataCli() {
|
|
|
7116
7375
|
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
7117
7376
|
return acc;
|
|
7118
7377
|
}, {});
|
|
7119
|
-
|
|
7378
|
+
const runRetentionTotals = result.runMetadataRetention.runs.reduce((acc, row) => {
|
|
7379
|
+
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
7380
|
+
return acc;
|
|
7381
|
+
}, {});
|
|
7382
|
+
console.log(
|
|
7383
|
+
JSON.stringify(
|
|
7384
|
+
{
|
|
7385
|
+
ok: true,
|
|
7386
|
+
totals: byAction,
|
|
7387
|
+
runMetadataRetention: { totals: runRetentionTotals, details: result.runMetadataRetention.runs },
|
|
7388
|
+
details: result.workers
|
|
7389
|
+
},
|
|
7390
|
+
null,
|
|
7391
|
+
2
|
|
7392
|
+
)
|
|
7393
|
+
);
|
|
7120
7394
|
}
|
|
7121
7395
|
|
|
7122
7396
|
// src/stale-reconcile.ts
|
|
@@ -7133,7 +7407,7 @@ function reconcileStaleWorkers() {
|
|
|
7133
7407
|
const now = Date.now();
|
|
7134
7408
|
for (const run of listRunRecords()) {
|
|
7135
7409
|
for (const name of Object.keys(run.workers || {})) {
|
|
7136
|
-
const workerPath =
|
|
7410
|
+
const workerPath = path31.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
7137
7411
|
const worker = readJson(workerPath, void 0);
|
|
7138
7412
|
if (!worker || worker.status !== "running") {
|
|
7139
7413
|
outcomes.push({
|
|
@@ -7216,16 +7490,28 @@ function reconcileRunsCli() {
|
|
|
7216
7490
|
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
7217
7491
|
return acc;
|
|
7218
7492
|
}, {});
|
|
7493
|
+
const runRetentionTotals = result.metadataReconcile.runMetadataRetention.runs.reduce((acc, row) => {
|
|
7494
|
+
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
7495
|
+
return acc;
|
|
7496
|
+
}, {});
|
|
7219
7497
|
console.log(
|
|
7220
7498
|
JSON.stringify(
|
|
7221
7499
|
{
|
|
7222
7500
|
ok: true,
|
|
7223
7501
|
workers: { markedExited, killedStale, skipped, total: result.workers.length },
|
|
7224
|
-
metadataReconcile: {
|
|
7502
|
+
metadataReconcile: {
|
|
7503
|
+
totals: metadataTotals,
|
|
7504
|
+
total: result.metadataReconcile.workers.length,
|
|
7505
|
+
runMetadataRetention: {
|
|
7506
|
+
totals: runRetentionTotals,
|
|
7507
|
+
total: result.metadataReconcile.runMetadataRetention.runs.length
|
|
7508
|
+
}
|
|
7509
|
+
},
|
|
7225
7510
|
finalizedRuns: result.finalizedRuns.length,
|
|
7226
7511
|
details: {
|
|
7227
7512
|
workers: result.workers,
|
|
7228
7513
|
metadataReconcile: result.metadataReconcile.workers,
|
|
7514
|
+
runMetadataRetention: result.metadataReconcile.runMetadataRetention.runs,
|
|
7229
7515
|
finalizedRuns: result.finalizedRuns
|
|
7230
7516
|
}
|
|
7231
7517
|
},
|
|
@@ -7237,7 +7523,7 @@ function reconcileRunsCli() {
|
|
|
7237
7523
|
|
|
7238
7524
|
// src/run-list.ts
|
|
7239
7525
|
function heartbeatByteLength(heartbeatPath) {
|
|
7240
|
-
if (!heartbeatPath || !
|
|
7526
|
+
if (!heartbeatPath || !existsSync25(heartbeatPath)) return 0;
|
|
7241
7527
|
try {
|
|
7242
7528
|
return readFileSync12(heartbeatPath, "utf8").trim().length;
|
|
7243
7529
|
} catch {
|
|
@@ -7245,7 +7531,7 @@ function heartbeatByteLength(heartbeatPath) {
|
|
|
7245
7531
|
}
|
|
7246
7532
|
}
|
|
7247
7533
|
function workerEvidence(run, workerName) {
|
|
7248
|
-
const workerPath =
|
|
7534
|
+
const workerPath = path32.join(runDirectory(run.id), "workers", safeSlug(workerName), "worker.json");
|
|
7249
7535
|
const worker = readJson(workerPath, void 0);
|
|
7250
7536
|
if (!worker) {
|
|
7251
7537
|
return {
|
|
@@ -7302,7 +7588,7 @@ function aggregateRunAttention(workers) {
|
|
|
7302
7588
|
function countOpenWorkers(run) {
|
|
7303
7589
|
let open = 0;
|
|
7304
7590
|
for (const name of Object.keys(run.workers || {})) {
|
|
7305
|
-
const workerPath =
|
|
7591
|
+
const workerPath = path32.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
7306
7592
|
const worker = readJson(workerPath, void 0);
|
|
7307
7593
|
if (!worker) continue;
|
|
7308
7594
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
@@ -7350,75 +7636,6 @@ function listRunsCli() {
|
|
|
7350
7636
|
console.log(JSON.stringify(buildRunListRows(), null, 2));
|
|
7351
7637
|
}
|
|
7352
7638
|
|
|
7353
|
-
// src/default-repo.ts
|
|
7354
|
-
import path31 from "node:path";
|
|
7355
|
-
function expandConfiguredRepo(value) {
|
|
7356
|
-
return path31.resolve(resolveUserPath(value.trim()));
|
|
7357
|
-
}
|
|
7358
|
-
function fromConfigured(value, source, persistedInConfig) {
|
|
7359
|
-
const trimmed = value?.trim();
|
|
7360
|
-
if (!trimmed) return null;
|
|
7361
|
-
return {
|
|
7362
|
-
repo: expandConfiguredRepo(trimmed),
|
|
7363
|
-
source,
|
|
7364
|
-
persistedInConfig
|
|
7365
|
-
};
|
|
7366
|
-
}
|
|
7367
|
-
function resolveDefaultRepo(opts = {}) {
|
|
7368
|
-
const env = opts.env ?? process.env;
|
|
7369
|
-
const config = opts.config ?? loadUserConfig();
|
|
7370
|
-
const fromConfig = fromConfigured(config.defaultRepo, "config", true);
|
|
7371
|
-
if (fromConfig) return fromConfig;
|
|
7372
|
-
const fromDefaultEnv = fromConfigured(env.KYNVER_DEFAULT_REPO, "env_default_repo", false);
|
|
7373
|
-
if (fromDefaultEnv) return fromDefaultEnv;
|
|
7374
|
-
const fromHarnessEnv = fromConfigured(env.KYNVER_HARNESS_REPO, "env_harness_repo", false);
|
|
7375
|
-
if (fromHarnessEnv) return fromHarnessEnv;
|
|
7376
|
-
const discovered = discoverDefaultRepo({
|
|
7377
|
-
cwd: opts.cwd,
|
|
7378
|
-
runtimeModuleUrl: opts.runtimeModuleUrl
|
|
7379
|
-
});
|
|
7380
|
-
if (!discovered) return null;
|
|
7381
|
-
return {
|
|
7382
|
-
repo: discovered.repo,
|
|
7383
|
-
source: discovered.source,
|
|
7384
|
-
persistedInConfig: false
|
|
7385
|
-
};
|
|
7386
|
-
}
|
|
7387
|
-
function persistDefaultRepo(repo, existing) {
|
|
7388
|
-
const config = {
|
|
7389
|
-
...existing ?? loadUserConfig(),
|
|
7390
|
-
defaultRepo: redactHomePath(path31.resolve(repo))
|
|
7391
|
-
};
|
|
7392
|
-
saveUserConfig(config);
|
|
7393
|
-
return config;
|
|
7394
|
-
}
|
|
7395
|
-
function remediateDefaultRepo(opts) {
|
|
7396
|
-
const existing = opts?.config ?? loadUserConfig();
|
|
7397
|
-
const resolved = resolveDefaultRepo({ ...opts, config: existing });
|
|
7398
|
-
if (!resolved) {
|
|
7399
|
-
return {
|
|
7400
|
-
ok: false,
|
|
7401
|
-
reason: "No Kynver git checkout found. Clone the repo, cd into it, then run `kynver setup --repo /path/to/Kynver` (or export KYNVER_DEFAULT_REPO)."
|
|
7402
|
-
};
|
|
7403
|
-
}
|
|
7404
|
-
if (resolved.persistedInConfig) {
|
|
7405
|
-
return { ok: true, resolved, config: existing };
|
|
7406
|
-
}
|
|
7407
|
-
const config = persistDefaultRepo(resolved.repo, existing);
|
|
7408
|
-
return {
|
|
7409
|
-
ok: true,
|
|
7410
|
-
resolved: { ...resolved, persistedInConfig: true, source: "config" },
|
|
7411
|
-
config
|
|
7412
|
-
};
|
|
7413
|
-
}
|
|
7414
|
-
function formatResolvedDefaultRepo(resolved) {
|
|
7415
|
-
return {
|
|
7416
|
-
defaultRepo: displayUserPath(resolved.repo),
|
|
7417
|
-
source: resolved.source,
|
|
7418
|
-
persistedInConfig: resolved.persistedInConfig
|
|
7419
|
-
};
|
|
7420
|
-
}
|
|
7421
|
-
|
|
7422
7639
|
// src/worktree.ts
|
|
7423
7640
|
function resolveCreateRunRepo(args) {
|
|
7424
7641
|
const explicit = typeof args.repo === "string" ? args.repo.trim() : "";
|
|
@@ -7433,7 +7650,7 @@ function createRun(args) {
|
|
|
7433
7650
|
ensureGitRepo(repo);
|
|
7434
7651
|
const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
|
|
7435
7652
|
const dir = runDirectory(id);
|
|
7436
|
-
if (
|
|
7653
|
+
if (existsSync26(dir)) failExists(`run already exists: ${id}`);
|
|
7437
7654
|
mkdirSync5(dir, { recursive: true });
|
|
7438
7655
|
const base = String(args.base || "origin/main");
|
|
7439
7656
|
const baseCommit = git(repo, ["rev-parse", base]).trim();
|
|
@@ -7447,7 +7664,7 @@ function createRun(args) {
|
|
|
7447
7664
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7448
7665
|
workers: {}
|
|
7449
7666
|
};
|
|
7450
|
-
writeJson(
|
|
7667
|
+
writeJson(path33.join(dir, "run.json"), run);
|
|
7451
7668
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
7452
7669
|
}
|
|
7453
7670
|
function listRuns() {
|
|
@@ -7459,7 +7676,7 @@ function failExists(message) {
|
|
|
7459
7676
|
}
|
|
7460
7677
|
|
|
7461
7678
|
// src/sweep.ts
|
|
7462
|
-
import
|
|
7679
|
+
import path34 from "node:path";
|
|
7463
7680
|
async function sweepRun(args) {
|
|
7464
7681
|
const pipeline = args.pipeline === true || args.pipeline === "true";
|
|
7465
7682
|
try {
|
|
@@ -7472,7 +7689,7 @@ async function sweepRun(args) {
|
|
|
7472
7689
|
const releasedLocalOrphans = [];
|
|
7473
7690
|
for (const name of Object.keys(run.workers || {})) {
|
|
7474
7691
|
const worker = readJson(
|
|
7475
|
-
|
|
7692
|
+
path34.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
7476
7693
|
void 0
|
|
7477
7694
|
);
|
|
7478
7695
|
if (!worker || !worker.dispatched || !worker.taskId) continue;
|
|
@@ -7520,14 +7737,14 @@ async function sweepRun(args) {
|
|
|
7520
7737
|
}
|
|
7521
7738
|
|
|
7522
7739
|
// src/harness-storage-snapshot.ts
|
|
7523
|
-
import { existsSync as
|
|
7524
|
-
import
|
|
7740
|
+
import { existsSync as existsSync28, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
|
|
7741
|
+
import path36 from "node:path";
|
|
7525
7742
|
|
|
7526
7743
|
// src/cleanup-dir-size.ts
|
|
7527
|
-
import { existsSync as
|
|
7528
|
-
import
|
|
7744
|
+
import { existsSync as existsSync27, readdirSync as readdirSync7, statSync as statSync5 } from "node:fs";
|
|
7745
|
+
import path35 from "node:path";
|
|
7529
7746
|
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
7530
|
-
if (!
|
|
7747
|
+
if (!existsSync27(root)) return 0;
|
|
7531
7748
|
let total = 0;
|
|
7532
7749
|
let seen = 0;
|
|
7533
7750
|
const stack = [root];
|
|
@@ -7535,16 +7752,16 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
7535
7752
|
const current = stack.pop();
|
|
7536
7753
|
let entries;
|
|
7537
7754
|
try {
|
|
7538
|
-
entries =
|
|
7755
|
+
entries = readdirSync7(current);
|
|
7539
7756
|
} catch {
|
|
7540
7757
|
continue;
|
|
7541
7758
|
}
|
|
7542
7759
|
for (const name of entries) {
|
|
7543
7760
|
if (seen++ > maxEntries) return null;
|
|
7544
|
-
const full =
|
|
7761
|
+
const full = path35.join(current, name);
|
|
7545
7762
|
let st;
|
|
7546
7763
|
try {
|
|
7547
|
-
st =
|
|
7764
|
+
st = statSync5(full);
|
|
7548
7765
|
} catch {
|
|
7549
7766
|
continue;
|
|
7550
7767
|
}
|
|
@@ -7561,7 +7778,7 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
7561
7778
|
const worktreesDir = harnessWorktreesDir(harnessRoot);
|
|
7562
7779
|
const now = opts.now ?? Date.now();
|
|
7563
7780
|
const scannedAt = new Date(now).toISOString();
|
|
7564
|
-
if (!
|
|
7781
|
+
if (!existsSync28(worktreesDir)) {
|
|
7565
7782
|
return {
|
|
7566
7783
|
harnessRoot,
|
|
7567
7784
|
worktreesDir,
|
|
@@ -7578,7 +7795,7 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
7578
7795
|
let oldestMs = null;
|
|
7579
7796
|
let entries;
|
|
7580
7797
|
try {
|
|
7581
|
-
entries =
|
|
7798
|
+
entries = readdirSync8(worktreesDir, { withFileTypes: true });
|
|
7582
7799
|
} catch {
|
|
7583
7800
|
return {
|
|
7584
7801
|
harnessRoot,
|
|
@@ -7593,14 +7810,14 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
7593
7810
|
for (const runEntry of entries) {
|
|
7594
7811
|
if (!runEntry.isDirectory()) continue;
|
|
7595
7812
|
runCount += 1;
|
|
7596
|
-
const runPath =
|
|
7813
|
+
const runPath = path36.join(worktreesDir, runEntry.name);
|
|
7597
7814
|
try {
|
|
7598
|
-
const st =
|
|
7815
|
+
const st = statSync6(runPath);
|
|
7599
7816
|
oldestMs = oldestMs === null ? st.mtimeMs : Math.min(oldestMs, st.mtimeMs);
|
|
7600
7817
|
} catch {
|
|
7601
7818
|
}
|
|
7602
7819
|
try {
|
|
7603
|
-
for (const workerEntry of
|
|
7820
|
+
for (const workerEntry of readdirSync8(runPath, { withFileTypes: true })) {
|
|
7604
7821
|
if (workerEntry.isDirectory()) workerCount += 1;
|
|
7605
7822
|
}
|
|
7606
7823
|
} catch {
|
|
@@ -7628,10 +7845,10 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
7628
7845
|
}
|
|
7629
7846
|
|
|
7630
7847
|
// src/cleanup.ts
|
|
7631
|
-
import
|
|
7848
|
+
import path47 from "node:path";
|
|
7632
7849
|
|
|
7633
7850
|
// src/cleanup-guards.ts
|
|
7634
|
-
import
|
|
7851
|
+
import path37 from "node:path";
|
|
7635
7852
|
|
|
7636
7853
|
// src/cleanup-run-liveness.ts
|
|
7637
7854
|
function isWorkerProcessLive(indexed) {
|
|
@@ -7755,7 +7972,7 @@ function skipWorktreeRemoval(input) {
|
|
|
7755
7972
|
function skipDependencyCacheRemoval(input) {
|
|
7756
7973
|
const { indexed, nodeModulesAgeMs, ageMs, worktreePath, activeWorktreePaths, diskPressure } = input;
|
|
7757
7974
|
if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
|
|
7758
|
-
if (activeWorktreePaths.has(
|
|
7975
|
+
if (activeWorktreePaths.has(path37.resolve(worktreePath))) return "active_worker";
|
|
7759
7976
|
if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
|
|
7760
7977
|
if (indexed && hasUnrestorableWorktreeChanges(indexed.status)) return "dirty_worktree";
|
|
7761
7978
|
return null;
|
|
@@ -7782,11 +7999,11 @@ var LIVE_SKIP_REASONS = /* @__PURE__ */ new Set([
|
|
|
7782
7999
|
function collectPreservedLivePaths(actions, skips) {
|
|
7783
8000
|
const out = [];
|
|
7784
8001
|
const seen = /* @__PURE__ */ new Set();
|
|
7785
|
-
const push = (
|
|
7786
|
-
const key = `${
|
|
8002
|
+
const push = (path63, reason, detail) => {
|
|
8003
|
+
const key = `${path63}\0${reason}`;
|
|
7787
8004
|
if (seen.has(key) || out.length >= MAX_PRESERVED_LIVE_PATH_SAMPLES) return;
|
|
7788
8005
|
seen.add(key);
|
|
7789
|
-
out.push({ path:
|
|
8006
|
+
out.push({ path: path63, reason, ...detail ? { detail } : {} });
|
|
7790
8007
|
};
|
|
7791
8008
|
for (const skip2 of skips) {
|
|
7792
8009
|
if (!LIVE_SKIP_REASONS.has(skip2.reason)) continue;
|
|
@@ -7801,11 +8018,11 @@ function collectPreservedLivePaths(actions, skips) {
|
|
|
7801
8018
|
}
|
|
7802
8019
|
|
|
7803
8020
|
// src/cleanup-run-directory.ts
|
|
7804
|
-
import { existsSync as
|
|
7805
|
-
import
|
|
8021
|
+
import { existsSync as existsSync29, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
|
|
8022
|
+
import path39 from "node:path";
|
|
7806
8023
|
|
|
7807
8024
|
// src/cleanup-active-worktrees.ts
|
|
7808
|
-
import
|
|
8025
|
+
import path38 from "node:path";
|
|
7809
8026
|
function isActiveHarnessWorker2(worker, runBase, runBaseCommit) {
|
|
7810
8027
|
const status = computeWorkerStatus(worker, { base: runBase, baseCommit: runBaseCommit });
|
|
7811
8028
|
return status.alive && !status.finalResult && status.attention.state !== "done";
|
|
@@ -7818,17 +8035,20 @@ function collectActiveWorktreeGuards(harnessRoots) {
|
|
|
7818
8035
|
let runHasLive = false;
|
|
7819
8036
|
for (const name of Object.keys(run.workers || {})) {
|
|
7820
8037
|
const worker = readJson(
|
|
7821
|
-
|
|
8038
|
+
path38.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
|
|
7822
8039
|
void 0
|
|
7823
8040
|
);
|
|
7824
8041
|
if (!worker?.worktreePath) continue;
|
|
7825
|
-
const worktreePath =
|
|
8042
|
+
const worktreePath = path38.resolve(worker.worktreePath);
|
|
7826
8043
|
if (!isActiveHarnessWorker2(worker, run.base, run.baseCommit)) continue;
|
|
7827
8044
|
runHasLive = true;
|
|
7828
8045
|
activeWorktreePaths.add(worktreePath);
|
|
7829
8046
|
}
|
|
7830
8047
|
if (runHasLive) liveRunKeys.add(`${harnessRoot}\0${run.id}`);
|
|
7831
8048
|
}
|
|
8049
|
+
for (const key of collectFilesystemLiveRunKeys(harnessRoot)) {
|
|
8050
|
+
liveRunKeys.add(key);
|
|
8051
|
+
}
|
|
7832
8052
|
}
|
|
7833
8053
|
return { activeWorktreePaths, liveRunKeys };
|
|
7834
8054
|
}
|
|
@@ -7840,20 +8060,20 @@ function isWorktreeOnLiveRun(worktreePath, harnessRoot, runId, liveRunKeys) {
|
|
|
7840
8060
|
// src/cleanup-run-directory.ts
|
|
7841
8061
|
function pathAgeMs(target, now) {
|
|
7842
8062
|
try {
|
|
7843
|
-
const mtime =
|
|
8063
|
+
const mtime = statSync7(target).mtimeMs;
|
|
7844
8064
|
return Math.max(0, now - mtime);
|
|
7845
8065
|
} catch {
|
|
7846
8066
|
return 0;
|
|
7847
8067
|
}
|
|
7848
8068
|
}
|
|
7849
8069
|
function loadRunStatus(harnessRoot, runId) {
|
|
7850
|
-
const runPath =
|
|
7851
|
-
if (!
|
|
8070
|
+
const runPath = path39.join(harnessRoot, "runs", runId, "run.json");
|
|
8071
|
+
if (!existsSync29(runPath)) return null;
|
|
7852
8072
|
return readJson(runPath, null);
|
|
7853
8073
|
}
|
|
7854
8074
|
function runDirectoryIsEmpty(runPath) {
|
|
7855
8075
|
try {
|
|
7856
|
-
const entries =
|
|
8076
|
+
const entries = readdirSync9(runPath);
|
|
7857
8077
|
return entries.length === 0;
|
|
7858
8078
|
} catch {
|
|
7859
8079
|
return false;
|
|
@@ -7871,11 +8091,11 @@ function skipRunDirectoryRemoval(input) {
|
|
|
7871
8091
|
return null;
|
|
7872
8092
|
}
|
|
7873
8093
|
function scanStaleRunDirectoryCandidates(opts) {
|
|
7874
|
-
if (!
|
|
8094
|
+
if (!existsSync29(opts.worktreesDir)) return [];
|
|
7875
8095
|
const candidates = [];
|
|
7876
8096
|
let entries;
|
|
7877
8097
|
try {
|
|
7878
|
-
entries =
|
|
8098
|
+
entries = readdirSync9(opts.worktreesDir, { withFileTypes: true });
|
|
7879
8099
|
} catch {
|
|
7880
8100
|
return [];
|
|
7881
8101
|
}
|
|
@@ -7883,7 +8103,7 @@ function scanStaleRunDirectoryCandidates(opts) {
|
|
|
7883
8103
|
if (!runEntry.isDirectory()) continue;
|
|
7884
8104
|
const runId = runEntry.name;
|
|
7885
8105
|
if (opts.runIdFilter && runId !== opts.runIdFilter) continue;
|
|
7886
|
-
const runPath =
|
|
8106
|
+
const runPath = path39.join(opts.worktreesDir, runId);
|
|
7887
8107
|
if (!runDirectoryIsEmpty(runPath)) continue;
|
|
7888
8108
|
candidates.push({
|
|
7889
8109
|
kind: "remove_run_directory",
|
|
@@ -7898,10 +8118,22 @@ function scanStaleRunDirectoryCandidates(opts) {
|
|
|
7898
8118
|
}
|
|
7899
8119
|
|
|
7900
8120
|
// src/cleanup-execute.ts
|
|
7901
|
-
import { existsSync as
|
|
7902
|
-
import
|
|
8121
|
+
import { existsSync as existsSync30, rmSync as rmSync2 } from "node:fs";
|
|
8122
|
+
import path40 from "node:path";
|
|
8123
|
+
function skipRunMetadataRemoval(candidate) {
|
|
8124
|
+
const harnessRoot = candidate.harnessRoot;
|
|
8125
|
+
if (!harnessRoot || !isHarnessRunMetadataPath(candidate.path, harnessRoot)) return null;
|
|
8126
|
+
return {
|
|
8127
|
+
...candidate,
|
|
8128
|
+
executed: false,
|
|
8129
|
+
skipped: true,
|
|
8130
|
+
skipReason: "run_metadata_protected"
|
|
8131
|
+
};
|
|
8132
|
+
}
|
|
7903
8133
|
function removeDependencyCache(candidate, execute) {
|
|
7904
|
-
|
|
8134
|
+
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
8135
|
+
if (metadataSkip) return metadataSkip;
|
|
8136
|
+
if (!existsSync30(candidate.path)) {
|
|
7905
8137
|
return {
|
|
7906
8138
|
...candidate,
|
|
7907
8139
|
executed: false,
|
|
@@ -7941,7 +8173,9 @@ function removeBuildCache(candidate, execute) {
|
|
|
7941
8173
|
return removeDependencyCache(candidate, execute);
|
|
7942
8174
|
}
|
|
7943
8175
|
function removeRunDirectory(candidate, execute) {
|
|
7944
|
-
|
|
8176
|
+
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
8177
|
+
if (metadataSkip) return metadataSkip;
|
|
8178
|
+
if (!existsSync30(candidate.path)) {
|
|
7945
8179
|
return {
|
|
7946
8180
|
...candidate,
|
|
7947
8181
|
executed: false,
|
|
@@ -7972,7 +8206,9 @@ function removeRunDirectory(candidate, execute) {
|
|
|
7972
8206
|
}
|
|
7973
8207
|
}
|
|
7974
8208
|
function removeWorktree(candidate, execute) {
|
|
7975
|
-
|
|
8209
|
+
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
8210
|
+
if (metadataSkip) return metadataSkip;
|
|
8211
|
+
if (!existsSync30(candidate.path)) {
|
|
7976
8212
|
return {
|
|
7977
8213
|
...candidate,
|
|
7978
8214
|
executed: false,
|
|
@@ -7989,7 +8225,7 @@ function removeWorktree(candidate, execute) {
|
|
|
7989
8225
|
if (repo) {
|
|
7990
8226
|
git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
|
|
7991
8227
|
}
|
|
7992
|
-
if (
|
|
8228
|
+
if (existsSync30(candidate.path)) {
|
|
7993
8229
|
rmSync2(candidate.path, { recursive: true, force: true });
|
|
7994
8230
|
}
|
|
7995
8231
|
return {
|
|
@@ -8009,15 +8245,15 @@ function removeWorktree(candidate, execute) {
|
|
|
8009
8245
|
}
|
|
8010
8246
|
}
|
|
8011
8247
|
function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
|
|
8012
|
-
const resolved =
|
|
8013
|
-
const suffix = `${
|
|
8248
|
+
const resolved = path40.resolve(targetPath);
|
|
8249
|
+
const suffix = `${path40.sep}${cacheDirName}`;
|
|
8014
8250
|
const cachePath = resolved.endsWith(suffix) ? resolved : null;
|
|
8015
8251
|
if (!cachePath) return "path_outside_harness";
|
|
8016
|
-
const rel =
|
|
8017
|
-
if (rel.startsWith("..") ||
|
|
8018
|
-
const parts = rel.split(
|
|
8252
|
+
const rel = path40.relative(worktreesDir, cachePath);
|
|
8253
|
+
if (rel.startsWith("..") || path40.isAbsolute(rel)) return "path_outside_harness";
|
|
8254
|
+
const parts = rel.split(path40.sep);
|
|
8019
8255
|
if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
|
|
8020
|
-
if (!resolved.startsWith(
|
|
8256
|
+
if (!resolved.startsWith(path40.resolve(harnessRoot))) return "path_outside_harness";
|
|
8021
8257
|
return null;
|
|
8022
8258
|
}
|
|
8023
8259
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
@@ -8027,37 +8263,37 @@ function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
|
8027
8263
|
return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
|
|
8028
8264
|
}
|
|
8029
8265
|
function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
8030
|
-
const resolved =
|
|
8031
|
-
const relToWt =
|
|
8032
|
-
if (relToWt.startsWith("..") ||
|
|
8033
|
-
const parts = relToWt.split(
|
|
8266
|
+
const resolved = path40.resolve(targetPath);
|
|
8267
|
+
const relToWt = path40.relative(worktreesDir, resolved);
|
|
8268
|
+
if (relToWt.startsWith("..") || path40.isAbsolute(relToWt)) return "path_outside_harness";
|
|
8269
|
+
const parts = relToWt.split(path40.sep);
|
|
8034
8270
|
if (parts.length < 3) return "path_outside_harness";
|
|
8035
|
-
if (!resolved.startsWith(
|
|
8271
|
+
if (!resolved.startsWith(path40.resolve(harnessRoot))) return "path_outside_harness";
|
|
8036
8272
|
return null;
|
|
8037
8273
|
}
|
|
8038
8274
|
|
|
8039
8275
|
// src/cleanup-scan.ts
|
|
8040
|
-
import { existsSync as
|
|
8041
|
-
import
|
|
8276
|
+
import { existsSync as existsSync31, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
|
|
8277
|
+
import path41 from "node:path";
|
|
8042
8278
|
function pathAgeMs2(target, now) {
|
|
8043
8279
|
try {
|
|
8044
|
-
const mtime =
|
|
8280
|
+
const mtime = statSync8(target).mtimeMs;
|
|
8045
8281
|
return Math.max(0, now - mtime);
|
|
8046
8282
|
} catch {
|
|
8047
8283
|
return 0;
|
|
8048
8284
|
}
|
|
8049
8285
|
}
|
|
8050
8286
|
function isPathInside(child, parent) {
|
|
8051
|
-
const rel =
|
|
8052
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
8287
|
+
const rel = path41.relative(parent, child);
|
|
8288
|
+
return rel === "" || !rel.startsWith("..") && !path41.isAbsolute(rel);
|
|
8053
8289
|
}
|
|
8054
8290
|
function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
|
|
8055
8291
|
const out = [];
|
|
8056
8292
|
for (const rel of HARNESS_BUILD_CACHE_RELATIVE_PATHS) {
|
|
8057
8293
|
if (rel === ".next") continue;
|
|
8058
|
-
const target =
|
|
8059
|
-
if (!
|
|
8060
|
-
const resolved =
|
|
8294
|
+
const target = path41.join(worktreePath, rel);
|
|
8295
|
+
if (!existsSync31(target)) continue;
|
|
8296
|
+
const resolved = path41.resolve(target);
|
|
8061
8297
|
if (seen.has(resolved)) continue;
|
|
8062
8298
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
8063
8299
|
seen.add(resolved);
|
|
@@ -8086,13 +8322,13 @@ function scanBuildCacheCandidates(opts) {
|
|
|
8086
8322
|
})
|
|
8087
8323
|
);
|
|
8088
8324
|
}
|
|
8089
|
-
if (!opts.includeOrphans || !
|
|
8090
|
-
for (const runEntry of
|
|
8325
|
+
if (!opts.includeOrphans || !existsSync31(opts.worktreesDir)) return candidates;
|
|
8326
|
+
for (const runEntry of readdirSync10(opts.worktreesDir, { withFileTypes: true })) {
|
|
8091
8327
|
if (!runEntry.isDirectory()) continue;
|
|
8092
|
-
const runPath =
|
|
8093
|
-
for (const workerEntry of
|
|
8328
|
+
const runPath = path41.join(opts.worktreesDir, runEntry.name);
|
|
8329
|
+
for (const workerEntry of readdirSync10(runPath, { withFileTypes: true })) {
|
|
8094
8330
|
if (!workerEntry.isDirectory()) continue;
|
|
8095
|
-
const worktreePath =
|
|
8331
|
+
const worktreePath = path41.join(runPath, workerEntry.name);
|
|
8096
8332
|
candidates.push(
|
|
8097
8333
|
...collectBuildCacheForWorktree(worktreePath, opts, seen, {
|
|
8098
8334
|
runId: runEntry.name,
|
|
@@ -8113,7 +8349,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
8113
8349
|
for (const entry of opts.index.values()) {
|
|
8114
8350
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
8115
8351
|
const resolved = entry.worktreePath;
|
|
8116
|
-
if (!
|
|
8352
|
+
if (!existsSync31(resolved)) continue;
|
|
8117
8353
|
if (seen.has(resolved)) continue;
|
|
8118
8354
|
seen.add(resolved);
|
|
8119
8355
|
candidates.push({
|
|
@@ -8127,24 +8363,24 @@ function scanWorktreeCandidates(opts) {
|
|
|
8127
8363
|
});
|
|
8128
8364
|
}
|
|
8129
8365
|
}
|
|
8130
|
-
if (!orphanEnabled || !
|
|
8366
|
+
if (!orphanEnabled || !existsSync31(opts.worktreesDir)) return candidates;
|
|
8131
8367
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
8132
8368
|
for (const entry of opts.index.values()) {
|
|
8133
|
-
indexedPaths.add(
|
|
8369
|
+
indexedPaths.add(path41.resolve(entry.worktreePath));
|
|
8134
8370
|
}
|
|
8135
|
-
for (const runEntry of
|
|
8371
|
+
for (const runEntry of readdirSync10(opts.worktreesDir, { withFileTypes: true })) {
|
|
8136
8372
|
if (!runEntry.isDirectory()) continue;
|
|
8137
8373
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
8138
|
-
const runPath =
|
|
8374
|
+
const runPath = path41.join(opts.worktreesDir, runEntry.name);
|
|
8139
8375
|
let workerEntries;
|
|
8140
8376
|
try {
|
|
8141
|
-
workerEntries =
|
|
8377
|
+
workerEntries = readdirSync10(runPath, { withFileTypes: true });
|
|
8142
8378
|
} catch {
|
|
8143
8379
|
continue;
|
|
8144
8380
|
}
|
|
8145
8381
|
for (const workerEntry of workerEntries) {
|
|
8146
8382
|
if (!workerEntry.isDirectory()) continue;
|
|
8147
|
-
const worktreePath =
|
|
8383
|
+
const worktreePath = path41.resolve(path41.join(runPath, workerEntry.name));
|
|
8148
8384
|
if (seen.has(worktreePath)) continue;
|
|
8149
8385
|
if (indexedPaths.has(worktreePath)) continue;
|
|
8150
8386
|
if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
|
|
@@ -8163,27 +8399,27 @@ function scanWorktreeCandidates(opts) {
|
|
|
8163
8399
|
}
|
|
8164
8400
|
|
|
8165
8401
|
// src/cleanup-dependency-scan.ts
|
|
8166
|
-
import { existsSync as
|
|
8167
|
-
import
|
|
8402
|
+
import { existsSync as existsSync32, readdirSync as readdirSync11, statSync as statSync9 } from "node:fs";
|
|
8403
|
+
import path42 from "node:path";
|
|
8168
8404
|
var DEPENDENCY_CACHE_DIRS = [
|
|
8169
8405
|
{ dirName: "node_modules", kind: "remove_node_modules" },
|
|
8170
8406
|
{ dirName: ".next", kind: "remove_next_cache" }
|
|
8171
8407
|
];
|
|
8172
8408
|
function pathAgeMs3(target, now) {
|
|
8173
8409
|
try {
|
|
8174
|
-
const mtime =
|
|
8410
|
+
const mtime = statSync9(target).mtimeMs;
|
|
8175
8411
|
return Math.max(0, now - mtime);
|
|
8176
8412
|
} catch {
|
|
8177
8413
|
return 0;
|
|
8178
8414
|
}
|
|
8179
8415
|
}
|
|
8180
8416
|
function isPathInside2(child, parent) {
|
|
8181
|
-
const rel =
|
|
8182
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
8417
|
+
const rel = path42.relative(parent, child);
|
|
8418
|
+
return rel === "" || !rel.startsWith("..") && !path42.isAbsolute(rel);
|
|
8183
8419
|
}
|
|
8184
8420
|
function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
8185
|
-
if (!
|
|
8186
|
-
const resolved =
|
|
8421
|
+
if (!existsSync32(targetPath)) return;
|
|
8422
|
+
const resolved = path42.resolve(targetPath);
|
|
8187
8423
|
if (seen.has(resolved)) return;
|
|
8188
8424
|
if (!isPathInside2(resolved, opts.harnessRoot)) return;
|
|
8189
8425
|
seen.add(resolved);
|
|
@@ -8200,7 +8436,7 @@ function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
|
8200
8436
|
}
|
|
8201
8437
|
function scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, meta) {
|
|
8202
8438
|
for (const entry of DEPENDENCY_CACHE_DIRS) {
|
|
8203
|
-
pushCandidate2(candidates, seen, opts,
|
|
8439
|
+
pushCandidate2(candidates, seen, opts, path42.join(worktreePath, entry.dirName), entry.kind, meta);
|
|
8204
8440
|
}
|
|
8205
8441
|
}
|
|
8206
8442
|
function scanDependencyCacheCandidates(opts) {
|
|
@@ -8214,20 +8450,20 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
8214
8450
|
repo: entry.run.repo
|
|
8215
8451
|
});
|
|
8216
8452
|
}
|
|
8217
|
-
if (!
|
|
8218
|
-
for (const runEntry of
|
|
8453
|
+
if (!existsSync32(opts.worktreesDir)) return candidates;
|
|
8454
|
+
for (const runEntry of readdirSync11(opts.worktreesDir, { withFileTypes: true })) {
|
|
8219
8455
|
if (!runEntry.isDirectory()) continue;
|
|
8220
8456
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
8221
|
-
const runPath =
|
|
8457
|
+
const runPath = path42.join(opts.worktreesDir, runEntry.name);
|
|
8222
8458
|
let workerEntries;
|
|
8223
8459
|
try {
|
|
8224
|
-
workerEntries =
|
|
8460
|
+
workerEntries = readdirSync11(runPath, { withFileTypes: true });
|
|
8225
8461
|
} catch {
|
|
8226
8462
|
continue;
|
|
8227
8463
|
}
|
|
8228
8464
|
for (const workerEntry of workerEntries) {
|
|
8229
8465
|
if (!workerEntry.isDirectory()) continue;
|
|
8230
|
-
const worktreePath =
|
|
8466
|
+
const worktreePath = path42.join(runPath, workerEntry.name);
|
|
8231
8467
|
scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, {
|
|
8232
8468
|
runId: runEntry.name,
|
|
8233
8469
|
worker: workerEntry.name
|
|
@@ -8238,11 +8474,11 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
8238
8474
|
}
|
|
8239
8475
|
|
|
8240
8476
|
// src/cleanup-duplicate-worktrees.ts
|
|
8241
|
-
import { existsSync as
|
|
8242
|
-
import
|
|
8477
|
+
import { existsSync as existsSync33, statSync as statSync10 } from "node:fs";
|
|
8478
|
+
import path43 from "node:path";
|
|
8243
8479
|
function pathAgeMs4(target, now) {
|
|
8244
8480
|
try {
|
|
8245
|
-
const mtime =
|
|
8481
|
+
const mtime = statSync10(target).mtimeMs;
|
|
8246
8482
|
return Math.max(0, now - mtime);
|
|
8247
8483
|
} catch {
|
|
8248
8484
|
return 0;
|
|
@@ -8269,8 +8505,8 @@ function parseWorktreePorcelain(output) {
|
|
|
8269
8505
|
return records;
|
|
8270
8506
|
}
|
|
8271
8507
|
function isUnderWorktreesDir(worktreePath, worktreesDir) {
|
|
8272
|
-
const rel =
|
|
8273
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
8508
|
+
const rel = path43.relative(path43.resolve(worktreesDir), path43.resolve(worktreePath));
|
|
8509
|
+
return rel !== "" && !rel.startsWith("..") && !path43.isAbsolute(rel);
|
|
8274
8510
|
}
|
|
8275
8511
|
function isCleanWorktree(worktreePath, repoRoot) {
|
|
8276
8512
|
try {
|
|
@@ -8283,14 +8519,14 @@ function isCleanWorktree(worktreePath, repoRoot) {
|
|
|
8283
8519
|
}
|
|
8284
8520
|
}
|
|
8285
8521
|
function scanDuplicateWorktreeCandidates(opts) {
|
|
8286
|
-
if (!opts.includeOrphans || !
|
|
8522
|
+
if (!opts.includeOrphans || !existsSync33(opts.worktreesDir)) return [];
|
|
8287
8523
|
const repos = /* @__PURE__ */ new Set();
|
|
8288
8524
|
for (const entry of opts.index.values()) {
|
|
8289
|
-
if (entry.run.repo) repos.add(
|
|
8525
|
+
if (entry.run.repo) repos.add(path43.resolve(entry.run.repo));
|
|
8290
8526
|
}
|
|
8291
8527
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
8292
8528
|
for (const entry of opts.index.values()) {
|
|
8293
|
-
indexedPaths.add(
|
|
8529
|
+
indexedPaths.add(path43.resolve(entry.worktreePath));
|
|
8294
8530
|
}
|
|
8295
8531
|
const candidates = [];
|
|
8296
8532
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -8303,15 +8539,15 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
8303
8539
|
}
|
|
8304
8540
|
const worktrees = parseWorktreePorcelain(porcelain);
|
|
8305
8541
|
for (const wt of worktrees) {
|
|
8306
|
-
const resolved =
|
|
8307
|
-
if (resolved ===
|
|
8542
|
+
const resolved = path43.resolve(wt.path);
|
|
8543
|
+
if (resolved === path43.resolve(repoRoot)) continue;
|
|
8308
8544
|
if (!isUnderWorktreesDir(resolved, opts.worktreesDir)) continue;
|
|
8309
8545
|
if (indexedPaths.has(resolved)) continue;
|
|
8310
8546
|
if (seen.has(resolved)) continue;
|
|
8311
|
-
if (!
|
|
8547
|
+
if (!existsSync33(resolved)) continue;
|
|
8312
8548
|
if (!isCleanWorktree(resolved, repoRoot)) continue;
|
|
8313
|
-
const rel =
|
|
8314
|
-
const parts = rel.split(
|
|
8549
|
+
const rel = path43.relative(opts.worktreesDir, resolved);
|
|
8550
|
+
const parts = rel.split(path43.sep);
|
|
8315
8551
|
const runId = parts[0];
|
|
8316
8552
|
const worker = parts[1] ?? "unknown";
|
|
8317
8553
|
seen.add(resolved);
|
|
@@ -8330,12 +8566,12 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
8330
8566
|
}
|
|
8331
8567
|
|
|
8332
8568
|
// src/cleanup-worktree-index.ts
|
|
8333
|
-
import
|
|
8569
|
+
import path44 from "node:path";
|
|
8334
8570
|
function buildWorktreeIndexAt(harnessRoot) {
|
|
8335
8571
|
const index = /* @__PURE__ */ new Map();
|
|
8336
8572
|
for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
|
|
8337
8573
|
for (const name of Object.keys(run.workers || {})) {
|
|
8338
|
-
const workerPath =
|
|
8574
|
+
const workerPath = path44.join(
|
|
8339
8575
|
runDirectoryAt(harnessRoot, run.id),
|
|
8340
8576
|
"workers",
|
|
8341
8577
|
safeSlug(name),
|
|
@@ -8344,9 +8580,9 @@ function buildWorktreeIndexAt(harnessRoot) {
|
|
|
8344
8580
|
const worker = readJson(workerPath, void 0);
|
|
8345
8581
|
if (!worker?.worktreePath) continue;
|
|
8346
8582
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
8347
|
-
index.set(
|
|
8583
|
+
index.set(path44.resolve(worker.worktreePath), {
|
|
8348
8584
|
harnessRoot,
|
|
8349
|
-
worktreePath:
|
|
8585
|
+
worktreePath: path44.resolve(worker.worktreePath),
|
|
8350
8586
|
runId: run.id,
|
|
8351
8587
|
workerName: name,
|
|
8352
8588
|
run,
|
|
@@ -8410,15 +8646,15 @@ function resolvePipelineHarnessRetention(runId) {
|
|
|
8410
8646
|
}
|
|
8411
8647
|
|
|
8412
8648
|
// src/cleanup-orphan-safety.ts
|
|
8413
|
-
import { existsSync as
|
|
8414
|
-
import
|
|
8649
|
+
import { existsSync as existsSync34, statSync as statSync11 } from "node:fs";
|
|
8650
|
+
import path45 from "node:path";
|
|
8415
8651
|
var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
|
|
8416
8652
|
function assessOrphanWorktreeSafety(input) {
|
|
8417
8653
|
const now = input.now ?? Date.now();
|
|
8418
8654
|
const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
|
|
8419
|
-
if (!
|
|
8655
|
+
if (!existsSync34(input.worktreePath)) return null;
|
|
8420
8656
|
if (input.runId && input.workerName) {
|
|
8421
|
-
const heartbeatPath =
|
|
8657
|
+
const heartbeatPath = path45.join(
|
|
8422
8658
|
input.harnessRoot,
|
|
8423
8659
|
"runs",
|
|
8424
8660
|
input.runId,
|
|
@@ -8427,13 +8663,13 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
8427
8663
|
"heartbeat.jsonl"
|
|
8428
8664
|
);
|
|
8429
8665
|
try {
|
|
8430
|
-
const mtime =
|
|
8666
|
+
const mtime = statSync11(heartbeatPath).mtimeMs;
|
|
8431
8667
|
if (now - mtime < heartbeatFreshMs) return "active_worker";
|
|
8432
8668
|
} catch {
|
|
8433
8669
|
}
|
|
8434
8670
|
}
|
|
8435
|
-
const gitDir =
|
|
8436
|
-
if (!
|
|
8671
|
+
const gitDir = path45.join(input.worktreePath, ".git");
|
|
8672
|
+
if (!existsSync34(gitDir)) return null;
|
|
8437
8673
|
const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
|
|
8438
8674
|
if (porcelain.status !== 0) return "pr_or_unmerged_commits";
|
|
8439
8675
|
const dirtyLines = porcelain.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
@@ -8462,12 +8698,12 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
8462
8698
|
}
|
|
8463
8699
|
|
|
8464
8700
|
// src/cleanup-harness-roots.ts
|
|
8465
|
-
import { existsSync as
|
|
8701
|
+
import { existsSync as existsSync35 } from "node:fs";
|
|
8466
8702
|
import { homedir as homedir11 } from "node:os";
|
|
8467
|
-
import
|
|
8703
|
+
import path46 from "node:path";
|
|
8468
8704
|
var WELL_KNOWN_HARNESS_SCAN_ROOTS = [
|
|
8469
8705
|
"/var/tmp/kynver-harness",
|
|
8470
|
-
|
|
8706
|
+
path46.join(homedir11(), ".openclaw", "harness")
|
|
8471
8707
|
];
|
|
8472
8708
|
function addRoot(seen, roots, candidate) {
|
|
8473
8709
|
if (!candidate?.trim()) return;
|
|
@@ -8489,8 +8725,8 @@ function resolveHarnessScanRoots(options = {}) {
|
|
|
8489
8725
|
for (const candidate of extra ?? []) addRoot(seen, roots, candidate);
|
|
8490
8726
|
if (shouldScanWellKnownRoots(options)) {
|
|
8491
8727
|
for (const candidate of WELL_KNOWN_HARNESS_SCAN_ROOTS) {
|
|
8492
|
-
const resolved =
|
|
8493
|
-
if (!seen.has(resolved) &&
|
|
8728
|
+
const resolved = path46.resolve(candidate);
|
|
8729
|
+
if (!seen.has(resolved) && existsSync35(resolved)) addRoot(seen, roots, resolved);
|
|
8494
8730
|
}
|
|
8495
8731
|
}
|
|
8496
8732
|
return roots;
|
|
@@ -8579,9 +8815,9 @@ function mergeWorktreeIndexes(scanRoots) {
|
|
|
8579
8815
|
}
|
|
8580
8816
|
function worktreePathForCandidate(candidate, worktreesDir) {
|
|
8581
8817
|
if (candidate.runId && candidate.worker) {
|
|
8582
|
-
return
|
|
8818
|
+
return path47.join(worktreesDir, candidate.runId, candidate.worker);
|
|
8583
8819
|
}
|
|
8584
|
-
return
|
|
8820
|
+
return path47.resolve(candidate.path, "..");
|
|
8585
8821
|
}
|
|
8586
8822
|
function runHarnessCleanup(options = {}) {
|
|
8587
8823
|
let retention = resolveHarnessRetention(options);
|
|
@@ -8598,7 +8834,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8598
8834
|
const atSweepCap = () => actions.length >= maxActions;
|
|
8599
8835
|
for (const harnessRoot of paths.scanRoots) {
|
|
8600
8836
|
if (atSweepCap()) break;
|
|
8601
|
-
const worktreesDir =
|
|
8837
|
+
const worktreesDir = path47.join(harnessRoot, "worktrees");
|
|
8602
8838
|
const scanOpts = {
|
|
8603
8839
|
harnessRoot,
|
|
8604
8840
|
worktreesDir,
|
|
@@ -8612,7 +8848,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8612
8848
|
for (const raw of scanDependencyCacheCandidates(scanOpts)) {
|
|
8613
8849
|
if (atSweepCap()) break;
|
|
8614
8850
|
const candidate = attachCandidateBytes(raw, retention.accountBytes);
|
|
8615
|
-
const resolved =
|
|
8851
|
+
const resolved = path47.resolve(candidate.path);
|
|
8616
8852
|
if (processedPaths.has(resolved)) continue;
|
|
8617
8853
|
processedPaths.add(resolved);
|
|
8618
8854
|
const pathSkip = pathGuardForDependencyCache(candidate, harnessRoot, worktreesDir);
|
|
@@ -8622,7 +8858,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8622
8858
|
continue;
|
|
8623
8859
|
}
|
|
8624
8860
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
8625
|
-
const indexed = index.get(
|
|
8861
|
+
const indexed = index.get(path47.resolve(worktreePath)) ?? null;
|
|
8626
8862
|
const guardReason = skipDependencyCacheRemoval({
|
|
8627
8863
|
indexed,
|
|
8628
8864
|
includeOrphans: true,
|
|
@@ -8642,7 +8878,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8642
8878
|
for (const raw of scanBuildCacheCandidates(scanOpts)) {
|
|
8643
8879
|
if (atSweepCap()) break;
|
|
8644
8880
|
const candidate = attachCandidateBytes(raw, retention.accountBytes);
|
|
8645
|
-
const resolved =
|
|
8881
|
+
const resolved = path47.resolve(candidate.path);
|
|
8646
8882
|
if (processedPaths.has(resolved)) continue;
|
|
8647
8883
|
processedPaths.add(resolved);
|
|
8648
8884
|
const pathSkip = isHarnessBuildCachePath(candidate.path, harnessRoot, worktreesDir);
|
|
@@ -8652,7 +8888,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8652
8888
|
continue;
|
|
8653
8889
|
}
|
|
8654
8890
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
8655
|
-
const indexed = index.get(
|
|
8891
|
+
const indexed = index.get(path47.resolve(worktreePath)) ?? null;
|
|
8656
8892
|
const guardReason = skipBuildCacheRemoval({
|
|
8657
8893
|
indexed,
|
|
8658
8894
|
includeOrphans: true,
|
|
@@ -8676,11 +8912,11 @@ function runHarnessCleanup(options = {}) {
|
|
|
8676
8912
|
const worktreeSeen = /* @__PURE__ */ new Set();
|
|
8677
8913
|
for (const raw of worktreeCandidates) {
|
|
8678
8914
|
if (atSweepCap()) break;
|
|
8679
|
-
const resolved =
|
|
8915
|
+
const resolved = path47.resolve(raw.path);
|
|
8680
8916
|
if (worktreeSeen.has(resolved)) continue;
|
|
8681
8917
|
worktreeSeen.add(resolved);
|
|
8682
8918
|
const candidate = attachCandidateBytes({ ...raw, path: resolved }, retention.accountBytes);
|
|
8683
|
-
const indexed = index.get(
|
|
8919
|
+
const indexed = index.get(path47.resolve(candidate.path)) ?? null;
|
|
8684
8920
|
const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
|
|
8685
8921
|
worktreePath: candidate.path,
|
|
8686
8922
|
harnessRoot,
|
|
@@ -8690,7 +8926,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8690
8926
|
});
|
|
8691
8927
|
const guardSkip = skipWorktreeRemoval({
|
|
8692
8928
|
indexed,
|
|
8693
|
-
worktreePath:
|
|
8929
|
+
worktreePath: path47.resolve(candidate.path),
|
|
8694
8930
|
includeOrphans: retention.includeOrphans,
|
|
8695
8931
|
worktreesAgeMs: retention.worktreesAgeMs,
|
|
8696
8932
|
terminalWorktreesAgeMs: retention.terminalWorktreesAgeMs,
|
|
@@ -8717,10 +8953,10 @@ function runHarnessCleanup(options = {}) {
|
|
|
8717
8953
|
})) {
|
|
8718
8954
|
if (atSweepCap()) break;
|
|
8719
8955
|
const candidate = attachCandidateBytes(raw, retention.accountBytes);
|
|
8720
|
-
const resolved =
|
|
8956
|
+
const resolved = path47.resolve(candidate.path);
|
|
8721
8957
|
if (processedPaths.has(resolved)) continue;
|
|
8722
8958
|
processedPaths.add(resolved);
|
|
8723
|
-
const runId = candidate.runId ??
|
|
8959
|
+
const runId = candidate.runId ?? path47.basename(resolved);
|
|
8724
8960
|
const dirSkip = skipRunDirectoryRemoval({
|
|
8725
8961
|
harnessRoot,
|
|
8726
8962
|
runId,
|
|
@@ -8812,8 +9048,8 @@ import { mkdirSync as mkdirSync7, realpathSync } from "node:fs";
|
|
|
8812
9048
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
8813
9049
|
|
|
8814
9050
|
// src/discard-disposable.ts
|
|
8815
|
-
import { existsSync as
|
|
8816
|
-
import
|
|
9051
|
+
import { existsSync as existsSync36, rmSync as rmSync3 } from "node:fs";
|
|
9052
|
+
import path48 from "node:path";
|
|
8817
9053
|
function normalizeRelativePath2(value) {
|
|
8818
9054
|
const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
8819
9055
|
if (!normalized || normalized.startsWith("/") || normalized.includes("..")) {
|
|
@@ -8834,15 +9070,15 @@ function discardDisposableArtifacts(args) {
|
|
|
8834
9070
|
if (paths.length === 0) {
|
|
8835
9071
|
return { ok: false, removed: [], reason: "requires at least one --path" };
|
|
8836
9072
|
}
|
|
8837
|
-
const worktreeRoot =
|
|
9073
|
+
const worktreeRoot = path48.resolve(worker.worktreePath);
|
|
8838
9074
|
const removed = [];
|
|
8839
9075
|
for (const raw of paths) {
|
|
8840
9076
|
const rel = normalizeRelativePath2(raw);
|
|
8841
|
-
const abs =
|
|
8842
|
-
if (!abs.startsWith(worktreeRoot +
|
|
9077
|
+
const abs = path48.resolve(worktreeRoot, rel);
|
|
9078
|
+
if (!abs.startsWith(worktreeRoot + path48.sep) && abs !== worktreeRoot) {
|
|
8843
9079
|
return { ok: false, removed, reason: `path escapes worktree: ${raw}` };
|
|
8844
9080
|
}
|
|
8845
|
-
if (!
|
|
9081
|
+
if (!existsSync36(abs)) {
|
|
8846
9082
|
return { ok: false, removed, reason: `path not found: ${raw}` };
|
|
8847
9083
|
}
|
|
8848
9084
|
rmSync3(abs, { recursive: true, force: true });
|
|
@@ -8864,8 +9100,472 @@ function discardDisposableCli(args) {
|
|
|
8864
9100
|
if (!result.ok) process.exit(1);
|
|
8865
9101
|
}
|
|
8866
9102
|
|
|
8867
|
-
// src/
|
|
9103
|
+
// src/cron/cron-env.ts
|
|
9104
|
+
import { existsSync as existsSync37 } from "node:fs";
|
|
9105
|
+
import { homedir as homedir12 } from "node:os";
|
|
9106
|
+
import path49 from "node:path";
|
|
9107
|
+
function envFlag3(name, defaultValue) {
|
|
9108
|
+
const raw = process.env[name]?.trim().toLowerCase();
|
|
9109
|
+
if (!raw) return defaultValue;
|
|
9110
|
+
if (raw === "0" || raw === "false" || raw === "no" || raw === "off") return false;
|
|
9111
|
+
if (raw === "1" || raw === "true" || raw === "yes" || raw === "on") return true;
|
|
9112
|
+
return defaultValue;
|
|
9113
|
+
}
|
|
9114
|
+
function envInt(name, fallback, min = 1) {
|
|
9115
|
+
const n = Number(process.env[name]);
|
|
9116
|
+
if (!Number.isFinite(n) || n < min) return fallback;
|
|
9117
|
+
return Math.floor(n);
|
|
9118
|
+
}
|
|
9119
|
+
function defaultKynverCronStorePath() {
|
|
9120
|
+
const explicit = process.env.KYNVER_CRON_STORE_PATH?.trim() || process.env.OPENCLAW_CRON_STORE_PATH?.trim();
|
|
9121
|
+
if (explicit) return explicit;
|
|
9122
|
+
return path49.join(homedir12(), ".kynver", "agent-os-cron.json");
|
|
9123
|
+
}
|
|
9124
|
+
function defaultKynverCronStatePath(storePath = defaultKynverCronStorePath()) {
|
|
9125
|
+
const explicit = process.env.KYNVER_CRON_TICK_STATE_PATH?.trim();
|
|
9126
|
+
if (explicit) return explicit;
|
|
9127
|
+
return `${storePath.replace(/\.json$/i, "")}.tick-state.json`;
|
|
9128
|
+
}
|
|
9129
|
+
function resolveKynverCronFireBaseUrl() {
|
|
9130
|
+
const config = loadUserConfig();
|
|
9131
|
+
return process.env.KYNVER_API_URL?.trim() || config.apiBaseUrl?.trim() || process.env.KYNVER_CRON_FIRE_BASE_URL?.trim() || process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || null;
|
|
9132
|
+
}
|
|
9133
|
+
function resolveKynverCronSecret() {
|
|
9134
|
+
return process.env.KYNVER_CRON_SECRET?.trim() || process.env.OPENCLAW_CRON_SECRET?.trim() || process.env.KYNVER_RUNTIME_SECRET?.trim() || null;
|
|
9135
|
+
}
|
|
9136
|
+
function resolveKynverCronEnv() {
|
|
9137
|
+
const storePath = defaultKynverCronStorePath();
|
|
9138
|
+
const statePath = defaultKynverCronStatePath(storePath);
|
|
9139
|
+
const fireBaseUrl = resolveKynverCronFireBaseUrl();
|
|
9140
|
+
const secret = resolveKynverCronSecret();
|
|
9141
|
+
const credsReady = Boolean(fireBaseUrl && secret);
|
|
9142
|
+
const storeExists = existsSync37(storePath);
|
|
9143
|
+
const defaultEnabled = credsReady && (storeExists || envFlag3("KYNVER_CRON_TICK_FORCE", false));
|
|
9144
|
+
return {
|
|
9145
|
+
storePath,
|
|
9146
|
+
statePath,
|
|
9147
|
+
lockPath: `${statePath}.lock`,
|
|
9148
|
+
fireBaseUrl,
|
|
9149
|
+
secret,
|
|
9150
|
+
tickEnabled: envFlag3("KYNVER_CRON_TICK_ENABLED", defaultEnabled),
|
|
9151
|
+
tickIntervalMs: envInt("KYNVER_CRON_TICK_INTERVAL_MS", 6e4, 5e3),
|
|
9152
|
+
missedRunPolicy: process.env.KYNVER_CRON_MISSED_RUN_POLICY?.trim().toLowerCase() === "skip" ? "skip" : "catch_up",
|
|
9153
|
+
maxCatchUpPerTick: envInt("KYNVER_CRON_MAX_CATCH_UP_PER_TICK", 3, 0),
|
|
9154
|
+
maxRetries: envInt("KYNVER_CRON_MAX_RETRIES", 3, 0),
|
|
9155
|
+
retryBackoffMs: envInt("KYNVER_CRON_RETRY_BACKOFF_MS", 6e4, 1e3),
|
|
9156
|
+
inflightLeaseMs: envInt("KYNVER_CRON_INFLIGHT_LEASE_MS", 12e4, 5e3)
|
|
9157
|
+
};
|
|
9158
|
+
}
|
|
9159
|
+
function isKynverCronDaemonPrimary(env = resolveKynverCronEnv()) {
|
|
9160
|
+
return env.tickEnabled && Boolean(env.fireBaseUrl && env.secret);
|
|
9161
|
+
}
|
|
9162
|
+
|
|
9163
|
+
// src/cron/cron-fire.ts
|
|
9164
|
+
function trimTrailingSlash2(url) {
|
|
9165
|
+
return url.replace(/\/+$/, "");
|
|
9166
|
+
}
|
|
9167
|
+
async function fireKynverCronJob(input) {
|
|
9168
|
+
const doFetch = input.fetchFn ?? fetch;
|
|
9169
|
+
const callbackPath = input.entry.spec.callbackPath.startsWith("/") ? input.entry.spec.callbackPath : `/${input.entry.spec.callbackPath}`;
|
|
9170
|
+
const url = `${trimTrailingSlash2(input.baseUrl)}${callbackPath}`;
|
|
9171
|
+
const jobId = input.jobId ?? input.entry.spec.dedupeKey ?? null;
|
|
9172
|
+
const body = {
|
|
9173
|
+
source: "kynver-cron",
|
|
9174
|
+
jobId,
|
|
9175
|
+
agentOsId: input.entry.spec.target.agentOsId,
|
|
9176
|
+
kind: input.entry.spec.kind,
|
|
9177
|
+
target: input.entry.spec.target,
|
|
9178
|
+
...input.entry.spec.payload !== void 0 && { payload: input.entry.spec.payload }
|
|
9179
|
+
};
|
|
9180
|
+
const res = await doFetch(url, {
|
|
9181
|
+
method: "POST",
|
|
9182
|
+
headers: buildHarnessCallbackHeaders(input.secret),
|
|
9183
|
+
body: JSON.stringify(body)
|
|
9184
|
+
});
|
|
9185
|
+
let parsed = null;
|
|
9186
|
+
try {
|
|
9187
|
+
parsed = await res.json();
|
|
9188
|
+
} catch {
|
|
9189
|
+
parsed = null;
|
|
9190
|
+
}
|
|
9191
|
+
return { ok: res.ok, status: res.status, body: parsed };
|
|
9192
|
+
}
|
|
9193
|
+
|
|
9194
|
+
// src/cron/cron-lock.ts
|
|
9195
|
+
import { closeSync as closeSync6, existsSync as existsSync38, openSync as openSync6, readFileSync as readFileSync13, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
9196
|
+
var STALE_LOCK_MS = 10 * 6e4;
|
|
9197
|
+
function readLockInfo(lockPath) {
|
|
9198
|
+
if (!existsSync38(lockPath)) return null;
|
|
9199
|
+
try {
|
|
9200
|
+
const parsed = JSON.parse(readFileSync13(lockPath, "utf8"));
|
|
9201
|
+
if (typeof parsed.pid === "number" && typeof parsed.at === "string") return parsed;
|
|
9202
|
+
} catch {
|
|
9203
|
+
return null;
|
|
9204
|
+
}
|
|
9205
|
+
return null;
|
|
9206
|
+
}
|
|
9207
|
+
function lockIsStale(lockPath) {
|
|
9208
|
+
const info = readLockInfo(lockPath);
|
|
9209
|
+
if (!info) return true;
|
|
9210
|
+
if (!isPidAlive(info.pid)) return true;
|
|
9211
|
+
const atMs = Date.parse(info.at);
|
|
9212
|
+
if (Number.isNaN(atMs)) return true;
|
|
9213
|
+
return Date.now() - atMs > STALE_LOCK_MS;
|
|
9214
|
+
}
|
|
9215
|
+
function tryAcquireCronTickLock(lockPath) {
|
|
9216
|
+
if (existsSync38(lockPath) && !lockIsStale(lockPath)) {
|
|
9217
|
+
const info = readLockInfo(lockPath);
|
|
9218
|
+
return {
|
|
9219
|
+
acquired: false,
|
|
9220
|
+
reason: info ? `held by pid ${info.pid}` : "held by another process"
|
|
9221
|
+
};
|
|
9222
|
+
}
|
|
9223
|
+
if (existsSync38(lockPath)) {
|
|
9224
|
+
try {
|
|
9225
|
+
unlinkSync2(lockPath);
|
|
9226
|
+
} catch {
|
|
9227
|
+
}
|
|
9228
|
+
}
|
|
9229
|
+
try {
|
|
9230
|
+
const fd = openSync6(lockPath, "wx");
|
|
9231
|
+
writeFileSync4(
|
|
9232
|
+
fd,
|
|
9233
|
+
JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() }),
|
|
9234
|
+
"utf8"
|
|
9235
|
+
);
|
|
9236
|
+
closeSync6(fd);
|
|
9237
|
+
return { acquired: true };
|
|
9238
|
+
} catch (err) {
|
|
9239
|
+
if (err.code === "EEXIST") {
|
|
9240
|
+
return { acquired: false, reason: "concurrent acquire" };
|
|
9241
|
+
}
|
|
9242
|
+
throw err;
|
|
9243
|
+
}
|
|
9244
|
+
}
|
|
9245
|
+
function releaseCronTickLock(lockPath) {
|
|
9246
|
+
try {
|
|
9247
|
+
unlinkSync2(lockPath);
|
|
9248
|
+
} catch {
|
|
9249
|
+
}
|
|
9250
|
+
}
|
|
9251
|
+
|
|
9252
|
+
// src/cron/cron-match.ts
|
|
9253
|
+
var CRON_RE = /^[\d*/,\-?LW#]+\s+[\d*/,\-?LW#]+\s+[\d*/,\-?LW#]+\s+[\d*/,\-?LW#]+\s+[\d*/,\-?LW#]+$/;
|
|
9254
|
+
function isCronExpression(value) {
|
|
9255
|
+
return CRON_RE.test(value.trim());
|
|
9256
|
+
}
|
|
9257
|
+
function parseList(field, min, max) {
|
|
9258
|
+
const out = /* @__PURE__ */ new Set();
|
|
9259
|
+
for (const part of field.split(",")) {
|
|
9260
|
+
const token = part.trim();
|
|
9261
|
+
if (!token) continue;
|
|
9262
|
+
if (token === "*") {
|
|
9263
|
+
for (let i = min; i <= max; i++) out.add(i);
|
|
9264
|
+
continue;
|
|
9265
|
+
}
|
|
9266
|
+
const stepMatch = /^(.+)\/(\d+)$/.exec(token);
|
|
9267
|
+
const base = stepMatch ? stepMatch[1] : token;
|
|
9268
|
+
const step = stepMatch ? Math.max(1, Number(stepMatch[2])) : 1;
|
|
9269
|
+
if (base === "*") {
|
|
9270
|
+
for (let i = min; i <= max; i += step) out.add(i);
|
|
9271
|
+
continue;
|
|
9272
|
+
}
|
|
9273
|
+
const rangeMatch = /^(\d+)-(\d+)$/.exec(base);
|
|
9274
|
+
if (rangeMatch) {
|
|
9275
|
+
const start = Math.max(min, Number(rangeMatch[1]));
|
|
9276
|
+
const end = Math.min(max, Number(rangeMatch[2]));
|
|
9277
|
+
for (let i = start; i <= end; i += step) out.add(i);
|
|
9278
|
+
continue;
|
|
9279
|
+
}
|
|
9280
|
+
const n = Number(base);
|
|
9281
|
+
if (Number.isInteger(n) && n >= min && n <= max) out.add(n);
|
|
9282
|
+
}
|
|
9283
|
+
return out;
|
|
9284
|
+
}
|
|
9285
|
+
function fieldMatches(field, value, min, max) {
|
|
9286
|
+
const trimmed = field.trim();
|
|
9287
|
+
if (trimmed === "*") return true;
|
|
9288
|
+
return parseList(trimmed, min, max).has(value);
|
|
9289
|
+
}
|
|
9290
|
+
function cronMatchesUtc(expr, at) {
|
|
9291
|
+
const parts = expr.trim().split(/\s+/);
|
|
9292
|
+
if (parts.length !== 5) return false;
|
|
9293
|
+
const [minF, hourF, domF, monF, dowF] = parts;
|
|
9294
|
+
return fieldMatches(minF, at.getUTCMinutes(), 0, 59) && fieldMatches(hourF, at.getUTCHours(), 0, 23) && fieldMatches(domF, at.getUTCDate(), 1, 31) && fieldMatches(monF, at.getUTCMonth() + 1, 1, 12) && fieldMatches(dowF, at.getUTCDay(), 0, 6);
|
|
9295
|
+
}
|
|
9296
|
+
var MAX_LOOKAHEAD_MINUTES = 366 * 24 * 60;
|
|
9297
|
+
function truncateToUtcMinute(at) {
|
|
9298
|
+
return new Date(
|
|
9299
|
+
Date.UTC(
|
|
9300
|
+
at.getUTCFullYear(),
|
|
9301
|
+
at.getUTCMonth(),
|
|
9302
|
+
at.getUTCDate(),
|
|
9303
|
+
at.getUTCHours(),
|
|
9304
|
+
at.getUTCMinutes(),
|
|
9305
|
+
0,
|
|
9306
|
+
0
|
|
9307
|
+
)
|
|
9308
|
+
);
|
|
9309
|
+
}
|
|
9310
|
+
function computeNextCronFireUtc(expr, after) {
|
|
9311
|
+
if (!isCronExpression(expr)) return null;
|
|
9312
|
+
let cursor = truncateToUtcMinute(after);
|
|
9313
|
+
cursor = new Date(cursor.getTime() + 6e4);
|
|
9314
|
+
for (let i = 0; i < MAX_LOOKAHEAD_MINUTES; i++) {
|
|
9315
|
+
if (cronMatchesUtc(expr, cursor)) return cursor;
|
|
9316
|
+
cursor = new Date(cursor.getTime() + 6e4);
|
|
9317
|
+
}
|
|
9318
|
+
return null;
|
|
9319
|
+
}
|
|
9320
|
+
function computeInitialNextFire(spec, now) {
|
|
9321
|
+
if (spec.scheduleKind === "runAt" && spec.runAt) {
|
|
9322
|
+
const ms = Date.parse(spec.runAt);
|
|
9323
|
+
return Number.isNaN(ms) ? null : new Date(ms).toISOString();
|
|
9324
|
+
}
|
|
9325
|
+
if (spec.scheduleKind === "cron" && spec.cron) {
|
|
9326
|
+
const next = computeNextCronFireUtc(spec.cron.trim(), now);
|
|
9327
|
+
return next ? next.toISOString() : null;
|
|
9328
|
+
}
|
|
9329
|
+
return null;
|
|
9330
|
+
}
|
|
9331
|
+
function advanceRecurringNextFire(spec, fromInclusive) {
|
|
9332
|
+
if (spec.scheduleKind !== "cron" || !spec.cron?.trim()) return null;
|
|
9333
|
+
const next = computeNextCronFireUtc(spec.cron.trim(), fromInclusive);
|
|
9334
|
+
return next ? next.toISOString() : null;
|
|
9335
|
+
}
|
|
9336
|
+
|
|
9337
|
+
// src/cron/cron-store.ts
|
|
9338
|
+
import { promises as fs2 } from "node:fs";
|
|
9339
|
+
async function readFileIfExists(filePath) {
|
|
9340
|
+
try {
|
|
9341
|
+
return await fs2.readFile(filePath, "utf8");
|
|
9342
|
+
} catch (err) {
|
|
9343
|
+
if (err.code === "ENOENT") return null;
|
|
9344
|
+
throw err;
|
|
9345
|
+
}
|
|
9346
|
+
}
|
|
9347
|
+
function parseCronStore(raw) {
|
|
9348
|
+
if (!raw) return [];
|
|
9349
|
+
try {
|
|
9350
|
+
const parsed = JSON.parse(raw);
|
|
9351
|
+
return Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
9352
|
+
} catch {
|
|
9353
|
+
return [];
|
|
9354
|
+
}
|
|
9355
|
+
}
|
|
9356
|
+
async function loadCronJobs(storePath = defaultKynverCronStorePath()) {
|
|
9357
|
+
const raw = await readFileIfExists(storePath);
|
|
9358
|
+
return parseCronStore(raw);
|
|
9359
|
+
}
|
|
9360
|
+
|
|
9361
|
+
// src/cron/cron-tick-state.ts
|
|
9362
|
+
import { randomBytes } from "node:crypto";
|
|
9363
|
+
import { promises as fs3 } from "node:fs";
|
|
8868
9364
|
import path50 from "node:path";
|
|
9365
|
+
var EMPTY = { version: 1, jobs: {} };
|
|
9366
|
+
async function readFileIfExists2(filePath) {
|
|
9367
|
+
try {
|
|
9368
|
+
return await fs3.readFile(filePath, "utf8");
|
|
9369
|
+
} catch (err) {
|
|
9370
|
+
if (err.code === "ENOENT") return null;
|
|
9371
|
+
throw err;
|
|
9372
|
+
}
|
|
9373
|
+
}
|
|
9374
|
+
function parseCronTickState(raw) {
|
|
9375
|
+
if (!raw) return { ...EMPTY, jobs: { ...EMPTY.jobs } };
|
|
9376
|
+
try {
|
|
9377
|
+
const parsed = JSON.parse(raw);
|
|
9378
|
+
if (parsed?.version !== 1 || typeof parsed.jobs !== "object" || !parsed.jobs) {
|
|
9379
|
+
return { ...EMPTY, jobs: {} };
|
|
9380
|
+
}
|
|
9381
|
+
return parsed;
|
|
9382
|
+
} catch {
|
|
9383
|
+
return { ...EMPTY, jobs: {} };
|
|
9384
|
+
}
|
|
9385
|
+
}
|
|
9386
|
+
async function loadCronTickState(statePath) {
|
|
9387
|
+
const raw = await readFileIfExists2(statePath);
|
|
9388
|
+
return parseCronTickState(raw);
|
|
9389
|
+
}
|
|
9390
|
+
async function writeStateAtomic(statePath, state) {
|
|
9391
|
+
await fs3.mkdir(path50.dirname(statePath), { recursive: true });
|
|
9392
|
+
const suffix = randomBytes(6).toString("hex");
|
|
9393
|
+
const tmp = `${statePath}.tmp-${process.pid}-${Date.now()}-${suffix}`;
|
|
9394
|
+
await fs3.writeFile(tmp, `${JSON.stringify(state, null, 2)}
|
|
9395
|
+
`, "utf8");
|
|
9396
|
+
try {
|
|
9397
|
+
await fs3.rename(tmp, statePath);
|
|
9398
|
+
} catch (err) {
|
|
9399
|
+
const code = err.code;
|
|
9400
|
+
if (code !== "EPERM" && code !== "EEXIST" && code !== "EACCES") throw err;
|
|
9401
|
+
await fs3.unlink(tmp).catch(() => {
|
|
9402
|
+
});
|
|
9403
|
+
}
|
|
9404
|
+
}
|
|
9405
|
+
async function saveCronTickState(statePath, state) {
|
|
9406
|
+
await writeStateAtomic(statePath, state);
|
|
9407
|
+
}
|
|
9408
|
+
function getOrCreateJobState(state, providerScheduleId) {
|
|
9409
|
+
const existing = state.jobs[providerScheduleId];
|
|
9410
|
+
if (existing) return existing;
|
|
9411
|
+
const created = {
|
|
9412
|
+
providerScheduleId,
|
|
9413
|
+
nextFireAt: null,
|
|
9414
|
+
lastFiredAt: null,
|
|
9415
|
+
lastAttemptAt: null,
|
|
9416
|
+
consecutiveFailures: 0,
|
|
9417
|
+
completedAt: null,
|
|
9418
|
+
inflightUntil: null
|
|
9419
|
+
};
|
|
9420
|
+
state.jobs[providerScheduleId] = created;
|
|
9421
|
+
return created;
|
|
9422
|
+
}
|
|
9423
|
+
|
|
9424
|
+
// src/cron/cron-tick.ts
|
|
9425
|
+
function isInflight(job, nowMs) {
|
|
9426
|
+
if (!job.inflightUntil) return false;
|
|
9427
|
+
const until = Date.parse(job.inflightUntil);
|
|
9428
|
+
return !Number.isNaN(until) && until > nowMs;
|
|
9429
|
+
}
|
|
9430
|
+
function isCompleted(job) {
|
|
9431
|
+
return Boolean(job.completedAt);
|
|
9432
|
+
}
|
|
9433
|
+
function backoffReady(job, env, nowMs) {
|
|
9434
|
+
if (job.consecutiveFailures === 0) return true;
|
|
9435
|
+
if (job.consecutiveFailures > env.maxRetries) return false;
|
|
9436
|
+
if (!job.lastAttemptAt) return true;
|
|
9437
|
+
const last = Date.parse(job.lastAttemptAt);
|
|
9438
|
+
if (Number.isNaN(last)) return true;
|
|
9439
|
+
return nowMs - last >= env.retryBackoffMs;
|
|
9440
|
+
}
|
|
9441
|
+
function ensureNextFire(entry, job, now) {
|
|
9442
|
+
if (job.nextFireAt) return job.nextFireAt;
|
|
9443
|
+
const initial = computeInitialNextFire(entry.spec, now);
|
|
9444
|
+
job.nextFireAt = initial;
|
|
9445
|
+
return initial;
|
|
9446
|
+
}
|
|
9447
|
+
function isDue(entry, job, nowMs, env) {
|
|
9448
|
+
if (entry.paused || isCompleted(job) || isInflight(job, nowMs)) return false;
|
|
9449
|
+
if (!backoffReady(job, env, nowMs)) return false;
|
|
9450
|
+
const nextMs = job.nextFireAt ? Date.parse(job.nextFireAt) : NaN;
|
|
9451
|
+
if (Number.isNaN(nextMs)) return false;
|
|
9452
|
+
return nowMs >= nextMs;
|
|
9453
|
+
}
|
|
9454
|
+
function advanceRecurringBeforeFire(entry, job, now) {
|
|
9455
|
+
if (entry.spec.scheduleKind !== "cron") return;
|
|
9456
|
+
job.nextFireAt = advanceRecurringNextFire(entry.spec, now);
|
|
9457
|
+
}
|
|
9458
|
+
async function runKynverCronTick(opts = {}) {
|
|
9459
|
+
const env = opts.env ?? resolveKynverCronEnv();
|
|
9460
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
9461
|
+
const nowMs = now.getTime();
|
|
9462
|
+
if (!env.tickEnabled) {
|
|
9463
|
+
return { enabled: false, skipped: "tick_disabled", scanned: 0, due: 0, fired: 0, skippedJobs: 0, errors: 0 };
|
|
9464
|
+
}
|
|
9465
|
+
if (!env.fireBaseUrl || !env.secret) {
|
|
9466
|
+
return {
|
|
9467
|
+
enabled: true,
|
|
9468
|
+
skipped: "missing_fire_credentials",
|
|
9469
|
+
scanned: 0,
|
|
9470
|
+
due: 0,
|
|
9471
|
+
fired: 0,
|
|
9472
|
+
skippedJobs: 0,
|
|
9473
|
+
errors: 0
|
|
9474
|
+
};
|
|
9475
|
+
}
|
|
9476
|
+
const lock = tryAcquireCronTickLock(env.lockPath);
|
|
9477
|
+
if (!lock.acquired) {
|
|
9478
|
+
return {
|
|
9479
|
+
enabled: true,
|
|
9480
|
+
skipped: lock.reason ?? "lock_not_acquired",
|
|
9481
|
+
scanned: 0,
|
|
9482
|
+
due: 0,
|
|
9483
|
+
fired: 0,
|
|
9484
|
+
skippedJobs: 0,
|
|
9485
|
+
errors: 0,
|
|
9486
|
+
lockHeld: true
|
|
9487
|
+
};
|
|
9488
|
+
}
|
|
9489
|
+
try {
|
|
9490
|
+
const entries = await loadCronJobs(env.storePath);
|
|
9491
|
+
const filtered = opts.agentOsIdFilter ? entries.filter((e) => e.spec.target.agentOsId === opts.agentOsIdFilter) : entries;
|
|
9492
|
+
const state = await loadCronTickState(env.statePath);
|
|
9493
|
+
const dueEntries = [];
|
|
9494
|
+
for (const entry of filtered) {
|
|
9495
|
+
const job = getOrCreateJobState(state, entry.providerScheduleId);
|
|
9496
|
+
ensureNextFire(entry, job, now);
|
|
9497
|
+
if (isDue(entry, job, nowMs, env)) {
|
|
9498
|
+
dueEntries.push({ entry, job });
|
|
9499
|
+
}
|
|
9500
|
+
}
|
|
9501
|
+
dueEntries.sort((a, b) => {
|
|
9502
|
+
const aMs = Date.parse(a.job.nextFireAt ?? "") || 0;
|
|
9503
|
+
const bMs = Date.parse(b.job.nextFireAt ?? "") || 0;
|
|
9504
|
+
return aMs - bMs;
|
|
9505
|
+
});
|
|
9506
|
+
let fired = 0;
|
|
9507
|
+
let errors = 0;
|
|
9508
|
+
let skippedJobs = 0;
|
|
9509
|
+
let catchUpBudget = env.maxCatchUpPerTick;
|
|
9510
|
+
for (const { entry, job } of dueEntries) {
|
|
9511
|
+
if (env.missedRunPolicy === "skip" && entry.spec.scheduleKind === "cron") {
|
|
9512
|
+
const next = Date.parse(job.nextFireAt ?? "");
|
|
9513
|
+
if (!Number.isNaN(next) && next < nowMs - env.tickIntervalMs * 2) {
|
|
9514
|
+
job.nextFireAt = advanceRecurringNextFire(entry.spec, now);
|
|
9515
|
+
skippedJobs++;
|
|
9516
|
+
continue;
|
|
9517
|
+
}
|
|
9518
|
+
}
|
|
9519
|
+
if (catchUpBudget <= 0) {
|
|
9520
|
+
skippedJobs++;
|
|
9521
|
+
continue;
|
|
9522
|
+
}
|
|
9523
|
+
job.inflightUntil = new Date(nowMs + env.inflightLeaseMs).toISOString();
|
|
9524
|
+
job.lastAttemptAt = now.toISOString();
|
|
9525
|
+
advanceRecurringBeforeFire(entry, job, now);
|
|
9526
|
+
try {
|
|
9527
|
+
const result = await fireKynverCronJob({
|
|
9528
|
+
entry,
|
|
9529
|
+
baseUrl: env.fireBaseUrl,
|
|
9530
|
+
secret: env.secret,
|
|
9531
|
+
fetchFn: opts.fetchFn
|
|
9532
|
+
});
|
|
9533
|
+
job.inflightUntil = null;
|
|
9534
|
+
if (result.ok) {
|
|
9535
|
+
job.lastFiredAt = now.toISOString();
|
|
9536
|
+
job.consecutiveFailures = 0;
|
|
9537
|
+
if (entry.spec.scheduleKind === "runAt") {
|
|
9538
|
+
job.completedAt = now.toISOString();
|
|
9539
|
+
job.nextFireAt = null;
|
|
9540
|
+
}
|
|
9541
|
+
fired++;
|
|
9542
|
+
catchUpBudget--;
|
|
9543
|
+
} else {
|
|
9544
|
+
job.consecutiveFailures += 1;
|
|
9545
|
+
errors++;
|
|
9546
|
+
}
|
|
9547
|
+
} catch {
|
|
9548
|
+
job.inflightUntil = null;
|
|
9549
|
+
job.consecutiveFailures += 1;
|
|
9550
|
+
errors++;
|
|
9551
|
+
}
|
|
9552
|
+
}
|
|
9553
|
+
await saveCronTickState(env.statePath, state);
|
|
9554
|
+
return {
|
|
9555
|
+
enabled: true,
|
|
9556
|
+
scanned: filtered.length,
|
|
9557
|
+
due: dueEntries.length,
|
|
9558
|
+
fired,
|
|
9559
|
+
skippedJobs,
|
|
9560
|
+
errors
|
|
9561
|
+
};
|
|
9562
|
+
} finally {
|
|
9563
|
+
releaseCronTickLock(env.lockPath);
|
|
9564
|
+
}
|
|
9565
|
+
}
|
|
9566
|
+
|
|
9567
|
+
// src/pipeline-tick.ts
|
|
9568
|
+
import path53 from "node:path";
|
|
8869
9569
|
|
|
8870
9570
|
// src/pipeline-dispatch.ts
|
|
8871
9571
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -8931,35 +9631,72 @@ async function runPipelineDispatch(args, slots) {
|
|
|
8931
9631
|
};
|
|
8932
9632
|
}
|
|
8933
9633
|
|
|
9634
|
+
// src/pipeline-exact-targets.ts
|
|
9635
|
+
function operatorExactTargetTaskIds(operatorTick) {
|
|
9636
|
+
if (!operatorTick || typeof operatorTick !== "object") return [];
|
|
9637
|
+
const body = operatorTick;
|
|
9638
|
+
const raw = body.response?.dispatch?.exactTargetTaskIds;
|
|
9639
|
+
if (!Array.isArray(raw)) return [];
|
|
9640
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9641
|
+
const out = [];
|
|
9642
|
+
for (const value of raw) {
|
|
9643
|
+
if (typeof value !== "string") continue;
|
|
9644
|
+
const id = value.trim();
|
|
9645
|
+
if (!id || seen.has(id)) continue;
|
|
9646
|
+
seen.add(id);
|
|
9647
|
+
out.push(id);
|
|
9648
|
+
}
|
|
9649
|
+
return out;
|
|
9650
|
+
}
|
|
9651
|
+
|
|
8934
9652
|
// src/pipeline-max-starts.ts
|
|
8935
9653
|
function operatorDispatchFromTick(operatorTick) {
|
|
8936
9654
|
const body = operatorTick;
|
|
8937
9655
|
const dispatch = body.response?.dispatch;
|
|
8938
9656
|
return dispatch && typeof dispatch === "object" ? dispatch : null;
|
|
8939
9657
|
}
|
|
9658
|
+
function nonNegativeInt(value) {
|
|
9659
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
9660
|
+
return Math.max(0, Math.floor(value));
|
|
9661
|
+
}
|
|
8940
9662
|
function resolvePipelineMaxStarts(resourceGate, operatorTick) {
|
|
8941
9663
|
const dispatch = operatorDispatchFromTick(operatorTick);
|
|
8942
|
-
const advised =
|
|
9664
|
+
const advised = nonNegativeInt(dispatch?.recommendedMaxStarts);
|
|
9665
|
+
const actionableReady = nonNegativeInt(dispatch?.actionableReady);
|
|
9666
|
+
const queuedTasks = nonNegativeInt(dispatch?.queuedTasks);
|
|
9667
|
+
const boardAdvancedThisTick = nonNegativeInt(dispatch?.boardAdvancedThisTick) ?? 0;
|
|
9668
|
+
const leaseReapedThisTick = nonNegativeInt(dispatch?.leaseReapedThisTick) ?? 0;
|
|
9669
|
+
const hygieneAdvanced = boardAdvancedThisTick + leaseReapedThisTick;
|
|
9670
|
+
const readyFloor = actionableReady ?? queuedTasks;
|
|
8943
9671
|
let maxStarts = resourceGate.slotsAvailable;
|
|
8944
|
-
if (
|
|
9672
|
+
if (readyFloor !== null) {
|
|
9673
|
+
maxStarts = Math.min(maxStarts, readyFloor);
|
|
9674
|
+
} else if (advised !== null) {
|
|
8945
9675
|
maxStarts = Math.min(maxStarts, advised);
|
|
8946
9676
|
}
|
|
8947
|
-
|
|
8948
|
-
|
|
8949
|
-
|
|
8950
|
-
|
|
9677
|
+
if (readyFloor === null && advised !== null) {
|
|
9678
|
+
maxStarts = Math.max(maxStarts, Math.min(resourceGate.slotsAvailable, advised));
|
|
9679
|
+
}
|
|
9680
|
+
const underutilized = dispatch?.underutilized === true || (readyFloor ?? 0) > 0 && resourceGate.slotsAvailable > 0 && resourceGate.maxConcurrentWorkers > 0 && resourceGate.activeWorkers < resourceGate.maxConcurrentWorkers;
|
|
9681
|
+
if (resourceGate.slotsAvailable > 0 && maxStarts === 0 && (underutilized || hygieneAdvanced > 0)) {
|
|
9682
|
+
const ready = readyFloor ?? (hygieneAdvanced > 0 ? hygieneAdvanced : 1);
|
|
8951
9683
|
maxStarts = Math.min(resourceGate.slotsAvailable, Math.max(1, ready));
|
|
8952
9684
|
}
|
|
9685
|
+
const nonDispatchableReady = queuedTasks !== null && actionableReady !== null ? Math.max(0, queuedTasks - actionableReady) : null;
|
|
8953
9686
|
return {
|
|
8954
9687
|
maxStarts: Math.max(0, maxStarts),
|
|
8955
9688
|
underutilized,
|
|
8956
9689
|
advisedStarts: advised,
|
|
8957
|
-
|
|
9690
|
+
actionableReady,
|
|
9691
|
+
queuedTasks,
|
|
9692
|
+
nonDispatchableReady,
|
|
9693
|
+
boardAdvancedThisTick,
|
|
9694
|
+
leaseReapedThisTick
|
|
8958
9695
|
};
|
|
8959
9696
|
}
|
|
8960
9697
|
|
|
8961
9698
|
// src/plan-progress-daemon-sync.ts
|
|
8962
|
-
import
|
|
9699
|
+
import path51 from "node:path";
|
|
8963
9700
|
|
|
8964
9701
|
// src/plan-progress-sync.ts
|
|
8965
9702
|
async function syncPlanProgress(args) {
|
|
@@ -8983,7 +9720,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
8983
9720
|
const outcomes = [];
|
|
8984
9721
|
for (const name of Object.keys(run.workers || {})) {
|
|
8985
9722
|
const worker = readJson(
|
|
8986
|
-
|
|
9723
|
+
path51.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
8987
9724
|
void 0
|
|
8988
9725
|
);
|
|
8989
9726
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -9033,8 +9770,8 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
9033
9770
|
|
|
9034
9771
|
// src/installed-package-versions.ts
|
|
9035
9772
|
import { readFile } from "node:fs/promises";
|
|
9036
|
-
import { homedir as
|
|
9037
|
-
import
|
|
9773
|
+
import { homedir as homedir13 } from "node:os";
|
|
9774
|
+
import path52 from "node:path";
|
|
9038
9775
|
var MANAGED_PACKAGES = [
|
|
9039
9776
|
"@kynver-app/runtime",
|
|
9040
9777
|
"@kynver-app/openclaw-agent-os",
|
|
@@ -9048,13 +9785,13 @@ function unique(values) {
|
|
|
9048
9785
|
return [...new Set(values.filter((value) => Boolean(value)))];
|
|
9049
9786
|
}
|
|
9050
9787
|
function moduleRoots() {
|
|
9051
|
-
const home =
|
|
9052
|
-
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ??
|
|
9053
|
-
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ?
|
|
9788
|
+
const home = homedir13();
|
|
9789
|
+
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path52.join(home, ".openclaw", "npm");
|
|
9790
|
+
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path52.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path52.join(home, ".npm-global", "lib", "node_modules"));
|
|
9054
9791
|
return unique([
|
|
9055
|
-
|
|
9056
|
-
|
|
9057
|
-
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot :
|
|
9792
|
+
path52.join(openClawPrefix, "lib", "node_modules"),
|
|
9793
|
+
path52.join(openClawPrefix, "node_modules"),
|
|
9794
|
+
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path52.join(npmGlobalRoot, "lib", "node_modules")
|
|
9058
9795
|
]);
|
|
9059
9796
|
}
|
|
9060
9797
|
async function readVersion(packageJsonPath) {
|
|
@@ -9070,7 +9807,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
|
|
|
9070
9807
|
const out = {};
|
|
9071
9808
|
for (const packageName of MANAGED_PACKAGES) {
|
|
9072
9809
|
for (const root of roots) {
|
|
9073
|
-
const packageJsonPath =
|
|
9810
|
+
const packageJsonPath = path52.join(root, packageName, "package.json");
|
|
9074
9811
|
const version = await readVersion(packageJsonPath);
|
|
9075
9812
|
if (!version) continue;
|
|
9076
9813
|
out[packageName] = { version, observedAt, path: packageJsonPath };
|
|
@@ -9086,11 +9823,11 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
9086
9823
|
const outcomes = [];
|
|
9087
9824
|
for (const name of Object.keys(run.workers || {})) {
|
|
9088
9825
|
const worker = readJson(
|
|
9089
|
-
|
|
9826
|
+
path53.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
9090
9827
|
void 0
|
|
9091
9828
|
);
|
|
9092
9829
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
9093
|
-
if (
|
|
9830
|
+
if (hasTerminalCompletionAck(worker)) {
|
|
9094
9831
|
outcomes.push({ worker: name, ok: true, taskId: worker.taskId ?? null, skipped: true });
|
|
9095
9832
|
continue;
|
|
9096
9833
|
}
|
|
@@ -9159,24 +9896,51 @@ async function runPipelineTick(args) {
|
|
|
9159
9896
|
let maxStarts = maxStartsAdvice.maxStarts;
|
|
9160
9897
|
const sweep = await sweepRun({ run: runId, agentOsId, pipeline: true, ...args });
|
|
9161
9898
|
let dispatch = null;
|
|
9162
|
-
|
|
9163
|
-
|
|
9899
|
+
let startedCount = 0;
|
|
9900
|
+
const exactTargetTaskIds = operatorExactTargetTaskIds(operatorTick);
|
|
9901
|
+
let remainingStarts = maxStarts;
|
|
9902
|
+
if (execute && remainingStarts > 0 && exactTargetTaskIds.length > 0) {
|
|
9903
|
+
const exactBudget = Math.min(remainingStarts, exactTargetTaskIds.length);
|
|
9904
|
+
const exact = await runPipelineDispatch(
|
|
9905
|
+
{
|
|
9906
|
+
...args,
|
|
9907
|
+
run: runId,
|
|
9908
|
+
agentOsId,
|
|
9909
|
+
targetTaskIds: exactTargetTaskIds.join(",")
|
|
9910
|
+
},
|
|
9911
|
+
exactBudget
|
|
9912
|
+
);
|
|
9913
|
+
const exactStarted = countDispatchStarts(exact);
|
|
9914
|
+
startedCount += exactStarted;
|
|
9915
|
+
remainingStarts = Math.max(0, remainingStarts - exactStarted);
|
|
9916
|
+
dispatch = { exactTargetTaskIds, exact, startedCount };
|
|
9917
|
+
}
|
|
9918
|
+
if (execute && remainingStarts > 0) {
|
|
9919
|
+
const broad = await runPipelineDispatch(
|
|
9164
9920
|
{
|
|
9165
9921
|
...args,
|
|
9166
9922
|
run: runId,
|
|
9167
9923
|
agentOsId
|
|
9168
9924
|
},
|
|
9169
|
-
|
|
9925
|
+
remainingStarts
|
|
9170
9926
|
);
|
|
9171
|
-
|
|
9172
|
-
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
|
|
9177
|
-
|
|
9927
|
+
const broadStarted = countDispatchStarts(broad);
|
|
9928
|
+
startedCount += broadStarted;
|
|
9929
|
+
dispatch = dispatch && typeof dispatch === "object" ? { ...dispatch, broad, startedCount } : broad;
|
|
9930
|
+
} else if (!execute || maxStarts <= 0) {
|
|
9931
|
+
if (!dispatch) {
|
|
9932
|
+
dispatch = {
|
|
9933
|
+
ok: true,
|
|
9934
|
+
skipped: true,
|
|
9935
|
+
reason: execute ? dispatchResourceGate.reason ?? "no slots or queued work" : "execute disabled",
|
|
9936
|
+
maxStarts: 0,
|
|
9937
|
+
dispatchAdvice: maxStartsAdvice,
|
|
9938
|
+
...exactTargetTaskIds.length ? { exactTargetTaskIds, exactOnly: true } : {}
|
|
9939
|
+
};
|
|
9940
|
+
}
|
|
9941
|
+
} else if (dispatch && typeof dispatch === "object") {
|
|
9942
|
+
dispatch = { ...dispatch, broadSkipped: true, startedCount };
|
|
9178
9943
|
}
|
|
9179
|
-
const startedCount = dispatch?.startedCount ?? 0;
|
|
9180
9944
|
const idle = !maxStartsAdvice.underutilized && maxStarts === 0 && completedWorkers.length === 0 && startedCount === 0;
|
|
9181
9945
|
return {
|
|
9182
9946
|
runId,
|
|
@@ -9192,6 +9956,7 @@ async function runPipelineTick(args) {
|
|
|
9192
9956
|
completionAckSync,
|
|
9193
9957
|
operatorTick,
|
|
9194
9958
|
sweep,
|
|
9959
|
+
dispatchAdvice: maxStartsAdvice,
|
|
9195
9960
|
dispatch,
|
|
9196
9961
|
idle
|
|
9197
9962
|
};
|
|
@@ -9215,8 +9980,18 @@ async function runDaemon(args) {
|
|
|
9215
9980
|
stopping = true;
|
|
9216
9981
|
});
|
|
9217
9982
|
console.error(JSON.stringify({ event: "daemon_start", runId, agentOsId, execute, intervalMs }));
|
|
9983
|
+
const cronEnv = resolveKynverCronEnv();
|
|
9218
9984
|
while (!stopping) {
|
|
9219
9985
|
try {
|
|
9986
|
+
if (cronEnv.tickEnabled) {
|
|
9987
|
+
const cronTick = await runKynverCronTick({
|
|
9988
|
+
env: cronEnv,
|
|
9989
|
+
agentOsIdFilter: agentOsId
|
|
9990
|
+
});
|
|
9991
|
+
if (cronTick.enabled && (cronTick.fired > 0 || cronTick.errors > 0)) {
|
|
9992
|
+
console.error(JSON.stringify({ event: "daemon_cron_tick", ...cronTick }));
|
|
9993
|
+
}
|
|
9994
|
+
}
|
|
9220
9995
|
const tick = await runPipelineTick({ run: runId, agentOsId, execute, ...args });
|
|
9221
9996
|
console.error(JSON.stringify({ event: "daemon_tick", ...tick }));
|
|
9222
9997
|
if (tick.idle) {
|
|
@@ -9235,7 +10010,7 @@ async function runDaemon(args) {
|
|
|
9235
10010
|
}
|
|
9236
10011
|
|
|
9237
10012
|
// src/plan-progress.ts
|
|
9238
|
-
import
|
|
10013
|
+
import path54 from "node:path";
|
|
9239
10014
|
|
|
9240
10015
|
// src/bounded-build/constants.ts
|
|
9241
10016
|
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
@@ -9522,7 +10297,7 @@ async function emitPlanProgress(args) {
|
|
|
9522
10297
|
}
|
|
9523
10298
|
function verifyPlanLocal(args) {
|
|
9524
10299
|
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
9525
|
-
const cwd =
|
|
10300
|
+
const cwd = path54.resolve(worktree);
|
|
9526
10301
|
const summary = runHarnessVerifyCommands(cwd);
|
|
9527
10302
|
const emitJson = args.json === true || args.json === "true";
|
|
9528
10303
|
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
@@ -9571,9 +10346,9 @@ async function verifyPlan(args) {
|
|
|
9571
10346
|
}
|
|
9572
10347
|
|
|
9573
10348
|
// src/harness-verify-cli.ts
|
|
9574
|
-
import
|
|
10349
|
+
import path55 from "node:path";
|
|
9575
10350
|
function runHarnessVerifyCli(args) {
|
|
9576
|
-
const cwd =
|
|
10351
|
+
const cwd = path55.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
9577
10352
|
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
9578
10353
|
const commands = [];
|
|
9579
10354
|
const rawCmd = args.command;
|
|
@@ -9617,7 +10392,7 @@ function runHarnessVerifyCli(args) {
|
|
|
9617
10392
|
}
|
|
9618
10393
|
|
|
9619
10394
|
// src/plan-persist-cli.ts
|
|
9620
|
-
import { readFileSync as
|
|
10395
|
+
import { readFileSync as readFileSync14 } from "node:fs";
|
|
9621
10396
|
var OPERATIONS = ["create", "add_version", "update_metadata"];
|
|
9622
10397
|
var FAILURE_KINDS = [
|
|
9623
10398
|
"approval_guard",
|
|
@@ -9629,7 +10404,7 @@ var FAILURE_KINDS = [
|
|
|
9629
10404
|
function readBodyArg(args) {
|
|
9630
10405
|
const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
|
|
9631
10406
|
if (bodyFile) {
|
|
9632
|
-
return { body:
|
|
10407
|
+
return { body: readFileSync14(bodyFile, "utf8"), bodyPathHint: bodyFile };
|
|
9633
10408
|
}
|
|
9634
10409
|
const inline = args.body ? String(args.body) : void 0;
|
|
9635
10410
|
if (inline) return { body: inline };
|
|
@@ -10002,7 +10777,7 @@ ${text.slice(0, 800)}`,
|
|
|
10002
10777
|
}
|
|
10003
10778
|
|
|
10004
10779
|
// src/monitor/monitor.service.ts
|
|
10005
|
-
import
|
|
10780
|
+
import path57 from "node:path";
|
|
10006
10781
|
|
|
10007
10782
|
// src/monitor/monitor.classify.ts
|
|
10008
10783
|
function classifyWorkerHealth(input) {
|
|
@@ -10054,11 +10829,11 @@ function classifyWorkerHealth(input) {
|
|
|
10054
10829
|
}
|
|
10055
10830
|
|
|
10056
10831
|
// src/monitor/monitor.store.ts
|
|
10057
|
-
import { existsSync as
|
|
10058
|
-
import
|
|
10832
|
+
import { existsSync as existsSync39, mkdirSync as mkdirSync6, readdirSync as readdirSync12, unlinkSync as unlinkSync3 } from "node:fs";
|
|
10833
|
+
import path56 from "node:path";
|
|
10059
10834
|
function monitorsDir() {
|
|
10060
10835
|
const { harnessRoot } = getHarnessPaths();
|
|
10061
|
-
const dir =
|
|
10836
|
+
const dir = path56.join(harnessRoot, "monitors");
|
|
10062
10837
|
mkdirSync6(dir, { recursive: true });
|
|
10063
10838
|
return dir;
|
|
10064
10839
|
}
|
|
@@ -10066,7 +10841,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
10066
10841
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
10067
10842
|
}
|
|
10068
10843
|
function monitorPath(monitorId) {
|
|
10069
|
-
return
|
|
10844
|
+
return path56.join(monitorsDir(), `${monitorId}.json`);
|
|
10070
10845
|
}
|
|
10071
10846
|
function loadMonitorSession(monitorId) {
|
|
10072
10847
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -10076,18 +10851,18 @@ function saveMonitorSession(session) {
|
|
|
10076
10851
|
}
|
|
10077
10852
|
function deleteMonitorSession(monitorId) {
|
|
10078
10853
|
const file = monitorPath(monitorId);
|
|
10079
|
-
if (!
|
|
10080
|
-
|
|
10854
|
+
if (!existsSync39(file)) return false;
|
|
10855
|
+
unlinkSync3(file);
|
|
10081
10856
|
return true;
|
|
10082
10857
|
}
|
|
10083
10858
|
function listMonitorSessions() {
|
|
10084
10859
|
const dir = monitorsDir();
|
|
10085
|
-
if (!
|
|
10860
|
+
if (!existsSync39(dir)) return [];
|
|
10086
10861
|
const entries = [];
|
|
10087
|
-
for (const name of
|
|
10862
|
+
for (const name of readdirSync12(dir)) {
|
|
10088
10863
|
if (!name.endsWith(".json")) continue;
|
|
10089
10864
|
const session = readJson(
|
|
10090
|
-
|
|
10865
|
+
path56.join(dir, name),
|
|
10091
10866
|
void 0
|
|
10092
10867
|
);
|
|
10093
10868
|
if (!session?.monitorId) continue;
|
|
@@ -10178,7 +10953,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
10178
10953
|
// src/monitor/monitor.service.ts
|
|
10179
10954
|
function workerRecord2(runId, name) {
|
|
10180
10955
|
return readJson(
|
|
10181
|
-
|
|
10956
|
+
path57.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
10182
10957
|
void 0
|
|
10183
10958
|
);
|
|
10184
10959
|
}
|
|
@@ -10384,21 +11159,21 @@ async function runMonitorLoop(args) {
|
|
|
10384
11159
|
|
|
10385
11160
|
// src/monitor/monitor-spawn.ts
|
|
10386
11161
|
import { spawn as spawn6 } from "node:child_process";
|
|
10387
|
-
import { closeSync as
|
|
10388
|
-
import
|
|
11162
|
+
import { closeSync as closeSync7, existsSync as existsSync40, openSync as openSync7 } from "node:fs";
|
|
11163
|
+
import path58 from "node:path";
|
|
10389
11164
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
10390
11165
|
function resolveDefaultCliPath2() {
|
|
10391
|
-
return
|
|
11166
|
+
return path58.join(fileURLToPath4(new URL(".", import.meta.url)), "cli.js");
|
|
10392
11167
|
}
|
|
10393
11168
|
function spawnMonitorSidecar(opts) {
|
|
10394
11169
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
10395
|
-
if (!
|
|
11170
|
+
if (!existsSync40(cliPath)) return void 0;
|
|
10396
11171
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
10397
11172
|
const { harnessRoot } = getHarnessPaths();
|
|
10398
|
-
const logPath =
|
|
11173
|
+
const logPath = path58.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
10399
11174
|
let logFd;
|
|
10400
11175
|
try {
|
|
10401
|
-
logFd =
|
|
11176
|
+
logFd = openSync7(logPath, "a");
|
|
10402
11177
|
} catch {
|
|
10403
11178
|
logFd = void 0;
|
|
10404
11179
|
}
|
|
@@ -10438,7 +11213,7 @@ function spawnMonitorSidecar(opts) {
|
|
|
10438
11213
|
env: process.env
|
|
10439
11214
|
})
|
|
10440
11215
|
);
|
|
10441
|
-
if (logFd !== void 0)
|
|
11216
|
+
if (logFd !== void 0) closeSync7(logFd);
|
|
10442
11217
|
child.unref();
|
|
10443
11218
|
const session = {
|
|
10444
11219
|
monitorId,
|
|
@@ -10455,7 +11230,7 @@ function spawnMonitorSidecar(opts) {
|
|
|
10455
11230
|
} catch {
|
|
10456
11231
|
if (logFd !== void 0) {
|
|
10457
11232
|
try {
|
|
10458
|
-
|
|
11233
|
+
closeSync7(logFd);
|
|
10459
11234
|
} catch {
|
|
10460
11235
|
}
|
|
10461
11236
|
}
|
|
@@ -10515,7 +11290,7 @@ async function monitorTickCli(args) {
|
|
|
10515
11290
|
}
|
|
10516
11291
|
|
|
10517
11292
|
// src/post-restart-unblock.ts
|
|
10518
|
-
import
|
|
11293
|
+
import path59 from "node:path";
|
|
10519
11294
|
function skip(runId, worker, taskId, agentOsId, leaseOwner, reason) {
|
|
10520
11295
|
return { runId, worker, taskId, agentOsId, leaseOwner, action: "skipped", reason };
|
|
10521
11296
|
}
|
|
@@ -10528,7 +11303,7 @@ async function postRestartUnblock(args) {
|
|
|
10528
11303
|
const errors = [];
|
|
10529
11304
|
for (const run of listRunRecords()) {
|
|
10530
11305
|
for (const name of Object.keys(run.workers ?? {})) {
|
|
10531
|
-
const workerPath =
|
|
11306
|
+
const workerPath = path59.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
10532
11307
|
const worker = readJson(workerPath, void 0);
|
|
10533
11308
|
if (!worker) {
|
|
10534
11309
|
skipped.push(skip(run.id, name, "", "", "", "worker.json missing"));
|
|
@@ -10640,12 +11415,12 @@ async function postRestartUnblockCli(args) {
|
|
|
10640
11415
|
}
|
|
10641
11416
|
|
|
10642
11417
|
// src/doctor/runtime-takeover.ts
|
|
10643
|
-
import
|
|
11418
|
+
import path61 from "node:path";
|
|
10644
11419
|
|
|
10645
11420
|
// src/doctor/runtime-takeover.probes.ts
|
|
10646
|
-
import { accessSync, constants, existsSync as
|
|
10647
|
-
import { homedir as
|
|
10648
|
-
import
|
|
11421
|
+
import { accessSync, constants, existsSync as existsSync41, readFileSync as readFileSync15 } from "node:fs";
|
|
11422
|
+
import { homedir as homedir14 } from "node:os";
|
|
11423
|
+
import path60 from "node:path";
|
|
10649
11424
|
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
10650
11425
|
function captureCommand(bin, args) {
|
|
10651
11426
|
try {
|
|
@@ -10674,7 +11449,7 @@ function tokenPrefix(token) {
|
|
|
10674
11449
|
return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
|
|
10675
11450
|
}
|
|
10676
11451
|
function isWritable(target) {
|
|
10677
|
-
if (!
|
|
11452
|
+
if (!existsSync41(target)) return false;
|
|
10678
11453
|
try {
|
|
10679
11454
|
accessSync(target, constants.W_OK);
|
|
10680
11455
|
return true;
|
|
@@ -10687,15 +11462,15 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
10687
11462
|
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
10688
11463
|
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
10689
11464
|
loadConfig: () => loadUserConfig(),
|
|
10690
|
-
configFilePath: () =>
|
|
10691
|
-
credentialsFilePath: () =>
|
|
11465
|
+
configFilePath: () => path60.join(homedir14(), ".kynver", "config.json"),
|
|
11466
|
+
credentialsFilePath: () => path60.join(homedir14(), ".kynver", "credentials"),
|
|
10692
11467
|
readCredentials: () => {
|
|
10693
|
-
const credPath =
|
|
10694
|
-
if (!
|
|
11468
|
+
const credPath = path60.join(homedir14(), ".kynver", "credentials");
|
|
11469
|
+
if (!existsSync41(credPath)) {
|
|
10695
11470
|
return { hasApiKey: false };
|
|
10696
11471
|
}
|
|
10697
11472
|
try {
|
|
10698
|
-
const parsed = JSON.parse(
|
|
11473
|
+
const parsed = JSON.parse(readFileSync15(credPath, "utf8"));
|
|
10699
11474
|
return {
|
|
10700
11475
|
hasApiKey: Boolean(parsed.apiKey?.trim()),
|
|
10701
11476
|
runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
|
|
@@ -10714,7 +11489,10 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
10714
11489
|
kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
|
|
10715
11490
|
opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
|
|
10716
11491
|
kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0,
|
|
10717
|
-
openclawCronStorePath: Boolean(
|
|
11492
|
+
openclawCronStorePath: Boolean(
|
|
11493
|
+
process.env.KYNVER_CRON_STORE_PATH?.trim() || process.env.OPENCLAW_CRON_STORE_PATH?.trim()
|
|
11494
|
+
),
|
|
11495
|
+
kynverCronDaemonPrimary: isKynverCronDaemonPrimary(),
|
|
10718
11496
|
qstashTokenPresent: Boolean(process.env.QSTASH_TOKEN?.trim()),
|
|
10719
11497
|
kynverHostedDeployment: (() => {
|
|
10720
11498
|
const v = process.env.KYNVER_HOSTED_DEPLOYMENT?.trim().toLowerCase();
|
|
@@ -10722,8 +11500,8 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
10722
11500
|
})()
|
|
10723
11501
|
}),
|
|
10724
11502
|
harnessRoot: () => resolveHarnessRoot(),
|
|
10725
|
-
legacyOpenclawHarnessRoot: () =>
|
|
10726
|
-
pathExists: (target) =>
|
|
11503
|
+
legacyOpenclawHarnessRoot: () => path60.join(homedir14(), ".openclaw", "harness"),
|
|
11504
|
+
pathExists: (target) => existsSync41(target),
|
|
10727
11505
|
pathWritable: (target) => isWritable(target),
|
|
10728
11506
|
vercelVersion: () => captureCommand("vercel", ["--version"]),
|
|
10729
11507
|
vercelWhoami: () => captureCommand("vercel", ["whoami"])
|
|
@@ -10743,8 +11521,10 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
10743
11521
|
const schedulerDetails = {
|
|
10744
11522
|
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
10745
11523
|
deploymentSchedulerProvider: ctx.deploymentSchedulerProvider ?? null,
|
|
10746
|
-
openclawCronStorePath: Boolean(env.openclawCronStorePath)
|
|
11524
|
+
openclawCronStorePath: Boolean(env.openclawCronStorePath),
|
|
11525
|
+
kynverCronDaemonPrimary: Boolean(env.kynverCronDaemonPrimary)
|
|
10747
11526
|
};
|
|
11527
|
+
const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
|
|
10748
11528
|
if (hasQstashCutover(env, ctx) && !hasLocalOpenClawDependency(env, ctx)) {
|
|
10749
11529
|
const source = env.kynverSchedulerProvider === "qstash" ? "KYNVER_SCHEDULER_PROVIDER=qstash on this host" : "deploymentSchedulerProvider=qstash in ~/.kynver/config.json";
|
|
10750
11530
|
return check({
|
|
@@ -10756,6 +11536,19 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
10756
11536
|
});
|
|
10757
11537
|
}
|
|
10758
11538
|
if (hasLocalOpenClawDependency(env, ctx)) {
|
|
11539
|
+
if (env.kynverCronDaemonPrimary && daemonDispatchReady) {
|
|
11540
|
+
return check({
|
|
11541
|
+
id: "hotspot_openclaw_scheduler",
|
|
11542
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
11543
|
+
status: "pass",
|
|
11544
|
+
summary: "Kynver Cron local store present; `kynver daemon` owns schedule fires (kynver-cron tick loop)",
|
|
11545
|
+
details: {
|
|
11546
|
+
...schedulerDetails,
|
|
11547
|
+
dispatchPath: "kynver-daemon-cron-tick",
|
|
11548
|
+
kynverCronDaemonPrimary: true
|
|
11549
|
+
}
|
|
11550
|
+
});
|
|
11551
|
+
}
|
|
10759
11552
|
const parts = [];
|
|
10760
11553
|
if (env.kynverSchedulerProvider === "openclaw-cron") {
|
|
10761
11554
|
parts.push("KYNVER_SCHEDULER_PROVIDER=openclaw-cron");
|
|
@@ -10764,21 +11557,20 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
10764
11557
|
parts.push("deploymentSchedulerProvider=openclaw-cron in config");
|
|
10765
11558
|
}
|
|
10766
11559
|
if (env.openclawCronStorePath) {
|
|
10767
|
-
parts.push("OPENCLAW_CRON_STORE_PATH set (local cron
|
|
11560
|
+
parts.push("KYNVER_CRON_STORE_PATH or OPENCLAW_CRON_STORE_PATH set (local cron store)");
|
|
10768
11561
|
}
|
|
10769
11562
|
return check({
|
|
10770
11563
|
id: "hotspot_openclaw_scheduler",
|
|
10771
11564
|
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
10772
11565
|
status: "warn",
|
|
10773
|
-
summary: `
|
|
10774
|
-
remediation: "
|
|
11566
|
+
summary: `Local cron store without daemon tick (${parts.join("; ")})`,
|
|
11567
|
+
remediation: "Run `kynver daemon` with KYNVER_CRON_SECRET + KYNVER_API_URL (or KYNVER_CRON_FIRE_BASE_URL) so the daemon-owned cron tick fires schedules. On hosted deploys use QStash (KYNVER_SCHEDULER_PROVIDER=qstash). Legacy OpenClaw cron env aliases still work during cutover.",
|
|
10775
11568
|
details: schedulerDetails
|
|
10776
11569
|
});
|
|
10777
11570
|
}
|
|
10778
11571
|
const runnerOpenclaw = env.kynverSchedulerProvider === "openclaw-cron";
|
|
10779
11572
|
const runnerQstash = env.kynverSchedulerProvider === "qstash";
|
|
10780
11573
|
const hostedDeployment = Boolean(env.qstashTokenPresent) || Boolean(env.kynverHostedDeployment);
|
|
10781
|
-
const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
|
|
10782
11574
|
const hostedSchedulerProcess = hostedDeployment && !daemonDispatchReady;
|
|
10783
11575
|
const deploymentNeedsQstash = hostedSchedulerProcess && !env.qstashTokenPresent && env.kynverSchedulerProvider !== "qstash";
|
|
10784
11576
|
const deploymentOpenclaw = hostedSchedulerProcess && env.kynverSchedulerProvider === "openclaw-cron";
|
|
@@ -11051,8 +11843,8 @@ function assessVercelCli(probes) {
|
|
|
11051
11843
|
}
|
|
11052
11844
|
function assessHarnessDirs(probes) {
|
|
11053
11845
|
const harnessRoot = probes.harnessRoot();
|
|
11054
|
-
const runsDir =
|
|
11055
|
-
const worktreesDir =
|
|
11846
|
+
const runsDir = path61.join(harnessRoot, "runs");
|
|
11847
|
+
const worktreesDir = path61.join(harnessRoot, "worktrees");
|
|
11056
11848
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
11057
11849
|
const displayRunsDir = redactHomePath(runsDir);
|
|
11058
11850
|
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
@@ -11255,6 +12047,11 @@ var RUNNER_SCHEDULER_CUTOVER_STEPS = [
|
|
|
11255
12047
|
function readSchedulerCutoverEnv(env = process.env) {
|
|
11256
12048
|
return {
|
|
11257
12049
|
kynverSchedulerProvider: env.KYNVER_SCHEDULER_PROVIDER?.trim() || null,
|
|
12050
|
+
kynverCronStorePath: env.KYNVER_CRON_STORE_PATH?.trim() || env.OPENCLAW_CRON_STORE_PATH?.trim() || null,
|
|
12051
|
+
kynverCronSecret: Boolean(
|
|
12052
|
+
env.KYNVER_CRON_SECRET?.trim() || env.OPENCLAW_CRON_SECRET?.trim()
|
|
12053
|
+
),
|
|
12054
|
+
kynverCronFireBaseUrl: env.KYNVER_CRON_FIRE_BASE_URL?.trim() || env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || null,
|
|
11258
12055
|
openclawCronStorePath: env.OPENCLAW_CRON_STORE_PATH?.trim() || null,
|
|
11259
12056
|
openclawCronSecret: Boolean(env.OPENCLAW_CRON_SECRET?.trim()),
|
|
11260
12057
|
openclawCronFireBaseUrl: env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || null
|
|
@@ -11265,8 +12062,13 @@ function assessSchedulerCutover(config, env = readSchedulerCutoverEnv()) {
|
|
|
11265
12062
|
if (env.kynverSchedulerProvider === "openclaw-cron") {
|
|
11266
12063
|
blockers.push("Runner still has KYNVER_SCHEDULER_PROVIDER=openclaw-cron");
|
|
11267
12064
|
}
|
|
11268
|
-
if (env.
|
|
11269
|
-
blockers.push(
|
|
12065
|
+
if (env.kynverCronStorePath && env.kynverSchedulerProvider !== "kynver-cron") {
|
|
12066
|
+
blockers.push(
|
|
12067
|
+
"Runner has KYNVER_CRON_STORE_PATH but KYNVER_SCHEDULER_PROVIDER is not kynver-cron \u2014 use `kynver daemon` cron tick or unset the store for QStash-only runners"
|
|
12068
|
+
);
|
|
12069
|
+
}
|
|
12070
|
+
if (env.openclawCronStorePath && !env.kynverCronStorePath) {
|
|
12071
|
+
blockers.push("Runner still has legacy OPENCLAW_CRON_STORE_PATH (prefer KYNVER_CRON_STORE_PATH)");
|
|
11270
12072
|
}
|
|
11271
12073
|
if (config.deploymentSchedulerProvider === "openclaw-cron") {
|
|
11272
12074
|
blockers.push("~/.kynver/config.json deploymentSchedulerProvider is still openclaw-cron");
|
|
@@ -11288,9 +12090,9 @@ function applySchedulerCutoverAttestation(config) {
|
|
|
11288
12090
|
}
|
|
11289
12091
|
|
|
11290
12092
|
// src/scheduler-cutover-cli.ts
|
|
11291
|
-
import
|
|
11292
|
-
import { homedir as
|
|
11293
|
-
var CONFIG_FILE2 =
|
|
12093
|
+
import path62 from "node:path";
|
|
12094
|
+
import { homedir as homedir15 } from "node:os";
|
|
12095
|
+
var CONFIG_FILE2 = path62.join(homedir15(), ".kynver", "config.json");
|
|
11294
12096
|
function runSchedulerCutoverCheckCli(json = false) {
|
|
11295
12097
|
const config = loadUserConfig();
|
|
11296
12098
|
const report = assessSchedulerCutover(config);
|
|
@@ -11318,7 +12120,10 @@ function runSchedulerCutoverCheckCli(json = false) {
|
|
|
11318
12120
|
` KYNVER_SCHEDULER_PROVIDER: ${report.runnerEnv.kynverSchedulerProvider ?? "(unset)"}`
|
|
11319
12121
|
);
|
|
11320
12122
|
console.log(
|
|
11321
|
-
`
|
|
12123
|
+
` KYNVER_CRON_STORE_PATH: ${report.runnerEnv.kynverCronStorePath ?? "(unset)"}`
|
|
12124
|
+
);
|
|
12125
|
+
console.log(
|
|
12126
|
+
` OPENCLAW_CRON_STORE_PATH (legacy): ${report.runnerEnv.openclawCronStorePath ?? "(unset)"}`
|
|
11322
12127
|
);
|
|
11323
12128
|
if (report.blockers.length) {
|
|
11324
12129
|
console.log("\nBlockers:");
|
|
@@ -11365,6 +12170,65 @@ function runSchedulerAttestCutoverCli(json = false) {
|
|
|
11365
12170
|
console.log(` config: ${payload.configPath}`);
|
|
11366
12171
|
}
|
|
11367
12172
|
|
|
12173
|
+
// src/cron/cron-status.ts
|
|
12174
|
+
async function buildKynverCronStatusReport(env = resolveKynverCronEnv()) {
|
|
12175
|
+
const jobs = await loadCronJobs(env.storePath).catch(() => []);
|
|
12176
|
+
const state = await loadCronTickState(env.statePath).catch(() => ({ version: 1, jobs: {} }));
|
|
12177
|
+
const credentialsReady = Boolean(env.fireBaseUrl && env.secret);
|
|
12178
|
+
const daemonPrimary = isKynverCronDaemonPrimary(env);
|
|
12179
|
+
let primary = "disabled";
|
|
12180
|
+
if (daemonPrimary) primary = "kynver-cron-daemon";
|
|
12181
|
+
else if (process.env.QSTASH_TOKEN?.trim()) primary = "qstash";
|
|
12182
|
+
return {
|
|
12183
|
+
primary,
|
|
12184
|
+
env: {
|
|
12185
|
+
storePath: env.storePath,
|
|
12186
|
+
statePath: env.statePath,
|
|
12187
|
+
tickEnabled: env.tickEnabled,
|
|
12188
|
+
fireBaseUrl: env.fireBaseUrl,
|
|
12189
|
+
missedRunPolicy: env.missedRunPolicy,
|
|
12190
|
+
maxCatchUpPerTick: env.maxCatchUpPerTick,
|
|
12191
|
+
maxRetries: env.maxRetries
|
|
12192
|
+
},
|
|
12193
|
+
jobCount: jobs.length,
|
|
12194
|
+
stateJobCount: Object.keys(state.jobs).length,
|
|
12195
|
+
credentialsReady,
|
|
12196
|
+
daemonPrimary
|
|
12197
|
+
};
|
|
12198
|
+
}
|
|
12199
|
+
|
|
12200
|
+
// src/cron/cron-tick-cli.ts
|
|
12201
|
+
async function runCronStatusCli(json) {
|
|
12202
|
+
const report = await buildKynverCronStatusReport();
|
|
12203
|
+
if (json) {
|
|
12204
|
+
console.log(JSON.stringify(report, null, 2));
|
|
12205
|
+
return;
|
|
12206
|
+
}
|
|
12207
|
+
console.log(`Kynver Cron primary: ${report.primary}`);
|
|
12208
|
+
console.log(` store: ${report.env.storePath} (${report.jobCount} jobs)`);
|
|
12209
|
+
console.log(` tick state: ${report.env.statePath} (${report.stateJobCount} tracked)`);
|
|
12210
|
+
console.log(` tick enabled: ${report.env.tickEnabled}`);
|
|
12211
|
+
console.log(` fire base URL: ${report.env.fireBaseUrl ?? "(unset)"}`);
|
|
12212
|
+
console.log(` credentials ready: ${report.credentialsReady}`);
|
|
12213
|
+
console.log(` daemon-owned primary: ${report.daemonPrimary}`);
|
|
12214
|
+
}
|
|
12215
|
+
async function runCronTickCli(args) {
|
|
12216
|
+
const agentOsId = typeof args.agentOsId === "string" ? args.agentOsId : void 0;
|
|
12217
|
+
const result = await runKynverCronTick({
|
|
12218
|
+
agentOsIdFilter: agentOsId ?? null
|
|
12219
|
+
});
|
|
12220
|
+
if (args.json === true) {
|
|
12221
|
+
console.log(JSON.stringify(result, null, 2));
|
|
12222
|
+
return;
|
|
12223
|
+
}
|
|
12224
|
+
console.log(
|
|
12225
|
+
JSON.stringify({
|
|
12226
|
+
event: "kynver_cron_tick",
|
|
12227
|
+
...result
|
|
12228
|
+
})
|
|
12229
|
+
);
|
|
12230
|
+
}
|
|
12231
|
+
|
|
11368
12232
|
// src/cli.ts
|
|
11369
12233
|
function isHelpFlag(arg) {
|
|
11370
12234
|
return arg === "help" || arg === "--help" || arg === "-h";
|
|
@@ -11415,6 +12279,8 @@ function usage(code = 0) {
|
|
|
11415
12279
|
" kynver doctor runtime-takeover",
|
|
11416
12280
|
" kynver scheduler cutover-check [--json]",
|
|
11417
12281
|
" kynver scheduler attest-cutover [--json]",
|
|
12282
|
+
" kynver cron status [--json]",
|
|
12283
|
+
" kynver cron tick [--agent-os-id AOS_ID] [--json]",
|
|
11418
12284
|
" kynver board contract [--agent-os-id ID] [--base-url URL] [--since ISO] [--limit N]"
|
|
11419
12285
|
].join("\n")
|
|
11420
12286
|
);
|
|
@@ -11426,7 +12292,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
11426
12292
|
const scope = argv.shift();
|
|
11427
12293
|
let action;
|
|
11428
12294
|
let rest;
|
|
11429
|
-
if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "harness" || scope === "monitor" || scope === "doctor" || scope === "scheduler" || scope === "board") {
|
|
12295
|
+
if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "harness" || scope === "monitor" || scope === "doctor" || scope === "scheduler" || scope === "cron" || scope === "board") {
|
|
11430
12296
|
action = argv.shift();
|
|
11431
12297
|
rest = argv;
|
|
11432
12298
|
} else {
|
|
@@ -11459,6 +12325,12 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
11459
12325
|
if (scope === "scheduler" && action === "attest-cutover") {
|
|
11460
12326
|
return runSchedulerAttestCutoverCli(args.json === true);
|
|
11461
12327
|
}
|
|
12328
|
+
if (scope === "cron" && action === "status") {
|
|
12329
|
+
return void await runCronStatusCli(args.json === true);
|
|
12330
|
+
}
|
|
12331
|
+
if (scope === "cron" && action === "tick") {
|
|
12332
|
+
return void await runCronTickCli(args);
|
|
12333
|
+
}
|
|
11462
12334
|
if (scope === "board" && action === "contract") {
|
|
11463
12335
|
return void await runCommandCenterContractCli(args);
|
|
11464
12336
|
}
|
|
@@ -11826,6 +12698,7 @@ export {
|
|
|
11826
12698
|
HERMES_OPENAI_CODEX_DEFAULT_MODEL,
|
|
11827
12699
|
OPENAI_CODEX_PROVIDER,
|
|
11828
12700
|
PACKAGE_VERSION,
|
|
12701
|
+
RUN_METADATA_ACTIVE_SIGNAL_MS,
|
|
11829
12702
|
TRANSIENT_OPENAI_CODEX_ERROR_CLASSES,
|
|
11830
12703
|
applyProductionDatabaseToProcess,
|
|
11831
12704
|
assessAutoCompleteEligibility,
|
|
@@ -11854,6 +12727,7 @@ export {
|
|
|
11854
12727
|
classifyVercelUrl,
|
|
11855
12728
|
classifyWorkerHealth,
|
|
11856
12729
|
codexProvider,
|
|
12730
|
+
collectFilesystemLiveRunKeys,
|
|
11857
12731
|
collectVercelEvidence,
|
|
11858
12732
|
compareProviderCandidates,
|
|
11859
12733
|
completeWorker,
|
|
@@ -11897,6 +12771,7 @@ export {
|
|
|
11897
12771
|
isFinishedWorkerStatus,
|
|
11898
12772
|
isForbiddenWorkerEnvKey,
|
|
11899
12773
|
isGeneratedHarnessPath,
|
|
12774
|
+
isHarnessRunMetadataPath,
|
|
11900
12775
|
isInspectableVercelTarget,
|
|
11901
12776
|
isKynverMonorepoRoot,
|
|
11902
12777
|
isLandingBlockedWorkerStatus,
|
|
@@ -11949,6 +12824,7 @@ export {
|
|
|
11949
12824
|
redactHarness,
|
|
11950
12825
|
redactProviderErrorText,
|
|
11951
12826
|
remediateDefaultRepo,
|
|
12827
|
+
repairMissingRunMetadata,
|
|
11952
12828
|
repairNestedRunsPath,
|
|
11953
12829
|
resolveBaseUrl,
|
|
11954
12830
|
resolveBoxKindFromEnv,
|
|
@@ -11965,6 +12841,7 @@ export {
|
|
|
11965
12841
|
resolveWorkerJsonPath,
|
|
11966
12842
|
runBoundedBuildCheck,
|
|
11967
12843
|
runDaemon,
|
|
12844
|
+
runDirHasActiveRetentionSignals,
|
|
11968
12845
|
runHarnessCleanup,
|
|
11969
12846
|
runHarnessVerifyCommands,
|
|
11970
12847
|
runMonitorTick,
|
|
@@ -11995,6 +12872,7 @@ export {
|
|
|
11995
12872
|
validateRunId,
|
|
11996
12873
|
validateTailLines,
|
|
11997
12874
|
validateWorkerName,
|
|
12875
|
+
workerDirHasActiveRetentionSignals,
|
|
11998
12876
|
workerStatus
|
|
11999
12877
|
};
|
|
12000
12878
|
//# sourceMappingURL=index.js.map
|