@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/cli.js
CHANGED
|
@@ -746,23 +746,23 @@ function isWslHost() {
|
|
|
746
746
|
function observeWslHostDisk(options = {}) {
|
|
747
747
|
const wsl = options.forceWsl === void 0 ? isWslHost() : options.forceWsl;
|
|
748
748
|
if (!wsl) return null;
|
|
749
|
-
const
|
|
749
|
+
const path62 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
|
|
750
750
|
const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;
|
|
751
751
|
const criticalBelowBytes = options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;
|
|
752
752
|
const statfs = options.statfs ?? statfsSync;
|
|
753
753
|
let stats;
|
|
754
754
|
try {
|
|
755
|
-
stats = statfs(
|
|
755
|
+
stats = statfs(path62);
|
|
756
756
|
} catch (error) {
|
|
757
757
|
return {
|
|
758
758
|
ok: false,
|
|
759
|
-
path:
|
|
759
|
+
path: path62,
|
|
760
760
|
freeBytes: 0,
|
|
761
761
|
totalBytes: 0,
|
|
762
762
|
usedPercent: 100,
|
|
763
763
|
warnBelowBytes,
|
|
764
764
|
criticalBelowBytes,
|
|
765
|
-
reason: `Windows host disk probe failed at ${
|
|
765
|
+
reason: `Windows host disk probe failed at ${path62}: ${error.message}`,
|
|
766
766
|
probeError: error.message
|
|
767
767
|
};
|
|
768
768
|
}
|
|
@@ -776,11 +776,11 @@ function observeWslHostDisk(options = {}) {
|
|
|
776
776
|
let reason = null;
|
|
777
777
|
if (!ok) {
|
|
778
778
|
const tag = criticalFree ? "critical" : "warning";
|
|
779
|
-
reason = `Windows host disk ${
|
|
779
|
+
reason = `Windows host disk ${path62} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
|
|
780
780
|
}
|
|
781
781
|
return {
|
|
782
782
|
ok,
|
|
783
|
-
path:
|
|
783
|
+
path: path62,
|
|
784
784
|
freeBytes,
|
|
785
785
|
totalBytes,
|
|
786
786
|
usedPercent,
|
|
@@ -800,12 +800,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
|
800
800
|
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
801
801
|
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
802
802
|
function observeRunnerDiskGate(input = {}) {
|
|
803
|
-
const
|
|
803
|
+
const path62 = input.diskPath?.trim() || "/";
|
|
804
804
|
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
805
805
|
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
806
806
|
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
807
807
|
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
808
|
-
const stats = statfsSync2(
|
|
808
|
+
const stats = statfsSync2(path62);
|
|
809
809
|
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
810
810
|
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
811
811
|
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
@@ -828,7 +828,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
828
828
|
}
|
|
829
829
|
return {
|
|
830
830
|
ok,
|
|
831
|
-
path:
|
|
831
|
+
path: path62,
|
|
832
832
|
freeBytes,
|
|
833
833
|
totalBytes,
|
|
834
834
|
usedPercent,
|
|
@@ -1813,7 +1813,9 @@ function computeWorkerStatus(worker, options = {}) {
|
|
|
1813
1813
|
finalResult,
|
|
1814
1814
|
error,
|
|
1815
1815
|
changedFiles,
|
|
1816
|
-
gitAncestry
|
|
1816
|
+
gitAncestry,
|
|
1817
|
+
instructionPolicyFingerprint: worker.instructionPolicyFingerprint ?? null,
|
|
1818
|
+
instructionPolicyEvidence: worker.instructionPolicyEvidence ?? null
|
|
1817
1819
|
};
|
|
1818
1820
|
}
|
|
1819
1821
|
function isFinishedWorkerStatus(status) {
|
|
@@ -2578,7 +2580,9 @@ function resolveOrchestrationRouting(input) {
|
|
|
2578
2580
|
const risk = classifyOrchestrationRisk(task);
|
|
2579
2581
|
const inventory = resolveInventory({
|
|
2580
2582
|
inventory: input.inventory,
|
|
2581
|
-
|
|
2583
|
+
// When callers pass a pre-built inventory (tests, CC snapshots), do not probe live
|
|
2584
|
+
// Hermes/Codex bindings — that would overwrite mocked oauth_local with subscription_hermes.
|
|
2585
|
+
codexBinding: input.codexBinding ?? (input.inventory ? null : resolveCodexOrchestrationAdapter())
|
|
2582
2586
|
});
|
|
2583
2587
|
const explicit = input.preferLowCost === false ? null : input.explicitProvider?.trim().toLowerCase();
|
|
2584
2588
|
const explicitModel = input.explicitModel?.trim() || void 0;
|
|
@@ -3392,6 +3396,7 @@ function buildPrompt(input) {
|
|
|
3392
3396
|
"",
|
|
3393
3397
|
...input.personaMarkdown?.trim() ? [input.personaMarkdown.trim(), ""] : [],
|
|
3394
3398
|
...input.instructionPolicyMarkdown?.trim() ? ["Operating rules (Lane A \u2014 from AgentOS memory policy):", input.instructionPolicyMarkdown.trim(), ""] : [],
|
|
3399
|
+
...input.memoryQualityMarkdown?.trim() ? [input.memoryQualityMarkdown.trim(), ""] : [],
|
|
3395
3400
|
...input.repairTargetPrUrl ? [
|
|
3396
3401
|
...repairTargetPromptLines({
|
|
3397
3402
|
targetPrUrl: input.repairTargetPrUrl,
|
|
@@ -3600,6 +3605,21 @@ function persistCompletionAck(worker, runId, fields) {
|
|
|
3600
3605
|
saveWorker(runId, worker);
|
|
3601
3606
|
}
|
|
3602
3607
|
|
|
3608
|
+
// src/completion-replay.ts
|
|
3609
|
+
function trimBlocker(value) {
|
|
3610
|
+
if (typeof value !== "string") return null;
|
|
3611
|
+
const trimmed = value.trim();
|
|
3612
|
+
return trimmed.length ? trimmed : null;
|
|
3613
|
+
}
|
|
3614
|
+
function shouldReplayHarnessCompletion(worker) {
|
|
3615
|
+
if (trimBlocker(worker.completionBlocker)) return true;
|
|
3616
|
+
if (worker.completionOutcome === "rejected") return true;
|
|
3617
|
+
return false;
|
|
3618
|
+
}
|
|
3619
|
+
function hasTerminalCompletionAck(worker) {
|
|
3620
|
+
return hasCompletionAck(worker) && !shouldReplayHarnessCompletion(worker);
|
|
3621
|
+
}
|
|
3622
|
+
|
|
3603
3623
|
// src/worker-ops.ts
|
|
3604
3624
|
import path17 from "node:path";
|
|
3605
3625
|
|
|
@@ -4171,8 +4191,8 @@ function dirtyPathsCoveredByDisposableRemoval(changedFiles, removed) {
|
|
|
4171
4191
|
if (removed.length === 0) return false;
|
|
4172
4192
|
const removedSet = new Set(removed.map((p) => normalizeRelativePath(p)));
|
|
4173
4193
|
return material.every((line) => {
|
|
4174
|
-
const
|
|
4175
|
-
return removedSet.has(
|
|
4194
|
+
const path62 = normalizeRelativePath(pathFromGitStatusLine(line));
|
|
4195
|
+
return removedSet.has(path62);
|
|
4176
4196
|
});
|
|
4177
4197
|
}
|
|
4178
4198
|
|
|
@@ -4418,7 +4438,7 @@ async function tryCompleteWorker(args) {
|
|
|
4418
4438
|
return { ok: true, skipped: true, reason: "worker-not-finished" };
|
|
4419
4439
|
}
|
|
4420
4440
|
const forceReplay = args.force === true || args.force === "true";
|
|
4421
|
-
if (!forceReplay &&
|
|
4441
|
+
if (!forceReplay && hasTerminalCompletionAck(worker)) {
|
|
4422
4442
|
return {
|
|
4423
4443
|
ok: true,
|
|
4424
4444
|
skipped: true,
|
|
@@ -4594,7 +4614,9 @@ async function completeWorker(args) {
|
|
|
4594
4614
|
}
|
|
4595
4615
|
}
|
|
4596
4616
|
function workerStatus(args) {
|
|
4597
|
-
const
|
|
4617
|
+
const runId = required(typeof args.run === "string" ? args.run : "", "--run");
|
|
4618
|
+
const name = required(typeof args.name === "string" ? args.name : "", "--name");
|
|
4619
|
+
const worker = loadWorker(runId, name);
|
|
4598
4620
|
const run = loadRun(worker.runId);
|
|
4599
4621
|
const status = computeWorkerStatus(worker, workerStatusOptions(run));
|
|
4600
4622
|
writeJson(path17.join(worker.workerDir, "last-status.json"), status);
|
|
@@ -4810,7 +4832,7 @@ async function autoCompleteWorker(raw) {
|
|
|
4810
4832
|
reason: "worker has no agentOsId/taskId \u2014 nothing to attribute completion to"
|
|
4811
4833
|
};
|
|
4812
4834
|
}
|
|
4813
|
-
if (
|
|
4835
|
+
if (hasTerminalCompletionAck(worker)) {
|
|
4814
4836
|
return {
|
|
4815
4837
|
worker: worker.name,
|
|
4816
4838
|
runId: worker.runId,
|
|
@@ -4823,7 +4845,7 @@ async function autoCompleteWorker(raw) {
|
|
|
4823
4845
|
const startMs = Date.now();
|
|
4824
4846
|
while (true) {
|
|
4825
4847
|
worker = loadWorker(args.run, args.name);
|
|
4826
|
-
if (
|
|
4848
|
+
if (hasTerminalCompletionAck(worker)) {
|
|
4827
4849
|
return {
|
|
4828
4850
|
worker: worker.name,
|
|
4829
4851
|
runId: worker.runId,
|
|
@@ -5029,6 +5051,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
5029
5051
|
planId: opts.planId,
|
|
5030
5052
|
taskId: opts.taskId,
|
|
5031
5053
|
instructionPolicyMarkdown: opts.instructionPolicyMarkdown,
|
|
5054
|
+
memoryQualityMarkdown: opts.memoryQualityPromptMarkdown ?? opts.memoryQualityCapture?.promptMarkdown ?? null,
|
|
5032
5055
|
personaMarkdown: opts.personaMarkdown,
|
|
5033
5056
|
model: launchModel,
|
|
5034
5057
|
repairTargetPrUrl: opts.repairTargetPrUrl,
|
|
@@ -5807,11 +5830,13 @@ function readHarnessWorkerContext(decision) {
|
|
|
5807
5830
|
const personaEvidence = ctx.personaEvidence && typeof ctx.personaEvidence === "object" ? ctx.personaEvidence : null;
|
|
5808
5831
|
const personaInjectionReady = ctx.personaInjectionReady === true;
|
|
5809
5832
|
const memoryQualityCapture = ctx.memoryQualityCapture && typeof ctx.memoryQualityCapture === "object" ? ctx.memoryQualityCapture : null;
|
|
5833
|
+
const memoryQualityPromptMarkdown = typeof ctx.memoryQualityPromptMarkdown === "string" ? ctx.memoryQualityPromptMarkdown : typeof memoryQualityCapture?.promptMarkdown === "string" ? memoryQualityCapture.promptMarkdown : null;
|
|
5810
5834
|
return {
|
|
5811
5835
|
instructionPolicyMarkdown: markdown,
|
|
5812
5836
|
instructionPolicyFingerprint: fingerprint,
|
|
5813
5837
|
instructionPolicyEvidence: evidence,
|
|
5814
5838
|
memoryQualityCapture,
|
|
5839
|
+
memoryQualityPromptMarkdown,
|
|
5815
5840
|
personaMarkdown,
|
|
5816
5841
|
personaSlug,
|
|
5817
5842
|
personaEvidence,
|
|
@@ -6068,6 +6093,7 @@ async function dispatchRun(args) {
|
|
|
6068
6093
|
instructionPolicyFingerprint: harnessContext?.instructionPolicyFingerprint ?? null,
|
|
6069
6094
|
instructionPolicyEvidence: harnessContext?.instructionPolicyEvidence ?? null,
|
|
6070
6095
|
memoryQualityCapture: harnessContext?.memoryQualityCapture ?? null,
|
|
6096
|
+
memoryQualityPromptMarkdown: harnessContext?.memoryQualityPromptMarkdown ?? null,
|
|
6071
6097
|
personaMarkdown: harnessContext?.personaMarkdown ?? null,
|
|
6072
6098
|
personaSlug: harnessContext?.personaSlug ?? expectedPersona,
|
|
6073
6099
|
personaEvidence: harnessContext?.personaEvidence ?? null,
|
|
@@ -6122,7 +6148,23 @@ async function dispatchRun(args) {
|
|
|
6122
6148
|
for (const decision of result.started) {
|
|
6123
6149
|
shouldContinueDispatch = await spawnClaimed(decision) && shouldContinueDispatch;
|
|
6124
6150
|
}
|
|
6125
|
-
skipped.push(
|
|
6151
|
+
skipped.push(
|
|
6152
|
+
...result.skipped ?? []
|
|
6153
|
+
);
|
|
6154
|
+
if (exactTargetMode) {
|
|
6155
|
+
for (const skipDecision of skipped) {
|
|
6156
|
+
const taskId = String(skipDecision.task.id);
|
|
6157
|
+
if (!exactTargetIds.has(taskId)) continue;
|
|
6158
|
+
outcomes.push({
|
|
6159
|
+
taskId,
|
|
6160
|
+
started: false,
|
|
6161
|
+
error: `exact_target_not_started:${skipDecision.skipReason}`,
|
|
6162
|
+
skipReason: skipDecision.skipReason,
|
|
6163
|
+
detail: skipDecision.reason ?? null,
|
|
6164
|
+
requestedTargetTaskIds: [...exactTargetIds]
|
|
6165
|
+
});
|
|
6166
|
+
}
|
|
6167
|
+
}
|
|
6126
6168
|
if (exactTargetMode) shouldContinueDispatch = false;
|
|
6127
6169
|
while (shouldContinueDispatch && outcomes.length < cappedStarts) {
|
|
6128
6170
|
if (exactTargetMode) break;
|
|
@@ -6235,15 +6277,15 @@ async function sweepRun(args) {
|
|
|
6235
6277
|
}
|
|
6236
6278
|
|
|
6237
6279
|
// src/worktree.ts
|
|
6238
|
-
import { existsSync as
|
|
6239
|
-
import
|
|
6280
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync5 } from "node:fs";
|
|
6281
|
+
import path33 from "node:path";
|
|
6240
6282
|
|
|
6241
6283
|
// src/run-list.ts
|
|
6242
|
-
import { existsSync as
|
|
6243
|
-
import
|
|
6284
|
+
import { existsSync as existsSync23, readFileSync as readFileSync10 } from "node:fs";
|
|
6285
|
+
import path31 from "node:path";
|
|
6244
6286
|
|
|
6245
6287
|
// src/stale-reconcile.ts
|
|
6246
|
-
import
|
|
6288
|
+
import path30 from "node:path";
|
|
6247
6289
|
|
|
6248
6290
|
// src/finalize.ts
|
|
6249
6291
|
import path25 from "node:path";
|
|
@@ -6304,8 +6346,8 @@ function finalizeStaleRuns() {
|
|
|
6304
6346
|
}
|
|
6305
6347
|
|
|
6306
6348
|
// src/worker-metadata-reconcile.ts
|
|
6307
|
-
import { existsSync as
|
|
6308
|
-
import
|
|
6349
|
+
import { existsSync as existsSync22, lstatSync, readdirSync as readdirSync6, readlinkSync, renameSync as renameSync2, rmSync } from "node:fs";
|
|
6350
|
+
import path29 from "node:path";
|
|
6309
6351
|
|
|
6310
6352
|
// src/worker-metadata-paths.ts
|
|
6311
6353
|
import path26 from "node:path";
|
|
@@ -6350,6 +6392,192 @@ function resolveWorkerJsonPath(input) {
|
|
|
6350
6392
|
return canonical;
|
|
6351
6393
|
}
|
|
6352
6394
|
|
|
6395
|
+
// src/run-metadata-retention.ts
|
|
6396
|
+
import { existsSync as existsSync21, readdirSync as readdirSync5, statSync as statSync4 } from "node:fs";
|
|
6397
|
+
import path28 from "node:path";
|
|
6398
|
+
|
|
6399
|
+
// src/default-repo.ts
|
|
6400
|
+
import path27 from "node:path";
|
|
6401
|
+
function expandConfiguredRepo(value) {
|
|
6402
|
+
return path27.resolve(resolveUserPath(value.trim()));
|
|
6403
|
+
}
|
|
6404
|
+
function fromConfigured(value, source, persistedInConfig) {
|
|
6405
|
+
const trimmed = value?.trim();
|
|
6406
|
+
if (!trimmed) return null;
|
|
6407
|
+
return {
|
|
6408
|
+
repo: expandConfiguredRepo(trimmed),
|
|
6409
|
+
source,
|
|
6410
|
+
persistedInConfig
|
|
6411
|
+
};
|
|
6412
|
+
}
|
|
6413
|
+
function resolveDefaultRepo(opts = {}) {
|
|
6414
|
+
const env = opts.env ?? process.env;
|
|
6415
|
+
const config = opts.config ?? loadUserConfig();
|
|
6416
|
+
const fromConfig = fromConfigured(config.defaultRepo, "config", true);
|
|
6417
|
+
if (fromConfig) return fromConfig;
|
|
6418
|
+
const fromDefaultEnv = fromConfigured(env.KYNVER_DEFAULT_REPO, "env_default_repo", false);
|
|
6419
|
+
if (fromDefaultEnv) return fromDefaultEnv;
|
|
6420
|
+
const fromHarnessEnv = fromConfigured(env.KYNVER_HARNESS_REPO, "env_harness_repo", false);
|
|
6421
|
+
if (fromHarnessEnv) return fromHarnessEnv;
|
|
6422
|
+
const discovered = discoverDefaultRepo({
|
|
6423
|
+
cwd: opts.cwd,
|
|
6424
|
+
runtimeModuleUrl: opts.runtimeModuleUrl
|
|
6425
|
+
});
|
|
6426
|
+
if (!discovered) return null;
|
|
6427
|
+
return {
|
|
6428
|
+
repo: discovered.repo,
|
|
6429
|
+
source: discovered.source,
|
|
6430
|
+
persistedInConfig: false
|
|
6431
|
+
};
|
|
6432
|
+
}
|
|
6433
|
+
function formatResolvedDefaultRepo(resolved) {
|
|
6434
|
+
return {
|
|
6435
|
+
defaultRepo: displayUserPath(resolved.repo),
|
|
6436
|
+
source: resolved.source,
|
|
6437
|
+
persistedInConfig: resolved.persistedInConfig
|
|
6438
|
+
};
|
|
6439
|
+
}
|
|
6440
|
+
|
|
6441
|
+
// src/run-metadata-retention.ts
|
|
6442
|
+
var RUN_METADATA_ACTIVE_SIGNAL_MS = 15 * 60 * 1e3;
|
|
6443
|
+
function isHarnessRunMetadataPath(targetPath, harnessRoot) {
|
|
6444
|
+
const resolved = path28.resolve(targetPath);
|
|
6445
|
+
const runsDir = path28.resolve(harnessRunsDir(harnessRoot));
|
|
6446
|
+
const rel = path28.relative(runsDir, resolved);
|
|
6447
|
+
return rel !== ".." && !rel.startsWith("..") && !path28.isAbsolute(rel);
|
|
6448
|
+
}
|
|
6449
|
+
function listRunDirIds(runsDir) {
|
|
6450
|
+
if (!existsSync21(runsDir)) return [];
|
|
6451
|
+
try {
|
|
6452
|
+
return readdirSync5(runsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name !== "runs").map((entry) => entry.name);
|
|
6453
|
+
} catch {
|
|
6454
|
+
return [];
|
|
6455
|
+
}
|
|
6456
|
+
}
|
|
6457
|
+
function listWorkerNamesOnDisk(runDir2) {
|
|
6458
|
+
const workersDir = path28.join(runDir2, "workers");
|
|
6459
|
+
if (!existsSync21(workersDir)) return [];
|
|
6460
|
+
try {
|
|
6461
|
+
return readdirSync5(workersDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
6462
|
+
} catch {
|
|
6463
|
+
return [];
|
|
6464
|
+
}
|
|
6465
|
+
}
|
|
6466
|
+
function pathRecentlyTouched(target, now, windowMs) {
|
|
6467
|
+
if (!existsSync21(target)) return false;
|
|
6468
|
+
try {
|
|
6469
|
+
const age = now - statSync4(target).mtimeMs;
|
|
6470
|
+
return Number.isFinite(age) && age >= 0 && age < windowMs;
|
|
6471
|
+
} catch {
|
|
6472
|
+
return false;
|
|
6473
|
+
}
|
|
6474
|
+
}
|
|
6475
|
+
function workerDirHasActiveRetentionSignals(workerDir, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
|
|
6476
|
+
if (!existsSync21(workerDir)) return false;
|
|
6477
|
+
const artifacts = workerArtifactPaths(workerDir);
|
|
6478
|
+
const worker = readJson(
|
|
6479
|
+
artifacts.workerJsonPath,
|
|
6480
|
+
void 0
|
|
6481
|
+
);
|
|
6482
|
+
if (worker?.status === "running" && isPidAlive(worker.pid)) return true;
|
|
6483
|
+
const heartbeat = parseHeartbeat(artifacts.heartbeatPath);
|
|
6484
|
+
if (heartbeat.lastHeartbeatAt) {
|
|
6485
|
+
const age = now - Date.parse(heartbeat.lastHeartbeatAt);
|
|
6486
|
+
if (Number.isFinite(age) && age >= 0 && age < windowMs) return true;
|
|
6487
|
+
}
|
|
6488
|
+
if (pathRecentlyTouched(artifacts.stdoutPath, now, windowMs)) return true;
|
|
6489
|
+
if (pathRecentlyTouched(artifacts.heartbeatPath, now, windowMs)) return true;
|
|
6490
|
+
return false;
|
|
6491
|
+
}
|
|
6492
|
+
function runDirHasActiveRetentionSignals(runDir2, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
|
|
6493
|
+
for (const name of listWorkerNamesOnDisk(runDir2)) {
|
|
6494
|
+
if (workerDirHasActiveRetentionSignals(path28.join(runDir2, "workers", name), now, windowMs)) {
|
|
6495
|
+
return true;
|
|
6496
|
+
}
|
|
6497
|
+
}
|
|
6498
|
+
return false;
|
|
6499
|
+
}
|
|
6500
|
+
function inferRepoFields() {
|
|
6501
|
+
const resolved = resolveDefaultRepo();
|
|
6502
|
+
return {
|
|
6503
|
+
repo: resolved?.repo ?? "",
|
|
6504
|
+
base: "origin/main",
|
|
6505
|
+
baseCommit: "unknown"
|
|
6506
|
+
};
|
|
6507
|
+
}
|
|
6508
|
+
function synthesizeRunFromDisk(harnessRoot, runId) {
|
|
6509
|
+
const runDir2 = path28.join(harnessRunsDir(harnessRoot), safeSlug(runId));
|
|
6510
|
+
if (!existsSync21(runDir2)) return null;
|
|
6511
|
+
const workerNames = listWorkerNamesOnDisk(runDir2);
|
|
6512
|
+
if (workerNames.length === 0) return null;
|
|
6513
|
+
let createdAt;
|
|
6514
|
+
try {
|
|
6515
|
+
createdAt = statSync4(runDir2).birthtime.toISOString();
|
|
6516
|
+
} catch {
|
|
6517
|
+
createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6518
|
+
}
|
|
6519
|
+
const workers = {};
|
|
6520
|
+
for (const name of workerNames) {
|
|
6521
|
+
const canonicalDir = canonicalWorkerDir(harnessRoot, runId, name);
|
|
6522
|
+
workers[name] = {
|
|
6523
|
+
workerDir: canonicalDir,
|
|
6524
|
+
statusPath: path28.join(canonicalDir, "worker.json")
|
|
6525
|
+
};
|
|
6526
|
+
}
|
|
6527
|
+
const hasActive = runDirHasActiveRetentionSignals(runDir2);
|
|
6528
|
+
return {
|
|
6529
|
+
id: runId,
|
|
6530
|
+
name: runId,
|
|
6531
|
+
...inferRepoFields(),
|
|
6532
|
+
status: hasActive ? "running" : "needs_attention",
|
|
6533
|
+
createdAt,
|
|
6534
|
+
workers
|
|
6535
|
+
};
|
|
6536
|
+
}
|
|
6537
|
+
function repairMissingRunMetadata(harnessRootInput) {
|
|
6538
|
+
const harnessRoot = normalizeHarnessRoot(harnessRootInput ?? resolveHarnessRoot());
|
|
6539
|
+
const runsDir = harnessRunsDir(harnessRoot);
|
|
6540
|
+
const outcomes = [];
|
|
6541
|
+
for (const runId of listRunDirIds(runsDir)) {
|
|
6542
|
+
const runJsonPath = path28.join(runsDir, runId, "run.json");
|
|
6543
|
+
if (existsSync21(runJsonPath)) {
|
|
6544
|
+
outcomes.push({
|
|
6545
|
+
runId,
|
|
6546
|
+
action: "skipped",
|
|
6547
|
+
reason: "run.json present"
|
|
6548
|
+
});
|
|
6549
|
+
continue;
|
|
6550
|
+
}
|
|
6551
|
+
const synthesized = synthesizeRunFromDisk(harnessRoot, runId);
|
|
6552
|
+
if (!synthesized) {
|
|
6553
|
+
outcomes.push({
|
|
6554
|
+
runId,
|
|
6555
|
+
action: "skipped",
|
|
6556
|
+
reason: "no worker dirs on disk"
|
|
6557
|
+
});
|
|
6558
|
+
continue;
|
|
6559
|
+
}
|
|
6560
|
+
saveRun(synthesized);
|
|
6561
|
+
outcomes.push({
|
|
6562
|
+
runId,
|
|
6563
|
+
action: "repaired_run_json",
|
|
6564
|
+
reason: runDirHasActiveRetentionSignals(path28.join(runsDir, runId)) ? "synthesized run.json while worker artifacts still active" : "synthesized run.json from orphan worker metadata dirs"
|
|
6565
|
+
});
|
|
6566
|
+
}
|
|
6567
|
+
return { runs: outcomes };
|
|
6568
|
+
}
|
|
6569
|
+
function collectFilesystemLiveRunKeys(harnessRoot, now = Date.now()) {
|
|
6570
|
+
const keys = /* @__PURE__ */ new Set();
|
|
6571
|
+
const runsDir = harnessRunsDir(harnessRoot);
|
|
6572
|
+
for (const runId of listRunDirIds(runsDir)) {
|
|
6573
|
+
const runDir2 = path28.join(runsDir, runId);
|
|
6574
|
+
if (runDirHasActiveRetentionSignals(runDir2, now)) {
|
|
6575
|
+
keys.add(`${harnessRoot}\0${runId}`);
|
|
6576
|
+
}
|
|
6577
|
+
}
|
|
6578
|
+
return keys;
|
|
6579
|
+
}
|
|
6580
|
+
|
|
6353
6581
|
// src/worker-metadata-reconcile.ts
|
|
6354
6582
|
function materializeSymlinkedRunDir(harnessRoot, runId) {
|
|
6355
6583
|
const canonical = canonicalRunDir(harnessRoot, runId);
|
|
@@ -6360,7 +6588,10 @@ function materializeSymlinkedRunDir(harnessRoot, runId) {
|
|
|
6360
6588
|
return null;
|
|
6361
6589
|
}
|
|
6362
6590
|
if (!stat.isSymbolicLink()) return null;
|
|
6363
|
-
const linkedTarget =
|
|
6591
|
+
const linkedTarget = path29.resolve(path29.dirname(canonical), readlinkSync(canonical));
|
|
6592
|
+
if (runDirHasActiveRetentionSignals(linkedTarget) || runDirHasActiveRetentionSignals(canonical)) {
|
|
6593
|
+
return null;
|
|
6594
|
+
}
|
|
6364
6595
|
const staging = `${canonical}.materialize-${Date.now()}`;
|
|
6365
6596
|
renameSync2(linkedTarget, staging);
|
|
6366
6597
|
rmSync(canonical);
|
|
@@ -6370,9 +6601,9 @@ function materializeSymlinkedRunDir(harnessRoot, runId) {
|
|
|
6370
6601
|
function relocateArtifacts(input) {
|
|
6371
6602
|
const moved = [];
|
|
6372
6603
|
for (const fileName of workerArtifactFileNames()) {
|
|
6373
|
-
const from =
|
|
6374
|
-
const to =
|
|
6375
|
-
if (!
|
|
6604
|
+
const from = path29.join(input.fromDir, fileName);
|
|
6605
|
+
const to = path29.join(input.toDir, fileName);
|
|
6606
|
+
if (!existsSync22(from) || existsSync22(to)) continue;
|
|
6376
6607
|
renameSync2(from, to);
|
|
6377
6608
|
moved.push(fileName);
|
|
6378
6609
|
}
|
|
@@ -6411,8 +6642,8 @@ function deriveSynthesizedStatus(input) {
|
|
|
6411
6642
|
function synthesizeWorkerFromArtifacts(input) {
|
|
6412
6643
|
const artifacts = workerArtifactPaths(input.canonicalDir);
|
|
6413
6644
|
const lastStatus = readJson(artifacts.lastStatusPath, void 0);
|
|
6414
|
-
const stdoutExists =
|
|
6415
|
-
const heartbeatExists =
|
|
6645
|
+
const stdoutExists = existsSync22(artifacts.stdoutPath);
|
|
6646
|
+
const heartbeatExists = existsSync22(artifacts.heartbeatPath);
|
|
6416
6647
|
const hasArtifacts = stdoutExists || heartbeatExists || lastStatus != null;
|
|
6417
6648
|
if (!hasArtifacts) return null;
|
|
6418
6649
|
const parsedStdout = stdoutExists ? parseHarnessStream(artifacts.stdoutPath) : { finalResult: null };
|
|
@@ -6428,7 +6659,7 @@ function synthesizeWorkerFromArtifacts(input) {
|
|
|
6428
6659
|
runId: input.run.id,
|
|
6429
6660
|
status,
|
|
6430
6661
|
branch: typeof lastStatus?.branch === "string" ? lastStatus.branch : `agent/${input.run.id}/${input.workerName}`,
|
|
6431
|
-
worktreePath: typeof lastStatus?.worktreePath === "string" ? lastStatus.worktreePath :
|
|
6662
|
+
worktreePath: typeof lastStatus?.worktreePath === "string" ? lastStatus.worktreePath : path29.join(getPaths().worktreesDir, input.run.id, input.workerName),
|
|
6432
6663
|
workerDir: input.canonicalDir,
|
|
6433
6664
|
stdoutPath: artifacts.stdoutPath,
|
|
6434
6665
|
stderrPath: artifacts.stderrPath,
|
|
@@ -6455,7 +6686,7 @@ function repairRunWorkerIndex(run, harnessRoot) {
|
|
|
6455
6686
|
const nextWorkers = { ...run.workers || {} };
|
|
6456
6687
|
for (const [name, ref] of Object.entries(nextWorkers)) {
|
|
6457
6688
|
const canonicalDir = canonicalWorkerDir(harnessRoot, run.id, name);
|
|
6458
|
-
const canonicalStatus =
|
|
6689
|
+
const canonicalStatus = path29.join(canonicalDir, "worker.json");
|
|
6459
6690
|
const nested = hasNestedRunsSegment(ref.workerDir) || hasNestedRunsSegment(ref.statusPath) || ref.workerDir !== canonicalDir || ref.statusPath !== canonicalStatus;
|
|
6460
6691
|
if (!nested) continue;
|
|
6461
6692
|
nextWorkers[name] = { workerDir: canonicalDir, statusPath: canonicalStatus };
|
|
@@ -6488,7 +6719,7 @@ function reconcileOneWorker(input) {
|
|
|
6488
6719
|
statusPath: input.statusPath
|
|
6489
6720
|
});
|
|
6490
6721
|
let worker = readJson(workerJsonPath, void 0);
|
|
6491
|
-
if (!worker &&
|
|
6722
|
+
if (!worker && existsSync22(canonicalArtifacts.workerJsonPath)) {
|
|
6492
6723
|
worker = readJson(canonicalArtifacts.workerJsonPath, void 0);
|
|
6493
6724
|
}
|
|
6494
6725
|
if (!worker) {
|
|
@@ -6508,15 +6739,15 @@ function reconcileOneWorker(input) {
|
|
|
6508
6739
|
});
|
|
6509
6740
|
return outcomes;
|
|
6510
6741
|
}
|
|
6511
|
-
const dirExists =
|
|
6512
|
-
const hasAnyArtifact = workerArtifactFileNames().some((f) =>
|
|
6742
|
+
const dirExists = existsSync22(canonicalDir);
|
|
6743
|
+
const hasAnyArtifact = workerArtifactFileNames().some((f) => existsSync22(path29.join(canonicalDir, f)));
|
|
6513
6744
|
if (dirExists && !hasAnyArtifact) {
|
|
6514
6745
|
const abandoned = {
|
|
6515
6746
|
name: input.workerName,
|
|
6516
6747
|
runId: input.run.id,
|
|
6517
6748
|
status: "exited",
|
|
6518
6749
|
branch: `agent/${input.run.id}/${input.workerName}`,
|
|
6519
|
-
worktreePath:
|
|
6750
|
+
worktreePath: path29.join(getPaths().worktreesDir, input.run.id, input.workerName),
|
|
6520
6751
|
workerDir: canonicalDir,
|
|
6521
6752
|
stdoutPath: canonicalArtifacts.stdoutPath,
|
|
6522
6753
|
stderrPath: canonicalArtifacts.stderrPath,
|
|
@@ -6563,11 +6794,11 @@ function reconcileOneWorker(input) {
|
|
|
6563
6794
|
return outcomes;
|
|
6564
6795
|
}
|
|
6565
6796
|
function listOrphanWorkerNames(run, harnessRoot) {
|
|
6566
|
-
const workersDir =
|
|
6567
|
-
if (!
|
|
6797
|
+
const workersDir = path29.join(runDirectory(run.id), "workers");
|
|
6798
|
+
if (!existsSync22(workersDir)) return [];
|
|
6568
6799
|
const indexed = new Set(Object.keys(run.workers || {}));
|
|
6569
6800
|
const orphans = [];
|
|
6570
|
-
for (const entry of
|
|
6801
|
+
for (const entry of readdirSync6(workersDir, { withFileTypes: true })) {
|
|
6571
6802
|
if (!entry.isDirectory()) continue;
|
|
6572
6803
|
if (indexed.has(entry.name)) continue;
|
|
6573
6804
|
orphans.push(entry.name);
|
|
@@ -6576,6 +6807,7 @@ function listOrphanWorkerNames(run, harnessRoot) {
|
|
|
6576
6807
|
}
|
|
6577
6808
|
function reconcileWorkerMetadata() {
|
|
6578
6809
|
const { harnessRoot } = getPaths();
|
|
6810
|
+
const runMetadataRetention = repairMissingRunMetadata(harnessRoot);
|
|
6579
6811
|
const outcomes = [];
|
|
6580
6812
|
for (const run of listRunRecords()) {
|
|
6581
6813
|
const materializedFrom = materializeSymlinkedRunDir(harnessRoot, run.id);
|
|
@@ -6602,7 +6834,7 @@ function reconcileWorkerMetadata() {
|
|
|
6602
6834
|
...run.workers || {},
|
|
6603
6835
|
[orphan]: {
|
|
6604
6836
|
workerDir: canonicalWorkerDir(harnessRoot, run.id, orphan),
|
|
6605
|
-
statusPath:
|
|
6837
|
+
statusPath: path29.join(canonicalWorkerDir(harnessRoot, run.id, orphan), "worker.json")
|
|
6606
6838
|
}
|
|
6607
6839
|
};
|
|
6608
6840
|
saveRun(run);
|
|
@@ -6625,7 +6857,7 @@ function reconcileWorkerMetadata() {
|
|
|
6625
6857
|
);
|
|
6626
6858
|
}
|
|
6627
6859
|
}
|
|
6628
|
-
return { workers: outcomes };
|
|
6860
|
+
return { workers: outcomes, runMetadataRetention };
|
|
6629
6861
|
}
|
|
6630
6862
|
|
|
6631
6863
|
// src/stale-reconcile.ts
|
|
@@ -6642,7 +6874,7 @@ function reconcileStaleWorkers() {
|
|
|
6642
6874
|
const now = Date.now();
|
|
6643
6875
|
for (const run of listRunRecords()) {
|
|
6644
6876
|
for (const name of Object.keys(run.workers || {})) {
|
|
6645
|
-
const workerPath =
|
|
6877
|
+
const workerPath = path30.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
6646
6878
|
const worker = readJson(workerPath, void 0);
|
|
6647
6879
|
if (!worker || worker.status !== "running") {
|
|
6648
6880
|
outcomes.push({
|
|
@@ -6725,16 +6957,28 @@ function reconcileRunsCli() {
|
|
|
6725
6957
|
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
6726
6958
|
return acc;
|
|
6727
6959
|
}, {});
|
|
6960
|
+
const runRetentionTotals = result.metadataReconcile.runMetadataRetention.runs.reduce((acc, row) => {
|
|
6961
|
+
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
6962
|
+
return acc;
|
|
6963
|
+
}, {});
|
|
6728
6964
|
console.log(
|
|
6729
6965
|
JSON.stringify(
|
|
6730
6966
|
{
|
|
6731
6967
|
ok: true,
|
|
6732
6968
|
workers: { markedExited, killedStale, skipped, total: result.workers.length },
|
|
6733
|
-
metadataReconcile: {
|
|
6969
|
+
metadataReconcile: {
|
|
6970
|
+
totals: metadataTotals,
|
|
6971
|
+
total: result.metadataReconcile.workers.length,
|
|
6972
|
+
runMetadataRetention: {
|
|
6973
|
+
totals: runRetentionTotals,
|
|
6974
|
+
total: result.metadataReconcile.runMetadataRetention.runs.length
|
|
6975
|
+
}
|
|
6976
|
+
},
|
|
6734
6977
|
finalizedRuns: result.finalizedRuns.length,
|
|
6735
6978
|
details: {
|
|
6736
6979
|
workers: result.workers,
|
|
6737
6980
|
metadataReconcile: result.metadataReconcile.workers,
|
|
6981
|
+
runMetadataRetention: result.metadataReconcile.runMetadataRetention.runs,
|
|
6738
6982
|
finalizedRuns: result.finalizedRuns
|
|
6739
6983
|
}
|
|
6740
6984
|
},
|
|
@@ -6746,7 +6990,7 @@ function reconcileRunsCli() {
|
|
|
6746
6990
|
|
|
6747
6991
|
// src/run-list.ts
|
|
6748
6992
|
function heartbeatByteLength(heartbeatPath) {
|
|
6749
|
-
if (!heartbeatPath || !
|
|
6993
|
+
if (!heartbeatPath || !existsSync23(heartbeatPath)) return 0;
|
|
6750
6994
|
try {
|
|
6751
6995
|
return readFileSync10(heartbeatPath, "utf8").trim().length;
|
|
6752
6996
|
} catch {
|
|
@@ -6754,7 +6998,7 @@ function heartbeatByteLength(heartbeatPath) {
|
|
|
6754
6998
|
}
|
|
6755
6999
|
}
|
|
6756
7000
|
function workerEvidence(run, workerName) {
|
|
6757
|
-
const workerPath =
|
|
7001
|
+
const workerPath = path31.join(runDirectory(run.id), "workers", safeSlug(workerName), "worker.json");
|
|
6758
7002
|
const worker = readJson(workerPath, void 0);
|
|
6759
7003
|
if (!worker) {
|
|
6760
7004
|
return {
|
|
@@ -6811,7 +7055,7 @@ function aggregateRunAttention(workers) {
|
|
|
6811
7055
|
function countOpenWorkers(run) {
|
|
6812
7056
|
let open = 0;
|
|
6813
7057
|
for (const name of Object.keys(run.workers || {})) {
|
|
6814
|
-
const workerPath =
|
|
7058
|
+
const workerPath = path31.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
6815
7059
|
const worker = readJson(workerPath, void 0);
|
|
6816
7060
|
if (!worker) continue;
|
|
6817
7061
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
@@ -6859,50 +7103,8 @@ function listRunsCli() {
|
|
|
6859
7103
|
console.log(JSON.stringify(buildRunListRows(), null, 2));
|
|
6860
7104
|
}
|
|
6861
7105
|
|
|
6862
|
-
// src/default-repo.ts
|
|
6863
|
-
import path30 from "node:path";
|
|
6864
|
-
function expandConfiguredRepo(value) {
|
|
6865
|
-
return path30.resolve(resolveUserPath(value.trim()));
|
|
6866
|
-
}
|
|
6867
|
-
function fromConfigured(value, source, persistedInConfig) {
|
|
6868
|
-
const trimmed = value?.trim();
|
|
6869
|
-
if (!trimmed) return null;
|
|
6870
|
-
return {
|
|
6871
|
-
repo: expandConfiguredRepo(trimmed),
|
|
6872
|
-
source,
|
|
6873
|
-
persistedInConfig
|
|
6874
|
-
};
|
|
6875
|
-
}
|
|
6876
|
-
function resolveDefaultRepo(opts = {}) {
|
|
6877
|
-
const env = opts.env ?? process.env;
|
|
6878
|
-
const config = opts.config ?? loadUserConfig();
|
|
6879
|
-
const fromConfig = fromConfigured(config.defaultRepo, "config", true);
|
|
6880
|
-
if (fromConfig) return fromConfig;
|
|
6881
|
-
const fromDefaultEnv = fromConfigured(env.KYNVER_DEFAULT_REPO, "env_default_repo", false);
|
|
6882
|
-
if (fromDefaultEnv) return fromDefaultEnv;
|
|
6883
|
-
const fromHarnessEnv = fromConfigured(env.KYNVER_HARNESS_REPO, "env_harness_repo", false);
|
|
6884
|
-
if (fromHarnessEnv) return fromHarnessEnv;
|
|
6885
|
-
const discovered = discoverDefaultRepo({
|
|
6886
|
-
cwd: opts.cwd,
|
|
6887
|
-
runtimeModuleUrl: opts.runtimeModuleUrl
|
|
6888
|
-
});
|
|
6889
|
-
if (!discovered) return null;
|
|
6890
|
-
return {
|
|
6891
|
-
repo: discovered.repo,
|
|
6892
|
-
source: discovered.source,
|
|
6893
|
-
persistedInConfig: false
|
|
6894
|
-
};
|
|
6895
|
-
}
|
|
6896
|
-
function formatResolvedDefaultRepo(resolved) {
|
|
6897
|
-
return {
|
|
6898
|
-
defaultRepo: displayUserPath(resolved.repo),
|
|
6899
|
-
source: resolved.source,
|
|
6900
|
-
persistedInConfig: resolved.persistedInConfig
|
|
6901
|
-
};
|
|
6902
|
-
}
|
|
6903
|
-
|
|
6904
7106
|
// src/validate.ts
|
|
6905
|
-
import
|
|
7107
|
+
import path32 from "node:path";
|
|
6906
7108
|
var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
6907
7109
|
function validateRunId(runId) {
|
|
6908
7110
|
const trimmed = runId.trim();
|
|
@@ -6910,7 +7112,7 @@ function validateRunId(runId) {
|
|
|
6910
7112
|
return trimmed;
|
|
6911
7113
|
}
|
|
6912
7114
|
function validateRepo(repo) {
|
|
6913
|
-
const resolved =
|
|
7115
|
+
const resolved = path32.resolve(repo);
|
|
6914
7116
|
if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
|
|
6915
7117
|
return resolved;
|
|
6916
7118
|
}
|
|
@@ -6929,7 +7131,7 @@ function createRun(args) {
|
|
|
6929
7131
|
ensureGitRepo(repo);
|
|
6930
7132
|
const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
|
|
6931
7133
|
const dir = runDirectory(id);
|
|
6932
|
-
if (
|
|
7134
|
+
if (existsSync24(dir)) failExists(`run already exists: ${id}`);
|
|
6933
7135
|
mkdirSync5(dir, { recursive: true });
|
|
6934
7136
|
const base = String(args.base || "origin/main");
|
|
6935
7137
|
const baseCommit = git(repo, ["rev-parse", base]).trim();
|
|
@@ -6943,7 +7145,7 @@ function createRun(args) {
|
|
|
6943
7145
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6944
7146
|
workers: {}
|
|
6945
7147
|
};
|
|
6946
|
-
writeJson(
|
|
7148
|
+
writeJson(path33.join(dir, "run.json"), run);
|
|
6947
7149
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
6948
7150
|
}
|
|
6949
7151
|
function listRuns() {
|
|
@@ -6955,8 +7157,8 @@ function failExists(message) {
|
|
|
6955
7157
|
}
|
|
6956
7158
|
|
|
6957
7159
|
// src/discard-disposable.ts
|
|
6958
|
-
import { existsSync as
|
|
6959
|
-
import
|
|
7160
|
+
import { existsSync as existsSync25, rmSync as rmSync2 } from "node:fs";
|
|
7161
|
+
import path34 from "node:path";
|
|
6960
7162
|
function normalizeRelativePath2(value) {
|
|
6961
7163
|
const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
6962
7164
|
if (!normalized || normalized.startsWith("/") || normalized.includes("..")) {
|
|
@@ -6977,15 +7179,15 @@ function discardDisposableArtifacts(args) {
|
|
|
6977
7179
|
if (paths.length === 0) {
|
|
6978
7180
|
return { ok: false, removed: [], reason: "requires at least one --path" };
|
|
6979
7181
|
}
|
|
6980
|
-
const worktreeRoot =
|
|
7182
|
+
const worktreeRoot = path34.resolve(worker.worktreePath);
|
|
6981
7183
|
const removed = [];
|
|
6982
7184
|
for (const raw of paths) {
|
|
6983
7185
|
const rel = normalizeRelativePath2(raw);
|
|
6984
|
-
const abs =
|
|
6985
|
-
if (!abs.startsWith(worktreeRoot +
|
|
7186
|
+
const abs = path34.resolve(worktreeRoot, rel);
|
|
7187
|
+
if (!abs.startsWith(worktreeRoot + path34.sep) && abs !== worktreeRoot) {
|
|
6986
7188
|
return { ok: false, removed, reason: `path escapes worktree: ${raw}` };
|
|
6987
7189
|
}
|
|
6988
|
-
if (!
|
|
7190
|
+
if (!existsSync25(abs)) {
|
|
6989
7191
|
return { ok: false, removed, reason: `path not found: ${raw}` };
|
|
6990
7192
|
}
|
|
6991
7193
|
rmSync2(abs, { recursive: true, force: true });
|
|
@@ -7007,8 +7209,472 @@ function discardDisposableCli(args) {
|
|
|
7007
7209
|
if (!result.ok) process.exit(1);
|
|
7008
7210
|
}
|
|
7009
7211
|
|
|
7212
|
+
// src/cron/cron-env.ts
|
|
7213
|
+
import { existsSync as existsSync26 } from "node:fs";
|
|
7214
|
+
import { homedir as homedir11 } from "node:os";
|
|
7215
|
+
import path35 from "node:path";
|
|
7216
|
+
function envFlag(name, defaultValue) {
|
|
7217
|
+
const raw = process.env[name]?.trim().toLowerCase();
|
|
7218
|
+
if (!raw) return defaultValue;
|
|
7219
|
+
if (raw === "0" || raw === "false" || raw === "no" || raw === "off") return false;
|
|
7220
|
+
if (raw === "1" || raw === "true" || raw === "yes" || raw === "on") return true;
|
|
7221
|
+
return defaultValue;
|
|
7222
|
+
}
|
|
7223
|
+
function envInt(name, fallback, min = 1) {
|
|
7224
|
+
const n = Number(process.env[name]);
|
|
7225
|
+
if (!Number.isFinite(n) || n < min) return fallback;
|
|
7226
|
+
return Math.floor(n);
|
|
7227
|
+
}
|
|
7228
|
+
function defaultKynverCronStorePath() {
|
|
7229
|
+
const explicit = process.env.KYNVER_CRON_STORE_PATH?.trim() || process.env.OPENCLAW_CRON_STORE_PATH?.trim();
|
|
7230
|
+
if (explicit) return explicit;
|
|
7231
|
+
return path35.join(homedir11(), ".kynver", "agent-os-cron.json");
|
|
7232
|
+
}
|
|
7233
|
+
function defaultKynverCronStatePath(storePath = defaultKynverCronStorePath()) {
|
|
7234
|
+
const explicit = process.env.KYNVER_CRON_TICK_STATE_PATH?.trim();
|
|
7235
|
+
if (explicit) return explicit;
|
|
7236
|
+
return `${storePath.replace(/\.json$/i, "")}.tick-state.json`;
|
|
7237
|
+
}
|
|
7238
|
+
function resolveKynverCronFireBaseUrl() {
|
|
7239
|
+
const config = loadUserConfig();
|
|
7240
|
+
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;
|
|
7241
|
+
}
|
|
7242
|
+
function resolveKynverCronSecret() {
|
|
7243
|
+
return process.env.KYNVER_CRON_SECRET?.trim() || process.env.OPENCLAW_CRON_SECRET?.trim() || process.env.KYNVER_RUNTIME_SECRET?.trim() || null;
|
|
7244
|
+
}
|
|
7245
|
+
function resolveKynverCronEnv() {
|
|
7246
|
+
const storePath = defaultKynverCronStorePath();
|
|
7247
|
+
const statePath = defaultKynverCronStatePath(storePath);
|
|
7248
|
+
const fireBaseUrl = resolveKynverCronFireBaseUrl();
|
|
7249
|
+
const secret = resolveKynverCronSecret();
|
|
7250
|
+
const credsReady = Boolean(fireBaseUrl && secret);
|
|
7251
|
+
const storeExists = existsSync26(storePath);
|
|
7252
|
+
const defaultEnabled = credsReady && (storeExists || envFlag("KYNVER_CRON_TICK_FORCE", false));
|
|
7253
|
+
return {
|
|
7254
|
+
storePath,
|
|
7255
|
+
statePath,
|
|
7256
|
+
lockPath: `${statePath}.lock`,
|
|
7257
|
+
fireBaseUrl,
|
|
7258
|
+
secret,
|
|
7259
|
+
tickEnabled: envFlag("KYNVER_CRON_TICK_ENABLED", defaultEnabled),
|
|
7260
|
+
tickIntervalMs: envInt("KYNVER_CRON_TICK_INTERVAL_MS", 6e4, 5e3),
|
|
7261
|
+
missedRunPolicy: process.env.KYNVER_CRON_MISSED_RUN_POLICY?.trim().toLowerCase() === "skip" ? "skip" : "catch_up",
|
|
7262
|
+
maxCatchUpPerTick: envInt("KYNVER_CRON_MAX_CATCH_UP_PER_TICK", 3, 0),
|
|
7263
|
+
maxRetries: envInt("KYNVER_CRON_MAX_RETRIES", 3, 0),
|
|
7264
|
+
retryBackoffMs: envInt("KYNVER_CRON_RETRY_BACKOFF_MS", 6e4, 1e3),
|
|
7265
|
+
inflightLeaseMs: envInt("KYNVER_CRON_INFLIGHT_LEASE_MS", 12e4, 5e3)
|
|
7266
|
+
};
|
|
7267
|
+
}
|
|
7268
|
+
function isKynverCronDaemonPrimary(env = resolveKynverCronEnv()) {
|
|
7269
|
+
return env.tickEnabled && Boolean(env.fireBaseUrl && env.secret);
|
|
7270
|
+
}
|
|
7271
|
+
|
|
7272
|
+
// src/cron/cron-fire.ts
|
|
7273
|
+
function trimTrailingSlash2(url) {
|
|
7274
|
+
return url.replace(/\/+$/, "");
|
|
7275
|
+
}
|
|
7276
|
+
async function fireKynverCronJob(input) {
|
|
7277
|
+
const doFetch = input.fetchFn ?? fetch;
|
|
7278
|
+
const callbackPath = input.entry.spec.callbackPath.startsWith("/") ? input.entry.spec.callbackPath : `/${input.entry.spec.callbackPath}`;
|
|
7279
|
+
const url = `${trimTrailingSlash2(input.baseUrl)}${callbackPath}`;
|
|
7280
|
+
const jobId = input.jobId ?? input.entry.spec.dedupeKey ?? null;
|
|
7281
|
+
const body = {
|
|
7282
|
+
source: "kynver-cron",
|
|
7283
|
+
jobId,
|
|
7284
|
+
agentOsId: input.entry.spec.target.agentOsId,
|
|
7285
|
+
kind: input.entry.spec.kind,
|
|
7286
|
+
target: input.entry.spec.target,
|
|
7287
|
+
...input.entry.spec.payload !== void 0 && { payload: input.entry.spec.payload }
|
|
7288
|
+
};
|
|
7289
|
+
const res = await doFetch(url, {
|
|
7290
|
+
method: "POST",
|
|
7291
|
+
headers: buildHarnessCallbackHeaders(input.secret),
|
|
7292
|
+
body: JSON.stringify(body)
|
|
7293
|
+
});
|
|
7294
|
+
let parsed = null;
|
|
7295
|
+
try {
|
|
7296
|
+
parsed = await res.json();
|
|
7297
|
+
} catch {
|
|
7298
|
+
parsed = null;
|
|
7299
|
+
}
|
|
7300
|
+
return { ok: res.ok, status: res.status, body: parsed };
|
|
7301
|
+
}
|
|
7302
|
+
|
|
7303
|
+
// src/cron/cron-lock.ts
|
|
7304
|
+
import { closeSync as closeSync6, existsSync as existsSync27, openSync as openSync6, readFileSync as readFileSync11, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
7305
|
+
var STALE_LOCK_MS = 10 * 6e4;
|
|
7306
|
+
function readLockInfo(lockPath) {
|
|
7307
|
+
if (!existsSync27(lockPath)) return null;
|
|
7308
|
+
try {
|
|
7309
|
+
const parsed = JSON.parse(readFileSync11(lockPath, "utf8"));
|
|
7310
|
+
if (typeof parsed.pid === "number" && typeof parsed.at === "string") return parsed;
|
|
7311
|
+
} catch {
|
|
7312
|
+
return null;
|
|
7313
|
+
}
|
|
7314
|
+
return null;
|
|
7315
|
+
}
|
|
7316
|
+
function lockIsStale(lockPath) {
|
|
7317
|
+
const info = readLockInfo(lockPath);
|
|
7318
|
+
if (!info) return true;
|
|
7319
|
+
if (!isPidAlive(info.pid)) return true;
|
|
7320
|
+
const atMs = Date.parse(info.at);
|
|
7321
|
+
if (Number.isNaN(atMs)) return true;
|
|
7322
|
+
return Date.now() - atMs > STALE_LOCK_MS;
|
|
7323
|
+
}
|
|
7324
|
+
function tryAcquireCronTickLock(lockPath) {
|
|
7325
|
+
if (existsSync27(lockPath) && !lockIsStale(lockPath)) {
|
|
7326
|
+
const info = readLockInfo(lockPath);
|
|
7327
|
+
return {
|
|
7328
|
+
acquired: false,
|
|
7329
|
+
reason: info ? `held by pid ${info.pid}` : "held by another process"
|
|
7330
|
+
};
|
|
7331
|
+
}
|
|
7332
|
+
if (existsSync27(lockPath)) {
|
|
7333
|
+
try {
|
|
7334
|
+
unlinkSync2(lockPath);
|
|
7335
|
+
} catch {
|
|
7336
|
+
}
|
|
7337
|
+
}
|
|
7338
|
+
try {
|
|
7339
|
+
const fd = openSync6(lockPath, "wx");
|
|
7340
|
+
writeFileSync4(
|
|
7341
|
+
fd,
|
|
7342
|
+
JSON.stringify({ pid: process.pid, at: (/* @__PURE__ */ new Date()).toISOString() }),
|
|
7343
|
+
"utf8"
|
|
7344
|
+
);
|
|
7345
|
+
closeSync6(fd);
|
|
7346
|
+
return { acquired: true };
|
|
7347
|
+
} catch (err) {
|
|
7348
|
+
if (err.code === "EEXIST") {
|
|
7349
|
+
return { acquired: false, reason: "concurrent acquire" };
|
|
7350
|
+
}
|
|
7351
|
+
throw err;
|
|
7352
|
+
}
|
|
7353
|
+
}
|
|
7354
|
+
function releaseCronTickLock(lockPath) {
|
|
7355
|
+
try {
|
|
7356
|
+
unlinkSync2(lockPath);
|
|
7357
|
+
} catch {
|
|
7358
|
+
}
|
|
7359
|
+
}
|
|
7360
|
+
|
|
7361
|
+
// src/cron/cron-match.ts
|
|
7362
|
+
var CRON_RE = /^[\d*/,\-?LW#]+\s+[\d*/,\-?LW#]+\s+[\d*/,\-?LW#]+\s+[\d*/,\-?LW#]+\s+[\d*/,\-?LW#]+$/;
|
|
7363
|
+
function isCronExpression(value) {
|
|
7364
|
+
return CRON_RE.test(value.trim());
|
|
7365
|
+
}
|
|
7366
|
+
function parseList(field, min, max) {
|
|
7367
|
+
const out = /* @__PURE__ */ new Set();
|
|
7368
|
+
for (const part of field.split(",")) {
|
|
7369
|
+
const token = part.trim();
|
|
7370
|
+
if (!token) continue;
|
|
7371
|
+
if (token === "*") {
|
|
7372
|
+
for (let i = min; i <= max; i++) out.add(i);
|
|
7373
|
+
continue;
|
|
7374
|
+
}
|
|
7375
|
+
const stepMatch = /^(.+)\/(\d+)$/.exec(token);
|
|
7376
|
+
const base = stepMatch ? stepMatch[1] : token;
|
|
7377
|
+
const step = stepMatch ? Math.max(1, Number(stepMatch[2])) : 1;
|
|
7378
|
+
if (base === "*") {
|
|
7379
|
+
for (let i = min; i <= max; i += step) out.add(i);
|
|
7380
|
+
continue;
|
|
7381
|
+
}
|
|
7382
|
+
const rangeMatch = /^(\d+)-(\d+)$/.exec(base);
|
|
7383
|
+
if (rangeMatch) {
|
|
7384
|
+
const start = Math.max(min, Number(rangeMatch[1]));
|
|
7385
|
+
const end = Math.min(max, Number(rangeMatch[2]));
|
|
7386
|
+
for (let i = start; i <= end; i += step) out.add(i);
|
|
7387
|
+
continue;
|
|
7388
|
+
}
|
|
7389
|
+
const n = Number(base);
|
|
7390
|
+
if (Number.isInteger(n) && n >= min && n <= max) out.add(n);
|
|
7391
|
+
}
|
|
7392
|
+
return out;
|
|
7393
|
+
}
|
|
7394
|
+
function fieldMatches(field, value, min, max) {
|
|
7395
|
+
const trimmed = field.trim();
|
|
7396
|
+
if (trimmed === "*") return true;
|
|
7397
|
+
return parseList(trimmed, min, max).has(value);
|
|
7398
|
+
}
|
|
7399
|
+
function cronMatchesUtc(expr, at) {
|
|
7400
|
+
const parts = expr.trim().split(/\s+/);
|
|
7401
|
+
if (parts.length !== 5) return false;
|
|
7402
|
+
const [minF, hourF, domF, monF, dowF] = parts;
|
|
7403
|
+
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);
|
|
7404
|
+
}
|
|
7405
|
+
var MAX_LOOKAHEAD_MINUTES = 366 * 24 * 60;
|
|
7406
|
+
function truncateToUtcMinute(at) {
|
|
7407
|
+
return new Date(
|
|
7408
|
+
Date.UTC(
|
|
7409
|
+
at.getUTCFullYear(),
|
|
7410
|
+
at.getUTCMonth(),
|
|
7411
|
+
at.getUTCDate(),
|
|
7412
|
+
at.getUTCHours(),
|
|
7413
|
+
at.getUTCMinutes(),
|
|
7414
|
+
0,
|
|
7415
|
+
0
|
|
7416
|
+
)
|
|
7417
|
+
);
|
|
7418
|
+
}
|
|
7419
|
+
function computeNextCronFireUtc(expr, after) {
|
|
7420
|
+
if (!isCronExpression(expr)) return null;
|
|
7421
|
+
let cursor = truncateToUtcMinute(after);
|
|
7422
|
+
cursor = new Date(cursor.getTime() + 6e4);
|
|
7423
|
+
for (let i = 0; i < MAX_LOOKAHEAD_MINUTES; i++) {
|
|
7424
|
+
if (cronMatchesUtc(expr, cursor)) return cursor;
|
|
7425
|
+
cursor = new Date(cursor.getTime() + 6e4);
|
|
7426
|
+
}
|
|
7427
|
+
return null;
|
|
7428
|
+
}
|
|
7429
|
+
function computeInitialNextFire(spec, now) {
|
|
7430
|
+
if (spec.scheduleKind === "runAt" && spec.runAt) {
|
|
7431
|
+
const ms = Date.parse(spec.runAt);
|
|
7432
|
+
return Number.isNaN(ms) ? null : new Date(ms).toISOString();
|
|
7433
|
+
}
|
|
7434
|
+
if (spec.scheduleKind === "cron" && spec.cron) {
|
|
7435
|
+
const next = computeNextCronFireUtc(spec.cron.trim(), now);
|
|
7436
|
+
return next ? next.toISOString() : null;
|
|
7437
|
+
}
|
|
7438
|
+
return null;
|
|
7439
|
+
}
|
|
7440
|
+
function advanceRecurringNextFire(spec, fromInclusive) {
|
|
7441
|
+
if (spec.scheduleKind !== "cron" || !spec.cron?.trim()) return null;
|
|
7442
|
+
const next = computeNextCronFireUtc(spec.cron.trim(), fromInclusive);
|
|
7443
|
+
return next ? next.toISOString() : null;
|
|
7444
|
+
}
|
|
7445
|
+
|
|
7446
|
+
// src/cron/cron-store.ts
|
|
7447
|
+
import { promises as fs } from "node:fs";
|
|
7448
|
+
async function readFileIfExists(filePath) {
|
|
7449
|
+
try {
|
|
7450
|
+
return await fs.readFile(filePath, "utf8");
|
|
7451
|
+
} catch (err) {
|
|
7452
|
+
if (err.code === "ENOENT") return null;
|
|
7453
|
+
throw err;
|
|
7454
|
+
}
|
|
7455
|
+
}
|
|
7456
|
+
function parseCronStore(raw) {
|
|
7457
|
+
if (!raw) return [];
|
|
7458
|
+
try {
|
|
7459
|
+
const parsed = JSON.parse(raw);
|
|
7460
|
+
return Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
7461
|
+
} catch {
|
|
7462
|
+
return [];
|
|
7463
|
+
}
|
|
7464
|
+
}
|
|
7465
|
+
async function loadCronJobs(storePath = defaultKynverCronStorePath()) {
|
|
7466
|
+
const raw = await readFileIfExists(storePath);
|
|
7467
|
+
return parseCronStore(raw);
|
|
7468
|
+
}
|
|
7469
|
+
|
|
7470
|
+
// src/cron/cron-tick-state.ts
|
|
7471
|
+
import { randomBytes } from "node:crypto";
|
|
7472
|
+
import { promises as fs2 } from "node:fs";
|
|
7473
|
+
import path36 from "node:path";
|
|
7474
|
+
var EMPTY = { version: 1, jobs: {} };
|
|
7475
|
+
async function readFileIfExists2(filePath) {
|
|
7476
|
+
try {
|
|
7477
|
+
return await fs2.readFile(filePath, "utf8");
|
|
7478
|
+
} catch (err) {
|
|
7479
|
+
if (err.code === "ENOENT") return null;
|
|
7480
|
+
throw err;
|
|
7481
|
+
}
|
|
7482
|
+
}
|
|
7483
|
+
function parseCronTickState(raw) {
|
|
7484
|
+
if (!raw) return { ...EMPTY, jobs: { ...EMPTY.jobs } };
|
|
7485
|
+
try {
|
|
7486
|
+
const parsed = JSON.parse(raw);
|
|
7487
|
+
if (parsed?.version !== 1 || typeof parsed.jobs !== "object" || !parsed.jobs) {
|
|
7488
|
+
return { ...EMPTY, jobs: {} };
|
|
7489
|
+
}
|
|
7490
|
+
return parsed;
|
|
7491
|
+
} catch {
|
|
7492
|
+
return { ...EMPTY, jobs: {} };
|
|
7493
|
+
}
|
|
7494
|
+
}
|
|
7495
|
+
async function loadCronTickState(statePath) {
|
|
7496
|
+
const raw = await readFileIfExists2(statePath);
|
|
7497
|
+
return parseCronTickState(raw);
|
|
7498
|
+
}
|
|
7499
|
+
async function writeStateAtomic(statePath, state) {
|
|
7500
|
+
await fs2.mkdir(path36.dirname(statePath), { recursive: true });
|
|
7501
|
+
const suffix = randomBytes(6).toString("hex");
|
|
7502
|
+
const tmp = `${statePath}.tmp-${process.pid}-${Date.now()}-${suffix}`;
|
|
7503
|
+
await fs2.writeFile(tmp, `${JSON.stringify(state, null, 2)}
|
|
7504
|
+
`, "utf8");
|
|
7505
|
+
try {
|
|
7506
|
+
await fs2.rename(tmp, statePath);
|
|
7507
|
+
} catch (err) {
|
|
7508
|
+
const code = err.code;
|
|
7509
|
+
if (code !== "EPERM" && code !== "EEXIST" && code !== "EACCES") throw err;
|
|
7510
|
+
await fs2.unlink(tmp).catch(() => {
|
|
7511
|
+
});
|
|
7512
|
+
}
|
|
7513
|
+
}
|
|
7514
|
+
async function saveCronTickState(statePath, state) {
|
|
7515
|
+
await writeStateAtomic(statePath, state);
|
|
7516
|
+
}
|
|
7517
|
+
function getOrCreateJobState(state, providerScheduleId) {
|
|
7518
|
+
const existing = state.jobs[providerScheduleId];
|
|
7519
|
+
if (existing) return existing;
|
|
7520
|
+
const created = {
|
|
7521
|
+
providerScheduleId,
|
|
7522
|
+
nextFireAt: null,
|
|
7523
|
+
lastFiredAt: null,
|
|
7524
|
+
lastAttemptAt: null,
|
|
7525
|
+
consecutiveFailures: 0,
|
|
7526
|
+
completedAt: null,
|
|
7527
|
+
inflightUntil: null
|
|
7528
|
+
};
|
|
7529
|
+
state.jobs[providerScheduleId] = created;
|
|
7530
|
+
return created;
|
|
7531
|
+
}
|
|
7532
|
+
|
|
7533
|
+
// src/cron/cron-tick.ts
|
|
7534
|
+
function isInflight(job, nowMs) {
|
|
7535
|
+
if (!job.inflightUntil) return false;
|
|
7536
|
+
const until = Date.parse(job.inflightUntil);
|
|
7537
|
+
return !Number.isNaN(until) && until > nowMs;
|
|
7538
|
+
}
|
|
7539
|
+
function isCompleted(job) {
|
|
7540
|
+
return Boolean(job.completedAt);
|
|
7541
|
+
}
|
|
7542
|
+
function backoffReady(job, env, nowMs) {
|
|
7543
|
+
if (job.consecutiveFailures === 0) return true;
|
|
7544
|
+
if (job.consecutiveFailures > env.maxRetries) return false;
|
|
7545
|
+
if (!job.lastAttemptAt) return true;
|
|
7546
|
+
const last = Date.parse(job.lastAttemptAt);
|
|
7547
|
+
if (Number.isNaN(last)) return true;
|
|
7548
|
+
return nowMs - last >= env.retryBackoffMs;
|
|
7549
|
+
}
|
|
7550
|
+
function ensureNextFire(entry, job, now) {
|
|
7551
|
+
if (job.nextFireAt) return job.nextFireAt;
|
|
7552
|
+
const initial = computeInitialNextFire(entry.spec, now);
|
|
7553
|
+
job.nextFireAt = initial;
|
|
7554
|
+
return initial;
|
|
7555
|
+
}
|
|
7556
|
+
function isDue(entry, job, nowMs, env) {
|
|
7557
|
+
if (entry.paused || isCompleted(job) || isInflight(job, nowMs)) return false;
|
|
7558
|
+
if (!backoffReady(job, env, nowMs)) return false;
|
|
7559
|
+
const nextMs = job.nextFireAt ? Date.parse(job.nextFireAt) : NaN;
|
|
7560
|
+
if (Number.isNaN(nextMs)) return false;
|
|
7561
|
+
return nowMs >= nextMs;
|
|
7562
|
+
}
|
|
7563
|
+
function advanceRecurringBeforeFire(entry, job, now) {
|
|
7564
|
+
if (entry.spec.scheduleKind !== "cron") return;
|
|
7565
|
+
job.nextFireAt = advanceRecurringNextFire(entry.spec, now);
|
|
7566
|
+
}
|
|
7567
|
+
async function runKynverCronTick(opts = {}) {
|
|
7568
|
+
const env = opts.env ?? resolveKynverCronEnv();
|
|
7569
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
7570
|
+
const nowMs = now.getTime();
|
|
7571
|
+
if (!env.tickEnabled) {
|
|
7572
|
+
return { enabled: false, skipped: "tick_disabled", scanned: 0, due: 0, fired: 0, skippedJobs: 0, errors: 0 };
|
|
7573
|
+
}
|
|
7574
|
+
if (!env.fireBaseUrl || !env.secret) {
|
|
7575
|
+
return {
|
|
7576
|
+
enabled: true,
|
|
7577
|
+
skipped: "missing_fire_credentials",
|
|
7578
|
+
scanned: 0,
|
|
7579
|
+
due: 0,
|
|
7580
|
+
fired: 0,
|
|
7581
|
+
skippedJobs: 0,
|
|
7582
|
+
errors: 0
|
|
7583
|
+
};
|
|
7584
|
+
}
|
|
7585
|
+
const lock = tryAcquireCronTickLock(env.lockPath);
|
|
7586
|
+
if (!lock.acquired) {
|
|
7587
|
+
return {
|
|
7588
|
+
enabled: true,
|
|
7589
|
+
skipped: lock.reason ?? "lock_not_acquired",
|
|
7590
|
+
scanned: 0,
|
|
7591
|
+
due: 0,
|
|
7592
|
+
fired: 0,
|
|
7593
|
+
skippedJobs: 0,
|
|
7594
|
+
errors: 0,
|
|
7595
|
+
lockHeld: true
|
|
7596
|
+
};
|
|
7597
|
+
}
|
|
7598
|
+
try {
|
|
7599
|
+
const entries = await loadCronJobs(env.storePath);
|
|
7600
|
+
const filtered = opts.agentOsIdFilter ? entries.filter((e) => e.spec.target.agentOsId === opts.agentOsIdFilter) : entries;
|
|
7601
|
+
const state = await loadCronTickState(env.statePath);
|
|
7602
|
+
const dueEntries = [];
|
|
7603
|
+
for (const entry of filtered) {
|
|
7604
|
+
const job = getOrCreateJobState(state, entry.providerScheduleId);
|
|
7605
|
+
ensureNextFire(entry, job, now);
|
|
7606
|
+
if (isDue(entry, job, nowMs, env)) {
|
|
7607
|
+
dueEntries.push({ entry, job });
|
|
7608
|
+
}
|
|
7609
|
+
}
|
|
7610
|
+
dueEntries.sort((a, b) => {
|
|
7611
|
+
const aMs = Date.parse(a.job.nextFireAt ?? "") || 0;
|
|
7612
|
+
const bMs = Date.parse(b.job.nextFireAt ?? "") || 0;
|
|
7613
|
+
return aMs - bMs;
|
|
7614
|
+
});
|
|
7615
|
+
let fired = 0;
|
|
7616
|
+
let errors = 0;
|
|
7617
|
+
let skippedJobs = 0;
|
|
7618
|
+
let catchUpBudget = env.maxCatchUpPerTick;
|
|
7619
|
+
for (const { entry, job } of dueEntries) {
|
|
7620
|
+
if (env.missedRunPolicy === "skip" && entry.spec.scheduleKind === "cron") {
|
|
7621
|
+
const next = Date.parse(job.nextFireAt ?? "");
|
|
7622
|
+
if (!Number.isNaN(next) && next < nowMs - env.tickIntervalMs * 2) {
|
|
7623
|
+
job.nextFireAt = advanceRecurringNextFire(entry.spec, now);
|
|
7624
|
+
skippedJobs++;
|
|
7625
|
+
continue;
|
|
7626
|
+
}
|
|
7627
|
+
}
|
|
7628
|
+
if (catchUpBudget <= 0) {
|
|
7629
|
+
skippedJobs++;
|
|
7630
|
+
continue;
|
|
7631
|
+
}
|
|
7632
|
+
job.inflightUntil = new Date(nowMs + env.inflightLeaseMs).toISOString();
|
|
7633
|
+
job.lastAttemptAt = now.toISOString();
|
|
7634
|
+
advanceRecurringBeforeFire(entry, job, now);
|
|
7635
|
+
try {
|
|
7636
|
+
const result = await fireKynverCronJob({
|
|
7637
|
+
entry,
|
|
7638
|
+
baseUrl: env.fireBaseUrl,
|
|
7639
|
+
secret: env.secret,
|
|
7640
|
+
fetchFn: opts.fetchFn
|
|
7641
|
+
});
|
|
7642
|
+
job.inflightUntil = null;
|
|
7643
|
+
if (result.ok) {
|
|
7644
|
+
job.lastFiredAt = now.toISOString();
|
|
7645
|
+
job.consecutiveFailures = 0;
|
|
7646
|
+
if (entry.spec.scheduleKind === "runAt") {
|
|
7647
|
+
job.completedAt = now.toISOString();
|
|
7648
|
+
job.nextFireAt = null;
|
|
7649
|
+
}
|
|
7650
|
+
fired++;
|
|
7651
|
+
catchUpBudget--;
|
|
7652
|
+
} else {
|
|
7653
|
+
job.consecutiveFailures += 1;
|
|
7654
|
+
errors++;
|
|
7655
|
+
}
|
|
7656
|
+
} catch {
|
|
7657
|
+
job.inflightUntil = null;
|
|
7658
|
+
job.consecutiveFailures += 1;
|
|
7659
|
+
errors++;
|
|
7660
|
+
}
|
|
7661
|
+
}
|
|
7662
|
+
await saveCronTickState(env.statePath, state);
|
|
7663
|
+
return {
|
|
7664
|
+
enabled: true,
|
|
7665
|
+
scanned: filtered.length,
|
|
7666
|
+
due: dueEntries.length,
|
|
7667
|
+
fired,
|
|
7668
|
+
skippedJobs,
|
|
7669
|
+
errors
|
|
7670
|
+
};
|
|
7671
|
+
} finally {
|
|
7672
|
+
releaseCronTickLock(env.lockPath);
|
|
7673
|
+
}
|
|
7674
|
+
}
|
|
7675
|
+
|
|
7010
7676
|
// src/pipeline-tick.ts
|
|
7011
|
-
import
|
|
7677
|
+
import path52 from "node:path";
|
|
7012
7678
|
|
|
7013
7679
|
// src/pipeline-dispatch.ts
|
|
7014
7680
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -7074,30 +7740,67 @@ async function runPipelineDispatch(args, slots) {
|
|
|
7074
7740
|
};
|
|
7075
7741
|
}
|
|
7076
7742
|
|
|
7743
|
+
// src/pipeline-exact-targets.ts
|
|
7744
|
+
function operatorExactTargetTaskIds(operatorTick) {
|
|
7745
|
+
if (!operatorTick || typeof operatorTick !== "object") return [];
|
|
7746
|
+
const body = operatorTick;
|
|
7747
|
+
const raw = body.response?.dispatch?.exactTargetTaskIds;
|
|
7748
|
+
if (!Array.isArray(raw)) return [];
|
|
7749
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7750
|
+
const out = [];
|
|
7751
|
+
for (const value of raw) {
|
|
7752
|
+
if (typeof value !== "string") continue;
|
|
7753
|
+
const id = value.trim();
|
|
7754
|
+
if (!id || seen.has(id)) continue;
|
|
7755
|
+
seen.add(id);
|
|
7756
|
+
out.push(id);
|
|
7757
|
+
}
|
|
7758
|
+
return out;
|
|
7759
|
+
}
|
|
7760
|
+
|
|
7077
7761
|
// src/pipeline-max-starts.ts
|
|
7078
7762
|
function operatorDispatchFromTick(operatorTick) {
|
|
7079
7763
|
const body = operatorTick;
|
|
7080
7764
|
const dispatch = body.response?.dispatch;
|
|
7081
7765
|
return dispatch && typeof dispatch === "object" ? dispatch : null;
|
|
7082
7766
|
}
|
|
7767
|
+
function nonNegativeInt(value) {
|
|
7768
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
7769
|
+
return Math.max(0, Math.floor(value));
|
|
7770
|
+
}
|
|
7083
7771
|
function resolvePipelineMaxStarts(resourceGate, operatorTick) {
|
|
7084
7772
|
const dispatch = operatorDispatchFromTick(operatorTick);
|
|
7085
|
-
const advised =
|
|
7773
|
+
const advised = nonNegativeInt(dispatch?.recommendedMaxStarts);
|
|
7774
|
+
const actionableReady = nonNegativeInt(dispatch?.actionableReady);
|
|
7775
|
+
const queuedTasks = nonNegativeInt(dispatch?.queuedTasks);
|
|
7776
|
+
const boardAdvancedThisTick = nonNegativeInt(dispatch?.boardAdvancedThisTick) ?? 0;
|
|
7777
|
+
const leaseReapedThisTick = nonNegativeInt(dispatch?.leaseReapedThisTick) ?? 0;
|
|
7778
|
+
const hygieneAdvanced = boardAdvancedThisTick + leaseReapedThisTick;
|
|
7779
|
+
const readyFloor = actionableReady ?? queuedTasks;
|
|
7086
7780
|
let maxStarts = resourceGate.slotsAvailable;
|
|
7087
|
-
if (
|
|
7781
|
+
if (readyFloor !== null) {
|
|
7782
|
+
maxStarts = Math.min(maxStarts, readyFloor);
|
|
7783
|
+
} else if (advised !== null) {
|
|
7088
7784
|
maxStarts = Math.min(maxStarts, advised);
|
|
7089
7785
|
}
|
|
7090
|
-
|
|
7091
|
-
|
|
7092
|
-
|
|
7093
|
-
|
|
7786
|
+
if (readyFloor === null && advised !== null) {
|
|
7787
|
+
maxStarts = Math.max(maxStarts, Math.min(resourceGate.slotsAvailable, advised));
|
|
7788
|
+
}
|
|
7789
|
+
const underutilized = dispatch?.underutilized === true || (readyFloor ?? 0) > 0 && resourceGate.slotsAvailable > 0 && resourceGate.maxConcurrentWorkers > 0 && resourceGate.activeWorkers < resourceGate.maxConcurrentWorkers;
|
|
7790
|
+
if (resourceGate.slotsAvailable > 0 && maxStarts === 0 && (underutilized || hygieneAdvanced > 0)) {
|
|
7791
|
+
const ready = readyFloor ?? (hygieneAdvanced > 0 ? hygieneAdvanced : 1);
|
|
7094
7792
|
maxStarts = Math.min(resourceGate.slotsAvailable, Math.max(1, ready));
|
|
7095
7793
|
}
|
|
7794
|
+
const nonDispatchableReady = queuedTasks !== null && actionableReady !== null ? Math.max(0, queuedTasks - actionableReady) : null;
|
|
7096
7795
|
return {
|
|
7097
7796
|
maxStarts: Math.max(0, maxStarts),
|
|
7098
7797
|
underutilized,
|
|
7099
7798
|
advisedStarts: advised,
|
|
7100
|
-
|
|
7799
|
+
actionableReady,
|
|
7800
|
+
queuedTasks,
|
|
7801
|
+
nonDispatchableReady,
|
|
7802
|
+
boardAdvancedThisTick,
|
|
7803
|
+
leaseReapedThisTick
|
|
7101
7804
|
};
|
|
7102
7805
|
}
|
|
7103
7806
|
|
|
@@ -7141,7 +7844,7 @@ function buildBoxResourceSnapshotFromGate(gate, input = {}) {
|
|
|
7141
7844
|
}
|
|
7142
7845
|
|
|
7143
7846
|
// src/plan-progress-daemon-sync.ts
|
|
7144
|
-
import
|
|
7847
|
+
import path37 from "node:path";
|
|
7145
7848
|
|
|
7146
7849
|
// src/plan-progress-sync.ts
|
|
7147
7850
|
async function syncPlanProgress(args) {
|
|
@@ -7165,7 +7868,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
7165
7868
|
const outcomes = [];
|
|
7166
7869
|
for (const name of Object.keys(run.workers || {})) {
|
|
7167
7870
|
const worker = readJson(
|
|
7168
|
-
|
|
7871
|
+
path37.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
7169
7872
|
void 0
|
|
7170
7873
|
);
|
|
7171
7874
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -7214,10 +7917,10 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
7214
7917
|
}
|
|
7215
7918
|
|
|
7216
7919
|
// src/cleanup.ts
|
|
7217
|
-
import
|
|
7920
|
+
import path50 from "node:path";
|
|
7218
7921
|
|
|
7219
7922
|
// src/cleanup-guards.ts
|
|
7220
|
-
import
|
|
7923
|
+
import path38 from "node:path";
|
|
7221
7924
|
|
|
7222
7925
|
// src/cleanup-run-liveness.ts
|
|
7223
7926
|
function isWorkerProcessLive(indexed) {
|
|
@@ -7341,7 +8044,7 @@ function skipWorktreeRemoval(input) {
|
|
|
7341
8044
|
function skipDependencyCacheRemoval(input) {
|
|
7342
8045
|
const { indexed, nodeModulesAgeMs, ageMs, worktreePath, activeWorktreePaths, diskPressure } = input;
|
|
7343
8046
|
if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
|
|
7344
|
-
if (activeWorktreePaths.has(
|
|
8047
|
+
if (activeWorktreePaths.has(path38.resolve(worktreePath))) return "active_worker";
|
|
7345
8048
|
if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
|
|
7346
8049
|
if (indexed && hasUnrestorableWorktreeChanges(indexed.status)) return "dirty_worktree";
|
|
7347
8050
|
return null;
|
|
@@ -7368,11 +8071,11 @@ var LIVE_SKIP_REASONS = /* @__PURE__ */ new Set([
|
|
|
7368
8071
|
function collectPreservedLivePaths(actions, skips) {
|
|
7369
8072
|
const out = [];
|
|
7370
8073
|
const seen = /* @__PURE__ */ new Set();
|
|
7371
|
-
const push = (
|
|
7372
|
-
const key = `${
|
|
8074
|
+
const push = (path62, reason, detail) => {
|
|
8075
|
+
const key = `${path62}\0${reason}`;
|
|
7373
8076
|
if (seen.has(key) || out.length >= MAX_PRESERVED_LIVE_PATH_SAMPLES) return;
|
|
7374
8077
|
seen.add(key);
|
|
7375
|
-
out.push({ path:
|
|
8078
|
+
out.push({ path: path62, reason, ...detail ? { detail } : {} });
|
|
7376
8079
|
};
|
|
7377
8080
|
for (const skip2 of skips) {
|
|
7378
8081
|
if (!LIVE_SKIP_REASONS.has(skip2.reason)) continue;
|
|
@@ -7387,11 +8090,11 @@ function collectPreservedLivePaths(actions, skips) {
|
|
|
7387
8090
|
}
|
|
7388
8091
|
|
|
7389
8092
|
// src/cleanup-run-directory.ts
|
|
7390
|
-
import { existsSync as
|
|
7391
|
-
import
|
|
8093
|
+
import { existsSync as existsSync28, readdirSync as readdirSync7, statSync as statSync5 } from "node:fs";
|
|
8094
|
+
import path40 from "node:path";
|
|
7392
8095
|
|
|
7393
8096
|
// src/cleanup-active-worktrees.ts
|
|
7394
|
-
import
|
|
8097
|
+
import path39 from "node:path";
|
|
7395
8098
|
function isActiveHarnessWorker2(worker, runBase, runBaseCommit) {
|
|
7396
8099
|
const status = computeWorkerStatus(worker, { base: runBase, baseCommit: runBaseCommit });
|
|
7397
8100
|
return status.alive && !status.finalResult && status.attention.state !== "done";
|
|
@@ -7404,17 +8107,20 @@ function collectActiveWorktreeGuards(harnessRoots) {
|
|
|
7404
8107
|
let runHasLive = false;
|
|
7405
8108
|
for (const name of Object.keys(run.workers || {})) {
|
|
7406
8109
|
const worker = readJson(
|
|
7407
|
-
|
|
8110
|
+
path39.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
|
|
7408
8111
|
void 0
|
|
7409
8112
|
);
|
|
7410
8113
|
if (!worker?.worktreePath) continue;
|
|
7411
|
-
const worktreePath =
|
|
8114
|
+
const worktreePath = path39.resolve(worker.worktreePath);
|
|
7412
8115
|
if (!isActiveHarnessWorker2(worker, run.base, run.baseCommit)) continue;
|
|
7413
8116
|
runHasLive = true;
|
|
7414
8117
|
activeWorktreePaths.add(worktreePath);
|
|
7415
8118
|
}
|
|
7416
8119
|
if (runHasLive) liveRunKeys.add(`${harnessRoot}\0${run.id}`);
|
|
7417
8120
|
}
|
|
8121
|
+
for (const key of collectFilesystemLiveRunKeys(harnessRoot)) {
|
|
8122
|
+
liveRunKeys.add(key);
|
|
8123
|
+
}
|
|
7418
8124
|
}
|
|
7419
8125
|
return { activeWorktreePaths, liveRunKeys };
|
|
7420
8126
|
}
|
|
@@ -7426,20 +8132,20 @@ function isWorktreeOnLiveRun(worktreePath, harnessRoot, runId, liveRunKeys) {
|
|
|
7426
8132
|
// src/cleanup-run-directory.ts
|
|
7427
8133
|
function pathAgeMs(target, now) {
|
|
7428
8134
|
try {
|
|
7429
|
-
const mtime =
|
|
8135
|
+
const mtime = statSync5(target).mtimeMs;
|
|
7430
8136
|
return Math.max(0, now - mtime);
|
|
7431
8137
|
} catch {
|
|
7432
8138
|
return 0;
|
|
7433
8139
|
}
|
|
7434
8140
|
}
|
|
7435
8141
|
function loadRunStatus(harnessRoot, runId) {
|
|
7436
|
-
const runPath =
|
|
7437
|
-
if (!
|
|
8142
|
+
const runPath = path40.join(harnessRoot, "runs", runId, "run.json");
|
|
8143
|
+
if (!existsSync28(runPath)) return null;
|
|
7438
8144
|
return readJson(runPath, null);
|
|
7439
8145
|
}
|
|
7440
8146
|
function runDirectoryIsEmpty(runPath) {
|
|
7441
8147
|
try {
|
|
7442
|
-
const entries =
|
|
8148
|
+
const entries = readdirSync7(runPath);
|
|
7443
8149
|
return entries.length === 0;
|
|
7444
8150
|
} catch {
|
|
7445
8151
|
return false;
|
|
@@ -7457,11 +8163,11 @@ function skipRunDirectoryRemoval(input) {
|
|
|
7457
8163
|
return null;
|
|
7458
8164
|
}
|
|
7459
8165
|
function scanStaleRunDirectoryCandidates(opts) {
|
|
7460
|
-
if (!
|
|
8166
|
+
if (!existsSync28(opts.worktreesDir)) return [];
|
|
7461
8167
|
const candidates = [];
|
|
7462
8168
|
let entries;
|
|
7463
8169
|
try {
|
|
7464
|
-
entries =
|
|
8170
|
+
entries = readdirSync7(opts.worktreesDir, { withFileTypes: true });
|
|
7465
8171
|
} catch {
|
|
7466
8172
|
return [];
|
|
7467
8173
|
}
|
|
@@ -7469,7 +8175,7 @@ function scanStaleRunDirectoryCandidates(opts) {
|
|
|
7469
8175
|
if (!runEntry.isDirectory()) continue;
|
|
7470
8176
|
const runId = runEntry.name;
|
|
7471
8177
|
if (opts.runIdFilter && runId !== opts.runIdFilter) continue;
|
|
7472
|
-
const runPath =
|
|
8178
|
+
const runPath = path40.join(opts.worktreesDir, runId);
|
|
7473
8179
|
if (!runDirectoryIsEmpty(runPath)) continue;
|
|
7474
8180
|
candidates.push({
|
|
7475
8181
|
kind: "remove_run_directory",
|
|
@@ -7484,14 +8190,14 @@ function scanStaleRunDirectoryCandidates(opts) {
|
|
|
7484
8190
|
}
|
|
7485
8191
|
|
|
7486
8192
|
// src/cleanup-execute.ts
|
|
7487
|
-
import { existsSync as
|
|
7488
|
-
import
|
|
8193
|
+
import { existsSync as existsSync30, rmSync as rmSync3 } from "node:fs";
|
|
8194
|
+
import path42 from "node:path";
|
|
7489
8195
|
|
|
7490
8196
|
// src/cleanup-dir-size.ts
|
|
7491
|
-
import { existsSync as
|
|
7492
|
-
import
|
|
8197
|
+
import { existsSync as existsSync29, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
|
|
8198
|
+
import path41 from "node:path";
|
|
7493
8199
|
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
7494
|
-
if (!
|
|
8200
|
+
if (!existsSync29(root)) return 0;
|
|
7495
8201
|
let total = 0;
|
|
7496
8202
|
let seen = 0;
|
|
7497
8203
|
const stack = [root];
|
|
@@ -7499,16 +8205,16 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
7499
8205
|
const current = stack.pop();
|
|
7500
8206
|
let entries;
|
|
7501
8207
|
try {
|
|
7502
|
-
entries =
|
|
8208
|
+
entries = readdirSync8(current);
|
|
7503
8209
|
} catch {
|
|
7504
8210
|
continue;
|
|
7505
8211
|
}
|
|
7506
8212
|
for (const name of entries) {
|
|
7507
8213
|
if (seen++ > maxEntries) return null;
|
|
7508
|
-
const full =
|
|
8214
|
+
const full = path41.join(current, name);
|
|
7509
8215
|
let st;
|
|
7510
8216
|
try {
|
|
7511
|
-
st =
|
|
8217
|
+
st = statSync6(full);
|
|
7512
8218
|
} catch {
|
|
7513
8219
|
continue;
|
|
7514
8220
|
}
|
|
@@ -7520,8 +8226,20 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
7520
8226
|
}
|
|
7521
8227
|
|
|
7522
8228
|
// src/cleanup-execute.ts
|
|
8229
|
+
function skipRunMetadataRemoval(candidate) {
|
|
8230
|
+
const harnessRoot = candidate.harnessRoot;
|
|
8231
|
+
if (!harnessRoot || !isHarnessRunMetadataPath(candidate.path, harnessRoot)) return null;
|
|
8232
|
+
return {
|
|
8233
|
+
...candidate,
|
|
8234
|
+
executed: false,
|
|
8235
|
+
skipped: true,
|
|
8236
|
+
skipReason: "run_metadata_protected"
|
|
8237
|
+
};
|
|
8238
|
+
}
|
|
7523
8239
|
function removeDependencyCache(candidate, execute) {
|
|
7524
|
-
|
|
8240
|
+
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
8241
|
+
if (metadataSkip) return metadataSkip;
|
|
8242
|
+
if (!existsSync30(candidate.path)) {
|
|
7525
8243
|
return {
|
|
7526
8244
|
...candidate,
|
|
7527
8245
|
executed: false,
|
|
@@ -7561,7 +8279,9 @@ function removeBuildCache(candidate, execute) {
|
|
|
7561
8279
|
return removeDependencyCache(candidate, execute);
|
|
7562
8280
|
}
|
|
7563
8281
|
function removeRunDirectory(candidate, execute) {
|
|
7564
|
-
|
|
8282
|
+
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
8283
|
+
if (metadataSkip) return metadataSkip;
|
|
8284
|
+
if (!existsSync30(candidate.path)) {
|
|
7565
8285
|
return {
|
|
7566
8286
|
...candidate,
|
|
7567
8287
|
executed: false,
|
|
@@ -7592,7 +8312,9 @@ function removeRunDirectory(candidate, execute) {
|
|
|
7592
8312
|
}
|
|
7593
8313
|
}
|
|
7594
8314
|
function removeWorktree(candidate, execute) {
|
|
7595
|
-
|
|
8315
|
+
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
8316
|
+
if (metadataSkip) return metadataSkip;
|
|
8317
|
+
if (!existsSync30(candidate.path)) {
|
|
7596
8318
|
return {
|
|
7597
8319
|
...candidate,
|
|
7598
8320
|
executed: false,
|
|
@@ -7609,7 +8331,7 @@ function removeWorktree(candidate, execute) {
|
|
|
7609
8331
|
if (repo) {
|
|
7610
8332
|
git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
|
|
7611
8333
|
}
|
|
7612
|
-
if (
|
|
8334
|
+
if (existsSync30(candidate.path)) {
|
|
7613
8335
|
rmSync3(candidate.path, { recursive: true, force: true });
|
|
7614
8336
|
}
|
|
7615
8337
|
return {
|
|
@@ -7629,15 +8351,15 @@ function removeWorktree(candidate, execute) {
|
|
|
7629
8351
|
}
|
|
7630
8352
|
}
|
|
7631
8353
|
function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
|
|
7632
|
-
const resolved =
|
|
7633
|
-
const suffix = `${
|
|
8354
|
+
const resolved = path42.resolve(targetPath);
|
|
8355
|
+
const suffix = `${path42.sep}${cacheDirName}`;
|
|
7634
8356
|
const cachePath = resolved.endsWith(suffix) ? resolved : null;
|
|
7635
8357
|
if (!cachePath) return "path_outside_harness";
|
|
7636
|
-
const rel =
|
|
7637
|
-
if (rel.startsWith("..") ||
|
|
7638
|
-
const parts = rel.split(
|
|
8358
|
+
const rel = path42.relative(worktreesDir, cachePath);
|
|
8359
|
+
if (rel.startsWith("..") || path42.isAbsolute(rel)) return "path_outside_harness";
|
|
8360
|
+
const parts = rel.split(path42.sep);
|
|
7639
8361
|
if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
|
|
7640
|
-
if (!resolved.startsWith(
|
|
8362
|
+
if (!resolved.startsWith(path42.resolve(harnessRoot))) return "path_outside_harness";
|
|
7641
8363
|
return null;
|
|
7642
8364
|
}
|
|
7643
8365
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
@@ -7647,37 +8369,37 @@ function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
|
7647
8369
|
return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
|
|
7648
8370
|
}
|
|
7649
8371
|
function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
7650
|
-
const resolved =
|
|
7651
|
-
const relToWt =
|
|
7652
|
-
if (relToWt.startsWith("..") ||
|
|
7653
|
-
const parts = relToWt.split(
|
|
8372
|
+
const resolved = path42.resolve(targetPath);
|
|
8373
|
+
const relToWt = path42.relative(worktreesDir, resolved);
|
|
8374
|
+
if (relToWt.startsWith("..") || path42.isAbsolute(relToWt)) return "path_outside_harness";
|
|
8375
|
+
const parts = relToWt.split(path42.sep);
|
|
7654
8376
|
if (parts.length < 3) return "path_outside_harness";
|
|
7655
|
-
if (!resolved.startsWith(
|
|
8377
|
+
if (!resolved.startsWith(path42.resolve(harnessRoot))) return "path_outside_harness";
|
|
7656
8378
|
return null;
|
|
7657
8379
|
}
|
|
7658
8380
|
|
|
7659
8381
|
// src/cleanup-scan.ts
|
|
7660
|
-
import { existsSync as
|
|
7661
|
-
import
|
|
8382
|
+
import { existsSync as existsSync31, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
|
|
8383
|
+
import path43 from "node:path";
|
|
7662
8384
|
function pathAgeMs2(target, now) {
|
|
7663
8385
|
try {
|
|
7664
|
-
const mtime =
|
|
8386
|
+
const mtime = statSync7(target).mtimeMs;
|
|
7665
8387
|
return Math.max(0, now - mtime);
|
|
7666
8388
|
} catch {
|
|
7667
8389
|
return 0;
|
|
7668
8390
|
}
|
|
7669
8391
|
}
|
|
7670
8392
|
function isPathInside(child, parent) {
|
|
7671
|
-
const rel =
|
|
7672
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
8393
|
+
const rel = path43.relative(parent, child);
|
|
8394
|
+
return rel === "" || !rel.startsWith("..") && !path43.isAbsolute(rel);
|
|
7673
8395
|
}
|
|
7674
8396
|
function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
|
|
7675
8397
|
const out = [];
|
|
7676
8398
|
for (const rel of HARNESS_BUILD_CACHE_RELATIVE_PATHS) {
|
|
7677
8399
|
if (rel === ".next") continue;
|
|
7678
|
-
const target =
|
|
7679
|
-
if (!
|
|
7680
|
-
const resolved =
|
|
8400
|
+
const target = path43.join(worktreePath, rel);
|
|
8401
|
+
if (!existsSync31(target)) continue;
|
|
8402
|
+
const resolved = path43.resolve(target);
|
|
7681
8403
|
if (seen.has(resolved)) continue;
|
|
7682
8404
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
7683
8405
|
seen.add(resolved);
|
|
@@ -7706,13 +8428,13 @@ function scanBuildCacheCandidates(opts) {
|
|
|
7706
8428
|
})
|
|
7707
8429
|
);
|
|
7708
8430
|
}
|
|
7709
|
-
if (!opts.includeOrphans || !
|
|
7710
|
-
for (const runEntry of
|
|
8431
|
+
if (!opts.includeOrphans || !existsSync31(opts.worktreesDir)) return candidates;
|
|
8432
|
+
for (const runEntry of readdirSync9(opts.worktreesDir, { withFileTypes: true })) {
|
|
7711
8433
|
if (!runEntry.isDirectory()) continue;
|
|
7712
|
-
const runPath =
|
|
7713
|
-
for (const workerEntry of
|
|
8434
|
+
const runPath = path43.join(opts.worktreesDir, runEntry.name);
|
|
8435
|
+
for (const workerEntry of readdirSync9(runPath, { withFileTypes: true })) {
|
|
7714
8436
|
if (!workerEntry.isDirectory()) continue;
|
|
7715
|
-
const worktreePath =
|
|
8437
|
+
const worktreePath = path43.join(runPath, workerEntry.name);
|
|
7716
8438
|
candidates.push(
|
|
7717
8439
|
...collectBuildCacheForWorktree(worktreePath, opts, seen, {
|
|
7718
8440
|
runId: runEntry.name,
|
|
@@ -7733,7 +8455,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
7733
8455
|
for (const entry of opts.index.values()) {
|
|
7734
8456
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
7735
8457
|
const resolved = entry.worktreePath;
|
|
7736
|
-
if (!
|
|
8458
|
+
if (!existsSync31(resolved)) continue;
|
|
7737
8459
|
if (seen.has(resolved)) continue;
|
|
7738
8460
|
seen.add(resolved);
|
|
7739
8461
|
candidates.push({
|
|
@@ -7747,24 +8469,24 @@ function scanWorktreeCandidates(opts) {
|
|
|
7747
8469
|
});
|
|
7748
8470
|
}
|
|
7749
8471
|
}
|
|
7750
|
-
if (!orphanEnabled || !
|
|
8472
|
+
if (!orphanEnabled || !existsSync31(opts.worktreesDir)) return candidates;
|
|
7751
8473
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
7752
8474
|
for (const entry of opts.index.values()) {
|
|
7753
|
-
indexedPaths.add(
|
|
8475
|
+
indexedPaths.add(path43.resolve(entry.worktreePath));
|
|
7754
8476
|
}
|
|
7755
|
-
for (const runEntry of
|
|
8477
|
+
for (const runEntry of readdirSync9(opts.worktreesDir, { withFileTypes: true })) {
|
|
7756
8478
|
if (!runEntry.isDirectory()) continue;
|
|
7757
8479
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
7758
|
-
const runPath =
|
|
8480
|
+
const runPath = path43.join(opts.worktreesDir, runEntry.name);
|
|
7759
8481
|
let workerEntries;
|
|
7760
8482
|
try {
|
|
7761
|
-
workerEntries =
|
|
8483
|
+
workerEntries = readdirSync9(runPath, { withFileTypes: true });
|
|
7762
8484
|
} catch {
|
|
7763
8485
|
continue;
|
|
7764
8486
|
}
|
|
7765
8487
|
for (const workerEntry of workerEntries) {
|
|
7766
8488
|
if (!workerEntry.isDirectory()) continue;
|
|
7767
|
-
const worktreePath =
|
|
8489
|
+
const worktreePath = path43.resolve(path43.join(runPath, workerEntry.name));
|
|
7768
8490
|
if (seen.has(worktreePath)) continue;
|
|
7769
8491
|
if (indexedPaths.has(worktreePath)) continue;
|
|
7770
8492
|
if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
|
|
@@ -7783,27 +8505,27 @@ function scanWorktreeCandidates(opts) {
|
|
|
7783
8505
|
}
|
|
7784
8506
|
|
|
7785
8507
|
// src/cleanup-dependency-scan.ts
|
|
7786
|
-
import { existsSync as
|
|
7787
|
-
import
|
|
8508
|
+
import { existsSync as existsSync32, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
|
|
8509
|
+
import path44 from "node:path";
|
|
7788
8510
|
var DEPENDENCY_CACHE_DIRS = [
|
|
7789
8511
|
{ dirName: "node_modules", kind: "remove_node_modules" },
|
|
7790
8512
|
{ dirName: ".next", kind: "remove_next_cache" }
|
|
7791
8513
|
];
|
|
7792
8514
|
function pathAgeMs3(target, now) {
|
|
7793
8515
|
try {
|
|
7794
|
-
const mtime =
|
|
8516
|
+
const mtime = statSync8(target).mtimeMs;
|
|
7795
8517
|
return Math.max(0, now - mtime);
|
|
7796
8518
|
} catch {
|
|
7797
8519
|
return 0;
|
|
7798
8520
|
}
|
|
7799
8521
|
}
|
|
7800
8522
|
function isPathInside2(child, parent) {
|
|
7801
|
-
const rel =
|
|
7802
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
8523
|
+
const rel = path44.relative(parent, child);
|
|
8524
|
+
return rel === "" || !rel.startsWith("..") && !path44.isAbsolute(rel);
|
|
7803
8525
|
}
|
|
7804
8526
|
function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
7805
|
-
if (!
|
|
7806
|
-
const resolved =
|
|
8527
|
+
if (!existsSync32(targetPath)) return;
|
|
8528
|
+
const resolved = path44.resolve(targetPath);
|
|
7807
8529
|
if (seen.has(resolved)) return;
|
|
7808
8530
|
if (!isPathInside2(resolved, opts.harnessRoot)) return;
|
|
7809
8531
|
seen.add(resolved);
|
|
@@ -7820,7 +8542,7 @@ function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
|
7820
8542
|
}
|
|
7821
8543
|
function scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, meta) {
|
|
7822
8544
|
for (const entry of DEPENDENCY_CACHE_DIRS) {
|
|
7823
|
-
pushCandidate2(candidates, seen, opts,
|
|
8545
|
+
pushCandidate2(candidates, seen, opts, path44.join(worktreePath, entry.dirName), entry.kind, meta);
|
|
7824
8546
|
}
|
|
7825
8547
|
}
|
|
7826
8548
|
function scanDependencyCacheCandidates(opts) {
|
|
@@ -7834,20 +8556,20 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
7834
8556
|
repo: entry.run.repo
|
|
7835
8557
|
});
|
|
7836
8558
|
}
|
|
7837
|
-
if (!
|
|
7838
|
-
for (const runEntry of
|
|
8559
|
+
if (!existsSync32(opts.worktreesDir)) return candidates;
|
|
8560
|
+
for (const runEntry of readdirSync10(opts.worktreesDir, { withFileTypes: true })) {
|
|
7839
8561
|
if (!runEntry.isDirectory()) continue;
|
|
7840
8562
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
7841
|
-
const runPath =
|
|
8563
|
+
const runPath = path44.join(opts.worktreesDir, runEntry.name);
|
|
7842
8564
|
let workerEntries;
|
|
7843
8565
|
try {
|
|
7844
|
-
workerEntries =
|
|
8566
|
+
workerEntries = readdirSync10(runPath, { withFileTypes: true });
|
|
7845
8567
|
} catch {
|
|
7846
8568
|
continue;
|
|
7847
8569
|
}
|
|
7848
8570
|
for (const workerEntry of workerEntries) {
|
|
7849
8571
|
if (!workerEntry.isDirectory()) continue;
|
|
7850
|
-
const worktreePath =
|
|
8572
|
+
const worktreePath = path44.join(runPath, workerEntry.name);
|
|
7851
8573
|
scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, {
|
|
7852
8574
|
runId: runEntry.name,
|
|
7853
8575
|
worker: workerEntry.name
|
|
@@ -7858,11 +8580,11 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
7858
8580
|
}
|
|
7859
8581
|
|
|
7860
8582
|
// src/cleanup-duplicate-worktrees.ts
|
|
7861
|
-
import { existsSync as
|
|
7862
|
-
import
|
|
8583
|
+
import { existsSync as existsSync33, statSync as statSync9 } from "node:fs";
|
|
8584
|
+
import path45 from "node:path";
|
|
7863
8585
|
function pathAgeMs4(target, now) {
|
|
7864
8586
|
try {
|
|
7865
|
-
const mtime =
|
|
8587
|
+
const mtime = statSync9(target).mtimeMs;
|
|
7866
8588
|
return Math.max(0, now - mtime);
|
|
7867
8589
|
} catch {
|
|
7868
8590
|
return 0;
|
|
@@ -7889,8 +8611,8 @@ function parseWorktreePorcelain(output) {
|
|
|
7889
8611
|
return records;
|
|
7890
8612
|
}
|
|
7891
8613
|
function isUnderWorktreesDir(worktreePath, worktreesDir) {
|
|
7892
|
-
const rel =
|
|
7893
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
8614
|
+
const rel = path45.relative(path45.resolve(worktreesDir), path45.resolve(worktreePath));
|
|
8615
|
+
return rel !== "" && !rel.startsWith("..") && !path45.isAbsolute(rel);
|
|
7894
8616
|
}
|
|
7895
8617
|
function isCleanWorktree(worktreePath, repoRoot) {
|
|
7896
8618
|
try {
|
|
@@ -7903,14 +8625,14 @@ function isCleanWorktree(worktreePath, repoRoot) {
|
|
|
7903
8625
|
}
|
|
7904
8626
|
}
|
|
7905
8627
|
function scanDuplicateWorktreeCandidates(opts) {
|
|
7906
|
-
if (!opts.includeOrphans || !
|
|
8628
|
+
if (!opts.includeOrphans || !existsSync33(opts.worktreesDir)) return [];
|
|
7907
8629
|
const repos = /* @__PURE__ */ new Set();
|
|
7908
8630
|
for (const entry of opts.index.values()) {
|
|
7909
|
-
if (entry.run.repo) repos.add(
|
|
8631
|
+
if (entry.run.repo) repos.add(path45.resolve(entry.run.repo));
|
|
7910
8632
|
}
|
|
7911
8633
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
7912
8634
|
for (const entry of opts.index.values()) {
|
|
7913
|
-
indexedPaths.add(
|
|
8635
|
+
indexedPaths.add(path45.resolve(entry.worktreePath));
|
|
7914
8636
|
}
|
|
7915
8637
|
const candidates = [];
|
|
7916
8638
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7923,15 +8645,15 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
7923
8645
|
}
|
|
7924
8646
|
const worktrees = parseWorktreePorcelain(porcelain);
|
|
7925
8647
|
for (const wt of worktrees) {
|
|
7926
|
-
const resolved =
|
|
7927
|
-
if (resolved ===
|
|
8648
|
+
const resolved = path45.resolve(wt.path);
|
|
8649
|
+
if (resolved === path45.resolve(repoRoot)) continue;
|
|
7928
8650
|
if (!isUnderWorktreesDir(resolved, opts.worktreesDir)) continue;
|
|
7929
8651
|
if (indexedPaths.has(resolved)) continue;
|
|
7930
8652
|
if (seen.has(resolved)) continue;
|
|
7931
|
-
if (!
|
|
8653
|
+
if (!existsSync33(resolved)) continue;
|
|
7932
8654
|
if (!isCleanWorktree(resolved, repoRoot)) continue;
|
|
7933
|
-
const rel =
|
|
7934
|
-
const parts = rel.split(
|
|
8655
|
+
const rel = path45.relative(opts.worktreesDir, resolved);
|
|
8656
|
+
const parts = rel.split(path45.sep);
|
|
7935
8657
|
const runId = parts[0];
|
|
7936
8658
|
const worker = parts[1] ?? "unknown";
|
|
7937
8659
|
seen.add(resolved);
|
|
@@ -7950,12 +8672,12 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
7950
8672
|
}
|
|
7951
8673
|
|
|
7952
8674
|
// src/cleanup-worktree-index.ts
|
|
7953
|
-
import
|
|
8675
|
+
import path46 from "node:path";
|
|
7954
8676
|
function buildWorktreeIndexAt(harnessRoot) {
|
|
7955
8677
|
const index = /* @__PURE__ */ new Map();
|
|
7956
8678
|
for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
|
|
7957
8679
|
for (const name of Object.keys(run.workers || {})) {
|
|
7958
|
-
const workerPath =
|
|
8680
|
+
const workerPath = path46.join(
|
|
7959
8681
|
runDirectoryAt(harnessRoot, run.id),
|
|
7960
8682
|
"workers",
|
|
7961
8683
|
safeSlug(name),
|
|
@@ -7964,9 +8686,9 @@ function buildWorktreeIndexAt(harnessRoot) {
|
|
|
7964
8686
|
const worker = readJson(workerPath, void 0);
|
|
7965
8687
|
if (!worker?.worktreePath) continue;
|
|
7966
8688
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
7967
|
-
index.set(
|
|
8689
|
+
index.set(path46.resolve(worker.worktreePath), {
|
|
7968
8690
|
harnessRoot,
|
|
7969
|
-
worktreePath:
|
|
8691
|
+
worktreePath: path46.resolve(worker.worktreePath),
|
|
7970
8692
|
runId: run.id,
|
|
7971
8693
|
workerName: name,
|
|
7972
8694
|
run,
|
|
@@ -7979,7 +8701,7 @@ function buildWorktreeIndexAt(harnessRoot) {
|
|
|
7979
8701
|
}
|
|
7980
8702
|
|
|
7981
8703
|
// src/cleanup-retention-config.ts
|
|
7982
|
-
function
|
|
8704
|
+
function envFlag2(name) {
|
|
7983
8705
|
const v = process.env[name];
|
|
7984
8706
|
return v === "1" || v === "true" || v === "yes";
|
|
7985
8707
|
}
|
|
@@ -7990,17 +8712,17 @@ function envMs(name, fallback) {
|
|
|
7990
8712
|
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
7991
8713
|
}
|
|
7992
8714
|
function resolveHarnessRetention(options = {}) {
|
|
7993
|
-
const execute = options.execute === true || options.execute !== false &&
|
|
7994
|
-
const finalizeStaleRuns2 = options.finalizeStaleRuns !== false && !
|
|
8715
|
+
const execute = options.execute === true || options.execute !== false && envFlag2("KYNVER_CLEANUP_EXECUTE");
|
|
8716
|
+
const finalizeStaleRuns2 = options.finalizeStaleRuns !== false && !envFlag2("KYNVER_CLEANUP_SKIP_FINALIZE");
|
|
7995
8717
|
const nodeModulesAgeMs = options.nodeModulesAgeMs ?? envMs("KYNVER_CLEANUP_NODE_MODULES_AGE_MS", DEFAULT_NODE_MODULES_AGE_MS);
|
|
7996
8718
|
const worktreesAgeMs = options.worktreesAgeMs ?? envMs("KYNVER_CLEANUP_WORKTREES_AGE_MS", 0);
|
|
7997
8719
|
const terminalWorktreesAgeMs = options.terminalWorktreesAgeMs ?? envMs("KYNVER_CLEANUP_TERMINAL_WORKTREES_AGE_MS", DEFAULT_TERMINAL_WORKTREES_AGE_MS);
|
|
7998
8720
|
const runDirectoriesAgeMs = options.runDirectoriesAgeMs ?? envMs("KYNVER_CLEANUP_RUN_DIRECTORIES_AGE_MS", DEFAULT_RUN_DIRECTORIES_AGE_MS);
|
|
7999
8721
|
const maxActionsPerSweep = options.maxActionsPerSweep ?? envMs("KYNVER_CLEANUP_MAX_ACTIONS_PER_SWEEP", DEFAULT_MAX_ACTIONS_PER_SWEEP);
|
|
8000
|
-
const includeOrphans = options.includeOrphans === true ||
|
|
8001
|
-
const scopeAll =
|
|
8722
|
+
const includeOrphans = options.includeOrphans === true || envFlag2("KYNVER_CLEANUP_INCLUDE_ORPHANS");
|
|
8723
|
+
const scopeAll = envFlag2("KYNVER_CLEANUP_SCOPE_ALL") || process.env.KYNVER_CLEANUP_SCOPE === "all";
|
|
8002
8724
|
const runIdFilter = scopeAll ? options.runIdFilter : options.runIdFilter ?? (process.env.KYNVER_CLEANUP_RUN_ID || void 0);
|
|
8003
|
-
const accountBytes = options.accountBytes !== false && !
|
|
8725
|
+
const accountBytes = options.accountBytes !== false && !envFlag2("KYNVER_CLEANUP_SKIP_BYTE_ACCOUNTING");
|
|
8004
8726
|
return {
|
|
8005
8727
|
execute,
|
|
8006
8728
|
finalizeStaleRuns: finalizeStaleRuns2,
|
|
@@ -8030,15 +8752,15 @@ function resolvePipelineHarnessRetention(runId) {
|
|
|
8030
8752
|
}
|
|
8031
8753
|
|
|
8032
8754
|
// src/cleanup-orphan-safety.ts
|
|
8033
|
-
import { existsSync as
|
|
8034
|
-
import
|
|
8755
|
+
import { existsSync as existsSync34, statSync as statSync10 } from "node:fs";
|
|
8756
|
+
import path47 from "node:path";
|
|
8035
8757
|
var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
|
|
8036
8758
|
function assessOrphanWorktreeSafety(input) {
|
|
8037
8759
|
const now = input.now ?? Date.now();
|
|
8038
8760
|
const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
|
|
8039
|
-
if (!
|
|
8761
|
+
if (!existsSync34(input.worktreePath)) return null;
|
|
8040
8762
|
if (input.runId && input.workerName) {
|
|
8041
|
-
const heartbeatPath =
|
|
8763
|
+
const heartbeatPath = path47.join(
|
|
8042
8764
|
input.harnessRoot,
|
|
8043
8765
|
"runs",
|
|
8044
8766
|
input.runId,
|
|
@@ -8047,13 +8769,13 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
8047
8769
|
"heartbeat.jsonl"
|
|
8048
8770
|
);
|
|
8049
8771
|
try {
|
|
8050
|
-
const mtime =
|
|
8772
|
+
const mtime = statSync10(heartbeatPath).mtimeMs;
|
|
8051
8773
|
if (now - mtime < heartbeatFreshMs) return "active_worker";
|
|
8052
8774
|
} catch {
|
|
8053
8775
|
}
|
|
8054
8776
|
}
|
|
8055
|
-
const gitDir =
|
|
8056
|
-
if (!
|
|
8777
|
+
const gitDir = path47.join(input.worktreePath, ".git");
|
|
8778
|
+
if (!existsSync34(gitDir)) return null;
|
|
8057
8779
|
const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
|
|
8058
8780
|
if (porcelain.status !== 0) return "pr_or_unmerged_commits";
|
|
8059
8781
|
const dirtyLines = porcelain.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
@@ -8082,14 +8804,14 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
8082
8804
|
}
|
|
8083
8805
|
|
|
8084
8806
|
// src/harness-storage-snapshot.ts
|
|
8085
|
-
import { existsSync as
|
|
8086
|
-
import
|
|
8807
|
+
import { existsSync as existsSync35, readdirSync as readdirSync11, statSync as statSync11 } from "node:fs";
|
|
8808
|
+
import path48 from "node:path";
|
|
8087
8809
|
function harnessStorageSnapshot(opts = {}) {
|
|
8088
8810
|
const harnessRoot = normalizeHarnessRoot(opts.harnessRoot ?? resolveHarnessRoot());
|
|
8089
8811
|
const worktreesDir = harnessWorktreesDir(harnessRoot);
|
|
8090
8812
|
const now = opts.now ?? Date.now();
|
|
8091
8813
|
const scannedAt = new Date(now).toISOString();
|
|
8092
|
-
if (!
|
|
8814
|
+
if (!existsSync35(worktreesDir)) {
|
|
8093
8815
|
return {
|
|
8094
8816
|
harnessRoot,
|
|
8095
8817
|
worktreesDir,
|
|
@@ -8106,7 +8828,7 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
8106
8828
|
let oldestMs = null;
|
|
8107
8829
|
let entries;
|
|
8108
8830
|
try {
|
|
8109
|
-
entries =
|
|
8831
|
+
entries = readdirSync11(worktreesDir, { withFileTypes: true });
|
|
8110
8832
|
} catch {
|
|
8111
8833
|
return {
|
|
8112
8834
|
harnessRoot,
|
|
@@ -8121,14 +8843,14 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
8121
8843
|
for (const runEntry of entries) {
|
|
8122
8844
|
if (!runEntry.isDirectory()) continue;
|
|
8123
8845
|
runCount += 1;
|
|
8124
|
-
const runPath =
|
|
8846
|
+
const runPath = path48.join(worktreesDir, runEntry.name);
|
|
8125
8847
|
try {
|
|
8126
|
-
const st =
|
|
8848
|
+
const st = statSync11(runPath);
|
|
8127
8849
|
oldestMs = oldestMs === null ? st.mtimeMs : Math.min(oldestMs, st.mtimeMs);
|
|
8128
8850
|
} catch {
|
|
8129
8851
|
}
|
|
8130
8852
|
try {
|
|
8131
|
-
for (const workerEntry of
|
|
8853
|
+
for (const workerEntry of readdirSync11(runPath, { withFileTypes: true })) {
|
|
8132
8854
|
if (workerEntry.isDirectory()) workerCount += 1;
|
|
8133
8855
|
}
|
|
8134
8856
|
} catch {
|
|
@@ -8156,12 +8878,12 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
8156
8878
|
}
|
|
8157
8879
|
|
|
8158
8880
|
// src/cleanup-harness-roots.ts
|
|
8159
|
-
import { existsSync as
|
|
8160
|
-
import { homedir as
|
|
8161
|
-
import
|
|
8881
|
+
import { existsSync as existsSync36 } from "node:fs";
|
|
8882
|
+
import { homedir as homedir12 } from "node:os";
|
|
8883
|
+
import path49 from "node:path";
|
|
8162
8884
|
var WELL_KNOWN_HARNESS_SCAN_ROOTS = [
|
|
8163
8885
|
"/var/tmp/kynver-harness",
|
|
8164
|
-
|
|
8886
|
+
path49.join(homedir12(), ".openclaw", "harness")
|
|
8165
8887
|
];
|
|
8166
8888
|
function addRoot(seen, roots, candidate) {
|
|
8167
8889
|
if (!candidate?.trim()) return;
|
|
@@ -8183,15 +8905,15 @@ function resolveHarnessScanRoots(options = {}) {
|
|
|
8183
8905
|
for (const candidate of extra ?? []) addRoot(seen, roots, candidate);
|
|
8184
8906
|
if (shouldScanWellKnownRoots(options)) {
|
|
8185
8907
|
for (const candidate of WELL_KNOWN_HARNESS_SCAN_ROOTS) {
|
|
8186
|
-
const resolved =
|
|
8187
|
-
if (!seen.has(resolved) &&
|
|
8908
|
+
const resolved = path49.resolve(candidate);
|
|
8909
|
+
if (!seen.has(resolved) && existsSync36(resolved)) addRoot(seen, roots, resolved);
|
|
8188
8910
|
}
|
|
8189
8911
|
}
|
|
8190
8912
|
return roots;
|
|
8191
8913
|
}
|
|
8192
8914
|
|
|
8193
8915
|
// src/cleanup-disk-pressure.ts
|
|
8194
|
-
function
|
|
8916
|
+
function envFlag3(name) {
|
|
8195
8917
|
const v = process.env[name];
|
|
8196
8918
|
return v === "1" || v === "true" || v === "yes";
|
|
8197
8919
|
}
|
|
@@ -8214,7 +8936,7 @@ function observeCleanupDiskPressure(input = {}) {
|
|
|
8214
8936
|
}
|
|
8215
8937
|
function applyDiskPressureToRetention(retention, pressure) {
|
|
8216
8938
|
if (!pressure.pressured) return retention;
|
|
8217
|
-
const executeOnPressure = retention.execute ||
|
|
8939
|
+
const executeOnPressure = retention.execute || envFlag3("KYNVER_CLEANUP_EXECUTE_ON_PRESSURE");
|
|
8218
8940
|
return {
|
|
8219
8941
|
...retention,
|
|
8220
8942
|
execute: executeOnPressure,
|
|
@@ -8273,9 +8995,9 @@ function mergeWorktreeIndexes(scanRoots) {
|
|
|
8273
8995
|
}
|
|
8274
8996
|
function worktreePathForCandidate(candidate, worktreesDir) {
|
|
8275
8997
|
if (candidate.runId && candidate.worker) {
|
|
8276
|
-
return
|
|
8998
|
+
return path50.join(worktreesDir, candidate.runId, candidate.worker);
|
|
8277
8999
|
}
|
|
8278
|
-
return
|
|
9000
|
+
return path50.resolve(candidate.path, "..");
|
|
8279
9001
|
}
|
|
8280
9002
|
function runHarnessCleanup(options = {}) {
|
|
8281
9003
|
let retention = resolveHarnessRetention(options);
|
|
@@ -8292,7 +9014,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8292
9014
|
const atSweepCap = () => actions.length >= maxActions;
|
|
8293
9015
|
for (const harnessRoot of paths.scanRoots) {
|
|
8294
9016
|
if (atSweepCap()) break;
|
|
8295
|
-
const worktreesDir =
|
|
9017
|
+
const worktreesDir = path50.join(harnessRoot, "worktrees");
|
|
8296
9018
|
const scanOpts = {
|
|
8297
9019
|
harnessRoot,
|
|
8298
9020
|
worktreesDir,
|
|
@@ -8306,7 +9028,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8306
9028
|
for (const raw of scanDependencyCacheCandidates(scanOpts)) {
|
|
8307
9029
|
if (atSweepCap()) break;
|
|
8308
9030
|
const candidate = attachCandidateBytes(raw, retention.accountBytes);
|
|
8309
|
-
const resolved =
|
|
9031
|
+
const resolved = path50.resolve(candidate.path);
|
|
8310
9032
|
if (processedPaths.has(resolved)) continue;
|
|
8311
9033
|
processedPaths.add(resolved);
|
|
8312
9034
|
const pathSkip = pathGuardForDependencyCache(candidate, harnessRoot, worktreesDir);
|
|
@@ -8316,7 +9038,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8316
9038
|
continue;
|
|
8317
9039
|
}
|
|
8318
9040
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
8319
|
-
const indexed = index.get(
|
|
9041
|
+
const indexed = index.get(path50.resolve(worktreePath)) ?? null;
|
|
8320
9042
|
const guardReason = skipDependencyCacheRemoval({
|
|
8321
9043
|
indexed,
|
|
8322
9044
|
includeOrphans: true,
|
|
@@ -8336,7 +9058,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8336
9058
|
for (const raw of scanBuildCacheCandidates(scanOpts)) {
|
|
8337
9059
|
if (atSweepCap()) break;
|
|
8338
9060
|
const candidate = attachCandidateBytes(raw, retention.accountBytes);
|
|
8339
|
-
const resolved =
|
|
9061
|
+
const resolved = path50.resolve(candidate.path);
|
|
8340
9062
|
if (processedPaths.has(resolved)) continue;
|
|
8341
9063
|
processedPaths.add(resolved);
|
|
8342
9064
|
const pathSkip = isHarnessBuildCachePath(candidate.path, harnessRoot, worktreesDir);
|
|
@@ -8346,7 +9068,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8346
9068
|
continue;
|
|
8347
9069
|
}
|
|
8348
9070
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
8349
|
-
const indexed = index.get(
|
|
9071
|
+
const indexed = index.get(path50.resolve(worktreePath)) ?? null;
|
|
8350
9072
|
const guardReason = skipBuildCacheRemoval({
|
|
8351
9073
|
indexed,
|
|
8352
9074
|
includeOrphans: true,
|
|
@@ -8370,11 +9092,11 @@ function runHarnessCleanup(options = {}) {
|
|
|
8370
9092
|
const worktreeSeen = /* @__PURE__ */ new Set();
|
|
8371
9093
|
for (const raw of worktreeCandidates) {
|
|
8372
9094
|
if (atSweepCap()) break;
|
|
8373
|
-
const resolved =
|
|
9095
|
+
const resolved = path50.resolve(raw.path);
|
|
8374
9096
|
if (worktreeSeen.has(resolved)) continue;
|
|
8375
9097
|
worktreeSeen.add(resolved);
|
|
8376
9098
|
const candidate = attachCandidateBytes({ ...raw, path: resolved }, retention.accountBytes);
|
|
8377
|
-
const indexed = index.get(
|
|
9099
|
+
const indexed = index.get(path50.resolve(candidate.path)) ?? null;
|
|
8378
9100
|
const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
|
|
8379
9101
|
worktreePath: candidate.path,
|
|
8380
9102
|
harnessRoot,
|
|
@@ -8384,7 +9106,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8384
9106
|
});
|
|
8385
9107
|
const guardSkip = skipWorktreeRemoval({
|
|
8386
9108
|
indexed,
|
|
8387
|
-
worktreePath:
|
|
9109
|
+
worktreePath: path50.resolve(candidate.path),
|
|
8388
9110
|
includeOrphans: retention.includeOrphans,
|
|
8389
9111
|
worktreesAgeMs: retention.worktreesAgeMs,
|
|
8390
9112
|
terminalWorktreesAgeMs: retention.terminalWorktreesAgeMs,
|
|
@@ -8411,10 +9133,10 @@ function runHarnessCleanup(options = {}) {
|
|
|
8411
9133
|
})) {
|
|
8412
9134
|
if (atSweepCap()) break;
|
|
8413
9135
|
const candidate = attachCandidateBytes(raw, retention.accountBytes);
|
|
8414
|
-
const resolved =
|
|
9136
|
+
const resolved = path50.resolve(candidate.path);
|
|
8415
9137
|
if (processedPaths.has(resolved)) continue;
|
|
8416
9138
|
processedPaths.add(resolved);
|
|
8417
|
-
const runId = candidate.runId ??
|
|
9139
|
+
const runId = candidate.runId ?? path50.basename(resolved);
|
|
8418
9140
|
const dirSkip = skipRunDirectoryRemoval({
|
|
8419
9141
|
harnessRoot,
|
|
8420
9142
|
runId,
|
|
@@ -8503,8 +9225,8 @@ function isPipelineCleanupEnabled() {
|
|
|
8503
9225
|
|
|
8504
9226
|
// src/installed-package-versions.ts
|
|
8505
9227
|
import { readFile } from "node:fs/promises";
|
|
8506
|
-
import { homedir as
|
|
8507
|
-
import
|
|
9228
|
+
import { homedir as homedir13 } from "node:os";
|
|
9229
|
+
import path51 from "node:path";
|
|
8508
9230
|
var MANAGED_PACKAGES = [
|
|
8509
9231
|
"@kynver-app/runtime",
|
|
8510
9232
|
"@kynver-app/openclaw-agent-os",
|
|
@@ -8518,13 +9240,13 @@ function unique(values) {
|
|
|
8518
9240
|
return [...new Set(values.filter((value) => Boolean(value)))];
|
|
8519
9241
|
}
|
|
8520
9242
|
function moduleRoots() {
|
|
8521
|
-
const home =
|
|
8522
|
-
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ??
|
|
8523
|
-
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ?
|
|
9243
|
+
const home = homedir13();
|
|
9244
|
+
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path51.join(home, ".openclaw", "npm");
|
|
9245
|
+
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path51.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path51.join(home, ".npm-global", "lib", "node_modules"));
|
|
8524
9246
|
return unique([
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot :
|
|
9247
|
+
path51.join(openClawPrefix, "lib", "node_modules"),
|
|
9248
|
+
path51.join(openClawPrefix, "node_modules"),
|
|
9249
|
+
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path51.join(npmGlobalRoot, "lib", "node_modules")
|
|
8528
9250
|
]);
|
|
8529
9251
|
}
|
|
8530
9252
|
async function readVersion(packageJsonPath) {
|
|
@@ -8540,7 +9262,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
|
|
|
8540
9262
|
const out = {};
|
|
8541
9263
|
for (const packageName of MANAGED_PACKAGES) {
|
|
8542
9264
|
for (const root of roots) {
|
|
8543
|
-
const packageJsonPath =
|
|
9265
|
+
const packageJsonPath = path51.join(root, packageName, "package.json");
|
|
8544
9266
|
const version = await readVersion(packageJsonPath);
|
|
8545
9267
|
if (!version) continue;
|
|
8546
9268
|
out[packageName] = { version, observedAt, path: packageJsonPath };
|
|
@@ -8556,11 +9278,11 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
8556
9278
|
const outcomes = [];
|
|
8557
9279
|
for (const name of Object.keys(run.workers || {})) {
|
|
8558
9280
|
const worker = readJson(
|
|
8559
|
-
|
|
9281
|
+
path52.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
8560
9282
|
void 0
|
|
8561
9283
|
);
|
|
8562
9284
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
8563
|
-
if (
|
|
9285
|
+
if (hasTerminalCompletionAck(worker)) {
|
|
8564
9286
|
outcomes.push({ worker: name, ok: true, taskId: worker.taskId ?? null, skipped: true });
|
|
8565
9287
|
continue;
|
|
8566
9288
|
}
|
|
@@ -8629,24 +9351,51 @@ async function runPipelineTick(args) {
|
|
|
8629
9351
|
let maxStarts = maxStartsAdvice.maxStarts;
|
|
8630
9352
|
const sweep = await sweepRun({ run: runId, agentOsId, pipeline: true, ...args });
|
|
8631
9353
|
let dispatch = null;
|
|
8632
|
-
|
|
8633
|
-
|
|
9354
|
+
let startedCount = 0;
|
|
9355
|
+
const exactTargetTaskIds = operatorExactTargetTaskIds(operatorTick);
|
|
9356
|
+
let remainingStarts = maxStarts;
|
|
9357
|
+
if (execute && remainingStarts > 0 && exactTargetTaskIds.length > 0) {
|
|
9358
|
+
const exactBudget = Math.min(remainingStarts, exactTargetTaskIds.length);
|
|
9359
|
+
const exact = await runPipelineDispatch(
|
|
9360
|
+
{
|
|
9361
|
+
...args,
|
|
9362
|
+
run: runId,
|
|
9363
|
+
agentOsId,
|
|
9364
|
+
targetTaskIds: exactTargetTaskIds.join(",")
|
|
9365
|
+
},
|
|
9366
|
+
exactBudget
|
|
9367
|
+
);
|
|
9368
|
+
const exactStarted = countDispatchStarts(exact);
|
|
9369
|
+
startedCount += exactStarted;
|
|
9370
|
+
remainingStarts = Math.max(0, remainingStarts - exactStarted);
|
|
9371
|
+
dispatch = { exactTargetTaskIds, exact, startedCount };
|
|
9372
|
+
}
|
|
9373
|
+
if (execute && remainingStarts > 0) {
|
|
9374
|
+
const broad = await runPipelineDispatch(
|
|
8634
9375
|
{
|
|
8635
9376
|
...args,
|
|
8636
9377
|
run: runId,
|
|
8637
9378
|
agentOsId
|
|
8638
9379
|
},
|
|
8639
|
-
|
|
9380
|
+
remainingStarts
|
|
8640
9381
|
);
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
9382
|
+
const broadStarted = countDispatchStarts(broad);
|
|
9383
|
+
startedCount += broadStarted;
|
|
9384
|
+
dispatch = dispatch && typeof dispatch === "object" ? { ...dispatch, broad, startedCount } : broad;
|
|
9385
|
+
} else if (!execute || maxStarts <= 0) {
|
|
9386
|
+
if (!dispatch) {
|
|
9387
|
+
dispatch = {
|
|
9388
|
+
ok: true,
|
|
9389
|
+
skipped: true,
|
|
9390
|
+
reason: execute ? dispatchResourceGate.reason ?? "no slots or queued work" : "execute disabled",
|
|
9391
|
+
maxStarts: 0,
|
|
9392
|
+
dispatchAdvice: maxStartsAdvice,
|
|
9393
|
+
...exactTargetTaskIds.length ? { exactTargetTaskIds, exactOnly: true } : {}
|
|
9394
|
+
};
|
|
9395
|
+
}
|
|
9396
|
+
} else if (dispatch && typeof dispatch === "object") {
|
|
9397
|
+
dispatch = { ...dispatch, broadSkipped: true, startedCount };
|
|
8648
9398
|
}
|
|
8649
|
-
const startedCount = dispatch?.startedCount ?? 0;
|
|
8650
9399
|
const idle = !maxStartsAdvice.underutilized && maxStarts === 0 && completedWorkers.length === 0 && startedCount === 0;
|
|
8651
9400
|
return {
|
|
8652
9401
|
runId,
|
|
@@ -8662,6 +9411,7 @@ async function runPipelineTick(args) {
|
|
|
8662
9411
|
completionAckSync,
|
|
8663
9412
|
operatorTick,
|
|
8664
9413
|
sweep,
|
|
9414
|
+
dispatchAdvice: maxStartsAdvice,
|
|
8665
9415
|
dispatch,
|
|
8666
9416
|
idle
|
|
8667
9417
|
};
|
|
@@ -8685,8 +9435,18 @@ async function runDaemon(args) {
|
|
|
8685
9435
|
stopping = true;
|
|
8686
9436
|
});
|
|
8687
9437
|
console.error(JSON.stringify({ event: "daemon_start", runId, agentOsId, execute, intervalMs }));
|
|
9438
|
+
const cronEnv = resolveKynverCronEnv();
|
|
8688
9439
|
while (!stopping) {
|
|
8689
9440
|
try {
|
|
9441
|
+
if (cronEnv.tickEnabled) {
|
|
9442
|
+
const cronTick = await runKynverCronTick({
|
|
9443
|
+
env: cronEnv,
|
|
9444
|
+
agentOsIdFilter: agentOsId
|
|
9445
|
+
});
|
|
9446
|
+
if (cronTick.enabled && (cronTick.fired > 0 || cronTick.errors > 0)) {
|
|
9447
|
+
console.error(JSON.stringify({ event: "daemon_cron_tick", ...cronTick }));
|
|
9448
|
+
}
|
|
9449
|
+
}
|
|
8690
9450
|
const tick = await runPipelineTick({ run: runId, agentOsId, execute, ...args });
|
|
8691
9451
|
console.error(JSON.stringify({ event: "daemon_tick", ...tick }));
|
|
8692
9452
|
if (tick.idle) {
|
|
@@ -8705,7 +9465,7 @@ async function runDaemon(args) {
|
|
|
8705
9465
|
}
|
|
8706
9466
|
|
|
8707
9467
|
// src/plan-progress.ts
|
|
8708
|
-
import
|
|
9468
|
+
import path53 from "node:path";
|
|
8709
9469
|
|
|
8710
9470
|
// src/bounded-build/constants.ts
|
|
8711
9471
|
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
@@ -8992,7 +9752,7 @@ async function emitPlanProgress(args) {
|
|
|
8992
9752
|
}
|
|
8993
9753
|
function verifyPlanLocal(args) {
|
|
8994
9754
|
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
8995
|
-
const cwd =
|
|
9755
|
+
const cwd = path53.resolve(worktree);
|
|
8996
9756
|
const summary = runHarnessVerifyCommands(cwd);
|
|
8997
9757
|
const emitJson = args.json === true || args.json === "true";
|
|
8998
9758
|
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
@@ -9041,9 +9801,9 @@ async function verifyPlan(args) {
|
|
|
9041
9801
|
}
|
|
9042
9802
|
|
|
9043
9803
|
// src/harness-verify-cli.ts
|
|
9044
|
-
import
|
|
9804
|
+
import path54 from "node:path";
|
|
9045
9805
|
function runHarnessVerifyCli(args) {
|
|
9046
|
-
const cwd =
|
|
9806
|
+
const cwd = path54.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
9047
9807
|
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
9048
9808
|
const commands = [];
|
|
9049
9809
|
const rawCmd = args.command;
|
|
@@ -9087,7 +9847,7 @@ function runHarnessVerifyCli(args) {
|
|
|
9087
9847
|
}
|
|
9088
9848
|
|
|
9089
9849
|
// src/plan-persist-cli.ts
|
|
9090
|
-
import { readFileSync as
|
|
9850
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
9091
9851
|
var OPERATIONS = ["create", "add_version", "update_metadata"];
|
|
9092
9852
|
var FAILURE_KINDS = [
|
|
9093
9853
|
"approval_guard",
|
|
@@ -9099,7 +9859,7 @@ var FAILURE_KINDS = [
|
|
|
9099
9859
|
function readBodyArg(args) {
|
|
9100
9860
|
const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
|
|
9101
9861
|
if (bodyFile) {
|
|
9102
|
-
return { body:
|
|
9862
|
+
return { body: readFileSync12(bodyFile, "utf8"), bodyPathHint: bodyFile };
|
|
9103
9863
|
}
|
|
9104
9864
|
const inline = args.body ? String(args.body) : void 0;
|
|
9105
9865
|
if (inline) return { body: inline };
|
|
@@ -9243,7 +10003,7 @@ function formatMonitorTickNotice(tick) {
|
|
|
9243
10003
|
}
|
|
9244
10004
|
|
|
9245
10005
|
// src/monitor/monitor.service.ts
|
|
9246
|
-
import
|
|
10006
|
+
import path56 from "node:path";
|
|
9247
10007
|
|
|
9248
10008
|
// src/monitor/monitor.classify.ts
|
|
9249
10009
|
function classifyWorkerHealth(input) {
|
|
@@ -9295,11 +10055,11 @@ function classifyWorkerHealth(input) {
|
|
|
9295
10055
|
}
|
|
9296
10056
|
|
|
9297
10057
|
// src/monitor/monitor.store.ts
|
|
9298
|
-
import { existsSync as
|
|
9299
|
-
import
|
|
10058
|
+
import { existsSync as existsSync37, mkdirSync as mkdirSync6, readdirSync as readdirSync12, unlinkSync as unlinkSync3 } from "node:fs";
|
|
10059
|
+
import path55 from "node:path";
|
|
9300
10060
|
function monitorsDir() {
|
|
9301
10061
|
const { harnessRoot } = getHarnessPaths();
|
|
9302
|
-
const dir =
|
|
10062
|
+
const dir = path55.join(harnessRoot, "monitors");
|
|
9303
10063
|
mkdirSync6(dir, { recursive: true });
|
|
9304
10064
|
return dir;
|
|
9305
10065
|
}
|
|
@@ -9307,7 +10067,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
9307
10067
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
9308
10068
|
}
|
|
9309
10069
|
function monitorPath(monitorId) {
|
|
9310
|
-
return
|
|
10070
|
+
return path55.join(monitorsDir(), `${monitorId}.json`);
|
|
9311
10071
|
}
|
|
9312
10072
|
function loadMonitorSession(monitorId) {
|
|
9313
10073
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -9317,18 +10077,18 @@ function saveMonitorSession(session) {
|
|
|
9317
10077
|
}
|
|
9318
10078
|
function deleteMonitorSession(monitorId) {
|
|
9319
10079
|
const file = monitorPath(monitorId);
|
|
9320
|
-
if (!
|
|
9321
|
-
|
|
10080
|
+
if (!existsSync37(file)) return false;
|
|
10081
|
+
unlinkSync3(file);
|
|
9322
10082
|
return true;
|
|
9323
10083
|
}
|
|
9324
10084
|
function listMonitorSessions() {
|
|
9325
10085
|
const dir = monitorsDir();
|
|
9326
|
-
if (!
|
|
10086
|
+
if (!existsSync37(dir)) return [];
|
|
9327
10087
|
const entries = [];
|
|
9328
|
-
for (const name of
|
|
10088
|
+
for (const name of readdirSync12(dir)) {
|
|
9329
10089
|
if (!name.endsWith(".json")) continue;
|
|
9330
10090
|
const session = readJson(
|
|
9331
|
-
|
|
10091
|
+
path55.join(dir, name),
|
|
9332
10092
|
void 0
|
|
9333
10093
|
);
|
|
9334
10094
|
if (!session?.monitorId) continue;
|
|
@@ -9419,7 +10179,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
9419
10179
|
// src/monitor/monitor.service.ts
|
|
9420
10180
|
function workerRecord2(runId, name) {
|
|
9421
10181
|
return readJson(
|
|
9422
|
-
|
|
10182
|
+
path56.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
9423
10183
|
void 0
|
|
9424
10184
|
);
|
|
9425
10185
|
}
|
|
@@ -9625,21 +10385,21 @@ async function runMonitorLoop(args) {
|
|
|
9625
10385
|
|
|
9626
10386
|
// src/monitor/monitor-spawn.ts
|
|
9627
10387
|
import { spawn as spawn6 } from "node:child_process";
|
|
9628
|
-
import { closeSync as
|
|
9629
|
-
import
|
|
10388
|
+
import { closeSync as closeSync7, existsSync as existsSync38, openSync as openSync7 } from "node:fs";
|
|
10389
|
+
import path57 from "node:path";
|
|
9630
10390
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
9631
10391
|
function resolveDefaultCliPath2() {
|
|
9632
|
-
return
|
|
10392
|
+
return path57.join(fileURLToPath3(new URL(".", import.meta.url)), "cli.js");
|
|
9633
10393
|
}
|
|
9634
10394
|
function spawnMonitorSidecar(opts) {
|
|
9635
10395
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
9636
|
-
if (!
|
|
10396
|
+
if (!existsSync38(cliPath)) return void 0;
|
|
9637
10397
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
9638
10398
|
const { harnessRoot } = getHarnessPaths();
|
|
9639
|
-
const logPath =
|
|
10399
|
+
const logPath = path57.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
9640
10400
|
let logFd;
|
|
9641
10401
|
try {
|
|
9642
|
-
logFd =
|
|
10402
|
+
logFd = openSync7(logPath, "a");
|
|
9643
10403
|
} catch {
|
|
9644
10404
|
logFd = void 0;
|
|
9645
10405
|
}
|
|
@@ -9679,7 +10439,7 @@ function spawnMonitorSidecar(opts) {
|
|
|
9679
10439
|
env: process.env
|
|
9680
10440
|
})
|
|
9681
10441
|
);
|
|
9682
|
-
if (logFd !== void 0)
|
|
10442
|
+
if (logFd !== void 0) closeSync7(logFd);
|
|
9683
10443
|
child.unref();
|
|
9684
10444
|
const session = {
|
|
9685
10445
|
monitorId,
|
|
@@ -9696,7 +10456,7 @@ function spawnMonitorSidecar(opts) {
|
|
|
9696
10456
|
} catch {
|
|
9697
10457
|
if (logFd !== void 0) {
|
|
9698
10458
|
try {
|
|
9699
|
-
|
|
10459
|
+
closeSync7(logFd);
|
|
9700
10460
|
} catch {
|
|
9701
10461
|
}
|
|
9702
10462
|
}
|
|
@@ -9756,13 +10516,13 @@ async function monitorTickCli(args) {
|
|
|
9756
10516
|
}
|
|
9757
10517
|
|
|
9758
10518
|
// src/package-version.ts
|
|
9759
|
-
import { existsSync as
|
|
10519
|
+
import { existsSync as existsSync39, readFileSync as readFileSync13 } from "node:fs";
|
|
9760
10520
|
import { dirname, join } from "node:path";
|
|
9761
10521
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
9762
10522
|
function resolvePackageRoot(moduleUrl) {
|
|
9763
10523
|
let dir = dirname(fileURLToPath4(moduleUrl));
|
|
9764
10524
|
for (let depth = 0; depth < 6; depth += 1) {
|
|
9765
|
-
if (
|
|
10525
|
+
if (existsSync39(join(dir, "package.json"))) return dir;
|
|
9766
10526
|
const parent = dirname(dir);
|
|
9767
10527
|
if (parent === dir) break;
|
|
9768
10528
|
dir = parent;
|
|
@@ -9771,7 +10531,7 @@ function resolvePackageRoot(moduleUrl) {
|
|
|
9771
10531
|
}
|
|
9772
10532
|
function readOwnPackageVersion(moduleUrl = import.meta.url) {
|
|
9773
10533
|
const pkgPath = join(resolvePackageRoot(moduleUrl), "package.json");
|
|
9774
|
-
const pkg = JSON.parse(
|
|
10534
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
|
|
9775
10535
|
if (typeof pkg.version !== "string" || !pkg.version.trim()) {
|
|
9776
10536
|
throw new Error(`Missing package.json version at ${pkgPath}`);
|
|
9777
10537
|
}
|
|
@@ -9792,7 +10552,7 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
|
|
|
9792
10552
|
}
|
|
9793
10553
|
|
|
9794
10554
|
// src/post-restart-unblock.ts
|
|
9795
|
-
import
|
|
10555
|
+
import path58 from "node:path";
|
|
9796
10556
|
function skip(runId, worker, taskId, agentOsId, leaseOwner, reason) {
|
|
9797
10557
|
return { runId, worker, taskId, agentOsId, leaseOwner, action: "skipped", reason };
|
|
9798
10558
|
}
|
|
@@ -9805,7 +10565,7 @@ async function postRestartUnblock(args) {
|
|
|
9805
10565
|
const errors = [];
|
|
9806
10566
|
for (const run of listRunRecords()) {
|
|
9807
10567
|
for (const name of Object.keys(run.workers ?? {})) {
|
|
9808
|
-
const workerPath =
|
|
10568
|
+
const workerPath = path58.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
9809
10569
|
const worker = readJson(workerPath, void 0);
|
|
9810
10570
|
if (!worker) {
|
|
9811
10571
|
skipped.push(skip(run.id, name, "", "", "", "worker.json missing"));
|
|
@@ -9917,12 +10677,12 @@ async function postRestartUnblockCli(args) {
|
|
|
9917
10677
|
}
|
|
9918
10678
|
|
|
9919
10679
|
// src/doctor/runtime-takeover.ts
|
|
9920
|
-
import
|
|
10680
|
+
import path60 from "node:path";
|
|
9921
10681
|
|
|
9922
10682
|
// src/doctor/runtime-takeover.probes.ts
|
|
9923
|
-
import { accessSync, constants, existsSync as
|
|
9924
|
-
import { homedir as
|
|
9925
|
-
import
|
|
10683
|
+
import { accessSync, constants, existsSync as existsSync40, readFileSync as readFileSync14 } from "node:fs";
|
|
10684
|
+
import { homedir as homedir14 } from "node:os";
|
|
10685
|
+
import path59 from "node:path";
|
|
9926
10686
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
9927
10687
|
function captureCommand(bin, args) {
|
|
9928
10688
|
try {
|
|
@@ -9951,7 +10711,7 @@ function tokenPrefix(token) {
|
|
|
9951
10711
|
return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
|
|
9952
10712
|
}
|
|
9953
10713
|
function isWritable(target) {
|
|
9954
|
-
if (!
|
|
10714
|
+
if (!existsSync40(target)) return false;
|
|
9955
10715
|
try {
|
|
9956
10716
|
accessSync(target, constants.W_OK);
|
|
9957
10717
|
return true;
|
|
@@ -9964,15 +10724,15 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
9964
10724
|
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
9965
10725
|
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
9966
10726
|
loadConfig: () => loadUserConfig(),
|
|
9967
|
-
configFilePath: () =>
|
|
9968
|
-
credentialsFilePath: () =>
|
|
10727
|
+
configFilePath: () => path59.join(homedir14(), ".kynver", "config.json"),
|
|
10728
|
+
credentialsFilePath: () => path59.join(homedir14(), ".kynver", "credentials"),
|
|
9969
10729
|
readCredentials: () => {
|
|
9970
|
-
const credPath =
|
|
9971
|
-
if (!
|
|
10730
|
+
const credPath = path59.join(homedir14(), ".kynver", "credentials");
|
|
10731
|
+
if (!existsSync40(credPath)) {
|
|
9972
10732
|
return { hasApiKey: false };
|
|
9973
10733
|
}
|
|
9974
10734
|
try {
|
|
9975
|
-
const parsed = JSON.parse(
|
|
10735
|
+
const parsed = JSON.parse(readFileSync14(credPath, "utf8"));
|
|
9976
10736
|
return {
|
|
9977
10737
|
hasApiKey: Boolean(parsed.apiKey?.trim()),
|
|
9978
10738
|
runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
|
|
@@ -9991,7 +10751,10 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
9991
10751
|
kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
|
|
9992
10752
|
opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
|
|
9993
10753
|
kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0,
|
|
9994
|
-
openclawCronStorePath: Boolean(
|
|
10754
|
+
openclawCronStorePath: Boolean(
|
|
10755
|
+
process.env.KYNVER_CRON_STORE_PATH?.trim() || process.env.OPENCLAW_CRON_STORE_PATH?.trim()
|
|
10756
|
+
),
|
|
10757
|
+
kynverCronDaemonPrimary: isKynverCronDaemonPrimary(),
|
|
9995
10758
|
qstashTokenPresent: Boolean(process.env.QSTASH_TOKEN?.trim()),
|
|
9996
10759
|
kynverHostedDeployment: (() => {
|
|
9997
10760
|
const v = process.env.KYNVER_HOSTED_DEPLOYMENT?.trim().toLowerCase();
|
|
@@ -9999,8 +10762,8 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
9999
10762
|
})()
|
|
10000
10763
|
}),
|
|
10001
10764
|
harnessRoot: () => resolveHarnessRoot(),
|
|
10002
|
-
legacyOpenclawHarnessRoot: () =>
|
|
10003
|
-
pathExists: (target) =>
|
|
10765
|
+
legacyOpenclawHarnessRoot: () => path59.join(homedir14(), ".openclaw", "harness"),
|
|
10766
|
+
pathExists: (target) => existsSync40(target),
|
|
10004
10767
|
pathWritable: (target) => isWritable(target),
|
|
10005
10768
|
vercelVersion: () => captureCommand("vercel", ["--version"]),
|
|
10006
10769
|
vercelWhoami: () => captureCommand("vercel", ["whoami"])
|
|
@@ -10020,8 +10783,10 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
10020
10783
|
const schedulerDetails = {
|
|
10021
10784
|
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
10022
10785
|
deploymentSchedulerProvider: ctx.deploymentSchedulerProvider ?? null,
|
|
10023
|
-
openclawCronStorePath: Boolean(env.openclawCronStorePath)
|
|
10786
|
+
openclawCronStorePath: Boolean(env.openclawCronStorePath),
|
|
10787
|
+
kynverCronDaemonPrimary: Boolean(env.kynverCronDaemonPrimary)
|
|
10024
10788
|
};
|
|
10789
|
+
const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
|
|
10025
10790
|
if (hasQstashCutover(env, ctx) && !hasLocalOpenClawDependency(env, ctx)) {
|
|
10026
10791
|
const source = env.kynverSchedulerProvider === "qstash" ? "KYNVER_SCHEDULER_PROVIDER=qstash on this host" : "deploymentSchedulerProvider=qstash in ~/.kynver/config.json";
|
|
10027
10792
|
return check({
|
|
@@ -10033,6 +10798,19 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
10033
10798
|
});
|
|
10034
10799
|
}
|
|
10035
10800
|
if (hasLocalOpenClawDependency(env, ctx)) {
|
|
10801
|
+
if (env.kynverCronDaemonPrimary && daemonDispatchReady) {
|
|
10802
|
+
return check({
|
|
10803
|
+
id: "hotspot_openclaw_scheduler",
|
|
10804
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
10805
|
+
status: "pass",
|
|
10806
|
+
summary: "Kynver Cron local store present; `kynver daemon` owns schedule fires (kynver-cron tick loop)",
|
|
10807
|
+
details: {
|
|
10808
|
+
...schedulerDetails,
|
|
10809
|
+
dispatchPath: "kynver-daemon-cron-tick",
|
|
10810
|
+
kynverCronDaemonPrimary: true
|
|
10811
|
+
}
|
|
10812
|
+
});
|
|
10813
|
+
}
|
|
10036
10814
|
const parts = [];
|
|
10037
10815
|
if (env.kynverSchedulerProvider === "openclaw-cron") {
|
|
10038
10816
|
parts.push("KYNVER_SCHEDULER_PROVIDER=openclaw-cron");
|
|
@@ -10041,21 +10819,20 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
10041
10819
|
parts.push("deploymentSchedulerProvider=openclaw-cron in config");
|
|
10042
10820
|
}
|
|
10043
10821
|
if (env.openclawCronStorePath) {
|
|
10044
|
-
parts.push("OPENCLAW_CRON_STORE_PATH set (local cron
|
|
10822
|
+
parts.push("KYNVER_CRON_STORE_PATH or OPENCLAW_CRON_STORE_PATH set (local cron store)");
|
|
10045
10823
|
}
|
|
10046
10824
|
return check({
|
|
10047
10825
|
id: "hotspot_openclaw_scheduler",
|
|
10048
10826
|
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
10049
10827
|
status: "warn",
|
|
10050
|
-
summary: `
|
|
10051
|
-
remediation: "
|
|
10828
|
+
summary: `Local cron store without daemon tick (${parts.join("; ")})`,
|
|
10829
|
+
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.",
|
|
10052
10830
|
details: schedulerDetails
|
|
10053
10831
|
});
|
|
10054
10832
|
}
|
|
10055
10833
|
const runnerOpenclaw = env.kynverSchedulerProvider === "openclaw-cron";
|
|
10056
10834
|
const runnerQstash = env.kynverSchedulerProvider === "qstash";
|
|
10057
10835
|
const hostedDeployment = Boolean(env.qstashTokenPresent) || Boolean(env.kynverHostedDeployment);
|
|
10058
|
-
const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
|
|
10059
10836
|
const hostedSchedulerProcess = hostedDeployment && !daemonDispatchReady;
|
|
10060
10837
|
const deploymentNeedsQstash = hostedSchedulerProcess && !env.qstashTokenPresent && env.kynverSchedulerProvider !== "qstash";
|
|
10061
10838
|
const deploymentOpenclaw = hostedSchedulerProcess && env.kynverSchedulerProvider === "openclaw-cron";
|
|
@@ -10328,8 +11105,8 @@ function assessVercelCli(probes) {
|
|
|
10328
11105
|
}
|
|
10329
11106
|
function assessHarnessDirs(probes) {
|
|
10330
11107
|
const harnessRoot = probes.harnessRoot();
|
|
10331
|
-
const runsDir =
|
|
10332
|
-
const worktreesDir =
|
|
11108
|
+
const runsDir = path60.join(harnessRoot, "runs");
|
|
11109
|
+
const worktreesDir = path60.join(harnessRoot, "worktrees");
|
|
10333
11110
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
10334
11111
|
const displayRunsDir = redactHomePath(runsDir);
|
|
10335
11112
|
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
@@ -10532,6 +11309,11 @@ var RUNNER_SCHEDULER_CUTOVER_STEPS = [
|
|
|
10532
11309
|
function readSchedulerCutoverEnv(env = process.env) {
|
|
10533
11310
|
return {
|
|
10534
11311
|
kynverSchedulerProvider: env.KYNVER_SCHEDULER_PROVIDER?.trim() || null,
|
|
11312
|
+
kynverCronStorePath: env.KYNVER_CRON_STORE_PATH?.trim() || env.OPENCLAW_CRON_STORE_PATH?.trim() || null,
|
|
11313
|
+
kynverCronSecret: Boolean(
|
|
11314
|
+
env.KYNVER_CRON_SECRET?.trim() || env.OPENCLAW_CRON_SECRET?.trim()
|
|
11315
|
+
),
|
|
11316
|
+
kynverCronFireBaseUrl: env.KYNVER_CRON_FIRE_BASE_URL?.trim() || env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || null,
|
|
10535
11317
|
openclawCronStorePath: env.OPENCLAW_CRON_STORE_PATH?.trim() || null,
|
|
10536
11318
|
openclawCronSecret: Boolean(env.OPENCLAW_CRON_SECRET?.trim()),
|
|
10537
11319
|
openclawCronFireBaseUrl: env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || null
|
|
@@ -10542,8 +11324,13 @@ function assessSchedulerCutover(config, env = readSchedulerCutoverEnv()) {
|
|
|
10542
11324
|
if (env.kynverSchedulerProvider === "openclaw-cron") {
|
|
10543
11325
|
blockers.push("Runner still has KYNVER_SCHEDULER_PROVIDER=openclaw-cron");
|
|
10544
11326
|
}
|
|
10545
|
-
if (env.
|
|
10546
|
-
blockers.push(
|
|
11327
|
+
if (env.kynverCronStorePath && env.kynverSchedulerProvider !== "kynver-cron") {
|
|
11328
|
+
blockers.push(
|
|
11329
|
+
"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"
|
|
11330
|
+
);
|
|
11331
|
+
}
|
|
11332
|
+
if (env.openclawCronStorePath && !env.kynverCronStorePath) {
|
|
11333
|
+
blockers.push("Runner still has legacy OPENCLAW_CRON_STORE_PATH (prefer KYNVER_CRON_STORE_PATH)");
|
|
10547
11334
|
}
|
|
10548
11335
|
if (config.deploymentSchedulerProvider === "openclaw-cron") {
|
|
10549
11336
|
blockers.push("~/.kynver/config.json deploymentSchedulerProvider is still openclaw-cron");
|
|
@@ -10565,9 +11352,9 @@ function applySchedulerCutoverAttestation(config) {
|
|
|
10565
11352
|
}
|
|
10566
11353
|
|
|
10567
11354
|
// src/scheduler-cutover-cli.ts
|
|
10568
|
-
import
|
|
10569
|
-
import { homedir as
|
|
10570
|
-
var CONFIG_FILE2 =
|
|
11355
|
+
import path61 from "node:path";
|
|
11356
|
+
import { homedir as homedir15 } from "node:os";
|
|
11357
|
+
var CONFIG_FILE2 = path61.join(homedir15(), ".kynver", "config.json");
|
|
10571
11358
|
function runSchedulerCutoverCheckCli(json = false) {
|
|
10572
11359
|
const config = loadUserConfig();
|
|
10573
11360
|
const report = assessSchedulerCutover(config);
|
|
@@ -10595,7 +11382,10 @@ function runSchedulerCutoverCheckCli(json = false) {
|
|
|
10595
11382
|
` KYNVER_SCHEDULER_PROVIDER: ${report.runnerEnv.kynverSchedulerProvider ?? "(unset)"}`
|
|
10596
11383
|
);
|
|
10597
11384
|
console.log(
|
|
10598
|
-
`
|
|
11385
|
+
` KYNVER_CRON_STORE_PATH: ${report.runnerEnv.kynverCronStorePath ?? "(unset)"}`
|
|
11386
|
+
);
|
|
11387
|
+
console.log(
|
|
11388
|
+
` OPENCLAW_CRON_STORE_PATH (legacy): ${report.runnerEnv.openclawCronStorePath ?? "(unset)"}`
|
|
10599
11389
|
);
|
|
10600
11390
|
if (report.blockers.length) {
|
|
10601
11391
|
console.log("\nBlockers:");
|
|
@@ -10642,6 +11432,65 @@ function runSchedulerAttestCutoverCli(json = false) {
|
|
|
10642
11432
|
console.log(` config: ${payload.configPath}`);
|
|
10643
11433
|
}
|
|
10644
11434
|
|
|
11435
|
+
// src/cron/cron-status.ts
|
|
11436
|
+
async function buildKynverCronStatusReport(env = resolveKynverCronEnv()) {
|
|
11437
|
+
const jobs = await loadCronJobs(env.storePath).catch(() => []);
|
|
11438
|
+
const state = await loadCronTickState(env.statePath).catch(() => ({ version: 1, jobs: {} }));
|
|
11439
|
+
const credentialsReady = Boolean(env.fireBaseUrl && env.secret);
|
|
11440
|
+
const daemonPrimary = isKynverCronDaemonPrimary(env);
|
|
11441
|
+
let primary = "disabled";
|
|
11442
|
+
if (daemonPrimary) primary = "kynver-cron-daemon";
|
|
11443
|
+
else if (process.env.QSTASH_TOKEN?.trim()) primary = "qstash";
|
|
11444
|
+
return {
|
|
11445
|
+
primary,
|
|
11446
|
+
env: {
|
|
11447
|
+
storePath: env.storePath,
|
|
11448
|
+
statePath: env.statePath,
|
|
11449
|
+
tickEnabled: env.tickEnabled,
|
|
11450
|
+
fireBaseUrl: env.fireBaseUrl,
|
|
11451
|
+
missedRunPolicy: env.missedRunPolicy,
|
|
11452
|
+
maxCatchUpPerTick: env.maxCatchUpPerTick,
|
|
11453
|
+
maxRetries: env.maxRetries
|
|
11454
|
+
},
|
|
11455
|
+
jobCount: jobs.length,
|
|
11456
|
+
stateJobCount: Object.keys(state.jobs).length,
|
|
11457
|
+
credentialsReady,
|
|
11458
|
+
daemonPrimary
|
|
11459
|
+
};
|
|
11460
|
+
}
|
|
11461
|
+
|
|
11462
|
+
// src/cron/cron-tick-cli.ts
|
|
11463
|
+
async function runCronStatusCli(json) {
|
|
11464
|
+
const report = await buildKynverCronStatusReport();
|
|
11465
|
+
if (json) {
|
|
11466
|
+
console.log(JSON.stringify(report, null, 2));
|
|
11467
|
+
return;
|
|
11468
|
+
}
|
|
11469
|
+
console.log(`Kynver Cron primary: ${report.primary}`);
|
|
11470
|
+
console.log(` store: ${report.env.storePath} (${report.jobCount} jobs)`);
|
|
11471
|
+
console.log(` tick state: ${report.env.statePath} (${report.stateJobCount} tracked)`);
|
|
11472
|
+
console.log(` tick enabled: ${report.env.tickEnabled}`);
|
|
11473
|
+
console.log(` fire base URL: ${report.env.fireBaseUrl ?? "(unset)"}`);
|
|
11474
|
+
console.log(` credentials ready: ${report.credentialsReady}`);
|
|
11475
|
+
console.log(` daemon-owned primary: ${report.daemonPrimary}`);
|
|
11476
|
+
}
|
|
11477
|
+
async function runCronTickCli(args) {
|
|
11478
|
+
const agentOsId = typeof args.agentOsId === "string" ? args.agentOsId : void 0;
|
|
11479
|
+
const result = await runKynverCronTick({
|
|
11480
|
+
agentOsIdFilter: agentOsId ?? null
|
|
11481
|
+
});
|
|
11482
|
+
if (args.json === true) {
|
|
11483
|
+
console.log(JSON.stringify(result, null, 2));
|
|
11484
|
+
return;
|
|
11485
|
+
}
|
|
11486
|
+
console.log(
|
|
11487
|
+
JSON.stringify({
|
|
11488
|
+
event: "kynver_cron_tick",
|
|
11489
|
+
...result
|
|
11490
|
+
})
|
|
11491
|
+
);
|
|
11492
|
+
}
|
|
11493
|
+
|
|
10645
11494
|
// src/cli.ts
|
|
10646
11495
|
function isHelpFlag(arg) {
|
|
10647
11496
|
return arg === "help" || arg === "--help" || arg === "-h";
|
|
@@ -10692,6 +11541,8 @@ function usage(code = 0) {
|
|
|
10692
11541
|
" kynver doctor runtime-takeover",
|
|
10693
11542
|
" kynver scheduler cutover-check [--json]",
|
|
10694
11543
|
" kynver scheduler attest-cutover [--json]",
|
|
11544
|
+
" kynver cron status [--json]",
|
|
11545
|
+
" kynver cron tick [--agent-os-id AOS_ID] [--json]",
|
|
10695
11546
|
" kynver board contract [--agent-os-id ID] [--base-url URL] [--since ISO] [--limit N]"
|
|
10696
11547
|
].join("\n")
|
|
10697
11548
|
);
|
|
@@ -10703,7 +11554,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
10703
11554
|
const scope = argv.shift();
|
|
10704
11555
|
let action;
|
|
10705
11556
|
let rest;
|
|
10706
|
-
if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "harness" || scope === "monitor" || scope === "doctor" || scope === "scheduler" || scope === "board") {
|
|
11557
|
+
if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "harness" || scope === "monitor" || scope === "doctor" || scope === "scheduler" || scope === "cron" || scope === "board") {
|
|
10707
11558
|
action = argv.shift();
|
|
10708
11559
|
rest = argv;
|
|
10709
11560
|
} else {
|
|
@@ -10736,6 +11587,12 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
10736
11587
|
if (scope === "scheduler" && action === "attest-cutover") {
|
|
10737
11588
|
return runSchedulerAttestCutoverCli(args.json === true);
|
|
10738
11589
|
}
|
|
11590
|
+
if (scope === "cron" && action === "status") {
|
|
11591
|
+
return void await runCronStatusCli(args.json === true);
|
|
11592
|
+
}
|
|
11593
|
+
if (scope === "cron" && action === "tick") {
|
|
11594
|
+
return void await runCronTickCli(args);
|
|
11595
|
+
}
|
|
10739
11596
|
if (scope === "board" && action === "contract") {
|
|
10740
11597
|
return void await runCommandCenterContractCli(args);
|
|
10741
11598
|
}
|