@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/index.js
CHANGED
|
@@ -35,16 +35,43 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// src/dispatch.ts
|
|
38
|
-
import
|
|
38
|
+
import path17 from "node:path";
|
|
39
39
|
|
|
40
40
|
// src/config.ts
|
|
41
41
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
42
|
+
import { homedir as homedir2 } from "node:os";
|
|
43
|
+
import path3 from "node:path";
|
|
44
|
+
|
|
45
|
+
// src/path-values.ts
|
|
42
46
|
import { homedir } from "node:os";
|
|
43
|
-
import
|
|
47
|
+
import path from "node:path";
|
|
48
|
+
function expandHomePath(value) {
|
|
49
|
+
if (value === "~") return homedir();
|
|
50
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
51
|
+
return path.join(homedir(), value.slice(2));
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
function resolveUserPath(value) {
|
|
56
|
+
return path.resolve(expandHomePath(value));
|
|
57
|
+
}
|
|
58
|
+
function redactHomePath(value) {
|
|
59
|
+
const expanded = expandHomePath(value);
|
|
60
|
+
const resolved = path.resolve(expanded);
|
|
61
|
+
const home = path.resolve(homedir());
|
|
62
|
+
if (resolved === home) return "~";
|
|
63
|
+
if (resolved.startsWith(`${home}${path.sep}`)) {
|
|
64
|
+
return `~/${path.relative(home, resolved).split(path.sep).join("/")}`;
|
|
65
|
+
}
|
|
66
|
+
return resolved.replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^\/Users\/[^/]+(?=\/|$)/, "~");
|
|
67
|
+
}
|
|
68
|
+
function displayUserPath(value) {
|
|
69
|
+
return redactHomePath(value);
|
|
70
|
+
}
|
|
44
71
|
|
|
45
72
|
// src/util.ts
|
|
46
73
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
47
|
-
import
|
|
74
|
+
import path2 from "node:path";
|
|
48
75
|
function fail(message) {
|
|
49
76
|
console.error(message);
|
|
50
77
|
process.exit(1);
|
|
@@ -73,7 +100,7 @@ function readJson(file, fallback) {
|
|
|
73
100
|
}
|
|
74
101
|
}
|
|
75
102
|
function writeJson(file, value) {
|
|
76
|
-
mkdirSync(
|
|
103
|
+
mkdirSync(path2.dirname(file), { recursive: true });
|
|
77
104
|
writeFileSync(file, `${JSON.stringify(value, null, 2)}
|
|
78
105
|
`);
|
|
79
106
|
}
|
|
@@ -109,7 +136,7 @@ function tailFile(file, lines) {
|
|
|
109
136
|
return data.split("\n").slice(-lines).join("\n");
|
|
110
137
|
}
|
|
111
138
|
function readMaybeFile(file) {
|
|
112
|
-
return file ? readFileSync2(
|
|
139
|
+
return file ? readFileSync2(path2.resolve(file), "utf8") : "";
|
|
113
140
|
}
|
|
114
141
|
function listRunIds(runsDir) {
|
|
115
142
|
if (!existsSync2(runsDir)) return [];
|
|
@@ -152,9 +179,9 @@ function secsAgo(ms) {
|
|
|
152
179
|
}
|
|
153
180
|
|
|
154
181
|
// src/config.ts
|
|
155
|
-
var CONFIG_DIR =
|
|
156
|
-
var CONFIG_FILE =
|
|
157
|
-
var CREDENTIALS_FILE =
|
|
182
|
+
var CONFIG_DIR = path3.join(homedir2(), ".kynver");
|
|
183
|
+
var CONFIG_FILE = path3.join(CONFIG_DIR, "config.json");
|
|
184
|
+
var CREDENTIALS_FILE = path3.join(CONFIG_DIR, "credentials");
|
|
158
185
|
function loadUserConfig() {
|
|
159
186
|
if (!existsSync3(CONFIG_FILE)) return {};
|
|
160
187
|
try {
|
|
@@ -165,9 +192,33 @@ function loadUserConfig() {
|
|
|
165
192
|
}
|
|
166
193
|
function saveUserConfig(config) {
|
|
167
194
|
mkdirSync2(CONFIG_DIR, { recursive: true });
|
|
168
|
-
writeFileSync2(CONFIG_FILE, `${JSON.stringify(config, null, 2)}
|
|
195
|
+
writeFileSync2(CONFIG_FILE, `${JSON.stringify(normalizeConfigPaths(config), null, 2)}
|
|
169
196
|
`, { mode: 384 });
|
|
170
197
|
}
|
|
198
|
+
function normalizeConfigPaths(config) {
|
|
199
|
+
return {
|
|
200
|
+
...config,
|
|
201
|
+
...config.harnessRoot?.trim() ? { harnessRoot: redactHomePath(config.harnessRoot.trim()) } : {},
|
|
202
|
+
...config.defaultRepo?.trim() ? { defaultRepo: redactHomePath(config.defaultRepo.trim()) } : {}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function presentUserConfig(config) {
|
|
206
|
+
return normalizeConfigPaths(config);
|
|
207
|
+
}
|
|
208
|
+
function inferSetupFields(existing, args) {
|
|
209
|
+
const creds = loadCredentialsFile();
|
|
210
|
+
const apiBaseUrl = (typeof args.apiBaseUrl === "string" ? args.apiBaseUrl : void 0) || existing.apiBaseUrl?.trim() || process.env.KYNVER_API_URL?.trim() || process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim();
|
|
211
|
+
const agentOsId = (typeof args.agentOsId === "string" ? args.agentOsId : void 0) || existing.agentOsId?.trim() || process.env.KYNVER_AGENT_OS_ID?.trim() || (creds.runnerToken?.trim().startsWith("krc1.") ? creds.runnerTokenAgentOsId?.trim() : void 0);
|
|
212
|
+
const defaultRepo = (typeof args.repo === "string" ? args.repo : void 0) || existing.defaultRepo?.trim() || process.env.KYNVER_DEFAULT_REPO?.trim() || process.env.KYNVER_HARNESS_REPO?.trim();
|
|
213
|
+
const harnessRoot = (typeof args.harnessRoot === "string" ? args.harnessRoot : void 0) || existing.harnessRoot?.trim() || process.env.KYNVER_HARNESS_ROOT?.trim() || process.env.OPUS_HARNESS_ROOT?.trim();
|
|
214
|
+
return {
|
|
215
|
+
...apiBaseUrl ? { apiBaseUrl: trimTrailingSlash(apiBaseUrl) } : {},
|
|
216
|
+
...agentOsId ? { agentOsId } : {},
|
|
217
|
+
...defaultRepo ? { defaultRepo } : {},
|
|
218
|
+
...harnessRoot ? { harnessRoot } : {},
|
|
219
|
+
...typeof args.agentOsSlug === "string" ? { agentOsSlug: args.agentOsSlug } : existing.agentOsSlug ? { agentOsSlug: existing.agentOsSlug } : {}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
171
222
|
function loadCredentialsFile() {
|
|
172
223
|
if (!existsSync3(CREDENTIALS_FILE)) return {};
|
|
173
224
|
try {
|
|
@@ -320,7 +371,7 @@ async function mintRunnerCredential(args) {
|
|
|
320
371
|
{
|
|
321
372
|
ok: true,
|
|
322
373
|
agentOsId,
|
|
323
|
-
credentialsPath: CREDENTIALS_FILE,
|
|
374
|
+
credentialsPath: displayUserPath(CREDENTIALS_FILE),
|
|
324
375
|
tokenPrefix: `${token.slice(0, 12)}\u2026`,
|
|
325
376
|
note: "Scoped runner token saved; callbacks use X-Kynver-Runner-Token."
|
|
326
377
|
},
|
|
@@ -355,16 +406,12 @@ function parseArgs(argv) {
|
|
|
355
406
|
async function runSetup(args) {
|
|
356
407
|
const existing = loadUserConfig();
|
|
357
408
|
const maxWorkersRaw = typeof args.maxWorkers === "string" ? args.maxWorkers : typeof args.maxConcurrentWorkers === "string" ? args.maxConcurrentWorkers : void 0;
|
|
358
|
-
const config = {
|
|
409
|
+
const config = normalizeConfigPaths({
|
|
359
410
|
...existing,
|
|
360
|
-
...
|
|
361
|
-
...typeof args.agentOsSlug === "string" ? { agentOsSlug: args.agentOsSlug } : {},
|
|
362
|
-
...typeof args.agentOsId === "string" ? { agentOsId: args.agentOsId } : {},
|
|
363
|
-
...typeof args.repo === "string" ? { defaultRepo: args.repo } : {},
|
|
364
|
-
...typeof args.harnessRoot === "string" ? { harnessRoot: args.harnessRoot } : {},
|
|
411
|
+
...inferSetupFields(existing, args),
|
|
365
412
|
...maxWorkersRaw ? { maxConcurrentWorkers: Math.max(1, Math.floor(Number(maxWorkersRaw))) } : {},
|
|
366
413
|
workerProvider: typeof args.provider === "string" ? args.provider : existing.workerProvider || "claude"
|
|
367
|
-
};
|
|
414
|
+
});
|
|
368
415
|
saveUserConfig(config);
|
|
369
416
|
let runnerCredentialNote;
|
|
370
417
|
const apiKey = loadApiKey();
|
|
@@ -385,8 +432,8 @@ async function runSetup(args) {
|
|
|
385
432
|
JSON.stringify(
|
|
386
433
|
{
|
|
387
434
|
ok: true,
|
|
388
|
-
configPath: CONFIG_FILE,
|
|
389
|
-
config,
|
|
435
|
+
configPath: displayUserPath(CONFIG_FILE),
|
|
436
|
+
config: presentUserConfig(config),
|
|
390
437
|
note: runnerCredentialNote ?? "Set worker limit once with --max-workers N (or omit to auto-size from RAM). Run `kynver login` + `kynver runner credential` for scoped callbacks."
|
|
391
438
|
},
|
|
392
439
|
null,
|
|
@@ -398,7 +445,7 @@ async function runLogin(args) {
|
|
|
398
445
|
const apiKey = typeof args.apiKey === "string" ? args.apiKey : process.env.KYNVER_API_KEY;
|
|
399
446
|
if (!apiKey) failConfig("kynver login requires --api-key or KYNVER_API_KEY");
|
|
400
447
|
saveApiKey(apiKey);
|
|
401
|
-
console.log(JSON.stringify({ ok: true, credentialsPath: CREDENTIALS_FILE }, null, 2));
|
|
448
|
+
console.log(JSON.stringify({ ok: true, credentialsPath: displayUserPath(CREDENTIALS_FILE) }, null, 2));
|
|
402
449
|
}
|
|
403
450
|
|
|
404
451
|
// src/callback-headers.ts
|
|
@@ -461,12 +508,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
|
461
508
|
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
462
509
|
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
463
510
|
function observeRunnerDiskGate(input = {}) {
|
|
464
|
-
const
|
|
511
|
+
const path35 = input.diskPath?.trim() || "/";
|
|
465
512
|
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
466
513
|
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
467
514
|
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
468
515
|
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
469
|
-
const stats = statfsSync(
|
|
516
|
+
const stats = statfsSync(path35);
|
|
470
517
|
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
471
518
|
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
472
519
|
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
@@ -486,7 +533,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
486
533
|
}
|
|
487
534
|
return {
|
|
488
535
|
ok,
|
|
489
|
-
path:
|
|
536
|
+
path: path35,
|
|
490
537
|
freeBytes,
|
|
491
538
|
totalBytes,
|
|
492
539
|
usedPercent,
|
|
@@ -501,21 +548,23 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
501
548
|
// src/resource-gate.ts
|
|
502
549
|
import { readFileSync as readFileSync6 } from "node:fs";
|
|
503
550
|
import os from "node:os";
|
|
504
|
-
import
|
|
551
|
+
import path6 from "node:path";
|
|
505
552
|
|
|
506
553
|
// src/run-store.ts
|
|
507
554
|
import { existsSync as existsSync5, readdirSync as readdirSync2 } from "node:fs";
|
|
508
|
-
import
|
|
555
|
+
import path5 from "node:path";
|
|
509
556
|
|
|
510
557
|
// src/paths.ts
|
|
511
558
|
import { existsSync as existsSync4 } from "node:fs";
|
|
512
|
-
import { homedir as
|
|
513
|
-
import
|
|
514
|
-
var LEGACY_ROOT =
|
|
559
|
+
import { homedir as homedir3 } from "node:os";
|
|
560
|
+
import path4 from "node:path";
|
|
561
|
+
var LEGACY_ROOT = path4.join(homedir3(), ".openclaw", "harness");
|
|
515
562
|
function resolveHarnessRoot() {
|
|
516
563
|
const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
|
|
517
|
-
if (env) return
|
|
518
|
-
const
|
|
564
|
+
if (env) return resolveUserPath(env);
|
|
565
|
+
const configured = loadUserConfig().harnessRoot?.trim();
|
|
566
|
+
if (configured) return resolveUserPath(configured);
|
|
567
|
+
const kynverRoot = path4.join(homedir3(), ".kynver", "harness");
|
|
519
568
|
if (existsSync4(kynverRoot)) return kynverRoot;
|
|
520
569
|
if (existsSync4(LEGACY_ROOT)) return LEGACY_ROOT;
|
|
521
570
|
return kynverRoot;
|
|
@@ -524,12 +573,12 @@ function getHarnessPaths() {
|
|
|
524
573
|
const harnessRoot = resolveHarnessRoot();
|
|
525
574
|
return {
|
|
526
575
|
harnessRoot,
|
|
527
|
-
runsDir:
|
|
528
|
-
worktreesDir:
|
|
576
|
+
runsDir: path4.join(harnessRoot, "runs"),
|
|
577
|
+
worktreesDir: path4.join(harnessRoot, "worktrees")
|
|
529
578
|
};
|
|
530
579
|
}
|
|
531
580
|
function runDir(runsDir, id) {
|
|
532
|
-
return
|
|
581
|
+
return path4.join(runsDir, safeSlug(id));
|
|
533
582
|
}
|
|
534
583
|
|
|
535
584
|
// src/run-store.ts
|
|
@@ -538,7 +587,7 @@ function getPaths() {
|
|
|
538
587
|
}
|
|
539
588
|
function loadRun(id) {
|
|
540
589
|
const { runsDir } = getPaths();
|
|
541
|
-
return readJson(
|
|
590
|
+
return readJson(path5.join(runDir(runsDir, safeSlug(id)), "run.json"));
|
|
542
591
|
}
|
|
543
592
|
function listRunRecords() {
|
|
544
593
|
const { runsDir } = getPaths();
|
|
@@ -547,7 +596,7 @@ function listRunRecords() {
|
|
|
547
596
|
for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
|
|
548
597
|
if (!entry.isDirectory()) continue;
|
|
549
598
|
const run = readJson(
|
|
550
|
-
|
|
599
|
+
path5.join(runsDir, entry.name, "run.json"),
|
|
551
600
|
void 0
|
|
552
601
|
);
|
|
553
602
|
if (run?.id) runs.push(run);
|
|
@@ -557,16 +606,16 @@ function listRunRecords() {
|
|
|
557
606
|
function loadWorker(runId, name) {
|
|
558
607
|
const { runsDir } = getPaths();
|
|
559
608
|
return readJson(
|
|
560
|
-
|
|
609
|
+
path5.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
|
|
561
610
|
);
|
|
562
611
|
}
|
|
563
612
|
function saveRun(run) {
|
|
564
613
|
const { runsDir } = getPaths();
|
|
565
|
-
writeJson(
|
|
614
|
+
writeJson(path5.join(runDir(runsDir, run.id), "run.json"), run);
|
|
566
615
|
}
|
|
567
616
|
function saveWorker(runId, worker) {
|
|
568
617
|
const { runsDir } = getPaths();
|
|
569
|
-
writeJson(
|
|
618
|
+
writeJson(path5.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
|
|
570
619
|
}
|
|
571
620
|
function runDirectory(id) {
|
|
572
621
|
const { runsDir } = getPaths();
|
|
@@ -1322,7 +1371,7 @@ function countActiveWorkersForRun(run) {
|
|
|
1322
1371
|
let active = 0;
|
|
1323
1372
|
for (const name of Object.keys(run.workers || {})) {
|
|
1324
1373
|
const worker = readJson(
|
|
1325
|
-
|
|
1374
|
+
path6.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1326
1375
|
void 0
|
|
1327
1376
|
);
|
|
1328
1377
|
if (!worker || !isActiveHarnessWorker(worker)) continue;
|
|
@@ -1530,6 +1579,7 @@ var claudeProvider = {
|
|
|
1530
1579
|
const model = preflight.model;
|
|
1531
1580
|
const stdoutFd = openSync(opts.stdoutPath, "a");
|
|
1532
1581
|
const stderrFd = openSync(opts.stderrPath, "a");
|
|
1582
|
+
const stdio = ["ignore", stdoutFd, stderrFd];
|
|
1533
1583
|
const child = spawn(
|
|
1534
1584
|
"claude",
|
|
1535
1585
|
[
|
|
@@ -1547,7 +1597,7 @@ var claudeProvider = {
|
|
|
1547
1597
|
hiddenSpawnOptions({
|
|
1548
1598
|
cwd: opts.worktreePath,
|
|
1549
1599
|
detached: true,
|
|
1550
|
-
stdio
|
|
1600
|
+
stdio,
|
|
1551
1601
|
env: scrubClaudeEnv(process.env)
|
|
1552
1602
|
})
|
|
1553
1603
|
);
|
|
@@ -1706,10 +1756,10 @@ function readHarnessRetryLimits() {
|
|
|
1706
1756
|
}
|
|
1707
1757
|
|
|
1708
1758
|
// src/lease-renewal.ts
|
|
1709
|
-
import
|
|
1759
|
+
import path7 from "node:path";
|
|
1710
1760
|
function workerRecord(runId, name) {
|
|
1711
1761
|
return readJson(
|
|
1712
|
-
|
|
1762
|
+
path7.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
1713
1763
|
void 0
|
|
1714
1764
|
);
|
|
1715
1765
|
}
|
|
@@ -1776,7 +1826,7 @@ function hasLiveWorkerForTask(runId, taskId) {
|
|
|
1776
1826
|
|
|
1777
1827
|
// src/supervisor.ts
|
|
1778
1828
|
import { existsSync as existsSync11, mkdirSync as mkdirSync3 } from "node:fs";
|
|
1779
|
-
import
|
|
1829
|
+
import path13 from "node:path";
|
|
1780
1830
|
|
|
1781
1831
|
// src/prompt.ts
|
|
1782
1832
|
function buildPrompt(input) {
|
|
@@ -1795,6 +1845,16 @@ function buildPrompt(input) {
|
|
|
1795
1845
|
"- After implementation: wait for report_reviewer then deep_reviewer confirmation (via MCP/session agents) before follow-up rows close.",
|
|
1796
1846
|
input.planId ? `Active planId: ${input.planId}${input.taskId ? ` \xB7 taskId: ${input.taskId}` : ""}` : "No planId on this worker \u2014 still emit progress when you touch plan-scoped work."
|
|
1797
1847
|
];
|
|
1848
|
+
const mergeGateLines = compact ? [
|
|
1849
|
+
"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)."
|
|
1850
|
+
] : [
|
|
1851
|
+
"GitHub Actions merge-gate cost control (Kynver/Hermes PRs):",
|
|
1852
|
+
"- Prefer local cached package verification (`node scripts/verify-pr-local.mjs --emit-json`) and Vercel preview evidence before GitHub Actions.",
|
|
1853
|
+
"- 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.",
|
|
1854
|
+
"- Record evidence: POST `/api/agent-os/by-id/<agentOsId>/pr-merge-gate/refresh` with prUrl + local/vercel payloads.",
|
|
1855
|
+
"- Request the final Actions run only when local + Vercel are green: POST `.../pr-merge-gate/request-run` (applies `merge-gate` label).",
|
|
1856
|
+
"- Empty failed Actions jobs (no runner/steps/logs) are infra/quota \u2014 do not enter repair loops; escalate to operator."
|
|
1857
|
+
];
|
|
1798
1858
|
const planArtifactLines = compact ? [
|
|
1799
1859
|
"Plan artifacts: when authoring/revising docs/superpowers/plans/, open a GitHub PR early and iterate from that PR branch; do not leave the canonical plan only in the harness worktree."
|
|
1800
1860
|
] : [
|
|
@@ -1816,10 +1876,14 @@ function buildPrompt(input) {
|
|
|
1816
1876
|
"Completion handoff (required): before you stop, ensure the harness records a final result \u2014 summarize outcome in your last message and append a heartbeat line with phase `complete`. If you leave uncommitted changes or committed work without a PR, the orchestrator blocks completion until a GitHub PR exists (or you discard/commit cleanly). Exiting with only dirty files and no PR routes to salvage review, not production review.",
|
|
1817
1877
|
"PR-ready handoff: for substantial implementation work, commit, push, and open a GitHub PR (draft OK) on your branch before finishing \u2014 or rely on the harness to run `gh pr create` at completion when `gh` is authenticated.",
|
|
1818
1878
|
"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.",
|
|
1879
|
+
"npm publish boundary: do not run `npm publish`, do not republish `@kynver-app/*` packages, and do not block on an operator to publish. When you need newer runtime code than npm, use this repo checkout (`npm run kynver:build`, `npm run kynver`) and record evidence: packages/kynver-runtime/package.json version + git ref in your completion report.",
|
|
1819
1880
|
"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.",
|
|
1881
|
+
"Landing-wrapper cleanup on a git ref: use `node scripts/agent-os-land-pr-cleanup-verify.mjs --ref origin/main` (JSON, exit 0). Do not use `git show <ref>:scripts/agent-os-land-pr.mjs | rg \u2026` \u2014 ripgrep exit 1 when markers are absent is reported as a failed command in Telegram/status tooling.",
|
|
1820
1882
|
"",
|
|
1821
1883
|
...progressLines,
|
|
1822
1884
|
"",
|
|
1885
|
+
...mergeGateLines,
|
|
1886
|
+
"",
|
|
1823
1887
|
...planArtifactLines,
|
|
1824
1888
|
"",
|
|
1825
1889
|
...input.personaMarkdown?.trim() ? [input.personaMarkdown.trim(), ""] : [],
|
|
@@ -1832,11 +1896,11 @@ function buildPrompt(input) {
|
|
|
1832
1896
|
// src/providers/cursor.ts
|
|
1833
1897
|
import { closeSync as closeSync2, existsSync as existsSync9, openSync as openSync2 } from "node:fs";
|
|
1834
1898
|
import { spawn as spawn2 } from "node:child_process";
|
|
1835
|
-
import
|
|
1899
|
+
import path9 from "node:path";
|
|
1836
1900
|
|
|
1837
1901
|
// src/providers/cursor-windows.ts
|
|
1838
1902
|
import { existsSync as existsSync8, readdirSync as readdirSync3 } from "node:fs";
|
|
1839
|
-
import
|
|
1903
|
+
import path8 from "node:path";
|
|
1840
1904
|
var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
|
|
1841
1905
|
function parseCursorVersionSortKey(versionName) {
|
|
1842
1906
|
const datePart = versionName.split("-")[0];
|
|
@@ -1847,7 +1911,7 @@ function parseCursorVersionSortKey(versionName) {
|
|
|
1847
1911
|
return Number(`${year}${month.padStart(2, "0")}${day.padStart(2, "0")}`);
|
|
1848
1912
|
}
|
|
1849
1913
|
function pickLatestCursorVersionDir(agentRoot) {
|
|
1850
|
-
const versionsRoot =
|
|
1914
|
+
const versionsRoot = path8.join(agentRoot, "versions");
|
|
1851
1915
|
if (!existsSync8(versionsRoot)) return null;
|
|
1852
1916
|
let bestDir = null;
|
|
1853
1917
|
let bestKey = -1;
|
|
@@ -1856,21 +1920,21 @@ function pickLatestCursorVersionDir(agentRoot) {
|
|
|
1856
1920
|
const key = parseCursorVersionSortKey(entry.name);
|
|
1857
1921
|
if (key == null || key <= bestKey) continue;
|
|
1858
1922
|
bestKey = key;
|
|
1859
|
-
bestDir =
|
|
1923
|
+
bestDir = path8.join(versionsRoot, entry.name);
|
|
1860
1924
|
}
|
|
1861
1925
|
return bestDir;
|
|
1862
1926
|
}
|
|
1863
1927
|
function resolveWindowsCursorBundled(agentRoot) {
|
|
1864
|
-
const root = agentRoot?.trim() ||
|
|
1865
|
-
const directNode =
|
|
1866
|
-
const directIndex =
|
|
1928
|
+
const root = agentRoot?.trim() || path8.join(process.env.LOCALAPPDATA || "", "cursor-agent");
|
|
1929
|
+
const directNode = path8.join(root, "node.exe");
|
|
1930
|
+
const directIndex = path8.join(root, "index.js");
|
|
1867
1931
|
if (existsSync8(directNode) && existsSync8(directIndex)) {
|
|
1868
1932
|
return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
|
|
1869
1933
|
}
|
|
1870
1934
|
const versionDir = pickLatestCursorVersionDir(root);
|
|
1871
1935
|
if (!versionDir) return null;
|
|
1872
|
-
const nodeExe =
|
|
1873
|
-
const indexJs =
|
|
1936
|
+
const nodeExe = path8.join(versionDir, "node.exe");
|
|
1937
|
+
const indexJs = path8.join(versionDir, "index.js");
|
|
1874
1938
|
if (!existsSync8(nodeExe) || !existsSync8(indexJs)) return null;
|
|
1875
1939
|
return { nodeExe, indexJs, versionDir };
|
|
1876
1940
|
}
|
|
@@ -1889,13 +1953,13 @@ function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
|
|
|
1889
1953
|
function resolveCursorSpawn(agentBin) {
|
|
1890
1954
|
if (process.platform === "win32") {
|
|
1891
1955
|
const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
|
|
1892
|
-
const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync9(
|
|
1956
|
+
const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync9(path9.join(path9.dirname(agentBin), "index.js"));
|
|
1893
1957
|
const isDefaultShim = agentBin === "agent";
|
|
1894
1958
|
if (isCursorWrapper || isBundledNode || isDefaultShim) {
|
|
1895
|
-
const bundled = isCursorWrapper ? resolveWindowsCursorBundled(
|
|
1959
|
+
const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path9.dirname(agentBin)) : isBundledNode ? {
|
|
1896
1960
|
nodeExe: agentBin,
|
|
1897
|
-
indexJs:
|
|
1898
|
-
versionDir:
|
|
1961
|
+
indexJs: path9.join(path9.dirname(agentBin), "index.js"),
|
|
1962
|
+
versionDir: path9.dirname(agentBin)
|
|
1899
1963
|
} : resolveWindowsCursorBundled();
|
|
1900
1964
|
if (bundled) {
|
|
1901
1965
|
return bundledSpawnTarget(bundled.nodeExe, bundled.indexJs, bundled.versionDir);
|
|
@@ -1915,7 +1979,7 @@ function resolveAgentBin() {
|
|
|
1915
1979
|
process.env.KYNVER_CURSOR_AGENT_ROOT?.trim() || void 0
|
|
1916
1980
|
);
|
|
1917
1981
|
if (bundled) return bundled.nodeExe;
|
|
1918
|
-
const localAgent =
|
|
1982
|
+
const localAgent = path9.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
|
|
1919
1983
|
if (existsSync9(localAgent)) return localAgent;
|
|
1920
1984
|
}
|
|
1921
1985
|
return "agent";
|
|
@@ -1925,7 +1989,7 @@ function cursorWorkerEnv(agentBin, spawnTarget) {
|
|
|
1925
1989
|
...process.env,
|
|
1926
1990
|
CI: "1",
|
|
1927
1991
|
NO_COLOR: "1",
|
|
1928
|
-
...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS:
|
|
1992
|
+
...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path9.basename(agentBin) || "agent.cmd" } : {}
|
|
1929
1993
|
};
|
|
1930
1994
|
}
|
|
1931
1995
|
var cursorProvider = {
|
|
@@ -1942,6 +2006,7 @@ var cursorProvider = {
|
|
|
1942
2006
|
const model = preflight.model;
|
|
1943
2007
|
const stdoutFd = openSync2(opts.stdoutPath, "a");
|
|
1944
2008
|
const stderrFd = openSync2(opts.stderrPath, "a");
|
|
2009
|
+
const stdio = ["ignore", stdoutFd, stderrFd];
|
|
1945
2010
|
const agentBin = resolveAgentBin();
|
|
1946
2011
|
const spawnTarget = resolveCursorSpawn(agentBin);
|
|
1947
2012
|
const child = spawn2(
|
|
@@ -1964,7 +2029,7 @@ var cursorProvider = {
|
|
|
1964
2029
|
cwd: opts.worktreePath,
|
|
1965
2030
|
detached: spawnTarget.detached,
|
|
1966
2031
|
shell: spawnTarget.shell,
|
|
1967
|
-
stdio
|
|
2032
|
+
stdio,
|
|
1968
2033
|
env: cursorWorkerEnv(agentBin, spawnTarget)
|
|
1969
2034
|
})
|
|
1970
2035
|
);
|
|
@@ -1999,7 +2064,7 @@ function resolveWorkerProvider(name) {
|
|
|
1999
2064
|
// src/auto-complete.ts
|
|
2000
2065
|
import { spawn as spawn3 } from "node:child_process";
|
|
2001
2066
|
import { existsSync as existsSync10, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
|
|
2002
|
-
import
|
|
2067
|
+
import path12 from "node:path";
|
|
2003
2068
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2004
2069
|
|
|
2005
2070
|
// src/completion-ack.ts
|
|
@@ -2016,7 +2081,7 @@ function persistCompletionAck(worker, runId, fields) {
|
|
|
2016
2081
|
}
|
|
2017
2082
|
|
|
2018
2083
|
// src/worker-ops.ts
|
|
2019
|
-
import
|
|
2084
|
+
import path11 from "node:path";
|
|
2020
2085
|
|
|
2021
2086
|
// src/pr-handoff/pr-handoff-assess.ts
|
|
2022
2087
|
var REVIEW_LANE_RULE = /^(lane:)?(review|deep_review|planning|landing)(:|$)/i;
|
|
@@ -2353,7 +2418,7 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
|
|
|
2353
2418
|
}
|
|
2354
2419
|
|
|
2355
2420
|
// src/worker-lifecycle.ts
|
|
2356
|
-
import
|
|
2421
|
+
import path10 from "node:path";
|
|
2357
2422
|
var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
|
|
2358
2423
|
"awaiting_review",
|
|
2359
2424
|
"blocked",
|
|
@@ -2409,7 +2474,7 @@ function syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick) {
|
|
|
2409
2474
|
const synced = [];
|
|
2410
2475
|
for (const name of Object.keys(run.workers || {})) {
|
|
2411
2476
|
const worker = readJson(
|
|
2412
|
-
|
|
2477
|
+
path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2413
2478
|
void 0
|
|
2414
2479
|
);
|
|
2415
2480
|
if (!worker?.taskId || isCompletionAcknowledged(worker)) continue;
|
|
@@ -2646,7 +2711,7 @@ function workerStatus(args) {
|
|
|
2646
2711
|
const worker = loadWorker(String(args.run), String(args.name));
|
|
2647
2712
|
const run = loadRun(worker.runId);
|
|
2648
2713
|
const status = computeWorkerStatus(worker, workerStatusOptions(run));
|
|
2649
|
-
writeJson(
|
|
2714
|
+
writeJson(path11.join(worker.workerDir, "last-status.json"), status);
|
|
2650
2715
|
console.log(JSON.stringify(status, null, 2));
|
|
2651
2716
|
}
|
|
2652
2717
|
function buildRunBoard(runId) {
|
|
@@ -2654,7 +2719,7 @@ function buildRunBoard(runId) {
|
|
|
2654
2719
|
const names = Object.keys(run.workers || {});
|
|
2655
2720
|
const workers = names.map((name) => {
|
|
2656
2721
|
const worker = readJson(
|
|
2657
|
-
|
|
2722
|
+
path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2658
2723
|
void 0
|
|
2659
2724
|
);
|
|
2660
2725
|
if (!worker) {
|
|
@@ -2682,16 +2747,17 @@ function buildRunBoard(runId) {
|
|
|
2682
2747
|
const completionRouteStatus = asString(completionResponse?.status);
|
|
2683
2748
|
const completionWarnings = Array.isArray(completionResponse?.warnings) ? completionResponse.warnings.filter((w) => typeof w === "string" && w.trim().length > 0) : [];
|
|
2684
2749
|
const prUrl = asString(completionTask?.prUrl) ?? asString(completionResponse?.prUrl);
|
|
2750
|
+
const completionReportedAt = asString(worker.completionReportedAt);
|
|
2685
2751
|
const lifecycleStage = deriveLifecycleStage({
|
|
2686
2752
|
finished: isFinishedWorkerStatus(status),
|
|
2687
2753
|
completionBlocker,
|
|
2688
2754
|
completionOutcome,
|
|
2689
|
-
completionReportedAt
|
|
2755
|
+
completionReportedAt
|
|
2690
2756
|
});
|
|
2691
2757
|
const nextAction = deriveNextAction({
|
|
2692
2758
|
completionBlocker,
|
|
2693
2759
|
completionOutcome,
|
|
2694
|
-
completionReportedAt
|
|
2760
|
+
completionReportedAt,
|
|
2695
2761
|
finished: isFinishedWorkerStatus(status)
|
|
2696
2762
|
});
|
|
2697
2763
|
const handoffState = deriveHandoffState({
|
|
@@ -2737,7 +2803,7 @@ function buildRunBoard(runId) {
|
|
|
2737
2803
|
gitAncestry: status.gitAncestry,
|
|
2738
2804
|
finalResult: status.finalResult,
|
|
2739
2805
|
lifecycleStage,
|
|
2740
|
-
completionReportedAt
|
|
2806
|
+
completionReportedAt,
|
|
2741
2807
|
completionOutcome: worker.completionOutcome ?? null,
|
|
2742
2808
|
completionRouteStatus,
|
|
2743
2809
|
completionRouteOutcome: completionOutcome,
|
|
@@ -2764,7 +2830,7 @@ function buildRunBoard(runId) {
|
|
|
2764
2830
|
needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
|
|
2765
2831
|
workers
|
|
2766
2832
|
};
|
|
2767
|
-
writeJson(
|
|
2833
|
+
writeJson(path11.join(runDirectory(run.id), "last-board.json"), board);
|
|
2768
2834
|
return board;
|
|
2769
2835
|
}
|
|
2770
2836
|
async function publishHarnessBoardSnapshot(args, source) {
|
|
@@ -2953,12 +3019,12 @@ async function autoCompleteWorkerCli(raw) {
|
|
|
2953
3019
|
}
|
|
2954
3020
|
}
|
|
2955
3021
|
function resolveDefaultCliPath() {
|
|
2956
|
-
return
|
|
3022
|
+
return path12.join(fileURLToPath2(new URL(".", import.meta.url)), "cli.js");
|
|
2957
3023
|
}
|
|
2958
3024
|
function spawnCompletionSidecar(opts) {
|
|
2959
3025
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath();
|
|
2960
3026
|
if (!existsSync10(cliPath)) return void 0;
|
|
2961
|
-
const logPath =
|
|
3027
|
+
const logPath = path12.join(opts.workerDir, "auto-complete.log");
|
|
2962
3028
|
let logFd;
|
|
2963
3029
|
try {
|
|
2964
3030
|
logFd = openSync3(logPath, "a");
|
|
@@ -3040,16 +3106,16 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3040
3106
|
launchModel = preflight.model;
|
|
3041
3107
|
}
|
|
3042
3108
|
const { worktreesDir } = getPaths();
|
|
3043
|
-
const workerDir =
|
|
3109
|
+
const workerDir = path13.join(runDirectory(run.id), "workers", name);
|
|
3044
3110
|
mkdirSync3(workerDir, { recursive: true });
|
|
3045
|
-
const worktreePath =
|
|
3111
|
+
const worktreePath = path13.join(worktreesDir, run.id, name);
|
|
3046
3112
|
const branch = opts.branch || `agent/${run.id}/${name}`;
|
|
3047
3113
|
if (existsSync11(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
|
|
3048
3114
|
git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
|
|
3049
3115
|
git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
|
|
3050
|
-
const stdoutPath =
|
|
3051
|
-
const stderrPath =
|
|
3052
|
-
const heartbeatPath =
|
|
3116
|
+
const stdoutPath = path13.join(workerDir, "stdout.jsonl");
|
|
3117
|
+
const stderrPath = path13.join(workerDir, "stderr.log");
|
|
3118
|
+
const heartbeatPath = path13.join(workerDir, "heartbeat.jsonl");
|
|
3053
3119
|
const prompt = buildPrompt({
|
|
3054
3120
|
task: opts.task,
|
|
3055
3121
|
ownedPaths: opts.ownedPaths || [],
|
|
@@ -3110,7 +3176,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3110
3176
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3111
3177
|
};
|
|
3112
3178
|
saveWorker(run.id, worker);
|
|
3113
|
-
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath:
|
|
3179
|
+
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path13.join(workerDir, "worker.json") } };
|
|
3114
3180
|
run.status = "running";
|
|
3115
3181
|
saveRun(run);
|
|
3116
3182
|
if (worker.agentOsId && worker.taskId) {
|
|
@@ -3402,18 +3468,18 @@ function buildPlanPersistIdempotencyKey(input) {
|
|
|
3402
3468
|
|
|
3403
3469
|
// src/plan-persist/paths.ts
|
|
3404
3470
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
3405
|
-
import { homedir as
|
|
3406
|
-
import
|
|
3471
|
+
import { homedir as homedir4 } from "node:os";
|
|
3472
|
+
import path14 from "node:path";
|
|
3407
3473
|
function resolveKynverStateRoot() {
|
|
3408
3474
|
const env = process.env.KYNVER_STATE_ROOT;
|
|
3409
|
-
if (env) return
|
|
3410
|
-
return
|
|
3475
|
+
if (env) return path14.resolve(env);
|
|
3476
|
+
return path14.join(homedir4(), ".kynver", "state");
|
|
3411
3477
|
}
|
|
3412
3478
|
function planOutboxDir() {
|
|
3413
|
-
return
|
|
3479
|
+
return path14.join(resolveKynverStateRoot(), "plan-outbox");
|
|
3414
3480
|
}
|
|
3415
3481
|
function planOutboxArchiveDir() {
|
|
3416
|
-
return
|
|
3482
|
+
return path14.join(resolveKynverStateRoot(), "plan-outbox-archive");
|
|
3417
3483
|
}
|
|
3418
3484
|
function ensurePlanOutboxDirs() {
|
|
3419
3485
|
const outboxDir = planOutboxDir();
|
|
@@ -3424,8 +3490,8 @@ function ensurePlanOutboxDirs() {
|
|
|
3424
3490
|
}
|
|
3425
3491
|
function isTmpOnlyPath(filePath) {
|
|
3426
3492
|
if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
|
|
3427
|
-
const resolved =
|
|
3428
|
-
return resolved.startsWith("/tmp/") || resolved.startsWith(
|
|
3493
|
+
const resolved = path14.resolve(filePath);
|
|
3494
|
+
return resolved.startsWith("/tmp/") || resolved.startsWith(path14.join("/var", "folders"));
|
|
3429
3495
|
}
|
|
3430
3496
|
|
|
3431
3497
|
// src/plan-persist/outbox-store.ts
|
|
@@ -3437,7 +3503,7 @@ import {
|
|
|
3437
3503
|
writeFileSync as writeFileSync3,
|
|
3438
3504
|
unlinkSync
|
|
3439
3505
|
} from "node:fs";
|
|
3440
|
-
import
|
|
3506
|
+
import path15 from "node:path";
|
|
3441
3507
|
import { randomUUID } from "node:crypto";
|
|
3442
3508
|
var DEFAULT_MAX_RETRIES = 12;
|
|
3443
3509
|
function listOutboxItems() {
|
|
@@ -3445,7 +3511,7 @@ function listOutboxItems() {
|
|
|
3445
3511
|
const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
|
|
3446
3512
|
const items = [];
|
|
3447
3513
|
for (const file of files) {
|
|
3448
|
-
const item = readOutboxItem(
|
|
3514
|
+
const item = readOutboxItem(path15.join(outboxDir, file));
|
|
3449
3515
|
if (item && item.queueStatus === "queued") items.push(item);
|
|
3450
3516
|
}
|
|
3451
3517
|
return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
@@ -3466,7 +3532,7 @@ function readOutboxItem(jsonPath) {
|
|
|
3466
3532
|
}
|
|
3467
3533
|
function readOutboxBody(item) {
|
|
3468
3534
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3469
|
-
const bodyFile =
|
|
3535
|
+
const bodyFile = path15.join(outboxDir, item.bodyPath);
|
|
3470
3536
|
return readFileSync7(bodyFile, "utf8");
|
|
3471
3537
|
}
|
|
3472
3538
|
function writeOutboxItem(input, opts) {
|
|
@@ -3474,8 +3540,8 @@ function writeOutboxItem(input, opts) {
|
|
|
3474
3540
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3475
3541
|
const id = opts.existing?.id ?? randomUUID();
|
|
3476
3542
|
const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
|
|
3477
|
-
const jsonPath =
|
|
3478
|
-
const bodyFile =
|
|
3543
|
+
const jsonPath = path15.join(outboxDir, `${id}.json`);
|
|
3544
|
+
const bodyFile = path15.join(outboxDir, bodyPath);
|
|
3479
3545
|
if (!opts.existing) {
|
|
3480
3546
|
writeFileSync3(bodyFile, input.body, "utf8");
|
|
3481
3547
|
}
|
|
@@ -3511,24 +3577,24 @@ function writeOutboxItem(input, opts) {
|
|
|
3511
3577
|
}
|
|
3512
3578
|
function saveOutboxItem(item) {
|
|
3513
3579
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3514
|
-
const jsonPath =
|
|
3580
|
+
const jsonPath = path15.join(outboxDir, `${item.id}.json`);
|
|
3515
3581
|
writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
|
|
3516
3582
|
`, { mode: 384 });
|
|
3517
3583
|
}
|
|
3518
3584
|
function archiveOutboxItem(item) {
|
|
3519
3585
|
const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
|
|
3520
|
-
const jsonSrc =
|
|
3521
|
-
const bodySrc =
|
|
3522
|
-
const jsonDst =
|
|
3523
|
-
const bodyDst =
|
|
3586
|
+
const jsonSrc = path15.join(outboxDir, `${item.id}.json`);
|
|
3587
|
+
const bodySrc = path15.join(outboxDir, item.bodyPath);
|
|
3588
|
+
const jsonDst = path15.join(archiveDir, `${item.id}.json`);
|
|
3589
|
+
const bodyDst = path15.join(archiveDir, item.bodyPath);
|
|
3524
3590
|
if (existsSync13(jsonSrc)) renameSync(jsonSrc, jsonDst);
|
|
3525
3591
|
if (existsSync13(bodySrc)) renameSync(bodySrc, bodyDst);
|
|
3526
3592
|
}
|
|
3527
3593
|
function outboxItemPaths(item) {
|
|
3528
3594
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3529
3595
|
return {
|
|
3530
|
-
jsonPath:
|
|
3531
|
-
bodyPath:
|
|
3596
|
+
jsonPath: path15.join(outboxDir, `${item.id}.json`),
|
|
3597
|
+
bodyPath: path15.join(outboxDir, item.bodyPath)
|
|
3532
3598
|
};
|
|
3533
3599
|
}
|
|
3534
3600
|
function outboxInputFromItem(item, body) {
|
|
@@ -3714,7 +3780,7 @@ function markOutboxFailed(item, message) {
|
|
|
3714
3780
|
}
|
|
3715
3781
|
|
|
3716
3782
|
// src/plan-persist/drain.ts
|
|
3717
|
-
import
|
|
3783
|
+
import path16 from "node:path";
|
|
3718
3784
|
async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
3719
3785
|
const items = listOutboxItems().filter(
|
|
3720
3786
|
(item) => opts.outboxId ? item.id === opts.outboxId : true
|
|
@@ -3748,7 +3814,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
|
3748
3814
|
return result;
|
|
3749
3815
|
}
|
|
3750
3816
|
function loadOutboxById(outboxId) {
|
|
3751
|
-
const jsonPath =
|
|
3817
|
+
const jsonPath = path16.join(planOutboxDir(), `${outboxId}.json`);
|
|
3752
3818
|
return readOutboxItem(jsonPath);
|
|
3753
3819
|
}
|
|
3754
3820
|
|
|
@@ -3848,7 +3914,7 @@ async function dispatchRun(args) {
|
|
|
3848
3914
|
const activeHarnessWorkers = [];
|
|
3849
3915
|
for (const name of Object.keys(run.workers || {})) {
|
|
3850
3916
|
const worker = readJson(
|
|
3851
|
-
|
|
3917
|
+
path17.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
3852
3918
|
void 0
|
|
3853
3919
|
);
|
|
3854
3920
|
if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
|
|
@@ -4061,7 +4127,7 @@ function redactHarness(text, secret) {
|
|
|
4061
4127
|
}
|
|
4062
4128
|
|
|
4063
4129
|
// src/validate.ts
|
|
4064
|
-
import
|
|
4130
|
+
import path18 from "node:path";
|
|
4065
4131
|
var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
4066
4132
|
var WORKER_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
|
|
4067
4133
|
function validateRunId(runId) {
|
|
@@ -4075,15 +4141,15 @@ function validateWorkerName(name) {
|
|
|
4075
4141
|
return trimmed;
|
|
4076
4142
|
}
|
|
4077
4143
|
function validateRepo(repo) {
|
|
4078
|
-
const resolved =
|
|
4144
|
+
const resolved = path18.resolve(repo);
|
|
4079
4145
|
if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
|
|
4080
4146
|
return resolved;
|
|
4081
4147
|
}
|
|
4082
4148
|
function validateOwnedPaths(repoRoot, ownedPaths) {
|
|
4083
4149
|
return ownedPaths.map((owned) => {
|
|
4084
|
-
const resolved =
|
|
4085
|
-
const rel =
|
|
4086
|
-
if (rel.startsWith("..") ||
|
|
4150
|
+
const resolved = path18.resolve(repoRoot, owned);
|
|
4151
|
+
const rel = path18.relative(repoRoot, resolved);
|
|
4152
|
+
if (rel.startsWith("..") || path18.isAbsolute(rel)) {
|
|
4087
4153
|
throw new Error(`owned path escapes repo: ${owned}`);
|
|
4088
4154
|
}
|
|
4089
4155
|
return resolved;
|
|
@@ -4096,7 +4162,7 @@ function validateTailLines(lines) {
|
|
|
4096
4162
|
|
|
4097
4163
|
// src/worktree.ts
|
|
4098
4164
|
import { existsSync as existsSync14, mkdirSync as mkdirSync5 } from "node:fs";
|
|
4099
|
-
import
|
|
4165
|
+
import path19 from "node:path";
|
|
4100
4166
|
function createRun(args) {
|
|
4101
4167
|
const repo = validateRepo(required(String(args.repo || ""), "--repo"));
|
|
4102
4168
|
ensureGitRepo(repo);
|
|
@@ -4116,12 +4182,12 @@ function createRun(args) {
|
|
|
4116
4182
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4117
4183
|
workers: {}
|
|
4118
4184
|
};
|
|
4119
|
-
writeJson(
|
|
4185
|
+
writeJson(path19.join(dir, "run.json"), run);
|
|
4120
4186
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
4121
4187
|
}
|
|
4122
4188
|
function listRuns() {
|
|
4123
4189
|
const { runsDir } = getPaths();
|
|
4124
|
-
const rows = listRunIds(runsDir).map((id) => readJson(
|
|
4190
|
+
const rows = listRunIds(runsDir).map((id) => readJson(path19.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
|
|
4125
4191
|
id: run.id,
|
|
4126
4192
|
name: run.name,
|
|
4127
4193
|
status: run.status,
|
|
@@ -4136,7 +4202,7 @@ function failExists(message) {
|
|
|
4136
4202
|
}
|
|
4137
4203
|
|
|
4138
4204
|
// src/sweep.ts
|
|
4139
|
-
import
|
|
4205
|
+
import path20 from "node:path";
|
|
4140
4206
|
async function sweepRun(args) {
|
|
4141
4207
|
const pipeline = args.pipeline === true || args.pipeline === "true";
|
|
4142
4208
|
try {
|
|
@@ -4149,7 +4215,7 @@ async function sweepRun(args) {
|
|
|
4149
4215
|
const releasedLocalOrphans = [];
|
|
4150
4216
|
for (const name of Object.keys(run.workers || {})) {
|
|
4151
4217
|
const worker = readJson(
|
|
4152
|
-
|
|
4218
|
+
path20.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4153
4219
|
void 0
|
|
4154
4220
|
);
|
|
4155
4221
|
if (!worker || !worker.dispatched || !worker.taskId) continue;
|
|
@@ -4196,7 +4262,7 @@ import { mkdirSync as mkdirSync7, realpathSync } from "node:fs";
|
|
|
4196
4262
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
4197
4263
|
|
|
4198
4264
|
// src/pipeline-tick.ts
|
|
4199
|
-
import
|
|
4265
|
+
import path29 from "node:path";
|
|
4200
4266
|
|
|
4201
4267
|
// src/pipeline-dispatch.ts
|
|
4202
4268
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -4250,10 +4316,10 @@ async function runPipelineDispatch(args, slots) {
|
|
|
4250
4316
|
}
|
|
4251
4317
|
|
|
4252
4318
|
// src/stale-reconcile.ts
|
|
4253
|
-
import
|
|
4319
|
+
import path22 from "node:path";
|
|
4254
4320
|
|
|
4255
4321
|
// src/finalize.ts
|
|
4256
|
-
import
|
|
4322
|
+
import path21 from "node:path";
|
|
4257
4323
|
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
|
|
4258
4324
|
function terminalStatusFor(run) {
|
|
4259
4325
|
const names = Object.keys(run.workers || {});
|
|
@@ -4264,7 +4330,7 @@ function terminalStatusFor(run) {
|
|
|
4264
4330
|
let anyLandingBlocked = false;
|
|
4265
4331
|
for (const name of names) {
|
|
4266
4332
|
const worker = readJson(
|
|
4267
|
-
|
|
4333
|
+
path21.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4268
4334
|
void 0
|
|
4269
4335
|
);
|
|
4270
4336
|
if (!worker) continue;
|
|
@@ -4316,7 +4382,7 @@ function reconcileStaleWorkers() {
|
|
|
4316
4382
|
const now = Date.now();
|
|
4317
4383
|
for (const run of listRunRecords()) {
|
|
4318
4384
|
for (const name of Object.keys(run.workers || {})) {
|
|
4319
|
-
const workerPath =
|
|
4385
|
+
const workerPath = path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
4320
4386
|
const worker = readJson(workerPath, void 0);
|
|
4321
4387
|
if (!worker || worker.status !== "running") {
|
|
4322
4388
|
outcomes.push({
|
|
@@ -4410,7 +4476,7 @@ function reconcileRunsCli() {
|
|
|
4410
4476
|
}
|
|
4411
4477
|
|
|
4412
4478
|
// src/plan-progress-daemon-sync.ts
|
|
4413
|
-
import
|
|
4479
|
+
import path23 from "node:path";
|
|
4414
4480
|
|
|
4415
4481
|
// src/plan-progress-sync.ts
|
|
4416
4482
|
async function syncPlanProgress(args) {
|
|
@@ -4434,7 +4500,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
4434
4500
|
const outcomes = [];
|
|
4435
4501
|
for (const name of Object.keys(run.workers || {})) {
|
|
4436
4502
|
const worker = readJson(
|
|
4437
|
-
|
|
4503
|
+
path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4438
4504
|
void 0
|
|
4439
4505
|
);
|
|
4440
4506
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -4483,7 +4549,7 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
4483
4549
|
}
|
|
4484
4550
|
|
|
4485
4551
|
// src/cleanup.ts
|
|
4486
|
-
import
|
|
4552
|
+
import path28 from "node:path";
|
|
4487
4553
|
|
|
4488
4554
|
// src/cleanup-types.ts
|
|
4489
4555
|
var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
@@ -4555,11 +4621,11 @@ function skipNodeModulesRemoval(input) {
|
|
|
4555
4621
|
|
|
4556
4622
|
// src/cleanup-execute.ts
|
|
4557
4623
|
import { existsSync as existsSync16, rmSync } from "node:fs";
|
|
4558
|
-
import
|
|
4624
|
+
import path25 from "node:path";
|
|
4559
4625
|
|
|
4560
4626
|
// src/cleanup-dir-size.ts
|
|
4561
4627
|
import { existsSync as existsSync15, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
|
|
4562
|
-
import
|
|
4628
|
+
import path24 from "node:path";
|
|
4563
4629
|
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
4564
4630
|
if (!existsSync15(root)) return 0;
|
|
4565
4631
|
let total = 0;
|
|
@@ -4575,7 +4641,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
4575
4641
|
}
|
|
4576
4642
|
for (const name of entries) {
|
|
4577
4643
|
if (seen++ > maxEntries) return null;
|
|
4578
|
-
const full =
|
|
4644
|
+
const full = path24.join(current, name);
|
|
4579
4645
|
let st;
|
|
4580
4646
|
try {
|
|
4581
4647
|
st = statSync2(full);
|
|
@@ -4659,20 +4725,20 @@ function removeWorktree(candidate, execute) {
|
|
|
4659
4725
|
}
|
|
4660
4726
|
}
|
|
4661
4727
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
4662
|
-
const resolved =
|
|
4663
|
-
const nm = resolved.endsWith(`${
|
|
4728
|
+
const resolved = path25.resolve(targetPath);
|
|
4729
|
+
const nm = resolved.endsWith(`${path25.sep}node_modules`) ? resolved : null;
|
|
4664
4730
|
if (!nm) return "path_outside_harness";
|
|
4665
|
-
const rel =
|
|
4666
|
-
if (rel.startsWith("..") ||
|
|
4667
|
-
const parts = rel.split(
|
|
4731
|
+
const rel = path25.relative(worktreesDir, nm);
|
|
4732
|
+
if (rel.startsWith("..") || path25.isAbsolute(rel)) return "path_outside_harness";
|
|
4733
|
+
const parts = rel.split(path25.sep);
|
|
4668
4734
|
if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
|
|
4669
|
-
if (!resolved.startsWith(
|
|
4735
|
+
if (!resolved.startsWith(path25.resolve(harnessRoot))) return "path_outside_harness";
|
|
4670
4736
|
return null;
|
|
4671
4737
|
}
|
|
4672
4738
|
|
|
4673
4739
|
// src/cleanup-scan.ts
|
|
4674
4740
|
import { existsSync as existsSync17, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
4675
|
-
import
|
|
4741
|
+
import path26 from "node:path";
|
|
4676
4742
|
function pathAgeMs(target, now) {
|
|
4677
4743
|
try {
|
|
4678
4744
|
const mtime = statSync3(target).mtimeMs;
|
|
@@ -4682,17 +4748,17 @@ function pathAgeMs(target, now) {
|
|
|
4682
4748
|
}
|
|
4683
4749
|
}
|
|
4684
4750
|
function isPathInside(child, parent) {
|
|
4685
|
-
const rel =
|
|
4686
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
4751
|
+
const rel = path26.relative(parent, child);
|
|
4752
|
+
return rel === "" || !rel.startsWith("..") && !path26.isAbsolute(rel);
|
|
4687
4753
|
}
|
|
4688
4754
|
function scanNodeModulesCandidates(opts) {
|
|
4689
4755
|
const candidates = [];
|
|
4690
4756
|
const seen = /* @__PURE__ */ new Set();
|
|
4691
4757
|
for (const entry of opts.index.values()) {
|
|
4692
4758
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
4693
|
-
const nm =
|
|
4759
|
+
const nm = path26.join(entry.worktreePath, "node_modules");
|
|
4694
4760
|
if (!existsSync17(nm)) continue;
|
|
4695
|
-
const resolved =
|
|
4761
|
+
const resolved = path26.resolve(nm);
|
|
4696
4762
|
if (seen.has(resolved)) continue;
|
|
4697
4763
|
seen.add(resolved);
|
|
4698
4764
|
candidates.push({
|
|
@@ -4708,13 +4774,13 @@ function scanNodeModulesCandidates(opts) {
|
|
|
4708
4774
|
if (!opts.includeOrphans || !existsSync17(opts.worktreesDir)) return candidates;
|
|
4709
4775
|
for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
|
|
4710
4776
|
if (!runEntry.isDirectory()) continue;
|
|
4711
|
-
const runPath =
|
|
4777
|
+
const runPath = path26.join(opts.worktreesDir, runEntry.name);
|
|
4712
4778
|
for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
|
|
4713
4779
|
if (!workerEntry.isDirectory()) continue;
|
|
4714
|
-
const worktreePath =
|
|
4715
|
-
const nm =
|
|
4780
|
+
const worktreePath = path26.join(runPath, workerEntry.name);
|
|
4781
|
+
const nm = path26.join(worktreePath, "node_modules");
|
|
4716
4782
|
if (!existsSync17(nm)) continue;
|
|
4717
|
-
const resolved =
|
|
4783
|
+
const resolved = path26.resolve(nm);
|
|
4718
4784
|
if (seen.has(resolved)) continue;
|
|
4719
4785
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
4720
4786
|
seen.add(resolved);
|
|
@@ -4754,17 +4820,17 @@ function scanWorktreeCandidates(opts) {
|
|
|
4754
4820
|
}
|
|
4755
4821
|
|
|
4756
4822
|
// src/cleanup-worktree-index.ts
|
|
4757
|
-
import
|
|
4823
|
+
import path27 from "node:path";
|
|
4758
4824
|
function buildWorktreeIndex() {
|
|
4759
4825
|
const index = /* @__PURE__ */ new Map();
|
|
4760
4826
|
for (const run of listRunRecords()) {
|
|
4761
4827
|
for (const name of Object.keys(run.workers || {})) {
|
|
4762
|
-
const workerPath =
|
|
4828
|
+
const workerPath = path27.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
4763
4829
|
const worker = readJson(workerPath, void 0);
|
|
4764
4830
|
if (!worker?.worktreePath) continue;
|
|
4765
4831
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
4766
|
-
index.set(
|
|
4767
|
-
worktreePath:
|
|
4832
|
+
index.set(path27.resolve(worker.worktreePath), {
|
|
4833
|
+
worktreePath: path27.resolve(worker.worktreePath),
|
|
4768
4834
|
runId: run.id,
|
|
4769
4835
|
workerName: name,
|
|
4770
4836
|
run,
|
|
@@ -4778,8 +4844,8 @@ function buildWorktreeIndex() {
|
|
|
4778
4844
|
|
|
4779
4845
|
// src/cleanup.ts
|
|
4780
4846
|
function resolveOptions(options = {}) {
|
|
4781
|
-
const harnessRoot = options.harnessRoot ?
|
|
4782
|
-
const { worktreesDir } = options.harnessRoot ? { worktreesDir:
|
|
4847
|
+
const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
|
|
4848
|
+
const { worktreesDir } = options.harnessRoot ? { worktreesDir: path28.join(harnessRoot, "worktrees") } : getHarnessPaths();
|
|
4783
4849
|
const execute = options.execute === true;
|
|
4784
4850
|
const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
|
|
4785
4851
|
const worktreesAgeMs = options.worktreesAgeMs ?? 0;
|
|
@@ -4823,7 +4889,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
4823
4889
|
actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
|
|
4824
4890
|
continue;
|
|
4825
4891
|
}
|
|
4826
|
-
const worktreePath =
|
|
4892
|
+
const worktreePath = path28.resolve(candidate.path, "..");
|
|
4827
4893
|
const indexed = index.get(worktreePath) ?? null;
|
|
4828
4894
|
const guardReason = skipNodeModulesRemoval({
|
|
4829
4895
|
indexed,
|
|
@@ -4839,7 +4905,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
4839
4905
|
actions.push(removeNodeModules(candidate, resolved.execute));
|
|
4840
4906
|
}
|
|
4841
4907
|
for (const candidate of scanWorktreeCandidates(scanOpts)) {
|
|
4842
|
-
const indexed = index.get(
|
|
4908
|
+
const indexed = index.get(path28.resolve(candidate.path)) ?? null;
|
|
4843
4909
|
const guardReason = skipWorktreeRemoval({
|
|
4844
4910
|
indexed,
|
|
4845
4911
|
includeOrphans: resolved.includeOrphans,
|
|
@@ -4908,7 +4974,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
4908
4974
|
const outcomes = [];
|
|
4909
4975
|
for (const name of Object.keys(run.workers || {})) {
|
|
4910
4976
|
const worker = readJson(
|
|
4911
|
-
|
|
4977
|
+
path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4912
4978
|
void 0
|
|
4913
4979
|
);
|
|
4914
4980
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -5243,7 +5309,7 @@ function runCleanupCli(args) {
|
|
|
5243
5309
|
}
|
|
5244
5310
|
|
|
5245
5311
|
// src/monitor/monitor.service.ts
|
|
5246
|
-
import
|
|
5312
|
+
import path31 from "node:path";
|
|
5247
5313
|
|
|
5248
5314
|
// src/monitor/monitor.classify.ts
|
|
5249
5315
|
function expectedLeaseOwner(runId) {
|
|
@@ -5300,10 +5366,10 @@ function classifyWorkerHealth(input) {
|
|
|
5300
5366
|
|
|
5301
5367
|
// src/monitor/monitor.store.ts
|
|
5302
5368
|
import { existsSync as existsSync18, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
|
|
5303
|
-
import
|
|
5369
|
+
import path30 from "node:path";
|
|
5304
5370
|
function monitorsDir() {
|
|
5305
5371
|
const { harnessRoot } = getHarnessPaths();
|
|
5306
|
-
const dir =
|
|
5372
|
+
const dir = path30.join(harnessRoot, "monitors");
|
|
5307
5373
|
mkdirSync6(dir, { recursive: true });
|
|
5308
5374
|
return dir;
|
|
5309
5375
|
}
|
|
@@ -5311,7 +5377,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
5311
5377
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
5312
5378
|
}
|
|
5313
5379
|
function monitorPath(monitorId) {
|
|
5314
|
-
return
|
|
5380
|
+
return path30.join(monitorsDir(), `${monitorId}.json`);
|
|
5315
5381
|
}
|
|
5316
5382
|
function loadMonitorSession(monitorId) {
|
|
5317
5383
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -5332,7 +5398,7 @@ function listMonitorSessions() {
|
|
|
5332
5398
|
for (const name of readdirSync7(dir)) {
|
|
5333
5399
|
if (!name.endsWith(".json")) continue;
|
|
5334
5400
|
const session = readJson(
|
|
5335
|
-
|
|
5401
|
+
path30.join(dir, name),
|
|
5336
5402
|
void 0
|
|
5337
5403
|
);
|
|
5338
5404
|
if (!session?.monitorId) continue;
|
|
@@ -5423,7 +5489,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
5423
5489
|
// src/monitor/monitor.service.ts
|
|
5424
5490
|
function workerRecord2(runId, name) {
|
|
5425
5491
|
return readJson(
|
|
5426
|
-
|
|
5492
|
+
path31.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
5427
5493
|
void 0
|
|
5428
5494
|
);
|
|
5429
5495
|
}
|
|
@@ -5626,17 +5692,17 @@ async function runMonitorLoop(args) {
|
|
|
5626
5692
|
// src/monitor/monitor-spawn.ts
|
|
5627
5693
|
import { spawn as spawn4 } from "node:child_process";
|
|
5628
5694
|
import { closeSync as closeSync4, existsSync as existsSync19, openSync as openSync4 } from "node:fs";
|
|
5629
|
-
import
|
|
5695
|
+
import path32 from "node:path";
|
|
5630
5696
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
5631
5697
|
function resolveDefaultCliPath2() {
|
|
5632
|
-
return
|
|
5698
|
+
return path32.join(fileURLToPath3(new URL(".", import.meta.url)), "..", "cli.js");
|
|
5633
5699
|
}
|
|
5634
5700
|
function spawnMonitorSidecar(opts) {
|
|
5635
5701
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
5636
5702
|
if (!existsSync19(cliPath)) return void 0;
|
|
5637
5703
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
5638
5704
|
const { harnessRoot } = getHarnessPaths();
|
|
5639
|
-
const logPath =
|
|
5705
|
+
const logPath = path32.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
5640
5706
|
let logFd;
|
|
5641
5707
|
try {
|
|
5642
5708
|
logFd = openSync4(logPath, "a");
|
|
@@ -5755,6 +5821,422 @@ async function monitorTickCli(args) {
|
|
|
5755
5821
|
console.log(JSON.stringify(tick, null, 2));
|
|
5756
5822
|
}
|
|
5757
5823
|
|
|
5824
|
+
// src/doctor/runtime-takeover.ts
|
|
5825
|
+
import path34 from "node:path";
|
|
5826
|
+
|
|
5827
|
+
// src/doctor/runtime-takeover.probes.ts
|
|
5828
|
+
import { accessSync, constants, existsSync as existsSync20, readFileSync as readFileSync9 } from "node:fs";
|
|
5829
|
+
import { homedir as homedir5 } from "node:os";
|
|
5830
|
+
import path33 from "node:path";
|
|
5831
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
5832
|
+
function captureCommand(bin, args) {
|
|
5833
|
+
try {
|
|
5834
|
+
const res = spawnSync3(bin, args, { encoding: "utf8" });
|
|
5835
|
+
const stdout = (res.stdout || "").trim();
|
|
5836
|
+
const stderr = (res.stderr || "").trim();
|
|
5837
|
+
const ok = res.status === 0;
|
|
5838
|
+
return {
|
|
5839
|
+
ok,
|
|
5840
|
+
stdout,
|
|
5841
|
+
stderr,
|
|
5842
|
+
error: res.error?.message
|
|
5843
|
+
};
|
|
5844
|
+
} catch (error) {
|
|
5845
|
+
return {
|
|
5846
|
+
ok: false,
|
|
5847
|
+
stdout: "",
|
|
5848
|
+
stderr: "",
|
|
5849
|
+
error: error.message
|
|
5850
|
+
};
|
|
5851
|
+
}
|
|
5852
|
+
}
|
|
5853
|
+
function tokenPrefix(token) {
|
|
5854
|
+
const trimmed = token?.trim();
|
|
5855
|
+
if (!trimmed) return void 0;
|
|
5856
|
+
return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
|
|
5857
|
+
}
|
|
5858
|
+
function isWritable(target) {
|
|
5859
|
+
if (!existsSync20(target)) return false;
|
|
5860
|
+
try {
|
|
5861
|
+
accessSync(target, constants.W_OK);
|
|
5862
|
+
return true;
|
|
5863
|
+
} catch {
|
|
5864
|
+
return false;
|
|
5865
|
+
}
|
|
5866
|
+
}
|
|
5867
|
+
var defaultRuntimeTakeoverProbes = {
|
|
5868
|
+
packageVersion: () => PACKAGE_VERSION,
|
|
5869
|
+
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
5870
|
+
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
5871
|
+
loadConfig: () => loadUserConfig(),
|
|
5872
|
+
configFilePath: () => path33.join(homedir5(), ".kynver", "config.json"),
|
|
5873
|
+
credentialsFilePath: () => path33.join(homedir5(), ".kynver", "credentials"),
|
|
5874
|
+
readCredentials: () => {
|
|
5875
|
+
const credPath = path33.join(homedir5(), ".kynver", "credentials");
|
|
5876
|
+
if (!existsSync20(credPath)) {
|
|
5877
|
+
return { hasApiKey: false };
|
|
5878
|
+
}
|
|
5879
|
+
try {
|
|
5880
|
+
const parsed = JSON.parse(readFileSync9(credPath, "utf8"));
|
|
5881
|
+
return {
|
|
5882
|
+
hasApiKey: Boolean(parsed.apiKey?.trim()),
|
|
5883
|
+
runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
|
|
5884
|
+
runnerTokenAgentOsId: parsed.runnerTokenAgentOsId
|
|
5885
|
+
};
|
|
5886
|
+
} catch {
|
|
5887
|
+
return { hasApiKey: false };
|
|
5888
|
+
}
|
|
5889
|
+
},
|
|
5890
|
+
envSnapshot: () => ({
|
|
5891
|
+
kynverApiUrl: process.env.KYNVER_API_URL?.trim() || void 0,
|
|
5892
|
+
openclawCronFireBaseUrl: process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim() || void 0,
|
|
5893
|
+
kynverRunnerTokenPrefix: tokenPrefix(process.env.KYNVER_RUNNER_TOKEN),
|
|
5894
|
+
kynverRuntimeSecret: Boolean(process.env.KYNVER_RUNTIME_SECRET?.trim()),
|
|
5895
|
+
openclawCronSecret: Boolean(process.env.OPENCLAW_CRON_SECRET?.trim()),
|
|
5896
|
+
kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
|
|
5897
|
+
opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
|
|
5898
|
+
kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0
|
|
5899
|
+
}),
|
|
5900
|
+
harnessRoot: () => resolveHarnessRoot(),
|
|
5901
|
+
legacyOpenclawHarnessRoot: () => path33.join(homedir5(), ".openclaw", "harness"),
|
|
5902
|
+
pathExists: (target) => existsSync20(target),
|
|
5903
|
+
pathWritable: (target) => isWritable(target),
|
|
5904
|
+
vercelVersion: () => captureCommand("vercel", ["--version"]),
|
|
5905
|
+
vercelWhoami: () => captureCommand("vercel", ["whoami"])
|
|
5906
|
+
};
|
|
5907
|
+
|
|
5908
|
+
// src/doctor/runtime-takeover.ts
|
|
5909
|
+
function check(partial) {
|
|
5910
|
+
return partial;
|
|
5911
|
+
}
|
|
5912
|
+
function summarizeCounts(sections) {
|
|
5913
|
+
const counts = { pass: 0, warn: 0, fail: 0 };
|
|
5914
|
+
for (const section of sections) {
|
|
5915
|
+
for (const item of section.checks) {
|
|
5916
|
+
counts[item.status] += 1;
|
|
5917
|
+
}
|
|
5918
|
+
}
|
|
5919
|
+
return counts;
|
|
5920
|
+
}
|
|
5921
|
+
function assessCliPackage(probes) {
|
|
5922
|
+
const runningVersion = probes.packageVersion();
|
|
5923
|
+
const which = probes.commandOnPath("kynver");
|
|
5924
|
+
const onPath = which.ok && which.stdout.length > 0;
|
|
5925
|
+
const firstPath = onPath ? which.stdout.split(/\r?\n/)[0]?.trim() : void 0;
|
|
5926
|
+
const displayCliPath = firstPath ? displayUserPath(firstPath) : void 0;
|
|
5927
|
+
const checks = [
|
|
5928
|
+
check({
|
|
5929
|
+
id: "cli_running_version",
|
|
5930
|
+
label: "Running @kynver-app/runtime version",
|
|
5931
|
+
status: "pass",
|
|
5932
|
+
summary: `@kynver-app/runtime ${runningVersion}`,
|
|
5933
|
+
details: { version: runningVersion }
|
|
5934
|
+
}),
|
|
5935
|
+
check({
|
|
5936
|
+
id: "cli_on_path",
|
|
5937
|
+
label: "kynver executable on PATH",
|
|
5938
|
+
status: onPath ? "pass" : "warn",
|
|
5939
|
+
summary: onPath ? `Found ${displayCliPath}` : "kynver not found on PATH (invoked via node/npx?)",
|
|
5940
|
+
remediation: onPath ? void 0 : "Install globally (`npm i -g @kynver-app/runtime`) or invoke via `npx @kynver-app/runtime`.",
|
|
5941
|
+
details: { path: displayCliPath }
|
|
5942
|
+
})
|
|
5943
|
+
];
|
|
5944
|
+
if (onPath && firstPath) {
|
|
5945
|
+
const versionProbe = probes.kynverVersion(firstPath);
|
|
5946
|
+
const installedVersion = versionProbe.stdout.replace(/^kynver\s+/i, "").trim() || void 0;
|
|
5947
|
+
const versionMatch = versionProbe.ok && (!installedVersion || installedVersion === runningVersion);
|
|
5948
|
+
checks.push(
|
|
5949
|
+
check({
|
|
5950
|
+
id: "cli_installed_version",
|
|
5951
|
+
label: "Installed kynver CLI version matches running package",
|
|
5952
|
+
status: versionMatch ? "pass" : "warn",
|
|
5953
|
+
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",
|
|
5954
|
+
remediation: versionMatch ? void 0 : "Reinstall or rebuild @kynver-app/runtime so PATH kynver matches the harness worktree version.",
|
|
5955
|
+
details: { installedVersion, runningVersion, path: displayCliPath }
|
|
5956
|
+
})
|
|
5957
|
+
);
|
|
5958
|
+
}
|
|
5959
|
+
return { id: "cli_package", label: "CLI / package", checks };
|
|
5960
|
+
}
|
|
5961
|
+
function assessUserConfig(probes) {
|
|
5962
|
+
const configPath = probes.configFilePath();
|
|
5963
|
+
const displayConfigPath = displayUserPath(configPath);
|
|
5964
|
+
const exists = probes.pathExists(configPath);
|
|
5965
|
+
const config = probes.loadConfig();
|
|
5966
|
+
const checks = [
|
|
5967
|
+
check({
|
|
5968
|
+
id: "config_file",
|
|
5969
|
+
label: "~/.kynver/config.json present",
|
|
5970
|
+
status: exists ? "pass" : "fail",
|
|
5971
|
+
summary: exists ? `Found ${displayConfigPath}` : `Missing ${displayConfigPath}`,
|
|
5972
|
+
remediation: exists ? void 0 : "Run `kynver setup --api-base-url <url> --agent-os-id <id> --repo <path>`.",
|
|
5973
|
+
details: { configPath: displayConfigPath }
|
|
5974
|
+
})
|
|
5975
|
+
];
|
|
5976
|
+
if (exists) {
|
|
5977
|
+
const apiBaseUrl = config.apiBaseUrl?.trim();
|
|
5978
|
+
const agentOsId = config.agentOsId?.trim();
|
|
5979
|
+
const defaultRepo = config.defaultRepo?.trim();
|
|
5980
|
+
const displayDefaultRepo = defaultRepo ? displayUserPath(defaultRepo) : null;
|
|
5981
|
+
checks.push(
|
|
5982
|
+
check({
|
|
5983
|
+
id: "config_api_base_url",
|
|
5984
|
+
label: "Default API base URL",
|
|
5985
|
+
status: apiBaseUrl ? "pass" : "warn",
|
|
5986
|
+
summary: apiBaseUrl ?? "Not set in config (KYNVER_API_URL / --base-url required per command)",
|
|
5987
|
+
remediation: apiBaseUrl ? void 0 : "Set `apiBaseUrl` via `kynver setup --api-base-url https://\u2026`.",
|
|
5988
|
+
details: { apiBaseUrl: apiBaseUrl ?? null }
|
|
5989
|
+
}),
|
|
5990
|
+
check({
|
|
5991
|
+
id: "config_agent_os_id",
|
|
5992
|
+
label: "Default AgentOS id",
|
|
5993
|
+
status: agentOsId ? "pass" : "warn",
|
|
5994
|
+
summary: agentOsId ?? "Not set (pass --agent-os-id on daemon/dispatch/worker commands)",
|
|
5995
|
+
remediation: agentOsId ? void 0 : "Set `agentOsId` via `kynver setup --agent-os-id <uuid>`.",
|
|
5996
|
+
details: { agentOsId: agentOsId ?? null, agentOsSlug: config.agentOsSlug ?? null }
|
|
5997
|
+
}),
|
|
5998
|
+
check({
|
|
5999
|
+
id: "config_default_repo",
|
|
6000
|
+
label: "Default repo path",
|
|
6001
|
+
status: defaultRepo ? "pass" : "warn",
|
|
6002
|
+
summary: displayDefaultRepo ?? "Not set (pass --repo on `kynver run create`)",
|
|
6003
|
+
remediation: defaultRepo ? void 0 : "Set `defaultRepo` via `kynver setup --repo /path/to/repo`.",
|
|
6004
|
+
details: {
|
|
6005
|
+
defaultRepo: displayDefaultRepo,
|
|
6006
|
+
harnessRoot: config.harnessRoot ? redactHomePath(config.harnessRoot) : null
|
|
6007
|
+
}
|
|
6008
|
+
})
|
|
6009
|
+
);
|
|
6010
|
+
}
|
|
6011
|
+
return { id: "user_config", label: "User config (~/.kynver)", checks };
|
|
6012
|
+
}
|
|
6013
|
+
function assessRunnerToken(probes) {
|
|
6014
|
+
const config = probes.loadConfig();
|
|
6015
|
+
const targetAgentOsId = config.agentOsId?.trim();
|
|
6016
|
+
const creds = probes.readCredentials();
|
|
6017
|
+
const env = probes.envSnapshot();
|
|
6018
|
+
const credPath = probes.credentialsFilePath();
|
|
6019
|
+
const displayCredPath = displayUserPath(credPath);
|
|
6020
|
+
const envToken = env.kynverRunnerTokenPrefix;
|
|
6021
|
+
const savedToken = creds.runnerTokenPrefix;
|
|
6022
|
+
const savedAgentOsId = creds.runnerTokenAgentOsId;
|
|
6023
|
+
const scopedSaved = Boolean(savedToken) && (!targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId);
|
|
6024
|
+
const hasScoped = Boolean(envToken?.startsWith("krc1.")) || scopedSaved && savedToken?.startsWith("krc1.");
|
|
6025
|
+
const checks = [
|
|
6026
|
+
check({
|
|
6027
|
+
id: "runner_token_scoped",
|
|
6028
|
+
label: "Scoped runner token (krc1.*) ready",
|
|
6029
|
+
status: hasScoped ? "pass" : "fail",
|
|
6030
|
+
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",
|
|
6031
|
+
remediation: hasScoped ? void 0 : "Run `kynver login` then `kynver runner credential --agent-os-id <id>` (or `kynver setup` with API key).",
|
|
6032
|
+
details: {
|
|
6033
|
+
source: envToken ? "env" : savedToken ? "credentials" : "none",
|
|
6034
|
+
tokenPrefix: envToken ?? (scopedSaved ? savedToken : void 0),
|
|
6035
|
+
agentOsId: targetAgentOsId ?? savedAgentOsId ?? null,
|
|
6036
|
+
credentialsPath: displayCredPath
|
|
6037
|
+
}
|
|
6038
|
+
}),
|
|
6039
|
+
check({
|
|
6040
|
+
id: "runner_token_agent_os_match",
|
|
6041
|
+
label: "Saved runner token matches configured agentOsId",
|
|
6042
|
+
status: !savedToken || !targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId ? "pass" : "warn",
|
|
6043
|
+
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}`,
|
|
6044
|
+
remediation: savedToken && targetAgentOsId && savedAgentOsId && savedAgentOsId !== targetAgentOsId ? "`kynver runner credential --agent-os-id <configured-id>` to mint a workspace-bound token." : void 0,
|
|
6045
|
+
details: { configuredAgentOsId: targetAgentOsId ?? null, savedAgentOsId: savedAgentOsId ?? null }
|
|
6046
|
+
}),
|
|
6047
|
+
check({
|
|
6048
|
+
id: "runner_api_key_for_refresh",
|
|
6049
|
+
label: "API key available for token refresh",
|
|
6050
|
+
status: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY?.trim()) ? "pass" : "warn",
|
|
6051
|
+
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",
|
|
6052
|
+
remediation: creds.hasApiKey || process.env.KYNVER_API_KEY ? void 0 : "Run `kynver login --api-key \u2026`.",
|
|
6053
|
+
details: { credentialsPath: displayCredPath, hasApiKey: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY) }
|
|
6054
|
+
})
|
|
6055
|
+
];
|
|
6056
|
+
return { id: "runner_token", label: "Runner token readiness", checks };
|
|
6057
|
+
}
|
|
6058
|
+
function assessVercelCli(probes) {
|
|
6059
|
+
const version = probes.vercelVersion();
|
|
6060
|
+
const whoami = probes.vercelWhoami();
|
|
6061
|
+
const installed = version.ok;
|
|
6062
|
+
return {
|
|
6063
|
+
id: "vercel_cli",
|
|
6064
|
+
label: "Vercel CLI",
|
|
6065
|
+
checks: [
|
|
6066
|
+
check({
|
|
6067
|
+
id: "vercel_installed",
|
|
6068
|
+
label: "Vercel CLI installed",
|
|
6069
|
+
status: installed ? "pass" : "warn",
|
|
6070
|
+
summary: installed ? version.stdout || "vercel CLI found" : version.error ?? "vercel not found on PATH",
|
|
6071
|
+
remediation: installed ? void 0 : "Install Vercel CLI (`npm i -g vercel`) for deploy evidence and env pulls.",
|
|
6072
|
+
details: { stderr: version.stderr || null }
|
|
6073
|
+
}),
|
|
6074
|
+
check({
|
|
6075
|
+
id: "vercel_authenticated",
|
|
6076
|
+
label: "Vercel CLI authenticated",
|
|
6077
|
+
status: !installed ? "warn" : whoami.ok ? "pass" : "warn",
|
|
6078
|
+
summary: !installed ? "Skipped \u2014 Vercel CLI not installed" : whoami.ok ? `Authenticated as ${whoami.stdout}` : whoami.stderr || whoami.error || "Not logged in",
|
|
6079
|
+
remediation: installed && !whoami.ok ? "Run `vercel login` and link the Kynver project if needed." : void 0,
|
|
6080
|
+
details: { account: whoami.ok ? whoami.stdout : null }
|
|
6081
|
+
})
|
|
6082
|
+
]
|
|
6083
|
+
};
|
|
6084
|
+
}
|
|
6085
|
+
function assessHarnessDirs(probes) {
|
|
6086
|
+
const harnessRoot = probes.harnessRoot();
|
|
6087
|
+
const runsDir = path34.join(harnessRoot, "runs");
|
|
6088
|
+
const worktreesDir = path34.join(harnessRoot, "worktrees");
|
|
6089
|
+
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
6090
|
+
const displayRunsDir = redactHomePath(runsDir);
|
|
6091
|
+
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
6092
|
+
const runsExists = probes.pathExists(runsDir);
|
|
6093
|
+
const worktreesExists = probes.pathExists(worktreesDir);
|
|
6094
|
+
return {
|
|
6095
|
+
id: "harness_dirs",
|
|
6096
|
+
label: "Harness / daemon directories",
|
|
6097
|
+
checks: [
|
|
6098
|
+
check({
|
|
6099
|
+
id: "harness_root",
|
|
6100
|
+
label: "Harness root resolved",
|
|
6101
|
+
status: "pass",
|
|
6102
|
+
summary: displayHarnessRoot,
|
|
6103
|
+
details: { harnessRoot: displayHarnessRoot }
|
|
6104
|
+
}),
|
|
6105
|
+
check({
|
|
6106
|
+
id: "harness_runs_dir",
|
|
6107
|
+
label: "Runs directory ready",
|
|
6108
|
+
status: runsExists && probes.pathWritable(runsDir) ? "pass" : runsExists ? "warn" : "warn",
|
|
6109
|
+
summary: runsExists ? probes.pathWritable(runsDir) ? `Writable ${displayRunsDir}` : `Exists but not writable: ${displayRunsDir}` : `Will be created on first run: ${displayRunsDir}`,
|
|
6110
|
+
remediation: runsExists && !probes.pathWritable(runsDir) ? `Fix permissions on ${displayRunsDir} or set KYNVER_HARNESS_ROOT to a writable path.` : void 0,
|
|
6111
|
+
details: { runsDir: displayRunsDir, exists: runsExists, writable: probes.pathWritable(runsDir) }
|
|
6112
|
+
}),
|
|
6113
|
+
check({
|
|
6114
|
+
id: "harness_worktrees_dir",
|
|
6115
|
+
label: "Worktrees directory ready",
|
|
6116
|
+
status: worktreesExists && probes.pathWritable(worktreesDir) ? "pass" : "warn",
|
|
6117
|
+
summary: worktreesExists ? probes.pathWritable(worktreesDir) ? `Writable ${displayWorktreesDir}` : `Exists but not writable: ${displayWorktreesDir}` : `Will be created on first worker: ${displayWorktreesDir}`,
|
|
6118
|
+
remediation: worktreesExists && !probes.pathWritable(worktreesDir) ? `Fix permissions on ${displayWorktreesDir}.` : void 0,
|
|
6119
|
+
details: { worktreesDir: displayWorktreesDir, exists: worktreesExists, writable: probes.pathWritable(worktreesDir) }
|
|
6120
|
+
})
|
|
6121
|
+
]
|
|
6122
|
+
};
|
|
6123
|
+
}
|
|
6124
|
+
function assessCallbackAuth(probes) {
|
|
6125
|
+
const config = probes.loadConfig();
|
|
6126
|
+
const env = probes.envSnapshot();
|
|
6127
|
+
const baseUrl = env.kynverApiUrl ?? config.apiBaseUrl?.trim() ?? env.openclawCronFireBaseUrl;
|
|
6128
|
+
const usingLegacyBase = !env.kynverApiUrl && !config.apiBaseUrl && Boolean(env.openclawCronFireBaseUrl);
|
|
6129
|
+
const legacySecret = env.openclawCronSecret || env.kynverRuntimeSecret;
|
|
6130
|
+
const envScoped = env.kynverRunnerTokenPrefix?.startsWith("krc1.");
|
|
6131
|
+
const creds = probes.readCredentials();
|
|
6132
|
+
const savedScoped = creds.runnerTokenPrefix?.startsWith("krc1.");
|
|
6133
|
+
const checks = [
|
|
6134
|
+
check({
|
|
6135
|
+
id: "callback_base_url",
|
|
6136
|
+
label: "Callback base URL configured",
|
|
6137
|
+
status: baseUrl ? usingLegacyBase ? "warn" : "pass" : "fail",
|
|
6138
|
+
summary: baseUrl ? usingLegacyBase ? `Using legacy OPENCLAW_CRON_FIRE_BASE_URL (${baseUrl})` : baseUrl : "No KYNVER_API_URL, config apiBaseUrl, or legacy OpenClaw base URL",
|
|
6139
|
+
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`.",
|
|
6140
|
+
details: {
|
|
6141
|
+
source: env.kynverApiUrl ? "KYNVER_API_URL" : config.apiBaseUrl ? "config.apiBaseUrl" : env.openclawCronFireBaseUrl ? "OPENCLAW_CRON_FIRE_BASE_URL" : "none",
|
|
6142
|
+
baseUrl: baseUrl ?? null
|
|
6143
|
+
}
|
|
6144
|
+
}),
|
|
6145
|
+
check({
|
|
6146
|
+
id: "callback_auth_mode",
|
|
6147
|
+
label: "Callback auth uses scoped runner token",
|
|
6148
|
+
status: envScoped || savedScoped ? "pass" : legacySecret ? "warn" : "fail",
|
|
6149
|
+
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",
|
|
6150
|
+
remediation: envScoped || savedScoped ? void 0 : "Mint a scoped token: `kynver runner credential --agent-os-id <id>`. Avoid shared OPENCLAW_CRON_SECRET on user runners.",
|
|
6151
|
+
details: {
|
|
6152
|
+
mode: envScoped || savedScoped ? "scoped" : legacySecret ? "legacy_global_secret" : "missing",
|
|
6153
|
+
legacyHeadersWhenNotScoped: ["X-OpenClaw-Cron-Secret", "X-Kynver-Runtime-Secret"]
|
|
6154
|
+
}
|
|
6155
|
+
})
|
|
6156
|
+
];
|
|
6157
|
+
return { id: "callback_auth", label: "Callback auth / config", checks };
|
|
6158
|
+
}
|
|
6159
|
+
function assessOpenclawHotspots(probes) {
|
|
6160
|
+
const env = probes.envSnapshot();
|
|
6161
|
+
const harnessRoot = probes.harnessRoot();
|
|
6162
|
+
const legacyRoot = probes.legacyOpenclawHarnessRoot();
|
|
6163
|
+
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
6164
|
+
const displayLegacyRoot = redactHomePath(legacyRoot);
|
|
6165
|
+
const displayOpusHarnessRoot = env.opusHarnessRoot ? redactHomePath(env.opusHarnessRoot) : null;
|
|
6166
|
+
const legacyHarnessActive = harnessRoot === legacyRoot && probes.pathExists(legacyRoot);
|
|
6167
|
+
const schedulerOpenclaw = !env.kynverSchedulerProvider || env.kynverSchedulerProvider === "openclaw-cron";
|
|
6168
|
+
const checks = [
|
|
6169
|
+
check({
|
|
6170
|
+
id: "hotspot_legacy_harness_root",
|
|
6171
|
+
label: "Legacy ~/.openclaw/harness still active",
|
|
6172
|
+
status: legacyHarnessActive ? "warn" : "pass",
|
|
6173
|
+
summary: legacyHarnessActive ? `Harness root is legacy ${displayLegacyRoot}` : env.opusHarnessRoot ? `OPUS_HARNESS_ROOT override in use (${displayOpusHarnessRoot})` : `Using ${displayHarnessRoot}`,
|
|
6174
|
+
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,
|
|
6175
|
+
details: { harnessRoot: displayHarnessRoot, legacyRoot: displayLegacyRoot, opusHarnessRoot: displayOpusHarnessRoot }
|
|
6176
|
+
}),
|
|
6177
|
+
check({
|
|
6178
|
+
id: "hotspot_openclaw_env_secrets",
|
|
6179
|
+
label: "OpenClaw deployment secrets in runner env",
|
|
6180
|
+
status: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "warn" : "pass",
|
|
6181
|
+
summary: env.openclawCronSecret || env.openclawCronFireBaseUrl ? [
|
|
6182
|
+
env.openclawCronSecret ? "OPENCLAW_CRON_SECRET set" : null,
|
|
6183
|
+
env.openclawCronFireBaseUrl ? "OPENCLAW_CRON_FIRE_BASE_URL set" : null
|
|
6184
|
+
].filter(Boolean).join("; ") : "No OpenClaw cron env overrides on this runner",
|
|
6185
|
+
remediation: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "Move to KYNVER_API_URL + scoped runner tokens; unset OpenClaw cron env on user-hosted runners." : void 0
|
|
6186
|
+
}),
|
|
6187
|
+
check({
|
|
6188
|
+
id: "hotspot_openclaw_scheduler",
|
|
6189
|
+
label: "openclaw-cron scheduler dependency (deployment)",
|
|
6190
|
+
status: schedulerOpenclaw ? "warn" : "pass",
|
|
6191
|
+
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}`,
|
|
6192
|
+
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,
|
|
6193
|
+
details: { schedulerProvider: env.kynverSchedulerProvider ?? null }
|
|
6194
|
+
}),
|
|
6195
|
+
check({
|
|
6196
|
+
id: "hotspot_lease_source_names",
|
|
6197
|
+
label: "Harness lease/completion source names",
|
|
6198
|
+
status: "pass",
|
|
6199
|
+
summary: "Runtime uses kynver-harness:* lease owners and completion source kynver-harness (OpenClaw names retired in runtime)",
|
|
6200
|
+
details: {
|
|
6201
|
+
leaseOwnerPattern: "kynver-harness:<runId>",
|
|
6202
|
+
completionSource: "kynver-harness",
|
|
6203
|
+
note: "OpenClaw adapter remains optional in packages/kynver-openclaw-agent-os only"
|
|
6204
|
+
}
|
|
6205
|
+
})
|
|
6206
|
+
];
|
|
6207
|
+
return { id: "openclaw_hotspots", label: "OpenClaw dependency hotspots", checks };
|
|
6208
|
+
}
|
|
6209
|
+
function assessRuntimeTakeoverReadiness(probes = defaultRuntimeTakeoverProbes) {
|
|
6210
|
+
const sections = [
|
|
6211
|
+
assessCliPackage(probes),
|
|
6212
|
+
assessUserConfig(probes),
|
|
6213
|
+
assessRunnerToken(probes),
|
|
6214
|
+
assessVercelCli(probes),
|
|
6215
|
+
assessHarnessDirs(probes),
|
|
6216
|
+
assessCallbackAuth(probes),
|
|
6217
|
+
assessOpenclawHotspots(probes)
|
|
6218
|
+
];
|
|
6219
|
+
const counts = summarizeCounts(sections);
|
|
6220
|
+
const ready = counts.fail === 0;
|
|
6221
|
+
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.`;
|
|
6222
|
+
return {
|
|
6223
|
+
command: "doctor runtime-takeover",
|
|
6224
|
+
ready,
|
|
6225
|
+
summary,
|
|
6226
|
+
counts,
|
|
6227
|
+
sections
|
|
6228
|
+
};
|
|
6229
|
+
}
|
|
6230
|
+
|
|
6231
|
+
// src/doctor/runtime-takeover-cli.ts
|
|
6232
|
+
function runRuntimeTakeoverDoctorCli() {
|
|
6233
|
+
const report = assessRuntimeTakeoverReadiness();
|
|
6234
|
+
console.log(JSON.stringify(report, null, 2));
|
|
6235
|
+
if (!report.ready) {
|
|
6236
|
+
process.exitCode = 1;
|
|
6237
|
+
}
|
|
6238
|
+
}
|
|
6239
|
+
|
|
5758
6240
|
// src/cli.ts
|
|
5759
6241
|
function isHelpFlag(arg) {
|
|
5760
6242
|
return arg === "help" || arg === "--help" || arg === "-h";
|
|
@@ -5797,7 +6279,8 @@ function usage(code = 0) {
|
|
|
5797
6279
|
" kynver monitor list",
|
|
5798
6280
|
" kynver monitor tick --run RUN_ID [--name worker] [--agent-os-id AOS_ID] [--auto-complete] [--renew-leases]",
|
|
5799
6281
|
" kynver monitor auto-complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--base-url URL] [--secret SECRET]",
|
|
5800
|
-
" kynver monitor run-loop --run RUN_ID --monitor-id ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS] [--auto-complete] [--renew-leases]"
|
|
6282
|
+
" kynver monitor run-loop --run RUN_ID --monitor-id ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS] [--auto-complete] [--renew-leases]",
|
|
6283
|
+
" kynver doctor runtime-takeover"
|
|
5801
6284
|
].join("\n")
|
|
5802
6285
|
);
|
|
5803
6286
|
process.exit(code);
|
|
@@ -5808,7 +6291,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
5808
6291
|
const scope = argv.shift();
|
|
5809
6292
|
let action;
|
|
5810
6293
|
let rest;
|
|
5811
|
-
if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "monitor") {
|
|
6294
|
+
if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner" || scope === "monitor" || scope === "doctor") {
|
|
5812
6295
|
action = argv.shift();
|
|
5813
6296
|
rest = argv;
|
|
5814
6297
|
} else {
|
|
@@ -5833,6 +6316,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
5833
6316
|
unknownCommand("plan", `outbox ${outboxAction ?? ""}`.trim());
|
|
5834
6317
|
}
|
|
5835
6318
|
if (scope === "cleanup") return runCleanupCli(args);
|
|
6319
|
+
if (scope === "doctor" && action === "runtime-takeover") return runRuntimeTakeoverDoctorCli();
|
|
5836
6320
|
if (scope === "run" && action === "create") return createRun(args);
|
|
5837
6321
|
if (scope === "run" && action === "list") return listRuns();
|
|
5838
6322
|
if (scope === "run" && action === "status") return runStatus(args);
|
|
@@ -5865,6 +6349,295 @@ if (isCliEntry) {
|
|
|
5865
6349
|
process.exit(1);
|
|
5866
6350
|
});
|
|
5867
6351
|
}
|
|
6352
|
+
|
|
6353
|
+
// src/vercel/vercel-url.ts
|
|
6354
|
+
var VERCEL_HOST_RE = /(^|\.)vercel\.app$/i;
|
|
6355
|
+
function tryParseUrl(raw) {
|
|
6356
|
+
const trimmed = raw.trim();
|
|
6357
|
+
if (!trimmed) return null;
|
|
6358
|
+
try {
|
|
6359
|
+
return new URL(trimmed);
|
|
6360
|
+
} catch {
|
|
6361
|
+
return null;
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
function parseDashboardDeployment(url) {
|
|
6365
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
6366
|
+
if (parts.length < 3) return null;
|
|
6367
|
+
const deploymentId = parts[parts.length - 1]?.trim();
|
|
6368
|
+
if (!deploymentId || deploymentId === "deployments") return null;
|
|
6369
|
+
return deploymentId;
|
|
6370
|
+
}
|
|
6371
|
+
function parseDeploymentsSegment(url) {
|
|
6372
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
6373
|
+
const idx = parts.indexOf("deployments");
|
|
6374
|
+
if (idx < 0 || idx >= parts.length - 1) return null;
|
|
6375
|
+
const deploymentId = parts[idx + 1]?.trim();
|
|
6376
|
+
return deploymentId || null;
|
|
6377
|
+
}
|
|
6378
|
+
function classifyVercelUrl(raw) {
|
|
6379
|
+
const empty = {
|
|
6380
|
+
kind: "unknown",
|
|
6381
|
+
previewUrl: null,
|
|
6382
|
+
inspectTarget: null,
|
|
6383
|
+
deploymentId: null
|
|
6384
|
+
};
|
|
6385
|
+
const trimmed = raw.trim();
|
|
6386
|
+
if (!trimmed) return empty;
|
|
6387
|
+
if (/^dpl_[a-z0-9]+$/i.test(trimmed)) {
|
|
6388
|
+
return {
|
|
6389
|
+
kind: "deployment_id",
|
|
6390
|
+
previewUrl: null,
|
|
6391
|
+
inspectTarget: trimmed,
|
|
6392
|
+
deploymentId: trimmed
|
|
6393
|
+
};
|
|
6394
|
+
}
|
|
6395
|
+
const url = tryParseUrl(trimmed);
|
|
6396
|
+
if (!url) return empty;
|
|
6397
|
+
if (VERCEL_HOST_RE.test(url.hostname)) {
|
|
6398
|
+
const hostUrl = url.origin;
|
|
6399
|
+
return {
|
|
6400
|
+
kind: "deployment_host",
|
|
6401
|
+
previewUrl: hostUrl,
|
|
6402
|
+
inspectTarget: hostUrl,
|
|
6403
|
+
deploymentId: null
|
|
6404
|
+
};
|
|
6405
|
+
}
|
|
6406
|
+
if (url.hostname === "vercel.com" || url.hostname.endsWith(".vercel.com")) {
|
|
6407
|
+
const deploymentId = parseDeploymentsSegment(url) ?? parseDashboardDeployment(url);
|
|
6408
|
+
return {
|
|
6409
|
+
kind: "dashboard",
|
|
6410
|
+
previewUrl: null,
|
|
6411
|
+
inspectTarget: deploymentId,
|
|
6412
|
+
deploymentId
|
|
6413
|
+
};
|
|
6414
|
+
}
|
|
6415
|
+
return empty;
|
|
6416
|
+
}
|
|
6417
|
+
function isDashboardVercelUrl(raw) {
|
|
6418
|
+
return classifyVercelUrl(raw).kind === "dashboard";
|
|
6419
|
+
}
|
|
6420
|
+
|
|
6421
|
+
// src/vercel/vercel-github-status.ts
|
|
6422
|
+
var VERCEL_CONTEXT_RE = /vercel/i;
|
|
6423
|
+
function normalizeGitHubStatusState(state) {
|
|
6424
|
+
const value = typeof state === "string" ? state.trim().toLowerCase() : "";
|
|
6425
|
+
if (value === "success") return "success";
|
|
6426
|
+
if (value === "pending") return "pending";
|
|
6427
|
+
if (value === "failure") return "failure";
|
|
6428
|
+
if (value === "error") return "error";
|
|
6429
|
+
return "unknown";
|
|
6430
|
+
}
|
|
6431
|
+
function isVercelStatusContext(context) {
|
|
6432
|
+
const label = typeof context === "string" ? context.trim() : "";
|
|
6433
|
+
return Boolean(label && VERCEL_CONTEXT_RE.test(label));
|
|
6434
|
+
}
|
|
6435
|
+
function pickVercelStatusContext(statuses) {
|
|
6436
|
+
const rows = Array.isArray(statuses) ? statuses : [];
|
|
6437
|
+
const vercelRows = rows.filter((row) => isVercelStatusContext(row.context));
|
|
6438
|
+
if (vercelRows.length === 0) return null;
|
|
6439
|
+
const score = (state) => {
|
|
6440
|
+
if (state === "failure" || state === "error") return 0;
|
|
6441
|
+
if (state === "pending") return 1;
|
|
6442
|
+
if (state === "success") return 2;
|
|
6443
|
+
return 1;
|
|
6444
|
+
};
|
|
6445
|
+
let best = null;
|
|
6446
|
+
let bestScore = -1;
|
|
6447
|
+
for (const row of vercelRows) {
|
|
6448
|
+
const rowScore = score(normalizeGitHubStatusState(row.state));
|
|
6449
|
+
if (rowScore > bestScore) {
|
|
6450
|
+
best = row;
|
|
6451
|
+
bestScore = rowScore;
|
|
6452
|
+
}
|
|
6453
|
+
}
|
|
6454
|
+
if (!best) return null;
|
|
6455
|
+
const targetUrl = typeof best.target_url === "string" && best.target_url.trim() ? best.target_url.trim() : null;
|
|
6456
|
+
const classified = targetUrl ? classifyVercelUrl(targetUrl) : null;
|
|
6457
|
+
return {
|
|
6458
|
+
context: String(best.context ?? "Vercel").trim(),
|
|
6459
|
+
state: normalizeGitHubStatusState(best.state),
|
|
6460
|
+
targetUrl,
|
|
6461
|
+
description: typeof best.description === "string" && best.description.trim() ? best.description.trim() : null,
|
|
6462
|
+
deploymentId: classified?.deploymentId ?? null,
|
|
6463
|
+
previewUrl: classified?.previewUrl ?? null,
|
|
6464
|
+
dashboardUrl: classified?.kind === "dashboard" ? targetUrl : null
|
|
6465
|
+
};
|
|
6466
|
+
}
|
|
6467
|
+
|
|
6468
|
+
// src/vercel/vercel-evidence.ts
|
|
6469
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
6470
|
+
var DEFAULT_INSPECT_WAIT_SECONDS = 120;
|
|
6471
|
+
function mapGitHubStateToEvidence(state) {
|
|
6472
|
+
if (state === "success") return "ready";
|
|
6473
|
+
if (state === "pending") return "building";
|
|
6474
|
+
if (state === "failure" || state === "error") return "error";
|
|
6475
|
+
return "unavailable";
|
|
6476
|
+
}
|
|
6477
|
+
function evidenceSummary(parts) {
|
|
6478
|
+
return parts.filter(Boolean).join("; ");
|
|
6479
|
+
}
|
|
6480
|
+
function resolveVercelInspectTarget(rawUrl) {
|
|
6481
|
+
const trimmed = typeof rawUrl === "string" ? rawUrl.trim() : "";
|
|
6482
|
+
if (!trimmed) {
|
|
6483
|
+
return { target: null, classified: null, reason: "missing target_url" };
|
|
6484
|
+
}
|
|
6485
|
+
const classified = classifyVercelUrl(trimmed);
|
|
6486
|
+
if (classified.inspectTarget) {
|
|
6487
|
+
return { target: classified.inspectTarget, classified, reason: null };
|
|
6488
|
+
}
|
|
6489
|
+
if (classified.kind === "dashboard") {
|
|
6490
|
+
return {
|
|
6491
|
+
target: null,
|
|
6492
|
+
classified,
|
|
6493
|
+
reason: "dashboard URL is not valid for vercel inspect"
|
|
6494
|
+
};
|
|
6495
|
+
}
|
|
6496
|
+
return { target: null, classified, reason: "unrecognized Vercel URL" };
|
|
6497
|
+
}
|
|
6498
|
+
function evidenceFromGitHubVercelStatus(statuses, options = {}) {
|
|
6499
|
+
const observedAt = options.observedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
6500
|
+
const row = pickVercelStatusContext(statuses);
|
|
6501
|
+
if (!row) {
|
|
6502
|
+
return {
|
|
6503
|
+
status: "not_run",
|
|
6504
|
+
previewUrl: null,
|
|
6505
|
+
deploymentUrl: null,
|
|
6506
|
+
summary: "No Vercel GitHub status context on commit",
|
|
6507
|
+
observedAt,
|
|
6508
|
+
inspectSkipped: true,
|
|
6509
|
+
inspectReason: "no_vercel_status_context",
|
|
6510
|
+
githubState: null,
|
|
6511
|
+
vercelContext: null
|
|
6512
|
+
};
|
|
6513
|
+
}
|
|
6514
|
+
const status = mapGitHubStateToEvidence(row.state);
|
|
6515
|
+
const previewUrl = row.previewUrl ?? row.dashboardUrl;
|
|
6516
|
+
const deploymentUrl = row.previewUrl ?? row.dashboardUrl;
|
|
6517
|
+
return {
|
|
6518
|
+
status,
|
|
6519
|
+
previewUrl,
|
|
6520
|
+
deploymentUrl,
|
|
6521
|
+
summary: evidenceSummary([
|
|
6522
|
+
`GitHub ${row.context}=${row.state}`,
|
|
6523
|
+
row.description ?? "",
|
|
6524
|
+
row.dashboardUrl ? "dashboard target_url (inspect skipped)" : ""
|
|
6525
|
+
]),
|
|
6526
|
+
observedAt,
|
|
6527
|
+
inspectSkipped: true,
|
|
6528
|
+
inspectReason: row.dashboardUrl && row.state === "success" ? "trusted_github_status_dashboard_url" : row.dashboardUrl ? "dashboard_url_not_inspectable" : "github_status_only",
|
|
6529
|
+
githubState: row.state,
|
|
6530
|
+
vercelContext: row.context
|
|
6531
|
+
};
|
|
6532
|
+
}
|
|
6533
|
+
function defaultRunVercelInspect(target, waitSeconds) {
|
|
6534
|
+
const args = ["inspect", target, "--wait", String(waitSeconds)];
|
|
6535
|
+
const result = spawnSync4("vercel", args, {
|
|
6536
|
+
encoding: "utf8",
|
|
6537
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
6538
|
+
});
|
|
6539
|
+
if (result.error) {
|
|
6540
|
+
return {
|
|
6541
|
+
ok: false,
|
|
6542
|
+
exitCode: result.status,
|
|
6543
|
+
stdout: "",
|
|
6544
|
+
stderr: "",
|
|
6545
|
+
error: result.error.message
|
|
6546
|
+
};
|
|
6547
|
+
}
|
|
6548
|
+
return {
|
|
6549
|
+
ok: result.status === 0,
|
|
6550
|
+
exitCode: result.status ?? 1,
|
|
6551
|
+
stdout: (result.stdout ?? "").trim(),
|
|
6552
|
+
stderr: (result.stderr ?? "").trim(),
|
|
6553
|
+
error: null
|
|
6554
|
+
};
|
|
6555
|
+
}
|
|
6556
|
+
function parseInspectReady(stdout, stderr) {
|
|
6557
|
+
const blob = `${stdout}
|
|
6558
|
+
${stderr}`.toLowerCase();
|
|
6559
|
+
if (/\b(status|state)\s*[:=]\s*(ready|completed)\b/.test(blob)) return true;
|
|
6560
|
+
if (/\bready\b/.test(blob) && !/\bnot ready\b/.test(blob)) return true;
|
|
6561
|
+
return false;
|
|
6562
|
+
}
|
|
6563
|
+
function collectVercelEvidence(input) {
|
|
6564
|
+
const observedAt = input.observedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
6565
|
+
const base = evidenceFromGitHubVercelStatus(input.statuses ?? [], { observedAt });
|
|
6566
|
+
const row = pickVercelStatusContext(input.statuses ?? []);
|
|
6567
|
+
if (!row) return base;
|
|
6568
|
+
if (row.state === "success") {
|
|
6569
|
+
return {
|
|
6570
|
+
...base,
|
|
6571
|
+
inspectSkipped: true,
|
|
6572
|
+
inspectReason: row.dashboardUrl ? "trusted_github_status_dashboard_url" : "trusted_github_status"
|
|
6573
|
+
};
|
|
6574
|
+
}
|
|
6575
|
+
if (!input.allowInspect) {
|
|
6576
|
+
return {
|
|
6577
|
+
...base,
|
|
6578
|
+
inspectSkipped: true,
|
|
6579
|
+
inspectReason: "inspect_disabled"
|
|
6580
|
+
};
|
|
6581
|
+
}
|
|
6582
|
+
const { target, reason } = resolveVercelInspectTarget(row.targetUrl);
|
|
6583
|
+
if (!target) {
|
|
6584
|
+
return {
|
|
6585
|
+
...base,
|
|
6586
|
+
inspectSkipped: true,
|
|
6587
|
+
inspectReason: reason ?? "not_inspectable",
|
|
6588
|
+
summary: evidenceSummary([
|
|
6589
|
+
base.summary ?? "",
|
|
6590
|
+
reason ?? "skipped vercel inspect"
|
|
6591
|
+
])
|
|
6592
|
+
};
|
|
6593
|
+
}
|
|
6594
|
+
const waitSeconds = input.waitSeconds ?? DEFAULT_INSPECT_WAIT_SECONDS;
|
|
6595
|
+
const runInspect = input.runInspect ?? defaultRunVercelInspect;
|
|
6596
|
+
const inspected = runInspect(target, waitSeconds);
|
|
6597
|
+
const observed = (/* @__PURE__ */ new Date()).toISOString();
|
|
6598
|
+
if (inspected.error?.includes("ENOENT")) {
|
|
6599
|
+
return {
|
|
6600
|
+
status: "unavailable",
|
|
6601
|
+
previewUrl: base.previewUrl,
|
|
6602
|
+
deploymentUrl: base.deploymentUrl,
|
|
6603
|
+
summary: "vercel CLI not found on PATH",
|
|
6604
|
+
observedAt: observed,
|
|
6605
|
+
inspectSkipped: true,
|
|
6606
|
+
inspectReason: "vercel_cli_missing",
|
|
6607
|
+
githubState: row.state,
|
|
6608
|
+
vercelContext: row.context
|
|
6609
|
+
};
|
|
6610
|
+
}
|
|
6611
|
+
if (!inspected.ok) {
|
|
6612
|
+
const detail = [inspected.stderr, inspected.stdout, inspected.error].filter(Boolean).join("\n").trim().slice(0, 500);
|
|
6613
|
+
return {
|
|
6614
|
+
status: row.state === "pending" ? "building" : "error",
|
|
6615
|
+
previewUrl: base.previewUrl,
|
|
6616
|
+
deploymentUrl: target.startsWith("http") ? target : base.deploymentUrl,
|
|
6617
|
+
summary: evidenceSummary([
|
|
6618
|
+
`vercel inspect ${target} failed (exit ${inspected.exitCode ?? "?"})`,
|
|
6619
|
+
detail
|
|
6620
|
+
]),
|
|
6621
|
+
observedAt: observed,
|
|
6622
|
+
inspectSkipped: false,
|
|
6623
|
+
inspectReason: null,
|
|
6624
|
+
githubState: row.state,
|
|
6625
|
+
vercelContext: row.context
|
|
6626
|
+
};
|
|
6627
|
+
}
|
|
6628
|
+
const ready = parseInspectReady(inspected.stdout, inspected.stderr);
|
|
6629
|
+
return {
|
|
6630
|
+
status: ready ? "ready" : row.state === "pending" ? "building" : "error",
|
|
6631
|
+
previewUrl: target.startsWith("http") ? target : base.previewUrl,
|
|
6632
|
+
deploymentUrl: target.startsWith("http") ? target : base.deploymentUrl,
|
|
6633
|
+
summary: ready ? `vercel inspect ${target} ready` : `vercel inspect ${target} completed without ready signal`,
|
|
6634
|
+
observedAt: observed,
|
|
6635
|
+
inspectSkipped: false,
|
|
6636
|
+
inspectReason: null,
|
|
6637
|
+
githubState: row.state,
|
|
6638
|
+
vercelContext: row.context
|
|
6639
|
+
};
|
|
6640
|
+
}
|
|
5868
6641
|
export {
|
|
5869
6642
|
DEFAULT_DISPATCH_LEASE_MS,
|
|
5870
6643
|
PACKAGE_VERSION,
|
|
@@ -5877,7 +6650,9 @@ export {
|
|
|
5877
6650
|
autoCompleteWorkerCli,
|
|
5878
6651
|
buildDispatchTaskText,
|
|
5879
6652
|
buildPrompt,
|
|
6653
|
+
classifyVercelUrl,
|
|
5880
6654
|
classifyWorkerHealth,
|
|
6655
|
+
collectVercelEvidence,
|
|
5881
6656
|
completeWorker,
|
|
5882
6657
|
computeAttention,
|
|
5883
6658
|
computeWorkerStatus,
|
|
@@ -5886,15 +6661,18 @@ export {
|
|
|
5886
6661
|
dispatchRun,
|
|
5887
6662
|
drainPlanOutbox,
|
|
5888
6663
|
ensurePrReadyHandoff,
|
|
6664
|
+
evidenceFromGitHubVercelStatus,
|
|
5889
6665
|
extractPlanOutboxFromTask,
|
|
5890
6666
|
extractPrUrlFromText,
|
|
5891
6667
|
formatPlanOutboxHandoffBlock,
|
|
5892
6668
|
getHarnessPaths,
|
|
5893
6669
|
getMonitorStatus,
|
|
5894
6670
|
hashPlanBody,
|
|
6671
|
+
isDashboardVercelUrl,
|
|
5895
6672
|
isFinishedWorkerStatus,
|
|
5896
6673
|
isLandingBlockedWorkerStatus,
|
|
5897
6674
|
isTerminalHeartbeatPhase,
|
|
6675
|
+
isVercelStatusContext,
|
|
5898
6676
|
landingContractAttentionReason,
|
|
5899
6677
|
listMonitors,
|
|
5900
6678
|
listOutboxItems,
|
|
@@ -5908,6 +6686,7 @@ export {
|
|
|
5908
6686
|
parseHarnessStream,
|
|
5909
6687
|
parseHeartbeat,
|
|
5910
6688
|
persistPlan,
|
|
6689
|
+
pickVercelStatusContext,
|
|
5911
6690
|
postJson,
|
|
5912
6691
|
preflightCursorModel,
|
|
5913
6692
|
reconcileRunsCli,
|
|
@@ -5917,6 +6696,7 @@ export {
|
|
|
5917
6696
|
resolveCallbackSecret,
|
|
5918
6697
|
resolveCallbackSecretWithMint,
|
|
5919
6698
|
resolveHarnessRoot,
|
|
6699
|
+
resolveVercelInspectTarget,
|
|
5920
6700
|
runDaemon,
|
|
5921
6701
|
runMonitorTick,
|
|
5922
6702
|
runStatus,
|