@kynver-app/runtime 0.1.38 → 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 +696 -170
- package/dist/cli.js.map +4 -4
- package/dist/index.js +997 -173
- 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,11 +2064,24 @@ 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
|
|
|
2070
|
+
// src/completion-ack.ts
|
|
2071
|
+
function hasCompletionAck(worker) {
|
|
2072
|
+
return Boolean(worker.completionReportedAt?.trim());
|
|
2073
|
+
}
|
|
2074
|
+
function persistCompletionAck(worker, runId, fields) {
|
|
2075
|
+
worker.completionReportedAt = fields.completionReportedAt;
|
|
2076
|
+
worker.completionOutcome = fields.completionOutcome;
|
|
2077
|
+
if (fields.completionResponse !== void 0) {
|
|
2078
|
+
worker.completionResponse = fields.completionResponse;
|
|
2079
|
+
}
|
|
2080
|
+
saveWorker(runId, worker);
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2005
2083
|
// src/worker-ops.ts
|
|
2006
|
-
import
|
|
2084
|
+
import path11 from "node:path";
|
|
2007
2085
|
|
|
2008
2086
|
// src/pr-handoff/pr-handoff-assess.ts
|
|
2009
2087
|
var REVIEW_LANE_RULE = /^(lane:)?(review|deep_review|planning|landing)(:|$)/i;
|
|
@@ -2339,21 +2417,8 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
|
|
|
2339
2417
|
};
|
|
2340
2418
|
}
|
|
2341
2419
|
|
|
2342
|
-
// src/completion-ack.ts
|
|
2343
|
-
function hasCompletionAck(worker) {
|
|
2344
|
-
return Boolean(worker.completionReportedAt?.trim());
|
|
2345
|
-
}
|
|
2346
|
-
function persistCompletionAck(worker, runId, fields) {
|
|
2347
|
-
worker.completionReportedAt = fields.completionReportedAt;
|
|
2348
|
-
worker.completionOutcome = fields.completionOutcome;
|
|
2349
|
-
if (fields.completionResponse !== void 0) {
|
|
2350
|
-
worker.completionResponse = fields.completionResponse;
|
|
2351
|
-
}
|
|
2352
|
-
saveWorker(runId, worker);
|
|
2353
|
-
}
|
|
2354
|
-
|
|
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) {
|
|
@@ -2848,7 +2914,7 @@ async function autoCompleteWorker(raw) {
|
|
|
2848
2914
|
const maxTotalMs = args.maxTotalMs ?? DEFAULT_MAX_TOTAL_MS;
|
|
2849
2915
|
const completeAttempts = args.completeAttempts ?? DEFAULT_COMPLETE_ATTEMPTS;
|
|
2850
2916
|
const completeBackoffMs = args.completeBackoffMs ?? DEFAULT_COMPLETE_BACKOFF_MS;
|
|
2851
|
-
|
|
2917
|
+
let worker = loadWorker(args.run, args.name);
|
|
2852
2918
|
if (!worker.agentOsId || !worker.taskId) {
|
|
2853
2919
|
return {
|
|
2854
2920
|
worker: worker.name,
|
|
@@ -2858,8 +2924,29 @@ async function autoCompleteWorker(raw) {
|
|
|
2858
2924
|
reason: "worker has no agentOsId/taskId \u2014 nothing to attribute completion to"
|
|
2859
2925
|
};
|
|
2860
2926
|
}
|
|
2927
|
+
if (hasCompletionAck(worker)) {
|
|
2928
|
+
return {
|
|
2929
|
+
worker: worker.name,
|
|
2930
|
+
runId: worker.runId,
|
|
2931
|
+
outcome: "completed",
|
|
2932
|
+
httpStatus: 200,
|
|
2933
|
+
attempts: 0,
|
|
2934
|
+
reason: "completion-already-acknowledged"
|
|
2935
|
+
};
|
|
2936
|
+
}
|
|
2861
2937
|
const startMs = Date.now();
|
|
2862
2938
|
while (true) {
|
|
2939
|
+
worker = loadWorker(args.run, args.name);
|
|
2940
|
+
if (hasCompletionAck(worker)) {
|
|
2941
|
+
return {
|
|
2942
|
+
worker: worker.name,
|
|
2943
|
+
runId: worker.runId,
|
|
2944
|
+
outcome: "completed",
|
|
2945
|
+
httpStatus: 200,
|
|
2946
|
+
attempts: 0,
|
|
2947
|
+
reason: "completion-already-acknowledged"
|
|
2948
|
+
};
|
|
2949
|
+
}
|
|
2863
2950
|
const status = computeWorkerStatus(worker);
|
|
2864
2951
|
if (isFinishedWorkerStatus(status)) break;
|
|
2865
2952
|
if (!isPidAlive(worker.pid)) break;
|
|
@@ -2923,20 +3010,21 @@ async function autoCompleteWorkerCli(raw) {
|
|
|
2923
3010
|
const outcome = await autoCompleteWorker(raw);
|
|
2924
3011
|
console.log(JSON.stringify(outcome, null, 2));
|
|
2925
3012
|
if (outcome.outcome === "missing_link" || outcome.outcome === "timed_out") {
|
|
2926
|
-
process.
|
|
3013
|
+
process.exit(1);
|
|
2927
3014
|
}
|
|
3015
|
+
process.exit(0);
|
|
2928
3016
|
} catch (error) {
|
|
2929
3017
|
console.error(`worker auto-complete failed: ${error.message}`);
|
|
2930
|
-
process.
|
|
3018
|
+
process.exit(1);
|
|
2931
3019
|
}
|
|
2932
3020
|
}
|
|
2933
3021
|
function resolveDefaultCliPath() {
|
|
2934
|
-
return
|
|
3022
|
+
return path12.join(fileURLToPath2(new URL(".", import.meta.url)), "cli.js");
|
|
2935
3023
|
}
|
|
2936
3024
|
function spawnCompletionSidecar(opts) {
|
|
2937
3025
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath();
|
|
2938
3026
|
if (!existsSync10(cliPath)) return void 0;
|
|
2939
|
-
const logPath =
|
|
3027
|
+
const logPath = path12.join(opts.workerDir, "auto-complete.log");
|
|
2940
3028
|
let logFd;
|
|
2941
3029
|
try {
|
|
2942
3030
|
logFd = openSync3(logPath, "a");
|
|
@@ -3018,16 +3106,16 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3018
3106
|
launchModel = preflight.model;
|
|
3019
3107
|
}
|
|
3020
3108
|
const { worktreesDir } = getPaths();
|
|
3021
|
-
const workerDir =
|
|
3109
|
+
const workerDir = path13.join(runDirectory(run.id), "workers", name);
|
|
3022
3110
|
mkdirSync3(workerDir, { recursive: true });
|
|
3023
|
-
const worktreePath =
|
|
3111
|
+
const worktreePath = path13.join(worktreesDir, run.id, name);
|
|
3024
3112
|
const branch = opts.branch || `agent/${run.id}/${name}`;
|
|
3025
3113
|
if (existsSync11(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
|
|
3026
3114
|
git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
|
|
3027
3115
|
git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
|
|
3028
|
-
const stdoutPath =
|
|
3029
|
-
const stderrPath =
|
|
3030
|
-
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");
|
|
3031
3119
|
const prompt = buildPrompt({
|
|
3032
3120
|
task: opts.task,
|
|
3033
3121
|
ownedPaths: opts.ownedPaths || [],
|
|
@@ -3088,7 +3176,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3088
3176
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3089
3177
|
};
|
|
3090
3178
|
saveWorker(run.id, worker);
|
|
3091
|
-
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath:
|
|
3179
|
+
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path13.join(workerDir, "worker.json") } };
|
|
3092
3180
|
run.status = "running";
|
|
3093
3181
|
saveRun(run);
|
|
3094
3182
|
if (worker.agentOsId && worker.taskId) {
|
|
@@ -3380,18 +3468,18 @@ function buildPlanPersistIdempotencyKey(input) {
|
|
|
3380
3468
|
|
|
3381
3469
|
// src/plan-persist/paths.ts
|
|
3382
3470
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
3383
|
-
import { homedir as
|
|
3384
|
-
import
|
|
3471
|
+
import { homedir as homedir4 } from "node:os";
|
|
3472
|
+
import path14 from "node:path";
|
|
3385
3473
|
function resolveKynverStateRoot() {
|
|
3386
3474
|
const env = process.env.KYNVER_STATE_ROOT;
|
|
3387
|
-
if (env) return
|
|
3388
|
-
return
|
|
3475
|
+
if (env) return path14.resolve(env);
|
|
3476
|
+
return path14.join(homedir4(), ".kynver", "state");
|
|
3389
3477
|
}
|
|
3390
3478
|
function planOutboxDir() {
|
|
3391
|
-
return
|
|
3479
|
+
return path14.join(resolveKynverStateRoot(), "plan-outbox");
|
|
3392
3480
|
}
|
|
3393
3481
|
function planOutboxArchiveDir() {
|
|
3394
|
-
return
|
|
3482
|
+
return path14.join(resolveKynverStateRoot(), "plan-outbox-archive");
|
|
3395
3483
|
}
|
|
3396
3484
|
function ensurePlanOutboxDirs() {
|
|
3397
3485
|
const outboxDir = planOutboxDir();
|
|
@@ -3402,8 +3490,8 @@ function ensurePlanOutboxDirs() {
|
|
|
3402
3490
|
}
|
|
3403
3491
|
function isTmpOnlyPath(filePath) {
|
|
3404
3492
|
if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
|
|
3405
|
-
const resolved =
|
|
3406
|
-
return resolved.startsWith("/tmp/") || resolved.startsWith(
|
|
3493
|
+
const resolved = path14.resolve(filePath);
|
|
3494
|
+
return resolved.startsWith("/tmp/") || resolved.startsWith(path14.join("/var", "folders"));
|
|
3407
3495
|
}
|
|
3408
3496
|
|
|
3409
3497
|
// src/plan-persist/outbox-store.ts
|
|
@@ -3415,7 +3503,7 @@ import {
|
|
|
3415
3503
|
writeFileSync as writeFileSync3,
|
|
3416
3504
|
unlinkSync
|
|
3417
3505
|
} from "node:fs";
|
|
3418
|
-
import
|
|
3506
|
+
import path15 from "node:path";
|
|
3419
3507
|
import { randomUUID } from "node:crypto";
|
|
3420
3508
|
var DEFAULT_MAX_RETRIES = 12;
|
|
3421
3509
|
function listOutboxItems() {
|
|
@@ -3423,7 +3511,7 @@ function listOutboxItems() {
|
|
|
3423
3511
|
const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
|
|
3424
3512
|
const items = [];
|
|
3425
3513
|
for (const file of files) {
|
|
3426
|
-
const item = readOutboxItem(
|
|
3514
|
+
const item = readOutboxItem(path15.join(outboxDir, file));
|
|
3427
3515
|
if (item && item.queueStatus === "queued") items.push(item);
|
|
3428
3516
|
}
|
|
3429
3517
|
return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
@@ -3444,7 +3532,7 @@ function readOutboxItem(jsonPath) {
|
|
|
3444
3532
|
}
|
|
3445
3533
|
function readOutboxBody(item) {
|
|
3446
3534
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3447
|
-
const bodyFile =
|
|
3535
|
+
const bodyFile = path15.join(outboxDir, item.bodyPath);
|
|
3448
3536
|
return readFileSync7(bodyFile, "utf8");
|
|
3449
3537
|
}
|
|
3450
3538
|
function writeOutboxItem(input, opts) {
|
|
@@ -3452,8 +3540,8 @@ function writeOutboxItem(input, opts) {
|
|
|
3452
3540
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3453
3541
|
const id = opts.existing?.id ?? randomUUID();
|
|
3454
3542
|
const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
|
|
3455
|
-
const jsonPath =
|
|
3456
|
-
const bodyFile =
|
|
3543
|
+
const jsonPath = path15.join(outboxDir, `${id}.json`);
|
|
3544
|
+
const bodyFile = path15.join(outboxDir, bodyPath);
|
|
3457
3545
|
if (!opts.existing) {
|
|
3458
3546
|
writeFileSync3(bodyFile, input.body, "utf8");
|
|
3459
3547
|
}
|
|
@@ -3489,24 +3577,24 @@ function writeOutboxItem(input, opts) {
|
|
|
3489
3577
|
}
|
|
3490
3578
|
function saveOutboxItem(item) {
|
|
3491
3579
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3492
|
-
const jsonPath =
|
|
3580
|
+
const jsonPath = path15.join(outboxDir, `${item.id}.json`);
|
|
3493
3581
|
writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
|
|
3494
3582
|
`, { mode: 384 });
|
|
3495
3583
|
}
|
|
3496
3584
|
function archiveOutboxItem(item) {
|
|
3497
3585
|
const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
|
|
3498
|
-
const jsonSrc =
|
|
3499
|
-
const bodySrc =
|
|
3500
|
-
const jsonDst =
|
|
3501
|
-
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);
|
|
3502
3590
|
if (existsSync13(jsonSrc)) renameSync(jsonSrc, jsonDst);
|
|
3503
3591
|
if (existsSync13(bodySrc)) renameSync(bodySrc, bodyDst);
|
|
3504
3592
|
}
|
|
3505
3593
|
function outboxItemPaths(item) {
|
|
3506
3594
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3507
3595
|
return {
|
|
3508
|
-
jsonPath:
|
|
3509
|
-
bodyPath:
|
|
3596
|
+
jsonPath: path15.join(outboxDir, `${item.id}.json`),
|
|
3597
|
+
bodyPath: path15.join(outboxDir, item.bodyPath)
|
|
3510
3598
|
};
|
|
3511
3599
|
}
|
|
3512
3600
|
function outboxInputFromItem(item, body) {
|
|
@@ -3692,7 +3780,7 @@ function markOutboxFailed(item, message) {
|
|
|
3692
3780
|
}
|
|
3693
3781
|
|
|
3694
3782
|
// src/plan-persist/drain.ts
|
|
3695
|
-
import
|
|
3783
|
+
import path16 from "node:path";
|
|
3696
3784
|
async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
3697
3785
|
const items = listOutboxItems().filter(
|
|
3698
3786
|
(item) => opts.outboxId ? item.id === opts.outboxId : true
|
|
@@ -3726,7 +3814,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
|
3726
3814
|
return result;
|
|
3727
3815
|
}
|
|
3728
3816
|
function loadOutboxById(outboxId) {
|
|
3729
|
-
const jsonPath =
|
|
3817
|
+
const jsonPath = path16.join(planOutboxDir(), `${outboxId}.json`);
|
|
3730
3818
|
return readOutboxItem(jsonPath);
|
|
3731
3819
|
}
|
|
3732
3820
|
|
|
@@ -3826,7 +3914,7 @@ async function dispatchRun(args) {
|
|
|
3826
3914
|
const activeHarnessWorkers = [];
|
|
3827
3915
|
for (const name of Object.keys(run.workers || {})) {
|
|
3828
3916
|
const worker = readJson(
|
|
3829
|
-
|
|
3917
|
+
path17.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
3830
3918
|
void 0
|
|
3831
3919
|
);
|
|
3832
3920
|
if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
|
|
@@ -4039,7 +4127,7 @@ function redactHarness(text, secret) {
|
|
|
4039
4127
|
}
|
|
4040
4128
|
|
|
4041
4129
|
// src/validate.ts
|
|
4042
|
-
import
|
|
4130
|
+
import path18 from "node:path";
|
|
4043
4131
|
var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
4044
4132
|
var WORKER_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
|
|
4045
4133
|
function validateRunId(runId) {
|
|
@@ -4053,15 +4141,15 @@ function validateWorkerName(name) {
|
|
|
4053
4141
|
return trimmed;
|
|
4054
4142
|
}
|
|
4055
4143
|
function validateRepo(repo) {
|
|
4056
|
-
const resolved =
|
|
4144
|
+
const resolved = path18.resolve(repo);
|
|
4057
4145
|
if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
|
|
4058
4146
|
return resolved;
|
|
4059
4147
|
}
|
|
4060
4148
|
function validateOwnedPaths(repoRoot, ownedPaths) {
|
|
4061
4149
|
return ownedPaths.map((owned) => {
|
|
4062
|
-
const resolved =
|
|
4063
|
-
const rel =
|
|
4064
|
-
if (rel.startsWith("..") ||
|
|
4150
|
+
const resolved = path18.resolve(repoRoot, owned);
|
|
4151
|
+
const rel = path18.relative(repoRoot, resolved);
|
|
4152
|
+
if (rel.startsWith("..") || path18.isAbsolute(rel)) {
|
|
4065
4153
|
throw new Error(`owned path escapes repo: ${owned}`);
|
|
4066
4154
|
}
|
|
4067
4155
|
return resolved;
|
|
@@ -4074,7 +4162,7 @@ function validateTailLines(lines) {
|
|
|
4074
4162
|
|
|
4075
4163
|
// src/worktree.ts
|
|
4076
4164
|
import { existsSync as existsSync14, mkdirSync as mkdirSync5 } from "node:fs";
|
|
4077
|
-
import
|
|
4165
|
+
import path19 from "node:path";
|
|
4078
4166
|
function createRun(args) {
|
|
4079
4167
|
const repo = validateRepo(required(String(args.repo || ""), "--repo"));
|
|
4080
4168
|
ensureGitRepo(repo);
|
|
@@ -4094,12 +4182,12 @@ function createRun(args) {
|
|
|
4094
4182
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4095
4183
|
workers: {}
|
|
4096
4184
|
};
|
|
4097
|
-
writeJson(
|
|
4185
|
+
writeJson(path19.join(dir, "run.json"), run);
|
|
4098
4186
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
4099
4187
|
}
|
|
4100
4188
|
function listRuns() {
|
|
4101
4189
|
const { runsDir } = getPaths();
|
|
4102
|
-
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) => ({
|
|
4103
4191
|
id: run.id,
|
|
4104
4192
|
name: run.name,
|
|
4105
4193
|
status: run.status,
|
|
@@ -4114,7 +4202,7 @@ function failExists(message) {
|
|
|
4114
4202
|
}
|
|
4115
4203
|
|
|
4116
4204
|
// src/sweep.ts
|
|
4117
|
-
import
|
|
4205
|
+
import path20 from "node:path";
|
|
4118
4206
|
async function sweepRun(args) {
|
|
4119
4207
|
const pipeline = args.pipeline === true || args.pipeline === "true";
|
|
4120
4208
|
try {
|
|
@@ -4127,7 +4215,7 @@ async function sweepRun(args) {
|
|
|
4127
4215
|
const releasedLocalOrphans = [];
|
|
4128
4216
|
for (const name of Object.keys(run.workers || {})) {
|
|
4129
4217
|
const worker = readJson(
|
|
4130
|
-
|
|
4218
|
+
path20.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4131
4219
|
void 0
|
|
4132
4220
|
);
|
|
4133
4221
|
if (!worker || !worker.dispatched || !worker.taskId) continue;
|
|
@@ -4174,7 +4262,7 @@ import { mkdirSync as mkdirSync7, realpathSync } from "node:fs";
|
|
|
4174
4262
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
4175
4263
|
|
|
4176
4264
|
// src/pipeline-tick.ts
|
|
4177
|
-
import
|
|
4265
|
+
import path29 from "node:path";
|
|
4178
4266
|
|
|
4179
4267
|
// src/pipeline-dispatch.ts
|
|
4180
4268
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -4228,10 +4316,10 @@ async function runPipelineDispatch(args, slots) {
|
|
|
4228
4316
|
}
|
|
4229
4317
|
|
|
4230
4318
|
// src/stale-reconcile.ts
|
|
4231
|
-
import
|
|
4319
|
+
import path22 from "node:path";
|
|
4232
4320
|
|
|
4233
4321
|
// src/finalize.ts
|
|
4234
|
-
import
|
|
4322
|
+
import path21 from "node:path";
|
|
4235
4323
|
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
|
|
4236
4324
|
function terminalStatusFor(run) {
|
|
4237
4325
|
const names = Object.keys(run.workers || {});
|
|
@@ -4242,7 +4330,7 @@ function terminalStatusFor(run) {
|
|
|
4242
4330
|
let anyLandingBlocked = false;
|
|
4243
4331
|
for (const name of names) {
|
|
4244
4332
|
const worker = readJson(
|
|
4245
|
-
|
|
4333
|
+
path21.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4246
4334
|
void 0
|
|
4247
4335
|
);
|
|
4248
4336
|
if (!worker) continue;
|
|
@@ -4294,7 +4382,7 @@ function reconcileStaleWorkers() {
|
|
|
4294
4382
|
const now = Date.now();
|
|
4295
4383
|
for (const run of listRunRecords()) {
|
|
4296
4384
|
for (const name of Object.keys(run.workers || {})) {
|
|
4297
|
-
const workerPath =
|
|
4385
|
+
const workerPath = path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
4298
4386
|
const worker = readJson(workerPath, void 0);
|
|
4299
4387
|
if (!worker || worker.status !== "running") {
|
|
4300
4388
|
outcomes.push({
|
|
@@ -4368,9 +4456,27 @@ function reconcileStaleWorkers() {
|
|
|
4368
4456
|
}
|
|
4369
4457
|
return { workers: outcomes, finalizedRuns: finalizeStaleRuns() };
|
|
4370
4458
|
}
|
|
4459
|
+
function reconcileRunsCli() {
|
|
4460
|
+
const result = reconcileStaleWorkers();
|
|
4461
|
+
const markedExited = result.workers.filter((w) => w.action === "marked_exited").length;
|
|
4462
|
+
const killedStale = result.workers.filter((w) => w.action === "killed_stale").length;
|
|
4463
|
+
const skipped = result.workers.filter((w) => w.action === "skipped").length;
|
|
4464
|
+
console.log(
|
|
4465
|
+
JSON.stringify(
|
|
4466
|
+
{
|
|
4467
|
+
ok: true,
|
|
4468
|
+
workers: { markedExited, killedStale, skipped, total: result.workers.length },
|
|
4469
|
+
finalizedRuns: result.finalizedRuns.length,
|
|
4470
|
+
details: { workers: result.workers, finalizedRuns: result.finalizedRuns }
|
|
4471
|
+
},
|
|
4472
|
+
null,
|
|
4473
|
+
2
|
|
4474
|
+
)
|
|
4475
|
+
);
|
|
4476
|
+
}
|
|
4371
4477
|
|
|
4372
4478
|
// src/plan-progress-daemon-sync.ts
|
|
4373
|
-
import
|
|
4479
|
+
import path23 from "node:path";
|
|
4374
4480
|
|
|
4375
4481
|
// src/plan-progress-sync.ts
|
|
4376
4482
|
async function syncPlanProgress(args) {
|
|
@@ -4394,7 +4500,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
4394
4500
|
const outcomes = [];
|
|
4395
4501
|
for (const name of Object.keys(run.workers || {})) {
|
|
4396
4502
|
const worker = readJson(
|
|
4397
|
-
|
|
4503
|
+
path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4398
4504
|
void 0
|
|
4399
4505
|
);
|
|
4400
4506
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -4443,7 +4549,7 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
4443
4549
|
}
|
|
4444
4550
|
|
|
4445
4551
|
// src/cleanup.ts
|
|
4446
|
-
import
|
|
4552
|
+
import path28 from "node:path";
|
|
4447
4553
|
|
|
4448
4554
|
// src/cleanup-types.ts
|
|
4449
4555
|
var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
@@ -4515,11 +4621,11 @@ function skipNodeModulesRemoval(input) {
|
|
|
4515
4621
|
|
|
4516
4622
|
// src/cleanup-execute.ts
|
|
4517
4623
|
import { existsSync as existsSync16, rmSync } from "node:fs";
|
|
4518
|
-
import
|
|
4624
|
+
import path25 from "node:path";
|
|
4519
4625
|
|
|
4520
4626
|
// src/cleanup-dir-size.ts
|
|
4521
4627
|
import { existsSync as existsSync15, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
|
|
4522
|
-
import
|
|
4628
|
+
import path24 from "node:path";
|
|
4523
4629
|
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
4524
4630
|
if (!existsSync15(root)) return 0;
|
|
4525
4631
|
let total = 0;
|
|
@@ -4535,7 +4641,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
4535
4641
|
}
|
|
4536
4642
|
for (const name of entries) {
|
|
4537
4643
|
if (seen++ > maxEntries) return null;
|
|
4538
|
-
const full =
|
|
4644
|
+
const full = path24.join(current, name);
|
|
4539
4645
|
let st;
|
|
4540
4646
|
try {
|
|
4541
4647
|
st = statSync2(full);
|
|
@@ -4619,20 +4725,20 @@ function removeWorktree(candidate, execute) {
|
|
|
4619
4725
|
}
|
|
4620
4726
|
}
|
|
4621
4727
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
4622
|
-
const resolved =
|
|
4623
|
-
const nm = resolved.endsWith(`${
|
|
4728
|
+
const resolved = path25.resolve(targetPath);
|
|
4729
|
+
const nm = resolved.endsWith(`${path25.sep}node_modules`) ? resolved : null;
|
|
4624
4730
|
if (!nm) return "path_outside_harness";
|
|
4625
|
-
const rel =
|
|
4626
|
-
if (rel.startsWith("..") ||
|
|
4627
|
-
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);
|
|
4628
4734
|
if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
|
|
4629
|
-
if (!resolved.startsWith(
|
|
4735
|
+
if (!resolved.startsWith(path25.resolve(harnessRoot))) return "path_outside_harness";
|
|
4630
4736
|
return null;
|
|
4631
4737
|
}
|
|
4632
4738
|
|
|
4633
4739
|
// src/cleanup-scan.ts
|
|
4634
4740
|
import { existsSync as existsSync17, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
4635
|
-
import
|
|
4741
|
+
import path26 from "node:path";
|
|
4636
4742
|
function pathAgeMs(target, now) {
|
|
4637
4743
|
try {
|
|
4638
4744
|
const mtime = statSync3(target).mtimeMs;
|
|
@@ -4642,17 +4748,17 @@ function pathAgeMs(target, now) {
|
|
|
4642
4748
|
}
|
|
4643
4749
|
}
|
|
4644
4750
|
function isPathInside(child, parent) {
|
|
4645
|
-
const rel =
|
|
4646
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
4751
|
+
const rel = path26.relative(parent, child);
|
|
4752
|
+
return rel === "" || !rel.startsWith("..") && !path26.isAbsolute(rel);
|
|
4647
4753
|
}
|
|
4648
4754
|
function scanNodeModulesCandidates(opts) {
|
|
4649
4755
|
const candidates = [];
|
|
4650
4756
|
const seen = /* @__PURE__ */ new Set();
|
|
4651
4757
|
for (const entry of opts.index.values()) {
|
|
4652
4758
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
4653
|
-
const nm =
|
|
4759
|
+
const nm = path26.join(entry.worktreePath, "node_modules");
|
|
4654
4760
|
if (!existsSync17(nm)) continue;
|
|
4655
|
-
const resolved =
|
|
4761
|
+
const resolved = path26.resolve(nm);
|
|
4656
4762
|
if (seen.has(resolved)) continue;
|
|
4657
4763
|
seen.add(resolved);
|
|
4658
4764
|
candidates.push({
|
|
@@ -4668,13 +4774,13 @@ function scanNodeModulesCandidates(opts) {
|
|
|
4668
4774
|
if (!opts.includeOrphans || !existsSync17(opts.worktreesDir)) return candidates;
|
|
4669
4775
|
for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
|
|
4670
4776
|
if (!runEntry.isDirectory()) continue;
|
|
4671
|
-
const runPath =
|
|
4777
|
+
const runPath = path26.join(opts.worktreesDir, runEntry.name);
|
|
4672
4778
|
for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
|
|
4673
4779
|
if (!workerEntry.isDirectory()) continue;
|
|
4674
|
-
const worktreePath =
|
|
4675
|
-
const nm =
|
|
4780
|
+
const worktreePath = path26.join(runPath, workerEntry.name);
|
|
4781
|
+
const nm = path26.join(worktreePath, "node_modules");
|
|
4676
4782
|
if (!existsSync17(nm)) continue;
|
|
4677
|
-
const resolved =
|
|
4783
|
+
const resolved = path26.resolve(nm);
|
|
4678
4784
|
if (seen.has(resolved)) continue;
|
|
4679
4785
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
4680
4786
|
seen.add(resolved);
|
|
@@ -4714,17 +4820,17 @@ function scanWorktreeCandidates(opts) {
|
|
|
4714
4820
|
}
|
|
4715
4821
|
|
|
4716
4822
|
// src/cleanup-worktree-index.ts
|
|
4717
|
-
import
|
|
4823
|
+
import path27 from "node:path";
|
|
4718
4824
|
function buildWorktreeIndex() {
|
|
4719
4825
|
const index = /* @__PURE__ */ new Map();
|
|
4720
4826
|
for (const run of listRunRecords()) {
|
|
4721
4827
|
for (const name of Object.keys(run.workers || {})) {
|
|
4722
|
-
const workerPath =
|
|
4828
|
+
const workerPath = path27.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
4723
4829
|
const worker = readJson(workerPath, void 0);
|
|
4724
4830
|
if (!worker?.worktreePath) continue;
|
|
4725
4831
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
4726
|
-
index.set(
|
|
4727
|
-
worktreePath:
|
|
4832
|
+
index.set(path27.resolve(worker.worktreePath), {
|
|
4833
|
+
worktreePath: path27.resolve(worker.worktreePath),
|
|
4728
4834
|
runId: run.id,
|
|
4729
4835
|
workerName: name,
|
|
4730
4836
|
run,
|
|
@@ -4738,8 +4844,8 @@ function buildWorktreeIndex() {
|
|
|
4738
4844
|
|
|
4739
4845
|
// src/cleanup.ts
|
|
4740
4846
|
function resolveOptions(options = {}) {
|
|
4741
|
-
const harnessRoot = options.harnessRoot ?
|
|
4742
|
-
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();
|
|
4743
4849
|
const execute = options.execute === true;
|
|
4744
4850
|
const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
|
|
4745
4851
|
const worktreesAgeMs = options.worktreesAgeMs ?? 0;
|
|
@@ -4783,7 +4889,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
4783
4889
|
actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
|
|
4784
4890
|
continue;
|
|
4785
4891
|
}
|
|
4786
|
-
const worktreePath =
|
|
4892
|
+
const worktreePath = path28.resolve(candidate.path, "..");
|
|
4787
4893
|
const indexed = index.get(worktreePath) ?? null;
|
|
4788
4894
|
const guardReason = skipNodeModulesRemoval({
|
|
4789
4895
|
indexed,
|
|
@@ -4799,7 +4905,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
4799
4905
|
actions.push(removeNodeModules(candidate, resolved.execute));
|
|
4800
4906
|
}
|
|
4801
4907
|
for (const candidate of scanWorktreeCandidates(scanOpts)) {
|
|
4802
|
-
const indexed = index.get(
|
|
4908
|
+
const indexed = index.get(path28.resolve(candidate.path)) ?? null;
|
|
4803
4909
|
const guardReason = skipWorktreeRemoval({
|
|
4804
4910
|
indexed,
|
|
4805
4911
|
includeOrphans: resolved.includeOrphans,
|
|
@@ -4868,7 +4974,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
4868
4974
|
const outcomes = [];
|
|
4869
4975
|
for (const name of Object.keys(run.workers || {})) {
|
|
4870
4976
|
const worker = readJson(
|
|
4871
|
-
|
|
4977
|
+
path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4872
4978
|
void 0
|
|
4873
4979
|
);
|
|
4874
4980
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -5203,7 +5309,7 @@ function runCleanupCli(args) {
|
|
|
5203
5309
|
}
|
|
5204
5310
|
|
|
5205
5311
|
// src/monitor/monitor.service.ts
|
|
5206
|
-
import
|
|
5312
|
+
import path31 from "node:path";
|
|
5207
5313
|
|
|
5208
5314
|
// src/monitor/monitor.classify.ts
|
|
5209
5315
|
function expectedLeaseOwner(runId) {
|
|
@@ -5260,10 +5366,10 @@ function classifyWorkerHealth(input) {
|
|
|
5260
5366
|
|
|
5261
5367
|
// src/monitor/monitor.store.ts
|
|
5262
5368
|
import { existsSync as existsSync18, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
|
|
5263
|
-
import
|
|
5369
|
+
import path30 from "node:path";
|
|
5264
5370
|
function monitorsDir() {
|
|
5265
5371
|
const { harnessRoot } = getHarnessPaths();
|
|
5266
|
-
const dir =
|
|
5372
|
+
const dir = path30.join(harnessRoot, "monitors");
|
|
5267
5373
|
mkdirSync6(dir, { recursive: true });
|
|
5268
5374
|
return dir;
|
|
5269
5375
|
}
|
|
@@ -5271,7 +5377,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
5271
5377
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
5272
5378
|
}
|
|
5273
5379
|
function monitorPath(monitorId) {
|
|
5274
|
-
return
|
|
5380
|
+
return path30.join(monitorsDir(), `${monitorId}.json`);
|
|
5275
5381
|
}
|
|
5276
5382
|
function loadMonitorSession(monitorId) {
|
|
5277
5383
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -5292,7 +5398,7 @@ function listMonitorSessions() {
|
|
|
5292
5398
|
for (const name of readdirSync7(dir)) {
|
|
5293
5399
|
if (!name.endsWith(".json")) continue;
|
|
5294
5400
|
const session = readJson(
|
|
5295
|
-
|
|
5401
|
+
path30.join(dir, name),
|
|
5296
5402
|
void 0
|
|
5297
5403
|
);
|
|
5298
5404
|
if (!session?.monitorId) continue;
|
|
@@ -5383,7 +5489,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
5383
5489
|
// src/monitor/monitor.service.ts
|
|
5384
5490
|
function workerRecord2(runId, name) {
|
|
5385
5491
|
return readJson(
|
|
5386
|
-
|
|
5492
|
+
path31.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
5387
5493
|
void 0
|
|
5388
5494
|
);
|
|
5389
5495
|
}
|
|
@@ -5586,17 +5692,17 @@ async function runMonitorLoop(args) {
|
|
|
5586
5692
|
// src/monitor/monitor-spawn.ts
|
|
5587
5693
|
import { spawn as spawn4 } from "node:child_process";
|
|
5588
5694
|
import { closeSync as closeSync4, existsSync as existsSync19, openSync as openSync4 } from "node:fs";
|
|
5589
|
-
import
|
|
5695
|
+
import path32 from "node:path";
|
|
5590
5696
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
5591
5697
|
function resolveDefaultCliPath2() {
|
|
5592
|
-
return
|
|
5698
|
+
return path32.join(fileURLToPath3(new URL(".", import.meta.url)), "..", "cli.js");
|
|
5593
5699
|
}
|
|
5594
5700
|
function spawnMonitorSidecar(opts) {
|
|
5595
5701
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
5596
5702
|
if (!existsSync19(cliPath)) return void 0;
|
|
5597
5703
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
5598
5704
|
const { harnessRoot } = getHarnessPaths();
|
|
5599
|
-
const logPath =
|
|
5705
|
+
const logPath = path32.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
5600
5706
|
let logFd;
|
|
5601
5707
|
try {
|
|
5602
5708
|
logFd = openSync4(logPath, "a");
|
|
@@ -5715,6 +5821,422 @@ async function monitorTickCli(args) {
|
|
|
5715
5821
|
console.log(JSON.stringify(tick, null, 2));
|
|
5716
5822
|
}
|
|
5717
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
|
+
|
|
5718
6240
|
// src/cli.ts
|
|
5719
6241
|
function isHelpFlag(arg) {
|
|
5720
6242
|
return arg === "help" || arg === "--help" || arg === "-h";
|
|
@@ -5736,7 +6258,7 @@ function usage(code = 0) {
|
|
|
5736
6258
|
" kynver run create --repo /path/repo [--name name] [--base origin/main]",
|
|
5737
6259
|
" kynver run list",
|
|
5738
6260
|
" kynver run status --run RUN_ID",
|
|
5739
|
-
" kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-
|
|
6261
|
+
" kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-8] [--disk-path /]",
|
|
5740
6262
|
" kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
|
|
5741
6263
|
' kynver worker start --run RUN_ID --name worker --task "..." [--owned path[,path]] [--model MODEL] [--provider claude|cursor] [--agent-os-id AOS_ID] [--task-id TASK_ID] [--wait]',
|
|
5742
6264
|
" kynver worker status --run RUN_ID --name worker",
|
|
@@ -5744,6 +6266,7 @@ function usage(code = 0) {
|
|
|
5744
6266
|
" kynver worker stop --run RUN_ID --name worker",
|
|
5745
6267
|
" kynver worker complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--task-id TASK_ID] [--base-url URL] [--secret SECRET]",
|
|
5746
6268
|
" kynver worker auto-complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--poll-ms 5000] [--max-total-ms 21600000] [--complete-attempts 3] [--complete-backoff-ms 5000] [--base-url URL] [--secret SECRET]",
|
|
6269
|
+
" kynver run reconcile",
|
|
5747
6270
|
" kynver plan progress --plan PLAN_ID --row ROW_KEY --role ROLE --status STATUS [--task TASK_ID] [--note NOTE] [--evidence type:value] [--agent-os-id AOS_ID]",
|
|
5748
6271
|
" kynver plan verify --plan PLAN_ID [--worktree PATH] [--task TASK_ID] [--human-override]",
|
|
5749
6272
|
" kynver plan persist --operation create|add_version|update_metadata --title TITLE (--body-file PATH | --body TEXT) [--slug SLUG] [--plan PLAN_ID] [--summary TEXT] [--failure-kind approval_guard|auth|network|server|tool_interruption]",
|
|
@@ -5756,7 +6279,8 @@ function usage(code = 0) {
|
|
|
5756
6279
|
" kynver monitor list",
|
|
5757
6280
|
" kynver monitor tick --run RUN_ID [--name worker] [--agent-os-id AOS_ID] [--auto-complete] [--renew-leases]",
|
|
5758
6281
|
" kynver monitor auto-complete --run RUN_ID --name worker [--agent-os-id AOS_ID] [--base-url URL] [--secret SECRET]",
|
|
5759
|
-
" 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"
|
|
5760
6284
|
].join("\n")
|
|
5761
6285
|
);
|
|
5762
6286
|
process.exit(code);
|
|
@@ -5767,7 +6291,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
5767
6291
|
const scope = argv.shift();
|
|
5768
6292
|
let action;
|
|
5769
6293
|
let rest;
|
|
5770
|
-
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") {
|
|
5771
6295
|
action = argv.shift();
|
|
5772
6296
|
rest = argv;
|
|
5773
6297
|
} else {
|
|
@@ -5792,11 +6316,13 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
5792
6316
|
unknownCommand("plan", `outbox ${outboxAction ?? ""}`.trim());
|
|
5793
6317
|
}
|
|
5794
6318
|
if (scope === "cleanup") return runCleanupCli(args);
|
|
6319
|
+
if (scope === "doctor" && action === "runtime-takeover") return runRuntimeTakeoverDoctorCli();
|
|
5795
6320
|
if (scope === "run" && action === "create") return createRun(args);
|
|
5796
6321
|
if (scope === "run" && action === "list") return listRuns();
|
|
5797
6322
|
if (scope === "run" && action === "status") return runStatus(args);
|
|
5798
6323
|
if (scope === "run" && action === "dispatch") return void await dispatchRun(args);
|
|
5799
6324
|
if (scope === "run" && action === "sweep") return void await sweepRun(args);
|
|
6325
|
+
if (scope === "run" && action === "reconcile") return reconcileRunsCli();
|
|
5800
6326
|
if (scope === "worker" && action === "start") return void await startWorker(args);
|
|
5801
6327
|
if (scope === "worker" && action === "status") return workerStatus(args);
|
|
5802
6328
|
if (scope === "worker" && action === "tail") return tailWorker(args);
|
|
@@ -5823,6 +6349,295 @@ if (isCliEntry) {
|
|
|
5823
6349
|
process.exit(1);
|
|
5824
6350
|
});
|
|
5825
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
|
+
}
|
|
5826
6641
|
export {
|
|
5827
6642
|
DEFAULT_DISPATCH_LEASE_MS,
|
|
5828
6643
|
PACKAGE_VERSION,
|
|
@@ -5835,7 +6650,9 @@ export {
|
|
|
5835
6650
|
autoCompleteWorkerCli,
|
|
5836
6651
|
buildDispatchTaskText,
|
|
5837
6652
|
buildPrompt,
|
|
6653
|
+
classifyVercelUrl,
|
|
5838
6654
|
classifyWorkerHealth,
|
|
6655
|
+
collectVercelEvidence,
|
|
5839
6656
|
completeWorker,
|
|
5840
6657
|
computeAttention,
|
|
5841
6658
|
computeWorkerStatus,
|
|
@@ -5844,15 +6661,18 @@ export {
|
|
|
5844
6661
|
dispatchRun,
|
|
5845
6662
|
drainPlanOutbox,
|
|
5846
6663
|
ensurePrReadyHandoff,
|
|
6664
|
+
evidenceFromGitHubVercelStatus,
|
|
5847
6665
|
extractPlanOutboxFromTask,
|
|
5848
6666
|
extractPrUrlFromText,
|
|
5849
6667
|
formatPlanOutboxHandoffBlock,
|
|
5850
6668
|
getHarnessPaths,
|
|
5851
6669
|
getMonitorStatus,
|
|
5852
6670
|
hashPlanBody,
|
|
6671
|
+
isDashboardVercelUrl,
|
|
5853
6672
|
isFinishedWorkerStatus,
|
|
5854
6673
|
isLandingBlockedWorkerStatus,
|
|
5855
6674
|
isTerminalHeartbeatPhase,
|
|
6675
|
+
isVercelStatusContext,
|
|
5856
6676
|
landingContractAttentionReason,
|
|
5857
6677
|
listMonitors,
|
|
5858
6678
|
listOutboxItems,
|
|
@@ -5866,13 +6686,17 @@ export {
|
|
|
5866
6686
|
parseHarnessStream,
|
|
5867
6687
|
parseHeartbeat,
|
|
5868
6688
|
persistPlan,
|
|
6689
|
+
pickVercelStatusContext,
|
|
5869
6690
|
postJson,
|
|
5870
6691
|
preflightCursorModel,
|
|
6692
|
+
reconcileRunsCli,
|
|
6693
|
+
reconcileStaleWorkers,
|
|
5871
6694
|
redactHarness,
|
|
5872
6695
|
resolveBaseUrl,
|
|
5873
6696
|
resolveCallbackSecret,
|
|
5874
6697
|
resolveCallbackSecretWithMint,
|
|
5875
6698
|
resolveHarnessRoot,
|
|
6699
|
+
resolveVercelInspectTarget,
|
|
5876
6700
|
runDaemon,
|
|
5877
6701
|
runMonitorTick,
|
|
5878
6702
|
runStatus,
|