@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/cli.js
CHANGED
|
@@ -6,12 +6,39 @@ import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
|
6
6
|
|
|
7
7
|
// src/config.ts
|
|
8
8
|
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9
|
+
import { homedir as homedir2 } from "node:os";
|
|
10
|
+
import path3 from "node:path";
|
|
11
|
+
|
|
12
|
+
// src/path-values.ts
|
|
9
13
|
import { homedir } from "node:os";
|
|
10
|
-
import
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
function expandHomePath(value) {
|
|
16
|
+
if (value === "~") return homedir();
|
|
17
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
18
|
+
return path.join(homedir(), value.slice(2));
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
function resolveUserPath(value) {
|
|
23
|
+
return path.resolve(expandHomePath(value));
|
|
24
|
+
}
|
|
25
|
+
function redactHomePath(value) {
|
|
26
|
+
const expanded = expandHomePath(value);
|
|
27
|
+
const resolved = path.resolve(expanded);
|
|
28
|
+
const home = path.resolve(homedir());
|
|
29
|
+
if (resolved === home) return "~";
|
|
30
|
+
if (resolved.startsWith(`${home}${path.sep}`)) {
|
|
31
|
+
return `~/${path.relative(home, resolved).split(path.sep).join("/")}`;
|
|
32
|
+
}
|
|
33
|
+
return resolved.replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^\/Users\/[^/]+(?=\/|$)/, "~");
|
|
34
|
+
}
|
|
35
|
+
function displayUserPath(value) {
|
|
36
|
+
return redactHomePath(value);
|
|
37
|
+
}
|
|
11
38
|
|
|
12
39
|
// src/util.ts
|
|
13
40
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
14
|
-
import
|
|
41
|
+
import path2 from "node:path";
|
|
15
42
|
function fail(message) {
|
|
16
43
|
console.error(message);
|
|
17
44
|
process.exit(1);
|
|
@@ -40,7 +67,7 @@ function readJson(file, fallback) {
|
|
|
40
67
|
}
|
|
41
68
|
}
|
|
42
69
|
function writeJson(file, value) {
|
|
43
|
-
mkdirSync(
|
|
70
|
+
mkdirSync(path2.dirname(file), { recursive: true });
|
|
44
71
|
writeFileSync(file, `${JSON.stringify(value, null, 2)}
|
|
45
72
|
`);
|
|
46
73
|
}
|
|
@@ -76,7 +103,7 @@ function tailFile(file, lines) {
|
|
|
76
103
|
return data.split("\n").slice(-lines).join("\n");
|
|
77
104
|
}
|
|
78
105
|
function readMaybeFile(file) {
|
|
79
|
-
return file ? readFileSync(
|
|
106
|
+
return file ? readFileSync(path2.resolve(file), "utf8") : "";
|
|
80
107
|
}
|
|
81
108
|
function listRunIds(runsDir) {
|
|
82
109
|
if (!existsSync(runsDir)) return [];
|
|
@@ -119,9 +146,9 @@ function secsAgo(ms) {
|
|
|
119
146
|
}
|
|
120
147
|
|
|
121
148
|
// src/config.ts
|
|
122
|
-
var CONFIG_DIR =
|
|
123
|
-
var CONFIG_FILE =
|
|
124
|
-
var CREDENTIALS_FILE =
|
|
149
|
+
var CONFIG_DIR = path3.join(homedir2(), ".kynver");
|
|
150
|
+
var CONFIG_FILE = path3.join(CONFIG_DIR, "config.json");
|
|
151
|
+
var CREDENTIALS_FILE = path3.join(CONFIG_DIR, "credentials");
|
|
125
152
|
function loadUserConfig() {
|
|
126
153
|
if (!existsSync2(CONFIG_FILE)) return {};
|
|
127
154
|
try {
|
|
@@ -132,9 +159,33 @@ function loadUserConfig() {
|
|
|
132
159
|
}
|
|
133
160
|
function saveUserConfig(config) {
|
|
134
161
|
mkdirSync2(CONFIG_DIR, { recursive: true });
|
|
135
|
-
writeFileSync2(CONFIG_FILE, `${JSON.stringify(config, null, 2)}
|
|
162
|
+
writeFileSync2(CONFIG_FILE, `${JSON.stringify(normalizeConfigPaths(config), null, 2)}
|
|
136
163
|
`, { mode: 384 });
|
|
137
164
|
}
|
|
165
|
+
function normalizeConfigPaths(config) {
|
|
166
|
+
return {
|
|
167
|
+
...config,
|
|
168
|
+
...config.harnessRoot?.trim() ? { harnessRoot: redactHomePath(config.harnessRoot.trim()) } : {},
|
|
169
|
+
...config.defaultRepo?.trim() ? { defaultRepo: redactHomePath(config.defaultRepo.trim()) } : {}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function presentUserConfig(config) {
|
|
173
|
+
return normalizeConfigPaths(config);
|
|
174
|
+
}
|
|
175
|
+
function inferSetupFields(existing, args) {
|
|
176
|
+
const creds = loadCredentialsFile();
|
|
177
|
+
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();
|
|
178
|
+
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);
|
|
179
|
+
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();
|
|
180
|
+
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();
|
|
181
|
+
return {
|
|
182
|
+
...apiBaseUrl ? { apiBaseUrl: trimTrailingSlash(apiBaseUrl) } : {},
|
|
183
|
+
...agentOsId ? { agentOsId } : {},
|
|
184
|
+
...defaultRepo ? { defaultRepo } : {},
|
|
185
|
+
...harnessRoot ? { harnessRoot } : {},
|
|
186
|
+
...typeof args.agentOsSlug === "string" ? { agentOsSlug: args.agentOsSlug } : existing.agentOsSlug ? { agentOsSlug: existing.agentOsSlug } : {}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
138
189
|
function loadCredentialsFile() {
|
|
139
190
|
if (!existsSync2(CREDENTIALS_FILE)) return {};
|
|
140
191
|
try {
|
|
@@ -280,7 +331,7 @@ async function mintRunnerCredential(args) {
|
|
|
280
331
|
{
|
|
281
332
|
ok: true,
|
|
282
333
|
agentOsId,
|
|
283
|
-
credentialsPath: CREDENTIALS_FILE,
|
|
334
|
+
credentialsPath: displayUserPath(CREDENTIALS_FILE),
|
|
284
335
|
tokenPrefix: `${token.slice(0, 12)}\u2026`,
|
|
285
336
|
note: "Scoped runner token saved; callbacks use X-Kynver-Runner-Token."
|
|
286
337
|
},
|
|
@@ -315,16 +366,12 @@ function parseArgs(argv) {
|
|
|
315
366
|
async function runSetup(args) {
|
|
316
367
|
const existing = loadUserConfig();
|
|
317
368
|
const maxWorkersRaw = typeof args.maxWorkers === "string" ? args.maxWorkers : typeof args.maxConcurrentWorkers === "string" ? args.maxConcurrentWorkers : void 0;
|
|
318
|
-
const config = {
|
|
369
|
+
const config = normalizeConfigPaths({
|
|
319
370
|
...existing,
|
|
320
|
-
...
|
|
321
|
-
...typeof args.agentOsSlug === "string" ? { agentOsSlug: args.agentOsSlug } : {},
|
|
322
|
-
...typeof args.agentOsId === "string" ? { agentOsId: args.agentOsId } : {},
|
|
323
|
-
...typeof args.repo === "string" ? { defaultRepo: args.repo } : {},
|
|
324
|
-
...typeof args.harnessRoot === "string" ? { harnessRoot: args.harnessRoot } : {},
|
|
371
|
+
...inferSetupFields(existing, args),
|
|
325
372
|
...maxWorkersRaw ? { maxConcurrentWorkers: Math.max(1, Math.floor(Number(maxWorkersRaw))) } : {},
|
|
326
373
|
workerProvider: typeof args.provider === "string" ? args.provider : existing.workerProvider || "claude"
|
|
327
|
-
};
|
|
374
|
+
});
|
|
328
375
|
saveUserConfig(config);
|
|
329
376
|
let runnerCredentialNote;
|
|
330
377
|
const apiKey = loadApiKey();
|
|
@@ -345,8 +392,8 @@ async function runSetup(args) {
|
|
|
345
392
|
JSON.stringify(
|
|
346
393
|
{
|
|
347
394
|
ok: true,
|
|
348
|
-
configPath: CONFIG_FILE,
|
|
349
|
-
config,
|
|
395
|
+
configPath: displayUserPath(CONFIG_FILE),
|
|
396
|
+
config: presentUserConfig(config),
|
|
350
397
|
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."
|
|
351
398
|
},
|
|
352
399
|
null,
|
|
@@ -358,11 +405,11 @@ async function runLogin(args) {
|
|
|
358
405
|
const apiKey = typeof args.apiKey === "string" ? args.apiKey : process.env.KYNVER_API_KEY;
|
|
359
406
|
if (!apiKey) failConfig("kynver login requires --api-key or KYNVER_API_KEY");
|
|
360
407
|
saveApiKey(apiKey);
|
|
361
|
-
console.log(JSON.stringify({ ok: true, credentialsPath: CREDENTIALS_FILE }, null, 2));
|
|
408
|
+
console.log(JSON.stringify({ ok: true, credentialsPath: displayUserPath(CREDENTIALS_FILE) }, null, 2));
|
|
362
409
|
}
|
|
363
410
|
|
|
364
411
|
// src/dispatch.ts
|
|
365
|
-
import
|
|
412
|
+
import path17 from "node:path";
|
|
366
413
|
|
|
367
414
|
// src/callback-headers.ts
|
|
368
415
|
function buildHarnessCallbackHeaders(secret) {
|
|
@@ -424,12 +471,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
|
424
471
|
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
425
472
|
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
426
473
|
function observeRunnerDiskGate(input = {}) {
|
|
427
|
-
const
|
|
474
|
+
const path38 = input.diskPath?.trim() || "/";
|
|
428
475
|
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
429
476
|
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
430
477
|
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
431
478
|
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
432
|
-
const stats = statfsSync(
|
|
479
|
+
const stats = statfsSync(path38);
|
|
433
480
|
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
434
481
|
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
435
482
|
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
@@ -449,7 +496,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
449
496
|
}
|
|
450
497
|
return {
|
|
451
498
|
ok,
|
|
452
|
-
path:
|
|
499
|
+
path: path38,
|
|
453
500
|
freeBytes,
|
|
454
501
|
totalBytes,
|
|
455
502
|
usedPercent,
|
|
@@ -462,23 +509,46 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
462
509
|
}
|
|
463
510
|
|
|
464
511
|
// src/resource-gate.ts
|
|
465
|
-
import
|
|
512
|
+
import os2 from "node:os";
|
|
513
|
+
|
|
514
|
+
// src/bounded-build/meminfo.ts
|
|
515
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
466
516
|
import os from "node:os";
|
|
467
|
-
|
|
517
|
+
function readMemAvailableBytes(meminfoText) {
|
|
518
|
+
if (meminfoText !== void 0) {
|
|
519
|
+
const match = meminfoText.match(/^MemAvailable:\s+(\d+)\s*kB/m);
|
|
520
|
+
if (match) return Number(match[1]) * 1024;
|
|
521
|
+
return os.freemem();
|
|
522
|
+
}
|
|
523
|
+
if (process.platform === "linux") {
|
|
524
|
+
try {
|
|
525
|
+
const meminfo = readFileSync3("/proc/meminfo", "utf8");
|
|
526
|
+
const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m);
|
|
527
|
+
if (match) return Number(match[1]) * 1024;
|
|
528
|
+
} catch {
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return os.freemem();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// src/resource-gate.ts
|
|
535
|
+
import path6 from "node:path";
|
|
468
536
|
|
|
469
537
|
// src/run-store.ts
|
|
470
538
|
import { existsSync as existsSync4, readdirSync as readdirSync2 } from "node:fs";
|
|
471
|
-
import
|
|
539
|
+
import path5 from "node:path";
|
|
472
540
|
|
|
473
541
|
// src/paths.ts
|
|
474
542
|
import { existsSync as existsSync3 } from "node:fs";
|
|
475
|
-
import { homedir as
|
|
476
|
-
import
|
|
477
|
-
var LEGACY_ROOT =
|
|
543
|
+
import { homedir as homedir3 } from "node:os";
|
|
544
|
+
import path4 from "node:path";
|
|
545
|
+
var LEGACY_ROOT = path4.join(homedir3(), ".openclaw", "harness");
|
|
478
546
|
function resolveHarnessRoot() {
|
|
479
547
|
const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
|
|
480
|
-
if (env) return
|
|
481
|
-
const
|
|
548
|
+
if (env) return resolveUserPath(env);
|
|
549
|
+
const configured = loadUserConfig().harnessRoot?.trim();
|
|
550
|
+
if (configured) return resolveUserPath(configured);
|
|
551
|
+
const kynverRoot = path4.join(homedir3(), ".kynver", "harness");
|
|
482
552
|
if (existsSync3(kynverRoot)) return kynverRoot;
|
|
483
553
|
if (existsSync3(LEGACY_ROOT)) return LEGACY_ROOT;
|
|
484
554
|
return kynverRoot;
|
|
@@ -487,12 +557,12 @@ function getHarnessPaths() {
|
|
|
487
557
|
const harnessRoot = resolveHarnessRoot();
|
|
488
558
|
return {
|
|
489
559
|
harnessRoot,
|
|
490
|
-
runsDir:
|
|
491
|
-
worktreesDir:
|
|
560
|
+
runsDir: path4.join(harnessRoot, "runs"),
|
|
561
|
+
worktreesDir: path4.join(harnessRoot, "worktrees")
|
|
492
562
|
};
|
|
493
563
|
}
|
|
494
564
|
function runDir(runsDir, id) {
|
|
495
|
-
return
|
|
565
|
+
return path4.join(runsDir, safeSlug(id));
|
|
496
566
|
}
|
|
497
567
|
|
|
498
568
|
// src/run-store.ts
|
|
@@ -501,7 +571,7 @@ function getPaths() {
|
|
|
501
571
|
}
|
|
502
572
|
function loadRun(id) {
|
|
503
573
|
const { runsDir } = getPaths();
|
|
504
|
-
return readJson(
|
|
574
|
+
return readJson(path5.join(runDir(runsDir, safeSlug(id)), "run.json"));
|
|
505
575
|
}
|
|
506
576
|
function listRunRecords() {
|
|
507
577
|
const { runsDir } = getPaths();
|
|
@@ -510,7 +580,7 @@ function listRunRecords() {
|
|
|
510
580
|
for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
|
|
511
581
|
if (!entry.isDirectory()) continue;
|
|
512
582
|
const run = readJson(
|
|
513
|
-
|
|
583
|
+
path5.join(runsDir, entry.name, "run.json"),
|
|
514
584
|
void 0
|
|
515
585
|
);
|
|
516
586
|
if (run?.id) runs.push(run);
|
|
@@ -520,16 +590,16 @@ function listRunRecords() {
|
|
|
520
590
|
function loadWorker(runId, name) {
|
|
521
591
|
const { runsDir } = getPaths();
|
|
522
592
|
return readJson(
|
|
523
|
-
|
|
593
|
+
path5.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
|
|
524
594
|
);
|
|
525
595
|
}
|
|
526
596
|
function saveRun(run) {
|
|
527
597
|
const { runsDir } = getPaths();
|
|
528
|
-
writeJson(
|
|
598
|
+
writeJson(path5.join(runDir(runsDir, run.id), "run.json"), run);
|
|
529
599
|
}
|
|
530
600
|
function saveWorker(runId, worker) {
|
|
531
601
|
const { runsDir } = getPaths();
|
|
532
|
-
writeJson(
|
|
602
|
+
writeJson(path5.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
|
|
533
603
|
}
|
|
534
604
|
function runDirectory(id) {
|
|
535
605
|
const { runsDir } = getPaths();
|
|
@@ -537,7 +607,7 @@ function runDirectory(id) {
|
|
|
537
607
|
}
|
|
538
608
|
|
|
539
609
|
// src/heartbeat.ts
|
|
540
|
-
import { existsSync as existsSync5, readFileSync as
|
|
610
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
|
|
541
611
|
var HEARTBEAT_FUTURE_SKEW_MS = 6e4;
|
|
542
612
|
function isTerminalHeartbeatPhase(phase) {
|
|
543
613
|
return phase === "complete";
|
|
@@ -559,7 +629,7 @@ function parseHeartbeat(file) {
|
|
|
559
629
|
if (!existsSync5(file)) return result;
|
|
560
630
|
const maxFutureMs = Date.now() + HEARTBEAT_FUTURE_SKEW_MS;
|
|
561
631
|
const clampedTo = new Date(maxFutureMs).toISOString();
|
|
562
|
-
const lines =
|
|
632
|
+
const lines = readFileSync4(file, "utf8").split("\n").filter(Boolean);
|
|
563
633
|
for (const line of lines) {
|
|
564
634
|
const entry = safeJson(line);
|
|
565
635
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
@@ -586,7 +656,155 @@ function parseHeartbeat(file) {
|
|
|
586
656
|
}
|
|
587
657
|
|
|
588
658
|
// src/stream.ts
|
|
589
|
-
import { existsSync as existsSync6, readFileSync as
|
|
659
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
|
|
660
|
+
|
|
661
|
+
// src/shell-command-outcome.ts
|
|
662
|
+
var NPM_AUDIT_RE = /\bnpm\s+audit\b/i;
|
|
663
|
+
function tidy(text, max = 200) {
|
|
664
|
+
const one = text.replace(/\s+/g, " ").trim();
|
|
665
|
+
return one.length > max ? `${one.slice(0, max - 1)}\u2026` : one;
|
|
666
|
+
}
|
|
667
|
+
function extractJsonObject(text) {
|
|
668
|
+
const trimmed = text.trim();
|
|
669
|
+
if (!trimmed) return null;
|
|
670
|
+
if (trimmed.startsWith("{")) {
|
|
671
|
+
try {
|
|
672
|
+
return JSON.parse(trimmed);
|
|
673
|
+
} catch {
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
const start = trimmed.indexOf("{");
|
|
677
|
+
const end = trimmed.lastIndexOf("}");
|
|
678
|
+
if (start >= 0 && end > start) {
|
|
679
|
+
try {
|
|
680
|
+
return JSON.parse(trimmed.slice(start, end + 1));
|
|
681
|
+
} catch {
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
function isRecord(value) {
|
|
688
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
689
|
+
}
|
|
690
|
+
function summarizeNpmAuditReport(report) {
|
|
691
|
+
const meta = report.metadata;
|
|
692
|
+
if (!isRecord(meta)) return null;
|
|
693
|
+
const vuln = meta.vulnerabilities;
|
|
694
|
+
if (!isRecord(vuln)) return null;
|
|
695
|
+
const num = (key) => typeof vuln[key] === "number" ? vuln[key] : 0;
|
|
696
|
+
const summary = {
|
|
697
|
+
info: num("info"),
|
|
698
|
+
low: num("low"),
|
|
699
|
+
moderate: num("moderate"),
|
|
700
|
+
high: num("high"),
|
|
701
|
+
critical: num("critical"),
|
|
702
|
+
total: num("total")
|
|
703
|
+
};
|
|
704
|
+
if (typeof vuln.total !== "number" && !summary.critical && !summary.high && !summary.moderate && !summary.low && !summary.info) {
|
|
705
|
+
return null;
|
|
706
|
+
}
|
|
707
|
+
return summary;
|
|
708
|
+
}
|
|
709
|
+
function formatAuditSummaryLine(audit) {
|
|
710
|
+
const parts = [];
|
|
711
|
+
if (audit.critical) parts.push(`${audit.critical} critical`);
|
|
712
|
+
if (audit.high) parts.push(`${audit.high} high`);
|
|
713
|
+
if (audit.moderate) parts.push(`${audit.moderate} moderate`);
|
|
714
|
+
if (audit.low) parts.push(`${audit.low} low`);
|
|
715
|
+
if (audit.info) parts.push(`${audit.info} info`);
|
|
716
|
+
const breakdown = parts.length ? parts.join(", ") : "see report";
|
|
717
|
+
return `npm audit: ${audit.total} vulnerabilit${audit.total === 1 ? "y" : "ies"} (${breakdown}) \u2014 remediation required`;
|
|
718
|
+
}
|
|
719
|
+
function npmAuditFailureReason(report, stderr) {
|
|
720
|
+
const err = report.error;
|
|
721
|
+
if (isRecord(err)) {
|
|
722
|
+
const summary = typeof err.summary === "string" ? err.summary.trim() : "";
|
|
723
|
+
const code = typeof err.code === "string" ? err.code.trim() : "";
|
|
724
|
+
if (summary) return code ? `${code}: ${summary}` : summary;
|
|
725
|
+
if (code) return code;
|
|
726
|
+
}
|
|
727
|
+
const detail = typeof report.message === "string" ? report.message.trim() : "";
|
|
728
|
+
if (detail) return detail;
|
|
729
|
+
const errTail = stderr.trim();
|
|
730
|
+
if (errTail) return tidy(errTail.split("\n").find(Boolean) ?? errTail, 160);
|
|
731
|
+
return "npm audit failed";
|
|
732
|
+
}
|
|
733
|
+
function classifyNpmAuditOutcome(input) {
|
|
734
|
+
const combined = `${input.stdout}
|
|
735
|
+
${input.stderr}`.trim();
|
|
736
|
+
const parsed = extractJsonObject(combined);
|
|
737
|
+
if (!parsed || !isRecord(parsed)) {
|
|
738
|
+
const tail = tidy(combined || `exit ${input.exitCode}`, 180);
|
|
739
|
+
return {
|
|
740
|
+
kind: "command_failure",
|
|
741
|
+
exitCode: input.exitCode,
|
|
742
|
+
summary: `npm audit failed (invalid or missing JSON): ${tail}`,
|
|
743
|
+
parseError: "invalid_json"
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
if (isRecord(parsed.error)) {
|
|
747
|
+
return {
|
|
748
|
+
kind: "command_failure",
|
|
749
|
+
exitCode: input.exitCode,
|
|
750
|
+
summary: `npm audit command failed: ${npmAuditFailureReason(parsed, input.stderr)}`
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
const audit = summarizeNpmAuditReport(parsed);
|
|
754
|
+
if (!audit) {
|
|
755
|
+
return {
|
|
756
|
+
kind: "command_failure",
|
|
757
|
+
exitCode: input.exitCode,
|
|
758
|
+
summary: "npm audit failed: JSON response missing vulnerability metadata",
|
|
759
|
+
parseError: "missing_metadata"
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
if (input.exitCode === 0 && audit.total === 0) {
|
|
763
|
+
return {
|
|
764
|
+
kind: "success",
|
|
765
|
+
exitCode: 0,
|
|
766
|
+
summary: "npm audit: no vulnerabilities reported",
|
|
767
|
+
audit
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
return {
|
|
771
|
+
kind: "audit_findings",
|
|
772
|
+
exitCode: input.exitCode,
|
|
773
|
+
summary: formatAuditSummaryLine(audit),
|
|
774
|
+
audit
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
function isNpmAuditCommand(command) {
|
|
778
|
+
return NPM_AUDIT_RE.test(command);
|
|
779
|
+
}
|
|
780
|
+
function classifyShellCommandOutcome(input) {
|
|
781
|
+
const stdout = input.stdout ?? "";
|
|
782
|
+
const stderr = input.stderr ?? "";
|
|
783
|
+
const interleaved = input.interleavedOutput ?? "";
|
|
784
|
+
if (isNpmAuditCommand(input.command)) {
|
|
785
|
+
const body = stdout.trim() || interleaved.trim() || stderr.trim();
|
|
786
|
+
return classifyNpmAuditOutcome({
|
|
787
|
+
exitCode: input.exitCode,
|
|
788
|
+
stdout: body,
|
|
789
|
+
stderr
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
if (input.exitCode === 0) {
|
|
793
|
+
return {
|
|
794
|
+
kind: "success",
|
|
795
|
+
exitCode: 0,
|
|
796
|
+
summary: `command succeeded (exit 0)`
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
const tail = tidy(interleaved || stdout || stderr || `exit ${input.exitCode}`, 180);
|
|
800
|
+
return {
|
|
801
|
+
kind: "command_failure",
|
|
802
|
+
exitCode: input.exitCode,
|
|
803
|
+
summary: `command failed (exit ${input.exitCode}): ${tail}`
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
// src/stream.ts
|
|
590
808
|
function eventTimestampIso(event) {
|
|
591
809
|
const tsMs = event.timestamp_ms;
|
|
592
810
|
return event.timestamp || event.ts || (tsMs ? new Date(tsMs).toISOString() : void 0);
|
|
@@ -607,16 +825,43 @@ function recordStreamResult(result, event) {
|
|
|
607
825
|
result.error = String(event.result || event.api_error_status || "stream result error");
|
|
608
826
|
}
|
|
609
827
|
}
|
|
828
|
+
function shellPayloadFromCursorEvent(event) {
|
|
829
|
+
if (event.type !== "tool_call" || event.subtype !== "completed") return null;
|
|
830
|
+
const toolCall = event.tool_call && typeof event.tool_call === "object" && !Array.isArray(event.tool_call) ? event.tool_call : null;
|
|
831
|
+
const shell = toolCall?.shellToolCall;
|
|
832
|
+
if (!shell || typeof shell !== "object" || Array.isArray(shell)) return null;
|
|
833
|
+
const shellObj = shell;
|
|
834
|
+
const args = shellObj.args;
|
|
835
|
+
const command = args && typeof args === "object" && !Array.isArray(args) && typeof args.command === "string" ? String(args.command) : "";
|
|
836
|
+
const result = shellObj.result;
|
|
837
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) return null;
|
|
838
|
+
const body = result.success ?? result.failure;
|
|
839
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return null;
|
|
840
|
+
const row = body;
|
|
841
|
+
const exitCode = typeof row.exitCode === "number" ? row.exitCode : 0;
|
|
842
|
+
return {
|
|
843
|
+
command,
|
|
844
|
+
exitCode,
|
|
845
|
+
stdout: typeof row.stdout === "string" ? row.stdout : "",
|
|
846
|
+
stderr: typeof row.stderr === "string" ? row.stderr : "",
|
|
847
|
+
interleaved: typeof row.interleavedOutput === "string" ? row.interleavedOutput : ""
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
function applyShellOutcome(parsed, outcome) {
|
|
851
|
+
if (outcome.kind === "success") return;
|
|
852
|
+
parsed.lastShellOutcome = outcome;
|
|
853
|
+
}
|
|
610
854
|
function parseHarnessStream(file) {
|
|
611
855
|
const result = {
|
|
612
856
|
firstEventAt: null,
|
|
613
857
|
lastEventAt: null,
|
|
614
858
|
currentTool: null,
|
|
615
859
|
finalResult: null,
|
|
616
|
-
error: null
|
|
860
|
+
error: null,
|
|
861
|
+
lastShellOutcome: null
|
|
617
862
|
};
|
|
618
863
|
if (!existsSync6(file)) return result;
|
|
619
|
-
const lines =
|
|
864
|
+
const lines = readFileSync5(file, "utf8").split("\n").filter(Boolean);
|
|
620
865
|
for (const line of lines) {
|
|
621
866
|
const event = safeJson(line);
|
|
622
867
|
if (!event) continue;
|
|
@@ -641,12 +886,44 @@ function parseHarnessStream(file) {
|
|
|
641
886
|
const name = cursorToolNameFromCall(toolCall);
|
|
642
887
|
if (name) result.currentTool = name;
|
|
643
888
|
}
|
|
889
|
+
const shell = shellPayloadFromCursorEvent(event);
|
|
890
|
+
if (shell) {
|
|
891
|
+
applyShellOutcome(
|
|
892
|
+
result,
|
|
893
|
+
classifyShellCommandOutcome({
|
|
894
|
+
command: shell.command,
|
|
895
|
+
exitCode: shell.exitCode,
|
|
896
|
+
stdout: shell.stdout,
|
|
897
|
+
stderr: shell.stderr,
|
|
898
|
+
interleavedOutput: shell.interleaved
|
|
899
|
+
})
|
|
900
|
+
);
|
|
901
|
+
}
|
|
644
902
|
if (event.type === "result") {
|
|
645
903
|
recordStreamResult(result, event);
|
|
646
904
|
}
|
|
647
905
|
}
|
|
648
906
|
return result;
|
|
649
907
|
}
|
|
908
|
+
function summarizeShellToolCallEvent(event) {
|
|
909
|
+
const shell = shellPayloadFromCursorEvent(event);
|
|
910
|
+
if (!shell) return void 0;
|
|
911
|
+
const outcome = classifyShellCommandOutcome({
|
|
912
|
+
command: shell.command,
|
|
913
|
+
exitCode: shell.exitCode,
|
|
914
|
+
stdout: shell.stdout,
|
|
915
|
+
stderr: shell.stderr,
|
|
916
|
+
interleavedOutput: shell.interleaved
|
|
917
|
+
});
|
|
918
|
+
const cmd = oneLine(shell.command).slice(0, 120);
|
|
919
|
+
if (outcome.kind === "audit_findings") {
|
|
920
|
+
return `[audit:findings] ${outcome.summary}${cmd ? ` \xB7 ${cmd}` : ""}`;
|
|
921
|
+
}
|
|
922
|
+
if (outcome.kind === "command_failure") {
|
|
923
|
+
return `[command:failed] ${outcome.summary}${cmd ? ` \xB7 ${cmd}` : ""}`;
|
|
924
|
+
}
|
|
925
|
+
return `[command:ok] exit 0${cmd ? ` \xB7 ${cmd}` : ""}`;
|
|
926
|
+
}
|
|
650
927
|
function summarizeEvent(event) {
|
|
651
928
|
if (event.type === "system" && event.subtype) {
|
|
652
929
|
return `[system:${event.subtype}] ${String(event.status || event.cwd || "")}`.trim();
|
|
@@ -679,6 +956,8 @@ function summarizeEvent(event) {
|
|
|
679
956
|
}
|
|
680
957
|
if (event.type === "tool_call") {
|
|
681
958
|
const subtype = String(event.subtype || "");
|
|
959
|
+
const shellSummary = subtype === "completed" ? summarizeShellToolCallEvent(event) : void 0;
|
|
960
|
+
if (shellSummary) return shellSummary;
|
|
682
961
|
const toolCall = event.tool_call && typeof event.tool_call === "object" && !Array.isArray(event.tool_call) ? event.tool_call : void 0;
|
|
683
962
|
const name = cursorToolNameFromCall(toolCall) ?? "tool";
|
|
684
963
|
return `[tool:${subtype}] ${name}`;
|
|
@@ -720,7 +999,7 @@ var FAILURE_PATTERNS = [
|
|
|
720
999
|
label: "provider authentication failed"
|
|
721
1000
|
}
|
|
722
1001
|
];
|
|
723
|
-
function
|
|
1002
|
+
function tidy2(errorText, max = 240) {
|
|
724
1003
|
const oneLine2 = errorText.replace(/\s+/g, " ").trim();
|
|
725
1004
|
return oneLine2.length > max ? `${oneLine2.slice(0, max - 1)}\u2026` : oneLine2;
|
|
726
1005
|
}
|
|
@@ -729,7 +1008,7 @@ function classifyExitFailure(errorText) {
|
|
|
729
1008
|
if (!text) return null;
|
|
730
1009
|
for (const pattern of FAILURE_PATTERNS) {
|
|
731
1010
|
if (pattern.test.test(text)) {
|
|
732
|
-
return { blocked: true, reason: `${pattern.label}: ${
|
|
1011
|
+
return { blocked: true, reason: `${pattern.label}: ${tidy2(text)}` };
|
|
733
1012
|
}
|
|
734
1013
|
}
|
|
735
1014
|
return null;
|
|
@@ -795,6 +1074,53 @@ function assessExitedWorkerSalvage(input) {
|
|
|
795
1074
|
|
|
796
1075
|
// src/git.ts
|
|
797
1076
|
import { spawnSync } from "node:child_process";
|
|
1077
|
+
|
|
1078
|
+
// src/worker-env.ts
|
|
1079
|
+
var FORBIDDEN_WORKER_ENV_KEYS = [
|
|
1080
|
+
"ANTHROPIC_API_KEY",
|
|
1081
|
+
"ANALYST_API_KEY",
|
|
1082
|
+
"RECRUITER_API_KEY",
|
|
1083
|
+
"AUTH_SECRET",
|
|
1084
|
+
"NEXTAUTH_SECRET",
|
|
1085
|
+
"DATABASE_URL",
|
|
1086
|
+
"PRODUCTION_DATABASE_URL",
|
|
1087
|
+
"REDIS_URL",
|
|
1088
|
+
"GOOGLE_CLIENT_SECRET",
|
|
1089
|
+
"GITHUB_CLIENT_SECRET",
|
|
1090
|
+
"KYNVER_API_KEY",
|
|
1091
|
+
"KYNVER_SERVICE_SECRET",
|
|
1092
|
+
"KYNVER_RUNTIME_SECRET",
|
|
1093
|
+
"OPENCLAW_CRON_SECRET",
|
|
1094
|
+
"QSTASH_TOKEN",
|
|
1095
|
+
"QSTASH_CURRENT_SIGNING_KEY",
|
|
1096
|
+
"QSTASH_NEXT_SIGNING_KEY",
|
|
1097
|
+
"TOOL_SECRETS_KEK",
|
|
1098
|
+
"TOOL_EXECUTOR_DISPATCH_SECRET",
|
|
1099
|
+
"CLOUDFLARE_API_TOKEN",
|
|
1100
|
+
"STRIPE_SECRET_KEY",
|
|
1101
|
+
"STRIPE_WEBHOOK_SECRET",
|
|
1102
|
+
"STRIPE_IDENTITY_WEBHOOK_SECRET",
|
|
1103
|
+
"VOYAGE_API_KEY",
|
|
1104
|
+
"PERPLEXITY_API_KEY",
|
|
1105
|
+
"FRED_API_KEY",
|
|
1106
|
+
"FMP_API_KEY",
|
|
1107
|
+
"CURSOR_API_KEY"
|
|
1108
|
+
];
|
|
1109
|
+
var FORBIDDEN_KEY_SET = new Set(FORBIDDEN_WORKER_ENV_KEYS);
|
|
1110
|
+
var FORBIDDEN_SUFFIXES = ["_SECRET", "_API_KEY"];
|
|
1111
|
+
function isForbiddenWorkerEnvKey(key) {
|
|
1112
|
+
if (FORBIDDEN_KEY_SET.has(key)) return true;
|
|
1113
|
+
return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));
|
|
1114
|
+
}
|
|
1115
|
+
function scrubWorkerEnv(env) {
|
|
1116
|
+
const next = { ...env };
|
|
1117
|
+
for (const key of Object.keys(next)) {
|
|
1118
|
+
if (isForbiddenWorkerEnvKey(key)) delete next[key];
|
|
1119
|
+
}
|
|
1120
|
+
return next;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// src/git.ts
|
|
798
1124
|
function git(cwd, args, options = {}) {
|
|
799
1125
|
const res = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
800
1126
|
if (res.status !== 0 && !options.allowFailure) {
|
|
@@ -910,11 +1236,6 @@ function unknownAncestry(base, error, head = null) {
|
|
|
910
1236
|
error
|
|
911
1237
|
};
|
|
912
1238
|
}
|
|
913
|
-
function scrubClaudeEnv(env) {
|
|
914
|
-
const next = { ...env };
|
|
915
|
-
delete next.ANTHROPIC_API_KEY;
|
|
916
|
-
return next;
|
|
917
|
-
}
|
|
918
1239
|
|
|
919
1240
|
// src/landing-gate.ts
|
|
920
1241
|
function trimOrNull2(value) {
|
|
@@ -1264,15 +1585,7 @@ function computeAutoMaxWorkers(totalMemBytes, opts = {}) {
|
|
|
1264
1585
|
return Math.min(raw, AUTO_MAX_WORKERS_CEILING);
|
|
1265
1586
|
}
|
|
1266
1587
|
function readAvailableMemBytes() {
|
|
1267
|
-
|
|
1268
|
-
try {
|
|
1269
|
-
const meminfo = readFileSync5("/proc/meminfo", "utf8");
|
|
1270
|
-
const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m);
|
|
1271
|
-
if (match) return Number(match[1]) * 1024;
|
|
1272
|
-
} catch {
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
return os.freemem();
|
|
1588
|
+
return readMemAvailableBytes();
|
|
1276
1589
|
}
|
|
1277
1590
|
function isActiveHarnessWorker(worker) {
|
|
1278
1591
|
const status = computeWorkerStatus(worker);
|
|
@@ -1282,7 +1595,7 @@ function countActiveWorkersForRun(run) {
|
|
|
1282
1595
|
let active = 0;
|
|
1283
1596
|
for (const name of Object.keys(run.workers || {})) {
|
|
1284
1597
|
const worker = readJson(
|
|
1285
|
-
|
|
1598
|
+
path6.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1286
1599
|
void 0
|
|
1287
1600
|
);
|
|
1288
1601
|
if (!worker || !isActiveHarnessWorker(worker)) continue;
|
|
@@ -1300,7 +1613,7 @@ function observeRunnerResourceGate(input) {
|
|
|
1300
1613
|
input.config,
|
|
1301
1614
|
input.configuredMaxWorkersOverride
|
|
1302
1615
|
);
|
|
1303
|
-
const totalMemBytes = input.totalMemBytes ??
|
|
1616
|
+
const totalMemBytes = input.totalMemBytes ?? os2.totalmem();
|
|
1304
1617
|
const freeMemBytes = input.freeMemBytes ?? readAvailableMemBytes();
|
|
1305
1618
|
const activeWorkers = input.activeWorkers ?? countActiveWorkersGlobal();
|
|
1306
1619
|
const budgetBytes = Math.max(0, Math.floor(totalMemBytes * memUtilization) - memReserveBytes);
|
|
@@ -1490,6 +1803,7 @@ var claudeProvider = {
|
|
|
1490
1803
|
const model = preflight.model;
|
|
1491
1804
|
const stdoutFd = openSync(opts.stdoutPath, "a");
|
|
1492
1805
|
const stderrFd = openSync(opts.stderrPath, "a");
|
|
1806
|
+
const stdio = ["ignore", stdoutFd, stderrFd];
|
|
1493
1807
|
const child = spawn(
|
|
1494
1808
|
"claude",
|
|
1495
1809
|
[
|
|
@@ -1507,8 +1821,8 @@ var claudeProvider = {
|
|
|
1507
1821
|
hiddenSpawnOptions({
|
|
1508
1822
|
cwd: opts.worktreePath,
|
|
1509
1823
|
detached: true,
|
|
1510
|
-
stdio
|
|
1511
|
-
env:
|
|
1824
|
+
stdio,
|
|
1825
|
+
env: scrubWorkerEnv(process.env)
|
|
1512
1826
|
})
|
|
1513
1827
|
);
|
|
1514
1828
|
closeSync(stdoutFd);
|
|
@@ -1666,10 +1980,10 @@ function readHarnessRetryLimits() {
|
|
|
1666
1980
|
}
|
|
1667
1981
|
|
|
1668
1982
|
// src/lease-renewal.ts
|
|
1669
|
-
import
|
|
1983
|
+
import path7 from "node:path";
|
|
1670
1984
|
function workerRecord(runId, name) {
|
|
1671
1985
|
return readJson(
|
|
1672
|
-
|
|
1986
|
+
path7.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
1673
1987
|
void 0
|
|
1674
1988
|
);
|
|
1675
1989
|
}
|
|
@@ -1736,7 +2050,7 @@ function hasLiveWorkerForTask(runId, taskId) {
|
|
|
1736
2050
|
|
|
1737
2051
|
// src/supervisor.ts
|
|
1738
2052
|
import { existsSync as existsSync10, mkdirSync as mkdirSync3 } from "node:fs";
|
|
1739
|
-
import
|
|
2053
|
+
import path13 from "node:path";
|
|
1740
2054
|
|
|
1741
2055
|
// src/prompt.ts
|
|
1742
2056
|
function buildPrompt(input) {
|
|
@@ -1755,6 +2069,16 @@ function buildPrompt(input) {
|
|
|
1755
2069
|
"- After implementation: wait for report_reviewer then deep_reviewer confirmation (via MCP/session agents) before follow-up rows close.",
|
|
1756
2070
|
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."
|
|
1757
2071
|
];
|
|
2072
|
+
const mergeGateLines = compact ? [
|
|
2073
|
+
"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)."
|
|
2074
|
+
] : [
|
|
2075
|
+
"GitHub Actions merge-gate cost control (Kynver/Hermes PRs):",
|
|
2076
|
+
"- Prefer local cached package verification (`node scripts/verify-pr-local.mjs --emit-json`) and Vercel preview evidence before GitHub Actions.",
|
|
2077
|
+
"- 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.",
|
|
2078
|
+
"- Record evidence: POST `/api/agent-os/by-id/<agentOsId>/pr-merge-gate/refresh` with prUrl + local/vercel payloads.",
|
|
2079
|
+
"- Request the final Actions run only when local + Vercel are green: POST `.../pr-merge-gate/request-run` (applies `merge-gate` label).",
|
|
2080
|
+
"- Empty failed Actions jobs (no runner/steps/logs) are infra/quota \u2014 do not enter repair loops; escalate to operator."
|
|
2081
|
+
];
|
|
1758
2082
|
const planArtifactLines = compact ? [
|
|
1759
2083
|
"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."
|
|
1760
2084
|
] : [
|
|
@@ -1776,10 +2100,14 @@ function buildPrompt(input) {
|
|
|
1776
2100
|
"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.",
|
|
1777
2101
|
"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.",
|
|
1778
2102
|
"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.",
|
|
2103
|
+
"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.",
|
|
1779
2104
|
"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.",
|
|
2105
|
+
"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.",
|
|
1780
2106
|
"",
|
|
1781
2107
|
...progressLines,
|
|
1782
2108
|
"",
|
|
2109
|
+
...mergeGateLines,
|
|
2110
|
+
"",
|
|
1783
2111
|
...planArtifactLines,
|
|
1784
2112
|
"",
|
|
1785
2113
|
...input.personaMarkdown?.trim() ? [input.personaMarkdown.trim(), ""] : [],
|
|
@@ -1792,11 +2120,11 @@ function buildPrompt(input) {
|
|
|
1792
2120
|
// src/providers/cursor.ts
|
|
1793
2121
|
import { closeSync as closeSync2, existsSync as existsSync8, openSync as openSync2 } from "node:fs";
|
|
1794
2122
|
import { spawn as spawn2 } from "node:child_process";
|
|
1795
|
-
import
|
|
2123
|
+
import path9 from "node:path";
|
|
1796
2124
|
|
|
1797
2125
|
// src/providers/cursor-windows.ts
|
|
1798
2126
|
import { existsSync as existsSync7, readdirSync as readdirSync3 } from "node:fs";
|
|
1799
|
-
import
|
|
2127
|
+
import path8 from "node:path";
|
|
1800
2128
|
var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
|
|
1801
2129
|
function parseCursorVersionSortKey(versionName) {
|
|
1802
2130
|
const datePart = versionName.split("-")[0];
|
|
@@ -1807,7 +2135,7 @@ function parseCursorVersionSortKey(versionName) {
|
|
|
1807
2135
|
return Number(`${year}${month.padStart(2, "0")}${day.padStart(2, "0")}`);
|
|
1808
2136
|
}
|
|
1809
2137
|
function pickLatestCursorVersionDir(agentRoot) {
|
|
1810
|
-
const versionsRoot =
|
|
2138
|
+
const versionsRoot = path8.join(agentRoot, "versions");
|
|
1811
2139
|
if (!existsSync7(versionsRoot)) return null;
|
|
1812
2140
|
let bestDir = null;
|
|
1813
2141
|
let bestKey = -1;
|
|
@@ -1816,21 +2144,21 @@ function pickLatestCursorVersionDir(agentRoot) {
|
|
|
1816
2144
|
const key = parseCursorVersionSortKey(entry.name);
|
|
1817
2145
|
if (key == null || key <= bestKey) continue;
|
|
1818
2146
|
bestKey = key;
|
|
1819
|
-
bestDir =
|
|
2147
|
+
bestDir = path8.join(versionsRoot, entry.name);
|
|
1820
2148
|
}
|
|
1821
2149
|
return bestDir;
|
|
1822
2150
|
}
|
|
1823
2151
|
function resolveWindowsCursorBundled(agentRoot) {
|
|
1824
|
-
const root = agentRoot?.trim() ||
|
|
1825
|
-
const directNode =
|
|
1826
|
-
const directIndex =
|
|
2152
|
+
const root = agentRoot?.trim() || path8.join(process.env.LOCALAPPDATA || "", "cursor-agent");
|
|
2153
|
+
const directNode = path8.join(root, "node.exe");
|
|
2154
|
+
const directIndex = path8.join(root, "index.js");
|
|
1827
2155
|
if (existsSync7(directNode) && existsSync7(directIndex)) {
|
|
1828
2156
|
return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
|
|
1829
2157
|
}
|
|
1830
2158
|
const versionDir = pickLatestCursorVersionDir(root);
|
|
1831
2159
|
if (!versionDir) return null;
|
|
1832
|
-
const nodeExe =
|
|
1833
|
-
const indexJs =
|
|
2160
|
+
const nodeExe = path8.join(versionDir, "node.exe");
|
|
2161
|
+
const indexJs = path8.join(versionDir, "index.js");
|
|
1834
2162
|
if (!existsSync7(nodeExe) || !existsSync7(indexJs)) return null;
|
|
1835
2163
|
return { nodeExe, indexJs, versionDir };
|
|
1836
2164
|
}
|
|
@@ -1849,13 +2177,13 @@ function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
|
|
|
1849
2177
|
function resolveCursorSpawn(agentBin) {
|
|
1850
2178
|
if (process.platform === "win32") {
|
|
1851
2179
|
const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
|
|
1852
|
-
const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync8(
|
|
2180
|
+
const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync8(path9.join(path9.dirname(agentBin), "index.js"));
|
|
1853
2181
|
const isDefaultShim = agentBin === "agent";
|
|
1854
2182
|
if (isCursorWrapper || isBundledNode || isDefaultShim) {
|
|
1855
|
-
const bundled = isCursorWrapper ? resolveWindowsCursorBundled(
|
|
2183
|
+
const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path9.dirname(agentBin)) : isBundledNode ? {
|
|
1856
2184
|
nodeExe: agentBin,
|
|
1857
|
-
indexJs:
|
|
1858
|
-
versionDir:
|
|
2185
|
+
indexJs: path9.join(path9.dirname(agentBin), "index.js"),
|
|
2186
|
+
versionDir: path9.dirname(agentBin)
|
|
1859
2187
|
} : resolveWindowsCursorBundled();
|
|
1860
2188
|
if (bundled) {
|
|
1861
2189
|
return bundledSpawnTarget(bundled.nodeExe, bundled.indexJs, bundled.versionDir);
|
|
@@ -1875,18 +2203,18 @@ function resolveAgentBin() {
|
|
|
1875
2203
|
process.env.KYNVER_CURSOR_AGENT_ROOT?.trim() || void 0
|
|
1876
2204
|
);
|
|
1877
2205
|
if (bundled) return bundled.nodeExe;
|
|
1878
|
-
const localAgent =
|
|
2206
|
+
const localAgent = path9.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
|
|
1879
2207
|
if (existsSync8(localAgent)) return localAgent;
|
|
1880
2208
|
}
|
|
1881
2209
|
return "agent";
|
|
1882
2210
|
}
|
|
1883
2211
|
function cursorWorkerEnv(agentBin, spawnTarget) {
|
|
1884
|
-
return {
|
|
2212
|
+
return scrubWorkerEnv({
|
|
1885
2213
|
...process.env,
|
|
1886
2214
|
CI: "1",
|
|
1887
2215
|
NO_COLOR: "1",
|
|
1888
|
-
...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS:
|
|
1889
|
-
};
|
|
2216
|
+
...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path9.basename(agentBin) || "agent.cmd" } : {}
|
|
2217
|
+
});
|
|
1890
2218
|
}
|
|
1891
2219
|
var cursorProvider = {
|
|
1892
2220
|
name: "cursor",
|
|
@@ -1902,6 +2230,7 @@ var cursorProvider = {
|
|
|
1902
2230
|
const model = preflight.model;
|
|
1903
2231
|
const stdoutFd = openSync2(opts.stdoutPath, "a");
|
|
1904
2232
|
const stderrFd = openSync2(opts.stderrPath, "a");
|
|
2233
|
+
const stdio = ["ignore", stdoutFd, stderrFd];
|
|
1905
2234
|
const agentBin = resolveAgentBin();
|
|
1906
2235
|
const spawnTarget = resolveCursorSpawn(agentBin);
|
|
1907
2236
|
const child = spawn2(
|
|
@@ -1924,7 +2253,7 @@ var cursorProvider = {
|
|
|
1924
2253
|
cwd: opts.worktreePath,
|
|
1925
2254
|
detached: spawnTarget.detached,
|
|
1926
2255
|
shell: spawnTarget.shell,
|
|
1927
|
-
stdio
|
|
2256
|
+
stdio,
|
|
1928
2257
|
env: cursorWorkerEnv(agentBin, spawnTarget)
|
|
1929
2258
|
})
|
|
1930
2259
|
);
|
|
@@ -1959,7 +2288,7 @@ function resolveWorkerProvider(name) {
|
|
|
1959
2288
|
// src/auto-complete.ts
|
|
1960
2289
|
import { spawn as spawn3 } from "node:child_process";
|
|
1961
2290
|
import { existsSync as existsSync9, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
|
|
1962
|
-
import
|
|
2291
|
+
import path12 from "node:path";
|
|
1963
2292
|
import { fileURLToPath } from "node:url";
|
|
1964
2293
|
|
|
1965
2294
|
// src/completion-ack.ts
|
|
@@ -1976,7 +2305,40 @@ function persistCompletionAck(worker, runId, fields) {
|
|
|
1976
2305
|
}
|
|
1977
2306
|
|
|
1978
2307
|
// src/worker-ops.ts
|
|
1979
|
-
import
|
|
2308
|
+
import path11 from "node:path";
|
|
2309
|
+
|
|
2310
|
+
// src/completion-response.ts
|
|
2311
|
+
function asRecord(value) {
|
|
2312
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2313
|
+
}
|
|
2314
|
+
function asString(value) {
|
|
2315
|
+
if (typeof value !== "string") return null;
|
|
2316
|
+
const trimmed = value.trim();
|
|
2317
|
+
return trimmed.length ? trimmed : null;
|
|
2318
|
+
}
|
|
2319
|
+
var ADVANCED_OUTCOMES = /* @__PURE__ */ new Set([
|
|
2320
|
+
"review_scheduled",
|
|
2321
|
+
"review_already_scheduled"
|
|
2322
|
+
]);
|
|
2323
|
+
function summarizeHarnessCompletionResponse(parsed) {
|
|
2324
|
+
const record = asRecord(parsed);
|
|
2325
|
+
if (!record) {
|
|
2326
|
+
return { routeOutcome: null, taskAdvanced: false, detail: null };
|
|
2327
|
+
}
|
|
2328
|
+
const outcome = asString(record.outcome);
|
|
2329
|
+
const detail = asString(record.detail) ?? asString(record.error);
|
|
2330
|
+
const task = asRecord(record.task);
|
|
2331
|
+
const taskStatus = task ? asString(task.status) : null;
|
|
2332
|
+
const taskAdvanced = outcome !== null && ADVANCED_OUTCOMES.has(outcome) || taskStatus === "awaiting_review" || taskStatus === "done";
|
|
2333
|
+
return {
|
|
2334
|
+
routeOutcome: outcome,
|
|
2335
|
+
taskAdvanced,
|
|
2336
|
+
detail
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
function completionPostSucceeded(summary) {
|
|
2340
|
+
return summary.taskAdvanced;
|
|
2341
|
+
}
|
|
1980
2342
|
|
|
1981
2343
|
// src/pr-handoff/pr-handoff-assess.ts
|
|
1982
2344
|
var REVIEW_LANE_RULE = /^(lane:)?(review|deep_review|planning|landing)(:|$)/i;
|
|
@@ -2038,6 +2400,36 @@ function buildPrHandoffSnapshotFromStatus(status, extras) {
|
|
|
2038
2400
|
|
|
2039
2401
|
// src/pr-handoff/pr-handoff-gh.ts
|
|
2040
2402
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2403
|
+
|
|
2404
|
+
// src/github-repo.ts
|
|
2405
|
+
function parseGithubOwnerRepo(remoteUrl) {
|
|
2406
|
+
const trimmed = remoteUrl.trim();
|
|
2407
|
+
if (!trimmed) return null;
|
|
2408
|
+
const ssh = trimmed.match(/^git@github\.com:([^/]+\/[^/\s]+?)(?:\.git)?$/i);
|
|
2409
|
+
if (ssh) return normalizeOwnerRepo(ssh[1]);
|
|
2410
|
+
const scp = trimmed.match(/^ssh:\/\/git@github\.com\/([^/]+\/[^/\s]+?)(?:\.git)?$/i);
|
|
2411
|
+
if (scp) return normalizeOwnerRepo(scp[1]);
|
|
2412
|
+
try {
|
|
2413
|
+
const url = new URL(trimmed.includes("://") ? trimmed : `https://${trimmed}`);
|
|
2414
|
+
if (url.hostname.toLowerCase() !== "github.com") return null;
|
|
2415
|
+
const parts = url.pathname.replace(/^\/+|\/+$/g, "").split("/");
|
|
2416
|
+
if (parts.length < 2) return null;
|
|
2417
|
+
const [owner, repo] = parts;
|
|
2418
|
+
if (!owner || !repo) return null;
|
|
2419
|
+
return `${owner}/${repo.replace(/\.git$/i, "")}`;
|
|
2420
|
+
} catch {
|
|
2421
|
+
return null;
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
function normalizeOwnerRepo(value) {
|
|
2425
|
+
const parts = value.split("/").filter(Boolean);
|
|
2426
|
+
if (parts.length < 2) return null;
|
|
2427
|
+
const owner = parts[0];
|
|
2428
|
+
const repo = parts[1].replace(/\.git$/i, "");
|
|
2429
|
+
return owner && repo ? `${owner}/${repo}` : null;
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
// src/pr-handoff/pr-handoff-gh.ts
|
|
2041
2433
|
function capture(bin, cwd, args) {
|
|
2042
2434
|
try {
|
|
2043
2435
|
const res = spawnSync2(bin, args, { cwd, encoding: "utf8" });
|
|
@@ -2060,21 +2452,13 @@ var defaultPrHandoffExec = {
|
|
|
2060
2452
|
git: (cwd, args) => gitCapture(cwd, args),
|
|
2061
2453
|
gh: (cwd, args) => capture("gh", cwd, args)
|
|
2062
2454
|
};
|
|
2063
|
-
function parseGithubRepo(remoteUrl) {
|
|
2064
|
-
const trimmed = remoteUrl.trim();
|
|
2065
|
-
const ssh = trimmed.match(/git@github\.com:([^/]+\/[^/.]+)(?:\.git)?/i);
|
|
2066
|
-
if (ssh) return ssh[1];
|
|
2067
|
-
const https = trimmed.match(/github\.com[/:]([^/]+\/[^/.]+?)(?:\.git)?/i);
|
|
2068
|
-
if (https) return https[1];
|
|
2069
|
-
return null;
|
|
2070
|
-
}
|
|
2071
2455
|
function firstLine(text) {
|
|
2072
2456
|
return text.split("\n").map((l) => l.trim()).find(Boolean) ?? "";
|
|
2073
2457
|
}
|
|
2074
2458
|
function resolveGithubRepo(worktreePath, exec) {
|
|
2075
2459
|
const remote = exec.git(worktreePath, ["remote", "get-url", "origin"]);
|
|
2076
2460
|
if (remote.status !== 0) return null;
|
|
2077
|
-
return
|
|
2461
|
+
return parseGithubOwnerRepo(remote.stdout);
|
|
2078
2462
|
}
|
|
2079
2463
|
function resolveHeadCommit(worktreePath, exec) {
|
|
2080
2464
|
const head = exec.git(worktreePath, ["rev-parse", "HEAD"]);
|
|
@@ -2313,7 +2697,7 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
|
|
|
2313
2697
|
}
|
|
2314
2698
|
|
|
2315
2699
|
// src/worker-lifecycle.ts
|
|
2316
|
-
import
|
|
2700
|
+
import path10 from "node:path";
|
|
2317
2701
|
var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
|
|
2318
2702
|
"awaiting_review",
|
|
2319
2703
|
"blocked",
|
|
@@ -2369,7 +2753,7 @@ function syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick) {
|
|
|
2369
2753
|
const synced = [];
|
|
2370
2754
|
for (const name of Object.keys(run.workers || {})) {
|
|
2371
2755
|
const worker = readJson(
|
|
2372
|
-
|
|
2756
|
+
path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2373
2757
|
void 0
|
|
2374
2758
|
);
|
|
2375
2759
|
if (!worker?.taskId || isCompletionAcknowledged(worker)) continue;
|
|
@@ -2408,10 +2792,10 @@ function completionErrorText(parsed) {
|
|
|
2408
2792
|
}
|
|
2409
2793
|
return void 0;
|
|
2410
2794
|
}
|
|
2411
|
-
function
|
|
2795
|
+
function asRecord2(value) {
|
|
2412
2796
|
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
2413
2797
|
}
|
|
2414
|
-
function
|
|
2798
|
+
function asString2(value) {
|
|
2415
2799
|
if (typeof value !== "string") return null;
|
|
2416
2800
|
const trimmed = value.trim();
|
|
2417
2801
|
return trimmed.length ? trimmed : null;
|
|
@@ -2531,7 +2915,27 @@ async function tryCompleteWorker(args) {
|
|
|
2531
2915
|
}
|
|
2532
2916
|
}
|
|
2533
2917
|
if (result.ok) {
|
|
2918
|
+
const summary = summarizeHarnessCompletionResponse(result.parsed);
|
|
2919
|
+
if (!completionPostSucceeded(summary)) {
|
|
2920
|
+
const detail2 = summary.detail ?? (summary.routeOutcome ? `harness completion returned ${summary.routeOutcome}` : "harness completion did not advance the linked task");
|
|
2921
|
+
const reason2 = `completion acknowledged but board not advanced: ${detail2}`;
|
|
2922
|
+
persistCompletionBlocker(worker, reason2);
|
|
2923
|
+
const ack2 = {
|
|
2924
|
+
completionReportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2925
|
+
completionOutcome: "rejected",
|
|
2926
|
+
completionResponse: result.parsed
|
|
2927
|
+
};
|
|
2928
|
+
persistCompletionAck(worker, worker.runId, ack2);
|
|
2929
|
+
return {
|
|
2930
|
+
ok: false,
|
|
2931
|
+
httpStatus: result.status,
|
|
2932
|
+
response: result.parsed,
|
|
2933
|
+
reason: reason2,
|
|
2934
|
+
completionBlocked: true
|
|
2935
|
+
};
|
|
2936
|
+
}
|
|
2534
2937
|
persistCompletionBlocker(worker, void 0);
|
|
2938
|
+
const routeOutcome = summary.routeOutcome ?? "acknowledged";
|
|
2535
2939
|
const ack = {
|
|
2536
2940
|
completionReportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2537
2941
|
completionOutcome: "acknowledged",
|
|
@@ -2544,6 +2948,7 @@ async function tryCompleteWorker(args) {
|
|
|
2544
2948
|
ok: true,
|
|
2545
2949
|
httpStatus: result.status,
|
|
2546
2950
|
response: result.parsed,
|
|
2951
|
+
reason: routeOutcome,
|
|
2547
2952
|
...prUrl ? { prHandoff: { prUrl } } : {}
|
|
2548
2953
|
};
|
|
2549
2954
|
}
|
|
@@ -2606,7 +3011,7 @@ function workerStatus(args) {
|
|
|
2606
3011
|
const worker = loadWorker(String(args.run), String(args.name));
|
|
2607
3012
|
const run = loadRun(worker.runId);
|
|
2608
3013
|
const status = computeWorkerStatus(worker, workerStatusOptions(run));
|
|
2609
|
-
writeJson(
|
|
3014
|
+
writeJson(path11.join(worker.workerDir, "last-status.json"), status);
|
|
2610
3015
|
console.log(JSON.stringify(status, null, 2));
|
|
2611
3016
|
}
|
|
2612
3017
|
function buildRunBoard(runId) {
|
|
@@ -2614,7 +3019,7 @@ function buildRunBoard(runId) {
|
|
|
2614
3019
|
const names = Object.keys(run.workers || {});
|
|
2615
3020
|
const workers = names.map((name) => {
|
|
2616
3021
|
const worker = readJson(
|
|
2617
|
-
|
|
3022
|
+
path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2618
3023
|
void 0
|
|
2619
3024
|
);
|
|
2620
3025
|
if (!worker) {
|
|
@@ -2636,22 +3041,23 @@ function buildRunBoard(runId) {
|
|
|
2636
3041
|
const completionBlocker = typeof rawBlocker === "string" && rawBlocker ? rawBlocker : void 0;
|
|
2637
3042
|
const boardStatus = completionBlocker ? "blocked" : status.status;
|
|
2638
3043
|
const boardAttention = completionBlocker ? "blocked" : status.attention.state;
|
|
2639
|
-
const completionResponse =
|
|
2640
|
-
const completionTask =
|
|
2641
|
-
const completionOutcome =
|
|
2642
|
-
const completionRouteStatus =
|
|
3044
|
+
const completionResponse = asRecord2(worker.completionResponse);
|
|
3045
|
+
const completionTask = asRecord2(completionResponse?.task);
|
|
3046
|
+
const completionOutcome = asString2(completionResponse?.outcome);
|
|
3047
|
+
const completionRouteStatus = asString2(completionResponse?.status);
|
|
2643
3048
|
const completionWarnings = Array.isArray(completionResponse?.warnings) ? completionResponse.warnings.filter((w) => typeof w === "string" && w.trim().length > 0) : [];
|
|
2644
|
-
const prUrl =
|
|
3049
|
+
const prUrl = asString2(completionTask?.prUrl) ?? asString2(completionResponse?.prUrl);
|
|
3050
|
+
const completionReportedAt = asString2(worker.completionReportedAt);
|
|
2645
3051
|
const lifecycleStage = deriveLifecycleStage({
|
|
2646
3052
|
finished: isFinishedWorkerStatus(status),
|
|
2647
3053
|
completionBlocker,
|
|
2648
3054
|
completionOutcome,
|
|
2649
|
-
completionReportedAt
|
|
3055
|
+
completionReportedAt
|
|
2650
3056
|
});
|
|
2651
3057
|
const nextAction = deriveNextAction({
|
|
2652
3058
|
completionBlocker,
|
|
2653
3059
|
completionOutcome,
|
|
2654
|
-
completionReportedAt
|
|
3060
|
+
completionReportedAt,
|
|
2655
3061
|
finished: isFinishedWorkerStatus(status)
|
|
2656
3062
|
});
|
|
2657
3063
|
const handoffState = deriveHandoffState({
|
|
@@ -2697,7 +3103,7 @@ function buildRunBoard(runId) {
|
|
|
2697
3103
|
gitAncestry: status.gitAncestry,
|
|
2698
3104
|
finalResult: status.finalResult,
|
|
2699
3105
|
lifecycleStage,
|
|
2700
|
-
completionReportedAt
|
|
3106
|
+
completionReportedAt,
|
|
2701
3107
|
completionOutcome: worker.completionOutcome ?? null,
|
|
2702
3108
|
completionRouteStatus,
|
|
2703
3109
|
completionRouteOutcome: completionOutcome,
|
|
@@ -2724,7 +3130,7 @@ function buildRunBoard(runId) {
|
|
|
2724
3130
|
needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
|
|
2725
3131
|
workers
|
|
2726
3132
|
};
|
|
2727
|
-
writeJson(
|
|
3133
|
+
writeJson(path11.join(runDirectory(run.id), "last-board.json"), board);
|
|
2728
3134
|
return board;
|
|
2729
3135
|
}
|
|
2730
3136
|
async function publishHarnessBoardSnapshot(args, source) {
|
|
@@ -2913,12 +3319,12 @@ async function autoCompleteWorkerCli(raw) {
|
|
|
2913
3319
|
}
|
|
2914
3320
|
}
|
|
2915
3321
|
function resolveDefaultCliPath() {
|
|
2916
|
-
return
|
|
3322
|
+
return path12.join(fileURLToPath(new URL(".", import.meta.url)), "cli.js");
|
|
2917
3323
|
}
|
|
2918
3324
|
function spawnCompletionSidecar(opts) {
|
|
2919
3325
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath();
|
|
2920
3326
|
if (!existsSync9(cliPath)) return void 0;
|
|
2921
|
-
const logPath =
|
|
3327
|
+
const logPath = path12.join(opts.workerDir, "auto-complete.log");
|
|
2922
3328
|
let logFd;
|
|
2923
3329
|
try {
|
|
2924
3330
|
logFd = openSync3(logPath, "a");
|
|
@@ -3000,16 +3406,16 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3000
3406
|
launchModel = preflight.model;
|
|
3001
3407
|
}
|
|
3002
3408
|
const { worktreesDir } = getPaths();
|
|
3003
|
-
const workerDir =
|
|
3409
|
+
const workerDir = path13.join(runDirectory(run.id), "workers", name);
|
|
3004
3410
|
mkdirSync3(workerDir, { recursive: true });
|
|
3005
|
-
const worktreePath =
|
|
3411
|
+
const worktreePath = path13.join(worktreesDir, run.id, name);
|
|
3006
3412
|
const branch = opts.branch || `agent/${run.id}/${name}`;
|
|
3007
3413
|
if (existsSync10(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
|
|
3008
3414
|
git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
|
|
3009
3415
|
git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
|
|
3010
|
-
const stdoutPath =
|
|
3011
|
-
const stderrPath =
|
|
3012
|
-
const heartbeatPath =
|
|
3416
|
+
const stdoutPath = path13.join(workerDir, "stdout.jsonl");
|
|
3417
|
+
const stderrPath = path13.join(workerDir, "stderr.log");
|
|
3418
|
+
const heartbeatPath = path13.join(workerDir, "heartbeat.jsonl");
|
|
3013
3419
|
const prompt = buildPrompt({
|
|
3014
3420
|
task: opts.task,
|
|
3015
3421
|
ownedPaths: opts.ownedPaths || [],
|
|
@@ -3070,7 +3476,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3070
3476
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3071
3477
|
};
|
|
3072
3478
|
saveWorker(run.id, worker);
|
|
3073
|
-
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath:
|
|
3479
|
+
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path13.join(workerDir, "worker.json") } };
|
|
3074
3480
|
run.status = "running";
|
|
3075
3481
|
saveRun(run);
|
|
3076
3482
|
if (worker.agentOsId && worker.taskId) {
|
|
@@ -3362,18 +3768,18 @@ function buildPlanPersistIdempotencyKey(input) {
|
|
|
3362
3768
|
|
|
3363
3769
|
// src/plan-persist/paths.ts
|
|
3364
3770
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
3365
|
-
import { homedir as
|
|
3366
|
-
import
|
|
3771
|
+
import { homedir as homedir4 } from "node:os";
|
|
3772
|
+
import path14 from "node:path";
|
|
3367
3773
|
function resolveKynverStateRoot() {
|
|
3368
3774
|
const env = process.env.KYNVER_STATE_ROOT;
|
|
3369
|
-
if (env) return
|
|
3370
|
-
return
|
|
3775
|
+
if (env) return path14.resolve(env);
|
|
3776
|
+
return path14.join(homedir4(), ".kynver", "state");
|
|
3371
3777
|
}
|
|
3372
3778
|
function planOutboxDir() {
|
|
3373
|
-
return
|
|
3779
|
+
return path14.join(resolveKynverStateRoot(), "plan-outbox");
|
|
3374
3780
|
}
|
|
3375
3781
|
function planOutboxArchiveDir() {
|
|
3376
|
-
return
|
|
3782
|
+
return path14.join(resolveKynverStateRoot(), "plan-outbox-archive");
|
|
3377
3783
|
}
|
|
3378
3784
|
function ensurePlanOutboxDirs() {
|
|
3379
3785
|
const outboxDir = planOutboxDir();
|
|
@@ -3384,8 +3790,8 @@ function ensurePlanOutboxDirs() {
|
|
|
3384
3790
|
}
|
|
3385
3791
|
function isTmpOnlyPath(filePath) {
|
|
3386
3792
|
if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
|
|
3387
|
-
const resolved =
|
|
3388
|
-
return resolved.startsWith("/tmp/") || resolved.startsWith(
|
|
3793
|
+
const resolved = path14.resolve(filePath);
|
|
3794
|
+
return resolved.startsWith("/tmp/") || resolved.startsWith(path14.join("/var", "folders"));
|
|
3389
3795
|
}
|
|
3390
3796
|
|
|
3391
3797
|
// src/plan-persist/outbox-store.ts
|
|
@@ -3397,7 +3803,7 @@ import {
|
|
|
3397
3803
|
writeFileSync as writeFileSync3,
|
|
3398
3804
|
unlinkSync
|
|
3399
3805
|
} from "node:fs";
|
|
3400
|
-
import
|
|
3806
|
+
import path15 from "node:path";
|
|
3401
3807
|
import { randomUUID } from "node:crypto";
|
|
3402
3808
|
var DEFAULT_MAX_RETRIES = 12;
|
|
3403
3809
|
function listOutboxItems() {
|
|
@@ -3405,7 +3811,7 @@ function listOutboxItems() {
|
|
|
3405
3811
|
const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
|
|
3406
3812
|
const items = [];
|
|
3407
3813
|
for (const file of files) {
|
|
3408
|
-
const item = readOutboxItem(
|
|
3814
|
+
const item = readOutboxItem(path15.join(outboxDir, file));
|
|
3409
3815
|
if (item && item.queueStatus === "queued") items.push(item);
|
|
3410
3816
|
}
|
|
3411
3817
|
return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
@@ -3426,7 +3832,7 @@ function readOutboxItem(jsonPath) {
|
|
|
3426
3832
|
}
|
|
3427
3833
|
function readOutboxBody(item) {
|
|
3428
3834
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3429
|
-
const bodyFile =
|
|
3835
|
+
const bodyFile = path15.join(outboxDir, item.bodyPath);
|
|
3430
3836
|
return readFileSync6(bodyFile, "utf8");
|
|
3431
3837
|
}
|
|
3432
3838
|
function writeOutboxItem(input, opts) {
|
|
@@ -3434,8 +3840,8 @@ function writeOutboxItem(input, opts) {
|
|
|
3434
3840
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3435
3841
|
const id = opts.existing?.id ?? randomUUID();
|
|
3436
3842
|
const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
|
|
3437
|
-
const jsonPath =
|
|
3438
|
-
const bodyFile =
|
|
3843
|
+
const jsonPath = path15.join(outboxDir, `${id}.json`);
|
|
3844
|
+
const bodyFile = path15.join(outboxDir, bodyPath);
|
|
3439
3845
|
if (!opts.existing) {
|
|
3440
3846
|
writeFileSync3(bodyFile, input.body, "utf8");
|
|
3441
3847
|
}
|
|
@@ -3471,24 +3877,24 @@ function writeOutboxItem(input, opts) {
|
|
|
3471
3877
|
}
|
|
3472
3878
|
function saveOutboxItem(item) {
|
|
3473
3879
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3474
|
-
const jsonPath =
|
|
3880
|
+
const jsonPath = path15.join(outboxDir, `${item.id}.json`);
|
|
3475
3881
|
writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
|
|
3476
3882
|
`, { mode: 384 });
|
|
3477
3883
|
}
|
|
3478
3884
|
function archiveOutboxItem(item) {
|
|
3479
3885
|
const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
|
|
3480
|
-
const jsonSrc =
|
|
3481
|
-
const bodySrc =
|
|
3482
|
-
const jsonDst =
|
|
3483
|
-
const bodyDst =
|
|
3886
|
+
const jsonSrc = path15.join(outboxDir, `${item.id}.json`);
|
|
3887
|
+
const bodySrc = path15.join(outboxDir, item.bodyPath);
|
|
3888
|
+
const jsonDst = path15.join(archiveDir, `${item.id}.json`);
|
|
3889
|
+
const bodyDst = path15.join(archiveDir, item.bodyPath);
|
|
3484
3890
|
if (existsSync12(jsonSrc)) renameSync(jsonSrc, jsonDst);
|
|
3485
3891
|
if (existsSync12(bodySrc)) renameSync(bodySrc, bodyDst);
|
|
3486
3892
|
}
|
|
3487
3893
|
function outboxItemPaths(item) {
|
|
3488
3894
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3489
3895
|
return {
|
|
3490
|
-
jsonPath:
|
|
3491
|
-
bodyPath:
|
|
3896
|
+
jsonPath: path15.join(outboxDir, `${item.id}.json`),
|
|
3897
|
+
bodyPath: path15.join(outboxDir, item.bodyPath)
|
|
3492
3898
|
};
|
|
3493
3899
|
}
|
|
3494
3900
|
function outboxInputFromItem(item, body) {
|
|
@@ -3674,7 +4080,7 @@ function markOutboxFailed(item, message) {
|
|
|
3674
4080
|
}
|
|
3675
4081
|
|
|
3676
4082
|
// src/plan-persist/drain.ts
|
|
3677
|
-
import
|
|
4083
|
+
import path16 from "node:path";
|
|
3678
4084
|
async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
3679
4085
|
const items = listOutboxItems().filter(
|
|
3680
4086
|
(item) => opts.outboxId ? item.id === opts.outboxId : true
|
|
@@ -3708,7 +4114,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
|
3708
4114
|
return result;
|
|
3709
4115
|
}
|
|
3710
4116
|
function loadOutboxById(outboxId) {
|
|
3711
|
-
const jsonPath =
|
|
4117
|
+
const jsonPath = path16.join(planOutboxDir(), `${outboxId}.json`);
|
|
3712
4118
|
return readOutboxItem(jsonPath);
|
|
3713
4119
|
}
|
|
3714
4120
|
|
|
@@ -3808,7 +4214,7 @@ async function dispatchRun(args) {
|
|
|
3808
4214
|
const activeHarnessWorkers = [];
|
|
3809
4215
|
for (const name of Object.keys(run.workers || {})) {
|
|
3810
4216
|
const worker = readJson(
|
|
3811
|
-
|
|
4217
|
+
path17.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
3812
4218
|
void 0
|
|
3813
4219
|
);
|
|
3814
4220
|
if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
|
|
@@ -3832,7 +4238,8 @@ async function dispatchRun(args) {
|
|
|
3832
4238
|
harnessBoardSnapshot: buildRunBoard(run.id),
|
|
3833
4239
|
...args.lane ? { lane: String(args.lane) } : {},
|
|
3834
4240
|
executor: args.executor ? String(args.executor) : "harness",
|
|
3835
|
-
...args.diskPath ? { diskPath: String(args.diskPath) } : {}
|
|
4241
|
+
...args.diskPath ? { diskPath: String(args.diskPath) } : {},
|
|
4242
|
+
...args.targetTaskId ? { targetTaskId: String(args.targetTaskId) } : {}
|
|
3836
4243
|
};
|
|
3837
4244
|
const dispatch = await postJsonWithCredentialRefresh(dispatchUrl, secret, body, { agentOsId, baseUrl: base });
|
|
3838
4245
|
const responseBody = dispatch.response;
|
|
@@ -4012,7 +4419,7 @@ async function dispatchRun(args) {
|
|
|
4012
4419
|
}
|
|
4013
4420
|
|
|
4014
4421
|
// src/sweep.ts
|
|
4015
|
-
import
|
|
4422
|
+
import path18 from "node:path";
|
|
4016
4423
|
async function sweepRun(args) {
|
|
4017
4424
|
const pipeline = args.pipeline === true || args.pipeline === "true";
|
|
4018
4425
|
try {
|
|
@@ -4025,7 +4432,7 @@ async function sweepRun(args) {
|
|
|
4025
4432
|
const releasedLocalOrphans = [];
|
|
4026
4433
|
for (const name of Object.keys(run.workers || {})) {
|
|
4027
4434
|
const worker = readJson(
|
|
4028
|
-
|
|
4435
|
+
path18.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4029
4436
|
void 0
|
|
4030
4437
|
);
|
|
4031
4438
|
if (!worker || !worker.dispatched || !worker.taskId) continue;
|
|
@@ -4069,10 +4476,10 @@ async function sweepRun(args) {
|
|
|
4069
4476
|
|
|
4070
4477
|
// src/worktree.ts
|
|
4071
4478
|
import { existsSync as existsSync13, mkdirSync as mkdirSync5 } from "node:fs";
|
|
4072
|
-
import
|
|
4479
|
+
import path20 from "node:path";
|
|
4073
4480
|
|
|
4074
4481
|
// src/validate.ts
|
|
4075
|
-
import
|
|
4482
|
+
import path19 from "node:path";
|
|
4076
4483
|
var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
4077
4484
|
function validateRunId(runId) {
|
|
4078
4485
|
const trimmed = runId.trim();
|
|
@@ -4080,7 +4487,7 @@ function validateRunId(runId) {
|
|
|
4080
4487
|
return trimmed;
|
|
4081
4488
|
}
|
|
4082
4489
|
function validateRepo(repo) {
|
|
4083
|
-
const resolved =
|
|
4490
|
+
const resolved = path19.resolve(repo);
|
|
4084
4491
|
if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
|
|
4085
4492
|
return resolved;
|
|
4086
4493
|
}
|
|
@@ -4105,12 +4512,12 @@ function createRun(args) {
|
|
|
4105
4512
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4106
4513
|
workers: {}
|
|
4107
4514
|
};
|
|
4108
|
-
writeJson(
|
|
4515
|
+
writeJson(path20.join(dir, "run.json"), run);
|
|
4109
4516
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
4110
4517
|
}
|
|
4111
4518
|
function listRuns() {
|
|
4112
4519
|
const { runsDir } = getPaths();
|
|
4113
|
-
const rows = listRunIds(runsDir).map((id) => readJson(
|
|
4520
|
+
const rows = listRunIds(runsDir).map((id) => readJson(path20.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
|
|
4114
4521
|
id: run.id,
|
|
4115
4522
|
name: run.name,
|
|
4116
4523
|
status: run.status,
|
|
@@ -4125,7 +4532,7 @@ function failExists(message) {
|
|
|
4125
4532
|
}
|
|
4126
4533
|
|
|
4127
4534
|
// src/pipeline-tick.ts
|
|
4128
|
-
import
|
|
4535
|
+
import path30 from "node:path";
|
|
4129
4536
|
|
|
4130
4537
|
// src/pipeline-dispatch.ts
|
|
4131
4538
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -4179,10 +4586,10 @@ async function runPipelineDispatch(args, slots) {
|
|
|
4179
4586
|
}
|
|
4180
4587
|
|
|
4181
4588
|
// src/stale-reconcile.ts
|
|
4182
|
-
import
|
|
4589
|
+
import path22 from "node:path";
|
|
4183
4590
|
|
|
4184
4591
|
// src/finalize.ts
|
|
4185
|
-
import
|
|
4592
|
+
import path21 from "node:path";
|
|
4186
4593
|
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
|
|
4187
4594
|
function terminalStatusFor(run) {
|
|
4188
4595
|
const names = Object.keys(run.workers || {});
|
|
@@ -4193,7 +4600,7 @@ function terminalStatusFor(run) {
|
|
|
4193
4600
|
let anyLandingBlocked = false;
|
|
4194
4601
|
for (const name of names) {
|
|
4195
4602
|
const worker = readJson(
|
|
4196
|
-
|
|
4603
|
+
path21.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4197
4604
|
void 0
|
|
4198
4605
|
);
|
|
4199
4606
|
if (!worker) continue;
|
|
@@ -4245,7 +4652,7 @@ function reconcileStaleWorkers() {
|
|
|
4245
4652
|
const now = Date.now();
|
|
4246
4653
|
for (const run of listRunRecords()) {
|
|
4247
4654
|
for (const name of Object.keys(run.workers || {})) {
|
|
4248
|
-
const workerPath =
|
|
4655
|
+
const workerPath = path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
4249
4656
|
const worker = readJson(workerPath, void 0);
|
|
4250
4657
|
if (!worker || worker.status !== "running") {
|
|
4251
4658
|
outcomes.push({
|
|
@@ -4339,7 +4746,7 @@ function reconcileRunsCli() {
|
|
|
4339
4746
|
}
|
|
4340
4747
|
|
|
4341
4748
|
// src/plan-progress-daemon-sync.ts
|
|
4342
|
-
import
|
|
4749
|
+
import path23 from "node:path";
|
|
4343
4750
|
|
|
4344
4751
|
// src/plan-progress-sync.ts
|
|
4345
4752
|
async function syncPlanProgress(args) {
|
|
@@ -4363,7 +4770,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
4363
4770
|
const outcomes = [];
|
|
4364
4771
|
for (const name of Object.keys(run.workers || {})) {
|
|
4365
4772
|
const worker = readJson(
|
|
4366
|
-
|
|
4773
|
+
path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4367
4774
|
void 0
|
|
4368
4775
|
);
|
|
4369
4776
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -4412,7 +4819,7 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
4412
4819
|
}
|
|
4413
4820
|
|
|
4414
4821
|
// src/cleanup.ts
|
|
4415
|
-
import
|
|
4822
|
+
import path28 from "node:path";
|
|
4416
4823
|
|
|
4417
4824
|
// src/cleanup-types.ts
|
|
4418
4825
|
var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
@@ -4484,11 +4891,11 @@ function skipNodeModulesRemoval(input) {
|
|
|
4484
4891
|
|
|
4485
4892
|
// src/cleanup-execute.ts
|
|
4486
4893
|
import { existsSync as existsSync15, rmSync } from "node:fs";
|
|
4487
|
-
import
|
|
4894
|
+
import path25 from "node:path";
|
|
4488
4895
|
|
|
4489
4896
|
// src/cleanup-dir-size.ts
|
|
4490
4897
|
import { existsSync as existsSync14, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
|
|
4491
|
-
import
|
|
4898
|
+
import path24 from "node:path";
|
|
4492
4899
|
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
4493
4900
|
if (!existsSync14(root)) return 0;
|
|
4494
4901
|
let total = 0;
|
|
@@ -4504,7 +4911,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
4504
4911
|
}
|
|
4505
4912
|
for (const name of entries) {
|
|
4506
4913
|
if (seen++ > maxEntries) return null;
|
|
4507
|
-
const full =
|
|
4914
|
+
const full = path24.join(current, name);
|
|
4508
4915
|
let st;
|
|
4509
4916
|
try {
|
|
4510
4917
|
st = statSync2(full);
|
|
@@ -4588,20 +4995,20 @@ function removeWorktree(candidate, execute) {
|
|
|
4588
4995
|
}
|
|
4589
4996
|
}
|
|
4590
4997
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
4591
|
-
const resolved =
|
|
4592
|
-
const nm = resolved.endsWith(`${
|
|
4998
|
+
const resolved = path25.resolve(targetPath);
|
|
4999
|
+
const nm = resolved.endsWith(`${path25.sep}node_modules`) ? resolved : null;
|
|
4593
5000
|
if (!nm) return "path_outside_harness";
|
|
4594
|
-
const rel =
|
|
4595
|
-
if (rel.startsWith("..") ||
|
|
4596
|
-
const parts = rel.split(
|
|
5001
|
+
const rel = path25.relative(worktreesDir, nm);
|
|
5002
|
+
if (rel.startsWith("..") || path25.isAbsolute(rel)) return "path_outside_harness";
|
|
5003
|
+
const parts = rel.split(path25.sep);
|
|
4597
5004
|
if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
|
|
4598
|
-
if (!resolved.startsWith(
|
|
5005
|
+
if (!resolved.startsWith(path25.resolve(harnessRoot))) return "path_outside_harness";
|
|
4599
5006
|
return null;
|
|
4600
5007
|
}
|
|
4601
5008
|
|
|
4602
5009
|
// src/cleanup-scan.ts
|
|
4603
5010
|
import { existsSync as existsSync16, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
4604
|
-
import
|
|
5011
|
+
import path26 from "node:path";
|
|
4605
5012
|
function pathAgeMs(target, now) {
|
|
4606
5013
|
try {
|
|
4607
5014
|
const mtime = statSync3(target).mtimeMs;
|
|
@@ -4611,17 +5018,17 @@ function pathAgeMs(target, now) {
|
|
|
4611
5018
|
}
|
|
4612
5019
|
}
|
|
4613
5020
|
function isPathInside(child, parent) {
|
|
4614
|
-
const rel =
|
|
4615
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
5021
|
+
const rel = path26.relative(parent, child);
|
|
5022
|
+
return rel === "" || !rel.startsWith("..") && !path26.isAbsolute(rel);
|
|
4616
5023
|
}
|
|
4617
5024
|
function scanNodeModulesCandidates(opts) {
|
|
4618
5025
|
const candidates = [];
|
|
4619
5026
|
const seen = /* @__PURE__ */ new Set();
|
|
4620
5027
|
for (const entry of opts.index.values()) {
|
|
4621
5028
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
4622
|
-
const nm =
|
|
5029
|
+
const nm = path26.join(entry.worktreePath, "node_modules");
|
|
4623
5030
|
if (!existsSync16(nm)) continue;
|
|
4624
|
-
const resolved =
|
|
5031
|
+
const resolved = path26.resolve(nm);
|
|
4625
5032
|
if (seen.has(resolved)) continue;
|
|
4626
5033
|
seen.add(resolved);
|
|
4627
5034
|
candidates.push({
|
|
@@ -4637,13 +5044,13 @@ function scanNodeModulesCandidates(opts) {
|
|
|
4637
5044
|
if (!opts.includeOrphans || !existsSync16(opts.worktreesDir)) return candidates;
|
|
4638
5045
|
for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
|
|
4639
5046
|
if (!runEntry.isDirectory()) continue;
|
|
4640
|
-
const runPath =
|
|
5047
|
+
const runPath = path26.join(opts.worktreesDir, runEntry.name);
|
|
4641
5048
|
for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
|
|
4642
5049
|
if (!workerEntry.isDirectory()) continue;
|
|
4643
|
-
const worktreePath =
|
|
4644
|
-
const nm =
|
|
5050
|
+
const worktreePath = path26.join(runPath, workerEntry.name);
|
|
5051
|
+
const nm = path26.join(worktreePath, "node_modules");
|
|
4645
5052
|
if (!existsSync16(nm)) continue;
|
|
4646
|
-
const resolved =
|
|
5053
|
+
const resolved = path26.resolve(nm);
|
|
4647
5054
|
if (seen.has(resolved)) continue;
|
|
4648
5055
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
4649
5056
|
seen.add(resolved);
|
|
@@ -4683,17 +5090,17 @@ function scanWorktreeCandidates(opts) {
|
|
|
4683
5090
|
}
|
|
4684
5091
|
|
|
4685
5092
|
// src/cleanup-worktree-index.ts
|
|
4686
|
-
import
|
|
5093
|
+
import path27 from "node:path";
|
|
4687
5094
|
function buildWorktreeIndex() {
|
|
4688
5095
|
const index = /* @__PURE__ */ new Map();
|
|
4689
5096
|
for (const run of listRunRecords()) {
|
|
4690
5097
|
for (const name of Object.keys(run.workers || {})) {
|
|
4691
|
-
const workerPath =
|
|
5098
|
+
const workerPath = path27.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
4692
5099
|
const worker = readJson(workerPath, void 0);
|
|
4693
5100
|
if (!worker?.worktreePath) continue;
|
|
4694
5101
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
4695
|
-
index.set(
|
|
4696
|
-
worktreePath:
|
|
5102
|
+
index.set(path27.resolve(worker.worktreePath), {
|
|
5103
|
+
worktreePath: path27.resolve(worker.worktreePath),
|
|
4697
5104
|
runId: run.id,
|
|
4698
5105
|
workerName: name,
|
|
4699
5106
|
run,
|
|
@@ -4707,8 +5114,8 @@ function buildWorktreeIndex() {
|
|
|
4707
5114
|
|
|
4708
5115
|
// src/cleanup.ts
|
|
4709
5116
|
function resolveOptions(options = {}) {
|
|
4710
|
-
const harnessRoot = options.harnessRoot ?
|
|
4711
|
-
const { worktreesDir } = options.harnessRoot ? { worktreesDir:
|
|
5117
|
+
const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
|
|
5118
|
+
const { worktreesDir } = options.harnessRoot ? { worktreesDir: path28.join(harnessRoot, "worktrees") } : getHarnessPaths();
|
|
4712
5119
|
const execute = options.execute === true;
|
|
4713
5120
|
const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
|
|
4714
5121
|
const worktreesAgeMs = options.worktreesAgeMs ?? 0;
|
|
@@ -4752,7 +5159,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
4752
5159
|
actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
|
|
4753
5160
|
continue;
|
|
4754
5161
|
}
|
|
4755
|
-
const worktreePath =
|
|
5162
|
+
const worktreePath = path28.resolve(candidate.path, "..");
|
|
4756
5163
|
const indexed = index.get(worktreePath) ?? null;
|
|
4757
5164
|
const guardReason = skipNodeModulesRemoval({
|
|
4758
5165
|
indexed,
|
|
@@ -4768,7 +5175,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
4768
5175
|
actions.push(removeNodeModules(candidate, resolved.execute));
|
|
4769
5176
|
}
|
|
4770
5177
|
for (const candidate of scanWorktreeCandidates(scanOpts)) {
|
|
4771
|
-
const indexed = index.get(
|
|
5178
|
+
const indexed = index.get(path28.resolve(candidate.path)) ?? null;
|
|
4772
5179
|
const guardReason = skipWorktreeRemoval({
|
|
4773
5180
|
indexed,
|
|
4774
5181
|
includeOrphans: resolved.includeOrphans,
|
|
@@ -4831,13 +5238,62 @@ function isPipelineCleanupEnabled() {
|
|
|
4831
5238
|
return process.env.KYNVER_PIPELINE_CLEANUP !== "0";
|
|
4832
5239
|
}
|
|
4833
5240
|
|
|
5241
|
+
// src/installed-package-versions.ts
|
|
5242
|
+
import { readFile } from "node:fs/promises";
|
|
5243
|
+
import { homedir as homedir5 } from "node:os";
|
|
5244
|
+
import path29 from "node:path";
|
|
5245
|
+
var MANAGED_PACKAGES = [
|
|
5246
|
+
"@kynver-app/runtime",
|
|
5247
|
+
"@kynver-app/openclaw-agent-os",
|
|
5248
|
+
"@kynver-app/mcp-agent-os"
|
|
5249
|
+
];
|
|
5250
|
+
function trim(value) {
|
|
5251
|
+
const out = value?.trim();
|
|
5252
|
+
return out ? out : null;
|
|
5253
|
+
}
|
|
5254
|
+
function unique(values) {
|
|
5255
|
+
return [...new Set(values.filter((value) => Boolean(value)))];
|
|
5256
|
+
}
|
|
5257
|
+
function moduleRoots() {
|
|
5258
|
+
const home = homedir5();
|
|
5259
|
+
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path29.join(home, ".openclaw", "npm");
|
|
5260
|
+
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"));
|
|
5261
|
+
return unique([
|
|
5262
|
+
path29.join(openClawPrefix, "lib", "node_modules"),
|
|
5263
|
+
path29.join(openClawPrefix, "node_modules"),
|
|
5264
|
+
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path29.join(npmGlobalRoot, "lib", "node_modules")
|
|
5265
|
+
]);
|
|
5266
|
+
}
|
|
5267
|
+
async function readVersion(packageJsonPath) {
|
|
5268
|
+
try {
|
|
5269
|
+
const parsed = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
5270
|
+
return typeof parsed.version === "string" && parsed.version.trim() ? parsed.version.trim() : null;
|
|
5271
|
+
} catch {
|
|
5272
|
+
return null;
|
|
5273
|
+
}
|
|
5274
|
+
}
|
|
5275
|
+
async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
5276
|
+
const roots = moduleRoots();
|
|
5277
|
+
const out = {};
|
|
5278
|
+
for (const packageName of MANAGED_PACKAGES) {
|
|
5279
|
+
for (const root of roots) {
|
|
5280
|
+
const packageJsonPath = path29.join(root, packageName, "package.json");
|
|
5281
|
+
const version = await readVersion(packageJsonPath);
|
|
5282
|
+
if (!version) continue;
|
|
5283
|
+
out[packageName] = { version, observedAt, path: packageJsonPath };
|
|
5284
|
+
break;
|
|
5285
|
+
}
|
|
5286
|
+
}
|
|
5287
|
+
return out;
|
|
5288
|
+
}
|
|
5289
|
+
|
|
4834
5290
|
// src/pipeline-tick.ts
|
|
4835
5291
|
async function completeFinishedWorkers(runId, args) {
|
|
4836
5292
|
const run = loadRun(runId);
|
|
4837
5293
|
const outcomes = [];
|
|
4838
5294
|
for (const name of Object.keys(run.workers || {})) {
|
|
4839
5295
|
const worker = readJson(
|
|
4840
|
-
|
|
5296
|
+
path30.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4841
5297
|
void 0
|
|
4842
5298
|
);
|
|
4843
5299
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -4868,12 +5324,14 @@ async function postOperatorTick(agentOsId, runId, resourceGate, args) {
|
|
|
4868
5324
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
4869
5325
|
const secret = await resolveCallbackSecretWithMint(args.secret ? String(args.secret) : void 0, agentOsId, { baseUrl: base });
|
|
4870
5326
|
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/operator/tick`;
|
|
5327
|
+
const packageVersions = await collectInstalledPackageVersions();
|
|
4871
5328
|
const res = await postJson(url, secret, {
|
|
4872
5329
|
agentOsId,
|
|
4873
5330
|
runId,
|
|
4874
5331
|
ingestHarness: true,
|
|
4875
5332
|
harnessBoardSnapshot: buildRunBoard(runId),
|
|
4876
|
-
resourceGate
|
|
5333
|
+
resourceGate,
|
|
5334
|
+
packageVersions
|
|
4877
5335
|
});
|
|
4878
5336
|
return { ok: res.ok, httpStatus: res.status, response: res.response };
|
|
4879
5337
|
}
|
|
@@ -4976,6 +5434,231 @@ async function runDaemon(args) {
|
|
|
4976
5434
|
console.error(JSON.stringify({ event: "daemon_stop", runId, agentOsId }));
|
|
4977
5435
|
}
|
|
4978
5436
|
|
|
5437
|
+
// src/plan-progress.ts
|
|
5438
|
+
import path31 from "node:path";
|
|
5439
|
+
|
|
5440
|
+
// src/bounded-build/constants.ts
|
|
5441
|
+
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
5442
|
+
var DEFAULT_BUILD_MEM_RESERVE_BYTES = 2 * 1024 * 1024 * 1024;
|
|
5443
|
+
var DEFAULT_NODE_OLD_SPACE_SIZE_MB = 1024;
|
|
5444
|
+
var DEFAULT_SYSTEMD_MEMORY_MAX = "1.5G";
|
|
5445
|
+
var DEFAULT_SYSTEMD_MEMORY_SWAP_MAX = "2G";
|
|
5446
|
+
|
|
5447
|
+
// src/bounded-build/node-options.ts
|
|
5448
|
+
var MAX_OLD_SPACE_RE = /--max-old-space-size=(\d+)/;
|
|
5449
|
+
function parsePositiveInt(value, fallback) {
|
|
5450
|
+
const n = Number(value);
|
|
5451
|
+
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
5452
|
+
return Math.floor(n);
|
|
5453
|
+
}
|
|
5454
|
+
function resolveNodeOldSpaceSizeMb() {
|
|
5455
|
+
return parsePositiveInt(process.env.KYNVER_NODE_OLD_SPACE_SIZE_MB, DEFAULT_NODE_OLD_SPACE_SIZE_MB);
|
|
5456
|
+
}
|
|
5457
|
+
function mergeNodeOptionsForBuildCheck(baseEnv = process.env) {
|
|
5458
|
+
const env = { ...baseEnv };
|
|
5459
|
+
if (process.env.KYNVER_NODE_OLD_SPACE_SIZE_MB === "0") {
|
|
5460
|
+
return env;
|
|
5461
|
+
}
|
|
5462
|
+
const existing = env.NODE_OPTIONS ?? "";
|
|
5463
|
+
if (MAX_OLD_SPACE_RE.test(existing)) {
|
|
5464
|
+
return env;
|
|
5465
|
+
}
|
|
5466
|
+
const mb = resolveNodeOldSpaceSizeMb();
|
|
5467
|
+
const flag = `--max-old-space-size=${mb}`;
|
|
5468
|
+
env.NODE_OPTIONS = existing.trim() ? `${existing.trim()} ${flag}` : flag;
|
|
5469
|
+
return env;
|
|
5470
|
+
}
|
|
5471
|
+
function formatNodeOptionsFlag(mb = resolveNodeOldSpaceSizeMb()) {
|
|
5472
|
+
return `--max-old-space-size=${mb}`;
|
|
5473
|
+
}
|
|
5474
|
+
|
|
5475
|
+
// src/bounded-build/systemd-wrap.ts
|
|
5476
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
5477
|
+
var systemdAvailableCache;
|
|
5478
|
+
function isSystemdRunAvailable() {
|
|
5479
|
+
if (process.env.KYNVER_BUILD_SKIP_SYSTEMD === "1" || process.env.KYNVER_BUILD_SKIP_SYSTEMD === "true") {
|
|
5480
|
+
return false;
|
|
5481
|
+
}
|
|
5482
|
+
if (systemdAvailableCache !== void 0) return systemdAvailableCache;
|
|
5483
|
+
if (process.platform !== "linux") {
|
|
5484
|
+
systemdAvailableCache = false;
|
|
5485
|
+
return false;
|
|
5486
|
+
}
|
|
5487
|
+
const res = spawnSync3("systemd-run", ["--version"], { encoding: "utf8", stdio: ["ignore", "ignore", "pipe"] });
|
|
5488
|
+
systemdAvailableCache = res.status === 0;
|
|
5489
|
+
return systemdAvailableCache;
|
|
5490
|
+
}
|
|
5491
|
+
function buildSystemdRunArgv(opts) {
|
|
5492
|
+
const memoryMax = opts.memoryMax ?? process.env.KYNVER_BUILD_SYSTEMD_MEMORY_MAX ?? DEFAULT_SYSTEMD_MEMORY_MAX;
|
|
5493
|
+
const memorySwapMax = opts.memorySwapMax ?? process.env.KYNVER_BUILD_SYSTEMD_MEMORY_SWAP_MAX ?? DEFAULT_SYSTEMD_MEMORY_SWAP_MAX;
|
|
5494
|
+
const argv = [
|
|
5495
|
+
"systemd-run",
|
|
5496
|
+
"--scope",
|
|
5497
|
+
"--collect",
|
|
5498
|
+
"-p",
|
|
5499
|
+
`MemoryMax=${memoryMax}`,
|
|
5500
|
+
"-p",
|
|
5501
|
+
`MemorySwapMax=${memorySwapMax}`
|
|
5502
|
+
];
|
|
5503
|
+
if (opts.cwd) {
|
|
5504
|
+
argv.push("--working-directory", opts.cwd);
|
|
5505
|
+
}
|
|
5506
|
+
argv.push("--", ...opts.command);
|
|
5507
|
+
return argv;
|
|
5508
|
+
}
|
|
5509
|
+
|
|
5510
|
+
// src/bounded-build/admission.ts
|
|
5511
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
5512
|
+
function positiveInt3(value, fallback) {
|
|
5513
|
+
const n = Number(value);
|
|
5514
|
+
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
5515
|
+
return Math.floor(n);
|
|
5516
|
+
}
|
|
5517
|
+
function resolveBuildAdmissionConfig(config = loadUserConfig()) {
|
|
5518
|
+
const envBudget = process.env.KYNVER_BUILD_MEM_BUDGET_BYTES ? positiveInt3(process.env.KYNVER_BUILD_MEM_BUDGET_BYTES, DEFAULT_BUILD_MEM_BUDGET_BYTES) : void 0;
|
|
5519
|
+
const envReserve = process.env.KYNVER_BUILD_MEM_RESERVE_BYTES ? positiveInt3(process.env.KYNVER_BUILD_MEM_RESERVE_BYTES, DEFAULT_BUILD_MEM_RESERVE_BYTES) : void 0;
|
|
5520
|
+
return {
|
|
5521
|
+
perBuildBudgetBytes: envBudget ?? positiveInt3(config.perWorkerMemBytes, DEFAULT_BUILD_MEM_BUDGET_BYTES),
|
|
5522
|
+
reserveBytes: envReserve ?? positiveInt3(config.memReserveBytes, DEFAULT_BUILD_MEM_RESERVE_BYTES)
|
|
5523
|
+
};
|
|
5524
|
+
}
|
|
5525
|
+
var activeBuilds = 0;
|
|
5526
|
+
function registerBuildStart() {
|
|
5527
|
+
activeBuilds += 1;
|
|
5528
|
+
}
|
|
5529
|
+
function registerBuildEnd() {
|
|
5530
|
+
activeBuilds = Math.max(0, activeBuilds - 1);
|
|
5531
|
+
}
|
|
5532
|
+
function assessBuildAdmission(opts = {}) {
|
|
5533
|
+
const cfg = { ...resolveBuildAdmissionConfig(), ...opts };
|
|
5534
|
+
const memAvailableBytes = opts.memAvailableBytes ?? readMemAvailableBytes();
|
|
5535
|
+
const requiredBytes = cfg.perBuildBudgetBytes + cfg.reserveBytes;
|
|
5536
|
+
const admitted = memAvailableBytes >= requiredBytes;
|
|
5537
|
+
return {
|
|
5538
|
+
admitted,
|
|
5539
|
+
memAvailableBytes,
|
|
5540
|
+
requiredBytes,
|
|
5541
|
+
activeBuilds,
|
|
5542
|
+
reason: admitted ? null : `insufficient memory: need ${requiredBytes} bytes available (budget ${cfg.perBuildBudgetBytes} + reserve ${cfg.reserveBytes}), have ${memAvailableBytes}`
|
|
5543
|
+
};
|
|
5544
|
+
}
|
|
5545
|
+
function sleepMs2(ms) {
|
|
5546
|
+
if (ms <= 0) return;
|
|
5547
|
+
spawnSync4(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
|
|
5548
|
+
stdio: "ignore"
|
|
5549
|
+
});
|
|
5550
|
+
}
|
|
5551
|
+
function waitForBuildAdmission(timeoutMs, pollMs = 2e3, opts = {}) {
|
|
5552
|
+
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
5553
|
+
let verdict = assessBuildAdmission({
|
|
5554
|
+
...opts,
|
|
5555
|
+
memAvailableBytes: opts.memAvailableBytes?.()
|
|
5556
|
+
});
|
|
5557
|
+
while (!verdict.admitted && Date.now() < deadline) {
|
|
5558
|
+
sleepMs2(Math.min(pollMs, deadline - Date.now()));
|
|
5559
|
+
verdict = assessBuildAdmission({
|
|
5560
|
+
...opts,
|
|
5561
|
+
memAvailableBytes: opts.memAvailableBytes?.()
|
|
5562
|
+
});
|
|
5563
|
+
}
|
|
5564
|
+
return verdict;
|
|
5565
|
+
}
|
|
5566
|
+
|
|
5567
|
+
// src/bounded-build/exec.ts
|
|
5568
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
5569
|
+
function envArgv(env) {
|
|
5570
|
+
const out = [];
|
|
5571
|
+
for (const [key, value] of Object.entries(env)) {
|
|
5572
|
+
if (value === void 0) continue;
|
|
5573
|
+
out.push(`${key}=${value}`);
|
|
5574
|
+
}
|
|
5575
|
+
return out;
|
|
5576
|
+
}
|
|
5577
|
+
function runSpawn(argv, opts) {
|
|
5578
|
+
const res = spawnSync5(argv[0], argv.slice(1), {
|
|
5579
|
+
cwd: opts.cwd,
|
|
5580
|
+
env: opts.env,
|
|
5581
|
+
encoding: "utf8",
|
|
5582
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5583
|
+
shell: opts.shell,
|
|
5584
|
+
timeout: opts.timeoutMs
|
|
5585
|
+
});
|
|
5586
|
+
return {
|
|
5587
|
+
exitCode: res.status ?? 1,
|
|
5588
|
+
stdout: (res.stdout ?? "").trim(),
|
|
5589
|
+
stderr: (res.stderr ?? "").trim()
|
|
5590
|
+
};
|
|
5591
|
+
}
|
|
5592
|
+
function runBoundedBuildCheck(input) {
|
|
5593
|
+
const waitMs = input.waitForAdmissionMs ?? 6e5;
|
|
5594
|
+
const admission = waitMs > 0 ? waitForBuildAdmission(waitMs) : assessBuildAdmission();
|
|
5595
|
+
if (!admission.admitted) {
|
|
5596
|
+
return {
|
|
5597
|
+
ok: false,
|
|
5598
|
+
exitCode: 1,
|
|
5599
|
+
stdout: "",
|
|
5600
|
+
stderr: admission.reason ?? "build admission denied",
|
|
5601
|
+
admitted: false,
|
|
5602
|
+
wrappedWithSystemd: false,
|
|
5603
|
+
nodeOptionsFlag: formatNodeOptionsFlag(),
|
|
5604
|
+
admission,
|
|
5605
|
+
command: input.command
|
|
5606
|
+
};
|
|
5607
|
+
}
|
|
5608
|
+
const env = mergeNodeOptionsForBuildCheck({ ...process.env, ...input.env });
|
|
5609
|
+
const nodeOptionsFlag = formatNodeOptionsFlag();
|
|
5610
|
+
const useSystemd = isSystemdRunAvailable();
|
|
5611
|
+
registerBuildStart();
|
|
5612
|
+
try {
|
|
5613
|
+
let result;
|
|
5614
|
+
if (useSystemd) {
|
|
5615
|
+
const argv = buildSystemdRunArgv({
|
|
5616
|
+
cwd: input.cwd,
|
|
5617
|
+
command: ["/usr/bin/env", ...envArgv(env), "/bin/bash", "-lc", input.command]
|
|
5618
|
+
});
|
|
5619
|
+
result = runSpawn(argv, { cwd: input.cwd, env, timeoutMs: input.timeoutMs });
|
|
5620
|
+
} else {
|
|
5621
|
+
result = runSpawn([input.command], {
|
|
5622
|
+
cwd: input.cwd,
|
|
5623
|
+
env,
|
|
5624
|
+
shell: true,
|
|
5625
|
+
timeoutMs: input.timeoutMs
|
|
5626
|
+
});
|
|
5627
|
+
}
|
|
5628
|
+
return {
|
|
5629
|
+
ok: result.exitCode === 0,
|
|
5630
|
+
exitCode: result.exitCode,
|
|
5631
|
+
stdout: result.stdout,
|
|
5632
|
+
stderr: result.stderr,
|
|
5633
|
+
admitted: true,
|
|
5634
|
+
wrappedWithSystemd: useSystemd,
|
|
5635
|
+
nodeOptionsFlag,
|
|
5636
|
+
admission,
|
|
5637
|
+
command: input.command
|
|
5638
|
+
};
|
|
5639
|
+
} finally {
|
|
5640
|
+
registerBuildEnd();
|
|
5641
|
+
}
|
|
5642
|
+
}
|
|
5643
|
+
|
|
5644
|
+
// src/harness-verify.ts
|
|
5645
|
+
var DEFAULT_HARNESS_VERIFY_COMMANDS = ["npm run typecheck", "npm run test"];
|
|
5646
|
+
function runHarnessVerifyCommands(cwd, commands = DEFAULT_HARNESS_VERIFY_COMMANDS, opts = {}) {
|
|
5647
|
+
const steps = [];
|
|
5648
|
+
let passed = true;
|
|
5649
|
+
for (const command of commands) {
|
|
5650
|
+
const result = runBoundedBuildCheck({
|
|
5651
|
+
cwd,
|
|
5652
|
+
command,
|
|
5653
|
+
waitForAdmissionMs: opts.waitForAdmissionMs,
|
|
5654
|
+
timeoutMs: opts.timeoutMs
|
|
5655
|
+
});
|
|
5656
|
+
steps.push({ command, result });
|
|
5657
|
+
if (!result.ok) passed = false;
|
|
5658
|
+
}
|
|
5659
|
+
return { passed, steps };
|
|
5660
|
+
}
|
|
5661
|
+
|
|
4979
5662
|
// src/plan-progress.ts
|
|
4980
5663
|
function parseEvidenceArg(raw) {
|
|
4981
5664
|
const idx = raw.indexOf(":");
|
|
@@ -5036,8 +5719,23 @@ async function emitPlanProgress(args) {
|
|
|
5036
5719
|
}
|
|
5037
5720
|
console.log(JSON.stringify(parsed, null, 2));
|
|
5038
5721
|
}
|
|
5722
|
+
function verifyPlanLocal(args) {
|
|
5723
|
+
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
5724
|
+
const cwd = path31.resolve(worktree);
|
|
5725
|
+
const summary = runHarnessVerifyCommands(cwd);
|
|
5726
|
+
const emitJson = args.json === true || args.json === "true";
|
|
5727
|
+
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
5728
|
+
if (emitJson) console.log(JSON.stringify(payload, null, 2));
|
|
5729
|
+
else console.log(summary.passed ? "local plan verify passed" : "local plan verify failed");
|
|
5730
|
+
if (!summary.passed) process.exit(1);
|
|
5731
|
+
}
|
|
5039
5732
|
async function verifyPlan(args) {
|
|
5040
5733
|
const planId = required(args.plan ? String(args.plan) : void 0, "plan");
|
|
5734
|
+
const localOnly = args.local === true || args.local === "true";
|
|
5735
|
+
if (localOnly) {
|
|
5736
|
+
verifyPlanLocal(args);
|
|
5737
|
+
return;
|
|
5738
|
+
}
|
|
5041
5739
|
const slug = loadUserConfig().agentOsSlug;
|
|
5042
5740
|
if (!slug) {
|
|
5043
5741
|
console.error("requires agentOsSlug in ~/.kynver/config.json for verify (session route)");
|
|
@@ -5071,6 +5769,52 @@ async function verifyPlan(args) {
|
|
|
5071
5769
|
console.log(JSON.stringify(parsed, null, 2));
|
|
5072
5770
|
}
|
|
5073
5771
|
|
|
5772
|
+
// src/harness-verify-cli.ts
|
|
5773
|
+
import path32 from "node:path";
|
|
5774
|
+
function runHarnessVerifyCli(args) {
|
|
5775
|
+
const cwd = path32.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
5776
|
+
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
5777
|
+
const commands = [];
|
|
5778
|
+
const rawCmd = args.command;
|
|
5779
|
+
if (Array.isArray(rawCmd)) {
|
|
5780
|
+
for (const c of rawCmd) commands.push(String(c));
|
|
5781
|
+
} else if (typeof rawCmd === "string") {
|
|
5782
|
+
commands.push(rawCmd);
|
|
5783
|
+
}
|
|
5784
|
+
const summary = runHarnessVerifyCommands(
|
|
5785
|
+
cwd,
|
|
5786
|
+
commands.length ? commands : DEFAULT_HARNESS_VERIFY_COMMANDS,
|
|
5787
|
+
{
|
|
5788
|
+
waitForAdmissionMs: args.waitForAdmissionMs ? Number(args.waitForAdmissionMs) : void 0,
|
|
5789
|
+
timeoutMs: args.timeoutMs ? Number(args.timeoutMs) : void 0
|
|
5790
|
+
}
|
|
5791
|
+
);
|
|
5792
|
+
const payload = {
|
|
5793
|
+
passed: summary.passed,
|
|
5794
|
+
worktree: cwd,
|
|
5795
|
+
steps: summary.steps.map((s) => ({
|
|
5796
|
+
command: s.command,
|
|
5797
|
+
ok: s.result.ok,
|
|
5798
|
+
exitCode: s.result.exitCode,
|
|
5799
|
+
admitted: s.result.admitted,
|
|
5800
|
+
wrappedWithSystemd: s.result.wrappedWithSystemd,
|
|
5801
|
+
nodeOptionsFlag: s.result.nodeOptionsFlag,
|
|
5802
|
+
admission: s.result.admission,
|
|
5803
|
+
stderr: s.result.stderr.slice(0, 4e3)
|
|
5804
|
+
}))
|
|
5805
|
+
};
|
|
5806
|
+
if (emitJson) {
|
|
5807
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
5808
|
+
} else {
|
|
5809
|
+
console.log(summary.passed ? "harness verify passed" : "harness verify failed");
|
|
5810
|
+
for (const step of payload.steps) {
|
|
5811
|
+
console.log(` ${step.ok ? "\u2713" : "\u2717"} ${step.command} (exit ${step.exitCode}, systemd=${step.wrappedWithSystemd})`);
|
|
5812
|
+
if (!step.ok && step.stderr) console.log(` ${step.stderr.split("\n")[0]}`);
|
|
5813
|
+
}
|
|
5814
|
+
}
|
|
5815
|
+
process.exit(summary.passed ? 0 : 1);
|
|
5816
|
+
}
|
|
5817
|
+
|
|
5074
5818
|
// src/plan-persist-cli.ts
|
|
5075
5819
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
5076
5820
|
var OPERATIONS = ["create", "add_version", "update_metadata"];
|
|
@@ -5172,7 +5916,7 @@ function runCleanupCli(args) {
|
|
|
5172
5916
|
}
|
|
5173
5917
|
|
|
5174
5918
|
// src/monitor/monitor.service.ts
|
|
5175
|
-
import
|
|
5919
|
+
import path34 from "node:path";
|
|
5176
5920
|
|
|
5177
5921
|
// src/monitor/monitor.classify.ts
|
|
5178
5922
|
function expectedLeaseOwner(runId) {
|
|
@@ -5229,10 +5973,10 @@ function classifyWorkerHealth(input) {
|
|
|
5229
5973
|
|
|
5230
5974
|
// src/monitor/monitor.store.ts
|
|
5231
5975
|
import { existsSync as existsSync17, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
|
|
5232
|
-
import
|
|
5976
|
+
import path33 from "node:path";
|
|
5233
5977
|
function monitorsDir() {
|
|
5234
5978
|
const { harnessRoot } = getHarnessPaths();
|
|
5235
|
-
const dir =
|
|
5979
|
+
const dir = path33.join(harnessRoot, "monitors");
|
|
5236
5980
|
mkdirSync6(dir, { recursive: true });
|
|
5237
5981
|
return dir;
|
|
5238
5982
|
}
|
|
@@ -5240,7 +5984,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
5240
5984
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
5241
5985
|
}
|
|
5242
5986
|
function monitorPath(monitorId) {
|
|
5243
|
-
return
|
|
5987
|
+
return path33.join(monitorsDir(), `${monitorId}.json`);
|
|
5244
5988
|
}
|
|
5245
5989
|
function loadMonitorSession(monitorId) {
|
|
5246
5990
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -5261,7 +6005,7 @@ function listMonitorSessions() {
|
|
|
5261
6005
|
for (const name of readdirSync7(dir)) {
|
|
5262
6006
|
if (!name.endsWith(".json")) continue;
|
|
5263
6007
|
const session = readJson(
|
|
5264
|
-
|
|
6008
|
+
path33.join(dir, name),
|
|
5265
6009
|
void 0
|
|
5266
6010
|
);
|
|
5267
6011
|
if (!session?.monitorId) continue;
|
|
@@ -5352,7 +6096,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
5352
6096
|
// src/monitor/monitor.service.ts
|
|
5353
6097
|
function workerRecord2(runId, name) {
|
|
5354
6098
|
return readJson(
|
|
5355
|
-
|
|
6099
|
+
path34.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
5356
6100
|
void 0
|
|
5357
6101
|
);
|
|
5358
6102
|
}
|
|
@@ -5555,17 +6299,17 @@ async function runMonitorLoop(args) {
|
|
|
5555
6299
|
// src/monitor/monitor-spawn.ts
|
|
5556
6300
|
import { spawn as spawn4 } from "node:child_process";
|
|
5557
6301
|
import { closeSync as closeSync4, existsSync as existsSync18, openSync as openSync4 } from "node:fs";
|
|
5558
|
-
import
|
|
6302
|
+
import path35 from "node:path";
|
|
5559
6303
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5560
6304
|
function resolveDefaultCliPath2() {
|
|
5561
|
-
return
|
|
6305
|
+
return path35.join(fileURLToPath2(new URL(".", import.meta.url)), "..", "cli.js");
|
|
5562
6306
|
}
|
|
5563
6307
|
function spawnMonitorSidecar(opts) {
|
|
5564
6308
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
5565
6309
|
if (!existsSync18(cliPath)) return void 0;
|
|
5566
6310
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
5567
6311
|
const { harnessRoot } = getHarnessPaths();
|
|
5568
|
-
const logPath =
|
|
6312
|
+
const logPath = path35.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
5569
6313
|
let logFd;
|
|
5570
6314
|
try {
|
|
5571
6315
|
logFd = openSync4(logPath, "a");
|
|
@@ -5720,6 +6464,453 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
|
|
|
5720
6464
|
return true;
|
|
5721
6465
|
}
|
|
5722
6466
|
|
|
6467
|
+
// src/doctor/runtime-takeover.ts
|
|
6468
|
+
import path37 from "node:path";
|
|
6469
|
+
|
|
6470
|
+
// src/doctor/runtime-takeover.probes.ts
|
|
6471
|
+
import { accessSync, constants, existsSync as existsSync20, readFileSync as readFileSync9 } from "node:fs";
|
|
6472
|
+
import { homedir as homedir6 } from "node:os";
|
|
6473
|
+
import path36 from "node:path";
|
|
6474
|
+
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
6475
|
+
function captureCommand(bin, args) {
|
|
6476
|
+
try {
|
|
6477
|
+
const res = spawnSync6(bin, args, { encoding: "utf8" });
|
|
6478
|
+
const stdout = (res.stdout || "").trim();
|
|
6479
|
+
const stderr = (res.stderr || "").trim();
|
|
6480
|
+
const ok = res.status === 0;
|
|
6481
|
+
return {
|
|
6482
|
+
ok,
|
|
6483
|
+
stdout,
|
|
6484
|
+
stderr,
|
|
6485
|
+
error: res.error?.message
|
|
6486
|
+
};
|
|
6487
|
+
} catch (error) {
|
|
6488
|
+
return {
|
|
6489
|
+
ok: false,
|
|
6490
|
+
stdout: "",
|
|
6491
|
+
stderr: "",
|
|
6492
|
+
error: error.message
|
|
6493
|
+
};
|
|
6494
|
+
}
|
|
6495
|
+
}
|
|
6496
|
+
function tokenPrefix(token) {
|
|
6497
|
+
const trimmed = token?.trim();
|
|
6498
|
+
if (!trimmed) return void 0;
|
|
6499
|
+
return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
|
|
6500
|
+
}
|
|
6501
|
+
function isWritable(target) {
|
|
6502
|
+
if (!existsSync20(target)) return false;
|
|
6503
|
+
try {
|
|
6504
|
+
accessSync(target, constants.W_OK);
|
|
6505
|
+
return true;
|
|
6506
|
+
} catch {
|
|
6507
|
+
return false;
|
|
6508
|
+
}
|
|
6509
|
+
}
|
|
6510
|
+
var defaultRuntimeTakeoverProbes = {
|
|
6511
|
+
packageVersion: () => PACKAGE_VERSION,
|
|
6512
|
+
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
6513
|
+
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
6514
|
+
loadConfig: () => loadUserConfig(),
|
|
6515
|
+
configFilePath: () => path36.join(homedir6(), ".kynver", "config.json"),
|
|
6516
|
+
credentialsFilePath: () => path36.join(homedir6(), ".kynver", "credentials"),
|
|
6517
|
+
readCredentials: () => {
|
|
6518
|
+
const credPath = path36.join(homedir6(), ".kynver", "credentials");
|
|
6519
|
+
if (!existsSync20(credPath)) {
|
|
6520
|
+
return { hasApiKey: false };
|
|
6521
|
+
}
|
|
6522
|
+
try {
|
|
6523
|
+
const parsed = JSON.parse(readFileSync9(credPath, "utf8"));
|
|
6524
|
+
return {
|
|
6525
|
+
hasApiKey: Boolean(parsed.apiKey?.trim()),
|
|
6526
|
+
runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
|
|
6527
|
+
runnerTokenAgentOsId: parsed.runnerTokenAgentOsId
|
|
6528
|
+
};
|
|
6529
|
+
} catch {
|
|
6530
|
+
return { hasApiKey: false };
|
|
6531
|
+
}
|
|
6532
|
+
},
|
|
6533
|
+
envSnapshot: () => ({
|
|
6534
|
+
kynverApiUrl: process.env.KYNVER_API_URL?.trim() || void 0,
|
|
6535
|
+
openclawCronFireBaseUrl: process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || void 0,
|
|
6536
|
+
kynverRunnerTokenPrefix: tokenPrefix(process.env.KYNVER_RUNNER_TOKEN),
|
|
6537
|
+
kynverRuntimeSecret: Boolean(process.env.KYNVER_RUNTIME_SECRET?.trim()),
|
|
6538
|
+
openclawCronSecret: Boolean(process.env.OPENCLAW_CRON_SECRET?.trim()),
|
|
6539
|
+
kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
|
|
6540
|
+
opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
|
|
6541
|
+
kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0
|
|
6542
|
+
}),
|
|
6543
|
+
harnessRoot: () => resolveHarnessRoot(),
|
|
6544
|
+
legacyOpenclawHarnessRoot: () => path36.join(homedir6(), ".openclaw", "harness"),
|
|
6545
|
+
pathExists: (target) => existsSync20(target),
|
|
6546
|
+
pathWritable: (target) => isWritable(target),
|
|
6547
|
+
vercelVersion: () => captureCommand("vercel", ["--version"]),
|
|
6548
|
+
vercelWhoami: () => captureCommand("vercel", ["whoami"])
|
|
6549
|
+
};
|
|
6550
|
+
|
|
6551
|
+
// src/doctor/runtime-takeover.ts
|
|
6552
|
+
function check(partial) {
|
|
6553
|
+
return partial;
|
|
6554
|
+
}
|
|
6555
|
+
function summarizeCounts(sections) {
|
|
6556
|
+
const counts = { pass: 0, warn: 0, fail: 0 };
|
|
6557
|
+
for (const section of sections) {
|
|
6558
|
+
for (const item of section.checks) {
|
|
6559
|
+
counts[item.status] += 1;
|
|
6560
|
+
}
|
|
6561
|
+
}
|
|
6562
|
+
return counts;
|
|
6563
|
+
}
|
|
6564
|
+
function assessCliPackage(probes) {
|
|
6565
|
+
const runningVersion = probes.packageVersion();
|
|
6566
|
+
const which = probes.commandOnPath("kynver");
|
|
6567
|
+
const onPath = which.ok && which.stdout.length > 0;
|
|
6568
|
+
const firstPath = onPath ? which.stdout.split(/\r?\n/)[0]?.trim() : void 0;
|
|
6569
|
+
const displayCliPath = firstPath ? displayUserPath(firstPath) : void 0;
|
|
6570
|
+
const checks = [
|
|
6571
|
+
check({
|
|
6572
|
+
id: "cli_running_version",
|
|
6573
|
+
label: "Running @kynver-app/runtime version",
|
|
6574
|
+
status: "pass",
|
|
6575
|
+
summary: `@kynver-app/runtime ${runningVersion}`,
|
|
6576
|
+
details: { version: runningVersion }
|
|
6577
|
+
}),
|
|
6578
|
+
check({
|
|
6579
|
+
id: "cli_on_path",
|
|
6580
|
+
label: "kynver executable on PATH",
|
|
6581
|
+
status: onPath ? "pass" : "warn",
|
|
6582
|
+
summary: onPath ? `Found ${displayCliPath}` : "kynver not found on PATH (invoked via node/npx?)",
|
|
6583
|
+
remediation: onPath ? void 0 : "Install globally (`npm i -g @kynver-app/runtime`) or invoke via `npx @kynver-app/runtime`.",
|
|
6584
|
+
details: { path: displayCliPath }
|
|
6585
|
+
})
|
|
6586
|
+
];
|
|
6587
|
+
if (onPath && firstPath) {
|
|
6588
|
+
const versionProbe = probes.kynverVersion(firstPath);
|
|
6589
|
+
const installedVersion = versionProbe.stdout.replace(/^kynver\s+/i, "").trim() || void 0;
|
|
6590
|
+
const versionMatch = versionProbe.ok && (!installedVersion || installedVersion === runningVersion);
|
|
6591
|
+
checks.push(
|
|
6592
|
+
check({
|
|
6593
|
+
id: "cli_installed_version",
|
|
6594
|
+
label: "Installed kynver CLI version matches running package",
|
|
6595
|
+
status: versionMatch ? "pass" : "warn",
|
|
6596
|
+
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",
|
|
6597
|
+
remediation: versionMatch ? void 0 : "Reinstall or rebuild @kynver-app/runtime so PATH kynver matches the harness worktree version.",
|
|
6598
|
+
details: { installedVersion, runningVersion, path: displayCliPath }
|
|
6599
|
+
})
|
|
6600
|
+
);
|
|
6601
|
+
}
|
|
6602
|
+
return { id: "cli_package", label: "CLI / package", checks };
|
|
6603
|
+
}
|
|
6604
|
+
function assessUserConfig(probes) {
|
|
6605
|
+
const configPath = probes.configFilePath();
|
|
6606
|
+
const displayConfigPath = displayUserPath(configPath);
|
|
6607
|
+
const exists = probes.pathExists(configPath);
|
|
6608
|
+
const config = probes.loadConfig();
|
|
6609
|
+
const checks = [
|
|
6610
|
+
check({
|
|
6611
|
+
id: "config_file",
|
|
6612
|
+
label: "~/.kynver/config.json present",
|
|
6613
|
+
status: exists ? "pass" : "fail",
|
|
6614
|
+
summary: exists ? `Found ${displayConfigPath}` : `Missing ${displayConfigPath}`,
|
|
6615
|
+
remediation: exists ? void 0 : "Run `kynver setup --api-base-url <url> --agent-os-id <id> --repo <path>`.",
|
|
6616
|
+
details: { configPath: displayConfigPath }
|
|
6617
|
+
})
|
|
6618
|
+
];
|
|
6619
|
+
if (exists) {
|
|
6620
|
+
const apiBaseUrl = config.apiBaseUrl?.trim();
|
|
6621
|
+
const agentOsId = config.agentOsId?.trim();
|
|
6622
|
+
const defaultRepo = config.defaultRepo?.trim();
|
|
6623
|
+
const displayDefaultRepo = defaultRepo ? displayUserPath(defaultRepo) : null;
|
|
6624
|
+
checks.push(
|
|
6625
|
+
check({
|
|
6626
|
+
id: "config_api_base_url",
|
|
6627
|
+
label: "Default API base URL",
|
|
6628
|
+
status: apiBaseUrl ? "pass" : "warn",
|
|
6629
|
+
summary: apiBaseUrl ?? "Not set in config (KYNVER_API_URL / --base-url required per command)",
|
|
6630
|
+
remediation: apiBaseUrl ? void 0 : "Set `apiBaseUrl` via `kynver setup --api-base-url https://\u2026`.",
|
|
6631
|
+
details: { apiBaseUrl: apiBaseUrl ?? null }
|
|
6632
|
+
}),
|
|
6633
|
+
check({
|
|
6634
|
+
id: "config_agent_os_id",
|
|
6635
|
+
label: "Default AgentOS id",
|
|
6636
|
+
status: agentOsId ? "pass" : "warn",
|
|
6637
|
+
summary: agentOsId ?? "Not set (pass --agent-os-id on daemon/dispatch/worker commands)",
|
|
6638
|
+
remediation: agentOsId ? void 0 : "Set `agentOsId` via `kynver setup --agent-os-id <uuid>`.",
|
|
6639
|
+
details: { agentOsId: agentOsId ?? null, agentOsSlug: config.agentOsSlug ?? null }
|
|
6640
|
+
}),
|
|
6641
|
+
check({
|
|
6642
|
+
id: "config_default_repo",
|
|
6643
|
+
label: "Default repo path",
|
|
6644
|
+
status: defaultRepo ? "pass" : "warn",
|
|
6645
|
+
summary: displayDefaultRepo ?? "Not set (pass --repo on `kynver run create`)",
|
|
6646
|
+
remediation: defaultRepo ? void 0 : "Set `defaultRepo` via `kynver setup --repo /path/to/repo`.",
|
|
6647
|
+
details: {
|
|
6648
|
+
defaultRepo: displayDefaultRepo,
|
|
6649
|
+
harnessRoot: config.harnessRoot ? redactHomePath(config.harnessRoot) : null
|
|
6650
|
+
}
|
|
6651
|
+
})
|
|
6652
|
+
);
|
|
6653
|
+
}
|
|
6654
|
+
return { id: "user_config", label: "User config (~/.kynver)", checks };
|
|
6655
|
+
}
|
|
6656
|
+
function assessRunnerToken(probes) {
|
|
6657
|
+
const config = probes.loadConfig();
|
|
6658
|
+
const targetAgentOsId = config.agentOsId?.trim();
|
|
6659
|
+
const creds = probes.readCredentials();
|
|
6660
|
+
const env = probes.envSnapshot();
|
|
6661
|
+
const credPath = probes.credentialsFilePath();
|
|
6662
|
+
const displayCredPath = displayUserPath(credPath);
|
|
6663
|
+
const envToken = env.kynverRunnerTokenPrefix;
|
|
6664
|
+
const savedToken = creds.runnerTokenPrefix;
|
|
6665
|
+
const savedAgentOsId = creds.runnerTokenAgentOsId;
|
|
6666
|
+
const scopedSaved = Boolean(savedToken) && (!targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId);
|
|
6667
|
+
const hasScoped = Boolean(envToken?.startsWith("krc1.")) || scopedSaved && savedToken?.startsWith("krc1.");
|
|
6668
|
+
const checks = [
|
|
6669
|
+
check({
|
|
6670
|
+
id: "runner_token_scoped",
|
|
6671
|
+
label: "Scoped runner token (krc1.*) ready",
|
|
6672
|
+
status: hasScoped ? "pass" : "fail",
|
|
6673
|
+
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",
|
|
6674
|
+
remediation: hasScoped ? void 0 : "Run `kynver login` then `kynver runner credential --agent-os-id <id>` (or `kynver setup` with API key).",
|
|
6675
|
+
details: {
|
|
6676
|
+
source: envToken ? "env" : savedToken ? "credentials" : "none",
|
|
6677
|
+
tokenPrefix: envToken ?? (scopedSaved ? savedToken : void 0),
|
|
6678
|
+
agentOsId: targetAgentOsId ?? savedAgentOsId ?? null,
|
|
6679
|
+
credentialsPath: displayCredPath
|
|
6680
|
+
}
|
|
6681
|
+
}),
|
|
6682
|
+
check({
|
|
6683
|
+
id: "runner_token_agent_os_match",
|
|
6684
|
+
label: "Saved runner token matches configured agentOsId",
|
|
6685
|
+
status: !savedToken || !targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId ? "pass" : "warn",
|
|
6686
|
+
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}`,
|
|
6687
|
+
remediation: savedToken && targetAgentOsId && savedAgentOsId && savedAgentOsId !== targetAgentOsId ? "`kynver runner credential --agent-os-id <configured-id>` to mint a workspace-bound token." : void 0,
|
|
6688
|
+
details: { configuredAgentOsId: targetAgentOsId ?? null, savedAgentOsId: savedAgentOsId ?? null }
|
|
6689
|
+
}),
|
|
6690
|
+
check({
|
|
6691
|
+
id: "runner_api_key_for_refresh",
|
|
6692
|
+
label: "API key available for token refresh",
|
|
6693
|
+
status: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY?.trim()) ? "pass" : "warn",
|
|
6694
|
+
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",
|
|
6695
|
+
remediation: creds.hasApiKey || process.env.KYNVER_API_KEY ? void 0 : "Run `kynver login --api-key \u2026`.",
|
|
6696
|
+
details: { credentialsPath: displayCredPath, hasApiKey: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY) }
|
|
6697
|
+
})
|
|
6698
|
+
];
|
|
6699
|
+
return { id: "runner_token", label: "Runner token readiness", checks };
|
|
6700
|
+
}
|
|
6701
|
+
function assessVercelCli(probes) {
|
|
6702
|
+
const version = probes.vercelVersion();
|
|
6703
|
+
const whoami = probes.vercelWhoami();
|
|
6704
|
+
const installed = version.ok;
|
|
6705
|
+
return {
|
|
6706
|
+
id: "vercel_cli",
|
|
6707
|
+
label: "Vercel CLI",
|
|
6708
|
+
checks: [
|
|
6709
|
+
check({
|
|
6710
|
+
id: "vercel_installed",
|
|
6711
|
+
label: "Vercel CLI installed",
|
|
6712
|
+
status: installed ? "pass" : "warn",
|
|
6713
|
+
summary: installed ? version.stdout || "vercel CLI found" : version.error ?? "vercel not found on PATH",
|
|
6714
|
+
remediation: installed ? void 0 : "Install Vercel CLI (`npm i -g vercel`) for deploy evidence and env pulls.",
|
|
6715
|
+
details: { stderr: version.stderr || null }
|
|
6716
|
+
}),
|
|
6717
|
+
check({
|
|
6718
|
+
id: "vercel_authenticated",
|
|
6719
|
+
label: "Vercel CLI authenticated",
|
|
6720
|
+
status: !installed ? "warn" : whoami.ok ? "pass" : "warn",
|
|
6721
|
+
summary: !installed ? "Skipped \u2014 Vercel CLI not installed" : whoami.ok ? `Authenticated as ${whoami.stdout}` : whoami.stderr || whoami.error || "Not logged in",
|
|
6722
|
+
remediation: installed && !whoami.ok ? "Run `vercel login` and link the Kynver project if needed." : void 0,
|
|
6723
|
+
details: { account: whoami.ok ? whoami.stdout : null }
|
|
6724
|
+
})
|
|
6725
|
+
]
|
|
6726
|
+
};
|
|
6727
|
+
}
|
|
6728
|
+
function assessHarnessDirs(probes) {
|
|
6729
|
+
const harnessRoot = probes.harnessRoot();
|
|
6730
|
+
const runsDir = path37.join(harnessRoot, "runs");
|
|
6731
|
+
const worktreesDir = path37.join(harnessRoot, "worktrees");
|
|
6732
|
+
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
6733
|
+
const displayRunsDir = redactHomePath(runsDir);
|
|
6734
|
+
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
6735
|
+
const runsExists = probes.pathExists(runsDir);
|
|
6736
|
+
const worktreesExists = probes.pathExists(worktreesDir);
|
|
6737
|
+
return {
|
|
6738
|
+
id: "harness_dirs",
|
|
6739
|
+
label: "Harness / daemon directories",
|
|
6740
|
+
checks: [
|
|
6741
|
+
check({
|
|
6742
|
+
id: "harness_root",
|
|
6743
|
+
label: "Harness root resolved",
|
|
6744
|
+
status: "pass",
|
|
6745
|
+
summary: displayHarnessRoot,
|
|
6746
|
+
details: { harnessRoot: displayHarnessRoot }
|
|
6747
|
+
}),
|
|
6748
|
+
check({
|
|
6749
|
+
id: "harness_runs_dir",
|
|
6750
|
+
label: "Runs directory ready",
|
|
6751
|
+
status: runsExists && probes.pathWritable(runsDir) ? "pass" : runsExists ? "warn" : "warn",
|
|
6752
|
+
summary: runsExists ? probes.pathWritable(runsDir) ? `Writable ${displayRunsDir}` : `Exists but not writable: ${displayRunsDir}` : `Will be created on first run: ${displayRunsDir}`,
|
|
6753
|
+
remediation: runsExists && !probes.pathWritable(runsDir) ? `Fix permissions on ${displayRunsDir} or set KYNVER_HARNESS_ROOT to a writable path.` : void 0,
|
|
6754
|
+
details: { runsDir: displayRunsDir, exists: runsExists, writable: probes.pathWritable(runsDir) }
|
|
6755
|
+
}),
|
|
6756
|
+
check({
|
|
6757
|
+
id: "harness_worktrees_dir",
|
|
6758
|
+
label: "Worktrees directory ready",
|
|
6759
|
+
status: worktreesExists && probes.pathWritable(worktreesDir) ? "pass" : "warn",
|
|
6760
|
+
summary: worktreesExists ? probes.pathWritable(worktreesDir) ? `Writable ${displayWorktreesDir}` : `Exists but not writable: ${displayWorktreesDir}` : `Will be created on first worker: ${displayWorktreesDir}`,
|
|
6761
|
+
remediation: worktreesExists && !probes.pathWritable(worktreesDir) ? `Fix permissions on ${displayWorktreesDir}.` : void 0,
|
|
6762
|
+
details: { worktreesDir: displayWorktreesDir, exists: worktreesExists, writable: probes.pathWritable(worktreesDir) }
|
|
6763
|
+
})
|
|
6764
|
+
]
|
|
6765
|
+
};
|
|
6766
|
+
}
|
|
6767
|
+
function assessCallbackAuth(probes) {
|
|
6768
|
+
const config = probes.loadConfig();
|
|
6769
|
+
const env = probes.envSnapshot();
|
|
6770
|
+
const baseUrl = env.kynverApiUrl ?? config.apiBaseUrl?.trim() ?? env.openclawCronFireBaseUrl;
|
|
6771
|
+
const usingLegacyBase = !env.kynverApiUrl && !config.apiBaseUrl && Boolean(env.openclawCronFireBaseUrl);
|
|
6772
|
+
const legacySecret = env.openclawCronSecret || env.kynverRuntimeSecret;
|
|
6773
|
+
const envScoped = env.kynverRunnerTokenPrefix?.startsWith("krc1.");
|
|
6774
|
+
const creds = probes.readCredentials();
|
|
6775
|
+
const savedScoped = creds.runnerTokenPrefix?.startsWith("krc1.");
|
|
6776
|
+
const checks = [
|
|
6777
|
+
check({
|
|
6778
|
+
id: "callback_base_url",
|
|
6779
|
+
label: "Callback base URL configured",
|
|
6780
|
+
status: baseUrl ? usingLegacyBase ? "warn" : "pass" : "fail",
|
|
6781
|
+
summary: baseUrl ? usingLegacyBase ? `Using legacy OPENCLAW_CRON_FIRE_BASE_URL (${baseUrl})` : baseUrl : "No KYNVER_API_URL, config apiBaseUrl, or legacy OpenClaw base URL",
|
|
6782
|
+
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`.",
|
|
6783
|
+
details: {
|
|
6784
|
+
source: env.kynverApiUrl ? "KYNVER_API_URL" : config.apiBaseUrl ? "config.apiBaseUrl" : env.openclawCronFireBaseUrl ? "OPENCLAW_CRON_FIRE_BASE_URL" : "none",
|
|
6785
|
+
baseUrl: baseUrl ?? null
|
|
6786
|
+
}
|
|
6787
|
+
}),
|
|
6788
|
+
check({
|
|
6789
|
+
id: "callback_auth_mode",
|
|
6790
|
+
label: "Callback auth uses scoped runner token",
|
|
6791
|
+
status: envScoped || savedScoped ? "pass" : legacySecret ? "warn" : "fail",
|
|
6792
|
+
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",
|
|
6793
|
+
remediation: envScoped || savedScoped ? void 0 : "Mint a scoped token: `kynver runner credential --agent-os-id <id>`. Avoid shared OPENCLAW_CRON_SECRET on user runners.",
|
|
6794
|
+
details: {
|
|
6795
|
+
mode: envScoped || savedScoped ? "scoped" : legacySecret ? "legacy_global_secret" : "missing",
|
|
6796
|
+
legacyHeadersWhenNotScoped: ["X-OpenClaw-Cron-Secret", "X-Kynver-Runtime-Secret"]
|
|
6797
|
+
}
|
|
6798
|
+
})
|
|
6799
|
+
];
|
|
6800
|
+
return { id: "callback_auth", label: "Callback auth / config", checks };
|
|
6801
|
+
}
|
|
6802
|
+
function assessOpenclawHotspots(probes) {
|
|
6803
|
+
const env = probes.envSnapshot();
|
|
6804
|
+
const harnessRoot = probes.harnessRoot();
|
|
6805
|
+
const legacyRoot = probes.legacyOpenclawHarnessRoot();
|
|
6806
|
+
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
6807
|
+
const displayLegacyRoot = redactHomePath(legacyRoot);
|
|
6808
|
+
const displayOpusHarnessRoot = env.opusHarnessRoot ? redactHomePath(env.opusHarnessRoot) : null;
|
|
6809
|
+
const legacyHarnessActive = harnessRoot === legacyRoot && probes.pathExists(legacyRoot);
|
|
6810
|
+
const schedulerOpenclaw = !env.kynverSchedulerProvider || env.kynverSchedulerProvider === "openclaw-cron";
|
|
6811
|
+
const checks = [
|
|
6812
|
+
check({
|
|
6813
|
+
id: "hotspot_legacy_harness_root",
|
|
6814
|
+
label: "Legacy ~/.openclaw/harness still active",
|
|
6815
|
+
status: legacyHarnessActive ? "warn" : "pass",
|
|
6816
|
+
summary: legacyHarnessActive ? `Harness root is legacy ${displayLegacyRoot}` : env.opusHarnessRoot ? `OPUS_HARNESS_ROOT override in use (${displayOpusHarnessRoot})` : `Using ${displayHarnessRoot}`,
|
|
6817
|
+
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,
|
|
6818
|
+
details: { harnessRoot: displayHarnessRoot, legacyRoot: displayLegacyRoot, opusHarnessRoot: displayOpusHarnessRoot }
|
|
6819
|
+
}),
|
|
6820
|
+
check({
|
|
6821
|
+
id: "hotspot_openclaw_env_secrets",
|
|
6822
|
+
label: "OpenClaw deployment secrets in runner env",
|
|
6823
|
+
status: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "warn" : "pass",
|
|
6824
|
+
summary: env.openclawCronSecret || env.openclawCronFireBaseUrl ? [
|
|
6825
|
+
env.openclawCronSecret ? "OPENCLAW_CRON_SECRET set" : null,
|
|
6826
|
+
env.openclawCronFireBaseUrl ? "OPENCLAW_CRON_FIRE_BASE_URL set" : null
|
|
6827
|
+
].filter(Boolean).join("; ") : "No OpenClaw cron env overrides on this runner",
|
|
6828
|
+
remediation: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "Move to KYNVER_API_URL + scoped runner tokens; unset OpenClaw cron env on user-hosted runners." : void 0
|
|
6829
|
+
}),
|
|
6830
|
+
check({
|
|
6831
|
+
id: "hotspot_openclaw_scheduler",
|
|
6832
|
+
label: "openclaw-cron scheduler dependency (deployment)",
|
|
6833
|
+
status: schedulerOpenclaw ? "warn" : "pass",
|
|
6834
|
+
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}`,
|
|
6835
|
+
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,
|
|
6836
|
+
details: { schedulerProvider: env.kynverSchedulerProvider ?? null }
|
|
6837
|
+
}),
|
|
6838
|
+
check({
|
|
6839
|
+
id: "hotspot_lease_source_names",
|
|
6840
|
+
label: "Harness lease/completion source names",
|
|
6841
|
+
status: "pass",
|
|
6842
|
+
summary: "Runtime uses kynver-harness:* lease owners and completion source kynver-harness (OpenClaw names retired in runtime)",
|
|
6843
|
+
details: {
|
|
6844
|
+
leaseOwnerPattern: "kynver-harness:<runId>",
|
|
6845
|
+
completionSource: "kynver-harness",
|
|
6846
|
+
note: "OpenClaw adapter remains optional in packages/kynver-openclaw-agent-os only"
|
|
6847
|
+
}
|
|
6848
|
+
})
|
|
6849
|
+
];
|
|
6850
|
+
return { id: "openclaw_hotspots", label: "OpenClaw dependency hotspots", checks };
|
|
6851
|
+
}
|
|
6852
|
+
function assessRuntimeTakeoverReadiness(probes = defaultRuntimeTakeoverProbes) {
|
|
6853
|
+
const sections = [
|
|
6854
|
+
assessCliPackage(probes),
|
|
6855
|
+
assessUserConfig(probes),
|
|
6856
|
+
assessRunnerToken(probes),
|
|
6857
|
+
assessVercelCli(probes),
|
|
6858
|
+
assessHarnessDirs(probes),
|
|
6859
|
+
assessCallbackAuth(probes),
|
|
6860
|
+
assessOpenclawHotspots(probes)
|
|
6861
|
+
];
|
|
6862
|
+
const counts = summarizeCounts(sections);
|
|
6863
|
+
const ready = counts.fail === 0;
|
|
6864
|
+
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.`;
|
|
6865
|
+
return {
|
|
6866
|
+
command: "doctor runtime-takeover",
|
|
6867
|
+
ready,
|
|
6868
|
+
summary,
|
|
6869
|
+
counts,
|
|
6870
|
+
sections
|
|
6871
|
+
};
|
|
6872
|
+
}
|
|
6873
|
+
|
|
6874
|
+
// src/doctor/runtime-takeover-cli.ts
|
|
6875
|
+
function runRuntimeTakeoverDoctorCli() {
|
|
6876
|
+
const report = assessRuntimeTakeoverReadiness();
|
|
6877
|
+
console.log(JSON.stringify(report, null, 2));
|
|
6878
|
+
if (!report.ready) {
|
|
6879
|
+
process.exitCode = 1;
|
|
6880
|
+
}
|
|
6881
|
+
}
|
|
6882
|
+
|
|
6883
|
+
// src/command-center-contract-cli.ts
|
|
6884
|
+
async function runCommandCenterContractCli(args) {
|
|
6885
|
+
const config = loadUserConfig();
|
|
6886
|
+
const agentOsId = (args.agentOsId ? String(args.agentOsId) : config.agentOsId) || "";
|
|
6887
|
+
if (!agentOsId) {
|
|
6888
|
+
console.error("requires --agent-os-id or agentOsId in ~/.kynver/config.json");
|
|
6889
|
+
process.exit(1);
|
|
6890
|
+
}
|
|
6891
|
+
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : config.apiBaseUrl);
|
|
6892
|
+
const secret = await resolveCallbackSecretWithMint(
|
|
6893
|
+
args.secret ? String(args.secret) : void 0,
|
|
6894
|
+
agentOsId,
|
|
6895
|
+
{ baseUrl: base }
|
|
6896
|
+
);
|
|
6897
|
+
const qs = new URLSearchParams();
|
|
6898
|
+
if (typeof args.since === "string" && args.since.trim()) qs.set("since", args.since.trim());
|
|
6899
|
+
if (args.limit != null && String(args.limit).trim()) {
|
|
6900
|
+
const n = Number(args.limit);
|
|
6901
|
+
if (Number.isFinite(n) && n > 0) qs.set("limit", String(Math.floor(n)));
|
|
6902
|
+
}
|
|
6903
|
+
const suffix = qs.toString() ? `?${qs.toString()}` : "";
|
|
6904
|
+
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/command-center/dashboard-contract${suffix}`;
|
|
6905
|
+
const res = await getJson(url, secret);
|
|
6906
|
+
if (!res.ok) {
|
|
6907
|
+
console.error(`dashboard-contract GET failed: HTTP ${res.status}`);
|
|
6908
|
+
if (res.response) console.error(JSON.stringify(res.response, null, 2));
|
|
6909
|
+
process.exit(1);
|
|
6910
|
+
}
|
|
6911
|
+
console.log(JSON.stringify(res.response, null, 2));
|
|
6912
|
+
}
|
|
6913
|
+
|
|
5723
6914
|
// src/cli.ts
|
|
5724
6915
|
function isHelpFlag(arg) {
|
|
5725
6916
|
return arg === "help" || arg === "--help" || arg === "-h";
|
|
@@ -5741,7 +6932,7 @@ function usage(code = 0) {
|
|
|
5741
6932
|
" kynver run create --repo /path/repo [--name name] [--base origin/main]",
|
|
5742
6933
|
" kynver run list",
|
|
5743
6934
|
" kynver run status --run RUN_ID",
|
|
5744
|
-
" 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 /]",
|
|
6935
|
+
" 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 /]",
|
|
5745
6936
|
" kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
|
|
5746
6937
|
' 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]',
|
|
5747
6938
|
" kynver worker status --run RUN_ID --name worker",
|
|
@@ -5751,7 +6942,8 @@ function usage(code = 0) {
|
|
|
5751
6942
|
" 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]",
|
|
5752
6943
|
" kynver run reconcile",
|
|
5753
6944
|
" 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]",
|
|
5754
|
-
" kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override]",
|
|
6945
|
+
" kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override] [--local]",
|
|
6946
|
+
" kynver harness verify --worktree PATH [--command CMD] [--json] [--wait-for-admission-ms MS] [--timeout-ms MS]",
|
|
5755
6947
|
" 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]",
|
|
5756
6948
|
" kynver plan outbox list",
|
|
5757
6949
|
" kynver plan outbox drain [--max N] [--id OUTBOX_ID]",
|
|
@@ -5762,7 +6954,9 @@ function usage(code = 0) {
|
|
|
5762
6954
|
" kynver monitor list",
|
|
5763
6955
|
" kynver monitor tick --run RUN_ID [--name worker] [--agent-os-id AOS_ID] [--auto-complete] [--renew-leases]",
|
|
5764
6956
|
" kynver monitor auto-complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--base-url URL] [--secret SECRET]",
|
|
5765
|
-
" kynver monitor run-loop --run RUN_ID --monitor-id ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS] [--auto-complete] [--renew-leases]"
|
|
6957
|
+
" kynver monitor run-loop --run RUN_ID --monitor-id ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS] [--auto-complete] [--renew-leases]",
|
|
6958
|
+
" kynver doctor runtime-takeover",
|
|
6959
|
+
" kynver board contract [--agent-os-id ID] [--base-url URL] [--since ISO] [--limit N]"
|
|
5766
6960
|
].join("\n")
|
|
5767
6961
|
);
|
|
5768
6962
|
process.exit(code);
|
|
@@ -5773,7 +6967,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
5773
6967
|
const scope = argv.shift();
|
|
5774
6968
|
let action;
|
|
5775
6969
|
let rest;
|
|
5776
|
-
if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "monitor") {
|
|
6970
|
+
if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "harness" || scope === "monitor" || scope === "doctor" || scope === "board") {
|
|
5777
6971
|
action = argv.shift();
|
|
5778
6972
|
rest = argv;
|
|
5779
6973
|
} else {
|
|
@@ -5790,6 +6984,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
5790
6984
|
if (scope === "daemon") return void await runDaemon(args);
|
|
5791
6985
|
if (scope === "plan" && action === "progress") return void await emitPlanProgress(args);
|
|
5792
6986
|
if (scope === "plan" && action === "verify") return void await verifyPlan(args);
|
|
6987
|
+
if (scope === "harness" && action === "verify") return runHarnessVerifyCli(args);
|
|
5793
6988
|
if (scope === "plan" && action === "persist") return void await runPlanPersist(args);
|
|
5794
6989
|
if (scope === "plan" && action === "outbox") {
|
|
5795
6990
|
const outboxAction = rest.shift();
|
|
@@ -5798,6 +6993,10 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
5798
6993
|
unknownCommand("plan", `outbox ${outboxAction ?? ""}`.trim());
|
|
5799
6994
|
}
|
|
5800
6995
|
if (scope === "cleanup") return runCleanupCli(args);
|
|
6996
|
+
if (scope === "doctor" && action === "runtime-takeover") return runRuntimeTakeoverDoctorCli();
|
|
6997
|
+
if (scope === "board" && action === "contract") {
|
|
6998
|
+
return void await runCommandCenterContractCli(args);
|
|
6999
|
+
}
|
|
5801
7000
|
if (scope === "run" && action === "create") return createRun(args);
|
|
5802
7001
|
if (scope === "run" && action === "list") return listRuns();
|
|
5803
7002
|
if (scope === "run" && action === "status") return runStatus(args);
|