@kynver-app/runtime 0.1.39 → 0.1.42
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +637 -153
- package/dist/cli.js.map +4 -4
- package/dist/index.js +936 -156
- package/dist/index.js.map +4 -4
- package/package.json +6 -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 path35 = 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(path35);
|
|
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: path35,
|
|
453
500
|
freeBytes,
|
|
454
501
|
totalBytes,
|
|
455
502
|
usedPercent,
|
|
@@ -464,21 +511,23 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
464
511
|
// src/resource-gate.ts
|
|
465
512
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
466
513
|
import os from "node:os";
|
|
467
|
-
import
|
|
514
|
+
import path6 from "node:path";
|
|
468
515
|
|
|
469
516
|
// src/run-store.ts
|
|
470
517
|
import { existsSync as existsSync4, readdirSync as readdirSync2 } from "node:fs";
|
|
471
|
-
import
|
|
518
|
+
import path5 from "node:path";
|
|
472
519
|
|
|
473
520
|
// src/paths.ts
|
|
474
521
|
import { existsSync as existsSync3 } from "node:fs";
|
|
475
|
-
import { homedir as
|
|
476
|
-
import
|
|
477
|
-
var LEGACY_ROOT =
|
|
522
|
+
import { homedir as homedir3 } from "node:os";
|
|
523
|
+
import path4 from "node:path";
|
|
524
|
+
var LEGACY_ROOT = path4.join(homedir3(), ".openclaw", "harness");
|
|
478
525
|
function resolveHarnessRoot() {
|
|
479
526
|
const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
|
|
480
|
-
if (env) return
|
|
481
|
-
const
|
|
527
|
+
if (env) return resolveUserPath(env);
|
|
528
|
+
const configured = loadUserConfig().harnessRoot?.trim();
|
|
529
|
+
if (configured) return resolveUserPath(configured);
|
|
530
|
+
const kynverRoot = path4.join(homedir3(), ".kynver", "harness");
|
|
482
531
|
if (existsSync3(kynverRoot)) return kynverRoot;
|
|
483
532
|
if (existsSync3(LEGACY_ROOT)) return LEGACY_ROOT;
|
|
484
533
|
return kynverRoot;
|
|
@@ -487,12 +536,12 @@ function getHarnessPaths() {
|
|
|
487
536
|
const harnessRoot = resolveHarnessRoot();
|
|
488
537
|
return {
|
|
489
538
|
harnessRoot,
|
|
490
|
-
runsDir:
|
|
491
|
-
worktreesDir:
|
|
539
|
+
runsDir: path4.join(harnessRoot, "runs"),
|
|
540
|
+
worktreesDir: path4.join(harnessRoot, "worktrees")
|
|
492
541
|
};
|
|
493
542
|
}
|
|
494
543
|
function runDir(runsDir, id) {
|
|
495
|
-
return
|
|
544
|
+
return path4.join(runsDir, safeSlug(id));
|
|
496
545
|
}
|
|
497
546
|
|
|
498
547
|
// src/run-store.ts
|
|
@@ -501,7 +550,7 @@ function getPaths() {
|
|
|
501
550
|
}
|
|
502
551
|
function loadRun(id) {
|
|
503
552
|
const { runsDir } = getPaths();
|
|
504
|
-
return readJson(
|
|
553
|
+
return readJson(path5.join(runDir(runsDir, safeSlug(id)), "run.json"));
|
|
505
554
|
}
|
|
506
555
|
function listRunRecords() {
|
|
507
556
|
const { runsDir } = getPaths();
|
|
@@ -510,7 +559,7 @@ function listRunRecords() {
|
|
|
510
559
|
for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
|
|
511
560
|
if (!entry.isDirectory()) continue;
|
|
512
561
|
const run = readJson(
|
|
513
|
-
|
|
562
|
+
path5.join(runsDir, entry.name, "run.json"),
|
|
514
563
|
void 0
|
|
515
564
|
);
|
|
516
565
|
if (run?.id) runs.push(run);
|
|
@@ -520,16 +569,16 @@ function listRunRecords() {
|
|
|
520
569
|
function loadWorker(runId, name) {
|
|
521
570
|
const { runsDir } = getPaths();
|
|
522
571
|
return readJson(
|
|
523
|
-
|
|
572
|
+
path5.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
|
|
524
573
|
);
|
|
525
574
|
}
|
|
526
575
|
function saveRun(run) {
|
|
527
576
|
const { runsDir } = getPaths();
|
|
528
|
-
writeJson(
|
|
577
|
+
writeJson(path5.join(runDir(runsDir, run.id), "run.json"), run);
|
|
529
578
|
}
|
|
530
579
|
function saveWorker(runId, worker) {
|
|
531
580
|
const { runsDir } = getPaths();
|
|
532
|
-
writeJson(
|
|
581
|
+
writeJson(path5.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
|
|
533
582
|
}
|
|
534
583
|
function runDirectory(id) {
|
|
535
584
|
const { runsDir } = getPaths();
|
|
@@ -1282,7 +1331,7 @@ function countActiveWorkersForRun(run) {
|
|
|
1282
1331
|
let active = 0;
|
|
1283
1332
|
for (const name of Object.keys(run.workers || {})) {
|
|
1284
1333
|
const worker = readJson(
|
|
1285
|
-
|
|
1334
|
+
path6.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1286
1335
|
void 0
|
|
1287
1336
|
);
|
|
1288
1337
|
if (!worker || !isActiveHarnessWorker(worker)) continue;
|
|
@@ -1490,6 +1539,7 @@ var claudeProvider = {
|
|
|
1490
1539
|
const model = preflight.model;
|
|
1491
1540
|
const stdoutFd = openSync(opts.stdoutPath, "a");
|
|
1492
1541
|
const stderrFd = openSync(opts.stderrPath, "a");
|
|
1542
|
+
const stdio = ["ignore", stdoutFd, stderrFd];
|
|
1493
1543
|
const child = spawn(
|
|
1494
1544
|
"claude",
|
|
1495
1545
|
[
|
|
@@ -1507,7 +1557,7 @@ var claudeProvider = {
|
|
|
1507
1557
|
hiddenSpawnOptions({
|
|
1508
1558
|
cwd: opts.worktreePath,
|
|
1509
1559
|
detached: true,
|
|
1510
|
-
stdio
|
|
1560
|
+
stdio,
|
|
1511
1561
|
env: scrubClaudeEnv(process.env)
|
|
1512
1562
|
})
|
|
1513
1563
|
);
|
|
@@ -1666,10 +1716,10 @@ function readHarnessRetryLimits() {
|
|
|
1666
1716
|
}
|
|
1667
1717
|
|
|
1668
1718
|
// src/lease-renewal.ts
|
|
1669
|
-
import
|
|
1719
|
+
import path7 from "node:path";
|
|
1670
1720
|
function workerRecord(runId, name) {
|
|
1671
1721
|
return readJson(
|
|
1672
|
-
|
|
1722
|
+
path7.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
1673
1723
|
void 0
|
|
1674
1724
|
);
|
|
1675
1725
|
}
|
|
@@ -1736,7 +1786,7 @@ function hasLiveWorkerForTask(runId, taskId) {
|
|
|
1736
1786
|
|
|
1737
1787
|
// src/supervisor.ts
|
|
1738
1788
|
import { existsSync as existsSync10, mkdirSync as mkdirSync3 } from "node:fs";
|
|
1739
|
-
import
|
|
1789
|
+
import path13 from "node:path";
|
|
1740
1790
|
|
|
1741
1791
|
// src/prompt.ts
|
|
1742
1792
|
function buildPrompt(input) {
|
|
@@ -1755,6 +1805,16 @@ function buildPrompt(input) {
|
|
|
1755
1805
|
"- After implementation: wait for report_reviewer then deep_reviewer confirmation (via MCP/session agents) before follow-up rows close.",
|
|
1756
1806
|
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
1807
|
];
|
|
1808
|
+
const mergeGateLines = compact ? [
|
|
1809
|
+
"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)."
|
|
1810
|
+
] : [
|
|
1811
|
+
"GitHub Actions merge-gate cost control (Kynver/Hermes PRs):",
|
|
1812
|
+
"- Prefer local cached package verification (`node scripts/verify-pr-local.mjs --emit-json`) and Vercel preview evidence before GitHub Actions.",
|
|
1813
|
+
"- 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.",
|
|
1814
|
+
"- Record evidence: POST `/api/agent-os/by-id/<agentOsId>/pr-merge-gate/refresh` with prUrl + local/vercel payloads.",
|
|
1815
|
+
"- Request the final Actions run only when local + Vercel are green: POST `.../pr-merge-gate/request-run` (applies `merge-gate` label).",
|
|
1816
|
+
"- Empty failed Actions jobs (no runner/steps/logs) are infra/quota \u2014 do not enter repair loops; escalate to operator."
|
|
1817
|
+
];
|
|
1758
1818
|
const planArtifactLines = compact ? [
|
|
1759
1819
|
"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
1820
|
] : [
|
|
@@ -1776,10 +1836,14 @@ function buildPrompt(input) {
|
|
|
1776
1836
|
"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
1837
|
"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
1838
|
"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.",
|
|
1839
|
+
"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
1840
|
"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.",
|
|
1841
|
+
"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
1842
|
"",
|
|
1781
1843
|
...progressLines,
|
|
1782
1844
|
"",
|
|
1845
|
+
...mergeGateLines,
|
|
1846
|
+
"",
|
|
1783
1847
|
...planArtifactLines,
|
|
1784
1848
|
"",
|
|
1785
1849
|
...input.personaMarkdown?.trim() ? [input.personaMarkdown.trim(), ""] : [],
|
|
@@ -1792,11 +1856,11 @@ function buildPrompt(input) {
|
|
|
1792
1856
|
// src/providers/cursor.ts
|
|
1793
1857
|
import { closeSync as closeSync2, existsSync as existsSync8, openSync as openSync2 } from "node:fs";
|
|
1794
1858
|
import { spawn as spawn2 } from "node:child_process";
|
|
1795
|
-
import
|
|
1859
|
+
import path9 from "node:path";
|
|
1796
1860
|
|
|
1797
1861
|
// src/providers/cursor-windows.ts
|
|
1798
1862
|
import { existsSync as existsSync7, readdirSync as readdirSync3 } from "node:fs";
|
|
1799
|
-
import
|
|
1863
|
+
import path8 from "node:path";
|
|
1800
1864
|
var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
|
|
1801
1865
|
function parseCursorVersionSortKey(versionName) {
|
|
1802
1866
|
const datePart = versionName.split("-")[0];
|
|
@@ -1807,7 +1871,7 @@ function parseCursorVersionSortKey(versionName) {
|
|
|
1807
1871
|
return Number(`${year}${month.padStart(2, "0")}${day.padStart(2, "0")}`);
|
|
1808
1872
|
}
|
|
1809
1873
|
function pickLatestCursorVersionDir(agentRoot) {
|
|
1810
|
-
const versionsRoot =
|
|
1874
|
+
const versionsRoot = path8.join(agentRoot, "versions");
|
|
1811
1875
|
if (!existsSync7(versionsRoot)) return null;
|
|
1812
1876
|
let bestDir = null;
|
|
1813
1877
|
let bestKey = -1;
|
|
@@ -1816,21 +1880,21 @@ function pickLatestCursorVersionDir(agentRoot) {
|
|
|
1816
1880
|
const key = parseCursorVersionSortKey(entry.name);
|
|
1817
1881
|
if (key == null || key <= bestKey) continue;
|
|
1818
1882
|
bestKey = key;
|
|
1819
|
-
bestDir =
|
|
1883
|
+
bestDir = path8.join(versionsRoot, entry.name);
|
|
1820
1884
|
}
|
|
1821
1885
|
return bestDir;
|
|
1822
1886
|
}
|
|
1823
1887
|
function resolveWindowsCursorBundled(agentRoot) {
|
|
1824
|
-
const root = agentRoot?.trim() ||
|
|
1825
|
-
const directNode =
|
|
1826
|
-
const directIndex =
|
|
1888
|
+
const root = agentRoot?.trim() || path8.join(process.env.LOCALAPPDATA || "", "cursor-agent");
|
|
1889
|
+
const directNode = path8.join(root, "node.exe");
|
|
1890
|
+
const directIndex = path8.join(root, "index.js");
|
|
1827
1891
|
if (existsSync7(directNode) && existsSync7(directIndex)) {
|
|
1828
1892
|
return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
|
|
1829
1893
|
}
|
|
1830
1894
|
const versionDir = pickLatestCursorVersionDir(root);
|
|
1831
1895
|
if (!versionDir) return null;
|
|
1832
|
-
const nodeExe =
|
|
1833
|
-
const indexJs =
|
|
1896
|
+
const nodeExe = path8.join(versionDir, "node.exe");
|
|
1897
|
+
const indexJs = path8.join(versionDir, "index.js");
|
|
1834
1898
|
if (!existsSync7(nodeExe) || !existsSync7(indexJs)) return null;
|
|
1835
1899
|
return { nodeExe, indexJs, versionDir };
|
|
1836
1900
|
}
|
|
@@ -1849,13 +1913,13 @@ function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
|
|
|
1849
1913
|
function resolveCursorSpawn(agentBin) {
|
|
1850
1914
|
if (process.platform === "win32") {
|
|
1851
1915
|
const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
|
|
1852
|
-
const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync8(
|
|
1916
|
+
const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync8(path9.join(path9.dirname(agentBin), "index.js"));
|
|
1853
1917
|
const isDefaultShim = agentBin === "agent";
|
|
1854
1918
|
if (isCursorWrapper || isBundledNode || isDefaultShim) {
|
|
1855
|
-
const bundled = isCursorWrapper ? resolveWindowsCursorBundled(
|
|
1919
|
+
const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path9.dirname(agentBin)) : isBundledNode ? {
|
|
1856
1920
|
nodeExe: agentBin,
|
|
1857
|
-
indexJs:
|
|
1858
|
-
versionDir:
|
|
1921
|
+
indexJs: path9.join(path9.dirname(agentBin), "index.js"),
|
|
1922
|
+
versionDir: path9.dirname(agentBin)
|
|
1859
1923
|
} : resolveWindowsCursorBundled();
|
|
1860
1924
|
if (bundled) {
|
|
1861
1925
|
return bundledSpawnTarget(bundled.nodeExe, bundled.indexJs, bundled.versionDir);
|
|
@@ -1875,7 +1939,7 @@ function resolveAgentBin() {
|
|
|
1875
1939
|
process.env.KYNVER_CURSOR_AGENT_ROOT?.trim() || void 0
|
|
1876
1940
|
);
|
|
1877
1941
|
if (bundled) return bundled.nodeExe;
|
|
1878
|
-
const localAgent =
|
|
1942
|
+
const localAgent = path9.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
|
|
1879
1943
|
if (existsSync8(localAgent)) return localAgent;
|
|
1880
1944
|
}
|
|
1881
1945
|
return "agent";
|
|
@@ -1885,7 +1949,7 @@ function cursorWorkerEnv(agentBin, spawnTarget) {
|
|
|
1885
1949
|
...process.env,
|
|
1886
1950
|
CI: "1",
|
|
1887
1951
|
NO_COLOR: "1",
|
|
1888
|
-
...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS:
|
|
1952
|
+
...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path9.basename(agentBin) || "agent.cmd" } : {}
|
|
1889
1953
|
};
|
|
1890
1954
|
}
|
|
1891
1955
|
var cursorProvider = {
|
|
@@ -1902,6 +1966,7 @@ var cursorProvider = {
|
|
|
1902
1966
|
const model = preflight.model;
|
|
1903
1967
|
const stdoutFd = openSync2(opts.stdoutPath, "a");
|
|
1904
1968
|
const stderrFd = openSync2(opts.stderrPath, "a");
|
|
1969
|
+
const stdio = ["ignore", stdoutFd, stderrFd];
|
|
1905
1970
|
const agentBin = resolveAgentBin();
|
|
1906
1971
|
const spawnTarget = resolveCursorSpawn(agentBin);
|
|
1907
1972
|
const child = spawn2(
|
|
@@ -1924,7 +1989,7 @@ var cursorProvider = {
|
|
|
1924
1989
|
cwd: opts.worktreePath,
|
|
1925
1990
|
detached: spawnTarget.detached,
|
|
1926
1991
|
shell: spawnTarget.shell,
|
|
1927
|
-
stdio
|
|
1992
|
+
stdio,
|
|
1928
1993
|
env: cursorWorkerEnv(agentBin, spawnTarget)
|
|
1929
1994
|
})
|
|
1930
1995
|
);
|
|
@@ -1959,7 +2024,7 @@ function resolveWorkerProvider(name) {
|
|
|
1959
2024
|
// src/auto-complete.ts
|
|
1960
2025
|
import { spawn as spawn3 } from "node:child_process";
|
|
1961
2026
|
import { existsSync as existsSync9, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
|
|
1962
|
-
import
|
|
2027
|
+
import path12 from "node:path";
|
|
1963
2028
|
import { fileURLToPath } from "node:url";
|
|
1964
2029
|
|
|
1965
2030
|
// src/completion-ack.ts
|
|
@@ -1976,7 +2041,7 @@ function persistCompletionAck(worker, runId, fields) {
|
|
|
1976
2041
|
}
|
|
1977
2042
|
|
|
1978
2043
|
// src/worker-ops.ts
|
|
1979
|
-
import
|
|
2044
|
+
import path11 from "node:path";
|
|
1980
2045
|
|
|
1981
2046
|
// src/pr-handoff/pr-handoff-assess.ts
|
|
1982
2047
|
var REVIEW_LANE_RULE = /^(lane:)?(review|deep_review|planning|landing)(:|$)/i;
|
|
@@ -2313,7 +2378,7 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
|
|
|
2313
2378
|
}
|
|
2314
2379
|
|
|
2315
2380
|
// src/worker-lifecycle.ts
|
|
2316
|
-
import
|
|
2381
|
+
import path10 from "node:path";
|
|
2317
2382
|
var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
|
|
2318
2383
|
"awaiting_review",
|
|
2319
2384
|
"blocked",
|
|
@@ -2369,7 +2434,7 @@ function syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick) {
|
|
|
2369
2434
|
const synced = [];
|
|
2370
2435
|
for (const name of Object.keys(run.workers || {})) {
|
|
2371
2436
|
const worker = readJson(
|
|
2372
|
-
|
|
2437
|
+
path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2373
2438
|
void 0
|
|
2374
2439
|
);
|
|
2375
2440
|
if (!worker?.taskId || isCompletionAcknowledged(worker)) continue;
|
|
@@ -2606,7 +2671,7 @@ function workerStatus(args) {
|
|
|
2606
2671
|
const worker = loadWorker(String(args.run), String(args.name));
|
|
2607
2672
|
const run = loadRun(worker.runId);
|
|
2608
2673
|
const status = computeWorkerStatus(worker, workerStatusOptions(run));
|
|
2609
|
-
writeJson(
|
|
2674
|
+
writeJson(path11.join(worker.workerDir, "last-status.json"), status);
|
|
2610
2675
|
console.log(JSON.stringify(status, null, 2));
|
|
2611
2676
|
}
|
|
2612
2677
|
function buildRunBoard(runId) {
|
|
@@ -2614,7 +2679,7 @@ function buildRunBoard(runId) {
|
|
|
2614
2679
|
const names = Object.keys(run.workers || {});
|
|
2615
2680
|
const workers = names.map((name) => {
|
|
2616
2681
|
const worker = readJson(
|
|
2617
|
-
|
|
2682
|
+
path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2618
2683
|
void 0
|
|
2619
2684
|
);
|
|
2620
2685
|
if (!worker) {
|
|
@@ -2642,16 +2707,17 @@ function buildRunBoard(runId) {
|
|
|
2642
2707
|
const completionRouteStatus = asString(completionResponse?.status);
|
|
2643
2708
|
const completionWarnings = Array.isArray(completionResponse?.warnings) ? completionResponse.warnings.filter((w) => typeof w === "string" && w.trim().length > 0) : [];
|
|
2644
2709
|
const prUrl = asString(completionTask?.prUrl) ?? asString(completionResponse?.prUrl);
|
|
2710
|
+
const completionReportedAt = asString(worker.completionReportedAt);
|
|
2645
2711
|
const lifecycleStage = deriveLifecycleStage({
|
|
2646
2712
|
finished: isFinishedWorkerStatus(status),
|
|
2647
2713
|
completionBlocker,
|
|
2648
2714
|
completionOutcome,
|
|
2649
|
-
completionReportedAt
|
|
2715
|
+
completionReportedAt
|
|
2650
2716
|
});
|
|
2651
2717
|
const nextAction = deriveNextAction({
|
|
2652
2718
|
completionBlocker,
|
|
2653
2719
|
completionOutcome,
|
|
2654
|
-
completionReportedAt
|
|
2720
|
+
completionReportedAt,
|
|
2655
2721
|
finished: isFinishedWorkerStatus(status)
|
|
2656
2722
|
});
|
|
2657
2723
|
const handoffState = deriveHandoffState({
|
|
@@ -2697,7 +2763,7 @@ function buildRunBoard(runId) {
|
|
|
2697
2763
|
gitAncestry: status.gitAncestry,
|
|
2698
2764
|
finalResult: status.finalResult,
|
|
2699
2765
|
lifecycleStage,
|
|
2700
|
-
completionReportedAt
|
|
2766
|
+
completionReportedAt,
|
|
2701
2767
|
completionOutcome: worker.completionOutcome ?? null,
|
|
2702
2768
|
completionRouteStatus,
|
|
2703
2769
|
completionRouteOutcome: completionOutcome,
|
|
@@ -2724,7 +2790,7 @@ function buildRunBoard(runId) {
|
|
|
2724
2790
|
needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
|
|
2725
2791
|
workers
|
|
2726
2792
|
};
|
|
2727
|
-
writeJson(
|
|
2793
|
+
writeJson(path11.join(runDirectory(run.id), "last-board.json"), board);
|
|
2728
2794
|
return board;
|
|
2729
2795
|
}
|
|
2730
2796
|
async function publishHarnessBoardSnapshot(args, source) {
|
|
@@ -2913,12 +2979,12 @@ async function autoCompleteWorkerCli(raw) {
|
|
|
2913
2979
|
}
|
|
2914
2980
|
}
|
|
2915
2981
|
function resolveDefaultCliPath() {
|
|
2916
|
-
return
|
|
2982
|
+
return path12.join(fileURLToPath(new URL(".", import.meta.url)), "cli.js");
|
|
2917
2983
|
}
|
|
2918
2984
|
function spawnCompletionSidecar(opts) {
|
|
2919
2985
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath();
|
|
2920
2986
|
if (!existsSync9(cliPath)) return void 0;
|
|
2921
|
-
const logPath =
|
|
2987
|
+
const logPath = path12.join(opts.workerDir, "auto-complete.log");
|
|
2922
2988
|
let logFd;
|
|
2923
2989
|
try {
|
|
2924
2990
|
logFd = openSync3(logPath, "a");
|
|
@@ -3000,16 +3066,16 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3000
3066
|
launchModel = preflight.model;
|
|
3001
3067
|
}
|
|
3002
3068
|
const { worktreesDir } = getPaths();
|
|
3003
|
-
const workerDir =
|
|
3069
|
+
const workerDir = path13.join(runDirectory(run.id), "workers", name);
|
|
3004
3070
|
mkdirSync3(workerDir, { recursive: true });
|
|
3005
|
-
const worktreePath =
|
|
3071
|
+
const worktreePath = path13.join(worktreesDir, run.id, name);
|
|
3006
3072
|
const branch = opts.branch || `agent/${run.id}/${name}`;
|
|
3007
3073
|
if (existsSync10(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
|
|
3008
3074
|
git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
|
|
3009
3075
|
git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
|
|
3010
|
-
const stdoutPath =
|
|
3011
|
-
const stderrPath =
|
|
3012
|
-
const heartbeatPath =
|
|
3076
|
+
const stdoutPath = path13.join(workerDir, "stdout.jsonl");
|
|
3077
|
+
const stderrPath = path13.join(workerDir, "stderr.log");
|
|
3078
|
+
const heartbeatPath = path13.join(workerDir, "heartbeat.jsonl");
|
|
3013
3079
|
const prompt = buildPrompt({
|
|
3014
3080
|
task: opts.task,
|
|
3015
3081
|
ownedPaths: opts.ownedPaths || [],
|
|
@@ -3070,7 +3136,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3070
3136
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3071
3137
|
};
|
|
3072
3138
|
saveWorker(run.id, worker);
|
|
3073
|
-
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath:
|
|
3139
|
+
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path13.join(workerDir, "worker.json") } };
|
|
3074
3140
|
run.status = "running";
|
|
3075
3141
|
saveRun(run);
|
|
3076
3142
|
if (worker.agentOsId && worker.taskId) {
|
|
@@ -3362,18 +3428,18 @@ function buildPlanPersistIdempotencyKey(input) {
|
|
|
3362
3428
|
|
|
3363
3429
|
// src/plan-persist/paths.ts
|
|
3364
3430
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
3365
|
-
import { homedir as
|
|
3366
|
-
import
|
|
3431
|
+
import { homedir as homedir4 } from "node:os";
|
|
3432
|
+
import path14 from "node:path";
|
|
3367
3433
|
function resolveKynverStateRoot() {
|
|
3368
3434
|
const env = process.env.KYNVER_STATE_ROOT;
|
|
3369
|
-
if (env) return
|
|
3370
|
-
return
|
|
3435
|
+
if (env) return path14.resolve(env);
|
|
3436
|
+
return path14.join(homedir4(), ".kynver", "state");
|
|
3371
3437
|
}
|
|
3372
3438
|
function planOutboxDir() {
|
|
3373
|
-
return
|
|
3439
|
+
return path14.join(resolveKynverStateRoot(), "plan-outbox");
|
|
3374
3440
|
}
|
|
3375
3441
|
function planOutboxArchiveDir() {
|
|
3376
|
-
return
|
|
3442
|
+
return path14.join(resolveKynverStateRoot(), "plan-outbox-archive");
|
|
3377
3443
|
}
|
|
3378
3444
|
function ensurePlanOutboxDirs() {
|
|
3379
3445
|
const outboxDir = planOutboxDir();
|
|
@@ -3384,8 +3450,8 @@ function ensurePlanOutboxDirs() {
|
|
|
3384
3450
|
}
|
|
3385
3451
|
function isTmpOnlyPath(filePath) {
|
|
3386
3452
|
if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
|
|
3387
|
-
const resolved =
|
|
3388
|
-
return resolved.startsWith("/tmp/") || resolved.startsWith(
|
|
3453
|
+
const resolved = path14.resolve(filePath);
|
|
3454
|
+
return resolved.startsWith("/tmp/") || resolved.startsWith(path14.join("/var", "folders"));
|
|
3389
3455
|
}
|
|
3390
3456
|
|
|
3391
3457
|
// src/plan-persist/outbox-store.ts
|
|
@@ -3397,7 +3463,7 @@ import {
|
|
|
3397
3463
|
writeFileSync as writeFileSync3,
|
|
3398
3464
|
unlinkSync
|
|
3399
3465
|
} from "node:fs";
|
|
3400
|
-
import
|
|
3466
|
+
import path15 from "node:path";
|
|
3401
3467
|
import { randomUUID } from "node:crypto";
|
|
3402
3468
|
var DEFAULT_MAX_RETRIES = 12;
|
|
3403
3469
|
function listOutboxItems() {
|
|
@@ -3405,7 +3471,7 @@ function listOutboxItems() {
|
|
|
3405
3471
|
const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
|
|
3406
3472
|
const items = [];
|
|
3407
3473
|
for (const file of files) {
|
|
3408
|
-
const item = readOutboxItem(
|
|
3474
|
+
const item = readOutboxItem(path15.join(outboxDir, file));
|
|
3409
3475
|
if (item && item.queueStatus === "queued") items.push(item);
|
|
3410
3476
|
}
|
|
3411
3477
|
return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
@@ -3426,7 +3492,7 @@ function readOutboxItem(jsonPath) {
|
|
|
3426
3492
|
}
|
|
3427
3493
|
function readOutboxBody(item) {
|
|
3428
3494
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3429
|
-
const bodyFile =
|
|
3495
|
+
const bodyFile = path15.join(outboxDir, item.bodyPath);
|
|
3430
3496
|
return readFileSync6(bodyFile, "utf8");
|
|
3431
3497
|
}
|
|
3432
3498
|
function writeOutboxItem(input, opts) {
|
|
@@ -3434,8 +3500,8 @@ function writeOutboxItem(input, opts) {
|
|
|
3434
3500
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3435
3501
|
const id = opts.existing?.id ?? randomUUID();
|
|
3436
3502
|
const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
|
|
3437
|
-
const jsonPath =
|
|
3438
|
-
const bodyFile =
|
|
3503
|
+
const jsonPath = path15.join(outboxDir, `${id}.json`);
|
|
3504
|
+
const bodyFile = path15.join(outboxDir, bodyPath);
|
|
3439
3505
|
if (!opts.existing) {
|
|
3440
3506
|
writeFileSync3(bodyFile, input.body, "utf8");
|
|
3441
3507
|
}
|
|
@@ -3471,24 +3537,24 @@ function writeOutboxItem(input, opts) {
|
|
|
3471
3537
|
}
|
|
3472
3538
|
function saveOutboxItem(item) {
|
|
3473
3539
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3474
|
-
const jsonPath =
|
|
3540
|
+
const jsonPath = path15.join(outboxDir, `${item.id}.json`);
|
|
3475
3541
|
writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
|
|
3476
3542
|
`, { mode: 384 });
|
|
3477
3543
|
}
|
|
3478
3544
|
function archiveOutboxItem(item) {
|
|
3479
3545
|
const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
|
|
3480
|
-
const jsonSrc =
|
|
3481
|
-
const bodySrc =
|
|
3482
|
-
const jsonDst =
|
|
3483
|
-
const bodyDst =
|
|
3546
|
+
const jsonSrc = path15.join(outboxDir, `${item.id}.json`);
|
|
3547
|
+
const bodySrc = path15.join(outboxDir, item.bodyPath);
|
|
3548
|
+
const jsonDst = path15.join(archiveDir, `${item.id}.json`);
|
|
3549
|
+
const bodyDst = path15.join(archiveDir, item.bodyPath);
|
|
3484
3550
|
if (existsSync12(jsonSrc)) renameSync(jsonSrc, jsonDst);
|
|
3485
3551
|
if (existsSync12(bodySrc)) renameSync(bodySrc, bodyDst);
|
|
3486
3552
|
}
|
|
3487
3553
|
function outboxItemPaths(item) {
|
|
3488
3554
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3489
3555
|
return {
|
|
3490
|
-
jsonPath:
|
|
3491
|
-
bodyPath:
|
|
3556
|
+
jsonPath: path15.join(outboxDir, `${item.id}.json`),
|
|
3557
|
+
bodyPath: path15.join(outboxDir, item.bodyPath)
|
|
3492
3558
|
};
|
|
3493
3559
|
}
|
|
3494
3560
|
function outboxInputFromItem(item, body) {
|
|
@@ -3674,7 +3740,7 @@ function markOutboxFailed(item, message) {
|
|
|
3674
3740
|
}
|
|
3675
3741
|
|
|
3676
3742
|
// src/plan-persist/drain.ts
|
|
3677
|
-
import
|
|
3743
|
+
import path16 from "node:path";
|
|
3678
3744
|
async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
3679
3745
|
const items = listOutboxItems().filter(
|
|
3680
3746
|
(item) => opts.outboxId ? item.id === opts.outboxId : true
|
|
@@ -3708,7 +3774,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
|
3708
3774
|
return result;
|
|
3709
3775
|
}
|
|
3710
3776
|
function loadOutboxById(outboxId) {
|
|
3711
|
-
const jsonPath =
|
|
3777
|
+
const jsonPath = path16.join(planOutboxDir(), `${outboxId}.json`);
|
|
3712
3778
|
return readOutboxItem(jsonPath);
|
|
3713
3779
|
}
|
|
3714
3780
|
|
|
@@ -3808,7 +3874,7 @@ async function dispatchRun(args) {
|
|
|
3808
3874
|
const activeHarnessWorkers = [];
|
|
3809
3875
|
for (const name of Object.keys(run.workers || {})) {
|
|
3810
3876
|
const worker = readJson(
|
|
3811
|
-
|
|
3877
|
+
path17.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
3812
3878
|
void 0
|
|
3813
3879
|
);
|
|
3814
3880
|
if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
|
|
@@ -4012,7 +4078,7 @@ async function dispatchRun(args) {
|
|
|
4012
4078
|
}
|
|
4013
4079
|
|
|
4014
4080
|
// src/sweep.ts
|
|
4015
|
-
import
|
|
4081
|
+
import path18 from "node:path";
|
|
4016
4082
|
async function sweepRun(args) {
|
|
4017
4083
|
const pipeline = args.pipeline === true || args.pipeline === "true";
|
|
4018
4084
|
try {
|
|
@@ -4025,7 +4091,7 @@ async function sweepRun(args) {
|
|
|
4025
4091
|
const releasedLocalOrphans = [];
|
|
4026
4092
|
for (const name of Object.keys(run.workers || {})) {
|
|
4027
4093
|
const worker = readJson(
|
|
4028
|
-
|
|
4094
|
+
path18.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4029
4095
|
void 0
|
|
4030
4096
|
);
|
|
4031
4097
|
if (!worker || !worker.dispatched || !worker.taskId) continue;
|
|
@@ -4069,10 +4135,10 @@ async function sweepRun(args) {
|
|
|
4069
4135
|
|
|
4070
4136
|
// src/worktree.ts
|
|
4071
4137
|
import { existsSync as existsSync13, mkdirSync as mkdirSync5 } from "node:fs";
|
|
4072
|
-
import
|
|
4138
|
+
import path20 from "node:path";
|
|
4073
4139
|
|
|
4074
4140
|
// src/validate.ts
|
|
4075
|
-
import
|
|
4141
|
+
import path19 from "node:path";
|
|
4076
4142
|
var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
4077
4143
|
function validateRunId(runId) {
|
|
4078
4144
|
const trimmed = runId.trim();
|
|
@@ -4080,7 +4146,7 @@ function validateRunId(runId) {
|
|
|
4080
4146
|
return trimmed;
|
|
4081
4147
|
}
|
|
4082
4148
|
function validateRepo(repo) {
|
|
4083
|
-
const resolved =
|
|
4149
|
+
const resolved = path19.resolve(repo);
|
|
4084
4150
|
if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
|
|
4085
4151
|
return resolved;
|
|
4086
4152
|
}
|
|
@@ -4105,12 +4171,12 @@ function createRun(args) {
|
|
|
4105
4171
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4106
4172
|
workers: {}
|
|
4107
4173
|
};
|
|
4108
|
-
writeJson(
|
|
4174
|
+
writeJson(path20.join(dir, "run.json"), run);
|
|
4109
4175
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
4110
4176
|
}
|
|
4111
4177
|
function listRuns() {
|
|
4112
4178
|
const { runsDir } = getPaths();
|
|
4113
|
-
const rows = listRunIds(runsDir).map((id) => readJson(
|
|
4179
|
+
const rows = listRunIds(runsDir).map((id) => readJson(path20.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
|
|
4114
4180
|
id: run.id,
|
|
4115
4181
|
name: run.name,
|
|
4116
4182
|
status: run.status,
|
|
@@ -4125,7 +4191,7 @@ function failExists(message) {
|
|
|
4125
4191
|
}
|
|
4126
4192
|
|
|
4127
4193
|
// src/pipeline-tick.ts
|
|
4128
|
-
import
|
|
4194
|
+
import path29 from "node:path";
|
|
4129
4195
|
|
|
4130
4196
|
// src/pipeline-dispatch.ts
|
|
4131
4197
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -4179,10 +4245,10 @@ async function runPipelineDispatch(args, slots) {
|
|
|
4179
4245
|
}
|
|
4180
4246
|
|
|
4181
4247
|
// src/stale-reconcile.ts
|
|
4182
|
-
import
|
|
4248
|
+
import path22 from "node:path";
|
|
4183
4249
|
|
|
4184
4250
|
// src/finalize.ts
|
|
4185
|
-
import
|
|
4251
|
+
import path21 from "node:path";
|
|
4186
4252
|
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
|
|
4187
4253
|
function terminalStatusFor(run) {
|
|
4188
4254
|
const names = Object.keys(run.workers || {});
|
|
@@ -4193,7 +4259,7 @@ function terminalStatusFor(run) {
|
|
|
4193
4259
|
let anyLandingBlocked = false;
|
|
4194
4260
|
for (const name of names) {
|
|
4195
4261
|
const worker = readJson(
|
|
4196
|
-
|
|
4262
|
+
path21.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4197
4263
|
void 0
|
|
4198
4264
|
);
|
|
4199
4265
|
if (!worker) continue;
|
|
@@ -4245,7 +4311,7 @@ function reconcileStaleWorkers() {
|
|
|
4245
4311
|
const now = Date.now();
|
|
4246
4312
|
for (const run of listRunRecords()) {
|
|
4247
4313
|
for (const name of Object.keys(run.workers || {})) {
|
|
4248
|
-
const workerPath =
|
|
4314
|
+
const workerPath = path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
4249
4315
|
const worker = readJson(workerPath, void 0);
|
|
4250
4316
|
if (!worker || worker.status !== "running") {
|
|
4251
4317
|
outcomes.push({
|
|
@@ -4339,7 +4405,7 @@ function reconcileRunsCli() {
|
|
|
4339
4405
|
}
|
|
4340
4406
|
|
|
4341
4407
|
// src/plan-progress-daemon-sync.ts
|
|
4342
|
-
import
|
|
4408
|
+
import path23 from "node:path";
|
|
4343
4409
|
|
|
4344
4410
|
// src/plan-progress-sync.ts
|
|
4345
4411
|
async function syncPlanProgress(args) {
|
|
@@ -4363,7 +4429,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
4363
4429
|
const outcomes = [];
|
|
4364
4430
|
for (const name of Object.keys(run.workers || {})) {
|
|
4365
4431
|
const worker = readJson(
|
|
4366
|
-
|
|
4432
|
+
path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4367
4433
|
void 0
|
|
4368
4434
|
);
|
|
4369
4435
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -4412,7 +4478,7 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
4412
4478
|
}
|
|
4413
4479
|
|
|
4414
4480
|
// src/cleanup.ts
|
|
4415
|
-
import
|
|
4481
|
+
import path28 from "node:path";
|
|
4416
4482
|
|
|
4417
4483
|
// src/cleanup-types.ts
|
|
4418
4484
|
var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
@@ -4484,11 +4550,11 @@ function skipNodeModulesRemoval(input) {
|
|
|
4484
4550
|
|
|
4485
4551
|
// src/cleanup-execute.ts
|
|
4486
4552
|
import { existsSync as existsSync15, rmSync } from "node:fs";
|
|
4487
|
-
import
|
|
4553
|
+
import path25 from "node:path";
|
|
4488
4554
|
|
|
4489
4555
|
// src/cleanup-dir-size.ts
|
|
4490
4556
|
import { existsSync as existsSync14, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
|
|
4491
|
-
import
|
|
4557
|
+
import path24 from "node:path";
|
|
4492
4558
|
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
4493
4559
|
if (!existsSync14(root)) return 0;
|
|
4494
4560
|
let total = 0;
|
|
@@ -4504,7 +4570,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
4504
4570
|
}
|
|
4505
4571
|
for (const name of entries) {
|
|
4506
4572
|
if (seen++ > maxEntries) return null;
|
|
4507
|
-
const full =
|
|
4573
|
+
const full = path24.join(current, name);
|
|
4508
4574
|
let st;
|
|
4509
4575
|
try {
|
|
4510
4576
|
st = statSync2(full);
|
|
@@ -4588,20 +4654,20 @@ function removeWorktree(candidate, execute) {
|
|
|
4588
4654
|
}
|
|
4589
4655
|
}
|
|
4590
4656
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
4591
|
-
const resolved =
|
|
4592
|
-
const nm = resolved.endsWith(`${
|
|
4657
|
+
const resolved = path25.resolve(targetPath);
|
|
4658
|
+
const nm = resolved.endsWith(`${path25.sep}node_modules`) ? resolved : null;
|
|
4593
4659
|
if (!nm) return "path_outside_harness";
|
|
4594
|
-
const rel =
|
|
4595
|
-
if (rel.startsWith("..") ||
|
|
4596
|
-
const parts = rel.split(
|
|
4660
|
+
const rel = path25.relative(worktreesDir, nm);
|
|
4661
|
+
if (rel.startsWith("..") || path25.isAbsolute(rel)) return "path_outside_harness";
|
|
4662
|
+
const parts = rel.split(path25.sep);
|
|
4597
4663
|
if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
|
|
4598
|
-
if (!resolved.startsWith(
|
|
4664
|
+
if (!resolved.startsWith(path25.resolve(harnessRoot))) return "path_outside_harness";
|
|
4599
4665
|
return null;
|
|
4600
4666
|
}
|
|
4601
4667
|
|
|
4602
4668
|
// src/cleanup-scan.ts
|
|
4603
4669
|
import { existsSync as existsSync16, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
4604
|
-
import
|
|
4670
|
+
import path26 from "node:path";
|
|
4605
4671
|
function pathAgeMs(target, now) {
|
|
4606
4672
|
try {
|
|
4607
4673
|
const mtime = statSync3(target).mtimeMs;
|
|
@@ -4611,17 +4677,17 @@ function pathAgeMs(target, now) {
|
|
|
4611
4677
|
}
|
|
4612
4678
|
}
|
|
4613
4679
|
function isPathInside(child, parent) {
|
|
4614
|
-
const rel =
|
|
4615
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
4680
|
+
const rel = path26.relative(parent, child);
|
|
4681
|
+
return rel === "" || !rel.startsWith("..") && !path26.isAbsolute(rel);
|
|
4616
4682
|
}
|
|
4617
4683
|
function scanNodeModulesCandidates(opts) {
|
|
4618
4684
|
const candidates = [];
|
|
4619
4685
|
const seen = /* @__PURE__ */ new Set();
|
|
4620
4686
|
for (const entry of opts.index.values()) {
|
|
4621
4687
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
4622
|
-
const nm =
|
|
4688
|
+
const nm = path26.join(entry.worktreePath, "node_modules");
|
|
4623
4689
|
if (!existsSync16(nm)) continue;
|
|
4624
|
-
const resolved =
|
|
4690
|
+
const resolved = path26.resolve(nm);
|
|
4625
4691
|
if (seen.has(resolved)) continue;
|
|
4626
4692
|
seen.add(resolved);
|
|
4627
4693
|
candidates.push({
|
|
@@ -4637,13 +4703,13 @@ function scanNodeModulesCandidates(opts) {
|
|
|
4637
4703
|
if (!opts.includeOrphans || !existsSync16(opts.worktreesDir)) return candidates;
|
|
4638
4704
|
for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
|
|
4639
4705
|
if (!runEntry.isDirectory()) continue;
|
|
4640
|
-
const runPath =
|
|
4706
|
+
const runPath = path26.join(opts.worktreesDir, runEntry.name);
|
|
4641
4707
|
for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
|
|
4642
4708
|
if (!workerEntry.isDirectory()) continue;
|
|
4643
|
-
const worktreePath =
|
|
4644
|
-
const nm =
|
|
4709
|
+
const worktreePath = path26.join(runPath, workerEntry.name);
|
|
4710
|
+
const nm = path26.join(worktreePath, "node_modules");
|
|
4645
4711
|
if (!existsSync16(nm)) continue;
|
|
4646
|
-
const resolved =
|
|
4712
|
+
const resolved = path26.resolve(nm);
|
|
4647
4713
|
if (seen.has(resolved)) continue;
|
|
4648
4714
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
4649
4715
|
seen.add(resolved);
|
|
@@ -4683,17 +4749,17 @@ function scanWorktreeCandidates(opts) {
|
|
|
4683
4749
|
}
|
|
4684
4750
|
|
|
4685
4751
|
// src/cleanup-worktree-index.ts
|
|
4686
|
-
import
|
|
4752
|
+
import path27 from "node:path";
|
|
4687
4753
|
function buildWorktreeIndex() {
|
|
4688
4754
|
const index = /* @__PURE__ */ new Map();
|
|
4689
4755
|
for (const run of listRunRecords()) {
|
|
4690
4756
|
for (const name of Object.keys(run.workers || {})) {
|
|
4691
|
-
const workerPath =
|
|
4757
|
+
const workerPath = path27.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
4692
4758
|
const worker = readJson(workerPath, void 0);
|
|
4693
4759
|
if (!worker?.worktreePath) continue;
|
|
4694
4760
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
4695
|
-
index.set(
|
|
4696
|
-
worktreePath:
|
|
4761
|
+
index.set(path27.resolve(worker.worktreePath), {
|
|
4762
|
+
worktreePath: path27.resolve(worker.worktreePath),
|
|
4697
4763
|
runId: run.id,
|
|
4698
4764
|
workerName: name,
|
|
4699
4765
|
run,
|
|
@@ -4707,8 +4773,8 @@ function buildWorktreeIndex() {
|
|
|
4707
4773
|
|
|
4708
4774
|
// src/cleanup.ts
|
|
4709
4775
|
function resolveOptions(options = {}) {
|
|
4710
|
-
const harnessRoot = options.harnessRoot ?
|
|
4711
|
-
const { worktreesDir } = options.harnessRoot ? { worktreesDir:
|
|
4776
|
+
const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
|
|
4777
|
+
const { worktreesDir } = options.harnessRoot ? { worktreesDir: path28.join(harnessRoot, "worktrees") } : getHarnessPaths();
|
|
4712
4778
|
const execute = options.execute === true;
|
|
4713
4779
|
const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
|
|
4714
4780
|
const worktreesAgeMs = options.worktreesAgeMs ?? 0;
|
|
@@ -4752,7 +4818,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
4752
4818
|
actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
|
|
4753
4819
|
continue;
|
|
4754
4820
|
}
|
|
4755
|
-
const worktreePath =
|
|
4821
|
+
const worktreePath = path28.resolve(candidate.path, "..");
|
|
4756
4822
|
const indexed = index.get(worktreePath) ?? null;
|
|
4757
4823
|
const guardReason = skipNodeModulesRemoval({
|
|
4758
4824
|
indexed,
|
|
@@ -4768,7 +4834,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
4768
4834
|
actions.push(removeNodeModules(candidate, resolved.execute));
|
|
4769
4835
|
}
|
|
4770
4836
|
for (const candidate of scanWorktreeCandidates(scanOpts)) {
|
|
4771
|
-
const indexed = index.get(
|
|
4837
|
+
const indexed = index.get(path28.resolve(candidate.path)) ?? null;
|
|
4772
4838
|
const guardReason = skipWorktreeRemoval({
|
|
4773
4839
|
indexed,
|
|
4774
4840
|
includeOrphans: resolved.includeOrphans,
|
|
@@ -4837,7 +4903,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
4837
4903
|
const outcomes = [];
|
|
4838
4904
|
for (const name of Object.keys(run.workers || {})) {
|
|
4839
4905
|
const worker = readJson(
|
|
4840
|
-
|
|
4906
|
+
path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4841
4907
|
void 0
|
|
4842
4908
|
);
|
|
4843
4909
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -5172,7 +5238,7 @@ function runCleanupCli(args) {
|
|
|
5172
5238
|
}
|
|
5173
5239
|
|
|
5174
5240
|
// src/monitor/monitor.service.ts
|
|
5175
|
-
import
|
|
5241
|
+
import path31 from "node:path";
|
|
5176
5242
|
|
|
5177
5243
|
// src/monitor/monitor.classify.ts
|
|
5178
5244
|
function expectedLeaseOwner(runId) {
|
|
@@ -5229,10 +5295,10 @@ function classifyWorkerHealth(input) {
|
|
|
5229
5295
|
|
|
5230
5296
|
// src/monitor/monitor.store.ts
|
|
5231
5297
|
import { existsSync as existsSync17, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
|
|
5232
|
-
import
|
|
5298
|
+
import path30 from "node:path";
|
|
5233
5299
|
function monitorsDir() {
|
|
5234
5300
|
const { harnessRoot } = getHarnessPaths();
|
|
5235
|
-
const dir =
|
|
5301
|
+
const dir = path30.join(harnessRoot, "monitors");
|
|
5236
5302
|
mkdirSync6(dir, { recursive: true });
|
|
5237
5303
|
return dir;
|
|
5238
5304
|
}
|
|
@@ -5240,7 +5306,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
5240
5306
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
5241
5307
|
}
|
|
5242
5308
|
function monitorPath(monitorId) {
|
|
5243
|
-
return
|
|
5309
|
+
return path30.join(monitorsDir(), `${monitorId}.json`);
|
|
5244
5310
|
}
|
|
5245
5311
|
function loadMonitorSession(monitorId) {
|
|
5246
5312
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -5261,7 +5327,7 @@ function listMonitorSessions() {
|
|
|
5261
5327
|
for (const name of readdirSync7(dir)) {
|
|
5262
5328
|
if (!name.endsWith(".json")) continue;
|
|
5263
5329
|
const session = readJson(
|
|
5264
|
-
|
|
5330
|
+
path30.join(dir, name),
|
|
5265
5331
|
void 0
|
|
5266
5332
|
);
|
|
5267
5333
|
if (!session?.monitorId) continue;
|
|
@@ -5352,7 +5418,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
5352
5418
|
// src/monitor/monitor.service.ts
|
|
5353
5419
|
function workerRecord2(runId, name) {
|
|
5354
5420
|
return readJson(
|
|
5355
|
-
|
|
5421
|
+
path31.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
5356
5422
|
void 0
|
|
5357
5423
|
);
|
|
5358
5424
|
}
|
|
@@ -5555,17 +5621,17 @@ async function runMonitorLoop(args) {
|
|
|
5555
5621
|
// src/monitor/monitor-spawn.ts
|
|
5556
5622
|
import { spawn as spawn4 } from "node:child_process";
|
|
5557
5623
|
import { closeSync as closeSync4, existsSync as existsSync18, openSync as openSync4 } from "node:fs";
|
|
5558
|
-
import
|
|
5624
|
+
import path32 from "node:path";
|
|
5559
5625
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5560
5626
|
function resolveDefaultCliPath2() {
|
|
5561
|
-
return
|
|
5627
|
+
return path32.join(fileURLToPath2(new URL(".", import.meta.url)), "..", "cli.js");
|
|
5562
5628
|
}
|
|
5563
5629
|
function spawnMonitorSidecar(opts) {
|
|
5564
5630
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
5565
5631
|
if (!existsSync18(cliPath)) return void 0;
|
|
5566
5632
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
5567
5633
|
const { harnessRoot } = getHarnessPaths();
|
|
5568
|
-
const logPath =
|
|
5634
|
+
const logPath = path32.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
5569
5635
|
let logFd;
|
|
5570
5636
|
try {
|
|
5571
5637
|
logFd = openSync4(logPath, "a");
|
|
@@ -5720,6 +5786,422 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
|
|
|
5720
5786
|
return true;
|
|
5721
5787
|
}
|
|
5722
5788
|
|
|
5789
|
+
// src/doctor/runtime-takeover.ts
|
|
5790
|
+
import path34 from "node:path";
|
|
5791
|
+
|
|
5792
|
+
// src/doctor/runtime-takeover.probes.ts
|
|
5793
|
+
import { accessSync, constants, existsSync as existsSync20, readFileSync as readFileSync9 } from "node:fs";
|
|
5794
|
+
import { homedir as homedir5 } from "node:os";
|
|
5795
|
+
import path33 from "node:path";
|
|
5796
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
5797
|
+
function captureCommand(bin, args) {
|
|
5798
|
+
try {
|
|
5799
|
+
const res = spawnSync3(bin, args, { encoding: "utf8" });
|
|
5800
|
+
const stdout = (res.stdout || "").trim();
|
|
5801
|
+
const stderr = (res.stderr || "").trim();
|
|
5802
|
+
const ok = res.status === 0;
|
|
5803
|
+
return {
|
|
5804
|
+
ok,
|
|
5805
|
+
stdout,
|
|
5806
|
+
stderr,
|
|
5807
|
+
error: res.error?.message
|
|
5808
|
+
};
|
|
5809
|
+
} catch (error) {
|
|
5810
|
+
return {
|
|
5811
|
+
ok: false,
|
|
5812
|
+
stdout: "",
|
|
5813
|
+
stderr: "",
|
|
5814
|
+
error: error.message
|
|
5815
|
+
};
|
|
5816
|
+
}
|
|
5817
|
+
}
|
|
5818
|
+
function tokenPrefix(token) {
|
|
5819
|
+
const trimmed = token?.trim();
|
|
5820
|
+
if (!trimmed) return void 0;
|
|
5821
|
+
return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
|
|
5822
|
+
}
|
|
5823
|
+
function isWritable(target) {
|
|
5824
|
+
if (!existsSync20(target)) return false;
|
|
5825
|
+
try {
|
|
5826
|
+
accessSync(target, constants.W_OK);
|
|
5827
|
+
return true;
|
|
5828
|
+
} catch {
|
|
5829
|
+
return false;
|
|
5830
|
+
}
|
|
5831
|
+
}
|
|
5832
|
+
var defaultRuntimeTakeoverProbes = {
|
|
5833
|
+
packageVersion: () => PACKAGE_VERSION,
|
|
5834
|
+
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
5835
|
+
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
5836
|
+
loadConfig: () => loadUserConfig(),
|
|
5837
|
+
configFilePath: () => path33.join(homedir5(), ".kynver", "config.json"),
|
|
5838
|
+
credentialsFilePath: () => path33.join(homedir5(), ".kynver", "credentials"),
|
|
5839
|
+
readCredentials: () => {
|
|
5840
|
+
const credPath = path33.join(homedir5(), ".kynver", "credentials");
|
|
5841
|
+
if (!existsSync20(credPath)) {
|
|
5842
|
+
return { hasApiKey: false };
|
|
5843
|
+
}
|
|
5844
|
+
try {
|
|
5845
|
+
const parsed = JSON.parse(readFileSync9(credPath, "utf8"));
|
|
5846
|
+
return {
|
|
5847
|
+
hasApiKey: Boolean(parsed.apiKey?.trim()),
|
|
5848
|
+
runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
|
|
5849
|
+
runnerTokenAgentOsId: parsed.runnerTokenAgentOsId
|
|
5850
|
+
};
|
|
5851
|
+
} catch {
|
|
5852
|
+
return { hasApiKey: false };
|
|
5853
|
+
}
|
|
5854
|
+
},
|
|
5855
|
+
envSnapshot: () => ({
|
|
5856
|
+
kynverApiUrl: process.env.KYNVER_API_URL?.trim() || void 0,
|
|
5857
|
+
openclawCronFireBaseUrl: process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || void 0,
|
|
5858
|
+
kynverRunnerTokenPrefix: tokenPrefix(process.env.KYNVER_RUNNER_TOKEN),
|
|
5859
|
+
kynverRuntimeSecret: Boolean(process.env.KYNVER_RUNTIME_SECRET?.trim()),
|
|
5860
|
+
openclawCronSecret: Boolean(process.env.OPENCLAW_CRON_SECRET?.trim()),
|
|
5861
|
+
kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
|
|
5862
|
+
opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
|
|
5863
|
+
kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0
|
|
5864
|
+
}),
|
|
5865
|
+
harnessRoot: () => resolveHarnessRoot(),
|
|
5866
|
+
legacyOpenclawHarnessRoot: () => path33.join(homedir5(), ".openclaw", "harness"),
|
|
5867
|
+
pathExists: (target) => existsSync20(target),
|
|
5868
|
+
pathWritable: (target) => isWritable(target),
|
|
5869
|
+
vercelVersion: () => captureCommand("vercel", ["--version"]),
|
|
5870
|
+
vercelWhoami: () => captureCommand("vercel", ["whoami"])
|
|
5871
|
+
};
|
|
5872
|
+
|
|
5873
|
+
// src/doctor/runtime-takeover.ts
|
|
5874
|
+
function check(partial) {
|
|
5875
|
+
return partial;
|
|
5876
|
+
}
|
|
5877
|
+
function summarizeCounts(sections) {
|
|
5878
|
+
const counts = { pass: 0, warn: 0, fail: 0 };
|
|
5879
|
+
for (const section of sections) {
|
|
5880
|
+
for (const item of section.checks) {
|
|
5881
|
+
counts[item.status] += 1;
|
|
5882
|
+
}
|
|
5883
|
+
}
|
|
5884
|
+
return counts;
|
|
5885
|
+
}
|
|
5886
|
+
function assessCliPackage(probes) {
|
|
5887
|
+
const runningVersion = probes.packageVersion();
|
|
5888
|
+
const which = probes.commandOnPath("kynver");
|
|
5889
|
+
const onPath = which.ok && which.stdout.length > 0;
|
|
5890
|
+
const firstPath = onPath ? which.stdout.split(/\r?\n/)[0]?.trim() : void 0;
|
|
5891
|
+
const displayCliPath = firstPath ? displayUserPath(firstPath) : void 0;
|
|
5892
|
+
const checks = [
|
|
5893
|
+
check({
|
|
5894
|
+
id: "cli_running_version",
|
|
5895
|
+
label: "Running @kynver-app/runtime version",
|
|
5896
|
+
status: "pass",
|
|
5897
|
+
summary: `@kynver-app/runtime ${runningVersion}`,
|
|
5898
|
+
details: { version: runningVersion }
|
|
5899
|
+
}),
|
|
5900
|
+
check({
|
|
5901
|
+
id: "cli_on_path",
|
|
5902
|
+
label: "kynver executable on PATH",
|
|
5903
|
+
status: onPath ? "pass" : "warn",
|
|
5904
|
+
summary: onPath ? `Found ${displayCliPath}` : "kynver not found on PATH (invoked via node/npx?)",
|
|
5905
|
+
remediation: onPath ? void 0 : "Install globally (`npm i -g @kynver-app/runtime`) or invoke via `npx @kynver-app/runtime`.",
|
|
5906
|
+
details: { path: displayCliPath }
|
|
5907
|
+
})
|
|
5908
|
+
];
|
|
5909
|
+
if (onPath && firstPath) {
|
|
5910
|
+
const versionProbe = probes.kynverVersion(firstPath);
|
|
5911
|
+
const installedVersion = versionProbe.stdout.replace(/^kynver\s+/i, "").trim() || void 0;
|
|
5912
|
+
const versionMatch = versionProbe.ok && (!installedVersion || installedVersion === runningVersion);
|
|
5913
|
+
checks.push(
|
|
5914
|
+
check({
|
|
5915
|
+
id: "cli_installed_version",
|
|
5916
|
+
label: "Installed kynver CLI version matches running package",
|
|
5917
|
+
status: versionMatch ? "pass" : "warn",
|
|
5918
|
+
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",
|
|
5919
|
+
remediation: versionMatch ? void 0 : "Reinstall or rebuild @kynver-app/runtime so PATH kynver matches the harness worktree version.",
|
|
5920
|
+
details: { installedVersion, runningVersion, path: displayCliPath }
|
|
5921
|
+
})
|
|
5922
|
+
);
|
|
5923
|
+
}
|
|
5924
|
+
return { id: "cli_package", label: "CLI / package", checks };
|
|
5925
|
+
}
|
|
5926
|
+
function assessUserConfig(probes) {
|
|
5927
|
+
const configPath = probes.configFilePath();
|
|
5928
|
+
const displayConfigPath = displayUserPath(configPath);
|
|
5929
|
+
const exists = probes.pathExists(configPath);
|
|
5930
|
+
const config = probes.loadConfig();
|
|
5931
|
+
const checks = [
|
|
5932
|
+
check({
|
|
5933
|
+
id: "config_file",
|
|
5934
|
+
label: "~/.kynver/config.json present",
|
|
5935
|
+
status: exists ? "pass" : "fail",
|
|
5936
|
+
summary: exists ? `Found ${displayConfigPath}` : `Missing ${displayConfigPath}`,
|
|
5937
|
+
remediation: exists ? void 0 : "Run `kynver setup --api-base-url <url> --agent-os-id <id> --repo <path>`.",
|
|
5938
|
+
details: { configPath: displayConfigPath }
|
|
5939
|
+
})
|
|
5940
|
+
];
|
|
5941
|
+
if (exists) {
|
|
5942
|
+
const apiBaseUrl = config.apiBaseUrl?.trim();
|
|
5943
|
+
const agentOsId = config.agentOsId?.trim();
|
|
5944
|
+
const defaultRepo = config.defaultRepo?.trim();
|
|
5945
|
+
const displayDefaultRepo = defaultRepo ? displayUserPath(defaultRepo) : null;
|
|
5946
|
+
checks.push(
|
|
5947
|
+
check({
|
|
5948
|
+
id: "config_api_base_url",
|
|
5949
|
+
label: "Default API base URL",
|
|
5950
|
+
status: apiBaseUrl ? "pass" : "warn",
|
|
5951
|
+
summary: apiBaseUrl ?? "Not set in config (KYNVER_API_URL / --base-url required per command)",
|
|
5952
|
+
remediation: apiBaseUrl ? void 0 : "Set `apiBaseUrl` via `kynver setup --api-base-url https://\u2026`.",
|
|
5953
|
+
details: { apiBaseUrl: apiBaseUrl ?? null }
|
|
5954
|
+
}),
|
|
5955
|
+
check({
|
|
5956
|
+
id: "config_agent_os_id",
|
|
5957
|
+
label: "Default AgentOS id",
|
|
5958
|
+
status: agentOsId ? "pass" : "warn",
|
|
5959
|
+
summary: agentOsId ?? "Not set (pass --agent-os-id on daemon/dispatch/worker commands)",
|
|
5960
|
+
remediation: agentOsId ? void 0 : "Set `agentOsId` via `kynver setup --agent-os-id <uuid>`.",
|
|
5961
|
+
details: { agentOsId: agentOsId ?? null, agentOsSlug: config.agentOsSlug ?? null }
|
|
5962
|
+
}),
|
|
5963
|
+
check({
|
|
5964
|
+
id: "config_default_repo",
|
|
5965
|
+
label: "Default repo path",
|
|
5966
|
+
status: defaultRepo ? "pass" : "warn",
|
|
5967
|
+
summary: displayDefaultRepo ?? "Not set (pass --repo on `kynver run create`)",
|
|
5968
|
+
remediation: defaultRepo ? void 0 : "Set `defaultRepo` via `kynver setup --repo /path/to/repo`.",
|
|
5969
|
+
details: {
|
|
5970
|
+
defaultRepo: displayDefaultRepo,
|
|
5971
|
+
harnessRoot: config.harnessRoot ? redactHomePath(config.harnessRoot) : null
|
|
5972
|
+
}
|
|
5973
|
+
})
|
|
5974
|
+
);
|
|
5975
|
+
}
|
|
5976
|
+
return { id: "user_config", label: "User config (~/.kynver)", checks };
|
|
5977
|
+
}
|
|
5978
|
+
function assessRunnerToken(probes) {
|
|
5979
|
+
const config = probes.loadConfig();
|
|
5980
|
+
const targetAgentOsId = config.agentOsId?.trim();
|
|
5981
|
+
const creds = probes.readCredentials();
|
|
5982
|
+
const env = probes.envSnapshot();
|
|
5983
|
+
const credPath = probes.credentialsFilePath();
|
|
5984
|
+
const displayCredPath = displayUserPath(credPath);
|
|
5985
|
+
const envToken = env.kynverRunnerTokenPrefix;
|
|
5986
|
+
const savedToken = creds.runnerTokenPrefix;
|
|
5987
|
+
const savedAgentOsId = creds.runnerTokenAgentOsId;
|
|
5988
|
+
const scopedSaved = Boolean(savedToken) && (!targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId);
|
|
5989
|
+
const hasScoped = Boolean(envToken?.startsWith("krc1.")) || scopedSaved && savedToken?.startsWith("krc1.");
|
|
5990
|
+
const checks = [
|
|
5991
|
+
check({
|
|
5992
|
+
id: "runner_token_scoped",
|
|
5993
|
+
label: "Scoped runner token (krc1.*) ready",
|
|
5994
|
+
status: hasScoped ? "pass" : "fail",
|
|
5995
|
+
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",
|
|
5996
|
+
remediation: hasScoped ? void 0 : "Run `kynver login` then `kynver runner credential --agent-os-id <id>` (or `kynver setup` with API key).",
|
|
5997
|
+
details: {
|
|
5998
|
+
source: envToken ? "env" : savedToken ? "credentials" : "none",
|
|
5999
|
+
tokenPrefix: envToken ?? (scopedSaved ? savedToken : void 0),
|
|
6000
|
+
agentOsId: targetAgentOsId ?? savedAgentOsId ?? null,
|
|
6001
|
+
credentialsPath: displayCredPath
|
|
6002
|
+
}
|
|
6003
|
+
}),
|
|
6004
|
+
check({
|
|
6005
|
+
id: "runner_token_agent_os_match",
|
|
6006
|
+
label: "Saved runner token matches configured agentOsId",
|
|
6007
|
+
status: !savedToken || !targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId ? "pass" : "warn",
|
|
6008
|
+
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}`,
|
|
6009
|
+
remediation: savedToken && targetAgentOsId && savedAgentOsId && savedAgentOsId !== targetAgentOsId ? "`kynver runner credential --agent-os-id <configured-id>` to mint a workspace-bound token." : void 0,
|
|
6010
|
+
details: { configuredAgentOsId: targetAgentOsId ?? null, savedAgentOsId: savedAgentOsId ?? null }
|
|
6011
|
+
}),
|
|
6012
|
+
check({
|
|
6013
|
+
id: "runner_api_key_for_refresh",
|
|
6014
|
+
label: "API key available for token refresh",
|
|
6015
|
+
status: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY?.trim()) ? "pass" : "warn",
|
|
6016
|
+
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",
|
|
6017
|
+
remediation: creds.hasApiKey || process.env.KYNVER_API_KEY ? void 0 : "Run `kynver login --api-key \u2026`.",
|
|
6018
|
+
details: { credentialsPath: displayCredPath, hasApiKey: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY) }
|
|
6019
|
+
})
|
|
6020
|
+
];
|
|
6021
|
+
return { id: "runner_token", label: "Runner token readiness", checks };
|
|
6022
|
+
}
|
|
6023
|
+
function assessVercelCli(probes) {
|
|
6024
|
+
const version = probes.vercelVersion();
|
|
6025
|
+
const whoami = probes.vercelWhoami();
|
|
6026
|
+
const installed = version.ok;
|
|
6027
|
+
return {
|
|
6028
|
+
id: "vercel_cli",
|
|
6029
|
+
label: "Vercel CLI",
|
|
6030
|
+
checks: [
|
|
6031
|
+
check({
|
|
6032
|
+
id: "vercel_installed",
|
|
6033
|
+
label: "Vercel CLI installed",
|
|
6034
|
+
status: installed ? "pass" : "warn",
|
|
6035
|
+
summary: installed ? version.stdout || "vercel CLI found" : version.error ?? "vercel not found on PATH",
|
|
6036
|
+
remediation: installed ? void 0 : "Install Vercel CLI (`npm i -g vercel`) for deploy evidence and env pulls.",
|
|
6037
|
+
details: { stderr: version.stderr || null }
|
|
6038
|
+
}),
|
|
6039
|
+
check({
|
|
6040
|
+
id: "vercel_authenticated",
|
|
6041
|
+
label: "Vercel CLI authenticated",
|
|
6042
|
+
status: !installed ? "warn" : whoami.ok ? "pass" : "warn",
|
|
6043
|
+
summary: !installed ? "Skipped \u2014 Vercel CLI not installed" : whoami.ok ? `Authenticated as ${whoami.stdout}` : whoami.stderr || whoami.error || "Not logged in",
|
|
6044
|
+
remediation: installed && !whoami.ok ? "Run `vercel login` and link the Kynver project if needed." : void 0,
|
|
6045
|
+
details: { account: whoami.ok ? whoami.stdout : null }
|
|
6046
|
+
})
|
|
6047
|
+
]
|
|
6048
|
+
};
|
|
6049
|
+
}
|
|
6050
|
+
function assessHarnessDirs(probes) {
|
|
6051
|
+
const harnessRoot = probes.harnessRoot();
|
|
6052
|
+
const runsDir = path34.join(harnessRoot, "runs");
|
|
6053
|
+
const worktreesDir = path34.join(harnessRoot, "worktrees");
|
|
6054
|
+
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
6055
|
+
const displayRunsDir = redactHomePath(runsDir);
|
|
6056
|
+
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
6057
|
+
const runsExists = probes.pathExists(runsDir);
|
|
6058
|
+
const worktreesExists = probes.pathExists(worktreesDir);
|
|
6059
|
+
return {
|
|
6060
|
+
id: "harness_dirs",
|
|
6061
|
+
label: "Harness / daemon directories",
|
|
6062
|
+
checks: [
|
|
6063
|
+
check({
|
|
6064
|
+
id: "harness_root",
|
|
6065
|
+
label: "Harness root resolved",
|
|
6066
|
+
status: "pass",
|
|
6067
|
+
summary: displayHarnessRoot,
|
|
6068
|
+
details: { harnessRoot: displayHarnessRoot }
|
|
6069
|
+
}),
|
|
6070
|
+
check({
|
|
6071
|
+
id: "harness_runs_dir",
|
|
6072
|
+
label: "Runs directory ready",
|
|
6073
|
+
status: runsExists && probes.pathWritable(runsDir) ? "pass" : runsExists ? "warn" : "warn",
|
|
6074
|
+
summary: runsExists ? probes.pathWritable(runsDir) ? `Writable ${displayRunsDir}` : `Exists but not writable: ${displayRunsDir}` : `Will be created on first run: ${displayRunsDir}`,
|
|
6075
|
+
remediation: runsExists && !probes.pathWritable(runsDir) ? `Fix permissions on ${displayRunsDir} or set KYNVER_HARNESS_ROOT to a writable path.` : void 0,
|
|
6076
|
+
details: { runsDir: displayRunsDir, exists: runsExists, writable: probes.pathWritable(runsDir) }
|
|
6077
|
+
}),
|
|
6078
|
+
check({
|
|
6079
|
+
id: "harness_worktrees_dir",
|
|
6080
|
+
label: "Worktrees directory ready",
|
|
6081
|
+
status: worktreesExists && probes.pathWritable(worktreesDir) ? "pass" : "warn",
|
|
6082
|
+
summary: worktreesExists ? probes.pathWritable(worktreesDir) ? `Writable ${displayWorktreesDir}` : `Exists but not writable: ${displayWorktreesDir}` : `Will be created on first worker: ${displayWorktreesDir}`,
|
|
6083
|
+
remediation: worktreesExists && !probes.pathWritable(worktreesDir) ? `Fix permissions on ${displayWorktreesDir}.` : void 0,
|
|
6084
|
+
details: { worktreesDir: displayWorktreesDir, exists: worktreesExists, writable: probes.pathWritable(worktreesDir) }
|
|
6085
|
+
})
|
|
6086
|
+
]
|
|
6087
|
+
};
|
|
6088
|
+
}
|
|
6089
|
+
function assessCallbackAuth(probes) {
|
|
6090
|
+
const config = probes.loadConfig();
|
|
6091
|
+
const env = probes.envSnapshot();
|
|
6092
|
+
const baseUrl = env.kynverApiUrl ?? config.apiBaseUrl?.trim() ?? env.openclawCronFireBaseUrl;
|
|
6093
|
+
const usingLegacyBase = !env.kynverApiUrl && !config.apiBaseUrl && Boolean(env.openclawCronFireBaseUrl);
|
|
6094
|
+
const legacySecret = env.openclawCronSecret || env.kynverRuntimeSecret;
|
|
6095
|
+
const envScoped = env.kynverRunnerTokenPrefix?.startsWith("krc1.");
|
|
6096
|
+
const creds = probes.readCredentials();
|
|
6097
|
+
const savedScoped = creds.runnerTokenPrefix?.startsWith("krc1.");
|
|
6098
|
+
const checks = [
|
|
6099
|
+
check({
|
|
6100
|
+
id: "callback_base_url",
|
|
6101
|
+
label: "Callback base URL configured",
|
|
6102
|
+
status: baseUrl ? usingLegacyBase ? "warn" : "pass" : "fail",
|
|
6103
|
+
summary: baseUrl ? usingLegacyBase ? `Using legacy OPENCLAW_CRON_FIRE_BASE_URL (${baseUrl})` : baseUrl : "No KYNVER_API_URL, config apiBaseUrl, or legacy OpenClaw base URL",
|
|
6104
|
+
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`.",
|
|
6105
|
+
details: {
|
|
6106
|
+
source: env.kynverApiUrl ? "KYNVER_API_URL" : config.apiBaseUrl ? "config.apiBaseUrl" : env.openclawCronFireBaseUrl ? "OPENCLAW_CRON_FIRE_BASE_URL" : "none",
|
|
6107
|
+
baseUrl: baseUrl ?? null
|
|
6108
|
+
}
|
|
6109
|
+
}),
|
|
6110
|
+
check({
|
|
6111
|
+
id: "callback_auth_mode",
|
|
6112
|
+
label: "Callback auth uses scoped runner token",
|
|
6113
|
+
status: envScoped || savedScoped ? "pass" : legacySecret ? "warn" : "fail",
|
|
6114
|
+
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",
|
|
6115
|
+
remediation: envScoped || savedScoped ? void 0 : "Mint a scoped token: `kynver runner credential --agent-os-id <id>`. Avoid shared OPENCLAW_CRON_SECRET on user runners.",
|
|
6116
|
+
details: {
|
|
6117
|
+
mode: envScoped || savedScoped ? "scoped" : legacySecret ? "legacy_global_secret" : "missing",
|
|
6118
|
+
legacyHeadersWhenNotScoped: ["X-OpenClaw-Cron-Secret", "X-Kynver-Runtime-Secret"]
|
|
6119
|
+
}
|
|
6120
|
+
})
|
|
6121
|
+
];
|
|
6122
|
+
return { id: "callback_auth", label: "Callback auth / config", checks };
|
|
6123
|
+
}
|
|
6124
|
+
function assessOpenclawHotspots(probes) {
|
|
6125
|
+
const env = probes.envSnapshot();
|
|
6126
|
+
const harnessRoot = probes.harnessRoot();
|
|
6127
|
+
const legacyRoot = probes.legacyOpenclawHarnessRoot();
|
|
6128
|
+
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
6129
|
+
const displayLegacyRoot = redactHomePath(legacyRoot);
|
|
6130
|
+
const displayOpusHarnessRoot = env.opusHarnessRoot ? redactHomePath(env.opusHarnessRoot) : null;
|
|
6131
|
+
const legacyHarnessActive = harnessRoot === legacyRoot && probes.pathExists(legacyRoot);
|
|
6132
|
+
const schedulerOpenclaw = !env.kynverSchedulerProvider || env.kynverSchedulerProvider === "openclaw-cron";
|
|
6133
|
+
const checks = [
|
|
6134
|
+
check({
|
|
6135
|
+
id: "hotspot_legacy_harness_root",
|
|
6136
|
+
label: "Legacy ~/.openclaw/harness still active",
|
|
6137
|
+
status: legacyHarnessActive ? "warn" : "pass",
|
|
6138
|
+
summary: legacyHarnessActive ? `Harness root is legacy ${displayLegacyRoot}` : env.opusHarnessRoot ? `OPUS_HARNESS_ROOT override in use (${displayOpusHarnessRoot})` : `Using ${displayHarnessRoot}`,
|
|
6139
|
+
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,
|
|
6140
|
+
details: { harnessRoot: displayHarnessRoot, legacyRoot: displayLegacyRoot, opusHarnessRoot: displayOpusHarnessRoot }
|
|
6141
|
+
}),
|
|
6142
|
+
check({
|
|
6143
|
+
id: "hotspot_openclaw_env_secrets",
|
|
6144
|
+
label: "OpenClaw deployment secrets in runner env",
|
|
6145
|
+
status: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "warn" : "pass",
|
|
6146
|
+
summary: env.openclawCronSecret || env.openclawCronFireBaseUrl ? [
|
|
6147
|
+
env.openclawCronSecret ? "OPENCLAW_CRON_SECRET set" : null,
|
|
6148
|
+
env.openclawCronFireBaseUrl ? "OPENCLAW_CRON_FIRE_BASE_URL set" : null
|
|
6149
|
+
].filter(Boolean).join("; ") : "No OpenClaw cron env overrides on this runner",
|
|
6150
|
+
remediation: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "Move to KYNVER_API_URL + scoped runner tokens; unset OpenClaw cron env on user-hosted runners." : void 0
|
|
6151
|
+
}),
|
|
6152
|
+
check({
|
|
6153
|
+
id: "hotspot_openclaw_scheduler",
|
|
6154
|
+
label: "openclaw-cron scheduler dependency (deployment)",
|
|
6155
|
+
status: schedulerOpenclaw ? "warn" : "pass",
|
|
6156
|
+
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}`,
|
|
6157
|
+
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,
|
|
6158
|
+
details: { schedulerProvider: env.kynverSchedulerProvider ?? null }
|
|
6159
|
+
}),
|
|
6160
|
+
check({
|
|
6161
|
+
id: "hotspot_lease_source_names",
|
|
6162
|
+
label: "Harness lease/completion source names",
|
|
6163
|
+
status: "pass",
|
|
6164
|
+
summary: "Runtime uses kynver-harness:* lease owners and completion source kynver-harness (OpenClaw names retired in runtime)",
|
|
6165
|
+
details: {
|
|
6166
|
+
leaseOwnerPattern: "kynver-harness:<runId>",
|
|
6167
|
+
completionSource: "kynver-harness",
|
|
6168
|
+
note: "OpenClaw adapter remains optional in packages/kynver-openclaw-agent-os only"
|
|
6169
|
+
}
|
|
6170
|
+
})
|
|
6171
|
+
];
|
|
6172
|
+
return { id: "openclaw_hotspots", label: "OpenClaw dependency hotspots", checks };
|
|
6173
|
+
}
|
|
6174
|
+
function assessRuntimeTakeoverReadiness(probes = defaultRuntimeTakeoverProbes) {
|
|
6175
|
+
const sections = [
|
|
6176
|
+
assessCliPackage(probes),
|
|
6177
|
+
assessUserConfig(probes),
|
|
6178
|
+
assessRunnerToken(probes),
|
|
6179
|
+
assessVercelCli(probes),
|
|
6180
|
+
assessHarnessDirs(probes),
|
|
6181
|
+
assessCallbackAuth(probes),
|
|
6182
|
+
assessOpenclawHotspots(probes)
|
|
6183
|
+
];
|
|
6184
|
+
const counts = summarizeCounts(sections);
|
|
6185
|
+
const ready = counts.fail === 0;
|
|
6186
|
+
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.`;
|
|
6187
|
+
return {
|
|
6188
|
+
command: "doctor runtime-takeover",
|
|
6189
|
+
ready,
|
|
6190
|
+
summary,
|
|
6191
|
+
counts,
|
|
6192
|
+
sections
|
|
6193
|
+
};
|
|
6194
|
+
}
|
|
6195
|
+
|
|
6196
|
+
// src/doctor/runtime-takeover-cli.ts
|
|
6197
|
+
function runRuntimeTakeoverDoctorCli() {
|
|
6198
|
+
const report = assessRuntimeTakeoverReadiness();
|
|
6199
|
+
console.log(JSON.stringify(report, null, 2));
|
|
6200
|
+
if (!report.ready) {
|
|
6201
|
+
process.exitCode = 1;
|
|
6202
|
+
}
|
|
6203
|
+
}
|
|
6204
|
+
|
|
5723
6205
|
// src/cli.ts
|
|
5724
6206
|
function isHelpFlag(arg) {
|
|
5725
6207
|
return arg === "help" || arg === "--help" || arg === "-h";
|
|
@@ -5762,7 +6244,8 @@ function usage(code = 0) {
|
|
|
5762
6244
|
" kynver monitor list",
|
|
5763
6245
|
" kynver monitor tick --run RUN_ID [--name worker] [--agent-os-id AOS_ID] [--auto-complete] [--renew-leases]",
|
|
5764
6246
|
" 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]"
|
|
6247
|
+
" kynver monitor run-loop --run RUN_ID --monitor-id ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS] [--auto-complete] [--renew-leases]",
|
|
6248
|
+
" kynver doctor runtime-takeover"
|
|
5766
6249
|
].join("\n")
|
|
5767
6250
|
);
|
|
5768
6251
|
process.exit(code);
|
|
@@ -5773,7 +6256,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
5773
6256
|
const scope = argv.shift();
|
|
5774
6257
|
let action;
|
|
5775
6258
|
let rest;
|
|
5776
|
-
if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "monitor") {
|
|
6259
|
+
if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "monitor" || scope === "doctor") {
|
|
5777
6260
|
action = argv.shift();
|
|
5778
6261
|
rest = argv;
|
|
5779
6262
|
} else {
|
|
@@ -5798,6 +6281,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
5798
6281
|
unknownCommand("plan", `outbox ${outboxAction ?? ""}`.trim());
|
|
5799
6282
|
}
|
|
5800
6283
|
if (scope === "cleanup") return runCleanupCli(args);
|
|
6284
|
+
if (scope === "doctor" && action === "runtime-takeover") return runRuntimeTakeoverDoctorCli();
|
|
5801
6285
|
if (scope === "run" && action === "create") return createRun(args);
|
|
5802
6286
|
if (scope === "run" && action === "list") return listRuns();
|
|
5803
6287
|
if (scope === "run" && action === "status") return runStatus(args);
|