@kynver-app/runtime 0.1.39 → 0.1.47
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/README.md +15 -0
- package/dist/auto-complete.d.ts +60 -0
- package/dist/bounded-build/admission.d.ts +30 -0
- package/dist/bounded-build/constants.d.ts +10 -0
- package/dist/bounded-build/exec.d.ts +26 -0
- package/dist/bounded-build/index.d.ts +6 -0
- package/dist/bounded-build/meminfo.d.ts +5 -0
- package/dist/bounded-build/node-options.d.ts +9 -0
- package/dist/bounded-build/systemd-wrap.d.ts +17 -0
- package/dist/callback-headers.d.ts +2 -0
- package/dist/callbacks.d.ts +38 -0
- package/dist/cleanup-cli.d.ts +1 -0
- package/dist/cleanup-dir-size.d.ts +4 -0
- package/dist/cleanup-execute.d.ts +4 -0
- package/dist/cleanup-guards.d.ts +18 -0
- package/dist/cleanup-scan.d.ts +14 -0
- package/dist/cleanup-types.d.ts +56 -0
- package/dist/cleanup-worktree-index.d.ts +13 -0
- package/dist/cleanup.d.ts +5 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +1398 -199
- package/dist/cli.js.map +4 -4
- package/dist/command-center-contract-cli.d.ts +1 -0
- package/dist/completion-ack.d.ts +10 -0
- package/dist/completion-response.d.ts +15 -0
- package/dist/config.d.ts +61 -0
- package/dist/daemon.d.ts +5 -0
- package/dist/disk-gate.d.ts +9 -0
- package/dist/dispatch.d.ts +16 -0
- package/dist/doctor/doctor.types.d.ts +25 -0
- package/dist/doctor/index.d.ts +4 -0
- package/dist/doctor/runtime-takeover-cli.d.ts +1 -0
- package/dist/doctor/runtime-takeover.d.ts +3 -0
- package/dist/doctor/runtime-takeover.probes.d.ts +37 -0
- package/dist/exit-classify.d.ts +12 -0
- package/dist/exited-salvage.d.ts +22 -0
- package/dist/finalize.d.ts +16 -0
- package/dist/fortress-engagement-gate.d.ts +5 -0
- package/dist/git.d.ts +37 -0
- package/dist/github-repo.d.ts +5 -0
- package/dist/harness-verify-cli.d.ts +1 -0
- package/dist/harness-verify.d.ts +19 -0
- package/dist/heartbeat.d.ts +22 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +1733 -202
- package/dist/index.js.map +4 -4
- package/dist/installed-package-versions.d.ts +6 -0
- package/dist/landing-contract-gate.d.ts +24 -0
- package/dist/landing-gate.d.ts +24 -0
- package/dist/lease-renewal.d.ts +15 -0
- package/dist/model-routing-task-enrich.d.ts +8 -0
- package/dist/model-routing.d.ts +29 -0
- package/dist/monitor/index.d.ts +7 -0
- package/dist/monitor/monitor-cli.d.ts +9 -0
- package/dist/monitor/monitor-loop.d.ts +1 -0
- package/dist/monitor/monitor-spawn.d.ts +18 -0
- package/dist/monitor/monitor.classify.d.ts +15 -0
- package/dist/monitor/monitor.service.d.ts +17 -0
- package/dist/monitor/monitor.store.d.ts +6 -0
- package/dist/monitor/monitor.task-lease.d.ts +11 -0
- package/dist/monitor/monitor.terminal.d.ts +11 -0
- package/dist/monitor/monitor.types.d.ts +74 -0
- package/dist/package-version.d.ts +5 -0
- package/dist/path-values.d.ts +5 -0
- package/dist/paths.d.ts +7 -0
- package/dist/pipeline-dispatch.d.ts +7 -0
- package/dist/pipeline-tick.d.ts +45 -0
- package/dist/plan-persist/agentos-api.d.ts +28 -0
- package/dist/plan-persist/body-hash.d.ts +3 -0
- package/dist/plan-persist/drain.d.ts +7 -0
- package/dist/plan-persist/errors.d.ts +9 -0
- package/dist/plan-persist/handoff.d.ts +7 -0
- package/dist/plan-persist/idempotency.d.ts +2 -0
- package/dist/plan-persist/index.d.ts +8 -0
- package/dist/plan-persist/outbox-store.d.ts +18 -0
- package/dist/plan-persist/paths.d.ts +8 -0
- package/dist/plan-persist/persist.d.ts +10 -0
- package/dist/plan-persist/readback.d.ts +17 -0
- package/dist/plan-persist/types.d.ts +91 -0
- package/dist/plan-persist-cli.d.ts +3 -0
- package/dist/plan-progress-daemon-sync.d.ts +7 -0
- package/dist/plan-progress-sync.d.ts +21 -0
- package/dist/plan-progress.d.ts +10 -0
- package/dist/pr-handoff/index.d.ts +4 -0
- package/dist/pr-handoff/pr-handoff-assess.d.ts +26 -0
- package/dist/pr-handoff/pr-handoff-gh.d.ts +44 -0
- package/dist/pr-handoff/pr-handoff.d.ts +8 -0
- package/dist/pr-handoff/pr-handoff.types.d.ts +45 -0
- package/dist/prompt.d.ts +14 -0
- package/dist/providers/claude.d.ts +4 -0
- package/dist/providers/cursor-windows.d.ts +7 -0
- package/dist/providers/cursor.d.ts +11 -0
- package/dist/providers/model-preflight.d.ts +31 -0
- package/dist/providers/registry.d.ts +4 -0
- package/dist/providers/types.d.ts +32 -0
- package/dist/redact.d.ts +1 -0
- package/dist/resource-gate.d.ts +53 -0
- package/dist/retry-limits.d.ts +8 -0
- package/dist/run-store.d.ts +30 -0
- package/dist/shell-command-outcome.d.ts +32 -0
- package/dist/stale-reconcile.d.ts +25 -0
- package/dist/status.d.ts +161 -0
- package/dist/stream.d.ts +20 -0
- package/dist/supervisor.d.ts +25 -0
- package/dist/sweep.d.ts +1 -0
- package/dist/util.d.ts +22 -0
- package/dist/validate.d.ts +5 -0
- package/dist/vercel/index.d.ts +3 -0
- package/dist/vercel/vercel-evidence.d.ts +48 -0
- package/dist/vercel/vercel-github-status.d.ts +19 -0
- package/dist/vercel/vercel-url.d.ts +16 -0
- package/dist/worker-env.d.ts +15 -0
- package/dist/worker-lifecycle.d.ts +28 -0
- package/dist/worker-ops.d.ts +20 -0
- package/dist/workspace-runtime-config.d.ts +8 -0
- package/dist/worktree.d.ts +4 -0
- package/package.json +8 -4
package/dist/index.js
CHANGED
|
@@ -35,16 +35,43 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// src/dispatch.ts
|
|
38
|
-
import
|
|
38
|
+
import path17 from "node:path";
|
|
39
39
|
|
|
40
40
|
// src/config.ts
|
|
41
41
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
42
|
+
import { homedir as homedir2 } from "node:os";
|
|
43
|
+
import path3 from "node:path";
|
|
44
|
+
|
|
45
|
+
// src/path-values.ts
|
|
42
46
|
import { homedir } from "node:os";
|
|
43
|
-
import
|
|
47
|
+
import path from "node:path";
|
|
48
|
+
function expandHomePath(value) {
|
|
49
|
+
if (value === "~") return homedir();
|
|
50
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
51
|
+
return path.join(homedir(), value.slice(2));
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
function resolveUserPath(value) {
|
|
56
|
+
return path.resolve(expandHomePath(value));
|
|
57
|
+
}
|
|
58
|
+
function redactHomePath(value) {
|
|
59
|
+
const expanded = expandHomePath(value);
|
|
60
|
+
const resolved = path.resolve(expanded);
|
|
61
|
+
const home = path.resolve(homedir());
|
|
62
|
+
if (resolved === home) return "~";
|
|
63
|
+
if (resolved.startsWith(`${home}${path.sep}`)) {
|
|
64
|
+
return `~/${path.relative(home, resolved).split(path.sep).join("/")}`;
|
|
65
|
+
}
|
|
66
|
+
return resolved.replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^\/Users\/[^/]+(?=\/|$)/, "~");
|
|
67
|
+
}
|
|
68
|
+
function displayUserPath(value) {
|
|
69
|
+
return redactHomePath(value);
|
|
70
|
+
}
|
|
44
71
|
|
|
45
72
|
// src/util.ts
|
|
46
73
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
47
|
-
import
|
|
74
|
+
import path2 from "node:path";
|
|
48
75
|
function fail(message) {
|
|
49
76
|
console.error(message);
|
|
50
77
|
process.exit(1);
|
|
@@ -73,7 +100,7 @@ function readJson(file, fallback) {
|
|
|
73
100
|
}
|
|
74
101
|
}
|
|
75
102
|
function writeJson(file, value) {
|
|
76
|
-
mkdirSync(
|
|
103
|
+
mkdirSync(path2.dirname(file), { recursive: true });
|
|
77
104
|
writeFileSync(file, `${JSON.stringify(value, null, 2)}
|
|
78
105
|
`);
|
|
79
106
|
}
|
|
@@ -109,7 +136,7 @@ function tailFile(file, lines) {
|
|
|
109
136
|
return data.split("\n").slice(-lines).join("\n");
|
|
110
137
|
}
|
|
111
138
|
function readMaybeFile(file) {
|
|
112
|
-
return file ? readFileSync2(
|
|
139
|
+
return file ? readFileSync2(path2.resolve(file), "utf8") : "";
|
|
113
140
|
}
|
|
114
141
|
function listRunIds(runsDir) {
|
|
115
142
|
if (!existsSync2(runsDir)) return [];
|
|
@@ -152,9 +179,9 @@ function secsAgo(ms) {
|
|
|
152
179
|
}
|
|
153
180
|
|
|
154
181
|
// src/config.ts
|
|
155
|
-
var CONFIG_DIR =
|
|
156
|
-
var CONFIG_FILE =
|
|
157
|
-
var CREDENTIALS_FILE =
|
|
182
|
+
var CONFIG_DIR = path3.join(homedir2(), ".kynver");
|
|
183
|
+
var CONFIG_FILE = path3.join(CONFIG_DIR, "config.json");
|
|
184
|
+
var CREDENTIALS_FILE = path3.join(CONFIG_DIR, "credentials");
|
|
158
185
|
function loadUserConfig() {
|
|
159
186
|
if (!existsSync3(CONFIG_FILE)) return {};
|
|
160
187
|
try {
|
|
@@ -165,9 +192,33 @@ function loadUserConfig() {
|
|
|
165
192
|
}
|
|
166
193
|
function saveUserConfig(config) {
|
|
167
194
|
mkdirSync2(CONFIG_DIR, { recursive: true });
|
|
168
|
-
writeFileSync2(CONFIG_FILE, `${JSON.stringify(config, null, 2)}
|
|
195
|
+
writeFileSync2(CONFIG_FILE, `${JSON.stringify(normalizeConfigPaths(config), null, 2)}
|
|
169
196
|
`, { mode: 384 });
|
|
170
197
|
}
|
|
198
|
+
function normalizeConfigPaths(config) {
|
|
199
|
+
return {
|
|
200
|
+
...config,
|
|
201
|
+
...config.harnessRoot?.trim() ? { harnessRoot: redactHomePath(config.harnessRoot.trim()) } : {},
|
|
202
|
+
...config.defaultRepo?.trim() ? { defaultRepo: redactHomePath(config.defaultRepo.trim()) } : {}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function presentUserConfig(config) {
|
|
206
|
+
return normalizeConfigPaths(config);
|
|
207
|
+
}
|
|
208
|
+
function inferSetupFields(existing, args) {
|
|
209
|
+
const creds = loadCredentialsFile();
|
|
210
|
+
const apiBaseUrl = (typeof args.apiBaseUrl === "string" ? args.apiBaseUrl : void 0) || existing.apiBaseUrl?.trim() || process.env.KYNVER_API_URL?.trim() || process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim();
|
|
211
|
+
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);
|
|
212
|
+
const defaultRepo = (typeof args.repo === "string" ? args.repo : void 0) || existing.defaultRepo?.trim() || process.env.KYNVER_DEFAULT_REPO?.trim() || process.env.KYNVER_HARNESS_REPO?.trim();
|
|
213
|
+
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();
|
|
214
|
+
return {
|
|
215
|
+
...apiBaseUrl ? { apiBaseUrl: trimTrailingSlash(apiBaseUrl) } : {},
|
|
216
|
+
...agentOsId ? { agentOsId } : {},
|
|
217
|
+
...defaultRepo ? { defaultRepo } : {},
|
|
218
|
+
...harnessRoot ? { harnessRoot } : {},
|
|
219
|
+
...typeof args.agentOsSlug === "string" ? { agentOsSlug: args.agentOsSlug } : existing.agentOsSlug ? { agentOsSlug: existing.agentOsSlug } : {}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
171
222
|
function loadCredentialsFile() {
|
|
172
223
|
if (!existsSync3(CREDENTIALS_FILE)) return {};
|
|
173
224
|
try {
|
|
@@ -320,7 +371,7 @@ async function mintRunnerCredential(args) {
|
|
|
320
371
|
{
|
|
321
372
|
ok: true,
|
|
322
373
|
agentOsId,
|
|
323
|
-
credentialsPath: CREDENTIALS_FILE,
|
|
374
|
+
credentialsPath: displayUserPath(CREDENTIALS_FILE),
|
|
324
375
|
tokenPrefix: `${token.slice(0, 12)}\u2026`,
|
|
325
376
|
note: "Scoped runner token saved; callbacks use X-Kynver-Runner-Token."
|
|
326
377
|
},
|
|
@@ -355,16 +406,12 @@ function parseArgs(argv) {
|
|
|
355
406
|
async function runSetup(args) {
|
|
356
407
|
const existing = loadUserConfig();
|
|
357
408
|
const maxWorkersRaw = typeof args.maxWorkers === "string" ? args.maxWorkers : typeof args.maxConcurrentWorkers === "string" ? args.maxConcurrentWorkers : void 0;
|
|
358
|
-
const config = {
|
|
409
|
+
const config = normalizeConfigPaths({
|
|
359
410
|
...existing,
|
|
360
|
-
...
|
|
361
|
-
...typeof args.agentOsSlug === "string" ? { agentOsSlug: args.agentOsSlug } : {},
|
|
362
|
-
...typeof args.agentOsId === "string" ? { agentOsId: args.agentOsId } : {},
|
|
363
|
-
...typeof args.repo === "string" ? { defaultRepo: args.repo } : {},
|
|
364
|
-
...typeof args.harnessRoot === "string" ? { harnessRoot: args.harnessRoot } : {},
|
|
411
|
+
...inferSetupFields(existing, args),
|
|
365
412
|
...maxWorkersRaw ? { maxConcurrentWorkers: Math.max(1, Math.floor(Number(maxWorkersRaw))) } : {},
|
|
366
413
|
workerProvider: typeof args.provider === "string" ? args.provider : existing.workerProvider || "claude"
|
|
367
|
-
};
|
|
414
|
+
});
|
|
368
415
|
saveUserConfig(config);
|
|
369
416
|
let runnerCredentialNote;
|
|
370
417
|
const apiKey = loadApiKey();
|
|
@@ -385,8 +432,8 @@ async function runSetup(args) {
|
|
|
385
432
|
JSON.stringify(
|
|
386
433
|
{
|
|
387
434
|
ok: true,
|
|
388
|
-
configPath: CONFIG_FILE,
|
|
389
|
-
config,
|
|
435
|
+
configPath: displayUserPath(CONFIG_FILE),
|
|
436
|
+
config: presentUserConfig(config),
|
|
390
437
|
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."
|
|
391
438
|
},
|
|
392
439
|
null,
|
|
@@ -398,7 +445,7 @@ async function runLogin(args) {
|
|
|
398
445
|
const apiKey = typeof args.apiKey === "string" ? args.apiKey : process.env.KYNVER_API_KEY;
|
|
399
446
|
if (!apiKey) failConfig("kynver login requires --api-key or KYNVER_API_KEY");
|
|
400
447
|
saveApiKey(apiKey);
|
|
401
|
-
console.log(JSON.stringify({ ok: true, credentialsPath: CREDENTIALS_FILE }, null, 2));
|
|
448
|
+
console.log(JSON.stringify({ ok: true, credentialsPath: displayUserPath(CREDENTIALS_FILE) }, null, 2));
|
|
402
449
|
}
|
|
403
450
|
|
|
404
451
|
// src/callback-headers.ts
|
|
@@ -461,12 +508,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
|
461
508
|
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
462
509
|
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
463
510
|
function observeRunnerDiskGate(input = {}) {
|
|
464
|
-
const
|
|
511
|
+
const path38 = input.diskPath?.trim() || "/";
|
|
465
512
|
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
466
513
|
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
467
514
|
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
468
515
|
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
469
|
-
const stats = statfsSync(
|
|
516
|
+
const stats = statfsSync(path38);
|
|
470
517
|
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
471
518
|
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
472
519
|
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
@@ -486,7 +533,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
486
533
|
}
|
|
487
534
|
return {
|
|
488
535
|
ok,
|
|
489
|
-
path:
|
|
536
|
+
path: path38,
|
|
490
537
|
freeBytes,
|
|
491
538
|
totalBytes,
|
|
492
539
|
usedPercent,
|
|
@@ -499,23 +546,46 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
499
546
|
}
|
|
500
547
|
|
|
501
548
|
// src/resource-gate.ts
|
|
502
|
-
import
|
|
549
|
+
import os2 from "node:os";
|
|
550
|
+
|
|
551
|
+
// src/bounded-build/meminfo.ts
|
|
552
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
503
553
|
import os from "node:os";
|
|
504
|
-
|
|
554
|
+
function readMemAvailableBytes(meminfoText) {
|
|
555
|
+
if (meminfoText !== void 0) {
|
|
556
|
+
const match = meminfoText.match(/^MemAvailable:\s+(\d+)\s*kB/m);
|
|
557
|
+
if (match) return Number(match[1]) * 1024;
|
|
558
|
+
return os.freemem();
|
|
559
|
+
}
|
|
560
|
+
if (process.platform === "linux") {
|
|
561
|
+
try {
|
|
562
|
+
const meminfo = readFileSync4("/proc/meminfo", "utf8");
|
|
563
|
+
const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m);
|
|
564
|
+
if (match) return Number(match[1]) * 1024;
|
|
565
|
+
} catch {
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return os.freemem();
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// src/resource-gate.ts
|
|
572
|
+
import path6 from "node:path";
|
|
505
573
|
|
|
506
574
|
// src/run-store.ts
|
|
507
575
|
import { existsSync as existsSync5, readdirSync as readdirSync2 } from "node:fs";
|
|
508
|
-
import
|
|
576
|
+
import path5 from "node:path";
|
|
509
577
|
|
|
510
578
|
// src/paths.ts
|
|
511
579
|
import { existsSync as existsSync4 } from "node:fs";
|
|
512
|
-
import { homedir as
|
|
513
|
-
import
|
|
514
|
-
var LEGACY_ROOT =
|
|
580
|
+
import { homedir as homedir3 } from "node:os";
|
|
581
|
+
import path4 from "node:path";
|
|
582
|
+
var LEGACY_ROOT = path4.join(homedir3(), ".openclaw", "harness");
|
|
515
583
|
function resolveHarnessRoot() {
|
|
516
584
|
const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
|
|
517
|
-
if (env) return
|
|
518
|
-
const
|
|
585
|
+
if (env) return resolveUserPath(env);
|
|
586
|
+
const configured = loadUserConfig().harnessRoot?.trim();
|
|
587
|
+
if (configured) return resolveUserPath(configured);
|
|
588
|
+
const kynverRoot = path4.join(homedir3(), ".kynver", "harness");
|
|
519
589
|
if (existsSync4(kynverRoot)) return kynverRoot;
|
|
520
590
|
if (existsSync4(LEGACY_ROOT)) return LEGACY_ROOT;
|
|
521
591
|
return kynverRoot;
|
|
@@ -524,12 +594,12 @@ function getHarnessPaths() {
|
|
|
524
594
|
const harnessRoot = resolveHarnessRoot();
|
|
525
595
|
return {
|
|
526
596
|
harnessRoot,
|
|
527
|
-
runsDir:
|
|
528
|
-
worktreesDir:
|
|
597
|
+
runsDir: path4.join(harnessRoot, "runs"),
|
|
598
|
+
worktreesDir: path4.join(harnessRoot, "worktrees")
|
|
529
599
|
};
|
|
530
600
|
}
|
|
531
601
|
function runDir(runsDir, id) {
|
|
532
|
-
return
|
|
602
|
+
return path4.join(runsDir, safeSlug(id));
|
|
533
603
|
}
|
|
534
604
|
|
|
535
605
|
// src/run-store.ts
|
|
@@ -538,7 +608,7 @@ function getPaths() {
|
|
|
538
608
|
}
|
|
539
609
|
function loadRun(id) {
|
|
540
610
|
const { runsDir } = getPaths();
|
|
541
|
-
return readJson(
|
|
611
|
+
return readJson(path5.join(runDir(runsDir, safeSlug(id)), "run.json"));
|
|
542
612
|
}
|
|
543
613
|
function listRunRecords() {
|
|
544
614
|
const { runsDir } = getPaths();
|
|
@@ -547,7 +617,7 @@ function listRunRecords() {
|
|
|
547
617
|
for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
|
|
548
618
|
if (!entry.isDirectory()) continue;
|
|
549
619
|
const run = readJson(
|
|
550
|
-
|
|
620
|
+
path5.join(runsDir, entry.name, "run.json"),
|
|
551
621
|
void 0
|
|
552
622
|
);
|
|
553
623
|
if (run?.id) runs.push(run);
|
|
@@ -557,16 +627,16 @@ function listRunRecords() {
|
|
|
557
627
|
function loadWorker(runId, name) {
|
|
558
628
|
const { runsDir } = getPaths();
|
|
559
629
|
return readJson(
|
|
560
|
-
|
|
630
|
+
path5.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
|
|
561
631
|
);
|
|
562
632
|
}
|
|
563
633
|
function saveRun(run) {
|
|
564
634
|
const { runsDir } = getPaths();
|
|
565
|
-
writeJson(
|
|
635
|
+
writeJson(path5.join(runDir(runsDir, run.id), "run.json"), run);
|
|
566
636
|
}
|
|
567
637
|
function saveWorker(runId, worker) {
|
|
568
638
|
const { runsDir } = getPaths();
|
|
569
|
-
writeJson(
|
|
639
|
+
writeJson(path5.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
|
|
570
640
|
}
|
|
571
641
|
function runDirectory(id) {
|
|
572
642
|
const { runsDir } = getPaths();
|
|
@@ -574,7 +644,7 @@ function runDirectory(id) {
|
|
|
574
644
|
}
|
|
575
645
|
|
|
576
646
|
// src/heartbeat.ts
|
|
577
|
-
import { existsSync as existsSync6, readFileSync as
|
|
647
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
|
|
578
648
|
var HEARTBEAT_FUTURE_SKEW_MS = 6e4;
|
|
579
649
|
function isTerminalHeartbeatPhase(phase) {
|
|
580
650
|
return phase === "complete";
|
|
@@ -596,7 +666,7 @@ function parseHeartbeat(file) {
|
|
|
596
666
|
if (!existsSync6(file)) return result;
|
|
597
667
|
const maxFutureMs = Date.now() + HEARTBEAT_FUTURE_SKEW_MS;
|
|
598
668
|
const clampedTo = new Date(maxFutureMs).toISOString();
|
|
599
|
-
const lines =
|
|
669
|
+
const lines = readFileSync5(file, "utf8").split("\n").filter(Boolean);
|
|
600
670
|
for (const line of lines) {
|
|
601
671
|
const entry = safeJson(line);
|
|
602
672
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
@@ -623,7 +693,155 @@ function parseHeartbeat(file) {
|
|
|
623
693
|
}
|
|
624
694
|
|
|
625
695
|
// src/stream.ts
|
|
626
|
-
import { existsSync as existsSync7, readFileSync as
|
|
696
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
|
|
697
|
+
|
|
698
|
+
// src/shell-command-outcome.ts
|
|
699
|
+
var NPM_AUDIT_RE = /\bnpm\s+audit\b/i;
|
|
700
|
+
function tidy(text, max = 200) {
|
|
701
|
+
const one = text.replace(/\s+/g, " ").trim();
|
|
702
|
+
return one.length > max ? `${one.slice(0, max - 1)}\u2026` : one;
|
|
703
|
+
}
|
|
704
|
+
function extractJsonObject(text) {
|
|
705
|
+
const trimmed = text.trim();
|
|
706
|
+
if (!trimmed) return null;
|
|
707
|
+
if (trimmed.startsWith("{")) {
|
|
708
|
+
try {
|
|
709
|
+
return JSON.parse(trimmed);
|
|
710
|
+
} catch {
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
const start = trimmed.indexOf("{");
|
|
714
|
+
const end = trimmed.lastIndexOf("}");
|
|
715
|
+
if (start >= 0 && end > start) {
|
|
716
|
+
try {
|
|
717
|
+
return JSON.parse(trimmed.slice(start, end + 1));
|
|
718
|
+
} catch {
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
function isRecord(value) {
|
|
725
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
726
|
+
}
|
|
727
|
+
function summarizeNpmAuditReport(report) {
|
|
728
|
+
const meta = report.metadata;
|
|
729
|
+
if (!isRecord(meta)) return null;
|
|
730
|
+
const vuln = meta.vulnerabilities;
|
|
731
|
+
if (!isRecord(vuln)) return null;
|
|
732
|
+
const num = (key) => typeof vuln[key] === "number" ? vuln[key] : 0;
|
|
733
|
+
const summary = {
|
|
734
|
+
info: num("info"),
|
|
735
|
+
low: num("low"),
|
|
736
|
+
moderate: num("moderate"),
|
|
737
|
+
high: num("high"),
|
|
738
|
+
critical: num("critical"),
|
|
739
|
+
total: num("total")
|
|
740
|
+
};
|
|
741
|
+
if (typeof vuln.total !== "number" && !summary.critical && !summary.high && !summary.moderate && !summary.low && !summary.info) {
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
return summary;
|
|
745
|
+
}
|
|
746
|
+
function formatAuditSummaryLine(audit) {
|
|
747
|
+
const parts = [];
|
|
748
|
+
if (audit.critical) parts.push(`${audit.critical} critical`);
|
|
749
|
+
if (audit.high) parts.push(`${audit.high} high`);
|
|
750
|
+
if (audit.moderate) parts.push(`${audit.moderate} moderate`);
|
|
751
|
+
if (audit.low) parts.push(`${audit.low} low`);
|
|
752
|
+
if (audit.info) parts.push(`${audit.info} info`);
|
|
753
|
+
const breakdown = parts.length ? parts.join(", ") : "see report";
|
|
754
|
+
return `npm audit: ${audit.total} vulnerabilit${audit.total === 1 ? "y" : "ies"} (${breakdown}) \u2014 remediation required`;
|
|
755
|
+
}
|
|
756
|
+
function npmAuditFailureReason(report, stderr) {
|
|
757
|
+
const err = report.error;
|
|
758
|
+
if (isRecord(err)) {
|
|
759
|
+
const summary = typeof err.summary === "string" ? err.summary.trim() : "";
|
|
760
|
+
const code = typeof err.code === "string" ? err.code.trim() : "";
|
|
761
|
+
if (summary) return code ? `${code}: ${summary}` : summary;
|
|
762
|
+
if (code) return code;
|
|
763
|
+
}
|
|
764
|
+
const detail = typeof report.message === "string" ? report.message.trim() : "";
|
|
765
|
+
if (detail) return detail;
|
|
766
|
+
const errTail = stderr.trim();
|
|
767
|
+
if (errTail) return tidy(errTail.split("\n").find(Boolean) ?? errTail, 160);
|
|
768
|
+
return "npm audit failed";
|
|
769
|
+
}
|
|
770
|
+
function classifyNpmAuditOutcome(input) {
|
|
771
|
+
const combined = `${input.stdout}
|
|
772
|
+
${input.stderr}`.trim();
|
|
773
|
+
const parsed = extractJsonObject(combined);
|
|
774
|
+
if (!parsed || !isRecord(parsed)) {
|
|
775
|
+
const tail = tidy(combined || `exit ${input.exitCode}`, 180);
|
|
776
|
+
return {
|
|
777
|
+
kind: "command_failure",
|
|
778
|
+
exitCode: input.exitCode,
|
|
779
|
+
summary: `npm audit failed (invalid or missing JSON): ${tail}`,
|
|
780
|
+
parseError: "invalid_json"
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
if (isRecord(parsed.error)) {
|
|
784
|
+
return {
|
|
785
|
+
kind: "command_failure",
|
|
786
|
+
exitCode: input.exitCode,
|
|
787
|
+
summary: `npm audit command failed: ${npmAuditFailureReason(parsed, input.stderr)}`
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
const audit = summarizeNpmAuditReport(parsed);
|
|
791
|
+
if (!audit) {
|
|
792
|
+
return {
|
|
793
|
+
kind: "command_failure",
|
|
794
|
+
exitCode: input.exitCode,
|
|
795
|
+
summary: "npm audit failed: JSON response missing vulnerability metadata",
|
|
796
|
+
parseError: "missing_metadata"
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
if (input.exitCode === 0 && audit.total === 0) {
|
|
800
|
+
return {
|
|
801
|
+
kind: "success",
|
|
802
|
+
exitCode: 0,
|
|
803
|
+
summary: "npm audit: no vulnerabilities reported",
|
|
804
|
+
audit
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
return {
|
|
808
|
+
kind: "audit_findings",
|
|
809
|
+
exitCode: input.exitCode,
|
|
810
|
+
summary: formatAuditSummaryLine(audit),
|
|
811
|
+
audit
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
function isNpmAuditCommand(command) {
|
|
815
|
+
return NPM_AUDIT_RE.test(command);
|
|
816
|
+
}
|
|
817
|
+
function classifyShellCommandOutcome(input) {
|
|
818
|
+
const stdout = input.stdout ?? "";
|
|
819
|
+
const stderr = input.stderr ?? "";
|
|
820
|
+
const interleaved = input.interleavedOutput ?? "";
|
|
821
|
+
if (isNpmAuditCommand(input.command)) {
|
|
822
|
+
const body = stdout.trim() || interleaved.trim() || stderr.trim();
|
|
823
|
+
return classifyNpmAuditOutcome({
|
|
824
|
+
exitCode: input.exitCode,
|
|
825
|
+
stdout: body,
|
|
826
|
+
stderr
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
if (input.exitCode === 0) {
|
|
830
|
+
return {
|
|
831
|
+
kind: "success",
|
|
832
|
+
exitCode: 0,
|
|
833
|
+
summary: `command succeeded (exit 0)`
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
const tail = tidy(interleaved || stdout || stderr || `exit ${input.exitCode}`, 180);
|
|
837
|
+
return {
|
|
838
|
+
kind: "command_failure",
|
|
839
|
+
exitCode: input.exitCode,
|
|
840
|
+
summary: `command failed (exit ${input.exitCode}): ${tail}`
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// src/stream.ts
|
|
627
845
|
function eventTimestampIso(event) {
|
|
628
846
|
const tsMs = event.timestamp_ms;
|
|
629
847
|
return event.timestamp || event.ts || (tsMs ? new Date(tsMs).toISOString() : void 0);
|
|
@@ -644,16 +862,43 @@ function recordStreamResult(result, event) {
|
|
|
644
862
|
result.error = String(event.result || event.api_error_status || "stream result error");
|
|
645
863
|
}
|
|
646
864
|
}
|
|
865
|
+
function shellPayloadFromCursorEvent(event) {
|
|
866
|
+
if (event.type !== "tool_call" || event.subtype !== "completed") return null;
|
|
867
|
+
const toolCall = event.tool_call && typeof event.tool_call === "object" && !Array.isArray(event.tool_call) ? event.tool_call : null;
|
|
868
|
+
const shell = toolCall?.shellToolCall;
|
|
869
|
+
if (!shell || typeof shell !== "object" || Array.isArray(shell)) return null;
|
|
870
|
+
const shellObj = shell;
|
|
871
|
+
const args = shellObj.args;
|
|
872
|
+
const command = args && typeof args === "object" && !Array.isArray(args) && typeof args.command === "string" ? String(args.command) : "";
|
|
873
|
+
const result = shellObj.result;
|
|
874
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) return null;
|
|
875
|
+
const body = result.success ?? result.failure;
|
|
876
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return null;
|
|
877
|
+
const row = body;
|
|
878
|
+
const exitCode = typeof row.exitCode === "number" ? row.exitCode : 0;
|
|
879
|
+
return {
|
|
880
|
+
command,
|
|
881
|
+
exitCode,
|
|
882
|
+
stdout: typeof row.stdout === "string" ? row.stdout : "",
|
|
883
|
+
stderr: typeof row.stderr === "string" ? row.stderr : "",
|
|
884
|
+
interleaved: typeof row.interleavedOutput === "string" ? row.interleavedOutput : ""
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
function applyShellOutcome(parsed, outcome) {
|
|
888
|
+
if (outcome.kind === "success") return;
|
|
889
|
+
parsed.lastShellOutcome = outcome;
|
|
890
|
+
}
|
|
647
891
|
function parseHarnessStream(file) {
|
|
648
892
|
const result = {
|
|
649
893
|
firstEventAt: null,
|
|
650
894
|
lastEventAt: null,
|
|
651
895
|
currentTool: null,
|
|
652
896
|
finalResult: null,
|
|
653
|
-
error: null
|
|
897
|
+
error: null,
|
|
898
|
+
lastShellOutcome: null
|
|
654
899
|
};
|
|
655
900
|
if (!existsSync7(file)) return result;
|
|
656
|
-
const lines =
|
|
901
|
+
const lines = readFileSync6(file, "utf8").split("\n").filter(Boolean);
|
|
657
902
|
for (const line of lines) {
|
|
658
903
|
const event = safeJson(line);
|
|
659
904
|
if (!event) continue;
|
|
@@ -678,6 +923,19 @@ function parseHarnessStream(file) {
|
|
|
678
923
|
const name = cursorToolNameFromCall(toolCall);
|
|
679
924
|
if (name) result.currentTool = name;
|
|
680
925
|
}
|
|
926
|
+
const shell = shellPayloadFromCursorEvent(event);
|
|
927
|
+
if (shell) {
|
|
928
|
+
applyShellOutcome(
|
|
929
|
+
result,
|
|
930
|
+
classifyShellCommandOutcome({
|
|
931
|
+
command: shell.command,
|
|
932
|
+
exitCode: shell.exitCode,
|
|
933
|
+
stdout: shell.stdout,
|
|
934
|
+
stderr: shell.stderr,
|
|
935
|
+
interleavedOutput: shell.interleaved
|
|
936
|
+
})
|
|
937
|
+
);
|
|
938
|
+
}
|
|
681
939
|
if (event.type === "result") {
|
|
682
940
|
recordStreamResult(result, event);
|
|
683
941
|
}
|
|
@@ -687,6 +945,25 @@ function parseHarnessStream(file) {
|
|
|
687
945
|
function parseClaudeStream(file) {
|
|
688
946
|
return parseHarnessStream(file);
|
|
689
947
|
}
|
|
948
|
+
function summarizeShellToolCallEvent(event) {
|
|
949
|
+
const shell = shellPayloadFromCursorEvent(event);
|
|
950
|
+
if (!shell) return void 0;
|
|
951
|
+
const outcome = classifyShellCommandOutcome({
|
|
952
|
+
command: shell.command,
|
|
953
|
+
exitCode: shell.exitCode,
|
|
954
|
+
stdout: shell.stdout,
|
|
955
|
+
stderr: shell.stderr,
|
|
956
|
+
interleavedOutput: shell.interleaved
|
|
957
|
+
});
|
|
958
|
+
const cmd = oneLine(shell.command).slice(0, 120);
|
|
959
|
+
if (outcome.kind === "audit_findings") {
|
|
960
|
+
return `[audit:findings] ${outcome.summary}${cmd ? ` \xB7 ${cmd}` : ""}`;
|
|
961
|
+
}
|
|
962
|
+
if (outcome.kind === "command_failure") {
|
|
963
|
+
return `[command:failed] ${outcome.summary}${cmd ? ` \xB7 ${cmd}` : ""}`;
|
|
964
|
+
}
|
|
965
|
+
return `[command:ok] exit 0${cmd ? ` \xB7 ${cmd}` : ""}`;
|
|
966
|
+
}
|
|
690
967
|
function summarizeEvent(event) {
|
|
691
968
|
if (event.type === "system" && event.subtype) {
|
|
692
969
|
return `[system:${event.subtype}] ${String(event.status || event.cwd || "")}`.trim();
|
|
@@ -719,6 +996,8 @@ function summarizeEvent(event) {
|
|
|
719
996
|
}
|
|
720
997
|
if (event.type === "tool_call") {
|
|
721
998
|
const subtype = String(event.subtype || "");
|
|
999
|
+
const shellSummary = subtype === "completed" ? summarizeShellToolCallEvent(event) : void 0;
|
|
1000
|
+
if (shellSummary) return shellSummary;
|
|
722
1001
|
const toolCall = event.tool_call && typeof event.tool_call === "object" && !Array.isArray(event.tool_call) ? event.tool_call : void 0;
|
|
723
1002
|
const name = cursorToolNameFromCall(toolCall) ?? "tool";
|
|
724
1003
|
return `[tool:${subtype}] ${name}`;
|
|
@@ -760,7 +1039,7 @@ var FAILURE_PATTERNS = [
|
|
|
760
1039
|
label: "provider authentication failed"
|
|
761
1040
|
}
|
|
762
1041
|
];
|
|
763
|
-
function
|
|
1042
|
+
function tidy2(errorText, max = 240) {
|
|
764
1043
|
const oneLine2 = errorText.replace(/\s+/g, " ").trim();
|
|
765
1044
|
return oneLine2.length > max ? `${oneLine2.slice(0, max - 1)}\u2026` : oneLine2;
|
|
766
1045
|
}
|
|
@@ -769,7 +1048,7 @@ function classifyExitFailure(errorText) {
|
|
|
769
1048
|
if (!text) return null;
|
|
770
1049
|
for (const pattern of FAILURE_PATTERNS) {
|
|
771
1050
|
if (pattern.test.test(text)) {
|
|
772
|
-
return { blocked: true, reason: `${pattern.label}: ${
|
|
1051
|
+
return { blocked: true, reason: `${pattern.label}: ${tidy2(text)}` };
|
|
773
1052
|
}
|
|
774
1053
|
}
|
|
775
1054
|
return null;
|
|
@@ -835,6 +1114,63 @@ function assessExitedWorkerSalvage(input) {
|
|
|
835
1114
|
|
|
836
1115
|
// src/git.ts
|
|
837
1116
|
import { spawnSync } from "node:child_process";
|
|
1117
|
+
|
|
1118
|
+
// src/worker-env.ts
|
|
1119
|
+
var FORBIDDEN_WORKER_ENV_KEYS = [
|
|
1120
|
+
"ANTHROPIC_API_KEY",
|
|
1121
|
+
"ANALYST_API_KEY",
|
|
1122
|
+
"RECRUITER_API_KEY",
|
|
1123
|
+
"AUTH_SECRET",
|
|
1124
|
+
"NEXTAUTH_SECRET",
|
|
1125
|
+
"DATABASE_URL",
|
|
1126
|
+
"PRODUCTION_DATABASE_URL",
|
|
1127
|
+
"REDIS_URL",
|
|
1128
|
+
"GOOGLE_CLIENT_SECRET",
|
|
1129
|
+
"GITHUB_CLIENT_SECRET",
|
|
1130
|
+
"KYNVER_API_KEY",
|
|
1131
|
+
"KYNVER_SERVICE_SECRET",
|
|
1132
|
+
"KYNVER_RUNTIME_SECRET",
|
|
1133
|
+
"OPENCLAW_CRON_SECRET",
|
|
1134
|
+
"QSTASH_TOKEN",
|
|
1135
|
+
"QSTASH_CURRENT_SIGNING_KEY",
|
|
1136
|
+
"QSTASH_NEXT_SIGNING_KEY",
|
|
1137
|
+
"TOOL_SECRETS_KEK",
|
|
1138
|
+
"TOOL_EXECUTOR_DISPATCH_SECRET",
|
|
1139
|
+
"CLOUDFLARE_API_TOKEN",
|
|
1140
|
+
"STRIPE_SECRET_KEY",
|
|
1141
|
+
"STRIPE_WEBHOOK_SECRET",
|
|
1142
|
+
"STRIPE_IDENTITY_WEBHOOK_SECRET",
|
|
1143
|
+
"VOYAGE_API_KEY",
|
|
1144
|
+
"PERPLEXITY_API_KEY",
|
|
1145
|
+
"FRED_API_KEY",
|
|
1146
|
+
"FMP_API_KEY",
|
|
1147
|
+
"CURSOR_API_KEY"
|
|
1148
|
+
];
|
|
1149
|
+
var FORBIDDEN_KEY_SET = new Set(FORBIDDEN_WORKER_ENV_KEYS);
|
|
1150
|
+
var FORBIDDEN_SUFFIXES = ["_SECRET", "_API_KEY"];
|
|
1151
|
+
function isForbiddenWorkerEnvKey(key) {
|
|
1152
|
+
if (FORBIDDEN_KEY_SET.has(key)) return true;
|
|
1153
|
+
return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));
|
|
1154
|
+
}
|
|
1155
|
+
function listForbiddenWorkerEnvKeys(env) {
|
|
1156
|
+
return Object.keys(env).filter(isForbiddenWorkerEnvKey).sort();
|
|
1157
|
+
}
|
|
1158
|
+
function scrubWorkerEnv(env) {
|
|
1159
|
+
const next = { ...env };
|
|
1160
|
+
for (const key of Object.keys(next)) {
|
|
1161
|
+
if (isForbiddenWorkerEnvKey(key)) delete next[key];
|
|
1162
|
+
}
|
|
1163
|
+
return next;
|
|
1164
|
+
}
|
|
1165
|
+
function auditWorkerEnv(env) {
|
|
1166
|
+
const forbiddenPresent = listForbiddenWorkerEnvKeys(env);
|
|
1167
|
+
return { forbiddenPresent, safe: forbiddenPresent.length === 0 };
|
|
1168
|
+
}
|
|
1169
|
+
function scrubClaudeEnv(env) {
|
|
1170
|
+
return scrubWorkerEnv(env);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// src/git.ts
|
|
838
1174
|
function git(cwd, args, options = {}) {
|
|
839
1175
|
const res = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
840
1176
|
if (res.status !== 0 && !options.allowFailure) {
|
|
@@ -950,11 +1286,6 @@ function unknownAncestry(base, error, head = null) {
|
|
|
950
1286
|
error
|
|
951
1287
|
};
|
|
952
1288
|
}
|
|
953
|
-
function scrubClaudeEnv(env) {
|
|
954
|
-
const next = { ...env };
|
|
955
|
-
delete next.ANTHROPIC_API_KEY;
|
|
956
|
-
return next;
|
|
957
|
-
}
|
|
958
1289
|
|
|
959
1290
|
// src/landing-gate.ts
|
|
960
1291
|
function trimOrNull2(value) {
|
|
@@ -1304,15 +1635,7 @@ function computeAutoMaxWorkers(totalMemBytes, opts = {}) {
|
|
|
1304
1635
|
return Math.min(raw, AUTO_MAX_WORKERS_CEILING);
|
|
1305
1636
|
}
|
|
1306
1637
|
function readAvailableMemBytes() {
|
|
1307
|
-
|
|
1308
|
-
try {
|
|
1309
|
-
const meminfo = readFileSync6("/proc/meminfo", "utf8");
|
|
1310
|
-
const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m);
|
|
1311
|
-
if (match) return Number(match[1]) * 1024;
|
|
1312
|
-
} catch {
|
|
1313
|
-
}
|
|
1314
|
-
}
|
|
1315
|
-
return os.freemem();
|
|
1638
|
+
return readMemAvailableBytes();
|
|
1316
1639
|
}
|
|
1317
1640
|
function isActiveHarnessWorker(worker) {
|
|
1318
1641
|
const status = computeWorkerStatus(worker);
|
|
@@ -1322,7 +1645,7 @@ function countActiveWorkersForRun(run) {
|
|
|
1322
1645
|
let active = 0;
|
|
1323
1646
|
for (const name of Object.keys(run.workers || {})) {
|
|
1324
1647
|
const worker = readJson(
|
|
1325
|
-
|
|
1648
|
+
path6.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1326
1649
|
void 0
|
|
1327
1650
|
);
|
|
1328
1651
|
if (!worker || !isActiveHarnessWorker(worker)) continue;
|
|
@@ -1340,7 +1663,7 @@ function observeRunnerResourceGate(input) {
|
|
|
1340
1663
|
input.config,
|
|
1341
1664
|
input.configuredMaxWorkersOverride
|
|
1342
1665
|
);
|
|
1343
|
-
const totalMemBytes = input.totalMemBytes ??
|
|
1666
|
+
const totalMemBytes = input.totalMemBytes ?? os2.totalmem();
|
|
1344
1667
|
const freeMemBytes = input.freeMemBytes ?? readAvailableMemBytes();
|
|
1345
1668
|
const activeWorkers = input.activeWorkers ?? countActiveWorkersGlobal();
|
|
1346
1669
|
const budgetBytes = Math.max(0, Math.floor(totalMemBytes * memUtilization) - memReserveBytes);
|
|
@@ -1530,6 +1853,7 @@ var claudeProvider = {
|
|
|
1530
1853
|
const model = preflight.model;
|
|
1531
1854
|
const stdoutFd = openSync(opts.stdoutPath, "a");
|
|
1532
1855
|
const stderrFd = openSync(opts.stderrPath, "a");
|
|
1856
|
+
const stdio = ["ignore", stdoutFd, stderrFd];
|
|
1533
1857
|
const child = spawn(
|
|
1534
1858
|
"claude",
|
|
1535
1859
|
[
|
|
@@ -1547,8 +1871,8 @@ var claudeProvider = {
|
|
|
1547
1871
|
hiddenSpawnOptions({
|
|
1548
1872
|
cwd: opts.worktreePath,
|
|
1549
1873
|
detached: true,
|
|
1550
|
-
stdio
|
|
1551
|
-
env:
|
|
1874
|
+
stdio,
|
|
1875
|
+
env: scrubWorkerEnv(process.env)
|
|
1552
1876
|
})
|
|
1553
1877
|
);
|
|
1554
1878
|
closeSync(stdoutFd);
|
|
@@ -1706,10 +2030,10 @@ function readHarnessRetryLimits() {
|
|
|
1706
2030
|
}
|
|
1707
2031
|
|
|
1708
2032
|
// src/lease-renewal.ts
|
|
1709
|
-
import
|
|
2033
|
+
import path7 from "node:path";
|
|
1710
2034
|
function workerRecord(runId, name) {
|
|
1711
2035
|
return readJson(
|
|
1712
|
-
|
|
2036
|
+
path7.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
1713
2037
|
void 0
|
|
1714
2038
|
);
|
|
1715
2039
|
}
|
|
@@ -1776,7 +2100,7 @@ function hasLiveWorkerForTask(runId, taskId) {
|
|
|
1776
2100
|
|
|
1777
2101
|
// src/supervisor.ts
|
|
1778
2102
|
import { existsSync as existsSync11, mkdirSync as mkdirSync3 } from "node:fs";
|
|
1779
|
-
import
|
|
2103
|
+
import path13 from "node:path";
|
|
1780
2104
|
|
|
1781
2105
|
// src/prompt.ts
|
|
1782
2106
|
function buildPrompt(input) {
|
|
@@ -1795,6 +2119,16 @@ function buildPrompt(input) {
|
|
|
1795
2119
|
"- After implementation: wait for report_reviewer then deep_reviewer confirmation (via MCP/session agents) before follow-up rows close.",
|
|
1796
2120
|
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."
|
|
1797
2121
|
];
|
|
2122
|
+
const mergeGateLines = compact ? [
|
|
2123
|
+
"Merge-gate cost control: run `node scripts/verify-pr-local.mjs --emit-json` and `node scripts/collect-pr-vercel-evidence.mjs --pr <url> --emit-json` (trusts GitHub Vercel status for dashboard target_url; never passes dashboard URLs to `vercel inspect`) before any GitHub Actions run; request merge-gate only via POST pr-merge-gate/request-run (one Actions run per PR head unless human approves extra)."
|
|
2124
|
+
] : [
|
|
2125
|
+
"GitHub Actions merge-gate cost control (Kynver/Hermes PRs):",
|
|
2126
|
+
"- Prefer local cached package verification (`node scripts/verify-pr-local.mjs --emit-json`) and Vercel preview evidence before GitHub Actions.",
|
|
2127
|
+
"- 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.",
|
|
2128
|
+
"- Record evidence: POST `/api/agent-os/by-id/<agentOsId>/pr-merge-gate/refresh` with prUrl + local/vercel payloads.",
|
|
2129
|
+
"- Request the final Actions run only when local + Vercel are green: POST `.../pr-merge-gate/request-run` (applies `merge-gate` label).",
|
|
2130
|
+
"- Empty failed Actions jobs (no runner/steps/logs) are infra/quota \u2014 do not enter repair loops; escalate to operator."
|
|
2131
|
+
];
|
|
1798
2132
|
const planArtifactLines = compact ? [
|
|
1799
2133
|
"Plan artifacts: when authoring/revising docs/superpowers/plans/, open a GitHub PR early and iterate from that PR branch; do not leave the canonical plan only in the harness worktree."
|
|
1800
2134
|
] : [
|
|
@@ -1816,10 +2150,14 @@ function buildPrompt(input) {
|
|
|
1816
2150
|
"Completion handoff (required): before you stop, ensure the harness records a final result \u2014 summarize outcome in your last message and append a heartbeat line with phase `complete`. If you leave uncommitted changes or committed work without a PR, the orchestrator blocks completion until a GitHub PR exists (or you discard/commit cleanly). Exiting with only dirty files and no PR routes to salvage review, not production review.",
|
|
1817
2151
|
"PR-ready handoff: for substantial implementation work, commit, push, and open a GitHub PR (draft OK) on your branch before finishing \u2014 or rely on the harness to run `gh pr create` at completion when `gh` is authenticated.",
|
|
1818
2152
|
"Worker resource guard: do not run full monorepo verification (`npm run typecheck`, `npm run build`, or equivalent) from this worker lane unless an operator explicitly requests it. Use targeted checks for touched paths and rely on CI/operator lanes for heavy gates.",
|
|
2153
|
+
"npm publish boundary: do not run `npm publish`, do not republish `@kynver-app/*` packages, and do not block on an operator to publish. When you need newer runtime code than npm, use this repo checkout (`npm run kynver:build`, `npm run kynver`) and record evidence: packages/kynver-runtime/package.json version + git ref in your completion report.",
|
|
1819
2154
|
"If verification fails (including OOM), append a heartbeat line immediately with the last command, failure reason, dirty-file status, commit/PR handoff state, and next action so recovery does not require log spelunking.",
|
|
2155
|
+
"Landing-wrapper cleanup on a git ref: use `node scripts/agent-os-land-pr-cleanup-verify.mjs --ref origin/main` (JSON, exit 0). Do not use `git show <ref>:scripts/agent-os-land-pr.mjs | rg \u2026` \u2014 ripgrep exit 1 when markers are absent is reported as a failed command in Telegram/status tooling.",
|
|
1820
2156
|
"",
|
|
1821
2157
|
...progressLines,
|
|
1822
2158
|
"",
|
|
2159
|
+
...mergeGateLines,
|
|
2160
|
+
"",
|
|
1823
2161
|
...planArtifactLines,
|
|
1824
2162
|
"",
|
|
1825
2163
|
...input.personaMarkdown?.trim() ? [input.personaMarkdown.trim(), ""] : [],
|
|
@@ -1832,11 +2170,11 @@ function buildPrompt(input) {
|
|
|
1832
2170
|
// src/providers/cursor.ts
|
|
1833
2171
|
import { closeSync as closeSync2, existsSync as existsSync9, openSync as openSync2 } from "node:fs";
|
|
1834
2172
|
import { spawn as spawn2 } from "node:child_process";
|
|
1835
|
-
import
|
|
2173
|
+
import path9 from "node:path";
|
|
1836
2174
|
|
|
1837
2175
|
// src/providers/cursor-windows.ts
|
|
1838
2176
|
import { existsSync as existsSync8, readdirSync as readdirSync3 } from "node:fs";
|
|
1839
|
-
import
|
|
2177
|
+
import path8 from "node:path";
|
|
1840
2178
|
var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
|
|
1841
2179
|
function parseCursorVersionSortKey(versionName) {
|
|
1842
2180
|
const datePart = versionName.split("-")[0];
|
|
@@ -1847,7 +2185,7 @@ function parseCursorVersionSortKey(versionName) {
|
|
|
1847
2185
|
return Number(`${year}${month.padStart(2, "0")}${day.padStart(2, "0")}`);
|
|
1848
2186
|
}
|
|
1849
2187
|
function pickLatestCursorVersionDir(agentRoot) {
|
|
1850
|
-
const versionsRoot =
|
|
2188
|
+
const versionsRoot = path8.join(agentRoot, "versions");
|
|
1851
2189
|
if (!existsSync8(versionsRoot)) return null;
|
|
1852
2190
|
let bestDir = null;
|
|
1853
2191
|
let bestKey = -1;
|
|
@@ -1856,21 +2194,21 @@ function pickLatestCursorVersionDir(agentRoot) {
|
|
|
1856
2194
|
const key = parseCursorVersionSortKey(entry.name);
|
|
1857
2195
|
if (key == null || key <= bestKey) continue;
|
|
1858
2196
|
bestKey = key;
|
|
1859
|
-
bestDir =
|
|
2197
|
+
bestDir = path8.join(versionsRoot, entry.name);
|
|
1860
2198
|
}
|
|
1861
2199
|
return bestDir;
|
|
1862
2200
|
}
|
|
1863
2201
|
function resolveWindowsCursorBundled(agentRoot) {
|
|
1864
|
-
const root = agentRoot?.trim() ||
|
|
1865
|
-
const directNode =
|
|
1866
|
-
const directIndex =
|
|
2202
|
+
const root = agentRoot?.trim() || path8.join(process.env.LOCALAPPDATA || "", "cursor-agent");
|
|
2203
|
+
const directNode = path8.join(root, "node.exe");
|
|
2204
|
+
const directIndex = path8.join(root, "index.js");
|
|
1867
2205
|
if (existsSync8(directNode) && existsSync8(directIndex)) {
|
|
1868
2206
|
return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
|
|
1869
2207
|
}
|
|
1870
2208
|
const versionDir = pickLatestCursorVersionDir(root);
|
|
1871
2209
|
if (!versionDir) return null;
|
|
1872
|
-
const nodeExe =
|
|
1873
|
-
const indexJs =
|
|
2210
|
+
const nodeExe = path8.join(versionDir, "node.exe");
|
|
2211
|
+
const indexJs = path8.join(versionDir, "index.js");
|
|
1874
2212
|
if (!existsSync8(nodeExe) || !existsSync8(indexJs)) return null;
|
|
1875
2213
|
return { nodeExe, indexJs, versionDir };
|
|
1876
2214
|
}
|
|
@@ -1889,13 +2227,13 @@ function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
|
|
|
1889
2227
|
function resolveCursorSpawn(agentBin) {
|
|
1890
2228
|
if (process.platform === "win32") {
|
|
1891
2229
|
const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
|
|
1892
|
-
const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync9(
|
|
2230
|
+
const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync9(path9.join(path9.dirname(agentBin), "index.js"));
|
|
1893
2231
|
const isDefaultShim = agentBin === "agent";
|
|
1894
2232
|
if (isCursorWrapper || isBundledNode || isDefaultShim) {
|
|
1895
|
-
const bundled = isCursorWrapper ? resolveWindowsCursorBundled(
|
|
2233
|
+
const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path9.dirname(agentBin)) : isBundledNode ? {
|
|
1896
2234
|
nodeExe: agentBin,
|
|
1897
|
-
indexJs:
|
|
1898
|
-
versionDir:
|
|
2235
|
+
indexJs: path9.join(path9.dirname(agentBin), "index.js"),
|
|
2236
|
+
versionDir: path9.dirname(agentBin)
|
|
1899
2237
|
} : resolveWindowsCursorBundled();
|
|
1900
2238
|
if (bundled) {
|
|
1901
2239
|
return bundledSpawnTarget(bundled.nodeExe, bundled.indexJs, bundled.versionDir);
|
|
@@ -1915,18 +2253,18 @@ function resolveAgentBin() {
|
|
|
1915
2253
|
process.env.KYNVER_CURSOR_AGENT_ROOT?.trim() || void 0
|
|
1916
2254
|
);
|
|
1917
2255
|
if (bundled) return bundled.nodeExe;
|
|
1918
|
-
const localAgent =
|
|
2256
|
+
const localAgent = path9.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
|
|
1919
2257
|
if (existsSync9(localAgent)) return localAgent;
|
|
1920
2258
|
}
|
|
1921
2259
|
return "agent";
|
|
1922
2260
|
}
|
|
1923
2261
|
function cursorWorkerEnv(agentBin, spawnTarget) {
|
|
1924
|
-
return {
|
|
2262
|
+
return scrubWorkerEnv({
|
|
1925
2263
|
...process.env,
|
|
1926
2264
|
CI: "1",
|
|
1927
2265
|
NO_COLOR: "1",
|
|
1928
|
-
...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS:
|
|
1929
|
-
};
|
|
2266
|
+
...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path9.basename(agentBin) || "agent.cmd" } : {}
|
|
2267
|
+
});
|
|
1930
2268
|
}
|
|
1931
2269
|
var cursorProvider = {
|
|
1932
2270
|
name: "cursor",
|
|
@@ -1942,6 +2280,7 @@ var cursorProvider = {
|
|
|
1942
2280
|
const model = preflight.model;
|
|
1943
2281
|
const stdoutFd = openSync2(opts.stdoutPath, "a");
|
|
1944
2282
|
const stderrFd = openSync2(opts.stderrPath, "a");
|
|
2283
|
+
const stdio = ["ignore", stdoutFd, stderrFd];
|
|
1945
2284
|
const agentBin = resolveAgentBin();
|
|
1946
2285
|
const spawnTarget = resolveCursorSpawn(agentBin);
|
|
1947
2286
|
const child = spawn2(
|
|
@@ -1964,7 +2303,7 @@ var cursorProvider = {
|
|
|
1964
2303
|
cwd: opts.worktreePath,
|
|
1965
2304
|
detached: spawnTarget.detached,
|
|
1966
2305
|
shell: spawnTarget.shell,
|
|
1967
|
-
stdio
|
|
2306
|
+
stdio,
|
|
1968
2307
|
env: cursorWorkerEnv(agentBin, spawnTarget)
|
|
1969
2308
|
})
|
|
1970
2309
|
);
|
|
@@ -1999,7 +2338,7 @@ function resolveWorkerProvider(name) {
|
|
|
1999
2338
|
// src/auto-complete.ts
|
|
2000
2339
|
import { spawn as spawn3 } from "node:child_process";
|
|
2001
2340
|
import { existsSync as existsSync10, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
|
|
2002
|
-
import
|
|
2341
|
+
import path12 from "node:path";
|
|
2003
2342
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2004
2343
|
|
|
2005
2344
|
// src/completion-ack.ts
|
|
@@ -2016,7 +2355,40 @@ function persistCompletionAck(worker, runId, fields) {
|
|
|
2016
2355
|
}
|
|
2017
2356
|
|
|
2018
2357
|
// src/worker-ops.ts
|
|
2019
|
-
import
|
|
2358
|
+
import path11 from "node:path";
|
|
2359
|
+
|
|
2360
|
+
// src/completion-response.ts
|
|
2361
|
+
function asRecord(value) {
|
|
2362
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2363
|
+
}
|
|
2364
|
+
function asString(value) {
|
|
2365
|
+
if (typeof value !== "string") return null;
|
|
2366
|
+
const trimmed = value.trim();
|
|
2367
|
+
return trimmed.length ? trimmed : null;
|
|
2368
|
+
}
|
|
2369
|
+
var ADVANCED_OUTCOMES = /* @__PURE__ */ new Set([
|
|
2370
|
+
"review_scheduled",
|
|
2371
|
+
"review_already_scheduled"
|
|
2372
|
+
]);
|
|
2373
|
+
function summarizeHarnessCompletionResponse(parsed) {
|
|
2374
|
+
const record = asRecord(parsed);
|
|
2375
|
+
if (!record) {
|
|
2376
|
+
return { routeOutcome: null, taskAdvanced: false, detail: null };
|
|
2377
|
+
}
|
|
2378
|
+
const outcome = asString(record.outcome);
|
|
2379
|
+
const detail = asString(record.detail) ?? asString(record.error);
|
|
2380
|
+
const task = asRecord(record.task);
|
|
2381
|
+
const taskStatus = task ? asString(task.status) : null;
|
|
2382
|
+
const taskAdvanced = outcome !== null && ADVANCED_OUTCOMES.has(outcome) || taskStatus === "awaiting_review" || taskStatus === "done";
|
|
2383
|
+
return {
|
|
2384
|
+
routeOutcome: outcome,
|
|
2385
|
+
taskAdvanced,
|
|
2386
|
+
detail
|
|
2387
|
+
};
|
|
2388
|
+
}
|
|
2389
|
+
function completionPostSucceeded(summary) {
|
|
2390
|
+
return summary.taskAdvanced;
|
|
2391
|
+
}
|
|
2020
2392
|
|
|
2021
2393
|
// src/pr-handoff/pr-handoff-assess.ts
|
|
2022
2394
|
var REVIEW_LANE_RULE = /^(lane:)?(review|deep_review|planning|landing)(:|$)/i;
|
|
@@ -2078,6 +2450,36 @@ function buildPrHandoffSnapshotFromStatus(status, extras) {
|
|
|
2078
2450
|
|
|
2079
2451
|
// src/pr-handoff/pr-handoff-gh.ts
|
|
2080
2452
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2453
|
+
|
|
2454
|
+
// src/github-repo.ts
|
|
2455
|
+
function parseGithubOwnerRepo(remoteUrl) {
|
|
2456
|
+
const trimmed = remoteUrl.trim();
|
|
2457
|
+
if (!trimmed) return null;
|
|
2458
|
+
const ssh = trimmed.match(/^git@github\.com:([^/]+\/[^/\s]+?)(?:\.git)?$/i);
|
|
2459
|
+
if (ssh) return normalizeOwnerRepo(ssh[1]);
|
|
2460
|
+
const scp = trimmed.match(/^ssh:\/\/git@github\.com\/([^/]+\/[^/\s]+?)(?:\.git)?$/i);
|
|
2461
|
+
if (scp) return normalizeOwnerRepo(scp[1]);
|
|
2462
|
+
try {
|
|
2463
|
+
const url = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`);
|
|
2464
|
+
if (url.hostname.toLowerCase() !== "github.com") return null;
|
|
2465
|
+
const parts = url.pathname.replace(/^\/+|\/+$/g, "").split("/");
|
|
2466
|
+
if (parts.length < 2) return null;
|
|
2467
|
+
const [owner, repo] = parts;
|
|
2468
|
+
if (!owner || !repo) return null;
|
|
2469
|
+
return `${owner}/${repo.replace(/\.git$/i, "")}`;
|
|
2470
|
+
} catch {
|
|
2471
|
+
return null;
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
function normalizeOwnerRepo(value) {
|
|
2475
|
+
const parts = value.split("/").filter(Boolean);
|
|
2476
|
+
if (parts.length < 2) return null;
|
|
2477
|
+
const owner = parts[0];
|
|
2478
|
+
const repo = parts[1].replace(/\.git$/i, "");
|
|
2479
|
+
return owner && repo ? `${owner}/${repo}` : null;
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
// src/pr-handoff/pr-handoff-gh.ts
|
|
2081
2483
|
function capture(bin, cwd, args) {
|
|
2082
2484
|
try {
|
|
2083
2485
|
const res = spawnSync2(bin, args, { cwd, encoding: "utf8" });
|
|
@@ -2100,21 +2502,13 @@ var defaultPrHandoffExec = {
|
|
|
2100
2502
|
git: (cwd, args) => gitCapture(cwd, args),
|
|
2101
2503
|
gh: (cwd, args) => capture("gh", cwd, args)
|
|
2102
2504
|
};
|
|
2103
|
-
function parseGithubRepo(remoteUrl) {
|
|
2104
|
-
const trimmed = remoteUrl.trim();
|
|
2105
|
-
const ssh = trimmed.match(/git@github\.com:([^/]+\/[^/.]+)(?:\.git)?/i);
|
|
2106
|
-
if (ssh) return ssh[1];
|
|
2107
|
-
const https = trimmed.match(/github\.com[/:]([^/]+\/[^/.]+?)(?:\.git)?/i);
|
|
2108
|
-
if (https) return https[1];
|
|
2109
|
-
return null;
|
|
2110
|
-
}
|
|
2111
2505
|
function firstLine(text) {
|
|
2112
2506
|
return text.split("\n").map((l) => l.trim()).find(Boolean) ?? "";
|
|
2113
2507
|
}
|
|
2114
2508
|
function resolveGithubRepo(worktreePath, exec) {
|
|
2115
2509
|
const remote = exec.git(worktreePath, ["remote", "get-url", "origin"]);
|
|
2116
2510
|
if (remote.status !== 0) return null;
|
|
2117
|
-
return
|
|
2511
|
+
return parseGithubOwnerRepo(remote.stdout);
|
|
2118
2512
|
}
|
|
2119
2513
|
function resolveHeadCommit(worktreePath, exec) {
|
|
2120
2514
|
const head = exec.git(worktreePath, ["rev-parse", "HEAD"]);
|
|
@@ -2353,7 +2747,7 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
|
|
|
2353
2747
|
}
|
|
2354
2748
|
|
|
2355
2749
|
// src/worker-lifecycle.ts
|
|
2356
|
-
import
|
|
2750
|
+
import path10 from "node:path";
|
|
2357
2751
|
var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
|
|
2358
2752
|
"awaiting_review",
|
|
2359
2753
|
"blocked",
|
|
@@ -2409,7 +2803,7 @@ function syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick) {
|
|
|
2409
2803
|
const synced = [];
|
|
2410
2804
|
for (const name of Object.keys(run.workers || {})) {
|
|
2411
2805
|
const worker = readJson(
|
|
2412
|
-
|
|
2806
|
+
path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2413
2807
|
void 0
|
|
2414
2808
|
);
|
|
2415
2809
|
if (!worker?.taskId || isCompletionAcknowledged(worker)) continue;
|
|
@@ -2448,10 +2842,10 @@ function completionErrorText(parsed) {
|
|
|
2448
2842
|
}
|
|
2449
2843
|
return void 0;
|
|
2450
2844
|
}
|
|
2451
|
-
function
|
|
2845
|
+
function asRecord2(value) {
|
|
2452
2846
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2453
2847
|
}
|
|
2454
|
-
function
|
|
2848
|
+
function asString2(value) {
|
|
2455
2849
|
if (typeof value !== "string") return null;
|
|
2456
2850
|
const trimmed = value.trim();
|
|
2457
2851
|
return trimmed.length ? trimmed : null;
|
|
@@ -2571,7 +2965,27 @@ async function tryCompleteWorker(args) {
|
|
|
2571
2965
|
}
|
|
2572
2966
|
}
|
|
2573
2967
|
if (result.ok) {
|
|
2968
|
+
const summary = summarizeHarnessCompletionResponse(result.parsed);
|
|
2969
|
+
if (!completionPostSucceeded(summary)) {
|
|
2970
|
+
const detail2 = summary.detail ?? (summary.routeOutcome ? `harness completion returned ${summary.routeOutcome}` : "harness completion did not advance the linked task");
|
|
2971
|
+
const reason2 = `completion acknowledged but board not advanced: ${detail2}`;
|
|
2972
|
+
persistCompletionBlocker(worker, reason2);
|
|
2973
|
+
const ack2 = {
|
|
2974
|
+
completionReportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2975
|
+
completionOutcome: "rejected",
|
|
2976
|
+
completionResponse: result.parsed
|
|
2977
|
+
};
|
|
2978
|
+
persistCompletionAck(worker, worker.runId, ack2);
|
|
2979
|
+
return {
|
|
2980
|
+
ok: false,
|
|
2981
|
+
httpStatus: result.status,
|
|
2982
|
+
response: result.parsed,
|
|
2983
|
+
reason: reason2,
|
|
2984
|
+
completionBlocked: true
|
|
2985
|
+
};
|
|
2986
|
+
}
|
|
2574
2987
|
persistCompletionBlocker(worker, void 0);
|
|
2988
|
+
const routeOutcome = summary.routeOutcome ?? "acknowledged";
|
|
2575
2989
|
const ack = {
|
|
2576
2990
|
completionReportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2577
2991
|
completionOutcome: "acknowledged",
|
|
@@ -2584,6 +2998,7 @@ async function tryCompleteWorker(args) {
|
|
|
2584
2998
|
ok: true,
|
|
2585
2999
|
httpStatus: result.status,
|
|
2586
3000
|
response: result.parsed,
|
|
3001
|
+
reason: routeOutcome,
|
|
2587
3002
|
...prUrl ? { prHandoff: { prUrl } } : {}
|
|
2588
3003
|
};
|
|
2589
3004
|
}
|
|
@@ -2646,7 +3061,7 @@ function workerStatus(args) {
|
|
|
2646
3061
|
const worker = loadWorker(String(args.run), String(args.name));
|
|
2647
3062
|
const run = loadRun(worker.runId);
|
|
2648
3063
|
const status = computeWorkerStatus(worker, workerStatusOptions(run));
|
|
2649
|
-
writeJson(
|
|
3064
|
+
writeJson(path11.join(worker.workerDir, "last-status.json"), status);
|
|
2650
3065
|
console.log(JSON.stringify(status, null, 2));
|
|
2651
3066
|
}
|
|
2652
3067
|
function buildRunBoard(runId) {
|
|
@@ -2654,7 +3069,7 @@ function buildRunBoard(runId) {
|
|
|
2654
3069
|
const names = Object.keys(run.workers || {});
|
|
2655
3070
|
const workers = names.map((name) => {
|
|
2656
3071
|
const worker = readJson(
|
|
2657
|
-
|
|
3072
|
+
path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2658
3073
|
void 0
|
|
2659
3074
|
);
|
|
2660
3075
|
if (!worker) {
|
|
@@ -2676,22 +3091,23 @@ function buildRunBoard(runId) {
|
|
|
2676
3091
|
const completionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
|
|
2677
3092
|
const boardStatus = completionBlocker ? "blocked" : status.status;
|
|
2678
3093
|
const boardAttention = completionBlocker ? "blocked" : status.attention.state;
|
|
2679
|
-
const completionResponse =
|
|
2680
|
-
const completionTask =
|
|
2681
|
-
const completionOutcome =
|
|
2682
|
-
const completionRouteStatus =
|
|
3094
|
+
const completionResponse = asRecord2(worker.completionResponse);
|
|
3095
|
+
const completionTask = asRecord2(completionResponse?.task);
|
|
3096
|
+
const completionOutcome = asString2(completionResponse?.outcome);
|
|
3097
|
+
const completionRouteStatus = asString2(completionResponse?.status);
|
|
2683
3098
|
const completionWarnings = Array.isArray(completionResponse?.warnings) ? completionResponse.warnings.filter((w) => typeof w === "string" && w.trim().length > 0) : [];
|
|
2684
|
-
const prUrl =
|
|
3099
|
+
const prUrl = asString2(completionTask?.prUrl) ?? asString2(completionResponse?.prUrl);
|
|
3100
|
+
const completionReportedAt = asString2(worker.completionReportedAt);
|
|
2685
3101
|
const lifecycleStage = deriveLifecycleStage({
|
|
2686
3102
|
finished: isFinishedWorkerStatus(status),
|
|
2687
3103
|
completionBlocker,
|
|
2688
3104
|
completionOutcome,
|
|
2689
|
-
completionReportedAt
|
|
3105
|
+
completionReportedAt
|
|
2690
3106
|
});
|
|
2691
3107
|
const nextAction = deriveNextAction({
|
|
2692
3108
|
completionBlocker,
|
|
2693
3109
|
completionOutcome,
|
|
2694
|
-
completionReportedAt
|
|
3110
|
+
completionReportedAt,
|
|
2695
3111
|
finished: isFinishedWorkerStatus(status)
|
|
2696
3112
|
});
|
|
2697
3113
|
const handoffState = deriveHandoffState({
|
|
@@ -2737,7 +3153,7 @@ function buildRunBoard(runId) {
|
|
|
2737
3153
|
gitAncestry: status.gitAncestry,
|
|
2738
3154
|
finalResult: status.finalResult,
|
|
2739
3155
|
lifecycleStage,
|
|
2740
|
-
completionReportedAt
|
|
3156
|
+
completionReportedAt,
|
|
2741
3157
|
completionOutcome: worker.completionOutcome ?? null,
|
|
2742
3158
|
completionRouteStatus,
|
|
2743
3159
|
completionRouteOutcome: completionOutcome,
|
|
@@ -2764,7 +3180,7 @@ function buildRunBoard(runId) {
|
|
|
2764
3180
|
needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
|
|
2765
3181
|
workers
|
|
2766
3182
|
};
|
|
2767
|
-
writeJson(
|
|
3183
|
+
writeJson(path11.join(runDirectory(run.id), "last-board.json"), board);
|
|
2768
3184
|
return board;
|
|
2769
3185
|
}
|
|
2770
3186
|
async function publishHarnessBoardSnapshot(args, source) {
|
|
@@ -2953,12 +3369,12 @@ async function autoCompleteWorkerCli(raw) {
|
|
|
2953
3369
|
}
|
|
2954
3370
|
}
|
|
2955
3371
|
function resolveDefaultCliPath() {
|
|
2956
|
-
return
|
|
3372
|
+
return path12.join(fileURLToPath2(new URL(".", import.meta.url)), "cli.js");
|
|
2957
3373
|
}
|
|
2958
3374
|
function spawnCompletionSidecar(opts) {
|
|
2959
3375
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath();
|
|
2960
3376
|
if (!existsSync10(cliPath)) return void 0;
|
|
2961
|
-
const logPath =
|
|
3377
|
+
const logPath = path12.join(opts.workerDir, "auto-complete.log");
|
|
2962
3378
|
let logFd;
|
|
2963
3379
|
try {
|
|
2964
3380
|
logFd = openSync3(logPath, "a");
|
|
@@ -3040,16 +3456,16 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3040
3456
|
launchModel = preflight.model;
|
|
3041
3457
|
}
|
|
3042
3458
|
const { worktreesDir } = getPaths();
|
|
3043
|
-
const workerDir =
|
|
3459
|
+
const workerDir = path13.join(runDirectory(run.id), "workers", name);
|
|
3044
3460
|
mkdirSync3(workerDir, { recursive: true });
|
|
3045
|
-
const worktreePath =
|
|
3461
|
+
const worktreePath = path13.join(worktreesDir, run.id, name);
|
|
3046
3462
|
const branch = opts.branch || `agent/${run.id}/${name}`;
|
|
3047
3463
|
if (existsSync11(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
|
|
3048
3464
|
git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
|
|
3049
3465
|
git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
|
|
3050
|
-
const stdoutPath =
|
|
3051
|
-
const stderrPath =
|
|
3052
|
-
const heartbeatPath =
|
|
3466
|
+
const stdoutPath = path13.join(workerDir, "stdout.jsonl");
|
|
3467
|
+
const stderrPath = path13.join(workerDir, "stderr.log");
|
|
3468
|
+
const heartbeatPath = path13.join(workerDir, "heartbeat.jsonl");
|
|
3053
3469
|
const prompt = buildPrompt({
|
|
3054
3470
|
task: opts.task,
|
|
3055
3471
|
ownedPaths: opts.ownedPaths || [],
|
|
@@ -3110,7 +3526,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3110
3526
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3111
3527
|
};
|
|
3112
3528
|
saveWorker(run.id, worker);
|
|
3113
|
-
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath:
|
|
3529
|
+
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path13.join(workerDir, "worker.json") } };
|
|
3114
3530
|
run.status = "running";
|
|
3115
3531
|
saveRun(run);
|
|
3116
3532
|
if (worker.agentOsId && worker.taskId) {
|
|
@@ -3402,18 +3818,18 @@ function buildPlanPersistIdempotencyKey(input) {
|
|
|
3402
3818
|
|
|
3403
3819
|
// src/plan-persist/paths.ts
|
|
3404
3820
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
3405
|
-
import { homedir as
|
|
3406
|
-
import
|
|
3821
|
+
import { homedir as homedir4 } from "node:os";
|
|
3822
|
+
import path14 from "node:path";
|
|
3407
3823
|
function resolveKynverStateRoot() {
|
|
3408
3824
|
const env = process.env.KYNVER_STATE_ROOT;
|
|
3409
|
-
if (env) return
|
|
3410
|
-
return
|
|
3825
|
+
if (env) return path14.resolve(env);
|
|
3826
|
+
return path14.join(homedir4(), ".kynver", "state");
|
|
3411
3827
|
}
|
|
3412
3828
|
function planOutboxDir() {
|
|
3413
|
-
return
|
|
3829
|
+
return path14.join(resolveKynverStateRoot(), "plan-outbox");
|
|
3414
3830
|
}
|
|
3415
3831
|
function planOutboxArchiveDir() {
|
|
3416
|
-
return
|
|
3832
|
+
return path14.join(resolveKynverStateRoot(), "plan-outbox-archive");
|
|
3417
3833
|
}
|
|
3418
3834
|
function ensurePlanOutboxDirs() {
|
|
3419
3835
|
const outboxDir = planOutboxDir();
|
|
@@ -3424,8 +3840,8 @@ function ensurePlanOutboxDirs() {
|
|
|
3424
3840
|
}
|
|
3425
3841
|
function isTmpOnlyPath(filePath) {
|
|
3426
3842
|
if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
|
|
3427
|
-
const resolved =
|
|
3428
|
-
return resolved.startsWith("/tmp/") || resolved.startsWith(
|
|
3843
|
+
const resolved = path14.resolve(filePath);
|
|
3844
|
+
return resolved.startsWith("/tmp/") || resolved.startsWith(path14.join("/var", "folders"));
|
|
3429
3845
|
}
|
|
3430
3846
|
|
|
3431
3847
|
// src/plan-persist/outbox-store.ts
|
|
@@ -3437,7 +3853,7 @@ import {
|
|
|
3437
3853
|
writeFileSync as writeFileSync3,
|
|
3438
3854
|
unlinkSync
|
|
3439
3855
|
} from "node:fs";
|
|
3440
|
-
import
|
|
3856
|
+
import path15 from "node:path";
|
|
3441
3857
|
import { randomUUID } from "node:crypto";
|
|
3442
3858
|
var DEFAULT_MAX_RETRIES = 12;
|
|
3443
3859
|
function listOutboxItems() {
|
|
@@ -3445,7 +3861,7 @@ function listOutboxItems() {
|
|
|
3445
3861
|
const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
|
|
3446
3862
|
const items = [];
|
|
3447
3863
|
for (const file of files) {
|
|
3448
|
-
const item = readOutboxItem(
|
|
3864
|
+
const item = readOutboxItem(path15.join(outboxDir, file));
|
|
3449
3865
|
if (item && item.queueStatus === "queued") items.push(item);
|
|
3450
3866
|
}
|
|
3451
3867
|
return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
@@ -3466,7 +3882,7 @@ function readOutboxItem(jsonPath) {
|
|
|
3466
3882
|
}
|
|
3467
3883
|
function readOutboxBody(item) {
|
|
3468
3884
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3469
|
-
const bodyFile =
|
|
3885
|
+
const bodyFile = path15.join(outboxDir, item.bodyPath);
|
|
3470
3886
|
return readFileSync7(bodyFile, "utf8");
|
|
3471
3887
|
}
|
|
3472
3888
|
function writeOutboxItem(input, opts) {
|
|
@@ -3474,8 +3890,8 @@ function writeOutboxItem(input, opts) {
|
|
|
3474
3890
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3475
3891
|
const id = opts.existing?.id ?? randomUUID();
|
|
3476
3892
|
const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
|
|
3477
|
-
const jsonPath =
|
|
3478
|
-
const bodyFile =
|
|
3893
|
+
const jsonPath = path15.join(outboxDir, `${id}.json`);
|
|
3894
|
+
const bodyFile = path15.join(outboxDir, bodyPath);
|
|
3479
3895
|
if (!opts.existing) {
|
|
3480
3896
|
writeFileSync3(bodyFile, input.body, "utf8");
|
|
3481
3897
|
}
|
|
@@ -3511,24 +3927,24 @@ function writeOutboxItem(input, opts) {
|
|
|
3511
3927
|
}
|
|
3512
3928
|
function saveOutboxItem(item) {
|
|
3513
3929
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3514
|
-
const jsonPath =
|
|
3930
|
+
const jsonPath = path15.join(outboxDir, `${item.id}.json`);
|
|
3515
3931
|
writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
|
|
3516
3932
|
`, { mode: 384 });
|
|
3517
3933
|
}
|
|
3518
3934
|
function archiveOutboxItem(item) {
|
|
3519
3935
|
const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
|
|
3520
|
-
const jsonSrc =
|
|
3521
|
-
const bodySrc =
|
|
3522
|
-
const jsonDst =
|
|
3523
|
-
const bodyDst =
|
|
3936
|
+
const jsonSrc = path15.join(outboxDir, `${item.id}.json`);
|
|
3937
|
+
const bodySrc = path15.join(outboxDir, item.bodyPath);
|
|
3938
|
+
const jsonDst = path15.join(archiveDir, `${item.id}.json`);
|
|
3939
|
+
const bodyDst = path15.join(archiveDir, item.bodyPath);
|
|
3524
3940
|
if (existsSync13(jsonSrc)) renameSync(jsonSrc, jsonDst);
|
|
3525
3941
|
if (existsSync13(bodySrc)) renameSync(bodySrc, bodyDst);
|
|
3526
3942
|
}
|
|
3527
3943
|
function outboxItemPaths(item) {
|
|
3528
3944
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3529
3945
|
return {
|
|
3530
|
-
jsonPath:
|
|
3531
|
-
bodyPath:
|
|
3946
|
+
jsonPath: path15.join(outboxDir, `${item.id}.json`),
|
|
3947
|
+
bodyPath: path15.join(outboxDir, item.bodyPath)
|
|
3532
3948
|
};
|
|
3533
3949
|
}
|
|
3534
3950
|
function outboxInputFromItem(item, body) {
|
|
@@ -3714,7 +4130,7 @@ function markOutboxFailed(item, message) {
|
|
|
3714
4130
|
}
|
|
3715
4131
|
|
|
3716
4132
|
// src/plan-persist/drain.ts
|
|
3717
|
-
import
|
|
4133
|
+
import path16 from "node:path";
|
|
3718
4134
|
async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
3719
4135
|
const items = listOutboxItems().filter(
|
|
3720
4136
|
(item) => opts.outboxId ? item.id === opts.outboxId : true
|
|
@@ -3748,7 +4164,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
|
3748
4164
|
return result;
|
|
3749
4165
|
}
|
|
3750
4166
|
function loadOutboxById(outboxId) {
|
|
3751
|
-
const jsonPath =
|
|
4167
|
+
const jsonPath = path16.join(planOutboxDir(), `${outboxId}.json`);
|
|
3752
4168
|
return readOutboxItem(jsonPath);
|
|
3753
4169
|
}
|
|
3754
4170
|
|
|
@@ -3848,7 +4264,7 @@ async function dispatchRun(args) {
|
|
|
3848
4264
|
const activeHarnessWorkers = [];
|
|
3849
4265
|
for (const name of Object.keys(run.workers || {})) {
|
|
3850
4266
|
const worker = readJson(
|
|
3851
|
-
|
|
4267
|
+
path17.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
3852
4268
|
void 0
|
|
3853
4269
|
);
|
|
3854
4270
|
if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
|
|
@@ -3872,7 +4288,8 @@ async function dispatchRun(args) {
|
|
|
3872
4288
|
harnessBoardSnapshot: buildRunBoard(run.id),
|
|
3873
4289
|
...args.lane ? { lane: String(args.lane) } : {},
|
|
3874
4290
|
executor: args.executor ? String(args.executor) : "harness",
|
|
3875
|
-
...args.diskPath ? { diskPath: String(args.diskPath) } : {}
|
|
4291
|
+
...args.diskPath ? { diskPath: String(args.diskPath) } : {},
|
|
4292
|
+
...args.targetTaskId ? { targetTaskId: String(args.targetTaskId) } : {}
|
|
3876
4293
|
};
|
|
3877
4294
|
const dispatch = await postJsonWithCredentialRefresh(dispatchUrl, secret, body, { agentOsId, baseUrl: base });
|
|
3878
4295
|
const responseBody = dispatch.response;
|
|
@@ -4051,6 +4468,13 @@ async function dispatchRun(args) {
|
|
|
4051
4468
|
}
|
|
4052
4469
|
}
|
|
4053
4470
|
|
|
4471
|
+
// src/fortress-engagement-gate.ts
|
|
4472
|
+
function isEngagementRequiredSkip(skip) {
|
|
4473
|
+
if (skip.skipReason === "engagement_required") return true;
|
|
4474
|
+
const reason = skip.reason ?? "";
|
|
4475
|
+
return reason.includes("engagement_required") || reason.includes("engagement_not_found") || reason.includes("engagement_expired") || reason.includes("engagement_revoked");
|
|
4476
|
+
}
|
|
4477
|
+
|
|
4054
4478
|
// src/redact.ts
|
|
4055
4479
|
function redactHarness(text, secret) {
|
|
4056
4480
|
let out = text;
|
|
@@ -4061,7 +4485,7 @@ function redactHarness(text, secret) {
|
|
|
4061
4485
|
}
|
|
4062
4486
|
|
|
4063
4487
|
// src/validate.ts
|
|
4064
|
-
import
|
|
4488
|
+
import path18 from "node:path";
|
|
4065
4489
|
var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
4066
4490
|
var WORKER_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
|
|
4067
4491
|
function validateRunId(runId) {
|
|
@@ -4075,15 +4499,15 @@ function validateWorkerName(name) {
|
|
|
4075
4499
|
return trimmed;
|
|
4076
4500
|
}
|
|
4077
4501
|
function validateRepo(repo) {
|
|
4078
|
-
const resolved =
|
|
4502
|
+
const resolved = path18.resolve(repo);
|
|
4079
4503
|
if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
|
|
4080
4504
|
return resolved;
|
|
4081
4505
|
}
|
|
4082
4506
|
function validateOwnedPaths(repoRoot, ownedPaths) {
|
|
4083
4507
|
return ownedPaths.map((owned) => {
|
|
4084
|
-
const resolved =
|
|
4085
|
-
const rel =
|
|
4086
|
-
if (rel.startsWith("..") ||
|
|
4508
|
+
const resolved = path18.resolve(repoRoot, owned);
|
|
4509
|
+
const rel = path18.relative(repoRoot, resolved);
|
|
4510
|
+
if (rel.startsWith("..") || path18.isAbsolute(rel)) {
|
|
4087
4511
|
throw new Error(`owned path escapes repo: ${owned}`);
|
|
4088
4512
|
}
|
|
4089
4513
|
return resolved;
|
|
@@ -4096,7 +4520,7 @@ function validateTailLines(lines) {
|
|
|
4096
4520
|
|
|
4097
4521
|
// src/worktree.ts
|
|
4098
4522
|
import { existsSync as existsSync14, mkdirSync as mkdirSync5 } from "node:fs";
|
|
4099
|
-
import
|
|
4523
|
+
import path19 from "node:path";
|
|
4100
4524
|
function createRun(args) {
|
|
4101
4525
|
const repo = validateRepo(required(String(args.repo || ""), "--repo"));
|
|
4102
4526
|
ensureGitRepo(repo);
|
|
@@ -4116,12 +4540,12 @@ function createRun(args) {
|
|
|
4116
4540
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4117
4541
|
workers: {}
|
|
4118
4542
|
};
|
|
4119
|
-
writeJson(
|
|
4543
|
+
writeJson(path19.join(dir, "run.json"), run);
|
|
4120
4544
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
4121
4545
|
}
|
|
4122
4546
|
function listRuns() {
|
|
4123
4547
|
const { runsDir } = getPaths();
|
|
4124
|
-
const rows = listRunIds(runsDir).map((id) => readJson(
|
|
4548
|
+
const rows = listRunIds(runsDir).map((id) => readJson(path19.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
|
|
4125
4549
|
id: run.id,
|
|
4126
4550
|
name: run.name,
|
|
4127
4551
|
status: run.status,
|
|
@@ -4136,7 +4560,7 @@ function failExists(message) {
|
|
|
4136
4560
|
}
|
|
4137
4561
|
|
|
4138
4562
|
// src/sweep.ts
|
|
4139
|
-
import
|
|
4563
|
+
import path20 from "node:path";
|
|
4140
4564
|
async function sweepRun(args) {
|
|
4141
4565
|
const pipeline = args.pipeline === true || args.pipeline === "true";
|
|
4142
4566
|
try {
|
|
@@ -4149,7 +4573,7 @@ async function sweepRun(args) {
|
|
|
4149
4573
|
const releasedLocalOrphans = [];
|
|
4150
4574
|
for (const name of Object.keys(run.workers || {})) {
|
|
4151
4575
|
const worker = readJson(
|
|
4152
|
-
|
|
4576
|
+
path20.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4153
4577
|
void 0
|
|
4154
4578
|
);
|
|
4155
4579
|
if (!worker || !worker.dispatched || !worker.taskId) continue;
|
|
@@ -4196,7 +4620,7 @@ import { mkdirSync as mkdirSync7, realpathSync } from "node:fs";
|
|
|
4196
4620
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
4197
4621
|
|
|
4198
4622
|
// src/pipeline-tick.ts
|
|
4199
|
-
import
|
|
4623
|
+
import path30 from "node:path";
|
|
4200
4624
|
|
|
4201
4625
|
// src/pipeline-dispatch.ts
|
|
4202
4626
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -4250,10 +4674,10 @@ async function runPipelineDispatch(args, slots) {
|
|
|
4250
4674
|
}
|
|
4251
4675
|
|
|
4252
4676
|
// src/stale-reconcile.ts
|
|
4253
|
-
import
|
|
4677
|
+
import path22 from "node:path";
|
|
4254
4678
|
|
|
4255
4679
|
// src/finalize.ts
|
|
4256
|
-
import
|
|
4680
|
+
import path21 from "node:path";
|
|
4257
4681
|
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
|
|
4258
4682
|
function terminalStatusFor(run) {
|
|
4259
4683
|
const names = Object.keys(run.workers || {});
|
|
@@ -4264,7 +4688,7 @@ function terminalStatusFor(run) {
|
|
|
4264
4688
|
let anyLandingBlocked = false;
|
|
4265
4689
|
for (const name of names) {
|
|
4266
4690
|
const worker = readJson(
|
|
4267
|
-
|
|
4691
|
+
path21.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4268
4692
|
void 0
|
|
4269
4693
|
);
|
|
4270
4694
|
if (!worker) continue;
|
|
@@ -4316,7 +4740,7 @@ function reconcileStaleWorkers() {
|
|
|
4316
4740
|
const now = Date.now();
|
|
4317
4741
|
for (const run of listRunRecords()) {
|
|
4318
4742
|
for (const name of Object.keys(run.workers || {})) {
|
|
4319
|
-
const workerPath =
|
|
4743
|
+
const workerPath = path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
4320
4744
|
const worker = readJson(workerPath, void 0);
|
|
4321
4745
|
if (!worker || worker.status !== "running") {
|
|
4322
4746
|
outcomes.push({
|
|
@@ -4410,7 +4834,7 @@ function reconcileRunsCli() {
|
|
|
4410
4834
|
}
|
|
4411
4835
|
|
|
4412
4836
|
// src/plan-progress-daemon-sync.ts
|
|
4413
|
-
import
|
|
4837
|
+
import path23 from "node:path";
|
|
4414
4838
|
|
|
4415
4839
|
// src/plan-progress-sync.ts
|
|
4416
4840
|
async function syncPlanProgress(args) {
|
|
@@ -4434,7 +4858,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
4434
4858
|
const outcomes = [];
|
|
4435
4859
|
for (const name of Object.keys(run.workers || {})) {
|
|
4436
4860
|
const worker = readJson(
|
|
4437
|
-
|
|
4861
|
+
path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4438
4862
|
void 0
|
|
4439
4863
|
);
|
|
4440
4864
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -4483,7 +4907,7 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
4483
4907
|
}
|
|
4484
4908
|
|
|
4485
4909
|
// src/cleanup.ts
|
|
4486
|
-
import
|
|
4910
|
+
import path28 from "node:path";
|
|
4487
4911
|
|
|
4488
4912
|
// src/cleanup-types.ts
|
|
4489
4913
|
var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
@@ -4555,11 +4979,11 @@ function skipNodeModulesRemoval(input) {
|
|
|
4555
4979
|
|
|
4556
4980
|
// src/cleanup-execute.ts
|
|
4557
4981
|
import { existsSync as existsSync16, rmSync } from "node:fs";
|
|
4558
|
-
import
|
|
4982
|
+
import path25 from "node:path";
|
|
4559
4983
|
|
|
4560
4984
|
// src/cleanup-dir-size.ts
|
|
4561
4985
|
import { existsSync as existsSync15, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
|
|
4562
|
-
import
|
|
4986
|
+
import path24 from "node:path";
|
|
4563
4987
|
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
4564
4988
|
if (!existsSync15(root)) return 0;
|
|
4565
4989
|
let total = 0;
|
|
@@ -4575,7 +4999,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
4575
4999
|
}
|
|
4576
5000
|
for (const name of entries) {
|
|
4577
5001
|
if (seen++ > maxEntries) return null;
|
|
4578
|
-
const full =
|
|
5002
|
+
const full = path24.join(current, name);
|
|
4579
5003
|
let st;
|
|
4580
5004
|
try {
|
|
4581
5005
|
st = statSync2(full);
|
|
@@ -4659,20 +5083,20 @@ function removeWorktree(candidate, execute) {
|
|
|
4659
5083
|
}
|
|
4660
5084
|
}
|
|
4661
5085
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
4662
|
-
const resolved =
|
|
4663
|
-
const nm = resolved.endsWith(`${
|
|
5086
|
+
const resolved = path25.resolve(targetPath);
|
|
5087
|
+
const nm = resolved.endsWith(`${path25.sep}node_modules`) ? resolved : null;
|
|
4664
5088
|
if (!nm) return "path_outside_harness";
|
|
4665
|
-
const rel =
|
|
4666
|
-
if (rel.startsWith("..") ||
|
|
4667
|
-
const parts = rel.split(
|
|
5089
|
+
const rel = path25.relative(worktreesDir, nm);
|
|
5090
|
+
if (rel.startsWith("..") || path25.isAbsolute(rel)) return "path_outside_harness";
|
|
5091
|
+
const parts = rel.split(path25.sep);
|
|
4668
5092
|
if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
|
|
4669
|
-
if (!resolved.startsWith(
|
|
5093
|
+
if (!resolved.startsWith(path25.resolve(harnessRoot))) return "path_outside_harness";
|
|
4670
5094
|
return null;
|
|
4671
5095
|
}
|
|
4672
5096
|
|
|
4673
5097
|
// src/cleanup-scan.ts
|
|
4674
5098
|
import { existsSync as existsSync17, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
4675
|
-
import
|
|
5099
|
+
import path26 from "node:path";
|
|
4676
5100
|
function pathAgeMs(target, now) {
|
|
4677
5101
|
try {
|
|
4678
5102
|
const mtime = statSync3(target).mtimeMs;
|
|
@@ -4682,17 +5106,17 @@ function pathAgeMs(target, now) {
|
|
|
4682
5106
|
}
|
|
4683
5107
|
}
|
|
4684
5108
|
function isPathInside(child, parent) {
|
|
4685
|
-
const rel =
|
|
4686
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
5109
|
+
const rel = path26.relative(parent, child);
|
|
5110
|
+
return rel === "" || !rel.startsWith("..") && !path26.isAbsolute(rel);
|
|
4687
5111
|
}
|
|
4688
5112
|
function scanNodeModulesCandidates(opts) {
|
|
4689
5113
|
const candidates = [];
|
|
4690
5114
|
const seen = /* @__PURE__ */ new Set();
|
|
4691
5115
|
for (const entry of opts.index.values()) {
|
|
4692
5116
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
4693
|
-
const nm =
|
|
5117
|
+
const nm = path26.join(entry.worktreePath, "node_modules");
|
|
4694
5118
|
if (!existsSync17(nm)) continue;
|
|
4695
|
-
const resolved =
|
|
5119
|
+
const resolved = path26.resolve(nm);
|
|
4696
5120
|
if (seen.has(resolved)) continue;
|
|
4697
5121
|
seen.add(resolved);
|
|
4698
5122
|
candidates.push({
|
|
@@ -4708,13 +5132,13 @@ function scanNodeModulesCandidates(opts) {
|
|
|
4708
5132
|
if (!opts.includeOrphans || !existsSync17(opts.worktreesDir)) return candidates;
|
|
4709
5133
|
for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
|
|
4710
5134
|
if (!runEntry.isDirectory()) continue;
|
|
4711
|
-
const runPath =
|
|
5135
|
+
const runPath = path26.join(opts.worktreesDir, runEntry.name);
|
|
4712
5136
|
for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
|
|
4713
5137
|
if (!workerEntry.isDirectory()) continue;
|
|
4714
|
-
const worktreePath =
|
|
4715
|
-
const nm =
|
|
5138
|
+
const worktreePath = path26.join(runPath, workerEntry.name);
|
|
5139
|
+
const nm = path26.join(worktreePath, "node_modules");
|
|
4716
5140
|
if (!existsSync17(nm)) continue;
|
|
4717
|
-
const resolved =
|
|
5141
|
+
const resolved = path26.resolve(nm);
|
|
4718
5142
|
if (seen.has(resolved)) continue;
|
|
4719
5143
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
4720
5144
|
seen.add(resolved);
|
|
@@ -4754,17 +5178,17 @@ function scanWorktreeCandidates(opts) {
|
|
|
4754
5178
|
}
|
|
4755
5179
|
|
|
4756
5180
|
// src/cleanup-worktree-index.ts
|
|
4757
|
-
import
|
|
5181
|
+
import path27 from "node:path";
|
|
4758
5182
|
function buildWorktreeIndex() {
|
|
4759
5183
|
const index = /* @__PURE__ */ new Map();
|
|
4760
5184
|
for (const run of listRunRecords()) {
|
|
4761
5185
|
for (const name of Object.keys(run.workers || {})) {
|
|
4762
|
-
const workerPath =
|
|
5186
|
+
const workerPath = path27.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
4763
5187
|
const worker = readJson(workerPath, void 0);
|
|
4764
5188
|
if (!worker?.worktreePath) continue;
|
|
4765
5189
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
4766
|
-
index.set(
|
|
4767
|
-
worktreePath:
|
|
5190
|
+
index.set(path27.resolve(worker.worktreePath), {
|
|
5191
|
+
worktreePath: path27.resolve(worker.worktreePath),
|
|
4768
5192
|
runId: run.id,
|
|
4769
5193
|
workerName: name,
|
|
4770
5194
|
run,
|
|
@@ -4778,8 +5202,8 @@ function buildWorktreeIndex() {
|
|
|
4778
5202
|
|
|
4779
5203
|
// src/cleanup.ts
|
|
4780
5204
|
function resolveOptions(options = {}) {
|
|
4781
|
-
const harnessRoot = options.harnessRoot ?
|
|
4782
|
-
const { worktreesDir } = options.harnessRoot ? { worktreesDir:
|
|
5205
|
+
const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
|
|
5206
|
+
const { worktreesDir } = options.harnessRoot ? { worktreesDir: path28.join(harnessRoot, "worktrees") } : getHarnessPaths();
|
|
4783
5207
|
const execute = options.execute === true;
|
|
4784
5208
|
const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
|
|
4785
5209
|
const worktreesAgeMs = options.worktreesAgeMs ?? 0;
|
|
@@ -4823,7 +5247,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
4823
5247
|
actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
|
|
4824
5248
|
continue;
|
|
4825
5249
|
}
|
|
4826
|
-
const worktreePath =
|
|
5250
|
+
const worktreePath = path28.resolve(candidate.path, "..");
|
|
4827
5251
|
const indexed = index.get(worktreePath) ?? null;
|
|
4828
5252
|
const guardReason = skipNodeModulesRemoval({
|
|
4829
5253
|
indexed,
|
|
@@ -4839,7 +5263,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
4839
5263
|
actions.push(removeNodeModules(candidate, resolved.execute));
|
|
4840
5264
|
}
|
|
4841
5265
|
for (const candidate of scanWorktreeCandidates(scanOpts)) {
|
|
4842
|
-
const indexed = index.get(
|
|
5266
|
+
const indexed = index.get(path28.resolve(candidate.path)) ?? null;
|
|
4843
5267
|
const guardReason = skipWorktreeRemoval({
|
|
4844
5268
|
indexed,
|
|
4845
5269
|
includeOrphans: resolved.includeOrphans,
|
|
@@ -4902,13 +5326,62 @@ function isPipelineCleanupEnabled() {
|
|
|
4902
5326
|
return process.env.KYNVER_PIPELINE_CLEANUP !== "0";
|
|
4903
5327
|
}
|
|
4904
5328
|
|
|
5329
|
+
// src/installed-package-versions.ts
|
|
5330
|
+
import { readFile } from "node:fs/promises";
|
|
5331
|
+
import { homedir as homedir5 } from "node:os";
|
|
5332
|
+
import path29 from "node:path";
|
|
5333
|
+
var MANAGED_PACKAGES = [
|
|
5334
|
+
"@kynver-app/runtime",
|
|
5335
|
+
"@kynver-app/openclaw-agent-os",
|
|
5336
|
+
"@kynver-app/mcp-agent-os"
|
|
5337
|
+
];
|
|
5338
|
+
function trim(value) {
|
|
5339
|
+
const out = value?.trim();
|
|
5340
|
+
return out ? out : null;
|
|
5341
|
+
}
|
|
5342
|
+
function unique(values) {
|
|
5343
|
+
return [...new Set(values.filter((value) => Boolean(value)))];
|
|
5344
|
+
}
|
|
5345
|
+
function moduleRoots() {
|
|
5346
|
+
const home = homedir5();
|
|
5347
|
+
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path29.join(home, ".openclaw", "npm");
|
|
5348
|
+
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path29.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path29.join(home, ".npm-global", "lib", "node_modules"));
|
|
5349
|
+
return unique([
|
|
5350
|
+
path29.join(openClawPrefix, "lib", "node_modules"),
|
|
5351
|
+
path29.join(openClawPrefix, "node_modules"),
|
|
5352
|
+
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path29.join(npmGlobalRoot, "lib", "node_modules")
|
|
5353
|
+
]);
|
|
5354
|
+
}
|
|
5355
|
+
async function readVersion(packageJsonPath) {
|
|
5356
|
+
try {
|
|
5357
|
+
const parsed = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
5358
|
+
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : null;
|
|
5359
|
+
} catch {
|
|
5360
|
+
return null;
|
|
5361
|
+
}
|
|
5362
|
+
}
|
|
5363
|
+
async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
5364
|
+
const roots = moduleRoots();
|
|
5365
|
+
const out = {};
|
|
5366
|
+
for (const packageName of MANAGED_PACKAGES) {
|
|
5367
|
+
for (const root of roots) {
|
|
5368
|
+
const packageJsonPath = path29.join(root, packageName, "package.json");
|
|
5369
|
+
const version = await readVersion(packageJsonPath);
|
|
5370
|
+
if (!version) continue;
|
|
5371
|
+
out[packageName] = { version, observedAt, path: packageJsonPath };
|
|
5372
|
+
break;
|
|
5373
|
+
}
|
|
5374
|
+
}
|
|
5375
|
+
return out;
|
|
5376
|
+
}
|
|
5377
|
+
|
|
4905
5378
|
// src/pipeline-tick.ts
|
|
4906
5379
|
async function completeFinishedWorkers(runId, args) {
|
|
4907
5380
|
const run = loadRun(runId);
|
|
4908
5381
|
const outcomes = [];
|
|
4909
5382
|
for (const name of Object.keys(run.workers || {})) {
|
|
4910
5383
|
const worker = readJson(
|
|
4911
|
-
|
|
5384
|
+
path30.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4912
5385
|
void 0
|
|
4913
5386
|
);
|
|
4914
5387
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -4939,12 +5412,14 @@ async function postOperatorTick(agentOsId, runId, resourceGate, args) {
|
|
|
4939
5412
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
4940
5413
|
const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, { baseUrl: base });
|
|
4941
5414
|
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/operator/tick`;
|
|
5415
|
+
const packageVersions = await collectInstalledPackageVersions();
|
|
4942
5416
|
const res = await postJson(url, secret, {
|
|
4943
5417
|
agentOsId,
|
|
4944
5418
|
runId,
|
|
4945
5419
|
ingestHarness: true,
|
|
4946
5420
|
harnessBoardSnapshot: buildRunBoard(runId),
|
|
4947
|
-
resourceGate
|
|
5421
|
+
resourceGate,
|
|
5422
|
+
packageVersions
|
|
4948
5423
|
});
|
|
4949
5424
|
return { ok: res.ok, httpStatus: res.status, response: res.response };
|
|
4950
5425
|
}
|
|
@@ -5047,6 +5522,231 @@ async function runDaemon(args) {
|
|
|
5047
5522
|
console.error(JSON.stringify({ event: "daemon_stop", runId, agentOsId }));
|
|
5048
5523
|
}
|
|
5049
5524
|
|
|
5525
|
+
// src/plan-progress.ts
|
|
5526
|
+
import path31 from "node:path";
|
|
5527
|
+
|
|
5528
|
+
// src/bounded-build/constants.ts
|
|
5529
|
+
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
5530
|
+
var DEFAULT_BUILD_MEM_RESERVE_BYTES = 2 * 1024 * 1024 * 1024;
|
|
5531
|
+
var DEFAULT_NODE_OLD_SPACE_SIZE_MB = 1024;
|
|
5532
|
+
var DEFAULT_SYSTEMD_MEMORY_MAX = "1.5G";
|
|
5533
|
+
var DEFAULT_SYSTEMD_MEMORY_SWAP_MAX = "2G";
|
|
5534
|
+
|
|
5535
|
+
// src/bounded-build/node-options.ts
|
|
5536
|
+
var MAX_OLD_SPACE_RE = /--max-old-space-size=(\d+)/;
|
|
5537
|
+
function parsePositiveInt(value, fallback) {
|
|
5538
|
+
const n = Number(value);
|
|
5539
|
+
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
5540
|
+
return Math.floor(n);
|
|
5541
|
+
}
|
|
5542
|
+
function resolveNodeOldSpaceSizeMb() {
|
|
5543
|
+
return parsePositiveInt(process.env.KYNVER_NODE_OLD_SPACE_SIZE_MB, DEFAULT_NODE_OLD_SPACE_SIZE_MB);
|
|
5544
|
+
}
|
|
5545
|
+
function mergeNodeOptionsForBuildCheck(baseEnv = process.env) {
|
|
5546
|
+
const env = { ...baseEnv };
|
|
5547
|
+
if (process.env.KYNVER_NODE_OLD_SPACE_SIZE_MB === "0") {
|
|
5548
|
+
return env;
|
|
5549
|
+
}
|
|
5550
|
+
const existing = env.NODE_OPTIONS ?? "";
|
|
5551
|
+
if (MAX_OLD_SPACE_RE.test(existing)) {
|
|
5552
|
+
return env;
|
|
5553
|
+
}
|
|
5554
|
+
const mb = resolveNodeOldSpaceSizeMb();
|
|
5555
|
+
const flag = `--max-old-space-size=${mb}`;
|
|
5556
|
+
env.NODE_OPTIONS = existing.trim() ? `${existing.trim()} ${flag}` : flag;
|
|
5557
|
+
return env;
|
|
5558
|
+
}
|
|
5559
|
+
function formatNodeOptionsFlag(mb = resolveNodeOldSpaceSizeMb()) {
|
|
5560
|
+
return `--max-old-space-size=${mb}`;
|
|
5561
|
+
}
|
|
5562
|
+
|
|
5563
|
+
// src/bounded-build/systemd-wrap.ts
|
|
5564
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
5565
|
+
var systemdAvailableCache;
|
|
5566
|
+
function isSystemdRunAvailable() {
|
|
5567
|
+
if (process.env.KYNVER_BUILD_SKIP_SYSTEMD === "1" || process.env.KYNVER_BUILD_SKIP_SYSTEMD === "true") {
|
|
5568
|
+
return false;
|
|
5569
|
+
}
|
|
5570
|
+
if (systemdAvailableCache !== void 0) return systemdAvailableCache;
|
|
5571
|
+
if (process.platform !== "linux") {
|
|
5572
|
+
systemdAvailableCache = false;
|
|
5573
|
+
return false;
|
|
5574
|
+
}
|
|
5575
|
+
const res = spawnSync3("systemd-run", ["--version"], { encoding: "utf8", stdio: ["ignore", "ignore", "pipe"] });
|
|
5576
|
+
systemdAvailableCache = res.status === 0;
|
|
5577
|
+
return systemdAvailableCache;
|
|
5578
|
+
}
|
|
5579
|
+
function buildSystemdRunArgv(opts) {
|
|
5580
|
+
const memoryMax = opts.memoryMax ?? process.env.KYNVER_BUILD_SYSTEMD_MEMORY_MAX ?? DEFAULT_SYSTEMD_MEMORY_MAX;
|
|
5581
|
+
const memorySwapMax = opts.memorySwapMax ?? process.env.KYNVER_BUILD_SYSTEMD_MEMORY_SWAP_MAX ?? DEFAULT_SYSTEMD_MEMORY_SWAP_MAX;
|
|
5582
|
+
const argv = [
|
|
5583
|
+
"systemd-run",
|
|
5584
|
+
"--scope",
|
|
5585
|
+
"--collect",
|
|
5586
|
+
"-p",
|
|
5587
|
+
`MemoryMax=${memoryMax}`,
|
|
5588
|
+
"-p",
|
|
5589
|
+
`MemorySwapMax=${memorySwapMax}`
|
|
5590
|
+
];
|
|
5591
|
+
if (opts.cwd) {
|
|
5592
|
+
argv.push("--working-directory", opts.cwd);
|
|
5593
|
+
}
|
|
5594
|
+
argv.push("--", ...opts.command);
|
|
5595
|
+
return argv;
|
|
5596
|
+
}
|
|
5597
|
+
|
|
5598
|
+
// src/bounded-build/admission.ts
|
|
5599
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
5600
|
+
function positiveInt3(value, fallback) {
|
|
5601
|
+
const n = Number(value);
|
|
5602
|
+
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
5603
|
+
return Math.floor(n);
|
|
5604
|
+
}
|
|
5605
|
+
function resolveBuildAdmissionConfig(config = loadUserConfig()) {
|
|
5606
|
+
const envBudget = process.env.KYNVER_BUILD_MEM_BUDGET_BYTES ? positiveInt3(process.env.KYNVER_BUILD_MEM_BUDGET_BYTES, DEFAULT_BUILD_MEM_BUDGET_BYTES) : void 0;
|
|
5607
|
+
const envReserve = process.env.KYNVER_BUILD_MEM_RESERVE_BYTES ? positiveInt3(process.env.KYNVER_BUILD_MEM_RESERVE_BYTES, DEFAULT_BUILD_MEM_RESERVE_BYTES) : void 0;
|
|
5608
|
+
return {
|
|
5609
|
+
perBuildBudgetBytes: envBudget ?? positiveInt3(config.perWorkerMemBytes, DEFAULT_BUILD_MEM_BUDGET_BYTES),
|
|
5610
|
+
reserveBytes: envReserve ?? positiveInt3(config.memReserveBytes, DEFAULT_BUILD_MEM_RESERVE_BYTES)
|
|
5611
|
+
};
|
|
5612
|
+
}
|
|
5613
|
+
var activeBuilds = 0;
|
|
5614
|
+
function registerBuildStart() {
|
|
5615
|
+
activeBuilds += 1;
|
|
5616
|
+
}
|
|
5617
|
+
function registerBuildEnd() {
|
|
5618
|
+
activeBuilds = Math.max(0, activeBuilds - 1);
|
|
5619
|
+
}
|
|
5620
|
+
function assessBuildAdmission(opts = {}) {
|
|
5621
|
+
const cfg = { ...resolveBuildAdmissionConfig(), ...opts };
|
|
5622
|
+
const memAvailableBytes = opts.memAvailableBytes ?? readMemAvailableBytes();
|
|
5623
|
+
const requiredBytes = cfg.perBuildBudgetBytes + cfg.reserveBytes;
|
|
5624
|
+
const admitted = memAvailableBytes >= requiredBytes;
|
|
5625
|
+
return {
|
|
5626
|
+
admitted,
|
|
5627
|
+
memAvailableBytes,
|
|
5628
|
+
requiredBytes,
|
|
5629
|
+
activeBuilds,
|
|
5630
|
+
reason: admitted ? null : `insufficient memory: need ${requiredBytes} bytes available (budget ${cfg.perBuildBudgetBytes} + reserve ${cfg.reserveBytes}), have ${memAvailableBytes}`
|
|
5631
|
+
};
|
|
5632
|
+
}
|
|
5633
|
+
function sleepMs2(ms) {
|
|
5634
|
+
if (ms <= 0) return;
|
|
5635
|
+
spawnSync4(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
|
|
5636
|
+
stdio: "ignore"
|
|
5637
|
+
});
|
|
5638
|
+
}
|
|
5639
|
+
function waitForBuildAdmission(timeoutMs, pollMs = 2e3, opts = {}) {
|
|
5640
|
+
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
5641
|
+
let verdict = assessBuildAdmission({
|
|
5642
|
+
...opts,
|
|
5643
|
+
memAvailableBytes: opts.memAvailableBytes?.()
|
|
5644
|
+
});
|
|
5645
|
+
while (!verdict.admitted && Date.now() < deadline) {
|
|
5646
|
+
sleepMs2(Math.min(pollMs, deadline - Date.now()));
|
|
5647
|
+
verdict = assessBuildAdmission({
|
|
5648
|
+
...opts,
|
|
5649
|
+
memAvailableBytes: opts.memAvailableBytes?.()
|
|
5650
|
+
});
|
|
5651
|
+
}
|
|
5652
|
+
return verdict;
|
|
5653
|
+
}
|
|
5654
|
+
|
|
5655
|
+
// src/bounded-build/exec.ts
|
|
5656
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
5657
|
+
function envArgv(env) {
|
|
5658
|
+
const out = [];
|
|
5659
|
+
for (const [key, value] of Object.entries(env)) {
|
|
5660
|
+
if (value === void 0) continue;
|
|
5661
|
+
out.push(`${key}=${value}`);
|
|
5662
|
+
}
|
|
5663
|
+
return out;
|
|
5664
|
+
}
|
|
5665
|
+
function runSpawn(argv, opts) {
|
|
5666
|
+
const res = spawnSync5(argv[0], argv.slice(1), {
|
|
5667
|
+
cwd: opts.cwd,
|
|
5668
|
+
env: opts.env,
|
|
5669
|
+
encoding: "utf8",
|
|
5670
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5671
|
+
shell: opts.shell,
|
|
5672
|
+
timeout: opts.timeoutMs
|
|
5673
|
+
});
|
|
5674
|
+
return {
|
|
5675
|
+
exitCode: res.status ?? 1,
|
|
5676
|
+
stdout: (res.stdout ?? "").trim(),
|
|
5677
|
+
stderr: (res.stderr ?? "").trim()
|
|
5678
|
+
};
|
|
5679
|
+
}
|
|
5680
|
+
function runBoundedBuildCheck(input) {
|
|
5681
|
+
const waitMs = input.waitForAdmissionMs ?? 6e5;
|
|
5682
|
+
const admission = waitMs > 0 ? waitForBuildAdmission(waitMs) : assessBuildAdmission();
|
|
5683
|
+
if (!admission.admitted) {
|
|
5684
|
+
return {
|
|
5685
|
+
ok: false,
|
|
5686
|
+
exitCode: 1,
|
|
5687
|
+
stdout: "",
|
|
5688
|
+
stderr: admission.reason ?? "build admission denied",
|
|
5689
|
+
admitted: false,
|
|
5690
|
+
wrappedWithSystemd: false,
|
|
5691
|
+
nodeOptionsFlag: formatNodeOptionsFlag(),
|
|
5692
|
+
admission,
|
|
5693
|
+
command: input.command
|
|
5694
|
+
};
|
|
5695
|
+
}
|
|
5696
|
+
const env = mergeNodeOptionsForBuildCheck({ ...process.env, ...input.env });
|
|
5697
|
+
const nodeOptionsFlag = formatNodeOptionsFlag();
|
|
5698
|
+
const useSystemd = isSystemdRunAvailable();
|
|
5699
|
+
registerBuildStart();
|
|
5700
|
+
try {
|
|
5701
|
+
let result;
|
|
5702
|
+
if (useSystemd) {
|
|
5703
|
+
const argv = buildSystemdRunArgv({
|
|
5704
|
+
cwd: input.cwd,
|
|
5705
|
+
command: ["/usr/bin/env", ...envArgv(env), "/bin/bash", "-lc", input.command]
|
|
5706
|
+
});
|
|
5707
|
+
result = runSpawn(argv, { cwd: input.cwd, env, timeoutMs: input.timeoutMs });
|
|
5708
|
+
} else {
|
|
5709
|
+
result = runSpawn([input.command], {
|
|
5710
|
+
cwd: input.cwd,
|
|
5711
|
+
env,
|
|
5712
|
+
shell: true,
|
|
5713
|
+
timeoutMs: input.timeoutMs
|
|
5714
|
+
});
|
|
5715
|
+
}
|
|
5716
|
+
return {
|
|
5717
|
+
ok: result.exitCode === 0,
|
|
5718
|
+
exitCode: result.exitCode,
|
|
5719
|
+
stdout: result.stdout,
|
|
5720
|
+
stderr: result.stderr,
|
|
5721
|
+
admitted: true,
|
|
5722
|
+
wrappedWithSystemd: useSystemd,
|
|
5723
|
+
nodeOptionsFlag,
|
|
5724
|
+
admission,
|
|
5725
|
+
command: input.command
|
|
5726
|
+
};
|
|
5727
|
+
} finally {
|
|
5728
|
+
registerBuildEnd();
|
|
5729
|
+
}
|
|
5730
|
+
}
|
|
5731
|
+
|
|
5732
|
+
// src/harness-verify.ts
|
|
5733
|
+
var DEFAULT_HARNESS_VERIFY_COMMANDS = ["npm run typecheck", "npm run test"];
|
|
5734
|
+
function runHarnessVerifyCommands(cwd, commands = DEFAULT_HARNESS_VERIFY_COMMANDS, opts = {}) {
|
|
5735
|
+
const steps = [];
|
|
5736
|
+
let passed = true;
|
|
5737
|
+
for (const command of commands) {
|
|
5738
|
+
const result = runBoundedBuildCheck({
|
|
5739
|
+
cwd,
|
|
5740
|
+
command,
|
|
5741
|
+
waitForAdmissionMs: opts.waitForAdmissionMs,
|
|
5742
|
+
timeoutMs: opts.timeoutMs
|
|
5743
|
+
});
|
|
5744
|
+
steps.push({ command, result });
|
|
5745
|
+
if (!result.ok) passed = false;
|
|
5746
|
+
}
|
|
5747
|
+
return { passed, steps };
|
|
5748
|
+
}
|
|
5749
|
+
|
|
5050
5750
|
// src/plan-progress.ts
|
|
5051
5751
|
function parseEvidenceArg(raw) {
|
|
5052
5752
|
const idx = raw.indexOf(":");
|
|
@@ -5107,8 +5807,23 @@ async function emitPlanProgress(args) {
|
|
|
5107
5807
|
}
|
|
5108
5808
|
console.log(JSON.stringify(parsed, null, 2));
|
|
5109
5809
|
}
|
|
5810
|
+
function verifyPlanLocal(args) {
|
|
5811
|
+
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
5812
|
+
const cwd = path31.resolve(worktree);
|
|
5813
|
+
const summary = runHarnessVerifyCommands(cwd);
|
|
5814
|
+
const emitJson = args.json === true || args.json === "true";
|
|
5815
|
+
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
5816
|
+
if (emitJson) console.log(JSON.stringify(payload, null, 2));
|
|
5817
|
+
else console.log(summary.passed ? "local plan verify passed" : "local plan verify failed");
|
|
5818
|
+
if (!summary.passed) process.exit(1);
|
|
5819
|
+
}
|
|
5110
5820
|
async function verifyPlan(args) {
|
|
5111
5821
|
const planId = required(args.plan ? String(args.plan) : void 0, "plan");
|
|
5822
|
+
const localOnly = args.local === true || args.local === "true";
|
|
5823
|
+
if (localOnly) {
|
|
5824
|
+
verifyPlanLocal(args);
|
|
5825
|
+
return;
|
|
5826
|
+
}
|
|
5112
5827
|
const slug = loadUserConfig().agentOsSlug;
|
|
5113
5828
|
if (!slug) {
|
|
5114
5829
|
console.error("requires agentOsSlug in ~/.kynver/config.json for verify (session route)");
|
|
@@ -5142,6 +5857,52 @@ async function verifyPlan(args) {
|
|
|
5142
5857
|
console.log(JSON.stringify(parsed, null, 2));
|
|
5143
5858
|
}
|
|
5144
5859
|
|
|
5860
|
+
// src/harness-verify-cli.ts
|
|
5861
|
+
import path32 from "node:path";
|
|
5862
|
+
function runHarnessVerifyCli(args) {
|
|
5863
|
+
const cwd = path32.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
5864
|
+
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
5865
|
+
const commands = [];
|
|
5866
|
+
const rawCmd = args.command;
|
|
5867
|
+
if (Array.isArray(rawCmd)) {
|
|
5868
|
+
for (const c of rawCmd) commands.push(String(c));
|
|
5869
|
+
} else if (typeof rawCmd === "string") {
|
|
5870
|
+
commands.push(rawCmd);
|
|
5871
|
+
}
|
|
5872
|
+
const summary = runHarnessVerifyCommands(
|
|
5873
|
+
cwd,
|
|
5874
|
+
commands.length ? commands : DEFAULT_HARNESS_VERIFY_COMMANDS,
|
|
5875
|
+
{
|
|
5876
|
+
waitForAdmissionMs: args.waitForAdmissionMs ? Number(args.waitForAdmissionMs) : void 0,
|
|
5877
|
+
timeoutMs: args.timeoutMs ? Number(args.timeoutMs) : void 0
|
|
5878
|
+
}
|
|
5879
|
+
);
|
|
5880
|
+
const payload = {
|
|
5881
|
+
passed: summary.passed,
|
|
5882
|
+
worktree: cwd,
|
|
5883
|
+
steps: summary.steps.map((s) => ({
|
|
5884
|
+
command: s.command,
|
|
5885
|
+
ok: s.result.ok,
|
|
5886
|
+
exitCode: s.result.exitCode,
|
|
5887
|
+
admitted: s.result.admitted,
|
|
5888
|
+
wrappedWithSystemd: s.result.wrappedWithSystemd,
|
|
5889
|
+
nodeOptionsFlag: s.result.nodeOptionsFlag,
|
|
5890
|
+
admission: s.result.admission,
|
|
5891
|
+
stderr: s.result.stderr.slice(0, 4e3)
|
|
5892
|
+
}))
|
|
5893
|
+
};
|
|
5894
|
+
if (emitJson) {
|
|
5895
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
5896
|
+
} else {
|
|
5897
|
+
console.log(summary.passed ? "harness verify passed" : "harness verify failed");
|
|
5898
|
+
for (const step of payload.steps) {
|
|
5899
|
+
console.log(` ${step.ok ? "\u2713" : "\u2717"} ${step.command} (exit ${step.exitCode}, systemd=${step.wrappedWithSystemd})`);
|
|
5900
|
+
if (!step.ok && step.stderr) console.log(` ${step.stderr.split("\n")[0]}`);
|
|
5901
|
+
}
|
|
5902
|
+
}
|
|
5903
|
+
process.exit(summary.passed ? 0 : 1);
|
|
5904
|
+
}
|
|
5905
|
+
|
|
5145
5906
|
// src/plan-persist-cli.ts
|
|
5146
5907
|
import { readFileSync as readFileSync8 } from "node:fs";
|
|
5147
5908
|
var OPERATIONS = ["create", "add_version", "update_metadata"];
|
|
@@ -5243,7 +6004,7 @@ function runCleanupCli(args) {
|
|
|
5243
6004
|
}
|
|
5244
6005
|
|
|
5245
6006
|
// src/monitor/monitor.service.ts
|
|
5246
|
-
import
|
|
6007
|
+
import path34 from "node:path";
|
|
5247
6008
|
|
|
5248
6009
|
// src/monitor/monitor.classify.ts
|
|
5249
6010
|
function expectedLeaseOwner(runId) {
|
|
@@ -5300,10 +6061,10 @@ function classifyWorkerHealth(input) {
|
|
|
5300
6061
|
|
|
5301
6062
|
// src/monitor/monitor.store.ts
|
|
5302
6063
|
import { existsSync as existsSync18, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
|
|
5303
|
-
import
|
|
6064
|
+
import path33 from "node:path";
|
|
5304
6065
|
function monitorsDir() {
|
|
5305
6066
|
const { harnessRoot } = getHarnessPaths();
|
|
5306
|
-
const dir =
|
|
6067
|
+
const dir = path33.join(harnessRoot, "monitors");
|
|
5307
6068
|
mkdirSync6(dir, { recursive: true });
|
|
5308
6069
|
return dir;
|
|
5309
6070
|
}
|
|
@@ -5311,7 +6072,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
5311
6072
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
5312
6073
|
}
|
|
5313
6074
|
function monitorPath(monitorId) {
|
|
5314
|
-
return
|
|
6075
|
+
return path33.join(monitorsDir(), `${monitorId}.json`);
|
|
5315
6076
|
}
|
|
5316
6077
|
function loadMonitorSession(monitorId) {
|
|
5317
6078
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -5332,7 +6093,7 @@ function listMonitorSessions() {
|
|
|
5332
6093
|
for (const name of readdirSync7(dir)) {
|
|
5333
6094
|
if (!name.endsWith(".json")) continue;
|
|
5334
6095
|
const session = readJson(
|
|
5335
|
-
|
|
6096
|
+
path33.join(dir, name),
|
|
5336
6097
|
void 0
|
|
5337
6098
|
);
|
|
5338
6099
|
if (!session?.monitorId) continue;
|
|
@@ -5423,7 +6184,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
5423
6184
|
// src/monitor/monitor.service.ts
|
|
5424
6185
|
function workerRecord2(runId, name) {
|
|
5425
6186
|
return readJson(
|
|
5426
|
-
|
|
6187
|
+
path34.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
5427
6188
|
void 0
|
|
5428
6189
|
);
|
|
5429
6190
|
}
|
|
@@ -5626,17 +6387,17 @@ async function runMonitorLoop(args) {
|
|
|
5626
6387
|
// src/monitor/monitor-spawn.ts
|
|
5627
6388
|
import { spawn as spawn4 } from "node:child_process";
|
|
5628
6389
|
import { closeSync as closeSync4, existsSync as existsSync19, openSync as openSync4 } from "node:fs";
|
|
5629
|
-
import
|
|
6390
|
+
import path35 from "node:path";
|
|
5630
6391
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
5631
6392
|
function resolveDefaultCliPath2() {
|
|
5632
|
-
return
|
|
6393
|
+
return path35.join(fileURLToPath3(new URL(".", import.meta.url)), "..", "cli.js");
|
|
5633
6394
|
}
|
|
5634
6395
|
function spawnMonitorSidecar(opts) {
|
|
5635
6396
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
5636
6397
|
if (!existsSync19(cliPath)) return void 0;
|
|
5637
6398
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
5638
6399
|
const { harnessRoot } = getHarnessPaths();
|
|
5639
|
-
const logPath =
|
|
6400
|
+
const logPath = path35.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
5640
6401
|
let logFd;
|
|
5641
6402
|
try {
|
|
5642
6403
|
logFd = openSync4(logPath, "a");
|
|
@@ -5755,6 +6516,453 @@ async function monitorTickCli(args) {
|
|
|
5755
6516
|
console.log(JSON.stringify(tick, null, 2));
|
|
5756
6517
|
}
|
|
5757
6518
|
|
|
6519
|
+
// src/doctor/runtime-takeover.ts
|
|
6520
|
+
import path37 from "node:path";
|
|
6521
|
+
|
|
6522
|
+
// src/doctor/runtime-takeover.probes.ts
|
|
6523
|
+
import { accessSync, constants, existsSync as existsSync20, readFileSync as readFileSync9 } from "node:fs";
|
|
6524
|
+
import { homedir as homedir6 } from "node:os";
|
|
6525
|
+
import path36 from "node:path";
|
|
6526
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
6527
|
+
function captureCommand(bin, args) {
|
|
6528
|
+
try {
|
|
6529
|
+
const res = spawnSync6(bin, args, { encoding: "utf8" });
|
|
6530
|
+
const stdout = (res.stdout || "").trim();
|
|
6531
|
+
const stderr = (res.stderr || "").trim();
|
|
6532
|
+
const ok = res.status === 0;
|
|
6533
|
+
return {
|
|
6534
|
+
ok,
|
|
6535
|
+
stdout,
|
|
6536
|
+
stderr,
|
|
6537
|
+
error: res.error?.message
|
|
6538
|
+
};
|
|
6539
|
+
} catch (error) {
|
|
6540
|
+
return {
|
|
6541
|
+
ok: false,
|
|
6542
|
+
stdout: "",
|
|
6543
|
+
stderr: "",
|
|
6544
|
+
error: error.message
|
|
6545
|
+
};
|
|
6546
|
+
}
|
|
6547
|
+
}
|
|
6548
|
+
function tokenPrefix(token) {
|
|
6549
|
+
const trimmed = token?.trim();
|
|
6550
|
+
if (!trimmed) return void 0;
|
|
6551
|
+
return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
|
|
6552
|
+
}
|
|
6553
|
+
function isWritable(target) {
|
|
6554
|
+
if (!existsSync20(target)) return false;
|
|
6555
|
+
try {
|
|
6556
|
+
accessSync(target, constants.W_OK);
|
|
6557
|
+
return true;
|
|
6558
|
+
} catch {
|
|
6559
|
+
return false;
|
|
6560
|
+
}
|
|
6561
|
+
}
|
|
6562
|
+
var defaultRuntimeTakeoverProbes = {
|
|
6563
|
+
packageVersion: () => PACKAGE_VERSION,
|
|
6564
|
+
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
6565
|
+
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
6566
|
+
loadConfig: () => loadUserConfig(),
|
|
6567
|
+
configFilePath: () => path36.join(homedir6(), ".kynver", "config.json"),
|
|
6568
|
+
credentialsFilePath: () => path36.join(homedir6(), ".kynver", "credentials"),
|
|
6569
|
+
readCredentials: () => {
|
|
6570
|
+
const credPath = path36.join(homedir6(), ".kynver", "credentials");
|
|
6571
|
+
if (!existsSync20(credPath)) {
|
|
6572
|
+
return { hasApiKey: false };
|
|
6573
|
+
}
|
|
6574
|
+
try {
|
|
6575
|
+
const parsed = JSON.parse(readFileSync9(credPath, "utf8"));
|
|
6576
|
+
return {
|
|
6577
|
+
hasApiKey: Boolean(parsed.apiKey?.trim()),
|
|
6578
|
+
runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
|
|
6579
|
+
runnerTokenAgentOsId: parsed.runnerTokenAgentOsId
|
|
6580
|
+
};
|
|
6581
|
+
} catch {
|
|
6582
|
+
return { hasApiKey: false };
|
|
6583
|
+
}
|
|
6584
|
+
},
|
|
6585
|
+
envSnapshot: () => ({
|
|
6586
|
+
kynverApiUrl: process.env.KYNVER_API_URL?.trim() || void 0,
|
|
6587
|
+
openclawCronFireBaseUrl: process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || void 0,
|
|
6588
|
+
kynverRunnerTokenPrefix: tokenPrefix(process.env.KYNVER_RUNNER_TOKEN),
|
|
6589
|
+
kynverRuntimeSecret: Boolean(process.env.KYNVER_RUNTIME_SECRET?.trim()),
|
|
6590
|
+
openclawCronSecret: Boolean(process.env.OPENCLAW_CRON_SECRET?.trim()),
|
|
6591
|
+
kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
|
|
6592
|
+
opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
|
|
6593
|
+
kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0
|
|
6594
|
+
}),
|
|
6595
|
+
harnessRoot: () => resolveHarnessRoot(),
|
|
6596
|
+
legacyOpenclawHarnessRoot: () => path36.join(homedir6(), ".openclaw", "harness"),
|
|
6597
|
+
pathExists: (target) => existsSync20(target),
|
|
6598
|
+
pathWritable: (target) => isWritable(target),
|
|
6599
|
+
vercelVersion: () => captureCommand("vercel", ["--version"]),
|
|
6600
|
+
vercelWhoami: () => captureCommand("vercel", ["whoami"])
|
|
6601
|
+
};
|
|
6602
|
+
|
|
6603
|
+
// src/doctor/runtime-takeover.ts
|
|
6604
|
+
function check(partial) {
|
|
6605
|
+
return partial;
|
|
6606
|
+
}
|
|
6607
|
+
function summarizeCounts(sections) {
|
|
6608
|
+
const counts = { pass: 0, warn: 0, fail: 0 };
|
|
6609
|
+
for (const section of sections) {
|
|
6610
|
+
for (const item of section.checks) {
|
|
6611
|
+
counts[item.status] += 1;
|
|
6612
|
+
}
|
|
6613
|
+
}
|
|
6614
|
+
return counts;
|
|
6615
|
+
}
|
|
6616
|
+
function assessCliPackage(probes) {
|
|
6617
|
+
const runningVersion = probes.packageVersion();
|
|
6618
|
+
const which = probes.commandOnPath("kynver");
|
|
6619
|
+
const onPath = which.ok && which.stdout.length > 0;
|
|
6620
|
+
const firstPath = onPath ? which.stdout.split(/\r?\n/)[0]?.trim() : void 0;
|
|
6621
|
+
const displayCliPath = firstPath ? displayUserPath(firstPath) : void 0;
|
|
6622
|
+
const checks = [
|
|
6623
|
+
check({
|
|
6624
|
+
id: "cli_running_version",
|
|
6625
|
+
label: "Running @kynver-app/runtime version",
|
|
6626
|
+
status: "pass",
|
|
6627
|
+
summary: `@kynver-app/runtime ${runningVersion}`,
|
|
6628
|
+
details: { version: runningVersion }
|
|
6629
|
+
}),
|
|
6630
|
+
check({
|
|
6631
|
+
id: "cli_on_path",
|
|
6632
|
+
label: "kynver executable on PATH",
|
|
6633
|
+
status: onPath ? "pass" : "warn",
|
|
6634
|
+
summary: onPath ? `Found ${displayCliPath}` : "kynver not found on PATH (invoked via node/npx?)",
|
|
6635
|
+
remediation: onPath ? void 0 : "Install globally (`npm i -g @kynver-app/runtime`) or invoke via `npx @kynver-app/runtime`.",
|
|
6636
|
+
details: { path: displayCliPath }
|
|
6637
|
+
})
|
|
6638
|
+
];
|
|
6639
|
+
if (onPath && firstPath) {
|
|
6640
|
+
const versionProbe = probes.kynverVersion(firstPath);
|
|
6641
|
+
const installedVersion = versionProbe.stdout.replace(/^kynver\s+/i, "").trim() || void 0;
|
|
6642
|
+
const versionMatch = versionProbe.ok && (!installedVersion || installedVersion === runningVersion);
|
|
6643
|
+
checks.push(
|
|
6644
|
+
check({
|
|
6645
|
+
id: "cli_installed_version",
|
|
6646
|
+
label: "Installed kynver CLI version matches running package",
|
|
6647
|
+
status: versionMatch ? "pass" : "warn",
|
|
6648
|
+
summary: versionProbe.ok && installedVersion ? versionMatch ? `Installed ${installedVersion}` : `Installed ${installedVersion} differs from running ${runningVersion}` : versionProbe.error ? `Could not read installed version (${versionProbe.error})` : versionProbe.stderr || "Could not read installed version",
|
|
6649
|
+
remediation: versionMatch ? void 0 : "Reinstall or rebuild @kynver-app/runtime so PATH kynver matches the harness worktree version.",
|
|
6650
|
+
details: { installedVersion, runningVersion, path: displayCliPath }
|
|
6651
|
+
})
|
|
6652
|
+
);
|
|
6653
|
+
}
|
|
6654
|
+
return { id: "cli_package", label: "CLI / package", checks };
|
|
6655
|
+
}
|
|
6656
|
+
function assessUserConfig(probes) {
|
|
6657
|
+
const configPath = probes.configFilePath();
|
|
6658
|
+
const displayConfigPath = displayUserPath(configPath);
|
|
6659
|
+
const exists = probes.pathExists(configPath);
|
|
6660
|
+
const config = probes.loadConfig();
|
|
6661
|
+
const checks = [
|
|
6662
|
+
check({
|
|
6663
|
+
id: "config_file",
|
|
6664
|
+
label: "~/.kynver/config.json present",
|
|
6665
|
+
status: exists ? "pass" : "fail",
|
|
6666
|
+
summary: exists ? `Found ${displayConfigPath}` : `Missing ${displayConfigPath}`,
|
|
6667
|
+
remediation: exists ? void 0 : "Run `kynver setup --api-base-url <url> --agent-os-id <id> --repo <path>`.",
|
|
6668
|
+
details: { configPath: displayConfigPath }
|
|
6669
|
+
})
|
|
6670
|
+
];
|
|
6671
|
+
if (exists) {
|
|
6672
|
+
const apiBaseUrl = config.apiBaseUrl?.trim();
|
|
6673
|
+
const agentOsId = config.agentOsId?.trim();
|
|
6674
|
+
const defaultRepo = config.defaultRepo?.trim();
|
|
6675
|
+
const displayDefaultRepo = defaultRepo ? displayUserPath(defaultRepo) : null;
|
|
6676
|
+
checks.push(
|
|
6677
|
+
check({
|
|
6678
|
+
id: "config_api_base_url",
|
|
6679
|
+
label: "Default API base URL",
|
|
6680
|
+
status: apiBaseUrl ? "pass" : "warn",
|
|
6681
|
+
summary: apiBaseUrl ?? "Not set in config (KYNVER_API_URL / --base-url required per command)",
|
|
6682
|
+
remediation: apiBaseUrl ? void 0 : "Set `apiBaseUrl` via `kynver setup --api-base-url https://\u2026`.",
|
|
6683
|
+
details: { apiBaseUrl: apiBaseUrl ?? null }
|
|
6684
|
+
}),
|
|
6685
|
+
check({
|
|
6686
|
+
id: "config_agent_os_id",
|
|
6687
|
+
label: "Default AgentOS id",
|
|
6688
|
+
status: agentOsId ? "pass" : "warn",
|
|
6689
|
+
summary: agentOsId ?? "Not set (pass --agent-os-id on daemon/dispatch/worker commands)",
|
|
6690
|
+
remediation: agentOsId ? void 0 : "Set `agentOsId` via `kynver setup --agent-os-id <uuid>`.",
|
|
6691
|
+
details: { agentOsId: agentOsId ?? null, agentOsSlug: config.agentOsSlug ?? null }
|
|
6692
|
+
}),
|
|
6693
|
+
check({
|
|
6694
|
+
id: "config_default_repo",
|
|
6695
|
+
label: "Default repo path",
|
|
6696
|
+
status: defaultRepo ? "pass" : "warn",
|
|
6697
|
+
summary: displayDefaultRepo ?? "Not set (pass --repo on `kynver run create`)",
|
|
6698
|
+
remediation: defaultRepo ? void 0 : "Set `defaultRepo` via `kynver setup --repo /path/to/repo`.",
|
|
6699
|
+
details: {
|
|
6700
|
+
defaultRepo: displayDefaultRepo,
|
|
6701
|
+
harnessRoot: config.harnessRoot ? redactHomePath(config.harnessRoot) : null
|
|
6702
|
+
}
|
|
6703
|
+
})
|
|
6704
|
+
);
|
|
6705
|
+
}
|
|
6706
|
+
return { id: "user_config", label: "User config (~/.kynver)", checks };
|
|
6707
|
+
}
|
|
6708
|
+
function assessRunnerToken(probes) {
|
|
6709
|
+
const config = probes.loadConfig();
|
|
6710
|
+
const targetAgentOsId = config.agentOsId?.trim();
|
|
6711
|
+
const creds = probes.readCredentials();
|
|
6712
|
+
const env = probes.envSnapshot();
|
|
6713
|
+
const credPath = probes.credentialsFilePath();
|
|
6714
|
+
const displayCredPath = displayUserPath(credPath);
|
|
6715
|
+
const envToken = env.kynverRunnerTokenPrefix;
|
|
6716
|
+
const savedToken = creds.runnerTokenPrefix;
|
|
6717
|
+
const savedAgentOsId = creds.runnerTokenAgentOsId;
|
|
6718
|
+
const scopedSaved = Boolean(savedToken) && (!targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId);
|
|
6719
|
+
const hasScoped = Boolean(envToken?.startsWith("krc1.")) || scopedSaved && savedToken?.startsWith("krc1.");
|
|
6720
|
+
const checks = [
|
|
6721
|
+
check({
|
|
6722
|
+
id: "runner_token_scoped",
|
|
6723
|
+
label: "Scoped runner token (krc1.*) ready",
|
|
6724
|
+
status: hasScoped ? "pass" : "fail",
|
|
6725
|
+
summary: hasScoped ? envToken ? "KYNVER_RUNNER_TOKEN is set (scoped prefix shown)" : `Saved scoped token for agentOsId ${savedAgentOsId ?? targetAgentOsId ?? "(unknown)"}` : "No scoped runner token for the configured AgentOS workspace",
|
|
6726
|
+
remediation: hasScoped ? void 0 : "Run `kynver login` then `kynver runner credential --agent-os-id <id>` (or `kynver setup` with API key).",
|
|
6727
|
+
details: {
|
|
6728
|
+
source: envToken ? "env" : savedToken ? "credentials" : "none",
|
|
6729
|
+
tokenPrefix: envToken ?? (scopedSaved ? savedToken : void 0),
|
|
6730
|
+
agentOsId: targetAgentOsId ?? savedAgentOsId ?? null,
|
|
6731
|
+
credentialsPath: displayCredPath
|
|
6732
|
+
}
|
|
6733
|
+
}),
|
|
6734
|
+
check({
|
|
6735
|
+
id: "runner_token_agent_os_match",
|
|
6736
|
+
label: "Saved runner token matches configured agentOsId",
|
|
6737
|
+
status: !savedToken || !targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId ? "pass" : "warn",
|
|
6738
|
+
summary: !savedToken || !savedAgentOsId ? "No saved token agentOsId to compare" : !targetAgentOsId ? "Config agentOsId unset \u2014 token scope not validated against config" : savedAgentOsId === targetAgentOsId ? "Saved token scoped to configured agentOsId" : `Saved token is for ${savedAgentOsId}, config expects ${targetAgentOsId}`,
|
|
6739
|
+
remediation: savedToken && targetAgentOsId && savedAgentOsId && savedAgentOsId !== targetAgentOsId ? "`kynver runner credential --agent-os-id <configured-id>` to mint a workspace-bound token." : void 0,
|
|
6740
|
+
details: { configuredAgentOsId: targetAgentOsId ?? null, savedAgentOsId: savedAgentOsId ?? null }
|
|
6741
|
+
}),
|
|
6742
|
+
check({
|
|
6743
|
+
id: "runner_api_key_for_refresh",
|
|
6744
|
+
label: "API key available for token refresh",
|
|
6745
|
+
status: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY?.trim()) ? "pass" : "warn",
|
|
6746
|
+
summary: creds.hasApiKey || process.env.KYNVER_API_KEY ? "KYNVER API key present for runner credential mint/refresh" : "No API key \u2014 401 callback recovery cannot auto-mint a replacement token",
|
|
6747
|
+
remediation: creds.hasApiKey || process.env.KYNVER_API_KEY ? void 0 : "Run `kynver login --api-key \u2026`.",
|
|
6748
|
+
details: { credentialsPath: displayCredPath, hasApiKey: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY) }
|
|
6749
|
+
})
|
|
6750
|
+
];
|
|
6751
|
+
return { id: "runner_token", label: "Runner token readiness", checks };
|
|
6752
|
+
}
|
|
6753
|
+
function assessVercelCli(probes) {
|
|
6754
|
+
const version = probes.vercelVersion();
|
|
6755
|
+
const whoami = probes.vercelWhoami();
|
|
6756
|
+
const installed = version.ok;
|
|
6757
|
+
return {
|
|
6758
|
+
id: "vercel_cli",
|
|
6759
|
+
label: "Vercel CLI",
|
|
6760
|
+
checks: [
|
|
6761
|
+
check({
|
|
6762
|
+
id: "vercel_installed",
|
|
6763
|
+
label: "Vercel CLI installed",
|
|
6764
|
+
status: installed ? "pass" : "warn",
|
|
6765
|
+
summary: installed ? version.stdout || "vercel CLI found" : version.error ?? "vercel not found on PATH",
|
|
6766
|
+
remediation: installed ? void 0 : "Install Vercel CLI (`npm i -g vercel`) for deploy evidence and env pulls.",
|
|
6767
|
+
details: { stderr: version.stderr || null }
|
|
6768
|
+
}),
|
|
6769
|
+
check({
|
|
6770
|
+
id: "vercel_authenticated",
|
|
6771
|
+
label: "Vercel CLI authenticated",
|
|
6772
|
+
status: !installed ? "warn" : whoami.ok ? "pass" : "warn",
|
|
6773
|
+
summary: !installed ? "Skipped \u2014 Vercel CLI not installed" : whoami.ok ? `Authenticated as ${whoami.stdout}` : whoami.stderr || whoami.error || "Not logged in",
|
|
6774
|
+
remediation: installed && !whoami.ok ? "Run `vercel login` and link the Kynver project if needed." : void 0,
|
|
6775
|
+
details: { account: whoami.ok ? whoami.stdout : null }
|
|
6776
|
+
})
|
|
6777
|
+
]
|
|
6778
|
+
};
|
|
6779
|
+
}
|
|
6780
|
+
function assessHarnessDirs(probes) {
|
|
6781
|
+
const harnessRoot = probes.harnessRoot();
|
|
6782
|
+
const runsDir = path37.join(harnessRoot, "runs");
|
|
6783
|
+
const worktreesDir = path37.join(harnessRoot, "worktrees");
|
|
6784
|
+
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
6785
|
+
const displayRunsDir = redactHomePath(runsDir);
|
|
6786
|
+
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
6787
|
+
const runsExists = probes.pathExists(runsDir);
|
|
6788
|
+
const worktreesExists = probes.pathExists(worktreesDir);
|
|
6789
|
+
return {
|
|
6790
|
+
id: "harness_dirs",
|
|
6791
|
+
label: "Harness / daemon directories",
|
|
6792
|
+
checks: [
|
|
6793
|
+
check({
|
|
6794
|
+
id: "harness_root",
|
|
6795
|
+
label: "Harness root resolved",
|
|
6796
|
+
status: "pass",
|
|
6797
|
+
summary: displayHarnessRoot,
|
|
6798
|
+
details: { harnessRoot: displayHarnessRoot }
|
|
6799
|
+
}),
|
|
6800
|
+
check({
|
|
6801
|
+
id: "harness_runs_dir",
|
|
6802
|
+
label: "Runs directory ready",
|
|
6803
|
+
status: runsExists && probes.pathWritable(runsDir) ? "pass" : runsExists ? "warn" : "warn",
|
|
6804
|
+
summary: runsExists ? probes.pathWritable(runsDir) ? `Writable ${displayRunsDir}` : `Exists but not writable: ${displayRunsDir}` : `Will be created on first run: ${displayRunsDir}`,
|
|
6805
|
+
remediation: runsExists && !probes.pathWritable(runsDir) ? `Fix permissions on ${displayRunsDir} or set KYNVER_HARNESS_ROOT to a writable path.` : void 0,
|
|
6806
|
+
details: { runsDir: displayRunsDir, exists: runsExists, writable: probes.pathWritable(runsDir) }
|
|
6807
|
+
}),
|
|
6808
|
+
check({
|
|
6809
|
+
id: "harness_worktrees_dir",
|
|
6810
|
+
label: "Worktrees directory ready",
|
|
6811
|
+
status: worktreesExists && probes.pathWritable(worktreesDir) ? "pass" : "warn",
|
|
6812
|
+
summary: worktreesExists ? probes.pathWritable(worktreesDir) ? `Writable ${displayWorktreesDir}` : `Exists but not writable: ${displayWorktreesDir}` : `Will be created on first worker: ${displayWorktreesDir}`,
|
|
6813
|
+
remediation: worktreesExists && !probes.pathWritable(worktreesDir) ? `Fix permissions on ${displayWorktreesDir}.` : void 0,
|
|
6814
|
+
details: { worktreesDir: displayWorktreesDir, exists: worktreesExists, writable: probes.pathWritable(worktreesDir) }
|
|
6815
|
+
})
|
|
6816
|
+
]
|
|
6817
|
+
};
|
|
6818
|
+
}
|
|
6819
|
+
function assessCallbackAuth(probes) {
|
|
6820
|
+
const config = probes.loadConfig();
|
|
6821
|
+
const env = probes.envSnapshot();
|
|
6822
|
+
const baseUrl = env.kynverApiUrl ?? config.apiBaseUrl?.trim() ?? env.openclawCronFireBaseUrl;
|
|
6823
|
+
const usingLegacyBase = !env.kynverApiUrl && !config.apiBaseUrl && Boolean(env.openclawCronFireBaseUrl);
|
|
6824
|
+
const legacySecret = env.openclawCronSecret || env.kynverRuntimeSecret;
|
|
6825
|
+
const envScoped = env.kynverRunnerTokenPrefix?.startsWith("krc1.");
|
|
6826
|
+
const creds = probes.readCredentials();
|
|
6827
|
+
const savedScoped = creds.runnerTokenPrefix?.startsWith("krc1.");
|
|
6828
|
+
const checks = [
|
|
6829
|
+
check({
|
|
6830
|
+
id: "callback_base_url",
|
|
6831
|
+
label: "Callback base URL configured",
|
|
6832
|
+
status: baseUrl ? usingLegacyBase ? "warn" : "pass" : "fail",
|
|
6833
|
+
summary: baseUrl ? usingLegacyBase ? `Using legacy OPENCLAW_CRON_FIRE_BASE_URL (${baseUrl})` : baseUrl : "No KYNVER_API_URL, config apiBaseUrl, or legacy OpenClaw base URL",
|
|
6834
|
+
remediation: baseUrl ? usingLegacyBase ? "Migrate to KYNVER_API_URL or `kynver setup --api-base-url` and drop OPENCLAW_CRON_FIRE_BASE_URL." : void 0 : "Set KYNVER_API_URL or run `kynver setup --api-base-url https://\u2026`.",
|
|
6835
|
+
details: {
|
|
6836
|
+
source: env.kynverApiUrl ? "KYNVER_API_URL" : config.apiBaseUrl ? "config.apiBaseUrl" : env.openclawCronFireBaseUrl ? "OPENCLAW_CRON_FIRE_BASE_URL" : "none",
|
|
6837
|
+
baseUrl: baseUrl ?? null
|
|
6838
|
+
}
|
|
6839
|
+
}),
|
|
6840
|
+
check({
|
|
6841
|
+
id: "callback_auth_mode",
|
|
6842
|
+
label: "Callback auth uses scoped runner token",
|
|
6843
|
+
status: envScoped || savedScoped ? "pass" : legacySecret ? "warn" : "fail",
|
|
6844
|
+
summary: envScoped ? "KYNVER_RUNNER_TOKEN scoped token will be sent as X-Kynver-Runner-Token" : savedScoped ? "Saved krc1.* token in ~/.kynver/credentials" : legacySecret ? "Deployment-level KYNVER_RUNTIME_SECRET / OPENCLAW_CRON_SECRET in use (legacy headers)" : "No callback secret configured",
|
|
6845
|
+
remediation: envScoped || savedScoped ? void 0 : "Mint a scoped token: `kynver runner credential --agent-os-id <id>`. Avoid shared OPENCLAW_CRON_SECRET on user runners.",
|
|
6846
|
+
details: {
|
|
6847
|
+
mode: envScoped || savedScoped ? "scoped" : legacySecret ? "legacy_global_secret" : "missing",
|
|
6848
|
+
legacyHeadersWhenNotScoped: ["X-OpenClaw-Cron-Secret", "X-Kynver-Runtime-Secret"]
|
|
6849
|
+
}
|
|
6850
|
+
})
|
|
6851
|
+
];
|
|
6852
|
+
return { id: "callback_auth", label: "Callback auth / config", checks };
|
|
6853
|
+
}
|
|
6854
|
+
function assessOpenclawHotspots(probes) {
|
|
6855
|
+
const env = probes.envSnapshot();
|
|
6856
|
+
const harnessRoot = probes.harnessRoot();
|
|
6857
|
+
const legacyRoot = probes.legacyOpenclawHarnessRoot();
|
|
6858
|
+
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
6859
|
+
const displayLegacyRoot = redactHomePath(legacyRoot);
|
|
6860
|
+
const displayOpusHarnessRoot = env.opusHarnessRoot ? redactHomePath(env.opusHarnessRoot) : null;
|
|
6861
|
+
const legacyHarnessActive = harnessRoot === legacyRoot && probes.pathExists(legacyRoot);
|
|
6862
|
+
const schedulerOpenclaw = !env.kynverSchedulerProvider || env.kynverSchedulerProvider === "openclaw-cron";
|
|
6863
|
+
const checks = [
|
|
6864
|
+
check({
|
|
6865
|
+
id: "hotspot_legacy_harness_root",
|
|
6866
|
+
label: "Legacy ~/.openclaw/harness still active",
|
|
6867
|
+
status: legacyHarnessActive ? "warn" : "pass",
|
|
6868
|
+
summary: legacyHarnessActive ? `Harness root is legacy ${displayLegacyRoot}` : env.opusHarnessRoot ? `OPUS_HARNESS_ROOT override in use (${displayOpusHarnessRoot})` : `Using ${displayHarnessRoot}`,
|
|
6869
|
+
remediation: legacyHarnessActive ? "Set KYNVER_HARNESS_ROOT=~/.kynver/harness (or run setup), migrate artifacts, retire OPUS_HARNESS_ROOT." : env.opusHarnessRoot ? "Prefer KYNVER_HARNESS_ROOT over OPUS_HARNESS_ROOT." : void 0,
|
|
6870
|
+
details: { harnessRoot: displayHarnessRoot, legacyRoot: displayLegacyRoot, opusHarnessRoot: displayOpusHarnessRoot }
|
|
6871
|
+
}),
|
|
6872
|
+
check({
|
|
6873
|
+
id: "hotspot_openclaw_env_secrets",
|
|
6874
|
+
label: "OpenClaw deployment secrets in runner env",
|
|
6875
|
+
status: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "warn" : "pass",
|
|
6876
|
+
summary: env.openclawCronSecret || env.openclawCronFireBaseUrl ? [
|
|
6877
|
+
env.openclawCronSecret ? "OPENCLAW_CRON_SECRET set" : null,
|
|
6878
|
+
env.openclawCronFireBaseUrl ? "OPENCLAW_CRON_FIRE_BASE_URL set" : null
|
|
6879
|
+
].filter(Boolean).join("; ") : "No OpenClaw cron env overrides on this runner",
|
|
6880
|
+
remediation: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "Move to KYNVER_API_URL + scoped runner tokens; unset OpenClaw cron env on user-hosted runners." : void 0
|
|
6881
|
+
}),
|
|
6882
|
+
check({
|
|
6883
|
+
id: "hotspot_openclaw_scheduler",
|
|
6884
|
+
label: "openclaw-cron scheduler dependency (deployment)",
|
|
6885
|
+
status: schedulerOpenclaw ? "warn" : "pass",
|
|
6886
|
+
summary: schedulerOpenclaw ? env.kynverSchedulerProvider === "openclaw-cron" ? "KYNVER_SCHEDULER_PROVIDER=openclaw-cron \u2014 AgentOS ticks still routed via OpenClaw local cron adapter" : "KYNVER_SCHEDULER_PROVIDER unset \u2014 server may fall back to openclaw-cron when QStash is absent" : `KYNVER_SCHEDULER_PROVIDER=${env.kynverSchedulerProvider}`,
|
|
6887
|
+
remediation: schedulerOpenclaw ? "On Kynver-hosted scheduler: set KYNVER_SCHEDULER_PROVIDER=qstash where QStash is configured; retire openclaw-cron stub when runtime daemon owns dispatch." : void 0,
|
|
6888
|
+
details: { schedulerProvider: env.kynverSchedulerProvider ?? null }
|
|
6889
|
+
}),
|
|
6890
|
+
check({
|
|
6891
|
+
id: "hotspot_lease_source_names",
|
|
6892
|
+
label: "Harness lease/completion source names",
|
|
6893
|
+
status: "pass",
|
|
6894
|
+
summary: "Runtime uses kynver-harness:* lease owners and completion source kynver-harness (OpenClaw names retired in runtime)",
|
|
6895
|
+
details: {
|
|
6896
|
+
leaseOwnerPattern: "kynver-harness:<runId>",
|
|
6897
|
+
completionSource: "kynver-harness",
|
|
6898
|
+
note: "OpenClaw adapter remains optional in packages/kynver-openclaw-agent-os only"
|
|
6899
|
+
}
|
|
6900
|
+
})
|
|
6901
|
+
];
|
|
6902
|
+
return { id: "openclaw_hotspots", label: "OpenClaw dependency hotspots", checks };
|
|
6903
|
+
}
|
|
6904
|
+
function assessRuntimeTakeoverReadiness(probes = defaultRuntimeTakeoverProbes) {
|
|
6905
|
+
const sections = [
|
|
6906
|
+
assessCliPackage(probes),
|
|
6907
|
+
assessUserConfig(probes),
|
|
6908
|
+
assessRunnerToken(probes),
|
|
6909
|
+
assessVercelCli(probes),
|
|
6910
|
+
assessHarnessDirs(probes),
|
|
6911
|
+
assessCallbackAuth(probes),
|
|
6912
|
+
assessOpenclawHotspots(probes)
|
|
6913
|
+
];
|
|
6914
|
+
const counts = summarizeCounts(sections);
|
|
6915
|
+
const ready = counts.fail === 0;
|
|
6916
|
+
const summary = ready ? counts.warn > 0 ? `Ready with ${counts.warn} warning(s) \u2014 review remediation before retiring OpenClaw as primary runtime agent.` : "Ready \u2014 Kynver runtime can serve as primary runtime agent on this host." : `${counts.fail} blocking check(s) \u2014 fix failures before OpenClaw takeover.`;
|
|
6917
|
+
return {
|
|
6918
|
+
command: "doctor runtime-takeover",
|
|
6919
|
+
ready,
|
|
6920
|
+
summary,
|
|
6921
|
+
counts,
|
|
6922
|
+
sections
|
|
6923
|
+
};
|
|
6924
|
+
}
|
|
6925
|
+
|
|
6926
|
+
// src/doctor/runtime-takeover-cli.ts
|
|
6927
|
+
function runRuntimeTakeoverDoctorCli() {
|
|
6928
|
+
const report = assessRuntimeTakeoverReadiness();
|
|
6929
|
+
console.log(JSON.stringify(report, null, 2));
|
|
6930
|
+
if (!report.ready) {
|
|
6931
|
+
process.exitCode = 1;
|
|
6932
|
+
}
|
|
6933
|
+
}
|
|
6934
|
+
|
|
6935
|
+
// src/command-center-contract-cli.ts
|
|
6936
|
+
async function runCommandCenterContractCli(args) {
|
|
6937
|
+
const config = loadUserConfig();
|
|
6938
|
+
const agentOsId = (args.agentOsId ? String(args.agentOsId) : config.agentOsId) || "";
|
|
6939
|
+
if (!agentOsId) {
|
|
6940
|
+
console.error("requires --agent-os-id or agentOsId in ~/.kynver/config.json");
|
|
6941
|
+
process.exit(1);
|
|
6942
|
+
}
|
|
6943
|
+
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : config.apiBaseUrl);
|
|
6944
|
+
const secret = await resolveCallbackSecretWithMint(
|
|
6945
|
+
args.secret ? String(args.secret) : void 0,
|
|
6946
|
+
agentOsId,
|
|
6947
|
+
{ baseUrl: base }
|
|
6948
|
+
);
|
|
6949
|
+
const qs = new URLSearchParams();
|
|
6950
|
+
if (typeof args.since === "string" && args.since.trim()) qs.set("since", args.since.trim());
|
|
6951
|
+
if (args.limit != null && String(args.limit).trim()) {
|
|
6952
|
+
const n = Number(args.limit);
|
|
6953
|
+
if (Number.isFinite(n) && n > 0) qs.set("limit", String(Math.floor(n)));
|
|
6954
|
+
}
|
|
6955
|
+
const suffix = qs.toString() ? `?${qs.toString()}` : "";
|
|
6956
|
+
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/command-center/dashboard-contract${suffix}`;
|
|
6957
|
+
const res = await getJson(url, secret);
|
|
6958
|
+
if (!res.ok) {
|
|
6959
|
+
console.error(`dashboard-contract GET failed: HTTP ${res.status}`);
|
|
6960
|
+
if (res.response) console.error(JSON.stringify(res.response, null, 2));
|
|
6961
|
+
process.exit(1);
|
|
6962
|
+
}
|
|
6963
|
+
console.log(JSON.stringify(res.response, null, 2));
|
|
6964
|
+
}
|
|
6965
|
+
|
|
5758
6966
|
// src/cli.ts
|
|
5759
6967
|
function isHelpFlag(arg) {
|
|
5760
6968
|
return arg === "help" || arg === "--help" || arg === "-h";
|
|
@@ -5776,7 +6984,7 @@ function usage(code = 0) {
|
|
|
5776
6984
|
" kynver run create --repo /path/repo [--name name] [--base origin/main]",
|
|
5777
6985
|
" kynver run list",
|
|
5778
6986
|
" kynver run status --run RUN_ID",
|
|
5779
|
-
" kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-8] [--disk-path /]",
|
|
6987
|
+
" kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--target-task-id TASK_ID] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-8] [--disk-path /]",
|
|
5780
6988
|
" kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
|
|
5781
6989
|
' kynver worker start --run RUN_ID --name worker --task "..." [--owned path[,path]] [--model MODEL] [--provider claude|cursor] [--agent-os-id AOS_ID] [--task-id TASK_ID] [--wait]',
|
|
5782
6990
|
" kynver worker status --run RUN_ID --name worker",
|
|
@@ -5786,7 +6994,8 @@ function usage(code = 0) {
|
|
|
5786
6994
|
" kynver worker auto-complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--poll-ms 5000] [--max-total-ms 21600000] [--complete-attempts 3] [--complete-backoff-ms 5000] [--base-url URL] [--secret SECRET]",
|
|
5787
6995
|
" kynver run reconcile",
|
|
5788
6996
|
" kynver plan progress --plan PLAN_ID --row ROW_KEY --role ROLE --status STATUS [--task TASK_ID] [--note NOTE] [--evidence type:value] [--agent-os-id AOS_ID]",
|
|
5789
|
-
" kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override]",
|
|
6997
|
+
" kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override] [--local]",
|
|
6998
|
+
" kynver harness verify --worktree PATH [--command CMD] [--json] [--wait-for-admission-ms MS] [--timeout-ms MS]",
|
|
5790
6999
|
" kynver plan persist --operation create|add_version|update_metadata --title TITLE (--body-file PATH | --body TEXT) [--slug SLUG] [--plan PLAN_ID] [--summary TEXT] [--failure-kind approval_guard|auth|network|server|tool_interruption]",
|
|
5791
7000
|
" kynver plan outbox list",
|
|
5792
7001
|
" kynver plan outbox drain [--max N] [--id OUTBOX_ID]",
|
|
@@ -5797,7 +7006,9 @@ function usage(code = 0) {
|
|
|
5797
7006
|
" kynver monitor list",
|
|
5798
7007
|
" kynver monitor tick --run RUN_ID [--name worker] [--agent-os-id AOS_ID] [--auto-complete] [--renew-leases]",
|
|
5799
7008
|
" kynver monitor auto-complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--base-url URL] [--secret SECRET]",
|
|
5800
|
-
" kynver monitor run-loop --run RUN_ID --monitor-id ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS] [--auto-complete] [--renew-leases]"
|
|
7009
|
+
" kynver monitor run-loop --run RUN_ID --monitor-id ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS] [--auto-complete] [--renew-leases]",
|
|
7010
|
+
" kynver doctor runtime-takeover",
|
|
7011
|
+
" kynver board contract [--agent-os-id ID] [--base-url URL] [--since ISO] [--limit N]"
|
|
5801
7012
|
].join("\n")
|
|
5802
7013
|
);
|
|
5803
7014
|
process.exit(code);
|
|
@@ -5808,7 +7019,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
5808
7019
|
const scope = argv.shift();
|
|
5809
7020
|
let action;
|
|
5810
7021
|
let rest;
|
|
5811
|
-
if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "monitor") {
|
|
7022
|
+
if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "harness" || scope === "monitor" || scope === "doctor" || scope === "board") {
|
|
5812
7023
|
action = argv.shift();
|
|
5813
7024
|
rest = argv;
|
|
5814
7025
|
} else {
|
|
@@ -5825,6 +7036,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
5825
7036
|
if (scope === "daemon") return void await runDaemon(args);
|
|
5826
7037
|
if (scope === "plan" && action === "progress") return void await emitPlanProgress(args);
|
|
5827
7038
|
if (scope === "plan" && action === "verify") return void await verifyPlan(args);
|
|
7039
|
+
if (scope === "harness" && action === "verify") return runHarnessVerifyCli(args);
|
|
5828
7040
|
if (scope === "plan" && action === "persist") return void await runPlanPersist(args);
|
|
5829
7041
|
if (scope === "plan" && action === "outbox") {
|
|
5830
7042
|
const outboxAction = rest.shift();
|
|
@@ -5833,6 +7045,10 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
5833
7045
|
unknownCommand("plan", `outbox ${outboxAction ?? ""}`.trim());
|
|
5834
7046
|
}
|
|
5835
7047
|
if (scope === "cleanup") return runCleanupCli(args);
|
|
7048
|
+
if (scope === "doctor" && action === "runtime-takeover") return runRuntimeTakeoverDoctorCli();
|
|
7049
|
+
if (scope === "board" && action === "contract") {
|
|
7050
|
+
return void await runCommandCenterContractCli(args);
|
|
7051
|
+
}
|
|
5836
7052
|
if (scope === "run" && action === "create") return createRun(args);
|
|
5837
7053
|
if (scope === "run" && action === "list") return listRuns();
|
|
5838
7054
|
if (scope === "run" && action === "status") return runStatus(args);
|
|
@@ -5865,19 +7081,317 @@ if (isCliEntry) {
|
|
|
5865
7081
|
process.exit(1);
|
|
5866
7082
|
});
|
|
5867
7083
|
}
|
|
7084
|
+
|
|
7085
|
+
// src/vercel/vercel-url.ts
|
|
7086
|
+
var VERCEL_HOST_RE = /(^|\.)vercel\.app$/i;
|
|
7087
|
+
function tryParseUrl(raw) {
|
|
7088
|
+
const trimmed = raw.trim();
|
|
7089
|
+
if (!trimmed) return null;
|
|
7090
|
+
try {
|
|
7091
|
+
return new URL(trimmed);
|
|
7092
|
+
} catch {
|
|
7093
|
+
return null;
|
|
7094
|
+
}
|
|
7095
|
+
}
|
|
7096
|
+
function parseDashboardDeployment(url) {
|
|
7097
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
7098
|
+
if (parts.length < 3) return null;
|
|
7099
|
+
const deploymentId = parts[parts.length - 1]?.trim();
|
|
7100
|
+
if (!deploymentId || deploymentId === "deployments") return null;
|
|
7101
|
+
return deploymentId;
|
|
7102
|
+
}
|
|
7103
|
+
function parseDeploymentsSegment(url) {
|
|
7104
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
7105
|
+
const idx = parts.indexOf("deployments");
|
|
7106
|
+
if (idx < 0 || idx >= parts.length - 1) return null;
|
|
7107
|
+
const deploymentId = parts[idx + 1]?.trim();
|
|
7108
|
+
return deploymentId || null;
|
|
7109
|
+
}
|
|
7110
|
+
function classifyVercelUrl(raw) {
|
|
7111
|
+
const empty = {
|
|
7112
|
+
kind: "unknown",
|
|
7113
|
+
previewUrl: null,
|
|
7114
|
+
inspectTarget: null,
|
|
7115
|
+
deploymentId: null
|
|
7116
|
+
};
|
|
7117
|
+
const trimmed = raw.trim();
|
|
7118
|
+
if (!trimmed) return empty;
|
|
7119
|
+
if (/^dpl_[a-z0-9]+$/i.test(trimmed)) {
|
|
7120
|
+
return {
|
|
7121
|
+
kind: "deployment_id",
|
|
7122
|
+
previewUrl: null,
|
|
7123
|
+
inspectTarget: trimmed,
|
|
7124
|
+
deploymentId: trimmed
|
|
7125
|
+
};
|
|
7126
|
+
}
|
|
7127
|
+
const url = tryParseUrl(trimmed);
|
|
7128
|
+
if (!url) return empty;
|
|
7129
|
+
if (VERCEL_HOST_RE.test(url.hostname)) {
|
|
7130
|
+
const hostUrl = url.origin;
|
|
7131
|
+
return {
|
|
7132
|
+
kind: "deployment_host",
|
|
7133
|
+
previewUrl: hostUrl,
|
|
7134
|
+
inspectTarget: hostUrl,
|
|
7135
|
+
deploymentId: null
|
|
7136
|
+
};
|
|
7137
|
+
}
|
|
7138
|
+
if (url.hostname === "vercel.com" || url.hostname.endsWith(".vercel.com")) {
|
|
7139
|
+
const deploymentId = parseDeploymentsSegment(url) ?? parseDashboardDeployment(url);
|
|
7140
|
+
return {
|
|
7141
|
+
kind: "dashboard",
|
|
7142
|
+
previewUrl: null,
|
|
7143
|
+
inspectTarget: deploymentId,
|
|
7144
|
+
deploymentId
|
|
7145
|
+
};
|
|
7146
|
+
}
|
|
7147
|
+
return empty;
|
|
7148
|
+
}
|
|
7149
|
+
function isDashboardVercelUrl(raw) {
|
|
7150
|
+
return classifyVercelUrl(raw).kind === "dashboard";
|
|
7151
|
+
}
|
|
7152
|
+
|
|
7153
|
+
// src/vercel/vercel-github-status.ts
|
|
7154
|
+
var VERCEL_CONTEXT_RE = /vercel/i;
|
|
7155
|
+
function normalizeGitHubStatusState(state) {
|
|
7156
|
+
const value = typeof state === "string" ? state.trim().toLowerCase() : "";
|
|
7157
|
+
if (value === "success") return "success";
|
|
7158
|
+
if (value === "pending") return "pending";
|
|
7159
|
+
if (value === "failure") return "failure";
|
|
7160
|
+
if (value === "error") return "error";
|
|
7161
|
+
return "unknown";
|
|
7162
|
+
}
|
|
7163
|
+
function isVercelStatusContext(context) {
|
|
7164
|
+
const label = typeof context === "string" ? context.trim() : "";
|
|
7165
|
+
return Boolean(label && VERCEL_CONTEXT_RE.test(label));
|
|
7166
|
+
}
|
|
7167
|
+
function pickVercelStatusContext(statuses) {
|
|
7168
|
+
const rows = Array.isArray(statuses) ? statuses : [];
|
|
7169
|
+
const vercelRows = rows.filter((row) => isVercelStatusContext(row.context));
|
|
7170
|
+
if (vercelRows.length === 0) return null;
|
|
7171
|
+
const score = (state) => {
|
|
7172
|
+
if (state === "failure" || state === "error") return 0;
|
|
7173
|
+
if (state === "pending") return 1;
|
|
7174
|
+
if (state === "success") return 2;
|
|
7175
|
+
return 1;
|
|
7176
|
+
};
|
|
7177
|
+
let best = null;
|
|
7178
|
+
let bestScore = Infinity;
|
|
7179
|
+
for (const row of vercelRows) {
|
|
7180
|
+
const rowScore = score(normalizeGitHubStatusState(row.state));
|
|
7181
|
+
if (rowScore < bestScore) {
|
|
7182
|
+
best = row;
|
|
7183
|
+
bestScore = rowScore;
|
|
7184
|
+
}
|
|
7185
|
+
}
|
|
7186
|
+
if (!best) return null;
|
|
7187
|
+
const targetUrl = typeof best.target_url === "string" && best.target_url.trim() ? best.target_url.trim() : null;
|
|
7188
|
+
const classified = targetUrl ? classifyVercelUrl(targetUrl) : null;
|
|
7189
|
+
return {
|
|
7190
|
+
context: String(best.context ?? "Vercel").trim(),
|
|
7191
|
+
state: normalizeGitHubStatusState(best.state),
|
|
7192
|
+
targetUrl,
|
|
7193
|
+
description: typeof best.description === "string" && best.description.trim() ? best.description.trim() : null,
|
|
7194
|
+
deploymentId: classified?.deploymentId ?? null,
|
|
7195
|
+
previewUrl: classified?.previewUrl ?? null,
|
|
7196
|
+
dashboardUrl: classified?.kind === "dashboard" ? targetUrl : null
|
|
7197
|
+
};
|
|
7198
|
+
}
|
|
7199
|
+
|
|
7200
|
+
// src/vercel/vercel-evidence.ts
|
|
7201
|
+
import { spawnSync as spawnSync7 } from "node:child_process";
|
|
7202
|
+
var DEFAULT_INSPECT_WAIT_SECONDS = 120;
|
|
7203
|
+
function mapGitHubStateToEvidence(state) {
|
|
7204
|
+
if (state === "success") return "ready";
|
|
7205
|
+
if (state === "pending") return "building";
|
|
7206
|
+
if (state === "failure" || state === "error") return "error";
|
|
7207
|
+
return "unavailable";
|
|
7208
|
+
}
|
|
7209
|
+
function evidenceSummary(parts) {
|
|
7210
|
+
return parts.filter(Boolean).join("; ");
|
|
7211
|
+
}
|
|
7212
|
+
function resolveVercelInspectTarget(rawUrl) {
|
|
7213
|
+
const trimmed = typeof rawUrl === "string" ? rawUrl.trim() : "";
|
|
7214
|
+
if (!trimmed) {
|
|
7215
|
+
return { target: null, classified: null, reason: "missing target_url" };
|
|
7216
|
+
}
|
|
7217
|
+
const classified = classifyVercelUrl(trimmed);
|
|
7218
|
+
if (classified.inspectTarget) {
|
|
7219
|
+
return { target: classified.inspectTarget, classified, reason: null };
|
|
7220
|
+
}
|
|
7221
|
+
if (classified.kind === "dashboard") {
|
|
7222
|
+
return {
|
|
7223
|
+
target: null,
|
|
7224
|
+
classified,
|
|
7225
|
+
reason: "dashboard URL is not valid for vercel inspect"
|
|
7226
|
+
};
|
|
7227
|
+
}
|
|
7228
|
+
return { target: null, classified, reason: "unrecognized Vercel URL" };
|
|
7229
|
+
}
|
|
7230
|
+
function evidenceFromGitHubVercelStatus(statuses, options = {}) {
|
|
7231
|
+
const observedAt = options.observedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
7232
|
+
const row = pickVercelStatusContext(statuses);
|
|
7233
|
+
if (!row) {
|
|
7234
|
+
return {
|
|
7235
|
+
status: "not_run",
|
|
7236
|
+
previewUrl: null,
|
|
7237
|
+
deploymentUrl: null,
|
|
7238
|
+
summary: "No Vercel GitHub status context on commit",
|
|
7239
|
+
observedAt,
|
|
7240
|
+
inspectSkipped: true,
|
|
7241
|
+
inspectReason: "no_vercel_status_context",
|
|
7242
|
+
githubState: null,
|
|
7243
|
+
vercelContext: null
|
|
7244
|
+
};
|
|
7245
|
+
}
|
|
7246
|
+
const status = mapGitHubStateToEvidence(row.state);
|
|
7247
|
+
const previewUrl = row.previewUrl ?? row.dashboardUrl;
|
|
7248
|
+
const deploymentUrl = row.previewUrl ?? row.dashboardUrl;
|
|
7249
|
+
return {
|
|
7250
|
+
status,
|
|
7251
|
+
previewUrl,
|
|
7252
|
+
deploymentUrl,
|
|
7253
|
+
summary: evidenceSummary([
|
|
7254
|
+
`GitHub ${row.context}=${row.state}`,
|
|
7255
|
+
row.description ?? "",
|
|
7256
|
+
row.dashboardUrl ? "dashboard target_url (inspect skipped)" : ""
|
|
7257
|
+
]),
|
|
7258
|
+
observedAt,
|
|
7259
|
+
inspectSkipped: true,
|
|
7260
|
+
inspectReason: row.dashboardUrl && row.state === "success" ? "trusted_github_status_dashboard_url" : row.dashboardUrl ? "dashboard_url_not_inspectable" : "github_status_only",
|
|
7261
|
+
githubState: row.state,
|
|
7262
|
+
vercelContext: row.context
|
|
7263
|
+
};
|
|
7264
|
+
}
|
|
7265
|
+
function defaultRunVercelInspect(target, waitSeconds) {
|
|
7266
|
+
const args = ["inspect", target, "--wait", String(waitSeconds)];
|
|
7267
|
+
const result = spawnSync7("vercel", args, {
|
|
7268
|
+
encoding: "utf8",
|
|
7269
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
7270
|
+
});
|
|
7271
|
+
if (result.error) {
|
|
7272
|
+
return {
|
|
7273
|
+
ok: false,
|
|
7274
|
+
exitCode: result.status,
|
|
7275
|
+
stdout: "",
|
|
7276
|
+
stderr: "",
|
|
7277
|
+
error: result.error.message
|
|
7278
|
+
};
|
|
7279
|
+
}
|
|
7280
|
+
return {
|
|
7281
|
+
ok: result.status === 0,
|
|
7282
|
+
exitCode: result.status ?? 1,
|
|
7283
|
+
stdout: (result.stdout ?? "").trim(),
|
|
7284
|
+
stderr: (result.stderr ?? "").trim(),
|
|
7285
|
+
error: null
|
|
7286
|
+
};
|
|
7287
|
+
}
|
|
7288
|
+
function parseInspectReady(stdout, stderr) {
|
|
7289
|
+
const blob = `${stdout}
|
|
7290
|
+
${stderr}`.toLowerCase();
|
|
7291
|
+
if (/\b(status|state)\s*[:=]\s*(ready|completed)\b/.test(blob)) return true;
|
|
7292
|
+
if (/\bready\b/.test(blob) && !/\bnot ready\b/.test(blob)) return true;
|
|
7293
|
+
return false;
|
|
7294
|
+
}
|
|
7295
|
+
function collectVercelEvidence(input) {
|
|
7296
|
+
const observedAt = input.observedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
7297
|
+
const base = evidenceFromGitHubVercelStatus(input.statuses ?? [], { observedAt });
|
|
7298
|
+
const row = pickVercelStatusContext(input.statuses ?? []);
|
|
7299
|
+
if (!row) return base;
|
|
7300
|
+
if (row.state === "success") {
|
|
7301
|
+
return {
|
|
7302
|
+
...base,
|
|
7303
|
+
inspectSkipped: true,
|
|
7304
|
+
inspectReason: row.dashboardUrl ? "trusted_github_status_dashboard_url" : "trusted_github_status"
|
|
7305
|
+
};
|
|
7306
|
+
}
|
|
7307
|
+
if (!input.allowInspect) {
|
|
7308
|
+
return {
|
|
7309
|
+
...base,
|
|
7310
|
+
inspectSkipped: true,
|
|
7311
|
+
inspectReason: "inspect_disabled"
|
|
7312
|
+
};
|
|
7313
|
+
}
|
|
7314
|
+
const { target, reason } = resolveVercelInspectTarget(row.targetUrl);
|
|
7315
|
+
if (!target) {
|
|
7316
|
+
return {
|
|
7317
|
+
...base,
|
|
7318
|
+
inspectSkipped: true,
|
|
7319
|
+
inspectReason: reason ?? "not_inspectable",
|
|
7320
|
+
summary: evidenceSummary([
|
|
7321
|
+
base.summary ?? "",
|
|
7322
|
+
reason ?? "skipped vercel inspect"
|
|
7323
|
+
])
|
|
7324
|
+
};
|
|
7325
|
+
}
|
|
7326
|
+
const waitSeconds = input.waitSeconds ?? DEFAULT_INSPECT_WAIT_SECONDS;
|
|
7327
|
+
const runInspect = input.runInspect ?? defaultRunVercelInspect;
|
|
7328
|
+
const inspected = runInspect(target, waitSeconds);
|
|
7329
|
+
const observed = (/* @__PURE__ */ new Date()).toISOString();
|
|
7330
|
+
if (inspected.error?.includes("ENOENT")) {
|
|
7331
|
+
return {
|
|
7332
|
+
status: "unavailable",
|
|
7333
|
+
previewUrl: base.previewUrl,
|
|
7334
|
+
deploymentUrl: base.deploymentUrl,
|
|
7335
|
+
summary: "vercel CLI not found on PATH",
|
|
7336
|
+
observedAt: observed,
|
|
7337
|
+
inspectSkipped: true,
|
|
7338
|
+
inspectReason: "vercel_cli_missing",
|
|
7339
|
+
githubState: row.state,
|
|
7340
|
+
vercelContext: row.context
|
|
7341
|
+
};
|
|
7342
|
+
}
|
|
7343
|
+
if (!inspected.ok) {
|
|
7344
|
+
const detail = [inspected.stderr, inspected.stdout, inspected.error].filter(Boolean).join("\n").trim().slice(0, 500);
|
|
7345
|
+
return {
|
|
7346
|
+
status: row.state === "pending" ? "building" : "error",
|
|
7347
|
+
previewUrl: base.previewUrl,
|
|
7348
|
+
deploymentUrl: target.startsWith("http") ? target : base.deploymentUrl,
|
|
7349
|
+
summary: evidenceSummary([
|
|
7350
|
+
`vercel inspect ${target} failed (exit ${inspected.exitCode ?? "?"})`,
|
|
7351
|
+
detail
|
|
7352
|
+
]),
|
|
7353
|
+
observedAt: observed,
|
|
7354
|
+
inspectSkipped: false,
|
|
7355
|
+
inspectReason: null,
|
|
7356
|
+
githubState: row.state,
|
|
7357
|
+
vercelContext: row.context
|
|
7358
|
+
};
|
|
7359
|
+
}
|
|
7360
|
+
const ready = parseInspectReady(inspected.stdout, inspected.stderr);
|
|
7361
|
+
return {
|
|
7362
|
+
status: ready ? "ready" : row.state === "pending" ? "building" : "error",
|
|
7363
|
+
previewUrl: target.startsWith("http") ? target : base.previewUrl,
|
|
7364
|
+
deploymentUrl: target.startsWith("http") ? target : base.deploymentUrl,
|
|
7365
|
+
summary: ready ? `vercel inspect ${target} ready` : `vercel inspect ${target} completed without ready signal`,
|
|
7366
|
+
observedAt: observed,
|
|
7367
|
+
inspectSkipped: false,
|
|
7368
|
+
inspectReason: null,
|
|
7369
|
+
githubState: row.state,
|
|
7370
|
+
vercelContext: row.context
|
|
7371
|
+
};
|
|
7372
|
+
}
|
|
5868
7373
|
export {
|
|
5869
7374
|
DEFAULT_DISPATCH_LEASE_MS,
|
|
7375
|
+
DEFAULT_HARNESS_VERIFY_COMMANDS,
|
|
7376
|
+
FORBIDDEN_WORKER_ENV_KEYS,
|
|
5870
7377
|
PACKAGE_VERSION,
|
|
5871
7378
|
assessAutoCompleteEligibility,
|
|
7379
|
+
assessBuildAdmission,
|
|
5872
7380
|
assessExitedWorkerSalvage,
|
|
5873
7381
|
assessPrHandoffRequirement,
|
|
5874
7382
|
assessWorkerLanding,
|
|
5875
7383
|
assessWorkerLandingContract,
|
|
7384
|
+
auditWorkerEnv,
|
|
5876
7385
|
autoCompleteWorker,
|
|
5877
7386
|
autoCompleteWorkerCli,
|
|
5878
7387
|
buildDispatchTaskText,
|
|
5879
7388
|
buildPrompt,
|
|
7389
|
+
buildSystemdRunArgv,
|
|
7390
|
+
classifyNpmAuditOutcome,
|
|
7391
|
+
classifyShellCommandOutcome,
|
|
7392
|
+
classifyVercelUrl,
|
|
5880
7393
|
classifyWorkerHealth,
|
|
7394
|
+
collectVercelEvidence,
|
|
5881
7395
|
completeWorker,
|
|
5882
7396
|
computeAttention,
|
|
5883
7397
|
computeWorkerStatus,
|
|
@@ -5886,21 +7400,29 @@ export {
|
|
|
5886
7400
|
dispatchRun,
|
|
5887
7401
|
drainPlanOutbox,
|
|
5888
7402
|
ensurePrReadyHandoff,
|
|
7403
|
+
evidenceFromGitHubVercelStatus,
|
|
5889
7404
|
extractPlanOutboxFromTask,
|
|
5890
7405
|
extractPrUrlFromText,
|
|
5891
7406
|
formatPlanOutboxHandoffBlock,
|
|
5892
7407
|
getHarnessPaths,
|
|
5893
7408
|
getMonitorStatus,
|
|
5894
7409
|
hashPlanBody,
|
|
7410
|
+
isDashboardVercelUrl,
|
|
7411
|
+
isEngagementRequiredSkip,
|
|
5895
7412
|
isFinishedWorkerStatus,
|
|
7413
|
+
isForbiddenWorkerEnvKey,
|
|
5896
7414
|
isLandingBlockedWorkerStatus,
|
|
7415
|
+
isSystemdRunAvailable,
|
|
5897
7416
|
isTerminalHeartbeatPhase,
|
|
7417
|
+
isVercelStatusContext,
|
|
5898
7418
|
landingContractAttentionReason,
|
|
7419
|
+
listForbiddenWorkerEnvKeys,
|
|
5899
7420
|
listMonitors,
|
|
5900
7421
|
listOutboxItems,
|
|
5901
7422
|
listRuns,
|
|
5902
7423
|
loadUserConfig,
|
|
5903
7424
|
main,
|
|
7425
|
+
mergeNodeOptionsForBuildCheck,
|
|
5904
7426
|
normalizeCursorModelAlias,
|
|
5905
7427
|
observeRunnerDiskGate,
|
|
5906
7428
|
parseArgs,
|
|
@@ -5908,8 +7430,10 @@ export {
|
|
|
5908
7430
|
parseHarnessStream,
|
|
5909
7431
|
parseHeartbeat,
|
|
5910
7432
|
persistPlan,
|
|
7433
|
+
pickVercelStatusContext,
|
|
5911
7434
|
postJson,
|
|
5912
7435
|
preflightCursorModel,
|
|
7436
|
+
readMemAvailableBytes,
|
|
5913
7437
|
reconcileRunsCli,
|
|
5914
7438
|
reconcileStaleWorkers,
|
|
5915
7439
|
redactHarness,
|
|
@@ -5917,16 +7441,23 @@ export {
|
|
|
5917
7441
|
resolveCallbackSecret,
|
|
5918
7442
|
resolveCallbackSecretWithMint,
|
|
5919
7443
|
resolveHarnessRoot,
|
|
7444
|
+
resolveVercelInspectTarget,
|
|
7445
|
+
runBoundedBuildCheck,
|
|
5920
7446
|
runDaemon,
|
|
7447
|
+
runHarnessVerifyCommands,
|
|
5921
7448
|
runMonitorTick,
|
|
5922
7449
|
runStatus,
|
|
5923
7450
|
saveUserConfig,
|
|
7451
|
+
scrubClaudeEnv,
|
|
7452
|
+
scrubWorkerEnv,
|
|
5924
7453
|
spawnCompletionSidecar,
|
|
5925
7454
|
spawnMonitorSidecar,
|
|
5926
7455
|
spawnWorkerProcess,
|
|
5927
7456
|
startWorker,
|
|
5928
7457
|
stopWorker,
|
|
5929
7458
|
summarizeEvent,
|
|
7459
|
+
summarizeNpmAuditReport,
|
|
7460
|
+
summarizeShellToolCallEvent,
|
|
5930
7461
|
sweepRun,
|
|
5931
7462
|
tailWorker,
|
|
5932
7463
|
terminalFinalResultFromHeartbeat,
|