@kynver-app/runtime 0.1.77 → 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 +1082 -302
- package/dist/cli.js.map +4 -4
- 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 +1119 -318
- package/dist/index.js.map +4 -4
- 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 +2 -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,
|
|
@@ -2580,7 +2580,9 @@ function resolveOrchestrationRouting(input) {
|
|
|
2580
2580
|
const risk = classifyOrchestrationRisk(task);
|
|
2581
2581
|
const inventory = resolveInventory({
|
|
2582
2582
|
inventory: input.inventory,
|
|
2583
|
-
|
|
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())
|
|
2584
2586
|
});
|
|
2585
2587
|
const explicit = input.preferLowCost === false ? null : input.explicitProvider?.trim().toLowerCase();
|
|
2586
2588
|
const explicitModel = input.explicitModel?.trim() || void 0;
|
|
@@ -3394,6 +3396,7 @@ function buildPrompt(input) {
|
|
|
3394
3396
|
"",
|
|
3395
3397
|
...input.personaMarkdown?.trim() ? [input.personaMarkdown.trim(), ""] : [],
|
|
3396
3398
|
...input.instructionPolicyMarkdown?.trim() ? ["Operating rules (Lane A \u2014 from AgentOS memory policy):", input.instructionPolicyMarkdown.trim(), ""] : [],
|
|
3399
|
+
...input.memoryQualityMarkdown?.trim() ? [input.memoryQualityMarkdown.trim(), ""] : [],
|
|
3397
3400
|
...input.repairTargetPrUrl ? [
|
|
3398
3401
|
...repairTargetPromptLines({
|
|
3399
3402
|
targetPrUrl: input.repairTargetPrUrl,
|
|
@@ -4188,8 +4191,8 @@ function dirtyPathsCoveredByDisposableRemoval(changedFiles, removed) {
|
|
|
4188
4191
|
if (removed.length === 0) return false;
|
|
4189
4192
|
const removedSet = new Set(removed.map((p) => normalizeRelativePath(p)));
|
|
4190
4193
|
return material.every((line) => {
|
|
4191
|
-
const
|
|
4192
|
-
return removedSet.has(
|
|
4194
|
+
const path62 = normalizeRelativePath(pathFromGitStatusLine(line));
|
|
4195
|
+
return removedSet.has(path62);
|
|
4193
4196
|
});
|
|
4194
4197
|
}
|
|
4195
4198
|
|
|
@@ -4611,7 +4614,9 @@ async function completeWorker(args) {
|
|
|
4611
4614
|
}
|
|
4612
4615
|
}
|
|
4613
4616
|
function workerStatus(args) {
|
|
4614
|
-
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);
|
|
4615
4620
|
const run = loadRun(worker.runId);
|
|
4616
4621
|
const status = computeWorkerStatus(worker, workerStatusOptions(run));
|
|
4617
4622
|
writeJson(path17.join(worker.workerDir, "last-status.json"), status);
|
|
@@ -5046,6 +5051,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
5046
5051
|
planId: opts.planId,
|
|
5047
5052
|
taskId: opts.taskId,
|
|
5048
5053
|
instructionPolicyMarkdown: opts.instructionPolicyMarkdown,
|
|
5054
|
+
memoryQualityMarkdown: opts.memoryQualityPromptMarkdown ?? opts.memoryQualityCapture?.promptMarkdown ?? null,
|
|
5049
5055
|
personaMarkdown: opts.personaMarkdown,
|
|
5050
5056
|
model: launchModel,
|
|
5051
5057
|
repairTargetPrUrl: opts.repairTargetPrUrl,
|
|
@@ -5824,11 +5830,13 @@ function readHarnessWorkerContext(decision) {
|
|
|
5824
5830
|
const personaEvidence = ctx.personaEvidence && typeof ctx.personaEvidence === "object" ? ctx.personaEvidence : null;
|
|
5825
5831
|
const personaInjectionReady = ctx.personaInjectionReady === true;
|
|
5826
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;
|
|
5827
5834
|
return {
|
|
5828
5835
|
instructionPolicyMarkdown: markdown,
|
|
5829
5836
|
instructionPolicyFingerprint: fingerprint,
|
|
5830
5837
|
instructionPolicyEvidence: evidence,
|
|
5831
5838
|
memoryQualityCapture,
|
|
5839
|
+
memoryQualityPromptMarkdown,
|
|
5832
5840
|
personaMarkdown,
|
|
5833
5841
|
personaSlug,
|
|
5834
5842
|
personaEvidence,
|
|
@@ -6085,6 +6093,7 @@ async function dispatchRun(args) {
|
|
|
6085
6093
|
instructionPolicyFingerprint: harnessContext?.instructionPolicyFingerprint ?? null,
|
|
6086
6094
|
instructionPolicyEvidence: harnessContext?.instructionPolicyEvidence ?? null,
|
|
6087
6095
|
memoryQualityCapture: harnessContext?.memoryQualityCapture ?? null,
|
|
6096
|
+
memoryQualityPromptMarkdown: harnessContext?.memoryQualityPromptMarkdown ?? null,
|
|
6088
6097
|
personaMarkdown: harnessContext?.personaMarkdown ?? null,
|
|
6089
6098
|
personaSlug: harnessContext?.personaSlug ?? expectedPersona,
|
|
6090
6099
|
personaEvidence: harnessContext?.personaEvidence ?? null,
|
|
@@ -6268,15 +6277,15 @@ async function sweepRun(args) {
|
|
|
6268
6277
|
}
|
|
6269
6278
|
|
|
6270
6279
|
// src/worktree.ts
|
|
6271
|
-
import { existsSync as
|
|
6272
|
-
import
|
|
6280
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync5 } from "node:fs";
|
|
6281
|
+
import path33 from "node:path";
|
|
6273
6282
|
|
|
6274
6283
|
// src/run-list.ts
|
|
6275
|
-
import { existsSync as
|
|
6276
|
-
import
|
|
6284
|
+
import { existsSync as existsSync23, readFileSync as readFileSync10 } from "node:fs";
|
|
6285
|
+
import path31 from "node:path";
|
|
6277
6286
|
|
|
6278
6287
|
// src/stale-reconcile.ts
|
|
6279
|
-
import
|
|
6288
|
+
import path30 from "node:path";
|
|
6280
6289
|
|
|
6281
6290
|
// src/finalize.ts
|
|
6282
6291
|
import path25 from "node:path";
|
|
@@ -6337,8 +6346,8 @@ function finalizeStaleRuns() {
|
|
|
6337
6346
|
}
|
|
6338
6347
|
|
|
6339
6348
|
// src/worker-metadata-reconcile.ts
|
|
6340
|
-
import { existsSync as
|
|
6341
|
-
import
|
|
6349
|
+
import { existsSync as existsSync22, lstatSync, readdirSync as readdirSync6, readlinkSync, renameSync as renameSync2, rmSync } from "node:fs";
|
|
6350
|
+
import path29 from "node:path";
|
|
6342
6351
|
|
|
6343
6352
|
// src/worker-metadata-paths.ts
|
|
6344
6353
|
import path26 from "node:path";
|
|
@@ -6383,6 +6392,192 @@ function resolveWorkerJsonPath(input) {
|
|
|
6383
6392
|
return canonical;
|
|
6384
6393
|
}
|
|
6385
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
|
+
|
|
6386
6581
|
// src/worker-metadata-reconcile.ts
|
|
6387
6582
|
function materializeSymlinkedRunDir(harnessRoot, runId) {
|
|
6388
6583
|
const canonical = canonicalRunDir(harnessRoot, runId);
|
|
@@ -6393,7 +6588,10 @@ function materializeSymlinkedRunDir(harnessRoot, runId) {
|
|
|
6393
6588
|
return null;
|
|
6394
6589
|
}
|
|
6395
6590
|
if (!stat.isSymbolicLink()) return null;
|
|
6396
|
-
const linkedTarget =
|
|
6591
|
+
const linkedTarget = path29.resolve(path29.dirname(canonical), readlinkSync(canonical));
|
|
6592
|
+
if (runDirHasActiveRetentionSignals(linkedTarget) || runDirHasActiveRetentionSignals(canonical)) {
|
|
6593
|
+
return null;
|
|
6594
|
+
}
|
|
6397
6595
|
const staging = `${canonical}.materialize-${Date.now()}`;
|
|
6398
6596
|
renameSync2(linkedTarget, staging);
|
|
6399
6597
|
rmSync(canonical);
|
|
@@ -6403,9 +6601,9 @@ function materializeSymlinkedRunDir(harnessRoot, runId) {
|
|
|
6403
6601
|
function relocateArtifacts(input) {
|
|
6404
6602
|
const moved = [];
|
|
6405
6603
|
for (const fileName of workerArtifactFileNames()) {
|
|
6406
|
-
const from =
|
|
6407
|
-
const to =
|
|
6408
|
-
if (!
|
|
6604
|
+
const from = path29.join(input.fromDir, fileName);
|
|
6605
|
+
const to = path29.join(input.toDir, fileName);
|
|
6606
|
+
if (!existsSync22(from) || existsSync22(to)) continue;
|
|
6409
6607
|
renameSync2(from, to);
|
|
6410
6608
|
moved.push(fileName);
|
|
6411
6609
|
}
|
|
@@ -6444,8 +6642,8 @@ function deriveSynthesizedStatus(input) {
|
|
|
6444
6642
|
function synthesizeWorkerFromArtifacts(input) {
|
|
6445
6643
|
const artifacts = workerArtifactPaths(input.canonicalDir);
|
|
6446
6644
|
const lastStatus = readJson(artifacts.lastStatusPath, void 0);
|
|
6447
|
-
const stdoutExists =
|
|
6448
|
-
const heartbeatExists =
|
|
6645
|
+
const stdoutExists = existsSync22(artifacts.stdoutPath);
|
|
6646
|
+
const heartbeatExists = existsSync22(artifacts.heartbeatPath);
|
|
6449
6647
|
const hasArtifacts = stdoutExists || heartbeatExists || lastStatus != null;
|
|
6450
6648
|
if (!hasArtifacts) return null;
|
|
6451
6649
|
const parsedStdout = stdoutExists ? parseHarnessStream(artifacts.stdoutPath) : { finalResult: null };
|
|
@@ -6461,7 +6659,7 @@ function synthesizeWorkerFromArtifacts(input) {
|
|
|
6461
6659
|
runId: input.run.id,
|
|
6462
6660
|
status,
|
|
6463
6661
|
branch: typeof lastStatus?.branch === "string" ? lastStatus.branch : `agent/${input.run.id}/${input.workerName}`,
|
|
6464
|
-
worktreePath: typeof lastStatus?.worktreePath === "string" ? lastStatus.worktreePath :
|
|
6662
|
+
worktreePath: typeof lastStatus?.worktreePath === "string" ? lastStatus.worktreePath : path29.join(getPaths().worktreesDir, input.run.id, input.workerName),
|
|
6465
6663
|
workerDir: input.canonicalDir,
|
|
6466
6664
|
stdoutPath: artifacts.stdoutPath,
|
|
6467
6665
|
stderrPath: artifacts.stderrPath,
|
|
@@ -6488,7 +6686,7 @@ function repairRunWorkerIndex(run, harnessRoot) {
|
|
|
6488
6686
|
const nextWorkers = { ...run.workers || {} };
|
|
6489
6687
|
for (const [name, ref] of Object.entries(nextWorkers)) {
|
|
6490
6688
|
const canonicalDir = canonicalWorkerDir(harnessRoot, run.id, name);
|
|
6491
|
-
const canonicalStatus =
|
|
6689
|
+
const canonicalStatus = path29.join(canonicalDir, "worker.json");
|
|
6492
6690
|
const nested = hasNestedRunsSegment(ref.workerDir) || hasNestedRunsSegment(ref.statusPath) || ref.workerDir !== canonicalDir || ref.statusPath !== canonicalStatus;
|
|
6493
6691
|
if (!nested) continue;
|
|
6494
6692
|
nextWorkers[name] = { workerDir: canonicalDir, statusPath: canonicalStatus };
|
|
@@ -6521,7 +6719,7 @@ function reconcileOneWorker(input) {
|
|
|
6521
6719
|
statusPath: input.statusPath
|
|
6522
6720
|
});
|
|
6523
6721
|
let worker = readJson(workerJsonPath, void 0);
|
|
6524
|
-
if (!worker &&
|
|
6722
|
+
if (!worker && existsSync22(canonicalArtifacts.workerJsonPath)) {
|
|
6525
6723
|
worker = readJson(canonicalArtifacts.workerJsonPath, void 0);
|
|
6526
6724
|
}
|
|
6527
6725
|
if (!worker) {
|
|
@@ -6541,15 +6739,15 @@ function reconcileOneWorker(input) {
|
|
|
6541
6739
|
});
|
|
6542
6740
|
return outcomes;
|
|
6543
6741
|
}
|
|
6544
|
-
const dirExists =
|
|
6545
|
-
const hasAnyArtifact = workerArtifactFileNames().some((f) =>
|
|
6742
|
+
const dirExists = existsSync22(canonicalDir);
|
|
6743
|
+
const hasAnyArtifact = workerArtifactFileNames().some((f) => existsSync22(path29.join(canonicalDir, f)));
|
|
6546
6744
|
if (dirExists && !hasAnyArtifact) {
|
|
6547
6745
|
const abandoned = {
|
|
6548
6746
|
name: input.workerName,
|
|
6549
6747
|
runId: input.run.id,
|
|
6550
6748
|
status: "exited",
|
|
6551
6749
|
branch: `agent/${input.run.id}/${input.workerName}`,
|
|
6552
|
-
worktreePath:
|
|
6750
|
+
worktreePath: path29.join(getPaths().worktreesDir, input.run.id, input.workerName),
|
|
6553
6751
|
workerDir: canonicalDir,
|
|
6554
6752
|
stdoutPath: canonicalArtifacts.stdoutPath,
|
|
6555
6753
|
stderrPath: canonicalArtifacts.stderrPath,
|
|
@@ -6596,11 +6794,11 @@ function reconcileOneWorker(input) {
|
|
|
6596
6794
|
return outcomes;
|
|
6597
6795
|
}
|
|
6598
6796
|
function listOrphanWorkerNames(run, harnessRoot) {
|
|
6599
|
-
const workersDir =
|
|
6600
|
-
if (!
|
|
6797
|
+
const workersDir = path29.join(runDirectory(run.id), "workers");
|
|
6798
|
+
if (!existsSync22(workersDir)) return [];
|
|
6601
6799
|
const indexed = new Set(Object.keys(run.workers || {}));
|
|
6602
6800
|
const orphans = [];
|
|
6603
|
-
for (const entry of
|
|
6801
|
+
for (const entry of readdirSync6(workersDir, { withFileTypes: true })) {
|
|
6604
6802
|
if (!entry.isDirectory()) continue;
|
|
6605
6803
|
if (indexed.has(entry.name)) continue;
|
|
6606
6804
|
orphans.push(entry.name);
|
|
@@ -6609,6 +6807,7 @@ function listOrphanWorkerNames(run, harnessRoot) {
|
|
|
6609
6807
|
}
|
|
6610
6808
|
function reconcileWorkerMetadata() {
|
|
6611
6809
|
const { harnessRoot } = getPaths();
|
|
6810
|
+
const runMetadataRetention = repairMissingRunMetadata(harnessRoot);
|
|
6612
6811
|
const outcomes = [];
|
|
6613
6812
|
for (const run of listRunRecords()) {
|
|
6614
6813
|
const materializedFrom = materializeSymlinkedRunDir(harnessRoot, run.id);
|
|
@@ -6635,7 +6834,7 @@ function reconcileWorkerMetadata() {
|
|
|
6635
6834
|
...run.workers || {},
|
|
6636
6835
|
[orphan]: {
|
|
6637
6836
|
workerDir: canonicalWorkerDir(harnessRoot, run.id, orphan),
|
|
6638
|
-
statusPath:
|
|
6837
|
+
statusPath: path29.join(canonicalWorkerDir(harnessRoot, run.id, orphan), "worker.json")
|
|
6639
6838
|
}
|
|
6640
6839
|
};
|
|
6641
6840
|
saveRun(run);
|
|
@@ -6658,7 +6857,7 @@ function reconcileWorkerMetadata() {
|
|
|
6658
6857
|
);
|
|
6659
6858
|
}
|
|
6660
6859
|
}
|
|
6661
|
-
return { workers: outcomes };
|
|
6860
|
+
return { workers: outcomes, runMetadataRetention };
|
|
6662
6861
|
}
|
|
6663
6862
|
|
|
6664
6863
|
// src/stale-reconcile.ts
|
|
@@ -6675,7 +6874,7 @@ function reconcileStaleWorkers() {
|
|
|
6675
6874
|
const now = Date.now();
|
|
6676
6875
|
for (const run of listRunRecords()) {
|
|
6677
6876
|
for (const name of Object.keys(run.workers || {})) {
|
|
6678
|
-
const workerPath =
|
|
6877
|
+
const workerPath = path30.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
6679
6878
|
const worker = readJson(workerPath, void 0);
|
|
6680
6879
|
if (!worker || worker.status !== "running") {
|
|
6681
6880
|
outcomes.push({
|
|
@@ -6758,16 +6957,28 @@ function reconcileRunsCli() {
|
|
|
6758
6957
|
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
6759
6958
|
return acc;
|
|
6760
6959
|
}, {});
|
|
6960
|
+
const runRetentionTotals = result.metadataReconcile.runMetadataRetention.runs.reduce((acc, row) => {
|
|
6961
|
+
acc[row.action] = (acc[row.action] ?? 0) + 1;
|
|
6962
|
+
return acc;
|
|
6963
|
+
}, {});
|
|
6761
6964
|
console.log(
|
|
6762
6965
|
JSON.stringify(
|
|
6763
6966
|
{
|
|
6764
6967
|
ok: true,
|
|
6765
6968
|
workers: { markedExited, killedStale, skipped, total: result.workers.length },
|
|
6766
|
-
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
|
+
},
|
|
6767
6977
|
finalizedRuns: result.finalizedRuns.length,
|
|
6768
6978
|
details: {
|
|
6769
6979
|
workers: result.workers,
|
|
6770
6980
|
metadataReconcile: result.metadataReconcile.workers,
|
|
6981
|
+
runMetadataRetention: result.metadataReconcile.runMetadataRetention.runs,
|
|
6771
6982
|
finalizedRuns: result.finalizedRuns
|
|
6772
6983
|
}
|
|
6773
6984
|
},
|
|
@@ -6779,7 +6990,7 @@ function reconcileRunsCli() {
|
|
|
6779
6990
|
|
|
6780
6991
|
// src/run-list.ts
|
|
6781
6992
|
function heartbeatByteLength(heartbeatPath) {
|
|
6782
|
-
if (!heartbeatPath || !
|
|
6993
|
+
if (!heartbeatPath || !existsSync23(heartbeatPath)) return 0;
|
|
6783
6994
|
try {
|
|
6784
6995
|
return readFileSync10(heartbeatPath, "utf8").trim().length;
|
|
6785
6996
|
} catch {
|
|
@@ -6787,7 +6998,7 @@ function heartbeatByteLength(heartbeatPath) {
|
|
|
6787
6998
|
}
|
|
6788
6999
|
}
|
|
6789
7000
|
function workerEvidence(run, workerName) {
|
|
6790
|
-
const workerPath =
|
|
7001
|
+
const workerPath = path31.join(runDirectory(run.id), "workers", safeSlug(workerName), "worker.json");
|
|
6791
7002
|
const worker = readJson(workerPath, void 0);
|
|
6792
7003
|
if (!worker) {
|
|
6793
7004
|
return {
|
|
@@ -6844,7 +7055,7 @@ function aggregateRunAttention(workers) {
|
|
|
6844
7055
|
function countOpenWorkers(run) {
|
|
6845
7056
|
let open = 0;
|
|
6846
7057
|
for (const name of Object.keys(run.workers || {})) {
|
|
6847
|
-
const workerPath =
|
|
7058
|
+
const workerPath = path31.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
6848
7059
|
const worker = readJson(workerPath, void 0);
|
|
6849
7060
|
if (!worker) continue;
|
|
6850
7061
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
@@ -6892,50 +7103,8 @@ function listRunsCli() {
|
|
|
6892
7103
|
console.log(JSON.stringify(buildRunListRows(), null, 2));
|
|
6893
7104
|
}
|
|
6894
7105
|
|
|
6895
|
-
// src/default-repo.ts
|
|
6896
|
-
import path30 from "node:path";
|
|
6897
|
-
function expandConfiguredRepo(value) {
|
|
6898
|
-
return path30.resolve(resolveUserPath(value.trim()));
|
|
6899
|
-
}
|
|
6900
|
-
function fromConfigured(value, source, persistedInConfig) {
|
|
6901
|
-
const trimmed = value?.trim();
|
|
6902
|
-
if (!trimmed) return null;
|
|
6903
|
-
return {
|
|
6904
|
-
repo: expandConfiguredRepo(trimmed),
|
|
6905
|
-
source,
|
|
6906
|
-
persistedInConfig
|
|
6907
|
-
};
|
|
6908
|
-
}
|
|
6909
|
-
function resolveDefaultRepo(opts = {}) {
|
|
6910
|
-
const env = opts.env ?? process.env;
|
|
6911
|
-
const config = opts.config ?? loadUserConfig();
|
|
6912
|
-
const fromConfig = fromConfigured(config.defaultRepo, "config", true);
|
|
6913
|
-
if (fromConfig) return fromConfig;
|
|
6914
|
-
const fromDefaultEnv = fromConfigured(env.KYNVER_DEFAULT_REPO, "env_default_repo", false);
|
|
6915
|
-
if (fromDefaultEnv) return fromDefaultEnv;
|
|
6916
|
-
const fromHarnessEnv = fromConfigured(env.KYNVER_HARNESS_REPO, "env_harness_repo", false);
|
|
6917
|
-
if (fromHarnessEnv) return fromHarnessEnv;
|
|
6918
|
-
const discovered = discoverDefaultRepo({
|
|
6919
|
-
cwd: opts.cwd,
|
|
6920
|
-
runtimeModuleUrl: opts.runtimeModuleUrl
|
|
6921
|
-
});
|
|
6922
|
-
if (!discovered) return null;
|
|
6923
|
-
return {
|
|
6924
|
-
repo: discovered.repo,
|
|
6925
|
-
source: discovered.source,
|
|
6926
|
-
persistedInConfig: false
|
|
6927
|
-
};
|
|
6928
|
-
}
|
|
6929
|
-
function formatResolvedDefaultRepo(resolved) {
|
|
6930
|
-
return {
|
|
6931
|
-
defaultRepo: displayUserPath(resolved.repo),
|
|
6932
|
-
source: resolved.source,
|
|
6933
|
-
persistedInConfig: resolved.persistedInConfig
|
|
6934
|
-
};
|
|
6935
|
-
}
|
|
6936
|
-
|
|
6937
7106
|
// src/validate.ts
|
|
6938
|
-
import
|
|
7107
|
+
import path32 from "node:path";
|
|
6939
7108
|
var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
6940
7109
|
function validateRunId(runId) {
|
|
6941
7110
|
const trimmed = runId.trim();
|
|
@@ -6943,7 +7112,7 @@ function validateRunId(runId) {
|
|
|
6943
7112
|
return trimmed;
|
|
6944
7113
|
}
|
|
6945
7114
|
function validateRepo(repo) {
|
|
6946
|
-
const resolved =
|
|
7115
|
+
const resolved = path32.resolve(repo);
|
|
6947
7116
|
if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
|
|
6948
7117
|
return resolved;
|
|
6949
7118
|
}
|
|
@@ -6962,7 +7131,7 @@ function createRun(args) {
|
|
|
6962
7131
|
ensureGitRepo(repo);
|
|
6963
7132
|
const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
|
|
6964
7133
|
const dir = runDirectory(id);
|
|
6965
|
-
if (
|
|
7134
|
+
if (existsSync24(dir)) failExists(`run already exists: ${id}`);
|
|
6966
7135
|
mkdirSync5(dir, { recursive: true });
|
|
6967
7136
|
const base = String(args.base || "origin/main");
|
|
6968
7137
|
const baseCommit = git(repo, ["rev-parse", base]).trim();
|
|
@@ -6976,7 +7145,7 @@ function createRun(args) {
|
|
|
6976
7145
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6977
7146
|
workers: {}
|
|
6978
7147
|
};
|
|
6979
|
-
writeJson(
|
|
7148
|
+
writeJson(path33.join(dir, "run.json"), run);
|
|
6980
7149
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
6981
7150
|
}
|
|
6982
7151
|
function listRuns() {
|
|
@@ -6988,8 +7157,8 @@ function failExists(message) {
|
|
|
6988
7157
|
}
|
|
6989
7158
|
|
|
6990
7159
|
// src/discard-disposable.ts
|
|
6991
|
-
import { existsSync as
|
|
6992
|
-
import
|
|
7160
|
+
import { existsSync as existsSync25, rmSync as rmSync2 } from "node:fs";
|
|
7161
|
+
import path34 from "node:path";
|
|
6993
7162
|
function normalizeRelativePath2(value) {
|
|
6994
7163
|
const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
6995
7164
|
if (!normalized || normalized.startsWith("/") || normalized.includes("..")) {
|
|
@@ -7010,15 +7179,15 @@ function discardDisposableArtifacts(args) {
|
|
|
7010
7179
|
if (paths.length === 0) {
|
|
7011
7180
|
return { ok: false, removed: [], reason: "requires at least one --path" };
|
|
7012
7181
|
}
|
|
7013
|
-
const worktreeRoot =
|
|
7182
|
+
const worktreeRoot = path34.resolve(worker.worktreePath);
|
|
7014
7183
|
const removed = [];
|
|
7015
7184
|
for (const raw of paths) {
|
|
7016
7185
|
const rel = normalizeRelativePath2(raw);
|
|
7017
|
-
const abs =
|
|
7018
|
-
if (!abs.startsWith(worktreeRoot +
|
|
7186
|
+
const abs = path34.resolve(worktreeRoot, rel);
|
|
7187
|
+
if (!abs.startsWith(worktreeRoot + path34.sep) && abs !== worktreeRoot) {
|
|
7019
7188
|
return { ok: false, removed, reason: `path escapes worktree: ${raw}` };
|
|
7020
7189
|
}
|
|
7021
|
-
if (!
|
|
7190
|
+
if (!existsSync25(abs)) {
|
|
7022
7191
|
return { ok: false, removed, reason: `path not found: ${raw}` };
|
|
7023
7192
|
}
|
|
7024
7193
|
rmSync2(abs, { recursive: true, force: true });
|
|
@@ -7040,8 +7209,472 @@ function discardDisposableCli(args) {
|
|
|
7040
7209
|
if (!result.ok) process.exit(1);
|
|
7041
7210
|
}
|
|
7042
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
|
+
|
|
7043
7676
|
// src/pipeline-tick.ts
|
|
7044
|
-
import
|
|
7677
|
+
import path52 from "node:path";
|
|
7045
7678
|
|
|
7046
7679
|
// src/pipeline-dispatch.ts
|
|
7047
7680
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -7131,24 +7764,43 @@ function operatorDispatchFromTick(operatorTick) {
|
|
|
7131
7764
|
const dispatch = body.response?.dispatch;
|
|
7132
7765
|
return dispatch && typeof dispatch === "object" ? dispatch : null;
|
|
7133
7766
|
}
|
|
7767
|
+
function nonNegativeInt(value) {
|
|
7768
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return null;
|
|
7769
|
+
return Math.max(0, Math.floor(value));
|
|
7770
|
+
}
|
|
7134
7771
|
function resolvePipelineMaxStarts(resourceGate, operatorTick) {
|
|
7135
7772
|
const dispatch = operatorDispatchFromTick(operatorTick);
|
|
7136
|
-
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;
|
|
7137
7780
|
let maxStarts = resourceGate.slotsAvailable;
|
|
7138
|
-
if (
|
|
7781
|
+
if (readyFloor !== null) {
|
|
7782
|
+
maxStarts = Math.min(maxStarts, readyFloor);
|
|
7783
|
+
} else if (advised !== null) {
|
|
7139
7784
|
maxStarts = Math.min(maxStarts, advised);
|
|
7140
7785
|
}
|
|
7141
|
-
|
|
7142
|
-
|
|
7143
|
-
|
|
7144
|
-
|
|
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);
|
|
7145
7792
|
maxStarts = Math.min(resourceGate.slotsAvailable, Math.max(1, ready));
|
|
7146
7793
|
}
|
|
7794
|
+
const nonDispatchableReady = queuedTasks !== null && actionableReady !== null ? Math.max(0, queuedTasks - actionableReady) : null;
|
|
7147
7795
|
return {
|
|
7148
7796
|
maxStarts: Math.max(0, maxStarts),
|
|
7149
7797
|
underutilized,
|
|
7150
7798
|
advisedStarts: advised,
|
|
7151
|
-
|
|
7799
|
+
actionableReady,
|
|
7800
|
+
queuedTasks,
|
|
7801
|
+
nonDispatchableReady,
|
|
7802
|
+
boardAdvancedThisTick,
|
|
7803
|
+
leaseReapedThisTick
|
|
7152
7804
|
};
|
|
7153
7805
|
}
|
|
7154
7806
|
|
|
@@ -7192,7 +7844,7 @@ function buildBoxResourceSnapshotFromGate(gate, input = {}) {
|
|
|
7192
7844
|
}
|
|
7193
7845
|
|
|
7194
7846
|
// src/plan-progress-daemon-sync.ts
|
|
7195
|
-
import
|
|
7847
|
+
import path37 from "node:path";
|
|
7196
7848
|
|
|
7197
7849
|
// src/plan-progress-sync.ts
|
|
7198
7850
|
async function syncPlanProgress(args) {
|
|
@@ -7216,7 +7868,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
7216
7868
|
const outcomes = [];
|
|
7217
7869
|
for (const name of Object.keys(run.workers || {})) {
|
|
7218
7870
|
const worker = readJson(
|
|
7219
|
-
|
|
7871
|
+
path37.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
7220
7872
|
void 0
|
|
7221
7873
|
);
|
|
7222
7874
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -7265,10 +7917,10 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
7265
7917
|
}
|
|
7266
7918
|
|
|
7267
7919
|
// src/cleanup.ts
|
|
7268
|
-
import
|
|
7920
|
+
import path50 from "node:path";
|
|
7269
7921
|
|
|
7270
7922
|
// src/cleanup-guards.ts
|
|
7271
|
-
import
|
|
7923
|
+
import path38 from "node:path";
|
|
7272
7924
|
|
|
7273
7925
|
// src/cleanup-run-liveness.ts
|
|
7274
7926
|
function isWorkerProcessLive(indexed) {
|
|
@@ -7392,7 +8044,7 @@ function skipWorktreeRemoval(input) {
|
|
|
7392
8044
|
function skipDependencyCacheRemoval(input) {
|
|
7393
8045
|
const { indexed, nodeModulesAgeMs, ageMs, worktreePath, activeWorktreePaths, diskPressure } = input;
|
|
7394
8046
|
if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
|
|
7395
|
-
if (activeWorktreePaths.has(
|
|
8047
|
+
if (activeWorktreePaths.has(path38.resolve(worktreePath))) return "active_worker";
|
|
7396
8048
|
if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
|
|
7397
8049
|
if (indexed && hasUnrestorableWorktreeChanges(indexed.status)) return "dirty_worktree";
|
|
7398
8050
|
return null;
|
|
@@ -7419,11 +8071,11 @@ var LIVE_SKIP_REASONS = /* @__PURE__ */ new Set([
|
|
|
7419
8071
|
function collectPreservedLivePaths(actions, skips) {
|
|
7420
8072
|
const out = [];
|
|
7421
8073
|
const seen = /* @__PURE__ */ new Set();
|
|
7422
|
-
const push = (
|
|
7423
|
-
const key = `${
|
|
8074
|
+
const push = (path62, reason, detail) => {
|
|
8075
|
+
const key = `${path62}\0${reason}`;
|
|
7424
8076
|
if (seen.has(key) || out.length >= MAX_PRESERVED_LIVE_PATH_SAMPLES) return;
|
|
7425
8077
|
seen.add(key);
|
|
7426
|
-
out.push({ path:
|
|
8078
|
+
out.push({ path: path62, reason, ...detail ? { detail } : {} });
|
|
7427
8079
|
};
|
|
7428
8080
|
for (const skip2 of skips) {
|
|
7429
8081
|
if (!LIVE_SKIP_REASONS.has(skip2.reason)) continue;
|
|
@@ -7438,11 +8090,11 @@ function collectPreservedLivePaths(actions, skips) {
|
|
|
7438
8090
|
}
|
|
7439
8091
|
|
|
7440
8092
|
// src/cleanup-run-directory.ts
|
|
7441
|
-
import { existsSync as
|
|
7442
|
-
import
|
|
8093
|
+
import { existsSync as existsSync28, readdirSync as readdirSync7, statSync as statSync5 } from "node:fs";
|
|
8094
|
+
import path40 from "node:path";
|
|
7443
8095
|
|
|
7444
8096
|
// src/cleanup-active-worktrees.ts
|
|
7445
|
-
import
|
|
8097
|
+
import path39 from "node:path";
|
|
7446
8098
|
function isActiveHarnessWorker2(worker, runBase, runBaseCommit) {
|
|
7447
8099
|
const status = computeWorkerStatus(worker, { base: runBase, baseCommit: runBaseCommit });
|
|
7448
8100
|
return status.alive && !status.finalResult && status.attention.state !== "done";
|
|
@@ -7455,17 +8107,20 @@ function collectActiveWorktreeGuards(harnessRoots) {
|
|
|
7455
8107
|
let runHasLive = false;
|
|
7456
8108
|
for (const name of Object.keys(run.workers || {})) {
|
|
7457
8109
|
const worker = readJson(
|
|
7458
|
-
|
|
8110
|
+
path39.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
|
|
7459
8111
|
void 0
|
|
7460
8112
|
);
|
|
7461
8113
|
if (!worker?.worktreePath) continue;
|
|
7462
|
-
const worktreePath =
|
|
8114
|
+
const worktreePath = path39.resolve(worker.worktreePath);
|
|
7463
8115
|
if (!isActiveHarnessWorker2(worker, run.base, run.baseCommit)) continue;
|
|
7464
8116
|
runHasLive = true;
|
|
7465
8117
|
activeWorktreePaths.add(worktreePath);
|
|
7466
8118
|
}
|
|
7467
8119
|
if (runHasLive) liveRunKeys.add(`${harnessRoot}\0${run.id}`);
|
|
7468
8120
|
}
|
|
8121
|
+
for (const key of collectFilesystemLiveRunKeys(harnessRoot)) {
|
|
8122
|
+
liveRunKeys.add(key);
|
|
8123
|
+
}
|
|
7469
8124
|
}
|
|
7470
8125
|
return { activeWorktreePaths, liveRunKeys };
|
|
7471
8126
|
}
|
|
@@ -7477,20 +8132,20 @@ function isWorktreeOnLiveRun(worktreePath, harnessRoot, runId, liveRunKeys) {
|
|
|
7477
8132
|
// src/cleanup-run-directory.ts
|
|
7478
8133
|
function pathAgeMs(target, now) {
|
|
7479
8134
|
try {
|
|
7480
|
-
const mtime =
|
|
8135
|
+
const mtime = statSync5(target).mtimeMs;
|
|
7481
8136
|
return Math.max(0, now - mtime);
|
|
7482
8137
|
} catch {
|
|
7483
8138
|
return 0;
|
|
7484
8139
|
}
|
|
7485
8140
|
}
|
|
7486
8141
|
function loadRunStatus(harnessRoot, runId) {
|
|
7487
|
-
const runPath =
|
|
7488
|
-
if (!
|
|
8142
|
+
const runPath = path40.join(harnessRoot, "runs", runId, "run.json");
|
|
8143
|
+
if (!existsSync28(runPath)) return null;
|
|
7489
8144
|
return readJson(runPath, null);
|
|
7490
8145
|
}
|
|
7491
8146
|
function runDirectoryIsEmpty(runPath) {
|
|
7492
8147
|
try {
|
|
7493
|
-
const entries =
|
|
8148
|
+
const entries = readdirSync7(runPath);
|
|
7494
8149
|
return entries.length === 0;
|
|
7495
8150
|
} catch {
|
|
7496
8151
|
return false;
|
|
@@ -7508,11 +8163,11 @@ function skipRunDirectoryRemoval(input) {
|
|
|
7508
8163
|
return null;
|
|
7509
8164
|
}
|
|
7510
8165
|
function scanStaleRunDirectoryCandidates(opts) {
|
|
7511
|
-
if (!
|
|
8166
|
+
if (!existsSync28(opts.worktreesDir)) return [];
|
|
7512
8167
|
const candidates = [];
|
|
7513
8168
|
let entries;
|
|
7514
8169
|
try {
|
|
7515
|
-
entries =
|
|
8170
|
+
entries = readdirSync7(opts.worktreesDir, { withFileTypes: true });
|
|
7516
8171
|
} catch {
|
|
7517
8172
|
return [];
|
|
7518
8173
|
}
|
|
@@ -7520,7 +8175,7 @@ function scanStaleRunDirectoryCandidates(opts) {
|
|
|
7520
8175
|
if (!runEntry.isDirectory()) continue;
|
|
7521
8176
|
const runId = runEntry.name;
|
|
7522
8177
|
if (opts.runIdFilter && runId !== opts.runIdFilter) continue;
|
|
7523
|
-
const runPath =
|
|
8178
|
+
const runPath = path40.join(opts.worktreesDir, runId);
|
|
7524
8179
|
if (!runDirectoryIsEmpty(runPath)) continue;
|
|
7525
8180
|
candidates.push({
|
|
7526
8181
|
kind: "remove_run_directory",
|
|
@@ -7535,14 +8190,14 @@ function scanStaleRunDirectoryCandidates(opts) {
|
|
|
7535
8190
|
}
|
|
7536
8191
|
|
|
7537
8192
|
// src/cleanup-execute.ts
|
|
7538
|
-
import { existsSync as
|
|
7539
|
-
import
|
|
8193
|
+
import { existsSync as existsSync30, rmSync as rmSync3 } from "node:fs";
|
|
8194
|
+
import path42 from "node:path";
|
|
7540
8195
|
|
|
7541
8196
|
// src/cleanup-dir-size.ts
|
|
7542
|
-
import { existsSync as
|
|
7543
|
-
import
|
|
8197
|
+
import { existsSync as existsSync29, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
|
|
8198
|
+
import path41 from "node:path";
|
|
7544
8199
|
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
7545
|
-
if (!
|
|
8200
|
+
if (!existsSync29(root)) return 0;
|
|
7546
8201
|
let total = 0;
|
|
7547
8202
|
let seen = 0;
|
|
7548
8203
|
const stack = [root];
|
|
@@ -7550,16 +8205,16 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
7550
8205
|
const current = stack.pop();
|
|
7551
8206
|
let entries;
|
|
7552
8207
|
try {
|
|
7553
|
-
entries =
|
|
8208
|
+
entries = readdirSync8(current);
|
|
7554
8209
|
} catch {
|
|
7555
8210
|
continue;
|
|
7556
8211
|
}
|
|
7557
8212
|
for (const name of entries) {
|
|
7558
8213
|
if (seen++ > maxEntries) return null;
|
|
7559
|
-
const full =
|
|
8214
|
+
const full = path41.join(current, name);
|
|
7560
8215
|
let st;
|
|
7561
8216
|
try {
|
|
7562
|
-
st =
|
|
8217
|
+
st = statSync6(full);
|
|
7563
8218
|
} catch {
|
|
7564
8219
|
continue;
|
|
7565
8220
|
}
|
|
@@ -7571,8 +8226,20 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
7571
8226
|
}
|
|
7572
8227
|
|
|
7573
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
|
+
}
|
|
7574
8239
|
function removeDependencyCache(candidate, execute) {
|
|
7575
|
-
|
|
8240
|
+
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
8241
|
+
if (metadataSkip) return metadataSkip;
|
|
8242
|
+
if (!existsSync30(candidate.path)) {
|
|
7576
8243
|
return {
|
|
7577
8244
|
...candidate,
|
|
7578
8245
|
executed: false,
|
|
@@ -7612,7 +8279,9 @@ function removeBuildCache(candidate, execute) {
|
|
|
7612
8279
|
return removeDependencyCache(candidate, execute);
|
|
7613
8280
|
}
|
|
7614
8281
|
function removeRunDirectory(candidate, execute) {
|
|
7615
|
-
|
|
8282
|
+
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
8283
|
+
if (metadataSkip) return metadataSkip;
|
|
8284
|
+
if (!existsSync30(candidate.path)) {
|
|
7616
8285
|
return {
|
|
7617
8286
|
...candidate,
|
|
7618
8287
|
executed: false,
|
|
@@ -7643,7 +8312,9 @@ function removeRunDirectory(candidate, execute) {
|
|
|
7643
8312
|
}
|
|
7644
8313
|
}
|
|
7645
8314
|
function removeWorktree(candidate, execute) {
|
|
7646
|
-
|
|
8315
|
+
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
8316
|
+
if (metadataSkip) return metadataSkip;
|
|
8317
|
+
if (!existsSync30(candidate.path)) {
|
|
7647
8318
|
return {
|
|
7648
8319
|
...candidate,
|
|
7649
8320
|
executed: false,
|
|
@@ -7660,7 +8331,7 @@ function removeWorktree(candidate, execute) {
|
|
|
7660
8331
|
if (repo) {
|
|
7661
8332
|
git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
|
|
7662
8333
|
}
|
|
7663
|
-
if (
|
|
8334
|
+
if (existsSync30(candidate.path)) {
|
|
7664
8335
|
rmSync3(candidate.path, { recursive: true, force: true });
|
|
7665
8336
|
}
|
|
7666
8337
|
return {
|
|
@@ -7680,15 +8351,15 @@ function removeWorktree(candidate, execute) {
|
|
|
7680
8351
|
}
|
|
7681
8352
|
}
|
|
7682
8353
|
function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
|
|
7683
|
-
const resolved =
|
|
7684
|
-
const suffix = `${
|
|
8354
|
+
const resolved = path42.resolve(targetPath);
|
|
8355
|
+
const suffix = `${path42.sep}${cacheDirName}`;
|
|
7685
8356
|
const cachePath = resolved.endsWith(suffix) ? resolved : null;
|
|
7686
8357
|
if (!cachePath) return "path_outside_harness";
|
|
7687
|
-
const rel =
|
|
7688
|
-
if (rel.startsWith("..") ||
|
|
7689
|
-
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);
|
|
7690
8361
|
if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
|
|
7691
|
-
if (!resolved.startsWith(
|
|
8362
|
+
if (!resolved.startsWith(path42.resolve(harnessRoot))) return "path_outside_harness";
|
|
7692
8363
|
return null;
|
|
7693
8364
|
}
|
|
7694
8365
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
@@ -7698,37 +8369,37 @@ function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
|
7698
8369
|
return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
|
|
7699
8370
|
}
|
|
7700
8371
|
function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
7701
|
-
const resolved =
|
|
7702
|
-
const relToWt =
|
|
7703
|
-
if (relToWt.startsWith("..") ||
|
|
7704
|
-
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);
|
|
7705
8376
|
if (parts.length < 3) return "path_outside_harness";
|
|
7706
|
-
if (!resolved.startsWith(
|
|
8377
|
+
if (!resolved.startsWith(path42.resolve(harnessRoot))) return "path_outside_harness";
|
|
7707
8378
|
return null;
|
|
7708
8379
|
}
|
|
7709
8380
|
|
|
7710
8381
|
// src/cleanup-scan.ts
|
|
7711
|
-
import { existsSync as
|
|
7712
|
-
import
|
|
8382
|
+
import { existsSync as existsSync31, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
|
|
8383
|
+
import path43 from "node:path";
|
|
7713
8384
|
function pathAgeMs2(target, now) {
|
|
7714
8385
|
try {
|
|
7715
|
-
const mtime =
|
|
8386
|
+
const mtime = statSync7(target).mtimeMs;
|
|
7716
8387
|
return Math.max(0, now - mtime);
|
|
7717
8388
|
} catch {
|
|
7718
8389
|
return 0;
|
|
7719
8390
|
}
|
|
7720
8391
|
}
|
|
7721
8392
|
function isPathInside(child, parent) {
|
|
7722
|
-
const rel =
|
|
7723
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
8393
|
+
const rel = path43.relative(parent, child);
|
|
8394
|
+
return rel === "" || !rel.startsWith("..") && !path43.isAbsolute(rel);
|
|
7724
8395
|
}
|
|
7725
8396
|
function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
|
|
7726
8397
|
const out = [];
|
|
7727
8398
|
for (const rel of HARNESS_BUILD_CACHE_RELATIVE_PATHS) {
|
|
7728
8399
|
if (rel === ".next") continue;
|
|
7729
|
-
const target =
|
|
7730
|
-
if (!
|
|
7731
|
-
const resolved =
|
|
8400
|
+
const target = path43.join(worktreePath, rel);
|
|
8401
|
+
if (!existsSync31(target)) continue;
|
|
8402
|
+
const resolved = path43.resolve(target);
|
|
7732
8403
|
if (seen.has(resolved)) continue;
|
|
7733
8404
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
7734
8405
|
seen.add(resolved);
|
|
@@ -7757,13 +8428,13 @@ function scanBuildCacheCandidates(opts) {
|
|
|
7757
8428
|
})
|
|
7758
8429
|
);
|
|
7759
8430
|
}
|
|
7760
|
-
if (!opts.includeOrphans || !
|
|
7761
|
-
for (const runEntry of
|
|
8431
|
+
if (!opts.includeOrphans || !existsSync31(opts.worktreesDir)) return candidates;
|
|
8432
|
+
for (const runEntry of readdirSync9(opts.worktreesDir, { withFileTypes: true })) {
|
|
7762
8433
|
if (!runEntry.isDirectory()) continue;
|
|
7763
|
-
const runPath =
|
|
7764
|
-
for (const workerEntry of
|
|
8434
|
+
const runPath = path43.join(opts.worktreesDir, runEntry.name);
|
|
8435
|
+
for (const workerEntry of readdirSync9(runPath, { withFileTypes: true })) {
|
|
7765
8436
|
if (!workerEntry.isDirectory()) continue;
|
|
7766
|
-
const worktreePath =
|
|
8437
|
+
const worktreePath = path43.join(runPath, workerEntry.name);
|
|
7767
8438
|
candidates.push(
|
|
7768
8439
|
...collectBuildCacheForWorktree(worktreePath, opts, seen, {
|
|
7769
8440
|
runId: runEntry.name,
|
|
@@ -7784,7 +8455,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
7784
8455
|
for (const entry of opts.index.values()) {
|
|
7785
8456
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
7786
8457
|
const resolved = entry.worktreePath;
|
|
7787
|
-
if (!
|
|
8458
|
+
if (!existsSync31(resolved)) continue;
|
|
7788
8459
|
if (seen.has(resolved)) continue;
|
|
7789
8460
|
seen.add(resolved);
|
|
7790
8461
|
candidates.push({
|
|
@@ -7798,24 +8469,24 @@ function scanWorktreeCandidates(opts) {
|
|
|
7798
8469
|
});
|
|
7799
8470
|
}
|
|
7800
8471
|
}
|
|
7801
|
-
if (!orphanEnabled || !
|
|
8472
|
+
if (!orphanEnabled || !existsSync31(opts.worktreesDir)) return candidates;
|
|
7802
8473
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
7803
8474
|
for (const entry of opts.index.values()) {
|
|
7804
|
-
indexedPaths.add(
|
|
8475
|
+
indexedPaths.add(path43.resolve(entry.worktreePath));
|
|
7805
8476
|
}
|
|
7806
|
-
for (const runEntry of
|
|
8477
|
+
for (const runEntry of readdirSync9(opts.worktreesDir, { withFileTypes: true })) {
|
|
7807
8478
|
if (!runEntry.isDirectory()) continue;
|
|
7808
8479
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
7809
|
-
const runPath =
|
|
8480
|
+
const runPath = path43.join(opts.worktreesDir, runEntry.name);
|
|
7810
8481
|
let workerEntries;
|
|
7811
8482
|
try {
|
|
7812
|
-
workerEntries =
|
|
8483
|
+
workerEntries = readdirSync9(runPath, { withFileTypes: true });
|
|
7813
8484
|
} catch {
|
|
7814
8485
|
continue;
|
|
7815
8486
|
}
|
|
7816
8487
|
for (const workerEntry of workerEntries) {
|
|
7817
8488
|
if (!workerEntry.isDirectory()) continue;
|
|
7818
|
-
const worktreePath =
|
|
8489
|
+
const worktreePath = path43.resolve(path43.join(runPath, workerEntry.name));
|
|
7819
8490
|
if (seen.has(worktreePath)) continue;
|
|
7820
8491
|
if (indexedPaths.has(worktreePath)) continue;
|
|
7821
8492
|
if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
|
|
@@ -7834,27 +8505,27 @@ function scanWorktreeCandidates(opts) {
|
|
|
7834
8505
|
}
|
|
7835
8506
|
|
|
7836
8507
|
// src/cleanup-dependency-scan.ts
|
|
7837
|
-
import { existsSync as
|
|
7838
|
-
import
|
|
8508
|
+
import { existsSync as existsSync32, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
|
|
8509
|
+
import path44 from "node:path";
|
|
7839
8510
|
var DEPENDENCY_CACHE_DIRS = [
|
|
7840
8511
|
{ dirName: "node_modules", kind: "remove_node_modules" },
|
|
7841
8512
|
{ dirName: ".next", kind: "remove_next_cache" }
|
|
7842
8513
|
];
|
|
7843
8514
|
function pathAgeMs3(target, now) {
|
|
7844
8515
|
try {
|
|
7845
|
-
const mtime =
|
|
8516
|
+
const mtime = statSync8(target).mtimeMs;
|
|
7846
8517
|
return Math.max(0, now - mtime);
|
|
7847
8518
|
} catch {
|
|
7848
8519
|
return 0;
|
|
7849
8520
|
}
|
|
7850
8521
|
}
|
|
7851
8522
|
function isPathInside2(child, parent) {
|
|
7852
|
-
const rel =
|
|
7853
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
8523
|
+
const rel = path44.relative(parent, child);
|
|
8524
|
+
return rel === "" || !rel.startsWith("..") && !path44.isAbsolute(rel);
|
|
7854
8525
|
}
|
|
7855
8526
|
function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
7856
|
-
if (!
|
|
7857
|
-
const resolved =
|
|
8527
|
+
if (!existsSync32(targetPath)) return;
|
|
8528
|
+
const resolved = path44.resolve(targetPath);
|
|
7858
8529
|
if (seen.has(resolved)) return;
|
|
7859
8530
|
if (!isPathInside2(resolved, opts.harnessRoot)) return;
|
|
7860
8531
|
seen.add(resolved);
|
|
@@ -7871,7 +8542,7 @@ function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
|
7871
8542
|
}
|
|
7872
8543
|
function scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, meta) {
|
|
7873
8544
|
for (const entry of DEPENDENCY_CACHE_DIRS) {
|
|
7874
|
-
pushCandidate2(candidates, seen, opts,
|
|
8545
|
+
pushCandidate2(candidates, seen, opts, path44.join(worktreePath, entry.dirName), entry.kind, meta);
|
|
7875
8546
|
}
|
|
7876
8547
|
}
|
|
7877
8548
|
function scanDependencyCacheCandidates(opts) {
|
|
@@ -7885,20 +8556,20 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
7885
8556
|
repo: entry.run.repo
|
|
7886
8557
|
});
|
|
7887
8558
|
}
|
|
7888
|
-
if (!
|
|
7889
|
-
for (const runEntry of
|
|
8559
|
+
if (!existsSync32(opts.worktreesDir)) return candidates;
|
|
8560
|
+
for (const runEntry of readdirSync10(opts.worktreesDir, { withFileTypes: true })) {
|
|
7890
8561
|
if (!runEntry.isDirectory()) continue;
|
|
7891
8562
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
7892
|
-
const runPath =
|
|
8563
|
+
const runPath = path44.join(opts.worktreesDir, runEntry.name);
|
|
7893
8564
|
let workerEntries;
|
|
7894
8565
|
try {
|
|
7895
|
-
workerEntries =
|
|
8566
|
+
workerEntries = readdirSync10(runPath, { withFileTypes: true });
|
|
7896
8567
|
} catch {
|
|
7897
8568
|
continue;
|
|
7898
8569
|
}
|
|
7899
8570
|
for (const workerEntry of workerEntries) {
|
|
7900
8571
|
if (!workerEntry.isDirectory()) continue;
|
|
7901
|
-
const worktreePath =
|
|
8572
|
+
const worktreePath = path44.join(runPath, workerEntry.name);
|
|
7902
8573
|
scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, {
|
|
7903
8574
|
runId: runEntry.name,
|
|
7904
8575
|
worker: workerEntry.name
|
|
@@ -7909,11 +8580,11 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
7909
8580
|
}
|
|
7910
8581
|
|
|
7911
8582
|
// src/cleanup-duplicate-worktrees.ts
|
|
7912
|
-
import { existsSync as
|
|
7913
|
-
import
|
|
8583
|
+
import { existsSync as existsSync33, statSync as statSync9 } from "node:fs";
|
|
8584
|
+
import path45 from "node:path";
|
|
7914
8585
|
function pathAgeMs4(target, now) {
|
|
7915
8586
|
try {
|
|
7916
|
-
const mtime =
|
|
8587
|
+
const mtime = statSync9(target).mtimeMs;
|
|
7917
8588
|
return Math.max(0, now - mtime);
|
|
7918
8589
|
} catch {
|
|
7919
8590
|
return 0;
|
|
@@ -7940,8 +8611,8 @@ function parseWorktreePorcelain(output) {
|
|
|
7940
8611
|
return records;
|
|
7941
8612
|
}
|
|
7942
8613
|
function isUnderWorktreesDir(worktreePath, worktreesDir) {
|
|
7943
|
-
const rel =
|
|
7944
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
8614
|
+
const rel = path45.relative(path45.resolve(worktreesDir), path45.resolve(worktreePath));
|
|
8615
|
+
return rel !== "" && !rel.startsWith("..") && !path45.isAbsolute(rel);
|
|
7945
8616
|
}
|
|
7946
8617
|
function isCleanWorktree(worktreePath, repoRoot) {
|
|
7947
8618
|
try {
|
|
@@ -7954,14 +8625,14 @@ function isCleanWorktree(worktreePath, repoRoot) {
|
|
|
7954
8625
|
}
|
|
7955
8626
|
}
|
|
7956
8627
|
function scanDuplicateWorktreeCandidates(opts) {
|
|
7957
|
-
if (!opts.includeOrphans || !
|
|
8628
|
+
if (!opts.includeOrphans || !existsSync33(opts.worktreesDir)) return [];
|
|
7958
8629
|
const repos = /* @__PURE__ */ new Set();
|
|
7959
8630
|
for (const entry of opts.index.values()) {
|
|
7960
|
-
if (entry.run.repo) repos.add(
|
|
8631
|
+
if (entry.run.repo) repos.add(path45.resolve(entry.run.repo));
|
|
7961
8632
|
}
|
|
7962
8633
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
7963
8634
|
for (const entry of opts.index.values()) {
|
|
7964
|
-
indexedPaths.add(
|
|
8635
|
+
indexedPaths.add(path45.resolve(entry.worktreePath));
|
|
7965
8636
|
}
|
|
7966
8637
|
const candidates = [];
|
|
7967
8638
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7974,15 +8645,15 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
7974
8645
|
}
|
|
7975
8646
|
const worktrees = parseWorktreePorcelain(porcelain);
|
|
7976
8647
|
for (const wt of worktrees) {
|
|
7977
|
-
const resolved =
|
|
7978
|
-
if (resolved ===
|
|
8648
|
+
const resolved = path45.resolve(wt.path);
|
|
8649
|
+
if (resolved === path45.resolve(repoRoot)) continue;
|
|
7979
8650
|
if (!isUnderWorktreesDir(resolved, opts.worktreesDir)) continue;
|
|
7980
8651
|
if (indexedPaths.has(resolved)) continue;
|
|
7981
8652
|
if (seen.has(resolved)) continue;
|
|
7982
|
-
if (!
|
|
8653
|
+
if (!existsSync33(resolved)) continue;
|
|
7983
8654
|
if (!isCleanWorktree(resolved, repoRoot)) continue;
|
|
7984
|
-
const rel =
|
|
7985
|
-
const parts = rel.split(
|
|
8655
|
+
const rel = path45.relative(opts.worktreesDir, resolved);
|
|
8656
|
+
const parts = rel.split(path45.sep);
|
|
7986
8657
|
const runId = parts[0];
|
|
7987
8658
|
const worker = parts[1] ?? "unknown";
|
|
7988
8659
|
seen.add(resolved);
|
|
@@ -8001,12 +8672,12 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
8001
8672
|
}
|
|
8002
8673
|
|
|
8003
8674
|
// src/cleanup-worktree-index.ts
|
|
8004
|
-
import
|
|
8675
|
+
import path46 from "node:path";
|
|
8005
8676
|
function buildWorktreeIndexAt(harnessRoot) {
|
|
8006
8677
|
const index = /* @__PURE__ */ new Map();
|
|
8007
8678
|
for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
|
|
8008
8679
|
for (const name of Object.keys(run.workers || {})) {
|
|
8009
|
-
const workerPath =
|
|
8680
|
+
const workerPath = path46.join(
|
|
8010
8681
|
runDirectoryAt(harnessRoot, run.id),
|
|
8011
8682
|
"workers",
|
|
8012
8683
|
safeSlug(name),
|
|
@@ -8015,9 +8686,9 @@ function buildWorktreeIndexAt(harnessRoot) {
|
|
|
8015
8686
|
const worker = readJson(workerPath, void 0);
|
|
8016
8687
|
if (!worker?.worktreePath) continue;
|
|
8017
8688
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
8018
|
-
index.set(
|
|
8689
|
+
index.set(path46.resolve(worker.worktreePath), {
|
|
8019
8690
|
harnessRoot,
|
|
8020
|
-
worktreePath:
|
|
8691
|
+
worktreePath: path46.resolve(worker.worktreePath),
|
|
8021
8692
|
runId: run.id,
|
|
8022
8693
|
workerName: name,
|
|
8023
8694
|
run,
|
|
@@ -8030,7 +8701,7 @@ function buildWorktreeIndexAt(harnessRoot) {
|
|
|
8030
8701
|
}
|
|
8031
8702
|
|
|
8032
8703
|
// src/cleanup-retention-config.ts
|
|
8033
|
-
function
|
|
8704
|
+
function envFlag2(name) {
|
|
8034
8705
|
const v = process.env[name];
|
|
8035
8706
|
return v === "1" || v === "true" || v === "yes";
|
|
8036
8707
|
}
|
|
@@ -8041,17 +8712,17 @@ function envMs(name, fallback) {
|
|
|
8041
8712
|
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
8042
8713
|
}
|
|
8043
8714
|
function resolveHarnessRetention(options = {}) {
|
|
8044
|
-
const execute = options.execute === true || options.execute !== false &&
|
|
8045
|
-
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");
|
|
8046
8717
|
const nodeModulesAgeMs = options.nodeModulesAgeMs ?? envMs("KYNVER_CLEANUP_NODE_MODULES_AGE_MS", DEFAULT_NODE_MODULES_AGE_MS);
|
|
8047
8718
|
const worktreesAgeMs = options.worktreesAgeMs ?? envMs("KYNVER_CLEANUP_WORKTREES_AGE_MS", 0);
|
|
8048
8719
|
const terminalWorktreesAgeMs = options.terminalWorktreesAgeMs ?? envMs("KYNVER_CLEANUP_TERMINAL_WORKTREES_AGE_MS", DEFAULT_TERMINAL_WORKTREES_AGE_MS);
|
|
8049
8720
|
const runDirectoriesAgeMs = options.runDirectoriesAgeMs ?? envMs("KYNVER_CLEANUP_RUN_DIRECTORIES_AGE_MS", DEFAULT_RUN_DIRECTORIES_AGE_MS);
|
|
8050
8721
|
const maxActionsPerSweep = options.maxActionsPerSweep ?? envMs("KYNVER_CLEANUP_MAX_ACTIONS_PER_SWEEP", DEFAULT_MAX_ACTIONS_PER_SWEEP);
|
|
8051
|
-
const includeOrphans = options.includeOrphans === true ||
|
|
8052
|
-
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";
|
|
8053
8724
|
const runIdFilter = scopeAll ? options.runIdFilter : options.runIdFilter ?? (process.env.KYNVER_CLEANUP_RUN_ID || void 0);
|
|
8054
|
-
const accountBytes = options.accountBytes !== false && !
|
|
8725
|
+
const accountBytes = options.accountBytes !== false && !envFlag2("KYNVER_CLEANUP_SKIP_BYTE_ACCOUNTING");
|
|
8055
8726
|
return {
|
|
8056
8727
|
execute,
|
|
8057
8728
|
finalizeStaleRuns: finalizeStaleRuns2,
|
|
@@ -8081,15 +8752,15 @@ function resolvePipelineHarnessRetention(runId) {
|
|
|
8081
8752
|
}
|
|
8082
8753
|
|
|
8083
8754
|
// src/cleanup-orphan-safety.ts
|
|
8084
|
-
import { existsSync as
|
|
8085
|
-
import
|
|
8755
|
+
import { existsSync as existsSync34, statSync as statSync10 } from "node:fs";
|
|
8756
|
+
import path47 from "node:path";
|
|
8086
8757
|
var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
|
|
8087
8758
|
function assessOrphanWorktreeSafety(input) {
|
|
8088
8759
|
const now = input.now ?? Date.now();
|
|
8089
8760
|
const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
|
|
8090
|
-
if (!
|
|
8761
|
+
if (!existsSync34(input.worktreePath)) return null;
|
|
8091
8762
|
if (input.runId && input.workerName) {
|
|
8092
|
-
const heartbeatPath =
|
|
8763
|
+
const heartbeatPath = path47.join(
|
|
8093
8764
|
input.harnessRoot,
|
|
8094
8765
|
"runs",
|
|
8095
8766
|
input.runId,
|
|
@@ -8098,13 +8769,13 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
8098
8769
|
"heartbeat.jsonl"
|
|
8099
8770
|
);
|
|
8100
8771
|
try {
|
|
8101
|
-
const mtime =
|
|
8772
|
+
const mtime = statSync10(heartbeatPath).mtimeMs;
|
|
8102
8773
|
if (now - mtime < heartbeatFreshMs) return "active_worker";
|
|
8103
8774
|
} catch {
|
|
8104
8775
|
}
|
|
8105
8776
|
}
|
|
8106
|
-
const gitDir =
|
|
8107
|
-
if (!
|
|
8777
|
+
const gitDir = path47.join(input.worktreePath, ".git");
|
|
8778
|
+
if (!existsSync34(gitDir)) return null;
|
|
8108
8779
|
const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
|
|
8109
8780
|
if (porcelain.status !== 0) return "pr_or_unmerged_commits";
|
|
8110
8781
|
const dirtyLines = porcelain.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
@@ -8133,14 +8804,14 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
8133
8804
|
}
|
|
8134
8805
|
|
|
8135
8806
|
// src/harness-storage-snapshot.ts
|
|
8136
|
-
import { existsSync as
|
|
8137
|
-
import
|
|
8807
|
+
import { existsSync as existsSync35, readdirSync as readdirSync11, statSync as statSync11 } from "node:fs";
|
|
8808
|
+
import path48 from "node:path";
|
|
8138
8809
|
function harnessStorageSnapshot(opts = {}) {
|
|
8139
8810
|
const harnessRoot = normalizeHarnessRoot(opts.harnessRoot ?? resolveHarnessRoot());
|
|
8140
8811
|
const worktreesDir = harnessWorktreesDir(harnessRoot);
|
|
8141
8812
|
const now = opts.now ?? Date.now();
|
|
8142
8813
|
const scannedAt = new Date(now).toISOString();
|
|
8143
|
-
if (!
|
|
8814
|
+
if (!existsSync35(worktreesDir)) {
|
|
8144
8815
|
return {
|
|
8145
8816
|
harnessRoot,
|
|
8146
8817
|
worktreesDir,
|
|
@@ -8157,7 +8828,7 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
8157
8828
|
let oldestMs = null;
|
|
8158
8829
|
let entries;
|
|
8159
8830
|
try {
|
|
8160
|
-
entries =
|
|
8831
|
+
entries = readdirSync11(worktreesDir, { withFileTypes: true });
|
|
8161
8832
|
} catch {
|
|
8162
8833
|
return {
|
|
8163
8834
|
harnessRoot,
|
|
@@ -8172,14 +8843,14 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
8172
8843
|
for (const runEntry of entries) {
|
|
8173
8844
|
if (!runEntry.isDirectory()) continue;
|
|
8174
8845
|
runCount += 1;
|
|
8175
|
-
const runPath =
|
|
8846
|
+
const runPath = path48.join(worktreesDir, runEntry.name);
|
|
8176
8847
|
try {
|
|
8177
|
-
const st =
|
|
8848
|
+
const st = statSync11(runPath);
|
|
8178
8849
|
oldestMs = oldestMs === null ? st.mtimeMs : Math.min(oldestMs, st.mtimeMs);
|
|
8179
8850
|
} catch {
|
|
8180
8851
|
}
|
|
8181
8852
|
try {
|
|
8182
|
-
for (const workerEntry of
|
|
8853
|
+
for (const workerEntry of readdirSync11(runPath, { withFileTypes: true })) {
|
|
8183
8854
|
if (workerEntry.isDirectory()) workerCount += 1;
|
|
8184
8855
|
}
|
|
8185
8856
|
} catch {
|
|
@@ -8207,12 +8878,12 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
8207
8878
|
}
|
|
8208
8879
|
|
|
8209
8880
|
// src/cleanup-harness-roots.ts
|
|
8210
|
-
import { existsSync as
|
|
8211
|
-
import { homedir as
|
|
8212
|
-
import
|
|
8881
|
+
import { existsSync as existsSync36 } from "node:fs";
|
|
8882
|
+
import { homedir as homedir12 } from "node:os";
|
|
8883
|
+
import path49 from "node:path";
|
|
8213
8884
|
var WELL_KNOWN_HARNESS_SCAN_ROOTS = [
|
|
8214
8885
|
"/var/tmp/kynver-harness",
|
|
8215
|
-
|
|
8886
|
+
path49.join(homedir12(), ".openclaw", "harness")
|
|
8216
8887
|
];
|
|
8217
8888
|
function addRoot(seen, roots, candidate) {
|
|
8218
8889
|
if (!candidate?.trim()) return;
|
|
@@ -8234,15 +8905,15 @@ function resolveHarnessScanRoots(options = {}) {
|
|
|
8234
8905
|
for (const candidate of extra ?? []) addRoot(seen, roots, candidate);
|
|
8235
8906
|
if (shouldScanWellKnownRoots(options)) {
|
|
8236
8907
|
for (const candidate of WELL_KNOWN_HARNESS_SCAN_ROOTS) {
|
|
8237
|
-
const resolved =
|
|
8238
|
-
if (!seen.has(resolved) &&
|
|
8908
|
+
const resolved = path49.resolve(candidate);
|
|
8909
|
+
if (!seen.has(resolved) && existsSync36(resolved)) addRoot(seen, roots, resolved);
|
|
8239
8910
|
}
|
|
8240
8911
|
}
|
|
8241
8912
|
return roots;
|
|
8242
8913
|
}
|
|
8243
8914
|
|
|
8244
8915
|
// src/cleanup-disk-pressure.ts
|
|
8245
|
-
function
|
|
8916
|
+
function envFlag3(name) {
|
|
8246
8917
|
const v = process.env[name];
|
|
8247
8918
|
return v === "1" || v === "true" || v === "yes";
|
|
8248
8919
|
}
|
|
@@ -8265,7 +8936,7 @@ function observeCleanupDiskPressure(input = {}) {
|
|
|
8265
8936
|
}
|
|
8266
8937
|
function applyDiskPressureToRetention(retention, pressure) {
|
|
8267
8938
|
if (!pressure.pressured) return retention;
|
|
8268
|
-
const executeOnPressure = retention.execute ||
|
|
8939
|
+
const executeOnPressure = retention.execute || envFlag3("KYNVER_CLEANUP_EXECUTE_ON_PRESSURE");
|
|
8269
8940
|
return {
|
|
8270
8941
|
...retention,
|
|
8271
8942
|
execute: executeOnPressure,
|
|
@@ -8324,9 +8995,9 @@ function mergeWorktreeIndexes(scanRoots) {
|
|
|
8324
8995
|
}
|
|
8325
8996
|
function worktreePathForCandidate(candidate, worktreesDir) {
|
|
8326
8997
|
if (candidate.runId && candidate.worker) {
|
|
8327
|
-
return
|
|
8998
|
+
return path50.join(worktreesDir, candidate.runId, candidate.worker);
|
|
8328
8999
|
}
|
|
8329
|
-
return
|
|
9000
|
+
return path50.resolve(candidate.path, "..");
|
|
8330
9001
|
}
|
|
8331
9002
|
function runHarnessCleanup(options = {}) {
|
|
8332
9003
|
let retention = resolveHarnessRetention(options);
|
|
@@ -8343,7 +9014,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8343
9014
|
const atSweepCap = () => actions.length >= maxActions;
|
|
8344
9015
|
for (const harnessRoot of paths.scanRoots) {
|
|
8345
9016
|
if (atSweepCap()) break;
|
|
8346
|
-
const worktreesDir =
|
|
9017
|
+
const worktreesDir = path50.join(harnessRoot, "worktrees");
|
|
8347
9018
|
const scanOpts = {
|
|
8348
9019
|
harnessRoot,
|
|
8349
9020
|
worktreesDir,
|
|
@@ -8357,7 +9028,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8357
9028
|
for (const raw of scanDependencyCacheCandidates(scanOpts)) {
|
|
8358
9029
|
if (atSweepCap()) break;
|
|
8359
9030
|
const candidate = attachCandidateBytes(raw, retention.accountBytes);
|
|
8360
|
-
const resolved =
|
|
9031
|
+
const resolved = path50.resolve(candidate.path);
|
|
8361
9032
|
if (processedPaths.has(resolved)) continue;
|
|
8362
9033
|
processedPaths.add(resolved);
|
|
8363
9034
|
const pathSkip = pathGuardForDependencyCache(candidate, harnessRoot, worktreesDir);
|
|
@@ -8367,7 +9038,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8367
9038
|
continue;
|
|
8368
9039
|
}
|
|
8369
9040
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
8370
|
-
const indexed = index.get(
|
|
9041
|
+
const indexed = index.get(path50.resolve(worktreePath)) ?? null;
|
|
8371
9042
|
const guardReason = skipDependencyCacheRemoval({
|
|
8372
9043
|
indexed,
|
|
8373
9044
|
includeOrphans: true,
|
|
@@ -8387,7 +9058,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8387
9058
|
for (const raw of scanBuildCacheCandidates(scanOpts)) {
|
|
8388
9059
|
if (atSweepCap()) break;
|
|
8389
9060
|
const candidate = attachCandidateBytes(raw, retention.accountBytes);
|
|
8390
|
-
const resolved =
|
|
9061
|
+
const resolved = path50.resolve(candidate.path);
|
|
8391
9062
|
if (processedPaths.has(resolved)) continue;
|
|
8392
9063
|
processedPaths.add(resolved);
|
|
8393
9064
|
const pathSkip = isHarnessBuildCachePath(candidate.path, harnessRoot, worktreesDir);
|
|
@@ -8397,7 +9068,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8397
9068
|
continue;
|
|
8398
9069
|
}
|
|
8399
9070
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
8400
|
-
const indexed = index.get(
|
|
9071
|
+
const indexed = index.get(path50.resolve(worktreePath)) ?? null;
|
|
8401
9072
|
const guardReason = skipBuildCacheRemoval({
|
|
8402
9073
|
indexed,
|
|
8403
9074
|
includeOrphans: true,
|
|
@@ -8421,11 +9092,11 @@ function runHarnessCleanup(options = {}) {
|
|
|
8421
9092
|
const worktreeSeen = /* @__PURE__ */ new Set();
|
|
8422
9093
|
for (const raw of worktreeCandidates) {
|
|
8423
9094
|
if (atSweepCap()) break;
|
|
8424
|
-
const resolved =
|
|
9095
|
+
const resolved = path50.resolve(raw.path);
|
|
8425
9096
|
if (worktreeSeen.has(resolved)) continue;
|
|
8426
9097
|
worktreeSeen.add(resolved);
|
|
8427
9098
|
const candidate = attachCandidateBytes({ ...raw, path: resolved }, retention.accountBytes);
|
|
8428
|
-
const indexed = index.get(
|
|
9099
|
+
const indexed = index.get(path50.resolve(candidate.path)) ?? null;
|
|
8429
9100
|
const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
|
|
8430
9101
|
worktreePath: candidate.path,
|
|
8431
9102
|
harnessRoot,
|
|
@@ -8435,7 +9106,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
8435
9106
|
});
|
|
8436
9107
|
const guardSkip = skipWorktreeRemoval({
|
|
8437
9108
|
indexed,
|
|
8438
|
-
worktreePath:
|
|
9109
|
+
worktreePath: path50.resolve(candidate.path),
|
|
8439
9110
|
includeOrphans: retention.includeOrphans,
|
|
8440
9111
|
worktreesAgeMs: retention.worktreesAgeMs,
|
|
8441
9112
|
terminalWorktreesAgeMs: retention.terminalWorktreesAgeMs,
|
|
@@ -8462,10 +9133,10 @@ function runHarnessCleanup(options = {}) {
|
|
|
8462
9133
|
})) {
|
|
8463
9134
|
if (atSweepCap()) break;
|
|
8464
9135
|
const candidate = attachCandidateBytes(raw, retention.accountBytes);
|
|
8465
|
-
const resolved =
|
|
9136
|
+
const resolved = path50.resolve(candidate.path);
|
|
8466
9137
|
if (processedPaths.has(resolved)) continue;
|
|
8467
9138
|
processedPaths.add(resolved);
|
|
8468
|
-
const runId = candidate.runId ??
|
|
9139
|
+
const runId = candidate.runId ?? path50.basename(resolved);
|
|
8469
9140
|
const dirSkip = skipRunDirectoryRemoval({
|
|
8470
9141
|
harnessRoot,
|
|
8471
9142
|
runId,
|
|
@@ -8554,8 +9225,8 @@ function isPipelineCleanupEnabled() {
|
|
|
8554
9225
|
|
|
8555
9226
|
// src/installed-package-versions.ts
|
|
8556
9227
|
import { readFile } from "node:fs/promises";
|
|
8557
|
-
import { homedir as
|
|
8558
|
-
import
|
|
9228
|
+
import { homedir as homedir13 } from "node:os";
|
|
9229
|
+
import path51 from "node:path";
|
|
8559
9230
|
var MANAGED_PACKAGES = [
|
|
8560
9231
|
"@kynver-app/runtime",
|
|
8561
9232
|
"@kynver-app/openclaw-agent-os",
|
|
@@ -8569,13 +9240,13 @@ function unique(values) {
|
|
|
8569
9240
|
return [...new Set(values.filter((value) => Boolean(value)))];
|
|
8570
9241
|
}
|
|
8571
9242
|
function moduleRoots() {
|
|
8572
|
-
const home =
|
|
8573
|
-
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ??
|
|
8574
|
-
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"));
|
|
8575
9246
|
return unique([
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
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")
|
|
8579
9250
|
]);
|
|
8580
9251
|
}
|
|
8581
9252
|
async function readVersion(packageJsonPath) {
|
|
@@ -8591,7 +9262,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
|
|
|
8591
9262
|
const out = {};
|
|
8592
9263
|
for (const packageName of MANAGED_PACKAGES) {
|
|
8593
9264
|
for (const root of roots) {
|
|
8594
|
-
const packageJsonPath =
|
|
9265
|
+
const packageJsonPath = path51.join(root, packageName, "package.json");
|
|
8595
9266
|
const version = await readVersion(packageJsonPath);
|
|
8596
9267
|
if (!version) continue;
|
|
8597
9268
|
out[packageName] = { version, observedAt, path: packageJsonPath };
|
|
@@ -8607,7 +9278,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
8607
9278
|
const outcomes = [];
|
|
8608
9279
|
for (const name of Object.keys(run.workers || {})) {
|
|
8609
9280
|
const worker = readJson(
|
|
8610
|
-
|
|
9281
|
+
path52.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
8611
9282
|
void 0
|
|
8612
9283
|
);
|
|
8613
9284
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -8718,6 +9389,7 @@ async function runPipelineTick(args) {
|
|
|
8718
9389
|
skipped: true,
|
|
8719
9390
|
reason: execute ? dispatchResourceGate.reason ?? "no slots or queued work" : "execute disabled",
|
|
8720
9391
|
maxStarts: 0,
|
|
9392
|
+
dispatchAdvice: maxStartsAdvice,
|
|
8721
9393
|
...exactTargetTaskIds.length ? { exactTargetTaskIds, exactOnly: true } : {}
|
|
8722
9394
|
};
|
|
8723
9395
|
}
|
|
@@ -8739,6 +9411,7 @@ async function runPipelineTick(args) {
|
|
|
8739
9411
|
completionAckSync,
|
|
8740
9412
|
operatorTick,
|
|
8741
9413
|
sweep,
|
|
9414
|
+
dispatchAdvice: maxStartsAdvice,
|
|
8742
9415
|
dispatch,
|
|
8743
9416
|
idle
|
|
8744
9417
|
};
|
|
@@ -8762,8 +9435,18 @@ async function runDaemon(args) {
|
|
|
8762
9435
|
stopping = true;
|
|
8763
9436
|
});
|
|
8764
9437
|
console.error(JSON.stringify({ event: "daemon_start", runId, agentOsId, execute, intervalMs }));
|
|
9438
|
+
const cronEnv = resolveKynverCronEnv();
|
|
8765
9439
|
while (!stopping) {
|
|
8766
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
|
+
}
|
|
8767
9450
|
const tick = await runPipelineTick({ run: runId, agentOsId, execute, ...args });
|
|
8768
9451
|
console.error(JSON.stringify({ event: "daemon_tick", ...tick }));
|
|
8769
9452
|
if (tick.idle) {
|
|
@@ -8782,7 +9465,7 @@ async function runDaemon(args) {
|
|
|
8782
9465
|
}
|
|
8783
9466
|
|
|
8784
9467
|
// src/plan-progress.ts
|
|
8785
|
-
import
|
|
9468
|
+
import path53 from "node:path";
|
|
8786
9469
|
|
|
8787
9470
|
// src/bounded-build/constants.ts
|
|
8788
9471
|
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
@@ -9069,7 +9752,7 @@ async function emitPlanProgress(args) {
|
|
|
9069
9752
|
}
|
|
9070
9753
|
function verifyPlanLocal(args) {
|
|
9071
9754
|
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
9072
|
-
const cwd =
|
|
9755
|
+
const cwd = path53.resolve(worktree);
|
|
9073
9756
|
const summary = runHarnessVerifyCommands(cwd);
|
|
9074
9757
|
const emitJson = args.json === true || args.json === "true";
|
|
9075
9758
|
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
@@ -9118,9 +9801,9 @@ async function verifyPlan(args) {
|
|
|
9118
9801
|
}
|
|
9119
9802
|
|
|
9120
9803
|
// src/harness-verify-cli.ts
|
|
9121
|
-
import
|
|
9804
|
+
import path54 from "node:path";
|
|
9122
9805
|
function runHarnessVerifyCli(args) {
|
|
9123
|
-
const cwd =
|
|
9806
|
+
const cwd = path54.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
9124
9807
|
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
9125
9808
|
const commands = [];
|
|
9126
9809
|
const rawCmd = args.command;
|
|
@@ -9164,7 +9847,7 @@ function runHarnessVerifyCli(args) {
|
|
|
9164
9847
|
}
|
|
9165
9848
|
|
|
9166
9849
|
// src/plan-persist-cli.ts
|
|
9167
|
-
import { readFileSync as
|
|
9850
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
9168
9851
|
var OPERATIONS = ["create", "add_version", "update_metadata"];
|
|
9169
9852
|
var FAILURE_KINDS = [
|
|
9170
9853
|
"approval_guard",
|
|
@@ -9176,7 +9859,7 @@ var FAILURE_KINDS = [
|
|
|
9176
9859
|
function readBodyArg(args) {
|
|
9177
9860
|
const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
|
|
9178
9861
|
if (bodyFile) {
|
|
9179
|
-
return { body:
|
|
9862
|
+
return { body: readFileSync12(bodyFile, "utf8"), bodyPathHint: bodyFile };
|
|
9180
9863
|
}
|
|
9181
9864
|
const inline = args.body ? String(args.body) : void 0;
|
|
9182
9865
|
if (inline) return { body: inline };
|
|
@@ -9320,7 +10003,7 @@ function formatMonitorTickNotice(tick) {
|
|
|
9320
10003
|
}
|
|
9321
10004
|
|
|
9322
10005
|
// src/monitor/monitor.service.ts
|
|
9323
|
-
import
|
|
10006
|
+
import path56 from "node:path";
|
|
9324
10007
|
|
|
9325
10008
|
// src/monitor/monitor.classify.ts
|
|
9326
10009
|
function classifyWorkerHealth(input) {
|
|
@@ -9372,11 +10055,11 @@ function classifyWorkerHealth(input) {
|
|
|
9372
10055
|
}
|
|
9373
10056
|
|
|
9374
10057
|
// src/monitor/monitor.store.ts
|
|
9375
|
-
import { existsSync as
|
|
9376
|
-
import
|
|
10058
|
+
import { existsSync as existsSync37, mkdirSync as mkdirSync6, readdirSync as readdirSync12, unlinkSync as unlinkSync3 } from "node:fs";
|
|
10059
|
+
import path55 from "node:path";
|
|
9377
10060
|
function monitorsDir() {
|
|
9378
10061
|
const { harnessRoot } = getHarnessPaths();
|
|
9379
|
-
const dir =
|
|
10062
|
+
const dir = path55.join(harnessRoot, "monitors");
|
|
9380
10063
|
mkdirSync6(dir, { recursive: true });
|
|
9381
10064
|
return dir;
|
|
9382
10065
|
}
|
|
@@ -9384,7 +10067,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
9384
10067
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
9385
10068
|
}
|
|
9386
10069
|
function monitorPath(monitorId) {
|
|
9387
|
-
return
|
|
10070
|
+
return path55.join(monitorsDir(), `${monitorId}.json`);
|
|
9388
10071
|
}
|
|
9389
10072
|
function loadMonitorSession(monitorId) {
|
|
9390
10073
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -9394,18 +10077,18 @@ function saveMonitorSession(session) {
|
|
|
9394
10077
|
}
|
|
9395
10078
|
function deleteMonitorSession(monitorId) {
|
|
9396
10079
|
const file = monitorPath(monitorId);
|
|
9397
|
-
if (!
|
|
9398
|
-
|
|
10080
|
+
if (!existsSync37(file)) return false;
|
|
10081
|
+
unlinkSync3(file);
|
|
9399
10082
|
return true;
|
|
9400
10083
|
}
|
|
9401
10084
|
function listMonitorSessions() {
|
|
9402
10085
|
const dir = monitorsDir();
|
|
9403
|
-
if (!
|
|
10086
|
+
if (!existsSync37(dir)) return [];
|
|
9404
10087
|
const entries = [];
|
|
9405
|
-
for (const name of
|
|
10088
|
+
for (const name of readdirSync12(dir)) {
|
|
9406
10089
|
if (!name.endsWith(".json")) continue;
|
|
9407
10090
|
const session = readJson(
|
|
9408
|
-
|
|
10091
|
+
path55.join(dir, name),
|
|
9409
10092
|
void 0
|
|
9410
10093
|
);
|
|
9411
10094
|
if (!session?.monitorId) continue;
|
|
@@ -9496,7 +10179,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
9496
10179
|
// src/monitor/monitor.service.ts
|
|
9497
10180
|
function workerRecord2(runId, name) {
|
|
9498
10181
|
return readJson(
|
|
9499
|
-
|
|
10182
|
+
path56.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
9500
10183
|
void 0
|
|
9501
10184
|
);
|
|
9502
10185
|
}
|
|
@@ -9702,21 +10385,21 @@ async function runMonitorLoop(args) {
|
|
|
9702
10385
|
|
|
9703
10386
|
// src/monitor/monitor-spawn.ts
|
|
9704
10387
|
import { spawn as spawn6 } from "node:child_process";
|
|
9705
|
-
import { closeSync as
|
|
9706
|
-
import
|
|
10388
|
+
import { closeSync as closeSync7, existsSync as existsSync38, openSync as openSync7 } from "node:fs";
|
|
10389
|
+
import path57 from "node:path";
|
|
9707
10390
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
9708
10391
|
function resolveDefaultCliPath2() {
|
|
9709
|
-
return
|
|
10392
|
+
return path57.join(fileURLToPath3(new URL(".", import.meta.url)), "cli.js");
|
|
9710
10393
|
}
|
|
9711
10394
|
function spawnMonitorSidecar(opts) {
|
|
9712
10395
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
9713
|
-
if (!
|
|
10396
|
+
if (!existsSync38(cliPath)) return void 0;
|
|
9714
10397
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
9715
10398
|
const { harnessRoot } = getHarnessPaths();
|
|
9716
|
-
const logPath =
|
|
10399
|
+
const logPath = path57.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
9717
10400
|
let logFd;
|
|
9718
10401
|
try {
|
|
9719
|
-
logFd =
|
|
10402
|
+
logFd = openSync7(logPath, "a");
|
|
9720
10403
|
} catch {
|
|
9721
10404
|
logFd = void 0;
|
|
9722
10405
|
}
|
|
@@ -9756,7 +10439,7 @@ function spawnMonitorSidecar(opts) {
|
|
|
9756
10439
|
env: process.env
|
|
9757
10440
|
})
|
|
9758
10441
|
);
|
|
9759
|
-
if (logFd !== void 0)
|
|
10442
|
+
if (logFd !== void 0) closeSync7(logFd);
|
|
9760
10443
|
child.unref();
|
|
9761
10444
|
const session = {
|
|
9762
10445
|
monitorId,
|
|
@@ -9773,7 +10456,7 @@ function spawnMonitorSidecar(opts) {
|
|
|
9773
10456
|
} catch {
|
|
9774
10457
|
if (logFd !== void 0) {
|
|
9775
10458
|
try {
|
|
9776
|
-
|
|
10459
|
+
closeSync7(logFd);
|
|
9777
10460
|
} catch {
|
|
9778
10461
|
}
|
|
9779
10462
|
}
|
|
@@ -9833,13 +10516,13 @@ async function monitorTickCli(args) {
|
|
|
9833
10516
|
}
|
|
9834
10517
|
|
|
9835
10518
|
// src/package-version.ts
|
|
9836
|
-
import { existsSync as
|
|
10519
|
+
import { existsSync as existsSync39, readFileSync as readFileSync13 } from "node:fs";
|
|
9837
10520
|
import { dirname, join } from "node:path";
|
|
9838
10521
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
9839
10522
|
function resolvePackageRoot(moduleUrl) {
|
|
9840
10523
|
let dir = dirname(fileURLToPath4(moduleUrl));
|
|
9841
10524
|
for (let depth = 0; depth < 6; depth += 1) {
|
|
9842
|
-
if (
|
|
10525
|
+
if (existsSync39(join(dir, "package.json"))) return dir;
|
|
9843
10526
|
const parent = dirname(dir);
|
|
9844
10527
|
if (parent === dir) break;
|
|
9845
10528
|
dir = parent;
|
|
@@ -9848,7 +10531,7 @@ function resolvePackageRoot(moduleUrl) {
|
|
|
9848
10531
|
}
|
|
9849
10532
|
function readOwnPackageVersion(moduleUrl = import.meta.url) {
|
|
9850
10533
|
const pkgPath = join(resolvePackageRoot(moduleUrl), "package.json");
|
|
9851
|
-
const pkg = JSON.parse(
|
|
10534
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
|
|
9852
10535
|
if (typeof pkg.version !== "string" || !pkg.version.trim()) {
|
|
9853
10536
|
throw new Error(`Missing package.json version at ${pkgPath}`);
|
|
9854
10537
|
}
|
|
@@ -9869,7 +10552,7 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
|
|
|
9869
10552
|
}
|
|
9870
10553
|
|
|
9871
10554
|
// src/post-restart-unblock.ts
|
|
9872
|
-
import
|
|
10555
|
+
import path58 from "node:path";
|
|
9873
10556
|
function skip(runId, worker, taskId, agentOsId, leaseOwner, reason) {
|
|
9874
10557
|
return { runId, worker, taskId, agentOsId, leaseOwner, action: "skipped", reason };
|
|
9875
10558
|
}
|
|
@@ -9882,7 +10565,7 @@ async function postRestartUnblock(args) {
|
|
|
9882
10565
|
const errors = [];
|
|
9883
10566
|
for (const run of listRunRecords()) {
|
|
9884
10567
|
for (const name of Object.keys(run.workers ?? {})) {
|
|
9885
|
-
const workerPath =
|
|
10568
|
+
const workerPath = path58.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
9886
10569
|
const worker = readJson(workerPath, void 0);
|
|
9887
10570
|
if (!worker) {
|
|
9888
10571
|
skipped.push(skip(run.id, name, "", "", "", "worker.json missing"));
|
|
@@ -9994,12 +10677,12 @@ async function postRestartUnblockCli(args) {
|
|
|
9994
10677
|
}
|
|
9995
10678
|
|
|
9996
10679
|
// src/doctor/runtime-takeover.ts
|
|
9997
|
-
import
|
|
10680
|
+
import path60 from "node:path";
|
|
9998
10681
|
|
|
9999
10682
|
// src/doctor/runtime-takeover.probes.ts
|
|
10000
|
-
import { accessSync, constants, existsSync as
|
|
10001
|
-
import { homedir as
|
|
10002
|
-
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";
|
|
10003
10686
|
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
10004
10687
|
function captureCommand(bin, args) {
|
|
10005
10688
|
try {
|
|
@@ -10028,7 +10711,7 @@ function tokenPrefix(token) {
|
|
|
10028
10711
|
return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
|
|
10029
10712
|
}
|
|
10030
10713
|
function isWritable(target) {
|
|
10031
|
-
if (!
|
|
10714
|
+
if (!existsSync40(target)) return false;
|
|
10032
10715
|
try {
|
|
10033
10716
|
accessSync(target, constants.W_OK);
|
|
10034
10717
|
return true;
|
|
@@ -10041,15 +10724,15 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
10041
10724
|
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
10042
10725
|
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
10043
10726
|
loadConfig: () => loadUserConfig(),
|
|
10044
|
-
configFilePath: () =>
|
|
10045
|
-
credentialsFilePath: () =>
|
|
10727
|
+
configFilePath: () => path59.join(homedir14(), ".kynver", "config.json"),
|
|
10728
|
+
credentialsFilePath: () => path59.join(homedir14(), ".kynver", "credentials"),
|
|
10046
10729
|
readCredentials: () => {
|
|
10047
|
-
const credPath =
|
|
10048
|
-
if (!
|
|
10730
|
+
const credPath = path59.join(homedir14(), ".kynver", "credentials");
|
|
10731
|
+
if (!existsSync40(credPath)) {
|
|
10049
10732
|
return { hasApiKey: false };
|
|
10050
10733
|
}
|
|
10051
10734
|
try {
|
|
10052
|
-
const parsed = JSON.parse(
|
|
10735
|
+
const parsed = JSON.parse(readFileSync14(credPath, "utf8"));
|
|
10053
10736
|
return {
|
|
10054
10737
|
hasApiKey: Boolean(parsed.apiKey?.trim()),
|
|
10055
10738
|
runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
|
|
@@ -10068,7 +10751,10 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
10068
10751
|
kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
|
|
10069
10752
|
opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
|
|
10070
10753
|
kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0,
|
|
10071
|
-
openclawCronStorePath: Boolean(
|
|
10754
|
+
openclawCronStorePath: Boolean(
|
|
10755
|
+
process.env.KYNVER_CRON_STORE_PATH?.trim() || process.env.OPENCLAW_CRON_STORE_PATH?.trim()
|
|
10756
|
+
),
|
|
10757
|
+
kynverCronDaemonPrimary: isKynverCronDaemonPrimary(),
|
|
10072
10758
|
qstashTokenPresent: Boolean(process.env.QSTASH_TOKEN?.trim()),
|
|
10073
10759
|
kynverHostedDeployment: (() => {
|
|
10074
10760
|
const v = process.env.KYNVER_HOSTED_DEPLOYMENT?.trim().toLowerCase();
|
|
@@ -10076,8 +10762,8 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
10076
10762
|
})()
|
|
10077
10763
|
}),
|
|
10078
10764
|
harnessRoot: () => resolveHarnessRoot(),
|
|
10079
|
-
legacyOpenclawHarnessRoot: () =>
|
|
10080
|
-
pathExists: (target) =>
|
|
10765
|
+
legacyOpenclawHarnessRoot: () => path59.join(homedir14(), ".openclaw", "harness"),
|
|
10766
|
+
pathExists: (target) => existsSync40(target),
|
|
10081
10767
|
pathWritable: (target) => isWritable(target),
|
|
10082
10768
|
vercelVersion: () => captureCommand("vercel", ["--version"]),
|
|
10083
10769
|
vercelWhoami: () => captureCommand("vercel", ["whoami"])
|
|
@@ -10097,8 +10783,10 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
10097
10783
|
const schedulerDetails = {
|
|
10098
10784
|
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
10099
10785
|
deploymentSchedulerProvider: ctx.deploymentSchedulerProvider ?? null,
|
|
10100
|
-
openclawCronStorePath: Boolean(env.openclawCronStorePath)
|
|
10786
|
+
openclawCronStorePath: Boolean(env.openclawCronStorePath),
|
|
10787
|
+
kynverCronDaemonPrimary: Boolean(env.kynverCronDaemonPrimary)
|
|
10101
10788
|
};
|
|
10789
|
+
const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
|
|
10102
10790
|
if (hasQstashCutover(env, ctx) && !hasLocalOpenClawDependency(env, ctx)) {
|
|
10103
10791
|
const source = env.kynverSchedulerProvider === "qstash" ? "KYNVER_SCHEDULER_PROVIDER=qstash on this host" : "deploymentSchedulerProvider=qstash in ~/.kynver/config.json";
|
|
10104
10792
|
return check({
|
|
@@ -10110,6 +10798,19 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
10110
10798
|
});
|
|
10111
10799
|
}
|
|
10112
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
|
+
}
|
|
10113
10814
|
const parts = [];
|
|
10114
10815
|
if (env.kynverSchedulerProvider === "openclaw-cron") {
|
|
10115
10816
|
parts.push("KYNVER_SCHEDULER_PROVIDER=openclaw-cron");
|
|
@@ -10118,21 +10819,20 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
10118
10819
|
parts.push("deploymentSchedulerProvider=openclaw-cron in config");
|
|
10119
10820
|
}
|
|
10120
10821
|
if (env.openclawCronStorePath) {
|
|
10121
|
-
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)");
|
|
10122
10823
|
}
|
|
10123
10824
|
return check({
|
|
10124
10825
|
id: "hotspot_openclaw_scheduler",
|
|
10125
10826
|
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
10126
10827
|
status: "warn",
|
|
10127
|
-
summary: `
|
|
10128
|
-
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.",
|
|
10129
10830
|
details: schedulerDetails
|
|
10130
10831
|
});
|
|
10131
10832
|
}
|
|
10132
10833
|
const runnerOpenclaw = env.kynverSchedulerProvider === "openclaw-cron";
|
|
10133
10834
|
const runnerQstash = env.kynverSchedulerProvider === "qstash";
|
|
10134
10835
|
const hostedDeployment = Boolean(env.qstashTokenPresent) || Boolean(env.kynverHostedDeployment);
|
|
10135
|
-
const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
|
|
10136
10836
|
const hostedSchedulerProcess = hostedDeployment && !daemonDispatchReady;
|
|
10137
10837
|
const deploymentNeedsQstash = hostedSchedulerProcess && !env.qstashTokenPresent && env.kynverSchedulerProvider !== "qstash";
|
|
10138
10838
|
const deploymentOpenclaw = hostedSchedulerProcess && env.kynverSchedulerProvider === "openclaw-cron";
|
|
@@ -10405,8 +11105,8 @@ function assessVercelCli(probes) {
|
|
|
10405
11105
|
}
|
|
10406
11106
|
function assessHarnessDirs(probes) {
|
|
10407
11107
|
const harnessRoot = probes.harnessRoot();
|
|
10408
|
-
const runsDir =
|
|
10409
|
-
const worktreesDir =
|
|
11108
|
+
const runsDir = path60.join(harnessRoot, "runs");
|
|
11109
|
+
const worktreesDir = path60.join(harnessRoot, "worktrees");
|
|
10410
11110
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
10411
11111
|
const displayRunsDir = redactHomePath(runsDir);
|
|
10412
11112
|
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
@@ -10609,6 +11309,11 @@ var RUNNER_SCHEDULER_CUTOVER_STEPS = [
|
|
|
10609
11309
|
function readSchedulerCutoverEnv(env = process.env) {
|
|
10610
11310
|
return {
|
|
10611
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,
|
|
10612
11317
|
openclawCronStorePath: env.OPENCLAW_CRON_STORE_PATH?.trim() || null,
|
|
10613
11318
|
openclawCronSecret: Boolean(env.OPENCLAW_CRON_SECRET?.trim()),
|
|
10614
11319
|
openclawCronFireBaseUrl: env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || null
|
|
@@ -10619,8 +11324,13 @@ function assessSchedulerCutover(config, env = readSchedulerCutoverEnv()) {
|
|
|
10619
11324
|
if (env.kynverSchedulerProvider === "openclaw-cron") {
|
|
10620
11325
|
blockers.push("Runner still has KYNVER_SCHEDULER_PROVIDER=openclaw-cron");
|
|
10621
11326
|
}
|
|
10622
|
-
if (env.
|
|
10623
|
-
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)");
|
|
10624
11334
|
}
|
|
10625
11335
|
if (config.deploymentSchedulerProvider === "openclaw-cron") {
|
|
10626
11336
|
blockers.push("~/.kynver/config.json deploymentSchedulerProvider is still openclaw-cron");
|
|
@@ -10642,9 +11352,9 @@ function applySchedulerCutoverAttestation(config) {
|
|
|
10642
11352
|
}
|
|
10643
11353
|
|
|
10644
11354
|
// src/scheduler-cutover-cli.ts
|
|
10645
|
-
import
|
|
10646
|
-
import { homedir as
|
|
10647
|
-
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");
|
|
10648
11358
|
function runSchedulerCutoverCheckCli(json = false) {
|
|
10649
11359
|
const config = loadUserConfig();
|
|
10650
11360
|
const report = assessSchedulerCutover(config);
|
|
@@ -10672,7 +11382,10 @@ function runSchedulerCutoverCheckCli(json = false) {
|
|
|
10672
11382
|
` KYNVER_SCHEDULER_PROVIDER: ${report.runnerEnv.kynverSchedulerProvider ?? "(unset)"}`
|
|
10673
11383
|
);
|
|
10674
11384
|
console.log(
|
|
10675
|
-
`
|
|
11385
|
+
` KYNVER_CRON_STORE_PATH: ${report.runnerEnv.kynverCronStorePath ?? "(unset)"}`
|
|
11386
|
+
);
|
|
11387
|
+
console.log(
|
|
11388
|
+
` OPENCLAW_CRON_STORE_PATH (legacy): ${report.runnerEnv.openclawCronStorePath ?? "(unset)"}`
|
|
10676
11389
|
);
|
|
10677
11390
|
if (report.blockers.length) {
|
|
10678
11391
|
console.log("\nBlockers:");
|
|
@@ -10719,6 +11432,65 @@ function runSchedulerAttestCutoverCli(json = false) {
|
|
|
10719
11432
|
console.log(` config: ${payload.configPath}`);
|
|
10720
11433
|
}
|
|
10721
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
|
+
|
|
10722
11494
|
// src/cli.ts
|
|
10723
11495
|
function isHelpFlag(arg) {
|
|
10724
11496
|
return arg === "help" || arg === "--help" || arg === "-h";
|
|
@@ -10769,6 +11541,8 @@ function usage(code = 0) {
|
|
|
10769
11541
|
" kynver doctor runtime-takeover",
|
|
10770
11542
|
" kynver scheduler cutover-check [--json]",
|
|
10771
11543
|
" kynver scheduler attest-cutover [--json]",
|
|
11544
|
+
" kynver cron status [--json]",
|
|
11545
|
+
" kynver cron tick [--agent-os-id AOS_ID] [--json]",
|
|
10772
11546
|
" kynver board contract [--agent-os-id ID] [--base-url URL] [--since ISO] [--limit N]"
|
|
10773
11547
|
].join("\n")
|
|
10774
11548
|
);
|
|
@@ -10780,7 +11554,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
10780
11554
|
const scope = argv.shift();
|
|
10781
11555
|
let action;
|
|
10782
11556
|
let rest;
|
|
10783
|
-
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") {
|
|
10784
11558
|
action = argv.shift();
|
|
10785
11559
|
rest = argv;
|
|
10786
11560
|
} else {
|
|
@@ -10813,6 +11587,12 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
10813
11587
|
if (scope === "scheduler" && action === "attest-cutover") {
|
|
10814
11588
|
return runSchedulerAttestCutoverCli(args.json === true);
|
|
10815
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
|
+
}
|
|
10816
11596
|
if (scope === "board" && action === "contract") {
|
|
10817
11597
|
return void await runCommandCenterContractCli(args);
|
|
10818
11598
|
}
|