@kynver-app/runtime 0.1.48 → 0.1.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.js +778 -492
- package/dist/cli.js.map +4 -4
- package/dist/default-repo-discovery.d.ts +15 -0
- package/dist/default-repo.d.ts +30 -0
- package/dist/doctor/runtime-takeover-scheduler.d.ts +20 -0
- package/dist/doctor/runtime-takeover.probes.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +839 -516
- package/dist/index.js.map +4 -4
- package/dist/pr-handoff/pr-handoff-assess.d.ts +10 -1
- package/dist/pr-handoff/pr-handoff.types.d.ts +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -35,43 +35,25 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
// src/dispatch.ts
|
|
38
|
-
import
|
|
38
|
+
import path18 from "node:path";
|
|
39
39
|
|
|
40
40
|
// src/config.ts
|
|
41
|
-
import { existsSync as
|
|
41
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
|
|
42
|
+
import { homedir as homedir3 } from "node:os";
|
|
43
|
+
import path4 from "node:path";
|
|
44
|
+
|
|
45
|
+
// src/default-repo-discovery.ts
|
|
46
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
42
47
|
import { homedir as homedir2 } from "node:os";
|
|
43
48
|
import path3 from "node:path";
|
|
49
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
44
50
|
|
|
45
|
-
// src/
|
|
46
|
-
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
|
-
}
|
|
51
|
+
// src/git.ts
|
|
52
|
+
import { spawnSync } from "node:child_process";
|
|
71
53
|
|
|
72
54
|
// src/util.ts
|
|
73
55
|
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
74
|
-
import
|
|
56
|
+
import path from "node:path";
|
|
75
57
|
function fail(message) {
|
|
76
58
|
console.error(message);
|
|
77
59
|
process.exit(1);
|
|
@@ -100,7 +82,7 @@ function readJson(file, fallback) {
|
|
|
100
82
|
}
|
|
101
83
|
}
|
|
102
84
|
function writeJson(file, value) {
|
|
103
|
-
mkdirSync(
|
|
85
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
104
86
|
writeFileSync(file, `${JSON.stringify(value, null, 2)}
|
|
105
87
|
`);
|
|
106
88
|
}
|
|
@@ -136,7 +118,7 @@ function tailFile(file, lines) {
|
|
|
136
118
|
return data.split("\n").slice(-lines).join("\n");
|
|
137
119
|
}
|
|
138
120
|
function readMaybeFile(file) {
|
|
139
|
-
return file ? readFileSync2(
|
|
121
|
+
return file ? readFileSync2(path.resolve(file), "utf8") : "";
|
|
140
122
|
}
|
|
141
123
|
function listRunIds(runsDir) {
|
|
142
124
|
if (!existsSync2(runsDir)) return [];
|
|
@@ -178,14 +160,280 @@ function secsAgo(ms) {
|
|
|
178
160
|
return Math.max(0, Math.round((Date.now() - ms) / 1e3));
|
|
179
161
|
}
|
|
180
162
|
|
|
163
|
+
// src/worker-env.ts
|
|
164
|
+
var FORBIDDEN_WORKER_ENV_KEYS = [
|
|
165
|
+
"ANTHROPIC_API_KEY",
|
|
166
|
+
"ANALYST_API_KEY",
|
|
167
|
+
"RECRUITER_API_KEY",
|
|
168
|
+
"AUTH_SECRET",
|
|
169
|
+
"NEXTAUTH_SECRET",
|
|
170
|
+
"DATABASE_URL",
|
|
171
|
+
"PRODUCTION_DATABASE_URL",
|
|
172
|
+
"REDIS_URL",
|
|
173
|
+
"GOOGLE_CLIENT_SECRET",
|
|
174
|
+
"GITHUB_CLIENT_SECRET",
|
|
175
|
+
"KYNVER_API_KEY",
|
|
176
|
+
"KYNVER_SERVICE_SECRET",
|
|
177
|
+
"KYNVER_RUNTIME_SECRET",
|
|
178
|
+
"OPENCLAW_CRON_SECRET",
|
|
179
|
+
"QSTASH_TOKEN",
|
|
180
|
+
"QSTASH_CURRENT_SIGNING_KEY",
|
|
181
|
+
"QSTASH_NEXT_SIGNING_KEY",
|
|
182
|
+
"TOOL_SECRETS_KEK",
|
|
183
|
+
"TOOL_EXECUTOR_DISPATCH_SECRET",
|
|
184
|
+
"CLOUDFLARE_API_TOKEN",
|
|
185
|
+
"STRIPE_SECRET_KEY",
|
|
186
|
+
"STRIPE_WEBHOOK_SECRET",
|
|
187
|
+
"STRIPE_IDENTITY_WEBHOOK_SECRET",
|
|
188
|
+
"VOYAGE_API_KEY",
|
|
189
|
+
"PERPLEXITY_API_KEY",
|
|
190
|
+
"FRED_API_KEY",
|
|
191
|
+
"FMP_API_KEY",
|
|
192
|
+
"CURSOR_API_KEY"
|
|
193
|
+
];
|
|
194
|
+
var FORBIDDEN_KEY_SET = new Set(FORBIDDEN_WORKER_ENV_KEYS);
|
|
195
|
+
var FORBIDDEN_SUFFIXES = ["_SECRET", "_API_KEY"];
|
|
196
|
+
function isForbiddenWorkerEnvKey(key) {
|
|
197
|
+
if (FORBIDDEN_KEY_SET.has(key)) return true;
|
|
198
|
+
return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));
|
|
199
|
+
}
|
|
200
|
+
function listForbiddenWorkerEnvKeys(env) {
|
|
201
|
+
return Object.keys(env).filter(isForbiddenWorkerEnvKey).sort();
|
|
202
|
+
}
|
|
203
|
+
function scrubWorkerEnv(env) {
|
|
204
|
+
const next = { ...env };
|
|
205
|
+
for (const key of Object.keys(next)) {
|
|
206
|
+
if (isForbiddenWorkerEnvKey(key)) delete next[key];
|
|
207
|
+
}
|
|
208
|
+
return next;
|
|
209
|
+
}
|
|
210
|
+
function auditWorkerEnv(env) {
|
|
211
|
+
const forbiddenPresent = listForbiddenWorkerEnvKeys(env);
|
|
212
|
+
return { forbiddenPresent, safe: forbiddenPresent.length === 0 };
|
|
213
|
+
}
|
|
214
|
+
function scrubClaudeEnv(env) {
|
|
215
|
+
return scrubWorkerEnv(env);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/git.ts
|
|
219
|
+
function git(cwd, args, options = {}) {
|
|
220
|
+
const res = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
221
|
+
if (res.status !== 0 && !options.allowFailure) {
|
|
222
|
+
const message = `git ${args.join(" ")} failed: ${res.stderr || res.stdout}`;
|
|
223
|
+
if (options.throwError) throw new Error(message);
|
|
224
|
+
fail(message);
|
|
225
|
+
}
|
|
226
|
+
return res.stdout || "";
|
|
227
|
+
}
|
|
228
|
+
function ensureGitRepo(repo) {
|
|
229
|
+
git(repo, ["rev-parse", "--show-toplevel"]);
|
|
230
|
+
}
|
|
231
|
+
function gitStatusShort(worktreePath) {
|
|
232
|
+
return git(worktreePath, ["status", "--short"], { allowFailure: true }).split("\n").map((line) => line.trim()).filter(Boolean);
|
|
233
|
+
}
|
|
234
|
+
function gitCapture(cwd, args) {
|
|
235
|
+
try {
|
|
236
|
+
const res = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
237
|
+
return {
|
|
238
|
+
status: res.status,
|
|
239
|
+
stdout: res.stdout || "",
|
|
240
|
+
stderr: res.stderr || "",
|
|
241
|
+
error: res.error ? res.error.message : null
|
|
242
|
+
};
|
|
243
|
+
} catch (error) {
|
|
244
|
+
return {
|
|
245
|
+
status: null,
|
|
246
|
+
stdout: "",
|
|
247
|
+
stderr: "",
|
|
248
|
+
error: error.message
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function gitIsAncestor(cwd, ancestor, descendant) {
|
|
253
|
+
const res = gitCapture(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]);
|
|
254
|
+
if (res.status === 0) return { isAncestor: true, error: null };
|
|
255
|
+
if (res.status === 1) return { isAncestor: false, error: null };
|
|
256
|
+
return { isAncestor: null, error: res.error || res.stderr || res.stdout || `git exited ${res.status}` };
|
|
257
|
+
}
|
|
258
|
+
function computeGitAncestry(worktreePath, baseOrOptions = "origin/main") {
|
|
259
|
+
const options = typeof baseOrOptions === "string" ? { base: baseOrOptions } : baseOrOptions;
|
|
260
|
+
const baseLabel = options.baseCommit?.trim() || options.base?.trim() || "origin/main";
|
|
261
|
+
const pinnedBaseCommit = options.baseCommit?.trim() || null;
|
|
262
|
+
if (!worktreePath) {
|
|
263
|
+
return unknownAncestry(baseLabel, "missing worktree path");
|
|
264
|
+
}
|
|
265
|
+
const head = gitCapture(worktreePath, ["rev-parse", "HEAD"]);
|
|
266
|
+
if (head.status !== 0) {
|
|
267
|
+
return unknownAncestry(baseLabel, head.error || head.stderr || head.stdout || "failed to resolve HEAD");
|
|
268
|
+
}
|
|
269
|
+
let baseSha;
|
|
270
|
+
if (pinnedBaseCommit) {
|
|
271
|
+
baseSha = pinnedBaseCommit;
|
|
272
|
+
} else {
|
|
273
|
+
const baseHead = gitCapture(worktreePath, ["rev-parse", baseLabel]);
|
|
274
|
+
if (baseHead.status !== 0) {
|
|
275
|
+
return unknownAncestry(
|
|
276
|
+
baseLabel,
|
|
277
|
+
baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${baseLabel}`,
|
|
278
|
+
head.stdout.trim()
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
baseSha = baseHead.stdout.trim();
|
|
282
|
+
}
|
|
283
|
+
const headSha = head.stdout.trim();
|
|
284
|
+
if (headSha === baseSha) {
|
|
285
|
+
return {
|
|
286
|
+
checked: true,
|
|
287
|
+
base: baseLabel,
|
|
288
|
+
head: headSha,
|
|
289
|
+
baseHead: baseSha,
|
|
290
|
+
baseIsAncestorOfHead: true,
|
|
291
|
+
headIsAncestorOfBase: true,
|
|
292
|
+
relation: "synced"
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
const baseIsAncestorOfHead = gitIsAncestor(worktreePath, baseSha, headSha);
|
|
296
|
+
const headIsAncestorOfBase = gitIsAncestor(worktreePath, headSha, baseSha);
|
|
297
|
+
const error = baseIsAncestorOfHead.error || headIsAncestorOfBase.error || void 0;
|
|
298
|
+
if (baseIsAncestorOfHead.isAncestor == null || headIsAncestorOfBase.isAncestor == null) {
|
|
299
|
+
return {
|
|
300
|
+
checked: false,
|
|
301
|
+
base: baseLabel,
|
|
302
|
+
head: headSha,
|
|
303
|
+
baseHead: baseSha,
|
|
304
|
+
baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
|
|
305
|
+
headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
|
|
306
|
+
relation: "unknown",
|
|
307
|
+
...error ? { error } : {}
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
const relation = baseIsAncestorOfHead.isAncestor ? "ahead" : headIsAncestorOfBase.isAncestor ? "merged" : "diverged";
|
|
311
|
+
return {
|
|
312
|
+
checked: true,
|
|
313
|
+
base: baseLabel,
|
|
314
|
+
head: headSha,
|
|
315
|
+
baseHead: baseSha,
|
|
316
|
+
baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
|
|
317
|
+
headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
|
|
318
|
+
relation,
|
|
319
|
+
...error ? { error } : {}
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function unknownAncestry(base, error, head = null) {
|
|
323
|
+
return {
|
|
324
|
+
checked: false,
|
|
325
|
+
base,
|
|
326
|
+
head,
|
|
327
|
+
baseHead: null,
|
|
328
|
+
baseIsAncestorOfHead: null,
|
|
329
|
+
headIsAncestorOfBase: null,
|
|
330
|
+
relation: "unknown",
|
|
331
|
+
error
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/path-values.ts
|
|
336
|
+
import { homedir } from "node:os";
|
|
337
|
+
import path2 from "node:path";
|
|
338
|
+
function expandHomePath(value) {
|
|
339
|
+
if (value === "~") return homedir();
|
|
340
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
341
|
+
return path2.join(homedir(), value.slice(2));
|
|
342
|
+
}
|
|
343
|
+
return value;
|
|
344
|
+
}
|
|
345
|
+
function resolveUserPath(value) {
|
|
346
|
+
return path2.resolve(expandHomePath(value));
|
|
347
|
+
}
|
|
348
|
+
function redactHomePath(value) {
|
|
349
|
+
const expanded = expandHomePath(value);
|
|
350
|
+
const resolved = path2.resolve(expanded);
|
|
351
|
+
const home = path2.resolve(homedir());
|
|
352
|
+
if (resolved === home) return "~";
|
|
353
|
+
if (resolved.startsWith(`${home}${path2.sep}`)) {
|
|
354
|
+
return `~/${path2.relative(home, resolved).split(path2.sep).join("/")}`;
|
|
355
|
+
}
|
|
356
|
+
return resolved.replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^\/Users\/[^/]+(?=\/|$)/, "~");
|
|
357
|
+
}
|
|
358
|
+
function displayUserPath(value) {
|
|
359
|
+
return redactHomePath(value);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// src/default-repo-discovery.ts
|
|
363
|
+
var WELL_KNOWN_REPO_DIRS = ["Kynver", "repos/Kynver", "code/Kynver", "projects/Kynver"];
|
|
364
|
+
function readPackageName(repoRoot) {
|
|
365
|
+
const pkgPath = path3.join(repoRoot, "package.json");
|
|
366
|
+
if (!existsSync3(pkgPath)) return null;
|
|
367
|
+
try {
|
|
368
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
|
|
369
|
+
return typeof pkg.name === "string" ? pkg.name.trim() : null;
|
|
370
|
+
} catch {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function isKynverMonorepoRoot(repoRoot) {
|
|
375
|
+
return readPackageName(repoRoot) === "kynver";
|
|
376
|
+
}
|
|
377
|
+
function gitRepoRoot(startDir) {
|
|
378
|
+
const resolvedStart = path3.resolve(startDir);
|
|
379
|
+
if (!existsSync3(resolvedStart)) return null;
|
|
380
|
+
const probe = gitCapture(resolvedStart, ["rev-parse", "--show-toplevel"]);
|
|
381
|
+
if (probe.status !== 0) return null;
|
|
382
|
+
const root = probe.stdout.trim();
|
|
383
|
+
return root.length ? path3.resolve(root) : null;
|
|
384
|
+
}
|
|
385
|
+
function resolveRuntimePackageRoot(moduleUrl = import.meta.url) {
|
|
386
|
+
let dir = path3.dirname(fileURLToPath2(moduleUrl));
|
|
387
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
388
|
+
const pkgPath = path3.join(dir, "package.json");
|
|
389
|
+
if (existsSync3(pkgPath)) {
|
|
390
|
+
try {
|
|
391
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
|
|
392
|
+
if (pkg.name === "@kynver-app/runtime") return dir;
|
|
393
|
+
} catch {
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
const parent = path3.dirname(dir);
|
|
397
|
+
if (parent === dir) break;
|
|
398
|
+
dir = parent;
|
|
399
|
+
}
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
function pushCandidate(seen, out, repo, source) {
|
|
403
|
+
if (!repo) return;
|
|
404
|
+
const resolved = path3.resolve(repo);
|
|
405
|
+
if (seen.has(resolved)) return;
|
|
406
|
+
if (!isKynverMonorepoRoot(resolved)) return;
|
|
407
|
+
seen.add(resolved);
|
|
408
|
+
out.push({ repo: resolved, source });
|
|
409
|
+
}
|
|
410
|
+
function discoverDefaultRepoCandidates(opts) {
|
|
411
|
+
const cwd = opts?.cwd ?? process.cwd();
|
|
412
|
+
const seen = /* @__PURE__ */ new Set();
|
|
413
|
+
const candidates = [];
|
|
414
|
+
pushCandidate(seen, candidates, gitRepoRoot(cwd), "cwd_git");
|
|
415
|
+
const runtimePkgRoot = resolveRuntimePackageRoot(opts?.runtimeModuleUrl ?? import.meta.url);
|
|
416
|
+
if (runtimePkgRoot) {
|
|
417
|
+
pushCandidate(seen, candidates, gitRepoRoot(runtimePkgRoot), "runtime_checkout");
|
|
418
|
+
}
|
|
419
|
+
const home = homedir2();
|
|
420
|
+
for (const rel of WELL_KNOWN_REPO_DIRS) {
|
|
421
|
+
pushCandidate(seen, candidates, resolveUserPath(path3.join(home, rel)), "well_known_path");
|
|
422
|
+
}
|
|
423
|
+
return candidates;
|
|
424
|
+
}
|
|
425
|
+
function discoverDefaultRepo(opts) {
|
|
426
|
+
return discoverDefaultRepoCandidates(opts)[0] ?? null;
|
|
427
|
+
}
|
|
428
|
+
|
|
181
429
|
// src/config.ts
|
|
182
|
-
var CONFIG_DIR =
|
|
183
|
-
var CONFIG_FILE =
|
|
184
|
-
var CREDENTIALS_FILE =
|
|
430
|
+
var CONFIG_DIR = path4.join(homedir3(), ".kynver");
|
|
431
|
+
var CONFIG_FILE = path4.join(CONFIG_DIR, "config.json");
|
|
432
|
+
var CREDENTIALS_FILE = path4.join(CONFIG_DIR, "credentials");
|
|
185
433
|
function loadUserConfig() {
|
|
186
|
-
if (!
|
|
434
|
+
if (!existsSync4(CONFIG_FILE)) return {};
|
|
187
435
|
try {
|
|
188
|
-
return JSON.parse(
|
|
436
|
+
return JSON.parse(readFileSync4(CONFIG_FILE, "utf8"));
|
|
189
437
|
} catch {
|
|
190
438
|
return {};
|
|
191
439
|
}
|
|
@@ -209,7 +457,8 @@ function inferSetupFields(existing, args) {
|
|
|
209
457
|
const creds = loadCredentialsFile();
|
|
210
458
|
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
459
|
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
|
|
460
|
+
const explicitRepo = typeof args.repo === "string" ? args.repo : args.discoverRepo === true || args.discoverRepo === "true" ? discoverDefaultRepo()?.repo : void 0;
|
|
461
|
+
const defaultRepo = explicitRepo || existing.defaultRepo?.trim() || process.env.KYNVER_DEFAULT_REPO?.trim() || process.env.KYNVER_HARNESS_REPO?.trim() || discoverDefaultRepo()?.repo;
|
|
213
462
|
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
463
|
return {
|
|
215
464
|
...apiBaseUrl ? { apiBaseUrl: trimTrailingSlash(apiBaseUrl) } : {},
|
|
@@ -220,9 +469,9 @@ function inferSetupFields(existing, args) {
|
|
|
220
469
|
};
|
|
221
470
|
}
|
|
222
471
|
function loadCredentialsFile() {
|
|
223
|
-
if (!
|
|
472
|
+
if (!existsSync4(CREDENTIALS_FILE)) return {};
|
|
224
473
|
try {
|
|
225
|
-
return JSON.parse(
|
|
474
|
+
return JSON.parse(readFileSync4(CREDENTIALS_FILE, "utf8"));
|
|
226
475
|
} catch {
|
|
227
476
|
return {};
|
|
228
477
|
}
|
|
@@ -508,12 +757,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
|
508
757
|
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
509
758
|
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
510
759
|
function observeRunnerDiskGate(input = {}) {
|
|
511
|
-
const
|
|
760
|
+
const path40 = input.diskPath?.trim() || "/";
|
|
512
761
|
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
513
762
|
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
514
763
|
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
515
764
|
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
516
|
-
const stats = statfsSync(
|
|
765
|
+
const stats = statfsSync(path40);
|
|
517
766
|
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
518
767
|
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
519
768
|
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
@@ -533,7 +782,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
533
782
|
}
|
|
534
783
|
return {
|
|
535
784
|
ok,
|
|
536
|
-
path:
|
|
785
|
+
path: path40,
|
|
537
786
|
freeBytes,
|
|
538
787
|
totalBytes,
|
|
539
788
|
usedPercent,
|
|
@@ -549,7 +798,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
549
798
|
import os2 from "node:os";
|
|
550
799
|
|
|
551
800
|
// src/bounded-build/meminfo.ts
|
|
552
|
-
import { readFileSync as
|
|
801
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
553
802
|
import os from "node:os";
|
|
554
803
|
function readMemAvailableBytes(meminfoText) {
|
|
555
804
|
if (meminfoText !== void 0) {
|
|
@@ -559,7 +808,7 @@ function readMemAvailableBytes(meminfoText) {
|
|
|
559
808
|
}
|
|
560
809
|
if (process.platform === "linux") {
|
|
561
810
|
try {
|
|
562
|
-
const meminfo =
|
|
811
|
+
const meminfo = readFileSync5("/proc/meminfo", "utf8");
|
|
563
812
|
const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m);
|
|
564
813
|
if (match) return Number(match[1]) * 1024;
|
|
565
814
|
} catch {
|
|
@@ -569,37 +818,37 @@ function readMemAvailableBytes(meminfoText) {
|
|
|
569
818
|
}
|
|
570
819
|
|
|
571
820
|
// src/resource-gate.ts
|
|
572
|
-
import
|
|
821
|
+
import path7 from "node:path";
|
|
573
822
|
|
|
574
823
|
// src/run-store.ts
|
|
575
|
-
import { existsSync as
|
|
576
|
-
import
|
|
824
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2 } from "node:fs";
|
|
825
|
+
import path6 from "node:path";
|
|
577
826
|
|
|
578
827
|
// src/paths.ts
|
|
579
|
-
import { existsSync as
|
|
580
|
-
import { homedir as
|
|
581
|
-
import
|
|
582
|
-
var LEGACY_ROOT =
|
|
828
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
829
|
+
import { homedir as homedir4 } from "node:os";
|
|
830
|
+
import path5 from "node:path";
|
|
831
|
+
var LEGACY_ROOT = path5.join(homedir4(), ".openclaw", "harness");
|
|
583
832
|
function resolveHarnessRoot() {
|
|
584
833
|
const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
|
|
585
834
|
if (env) return resolveUserPath(env);
|
|
586
835
|
const configured = loadUserConfig().harnessRoot?.trim();
|
|
587
836
|
if (configured) return resolveUserPath(configured);
|
|
588
|
-
const kynverRoot =
|
|
589
|
-
if (
|
|
590
|
-
if (
|
|
837
|
+
const kynverRoot = path5.join(homedir4(), ".kynver", "harness");
|
|
838
|
+
if (existsSync5(kynverRoot)) return kynverRoot;
|
|
839
|
+
if (existsSync5(LEGACY_ROOT)) return LEGACY_ROOT;
|
|
591
840
|
return kynverRoot;
|
|
592
841
|
}
|
|
593
842
|
function getHarnessPaths() {
|
|
594
843
|
const harnessRoot = resolveHarnessRoot();
|
|
595
844
|
return {
|
|
596
845
|
harnessRoot,
|
|
597
|
-
runsDir:
|
|
598
|
-
worktreesDir:
|
|
846
|
+
runsDir: path5.join(harnessRoot, "runs"),
|
|
847
|
+
worktreesDir: path5.join(harnessRoot, "worktrees")
|
|
599
848
|
};
|
|
600
849
|
}
|
|
601
850
|
function runDir(runsDir, id) {
|
|
602
|
-
return
|
|
851
|
+
return path5.join(runsDir, safeSlug(id));
|
|
603
852
|
}
|
|
604
853
|
|
|
605
854
|
// src/run-store.ts
|
|
@@ -608,16 +857,16 @@ function getPaths() {
|
|
|
608
857
|
}
|
|
609
858
|
function loadRun(id) {
|
|
610
859
|
const { runsDir } = getPaths();
|
|
611
|
-
return readJson(
|
|
860
|
+
return readJson(path6.join(runDir(runsDir, safeSlug(id)), "run.json"));
|
|
612
861
|
}
|
|
613
862
|
function listRunRecords() {
|
|
614
863
|
const { runsDir } = getPaths();
|
|
615
|
-
if (!
|
|
864
|
+
if (!existsSync6(runsDir)) return [];
|
|
616
865
|
const runs = [];
|
|
617
866
|
for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
|
|
618
867
|
if (!entry.isDirectory()) continue;
|
|
619
868
|
const run = readJson(
|
|
620
|
-
|
|
869
|
+
path6.join(runsDir, entry.name, "run.json"),
|
|
621
870
|
void 0
|
|
622
871
|
);
|
|
623
872
|
if (run?.id) runs.push(run);
|
|
@@ -627,16 +876,16 @@ function listRunRecords() {
|
|
|
627
876
|
function loadWorker(runId, name) {
|
|
628
877
|
const { runsDir } = getPaths();
|
|
629
878
|
return readJson(
|
|
630
|
-
|
|
879
|
+
path6.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
|
|
631
880
|
);
|
|
632
881
|
}
|
|
633
882
|
function saveRun(run) {
|
|
634
883
|
const { runsDir } = getPaths();
|
|
635
|
-
writeJson(
|
|
884
|
+
writeJson(path6.join(runDir(runsDir, run.id), "run.json"), run);
|
|
636
885
|
}
|
|
637
886
|
function saveWorker(runId, worker) {
|
|
638
887
|
const { runsDir } = getPaths();
|
|
639
|
-
writeJson(
|
|
888
|
+
writeJson(path6.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
|
|
640
889
|
}
|
|
641
890
|
function runDirectory(id) {
|
|
642
891
|
const { runsDir } = getPaths();
|
|
@@ -644,7 +893,7 @@ function runDirectory(id) {
|
|
|
644
893
|
}
|
|
645
894
|
|
|
646
895
|
// src/heartbeat.ts
|
|
647
|
-
import { existsSync as
|
|
896
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
|
|
648
897
|
var HEARTBEAT_FUTURE_SKEW_MS = 6e4;
|
|
649
898
|
function isTerminalHeartbeatPhase(phase) {
|
|
650
899
|
return phase === "complete";
|
|
@@ -663,10 +912,10 @@ function parseHeartbeat(file) {
|
|
|
663
912
|
heartbeatBlocker: null,
|
|
664
913
|
timestampAnomalies: []
|
|
665
914
|
};
|
|
666
|
-
if (!
|
|
915
|
+
if (!existsSync7(file)) return result;
|
|
667
916
|
const maxFutureMs = Date.now() + HEARTBEAT_FUTURE_SKEW_MS;
|
|
668
917
|
const clampedTo = new Date(maxFutureMs).toISOString();
|
|
669
|
-
const lines =
|
|
918
|
+
const lines = readFileSync6(file, "utf8").split("\n").filter(Boolean);
|
|
670
919
|
for (const line of lines) {
|
|
671
920
|
const entry = safeJson(line);
|
|
672
921
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
@@ -693,7 +942,7 @@ function parseHeartbeat(file) {
|
|
|
693
942
|
}
|
|
694
943
|
|
|
695
944
|
// src/stream.ts
|
|
696
|
-
import { existsSync as
|
|
945
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
|
|
697
946
|
|
|
698
947
|
// src/shell-command-outcome.ts
|
|
699
948
|
var NPM_AUDIT_RE = /\bnpm\s+audit\b/i;
|
|
@@ -897,8 +1146,8 @@ function parseHarnessStream(file) {
|
|
|
897
1146
|
error: null,
|
|
898
1147
|
lastShellOutcome: null
|
|
899
1148
|
};
|
|
900
|
-
if (!
|
|
901
|
-
const lines =
|
|
1149
|
+
if (!existsSync8(file)) return result;
|
|
1150
|
+
const lines = readFileSync7(file, "utf8").split("\n").filter(Boolean);
|
|
902
1151
|
for (const line of lines) {
|
|
903
1152
|
const event = safeJson(line);
|
|
904
1153
|
if (!event) continue;
|
|
@@ -1052,238 +1301,63 @@ function classifyExitFailure(errorText) {
|
|
|
1052
1301
|
}
|
|
1053
1302
|
}
|
|
1054
1303
|
return null;
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
// src/exited-salvage.ts
|
|
1058
|
-
function trimOrNull(value) {
|
|
1059
|
-
if (typeof value !== "string") return null;
|
|
1060
|
-
const trimmed = value.trim();
|
|
1061
|
-
return trimmed.length ? trimmed : null;
|
|
1062
|
-
}
|
|
1063
|
-
function hasFinalResult(value) {
|
|
1064
|
-
if (value === void 0 || value === null) return false;
|
|
1065
|
-
if (typeof value === "string") return value.trim().length > 0;
|
|
1066
|
-
if (typeof value === "boolean") return value;
|
|
1067
|
-
if (Array.isArray(value)) return value.length > 0;
|
|
1068
|
-
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
1069
|
-
return true;
|
|
1070
|
-
}
|
|
1071
|
-
function committedHeadFromAncestry(ancestry) {
|
|
1072
|
-
if (!ancestry?.checked) return null;
|
|
1073
|
-
if (ancestry.headIsAncestorOfBase !== false) return null;
|
|
1074
|
-
return trimOrNull(ancestry.head);
|
|
1075
|
-
}
|
|
1076
|
-
function buildAttentionReason(kind, uncommittedCount, headCommit) {
|
|
1077
|
-
const parts = ["exited_with_changes_salvage"];
|
|
1078
|
-
if (kind === "uncommitted" || kind === "both") {
|
|
1079
|
-
parts.push(
|
|
1080
|
-
`${uncommittedCount} uncommitted change${uncommittedCount === 1 ? "" : "s"} with no final result`
|
|
1081
|
-
);
|
|
1082
|
-
}
|
|
1083
|
-
if ((kind === "committed_ahead" || kind === "both") && headCommit) {
|
|
1084
|
-
const sha = headCommit.length > 12 ? headCommit.slice(0, 12) : headCommit;
|
|
1085
|
-
parts.push(`commit ${sha} ahead of base with no final result`);
|
|
1086
|
-
}
|
|
1087
|
-
parts.push("review worktree \u2014 commit, open a PR, or run a salvage worker before discarding");
|
|
1088
|
-
return parts.join(": ");
|
|
1089
|
-
}
|
|
1090
|
-
function assessExitedWorkerSalvage(input) {
|
|
1091
|
-
if (input.alive || hasFinalResult(input.finalResult)) return null;
|
|
1092
|
-
const uncommittedCount = (input.changedFiles ?? []).filter((line) => line.trim()).length;
|
|
1093
|
-
const headCommit = trimOrNull(input.headCommit) ?? committedHeadFromAncestry(input.gitAncestry);
|
|
1094
|
-
const hasUncommitted = uncommittedCount > 0;
|
|
1095
|
-
const hasCommittedAhead = Boolean(headCommit);
|
|
1096
|
-
if (!hasUncommitted && !hasCommittedAhead) {
|
|
1097
|
-
return {
|
|
1098
|
-
kind: "none",
|
|
1099
|
-
salvageable: false,
|
|
1100
|
-
uncommittedCount: 0,
|
|
1101
|
-
headCommit: null,
|
|
1102
|
-
attentionReason: "process exited without a final result"
|
|
1103
|
-
};
|
|
1104
|
-
}
|
|
1105
|
-
const kind = hasUncommitted && hasCommittedAhead ? "both" : hasUncommitted ? "uncommitted" : "committed_ahead";
|
|
1106
|
-
return {
|
|
1107
|
-
kind,
|
|
1108
|
-
salvageable: true,
|
|
1109
|
-
uncommittedCount,
|
|
1110
|
-
headCommit,
|
|
1111
|
-
attentionReason: buildAttentionReason(kind, uncommittedCount, headCommit)
|
|
1112
|
-
};
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
// src/git.ts
|
|
1116
|
-
import { spawnSync } from "node:child_process";
|
|
1117
|
-
|
|
1118
|
-
// src/worker-env.ts
|
|
1119
|
-
var FORBIDDEN_WORKER_ENV_KEYS = [
|
|
1120
|
-
"ANTHROPIC_API_KEY",
|
|
1121
|
-
"ANALYST_API_KEY",
|
|
1122
|
-
"RECRUITER_API_KEY",
|
|
1123
|
-
"AUTH_SECRET",
|
|
1124
|
-
"NEXTAUTH_SECRET",
|
|
1125
|
-
"DATABASE_URL",
|
|
1126
|
-
"PRODUCTION_DATABASE_URL",
|
|
1127
|
-
"REDIS_URL",
|
|
1128
|
-
"GOOGLE_CLIENT_SECRET",
|
|
1129
|
-
"GITHUB_CLIENT_SECRET",
|
|
1130
|
-
"KYNVER_API_KEY",
|
|
1131
|
-
"KYNVER_SERVICE_SECRET",
|
|
1132
|
-
"KYNVER_RUNTIME_SECRET",
|
|
1133
|
-
"OPENCLAW_CRON_SECRET",
|
|
1134
|
-
"QSTASH_TOKEN",
|
|
1135
|
-
"QSTASH_CURRENT_SIGNING_KEY",
|
|
1136
|
-
"QSTASH_NEXT_SIGNING_KEY",
|
|
1137
|
-
"TOOL_SECRETS_KEK",
|
|
1138
|
-
"TOOL_EXECUTOR_DISPATCH_SECRET",
|
|
1139
|
-
"CLOUDFLARE_API_TOKEN",
|
|
1140
|
-
"STRIPE_SECRET_KEY",
|
|
1141
|
-
"STRIPE_WEBHOOK_SECRET",
|
|
1142
|
-
"STRIPE_IDENTITY_WEBHOOK_SECRET",
|
|
1143
|
-
"VOYAGE_API_KEY",
|
|
1144
|
-
"PERPLEXITY_API_KEY",
|
|
1145
|
-
"FRED_API_KEY",
|
|
1146
|
-
"FMP_API_KEY",
|
|
1147
|
-
"CURSOR_API_KEY"
|
|
1148
|
-
];
|
|
1149
|
-
var FORBIDDEN_KEY_SET = new Set(FORBIDDEN_WORKER_ENV_KEYS);
|
|
1150
|
-
var FORBIDDEN_SUFFIXES = ["_SECRET", "_API_KEY"];
|
|
1151
|
-
function isForbiddenWorkerEnvKey(key) {
|
|
1152
|
-
if (FORBIDDEN_KEY_SET.has(key)) return true;
|
|
1153
|
-
return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));
|
|
1154
|
-
}
|
|
1155
|
-
function listForbiddenWorkerEnvKeys(env) {
|
|
1156
|
-
return Object.keys(env).filter(isForbiddenWorkerEnvKey).sort();
|
|
1157
|
-
}
|
|
1158
|
-
function scrubWorkerEnv(env) {
|
|
1159
|
-
const next = { ...env };
|
|
1160
|
-
for (const key of Object.keys(next)) {
|
|
1161
|
-
if (isForbiddenWorkerEnvKey(key)) delete next[key];
|
|
1162
|
-
}
|
|
1163
|
-
return next;
|
|
1164
|
-
}
|
|
1165
|
-
function auditWorkerEnv(env) {
|
|
1166
|
-
const forbiddenPresent = listForbiddenWorkerEnvKeys(env);
|
|
1167
|
-
return { forbiddenPresent, safe: forbiddenPresent.length === 0 };
|
|
1168
|
-
}
|
|
1169
|
-
function scrubClaudeEnv(env) {
|
|
1170
|
-
return scrubWorkerEnv(env);
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
// src/git.ts
|
|
1174
|
-
function git(cwd, args, options = {}) {
|
|
1175
|
-
const res = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
1176
|
-
if (res.status !== 0 && !options.allowFailure) {
|
|
1177
|
-
const message = `git ${args.join(" ")} failed: ${res.stderr || res.stdout}`;
|
|
1178
|
-
if (options.throwError) throw new Error(message);
|
|
1179
|
-
fail(message);
|
|
1180
|
-
}
|
|
1181
|
-
return res.stdout || "";
|
|
1182
|
-
}
|
|
1183
|
-
function ensureGitRepo(repo) {
|
|
1184
|
-
git(repo, ["rev-parse", "--show-toplevel"]);
|
|
1185
|
-
}
|
|
1186
|
-
function gitStatusShort(worktreePath) {
|
|
1187
|
-
return git(worktreePath, ["status", "--short"], { allowFailure: true }).split("\n").map((line) => line.trim()).filter(Boolean);
|
|
1188
|
-
}
|
|
1189
|
-
function gitCapture(cwd, args) {
|
|
1190
|
-
try {
|
|
1191
|
-
const res = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
1192
|
-
return {
|
|
1193
|
-
status: res.status,
|
|
1194
|
-
stdout: res.stdout || "",
|
|
1195
|
-
stderr: res.stderr || "",
|
|
1196
|
-
error: res.error ? res.error.message : null
|
|
1197
|
-
};
|
|
1198
|
-
} catch (error) {
|
|
1199
|
-
return {
|
|
1200
|
-
status: null,
|
|
1201
|
-
stdout: "",
|
|
1202
|
-
stderr: "",
|
|
1203
|
-
error: error.message
|
|
1204
|
-
};
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
function gitIsAncestor(cwd, ancestor, descendant) {
|
|
1208
|
-
const res = gitCapture(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]);
|
|
1209
|
-
if (res.status === 0) return { isAncestor: true, error: null };
|
|
1210
|
-
if (res.status === 1) return { isAncestor: false, error: null };
|
|
1211
|
-
return { isAncestor: null, error: res.error || res.stderr || res.stdout || `git exited ${res.status}` };
|
|
1212
|
-
}
|
|
1213
|
-
function computeGitAncestry(worktreePath, baseOrOptions = "origin/main") {
|
|
1214
|
-
const options = typeof baseOrOptions === "string" ? { base: baseOrOptions } : baseOrOptions;
|
|
1215
|
-
const baseLabel = options.baseCommit?.trim() || options.base?.trim() || "origin/main";
|
|
1216
|
-
const pinnedBaseCommit = options.baseCommit?.trim() || null;
|
|
1217
|
-
if (!worktreePath) {
|
|
1218
|
-
return unknownAncestry(baseLabel, "missing worktree path");
|
|
1219
|
-
}
|
|
1220
|
-
const head = gitCapture(worktreePath, ["rev-parse", "HEAD"]);
|
|
1221
|
-
if (head.status !== 0) {
|
|
1222
|
-
return unknownAncestry(baseLabel, head.error || head.stderr || head.stdout || "failed to resolve HEAD");
|
|
1223
|
-
}
|
|
1224
|
-
let baseSha;
|
|
1225
|
-
if (pinnedBaseCommit) {
|
|
1226
|
-
baseSha = pinnedBaseCommit;
|
|
1227
|
-
} else {
|
|
1228
|
-
const baseHead = gitCapture(worktreePath, ["rev-parse", baseLabel]);
|
|
1229
|
-
if (baseHead.status !== 0) {
|
|
1230
|
-
return unknownAncestry(
|
|
1231
|
-
baseLabel,
|
|
1232
|
-
baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${baseLabel}`,
|
|
1233
|
-
head.stdout.trim()
|
|
1234
|
-
);
|
|
1235
|
-
}
|
|
1236
|
-
baseSha = baseHead.stdout.trim();
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
// src/exited-salvage.ts
|
|
1307
|
+
function trimOrNull(value) {
|
|
1308
|
+
if (typeof value !== "string") return null;
|
|
1309
|
+
const trimmed = value.trim();
|
|
1310
|
+
return trimmed.length ? trimmed : null;
|
|
1311
|
+
}
|
|
1312
|
+
function hasFinalResult(value) {
|
|
1313
|
+
if (value === void 0 || value === null) return false;
|
|
1314
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
1315
|
+
if (typeof value === "boolean") return value;
|
|
1316
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
1317
|
+
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
1318
|
+
return true;
|
|
1319
|
+
}
|
|
1320
|
+
function committedHeadFromAncestry(ancestry) {
|
|
1321
|
+
if (!ancestry?.checked) return null;
|
|
1322
|
+
if (ancestry.headIsAncestorOfBase !== false) return null;
|
|
1323
|
+
return trimOrNull(ancestry.head);
|
|
1324
|
+
}
|
|
1325
|
+
function buildAttentionReason(kind, uncommittedCount, headCommit) {
|
|
1326
|
+
const parts = ["exited_with_changes_salvage"];
|
|
1327
|
+
if (kind === "uncommitted" || kind === "both") {
|
|
1328
|
+
parts.push(
|
|
1329
|
+
`${uncommittedCount} uncommitted change${uncommittedCount === 1 ? "" : "s"} with no final result`
|
|
1330
|
+
);
|
|
1237
1331
|
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
checked: true,
|
|
1242
|
-
base: baseLabel,
|
|
1243
|
-
head: headSha,
|
|
1244
|
-
baseHead: baseSha,
|
|
1245
|
-
baseIsAncestorOfHead: true,
|
|
1246
|
-
headIsAncestorOfBase: true,
|
|
1247
|
-
relation: "synced"
|
|
1248
|
-
};
|
|
1332
|
+
if ((kind === "committed_ahead" || kind === "both") && headCommit) {
|
|
1333
|
+
const sha = headCommit.length > 12 ? headCommit.slice(0, 12) : headCommit;
|
|
1334
|
+
parts.push(`commit ${sha} ahead of base with no final result`);
|
|
1249
1335
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1336
|
+
parts.push("review worktree \u2014 commit, open a PR, or run a salvage worker before discarding");
|
|
1337
|
+
return parts.join(": ");
|
|
1338
|
+
}
|
|
1339
|
+
function assessExitedWorkerSalvage(input) {
|
|
1340
|
+
if (input.alive || hasFinalResult(input.finalResult)) return null;
|
|
1341
|
+
const uncommittedCount = (input.changedFiles ?? []).filter((line) => line.trim()).length;
|
|
1342
|
+
const headCommit = trimOrNull(input.headCommit) ?? committedHeadFromAncestry(input.gitAncestry);
|
|
1343
|
+
const hasUncommitted = uncommittedCount > 0;
|
|
1344
|
+
const hasCommittedAhead = Boolean(headCommit);
|
|
1345
|
+
if (!hasUncommitted && !hasCommittedAhead) {
|
|
1254
1346
|
return {
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
|
|
1261
|
-
relation: "unknown",
|
|
1262
|
-
...error ? { error } : {}
|
|
1347
|
+
kind: "none",
|
|
1348
|
+
salvageable: false,
|
|
1349
|
+
uncommittedCount: 0,
|
|
1350
|
+
headCommit: null,
|
|
1351
|
+
attentionReason: "process exited without a final result"
|
|
1263
1352
|
};
|
|
1264
1353
|
}
|
|
1265
|
-
const
|
|
1266
|
-
return {
|
|
1267
|
-
checked: true,
|
|
1268
|
-
base: baseLabel,
|
|
1269
|
-
head: headSha,
|
|
1270
|
-
baseHead: baseSha,
|
|
1271
|
-
baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
|
|
1272
|
-
headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
|
|
1273
|
-
relation,
|
|
1274
|
-
...error ? { error } : {}
|
|
1275
|
-
};
|
|
1276
|
-
}
|
|
1277
|
-
function unknownAncestry(base, error, head = null) {
|
|
1354
|
+
const kind = hasUncommitted && hasCommittedAhead ? "both" : hasUncommitted ? "uncommitted" : "committed_ahead";
|
|
1278
1355
|
return {
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
headIsAncestorOfBase: null,
|
|
1285
|
-
relation: "unknown",
|
|
1286
|
-
error
|
|
1356
|
+
kind,
|
|
1357
|
+
salvageable: true,
|
|
1358
|
+
uncommittedCount,
|
|
1359
|
+
headCommit,
|
|
1360
|
+
attentionReason: buildAttentionReason(kind, uncommittedCount, headCommit)
|
|
1287
1361
|
};
|
|
1288
1362
|
}
|
|
1289
1363
|
|
|
@@ -1645,7 +1719,7 @@ function countActiveWorkersForRun(run) {
|
|
|
1645
1719
|
let active = 0;
|
|
1646
1720
|
for (const name of Object.keys(run.workers || {})) {
|
|
1647
1721
|
const worker = readJson(
|
|
1648
|
-
|
|
1722
|
+
path7.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1649
1723
|
void 0
|
|
1650
1724
|
);
|
|
1651
1725
|
if (!worker || !isActiveHarnessWorker(worker)) continue;
|
|
@@ -1956,7 +2030,7 @@ function inferModelRoutingFromTask(task) {
|
|
|
1956
2030
|
rule: "lane:landing"
|
|
1957
2031
|
};
|
|
1958
2032
|
}
|
|
1959
|
-
if (ref.includes("review") || title
|
|
2033
|
+
if (ref.includes("review") || /^review[\s:]/.test(title) || roleLane.includes("review")) {
|
|
1960
2034
|
if (isOpusLane(ref, title) || roleLane === "deep_reviewer") {
|
|
1961
2035
|
return { model: "claude-opus-4-7", provider: "claude", rule: "lane:deep_review" };
|
|
1962
2036
|
}
|
|
@@ -2030,10 +2104,10 @@ function readHarnessRetryLimits() {
|
|
|
2030
2104
|
}
|
|
2031
2105
|
|
|
2032
2106
|
// src/lease-renewal.ts
|
|
2033
|
-
import
|
|
2107
|
+
import path8 from "node:path";
|
|
2034
2108
|
function workerRecord(runId, name) {
|
|
2035
2109
|
return readJson(
|
|
2036
|
-
|
|
2110
|
+
path8.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
2037
2111
|
void 0
|
|
2038
2112
|
);
|
|
2039
2113
|
}
|
|
@@ -2099,8 +2173,8 @@ function hasLiveWorkerForTask(runId, taskId) {
|
|
|
2099
2173
|
}
|
|
2100
2174
|
|
|
2101
2175
|
// src/supervisor.ts
|
|
2102
|
-
import { existsSync as
|
|
2103
|
-
import
|
|
2176
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync3 } from "node:fs";
|
|
2177
|
+
import path14 from "node:path";
|
|
2104
2178
|
|
|
2105
2179
|
// src/prompt.ts
|
|
2106
2180
|
function buildPrompt(input) {
|
|
@@ -2169,13 +2243,13 @@ function buildPrompt(input) {
|
|
|
2169
2243
|
}
|
|
2170
2244
|
|
|
2171
2245
|
// src/providers/cursor.ts
|
|
2172
|
-
import { closeSync as closeSync2, existsSync as
|
|
2246
|
+
import { closeSync as closeSync2, existsSync as existsSync10, openSync as openSync2 } from "node:fs";
|
|
2173
2247
|
import { spawn as spawn2 } from "node:child_process";
|
|
2174
|
-
import
|
|
2248
|
+
import path10 from "node:path";
|
|
2175
2249
|
|
|
2176
2250
|
// src/providers/cursor-windows.ts
|
|
2177
|
-
import { existsSync as
|
|
2178
|
-
import
|
|
2251
|
+
import { existsSync as existsSync9, readdirSync as readdirSync3 } from "node:fs";
|
|
2252
|
+
import path9 from "node:path";
|
|
2179
2253
|
var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
|
|
2180
2254
|
function parseCursorVersionSortKey(versionName) {
|
|
2181
2255
|
const datePart = versionName.split("-")[0];
|
|
@@ -2186,8 +2260,8 @@ function parseCursorVersionSortKey(versionName) {
|
|
|
2186
2260
|
return Number(`${year}${month.padStart(2, "0")}${day.padStart(2, "0")}`);
|
|
2187
2261
|
}
|
|
2188
2262
|
function pickLatestCursorVersionDir(agentRoot) {
|
|
2189
|
-
const versionsRoot =
|
|
2190
|
-
if (!
|
|
2263
|
+
const versionsRoot = path9.join(agentRoot, "versions");
|
|
2264
|
+
if (!existsSync9(versionsRoot)) return null;
|
|
2191
2265
|
let bestDir = null;
|
|
2192
2266
|
let bestKey = -1;
|
|
2193
2267
|
for (const entry of readdirSync3(versionsRoot, { withFileTypes: true })) {
|
|
@@ -2195,22 +2269,22 @@ function pickLatestCursorVersionDir(agentRoot) {
|
|
|
2195
2269
|
const key = parseCursorVersionSortKey(entry.name);
|
|
2196
2270
|
if (key == null || key <= bestKey) continue;
|
|
2197
2271
|
bestKey = key;
|
|
2198
|
-
bestDir =
|
|
2272
|
+
bestDir = path9.join(versionsRoot, entry.name);
|
|
2199
2273
|
}
|
|
2200
2274
|
return bestDir;
|
|
2201
2275
|
}
|
|
2202
2276
|
function resolveWindowsCursorBundled(agentRoot) {
|
|
2203
|
-
const root = agentRoot?.trim() ||
|
|
2204
|
-
const directNode =
|
|
2205
|
-
const directIndex =
|
|
2206
|
-
if (
|
|
2277
|
+
const root = agentRoot?.trim() || path9.join(process.env.LOCALAPPDATA || "", "cursor-agent");
|
|
2278
|
+
const directNode = path9.join(root, "node.exe");
|
|
2279
|
+
const directIndex = path9.join(root, "index.js");
|
|
2280
|
+
if (existsSync9(directNode) && existsSync9(directIndex)) {
|
|
2207
2281
|
return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
|
|
2208
2282
|
}
|
|
2209
2283
|
const versionDir = pickLatestCursorVersionDir(root);
|
|
2210
2284
|
if (!versionDir) return null;
|
|
2211
|
-
const nodeExe =
|
|
2212
|
-
const indexJs =
|
|
2213
|
-
if (!
|
|
2285
|
+
const nodeExe = path9.join(versionDir, "node.exe");
|
|
2286
|
+
const indexJs = path9.join(versionDir, "index.js");
|
|
2287
|
+
if (!existsSync9(nodeExe) || !existsSync9(indexJs)) return null;
|
|
2214
2288
|
return { nodeExe, indexJs, versionDir };
|
|
2215
2289
|
}
|
|
2216
2290
|
|
|
@@ -2228,13 +2302,13 @@ function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
|
|
|
2228
2302
|
function resolveCursorSpawn(agentBin) {
|
|
2229
2303
|
if (process.platform === "win32") {
|
|
2230
2304
|
const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
|
|
2231
|
-
const isBundledNode = /node\.exe$/i.test(agentBin) &&
|
|
2305
|
+
const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync10(path10.join(path10.dirname(agentBin), "index.js"));
|
|
2232
2306
|
const isDefaultShim = agentBin === "agent";
|
|
2233
2307
|
if (isCursorWrapper || isBundledNode || isDefaultShim) {
|
|
2234
|
-
const bundled = isCursorWrapper ? resolveWindowsCursorBundled(
|
|
2308
|
+
const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path10.dirname(agentBin)) : isBundledNode ? {
|
|
2235
2309
|
nodeExe: agentBin,
|
|
2236
|
-
indexJs:
|
|
2237
|
-
versionDir:
|
|
2310
|
+
indexJs: path10.join(path10.dirname(agentBin), "index.js"),
|
|
2311
|
+
versionDir: path10.dirname(agentBin)
|
|
2238
2312
|
} : resolveWindowsCursorBundled();
|
|
2239
2313
|
if (bundled) {
|
|
2240
2314
|
return bundledSpawnTarget(bundled.nodeExe, bundled.indexJs, bundled.versionDir);
|
|
@@ -2254,8 +2328,8 @@ function resolveAgentBin() {
|
|
|
2254
2328
|
process.env.KYNVER_CURSOR_AGENT_ROOT?.trim() || void 0
|
|
2255
2329
|
);
|
|
2256
2330
|
if (bundled) return bundled.nodeExe;
|
|
2257
|
-
const localAgent =
|
|
2258
|
-
if (
|
|
2331
|
+
const localAgent = path10.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
|
|
2332
|
+
if (existsSync10(localAgent)) return localAgent;
|
|
2259
2333
|
}
|
|
2260
2334
|
return "agent";
|
|
2261
2335
|
}
|
|
@@ -2264,7 +2338,7 @@ function cursorWorkerEnv(agentBin, spawnTarget) {
|
|
|
2264
2338
|
...process.env,
|
|
2265
2339
|
CI: "1",
|
|
2266
2340
|
NO_COLOR: "1",
|
|
2267
|
-
...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS:
|
|
2341
|
+
...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path10.basename(agentBin) || "agent.cmd" } : {}
|
|
2268
2342
|
});
|
|
2269
2343
|
}
|
|
2270
2344
|
var cursorProvider = {
|
|
@@ -2338,9 +2412,9 @@ function resolveWorkerProvider(name) {
|
|
|
2338
2412
|
|
|
2339
2413
|
// src/auto-complete.ts
|
|
2340
2414
|
import { spawn as spawn3 } from "node:child_process";
|
|
2341
|
-
import { existsSync as
|
|
2342
|
-
import
|
|
2343
|
-
import { fileURLToPath as
|
|
2415
|
+
import { existsSync as existsSync11, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
|
|
2416
|
+
import path13 from "node:path";
|
|
2417
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
2344
2418
|
|
|
2345
2419
|
// src/completion-ack.ts
|
|
2346
2420
|
function hasCompletionAck(worker) {
|
|
@@ -2356,7 +2430,7 @@ function persistCompletionAck(worker, runId, fields) {
|
|
|
2356
2430
|
}
|
|
2357
2431
|
|
|
2358
2432
|
// src/worker-ops.ts
|
|
2359
|
-
import
|
|
2433
|
+
import path12 from "node:path";
|
|
2360
2434
|
|
|
2361
2435
|
// src/completion-response.ts
|
|
2362
2436
|
function asRecord(value) {
|
|
@@ -2419,6 +2493,11 @@ function isHarnessExpertReviewWorker(worker) {
|
|
|
2419
2493
|
|
|
2420
2494
|
// src/pr-handoff/pr-handoff-assess.ts
|
|
2421
2495
|
var REVIEW_LANE_RULE = /^(lane:)?(review|deep_review|planning|landing)(:|$)/i;
|
|
2496
|
+
var REVIEW_PERSONA_SLUGS = /* @__PURE__ */ new Set(["lorentz", "sentinel"]);
|
|
2497
|
+
var NO_PR_COMMITS_BETWEEN_RE = /no commits between/i;
|
|
2498
|
+
function isGhNoCommitsBetweenError(detail) {
|
|
2499
|
+
return Boolean(detail && NO_PR_COMMITS_BETWEEN_RE.test(detail));
|
|
2500
|
+
}
|
|
2422
2501
|
function trimOrNull4(value) {
|
|
2423
2502
|
if (typeof value !== "string") return null;
|
|
2424
2503
|
const trimmed = value.trim();
|
|
@@ -2437,8 +2516,30 @@ function extractPrUrlFromText(value) {
|
|
|
2437
2516
|
);
|
|
2438
2517
|
return m ? trimOrNull4(m[0]) : null;
|
|
2439
2518
|
}
|
|
2440
|
-
function
|
|
2519
|
+
function countCommitsAheadOfBase(worktreePath, baseRef, exec) {
|
|
2520
|
+
const base = baseRef.trim();
|
|
2521
|
+
if (!base) return null;
|
|
2522
|
+
const count = exec.git(worktreePath, ["rev-list", "--count", `${base}..HEAD`]);
|
|
2523
|
+
if (count.status !== 0) return null;
|
|
2524
|
+
const n = Number.parseInt(count.stdout.trim(), 10);
|
|
2525
|
+
return Number.isFinite(n) ? n : null;
|
|
2526
|
+
}
|
|
2527
|
+
function isReviewArtifactWorker(worker, snapshot) {
|
|
2528
|
+
if (snapshot.changedFiles.length > 0) return false;
|
|
2529
|
+
const persona = trimOrNull4(worker.personaSlug)?.toLowerCase();
|
|
2530
|
+
if (persona && REVIEW_PERSONA_SLUGS.has(persona)) return true;
|
|
2531
|
+
const rule = trimOrNull4(worker.routingRule) ?? "";
|
|
2532
|
+
if (rule && REVIEW_LANE_RULE.test(rule)) return true;
|
|
2533
|
+
return false;
|
|
2534
|
+
}
|
|
2535
|
+
function hasWorkProduct(snapshot, options) {
|
|
2441
2536
|
if (snapshot.changedFiles.length > 0) return true;
|
|
2537
|
+
const baseRef = trimOrNull4(options?.baseRef);
|
|
2538
|
+
if (baseRef && options?.exec && options.worktreePath) {
|
|
2539
|
+
const ahead = countCommitsAheadOfBase(options.worktreePath, baseRef, options.exec);
|
|
2540
|
+
if (ahead === 0) return false;
|
|
2541
|
+
if (ahead !== null && ahead > 0) return true;
|
|
2542
|
+
}
|
|
2442
2543
|
if (trimOrNull4(snapshot.headCommit)) return true;
|
|
2443
2544
|
if (committedHead(snapshot.gitAncestry)) return true;
|
|
2444
2545
|
return false;
|
|
@@ -2459,6 +2560,13 @@ function assessPrHandoffRequirement(input) {
|
|
|
2459
2560
|
if (rule && REVIEW_LANE_RULE.test(rule)) {
|
|
2460
2561
|
return { required: false, reason: "review_lane" };
|
|
2461
2562
|
}
|
|
2563
|
+
const workerCtx = input.worker ?? {
|
|
2564
|
+
personaSlug: input.personaSlug,
|
|
2565
|
+
routingRule: input.routingRule
|
|
2566
|
+
};
|
|
2567
|
+
if (isReviewArtifactWorker(workerCtx, input.snapshot)) {
|
|
2568
|
+
return { required: false, reason: "review_artifact" };
|
|
2569
|
+
}
|
|
2462
2570
|
if (trimOrNull4(input.patchPath) || trimOrNull4(input.artifactBundlePath)) {
|
|
2463
2571
|
return { required: false, reason: "patch_or_bundle" };
|
|
2464
2572
|
}
|
|
@@ -2466,7 +2574,12 @@ function assessPrHandoffRequirement(input) {
|
|
|
2466
2574
|
if (prUrl) {
|
|
2467
2575
|
return { required: false, reason: "already_has_pr" };
|
|
2468
2576
|
}
|
|
2469
|
-
|
|
2577
|
+
const workProductOpts = input.exec && input.baseRef ? {
|
|
2578
|
+
baseRef: input.baseRef,
|
|
2579
|
+
exec: input.exec,
|
|
2580
|
+
worktreePath: input.snapshot.worktreePath
|
|
2581
|
+
} : void 0;
|
|
2582
|
+
if (!hasWorkProduct(input.snapshot, workProductOpts)) {
|
|
2470
2583
|
return { required: false, reason: "no_work_product" };
|
|
2471
2584
|
}
|
|
2472
2585
|
return { required: true, snapshot: input.snapshot };
|
|
@@ -2675,15 +2788,19 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
|
|
|
2675
2788
|
prUrl: prUrlHint,
|
|
2676
2789
|
headCommit: null
|
|
2677
2790
|
});
|
|
2791
|
+
const baseRef = input.run.baseCommit?.trim() || input.run.base?.trim() || "origin/main";
|
|
2678
2792
|
const requirement = assessPrHandoffRequirement({
|
|
2679
2793
|
dispatched: input.worker.dispatched,
|
|
2680
2794
|
routingRule: input.worker.routingRule,
|
|
2795
|
+
personaSlug: input.worker.personaSlug,
|
|
2681
2796
|
prUrl: prUrlHint,
|
|
2682
2797
|
taskTitle: input.worker.taskTitle,
|
|
2683
2798
|
executorRef: input.worker.executorRef,
|
|
2684
2799
|
parentTaskId: input.worker.parentTaskId,
|
|
2685
|
-
personaSlug: input.worker.personaSlug,
|
|
2686
2800
|
taskPrUrl: input.worker.taskPrUrl,
|
|
2801
|
+
baseRef,
|
|
2802
|
+
exec,
|
|
2803
|
+
worker: input.worker,
|
|
2687
2804
|
snapshot
|
|
2688
2805
|
});
|
|
2689
2806
|
if (!requirement.required) {
|
|
@@ -2768,6 +2885,14 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
|
|
|
2768
2885
|
exec
|
|
2769
2886
|
});
|
|
2770
2887
|
if (!pr.ok || !pr.prUrl) {
|
|
2888
|
+
if (isGhNoCommitsBetweenError(pr.detail)) {
|
|
2889
|
+
return {
|
|
2890
|
+
ok: true,
|
|
2891
|
+
headCommit: headCommit ?? void 0,
|
|
2892
|
+
committed,
|
|
2893
|
+
pushed
|
|
2894
|
+
};
|
|
2895
|
+
}
|
|
2771
2896
|
const dirty = snapshot.changedFiles.length;
|
|
2772
2897
|
const detail = dirty ? `${dirty} uncommitted change(s) and no PR URL after handoff attempt` : "no PR URL after handoff attempt";
|
|
2773
2898
|
return {
|
|
@@ -2787,7 +2912,7 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
|
|
|
2787
2912
|
}
|
|
2788
2913
|
|
|
2789
2914
|
// src/worker-lifecycle.ts
|
|
2790
|
-
import
|
|
2915
|
+
import path11 from "node:path";
|
|
2791
2916
|
var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
|
|
2792
2917
|
"awaiting_review",
|
|
2793
2918
|
"blocked",
|
|
@@ -2843,7 +2968,7 @@ function syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick) {
|
|
|
2843
2968
|
const synced = [];
|
|
2844
2969
|
for (const name of Object.keys(run.workers || {})) {
|
|
2845
2970
|
const worker = readJson(
|
|
2846
|
-
|
|
2971
|
+
path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2847
2972
|
void 0
|
|
2848
2973
|
);
|
|
2849
2974
|
if (!worker?.taskId || isCompletionAcknowledged(worker)) continue;
|
|
@@ -3101,7 +3226,7 @@ function workerStatus(args) {
|
|
|
3101
3226
|
const worker = loadWorker(String(args.run), String(args.name));
|
|
3102
3227
|
const run = loadRun(worker.runId);
|
|
3103
3228
|
const status = computeWorkerStatus(worker, workerStatusOptions(run));
|
|
3104
|
-
writeJson(
|
|
3229
|
+
writeJson(path12.join(worker.workerDir, "last-status.json"), status);
|
|
3105
3230
|
console.log(JSON.stringify(status, null, 2));
|
|
3106
3231
|
}
|
|
3107
3232
|
function buildRunBoard(runId) {
|
|
@@ -3109,7 +3234,7 @@ function buildRunBoard(runId) {
|
|
|
3109
3234
|
const names = Object.keys(run.workers || {});
|
|
3110
3235
|
const workers = names.map((name) => {
|
|
3111
3236
|
const worker = readJson(
|
|
3112
|
-
|
|
3237
|
+
path12.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
3113
3238
|
void 0
|
|
3114
3239
|
);
|
|
3115
3240
|
if (!worker) {
|
|
@@ -3220,7 +3345,7 @@ function buildRunBoard(runId) {
|
|
|
3220
3345
|
needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
|
|
3221
3346
|
workers
|
|
3222
3347
|
};
|
|
3223
|
-
writeJson(
|
|
3348
|
+
writeJson(path12.join(runDirectory(run.id), "last-board.json"), board);
|
|
3224
3349
|
return board;
|
|
3225
3350
|
}
|
|
3226
3351
|
async function publishHarnessBoardSnapshot(args, source) {
|
|
@@ -3409,12 +3534,12 @@ async function autoCompleteWorkerCli(raw) {
|
|
|
3409
3534
|
}
|
|
3410
3535
|
}
|
|
3411
3536
|
function resolveDefaultCliPath() {
|
|
3412
|
-
return
|
|
3537
|
+
return path13.join(fileURLToPath3(new URL(".", import.meta.url)), "cli.js");
|
|
3413
3538
|
}
|
|
3414
3539
|
function spawnCompletionSidecar(opts) {
|
|
3415
3540
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath();
|
|
3416
|
-
if (!
|
|
3417
|
-
const logPath =
|
|
3541
|
+
if (!existsSync11(cliPath)) return void 0;
|
|
3542
|
+
const logPath = path13.join(opts.workerDir, "auto-complete.log");
|
|
3418
3543
|
let logFd;
|
|
3419
3544
|
try {
|
|
3420
3545
|
logFd = openSync3(logPath, "a");
|
|
@@ -3496,16 +3621,16 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3496
3621
|
launchModel = preflight.model;
|
|
3497
3622
|
}
|
|
3498
3623
|
const { worktreesDir } = getPaths();
|
|
3499
|
-
const workerDir =
|
|
3624
|
+
const workerDir = path14.join(runDirectory(run.id), "workers", name);
|
|
3500
3625
|
mkdirSync3(workerDir, { recursive: true });
|
|
3501
|
-
const worktreePath =
|
|
3626
|
+
const worktreePath = path14.join(worktreesDir, run.id, name);
|
|
3502
3627
|
const branch = opts.branch || `agent/${run.id}/${name}`;
|
|
3503
|
-
if (
|
|
3628
|
+
if (existsSync12(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
|
|
3504
3629
|
git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
|
|
3505
3630
|
git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
|
|
3506
|
-
const stdoutPath =
|
|
3507
|
-
const stderrPath =
|
|
3508
|
-
const heartbeatPath =
|
|
3631
|
+
const stdoutPath = path14.join(workerDir, "stdout.jsonl");
|
|
3632
|
+
const stderrPath = path14.join(workerDir, "stderr.log");
|
|
3633
|
+
const heartbeatPath = path14.join(workerDir, "heartbeat.jsonl");
|
|
3509
3634
|
const prompt = buildPrompt({
|
|
3510
3635
|
task: opts.task,
|
|
3511
3636
|
ownedPaths: opts.ownedPaths || [],
|
|
@@ -3570,7 +3695,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3570
3695
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3571
3696
|
};
|
|
3572
3697
|
saveWorker(run.id, worker);
|
|
3573
|
-
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath:
|
|
3698
|
+
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path14.join(workerDir, "worker.json") } };
|
|
3574
3699
|
run.status = "running";
|
|
3575
3700
|
saveRun(run);
|
|
3576
3701
|
if (worker.agentOsId && worker.taskId) {
|
|
@@ -3862,18 +3987,18 @@ function buildPlanPersistIdempotencyKey(input) {
|
|
|
3862
3987
|
|
|
3863
3988
|
// src/plan-persist/paths.ts
|
|
3864
3989
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
3865
|
-
import { homedir as
|
|
3866
|
-
import
|
|
3990
|
+
import { homedir as homedir5 } from "node:os";
|
|
3991
|
+
import path15 from "node:path";
|
|
3867
3992
|
function resolveKynverStateRoot() {
|
|
3868
3993
|
const env = process.env.KYNVER_STATE_ROOT;
|
|
3869
|
-
if (env) return
|
|
3870
|
-
return
|
|
3994
|
+
if (env) return path15.resolve(env);
|
|
3995
|
+
return path15.join(homedir5(), ".kynver", "state");
|
|
3871
3996
|
}
|
|
3872
3997
|
function planOutboxDir() {
|
|
3873
|
-
return
|
|
3998
|
+
return path15.join(resolveKynverStateRoot(), "plan-outbox");
|
|
3874
3999
|
}
|
|
3875
4000
|
function planOutboxArchiveDir() {
|
|
3876
|
-
return
|
|
4001
|
+
return path15.join(resolveKynverStateRoot(), "plan-outbox-archive");
|
|
3877
4002
|
}
|
|
3878
4003
|
function ensurePlanOutboxDirs() {
|
|
3879
4004
|
const outboxDir = planOutboxDir();
|
|
@@ -3884,20 +4009,20 @@ function ensurePlanOutboxDirs() {
|
|
|
3884
4009
|
}
|
|
3885
4010
|
function isTmpOnlyPath(filePath) {
|
|
3886
4011
|
if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
|
|
3887
|
-
const resolved =
|
|
3888
|
-
return resolved.startsWith("/tmp/") || resolved.startsWith(
|
|
4012
|
+
const resolved = path15.resolve(filePath);
|
|
4013
|
+
return resolved.startsWith("/tmp/") || resolved.startsWith(path15.join("/var", "folders"));
|
|
3889
4014
|
}
|
|
3890
4015
|
|
|
3891
4016
|
// src/plan-persist/outbox-store.ts
|
|
3892
4017
|
import {
|
|
3893
|
-
existsSync as
|
|
3894
|
-
readFileSync as
|
|
4018
|
+
existsSync as existsSync14,
|
|
4019
|
+
readFileSync as readFileSync8,
|
|
3895
4020
|
renameSync,
|
|
3896
4021
|
readdirSync as readdirSync4,
|
|
3897
4022
|
writeFileSync as writeFileSync3,
|
|
3898
4023
|
unlinkSync
|
|
3899
4024
|
} from "node:fs";
|
|
3900
|
-
import
|
|
4025
|
+
import path16 from "node:path";
|
|
3901
4026
|
import { randomUUID } from "node:crypto";
|
|
3902
4027
|
var DEFAULT_MAX_RETRIES = 12;
|
|
3903
4028
|
function listOutboxItems() {
|
|
@@ -3905,7 +4030,7 @@ function listOutboxItems() {
|
|
|
3905
4030
|
const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
|
|
3906
4031
|
const items = [];
|
|
3907
4032
|
for (const file of files) {
|
|
3908
|
-
const item = readOutboxItem(
|
|
4033
|
+
const item = readOutboxItem(path16.join(outboxDir, file));
|
|
3909
4034
|
if (item && item.queueStatus === "queued") items.push(item);
|
|
3910
4035
|
}
|
|
3911
4036
|
return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
@@ -3917,25 +4042,25 @@ function findOutboxByIdempotencyKey(key) {
|
|
|
3917
4042
|
return null;
|
|
3918
4043
|
}
|
|
3919
4044
|
function readOutboxItem(jsonPath) {
|
|
3920
|
-
if (!
|
|
4045
|
+
if (!existsSync14(jsonPath)) return null;
|
|
3921
4046
|
try {
|
|
3922
|
-
return JSON.parse(
|
|
4047
|
+
return JSON.parse(readFileSync8(jsonPath, "utf8"));
|
|
3923
4048
|
} catch {
|
|
3924
4049
|
return null;
|
|
3925
4050
|
}
|
|
3926
4051
|
}
|
|
3927
4052
|
function readOutboxBody(item) {
|
|
3928
4053
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3929
|
-
const bodyFile =
|
|
3930
|
-
return
|
|
4054
|
+
const bodyFile = path16.join(outboxDir, item.bodyPath);
|
|
4055
|
+
return readFileSync8(bodyFile, "utf8");
|
|
3931
4056
|
}
|
|
3932
4057
|
function writeOutboxItem(input, opts) {
|
|
3933
4058
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3934
4059
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3935
4060
|
const id = opts.existing?.id ?? randomUUID();
|
|
3936
4061
|
const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
|
|
3937
|
-
const jsonPath =
|
|
3938
|
-
const bodyFile =
|
|
4062
|
+
const jsonPath = path16.join(outboxDir, `${id}.json`);
|
|
4063
|
+
const bodyFile = path16.join(outboxDir, bodyPath);
|
|
3939
4064
|
if (!opts.existing) {
|
|
3940
4065
|
writeFileSync3(bodyFile, input.body, "utf8");
|
|
3941
4066
|
}
|
|
@@ -3971,24 +4096,24 @@ function writeOutboxItem(input, opts) {
|
|
|
3971
4096
|
}
|
|
3972
4097
|
function saveOutboxItem(item) {
|
|
3973
4098
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3974
|
-
const jsonPath =
|
|
4099
|
+
const jsonPath = path16.join(outboxDir, `${item.id}.json`);
|
|
3975
4100
|
writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
|
|
3976
4101
|
`, { mode: 384 });
|
|
3977
4102
|
}
|
|
3978
4103
|
function archiveOutboxItem(item) {
|
|
3979
4104
|
const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
|
|
3980
|
-
const jsonSrc =
|
|
3981
|
-
const bodySrc =
|
|
3982
|
-
const jsonDst =
|
|
3983
|
-
const bodyDst =
|
|
3984
|
-
if (
|
|
3985
|
-
if (
|
|
4105
|
+
const jsonSrc = path16.join(outboxDir, `${item.id}.json`);
|
|
4106
|
+
const bodySrc = path16.join(outboxDir, item.bodyPath);
|
|
4107
|
+
const jsonDst = path16.join(archiveDir, `${item.id}.json`);
|
|
4108
|
+
const bodyDst = path16.join(archiveDir, item.bodyPath);
|
|
4109
|
+
if (existsSync14(jsonSrc)) renameSync(jsonSrc, jsonDst);
|
|
4110
|
+
if (existsSync14(bodySrc)) renameSync(bodySrc, bodyDst);
|
|
3986
4111
|
}
|
|
3987
4112
|
function outboxItemPaths(item) {
|
|
3988
4113
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3989
4114
|
return {
|
|
3990
|
-
jsonPath:
|
|
3991
|
-
bodyPath:
|
|
4115
|
+
jsonPath: path16.join(outboxDir, `${item.id}.json`),
|
|
4116
|
+
bodyPath: path16.join(outboxDir, item.bodyPath)
|
|
3992
4117
|
};
|
|
3993
4118
|
}
|
|
3994
4119
|
function outboxInputFromItem(item, body) {
|
|
@@ -4174,7 +4299,7 @@ function markOutboxFailed(item, message) {
|
|
|
4174
4299
|
}
|
|
4175
4300
|
|
|
4176
4301
|
// src/plan-persist/drain.ts
|
|
4177
|
-
import
|
|
4302
|
+
import path17 from "node:path";
|
|
4178
4303
|
async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
4179
4304
|
const items = listOutboxItems().filter(
|
|
4180
4305
|
(item) => opts.outboxId ? item.id === opts.outboxId : true
|
|
@@ -4208,7 +4333,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
|
4208
4333
|
return result;
|
|
4209
4334
|
}
|
|
4210
4335
|
function loadOutboxById(outboxId) {
|
|
4211
|
-
const jsonPath =
|
|
4336
|
+
const jsonPath = path17.join(planOutboxDir(), `${outboxId}.json`);
|
|
4212
4337
|
return readOutboxItem(jsonPath);
|
|
4213
4338
|
}
|
|
4214
4339
|
|
|
@@ -4308,7 +4433,7 @@ async function dispatchRun(args) {
|
|
|
4308
4433
|
const activeHarnessWorkers = [];
|
|
4309
4434
|
for (const name of Object.keys(run.workers || {})) {
|
|
4310
4435
|
const worker = readJson(
|
|
4311
|
-
|
|
4436
|
+
path18.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4312
4437
|
void 0
|
|
4313
4438
|
);
|
|
4314
4439
|
if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
|
|
@@ -4533,7 +4658,7 @@ function redactHarness(text, secret) {
|
|
|
4533
4658
|
}
|
|
4534
4659
|
|
|
4535
4660
|
// src/validate.ts
|
|
4536
|
-
import
|
|
4661
|
+
import path19 from "node:path";
|
|
4537
4662
|
var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
4538
4663
|
var WORKER_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
|
|
4539
4664
|
function validateRunId(runId) {
|
|
@@ -4547,15 +4672,15 @@ function validateWorkerName(name) {
|
|
|
4547
4672
|
return trimmed;
|
|
4548
4673
|
}
|
|
4549
4674
|
function validateRepo(repo) {
|
|
4550
|
-
const resolved =
|
|
4675
|
+
const resolved = path19.resolve(repo);
|
|
4551
4676
|
if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
|
|
4552
4677
|
return resolved;
|
|
4553
4678
|
}
|
|
4554
4679
|
function validateOwnedPaths(repoRoot, ownedPaths) {
|
|
4555
4680
|
return ownedPaths.map((owned) => {
|
|
4556
|
-
const resolved =
|
|
4557
|
-
const rel =
|
|
4558
|
-
if (rel.startsWith("..") ||
|
|
4681
|
+
const resolved = path19.resolve(repoRoot, owned);
|
|
4682
|
+
const rel = path19.relative(repoRoot, resolved);
|
|
4683
|
+
if (rel.startsWith("..") || path19.isAbsolute(rel)) {
|
|
4559
4684
|
throw new Error(`owned path escapes repo: ${owned}`);
|
|
4560
4685
|
}
|
|
4561
4686
|
return resolved;
|
|
@@ -4567,14 +4692,93 @@ function validateTailLines(lines) {
|
|
|
4567
4692
|
}
|
|
4568
4693
|
|
|
4569
4694
|
// src/worktree.ts
|
|
4570
|
-
import { existsSync as
|
|
4571
|
-
import
|
|
4695
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync5 } from "node:fs";
|
|
4696
|
+
import path21 from "node:path";
|
|
4697
|
+
|
|
4698
|
+
// src/default-repo.ts
|
|
4699
|
+
import path20 from "node:path";
|
|
4700
|
+
function expandConfiguredRepo(value) {
|
|
4701
|
+
return path20.resolve(resolveUserPath(value.trim()));
|
|
4702
|
+
}
|
|
4703
|
+
function fromConfigured(value, source, persistedInConfig) {
|
|
4704
|
+
const trimmed = value?.trim();
|
|
4705
|
+
if (!trimmed) return null;
|
|
4706
|
+
return {
|
|
4707
|
+
repo: expandConfiguredRepo(trimmed),
|
|
4708
|
+
source,
|
|
4709
|
+
persistedInConfig
|
|
4710
|
+
};
|
|
4711
|
+
}
|
|
4712
|
+
function resolveDefaultRepo(opts = {}) {
|
|
4713
|
+
const env = opts.env ?? process.env;
|
|
4714
|
+
const config = opts.config ?? loadUserConfig();
|
|
4715
|
+
const fromConfig = fromConfigured(config.defaultRepo, "config", true);
|
|
4716
|
+
if (fromConfig) return fromConfig;
|
|
4717
|
+
const fromDefaultEnv = fromConfigured(env.KYNVER_DEFAULT_REPO, "env_default_repo", false);
|
|
4718
|
+
if (fromDefaultEnv) return fromDefaultEnv;
|
|
4719
|
+
const fromHarnessEnv = fromConfigured(env.KYNVER_HARNESS_REPO, "env_harness_repo", false);
|
|
4720
|
+
if (fromHarnessEnv) return fromHarnessEnv;
|
|
4721
|
+
const discovered = discoverDefaultRepo({
|
|
4722
|
+
cwd: opts.cwd,
|
|
4723
|
+
runtimeModuleUrl: opts.runtimeModuleUrl
|
|
4724
|
+
});
|
|
4725
|
+
if (!discovered) return null;
|
|
4726
|
+
return {
|
|
4727
|
+
repo: discovered.repo,
|
|
4728
|
+
source: discovered.source,
|
|
4729
|
+
persistedInConfig: false
|
|
4730
|
+
};
|
|
4731
|
+
}
|
|
4732
|
+
function persistDefaultRepo(repo, existing) {
|
|
4733
|
+
const config = {
|
|
4734
|
+
...existing ?? loadUserConfig(),
|
|
4735
|
+
defaultRepo: redactHomePath(path20.resolve(repo))
|
|
4736
|
+
};
|
|
4737
|
+
saveUserConfig(config);
|
|
4738
|
+
return config;
|
|
4739
|
+
}
|
|
4740
|
+
function remediateDefaultRepo(opts) {
|
|
4741
|
+
const existing = opts?.config ?? loadUserConfig();
|
|
4742
|
+
const resolved = resolveDefaultRepo({ ...opts, config: existing });
|
|
4743
|
+
if (!resolved) {
|
|
4744
|
+
return {
|
|
4745
|
+
ok: false,
|
|
4746
|
+
reason: "No Kynver git checkout found. Clone the repo, cd into it, then run `kynver setup --repo /path/to/Kynver` (or export KYNVER_DEFAULT_REPO)."
|
|
4747
|
+
};
|
|
4748
|
+
}
|
|
4749
|
+
if (resolved.persistedInConfig) {
|
|
4750
|
+
return { ok: true, resolved, config: existing };
|
|
4751
|
+
}
|
|
4752
|
+
const config = persistDefaultRepo(resolved.repo, existing);
|
|
4753
|
+
return {
|
|
4754
|
+
ok: true,
|
|
4755
|
+
resolved: { ...resolved, persistedInConfig: true, source: "config" },
|
|
4756
|
+
config
|
|
4757
|
+
};
|
|
4758
|
+
}
|
|
4759
|
+
function formatResolvedDefaultRepo(resolved) {
|
|
4760
|
+
return {
|
|
4761
|
+
defaultRepo: displayUserPath(resolved.repo),
|
|
4762
|
+
source: resolved.source,
|
|
4763
|
+
persistedInConfig: resolved.persistedInConfig
|
|
4764
|
+
};
|
|
4765
|
+
}
|
|
4766
|
+
|
|
4767
|
+
// src/worktree.ts
|
|
4768
|
+
function resolveCreateRunRepo(args) {
|
|
4769
|
+
const explicit = typeof args.repo === "string" ? args.repo.trim() : "";
|
|
4770
|
+
if (explicit) return explicit;
|
|
4771
|
+
const resolved = resolveDefaultRepo();
|
|
4772
|
+
if (resolved) return resolved.repo;
|
|
4773
|
+
required("", "--repo (or set defaultRepo via `kynver setup` / KYNVER_DEFAULT_REPO)");
|
|
4774
|
+
return "";
|
|
4775
|
+
}
|
|
4572
4776
|
function createRun(args) {
|
|
4573
|
-
const repo = validateRepo(
|
|
4777
|
+
const repo = validateRepo(resolveCreateRunRepo(args));
|
|
4574
4778
|
ensureGitRepo(repo);
|
|
4575
4779
|
const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
|
|
4576
4780
|
const dir = runDirectory(id);
|
|
4577
|
-
if (
|
|
4781
|
+
if (existsSync15(dir)) failExists(`run already exists: ${id}`);
|
|
4578
4782
|
mkdirSync5(dir, { recursive: true });
|
|
4579
4783
|
const base = String(args.base || "origin/main");
|
|
4580
4784
|
const baseCommit = git(repo, ["rev-parse", base]).trim();
|
|
@@ -4588,12 +4792,12 @@ function createRun(args) {
|
|
|
4588
4792
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4589
4793
|
workers: {}
|
|
4590
4794
|
};
|
|
4591
|
-
writeJson(
|
|
4795
|
+
writeJson(path21.join(dir, "run.json"), run);
|
|
4592
4796
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
4593
4797
|
}
|
|
4594
4798
|
function listRuns() {
|
|
4595
4799
|
const { runsDir } = getPaths();
|
|
4596
|
-
const rows = listRunIds(runsDir).map((id) => readJson(
|
|
4800
|
+
const rows = listRunIds(runsDir).map((id) => readJson(path21.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
|
|
4597
4801
|
id: run.id,
|
|
4598
4802
|
name: run.name,
|
|
4599
4803
|
status: run.status,
|
|
@@ -4608,7 +4812,7 @@ function failExists(message) {
|
|
|
4608
4812
|
}
|
|
4609
4813
|
|
|
4610
4814
|
// src/sweep.ts
|
|
4611
|
-
import
|
|
4815
|
+
import path22 from "node:path";
|
|
4612
4816
|
async function sweepRun(args) {
|
|
4613
4817
|
const pipeline = args.pipeline === true || args.pipeline === "true";
|
|
4614
4818
|
try {
|
|
@@ -4621,7 +4825,7 @@ async function sweepRun(args) {
|
|
|
4621
4825
|
const releasedLocalOrphans = [];
|
|
4622
4826
|
for (const name of Object.keys(run.workers || {})) {
|
|
4623
4827
|
const worker = readJson(
|
|
4624
|
-
|
|
4828
|
+
path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4625
4829
|
void 0
|
|
4626
4830
|
);
|
|
4627
4831
|
if (!worker || !worker.dispatched || !worker.taskId) continue;
|
|
@@ -4665,10 +4869,10 @@ async function sweepRun(args) {
|
|
|
4665
4869
|
|
|
4666
4870
|
// src/cli.ts
|
|
4667
4871
|
import { mkdirSync as mkdirSync7, realpathSync } from "node:fs";
|
|
4668
|
-
import { fileURLToPath as
|
|
4872
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
4669
4873
|
|
|
4670
4874
|
// src/pipeline-tick.ts
|
|
4671
|
-
import
|
|
4875
|
+
import path32 from "node:path";
|
|
4672
4876
|
|
|
4673
4877
|
// src/pipeline-dispatch.ts
|
|
4674
4878
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -4722,10 +4926,10 @@ async function runPipelineDispatch(args, slots) {
|
|
|
4722
4926
|
}
|
|
4723
4927
|
|
|
4724
4928
|
// src/stale-reconcile.ts
|
|
4725
|
-
import
|
|
4929
|
+
import path24 from "node:path";
|
|
4726
4930
|
|
|
4727
4931
|
// src/finalize.ts
|
|
4728
|
-
import
|
|
4932
|
+
import path23 from "node:path";
|
|
4729
4933
|
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
|
|
4730
4934
|
function terminalStatusFor(run) {
|
|
4731
4935
|
const names = Object.keys(run.workers || {});
|
|
@@ -4736,7 +4940,7 @@ function terminalStatusFor(run) {
|
|
|
4736
4940
|
let anyLandingBlocked = false;
|
|
4737
4941
|
for (const name of names) {
|
|
4738
4942
|
const worker = readJson(
|
|
4739
|
-
|
|
4943
|
+
path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4740
4944
|
void 0
|
|
4741
4945
|
);
|
|
4742
4946
|
if (!worker) continue;
|
|
@@ -4788,7 +4992,7 @@ function reconcileStaleWorkers() {
|
|
|
4788
4992
|
const now = Date.now();
|
|
4789
4993
|
for (const run of listRunRecords()) {
|
|
4790
4994
|
for (const name of Object.keys(run.workers || {})) {
|
|
4791
|
-
const workerPath =
|
|
4995
|
+
const workerPath = path24.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
4792
4996
|
const worker = readJson(workerPath, void 0);
|
|
4793
4997
|
if (!worker || worker.status !== "running") {
|
|
4794
4998
|
outcomes.push({
|
|
@@ -4882,7 +5086,7 @@ function reconcileRunsCli() {
|
|
|
4882
5086
|
}
|
|
4883
5087
|
|
|
4884
5088
|
// src/plan-progress-daemon-sync.ts
|
|
4885
|
-
import
|
|
5089
|
+
import path25 from "node:path";
|
|
4886
5090
|
|
|
4887
5091
|
// src/plan-progress-sync.ts
|
|
4888
5092
|
async function syncPlanProgress(args) {
|
|
@@ -4906,7 +5110,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
4906
5110
|
const outcomes = [];
|
|
4907
5111
|
for (const name of Object.keys(run.workers || {})) {
|
|
4908
5112
|
const worker = readJson(
|
|
4909
|
-
|
|
5113
|
+
path25.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4910
5114
|
void 0
|
|
4911
5115
|
);
|
|
4912
5116
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -4955,7 +5159,7 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
4955
5159
|
}
|
|
4956
5160
|
|
|
4957
5161
|
// src/cleanup.ts
|
|
4958
|
-
import
|
|
5162
|
+
import path30 from "node:path";
|
|
4959
5163
|
|
|
4960
5164
|
// src/cleanup-types.ts
|
|
4961
5165
|
var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
@@ -5026,14 +5230,14 @@ function skipNodeModulesRemoval(input) {
|
|
|
5026
5230
|
}
|
|
5027
5231
|
|
|
5028
5232
|
// src/cleanup-execute.ts
|
|
5029
|
-
import { existsSync as
|
|
5030
|
-
import
|
|
5233
|
+
import { existsSync as existsSync17, rmSync } from "node:fs";
|
|
5234
|
+
import path27 from "node:path";
|
|
5031
5235
|
|
|
5032
5236
|
// src/cleanup-dir-size.ts
|
|
5033
|
-
import { existsSync as
|
|
5034
|
-
import
|
|
5237
|
+
import { existsSync as existsSync16, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
|
|
5238
|
+
import path26 from "node:path";
|
|
5035
5239
|
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
5036
|
-
if (!
|
|
5240
|
+
if (!existsSync16(root)) return 0;
|
|
5037
5241
|
let total = 0;
|
|
5038
5242
|
let seen = 0;
|
|
5039
5243
|
const stack = [root];
|
|
@@ -5047,7 +5251,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
5047
5251
|
}
|
|
5048
5252
|
for (const name of entries) {
|
|
5049
5253
|
if (seen++ > maxEntries) return null;
|
|
5050
|
-
const full =
|
|
5254
|
+
const full = path26.join(current, name);
|
|
5051
5255
|
let st;
|
|
5052
5256
|
try {
|
|
5053
5257
|
st = statSync2(full);
|
|
@@ -5063,7 +5267,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
5063
5267
|
|
|
5064
5268
|
// src/cleanup-execute.ts
|
|
5065
5269
|
function removeNodeModules(candidate, execute) {
|
|
5066
|
-
if (!
|
|
5270
|
+
if (!existsSync17(candidate.path)) {
|
|
5067
5271
|
return {
|
|
5068
5272
|
...candidate,
|
|
5069
5273
|
executed: false,
|
|
@@ -5094,7 +5298,7 @@ function removeNodeModules(candidate, execute) {
|
|
|
5094
5298
|
}
|
|
5095
5299
|
}
|
|
5096
5300
|
function removeWorktree(candidate, execute) {
|
|
5097
|
-
if (!
|
|
5301
|
+
if (!existsSync17(candidate.path)) {
|
|
5098
5302
|
return {
|
|
5099
5303
|
...candidate,
|
|
5100
5304
|
executed: false,
|
|
@@ -5111,7 +5315,7 @@ function removeWorktree(candidate, execute) {
|
|
|
5111
5315
|
if (repo) {
|
|
5112
5316
|
git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
|
|
5113
5317
|
}
|
|
5114
|
-
if (
|
|
5318
|
+
if (existsSync17(candidate.path)) {
|
|
5115
5319
|
rmSync(candidate.path, { recursive: true, force: true });
|
|
5116
5320
|
}
|
|
5117
5321
|
return {
|
|
@@ -5131,20 +5335,20 @@ function removeWorktree(candidate, execute) {
|
|
|
5131
5335
|
}
|
|
5132
5336
|
}
|
|
5133
5337
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
5134
|
-
const resolved =
|
|
5135
|
-
const nm = resolved.endsWith(`${
|
|
5338
|
+
const resolved = path27.resolve(targetPath);
|
|
5339
|
+
const nm = resolved.endsWith(`${path27.sep}node_modules`) ? resolved : null;
|
|
5136
5340
|
if (!nm) return "path_outside_harness";
|
|
5137
|
-
const rel =
|
|
5138
|
-
if (rel.startsWith("..") ||
|
|
5139
|
-
const parts = rel.split(
|
|
5341
|
+
const rel = path27.relative(worktreesDir, nm);
|
|
5342
|
+
if (rel.startsWith("..") || path27.isAbsolute(rel)) return "path_outside_harness";
|
|
5343
|
+
const parts = rel.split(path27.sep);
|
|
5140
5344
|
if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
|
|
5141
|
-
if (!resolved.startsWith(
|
|
5345
|
+
if (!resolved.startsWith(path27.resolve(harnessRoot))) return "path_outside_harness";
|
|
5142
5346
|
return null;
|
|
5143
5347
|
}
|
|
5144
5348
|
|
|
5145
5349
|
// src/cleanup-scan.ts
|
|
5146
|
-
import { existsSync as
|
|
5147
|
-
import
|
|
5350
|
+
import { existsSync as existsSync18, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
5351
|
+
import path28 from "node:path";
|
|
5148
5352
|
function pathAgeMs(target, now) {
|
|
5149
5353
|
try {
|
|
5150
5354
|
const mtime = statSync3(target).mtimeMs;
|
|
@@ -5154,17 +5358,17 @@ function pathAgeMs(target, now) {
|
|
|
5154
5358
|
}
|
|
5155
5359
|
}
|
|
5156
5360
|
function isPathInside(child, parent) {
|
|
5157
|
-
const rel =
|
|
5158
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
5361
|
+
const rel = path28.relative(parent, child);
|
|
5362
|
+
return rel === "" || !rel.startsWith("..") && !path28.isAbsolute(rel);
|
|
5159
5363
|
}
|
|
5160
5364
|
function scanNodeModulesCandidates(opts) {
|
|
5161
5365
|
const candidates = [];
|
|
5162
5366
|
const seen = /* @__PURE__ */ new Set();
|
|
5163
5367
|
for (const entry of opts.index.values()) {
|
|
5164
5368
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
5165
|
-
const nm =
|
|
5166
|
-
if (!
|
|
5167
|
-
const resolved =
|
|
5369
|
+
const nm = path28.join(entry.worktreePath, "node_modules");
|
|
5370
|
+
if (!existsSync18(nm)) continue;
|
|
5371
|
+
const resolved = path28.resolve(nm);
|
|
5168
5372
|
if (seen.has(resolved)) continue;
|
|
5169
5373
|
seen.add(resolved);
|
|
5170
5374
|
candidates.push({
|
|
@@ -5177,16 +5381,16 @@ function scanNodeModulesCandidates(opts) {
|
|
|
5177
5381
|
ageMs: pathAgeMs(resolved, opts.now)
|
|
5178
5382
|
});
|
|
5179
5383
|
}
|
|
5180
|
-
if (!opts.includeOrphans || !
|
|
5384
|
+
if (!opts.includeOrphans || !existsSync18(opts.worktreesDir)) return candidates;
|
|
5181
5385
|
for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
|
|
5182
5386
|
if (!runEntry.isDirectory()) continue;
|
|
5183
|
-
const runPath =
|
|
5387
|
+
const runPath = path28.join(opts.worktreesDir, runEntry.name);
|
|
5184
5388
|
for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
|
|
5185
5389
|
if (!workerEntry.isDirectory()) continue;
|
|
5186
|
-
const worktreePath =
|
|
5187
|
-
const nm =
|
|
5188
|
-
if (!
|
|
5189
|
-
const resolved =
|
|
5390
|
+
const worktreePath = path28.join(runPath, workerEntry.name);
|
|
5391
|
+
const nm = path28.join(worktreePath, "node_modules");
|
|
5392
|
+
if (!existsSync18(nm)) continue;
|
|
5393
|
+
const resolved = path28.resolve(nm);
|
|
5190
5394
|
if (seen.has(resolved)) continue;
|
|
5191
5395
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
5192
5396
|
seen.add(resolved);
|
|
@@ -5209,7 +5413,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
5209
5413
|
for (const entry of opts.index.values()) {
|
|
5210
5414
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
5211
5415
|
const resolved = entry.worktreePath;
|
|
5212
|
-
if (!
|
|
5416
|
+
if (!existsSync18(resolved)) continue;
|
|
5213
5417
|
if (seen.has(resolved)) continue;
|
|
5214
5418
|
seen.add(resolved);
|
|
5215
5419
|
candidates.push({
|
|
@@ -5226,17 +5430,17 @@ function scanWorktreeCandidates(opts) {
|
|
|
5226
5430
|
}
|
|
5227
5431
|
|
|
5228
5432
|
// src/cleanup-worktree-index.ts
|
|
5229
|
-
import
|
|
5433
|
+
import path29 from "node:path";
|
|
5230
5434
|
function buildWorktreeIndex() {
|
|
5231
5435
|
const index = /* @__PURE__ */ new Map();
|
|
5232
5436
|
for (const run of listRunRecords()) {
|
|
5233
5437
|
for (const name of Object.keys(run.workers || {})) {
|
|
5234
|
-
const workerPath =
|
|
5438
|
+
const workerPath = path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
5235
5439
|
const worker = readJson(workerPath, void 0);
|
|
5236
5440
|
if (!worker?.worktreePath) continue;
|
|
5237
5441
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
5238
|
-
index.set(
|
|
5239
|
-
worktreePath:
|
|
5442
|
+
index.set(path29.resolve(worker.worktreePath), {
|
|
5443
|
+
worktreePath: path29.resolve(worker.worktreePath),
|
|
5240
5444
|
runId: run.id,
|
|
5241
5445
|
workerName: name,
|
|
5242
5446
|
run,
|
|
@@ -5251,7 +5455,7 @@ function buildWorktreeIndex() {
|
|
|
5251
5455
|
// src/cleanup.ts
|
|
5252
5456
|
function resolveOptions(options = {}) {
|
|
5253
5457
|
const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
|
|
5254
|
-
const { worktreesDir } = options.harnessRoot ? { worktreesDir:
|
|
5458
|
+
const { worktreesDir } = options.harnessRoot ? { worktreesDir: path30.join(harnessRoot, "worktrees") } : getHarnessPaths();
|
|
5255
5459
|
const execute = options.execute === true;
|
|
5256
5460
|
const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
|
|
5257
5461
|
const worktreesAgeMs = options.worktreesAgeMs ?? 0;
|
|
@@ -5295,7 +5499,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
5295
5499
|
actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
|
|
5296
5500
|
continue;
|
|
5297
5501
|
}
|
|
5298
|
-
const worktreePath =
|
|
5502
|
+
const worktreePath = path30.resolve(candidate.path, "..");
|
|
5299
5503
|
const indexed = index.get(worktreePath) ?? null;
|
|
5300
5504
|
const guardReason = skipNodeModulesRemoval({
|
|
5301
5505
|
indexed,
|
|
@@ -5311,7 +5515,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
5311
5515
|
actions.push(removeNodeModules(candidate, resolved.execute));
|
|
5312
5516
|
}
|
|
5313
5517
|
for (const candidate of scanWorktreeCandidates(scanOpts)) {
|
|
5314
|
-
const indexed = index.get(
|
|
5518
|
+
const indexed = index.get(path30.resolve(candidate.path)) ?? null;
|
|
5315
5519
|
const guardReason = skipWorktreeRemoval({
|
|
5316
5520
|
indexed,
|
|
5317
5521
|
includeOrphans: resolved.includeOrphans,
|
|
@@ -5376,8 +5580,8 @@ function isPipelineCleanupEnabled() {
|
|
|
5376
5580
|
|
|
5377
5581
|
// src/installed-package-versions.ts
|
|
5378
5582
|
import { readFile } from "node:fs/promises";
|
|
5379
|
-
import { homedir as
|
|
5380
|
-
import
|
|
5583
|
+
import { homedir as homedir6 } from "node:os";
|
|
5584
|
+
import path31 from "node:path";
|
|
5381
5585
|
var MANAGED_PACKAGES = [
|
|
5382
5586
|
"@kynver-app/runtime",
|
|
5383
5587
|
"@kynver-app/openclaw-agent-os",
|
|
@@ -5391,13 +5595,13 @@ function unique(values) {
|
|
|
5391
5595
|
return [...new Set(values.filter((value) => Boolean(value)))];
|
|
5392
5596
|
}
|
|
5393
5597
|
function moduleRoots() {
|
|
5394
|
-
const home =
|
|
5395
|
-
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ??
|
|
5396
|
-
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ?
|
|
5598
|
+
const home = homedir6();
|
|
5599
|
+
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path31.join(home, ".openclaw", "npm");
|
|
5600
|
+
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path31.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path31.join(home, ".npm-global", "lib", "node_modules"));
|
|
5397
5601
|
return unique([
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot :
|
|
5602
|
+
path31.join(openClawPrefix, "lib", "node_modules"),
|
|
5603
|
+
path31.join(openClawPrefix, "node_modules"),
|
|
5604
|
+
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path31.join(npmGlobalRoot, "lib", "node_modules")
|
|
5401
5605
|
]);
|
|
5402
5606
|
}
|
|
5403
5607
|
async function readVersion(packageJsonPath) {
|
|
@@ -5413,7 +5617,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
|
|
|
5413
5617
|
const out = {};
|
|
5414
5618
|
for (const packageName of MANAGED_PACKAGES) {
|
|
5415
5619
|
for (const root of roots) {
|
|
5416
|
-
const packageJsonPath =
|
|
5620
|
+
const packageJsonPath = path31.join(root, packageName, "package.json");
|
|
5417
5621
|
const version = await readVersion(packageJsonPath);
|
|
5418
5622
|
if (!version) continue;
|
|
5419
5623
|
out[packageName] = { version, observedAt, path: packageJsonPath };
|
|
@@ -5429,7 +5633,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
5429
5633
|
const outcomes = [];
|
|
5430
5634
|
for (const name of Object.keys(run.workers || {})) {
|
|
5431
5635
|
const worker = readJson(
|
|
5432
|
-
|
|
5636
|
+
path32.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
5433
5637
|
void 0
|
|
5434
5638
|
);
|
|
5435
5639
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -5571,7 +5775,7 @@ async function runDaemon(args) {
|
|
|
5571
5775
|
}
|
|
5572
5776
|
|
|
5573
5777
|
// src/plan-progress.ts
|
|
5574
|
-
import
|
|
5778
|
+
import path33 from "node:path";
|
|
5575
5779
|
|
|
5576
5780
|
// src/bounded-build/constants.ts
|
|
5577
5781
|
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
@@ -5857,7 +6061,7 @@ async function emitPlanProgress(args) {
|
|
|
5857
6061
|
}
|
|
5858
6062
|
function verifyPlanLocal(args) {
|
|
5859
6063
|
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
5860
|
-
const cwd =
|
|
6064
|
+
const cwd = path33.resolve(worktree);
|
|
5861
6065
|
const summary = runHarnessVerifyCommands(cwd);
|
|
5862
6066
|
const emitJson = args.json === true || args.json === "true";
|
|
5863
6067
|
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
@@ -5906,9 +6110,9 @@ async function verifyPlan(args) {
|
|
|
5906
6110
|
}
|
|
5907
6111
|
|
|
5908
6112
|
// src/harness-verify-cli.ts
|
|
5909
|
-
import
|
|
6113
|
+
import path34 from "node:path";
|
|
5910
6114
|
function runHarnessVerifyCli(args) {
|
|
5911
|
-
const cwd =
|
|
6115
|
+
const cwd = path34.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
5912
6116
|
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
5913
6117
|
const commands = [];
|
|
5914
6118
|
const rawCmd = args.command;
|
|
@@ -5952,7 +6156,7 @@ function runHarnessVerifyCli(args) {
|
|
|
5952
6156
|
}
|
|
5953
6157
|
|
|
5954
6158
|
// src/plan-persist-cli.ts
|
|
5955
|
-
import { readFileSync as
|
|
6159
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
5956
6160
|
var OPERATIONS = ["create", "add_version", "update_metadata"];
|
|
5957
6161
|
var FAILURE_KINDS = [
|
|
5958
6162
|
"approval_guard",
|
|
@@ -5964,7 +6168,7 @@ var FAILURE_KINDS = [
|
|
|
5964
6168
|
function readBodyArg(args) {
|
|
5965
6169
|
const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
|
|
5966
6170
|
if (bodyFile) {
|
|
5967
|
-
return { body:
|
|
6171
|
+
return { body: readFileSync9(bodyFile, "utf8"), bodyPathHint: bodyFile };
|
|
5968
6172
|
}
|
|
5969
6173
|
const inline = args.body ? String(args.body) : void 0;
|
|
5970
6174
|
if (inline) return { body: inline };
|
|
@@ -6052,7 +6256,7 @@ function runCleanupCli(args) {
|
|
|
6052
6256
|
}
|
|
6053
6257
|
|
|
6054
6258
|
// src/monitor/monitor.service.ts
|
|
6055
|
-
import
|
|
6259
|
+
import path36 from "node:path";
|
|
6056
6260
|
|
|
6057
6261
|
// src/monitor/monitor.classify.ts
|
|
6058
6262
|
function expectedLeaseOwner(runId) {
|
|
@@ -6108,11 +6312,11 @@ function classifyWorkerHealth(input) {
|
|
|
6108
6312
|
}
|
|
6109
6313
|
|
|
6110
6314
|
// src/monitor/monitor.store.ts
|
|
6111
|
-
import { existsSync as
|
|
6112
|
-
import
|
|
6315
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
|
|
6316
|
+
import path35 from "node:path";
|
|
6113
6317
|
function monitorsDir() {
|
|
6114
6318
|
const { harnessRoot } = getHarnessPaths();
|
|
6115
|
-
const dir =
|
|
6319
|
+
const dir = path35.join(harnessRoot, "monitors");
|
|
6116
6320
|
mkdirSync6(dir, { recursive: true });
|
|
6117
6321
|
return dir;
|
|
6118
6322
|
}
|
|
@@ -6120,7 +6324,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
6120
6324
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
6121
6325
|
}
|
|
6122
6326
|
function monitorPath(monitorId) {
|
|
6123
|
-
return
|
|
6327
|
+
return path35.join(monitorsDir(), `${monitorId}.json`);
|
|
6124
6328
|
}
|
|
6125
6329
|
function loadMonitorSession(monitorId) {
|
|
6126
6330
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -6130,18 +6334,18 @@ function saveMonitorSession(session) {
|
|
|
6130
6334
|
}
|
|
6131
6335
|
function deleteMonitorSession(monitorId) {
|
|
6132
6336
|
const file = monitorPath(monitorId);
|
|
6133
|
-
if (!
|
|
6337
|
+
if (!existsSync19(file)) return false;
|
|
6134
6338
|
unlinkSync2(file);
|
|
6135
6339
|
return true;
|
|
6136
6340
|
}
|
|
6137
6341
|
function listMonitorSessions() {
|
|
6138
6342
|
const dir = monitorsDir();
|
|
6139
|
-
if (!
|
|
6343
|
+
if (!existsSync19(dir)) return [];
|
|
6140
6344
|
const entries = [];
|
|
6141
6345
|
for (const name of readdirSync7(dir)) {
|
|
6142
6346
|
if (!name.endsWith(".json")) continue;
|
|
6143
6347
|
const session = readJson(
|
|
6144
|
-
|
|
6348
|
+
path35.join(dir, name),
|
|
6145
6349
|
void 0
|
|
6146
6350
|
);
|
|
6147
6351
|
if (!session?.monitorId) continue;
|
|
@@ -6232,7 +6436,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
6232
6436
|
// src/monitor/monitor.service.ts
|
|
6233
6437
|
function workerRecord2(runId, name) {
|
|
6234
6438
|
return readJson(
|
|
6235
|
-
|
|
6439
|
+
path36.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
6236
6440
|
void 0
|
|
6237
6441
|
);
|
|
6238
6442
|
}
|
|
@@ -6434,18 +6638,18 @@ async function runMonitorLoop(args) {
|
|
|
6434
6638
|
|
|
6435
6639
|
// src/monitor/monitor-spawn.ts
|
|
6436
6640
|
import { spawn as spawn4 } from "node:child_process";
|
|
6437
|
-
import { closeSync as closeSync4, existsSync as
|
|
6438
|
-
import
|
|
6439
|
-
import { fileURLToPath as
|
|
6641
|
+
import { closeSync as closeSync4, existsSync as existsSync20, openSync as openSync4 } from "node:fs";
|
|
6642
|
+
import path37 from "node:path";
|
|
6643
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
6440
6644
|
function resolveDefaultCliPath2() {
|
|
6441
|
-
return
|
|
6645
|
+
return path37.join(fileURLToPath4(new URL(".", import.meta.url)), "..", "cli.js");
|
|
6442
6646
|
}
|
|
6443
6647
|
function spawnMonitorSidecar(opts) {
|
|
6444
6648
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
6445
|
-
if (!
|
|
6649
|
+
if (!existsSync20(cliPath)) return void 0;
|
|
6446
6650
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
6447
6651
|
const { harnessRoot } = getHarnessPaths();
|
|
6448
|
-
const logPath =
|
|
6652
|
+
const logPath = path37.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
6449
6653
|
let logFd;
|
|
6450
6654
|
try {
|
|
6451
6655
|
logFd = openSync4(logPath, "a");
|
|
@@ -6565,12 +6769,12 @@ async function monitorTickCli(args) {
|
|
|
6565
6769
|
}
|
|
6566
6770
|
|
|
6567
6771
|
// src/doctor/runtime-takeover.ts
|
|
6568
|
-
import
|
|
6772
|
+
import path39 from "node:path";
|
|
6569
6773
|
|
|
6570
6774
|
// src/doctor/runtime-takeover.probes.ts
|
|
6571
|
-
import { accessSync, constants, existsSync as
|
|
6572
|
-
import { homedir as
|
|
6573
|
-
import
|
|
6775
|
+
import { accessSync, constants, existsSync as existsSync21, readFileSync as readFileSync10 } from "node:fs";
|
|
6776
|
+
import { homedir as homedir7 } from "node:os";
|
|
6777
|
+
import path38 from "node:path";
|
|
6574
6778
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
6575
6779
|
function captureCommand(bin, args) {
|
|
6576
6780
|
try {
|
|
@@ -6599,7 +6803,7 @@ function tokenPrefix(token) {
|
|
|
6599
6803
|
return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
|
|
6600
6804
|
}
|
|
6601
6805
|
function isWritable(target) {
|
|
6602
|
-
if (!
|
|
6806
|
+
if (!existsSync21(target)) return false;
|
|
6603
6807
|
try {
|
|
6604
6808
|
accessSync(target, constants.W_OK);
|
|
6605
6809
|
return true;
|
|
@@ -6612,15 +6816,15 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
6612
6816
|
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
6613
6817
|
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
6614
6818
|
loadConfig: () => loadUserConfig(),
|
|
6615
|
-
configFilePath: () =>
|
|
6616
|
-
credentialsFilePath: () =>
|
|
6819
|
+
configFilePath: () => path38.join(homedir7(), ".kynver", "config.json"),
|
|
6820
|
+
credentialsFilePath: () => path38.join(homedir7(), ".kynver", "credentials"),
|
|
6617
6821
|
readCredentials: () => {
|
|
6618
|
-
const credPath =
|
|
6619
|
-
if (!
|
|
6822
|
+
const credPath = path38.join(homedir7(), ".kynver", "credentials");
|
|
6823
|
+
if (!existsSync21(credPath)) {
|
|
6620
6824
|
return { hasApiKey: false };
|
|
6621
6825
|
}
|
|
6622
6826
|
try {
|
|
6623
|
-
const parsed = JSON.parse(
|
|
6827
|
+
const parsed = JSON.parse(readFileSync10(credPath, "utf8"));
|
|
6624
6828
|
return {
|
|
6625
6829
|
hasApiKey: Boolean(parsed.apiKey?.trim()),
|
|
6626
6830
|
runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
|
|
@@ -6638,20 +6842,119 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
6638
6842
|
openclawCronSecret: Boolean(process.env.OPENCLAW_CRON_SECRET?.trim()),
|
|
6639
6843
|
kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
|
|
6640
6844
|
opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
|
|
6641
|
-
kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0
|
|
6845
|
+
kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0,
|
|
6846
|
+
qstashTokenPresent: Boolean(process.env.QSTASH_TOKEN?.trim()),
|
|
6847
|
+
kynverHostedDeployment: (() => {
|
|
6848
|
+
const v = process.env.KYNVER_HOSTED_DEPLOYMENT?.trim().toLowerCase();
|
|
6849
|
+
return v === "1" || v === "true" || v === "yes";
|
|
6850
|
+
})()
|
|
6642
6851
|
}),
|
|
6643
6852
|
harnessRoot: () => resolveHarnessRoot(),
|
|
6644
|
-
legacyOpenclawHarnessRoot: () =>
|
|
6645
|
-
pathExists: (target) =>
|
|
6853
|
+
legacyOpenclawHarnessRoot: () => path38.join(homedir7(), ".openclaw", "harness"),
|
|
6854
|
+
pathExists: (target) => existsSync21(target),
|
|
6646
6855
|
pathWritable: (target) => isWritable(target),
|
|
6647
6856
|
vercelVersion: () => captureCommand("vercel", ["--version"]),
|
|
6648
6857
|
vercelWhoami: () => captureCommand("vercel", ["whoami"])
|
|
6649
6858
|
};
|
|
6650
6859
|
|
|
6651
|
-
// src/doctor/runtime-takeover.ts
|
|
6860
|
+
// src/doctor/runtime-takeover-scheduler.ts
|
|
6652
6861
|
function check(partial) {
|
|
6653
6862
|
return partial;
|
|
6654
6863
|
}
|
|
6864
|
+
function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
6865
|
+
const runnerOpenclaw = env.kynverSchedulerProvider === "openclaw-cron";
|
|
6866
|
+
const runnerQstash = env.kynverSchedulerProvider === "qstash";
|
|
6867
|
+
const hostedDeployment = Boolean(env.qstashTokenPresent) || Boolean(env.kynverHostedDeployment);
|
|
6868
|
+
const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
|
|
6869
|
+
const hostedSchedulerProcess = hostedDeployment && !daemonDispatchReady;
|
|
6870
|
+
const deploymentNeedsQstash = hostedSchedulerProcess && !env.qstashTokenPresent && env.kynverSchedulerProvider !== "qstash";
|
|
6871
|
+
const deploymentOpenclaw = hostedSchedulerProcess && env.kynverSchedulerProvider === "openclaw-cron";
|
|
6872
|
+
if (daemonDispatchReady && runnerOpenclaw) {
|
|
6873
|
+
return check({
|
|
6874
|
+
id: "hotspot_openclaw_scheduler",
|
|
6875
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6876
|
+
status: "warn",
|
|
6877
|
+
summary: "KYNVER_SCHEDULER_PROVIDER=openclaw-cron on this runner \u2014 dispatch is owned by kynver daemon; unset the OpenClaw override",
|
|
6878
|
+
remediation: "Unset KYNVER_SCHEDULER_PROVIDER on user runners. Use `kynver daemon` (pipeline-tick \u2192 operator/tick). On the Kynver server set KYNVER_SCHEDULER_PROVIDER=qstash when QStash is configured.",
|
|
6879
|
+
details: {
|
|
6880
|
+
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
6881
|
+
dispatchPath: "kynver-daemon-pipeline-tick",
|
|
6882
|
+
hostedDeployment
|
|
6883
|
+
}
|
|
6884
|
+
});
|
|
6885
|
+
}
|
|
6886
|
+
if (daemonDispatchReady) {
|
|
6887
|
+
return check({
|
|
6888
|
+
id: "hotspot_openclaw_scheduler",
|
|
6889
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6890
|
+
status: "pass",
|
|
6891
|
+
summary: runnerQstash ? "Runner override qstash present; hosted dispatch still owned by kynver daemon pipeline-tick" : "Hosted dispatch owned by kynver daemon (pipeline-tick \u2192 operator/tick); no OpenClaw cron on runner",
|
|
6892
|
+
details: {
|
|
6893
|
+
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
6894
|
+
dispatchPath: "kynver-daemon-pipeline-tick",
|
|
6895
|
+
hostedDeployment
|
|
6896
|
+
}
|
|
6897
|
+
});
|
|
6898
|
+
}
|
|
6899
|
+
if (runnerOpenclaw) {
|
|
6900
|
+
return check({
|
|
6901
|
+
id: "hotspot_openclaw_scheduler",
|
|
6902
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6903
|
+
status: "warn",
|
|
6904
|
+
summary: "KYNVER_SCHEDULER_PROVIDER=openclaw-cron on this runner \u2014 hosted dispatch still depends on the OpenClaw local-cron adapter",
|
|
6905
|
+
remediation: "Unset KYNVER_SCHEDULER_PROVIDER on user runners. Use `kynver daemon` (pipeline-tick \u2192 operator/tick). On the Kynver server set KYNVER_SCHEDULER_PROVIDER=qstash when QStash is configured.",
|
|
6906
|
+
details: { schedulerProvider: env.kynverSchedulerProvider ?? null, hostedDeployment }
|
|
6907
|
+
});
|
|
6908
|
+
}
|
|
6909
|
+
if (deploymentOpenclaw || deploymentNeedsQstash) {
|
|
6910
|
+
return check({
|
|
6911
|
+
id: "hotspot_openclaw_scheduler",
|
|
6912
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6913
|
+
status: "warn",
|
|
6914
|
+
summary: deploymentOpenclaw ? "Hosted deployment has KYNVER_SCHEDULER_PROVIDER=openclaw-cron \u2014 AgentOS scheduled ticks should use QStash" : env.kynverSchedulerProvider ? `Hosted deployment without QSTASH_TOKEN (KYNVER_SCHEDULER_PROVIDER=${env.kynverSchedulerProvider}) \u2014 scheduler may fall back to openclaw-cron` : "KYNVER_SCHEDULER_PROVIDER unset on hosted deployment; without QSTASH_TOKEN the server may fall back to openclaw-cron",
|
|
6915
|
+
remediation: "Set QSTASH_TOKEN and KYNVER_SCHEDULER_PROVIDER=qstash on the Kynver server. User runners use `kynver daemon` for dispatch and should not set a scheduler provider.",
|
|
6916
|
+
details: {
|
|
6917
|
+
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
6918
|
+
qstashTokenPresent: env.qstashTokenPresent ?? false,
|
|
6919
|
+
hostedDeployment
|
|
6920
|
+
}
|
|
6921
|
+
});
|
|
6922
|
+
}
|
|
6923
|
+
if (hostedDeployment && env.qstashTokenPresent && !env.kynverSchedulerProvider) {
|
|
6924
|
+
return check({
|
|
6925
|
+
id: "hotspot_openclaw_scheduler",
|
|
6926
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6927
|
+
status: "pass",
|
|
6928
|
+
summary: "QSTASH_TOKEN present; hosted scheduler auto-selects qstash (explicit KYNVER_SCHEDULER_PROVIDER=qstash optional)",
|
|
6929
|
+
details: {
|
|
6930
|
+
schedulerProvider: null,
|
|
6931
|
+
qstashTokenPresent: true,
|
|
6932
|
+
hostedDeployment
|
|
6933
|
+
}
|
|
6934
|
+
});
|
|
6935
|
+
}
|
|
6936
|
+
if (!env.kynverSchedulerProvider) {
|
|
6937
|
+
return check({
|
|
6938
|
+
id: "hotspot_openclaw_scheduler",
|
|
6939
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6940
|
+
status: "pass",
|
|
6941
|
+
summary: "No KYNVER_SCHEDULER_PROVIDER on runner (expected) \u2014 finish runner setup so daemon pipeline-tick owns dispatch",
|
|
6942
|
+
details: { schedulerProvider: null, dispatchPath: "kynver-daemon-pipeline-tick-pending" }
|
|
6943
|
+
});
|
|
6944
|
+
}
|
|
6945
|
+
return check({
|
|
6946
|
+
id: "hotspot_openclaw_scheduler",
|
|
6947
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6948
|
+
status: "pass",
|
|
6949
|
+
summary: `KYNVER_SCHEDULER_PROVIDER=${env.kynverSchedulerProvider}`,
|
|
6950
|
+
details: { schedulerProvider: env.kynverSchedulerProvider ?? null }
|
|
6951
|
+
});
|
|
6952
|
+
}
|
|
6953
|
+
|
|
6954
|
+
// src/doctor/runtime-takeover.ts
|
|
6955
|
+
function check2(partial) {
|
|
6956
|
+
return partial;
|
|
6957
|
+
}
|
|
6655
6958
|
function summarizeCounts(sections) {
|
|
6656
6959
|
const counts = { pass: 0, warn: 0, fail: 0 };
|
|
6657
6960
|
for (const section of sections) {
|
|
@@ -6668,14 +6971,14 @@ function assessCliPackage(probes) {
|
|
|
6668
6971
|
const firstPath = onPath ? which.stdout.split(/\r?\n/)[0]?.trim() : void 0;
|
|
6669
6972
|
const displayCliPath = firstPath ? displayUserPath(firstPath) : void 0;
|
|
6670
6973
|
const checks = [
|
|
6671
|
-
|
|
6974
|
+
check2({
|
|
6672
6975
|
id: "cli_running_version",
|
|
6673
6976
|
label: "Running @kynver-app/runtime version",
|
|
6674
6977
|
status: "pass",
|
|
6675
6978
|
summary: `@kynver-app/runtime ${runningVersion}`,
|
|
6676
6979
|
details: { version: runningVersion }
|
|
6677
6980
|
}),
|
|
6678
|
-
|
|
6981
|
+
check2({
|
|
6679
6982
|
id: "cli_on_path",
|
|
6680
6983
|
label: "kynver executable on PATH",
|
|
6681
6984
|
status: onPath ? "pass" : "warn",
|
|
@@ -6689,7 +6992,7 @@ function assessCliPackage(probes) {
|
|
|
6689
6992
|
const installedVersion = versionProbe.stdout.replace(/^kynver\s+/i, "").trim() || void 0;
|
|
6690
6993
|
const versionMatch = versionProbe.ok && (!installedVersion || installedVersion === runningVersion);
|
|
6691
6994
|
checks.push(
|
|
6692
|
-
|
|
6995
|
+
check2({
|
|
6693
6996
|
id: "cli_installed_version",
|
|
6694
6997
|
label: "Installed kynver CLI version matches running package",
|
|
6695
6998
|
status: versionMatch ? "pass" : "warn",
|
|
@@ -6707,7 +7010,7 @@ function assessUserConfig(probes) {
|
|
|
6707
7010
|
const exists = probes.pathExists(configPath);
|
|
6708
7011
|
const config = probes.loadConfig();
|
|
6709
7012
|
const checks = [
|
|
6710
|
-
|
|
7013
|
+
check2({
|
|
6711
7014
|
id: "config_file",
|
|
6712
7015
|
label: "~/.kynver/config.json present",
|
|
6713
7016
|
status: exists ? "pass" : "fail",
|
|
@@ -6719,10 +7022,16 @@ function assessUserConfig(probes) {
|
|
|
6719
7022
|
if (exists) {
|
|
6720
7023
|
const apiBaseUrl = config.apiBaseUrl?.trim();
|
|
6721
7024
|
const agentOsId = config.agentOsId?.trim();
|
|
6722
|
-
const
|
|
6723
|
-
const
|
|
7025
|
+
const resolvedDefaultRepo = resolveDefaultRepo({ config });
|
|
7026
|
+
const formatted = resolvedDefaultRepo ? formatResolvedDefaultRepo(resolvedDefaultRepo) : null;
|
|
7027
|
+
let defaultRepoRemediation;
|
|
7028
|
+
if (!resolvedDefaultRepo) {
|
|
7029
|
+
defaultRepoRemediation = "Run `kynver setup` from a Kynver checkout (auto-discovers repo) or `kynver setup --repo /path/to/Kynver`.";
|
|
7030
|
+
} else if (!resolvedDefaultRepo.persistedInConfig) {
|
|
7031
|
+
defaultRepoRemediation = "Run `kynver setup` (no --repo) to persist discovered defaultRepo in ~/.kynver/config.json.";
|
|
7032
|
+
}
|
|
6724
7033
|
checks.push(
|
|
6725
|
-
|
|
7034
|
+
check2({
|
|
6726
7035
|
id: "config_api_base_url",
|
|
6727
7036
|
label: "Default API base URL",
|
|
6728
7037
|
status: apiBaseUrl ? "pass" : "warn",
|
|
@@ -6730,7 +7039,7 @@ function assessUserConfig(probes) {
|
|
|
6730
7039
|
remediation: apiBaseUrl ? void 0 : "Set `apiBaseUrl` via `kynver setup --api-base-url https://\u2026`.",
|
|
6731
7040
|
details: { apiBaseUrl: apiBaseUrl ?? null }
|
|
6732
7041
|
}),
|
|
6733
|
-
|
|
7042
|
+
check2({
|
|
6734
7043
|
id: "config_agent_os_id",
|
|
6735
7044
|
label: "Default AgentOS id",
|
|
6736
7045
|
status: agentOsId ? "pass" : "warn",
|
|
@@ -6738,14 +7047,16 @@ function assessUserConfig(probes) {
|
|
|
6738
7047
|
remediation: agentOsId ? void 0 : "Set `agentOsId` via `kynver setup --agent-os-id <uuid>`.",
|
|
6739
7048
|
details: { agentOsId: agentOsId ?? null, agentOsSlug: config.agentOsSlug ?? null }
|
|
6740
7049
|
}),
|
|
6741
|
-
|
|
7050
|
+
check2({
|
|
6742
7051
|
id: "config_default_repo",
|
|
6743
7052
|
label: "Default repo path",
|
|
6744
|
-
status:
|
|
6745
|
-
summary:
|
|
6746
|
-
remediation:
|
|
7053
|
+
status: resolvedDefaultRepo ? "pass" : "warn",
|
|
7054
|
+
summary: resolvedDefaultRepo ? `${formatted.defaultRepo} (${resolvedDefaultRepo.source}${resolvedDefaultRepo.persistedInConfig ? "" : ", not persisted"})` : "Not set (pass --repo on `kynver run create` or run `kynver setup` to auto-discover)",
|
|
7055
|
+
remediation: defaultRepoRemediation,
|
|
6747
7056
|
details: {
|
|
6748
|
-
defaultRepo:
|
|
7057
|
+
defaultRepo: formatted?.defaultRepo ?? null,
|
|
7058
|
+
source: formatted?.source ?? null,
|
|
7059
|
+
persistedInConfig: formatted?.persistedInConfig ?? false,
|
|
6749
7060
|
harnessRoot: config.harnessRoot ? redactHomePath(config.harnessRoot) : null
|
|
6750
7061
|
}
|
|
6751
7062
|
})
|
|
@@ -6766,7 +7077,7 @@ function assessRunnerToken(probes) {
|
|
|
6766
7077
|
const scopedSaved = Boolean(savedToken) && (!targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId);
|
|
6767
7078
|
const hasScoped = Boolean(envToken?.startsWith("krc1.")) || scopedSaved && savedToken?.startsWith("krc1.");
|
|
6768
7079
|
const checks = [
|
|
6769
|
-
|
|
7080
|
+
check2({
|
|
6770
7081
|
id: "runner_token_scoped",
|
|
6771
7082
|
label: "Scoped runner token (krc1.*) ready",
|
|
6772
7083
|
status: hasScoped ? "pass" : "fail",
|
|
@@ -6779,7 +7090,7 @@ function assessRunnerToken(probes) {
|
|
|
6779
7090
|
credentialsPath: displayCredPath
|
|
6780
7091
|
}
|
|
6781
7092
|
}),
|
|
6782
|
-
|
|
7093
|
+
check2({
|
|
6783
7094
|
id: "runner_token_agent_os_match",
|
|
6784
7095
|
label: "Saved runner token matches configured agentOsId",
|
|
6785
7096
|
status: !savedToken || !targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId ? "pass" : "warn",
|
|
@@ -6787,7 +7098,7 @@ function assessRunnerToken(probes) {
|
|
|
6787
7098
|
remediation: savedToken && targetAgentOsId && savedAgentOsId && savedAgentOsId !== targetAgentOsId ? "`kynver runner credential --agent-os-id <configured-id>` to mint a workspace-bound token." : void 0,
|
|
6788
7099
|
details: { configuredAgentOsId: targetAgentOsId ?? null, savedAgentOsId: savedAgentOsId ?? null }
|
|
6789
7100
|
}),
|
|
6790
|
-
|
|
7101
|
+
check2({
|
|
6791
7102
|
id: "runner_api_key_for_refresh",
|
|
6792
7103
|
label: "API key available for token refresh",
|
|
6793
7104
|
status: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY?.trim()) ? "pass" : "warn",
|
|
@@ -6806,7 +7117,7 @@ function assessVercelCli(probes) {
|
|
|
6806
7117
|
id: "vercel_cli",
|
|
6807
7118
|
label: "Vercel CLI",
|
|
6808
7119
|
checks: [
|
|
6809
|
-
|
|
7120
|
+
check2({
|
|
6810
7121
|
id: "vercel_installed",
|
|
6811
7122
|
label: "Vercel CLI installed",
|
|
6812
7123
|
status: installed ? "pass" : "warn",
|
|
@@ -6814,7 +7125,7 @@ function assessVercelCli(probes) {
|
|
|
6814
7125
|
remediation: installed ? void 0 : "Install Vercel CLI (`npm i -g vercel`) for deploy evidence and env pulls.",
|
|
6815
7126
|
details: { stderr: version.stderr || null }
|
|
6816
7127
|
}),
|
|
6817
|
-
|
|
7128
|
+
check2({
|
|
6818
7129
|
id: "vercel_authenticated",
|
|
6819
7130
|
label: "Vercel CLI authenticated",
|
|
6820
7131
|
status: !installed ? "warn" : whoami.ok ? "pass" : "warn",
|
|
@@ -6827,8 +7138,8 @@ function assessVercelCli(probes) {
|
|
|
6827
7138
|
}
|
|
6828
7139
|
function assessHarnessDirs(probes) {
|
|
6829
7140
|
const harnessRoot = probes.harnessRoot();
|
|
6830
|
-
const runsDir =
|
|
6831
|
-
const worktreesDir =
|
|
7141
|
+
const runsDir = path39.join(harnessRoot, "runs");
|
|
7142
|
+
const worktreesDir = path39.join(harnessRoot, "worktrees");
|
|
6832
7143
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
6833
7144
|
const displayRunsDir = redactHomePath(runsDir);
|
|
6834
7145
|
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
@@ -6838,14 +7149,14 @@ function assessHarnessDirs(probes) {
|
|
|
6838
7149
|
id: "harness_dirs",
|
|
6839
7150
|
label: "Harness / daemon directories",
|
|
6840
7151
|
checks: [
|
|
6841
|
-
|
|
7152
|
+
check2({
|
|
6842
7153
|
id: "harness_root",
|
|
6843
7154
|
label: "Harness root resolved",
|
|
6844
7155
|
status: "pass",
|
|
6845
7156
|
summary: displayHarnessRoot,
|
|
6846
7157
|
details: { harnessRoot: displayHarnessRoot }
|
|
6847
7158
|
}),
|
|
6848
|
-
|
|
7159
|
+
check2({
|
|
6849
7160
|
id: "harness_runs_dir",
|
|
6850
7161
|
label: "Runs directory ready",
|
|
6851
7162
|
status: runsExists && probes.pathWritable(runsDir) ? "pass" : runsExists ? "warn" : "warn",
|
|
@@ -6853,7 +7164,7 @@ function assessHarnessDirs(probes) {
|
|
|
6853
7164
|
remediation: runsExists && !probes.pathWritable(runsDir) ? `Fix permissions on ${displayRunsDir} or set KYNVER_HARNESS_ROOT to a writable path.` : void 0,
|
|
6854
7165
|
details: { runsDir: displayRunsDir, exists: runsExists, writable: probes.pathWritable(runsDir) }
|
|
6855
7166
|
}),
|
|
6856
|
-
|
|
7167
|
+
check2({
|
|
6857
7168
|
id: "harness_worktrees_dir",
|
|
6858
7169
|
label: "Worktrees directory ready",
|
|
6859
7170
|
status: worktreesExists && probes.pathWritable(worktreesDir) ? "pass" : "warn",
|
|
@@ -6874,7 +7185,7 @@ function assessCallbackAuth(probes) {
|
|
|
6874
7185
|
const creds = probes.readCredentials();
|
|
6875
7186
|
const savedScoped = creds.runnerTokenPrefix?.startsWith("krc1.");
|
|
6876
7187
|
const checks = [
|
|
6877
|
-
|
|
7188
|
+
check2({
|
|
6878
7189
|
id: "callback_base_url",
|
|
6879
7190
|
label: "Callback base URL configured",
|
|
6880
7191
|
status: baseUrl ? usingLegacyBase ? "warn" : "pass" : "fail",
|
|
@@ -6885,7 +7196,7 @@ function assessCallbackAuth(probes) {
|
|
|
6885
7196
|
baseUrl: baseUrl ?? null
|
|
6886
7197
|
}
|
|
6887
7198
|
}),
|
|
6888
|
-
|
|
7199
|
+
check2({
|
|
6889
7200
|
id: "callback_auth_mode",
|
|
6890
7201
|
label: "Callback auth uses scoped runner token",
|
|
6891
7202
|
status: envScoped || savedScoped ? "pass" : legacySecret ? "warn" : "fail",
|
|
@@ -6901,15 +7212,22 @@ function assessCallbackAuth(probes) {
|
|
|
6901
7212
|
}
|
|
6902
7213
|
function assessOpenclawHotspots(probes) {
|
|
6903
7214
|
const env = probes.envSnapshot();
|
|
7215
|
+
const config = probes.loadConfig();
|
|
7216
|
+
const creds = probes.readCredentials();
|
|
6904
7217
|
const harnessRoot = probes.harnessRoot();
|
|
6905
7218
|
const legacyRoot = probes.legacyOpenclawHarnessRoot();
|
|
6906
7219
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
6907
7220
|
const displayLegacyRoot = redactHomePath(legacyRoot);
|
|
6908
7221
|
const displayOpusHarnessRoot = env.opusHarnessRoot ? redactHomePath(env.opusHarnessRoot) : null;
|
|
6909
7222
|
const legacyHarnessActive = harnessRoot === legacyRoot && probes.pathExists(legacyRoot);
|
|
6910
|
-
const
|
|
7223
|
+
const targetAgentOsId = config.agentOsId?.trim();
|
|
7224
|
+
const envToken = env.kynverRunnerTokenPrefix;
|
|
7225
|
+
const savedToken = creds.runnerTokenPrefix;
|
|
7226
|
+
const savedAgentOsId = creds.runnerTokenAgentOsId;
|
|
7227
|
+
const scopedSaved = Boolean(savedToken) && (!targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId);
|
|
7228
|
+
const hasScopedRunnerToken = Boolean(envToken?.startsWith("krc1.")) || scopedSaved && Boolean(savedToken?.startsWith("krc1."));
|
|
6911
7229
|
const checks = [
|
|
6912
|
-
|
|
7230
|
+
check2({
|
|
6913
7231
|
id: "hotspot_legacy_harness_root",
|
|
6914
7232
|
label: "Legacy ~/.openclaw/harness still active",
|
|
6915
7233
|
status: legacyHarnessActive ? "warn" : "pass",
|
|
@@ -6917,7 +7235,7 @@ function assessOpenclawHotspots(probes) {
|
|
|
6917
7235
|
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,
|
|
6918
7236
|
details: { harnessRoot: displayHarnessRoot, legacyRoot: displayLegacyRoot, opusHarnessRoot: displayOpusHarnessRoot }
|
|
6919
7237
|
}),
|
|
6920
|
-
|
|
7238
|
+
check2({
|
|
6921
7239
|
id: "hotspot_openclaw_env_secrets",
|
|
6922
7240
|
label: "OpenClaw deployment secrets in runner env",
|
|
6923
7241
|
status: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "warn" : "pass",
|
|
@@ -6927,15 +7245,12 @@ function assessOpenclawHotspots(probes) {
|
|
|
6927
7245
|
].filter(Boolean).join("; ") : "No OpenClaw cron env overrides on this runner",
|
|
6928
7246
|
remediation: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "Move to KYNVER_API_URL + scoped runner tokens; unset OpenClaw cron env on user-hosted runners." : void 0
|
|
6929
7247
|
}),
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
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}`,
|
|
6935
|
-
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,
|
|
6936
|
-
details: { schedulerProvider: env.kynverSchedulerProvider ?? null }
|
|
7248
|
+
assessRuntimeTakeoverScheduler(env, {
|
|
7249
|
+
agentOsId: targetAgentOsId ?? null,
|
|
7250
|
+
apiBaseUrl: config.apiBaseUrl?.trim() ?? env.kynverApiUrl ?? null,
|
|
7251
|
+
hasScopedRunnerToken
|
|
6937
7252
|
}),
|
|
6938
|
-
|
|
7253
|
+
check2({
|
|
6939
7254
|
id: "hotspot_lease_source_names",
|
|
6940
7255
|
label: "Harness lease/completion source names",
|
|
6941
7256
|
status: "pass",
|
|
@@ -7027,9 +7342,9 @@ function usage(code = 0) {
|
|
|
7027
7342
|
"Usage:",
|
|
7028
7343
|
" kynver login --api-key KEY",
|
|
7029
7344
|
" kynver runner credential [--agent-os-id ID] [--base-url URL]",
|
|
7030
|
-
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--max-workers N] [--provider claude|cursor]",
|
|
7345
|
+
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--discover-repo] [--max-workers N] [--provider claude|cursor]",
|
|
7031
7346
|
" kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS]",
|
|
7032
|
-
" kynver run create --repo /path/repo [--name name] [--base origin/main]",
|
|
7347
|
+
" kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
|
|
7033
7348
|
" kynver run list",
|
|
7034
7349
|
" kynver run status --run RUN_ID",
|
|
7035
7350
|
" kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--target-task-id TASK_ID] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-8] [--disk-path /]",
|
|
@@ -7122,7 +7437,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
7122
7437
|
if (scope === "monitor" && action === "run-loop") return void await monitorRunLoopCli(args);
|
|
7123
7438
|
unknownCommand(scope, action);
|
|
7124
7439
|
}
|
|
7125
|
-
var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(
|
|
7440
|
+
var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(fileURLToPath5(import.meta.url));
|
|
7126
7441
|
if (isCliEntry) {
|
|
7127
7442
|
void main().catch((error) => {
|
|
7128
7443
|
console.error(error);
|
|
@@ -7445,6 +7760,8 @@ export {
|
|
|
7445
7760
|
computeWorkerStatus,
|
|
7446
7761
|
createRun,
|
|
7447
7762
|
deriveRunStatus,
|
|
7763
|
+
discoverDefaultRepo,
|
|
7764
|
+
discoverDefaultRepoCandidates,
|
|
7448
7765
|
dispatchRun,
|
|
7449
7766
|
drainPlanOutbox,
|
|
7450
7767
|
ensurePrReadyHandoff,
|
|
@@ -7452,13 +7769,16 @@ export {
|
|
|
7452
7769
|
extractPlanOutboxFromTask,
|
|
7453
7770
|
extractPrUrlFromText,
|
|
7454
7771
|
formatPlanOutboxHandoffBlock,
|
|
7772
|
+
formatResolvedDefaultRepo,
|
|
7455
7773
|
getHarnessPaths,
|
|
7456
7774
|
getMonitorStatus,
|
|
7775
|
+
gitRepoRoot,
|
|
7457
7776
|
hashPlanBody,
|
|
7458
7777
|
isDashboardVercelUrl,
|
|
7459
7778
|
isEngagementRequiredSkip,
|
|
7460
7779
|
isFinishedWorkerStatus,
|
|
7461
7780
|
isForbiddenWorkerEnvKey,
|
|
7781
|
+
isKynverMonorepoRoot,
|
|
7462
7782
|
isLandingBlockedWorkerStatus,
|
|
7463
7783
|
isSystemdRunAvailable,
|
|
7464
7784
|
isTerminalHeartbeatPhase,
|
|
@@ -7477,6 +7797,7 @@ export {
|
|
|
7477
7797
|
parseClaudeStream,
|
|
7478
7798
|
parseHarnessStream,
|
|
7479
7799
|
parseHeartbeat,
|
|
7800
|
+
persistDefaultRepo,
|
|
7480
7801
|
persistPlan,
|
|
7481
7802
|
pickVercelStatusContext,
|
|
7482
7803
|
postJson,
|
|
@@ -7485,9 +7806,11 @@ export {
|
|
|
7485
7806
|
reconcileRunsCli,
|
|
7486
7807
|
reconcileStaleWorkers,
|
|
7487
7808
|
redactHarness,
|
|
7809
|
+
remediateDefaultRepo,
|
|
7488
7810
|
resolveBaseUrl,
|
|
7489
7811
|
resolveCallbackSecret,
|
|
7490
7812
|
resolveCallbackSecretWithMint,
|
|
7813
|
+
resolveDefaultRepo,
|
|
7491
7814
|
resolveHarnessRoot,
|
|
7492
7815
|
resolveVercelInspectTarget,
|
|
7493
7816
|
runBoundedBuildCheck,
|