@kynver-app/runtime 0.1.91 → 0.1.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/box-identity.d.ts +19 -0
- package/dist/box-resource-snapshot-shared.d.ts +2 -1
- package/dist/cleanup-execute.d.ts +2 -4
- package/dist/cleanup-harness-path-validate.d.ts +5 -0
- package/dist/cleanup-path-ownership.d.ts +9 -0
- package/dist/cleanup-privileged-remove.d.ts +13 -0
- package/dist/cleanup-remove-path.d.ts +17 -0
- package/dist/cleanup-types.d.ts +2 -0
- package/dist/cli.js +1535 -1096
- package/dist/cli.js.map +4 -4
- package/dist/config.d.ts +6 -0
- package/dist/daemon-box-identity.d.ts +16 -0
- package/dist/harness-worktree-build-guard.d.ts +12 -0
- package/dist/index.js +1724 -1280
- package/dist/index.js.map +4 -4
- package/dist/resource-gate.d.ts +5 -0
- package/dist/worker-cap-source.d.ts +29 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,9 +5,9 @@ import { mkdirSync as mkdirSync8, realpathSync } from "node:fs";
|
|
|
5
5
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
6
6
|
|
|
7
7
|
// src/config.ts
|
|
8
|
-
import { existsSync as
|
|
9
|
-
import { homedir as
|
|
10
|
-
import
|
|
8
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9
|
+
import { homedir as homedir4, totalmem } from "node:os";
|
|
10
|
+
import path8 from "node:path";
|
|
11
11
|
|
|
12
12
|
// src/default-repo-discovery.ts
|
|
13
13
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
@@ -381,768 +381,440 @@ function discoverDefaultRepo(opts) {
|
|
|
381
381
|
return discoverDefaultRepoCandidates(opts)[0] ?? null;
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
-
// src/
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
384
|
+
// src/box-identity.ts
|
|
385
|
+
function normalizeWorkerPoolBoxKind(raw) {
|
|
386
|
+
const kind = (raw ?? "").trim().toLowerCase();
|
|
387
|
+
if (kind === "ghost" || kind === "forge") return kind;
|
|
388
|
+
if (kind.includes("forge")) return "forge";
|
|
389
|
+
if (kind.includes("ghost") || kind.includes("openclaw")) return "ghost";
|
|
390
|
+
return "forge";
|
|
391
|
+
}
|
|
392
|
+
function trimEnv(env, key) {
|
|
393
|
+
const value = env[key]?.trim();
|
|
394
|
+
return value || null;
|
|
395
|
+
}
|
|
396
|
+
function resolveBoxIdentity(env = process.env, config = {}) {
|
|
397
|
+
const warnings = [];
|
|
398
|
+
const configKind = config.boxKind?.trim();
|
|
399
|
+
if (configKind) {
|
|
400
|
+
return {
|
|
401
|
+
boxKind: normalizeWorkerPoolBoxKind(configKind),
|
|
402
|
+
source: "config",
|
|
403
|
+
slugInferenceBlocked: false,
|
|
404
|
+
warnings
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
const envKind = trimEnv(env, "KYNVER_BOX_KIND");
|
|
408
|
+
if (envKind) {
|
|
409
|
+
return {
|
|
410
|
+
boxKind: normalizeWorkerPoolBoxKind(envKind),
|
|
411
|
+
source: "env",
|
|
412
|
+
slugInferenceBlocked: false,
|
|
413
|
+
warnings
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
const agentOsSlug = trimEnv(env, "KYNVER_AGENT_OS_SLUG");
|
|
417
|
+
if (agentOsSlug) {
|
|
418
|
+
warnings.push(
|
|
419
|
+
`KYNVER_AGENT_OS_SLUG=${agentOsSlug} is a workspace slug, not box identity \u2014 set boxKind via \`kynver setup --box-kind forge|ghost\` or KYNVER_BOX_KIND (defaulting box kind to forge)`
|
|
420
|
+
);
|
|
394
421
|
}
|
|
422
|
+
return {
|
|
423
|
+
boxKind: "forge",
|
|
424
|
+
source: "default",
|
|
425
|
+
slugInferenceBlocked: Boolean(agentOsSlug),
|
|
426
|
+
warnings
|
|
427
|
+
};
|
|
395
428
|
}
|
|
396
|
-
function
|
|
397
|
-
|
|
398
|
-
writeFileSync2(CONFIG_FILE, `${JSON.stringify(normalizeConfigPaths(config), null, 2)}
|
|
399
|
-
`, { mode: 384 });
|
|
429
|
+
function resolveBoxKindFromConfig(config = {}, env = process.env) {
|
|
430
|
+
return resolveBoxIdentity(env, config).boxKind;
|
|
400
431
|
}
|
|
401
|
-
|
|
432
|
+
|
|
433
|
+
// src/resource-gate.ts
|
|
434
|
+
import os2 from "node:os";
|
|
435
|
+
|
|
436
|
+
// src/bounded-build/meminfo.ts
|
|
437
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
438
|
+
import os from "node:os";
|
|
439
|
+
function readMemAvailableBytes(meminfoText) {
|
|
440
|
+
if (meminfoText !== void 0) {
|
|
441
|
+
const match = meminfoText.match(/^MemAvailable:\s+(\d+)\s*kB/m);
|
|
442
|
+
if (match) return Number(match[1]) * 1024;
|
|
443
|
+
return os.freemem();
|
|
444
|
+
}
|
|
445
|
+
if (process.platform === "linux") {
|
|
446
|
+
try {
|
|
447
|
+
const meminfo = readFileSync3("/proc/meminfo", "utf8");
|
|
448
|
+
const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m);
|
|
449
|
+
if (match) return Number(match[1]) * 1024;
|
|
450
|
+
} catch {
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return os.freemem();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/resource-gate.ts
|
|
457
|
+
import path7 from "node:path";
|
|
458
|
+
|
|
459
|
+
// src/disk-gate.ts
|
|
460
|
+
import { statfsSync as statfsSync2 } from "node:fs";
|
|
461
|
+
|
|
462
|
+
// src/wsl-host.ts
|
|
463
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, statfsSync } from "node:fs";
|
|
464
|
+
var DEFAULT_WSL_HOST_WARN_FREE_BYTES = 25 * 1024 * 1024 * 1024;
|
|
465
|
+
var DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES = 12 * 1024 * 1024 * 1024;
|
|
466
|
+
var DEFAULT_WSL_HOST_MOUNT = "/mnt/c";
|
|
467
|
+
function isWslHost() {
|
|
468
|
+
if (process.platform !== "linux") return false;
|
|
469
|
+
for (const probe of ["/proc/sys/kernel/osrelease", "/proc/version"]) {
|
|
470
|
+
try {
|
|
471
|
+
if (!existsSync3(probe)) continue;
|
|
472
|
+
const text = readFileSync4(probe, "utf8");
|
|
473
|
+
if (/microsoft|wsl/i.test(text)) return true;
|
|
474
|
+
} catch {
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return false;
|
|
478
|
+
}
|
|
479
|
+
function observeWslHostDisk(options = {}) {
|
|
480
|
+
const wsl = options.forceWsl === void 0 ? isWslHost() : options.forceWsl;
|
|
481
|
+
if (!wsl) return null;
|
|
482
|
+
const path67 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
|
|
483
|
+
const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;
|
|
484
|
+
const criticalBelowBytes = options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;
|
|
485
|
+
const statfs = options.statfs ?? statfsSync;
|
|
486
|
+
let stats;
|
|
487
|
+
try {
|
|
488
|
+
stats = statfs(path67);
|
|
489
|
+
} catch (error) {
|
|
490
|
+
return {
|
|
491
|
+
ok: false,
|
|
492
|
+
path: path67,
|
|
493
|
+
freeBytes: 0,
|
|
494
|
+
totalBytes: 0,
|
|
495
|
+
usedPercent: 100,
|
|
496
|
+
warnBelowBytes,
|
|
497
|
+
criticalBelowBytes,
|
|
498
|
+
reason: `Windows host disk probe failed at ${path67}: ${error.message}`,
|
|
499
|
+
probeError: error.message
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
503
|
+
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
504
|
+
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
505
|
+
const lowFree = freeBytes < warnBelowBytes;
|
|
506
|
+
const criticalFree = freeBytes < criticalBelowBytes;
|
|
507
|
+
const ok = !lowFree && !criticalFree;
|
|
508
|
+
const freeGiB = (freeBytes / (1024 * 1024 * 1024)).toFixed(1);
|
|
509
|
+
let reason = null;
|
|
510
|
+
if (!ok) {
|
|
511
|
+
const tag = criticalFree ? "critical" : "warning";
|
|
512
|
+
reason = `Windows host disk ${path67} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
|
|
513
|
+
}
|
|
402
514
|
return {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
515
|
+
ok,
|
|
516
|
+
path: path67,
|
|
517
|
+
freeBytes,
|
|
518
|
+
totalBytes,
|
|
519
|
+
usedPercent,
|
|
520
|
+
warnBelowBytes,
|
|
521
|
+
criticalBelowBytes,
|
|
522
|
+
reason,
|
|
523
|
+
probeError: null
|
|
406
524
|
};
|
|
407
525
|
}
|
|
408
|
-
function
|
|
409
|
-
return
|
|
526
|
+
function summarizeWslRecoverySteps() {
|
|
527
|
+
return "Recovery: 1) free Windows C: (empty Recycle Bin / Storage Sense / clear %TEMP%); 2) shut down WSL (`wsl --shutdown`) then compact the VHDX (`Optimize-VHD` or `diskpart compact vdisk`); 3) clear local node_modules / .next / harness worktrees before restarting workers. Full runbook: docs/runbooks/wsl-disk-pressure.md.";
|
|
410
528
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
529
|
+
|
|
530
|
+
// src/disk-gate.ts
|
|
531
|
+
var DEFAULT_WARN_FREE_BYTES = 30 * 1024 * 1024 * 1024;
|
|
532
|
+
var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
533
|
+
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
534
|
+
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
535
|
+
function observeRunnerDiskGate(input = {}) {
|
|
536
|
+
const path67 = input.diskPath?.trim() || "/";
|
|
537
|
+
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
538
|
+
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
539
|
+
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
540
|
+
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
541
|
+
const stats = statfsSync2(path67);
|
|
542
|
+
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
543
|
+
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
544
|
+
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
545
|
+
const lowFree = freeBytes < warnBelowBytes;
|
|
546
|
+
const criticalFree = freeBytes < criticalBelowBytes;
|
|
547
|
+
const highUse = usedPercent > maxUsedPercent;
|
|
548
|
+
const hardHighUse = usedPercent > hardMaxUsedPercent;
|
|
549
|
+
const localOk = !lowFree && !criticalFree && !highUse && !hardHighUse;
|
|
550
|
+
const wslHost = input.skipWslHostCheck ? null : observeWslHostDisk(input.wslHost);
|
|
551
|
+
const ok = localOk && (wslHost ? wslHost.ok : true);
|
|
552
|
+
let reason = null;
|
|
553
|
+
if (!ok) {
|
|
554
|
+
reason = [
|
|
555
|
+
criticalFree ? `free space below critical ${criticalBelowBytes} bytes` : null,
|
|
556
|
+
lowFree ? `free space below warning ${warnBelowBytes} bytes` : null,
|
|
557
|
+
hardHighUse ? `used percent above hard cap ${hardMaxUsedPercent}%` : null,
|
|
558
|
+
highUse ? `used percent above cap ${maxUsedPercent}%` : null,
|
|
559
|
+
wslHost && !wslHost.ok ? wslHost.reason : null
|
|
560
|
+
].filter(Boolean).join("; ");
|
|
561
|
+
}
|
|
418
562
|
return {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
563
|
+
ok,
|
|
564
|
+
path: path67,
|
|
565
|
+
freeBytes,
|
|
566
|
+
totalBytes,
|
|
567
|
+
usedPercent,
|
|
568
|
+
warnBelowBytes,
|
|
569
|
+
criticalBelowBytes,
|
|
570
|
+
maxUsedPercent,
|
|
571
|
+
hardMaxUsedPercent,
|
|
572
|
+
reason,
|
|
573
|
+
wslHost
|
|
424
574
|
};
|
|
425
575
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
576
|
+
|
|
577
|
+
// src/run-store.ts
|
|
578
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
579
|
+
import path5 from "node:path";
|
|
580
|
+
|
|
581
|
+
// src/paths.ts
|
|
582
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
583
|
+
import { homedir as homedir3 } from "node:os";
|
|
584
|
+
import path4 from "node:path";
|
|
585
|
+
var LEGACY_ROOT = path4.join(homedir3(), ".openclaw", "harness");
|
|
586
|
+
var HARNESS_LAYOUT_DIR_NAMES = /* @__PURE__ */ new Set(["runs", "worktrees"]);
|
|
587
|
+
function normalizeHarnessRoot(root) {
|
|
588
|
+
let resolved = path4.resolve(resolveUserPath(root.trim()));
|
|
589
|
+
while (HARNESS_LAYOUT_DIR_NAMES.has(path4.basename(resolved))) {
|
|
590
|
+
resolved = path4.dirname(resolved);
|
|
432
591
|
}
|
|
592
|
+
return resolved;
|
|
433
593
|
}
|
|
434
|
-
function
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
594
|
+
function resolveHarnessRoot() {
|
|
595
|
+
const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
|
|
596
|
+
if (env) return normalizeHarnessRoot(env);
|
|
597
|
+
const configured = loadUserConfig().harnessRoot?.trim();
|
|
598
|
+
if (configured) return normalizeHarnessRoot(configured);
|
|
599
|
+
const kynverRoot = path4.join(homedir3(), ".kynver", "harness");
|
|
600
|
+
if (existsSync4(kynverRoot)) return kynverRoot;
|
|
601
|
+
if (existsSync4(LEGACY_ROOT)) return LEGACY_ROOT;
|
|
602
|
+
return kynverRoot;
|
|
438
603
|
}
|
|
439
|
-
function
|
|
440
|
-
|
|
441
|
-
return loadCredentialsFile().apiKey;
|
|
604
|
+
function harnessRunsDir(harnessRoot) {
|
|
605
|
+
return path4.join(normalizeHarnessRoot(harnessRoot), "runs");
|
|
442
606
|
}
|
|
443
|
-
function
|
|
444
|
-
|
|
607
|
+
function harnessWorktreesDir(harnessRoot) {
|
|
608
|
+
return path4.join(normalizeHarnessRoot(harnessRoot), "worktrees");
|
|
445
609
|
}
|
|
446
|
-
function
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
}
|
|
454
|
-
return creds.runnerToken;
|
|
610
|
+
function getHarnessPaths() {
|
|
611
|
+
const harnessRoot = resolveHarnessRoot();
|
|
612
|
+
return {
|
|
613
|
+
harnessRoot,
|
|
614
|
+
runsDir: harnessRunsDir(harnessRoot),
|
|
615
|
+
worktreesDir: harnessWorktreesDir(harnessRoot)
|
|
616
|
+
};
|
|
455
617
|
}
|
|
456
|
-
function
|
|
457
|
-
|
|
458
|
-
...loadCredentialsFile(),
|
|
459
|
-
runnerToken: token,
|
|
460
|
-
runnerTokenAgentOsId: agentOsId
|
|
461
|
-
});
|
|
618
|
+
function runDir(runsDir, id) {
|
|
619
|
+
return path4.join(runsDir, safeSlug(id));
|
|
462
620
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
return
|
|
621
|
+
|
|
622
|
+
// src/run-store.ts
|
|
623
|
+
function getPaths() {
|
|
624
|
+
return getHarnessPaths();
|
|
467
625
|
}
|
|
468
|
-
function
|
|
469
|
-
const
|
|
470
|
-
return
|
|
626
|
+
function loadRun(id) {
|
|
627
|
+
const { runsDir } = getPaths();
|
|
628
|
+
return readJson(path5.join(runDir(runsDir, safeSlug(id)), "run.json"));
|
|
471
629
|
}
|
|
472
|
-
function
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
const globalSecret = process.env.KYNVER_RUNTIME_SECRET || process.env.KYNVER_CRON_SECRET || process.env.OPENCLAW_CRON_SECRET;
|
|
476
|
-
if (globalSecret) {
|
|
477
|
-
console.warn(
|
|
478
|
-
"[kynver] using deployment-level callback secret; run `kynver runner credential --agent-os-id <id>` for a scoped token"
|
|
479
|
-
);
|
|
480
|
-
return String(globalSecret);
|
|
481
|
-
}
|
|
482
|
-
return void 0;
|
|
630
|
+
function listRunRecords() {
|
|
631
|
+
const { runsDir } = getPaths();
|
|
632
|
+
return listRunRecordsAt(runsDir);
|
|
483
633
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
if (configured) return configured;
|
|
487
|
-
const apiKey = loadApiKey();
|
|
488
|
-
const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);
|
|
489
|
-
if (apiKey && agentOsId && baseUrl) {
|
|
490
|
-
try {
|
|
491
|
-
const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });
|
|
492
|
-
saveRunnerToken(agentOsId, token);
|
|
493
|
-
return token;
|
|
494
|
-
} catch (error) {
|
|
495
|
-
failConfig(`runner credential mint failed: ${error.message}`);
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
failConfig(
|
|
499
|
-
"requires --secret, KYNVER_RUNNER_TOKEN, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, KYNVER_API_KEY with an API base URL to mint one, or (legacy) KYNVER_RUNTIME_SECRET / KYNVER_CRON_SECRET / OPENCLAW_CRON_SECRET"
|
|
500
|
-
);
|
|
634
|
+
function listRunRecordsForHarnessRoot(harnessRoot) {
|
|
635
|
+
return listRunRecordsAt(harnessRunsDir(harnessRoot));
|
|
501
636
|
}
|
|
502
|
-
|
|
503
|
-
const apiKey = loadApiKey();
|
|
504
|
-
const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);
|
|
505
|
-
if (!apiKey || !agentOsId || !baseUrl) return null;
|
|
637
|
+
function isRunDirectoryEntry(runDirPath) {
|
|
506
638
|
try {
|
|
507
|
-
|
|
508
|
-
saveRunnerToken(agentOsId, token);
|
|
509
|
-
return token;
|
|
639
|
+
return statSync2(runDirPath).isDirectory();
|
|
510
640
|
} catch {
|
|
511
|
-
return
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
async function refreshRunnerTokenForAuthFailure(rejectedSecret, agentOsId, opts) {
|
|
515
|
-
const apiKey = loadApiKey();
|
|
516
|
-
const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);
|
|
517
|
-
if (!apiKey) return { ok: false, reason: "KYNVER_API_KEY is required to refresh a rejected runner token" };
|
|
518
|
-
if (!agentOsId) return { ok: false, reason: "agentOsId is required to refresh a rejected runner token" };
|
|
519
|
-
if (!baseUrl) return { ok: false, reason: "KYNVER_API_URL or --base-url is required to refresh a rejected runner token" };
|
|
520
|
-
try {
|
|
521
|
-
const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });
|
|
522
|
-
if (token && token !== rejectedSecret) {
|
|
523
|
-
saveRunnerToken(agentOsId, token);
|
|
524
|
-
return { ok: true, token };
|
|
525
|
-
}
|
|
526
|
-
return { ok: false, reason: "runner credential refresh returned the rejected token" };
|
|
527
|
-
} catch (error) {
|
|
528
|
-
return { ok: false, reason: error.message };
|
|
641
|
+
return false;
|
|
529
642
|
}
|
|
530
643
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
},
|
|
542
|
-
body: JSON.stringify({})
|
|
543
|
-
});
|
|
544
|
-
const text = await res.text();
|
|
545
|
-
let parsed = null;
|
|
546
|
-
try {
|
|
547
|
-
parsed = JSON.parse(text);
|
|
548
|
-
} catch {
|
|
549
|
-
parsed = null;
|
|
550
|
-
}
|
|
551
|
-
if (!res.ok || !parsed?.token) {
|
|
552
|
-
throw new Error(
|
|
553
|
-
`runner credential mint failed (${res.status}): ${parsed?.error ?? text.slice(0, 200)}`
|
|
644
|
+
function listRunRecordsAt(runsDir) {
|
|
645
|
+
if (!existsSync5(runsDir)) return [];
|
|
646
|
+
const runs = [];
|
|
647
|
+
for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
|
|
648
|
+
if (entry.name === "runs") continue;
|
|
649
|
+
const runDir2 = path5.join(runsDir, entry.name);
|
|
650
|
+
if (!isRunDirectoryEntry(runDir2)) continue;
|
|
651
|
+
const run = readJson(
|
|
652
|
+
path5.join(runDir2, "run.json"),
|
|
653
|
+
void 0
|
|
554
654
|
);
|
|
655
|
+
if (run?.id) runs.push(run);
|
|
555
656
|
}
|
|
556
|
-
return
|
|
657
|
+
return runs;
|
|
557
658
|
}
|
|
558
|
-
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
baseUrl: args.baseUrl ? String(args.baseUrl) : void 0
|
|
564
|
-
});
|
|
565
|
-
saveRunnerToken(agentOsId, token);
|
|
566
|
-
console.log(
|
|
567
|
-
JSON.stringify(
|
|
568
|
-
{
|
|
569
|
-
ok: true,
|
|
570
|
-
agentOsId,
|
|
571
|
-
credentialsPath: displayUserPath(CREDENTIALS_FILE),
|
|
572
|
-
tokenPrefix: `${token.slice(0, 12)}\u2026`,
|
|
573
|
-
note: "Scoped runner token saved; callbacks use X-Kynver-Runner-Token."
|
|
574
|
-
},
|
|
575
|
-
null,
|
|
576
|
-
2
|
|
577
|
-
)
|
|
578
|
-
);
|
|
579
|
-
} catch (err) {
|
|
580
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
581
|
-
process.exit(1);
|
|
582
|
-
}
|
|
659
|
+
function loadWorker(runId, name) {
|
|
660
|
+
const { runsDir } = getPaths();
|
|
661
|
+
return readJson(
|
|
662
|
+
path5.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
|
|
663
|
+
);
|
|
583
664
|
}
|
|
584
|
-
function
|
|
585
|
-
|
|
586
|
-
|
|
665
|
+
function saveRun(run) {
|
|
666
|
+
const { runsDir } = getPaths();
|
|
667
|
+
writeJson(path5.join(runDir(runsDir, run.id), "run.json"), run);
|
|
587
668
|
}
|
|
588
|
-
function
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
-
const item = argv[i];
|
|
592
|
-
if (!item.startsWith("--")) continue;
|
|
593
|
-
const key = item.slice(2).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
594
|
-
const next = argv[i + 1];
|
|
595
|
-
if (!next || next.startsWith("--")) args[key] = true;
|
|
596
|
-
else {
|
|
597
|
-
args[key] = next;
|
|
598
|
-
i++;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
return args;
|
|
669
|
+
function saveWorker(runId, worker) {
|
|
670
|
+
const { runsDir } = getPaths();
|
|
671
|
+
writeJson(path5.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
|
|
602
672
|
}
|
|
603
|
-
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
const config = normalizeConfigPaths({
|
|
607
|
-
...existing,
|
|
608
|
-
...inferSetupFields(existing, args),
|
|
609
|
-
...maxWorkersRaw ? { maxConcurrentWorkers: Math.max(1, Math.floor(Number(maxWorkersRaw))) } : {},
|
|
610
|
-
workerProvider: typeof args.provider === "string" ? args.provider : existing.workerProvider || "cursor"
|
|
611
|
-
});
|
|
612
|
-
saveUserConfig(config);
|
|
613
|
-
let runnerCredentialNote;
|
|
614
|
-
const apiKey = loadApiKey();
|
|
615
|
-
const agentOsId = config.agentOsId;
|
|
616
|
-
if (apiKey && agentOsId) {
|
|
617
|
-
try {
|
|
618
|
-
const token = await fetchRunnerCredential(agentOsId, {
|
|
619
|
-
baseUrl: typeof args.apiBaseUrl === "string" ? args.apiBaseUrl : config.apiBaseUrl,
|
|
620
|
-
apiKey
|
|
621
|
-
});
|
|
622
|
-
saveRunnerToken(agentOsId, token);
|
|
623
|
-
runnerCredentialNote = "Scoped runner token minted and saved to ~/.kynver/credentials.";
|
|
624
|
-
} catch {
|
|
625
|
-
runnerCredentialNote = "Runner token not minted (server offline or master secret unset). Run `kynver runner credential` after deploy.";
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
console.log(
|
|
629
|
-
JSON.stringify(
|
|
630
|
-
{
|
|
631
|
-
ok: true,
|
|
632
|
-
configPath: displayUserPath(CONFIG_FILE),
|
|
633
|
-
config: presentUserConfig(config),
|
|
634
|
-
note: runnerCredentialNote ?? "Set worker limit once with --max-workers N (or omit to auto-size from RAM). Run `kynver login` + `kynver runner credential` for scoped callbacks."
|
|
635
|
-
},
|
|
636
|
-
null,
|
|
637
|
-
2
|
|
638
|
-
)
|
|
639
|
-
);
|
|
673
|
+
function runDirectory(id) {
|
|
674
|
+
const { harnessRoot } = getPaths();
|
|
675
|
+
return runDirectoryAt(harnessRoot, id);
|
|
640
676
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
if (!apiKey) failConfig("kynver login requires --api-key or KYNVER_API_KEY");
|
|
644
|
-
saveApiKey(apiKey);
|
|
645
|
-
console.log(JSON.stringify({ ok: true, credentialsPath: displayUserPath(CREDENTIALS_FILE) }, null, 2));
|
|
677
|
+
function runDirectoryAt(harnessRoot, id) {
|
|
678
|
+
return runDir(harnessRunsDir(harnessRoot), safeSlug(id));
|
|
646
679
|
}
|
|
647
680
|
|
|
648
|
-
// src/
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
};
|
|
681
|
+
// src/run-worker-index.ts
|
|
682
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3 } from "node:fs";
|
|
683
|
+
import path6 from "node:path";
|
|
684
|
+
function listRunWorkerNames(run) {
|
|
685
|
+
const names = /* @__PURE__ */ new Set();
|
|
686
|
+
for (const name of Object.keys(run.workers || {})) {
|
|
687
|
+
names.add(safeSlug(name));
|
|
656
688
|
}
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
// rename compat window.
|
|
663
|
-
"X-Kynver-Cron-Secret": trimmed,
|
|
664
|
-
"X-OpenClaw-Cron-Secret": trimmed,
|
|
665
|
-
"X-Kynver-Runtime-Secret": trimmed
|
|
666
|
-
};
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// src/callbacks.ts
|
|
670
|
-
function callbackTimeoutMs() {
|
|
671
|
-
const parsed = Number(process.env.KYNVER_CALLBACK_TIMEOUT_MS);
|
|
672
|
-
if (Number.isFinite(parsed) && parsed > 0) return Math.floor(parsed);
|
|
673
|
-
return 3e4;
|
|
674
|
-
}
|
|
675
|
-
async function withTimeout(fn) {
|
|
676
|
-
const controller = new AbortController();
|
|
677
|
-
const timeout = setTimeout(() => controller.abort(), callbackTimeoutMs());
|
|
678
|
-
try {
|
|
679
|
-
return await fn(controller.signal);
|
|
680
|
-
} finally {
|
|
681
|
-
clearTimeout(timeout);
|
|
689
|
+
const workersDir = path6.join(runDirectory(run.id), "workers");
|
|
690
|
+
if (!existsSync6(workersDir)) return [...names];
|
|
691
|
+
for (const entry of readdirSync3(workersDir, { withFileTypes: true })) {
|
|
692
|
+
if (!entry.isDirectory()) continue;
|
|
693
|
+
names.add(safeSlug(entry.name));
|
|
682
694
|
}
|
|
695
|
+
return [...names];
|
|
683
696
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
);
|
|
693
|
-
let response = null;
|
|
697
|
+
|
|
698
|
+
// src/heartbeat.ts
|
|
699
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "node:fs";
|
|
700
|
+
|
|
701
|
+
// src/heartbeat-final-result.ts
|
|
702
|
+
function tryParseJsonObject(text) {
|
|
703
|
+
const trimmed = text.trim();
|
|
704
|
+
if (!trimmed.startsWith("{")) return null;
|
|
694
705
|
try {
|
|
695
|
-
|
|
706
|
+
const parsed = JSON.parse(trimmed);
|
|
707
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
708
|
+
return parsed;
|
|
709
|
+
}
|
|
696
710
|
} catch {
|
|
697
|
-
|
|
711
|
+
return null;
|
|
698
712
|
}
|
|
699
|
-
return
|
|
700
|
-
}
|
|
701
|
-
async function postJsonWithCredentialRefresh(url, secret, body, opts) {
|
|
702
|
-
const first = await postJson(url, secret, body);
|
|
703
|
-
if (first.ok || first.status !== 401) return first;
|
|
704
|
-
const refreshed = await refreshRunnerTokenForAuthFailure(secret, opts.agentOsId, { baseUrl: opts.baseUrl });
|
|
705
|
-
if (!refreshed.ok) return { ...first, authRefreshFailure: refreshed.reason };
|
|
706
|
-
const retry = await postJson(url, refreshed.token, body);
|
|
707
|
-
return { ...retry, refreshedAuth: true };
|
|
713
|
+
return null;
|
|
708
714
|
}
|
|
709
|
-
|
|
710
|
-
const
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
} catch {
|
|
721
|
-
response = null;
|
|
715
|
+
function embeddedRecordFromProse(text) {
|
|
716
|
+
const trimmed = text.trim();
|
|
717
|
+
if (!trimmed) return null;
|
|
718
|
+
const direct = tryParseJsonObject(trimmed);
|
|
719
|
+
if (direct) return direct;
|
|
720
|
+
const candidates = [];
|
|
721
|
+
const fenceRe = /```(?:json)?\s*([\s\S]*?)```/gi;
|
|
722
|
+
let fenceMatch;
|
|
723
|
+
while ((fenceMatch = fenceRe.exec(trimmed)) !== null) {
|
|
724
|
+
const fromFence = tryParseJsonObject(fenceMatch[1] ?? "");
|
|
725
|
+
if (fromFence) candidates.push(fromFence);
|
|
722
726
|
}
|
|
723
|
-
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
return (value ?? "").trim().toLowerCase();
|
|
729
|
-
}
|
|
730
|
-
function roleLaneToDispatchLane(roleLane) {
|
|
731
|
-
switch (trimLower(roleLane)) {
|
|
732
|
-
case "implementer":
|
|
733
|
-
case "repair_implementer":
|
|
734
|
-
case "plan_author":
|
|
735
|
-
case "runtime_verifier":
|
|
736
|
-
return "implementation";
|
|
737
|
-
case "plan_reviewer":
|
|
738
|
-
case "report_reviewer":
|
|
739
|
-
case "deep_reviewer":
|
|
740
|
-
return "review";
|
|
741
|
-
default:
|
|
742
|
-
return null;
|
|
727
|
+
const firstBrace = trimmed.indexOf("{");
|
|
728
|
+
const lastBrace = trimmed.lastIndexOf("}");
|
|
729
|
+
if (firstBrace >= 0 && lastBrace > firstBrace) {
|
|
730
|
+
const slice = tryParseJsonObject(trimmed.slice(firstBrace, lastBrace + 1));
|
|
731
|
+
if (slice) candidates.push(slice);
|
|
743
732
|
}
|
|
733
|
+
return candidates.length > 0 ? candidates[candidates.length - 1] : null;
|
|
744
734
|
}
|
|
745
|
-
function
|
|
746
|
-
const
|
|
747
|
-
if (
|
|
748
|
-
|
|
749
|
-
|
|
735
|
+
function terminalFinalResultFromHeartbeatRow(row) {
|
|
736
|
+
const explicit = row.finalResult ?? row.final_result;
|
|
737
|
+
if (explicit !== void 0 && explicit !== null) {
|
|
738
|
+
if (typeof explicit === "string") {
|
|
739
|
+
const embedded2 = embeddedRecordFromProse(explicit);
|
|
740
|
+
return embedded2 ?? (explicit.trim() || null);
|
|
741
|
+
}
|
|
742
|
+
return explicit;
|
|
750
743
|
}
|
|
751
|
-
const
|
|
752
|
-
if (
|
|
753
|
-
|
|
754
|
-
if (
|
|
755
|
-
return
|
|
756
|
-
}
|
|
757
|
-
function resolveDispatchNextLaneFilter(raw) {
|
|
758
|
-
return normalizeDispatchNextLaneFilter(raw) ?? "any";
|
|
744
|
+
const summary = typeof row.summary === "string" ? row.summary.trim() : "";
|
|
745
|
+
if (!summary) return null;
|
|
746
|
+
const embedded = embeddedRecordFromProse(summary);
|
|
747
|
+
if (embedded) return embedded;
|
|
748
|
+
return summary;
|
|
759
749
|
}
|
|
760
750
|
|
|
761
|
-
// src/
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
import { existsSync as existsSync4, readFileSync as readFileSync4, statfsSync } from "node:fs";
|
|
766
|
-
var DEFAULT_WSL_HOST_WARN_FREE_BYTES = 25 * 1024 * 1024 * 1024;
|
|
767
|
-
var DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES = 12 * 1024 * 1024 * 1024;
|
|
768
|
-
var DEFAULT_WSL_HOST_MOUNT = "/mnt/c";
|
|
769
|
-
function isWslHost() {
|
|
770
|
-
if (process.platform !== "linux") return false;
|
|
771
|
-
for (const probe of ["/proc/sys/kernel/osrelease", "/proc/version"]) {
|
|
772
|
-
try {
|
|
773
|
-
if (!existsSync4(probe)) continue;
|
|
774
|
-
const text = readFileSync4(probe, "utf8");
|
|
775
|
-
if (/microsoft|wsl/i.test(text)) return true;
|
|
776
|
-
} catch {
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
return false;
|
|
751
|
+
// src/heartbeat.ts
|
|
752
|
+
var HEARTBEAT_FUTURE_SKEW_MS = 6e4;
|
|
753
|
+
function isTerminalHeartbeatPhase(phase) {
|
|
754
|
+
return phase === "complete";
|
|
780
755
|
}
|
|
781
|
-
function
|
|
782
|
-
|
|
783
|
-
if (
|
|
784
|
-
|
|
785
|
-
const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;
|
|
786
|
-
const criticalBelowBytes = options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;
|
|
787
|
-
const statfs = options.statfs ?? statfsSync;
|
|
788
|
-
let stats;
|
|
789
|
-
try {
|
|
790
|
-
stats = statfs(path65);
|
|
791
|
-
} catch (error) {
|
|
792
|
-
return {
|
|
793
|
-
ok: false,
|
|
794
|
-
path: path65,
|
|
795
|
-
freeBytes: 0,
|
|
796
|
-
totalBytes: 0,
|
|
797
|
-
usedPercent: 100,
|
|
798
|
-
warnBelowBytes,
|
|
799
|
-
criticalBelowBytes,
|
|
800
|
-
reason: `Windows host disk probe failed at ${path65}: ${error.message}`,
|
|
801
|
-
probeError: error.message
|
|
802
|
-
};
|
|
803
|
-
}
|
|
804
|
-
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
805
|
-
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
806
|
-
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
807
|
-
const lowFree = freeBytes < warnBelowBytes;
|
|
808
|
-
const criticalFree = freeBytes < criticalBelowBytes;
|
|
809
|
-
const ok = !lowFree && !criticalFree;
|
|
810
|
-
const freeGiB = (freeBytes / (1024 * 1024 * 1024)).toFixed(1);
|
|
811
|
-
let reason = null;
|
|
812
|
-
if (!ok) {
|
|
813
|
-
const tag = criticalFree ? "critical" : "warning";
|
|
814
|
-
reason = `Windows host disk ${path65} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
|
|
756
|
+
function terminalFinalResultFromHeartbeat(heartbeat) {
|
|
757
|
+
if (!isTerminalHeartbeatPhase(heartbeat.lastHeartbeatPhase)) return null;
|
|
758
|
+
if (heartbeat.terminalFinalResult !== void 0 && heartbeat.terminalFinalResult !== null) {
|
|
759
|
+
return heartbeat.terminalFinalResult;
|
|
815
760
|
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
path: path65,
|
|
819
|
-
freeBytes,
|
|
820
|
-
totalBytes,
|
|
821
|
-
usedPercent,
|
|
822
|
-
warnBelowBytes,
|
|
823
|
-
criticalBelowBytes,
|
|
824
|
-
reason,
|
|
825
|
-
probeError: null
|
|
826
|
-
};
|
|
827
|
-
}
|
|
828
|
-
function summarizeWslRecoverySteps() {
|
|
829
|
-
return "Recovery: 1) free Windows C: (empty Recycle Bin / Storage Sense / clear %TEMP%); 2) shut down WSL (`wsl --shutdown`) then compact the VHDX (`Optimize-VHD` or `diskpart compact vdisk`); 3) clear local node_modules / .next / harness worktrees before restarting workers. Full runbook: docs/runbooks/wsl-disk-pressure.md.";
|
|
761
|
+
const summary = heartbeat.lastHeartbeatSummary?.trim();
|
|
762
|
+
return summary || "completed";
|
|
830
763
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
843
|
-
const stats = statfsSync2(path65);
|
|
844
|
-
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
845
|
-
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
846
|
-
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
847
|
-
const lowFree = freeBytes < warnBelowBytes;
|
|
848
|
-
const criticalFree = freeBytes < criticalBelowBytes;
|
|
849
|
-
const highUse = usedPercent > maxUsedPercent;
|
|
850
|
-
const hardHighUse = usedPercent > hardMaxUsedPercent;
|
|
851
|
-
const localOk = !lowFree && !criticalFree && !highUse && !hardHighUse;
|
|
852
|
-
const wslHost = input.skipWslHostCheck ? null : observeWslHostDisk(input.wslHost);
|
|
853
|
-
const ok = localOk && (wslHost ? wslHost.ok : true);
|
|
854
|
-
let reason = null;
|
|
855
|
-
if (!ok) {
|
|
856
|
-
reason = [
|
|
857
|
-
criticalFree ? `free space below critical ${criticalBelowBytes} bytes` : null,
|
|
858
|
-
lowFree ? `free space below warning ${warnBelowBytes} bytes` : null,
|
|
859
|
-
hardHighUse ? `used percent above hard cap ${hardMaxUsedPercent}%` : null,
|
|
860
|
-
highUse ? `used percent above cap ${maxUsedPercent}%` : null,
|
|
861
|
-
wslHost && !wslHost.ok ? wslHost.reason : null
|
|
862
|
-
].filter(Boolean).join("; ");
|
|
863
|
-
}
|
|
864
|
-
return {
|
|
865
|
-
ok,
|
|
866
|
-
path: path65,
|
|
867
|
-
freeBytes,
|
|
868
|
-
totalBytes,
|
|
869
|
-
usedPercent,
|
|
870
|
-
warnBelowBytes,
|
|
871
|
-
criticalBelowBytes,
|
|
872
|
-
maxUsedPercent,
|
|
873
|
-
hardMaxUsedPercent,
|
|
874
|
-
reason,
|
|
875
|
-
wslHost
|
|
764
|
+
function parseHeartbeat(file) {
|
|
765
|
+
const result = {
|
|
766
|
+
heartbeatCount: 0,
|
|
767
|
+
lastHeartbeatAt: null,
|
|
768
|
+
lastHeartbeatPhase: null,
|
|
769
|
+
lastHeartbeatSummary: null,
|
|
770
|
+
terminalFinalResult: null,
|
|
771
|
+
heartbeatBlocker: null,
|
|
772
|
+
timestampAnomalies: [],
|
|
773
|
+
lastBoxResourceSnapshot: null,
|
|
774
|
+
lastPrEvidence: []
|
|
876
775
|
};
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
776
|
+
if (!existsSync7(file)) return result;
|
|
777
|
+
const maxFutureMs = Date.now() + HEARTBEAT_FUTURE_SKEW_MS;
|
|
778
|
+
const clampedTo = new Date(maxFutureMs).toISOString();
|
|
779
|
+
const lines = readFileSync5(file, "utf8").split("\n").filter(Boolean);
|
|
780
|
+
for (const line of lines) {
|
|
781
|
+
const entry = safeJson(line);
|
|
782
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
783
|
+
const row = entry;
|
|
784
|
+
result.heartbeatCount++;
|
|
785
|
+
if (row.ts) {
|
|
786
|
+
const ts = String(row.ts);
|
|
787
|
+
const tsMs = Date.parse(ts);
|
|
788
|
+
if (Number.isFinite(tsMs) && tsMs > maxFutureMs) {
|
|
789
|
+
result.timestampAnomalies.push({
|
|
790
|
+
kind: "future_heartbeat_timestamp",
|
|
791
|
+
observedAt: ts,
|
|
792
|
+
clampedTo
|
|
793
|
+
});
|
|
794
|
+
} else {
|
|
795
|
+
result.lastHeartbeatAt = ts;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (row.phase !== void 0 && row.phase !== null) result.lastHeartbeatPhase = String(row.phase);
|
|
799
|
+
if (row.summary !== void 0 && row.summary !== null) result.lastHeartbeatSummary = String(row.summary);
|
|
800
|
+
if (isTerminalHeartbeatPhase(result.lastHeartbeatPhase)) {
|
|
801
|
+
result.terminalFinalResult = terminalFinalResultFromHeartbeatRow(row);
|
|
802
|
+
}
|
|
803
|
+
result.heartbeatBlocker = row.blocker ? String(row.blocker) : null;
|
|
804
|
+
if (row.boxResourceSnapshot && typeof row.boxResourceSnapshot === "object" && !Array.isArray(row.boxResourceSnapshot)) {
|
|
805
|
+
result.lastBoxResourceSnapshot = row.boxResourceSnapshot;
|
|
806
|
+
}
|
|
807
|
+
if (Array.isArray(row.prEvidence)) {
|
|
808
|
+
result.lastPrEvidence = row.prEvidence.filter(
|
|
809
|
+
(entry2) => !!entry2 && typeof entry2 === "object" && typeof entry2.prUrl === "string"
|
|
810
|
+
);
|
|
897
811
|
}
|
|
898
812
|
}
|
|
899
|
-
return
|
|
813
|
+
return result;
|
|
900
814
|
}
|
|
901
815
|
|
|
902
|
-
// src/
|
|
903
|
-
import
|
|
904
|
-
|
|
905
|
-
// src/run-store.ts
|
|
906
|
-
import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2 } from "node:fs";
|
|
907
|
-
import path6 from "node:path";
|
|
908
|
-
|
|
909
|
-
// src/paths.ts
|
|
910
|
-
import { existsSync as existsSync5 } from "node:fs";
|
|
911
|
-
import { homedir as homedir4 } from "node:os";
|
|
912
|
-
import path5 from "node:path";
|
|
913
|
-
var LEGACY_ROOT = path5.join(homedir4(), ".openclaw", "harness");
|
|
914
|
-
var HARNESS_LAYOUT_DIR_NAMES = /* @__PURE__ */ new Set(["runs", "worktrees"]);
|
|
915
|
-
function normalizeHarnessRoot(root) {
|
|
916
|
-
let resolved = path5.resolve(resolveUserPath(root.trim()));
|
|
917
|
-
while (HARNESS_LAYOUT_DIR_NAMES.has(path5.basename(resolved))) {
|
|
918
|
-
resolved = path5.dirname(resolved);
|
|
919
|
-
}
|
|
920
|
-
return resolved;
|
|
921
|
-
}
|
|
922
|
-
function resolveHarnessRoot() {
|
|
923
|
-
const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
|
|
924
|
-
if (env) return normalizeHarnessRoot(env);
|
|
925
|
-
const configured = loadUserConfig().harnessRoot?.trim();
|
|
926
|
-
if (configured) return normalizeHarnessRoot(configured);
|
|
927
|
-
const kynverRoot = path5.join(homedir4(), ".kynver", "harness");
|
|
928
|
-
if (existsSync5(kynverRoot)) return kynverRoot;
|
|
929
|
-
if (existsSync5(LEGACY_ROOT)) return LEGACY_ROOT;
|
|
930
|
-
return kynverRoot;
|
|
931
|
-
}
|
|
932
|
-
function harnessRunsDir(harnessRoot) {
|
|
933
|
-
return path5.join(normalizeHarnessRoot(harnessRoot), "runs");
|
|
934
|
-
}
|
|
935
|
-
function harnessWorktreesDir(harnessRoot) {
|
|
936
|
-
return path5.join(normalizeHarnessRoot(harnessRoot), "worktrees");
|
|
937
|
-
}
|
|
938
|
-
function getHarnessPaths() {
|
|
939
|
-
const harnessRoot = resolveHarnessRoot();
|
|
940
|
-
return {
|
|
941
|
-
harnessRoot,
|
|
942
|
-
runsDir: harnessRunsDir(harnessRoot),
|
|
943
|
-
worktreesDir: harnessWorktreesDir(harnessRoot)
|
|
944
|
-
};
|
|
945
|
-
}
|
|
946
|
-
function runDir(runsDir, id) {
|
|
947
|
-
return path5.join(runsDir, safeSlug(id));
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
// src/run-store.ts
|
|
951
|
-
function getPaths() {
|
|
952
|
-
return getHarnessPaths();
|
|
953
|
-
}
|
|
954
|
-
function loadRun(id) {
|
|
955
|
-
const { runsDir } = getPaths();
|
|
956
|
-
return readJson(path6.join(runDir(runsDir, safeSlug(id)), "run.json"));
|
|
957
|
-
}
|
|
958
|
-
function listRunRecords() {
|
|
959
|
-
const { runsDir } = getPaths();
|
|
960
|
-
return listRunRecordsAt(runsDir);
|
|
961
|
-
}
|
|
962
|
-
function listRunRecordsForHarnessRoot(harnessRoot) {
|
|
963
|
-
return listRunRecordsAt(harnessRunsDir(harnessRoot));
|
|
964
|
-
}
|
|
965
|
-
function isRunDirectoryEntry(runDirPath) {
|
|
966
|
-
try {
|
|
967
|
-
return statSync2(runDirPath).isDirectory();
|
|
968
|
-
} catch {
|
|
969
|
-
return false;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
function listRunRecordsAt(runsDir) {
|
|
973
|
-
if (!existsSync6(runsDir)) return [];
|
|
974
|
-
const runs = [];
|
|
975
|
-
for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
|
|
976
|
-
if (entry.name === "runs") continue;
|
|
977
|
-
const runDir2 = path6.join(runsDir, entry.name);
|
|
978
|
-
if (!isRunDirectoryEntry(runDir2)) continue;
|
|
979
|
-
const run = readJson(
|
|
980
|
-
path6.join(runDir2, "run.json"),
|
|
981
|
-
void 0
|
|
982
|
-
);
|
|
983
|
-
if (run?.id) runs.push(run);
|
|
984
|
-
}
|
|
985
|
-
return runs;
|
|
986
|
-
}
|
|
987
|
-
function loadWorker(runId, name) {
|
|
988
|
-
const { runsDir } = getPaths();
|
|
989
|
-
return readJson(
|
|
990
|
-
path6.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
|
|
991
|
-
);
|
|
992
|
-
}
|
|
993
|
-
function saveRun(run) {
|
|
994
|
-
const { runsDir } = getPaths();
|
|
995
|
-
writeJson(path6.join(runDir(runsDir, run.id), "run.json"), run);
|
|
996
|
-
}
|
|
997
|
-
function saveWorker(runId, worker) {
|
|
998
|
-
const { runsDir } = getPaths();
|
|
999
|
-
writeJson(path6.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
|
|
1000
|
-
}
|
|
1001
|
-
function runDirectory(id) {
|
|
1002
|
-
const { harnessRoot } = getPaths();
|
|
1003
|
-
return runDirectoryAt(harnessRoot, id);
|
|
1004
|
-
}
|
|
1005
|
-
function runDirectoryAt(harnessRoot, id) {
|
|
1006
|
-
return runDir(harnessRunsDir(harnessRoot), safeSlug(id));
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
// src/run-worker-index.ts
|
|
1010
|
-
import { existsSync as existsSync7, readdirSync as readdirSync3 } from "node:fs";
|
|
1011
|
-
import path7 from "node:path";
|
|
1012
|
-
function listRunWorkerNames(run) {
|
|
1013
|
-
const names = /* @__PURE__ */ new Set();
|
|
1014
|
-
for (const name of Object.keys(run.workers || {})) {
|
|
1015
|
-
names.add(safeSlug(name));
|
|
1016
|
-
}
|
|
1017
|
-
const workersDir = path7.join(runDirectory(run.id), "workers");
|
|
1018
|
-
if (!existsSync7(workersDir)) return [...names];
|
|
1019
|
-
for (const entry of readdirSync3(workersDir, { withFileTypes: true })) {
|
|
1020
|
-
if (!entry.isDirectory()) continue;
|
|
1021
|
-
names.add(safeSlug(entry.name));
|
|
1022
|
-
}
|
|
1023
|
-
return [...names];
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
// src/heartbeat.ts
|
|
1027
|
-
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
|
|
1028
|
-
|
|
1029
|
-
// src/heartbeat-final-result.ts
|
|
1030
|
-
function tryParseJsonObject(text) {
|
|
1031
|
-
const trimmed = text.trim();
|
|
1032
|
-
if (!trimmed.startsWith("{")) return null;
|
|
1033
|
-
try {
|
|
1034
|
-
const parsed = JSON.parse(trimmed);
|
|
1035
|
-
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1036
|
-
return parsed;
|
|
1037
|
-
}
|
|
1038
|
-
} catch {
|
|
1039
|
-
return null;
|
|
1040
|
-
}
|
|
1041
|
-
return null;
|
|
1042
|
-
}
|
|
1043
|
-
function embeddedRecordFromProse(text) {
|
|
1044
|
-
const trimmed = text.trim();
|
|
1045
|
-
if (!trimmed) return null;
|
|
1046
|
-
const direct = tryParseJsonObject(trimmed);
|
|
1047
|
-
if (direct) return direct;
|
|
1048
|
-
const candidates = [];
|
|
1049
|
-
const fenceRe = /```(?:json)?\s*([\s\S]*?)```/gi;
|
|
1050
|
-
let fenceMatch;
|
|
1051
|
-
while ((fenceMatch = fenceRe.exec(trimmed)) !== null) {
|
|
1052
|
-
const fromFence = tryParseJsonObject(fenceMatch[1] ?? "");
|
|
1053
|
-
if (fromFence) candidates.push(fromFence);
|
|
1054
|
-
}
|
|
1055
|
-
const firstBrace = trimmed.indexOf("{");
|
|
1056
|
-
const lastBrace = trimmed.lastIndexOf("}");
|
|
1057
|
-
if (firstBrace >= 0 && lastBrace > firstBrace) {
|
|
1058
|
-
const slice = tryParseJsonObject(trimmed.slice(firstBrace, lastBrace + 1));
|
|
1059
|
-
if (slice) candidates.push(slice);
|
|
1060
|
-
}
|
|
1061
|
-
return candidates.length > 0 ? candidates[candidates.length - 1] : null;
|
|
1062
|
-
}
|
|
1063
|
-
function terminalFinalResultFromHeartbeatRow(row) {
|
|
1064
|
-
const explicit = row.finalResult ?? row.final_result;
|
|
1065
|
-
if (explicit !== void 0 && explicit !== null) {
|
|
1066
|
-
if (typeof explicit === "string") {
|
|
1067
|
-
const embedded2 = embeddedRecordFromProse(explicit);
|
|
1068
|
-
return embedded2 ?? (explicit.trim() || null);
|
|
1069
|
-
}
|
|
1070
|
-
return explicit;
|
|
1071
|
-
}
|
|
1072
|
-
const summary = typeof row.summary === "string" ? row.summary.trim() : "";
|
|
1073
|
-
if (!summary) return null;
|
|
1074
|
-
const embedded = embeddedRecordFromProse(summary);
|
|
1075
|
-
if (embedded) return embedded;
|
|
1076
|
-
return summary;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
// src/heartbeat.ts
|
|
1080
|
-
var HEARTBEAT_FUTURE_SKEW_MS = 6e4;
|
|
1081
|
-
function isTerminalHeartbeatPhase(phase) {
|
|
1082
|
-
return phase === "complete";
|
|
1083
|
-
}
|
|
1084
|
-
function terminalFinalResultFromHeartbeat(heartbeat) {
|
|
1085
|
-
if (!isTerminalHeartbeatPhase(heartbeat.lastHeartbeatPhase)) return null;
|
|
1086
|
-
if (heartbeat.terminalFinalResult !== void 0 && heartbeat.terminalFinalResult !== null) {
|
|
1087
|
-
return heartbeat.terminalFinalResult;
|
|
1088
|
-
}
|
|
1089
|
-
const summary = heartbeat.lastHeartbeatSummary?.trim();
|
|
1090
|
-
return summary || "completed";
|
|
1091
|
-
}
|
|
1092
|
-
function parseHeartbeat(file) {
|
|
1093
|
-
const result = {
|
|
1094
|
-
heartbeatCount: 0,
|
|
1095
|
-
lastHeartbeatAt: null,
|
|
1096
|
-
lastHeartbeatPhase: null,
|
|
1097
|
-
lastHeartbeatSummary: null,
|
|
1098
|
-
terminalFinalResult: null,
|
|
1099
|
-
heartbeatBlocker: null,
|
|
1100
|
-
timestampAnomalies: [],
|
|
1101
|
-
lastBoxResourceSnapshot: null,
|
|
1102
|
-
lastPrEvidence: []
|
|
1103
|
-
};
|
|
1104
|
-
if (!existsSync8(file)) return result;
|
|
1105
|
-
const maxFutureMs = Date.now() + HEARTBEAT_FUTURE_SKEW_MS;
|
|
1106
|
-
const clampedTo = new Date(maxFutureMs).toISOString();
|
|
1107
|
-
const lines = readFileSync6(file, "utf8").split("\n").filter(Boolean);
|
|
1108
|
-
for (const line of lines) {
|
|
1109
|
-
const entry = safeJson(line);
|
|
1110
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
1111
|
-
const row = entry;
|
|
1112
|
-
result.heartbeatCount++;
|
|
1113
|
-
if (row.ts) {
|
|
1114
|
-
const ts = String(row.ts);
|
|
1115
|
-
const tsMs = Date.parse(ts);
|
|
1116
|
-
if (Number.isFinite(tsMs) && tsMs > maxFutureMs) {
|
|
1117
|
-
result.timestampAnomalies.push({
|
|
1118
|
-
kind: "future_heartbeat_timestamp",
|
|
1119
|
-
observedAt: ts,
|
|
1120
|
-
clampedTo
|
|
1121
|
-
});
|
|
1122
|
-
} else {
|
|
1123
|
-
result.lastHeartbeatAt = ts;
|
|
1124
|
-
}
|
|
1125
|
-
}
|
|
1126
|
-
if (row.phase !== void 0 && row.phase !== null) result.lastHeartbeatPhase = String(row.phase);
|
|
1127
|
-
if (row.summary !== void 0 && row.summary !== null) result.lastHeartbeatSummary = String(row.summary);
|
|
1128
|
-
if (isTerminalHeartbeatPhase(result.lastHeartbeatPhase)) {
|
|
1129
|
-
result.terminalFinalResult = terminalFinalResultFromHeartbeatRow(row);
|
|
1130
|
-
}
|
|
1131
|
-
result.heartbeatBlocker = row.blocker ? String(row.blocker) : null;
|
|
1132
|
-
if (row.boxResourceSnapshot && typeof row.boxResourceSnapshot === "object" && !Array.isArray(row.boxResourceSnapshot)) {
|
|
1133
|
-
result.lastBoxResourceSnapshot = row.boxResourceSnapshot;
|
|
1134
|
-
}
|
|
1135
|
-
if (Array.isArray(row.prEvidence)) {
|
|
1136
|
-
result.lastPrEvidence = row.prEvidence.filter(
|
|
1137
|
-
(entry2) => !!entry2 && typeof entry2 === "object" && typeof entry2.prUrl === "string"
|
|
1138
|
-
);
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
return result;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
// src/stream.ts
|
|
1145
|
-
import { existsSync as existsSync9, readFileSync as readFileSync7 } from "node:fs";
|
|
816
|
+
// src/stream.ts
|
|
817
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "node:fs";
|
|
1146
818
|
|
|
1147
819
|
// src/repo-search.ts
|
|
1148
820
|
var RG_BINARIES = /* @__PURE__ */ new Set(["rg", "ripgrep", "grep"]);
|
|
@@ -1626,8 +1298,8 @@ function parseHarnessStream(file) {
|
|
|
1626
1298
|
error: null,
|
|
1627
1299
|
lastShellOutcome: null
|
|
1628
1300
|
};
|
|
1629
|
-
if (!
|
|
1630
|
-
const lines =
|
|
1301
|
+
if (!existsSync8(file)) return result;
|
|
1302
|
+
const lines = readFileSync6(file, "utf8").split("\n").filter(Boolean);
|
|
1631
1303
|
for (const line of lines) {
|
|
1632
1304
|
const event = safeJson(line);
|
|
1633
1305
|
if (!event) continue;
|
|
@@ -2221,161 +1893,675 @@ function computeWorkerStatus(worker, options = {}) {
|
|
|
2221
1893
|
});
|
|
2222
1894
|
const workerStatusLabel = completionBlocker || attention.state === "blocked" ? "blocked" : completionAcknowledged || attention.state === "done" ? "done" : finalResult ? "exited" : alive ? "running" : "exited";
|
|
2223
1895
|
return {
|
|
2224
|
-
runId: worker.runId,
|
|
2225
|
-
worker: worker.name,
|
|
2226
|
-
pid: worker.pid,
|
|
2227
|
-
alive,
|
|
2228
|
-
status: workerStatusLabel,
|
|
2229
|
-
attention,
|
|
2230
|
-
branch: worker.branch,
|
|
2231
|
-
worktreePath: worker.worktreePath,
|
|
2232
|
-
ownedPaths: worker.ownedPaths,
|
|
2233
|
-
stdoutBytes,
|
|
2234
|
-
stderrBytes,
|
|
2235
|
-
heartbeatBytes,
|
|
2236
|
-
firstEventAt: parsed.firstEventAt,
|
|
2237
|
-
lastEventAt: parsed.lastEventAt,
|
|
2238
|
-
lastActivityAt,
|
|
2239
|
-
currentTool: completionAcknowledged ? null : parsed.currentTool,
|
|
2240
|
-
heartbeatCount: heartbeat.heartbeatCount,
|
|
2241
|
-
lastHeartbeatAt: heartbeat.lastHeartbeatAt,
|
|
2242
|
-
lastHeartbeatPhase: heartbeat.lastHeartbeatPhase,
|
|
2243
|
-
lastHeartbeatSummary: heartbeat.lastHeartbeatSummary,
|
|
2244
|
-
heartbeatBlocker: heartbeat.heartbeatBlocker,
|
|
2245
|
-
timestampAnomalies: heartbeat.timestampAnomalies,
|
|
2246
|
-
finalResult,
|
|
2247
|
-
error,
|
|
2248
|
-
changedFiles,
|
|
2249
|
-
gitAncestry,
|
|
2250
|
-
instructionPolicyFingerprint: worker.instructionPolicyFingerprint ?? null,
|
|
2251
|
-
instructionPolicyEvidence: worker.instructionPolicyEvidence ?? null
|
|
1896
|
+
runId: worker.runId,
|
|
1897
|
+
worker: worker.name,
|
|
1898
|
+
pid: worker.pid,
|
|
1899
|
+
alive,
|
|
1900
|
+
status: workerStatusLabel,
|
|
1901
|
+
attention,
|
|
1902
|
+
branch: worker.branch,
|
|
1903
|
+
worktreePath: worker.worktreePath,
|
|
1904
|
+
ownedPaths: worker.ownedPaths,
|
|
1905
|
+
stdoutBytes,
|
|
1906
|
+
stderrBytes,
|
|
1907
|
+
heartbeatBytes,
|
|
1908
|
+
firstEventAt: parsed.firstEventAt,
|
|
1909
|
+
lastEventAt: parsed.lastEventAt,
|
|
1910
|
+
lastActivityAt,
|
|
1911
|
+
currentTool: completionAcknowledged ? null : parsed.currentTool,
|
|
1912
|
+
heartbeatCount: heartbeat.heartbeatCount,
|
|
1913
|
+
lastHeartbeatAt: heartbeat.lastHeartbeatAt,
|
|
1914
|
+
lastHeartbeatPhase: heartbeat.lastHeartbeatPhase,
|
|
1915
|
+
lastHeartbeatSummary: heartbeat.lastHeartbeatSummary,
|
|
1916
|
+
heartbeatBlocker: heartbeat.heartbeatBlocker,
|
|
1917
|
+
timestampAnomalies: heartbeat.timestampAnomalies,
|
|
1918
|
+
finalResult,
|
|
1919
|
+
error,
|
|
1920
|
+
changedFiles,
|
|
1921
|
+
gitAncestry,
|
|
1922
|
+
instructionPolicyFingerprint: worker.instructionPolicyFingerprint ?? null,
|
|
1923
|
+
instructionPolicyEvidence: worker.instructionPolicyEvidence ?? null
|
|
1924
|
+
};
|
|
1925
|
+
}
|
|
1926
|
+
function isFinishedWorkerStatus(status) {
|
|
1927
|
+
if (status.finalResult) return true;
|
|
1928
|
+
if (status.alive === false) return true;
|
|
1929
|
+
if (status.status === "exited" || status.status === "done") return true;
|
|
1930
|
+
return false;
|
|
1931
|
+
}
|
|
1932
|
+
function isLandingBlockedWorkerStatus(status) {
|
|
1933
|
+
if (!status.finalResult) return false;
|
|
1934
|
+
return status.attention.state === "needs_attention" || status.attention.state === "blocked";
|
|
1935
|
+
}
|
|
1936
|
+
function deriveRunStatus(fallback, workers) {
|
|
1937
|
+
if (workers.length === 0) return fallback;
|
|
1938
|
+
if (workers.some((w) => w.attention === "needs_attention" || w.attention === "stale" || w.attention === "blocked")) {
|
|
1939
|
+
return "needs_attention";
|
|
1940
|
+
}
|
|
1941
|
+
if (workers.every((w) => w.status === "done")) return "done";
|
|
1942
|
+
if (workers.some((w) => w.status === "running")) return "running";
|
|
1943
|
+
return fallback;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// src/resource-gate.ts
|
|
1947
|
+
var DEFAULT_PER_WORKER_MEM_BYTES = 500 * 1024 * 1024;
|
|
1948
|
+
var DEFAULT_MEM_RESERVE_BYTES = 4 * 1024 * 1024 * 1024;
|
|
1949
|
+
var DEFAULT_MEM_UTILIZATION = 0.85;
|
|
1950
|
+
var AUTO_MAX_WORKERS_CEILING = 64;
|
|
1951
|
+
function positiveInt(value, fallback) {
|
|
1952
|
+
const n = Number(value);
|
|
1953
|
+
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
1954
|
+
return Math.floor(n);
|
|
1955
|
+
}
|
|
1956
|
+
function resolveResourceConfig(config = loadUserConfig(), configuredMaxWorkersOverride, totalMemBytes) {
|
|
1957
|
+
const perWorkerMemBytes = positiveInt(config.perWorkerMemBytes, DEFAULT_PER_WORKER_MEM_BYTES);
|
|
1958
|
+
const memReserveBytes = positiveInt(config.memReserveBytes, DEFAULT_MEM_RESERVE_BYTES);
|
|
1959
|
+
const memUtilization = Math.min(
|
|
1960
|
+
1,
|
|
1961
|
+
Math.max(0.1, Number(config.memUtilization) > 0 ? Number(config.memUtilization) : DEFAULT_MEM_UTILIZATION)
|
|
1962
|
+
);
|
|
1963
|
+
const cap = resolveWorkerCap({
|
|
1964
|
+
config,
|
|
1965
|
+
configuredMaxWorkersOverride,
|
|
1966
|
+
totalMemBytes: totalMemBytes ?? os2.totalmem()
|
|
1967
|
+
});
|
|
1968
|
+
return {
|
|
1969
|
+
perWorkerMemBytes,
|
|
1970
|
+
memReserveBytes,
|
|
1971
|
+
memUtilization,
|
|
1972
|
+
configuredMaxWorkers: cap.configuredMaxWorkers,
|
|
1973
|
+
autoCap: cap.autoCap,
|
|
1974
|
+
workerCapSource: cap.workerCapSource
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
function computeAutoMaxWorkers(totalMemBytes, opts = {}) {
|
|
1978
|
+
const perWorkerMemBytes = opts.perWorkerMemBytes ?? DEFAULT_PER_WORKER_MEM_BYTES;
|
|
1979
|
+
const memReserveBytes = opts.memReserveBytes ?? DEFAULT_MEM_RESERVE_BYTES;
|
|
1980
|
+
const memUtilization = opts.memUtilization ?? DEFAULT_MEM_UTILIZATION;
|
|
1981
|
+
const budgetBytes = Math.max(0, Math.floor(totalMemBytes * memUtilization) - memReserveBytes);
|
|
1982
|
+
const raw = Math.max(1, Math.floor(budgetBytes / perWorkerMemBytes));
|
|
1983
|
+
return Math.min(raw, AUTO_MAX_WORKERS_CEILING);
|
|
1984
|
+
}
|
|
1985
|
+
function readAvailableMemBytes() {
|
|
1986
|
+
return readMemAvailableBytes();
|
|
1987
|
+
}
|
|
1988
|
+
function isActiveHarnessWorker(worker) {
|
|
1989
|
+
if (typeof worker.completionBlocker === "string" && worker.completionBlocker.trim()) {
|
|
1990
|
+
return false;
|
|
1991
|
+
}
|
|
1992
|
+
const status = computeWorkerStatus(worker);
|
|
1993
|
+
return status.alive && !status.finalResult && status.attention.state !== "done";
|
|
1994
|
+
}
|
|
1995
|
+
function countActiveWorkersForRun(run) {
|
|
1996
|
+
let active = 0;
|
|
1997
|
+
for (const name of listRunWorkerNames(run)) {
|
|
1998
|
+
const worker = readJson(
|
|
1999
|
+
path7.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2000
|
+
void 0
|
|
2001
|
+
);
|
|
2002
|
+
if (!worker || !isActiveHarnessWorker(worker)) continue;
|
|
2003
|
+
active++;
|
|
2004
|
+
}
|
|
2005
|
+
return active;
|
|
2006
|
+
}
|
|
2007
|
+
function countActiveWorkersGlobal() {
|
|
2008
|
+
let active = 0;
|
|
2009
|
+
for (const run of listRunRecords()) active += countActiveWorkersForRun(run);
|
|
2010
|
+
return active;
|
|
2011
|
+
}
|
|
2012
|
+
function observeRunnerResourceGate(input) {
|
|
2013
|
+
const config = input.config ?? loadUserConfig();
|
|
2014
|
+
const totalMemBytes = input.totalMemBytes ?? os2.totalmem();
|
|
2015
|
+
const { perWorkerMemBytes, memReserveBytes, memUtilization, configuredMaxWorkers, autoCap: resolvedAutoCap, workerCapSource } = resolveResourceConfig(config, input.configuredMaxWorkersOverride, totalMemBytes);
|
|
2016
|
+
const boxKind = resolveBoxKindFromConfig(config);
|
|
2017
|
+
const freeMemBytes = input.freeMemBytes ?? readAvailableMemBytes();
|
|
2018
|
+
const activeWorkers = input.activeWorkers ?? countActiveWorkersGlobal();
|
|
2019
|
+
const budgetBytes = Math.max(0, Math.floor(totalMemBytes * memUtilization) - memReserveBytes);
|
|
2020
|
+
const capacityFromTotal = Math.max(0, Math.floor(budgetBytes / perWorkerMemBytes));
|
|
2021
|
+
const capacityFromFree = Math.max(0, Math.floor(Math.max(0, freeMemBytes - memReserveBytes) / perWorkerMemBytes));
|
|
2022
|
+
const autoCap = resolvedAutoCap;
|
|
2023
|
+
const targetCap = configuredMaxWorkers ?? autoCap;
|
|
2024
|
+
const maxConcurrentWorkers = Math.max(0, Math.min(targetCap, capacityFromTotal));
|
|
2025
|
+
const slotsByCapacity = Math.max(0, maxConcurrentWorkers - activeWorkers);
|
|
2026
|
+
const slotsByFreeMem = capacityFromFree;
|
|
2027
|
+
let slotsAvailable = Math.min(slotsByCapacity, slotsByFreeMem);
|
|
2028
|
+
const skipDisk = input.skipDiskGate || process.env.KYNVER_RESOURCE_GATE_SKIP_DISK === "1";
|
|
2029
|
+
const diskGate = skipDisk ? void 0 : observeRunnerDiskGate({
|
|
2030
|
+
diskPath: input.diskPath?.trim() || process.env.KYNVER_DISK_GUARD_PATH?.trim() || "/"
|
|
2031
|
+
});
|
|
2032
|
+
if (diskGate && !diskGate.ok) slotsAvailable = 0;
|
|
2033
|
+
let reason = null;
|
|
2034
|
+
if (slotsAvailable <= 0) {
|
|
2035
|
+
if (diskGate && !diskGate.ok) {
|
|
2036
|
+
reason = diskGate.reason ?? "disk gate blocked worker admission";
|
|
2037
|
+
} else if (activeWorkers >= maxConcurrentWorkers) {
|
|
2038
|
+
reason = `at worker limit (${activeWorkers}/${maxConcurrentWorkers} running)`;
|
|
2039
|
+
} else if (capacityFromFree <= 0) {
|
|
2040
|
+
reason = "insufficient free memory \u2014 waiting for workers to finish";
|
|
2041
|
+
} else {
|
|
2042
|
+
reason = "no worker slots available";
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
return {
|
|
2046
|
+
ok: slotsAvailable > 0,
|
|
2047
|
+
totalMemBytes,
|
|
2048
|
+
freeMemBytes,
|
|
2049
|
+
memReserveBytes,
|
|
2050
|
+
perWorkerMemBytes,
|
|
2051
|
+
configuredMaxWorkers,
|
|
2052
|
+
workerCapSource,
|
|
2053
|
+
boxKind,
|
|
2054
|
+
autoCap,
|
|
2055
|
+
capacityWorkers: capacityFromTotal,
|
|
2056
|
+
maxConcurrentWorkers,
|
|
2057
|
+
activeWorkers,
|
|
2058
|
+
slotsAvailable,
|
|
2059
|
+
reason,
|
|
2060
|
+
...diskGate ? { diskGate } : {}
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
// src/worker-cap-source.ts
|
|
2065
|
+
function positiveInt2(value, fallback) {
|
|
2066
|
+
const n = Number(value);
|
|
2067
|
+
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
2068
|
+
return Math.floor(n);
|
|
2069
|
+
}
|
|
2070
|
+
function resolveWorkerCap(input) {
|
|
2071
|
+
const config = input.config ?? {};
|
|
2072
|
+
const env = input.env ?? process.env;
|
|
2073
|
+
const perWorkerMemBytes = positiveInt2(config.perWorkerMemBytes, DEFAULT_PER_WORKER_MEM_BYTES);
|
|
2074
|
+
const memReserveBytes = positiveInt2(config.memReserveBytes, DEFAULT_MEM_RESERVE_BYTES);
|
|
2075
|
+
const memUtilization = Math.min(
|
|
2076
|
+
1,
|
|
2077
|
+
Math.max(0.1, Number(config.memUtilization) > 0 ? Number(config.memUtilization) : DEFAULT_MEM_UTILIZATION)
|
|
2078
|
+
);
|
|
2079
|
+
const autoCap = computeAutoMaxWorkers(input.totalMemBytes, {
|
|
2080
|
+
perWorkerMemBytes,
|
|
2081
|
+
memReserveBytes,
|
|
2082
|
+
memUtilization
|
|
2083
|
+
});
|
|
2084
|
+
if (input.configuredMaxWorkersOverride !== void 0 && input.configuredMaxWorkersOverride !== null) {
|
|
2085
|
+
return {
|
|
2086
|
+
configuredMaxWorkers: positiveInt2(input.configuredMaxWorkersOverride, autoCap),
|
|
2087
|
+
autoCap,
|
|
2088
|
+
workerCapSource: "workspace_override"
|
|
2089
|
+
};
|
|
2090
|
+
}
|
|
2091
|
+
if (config.maxConcurrentWorkers !== void 0 && config.maxConcurrentWorkers !== null) {
|
|
2092
|
+
const configured = positiveInt2(config.maxConcurrentWorkers, 0) || null;
|
|
2093
|
+
if (configured) {
|
|
2094
|
+
return {
|
|
2095
|
+
configuredMaxWorkers: Math.min(configured, AUTO_MAX_WORKERS_CEILING),
|
|
2096
|
+
autoCap,
|
|
2097
|
+
workerCapSource: "config"
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
const envIntentional = env.KYNVER_MAX_WORKERS_INTENTIONAL === "1" || env.KYNVER_MAX_WORKERS_INTENTIONAL === "true";
|
|
2102
|
+
const envCap = envIntentional && env.KYNVER_MAX_WORKERS ? positiveInt2(env.KYNVER_MAX_WORKERS, 0) || null : null;
|
|
2103
|
+
if (envCap) {
|
|
2104
|
+
return {
|
|
2105
|
+
configuredMaxWorkers: Math.min(envCap, AUTO_MAX_WORKERS_CEILING),
|
|
2106
|
+
autoCap,
|
|
2107
|
+
workerCapSource: "env"
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
return {
|
|
2111
|
+
configuredMaxWorkers: null,
|
|
2112
|
+
autoCap,
|
|
2113
|
+
workerCapSource: "auto"
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
function recommendSetupWorkerCap(input = {}) {
|
|
2117
|
+
const totalMemBytes = input.totalMemBytes ?? 0;
|
|
2118
|
+
const autoCap = computeAutoMaxWorkers(totalMemBytes, {
|
|
2119
|
+
perWorkerMemBytes: positiveInt2(input.config?.perWorkerMemBytes, DEFAULT_PER_WORKER_MEM_BYTES),
|
|
2120
|
+
memReserveBytes: positiveInt2(input.config?.memReserveBytes, DEFAULT_MEM_RESERVE_BYTES),
|
|
2121
|
+
memUtilization: input.config?.memUtilization && Number(input.config.memUtilization) > 0 ? Number(input.config.memUtilization) : DEFAULT_MEM_UTILIZATION
|
|
2122
|
+
});
|
|
2123
|
+
const diskGateOk = input.diskGateOk ?? true;
|
|
2124
|
+
const recommendedMaxWorkers = diskGateOk ? autoCap : Math.max(1, Math.min(autoCap, 4));
|
|
2125
|
+
return {
|
|
2126
|
+
totalMemBytes,
|
|
2127
|
+
autoCap,
|
|
2128
|
+
recommendedMaxWorkers,
|
|
2129
|
+
diskPath: input.diskPath ?? "/",
|
|
2130
|
+
diskGateOk,
|
|
2131
|
+
diskFreeBytes: input.diskFreeBytes ?? null
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
// src/config.ts
|
|
2136
|
+
import os3 from "node:os";
|
|
2137
|
+
var CONFIG_DIR = path8.join(homedir4(), ".kynver");
|
|
2138
|
+
var CONFIG_FILE = path8.join(CONFIG_DIR, "config.json");
|
|
2139
|
+
var CREDENTIALS_FILE = path8.join(CONFIG_DIR, "credentials");
|
|
2140
|
+
function loadUserConfig() {
|
|
2141
|
+
if (!existsSync9(CONFIG_FILE)) return {};
|
|
2142
|
+
try {
|
|
2143
|
+
return JSON.parse(readFileSync7(CONFIG_FILE, "utf8"));
|
|
2144
|
+
} catch {
|
|
2145
|
+
return {};
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
function saveUserConfig(config) {
|
|
2149
|
+
mkdirSync2(CONFIG_DIR, { recursive: true });
|
|
2150
|
+
writeFileSync2(CONFIG_FILE, `${JSON.stringify(normalizeConfigPaths(config), null, 2)}
|
|
2151
|
+
`, { mode: 384 });
|
|
2152
|
+
}
|
|
2153
|
+
function normalizeConfigPaths(config) {
|
|
2154
|
+
return {
|
|
2155
|
+
...config,
|
|
2156
|
+
...config.harnessRoot?.trim() ? { harnessRoot: redactHomePath(config.harnessRoot.trim()) } : {},
|
|
2157
|
+
...config.defaultRepo?.trim() ? { defaultRepo: redactHomePath(config.defaultRepo.trim()) } : {}
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
function presentUserConfig(config) {
|
|
2161
|
+
return normalizeConfigPaths(config);
|
|
2162
|
+
}
|
|
2163
|
+
function inferSetupFields(existing, args) {
|
|
2164
|
+
const creds = loadCredentialsFile();
|
|
2165
|
+
const apiBaseUrl = (typeof args.apiBaseUrl === "string" ? args.apiBaseUrl : void 0) || existing.apiBaseUrl?.trim() || process.env.KYNVER_API_URL?.trim() || process.env.KYNVER_CRON_FIRE_BASE_URL?.trim() || process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim();
|
|
2166
|
+
const agentOsId = (typeof args.agentOsId === "string" ? args.agentOsId : void 0) || existing.agentOsId?.trim() || process.env.KYNVER_AGENT_OS_ID?.trim() || (creds.runnerToken?.trim().startsWith("krc1.") ? creds.runnerTokenAgentOsId?.trim() : void 0);
|
|
2167
|
+
const explicitRepo = typeof args.repo === "string" ? args.repo : args.discoverRepo === true || args.discoverRepo === "true" ? discoverDefaultRepo()?.repo : void 0;
|
|
2168
|
+
const defaultRepo = explicitRepo || existing.defaultRepo?.trim() || process.env.KYNVER_DEFAULT_REPO?.trim() || process.env.KYNVER_HARNESS_REPO?.trim() || discoverDefaultRepo()?.repo;
|
|
2169
|
+
const harnessRoot = (typeof args.harnessRoot === "string" ? args.harnessRoot : void 0) || existing.harnessRoot?.trim() || process.env.KYNVER_HARNESS_ROOT?.trim() || process.env.OPUS_HARNESS_ROOT?.trim();
|
|
2170
|
+
return {
|
|
2171
|
+
...apiBaseUrl ? { apiBaseUrl: trimTrailingSlash(apiBaseUrl) } : {},
|
|
2172
|
+
...agentOsId ? { agentOsId } : {},
|
|
2173
|
+
...defaultRepo ? { defaultRepo } : {},
|
|
2174
|
+
...harnessRoot ? { harnessRoot } : {},
|
|
2175
|
+
...typeof args.agentOsSlug === "string" ? { agentOsSlug: args.agentOsSlug } : existing.agentOsSlug ? { agentOsSlug: existing.agentOsSlug } : {}
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
var SETUP_PER_WORKER_MEM_BYTES = 500 * 1024 * 1024;
|
|
2179
|
+
var SETUP_MEM_RESERVE_BYTES = 4 * 1024 * 1024 * 1024;
|
|
2180
|
+
function resolveSetupWorkerConfig(existing, args, totalMemBytes = totalmem()) {
|
|
2181
|
+
const maxWorkersRaw = typeof args.maxWorkers === "string" ? args.maxWorkers : typeof args.maxConcurrentWorkers === "string" ? args.maxConcurrentWorkers : void 0;
|
|
2182
|
+
const explicitBoxKindArg = typeof args.boxKind === "string" ? args.boxKind : typeof args["box-kind"] === "string" ? String(args["box-kind"]) : void 0;
|
|
2183
|
+
const boxKind = resolveBoxIdentity(process.env, {
|
|
2184
|
+
...existing,
|
|
2185
|
+
...explicitBoxKindArg ? { boxKind: normalizeWorkerPoolBoxKind(explicitBoxKindArg) } : {}
|
|
2186
|
+
}).boxKind;
|
|
2187
|
+
const diskGate = observeRunnerDiskGate({
|
|
2188
|
+
diskPath: typeof args.diskPath === "string" ? args.diskPath : "/"
|
|
2189
|
+
});
|
|
2190
|
+
const capRecommendation = recommendSetupWorkerCap({
|
|
2191
|
+
totalMemBytes,
|
|
2192
|
+
diskPath: diskGate.path,
|
|
2193
|
+
diskGateOk: diskGate.ok,
|
|
2194
|
+
diskFreeBytes: diskGate.freeBytes,
|
|
2195
|
+
config: existing
|
|
2196
|
+
});
|
|
2197
|
+
if (maxWorkersRaw) {
|
|
2198
|
+
return {
|
|
2199
|
+
maxConcurrentWorkers: Math.max(1, Math.floor(Number(maxWorkersRaw))),
|
|
2200
|
+
maxConcurrentWorkersSource: "setup-flag",
|
|
2201
|
+
boxKind
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
if (existing.maxConcurrentWorkers !== void 0 && existing.maxConcurrentWorkers !== null) {
|
|
2205
|
+
return {
|
|
2206
|
+
maxConcurrentWorkers: Math.max(1, Math.floor(Number(existing.maxConcurrentWorkers))),
|
|
2207
|
+
maxConcurrentWorkersSource: existing.maxConcurrentWorkersSource ?? "operator",
|
|
2208
|
+
boxKind
|
|
2209
|
+
};
|
|
2210
|
+
}
|
|
2211
|
+
return {
|
|
2212
|
+
maxConcurrentWorkers: capRecommendation.recommendedMaxWorkers,
|
|
2213
|
+
maxConcurrentWorkersSource: "setup-auto",
|
|
2214
|
+
boxKind
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
2217
|
+
function loadCredentialsFile() {
|
|
2218
|
+
if (!existsSync9(CREDENTIALS_FILE)) return {};
|
|
2219
|
+
try {
|
|
2220
|
+
return JSON.parse(readFileSync7(CREDENTIALS_FILE, "utf8"));
|
|
2221
|
+
} catch {
|
|
2222
|
+
return {};
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
function saveCredentialsFile(parsed) {
|
|
2226
|
+
mkdirSync2(CONFIG_DIR, { recursive: true });
|
|
2227
|
+
writeFileSync2(CREDENTIALS_FILE, `${JSON.stringify(parsed, null, 2)}
|
|
2228
|
+
`, { mode: 384 });
|
|
2229
|
+
}
|
|
2230
|
+
function loadApiKey() {
|
|
2231
|
+
if (process.env.KYNVER_API_KEY) return process.env.KYNVER_API_KEY;
|
|
2232
|
+
return loadCredentialsFile().apiKey;
|
|
2233
|
+
}
|
|
2234
|
+
function saveApiKey(apiKey) {
|
|
2235
|
+
saveCredentialsFile({ ...loadCredentialsFile(), apiKey });
|
|
2236
|
+
}
|
|
2237
|
+
function loadRunnerToken(agentOsId) {
|
|
2238
|
+
const envToken = process.env.KYNVER_RUNNER_TOKEN?.trim();
|
|
2239
|
+
if (envToken) return envToken;
|
|
2240
|
+
const creds = loadCredentialsFile();
|
|
2241
|
+
if (!creds.runnerToken) return void 0;
|
|
2242
|
+
if (agentOsId && creds.runnerTokenAgentOsId && creds.runnerTokenAgentOsId !== agentOsId) {
|
|
2243
|
+
return void 0;
|
|
2244
|
+
}
|
|
2245
|
+
return creds.runnerToken;
|
|
2246
|
+
}
|
|
2247
|
+
function saveRunnerToken(agentOsId, token) {
|
|
2248
|
+
saveCredentialsFile({
|
|
2249
|
+
...loadCredentialsFile(),
|
|
2250
|
+
runnerToken: token,
|
|
2251
|
+
runnerTokenAgentOsId: agentOsId
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
function resolveBaseUrl(argsBaseUrl) {
|
|
2255
|
+
const baseUrl = resolveConfiguredBaseUrl(argsBaseUrl);
|
|
2256
|
+
if (!baseUrl) failConfig("requires --base-url, KYNVER_API_URL, KYNVER_CRON_FIRE_BASE_URL, or ~/.kynver/config.json apiBaseUrl");
|
|
2257
|
+
return baseUrl;
|
|
2258
|
+
}
|
|
2259
|
+
function resolveConfiguredBaseUrl(argsBaseUrl) {
|
|
2260
|
+
const baseUrl = argsBaseUrl || process.env.KYNVER_API_URL || process.env.KYNVER_CRON_FIRE_BASE_URL || process.env.OPENCLAW_CRON_FIRE_BASE_URL || loadUserConfig().apiBaseUrl;
|
|
2261
|
+
return baseUrl ? trimTrailingSlash(String(baseUrl)) : void 0;
|
|
2262
|
+
}
|
|
2263
|
+
function resolveConfiguredCallbackSecret(argsSecret, agentOsId) {
|
|
2264
|
+
const scoped = argsSecret || loadRunnerToken(agentOsId) || (agentOsId ? void 0 : loadRunnerToken(loadUserConfig().agentOsId));
|
|
2265
|
+
if (scoped) return String(scoped);
|
|
2266
|
+
const globalSecret = process.env.KYNVER_RUNTIME_SECRET || process.env.KYNVER_CRON_SECRET || process.env.OPENCLAW_CRON_SECRET;
|
|
2267
|
+
if (globalSecret) {
|
|
2268
|
+
console.warn(
|
|
2269
|
+
"[kynver] using deployment-level callback secret; run `kynver runner credential --agent-os-id <id>` for a scoped token"
|
|
2270
|
+
);
|
|
2271
|
+
return String(globalSecret);
|
|
2272
|
+
}
|
|
2273
|
+
return void 0;
|
|
2274
|
+
}
|
|
2275
|
+
async function resolveCallbackSecretWithMint(argsSecret, agentOsId, opts) {
|
|
2276
|
+
const configured = resolveConfiguredCallbackSecret(argsSecret, agentOsId);
|
|
2277
|
+
if (configured) return configured;
|
|
2278
|
+
const apiKey = loadApiKey();
|
|
2279
|
+
const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);
|
|
2280
|
+
if (apiKey && agentOsId && baseUrl) {
|
|
2281
|
+
try {
|
|
2282
|
+
const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });
|
|
2283
|
+
saveRunnerToken(agentOsId, token);
|
|
2284
|
+
return token;
|
|
2285
|
+
} catch (error) {
|
|
2286
|
+
failConfig(`runner credential mint failed: ${error.message}`);
|
|
2287
|
+
}
|
|
2288
|
+
}
|
|
2289
|
+
failConfig(
|
|
2290
|
+
"requires --secret, KYNVER_RUNNER_TOKEN, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, KYNVER_API_KEY with an API base URL to mint one, or (legacy) KYNVER_RUNTIME_SECRET / KYNVER_CRON_SECRET / OPENCLAW_CRON_SECRET"
|
|
2291
|
+
);
|
|
2292
|
+
}
|
|
2293
|
+
async function refreshRunnerToken(agentOsId, opts) {
|
|
2294
|
+
const apiKey = loadApiKey();
|
|
2295
|
+
const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);
|
|
2296
|
+
if (!apiKey || !agentOsId || !baseUrl) return null;
|
|
2297
|
+
try {
|
|
2298
|
+
const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });
|
|
2299
|
+
saveRunnerToken(agentOsId, token);
|
|
2300
|
+
return token;
|
|
2301
|
+
} catch {
|
|
2302
|
+
return null;
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
async function refreshRunnerTokenForAuthFailure(rejectedSecret, agentOsId, opts) {
|
|
2306
|
+
const apiKey = loadApiKey();
|
|
2307
|
+
const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);
|
|
2308
|
+
if (!apiKey) return { ok: false, reason: "KYNVER_API_KEY is required to refresh a rejected runner token" };
|
|
2309
|
+
if (!agentOsId) return { ok: false, reason: "agentOsId is required to refresh a rejected runner token" };
|
|
2310
|
+
if (!baseUrl) return { ok: false, reason: "KYNVER_API_URL or --base-url is required to refresh a rejected runner token" };
|
|
2311
|
+
try {
|
|
2312
|
+
const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });
|
|
2313
|
+
if (token && token !== rejectedSecret) {
|
|
2314
|
+
saveRunnerToken(agentOsId, token);
|
|
2315
|
+
return { ok: true, token };
|
|
2316
|
+
}
|
|
2317
|
+
return { ok: false, reason: "runner credential refresh returned the rejected token" };
|
|
2318
|
+
} catch (error) {
|
|
2319
|
+
return { ok: false, reason: error.message };
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
async function fetchRunnerCredential(agentOsId, opts) {
|
|
2323
|
+
const apiKey = opts?.apiKey || loadApiKey();
|
|
2324
|
+
if (!apiKey) throw new Error("API key required \u2014 run `kynver login` first");
|
|
2325
|
+
const base = resolveBaseUrl(opts?.baseUrl);
|
|
2326
|
+
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/runner-credentials`;
|
|
2327
|
+
const res = await fetch(url, {
|
|
2328
|
+
method: "POST",
|
|
2329
|
+
headers: {
|
|
2330
|
+
"Content-Type": "application/json",
|
|
2331
|
+
Authorization: `Bearer ${apiKey}`
|
|
2332
|
+
},
|
|
2333
|
+
body: JSON.stringify({})
|
|
2334
|
+
});
|
|
2335
|
+
const text = await res.text();
|
|
2336
|
+
let parsed = null;
|
|
2337
|
+
try {
|
|
2338
|
+
parsed = JSON.parse(text);
|
|
2339
|
+
} catch {
|
|
2340
|
+
parsed = null;
|
|
2341
|
+
}
|
|
2342
|
+
if (!res.ok || !parsed?.token) {
|
|
2343
|
+
throw new Error(
|
|
2344
|
+
`runner credential mint failed (${res.status}): ${parsed?.error ?? text.slice(0, 200)}`
|
|
2345
|
+
);
|
|
2346
|
+
}
|
|
2347
|
+
return parsed.token;
|
|
2348
|
+
}
|
|
2349
|
+
async function mintRunnerCredential(args) {
|
|
2350
|
+
const agentOsId = (args.agentOsId ? String(args.agentOsId) : loadUserConfig().agentOsId) || "";
|
|
2351
|
+
if (!agentOsId) failConfig("runner credential requires --agent-os-id or agentOsId in ~/.kynver/config.json");
|
|
2352
|
+
try {
|
|
2353
|
+
const token = await fetchRunnerCredential(agentOsId, {
|
|
2354
|
+
baseUrl: args.baseUrl ? String(args.baseUrl) : void 0
|
|
2355
|
+
});
|
|
2356
|
+
saveRunnerToken(agentOsId, token);
|
|
2357
|
+
console.log(
|
|
2358
|
+
JSON.stringify(
|
|
2359
|
+
{
|
|
2360
|
+
ok: true,
|
|
2361
|
+
agentOsId,
|
|
2362
|
+
credentialsPath: displayUserPath(CREDENTIALS_FILE),
|
|
2363
|
+
tokenPrefix: `${token.slice(0, 12)}\u2026`,
|
|
2364
|
+
note: "Scoped runner token saved; callbacks use X-Kynver-Runner-Token."
|
|
2365
|
+
},
|
|
2366
|
+
null,
|
|
2367
|
+
2
|
|
2368
|
+
)
|
|
2369
|
+
);
|
|
2370
|
+
} catch (err) {
|
|
2371
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
2372
|
+
process.exit(1);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
function failConfig(message) {
|
|
2376
|
+
console.error(message);
|
|
2377
|
+
process.exit(1);
|
|
2378
|
+
}
|
|
2379
|
+
function parseArgs(argv) {
|
|
2380
|
+
const args = {};
|
|
2381
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2382
|
+
const item = argv[i];
|
|
2383
|
+
if (!item.startsWith("--")) continue;
|
|
2384
|
+
const key = item.slice(2).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
2385
|
+
const next = argv[i + 1];
|
|
2386
|
+
if (!next || next.startsWith("--")) args[key] = true;
|
|
2387
|
+
else {
|
|
2388
|
+
args[key] = next;
|
|
2389
|
+
i++;
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
return args;
|
|
2393
|
+
}
|
|
2394
|
+
async function runSetup(args) {
|
|
2395
|
+
const existing = loadUserConfig();
|
|
2396
|
+
const diskGate = observeRunnerDiskGate({
|
|
2397
|
+
diskPath: typeof args.diskPath === "string" ? args.diskPath : "/"
|
|
2398
|
+
});
|
|
2399
|
+
const capRecommendation = recommendSetupWorkerCap({
|
|
2400
|
+
totalMemBytes: os3.totalmem(),
|
|
2401
|
+
diskPath: diskGate.path,
|
|
2402
|
+
diskGateOk: diskGate.ok,
|
|
2403
|
+
diskFreeBytes: diskGate.freeBytes,
|
|
2404
|
+
config: existing
|
|
2405
|
+
});
|
|
2406
|
+
const workerConfig = resolveSetupWorkerConfig(existing, args);
|
|
2407
|
+
const config = normalizeConfigPaths({
|
|
2408
|
+
...existing,
|
|
2409
|
+
...inferSetupFields(existing, args),
|
|
2410
|
+
...workerConfig,
|
|
2411
|
+
workerProvider: typeof args.provider === "string" ? args.provider : existing.workerProvider || "cursor"
|
|
2412
|
+
});
|
|
2413
|
+
saveUserConfig(config);
|
|
2414
|
+
const boxIdentity = resolveBoxIdentity(process.env, config);
|
|
2415
|
+
let runnerCredentialNote;
|
|
2416
|
+
const apiKey = loadApiKey();
|
|
2417
|
+
const agentOsId = config.agentOsId;
|
|
2418
|
+
if (apiKey && agentOsId) {
|
|
2419
|
+
try {
|
|
2420
|
+
const token = await fetchRunnerCredential(agentOsId, {
|
|
2421
|
+
baseUrl: typeof args.apiBaseUrl === "string" ? args.apiBaseUrl : config.apiBaseUrl,
|
|
2422
|
+
apiKey
|
|
2423
|
+
});
|
|
2424
|
+
saveRunnerToken(agentOsId, token);
|
|
2425
|
+
runnerCredentialNote = "Scoped runner token minted and saved to ~/.kynver/credentials.";
|
|
2426
|
+
} catch {
|
|
2427
|
+
runnerCredentialNote = "Runner token not minted (server offline or master secret unset). Run `kynver runner credential` after deploy.";
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
console.log(
|
|
2431
|
+
JSON.stringify(
|
|
2432
|
+
{
|
|
2433
|
+
ok: true,
|
|
2434
|
+
configPath: displayUserPath(CONFIG_FILE),
|
|
2435
|
+
config: presentUserConfig(config),
|
|
2436
|
+
boxKind: config.boxKind,
|
|
2437
|
+
boxKindSource: boxIdentity.source,
|
|
2438
|
+
workerCapRecommendation: capRecommendation,
|
|
2439
|
+
...boxIdentity.warnings.length ? { boxIdentityWarnings: boxIdentity.warnings } : {},
|
|
2440
|
+
note: runnerCredentialNote ?? "boxKind and maxConcurrentWorkers persisted; override with --box-kind and --max-workers. Run `kynver login` + `kynver runner credential` for scoped callbacks."
|
|
2441
|
+
},
|
|
2442
|
+
null,
|
|
2443
|
+
2
|
|
2444
|
+
)
|
|
2445
|
+
);
|
|
2446
|
+
}
|
|
2447
|
+
async function runLogin(args) {
|
|
2448
|
+
const apiKey = typeof args.apiKey === "string" ? args.apiKey : process.env.KYNVER_API_KEY;
|
|
2449
|
+
if (!apiKey) failConfig("kynver login requires --api-key or KYNVER_API_KEY");
|
|
2450
|
+
saveApiKey(apiKey);
|
|
2451
|
+
console.log(JSON.stringify({ ok: true, credentialsPath: displayUserPath(CREDENTIALS_FILE) }, null, 2));
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
// src/callback-headers.ts
|
|
2455
|
+
function buildHarnessCallbackHeaders(secret) {
|
|
2456
|
+
const trimmed = String(secret).trim();
|
|
2457
|
+
if (trimmed.startsWith("krc1.")) {
|
|
2458
|
+
return {
|
|
2459
|
+
"Content-Type": "application/json",
|
|
2460
|
+
"X-Kynver-Runner-Token": trimmed
|
|
2461
|
+
};
|
|
2462
|
+
}
|
|
2463
|
+
return {
|
|
2464
|
+
"Content-Type": "application/json",
|
|
2465
|
+
// Canonical header. We keep sending the legacy `X-OpenClaw-Cron-Secret`
|
|
2466
|
+
// (and `X-Kynver-Runtime-Secret`) so an un-upgraded Kynver server that
|
|
2467
|
+
// only reads the old header still authenticates this runner during the
|
|
2468
|
+
// rename compat window.
|
|
2469
|
+
"X-Kynver-Cron-Secret": trimmed,
|
|
2470
|
+
"X-OpenClaw-Cron-Secret": trimmed,
|
|
2471
|
+
"X-Kynver-Runtime-Secret": trimmed
|
|
2252
2472
|
};
|
|
2253
2473
|
}
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
return
|
|
2474
|
+
|
|
2475
|
+
// src/callbacks.ts
|
|
2476
|
+
function callbackTimeoutMs() {
|
|
2477
|
+
const parsed = Number(process.env.KYNVER_CALLBACK_TIMEOUT_MS);
|
|
2478
|
+
if (Number.isFinite(parsed) && parsed > 0) return Math.floor(parsed);
|
|
2479
|
+
return 3e4;
|
|
2259
2480
|
}
|
|
2260
|
-
function
|
|
2261
|
-
|
|
2262
|
-
|
|
2481
|
+
async function withTimeout(fn) {
|
|
2482
|
+
const controller = new AbortController();
|
|
2483
|
+
const timeout = setTimeout(() => controller.abort(), callbackTimeoutMs());
|
|
2484
|
+
try {
|
|
2485
|
+
return await fn(controller.signal);
|
|
2486
|
+
} finally {
|
|
2487
|
+
clearTimeout(timeout);
|
|
2488
|
+
}
|
|
2263
2489
|
}
|
|
2264
|
-
function
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2490
|
+
async function postJson(url, secret, body) {
|
|
2491
|
+
const res = await withTimeout(
|
|
2492
|
+
(signal) => fetch(url, {
|
|
2493
|
+
method: "POST",
|
|
2494
|
+
headers: buildHarnessCallbackHeaders(secret),
|
|
2495
|
+
body: JSON.stringify(body),
|
|
2496
|
+
signal
|
|
2497
|
+
})
|
|
2498
|
+
);
|
|
2499
|
+
let response = null;
|
|
2500
|
+
try {
|
|
2501
|
+
response = await res.json();
|
|
2502
|
+
} catch {
|
|
2503
|
+
response = null;
|
|
2268
2504
|
}
|
|
2269
|
-
|
|
2270
|
-
if (workers.some((w) => w.status === "running")) return "running";
|
|
2271
|
-
return fallback;
|
|
2505
|
+
return { ok: res.ok, status: res.status, response };
|
|
2272
2506
|
}
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
const n = Number(value);
|
|
2281
|
-
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
2282
|
-
return Math.floor(n);
|
|
2507
|
+
async function postJsonWithCredentialRefresh(url, secret, body, opts) {
|
|
2508
|
+
const first = await postJson(url, secret, body);
|
|
2509
|
+
if (first.ok || first.status !== 401) return first;
|
|
2510
|
+
const refreshed = await refreshRunnerTokenForAuthFailure(secret, opts.agentOsId, { baseUrl: opts.baseUrl });
|
|
2511
|
+
if (!refreshed.ok) return { ...first, authRefreshFailure: refreshed.reason };
|
|
2512
|
+
const retry = await postJson(url, refreshed.token, body);
|
|
2513
|
+
return { ...retry, refreshedAuth: true };
|
|
2283
2514
|
}
|
|
2284
|
-
function
|
|
2285
|
-
const
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2515
|
+
async function getJson(url, secret) {
|
|
2516
|
+
const res = await withTimeout(
|
|
2517
|
+
(signal) => fetch(url, {
|
|
2518
|
+
method: "GET",
|
|
2519
|
+
headers: buildHarnessCallbackHeaders(secret),
|
|
2520
|
+
signal
|
|
2521
|
+
})
|
|
2290
2522
|
);
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
const memUtilization = opts.memUtilization ?? DEFAULT_MEM_UTILIZATION;
|
|
2299
|
-
const budgetBytes = Math.max(0, Math.floor(totalMemBytes * memUtilization) - memReserveBytes);
|
|
2300
|
-
const raw = Math.max(1, Math.floor(budgetBytes / perWorkerMemBytes));
|
|
2301
|
-
return Math.min(raw, AUTO_MAX_WORKERS_CEILING);
|
|
2523
|
+
let response = null;
|
|
2524
|
+
try {
|
|
2525
|
+
response = await res.json();
|
|
2526
|
+
} catch {
|
|
2527
|
+
response = null;
|
|
2528
|
+
}
|
|
2529
|
+
return { ok: res.ok, status: res.status, response };
|
|
2302
2530
|
}
|
|
2303
|
-
|
|
2304
|
-
|
|
2531
|
+
|
|
2532
|
+
// src/dispatch-lane-normalization.ts
|
|
2533
|
+
function trimLower(value) {
|
|
2534
|
+
return (value ?? "").trim().toLowerCase();
|
|
2305
2535
|
}
|
|
2306
|
-
function
|
|
2307
|
-
|
|
2308
|
-
|
|
2536
|
+
function roleLaneToDispatchLane(roleLane) {
|
|
2537
|
+
switch (trimLower(roleLane)) {
|
|
2538
|
+
case "implementer":
|
|
2539
|
+
case "repair_implementer":
|
|
2540
|
+
case "plan_author":
|
|
2541
|
+
case "runtime_verifier":
|
|
2542
|
+
return "implementation";
|
|
2543
|
+
case "plan_reviewer":
|
|
2544
|
+
case "report_reviewer":
|
|
2545
|
+
case "deep_reviewer":
|
|
2546
|
+
return "review";
|
|
2547
|
+
default:
|
|
2548
|
+
return null;
|
|
2309
2549
|
}
|
|
2310
|
-
const status = computeWorkerStatus(worker);
|
|
2311
|
-
return status.alive && !status.finalResult && status.attention.state !== "done";
|
|
2312
2550
|
}
|
|
2313
|
-
function
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
void 0
|
|
2319
|
-
);
|
|
2320
|
-
if (!worker || !isActiveHarnessWorker(worker)) continue;
|
|
2321
|
-
active++;
|
|
2551
|
+
function normalizeDispatchNextLaneFilter(raw) {
|
|
2552
|
+
const key = trimLower(raw);
|
|
2553
|
+
if (!key) return void 0;
|
|
2554
|
+
if (key === "implementation" || key === "review" || key === "landing" || key === "any") {
|
|
2555
|
+
return key;
|
|
2322
2556
|
}
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
return active;
|
|
2557
|
+
const mapped = roleLaneToDispatchLane(key);
|
|
2558
|
+
if (mapped) return mapped;
|
|
2559
|
+
if (key === "implement" || key === "repair" || key === "coding") return "implementation";
|
|
2560
|
+
if (key === "land" || key === "merge") return "landing";
|
|
2561
|
+
return void 0;
|
|
2329
2562
|
}
|
|
2330
|
-
function
|
|
2331
|
-
|
|
2332
|
-
input.config,
|
|
2333
|
-
input.configuredMaxWorkersOverride
|
|
2334
|
-
);
|
|
2335
|
-
const totalMemBytes = input.totalMemBytes ?? os2.totalmem();
|
|
2336
|
-
const freeMemBytes = input.freeMemBytes ?? readAvailableMemBytes();
|
|
2337
|
-
const activeWorkers = input.activeWorkers ?? countActiveWorkersGlobal();
|
|
2338
|
-
const budgetBytes = Math.max(0, Math.floor(totalMemBytes * memUtilization) - memReserveBytes);
|
|
2339
|
-
const capacityFromTotal = Math.max(0, Math.floor(budgetBytes / perWorkerMemBytes));
|
|
2340
|
-
const capacityFromFree = Math.max(0, Math.floor(Math.max(0, freeMemBytes - memReserveBytes) / perWorkerMemBytes));
|
|
2341
|
-
const autoCap = computeAutoMaxWorkers(totalMemBytes, { perWorkerMemBytes, memReserveBytes, memUtilization });
|
|
2342
|
-
const targetCap = configuredMaxWorkers ?? autoCap;
|
|
2343
|
-
const maxConcurrentWorkers = Math.max(0, Math.min(targetCap, capacityFromTotal));
|
|
2344
|
-
const slotsByCapacity = Math.max(0, maxConcurrentWorkers - activeWorkers);
|
|
2345
|
-
const slotsByFreeMem = capacityFromFree;
|
|
2346
|
-
let slotsAvailable = Math.min(slotsByCapacity, slotsByFreeMem);
|
|
2347
|
-
const skipDisk = input.skipDiskGate || process.env.KYNVER_RESOURCE_GATE_SKIP_DISK === "1";
|
|
2348
|
-
const diskGate = skipDisk ? void 0 : observeRunnerDiskGate({
|
|
2349
|
-
diskPath: input.diskPath?.trim() || process.env.KYNVER_DISK_GUARD_PATH?.trim() || "/"
|
|
2350
|
-
});
|
|
2351
|
-
if (diskGate && !diskGate.ok) slotsAvailable = 0;
|
|
2352
|
-
let reason = null;
|
|
2353
|
-
if (slotsAvailable <= 0) {
|
|
2354
|
-
if (diskGate && !diskGate.ok) {
|
|
2355
|
-
reason = diskGate.reason ?? "disk gate blocked worker admission";
|
|
2356
|
-
} else if (activeWorkers >= maxConcurrentWorkers) {
|
|
2357
|
-
reason = `at worker limit (${activeWorkers}/${maxConcurrentWorkers} running)`;
|
|
2358
|
-
} else if (capacityFromFree <= 0) {
|
|
2359
|
-
reason = "insufficient free memory \u2014 waiting for workers to finish";
|
|
2360
|
-
} else {
|
|
2361
|
-
reason = "no worker slots available";
|
|
2362
|
-
}
|
|
2363
|
-
}
|
|
2364
|
-
return {
|
|
2365
|
-
ok: slotsAvailable > 0,
|
|
2366
|
-
totalMemBytes,
|
|
2367
|
-
freeMemBytes,
|
|
2368
|
-
memReserveBytes,
|
|
2369
|
-
perWorkerMemBytes,
|
|
2370
|
-
configuredMaxWorkers,
|
|
2371
|
-
autoCap,
|
|
2372
|
-
capacityWorkers: capacityFromTotal,
|
|
2373
|
-
maxConcurrentWorkers,
|
|
2374
|
-
activeWorkers,
|
|
2375
|
-
slotsAvailable,
|
|
2376
|
-
reason,
|
|
2377
|
-
...diskGate ? { diskGate } : {}
|
|
2378
|
-
};
|
|
2563
|
+
function resolveDispatchNextLaneFilter(raw) {
|
|
2564
|
+
return normalizeDispatchNextLaneFilter(raw) ?? "any";
|
|
2379
2565
|
}
|
|
2380
2566
|
|
|
2381
2567
|
// src/worker-persona-catalog.ts
|
|
@@ -3639,15 +3825,15 @@ function resolveModelFallback(startedModel, launchModel, providerDefault) {
|
|
|
3639
3825
|
}
|
|
3640
3826
|
|
|
3641
3827
|
// src/retry-limits.ts
|
|
3642
|
-
function
|
|
3828
|
+
function positiveInt3(value, fallback) {
|
|
3643
3829
|
const n = Number(value);
|
|
3644
3830
|
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
3645
3831
|
return Math.floor(n);
|
|
3646
3832
|
}
|
|
3647
3833
|
function readHarnessRetryLimits() {
|
|
3648
3834
|
return {
|
|
3649
|
-
maxTaskAttempts:
|
|
3650
|
-
dispatchCooldownMs:
|
|
3835
|
+
maxTaskAttempts: positiveInt3(process.env.KYNVER_MAX_TASK_ATTEMPTS, 4),
|
|
3836
|
+
dispatchCooldownMs: positiveInt3(process.env.KYNVER_DISPATCH_COOLDOWN_MS, 5e3)
|
|
3651
3837
|
};
|
|
3652
3838
|
}
|
|
3653
3839
|
|
|
@@ -3688,22 +3874,12 @@ function resolveHarnessLeaseOwnerForRenewal(input) {
|
|
|
3688
3874
|
}
|
|
3689
3875
|
|
|
3690
3876
|
// src/runner-identity.ts
|
|
3691
|
-
import
|
|
3877
|
+
import os5 from "node:os";
|
|
3692
3878
|
|
|
3693
3879
|
// src/box-resource-snapshot-shared.ts
|
|
3694
|
-
import
|
|
3695
|
-
function normalizeWorkerPoolBoxKind(raw) {
|
|
3696
|
-
const kind = (raw ?? "").trim().toLowerCase();
|
|
3697
|
-
if (kind === "ghost" || kind === "forge") return kind;
|
|
3698
|
-
if (kind.includes("forge")) return "forge";
|
|
3699
|
-
if (kind.includes("ghost") || kind.includes("openclaw")) return "ghost";
|
|
3700
|
-
return "forge";
|
|
3701
|
-
}
|
|
3702
|
-
function resolveBoxKindFromEnv(env = process.env) {
|
|
3703
|
-
return normalizeWorkerPoolBoxKind(env.KYNVER_BOX_KIND ?? env.KYNVER_AGENT_OS_SLUG ?? "forge");
|
|
3704
|
-
}
|
|
3880
|
+
import os4 from "node:os";
|
|
3705
3881
|
function defaultBoxId(boxKind, hostLabel) {
|
|
3706
|
-
const host = (hostLabel ??
|
|
3882
|
+
const host = (hostLabel ?? os4.hostname()).trim().toLowerCase().replace(/\s+/g, "-") || "unknown-host";
|
|
3707
3883
|
return `${boxKind}:${host}`;
|
|
3708
3884
|
}
|
|
3709
3885
|
|
|
@@ -3714,10 +3890,10 @@ function trimOrNull5(value) {
|
|
|
3714
3890
|
}
|
|
3715
3891
|
function resolveRunnerPresencePayload(input = {}) {
|
|
3716
3892
|
const env = input.env ?? process.env;
|
|
3717
|
-
const runnerId = trimOrNull5(env.KYNVER_RUNTIME_ID) ?? trimOrNull5(env.OPENCLAW_RUNTIME_ID) ?? trimOrNull5(env.HOSTNAME) ??
|
|
3893
|
+
const runnerId = trimOrNull5(env.KYNVER_RUNTIME_ID) ?? trimOrNull5(env.OPENCLAW_RUNTIME_ID) ?? trimOrNull5(env.HOSTNAME) ?? os5.hostname();
|
|
3718
3894
|
return {
|
|
3719
3895
|
runnerId,
|
|
3720
|
-
hostname: trimOrNull5(env.HOSTNAME) ??
|
|
3896
|
+
hostname: trimOrNull5(env.HOSTNAME) ?? os5.hostname(),
|
|
3721
3897
|
profile: trimOrNull5(env.KYNVER_RUNNER_PROFILE) ?? trimOrNull5(env.OPENCLAW_RUNNER_PROFILE),
|
|
3722
3898
|
harnessRepo: trimOrNull5(env.KYNVER_HARNESS_REPO) ?? trimOrNull5(env.KYNVER_DEFAULT_REPO),
|
|
3723
3899
|
runId: input.runId ?? null
|
|
@@ -3893,13 +4069,13 @@ function buildPrompt(input) {
|
|
|
3893
4069
|
input.planId ? `Active planId: ${input.planId}${input.taskId ? ` \xB7 taskId: ${input.taskId}` : ""}` : "No planId on this worker \u2014 still emit progress when you touch plan-scoped work."
|
|
3894
4070
|
];
|
|
3895
4071
|
const mergeGateLines = compact ? [
|
|
3896
|
-
"Merge-gate cost control:
|
|
4072
|
+
"Merge-gate cost control: do not use Vercel previews/builds for PR verification. Run `node scripts/agent-os-pr-merge-gate.mjs --pr <url> --agent-os-id <id>` (or `verify-pr-local.mjs --from-pr` + POST pr-merge-gate/refresh) before any GitHub Actions run; request merge-gate only via refresh then POST pr-merge-gate/request-run (one Actions run per PR head unless human approves extra)."
|
|
3897
4073
|
] : [
|
|
3898
4074
|
"GitHub Actions merge-gate cost control (Kynver/Hermes PRs):",
|
|
3899
|
-
"- Prefer local cached package verification (`node scripts/verify-pr-local.mjs --emit-json`)
|
|
4075
|
+
"- Prefer local cached package verification (`node scripts/verify-pr-local.mjs --emit-json`) before GitHub Actions. Do not use Vercel previews/builds as PR evidence.",
|
|
3900
4076
|
"- Do not push empty commits to re-trigger CI. One budgeted merge-gate Actions run per PR candidate (head SHA) unless a human approves extra.",
|
|
3901
|
-
"- Record evidence: POST `/api/agent-os/by-id/<agentOsId>/pr-merge-gate/refresh` with prUrl + local
|
|
3902
|
-
"- Request the final Actions run only when local
|
|
4077
|
+
"- Record evidence: POST `/api/agent-os/by-id/<agentOsId>/pr-merge-gate/refresh` with prUrl + local payloads.",
|
|
4078
|
+
"- Request the final Actions run only when local verification is green: POST `.../pr-merge-gate/request-run` (applies `merge-gate` label).",
|
|
3903
4079
|
"- Empty failed Actions jobs (no runner/steps/logs) are infra/quota \u2014 do not enter repair loops; escalate to operator."
|
|
3904
4080
|
];
|
|
3905
4081
|
const planArtifactLines = compact ? [
|
|
@@ -3953,7 +4129,7 @@ function buildPrompt(input) {
|
|
|
3953
4129
|
// src/providers/cursor.ts
|
|
3954
4130
|
import { closeSync as closeSync4, existsSync as existsSync17, mkdirSync as mkdirSync3, openSync as openSync4, statSync as statSync4, unlinkSync } from "node:fs";
|
|
3955
4131
|
import { spawn as spawn4 } from "node:child_process";
|
|
3956
|
-
import
|
|
4132
|
+
import os6 from "node:os";
|
|
3957
4133
|
import path16 from "node:path";
|
|
3958
4134
|
|
|
3959
4135
|
// src/providers/cursor-windows.ts
|
|
@@ -4064,7 +4240,7 @@ function positiveIntEnv(name, fallback) {
|
|
|
4064
4240
|
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : fallback;
|
|
4065
4241
|
}
|
|
4066
4242
|
function cursorStartLockPath() {
|
|
4067
|
-
const root = process.env.KYNVER_CURSOR_START_LOCK_DIR?.trim() || path16.join(
|
|
4243
|
+
const root = process.env.KYNVER_CURSOR_START_LOCK_DIR?.trim() || path16.join(os6.homedir(), ".kynver", "locks");
|
|
4068
4244
|
mkdirSync3(root, { recursive: true });
|
|
4069
4245
|
return path16.join(root, "cursor-agent-start.lock");
|
|
4070
4246
|
}
|
|
@@ -4799,8 +4975,8 @@ function dirtyPathsCoveredByDisposableRemoval(changedFiles, removed) {
|
|
|
4799
4975
|
if (removed.length === 0) return false;
|
|
4800
4976
|
const removedSet = new Set(removed.map((p) => normalizeRelativePath(p)));
|
|
4801
4977
|
return material.every((line) => {
|
|
4802
|
-
const
|
|
4803
|
-
return removedSet.has(
|
|
4978
|
+
const path67 = normalizeRelativePath(pathFromGitStatusLine(line));
|
|
4979
|
+
return removedSet.has(path67);
|
|
4804
4980
|
});
|
|
4805
4981
|
}
|
|
4806
4982
|
|
|
@@ -8113,6 +8289,45 @@ function discardDisposableCli(args) {
|
|
|
8113
8289
|
if (!result.ok) process.exit(1);
|
|
8114
8290
|
}
|
|
8115
8291
|
|
|
8292
|
+
// src/daemon-box-identity.ts
|
|
8293
|
+
import os7 from "node:os";
|
|
8294
|
+
function emitDaemonIdentityMessage(level, message) {
|
|
8295
|
+
console.error(JSON.stringify({ event: "daemon_identity", level, message }));
|
|
8296
|
+
}
|
|
8297
|
+
function validateDaemonInstallIdentity(config = loadUserConfig(), env = process.env) {
|
|
8298
|
+
const box = resolveBoxIdentity(env, config);
|
|
8299
|
+
const cap = resolveWorkerCap({
|
|
8300
|
+
config,
|
|
8301
|
+
totalMemBytes: os7.totalmem(),
|
|
8302
|
+
env
|
|
8303
|
+
});
|
|
8304
|
+
const warnings = [...box.warnings];
|
|
8305
|
+
const errors = [];
|
|
8306
|
+
if (!config.boxKind?.trim() && !env.KYNVER_BOX_KIND?.trim()) {
|
|
8307
|
+
warnings.push(
|
|
8308
|
+
"boxKind is not persisted in ~/.kynver/config.json \u2014 run `kynver setup --box-kind forge|ghost` so Command Center attributes snapshots to the correct pool"
|
|
8309
|
+
);
|
|
8310
|
+
}
|
|
8311
|
+
if (box.slugInferenceBlocked) {
|
|
8312
|
+
const strict = env.KYNVER_DAEMON_STRICT_IDENTITY === "1" || env.KYNVER_DAEMON_STRICT_IDENTITY === "true";
|
|
8313
|
+
const msg = "ambiguous box identity: KYNVER_AGENT_OS_SLUG is set without KYNVER_BOX_KIND or config.boxKind; treating this host as forge";
|
|
8314
|
+
if (strict) errors.push(msg);
|
|
8315
|
+
else warnings.push(msg);
|
|
8316
|
+
}
|
|
8317
|
+
const ok = errors.length === 0;
|
|
8318
|
+
for (const warning of warnings) emitDaemonIdentityMessage("warn", warning);
|
|
8319
|
+
for (const error of errors) emitDaemonIdentityMessage("error", error);
|
|
8320
|
+
return {
|
|
8321
|
+
ok,
|
|
8322
|
+
box,
|
|
8323
|
+
workerCapSource: cap.workerCapSource,
|
|
8324
|
+
maxConcurrentWorkers: cap.configuredMaxWorkers ?? cap.autoCap,
|
|
8325
|
+
autoCap: cap.autoCap,
|
|
8326
|
+
warnings,
|
|
8327
|
+
errors
|
|
8328
|
+
};
|
|
8329
|
+
}
|
|
8330
|
+
|
|
8116
8331
|
// src/cron/cron-env.ts
|
|
8117
8332
|
import { existsSync as existsSync27 } from "node:fs";
|
|
8118
8333
|
import { homedir as homedir11 } from "node:os";
|
|
@@ -8578,7 +8793,7 @@ async function runKynverCronTick(opts = {}) {
|
|
|
8578
8793
|
}
|
|
8579
8794
|
|
|
8580
8795
|
// src/pipeline-tick.ts
|
|
8581
|
-
import
|
|
8796
|
+
import path54 from "node:path";
|
|
8582
8797
|
|
|
8583
8798
|
// src/pipeline-dispatch.ts
|
|
8584
8799
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -8709,10 +8924,10 @@ function resolvePipelineMaxStarts(resourceGate, operatorTick) {
|
|
|
8709
8924
|
}
|
|
8710
8925
|
|
|
8711
8926
|
// src/box-resource-snapshot.ts
|
|
8712
|
-
import
|
|
8927
|
+
import os8 from "node:os";
|
|
8713
8928
|
function buildBoxResourceSnapshotFromGate(gate, input = {}) {
|
|
8714
|
-
const boxKind = (input.boxKind ??
|
|
8715
|
-
const hostLabel = input.hostLabel ??
|
|
8929
|
+
const boxKind = (input.boxKind ?? resolveBoxKindFromConfig(loadUserConfig())).trim().toLowerCase() || "forge";
|
|
8930
|
+
const hostLabel = input.hostLabel ?? os8.hostname();
|
|
8716
8931
|
const boxId = input.boxId ?? defaultBoxId(boxKind, hostLabel);
|
|
8717
8932
|
return {
|
|
8718
8933
|
boxId,
|
|
@@ -8787,7 +9002,8 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
8787
9002
|
|
|
8788
9003
|
// src/workspace-runtime-config.ts
|
|
8789
9004
|
function shouldApplyWorkspaceRuntimePreferences(env = process.env) {
|
|
8790
|
-
|
|
9005
|
+
const config = loadUserConfig();
|
|
9006
|
+
return resolveBoxKindFromConfig(config, env) !== "forge";
|
|
8791
9007
|
}
|
|
8792
9008
|
function configuredMaxWorkersOverrideForBox(preferences, env = process.env) {
|
|
8793
9009
|
if (!shouldApplyWorkspaceRuntimePreferences(env)) return void 0;
|
|
@@ -8814,7 +9030,7 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
8814
9030
|
}
|
|
8815
9031
|
|
|
8816
9032
|
// src/cleanup.ts
|
|
8817
|
-
import
|
|
9033
|
+
import path52 from "node:path";
|
|
8818
9034
|
|
|
8819
9035
|
// src/cleanup-guards.ts
|
|
8820
9036
|
import path39 from "node:path";
|
|
@@ -8985,11 +9201,11 @@ var LIVE_SKIP_REASONS = /* @__PURE__ */ new Set([
|
|
|
8985
9201
|
function collectPreservedLivePaths(actions, skips) {
|
|
8986
9202
|
const out = [];
|
|
8987
9203
|
const seen = /* @__PURE__ */ new Set();
|
|
8988
|
-
const push = (
|
|
8989
|
-
const key = `${
|
|
9204
|
+
const push = (path67, reason, detail) => {
|
|
9205
|
+
const key = `${path67}\0${reason}`;
|
|
8990
9206
|
if (seen.has(key) || out.length >= MAX_PRESERVED_LIVE_PATH_SAMPLES) return;
|
|
8991
9207
|
seen.add(key);
|
|
8992
|
-
out.push({ path:
|
|
9208
|
+
out.push({ path: path67, reason, ...detail ? { detail } : {} });
|
|
8993
9209
|
};
|
|
8994
9210
|
for (const skip2 of skips) {
|
|
8995
9211
|
if (!LIVE_SKIP_REASONS.has(skip2.reason)) continue;
|
|
@@ -9118,8 +9334,7 @@ function scanStaleRunDirectoryCandidates(opts) {
|
|
|
9118
9334
|
}
|
|
9119
9335
|
|
|
9120
9336
|
// src/cleanup-execute.ts
|
|
9121
|
-
import { existsSync as
|
|
9122
|
-
import path43 from "node:path";
|
|
9337
|
+
import { existsSync as existsSync33, rmSync as rmSync4 } from "node:fs";
|
|
9123
9338
|
|
|
9124
9339
|
// src/cleanup-dir-size.ts
|
|
9125
9340
|
import { execFileSync } from "node:child_process";
|
|
@@ -9172,50 +9387,232 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
9172
9387
|
return total;
|
|
9173
9388
|
}
|
|
9174
9389
|
|
|
9175
|
-
// src/cleanup-
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
|
|
9390
|
+
// src/cleanup-remove-path.ts
|
|
9391
|
+
import { existsSync as existsSync32, rmSync as rmSync3 } from "node:fs";
|
|
9392
|
+
|
|
9393
|
+
// src/cleanup-path-ownership.ts
|
|
9394
|
+
import { lstatSync as lstatSync2, readdirSync as readdirSync11 } from "node:fs";
|
|
9395
|
+
function readPathOwnership(targetPath) {
|
|
9396
|
+
try {
|
|
9397
|
+
const st = lstatSync2(targetPath);
|
|
9398
|
+
const effectiveUid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
9399
|
+
const effectiveGid = typeof process.getgid === "function" ? process.getgid() : null;
|
|
9400
|
+
const foreign = effectiveUid !== null && (st.uid !== effectiveUid || effectiveGid !== null && st.gid !== effectiveGid);
|
|
9401
|
+
return { uid: st.uid, gid: st.gid, foreign };
|
|
9402
|
+
} catch {
|
|
9403
|
+
return null;
|
|
9404
|
+
}
|
|
9405
|
+
}
|
|
9406
|
+
function pathHasForeignOwnedEntry(targetPath, maxEntries = 32) {
|
|
9407
|
+
const root = readPathOwnership(targetPath);
|
|
9408
|
+
if (!root) return false;
|
|
9409
|
+
if (root.foreign) return true;
|
|
9410
|
+
try {
|
|
9411
|
+
const names = readdirSync11(targetPath);
|
|
9412
|
+
let checked = 0;
|
|
9413
|
+
for (const name of names) {
|
|
9414
|
+
if (checked >= maxEntries) break;
|
|
9415
|
+
const child = `${targetPath.replace(/\/$/, "")}/${name}`;
|
|
9416
|
+
const info = readPathOwnership(child);
|
|
9417
|
+
checked += 1;
|
|
9418
|
+
if (info?.foreign) return true;
|
|
9419
|
+
}
|
|
9420
|
+
} catch {
|
|
9421
|
+
}
|
|
9422
|
+
return false;
|
|
9423
|
+
}
|
|
9424
|
+
|
|
9425
|
+
// src/cleanup-privileged-remove.ts
|
|
9426
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
9427
|
+
import path44 from "node:path";
|
|
9428
|
+
|
|
9429
|
+
// src/cleanup-harness-path-validate.ts
|
|
9430
|
+
import path43 from "node:path";
|
|
9431
|
+
function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
|
|
9432
|
+
const resolved = path43.resolve(targetPath);
|
|
9433
|
+
const suffix = `${path43.sep}${cacheDirName}`;
|
|
9434
|
+
const cachePath = resolved.endsWith(suffix) ? resolved : null;
|
|
9435
|
+
if (!cachePath) return "path_outside_harness";
|
|
9436
|
+
const rel = path43.relative(worktreesDir, cachePath);
|
|
9437
|
+
if (rel.startsWith("..") || path43.isAbsolute(rel)) return "path_outside_harness";
|
|
9438
|
+
const parts = rel.split(path43.sep);
|
|
9439
|
+
if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
|
|
9440
|
+
if (!resolved.startsWith(path43.resolve(harnessRoot))) return "path_outside_harness";
|
|
9441
|
+
return null;
|
|
9442
|
+
}
|
|
9443
|
+
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
9444
|
+
return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, "node_modules");
|
|
9445
|
+
}
|
|
9446
|
+
function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
9447
|
+
return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
|
|
9448
|
+
}
|
|
9449
|
+
function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
9450
|
+
const resolved = path43.resolve(targetPath);
|
|
9451
|
+
const relToWt = path43.relative(worktreesDir, resolved);
|
|
9452
|
+
if (relToWt.startsWith("..") || path43.isAbsolute(relToWt)) return "path_outside_harness";
|
|
9453
|
+
const parts = relToWt.split(path43.sep);
|
|
9454
|
+
if (parts.length < 3) return "path_outside_harness";
|
|
9455
|
+
if (!resolved.startsWith(path43.resolve(harnessRoot))) return "path_outside_harness";
|
|
9456
|
+
return null;
|
|
9457
|
+
}
|
|
9458
|
+
function isHarnessGeneratedCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
9459
|
+
const resolved = path43.resolve(targetPath);
|
|
9460
|
+
return isHarnessNodeModulesPath(resolved, harnessRoot, worktreesDir) === null || isHarnessNextCachePath(resolved, harnessRoot, worktreesDir) === null || isHarnessBuildCachePath(resolved, harnessRoot, worktreesDir) === null;
|
|
9461
|
+
}
|
|
9462
|
+
|
|
9463
|
+
// src/cleanup-privileged-remove.ts
|
|
9464
|
+
function resolvePrivilegedCleanupMode() {
|
|
9465
|
+
const raw = (process.env.KYNVER_CLEANUP_PRIVILEGED ?? "auto").trim().toLowerCase();
|
|
9466
|
+
if (raw === "0" || raw === "false" || raw === "off" || raw === "no") return "off";
|
|
9467
|
+
if (raw === "1" || raw === "true" || raw === "force" || raw === "yes") return "force";
|
|
9468
|
+
return "auto";
|
|
9469
|
+
}
|
|
9470
|
+
function runSudoNonInteractive(argv) {
|
|
9471
|
+
const res = spawnSync4("sudo", ["-n", ...argv], {
|
|
9472
|
+
encoding: "utf8",
|
|
9473
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
9474
|
+
});
|
|
9179
9475
|
return {
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
skipped: true,
|
|
9183
|
-
skipReason: "run_metadata_protected"
|
|
9476
|
+
ok: res.status === 0,
|
|
9477
|
+
stderr: `${res.stderr ?? ""}${res.stdout ?? ""}`.trim()
|
|
9184
9478
|
};
|
|
9185
9479
|
}
|
|
9186
|
-
function
|
|
9187
|
-
|
|
9188
|
-
|
|
9480
|
+
function tryPrivilegedReclaimHarnessCache(targetPath, harnessRoot, worktreesDir) {
|
|
9481
|
+
if (!isHarnessGeneratedCachePath(targetPath, harnessRoot, worktreesDir)) {
|
|
9482
|
+
return { ok: false, error: "path is not an allowed harness generated cache" };
|
|
9483
|
+
}
|
|
9484
|
+
const effectiveUid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
9485
|
+
const effectiveGid = typeof process.getgid === "function" ? process.getgid() : null;
|
|
9486
|
+
if (effectiveUid === null || effectiveGid === null) {
|
|
9487
|
+
return { ok: false, error: "privileged reclaim requires POSIX uid/gid" };
|
|
9488
|
+
}
|
|
9489
|
+
const chown = runSudoNonInteractive([
|
|
9490
|
+
"chown",
|
|
9491
|
+
"-R",
|
|
9492
|
+
`${effectiveUid}:${effectiveGid}`,
|
|
9493
|
+
path44.resolve(targetPath)
|
|
9494
|
+
]);
|
|
9495
|
+
if (chown.ok) {
|
|
9496
|
+
return { ok: true, method: "chown_then_rm" };
|
|
9497
|
+
}
|
|
9498
|
+
const rm = runSudoNonInteractive(["rm", "-rf", path44.resolve(targetPath)]);
|
|
9499
|
+
if (rm.ok) {
|
|
9500
|
+
return { ok: true, method: "sudo_rm" };
|
|
9501
|
+
}
|
|
9502
|
+
const detail = chown.stderr || rm.stderr || "sudo -n failed (password required or not permitted)";
|
|
9503
|
+
return {
|
|
9504
|
+
ok: false,
|
|
9505
|
+
error: detail
|
|
9506
|
+
};
|
|
9507
|
+
}
|
|
9508
|
+
var HARNESS_ROOT_OWNED_CACHE_RUNBOOK = "Root-owned harness caches require operator reclaim: configure passwordless sudo for chown/rm under ~/.kynver/harness/worktrees, or run `node scripts/reclaim-harness-root-owned-cache.mjs --execute` as the harness owner with sudo available. See docs/runbooks/harness-root-owned-cache-reclaim.md.";
|
|
9509
|
+
|
|
9510
|
+
// src/cleanup-remove-path.ts
|
|
9511
|
+
function permissionFailure(error) {
|
|
9512
|
+
const code = error?.code;
|
|
9513
|
+
return code === "EACCES" || code === "EPERM";
|
|
9514
|
+
}
|
|
9515
|
+
function removeHarnessGeneratedPath(candidate, execute, deps = {}) {
|
|
9189
9516
|
if (!existsSync32(candidate.path)) {
|
|
9190
|
-
return {
|
|
9191
|
-
...candidate,
|
|
9192
|
-
executed: false,
|
|
9193
|
-
skipped: true,
|
|
9194
|
-
skipReason: "missing_worktree"
|
|
9195
|
-
};
|
|
9517
|
+
return { executed: false, skipped: true, skipReason: "missing_worktree" };
|
|
9196
9518
|
}
|
|
9197
9519
|
if (!execute) {
|
|
9198
|
-
return {
|
|
9520
|
+
return { executed: false, skipped: true, skipReason: "dry_run" };
|
|
9199
9521
|
}
|
|
9522
|
+
const harnessRoot = candidate.harnessRoot;
|
|
9523
|
+
const worktreesDir = harnessRoot ? harnessWorktreesDir(harnessRoot) : null;
|
|
9524
|
+
const bytesBefore = candidate.bytes ?? directorySizeBytes(candidate.path);
|
|
9525
|
+
const bytesReported = bytesBefore ?? void 0;
|
|
9526
|
+
const removePath = deps.removePath ?? rmSync3;
|
|
9527
|
+
const hasForeignOwnedEntry = deps.hasForeignOwnedEntry ?? pathHasForeignOwnedEntry;
|
|
9200
9528
|
try {
|
|
9201
|
-
|
|
9202
|
-
|
|
9203
|
-
return {
|
|
9204
|
-
...candidate,
|
|
9205
|
-
bytes: bytesBefore,
|
|
9206
|
-
executed: true,
|
|
9207
|
-
skipped: false
|
|
9208
|
-
};
|
|
9529
|
+
removePath(candidate.path, { recursive: true, force: true });
|
|
9530
|
+
return { executed: true, skipped: false, bytes: bytesReported };
|
|
9209
9531
|
} catch (error) {
|
|
9532
|
+
if (!permissionFailure(error) || !harnessRoot || !worktreesDir) {
|
|
9533
|
+
return {
|
|
9534
|
+
executed: false,
|
|
9535
|
+
skipped: true,
|
|
9536
|
+
skipReason: "remove_failed",
|
|
9537
|
+
error: error.message
|
|
9538
|
+
};
|
|
9539
|
+
}
|
|
9540
|
+
const foreign = hasForeignOwnedEntry(candidate.path);
|
|
9541
|
+
const mode = resolvePrivilegedCleanupMode();
|
|
9542
|
+
const shouldTryPrivileged = mode === "force" || mode === "auto" && foreign;
|
|
9543
|
+
if (!shouldTryPrivileged) {
|
|
9544
|
+
return foreign ? {
|
|
9545
|
+
executed: false,
|
|
9546
|
+
skipped: true,
|
|
9547
|
+
skipReason: "foreign_owner",
|
|
9548
|
+
error: `${error.message}; ${HARNESS_ROOT_OWNED_CACHE_RUNBOOK}`
|
|
9549
|
+
} : {
|
|
9550
|
+
executed: false,
|
|
9551
|
+
skipped: true,
|
|
9552
|
+
skipReason: "remove_failed",
|
|
9553
|
+
error: error.message
|
|
9554
|
+
};
|
|
9555
|
+
}
|
|
9556
|
+
const reclaim = tryPrivilegedReclaimHarnessCache(candidate.path, harnessRoot, worktreesDir);
|
|
9557
|
+
if (reclaim.ok && reclaim.method === "sudo_rm") {
|
|
9558
|
+
return {
|
|
9559
|
+
executed: true,
|
|
9560
|
+
skipped: false,
|
|
9561
|
+
bytes: bytesReported,
|
|
9562
|
+
privilegedReclaim: true
|
|
9563
|
+
};
|
|
9564
|
+
}
|
|
9565
|
+
if (reclaim.ok) {
|
|
9566
|
+
try {
|
|
9567
|
+
removePath(candidate.path, { recursive: true, force: true });
|
|
9568
|
+
return {
|
|
9569
|
+
executed: true,
|
|
9570
|
+
skipped: false,
|
|
9571
|
+
bytes: bytesReported,
|
|
9572
|
+
privilegedReclaim: true
|
|
9573
|
+
};
|
|
9574
|
+
} catch (retryError) {
|
|
9575
|
+
return {
|
|
9576
|
+
executed: false,
|
|
9577
|
+
skipped: true,
|
|
9578
|
+
skipReason: "foreign_owner",
|
|
9579
|
+
error: `${retryError.message}; privileged chown succeeded but rm still failed`
|
|
9580
|
+
};
|
|
9581
|
+
}
|
|
9582
|
+
}
|
|
9210
9583
|
return {
|
|
9211
|
-
...candidate,
|
|
9212
9584
|
executed: false,
|
|
9213
9585
|
skipped: true,
|
|
9214
|
-
skipReason: "
|
|
9215
|
-
error: error.message
|
|
9586
|
+
skipReason: "foreign_owner",
|
|
9587
|
+
error: `${error.message}; privileged reclaim failed: ${reclaim.error}; ${HARNESS_ROOT_OWNED_CACHE_RUNBOOK}`
|
|
9216
9588
|
};
|
|
9217
9589
|
}
|
|
9218
9590
|
}
|
|
9591
|
+
|
|
9592
|
+
// src/cleanup-execute.ts
|
|
9593
|
+
function skipRunMetadataRemoval(candidate) {
|
|
9594
|
+
const harnessRoot = candidate.harnessRoot;
|
|
9595
|
+
if (!harnessRoot || !isHarnessRunMetadataPath(candidate.path, harnessRoot)) return null;
|
|
9596
|
+
return {
|
|
9597
|
+
...candidate,
|
|
9598
|
+
executed: false,
|
|
9599
|
+
skipped: true,
|
|
9600
|
+
skipReason: "run_metadata_protected"
|
|
9601
|
+
};
|
|
9602
|
+
}
|
|
9603
|
+
function removeDependencyCache(candidate, execute) {
|
|
9604
|
+
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
9605
|
+
if (metadataSkip) return metadataSkip;
|
|
9606
|
+
const outcome = removeHarnessGeneratedPath(candidate, execute);
|
|
9607
|
+
return {
|
|
9608
|
+
...candidate,
|
|
9609
|
+
bytes: outcome.bytes ?? candidate.bytes,
|
|
9610
|
+
executed: outcome.executed,
|
|
9611
|
+
skipped: outcome.skipped,
|
|
9612
|
+
skipReason: outcome.skipReason,
|
|
9613
|
+
error: outcome.error
|
|
9614
|
+
};
|
|
9615
|
+
}
|
|
9219
9616
|
function removeNodeModules(candidate, execute) {
|
|
9220
9617
|
return removeDependencyCache(candidate, execute);
|
|
9221
9618
|
}
|
|
@@ -9228,7 +9625,7 @@ function removeBuildCache(candidate, execute) {
|
|
|
9228
9625
|
function removeRunDirectory(candidate, execute) {
|
|
9229
9626
|
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
9230
9627
|
if (metadataSkip) return metadataSkip;
|
|
9231
|
-
if (!
|
|
9628
|
+
if (!existsSync33(candidate.path)) {
|
|
9232
9629
|
return {
|
|
9233
9630
|
...candidate,
|
|
9234
9631
|
executed: false,
|
|
@@ -9241,7 +9638,7 @@ function removeRunDirectory(candidate, execute) {
|
|
|
9241
9638
|
}
|
|
9242
9639
|
try {
|
|
9243
9640
|
const bytesBefore = candidate.bytes ?? directorySizeBytes(candidate.path);
|
|
9244
|
-
|
|
9641
|
+
rmSync4(candidate.path, { recursive: true, force: true });
|
|
9245
9642
|
return {
|
|
9246
9643
|
...candidate,
|
|
9247
9644
|
bytes: bytesBefore,
|
|
@@ -9261,7 +9658,7 @@ function removeRunDirectory(candidate, execute) {
|
|
|
9261
9658
|
function removeWorktree(candidate, execute) {
|
|
9262
9659
|
const metadataSkip = skipRunMetadataRemoval(candidate);
|
|
9263
9660
|
if (metadataSkip) return metadataSkip;
|
|
9264
|
-
if (!
|
|
9661
|
+
if (!existsSync33(candidate.path)) {
|
|
9265
9662
|
return {
|
|
9266
9663
|
...candidate,
|
|
9267
9664
|
executed: false,
|
|
@@ -9278,8 +9675,8 @@ function removeWorktree(candidate, execute) {
|
|
|
9278
9675
|
if (repo) {
|
|
9279
9676
|
git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
|
|
9280
9677
|
}
|
|
9281
|
-
if (
|
|
9282
|
-
|
|
9678
|
+
if (existsSync33(candidate.path)) {
|
|
9679
|
+
rmSync4(candidate.path, { recursive: true, force: true });
|
|
9283
9680
|
}
|
|
9284
9681
|
return {
|
|
9285
9682
|
...candidate,
|
|
@@ -9297,37 +9694,10 @@ function removeWorktree(candidate, execute) {
|
|
|
9297
9694
|
};
|
|
9298
9695
|
}
|
|
9299
9696
|
}
|
|
9300
|
-
function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
|
|
9301
|
-
const resolved = path43.resolve(targetPath);
|
|
9302
|
-
const suffix = `${path43.sep}${cacheDirName}`;
|
|
9303
|
-
const cachePath = resolved.endsWith(suffix) ? resolved : null;
|
|
9304
|
-
if (!cachePath) return "path_outside_harness";
|
|
9305
|
-
const rel = path43.relative(worktreesDir, cachePath);
|
|
9306
|
-
if (rel.startsWith("..") || path43.isAbsolute(rel)) return "path_outside_harness";
|
|
9307
|
-
const parts = rel.split(path43.sep);
|
|
9308
|
-
if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
|
|
9309
|
-
if (!resolved.startsWith(path43.resolve(harnessRoot))) return "path_outside_harness";
|
|
9310
|
-
return null;
|
|
9311
|
-
}
|
|
9312
|
-
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
9313
|
-
return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, "node_modules");
|
|
9314
|
-
}
|
|
9315
|
-
function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
9316
|
-
return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
|
|
9317
|
-
}
|
|
9318
|
-
function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
|
|
9319
|
-
const resolved = path43.resolve(targetPath);
|
|
9320
|
-
const relToWt = path43.relative(worktreesDir, resolved);
|
|
9321
|
-
if (relToWt.startsWith("..") || path43.isAbsolute(relToWt)) return "path_outside_harness";
|
|
9322
|
-
const parts = relToWt.split(path43.sep);
|
|
9323
|
-
if (parts.length < 3) return "path_outside_harness";
|
|
9324
|
-
if (!resolved.startsWith(path43.resolve(harnessRoot))) return "path_outside_harness";
|
|
9325
|
-
return null;
|
|
9326
|
-
}
|
|
9327
9697
|
|
|
9328
9698
|
// src/cleanup-scan.ts
|
|
9329
|
-
import { existsSync as
|
|
9330
|
-
import
|
|
9699
|
+
import { existsSync as existsSync34, readdirSync as readdirSync12, statSync as statSync9 } from "node:fs";
|
|
9700
|
+
import path45 from "node:path";
|
|
9331
9701
|
function pathAgeMs2(target, now) {
|
|
9332
9702
|
try {
|
|
9333
9703
|
const mtime = statSync9(target).mtimeMs;
|
|
@@ -9337,16 +9707,16 @@ function pathAgeMs2(target, now) {
|
|
|
9337
9707
|
}
|
|
9338
9708
|
}
|
|
9339
9709
|
function isPathInside(child, parent) {
|
|
9340
|
-
const rel =
|
|
9341
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
9710
|
+
const rel = path45.relative(parent, child);
|
|
9711
|
+
return rel === "" || !rel.startsWith("..") && !path45.isAbsolute(rel);
|
|
9342
9712
|
}
|
|
9343
9713
|
function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
|
|
9344
9714
|
const out = [];
|
|
9345
9715
|
for (const rel of HARNESS_BUILD_CACHE_RELATIVE_PATHS) {
|
|
9346
9716
|
if (rel === ".next") continue;
|
|
9347
|
-
const target =
|
|
9348
|
-
if (!
|
|
9349
|
-
const resolved =
|
|
9717
|
+
const target = path45.join(worktreePath, rel);
|
|
9718
|
+
if (!existsSync34(target)) continue;
|
|
9719
|
+
const resolved = path45.resolve(target);
|
|
9350
9720
|
if (seen.has(resolved)) continue;
|
|
9351
9721
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
9352
9722
|
seen.add(resolved);
|
|
@@ -9375,13 +9745,13 @@ function scanBuildCacheCandidates(opts) {
|
|
|
9375
9745
|
})
|
|
9376
9746
|
);
|
|
9377
9747
|
}
|
|
9378
|
-
if (!opts.includeOrphans || !
|
|
9379
|
-
for (const runEntry of
|
|
9748
|
+
if (!opts.includeOrphans || !existsSync34(opts.worktreesDir)) return candidates;
|
|
9749
|
+
for (const runEntry of readdirSync12(opts.worktreesDir, { withFileTypes: true })) {
|
|
9380
9750
|
if (!runEntry.isDirectory()) continue;
|
|
9381
|
-
const runPath =
|
|
9382
|
-
for (const workerEntry of
|
|
9751
|
+
const runPath = path45.join(opts.worktreesDir, runEntry.name);
|
|
9752
|
+
for (const workerEntry of readdirSync12(runPath, { withFileTypes: true })) {
|
|
9383
9753
|
if (!workerEntry.isDirectory()) continue;
|
|
9384
|
-
const worktreePath =
|
|
9754
|
+
const worktreePath = path45.join(runPath, workerEntry.name);
|
|
9385
9755
|
candidates.push(
|
|
9386
9756
|
...collectBuildCacheForWorktree(worktreePath, opts, seen, {
|
|
9387
9757
|
runId: runEntry.name,
|
|
@@ -9402,7 +9772,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
9402
9772
|
for (const entry of opts.index.values()) {
|
|
9403
9773
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
9404
9774
|
const resolved = entry.worktreePath;
|
|
9405
|
-
if (!
|
|
9775
|
+
if (!existsSync34(resolved)) continue;
|
|
9406
9776
|
if (seen.has(resolved)) continue;
|
|
9407
9777
|
seen.add(resolved);
|
|
9408
9778
|
candidates.push({
|
|
@@ -9416,24 +9786,24 @@ function scanWorktreeCandidates(opts) {
|
|
|
9416
9786
|
});
|
|
9417
9787
|
}
|
|
9418
9788
|
}
|
|
9419
|
-
if (!orphanEnabled || !
|
|
9789
|
+
if (!orphanEnabled || !existsSync34(opts.worktreesDir)) return candidates;
|
|
9420
9790
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
9421
9791
|
for (const entry of opts.index.values()) {
|
|
9422
|
-
indexedPaths.add(
|
|
9792
|
+
indexedPaths.add(path45.resolve(entry.worktreePath));
|
|
9423
9793
|
}
|
|
9424
|
-
for (const runEntry of
|
|
9794
|
+
for (const runEntry of readdirSync12(opts.worktreesDir, { withFileTypes: true })) {
|
|
9425
9795
|
if (!runEntry.isDirectory()) continue;
|
|
9426
9796
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
9427
|
-
const runPath =
|
|
9797
|
+
const runPath = path45.join(opts.worktreesDir, runEntry.name);
|
|
9428
9798
|
let workerEntries;
|
|
9429
9799
|
try {
|
|
9430
|
-
workerEntries =
|
|
9800
|
+
workerEntries = readdirSync12(runPath, { withFileTypes: true });
|
|
9431
9801
|
} catch {
|
|
9432
9802
|
continue;
|
|
9433
9803
|
}
|
|
9434
9804
|
for (const workerEntry of workerEntries) {
|
|
9435
9805
|
if (!workerEntry.isDirectory()) continue;
|
|
9436
|
-
const worktreePath =
|
|
9806
|
+
const worktreePath = path45.resolve(path45.join(runPath, workerEntry.name));
|
|
9437
9807
|
if (seen.has(worktreePath)) continue;
|
|
9438
9808
|
if (indexedPaths.has(worktreePath)) continue;
|
|
9439
9809
|
if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
|
|
@@ -9452,8 +9822,8 @@ function scanWorktreeCandidates(opts) {
|
|
|
9452
9822
|
}
|
|
9453
9823
|
|
|
9454
9824
|
// src/cleanup-dependency-scan.ts
|
|
9455
|
-
import { existsSync as
|
|
9456
|
-
import
|
|
9825
|
+
import { existsSync as existsSync35, readdirSync as readdirSync13, statSync as statSync10 } from "node:fs";
|
|
9826
|
+
import path46 from "node:path";
|
|
9457
9827
|
var DEPENDENCY_CACHE_DIRS = [
|
|
9458
9828
|
{ dirName: "node_modules", kind: "remove_node_modules" },
|
|
9459
9829
|
{ dirName: ".next", kind: "remove_next_cache" }
|
|
@@ -9467,12 +9837,12 @@ function pathAgeMs3(target, now) {
|
|
|
9467
9837
|
}
|
|
9468
9838
|
}
|
|
9469
9839
|
function isPathInside2(child, parent) {
|
|
9470
|
-
const rel =
|
|
9471
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
9840
|
+
const rel = path46.relative(parent, child);
|
|
9841
|
+
return rel === "" || !rel.startsWith("..") && !path46.isAbsolute(rel);
|
|
9472
9842
|
}
|
|
9473
9843
|
function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
9474
|
-
if (!
|
|
9475
|
-
const resolved =
|
|
9844
|
+
if (!existsSync35(targetPath)) return;
|
|
9845
|
+
const resolved = path46.resolve(targetPath);
|
|
9476
9846
|
if (seen.has(resolved)) return;
|
|
9477
9847
|
if (!isPathInside2(resolved, opts.harnessRoot)) return;
|
|
9478
9848
|
seen.add(resolved);
|
|
@@ -9489,7 +9859,7 @@ function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
|
|
|
9489
9859
|
}
|
|
9490
9860
|
function scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, meta) {
|
|
9491
9861
|
for (const entry of DEPENDENCY_CACHE_DIRS) {
|
|
9492
|
-
pushCandidate2(candidates, seen, opts,
|
|
9862
|
+
pushCandidate2(candidates, seen, opts, path46.join(worktreePath, entry.dirName), entry.kind, meta);
|
|
9493
9863
|
}
|
|
9494
9864
|
}
|
|
9495
9865
|
function scanDependencyCacheCandidates(opts) {
|
|
@@ -9503,20 +9873,20 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
9503
9873
|
repo: entry.run.repo
|
|
9504
9874
|
});
|
|
9505
9875
|
}
|
|
9506
|
-
if (!opts.includeOrphans || !
|
|
9507
|
-
for (const runEntry of
|
|
9876
|
+
if (!opts.includeOrphans || !existsSync35(opts.worktreesDir)) return candidates;
|
|
9877
|
+
for (const runEntry of readdirSync13(opts.worktreesDir, { withFileTypes: true })) {
|
|
9508
9878
|
if (!runEntry.isDirectory()) continue;
|
|
9509
9879
|
if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
|
|
9510
|
-
const runPath =
|
|
9880
|
+
const runPath = path46.join(opts.worktreesDir, runEntry.name);
|
|
9511
9881
|
let workerEntries;
|
|
9512
9882
|
try {
|
|
9513
|
-
workerEntries =
|
|
9883
|
+
workerEntries = readdirSync13(runPath, { withFileTypes: true });
|
|
9514
9884
|
} catch {
|
|
9515
9885
|
continue;
|
|
9516
9886
|
}
|
|
9517
9887
|
for (const workerEntry of workerEntries) {
|
|
9518
9888
|
if (!workerEntry.isDirectory()) continue;
|
|
9519
|
-
const worktreePath =
|
|
9889
|
+
const worktreePath = path46.join(runPath, workerEntry.name);
|
|
9520
9890
|
scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, {
|
|
9521
9891
|
runId: runEntry.name,
|
|
9522
9892
|
worker: workerEntry.name
|
|
@@ -9527,8 +9897,8 @@ function scanDependencyCacheCandidates(opts) {
|
|
|
9527
9897
|
}
|
|
9528
9898
|
|
|
9529
9899
|
// src/cleanup-duplicate-worktrees.ts
|
|
9530
|
-
import { existsSync as
|
|
9531
|
-
import
|
|
9900
|
+
import { existsSync as existsSync36, statSync as statSync11 } from "node:fs";
|
|
9901
|
+
import path47 from "node:path";
|
|
9532
9902
|
function pathAgeMs4(target, now) {
|
|
9533
9903
|
try {
|
|
9534
9904
|
const mtime = statSync11(target).mtimeMs;
|
|
@@ -9558,8 +9928,8 @@ function parseWorktreePorcelain(output) {
|
|
|
9558
9928
|
return records;
|
|
9559
9929
|
}
|
|
9560
9930
|
function isUnderWorktreesDir(worktreePath, worktreesDir) {
|
|
9561
|
-
const rel =
|
|
9562
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
9931
|
+
const rel = path47.relative(path47.resolve(worktreesDir), path47.resolve(worktreePath));
|
|
9932
|
+
return rel !== "" && !rel.startsWith("..") && !path47.isAbsolute(rel);
|
|
9563
9933
|
}
|
|
9564
9934
|
function isCleanWorktree(worktreePath, repoRoot) {
|
|
9565
9935
|
try {
|
|
@@ -9572,14 +9942,14 @@ function isCleanWorktree(worktreePath, repoRoot) {
|
|
|
9572
9942
|
}
|
|
9573
9943
|
}
|
|
9574
9944
|
function scanDuplicateWorktreeCandidates(opts) {
|
|
9575
|
-
if (!opts.includeOrphans || !
|
|
9945
|
+
if (!opts.includeOrphans || !existsSync36(opts.worktreesDir)) return [];
|
|
9576
9946
|
const repos = /* @__PURE__ */ new Set();
|
|
9577
9947
|
for (const entry of opts.index.values()) {
|
|
9578
|
-
if (entry.run.repo) repos.add(
|
|
9948
|
+
if (entry.run.repo) repos.add(path47.resolve(entry.run.repo));
|
|
9579
9949
|
}
|
|
9580
9950
|
const indexedPaths = /* @__PURE__ */ new Set();
|
|
9581
9951
|
for (const entry of opts.index.values()) {
|
|
9582
|
-
indexedPaths.add(
|
|
9952
|
+
indexedPaths.add(path47.resolve(entry.worktreePath));
|
|
9583
9953
|
}
|
|
9584
9954
|
const candidates = [];
|
|
9585
9955
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -9592,15 +9962,15 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
9592
9962
|
}
|
|
9593
9963
|
const worktrees = parseWorktreePorcelain(porcelain);
|
|
9594
9964
|
for (const wt of worktrees) {
|
|
9595
|
-
const resolved =
|
|
9596
|
-
if (resolved ===
|
|
9965
|
+
const resolved = path47.resolve(wt.path);
|
|
9966
|
+
if (resolved === path47.resolve(repoRoot)) continue;
|
|
9597
9967
|
if (!isUnderWorktreesDir(resolved, opts.worktreesDir)) continue;
|
|
9598
9968
|
if (indexedPaths.has(resolved)) continue;
|
|
9599
9969
|
if (seen.has(resolved)) continue;
|
|
9600
|
-
if (!
|
|
9970
|
+
if (!existsSync36(resolved)) continue;
|
|
9601
9971
|
if (!isCleanWorktree(resolved, repoRoot)) continue;
|
|
9602
|
-
const rel =
|
|
9603
|
-
const parts = rel.split(
|
|
9972
|
+
const rel = path47.relative(opts.worktreesDir, resolved);
|
|
9973
|
+
const parts = rel.split(path47.sep);
|
|
9604
9974
|
const runId = parts[0];
|
|
9605
9975
|
const worker = parts[1] ?? "unknown";
|
|
9606
9976
|
seen.add(resolved);
|
|
@@ -9619,12 +9989,12 @@ function scanDuplicateWorktreeCandidates(opts) {
|
|
|
9619
9989
|
}
|
|
9620
9990
|
|
|
9621
9991
|
// src/cleanup-worktree-index.ts
|
|
9622
|
-
import
|
|
9992
|
+
import path48 from "node:path";
|
|
9623
9993
|
function buildWorktreeIndexAt(harnessRoot) {
|
|
9624
9994
|
const index = /* @__PURE__ */ new Map();
|
|
9625
9995
|
for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
|
|
9626
9996
|
for (const name of Object.keys(run.workers || {})) {
|
|
9627
|
-
const workerPath =
|
|
9997
|
+
const workerPath = path48.join(
|
|
9628
9998
|
runDirectoryAt(harnessRoot, run.id),
|
|
9629
9999
|
"workers",
|
|
9630
10000
|
safeSlug(name),
|
|
@@ -9632,9 +10002,9 @@ function buildWorktreeIndexAt(harnessRoot) {
|
|
|
9632
10002
|
);
|
|
9633
10003
|
const worker = readJson(workerPath, void 0);
|
|
9634
10004
|
if (!worker?.worktreePath) continue;
|
|
9635
|
-
index.set(
|
|
10005
|
+
index.set(path48.resolve(worker.worktreePath), {
|
|
9636
10006
|
harnessRoot,
|
|
9637
|
-
worktreePath:
|
|
10007
|
+
worktreePath: path48.resolve(worker.worktreePath),
|
|
9638
10008
|
runId: run.id,
|
|
9639
10009
|
workerName: name,
|
|
9640
10010
|
run,
|
|
@@ -9703,15 +10073,15 @@ function resolvePipelineHarnessRetention(runId) {
|
|
|
9703
10073
|
}
|
|
9704
10074
|
|
|
9705
10075
|
// src/cleanup-orphan-safety.ts
|
|
9706
|
-
import { existsSync as
|
|
9707
|
-
import
|
|
10076
|
+
import { existsSync as existsSync37, statSync as statSync12 } from "node:fs";
|
|
10077
|
+
import path49 from "node:path";
|
|
9708
10078
|
var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
|
|
9709
10079
|
function assessOrphanWorktreeSafety(input) {
|
|
9710
10080
|
const now = input.now ?? Date.now();
|
|
9711
10081
|
const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
|
|
9712
|
-
if (!
|
|
10082
|
+
if (!existsSync37(input.worktreePath)) return null;
|
|
9713
10083
|
if (input.runId && input.workerName) {
|
|
9714
|
-
const heartbeatPath =
|
|
10084
|
+
const heartbeatPath = path49.join(
|
|
9715
10085
|
input.harnessRoot,
|
|
9716
10086
|
"runs",
|
|
9717
10087
|
input.runId,
|
|
@@ -9725,8 +10095,8 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
9725
10095
|
} catch {
|
|
9726
10096
|
}
|
|
9727
10097
|
}
|
|
9728
|
-
const gitDir =
|
|
9729
|
-
if (!
|
|
10098
|
+
const gitDir = path49.join(input.worktreePath, ".git");
|
|
10099
|
+
if (!existsSync37(gitDir)) return null;
|
|
9730
10100
|
const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
|
|
9731
10101
|
if (porcelain.status !== 0) return "pr_or_unmerged_commits";
|
|
9732
10102
|
const dirtyLines = porcelain.stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
@@ -9755,14 +10125,14 @@ function assessOrphanWorktreeSafety(input) {
|
|
|
9755
10125
|
}
|
|
9756
10126
|
|
|
9757
10127
|
// src/harness-storage-snapshot.ts
|
|
9758
|
-
import { existsSync as
|
|
9759
|
-
import
|
|
10128
|
+
import { existsSync as existsSync38, readdirSync as readdirSync14, statSync as statSync13 } from "node:fs";
|
|
10129
|
+
import path50 from "node:path";
|
|
9760
10130
|
function harnessStorageSnapshot(opts = {}) {
|
|
9761
10131
|
const harnessRoot = normalizeHarnessRoot(opts.harnessRoot ?? resolveHarnessRoot());
|
|
9762
10132
|
const worktreesDir = harnessWorktreesDir(harnessRoot);
|
|
9763
10133
|
const now = opts.now ?? Date.now();
|
|
9764
10134
|
const scannedAt = new Date(now).toISOString();
|
|
9765
|
-
if (!
|
|
10135
|
+
if (!existsSync38(worktreesDir)) {
|
|
9766
10136
|
return {
|
|
9767
10137
|
harnessRoot,
|
|
9768
10138
|
worktreesDir,
|
|
@@ -9779,7 +10149,7 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
9779
10149
|
let oldestMs = null;
|
|
9780
10150
|
let entries;
|
|
9781
10151
|
try {
|
|
9782
|
-
entries =
|
|
10152
|
+
entries = readdirSync14(worktreesDir, { withFileTypes: true });
|
|
9783
10153
|
} catch {
|
|
9784
10154
|
return {
|
|
9785
10155
|
harnessRoot,
|
|
@@ -9794,14 +10164,14 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
9794
10164
|
for (const runEntry of entries) {
|
|
9795
10165
|
if (!runEntry.isDirectory()) continue;
|
|
9796
10166
|
runCount += 1;
|
|
9797
|
-
const runPath =
|
|
10167
|
+
const runPath = path50.join(worktreesDir, runEntry.name);
|
|
9798
10168
|
try {
|
|
9799
10169
|
const st = statSync13(runPath);
|
|
9800
10170
|
oldestMs = oldestMs === null ? st.mtimeMs : Math.min(oldestMs, st.mtimeMs);
|
|
9801
10171
|
} catch {
|
|
9802
10172
|
}
|
|
9803
10173
|
try {
|
|
9804
|
-
for (const workerEntry of
|
|
10174
|
+
for (const workerEntry of readdirSync14(runPath, { withFileTypes: true })) {
|
|
9805
10175
|
if (workerEntry.isDirectory()) workerCount += 1;
|
|
9806
10176
|
}
|
|
9807
10177
|
} catch {
|
|
@@ -9829,12 +10199,12 @@ function harnessStorageSnapshot(opts = {}) {
|
|
|
9829
10199
|
}
|
|
9830
10200
|
|
|
9831
10201
|
// src/cleanup-harness-roots.ts
|
|
9832
|
-
import { existsSync as
|
|
10202
|
+
import { existsSync as existsSync39 } from "node:fs";
|
|
9833
10203
|
import { homedir as homedir12 } from "node:os";
|
|
9834
|
-
import
|
|
10204
|
+
import path51 from "node:path";
|
|
9835
10205
|
var WELL_KNOWN_HARNESS_SCAN_ROOTS = [
|
|
9836
10206
|
"/var/tmp/kynver-harness",
|
|
9837
|
-
|
|
10207
|
+
path51.join(homedir12(), ".openclaw", "harness")
|
|
9838
10208
|
];
|
|
9839
10209
|
function addRoot(seen, roots, candidate) {
|
|
9840
10210
|
if (!candidate?.trim()) return;
|
|
@@ -9856,8 +10226,8 @@ function resolveHarnessScanRoots(options = {}) {
|
|
|
9856
10226
|
for (const candidate of extra ?? []) addRoot(seen, roots, candidate);
|
|
9857
10227
|
if (shouldScanWellKnownRoots(options)) {
|
|
9858
10228
|
for (const candidate of WELL_KNOWN_HARNESS_SCAN_ROOTS) {
|
|
9859
|
-
const resolved =
|
|
9860
|
-
if (!seen.has(resolved) &&
|
|
10229
|
+
const resolved = path51.resolve(candidate);
|
|
10230
|
+
if (!seen.has(resolved) && existsSync39(resolved)) addRoot(seen, roots, resolved);
|
|
9861
10231
|
}
|
|
9862
10232
|
}
|
|
9863
10233
|
return roots;
|
|
@@ -9887,11 +10257,19 @@ function observeCleanupDiskPressure(input = {}) {
|
|
|
9887
10257
|
}
|
|
9888
10258
|
function applyDiskPressureToRetention(retention, pressure) {
|
|
9889
10259
|
if (!pressure.pressured) return retention;
|
|
9890
|
-
const executeOnPressure = retention.execute || envFlag3("
|
|
10260
|
+
const executeOnPressure = retention.execute || !envFlag3("KYNVER_CLEANUP_DRY_RUN_ON_PRESSURE");
|
|
9891
10261
|
return {
|
|
9892
10262
|
...retention,
|
|
9893
10263
|
execute: executeOnPressure,
|
|
9894
10264
|
nodeModulesAgeMs: 0,
|
|
10265
|
+
// Disk pressure means the current-run-only cleanup scope has already fallen
|
|
10266
|
+
// behind. Expand the sweep to every known harness root/run, but keep the
|
|
10267
|
+
// worktree salvage/PR/dirty/live-worker guards in the cleanup pipeline.
|
|
10268
|
+
runIdFilter: void 0,
|
|
10269
|
+
includeOrphans: true,
|
|
10270
|
+
terminalWorktreesAgeMs: 0,
|
|
10271
|
+
runDirectoriesAgeMs: 0,
|
|
10272
|
+
worktreesAgeMs: retention.worktreesAgeMs > 0 ? retention.worktreesAgeMs : DEFAULT_WORKTREES_AGE_MS,
|
|
9895
10273
|
diskPressure: true,
|
|
9896
10274
|
diskGate: pressure.diskGate
|
|
9897
10275
|
};
|
|
@@ -10003,9 +10381,9 @@ function mergeWorktreeIndexes(scanRoots) {
|
|
|
10003
10381
|
}
|
|
10004
10382
|
function worktreePathForCandidate(candidate, worktreesDir) {
|
|
10005
10383
|
if (candidate.runId && candidate.worker) {
|
|
10006
|
-
return
|
|
10384
|
+
return path52.join(worktreesDir, candidate.runId, candidate.worker);
|
|
10007
10385
|
}
|
|
10008
|
-
return
|
|
10386
|
+
return path52.resolve(candidate.path, "..");
|
|
10009
10387
|
}
|
|
10010
10388
|
function runHarnessCleanup(options = {}) {
|
|
10011
10389
|
let retention = resolveHarnessRetention(options);
|
|
@@ -10030,7 +10408,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10030
10408
|
for (const harnessRoot of paths.scanRoots) {
|
|
10031
10409
|
if (atSweepCap()) break;
|
|
10032
10410
|
emitCleanupProgress("root", harnessRoot);
|
|
10033
|
-
const worktreesDir =
|
|
10411
|
+
const worktreesDir = path52.join(harnessRoot, "worktrees");
|
|
10034
10412
|
const scanOpts = {
|
|
10035
10413
|
harnessRoot,
|
|
10036
10414
|
worktreesDir,
|
|
@@ -10043,7 +10421,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10043
10421
|
};
|
|
10044
10422
|
for (const raw of scanDependencyCacheCandidates(scanOpts)) {
|
|
10045
10423
|
if (atSweepCap()) break;
|
|
10046
|
-
const resolved =
|
|
10424
|
+
const resolved = path52.resolve(raw.path);
|
|
10047
10425
|
if (processedPaths.has(resolved)) continue;
|
|
10048
10426
|
processedPaths.add(resolved);
|
|
10049
10427
|
const candidate = { ...raw, path: resolved };
|
|
@@ -10054,7 +10432,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10054
10432
|
continue;
|
|
10055
10433
|
}
|
|
10056
10434
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
10057
|
-
const indexed = index.get(
|
|
10435
|
+
const indexed = index.get(path52.resolve(worktreePath)) ?? null;
|
|
10058
10436
|
const guardReason = skipDependencyCacheRemoval({
|
|
10059
10437
|
indexed,
|
|
10060
10438
|
includeOrphans: true,
|
|
@@ -10078,7 +10456,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10078
10456
|
}
|
|
10079
10457
|
for (const raw of scanBuildCacheCandidates(scanOpts)) {
|
|
10080
10458
|
if (atSweepCap()) break;
|
|
10081
|
-
const resolved =
|
|
10459
|
+
const resolved = path52.resolve(raw.path);
|
|
10082
10460
|
if (processedPaths.has(resolved)) continue;
|
|
10083
10461
|
processedPaths.add(resolved);
|
|
10084
10462
|
const candidate = { ...raw, path: resolved };
|
|
@@ -10089,7 +10467,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10089
10467
|
continue;
|
|
10090
10468
|
}
|
|
10091
10469
|
const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
|
|
10092
|
-
const indexed = index.get(
|
|
10470
|
+
const indexed = index.get(path52.resolve(worktreePath)) ?? null;
|
|
10093
10471
|
const guardReason = skipBuildCacheRemoval({
|
|
10094
10472
|
indexed,
|
|
10095
10473
|
includeOrphans: true,
|
|
@@ -10119,11 +10497,11 @@ function runHarnessCleanup(options = {}) {
|
|
|
10119
10497
|
const worktreeSeen = /* @__PURE__ */ new Set();
|
|
10120
10498
|
for (const raw of worktreeCandidates) {
|
|
10121
10499
|
if (atSweepCap()) break;
|
|
10122
|
-
const resolved =
|
|
10500
|
+
const resolved = path52.resolve(raw.path);
|
|
10123
10501
|
if (worktreeSeen.has(resolved)) continue;
|
|
10124
10502
|
worktreeSeen.add(resolved);
|
|
10125
10503
|
const candidate = { ...raw, path: resolved };
|
|
10126
|
-
const indexed = index.get(
|
|
10504
|
+
const indexed = index.get(path52.resolve(candidate.path)) ?? null;
|
|
10127
10505
|
const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
|
|
10128
10506
|
worktreePath: candidate.path,
|
|
10129
10507
|
harnessRoot,
|
|
@@ -10133,7 +10511,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
10133
10511
|
});
|
|
10134
10512
|
const guardSkip = skipWorktreeRemoval({
|
|
10135
10513
|
indexed,
|
|
10136
|
-
worktreePath:
|
|
10514
|
+
worktreePath: path52.resolve(candidate.path),
|
|
10137
10515
|
includeOrphans: retention.includeOrphans,
|
|
10138
10516
|
worktreesAgeMs: retention.worktreesAgeMs,
|
|
10139
10517
|
terminalWorktreesAgeMs: retention.terminalWorktreesAgeMs,
|
|
@@ -10165,11 +10543,11 @@ function runHarnessCleanup(options = {}) {
|
|
|
10165
10543
|
now: paths.now
|
|
10166
10544
|
})) {
|
|
10167
10545
|
if (atSweepCap()) break;
|
|
10168
|
-
const resolved =
|
|
10546
|
+
const resolved = path52.resolve(raw.path);
|
|
10169
10547
|
if (processedPaths.has(resolved)) continue;
|
|
10170
10548
|
processedPaths.add(resolved);
|
|
10171
10549
|
const candidate = { ...raw, path: resolved };
|
|
10172
|
-
const runId = candidate.runId ??
|
|
10550
|
+
const runId = candidate.runId ?? path52.basename(resolved);
|
|
10173
10551
|
const dirSkip = skipRunDirectoryRemoval({
|
|
10174
10552
|
harnessRoot,
|
|
10175
10553
|
runId,
|
|
@@ -10305,7 +10683,7 @@ function isPipelineCleanupEnabled() {
|
|
|
10305
10683
|
// src/installed-package-versions.ts
|
|
10306
10684
|
import { readFile } from "node:fs/promises";
|
|
10307
10685
|
import { homedir as homedir13 } from "node:os";
|
|
10308
|
-
import
|
|
10686
|
+
import path53 from "node:path";
|
|
10309
10687
|
var MANAGED_PACKAGES = [
|
|
10310
10688
|
"@kynver-app/runtime",
|
|
10311
10689
|
"@kynver-app/openclaw-agent-os",
|
|
@@ -10320,12 +10698,12 @@ function unique(values) {
|
|
|
10320
10698
|
}
|
|
10321
10699
|
function moduleRoots() {
|
|
10322
10700
|
const home = homedir13();
|
|
10323
|
-
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ??
|
|
10324
|
-
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ?
|
|
10701
|
+
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path53.join(home, ".openclaw", "npm");
|
|
10702
|
+
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path53.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path53.join(home, ".npm-global", "lib", "node_modules"));
|
|
10325
10703
|
return unique([
|
|
10326
|
-
|
|
10327
|
-
|
|
10328
|
-
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot :
|
|
10704
|
+
path53.join(openClawPrefix, "lib", "node_modules"),
|
|
10705
|
+
path53.join(openClawPrefix, "node_modules"),
|
|
10706
|
+
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path53.join(npmGlobalRoot, "lib", "node_modules")
|
|
10329
10707
|
]);
|
|
10330
10708
|
}
|
|
10331
10709
|
async function readVersion(packageJsonPath) {
|
|
@@ -10341,7 +10719,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
|
|
|
10341
10719
|
const out = {};
|
|
10342
10720
|
for (const packageName of MANAGED_PACKAGES) {
|
|
10343
10721
|
for (const root of roots) {
|
|
10344
|
-
const packageJsonPath =
|
|
10722
|
+
const packageJsonPath = path53.join(root, packageName, "package.json");
|
|
10345
10723
|
const version = await readVersion(packageJsonPath);
|
|
10346
10724
|
if (!version) continue;
|
|
10347
10725
|
out[packageName] = { version, observedAt, path: packageJsonPath };
|
|
@@ -10357,7 +10735,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
10357
10735
|
const outcomes = [];
|
|
10358
10736
|
for (const name of Object.keys(run.workers || {})) {
|
|
10359
10737
|
const worker = readJson(
|
|
10360
|
-
|
|
10738
|
+
path54.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
10361
10739
|
void 0
|
|
10362
10740
|
);
|
|
10363
10741
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -10396,7 +10774,10 @@ async function postOperatorTick(agentOsId, runId, resourceGate, args, harnessCle
|
|
|
10396
10774
|
ingestHarness: true,
|
|
10397
10775
|
harnessBoardSnapshot: buildRunBoard(runId),
|
|
10398
10776
|
resourceGate,
|
|
10399
|
-
boxResourceSnapshot: buildBoxResourceSnapshotFromGate(resourceGate, {
|
|
10777
|
+
boxResourceSnapshot: buildBoxResourceSnapshotFromGate(resourceGate, {
|
|
10778
|
+
harnessRunId: runId,
|
|
10779
|
+
boxKind: resolveBoxKindFromConfig(loadUserConfig())
|
|
10780
|
+
}),
|
|
10400
10781
|
packageVersions,
|
|
10401
10782
|
...harnessCleanup ? { harnessCleanup } : {},
|
|
10402
10783
|
runnerPresence: resolveRunnerPresencePayload({ runId }),
|
|
@@ -10514,7 +10895,30 @@ async function runDaemon(args) {
|
|
|
10514
10895
|
process.on("SIGTERM", () => {
|
|
10515
10896
|
stopping = true;
|
|
10516
10897
|
});
|
|
10517
|
-
|
|
10898
|
+
const identity = validateDaemonInstallIdentity(loadUserConfig());
|
|
10899
|
+
if (!identity.ok) {
|
|
10900
|
+
console.error(
|
|
10901
|
+
JSON.stringify({
|
|
10902
|
+
event: "daemon_start_blocked",
|
|
10903
|
+
runId,
|
|
10904
|
+
agentOsId,
|
|
10905
|
+
errors: identity.errors
|
|
10906
|
+
})
|
|
10907
|
+
);
|
|
10908
|
+
process.exit(1);
|
|
10909
|
+
}
|
|
10910
|
+
console.error(
|
|
10911
|
+
JSON.stringify({
|
|
10912
|
+
event: "daemon_start",
|
|
10913
|
+
runId,
|
|
10914
|
+
agentOsId,
|
|
10915
|
+
execute,
|
|
10916
|
+
intervalMs,
|
|
10917
|
+
boxKind: identity.box.boxKind,
|
|
10918
|
+
workerCapSource: identity.workerCapSource,
|
|
10919
|
+
maxConcurrentWorkers: identity.maxConcurrentWorkers
|
|
10920
|
+
})
|
|
10921
|
+
);
|
|
10518
10922
|
const cronEnv = resolveKynverCronEnv();
|
|
10519
10923
|
while (!stopping) {
|
|
10520
10924
|
try {
|
|
@@ -10545,7 +10949,7 @@ async function runDaemon(args) {
|
|
|
10545
10949
|
}
|
|
10546
10950
|
|
|
10547
10951
|
// src/plan-progress.ts
|
|
10548
|
-
import
|
|
10952
|
+
import path56 from "node:path";
|
|
10549
10953
|
|
|
10550
10954
|
// src/bounded-build/constants.ts
|
|
10551
10955
|
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
@@ -10583,7 +10987,7 @@ function formatNodeOptionsFlag(mb = resolveNodeOldSpaceSizeMb()) {
|
|
|
10583
10987
|
}
|
|
10584
10988
|
|
|
10585
10989
|
// src/bounded-build/systemd-wrap.ts
|
|
10586
|
-
import { spawnSync as
|
|
10990
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
10587
10991
|
var systemdAvailableCache;
|
|
10588
10992
|
function isSystemdRunAvailable() {
|
|
10589
10993
|
if (process.env.KYNVER_BUILD_SKIP_SYSTEMD === "1" || process.env.KYNVER_BUILD_SKIP_SYSTEMD === "true") {
|
|
@@ -10594,7 +10998,7 @@ function isSystemdRunAvailable() {
|
|
|
10594
10998
|
systemdAvailableCache = false;
|
|
10595
10999
|
return false;
|
|
10596
11000
|
}
|
|
10597
|
-
const res =
|
|
11001
|
+
const res = spawnSync5("systemd-run", ["--version"], { encoding: "utf8", stdio: ["ignore", "ignore", "pipe"] });
|
|
10598
11002
|
systemdAvailableCache = res.status === 0;
|
|
10599
11003
|
return systemdAvailableCache;
|
|
10600
11004
|
}
|
|
@@ -10618,18 +11022,18 @@ function buildSystemdRunArgv(opts) {
|
|
|
10618
11022
|
}
|
|
10619
11023
|
|
|
10620
11024
|
// src/bounded-build/admission.ts
|
|
10621
|
-
import { spawnSync as
|
|
10622
|
-
function
|
|
11025
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
11026
|
+
function positiveInt4(value, fallback) {
|
|
10623
11027
|
const n = Number(value);
|
|
10624
11028
|
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
10625
11029
|
return Math.floor(n);
|
|
10626
11030
|
}
|
|
10627
11031
|
function resolveBuildAdmissionConfig(config = loadUserConfig()) {
|
|
10628
|
-
const envBudget = process.env.KYNVER_BUILD_MEM_BUDGET_BYTES ?
|
|
10629
|
-
const envReserve = process.env.KYNVER_BUILD_MEM_RESERVE_BYTES ?
|
|
11032
|
+
const envBudget = process.env.KYNVER_BUILD_MEM_BUDGET_BYTES ? positiveInt4(process.env.KYNVER_BUILD_MEM_BUDGET_BYTES, DEFAULT_BUILD_MEM_BUDGET_BYTES) : void 0;
|
|
11033
|
+
const envReserve = process.env.KYNVER_BUILD_MEM_RESERVE_BYTES ? positiveInt4(process.env.KYNVER_BUILD_MEM_RESERVE_BYTES, DEFAULT_BUILD_MEM_RESERVE_BYTES) : void 0;
|
|
10630
11034
|
return {
|
|
10631
|
-
perBuildBudgetBytes: envBudget ??
|
|
10632
|
-
reserveBytes: envReserve ??
|
|
11035
|
+
perBuildBudgetBytes: envBudget ?? positiveInt4(config.perWorkerMemBytes, DEFAULT_BUILD_MEM_BUDGET_BYTES),
|
|
11036
|
+
reserveBytes: envReserve ?? positiveInt4(config.memReserveBytes, DEFAULT_BUILD_MEM_RESERVE_BYTES)
|
|
10633
11037
|
};
|
|
10634
11038
|
}
|
|
10635
11039
|
var activeBuilds = 0;
|
|
@@ -10654,7 +11058,7 @@ function assessBuildAdmission(opts = {}) {
|
|
|
10654
11058
|
}
|
|
10655
11059
|
function sleepMs2(ms) {
|
|
10656
11060
|
if (ms <= 0) return;
|
|
10657
|
-
|
|
11061
|
+
spawnSync6(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
|
|
10658
11062
|
stdio: "ignore"
|
|
10659
11063
|
});
|
|
10660
11064
|
}
|
|
@@ -10675,7 +11079,28 @@ function waitForBuildAdmission(timeoutMs, pollMs = 2e3, opts = {}) {
|
|
|
10675
11079
|
}
|
|
10676
11080
|
|
|
10677
11081
|
// src/bounded-build/exec.ts
|
|
10678
|
-
import { spawnSync as
|
|
11082
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
11083
|
+
|
|
11084
|
+
// src/harness-worktree-build-guard.ts
|
|
11085
|
+
import path55 from "node:path";
|
|
11086
|
+
function isPathUnderHarnessWorktree(cwd) {
|
|
11087
|
+
const worktreesDir = harnessWorktreesDir(resolveHarnessRoot());
|
|
11088
|
+
const rel = path55.relative(worktreesDir, path55.resolve(cwd));
|
|
11089
|
+
return rel.length > 0 && !rel.startsWith("..") && !path55.isAbsolute(rel);
|
|
11090
|
+
}
|
|
11091
|
+
function assessHarnessWorktreeBuildGuard(cwd) {
|
|
11092
|
+
if (!isPathUnderHarnessWorktree(cwd)) return { ok: true };
|
|
11093
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : null;
|
|
11094
|
+
if (uid === 0) {
|
|
11095
|
+
return {
|
|
11096
|
+
ok: false,
|
|
11097
|
+
reason: "Refusing build/install as root inside a harness worktree \u2014 generated caches become root-owned and block daemon cleanup. Run kynver daemon and workers as the harness owner (never sudo kynver)."
|
|
11098
|
+
};
|
|
11099
|
+
}
|
|
11100
|
+
return { ok: true };
|
|
11101
|
+
}
|
|
11102
|
+
|
|
11103
|
+
// src/bounded-build/exec.ts
|
|
10679
11104
|
function envArgv(env) {
|
|
10680
11105
|
const out = [];
|
|
10681
11106
|
for (const [key, value] of Object.entries(env)) {
|
|
@@ -10685,7 +11110,7 @@ function envArgv(env) {
|
|
|
10685
11110
|
return out;
|
|
10686
11111
|
}
|
|
10687
11112
|
function runSpawn(argv, opts) {
|
|
10688
|
-
const res =
|
|
11113
|
+
const res = spawnSync7(argv[0], argv.slice(1), {
|
|
10689
11114
|
cwd: opts.cwd,
|
|
10690
11115
|
env: opts.env,
|
|
10691
11116
|
encoding: "utf8",
|
|
@@ -10715,6 +11140,20 @@ function runBoundedBuildCheck(input) {
|
|
|
10715
11140
|
command: input.command
|
|
10716
11141
|
};
|
|
10717
11142
|
}
|
|
11143
|
+
const worktreeGuard = assessHarnessWorktreeBuildGuard(input.cwd);
|
|
11144
|
+
if (!worktreeGuard.ok) {
|
|
11145
|
+
return {
|
|
11146
|
+
ok: false,
|
|
11147
|
+
exitCode: 1,
|
|
11148
|
+
stdout: "",
|
|
11149
|
+
stderr: worktreeGuard.reason,
|
|
11150
|
+
admitted: true,
|
|
11151
|
+
wrappedWithSystemd: false,
|
|
11152
|
+
nodeOptionsFlag: formatNodeOptionsFlag(),
|
|
11153
|
+
admission,
|
|
11154
|
+
command: input.command
|
|
11155
|
+
};
|
|
11156
|
+
}
|
|
10718
11157
|
const env = mergeNodeOptionsForBuildCheck({ ...process.env, ...input.env });
|
|
10719
11158
|
const nodeOptionsFlag = formatNodeOptionsFlag();
|
|
10720
11159
|
const useSystemd = isSystemdRunAvailable();
|
|
@@ -10832,7 +11271,7 @@ async function emitPlanProgress(args) {
|
|
|
10832
11271
|
}
|
|
10833
11272
|
function verifyPlanLocal(args) {
|
|
10834
11273
|
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
10835
|
-
const cwd =
|
|
11274
|
+
const cwd = path56.resolve(worktree);
|
|
10836
11275
|
const summary = runHarnessVerifyCommands(cwd);
|
|
10837
11276
|
const emitJson = args.json === true || args.json === "true";
|
|
10838
11277
|
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
@@ -10881,9 +11320,9 @@ async function verifyPlan(args) {
|
|
|
10881
11320
|
}
|
|
10882
11321
|
|
|
10883
11322
|
// src/harness-verify-cli.ts
|
|
10884
|
-
import
|
|
11323
|
+
import path57 from "node:path";
|
|
10885
11324
|
function runHarnessVerifyCli(args) {
|
|
10886
|
-
const cwd =
|
|
11325
|
+
const cwd = path57.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
10887
11326
|
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
10888
11327
|
const commands = [];
|
|
10889
11328
|
const rawCmd = args.command;
|
|
@@ -11090,7 +11529,7 @@ function formatMonitorTickNotice(tick) {
|
|
|
11090
11529
|
}
|
|
11091
11530
|
|
|
11092
11531
|
// src/monitor/monitor.service.ts
|
|
11093
|
-
import
|
|
11532
|
+
import path59 from "node:path";
|
|
11094
11533
|
|
|
11095
11534
|
// src/monitor/monitor.classify.ts
|
|
11096
11535
|
function classifyWorkerHealth(input) {
|
|
@@ -11142,11 +11581,11 @@ function classifyWorkerHealth(input) {
|
|
|
11142
11581
|
}
|
|
11143
11582
|
|
|
11144
11583
|
// src/monitor/monitor.store.ts
|
|
11145
|
-
import { existsSync as
|
|
11146
|
-
import
|
|
11584
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync7, readdirSync as readdirSync15, unlinkSync as unlinkSync4 } from "node:fs";
|
|
11585
|
+
import path58 from "node:path";
|
|
11147
11586
|
function monitorsDir() {
|
|
11148
11587
|
const { harnessRoot } = getHarnessPaths();
|
|
11149
|
-
const dir =
|
|
11588
|
+
const dir = path58.join(harnessRoot, "monitors");
|
|
11150
11589
|
mkdirSync7(dir, { recursive: true });
|
|
11151
11590
|
return dir;
|
|
11152
11591
|
}
|
|
@@ -11154,7 +11593,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
11154
11593
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
11155
11594
|
}
|
|
11156
11595
|
function monitorPath(monitorId) {
|
|
11157
|
-
return
|
|
11596
|
+
return path58.join(monitorsDir(), `${monitorId}.json`);
|
|
11158
11597
|
}
|
|
11159
11598
|
function loadMonitorSession(monitorId) {
|
|
11160
11599
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -11164,18 +11603,18 @@ function saveMonitorSession(session) {
|
|
|
11164
11603
|
}
|
|
11165
11604
|
function deleteMonitorSession(monitorId) {
|
|
11166
11605
|
const file = monitorPath(monitorId);
|
|
11167
|
-
if (!
|
|
11606
|
+
if (!existsSync40(file)) return false;
|
|
11168
11607
|
unlinkSync4(file);
|
|
11169
11608
|
return true;
|
|
11170
11609
|
}
|
|
11171
11610
|
function listMonitorSessions() {
|
|
11172
11611
|
const dir = monitorsDir();
|
|
11173
|
-
if (!
|
|
11612
|
+
if (!existsSync40(dir)) return [];
|
|
11174
11613
|
const entries = [];
|
|
11175
|
-
for (const name of
|
|
11614
|
+
for (const name of readdirSync15(dir)) {
|
|
11176
11615
|
if (!name.endsWith(".json")) continue;
|
|
11177
11616
|
const session = readJson(
|
|
11178
|
-
|
|
11617
|
+
path58.join(dir, name),
|
|
11179
11618
|
void 0
|
|
11180
11619
|
);
|
|
11181
11620
|
if (!session?.monitorId) continue;
|
|
@@ -11266,7 +11705,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
11266
11705
|
// src/monitor/monitor.service.ts
|
|
11267
11706
|
function workerRecord2(runId, name) {
|
|
11268
11707
|
return readJson(
|
|
11269
|
-
|
|
11708
|
+
path59.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
11270
11709
|
void 0
|
|
11271
11710
|
);
|
|
11272
11711
|
}
|
|
@@ -11472,18 +11911,18 @@ async function runMonitorLoop(args) {
|
|
|
11472
11911
|
|
|
11473
11912
|
// src/monitor/monitor-spawn.ts
|
|
11474
11913
|
import { spawn as spawn6 } from "node:child_process";
|
|
11475
|
-
import { closeSync as closeSync7, existsSync as
|
|
11476
|
-
import
|
|
11914
|
+
import { closeSync as closeSync7, existsSync as existsSync41, openSync as openSync7 } from "node:fs";
|
|
11915
|
+
import path60 from "node:path";
|
|
11477
11916
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
11478
11917
|
function resolveDefaultCliPath2() {
|
|
11479
|
-
return
|
|
11918
|
+
return path60.join(fileURLToPath3(new URL(".", import.meta.url)), "cli.js");
|
|
11480
11919
|
}
|
|
11481
11920
|
function spawnMonitorSidecar(opts) {
|
|
11482
11921
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
11483
|
-
if (!
|
|
11922
|
+
if (!existsSync41(cliPath)) return void 0;
|
|
11484
11923
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
11485
11924
|
const { harnessRoot } = getHarnessPaths();
|
|
11486
|
-
const logPath =
|
|
11925
|
+
const logPath = path60.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
11487
11926
|
let logFd;
|
|
11488
11927
|
try {
|
|
11489
11928
|
logFd = openSync7(logPath, "a");
|
|
@@ -11603,13 +12042,13 @@ async function monitorTickCli(args) {
|
|
|
11603
12042
|
}
|
|
11604
12043
|
|
|
11605
12044
|
// src/package-version.ts
|
|
11606
|
-
import { existsSync as
|
|
12045
|
+
import { existsSync as existsSync42, readFileSync as readFileSync13 } from "node:fs";
|
|
11607
12046
|
import { dirname, join } from "node:path";
|
|
11608
12047
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
11609
12048
|
function resolvePackageRoot(moduleUrl) {
|
|
11610
12049
|
let dir = dirname(fileURLToPath4(moduleUrl));
|
|
11611
12050
|
for (let depth = 0; depth < 6; depth += 1) {
|
|
11612
|
-
if (
|
|
12051
|
+
if (existsSync42(join(dir, "package.json"))) return dir;
|
|
11613
12052
|
const parent = dirname(dir);
|
|
11614
12053
|
if (parent === dir) break;
|
|
11615
12054
|
dir = parent;
|
|
@@ -11639,8 +12078,8 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
|
|
|
11639
12078
|
}
|
|
11640
12079
|
|
|
11641
12080
|
// src/memory-cost-package-version-guard.ts
|
|
11642
|
-
import { existsSync as
|
|
11643
|
-
import
|
|
12081
|
+
import { existsSync as existsSync43, readFileSync as readFileSync14 } from "node:fs";
|
|
12082
|
+
import path61 from "node:path";
|
|
11644
12083
|
var MEMORY_COST_PACKAGE_MIN_VERSIONS = {
|
|
11645
12084
|
"@kynver-app/runtime": "0.1.83",
|
|
11646
12085
|
"@kynver-app/openclaw-agent-os": "0.1.43",
|
|
@@ -11702,8 +12141,8 @@ function resolveRepoRoot(cwd, explicitRepoRoot) {
|
|
|
11702
12141
|
(value) => Boolean(value?.trim())
|
|
11703
12142
|
);
|
|
11704
12143
|
for (const candidate of candidates) {
|
|
11705
|
-
const resolved =
|
|
11706
|
-
if (
|
|
12144
|
+
const resolved = path61.resolve(candidate);
|
|
12145
|
+
if (existsSync43(path61.join(resolved, "packages/kynver-runtime/package.json")) && existsSync43(path61.join(resolved, "package.json"))) {
|
|
11707
12146
|
return resolved;
|
|
11708
12147
|
}
|
|
11709
12148
|
}
|
|
@@ -11715,7 +12154,7 @@ function probeRepoPackageVersions(input = {}) {
|
|
|
11715
12154
|
if (!repoRoot) return {};
|
|
11716
12155
|
const out = {};
|
|
11717
12156
|
for (const packageName of MEMORY_COST_MANAGED_PACKAGES) {
|
|
11718
|
-
const packageJsonPath =
|
|
12157
|
+
const packageJsonPath = path61.join(repoRoot, REPO_PACKAGE_JSON_RELATIVE[packageName]);
|
|
11719
12158
|
const version = readPackageJsonVersion(packageJsonPath);
|
|
11720
12159
|
if (!version) continue;
|
|
11721
12160
|
out[packageName] = { version, source: "repo", path: packageJsonPath };
|
|
@@ -11867,7 +12306,7 @@ function shouldEnforceMemoryCostPackageGuardCli(scope, action) {
|
|
|
11867
12306
|
}
|
|
11868
12307
|
|
|
11869
12308
|
// src/post-restart-unblock.ts
|
|
11870
|
-
import
|
|
12309
|
+
import path62 from "node:path";
|
|
11871
12310
|
function skip(runId, worker, taskId, agentOsId, leaseOwner, reason) {
|
|
11872
12311
|
return { runId, worker, taskId, agentOsId, leaseOwner, action: "skipped", reason };
|
|
11873
12312
|
}
|
|
@@ -11880,7 +12319,7 @@ async function postRestartUnblock(args) {
|
|
|
11880
12319
|
const errors = [];
|
|
11881
12320
|
for (const run of listRunRecords()) {
|
|
11882
12321
|
for (const name of Object.keys(run.workers ?? {})) {
|
|
11883
|
-
const workerPath =
|
|
12322
|
+
const workerPath = path62.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
11884
12323
|
const worker = readJson(workerPath, void 0);
|
|
11885
12324
|
if (!worker) {
|
|
11886
12325
|
skipped.push(skip(run.id, name, "", "", "", "worker.json missing"));
|
|
@@ -11992,9 +12431,9 @@ async function postRestartUnblockCli(args) {
|
|
|
11992
12431
|
}
|
|
11993
12432
|
|
|
11994
12433
|
// src/default-repo-cli.ts
|
|
11995
|
-
import
|
|
12434
|
+
import path63 from "node:path";
|
|
11996
12435
|
import { homedir as homedir14 } from "node:os";
|
|
11997
|
-
var CONFIG_FILE2 =
|
|
12436
|
+
var CONFIG_FILE2 = path63.join(homedir14(), ".kynver", "config.json");
|
|
11998
12437
|
function ensureDefaultRepo(opts) {
|
|
11999
12438
|
const existing = loadUserConfig();
|
|
12000
12439
|
const resolved = resolveDefaultRepo({ ...opts, config: existing });
|
|
@@ -12075,16 +12514,16 @@ function summarizeResolvedDefaultRepo(resolved) {
|
|
|
12075
12514
|
}
|
|
12076
12515
|
|
|
12077
12516
|
// src/doctor/runtime-takeover.ts
|
|
12078
|
-
import
|
|
12517
|
+
import path65 from "node:path";
|
|
12079
12518
|
|
|
12080
12519
|
// src/doctor/runtime-takeover.probes.ts
|
|
12081
|
-
import { accessSync, constants, existsSync as
|
|
12520
|
+
import { accessSync, constants, existsSync as existsSync44, readFileSync as readFileSync15 } from "node:fs";
|
|
12082
12521
|
import { homedir as homedir15 } from "node:os";
|
|
12083
|
-
import
|
|
12084
|
-
import { spawnSync as
|
|
12522
|
+
import path64 from "node:path";
|
|
12523
|
+
import { spawnSync as spawnSync8 } from "node:child_process";
|
|
12085
12524
|
function captureCommand(bin, args) {
|
|
12086
12525
|
try {
|
|
12087
|
-
const res =
|
|
12526
|
+
const res = spawnSync8(bin, args, { encoding: "utf8" });
|
|
12088
12527
|
const stdout = (res.stdout || "").trim();
|
|
12089
12528
|
const stderr = (res.stderr || "").trim();
|
|
12090
12529
|
const ok = res.status === 0;
|
|
@@ -12109,7 +12548,7 @@ function tokenPrefix(token) {
|
|
|
12109
12548
|
return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
|
|
12110
12549
|
}
|
|
12111
12550
|
function isWritable(target) {
|
|
12112
|
-
if (!
|
|
12551
|
+
if (!existsSync44(target)) return false;
|
|
12113
12552
|
try {
|
|
12114
12553
|
accessSync(target, constants.W_OK);
|
|
12115
12554
|
return true;
|
|
@@ -12122,11 +12561,11 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
12122
12561
|
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
12123
12562
|
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
12124
12563
|
loadConfig: () => loadUserConfig(),
|
|
12125
|
-
configFilePath: () =>
|
|
12126
|
-
credentialsFilePath: () =>
|
|
12564
|
+
configFilePath: () => path64.join(homedir15(), ".kynver", "config.json"),
|
|
12565
|
+
credentialsFilePath: () => path64.join(homedir15(), ".kynver", "credentials"),
|
|
12127
12566
|
readCredentials: () => {
|
|
12128
|
-
const credPath =
|
|
12129
|
-
if (!
|
|
12567
|
+
const credPath = path64.join(homedir15(), ".kynver", "credentials");
|
|
12568
|
+
if (!existsSync44(credPath)) {
|
|
12130
12569
|
return { hasApiKey: false };
|
|
12131
12570
|
}
|
|
12132
12571
|
try {
|
|
@@ -12160,8 +12599,8 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
12160
12599
|
})()
|
|
12161
12600
|
}),
|
|
12162
12601
|
harnessRoot: () => resolveHarnessRoot(),
|
|
12163
|
-
legacyOpenclawHarnessRoot: () =>
|
|
12164
|
-
pathExists: (target) =>
|
|
12602
|
+
legacyOpenclawHarnessRoot: () => path64.join(homedir15(), ".openclaw", "harness"),
|
|
12603
|
+
pathExists: (target) => existsSync44(target),
|
|
12165
12604
|
pathWritable: (target) => isWritable(target),
|
|
12166
12605
|
vercelVersion: () => captureCommand("vercel", ["--version"]),
|
|
12167
12606
|
vercelWhoami: () => captureCommand("vercel", ["whoami"])
|
|
@@ -12560,8 +12999,8 @@ function assessVercelCli(probes) {
|
|
|
12560
12999
|
}
|
|
12561
13000
|
function assessHarnessDirs(probes) {
|
|
12562
13001
|
const harnessRoot = probes.harnessRoot();
|
|
12563
|
-
const runsDir =
|
|
12564
|
-
const worktreesDir =
|
|
13002
|
+
const runsDir = path65.join(harnessRoot, "runs");
|
|
13003
|
+
const worktreesDir = path65.join(harnessRoot, "worktrees");
|
|
12565
13004
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
12566
13005
|
const displayRunsDir = redactHomePath(runsDir);
|
|
12567
13006
|
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
@@ -12825,9 +13264,9 @@ function applySchedulerCutoverAttestation(config) {
|
|
|
12825
13264
|
}
|
|
12826
13265
|
|
|
12827
13266
|
// src/scheduler-cutover-cli.ts
|
|
12828
|
-
import
|
|
13267
|
+
import path66 from "node:path";
|
|
12829
13268
|
import { homedir as homedir16 } from "node:os";
|
|
12830
|
-
var CONFIG_FILE3 =
|
|
13269
|
+
var CONFIG_FILE3 = path66.join(homedir16(), ".kynver", "config.json");
|
|
12831
13270
|
function runSchedulerCutoverCheckCli(json = false) {
|
|
12832
13271
|
const config = loadUserConfig();
|
|
12833
13272
|
const report = assessSchedulerCutover(config);
|
|
@@ -12980,7 +13419,7 @@ function usage(code = 0) {
|
|
|
12980
13419
|
"Usage:",
|
|
12981
13420
|
" kynver login --api-key KEY",
|
|
12982
13421
|
" kynver runner credential [--agent-os-id ID] [--base-url URL]",
|
|
12983
|
-
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--discover-repo] [--max-workers N] [--provider claude|cursor]",
|
|
13422
|
+
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--box-kind forge|ghost] [--repo PATH] [--discover-repo] [--max-workers N] [--provider claude|cursor]",
|
|
12984
13423
|
" kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS]",
|
|
12985
13424
|
" kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
|
|
12986
13425
|
" kynver run list",
|