@kynver-app/runtime 0.1.49 → 0.1.51
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/cleanup-guards.d.ts +3 -0
- package/dist/cleanup-retention-config.d.ts +14 -0
- package/dist/cleanup-run-liveness.d.ts +13 -0
- package/dist/cleanup-types.d.ts +12 -0
- package/dist/cleanup.d.ts +1 -1
- package/dist/cli.js +795 -546
- 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/finalize.d.ts +9 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +827 -541
- package/dist/index.js.map +4 -4
- 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;
|
|
@@ -1069,221 +1318,46 @@ function hasFinalResult(value) {
|
|
|
1069
1318
|
return true;
|
|
1070
1319
|
}
|
|
1071
1320
|
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();
|
|
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;
|
|
@@ -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) {
|
|
@@ -2838,7 +2912,7 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
|
|
|
2838
2912
|
}
|
|
2839
2913
|
|
|
2840
2914
|
// src/worker-lifecycle.ts
|
|
2841
|
-
import
|
|
2915
|
+
import path11 from "node:path";
|
|
2842
2916
|
var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
|
|
2843
2917
|
"awaiting_review",
|
|
2844
2918
|
"blocked",
|
|
@@ -2894,7 +2968,7 @@ function syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick) {
|
|
|
2894
2968
|
const synced = [];
|
|
2895
2969
|
for (const name of Object.keys(run.workers || {})) {
|
|
2896
2970
|
const worker = readJson(
|
|
2897
|
-
|
|
2971
|
+
path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2898
2972
|
void 0
|
|
2899
2973
|
);
|
|
2900
2974
|
if (!worker?.taskId || isCompletionAcknowledged(worker)) continue;
|
|
@@ -3152,7 +3226,7 @@ function workerStatus(args) {
|
|
|
3152
3226
|
const worker = loadWorker(String(args.run), String(args.name));
|
|
3153
3227
|
const run = loadRun(worker.runId);
|
|
3154
3228
|
const status = computeWorkerStatus(worker, workerStatusOptions(run));
|
|
3155
|
-
writeJson(
|
|
3229
|
+
writeJson(path12.join(worker.workerDir, "last-status.json"), status);
|
|
3156
3230
|
console.log(JSON.stringify(status, null, 2));
|
|
3157
3231
|
}
|
|
3158
3232
|
function buildRunBoard(runId) {
|
|
@@ -3160,7 +3234,7 @@ function buildRunBoard(runId) {
|
|
|
3160
3234
|
const names = Object.keys(run.workers || {});
|
|
3161
3235
|
const workers = names.map((name) => {
|
|
3162
3236
|
const worker = readJson(
|
|
3163
|
-
|
|
3237
|
+
path12.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
3164
3238
|
void 0
|
|
3165
3239
|
);
|
|
3166
3240
|
if (!worker) {
|
|
@@ -3271,7 +3345,7 @@ function buildRunBoard(runId) {
|
|
|
3271
3345
|
needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
|
|
3272
3346
|
workers
|
|
3273
3347
|
};
|
|
3274
|
-
writeJson(
|
|
3348
|
+
writeJson(path12.join(runDirectory(run.id), "last-board.json"), board);
|
|
3275
3349
|
return board;
|
|
3276
3350
|
}
|
|
3277
3351
|
async function publishHarnessBoardSnapshot(args, source) {
|
|
@@ -3460,12 +3534,12 @@ async function autoCompleteWorkerCli(raw) {
|
|
|
3460
3534
|
}
|
|
3461
3535
|
}
|
|
3462
3536
|
function resolveDefaultCliPath() {
|
|
3463
|
-
return
|
|
3537
|
+
return path13.join(fileURLToPath3(new URL(".", import.meta.url)), "cli.js");
|
|
3464
3538
|
}
|
|
3465
3539
|
function spawnCompletionSidecar(opts) {
|
|
3466
3540
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath();
|
|
3467
|
-
if (!
|
|
3468
|
-
const logPath =
|
|
3541
|
+
if (!existsSync11(cliPath)) return void 0;
|
|
3542
|
+
const logPath = path13.join(opts.workerDir, "auto-complete.log");
|
|
3469
3543
|
let logFd;
|
|
3470
3544
|
try {
|
|
3471
3545
|
logFd = openSync3(logPath, "a");
|
|
@@ -3547,16 +3621,16 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3547
3621
|
launchModel = preflight.model;
|
|
3548
3622
|
}
|
|
3549
3623
|
const { worktreesDir } = getPaths();
|
|
3550
|
-
const workerDir =
|
|
3624
|
+
const workerDir = path14.join(runDirectory(run.id), "workers", name);
|
|
3551
3625
|
mkdirSync3(workerDir, { recursive: true });
|
|
3552
|
-
const worktreePath =
|
|
3626
|
+
const worktreePath = path14.join(worktreesDir, run.id, name);
|
|
3553
3627
|
const branch = opts.branch || `agent/${run.id}/${name}`;
|
|
3554
|
-
if (
|
|
3628
|
+
if (existsSync12(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
|
|
3555
3629
|
git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
|
|
3556
3630
|
git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
|
|
3557
|
-
const stdoutPath =
|
|
3558
|
-
const stderrPath =
|
|
3559
|
-
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");
|
|
3560
3634
|
const prompt = buildPrompt({
|
|
3561
3635
|
task: opts.task,
|
|
3562
3636
|
ownedPaths: opts.ownedPaths || [],
|
|
@@ -3621,7 +3695,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3621
3695
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3622
3696
|
};
|
|
3623
3697
|
saveWorker(run.id, worker);
|
|
3624
|
-
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath:
|
|
3698
|
+
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path14.join(workerDir, "worker.json") } };
|
|
3625
3699
|
run.status = "running";
|
|
3626
3700
|
saveRun(run);
|
|
3627
3701
|
if (worker.agentOsId && worker.taskId) {
|
|
@@ -3913,18 +3987,18 @@ function buildPlanPersistIdempotencyKey(input) {
|
|
|
3913
3987
|
|
|
3914
3988
|
// src/plan-persist/paths.ts
|
|
3915
3989
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
3916
|
-
import { homedir as
|
|
3917
|
-
import
|
|
3990
|
+
import { homedir as homedir5 } from "node:os";
|
|
3991
|
+
import path15 from "node:path";
|
|
3918
3992
|
function resolveKynverStateRoot() {
|
|
3919
3993
|
const env = process.env.KYNVER_STATE_ROOT;
|
|
3920
|
-
if (env) return
|
|
3921
|
-
return
|
|
3994
|
+
if (env) return path15.resolve(env);
|
|
3995
|
+
return path15.join(homedir5(), ".kynver", "state");
|
|
3922
3996
|
}
|
|
3923
3997
|
function planOutboxDir() {
|
|
3924
|
-
return
|
|
3998
|
+
return path15.join(resolveKynverStateRoot(), "plan-outbox");
|
|
3925
3999
|
}
|
|
3926
4000
|
function planOutboxArchiveDir() {
|
|
3927
|
-
return
|
|
4001
|
+
return path15.join(resolveKynverStateRoot(), "plan-outbox-archive");
|
|
3928
4002
|
}
|
|
3929
4003
|
function ensurePlanOutboxDirs() {
|
|
3930
4004
|
const outboxDir = planOutboxDir();
|
|
@@ -3935,20 +4009,20 @@ function ensurePlanOutboxDirs() {
|
|
|
3935
4009
|
}
|
|
3936
4010
|
function isTmpOnlyPath(filePath) {
|
|
3937
4011
|
if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
|
|
3938
|
-
const resolved =
|
|
3939
|
-
return resolved.startsWith("/tmp/") || resolved.startsWith(
|
|
4012
|
+
const resolved = path15.resolve(filePath);
|
|
4013
|
+
return resolved.startsWith("/tmp/") || resolved.startsWith(path15.join("/var", "folders"));
|
|
3940
4014
|
}
|
|
3941
4015
|
|
|
3942
4016
|
// src/plan-persist/outbox-store.ts
|
|
3943
4017
|
import {
|
|
3944
|
-
existsSync as
|
|
3945
|
-
readFileSync as
|
|
4018
|
+
existsSync as existsSync14,
|
|
4019
|
+
readFileSync as readFileSync8,
|
|
3946
4020
|
renameSync,
|
|
3947
4021
|
readdirSync as readdirSync4,
|
|
3948
4022
|
writeFileSync as writeFileSync3,
|
|
3949
4023
|
unlinkSync
|
|
3950
4024
|
} from "node:fs";
|
|
3951
|
-
import
|
|
4025
|
+
import path16 from "node:path";
|
|
3952
4026
|
import { randomUUID } from "node:crypto";
|
|
3953
4027
|
var DEFAULT_MAX_RETRIES = 12;
|
|
3954
4028
|
function listOutboxItems() {
|
|
@@ -3956,7 +4030,7 @@ function listOutboxItems() {
|
|
|
3956
4030
|
const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
|
|
3957
4031
|
const items = [];
|
|
3958
4032
|
for (const file of files) {
|
|
3959
|
-
const item = readOutboxItem(
|
|
4033
|
+
const item = readOutboxItem(path16.join(outboxDir, file));
|
|
3960
4034
|
if (item && item.queueStatus === "queued") items.push(item);
|
|
3961
4035
|
}
|
|
3962
4036
|
return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
@@ -3968,25 +4042,25 @@ function findOutboxByIdempotencyKey(key) {
|
|
|
3968
4042
|
return null;
|
|
3969
4043
|
}
|
|
3970
4044
|
function readOutboxItem(jsonPath) {
|
|
3971
|
-
if (!
|
|
4045
|
+
if (!existsSync14(jsonPath)) return null;
|
|
3972
4046
|
try {
|
|
3973
|
-
return JSON.parse(
|
|
4047
|
+
return JSON.parse(readFileSync8(jsonPath, "utf8"));
|
|
3974
4048
|
} catch {
|
|
3975
4049
|
return null;
|
|
3976
4050
|
}
|
|
3977
4051
|
}
|
|
3978
4052
|
function readOutboxBody(item) {
|
|
3979
4053
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3980
|
-
const bodyFile =
|
|
3981
|
-
return
|
|
4054
|
+
const bodyFile = path16.join(outboxDir, item.bodyPath);
|
|
4055
|
+
return readFileSync8(bodyFile, "utf8");
|
|
3982
4056
|
}
|
|
3983
4057
|
function writeOutboxItem(input, opts) {
|
|
3984
4058
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3985
4059
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3986
4060
|
const id = opts.existing?.id ?? randomUUID();
|
|
3987
4061
|
const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
|
|
3988
|
-
const jsonPath =
|
|
3989
|
-
const bodyFile =
|
|
4062
|
+
const jsonPath = path16.join(outboxDir, `${id}.json`);
|
|
4063
|
+
const bodyFile = path16.join(outboxDir, bodyPath);
|
|
3990
4064
|
if (!opts.existing) {
|
|
3991
4065
|
writeFileSync3(bodyFile, input.body, "utf8");
|
|
3992
4066
|
}
|
|
@@ -4022,24 +4096,24 @@ function writeOutboxItem(input, opts) {
|
|
|
4022
4096
|
}
|
|
4023
4097
|
function saveOutboxItem(item) {
|
|
4024
4098
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
4025
|
-
const jsonPath =
|
|
4099
|
+
const jsonPath = path16.join(outboxDir, `${item.id}.json`);
|
|
4026
4100
|
writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
|
|
4027
4101
|
`, { mode: 384 });
|
|
4028
4102
|
}
|
|
4029
4103
|
function archiveOutboxItem(item) {
|
|
4030
4104
|
const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
|
|
4031
|
-
const jsonSrc =
|
|
4032
|
-
const bodySrc =
|
|
4033
|
-
const jsonDst =
|
|
4034
|
-
const bodyDst =
|
|
4035
|
-
if (
|
|
4036
|
-
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);
|
|
4037
4111
|
}
|
|
4038
4112
|
function outboxItemPaths(item) {
|
|
4039
4113
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
4040
4114
|
return {
|
|
4041
|
-
jsonPath:
|
|
4042
|
-
bodyPath:
|
|
4115
|
+
jsonPath: path16.join(outboxDir, `${item.id}.json`),
|
|
4116
|
+
bodyPath: path16.join(outboxDir, item.bodyPath)
|
|
4043
4117
|
};
|
|
4044
4118
|
}
|
|
4045
4119
|
function outboxInputFromItem(item, body) {
|
|
@@ -4225,7 +4299,7 @@ function markOutboxFailed(item, message) {
|
|
|
4225
4299
|
}
|
|
4226
4300
|
|
|
4227
4301
|
// src/plan-persist/drain.ts
|
|
4228
|
-
import
|
|
4302
|
+
import path17 from "node:path";
|
|
4229
4303
|
async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
4230
4304
|
const items = listOutboxItems().filter(
|
|
4231
4305
|
(item) => opts.outboxId ? item.id === opts.outboxId : true
|
|
@@ -4259,7 +4333,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
|
4259
4333
|
return result;
|
|
4260
4334
|
}
|
|
4261
4335
|
function loadOutboxById(outboxId) {
|
|
4262
|
-
const jsonPath =
|
|
4336
|
+
const jsonPath = path17.join(planOutboxDir(), `${outboxId}.json`);
|
|
4263
4337
|
return readOutboxItem(jsonPath);
|
|
4264
4338
|
}
|
|
4265
4339
|
|
|
@@ -4359,7 +4433,7 @@ async function dispatchRun(args) {
|
|
|
4359
4433
|
const activeHarnessWorkers = [];
|
|
4360
4434
|
for (const name of Object.keys(run.workers || {})) {
|
|
4361
4435
|
const worker = readJson(
|
|
4362
|
-
|
|
4436
|
+
path18.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4363
4437
|
void 0
|
|
4364
4438
|
);
|
|
4365
4439
|
if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
|
|
@@ -4584,7 +4658,7 @@ function redactHarness(text, secret) {
|
|
|
4584
4658
|
}
|
|
4585
4659
|
|
|
4586
4660
|
// src/validate.ts
|
|
4587
|
-
import
|
|
4661
|
+
import path19 from "node:path";
|
|
4588
4662
|
var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
4589
4663
|
var WORKER_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
|
|
4590
4664
|
function validateRunId(runId) {
|
|
@@ -4598,15 +4672,15 @@ function validateWorkerName(name) {
|
|
|
4598
4672
|
return trimmed;
|
|
4599
4673
|
}
|
|
4600
4674
|
function validateRepo(repo) {
|
|
4601
|
-
const resolved =
|
|
4675
|
+
const resolved = path19.resolve(repo);
|
|
4602
4676
|
if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
|
|
4603
4677
|
return resolved;
|
|
4604
4678
|
}
|
|
4605
4679
|
function validateOwnedPaths(repoRoot, ownedPaths) {
|
|
4606
4680
|
return ownedPaths.map((owned) => {
|
|
4607
|
-
const resolved =
|
|
4608
|
-
const rel =
|
|
4609
|
-
if (rel.startsWith("..") ||
|
|
4681
|
+
const resolved = path19.resolve(repoRoot, owned);
|
|
4682
|
+
const rel = path19.relative(repoRoot, resolved);
|
|
4683
|
+
if (rel.startsWith("..") || path19.isAbsolute(rel)) {
|
|
4610
4684
|
throw new Error(`owned path escapes repo: ${owned}`);
|
|
4611
4685
|
}
|
|
4612
4686
|
return resolved;
|
|
@@ -4618,14 +4692,93 @@ function validateTailLines(lines) {
|
|
|
4618
4692
|
}
|
|
4619
4693
|
|
|
4620
4694
|
// src/worktree.ts
|
|
4621
|
-
import { existsSync as
|
|
4622
|
-
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
|
+
}
|
|
4623
4776
|
function createRun(args) {
|
|
4624
|
-
const repo = validateRepo(
|
|
4777
|
+
const repo = validateRepo(resolveCreateRunRepo(args));
|
|
4625
4778
|
ensureGitRepo(repo);
|
|
4626
4779
|
const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
|
|
4627
4780
|
const dir = runDirectory(id);
|
|
4628
|
-
if (
|
|
4781
|
+
if (existsSync15(dir)) failExists(`run already exists: ${id}`);
|
|
4629
4782
|
mkdirSync5(dir, { recursive: true });
|
|
4630
4783
|
const base = String(args.base || "origin/main");
|
|
4631
4784
|
const baseCommit = git(repo, ["rev-parse", base]).trim();
|
|
@@ -4639,12 +4792,12 @@ function createRun(args) {
|
|
|
4639
4792
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4640
4793
|
workers: {}
|
|
4641
4794
|
};
|
|
4642
|
-
writeJson(
|
|
4795
|
+
writeJson(path21.join(dir, "run.json"), run);
|
|
4643
4796
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
4644
4797
|
}
|
|
4645
4798
|
function listRuns() {
|
|
4646
4799
|
const { runsDir } = getPaths();
|
|
4647
|
-
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) => ({
|
|
4648
4801
|
id: run.id,
|
|
4649
4802
|
name: run.name,
|
|
4650
4803
|
status: run.status,
|
|
@@ -4659,7 +4812,7 @@ function failExists(message) {
|
|
|
4659
4812
|
}
|
|
4660
4813
|
|
|
4661
4814
|
// src/sweep.ts
|
|
4662
|
-
import
|
|
4815
|
+
import path22 from "node:path";
|
|
4663
4816
|
async function sweepRun(args) {
|
|
4664
4817
|
const pipeline = args.pipeline === true || args.pipeline === "true";
|
|
4665
4818
|
try {
|
|
@@ -4672,7 +4825,7 @@ async function sweepRun(args) {
|
|
|
4672
4825
|
const releasedLocalOrphans = [];
|
|
4673
4826
|
for (const name of Object.keys(run.workers || {})) {
|
|
4674
4827
|
const worker = readJson(
|
|
4675
|
-
|
|
4828
|
+
path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4676
4829
|
void 0
|
|
4677
4830
|
);
|
|
4678
4831
|
if (!worker || !worker.dispatched || !worker.taskId) continue;
|
|
@@ -4716,10 +4869,10 @@ async function sweepRun(args) {
|
|
|
4716
4869
|
|
|
4717
4870
|
// src/cli.ts
|
|
4718
4871
|
import { mkdirSync as mkdirSync7, realpathSync } from "node:fs";
|
|
4719
|
-
import { fileURLToPath as
|
|
4872
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
4720
4873
|
|
|
4721
4874
|
// src/pipeline-tick.ts
|
|
4722
|
-
import
|
|
4875
|
+
import path32 from "node:path";
|
|
4723
4876
|
|
|
4724
4877
|
// src/pipeline-dispatch.ts
|
|
4725
4878
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -4773,12 +4926,19 @@ async function runPipelineDispatch(args, slots) {
|
|
|
4773
4926
|
}
|
|
4774
4927
|
|
|
4775
4928
|
// src/stale-reconcile.ts
|
|
4776
|
-
import
|
|
4929
|
+
import path24 from "node:path";
|
|
4777
4930
|
|
|
4778
4931
|
// src/finalize.ts
|
|
4779
|
-
import
|
|
4780
|
-
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set([
|
|
4781
|
-
|
|
4932
|
+
import path23 from "node:path";
|
|
4933
|
+
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set([
|
|
4934
|
+
"running",
|
|
4935
|
+
"dispatching",
|
|
4936
|
+
"pending",
|
|
4937
|
+
"queued",
|
|
4938
|
+
"needs_attention"
|
|
4939
|
+
]);
|
|
4940
|
+
var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "done"]);
|
|
4941
|
+
function deriveTerminalRunStatus(run) {
|
|
4782
4942
|
const names = Object.keys(run.workers || {});
|
|
4783
4943
|
if (names.length === 0) return "failed";
|
|
4784
4944
|
let anyAlive = false;
|
|
@@ -4787,7 +4947,7 @@ function terminalStatusFor(run) {
|
|
|
4787
4947
|
let anyLandingBlocked = false;
|
|
4788
4948
|
for (const name of names) {
|
|
4789
4949
|
const worker = readJson(
|
|
4790
|
-
|
|
4950
|
+
path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4791
4951
|
void 0
|
|
4792
4952
|
);
|
|
4793
4953
|
if (!worker) continue;
|
|
@@ -4816,7 +4976,7 @@ function finalizeStaleRuns() {
|
|
|
4816
4976
|
const finalized = [];
|
|
4817
4977
|
for (const run of listRunRecords()) {
|
|
4818
4978
|
if (!ACTIVE_RUN_STATUSES.has(run.status)) continue;
|
|
4819
|
-
const next =
|
|
4979
|
+
const next = deriveTerminalRunStatus(run);
|
|
4820
4980
|
if (!next || next === run.status) continue;
|
|
4821
4981
|
const from = run.status;
|
|
4822
4982
|
run.status = next;
|
|
@@ -4839,7 +4999,7 @@ function reconcileStaleWorkers() {
|
|
|
4839
4999
|
const now = Date.now();
|
|
4840
5000
|
for (const run of listRunRecords()) {
|
|
4841
5001
|
for (const name of Object.keys(run.workers || {})) {
|
|
4842
|
-
const workerPath =
|
|
5002
|
+
const workerPath = path24.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
4843
5003
|
const worker = readJson(workerPath, void 0);
|
|
4844
5004
|
if (!worker || worker.status !== "running") {
|
|
4845
5005
|
outcomes.push({
|
|
@@ -4933,7 +5093,7 @@ function reconcileRunsCli() {
|
|
|
4933
5093
|
}
|
|
4934
5094
|
|
|
4935
5095
|
// src/plan-progress-daemon-sync.ts
|
|
4936
|
-
import
|
|
5096
|
+
import path25 from "node:path";
|
|
4937
5097
|
|
|
4938
5098
|
// src/plan-progress-sync.ts
|
|
4939
5099
|
async function syncPlanProgress(args) {
|
|
@@ -4957,7 +5117,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
4957
5117
|
const outcomes = [];
|
|
4958
5118
|
for (const name of Object.keys(run.workers || {})) {
|
|
4959
5119
|
const worker = readJson(
|
|
4960
|
-
|
|
5120
|
+
path25.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4961
5121
|
void 0
|
|
4962
5122
|
);
|
|
4963
5123
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -5006,15 +5166,28 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
5006
5166
|
}
|
|
5007
5167
|
|
|
5008
5168
|
// src/cleanup.ts
|
|
5009
|
-
import
|
|
5169
|
+
import path30 from "node:path";
|
|
5010
5170
|
|
|
5011
|
-
// src/cleanup-
|
|
5012
|
-
|
|
5013
|
-
|
|
5171
|
+
// src/cleanup-run-liveness.ts
|
|
5172
|
+
function isWorkerProcessLive(indexed) {
|
|
5173
|
+
if (indexed.status.alive) return true;
|
|
5174
|
+
if (indexed.worker.status === "running") return true;
|
|
5175
|
+
return false;
|
|
5176
|
+
}
|
|
5177
|
+
function isRunStaleActive(indexed) {
|
|
5178
|
+
if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
|
|
5179
|
+
return deriveTerminalRunStatus(indexed.run) !== null;
|
|
5180
|
+
}
|
|
5181
|
+
function runBlocksWorktreeRemoval(indexed) {
|
|
5182
|
+
if (isWorkerProcessLive(indexed)) return true;
|
|
5183
|
+
if (indexed.worker.completionBlocker) return true;
|
|
5184
|
+
if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
|
|
5185
|
+
if (isRunStaleActive(indexed)) return false;
|
|
5186
|
+
if (!isFinishedWorkerStatus(indexed.status)) return true;
|
|
5187
|
+
return deriveTerminalRunStatus(indexed.run) === null;
|
|
5188
|
+
}
|
|
5014
5189
|
|
|
5015
5190
|
// src/cleanup-guards.ts
|
|
5016
|
-
var ACTIVE_RUN_STATUSES2 = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued", "needs_attention"]);
|
|
5017
|
-
var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
5018
5191
|
function prUrlFromFinalResult(finalResult) {
|
|
5019
5192
|
if (typeof finalResult === "string") {
|
|
5020
5193
|
const match = finalResult.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/i);
|
|
@@ -5036,18 +5209,29 @@ function isPrOrUnmergedWork(status) {
|
|
|
5036
5209
|
if (status.changedFiles.length > 0 && status.finalResult) return true;
|
|
5037
5210
|
return false;
|
|
5038
5211
|
}
|
|
5212
|
+
function materialWorktreeChanges(changedFiles) {
|
|
5213
|
+
return changedFiles.filter((line) => {
|
|
5214
|
+
const trimmed = line.trim();
|
|
5215
|
+
const pathPart = trimmed.startsWith("??") ? trimmed.slice(2).trim() : trimmed.length > 3 ? trimmed.slice(3).trim() : trimmed;
|
|
5216
|
+
return pathPart !== "node_modules" && !pathPart.startsWith("node_modules/");
|
|
5217
|
+
});
|
|
5218
|
+
}
|
|
5219
|
+
function hasUnrestorableWorktreeChanges(status) {
|
|
5220
|
+
if (materialWorktreeChanges(status.changedFiles).length > 0) return true;
|
|
5221
|
+
if (status.gitAncestry?.relation === "diverged") return true;
|
|
5222
|
+
return false;
|
|
5223
|
+
}
|
|
5039
5224
|
function skipWorktreeRemoval(input) {
|
|
5040
5225
|
const { indexed, includeOrphans, worktreesAgeMs, ageMs } = input;
|
|
5041
5226
|
if (worktreesAgeMs <= 0) return "worktrees_disabled";
|
|
5042
5227
|
if (ageMs < worktreesAgeMs) return "below_age_threshold";
|
|
5043
5228
|
if (!indexed) return includeOrphans ? null : "orphan_without_flag";
|
|
5044
|
-
if (
|
|
5045
|
-
if (!TERMINAL_RUN_STATUSES.has(indexed.run.status)) return "run_still_active";
|
|
5046
|
-
if (indexed.status.alive) return "active_worker";
|
|
5047
|
-
if (indexed.worker.status === "running") return "active_worker";
|
|
5229
|
+
if (isWorkerProcessLive(indexed)) return "active_worker";
|
|
5048
5230
|
if (indexed.worker.completionBlocker) return "completion_blocked";
|
|
5231
|
+
if (runBlocksWorktreeRemoval(indexed)) return "run_still_active";
|
|
5232
|
+
if (!isFinishedWorkerStatus(indexed.status)) return "run_still_active";
|
|
5049
5233
|
if (isPrOrUnmergedWork(indexed.status)) return "pr_or_unmerged_commits";
|
|
5050
|
-
if (indexed.status.changedFiles.length > 0) return "dirty_worktree";
|
|
5234
|
+
if (materialWorktreeChanges(indexed.status.changedFiles).length > 0) return "dirty_worktree";
|
|
5051
5235
|
const landing = assessWorkerLanding({
|
|
5052
5236
|
finalResult: indexed.status.finalResult,
|
|
5053
5237
|
changedFiles: indexed.status.changedFiles,
|
|
@@ -5061,30 +5245,31 @@ function skipNodeModulesRemoval(input) {
|
|
|
5061
5245
|
const { indexed, includeOrphans, nodeModulesAgeMs, ageMs } = input;
|
|
5062
5246
|
if (ageMs < nodeModulesAgeMs) return "below_age_threshold";
|
|
5063
5247
|
if (!indexed) return includeOrphans ? null : "orphan_without_flag";
|
|
5064
|
-
if (indexed
|
|
5065
|
-
if (indexed.worker.status === "running") return "active_worker";
|
|
5248
|
+
if (isWorkerProcessLive(indexed)) return "active_worker";
|
|
5066
5249
|
if (indexed.worker.completionBlocker) return "completion_blocked";
|
|
5067
|
-
if (
|
|
5068
|
-
if (indexed.status
|
|
5250
|
+
if (!isFinishedWorkerStatus(indexed.status)) return "run_still_active";
|
|
5251
|
+
if (hasUnrestorableWorktreeChanges(indexed.status)) return "dirty_worktree";
|
|
5069
5252
|
const landing = assessWorkerLanding({
|
|
5070
5253
|
finalResult: indexed.status.finalResult,
|
|
5071
5254
|
changedFiles: indexed.status.changedFiles,
|
|
5072
5255
|
gitAncestry: indexed.status.gitAncestry,
|
|
5073
5256
|
prUrl: prUrlFromFinalResult(indexed.status.finalResult)
|
|
5074
5257
|
});
|
|
5075
|
-
if (landing.blocked)
|
|
5258
|
+
if (landing.blocked && materialWorktreeChanges(indexed.status.changedFiles).length > 0) {
|
|
5259
|
+
return "landing_blocked";
|
|
5260
|
+
}
|
|
5076
5261
|
return null;
|
|
5077
5262
|
}
|
|
5078
5263
|
|
|
5079
5264
|
// src/cleanup-execute.ts
|
|
5080
|
-
import { existsSync as
|
|
5081
|
-
import
|
|
5265
|
+
import { existsSync as existsSync17, rmSync } from "node:fs";
|
|
5266
|
+
import path27 from "node:path";
|
|
5082
5267
|
|
|
5083
5268
|
// src/cleanup-dir-size.ts
|
|
5084
|
-
import { existsSync as
|
|
5085
|
-
import
|
|
5269
|
+
import { existsSync as existsSync16, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
|
|
5270
|
+
import path26 from "node:path";
|
|
5086
5271
|
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
5087
|
-
if (!
|
|
5272
|
+
if (!existsSync16(root)) return 0;
|
|
5088
5273
|
let total = 0;
|
|
5089
5274
|
let seen = 0;
|
|
5090
5275
|
const stack = [root];
|
|
@@ -5098,7 +5283,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
5098
5283
|
}
|
|
5099
5284
|
for (const name of entries) {
|
|
5100
5285
|
if (seen++ > maxEntries) return null;
|
|
5101
|
-
const full =
|
|
5286
|
+
const full = path26.join(current, name);
|
|
5102
5287
|
let st;
|
|
5103
5288
|
try {
|
|
5104
5289
|
st = statSync2(full);
|
|
@@ -5114,7 +5299,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
5114
5299
|
|
|
5115
5300
|
// src/cleanup-execute.ts
|
|
5116
5301
|
function removeNodeModules(candidate, execute) {
|
|
5117
|
-
if (!
|
|
5302
|
+
if (!existsSync17(candidate.path)) {
|
|
5118
5303
|
return {
|
|
5119
5304
|
...candidate,
|
|
5120
5305
|
executed: false,
|
|
@@ -5145,7 +5330,7 @@ function removeNodeModules(candidate, execute) {
|
|
|
5145
5330
|
}
|
|
5146
5331
|
}
|
|
5147
5332
|
function removeWorktree(candidate, execute) {
|
|
5148
|
-
if (!
|
|
5333
|
+
if (!existsSync17(candidate.path)) {
|
|
5149
5334
|
return {
|
|
5150
5335
|
...candidate,
|
|
5151
5336
|
executed: false,
|
|
@@ -5162,7 +5347,7 @@ function removeWorktree(candidate, execute) {
|
|
|
5162
5347
|
if (repo) {
|
|
5163
5348
|
git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
|
|
5164
5349
|
}
|
|
5165
|
-
if (
|
|
5350
|
+
if (existsSync17(candidate.path)) {
|
|
5166
5351
|
rmSync(candidate.path, { recursive: true, force: true });
|
|
5167
5352
|
}
|
|
5168
5353
|
return {
|
|
@@ -5182,20 +5367,20 @@ function removeWorktree(candidate, execute) {
|
|
|
5182
5367
|
}
|
|
5183
5368
|
}
|
|
5184
5369
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
5185
|
-
const resolved =
|
|
5186
|
-
const nm = resolved.endsWith(`${
|
|
5370
|
+
const resolved = path27.resolve(targetPath);
|
|
5371
|
+
const nm = resolved.endsWith(`${path27.sep}node_modules`) ? resolved : null;
|
|
5187
5372
|
if (!nm) return "path_outside_harness";
|
|
5188
|
-
const rel =
|
|
5189
|
-
if (rel.startsWith("..") ||
|
|
5190
|
-
const parts = rel.split(
|
|
5373
|
+
const rel = path27.relative(worktreesDir, nm);
|
|
5374
|
+
if (rel.startsWith("..") || path27.isAbsolute(rel)) return "path_outside_harness";
|
|
5375
|
+
const parts = rel.split(path27.sep);
|
|
5191
5376
|
if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
|
|
5192
|
-
if (!resolved.startsWith(
|
|
5377
|
+
if (!resolved.startsWith(path27.resolve(harnessRoot))) return "path_outside_harness";
|
|
5193
5378
|
return null;
|
|
5194
5379
|
}
|
|
5195
5380
|
|
|
5196
5381
|
// src/cleanup-scan.ts
|
|
5197
|
-
import { existsSync as
|
|
5198
|
-
import
|
|
5382
|
+
import { existsSync as existsSync18, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
5383
|
+
import path28 from "node:path";
|
|
5199
5384
|
function pathAgeMs(target, now) {
|
|
5200
5385
|
try {
|
|
5201
5386
|
const mtime = statSync3(target).mtimeMs;
|
|
@@ -5205,17 +5390,17 @@ function pathAgeMs(target, now) {
|
|
|
5205
5390
|
}
|
|
5206
5391
|
}
|
|
5207
5392
|
function isPathInside(child, parent) {
|
|
5208
|
-
const rel =
|
|
5209
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
5393
|
+
const rel = path28.relative(parent, child);
|
|
5394
|
+
return rel === "" || !rel.startsWith("..") && !path28.isAbsolute(rel);
|
|
5210
5395
|
}
|
|
5211
5396
|
function scanNodeModulesCandidates(opts) {
|
|
5212
5397
|
const candidates = [];
|
|
5213
5398
|
const seen = /* @__PURE__ */ new Set();
|
|
5214
5399
|
for (const entry of opts.index.values()) {
|
|
5215
5400
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
5216
|
-
const nm =
|
|
5217
|
-
if (!
|
|
5218
|
-
const resolved =
|
|
5401
|
+
const nm = path28.join(entry.worktreePath, "node_modules");
|
|
5402
|
+
if (!existsSync18(nm)) continue;
|
|
5403
|
+
const resolved = path28.resolve(nm);
|
|
5219
5404
|
if (seen.has(resolved)) continue;
|
|
5220
5405
|
seen.add(resolved);
|
|
5221
5406
|
candidates.push({
|
|
@@ -5228,16 +5413,16 @@ function scanNodeModulesCandidates(opts) {
|
|
|
5228
5413
|
ageMs: pathAgeMs(resolved, opts.now)
|
|
5229
5414
|
});
|
|
5230
5415
|
}
|
|
5231
|
-
if (!opts.includeOrphans || !
|
|
5416
|
+
if (!opts.includeOrphans || !existsSync18(opts.worktreesDir)) return candidates;
|
|
5232
5417
|
for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
|
|
5233
5418
|
if (!runEntry.isDirectory()) continue;
|
|
5234
|
-
const runPath =
|
|
5419
|
+
const runPath = path28.join(opts.worktreesDir, runEntry.name);
|
|
5235
5420
|
for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
|
|
5236
5421
|
if (!workerEntry.isDirectory()) continue;
|
|
5237
|
-
const worktreePath =
|
|
5238
|
-
const nm =
|
|
5239
|
-
if (!
|
|
5240
|
-
const resolved =
|
|
5422
|
+
const worktreePath = path28.join(runPath, workerEntry.name);
|
|
5423
|
+
const nm = path28.join(worktreePath, "node_modules");
|
|
5424
|
+
if (!existsSync18(nm)) continue;
|
|
5425
|
+
const resolved = path28.resolve(nm);
|
|
5241
5426
|
if (seen.has(resolved)) continue;
|
|
5242
5427
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
5243
5428
|
seen.add(resolved);
|
|
@@ -5260,7 +5445,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
5260
5445
|
for (const entry of opts.index.values()) {
|
|
5261
5446
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
5262
5447
|
const resolved = entry.worktreePath;
|
|
5263
|
-
if (!
|
|
5448
|
+
if (!existsSync18(resolved)) continue;
|
|
5264
5449
|
if (seen.has(resolved)) continue;
|
|
5265
5450
|
seen.add(resolved);
|
|
5266
5451
|
candidates.push({
|
|
@@ -5277,17 +5462,17 @@ function scanWorktreeCandidates(opts) {
|
|
|
5277
5462
|
}
|
|
5278
5463
|
|
|
5279
5464
|
// src/cleanup-worktree-index.ts
|
|
5280
|
-
import
|
|
5465
|
+
import path29 from "node:path";
|
|
5281
5466
|
function buildWorktreeIndex() {
|
|
5282
5467
|
const index = /* @__PURE__ */ new Map();
|
|
5283
5468
|
for (const run of listRunRecords()) {
|
|
5284
5469
|
for (const name of Object.keys(run.workers || {})) {
|
|
5285
|
-
const workerPath =
|
|
5470
|
+
const workerPath = path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
5286
5471
|
const worker = readJson(workerPath, void 0);
|
|
5287
5472
|
if (!worker?.worktreePath) continue;
|
|
5288
5473
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
5289
|
-
index.set(
|
|
5290
|
-
worktreePath:
|
|
5474
|
+
index.set(path29.resolve(worker.worktreePath), {
|
|
5475
|
+
worktreePath: path29.resolve(worker.worktreePath),
|
|
5291
5476
|
runId: run.id,
|
|
5292
5477
|
workerName: name,
|
|
5293
5478
|
run,
|
|
@@ -5299,59 +5484,109 @@ function buildWorktreeIndex() {
|
|
|
5299
5484
|
return index;
|
|
5300
5485
|
}
|
|
5301
5486
|
|
|
5302
|
-
// src/cleanup.ts
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
const
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
5487
|
+
// src/cleanup-types.ts
|
|
5488
|
+
var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
5489
|
+
var DEFAULT_WORKTREES_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
5490
|
+
|
|
5491
|
+
// src/cleanup-retention-config.ts
|
|
5492
|
+
function envFlag(name) {
|
|
5493
|
+
const v = process.env[name];
|
|
5494
|
+
return v === "1" || v === "true" || v === "yes";
|
|
5495
|
+
}
|
|
5496
|
+
function envMs(name, fallback) {
|
|
5497
|
+
const raw = process.env[name];
|
|
5498
|
+
if (!raw) return fallback;
|
|
5499
|
+
const n = Number(raw);
|
|
5500
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
5501
|
+
}
|
|
5502
|
+
function resolveHarnessRetention(options = {}) {
|
|
5503
|
+
const execute = options.execute === true || options.execute !== false && envFlag("KYNVER_CLEANUP_EXECUTE");
|
|
5504
|
+
const finalizeStaleRuns2 = options.finalizeStaleRuns !== false && !envFlag("KYNVER_CLEANUP_SKIP_FINALIZE");
|
|
5505
|
+
const nodeModulesAgeMs = options.nodeModulesAgeMs ?? envMs("KYNVER_CLEANUP_NODE_MODULES_AGE_MS", DEFAULT_NODE_MODULES_AGE_MS);
|
|
5506
|
+
const worktreesAgeMs = options.worktreesAgeMs ?? envMs("KYNVER_CLEANUP_WORKTREES_AGE_MS", 0);
|
|
5507
|
+
const includeOrphans = options.includeOrphans === true || envFlag("KYNVER_CLEANUP_INCLUDE_ORPHANS");
|
|
5508
|
+
const scopeAll = envFlag("KYNVER_CLEANUP_SCOPE_ALL") || process.env.KYNVER_CLEANUP_SCOPE === "all";
|
|
5509
|
+
const runIdFilter = scopeAll ? options.runIdFilter : options.runIdFilter ?? (process.env.KYNVER_CLEANUP_RUN_ID || void 0);
|
|
5510
|
+
const accountBytes = options.accountBytes !== false && !envFlag("KYNVER_CLEANUP_SKIP_BYTE_ACCOUNTING");
|
|
5312
5511
|
return {
|
|
5313
|
-
harnessRoot,
|
|
5314
|
-
worktreesDir,
|
|
5315
5512
|
execute,
|
|
5316
|
-
|
|
5513
|
+
finalizeStaleRuns: finalizeStaleRuns2,
|
|
5317
5514
|
nodeModulesAgeMs,
|
|
5318
|
-
worktreesAgeMs,
|
|
5515
|
+
worktreesAgeMs: worktreesAgeMs > 0 ? worktreesAgeMs : 0,
|
|
5319
5516
|
includeOrphans,
|
|
5320
|
-
runIdFilter,
|
|
5321
|
-
|
|
5517
|
+
runIdFilter: runIdFilter ? String(runIdFilter) : void 0,
|
|
5518
|
+
accountBytes
|
|
5322
5519
|
};
|
|
5323
5520
|
}
|
|
5521
|
+
function resolvePipelineHarnessRetention(runId) {
|
|
5522
|
+
const scopeAll = process.env.KYNVER_CLEANUP_SCOPE === "all";
|
|
5523
|
+
const envWorktrees = Number(process.env.KYNVER_CLEANUP_WORKTREES_AGE_MS);
|
|
5524
|
+
const worktreesAgeMs = scopeAll ? Number.isFinite(envWorktrees) && envWorktrees > 0 ? envWorktrees : DEFAULT_WORKTREES_AGE_MS : Number.isFinite(envWorktrees) && envWorktrees > 0 ? envWorktrees : 0;
|
|
5525
|
+
return resolveHarnessRetention({
|
|
5526
|
+
runIdFilter: scopeAll ? void 0 : runId,
|
|
5527
|
+
worktreesAgeMs,
|
|
5528
|
+
finalizeStaleRuns: true,
|
|
5529
|
+
accountBytes: true
|
|
5530
|
+
});
|
|
5531
|
+
}
|
|
5532
|
+
|
|
5533
|
+
// src/cleanup.ts
|
|
5534
|
+
function resolvePaths(options = {}) {
|
|
5535
|
+
const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
|
|
5536
|
+
const { worktreesDir } = options.harnessRoot ? { worktreesDir: path30.join(harnessRoot, "worktrees") } : getHarnessPaths();
|
|
5537
|
+
const now = options.now ?? Date.now();
|
|
5538
|
+
return { harnessRoot, worktreesDir, now };
|
|
5539
|
+
}
|
|
5324
5540
|
function recordSkip(skips, pathValue, reason, detail) {
|
|
5325
5541
|
skips.push({ path: pathValue, reason, ...detail ? { detail } : {} });
|
|
5326
5542
|
}
|
|
5543
|
+
function attachCandidateBytes(candidate, accountBytes) {
|
|
5544
|
+
if (!accountBytes || candidate.bytes != null) return candidate;
|
|
5545
|
+
return { ...candidate, bytes: directorySizeBytes(candidate.path) };
|
|
5546
|
+
}
|
|
5547
|
+
function tallySkipReasons(actions, skips) {
|
|
5548
|
+
const counts = {};
|
|
5549
|
+
for (const skip of skips) {
|
|
5550
|
+
counts[skip.reason] = (counts[skip.reason] ?? 0) + 1;
|
|
5551
|
+
}
|
|
5552
|
+
for (const action of actions) {
|
|
5553
|
+
if (action.skipReason) {
|
|
5554
|
+
counts[action.skipReason] = (counts[action.skipReason] ?? 0) + 1;
|
|
5555
|
+
}
|
|
5556
|
+
}
|
|
5557
|
+
return counts;
|
|
5558
|
+
}
|
|
5327
5559
|
function runHarnessCleanup(options = {}) {
|
|
5328
|
-
const
|
|
5560
|
+
const retention = resolveHarnessRetention(options);
|
|
5561
|
+
const paths = resolvePaths(options);
|
|
5562
|
+
const finalizedRuns = retention.finalizeStaleRuns ? finalizeStaleRuns().map((f) => ({ runId: f.runId, from: f.from, to: f.to })) : [];
|
|
5329
5563
|
const index = buildWorktreeIndex();
|
|
5330
5564
|
const scanOpts = {
|
|
5331
|
-
harnessRoot:
|
|
5332
|
-
worktreesDir:
|
|
5333
|
-
nodeModulesAgeMs:
|
|
5334
|
-
worktreesAgeMs:
|
|
5335
|
-
includeOrphans:
|
|
5336
|
-
runIdFilter:
|
|
5565
|
+
harnessRoot: paths.harnessRoot,
|
|
5566
|
+
worktreesDir: paths.worktreesDir,
|
|
5567
|
+
nodeModulesAgeMs: retention.nodeModulesAgeMs,
|
|
5568
|
+
worktreesAgeMs: retention.worktreesAgeMs,
|
|
5569
|
+
includeOrphans: retention.includeOrphans,
|
|
5570
|
+
runIdFilter: retention.runIdFilter,
|
|
5337
5571
|
index,
|
|
5338
|
-
now:
|
|
5572
|
+
now: paths.now
|
|
5339
5573
|
};
|
|
5340
5574
|
const skips = [];
|
|
5341
5575
|
const actions = [];
|
|
5342
|
-
for (const
|
|
5343
|
-
const
|
|
5576
|
+
for (const raw of scanNodeModulesCandidates(scanOpts)) {
|
|
5577
|
+
const candidate = attachCandidateBytes(raw, retention.accountBytes);
|
|
5578
|
+
const pathSkip = isHarnessNodeModulesPath(candidate.path, paths.harnessRoot, paths.worktreesDir);
|
|
5344
5579
|
if (pathSkip) {
|
|
5345
5580
|
recordSkip(skips, candidate.path, pathSkip);
|
|
5346
5581
|
actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
|
|
5347
5582
|
continue;
|
|
5348
5583
|
}
|
|
5349
|
-
const worktreePath =
|
|
5584
|
+
const worktreePath = path30.resolve(candidate.path, "..");
|
|
5350
5585
|
const indexed = index.get(worktreePath) ?? null;
|
|
5351
5586
|
const guardReason = skipNodeModulesRemoval({
|
|
5352
5587
|
indexed,
|
|
5353
|
-
includeOrphans:
|
|
5354
|
-
nodeModulesAgeMs:
|
|
5588
|
+
includeOrphans: retention.includeOrphans,
|
|
5589
|
+
nodeModulesAgeMs: retention.nodeModulesAgeMs,
|
|
5355
5590
|
ageMs: candidate.ageMs
|
|
5356
5591
|
});
|
|
5357
5592
|
if (guardReason) {
|
|
@@ -5359,14 +5594,15 @@ function runHarnessCleanup(options = {}) {
|
|
|
5359
5594
|
actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
|
|
5360
5595
|
continue;
|
|
5361
5596
|
}
|
|
5362
|
-
actions.push(removeNodeModules(candidate,
|
|
5597
|
+
actions.push(removeNodeModules(candidate, retention.execute));
|
|
5363
5598
|
}
|
|
5364
|
-
for (const
|
|
5365
|
-
const
|
|
5599
|
+
for (const raw of scanWorktreeCandidates(scanOpts)) {
|
|
5600
|
+
const candidate = attachCandidateBytes(raw, retention.accountBytes);
|
|
5601
|
+
const indexed = index.get(path30.resolve(candidate.path)) ?? null;
|
|
5366
5602
|
const guardReason = skipWorktreeRemoval({
|
|
5367
5603
|
indexed,
|
|
5368
|
-
includeOrphans:
|
|
5369
|
-
worktreesAgeMs:
|
|
5604
|
+
includeOrphans: retention.includeOrphans,
|
|
5605
|
+
worktreesAgeMs: retention.worktreesAgeMs,
|
|
5370
5606
|
ageMs: candidate.ageMs
|
|
5371
5607
|
});
|
|
5372
5608
|
if (guardReason) {
|
|
@@ -5374,51 +5610,55 @@ function runHarnessCleanup(options = {}) {
|
|
|
5374
5610
|
actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
|
|
5375
5611
|
continue;
|
|
5376
5612
|
}
|
|
5377
|
-
actions.push(removeWorktree(candidate,
|
|
5613
|
+
actions.push(removeWorktree(candidate, retention.execute));
|
|
5378
5614
|
}
|
|
5379
5615
|
let candidateBytes = 0;
|
|
5616
|
+
let reclaimableBytes = 0;
|
|
5380
5617
|
let removedBytes = 0;
|
|
5381
5618
|
let removedPaths = 0;
|
|
5382
5619
|
let skippedPaths = 0;
|
|
5383
5620
|
for (const action of actions) {
|
|
5384
5621
|
if (action.bytes) candidateBytes += action.bytes;
|
|
5622
|
+
if (!action.skipped && !action.executed && action.bytes) reclaimableBytes += action.bytes;
|
|
5385
5623
|
if (action.executed) {
|
|
5386
5624
|
removedPaths += 1;
|
|
5387
5625
|
removedBytes += action.bytes ?? 0;
|
|
5388
5626
|
} else if (action.skipped) {
|
|
5389
5627
|
skippedPaths += 1;
|
|
5628
|
+
if (action.skipReason === "dry_run" && action.bytes) reclaimableBytes += action.bytes;
|
|
5390
5629
|
}
|
|
5391
5630
|
}
|
|
5392
5631
|
return {
|
|
5393
|
-
harnessRoot:
|
|
5394
|
-
dryRun:
|
|
5395
|
-
execute:
|
|
5396
|
-
nodeModulesAgeMs:
|
|
5397
|
-
worktreesAgeMs:
|
|
5398
|
-
includeOrphans:
|
|
5399
|
-
scannedAt: new Date(
|
|
5632
|
+
harnessRoot: paths.harnessRoot,
|
|
5633
|
+
dryRun: !retention.execute,
|
|
5634
|
+
execute: retention.execute,
|
|
5635
|
+
nodeModulesAgeMs: retention.nodeModulesAgeMs,
|
|
5636
|
+
worktreesAgeMs: retention.worktreesAgeMs,
|
|
5637
|
+
includeOrphans: retention.includeOrphans,
|
|
5638
|
+
scannedAt: new Date(paths.now).toISOString(),
|
|
5639
|
+
finalizedRuns,
|
|
5400
5640
|
actions,
|
|
5401
5641
|
skips,
|
|
5402
5642
|
totals: {
|
|
5403
5643
|
candidateBytes,
|
|
5644
|
+
reclaimableBytes,
|
|
5404
5645
|
removedBytes,
|
|
5405
5646
|
removedPaths,
|
|
5406
|
-
skippedPaths
|
|
5647
|
+
skippedPaths,
|
|
5648
|
+
skipReasons: tallySkipReasons(actions, skips)
|
|
5407
5649
|
}
|
|
5408
5650
|
};
|
|
5409
5651
|
}
|
|
5410
5652
|
function runPipelineHarnessCleanup(runId) {
|
|
5411
|
-
const
|
|
5412
|
-
const worktreesAgeMs = Number(process.env.KYNVER_CLEANUP_WORKTREES_AGE_MS) || 0;
|
|
5413
|
-
const execute = process.env.KYNVER_CLEANUP_EXECUTE === "1";
|
|
5414
|
-
const includeOrphans = process.env.KYNVER_CLEANUP_INCLUDE_ORPHANS === "1";
|
|
5415
|
-
const scopeAll = process.env.KYNVER_CLEANUP_SCOPE === "all";
|
|
5653
|
+
const retention = resolvePipelineHarnessRetention(runId);
|
|
5416
5654
|
return runHarnessCleanup({
|
|
5417
|
-
execute,
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5655
|
+
execute: retention.execute,
|
|
5656
|
+
finalizeStaleRuns: retention.finalizeStaleRuns,
|
|
5657
|
+
accountBytes: retention.accountBytes,
|
|
5658
|
+
nodeModulesAgeMs: retention.nodeModulesAgeMs,
|
|
5659
|
+
worktreesAgeMs: retention.worktreesAgeMs,
|
|
5660
|
+
includeOrphans: retention.includeOrphans,
|
|
5661
|
+
runIdFilter: retention.runIdFilter
|
|
5422
5662
|
});
|
|
5423
5663
|
}
|
|
5424
5664
|
function isPipelineCleanupEnabled() {
|
|
@@ -5427,8 +5667,8 @@ function isPipelineCleanupEnabled() {
|
|
|
5427
5667
|
|
|
5428
5668
|
// src/installed-package-versions.ts
|
|
5429
5669
|
import { readFile } from "node:fs/promises";
|
|
5430
|
-
import { homedir as
|
|
5431
|
-
import
|
|
5670
|
+
import { homedir as homedir6 } from "node:os";
|
|
5671
|
+
import path31 from "node:path";
|
|
5432
5672
|
var MANAGED_PACKAGES = [
|
|
5433
5673
|
"@kynver-app/runtime",
|
|
5434
5674
|
"@kynver-app/openclaw-agent-os",
|
|
@@ -5442,13 +5682,13 @@ function unique(values) {
|
|
|
5442
5682
|
return [...new Set(values.filter((value) => Boolean(value)))];
|
|
5443
5683
|
}
|
|
5444
5684
|
function moduleRoots() {
|
|
5445
|
-
const home =
|
|
5446
|
-
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ??
|
|
5447
|
-
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ?
|
|
5685
|
+
const home = homedir6();
|
|
5686
|
+
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path31.join(home, ".openclaw", "npm");
|
|
5687
|
+
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"));
|
|
5448
5688
|
return unique([
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot :
|
|
5689
|
+
path31.join(openClawPrefix, "lib", "node_modules"),
|
|
5690
|
+
path31.join(openClawPrefix, "node_modules"),
|
|
5691
|
+
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path31.join(npmGlobalRoot, "lib", "node_modules")
|
|
5452
5692
|
]);
|
|
5453
5693
|
}
|
|
5454
5694
|
async function readVersion(packageJsonPath) {
|
|
@@ -5464,7 +5704,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
|
|
|
5464
5704
|
const out = {};
|
|
5465
5705
|
for (const packageName of MANAGED_PACKAGES) {
|
|
5466
5706
|
for (const root of roots) {
|
|
5467
|
-
const packageJsonPath =
|
|
5707
|
+
const packageJsonPath = path31.join(root, packageName, "package.json");
|
|
5468
5708
|
const version = await readVersion(packageJsonPath);
|
|
5469
5709
|
if (!version) continue;
|
|
5470
5710
|
out[packageName] = { version, observedAt, path: packageJsonPath };
|
|
@@ -5480,7 +5720,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
5480
5720
|
const outcomes = [];
|
|
5481
5721
|
for (const name of Object.keys(run.workers || {})) {
|
|
5482
5722
|
const worker = readJson(
|
|
5483
|
-
|
|
5723
|
+
path32.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
5484
5724
|
void 0
|
|
5485
5725
|
);
|
|
5486
5726
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -5622,7 +5862,7 @@ async function runDaemon(args) {
|
|
|
5622
5862
|
}
|
|
5623
5863
|
|
|
5624
5864
|
// src/plan-progress.ts
|
|
5625
|
-
import
|
|
5865
|
+
import path33 from "node:path";
|
|
5626
5866
|
|
|
5627
5867
|
// src/bounded-build/constants.ts
|
|
5628
5868
|
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
@@ -5908,7 +6148,7 @@ async function emitPlanProgress(args) {
|
|
|
5908
6148
|
}
|
|
5909
6149
|
function verifyPlanLocal(args) {
|
|
5910
6150
|
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
5911
|
-
const cwd =
|
|
6151
|
+
const cwd = path33.resolve(worktree);
|
|
5912
6152
|
const summary = runHarnessVerifyCommands(cwd);
|
|
5913
6153
|
const emitJson = args.json === true || args.json === "true";
|
|
5914
6154
|
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
@@ -5957,9 +6197,9 @@ async function verifyPlan(args) {
|
|
|
5957
6197
|
}
|
|
5958
6198
|
|
|
5959
6199
|
// src/harness-verify-cli.ts
|
|
5960
|
-
import
|
|
6200
|
+
import path34 from "node:path";
|
|
5961
6201
|
function runHarnessVerifyCli(args) {
|
|
5962
|
-
const cwd =
|
|
6202
|
+
const cwd = path34.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
5963
6203
|
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
5964
6204
|
const commands = [];
|
|
5965
6205
|
const rawCmd = args.command;
|
|
@@ -6003,7 +6243,7 @@ function runHarnessVerifyCli(args) {
|
|
|
6003
6243
|
}
|
|
6004
6244
|
|
|
6005
6245
|
// src/plan-persist-cli.ts
|
|
6006
|
-
import { readFileSync as
|
|
6246
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
6007
6247
|
var OPERATIONS = ["create", "add_version", "update_metadata"];
|
|
6008
6248
|
var FAILURE_KINDS = [
|
|
6009
6249
|
"approval_guard",
|
|
@@ -6015,7 +6255,7 @@ var FAILURE_KINDS = [
|
|
|
6015
6255
|
function readBodyArg(args) {
|
|
6016
6256
|
const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
|
|
6017
6257
|
if (bodyFile) {
|
|
6018
|
-
return { body:
|
|
6258
|
+
return { body: readFileSync9(bodyFile, "utf8"), bodyPathHint: bodyFile };
|
|
6019
6259
|
}
|
|
6020
6260
|
const inline = args.body ? String(args.body) : void 0;
|
|
6021
6261
|
if (inline) return { body: inline };
|
|
@@ -6085,12 +6325,14 @@ async function runPlanOutboxDrain(args) {
|
|
|
6085
6325
|
// src/cleanup-cli.ts
|
|
6086
6326
|
function runCleanupCli(args) {
|
|
6087
6327
|
const execute = args.execute === true || args.execute === "true";
|
|
6328
|
+
const skipFinalize = args.skipFinalize === true || args.skipFinalize === "true";
|
|
6088
6329
|
const nodeModulesAgeMs = args.nodeModulesAgeMs ? Number(args.nodeModulesAgeMs) : DEFAULT_NODE_MODULES_AGE_MS;
|
|
6089
6330
|
const worktreesAgeMs = args.worktreesAgeMs ? Number(args.worktreesAgeMs) : 0;
|
|
6090
6331
|
const includeOrphans = args.includeOrphans === true || args.includeOrphans === "true";
|
|
6091
6332
|
const harnessRoot = args.harnessRoot ? String(args.harnessRoot) : void 0;
|
|
6092
6333
|
const summary = runHarnessCleanup({
|
|
6093
6334
|
execute,
|
|
6335
|
+
finalizeStaleRuns: !skipFinalize,
|
|
6094
6336
|
nodeModulesAgeMs: Number.isFinite(nodeModulesAgeMs) ? nodeModulesAgeMs : DEFAULT_NODE_MODULES_AGE_MS,
|
|
6095
6337
|
worktreesAgeMs: Number.isFinite(worktreesAgeMs) ? worktreesAgeMs : 0,
|
|
6096
6338
|
includeOrphans,
|
|
@@ -6103,7 +6345,7 @@ function runCleanupCli(args) {
|
|
|
6103
6345
|
}
|
|
6104
6346
|
|
|
6105
6347
|
// src/monitor/monitor.service.ts
|
|
6106
|
-
import
|
|
6348
|
+
import path36 from "node:path";
|
|
6107
6349
|
|
|
6108
6350
|
// src/monitor/monitor.classify.ts
|
|
6109
6351
|
function expectedLeaseOwner(runId) {
|
|
@@ -6159,11 +6401,11 @@ function classifyWorkerHealth(input) {
|
|
|
6159
6401
|
}
|
|
6160
6402
|
|
|
6161
6403
|
// src/monitor/monitor.store.ts
|
|
6162
|
-
import { existsSync as
|
|
6163
|
-
import
|
|
6404
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
|
|
6405
|
+
import path35 from "node:path";
|
|
6164
6406
|
function monitorsDir() {
|
|
6165
6407
|
const { harnessRoot } = getHarnessPaths();
|
|
6166
|
-
const dir =
|
|
6408
|
+
const dir = path35.join(harnessRoot, "monitors");
|
|
6167
6409
|
mkdirSync6(dir, { recursive: true });
|
|
6168
6410
|
return dir;
|
|
6169
6411
|
}
|
|
@@ -6171,7 +6413,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
6171
6413
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
6172
6414
|
}
|
|
6173
6415
|
function monitorPath(monitorId) {
|
|
6174
|
-
return
|
|
6416
|
+
return path35.join(monitorsDir(), `${monitorId}.json`);
|
|
6175
6417
|
}
|
|
6176
6418
|
function loadMonitorSession(monitorId) {
|
|
6177
6419
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -6181,18 +6423,18 @@ function saveMonitorSession(session) {
|
|
|
6181
6423
|
}
|
|
6182
6424
|
function deleteMonitorSession(monitorId) {
|
|
6183
6425
|
const file = monitorPath(monitorId);
|
|
6184
|
-
if (!
|
|
6426
|
+
if (!existsSync19(file)) return false;
|
|
6185
6427
|
unlinkSync2(file);
|
|
6186
6428
|
return true;
|
|
6187
6429
|
}
|
|
6188
6430
|
function listMonitorSessions() {
|
|
6189
6431
|
const dir = monitorsDir();
|
|
6190
|
-
if (!
|
|
6432
|
+
if (!existsSync19(dir)) return [];
|
|
6191
6433
|
const entries = [];
|
|
6192
6434
|
for (const name of readdirSync7(dir)) {
|
|
6193
6435
|
if (!name.endsWith(".json")) continue;
|
|
6194
6436
|
const session = readJson(
|
|
6195
|
-
|
|
6437
|
+
path35.join(dir, name),
|
|
6196
6438
|
void 0
|
|
6197
6439
|
);
|
|
6198
6440
|
if (!session?.monitorId) continue;
|
|
@@ -6283,7 +6525,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
6283
6525
|
// src/monitor/monitor.service.ts
|
|
6284
6526
|
function workerRecord2(runId, name) {
|
|
6285
6527
|
return readJson(
|
|
6286
|
-
|
|
6528
|
+
path36.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
6287
6529
|
void 0
|
|
6288
6530
|
);
|
|
6289
6531
|
}
|
|
@@ -6485,18 +6727,18 @@ async function runMonitorLoop(args) {
|
|
|
6485
6727
|
|
|
6486
6728
|
// src/monitor/monitor-spawn.ts
|
|
6487
6729
|
import { spawn as spawn4 } from "node:child_process";
|
|
6488
|
-
import { closeSync as closeSync4, existsSync as
|
|
6489
|
-
import
|
|
6490
|
-
import { fileURLToPath as
|
|
6730
|
+
import { closeSync as closeSync4, existsSync as existsSync20, openSync as openSync4 } from "node:fs";
|
|
6731
|
+
import path37 from "node:path";
|
|
6732
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
6491
6733
|
function resolveDefaultCliPath2() {
|
|
6492
|
-
return
|
|
6734
|
+
return path37.join(fileURLToPath4(new URL(".", import.meta.url)), "..", "cli.js");
|
|
6493
6735
|
}
|
|
6494
6736
|
function spawnMonitorSidecar(opts) {
|
|
6495
6737
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
6496
|
-
if (!
|
|
6738
|
+
if (!existsSync20(cliPath)) return void 0;
|
|
6497
6739
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
6498
6740
|
const { harnessRoot } = getHarnessPaths();
|
|
6499
|
-
const logPath =
|
|
6741
|
+
const logPath = path37.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
6500
6742
|
let logFd;
|
|
6501
6743
|
try {
|
|
6502
6744
|
logFd = openSync4(logPath, "a");
|
|
@@ -6616,12 +6858,12 @@ async function monitorTickCli(args) {
|
|
|
6616
6858
|
}
|
|
6617
6859
|
|
|
6618
6860
|
// src/doctor/runtime-takeover.ts
|
|
6619
|
-
import
|
|
6861
|
+
import path39 from "node:path";
|
|
6620
6862
|
|
|
6621
6863
|
// src/doctor/runtime-takeover.probes.ts
|
|
6622
|
-
import { accessSync, constants, existsSync as
|
|
6623
|
-
import { homedir as
|
|
6624
|
-
import
|
|
6864
|
+
import { accessSync, constants, existsSync as existsSync21, readFileSync as readFileSync10 } from "node:fs";
|
|
6865
|
+
import { homedir as homedir7 } from "node:os";
|
|
6866
|
+
import path38 from "node:path";
|
|
6625
6867
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
6626
6868
|
function captureCommand(bin, args) {
|
|
6627
6869
|
try {
|
|
@@ -6650,7 +6892,7 @@ function tokenPrefix(token) {
|
|
|
6650
6892
|
return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
|
|
6651
6893
|
}
|
|
6652
6894
|
function isWritable(target) {
|
|
6653
|
-
if (!
|
|
6895
|
+
if (!existsSync21(target)) return false;
|
|
6654
6896
|
try {
|
|
6655
6897
|
accessSync(target, constants.W_OK);
|
|
6656
6898
|
return true;
|
|
@@ -6663,15 +6905,15 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
6663
6905
|
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
6664
6906
|
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
6665
6907
|
loadConfig: () => loadUserConfig(),
|
|
6666
|
-
configFilePath: () =>
|
|
6667
|
-
credentialsFilePath: () =>
|
|
6908
|
+
configFilePath: () => path38.join(homedir7(), ".kynver", "config.json"),
|
|
6909
|
+
credentialsFilePath: () => path38.join(homedir7(), ".kynver", "credentials"),
|
|
6668
6910
|
readCredentials: () => {
|
|
6669
|
-
const credPath =
|
|
6670
|
-
if (!
|
|
6911
|
+
const credPath = path38.join(homedir7(), ".kynver", "credentials");
|
|
6912
|
+
if (!existsSync21(credPath)) {
|
|
6671
6913
|
return { hasApiKey: false };
|
|
6672
6914
|
}
|
|
6673
6915
|
try {
|
|
6674
|
-
const parsed = JSON.parse(
|
|
6916
|
+
const parsed = JSON.parse(readFileSync10(credPath, "utf8"));
|
|
6675
6917
|
return {
|
|
6676
6918
|
hasApiKey: Boolean(parsed.apiKey?.trim()),
|
|
6677
6919
|
runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
|
|
@@ -6697,8 +6939,8 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
6697
6939
|
})()
|
|
6698
6940
|
}),
|
|
6699
6941
|
harnessRoot: () => resolveHarnessRoot(),
|
|
6700
|
-
legacyOpenclawHarnessRoot: () =>
|
|
6701
|
-
pathExists: (target) =>
|
|
6942
|
+
legacyOpenclawHarnessRoot: () => path38.join(homedir7(), ".openclaw", "harness"),
|
|
6943
|
+
pathExists: (target) => existsSync21(target),
|
|
6702
6944
|
pathWritable: (target) => isWritable(target),
|
|
6703
6945
|
vercelVersion: () => captureCommand("vercel", ["--version"]),
|
|
6704
6946
|
vercelWhoami: () => captureCommand("vercel", ["whoami"])
|
|
@@ -6713,8 +6955,36 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
6713
6955
|
const runnerQstash = env.kynverSchedulerProvider === "qstash";
|
|
6714
6956
|
const hostedDeployment = Boolean(env.qstashTokenPresent) || Boolean(env.kynverHostedDeployment);
|
|
6715
6957
|
const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
|
|
6716
|
-
const
|
|
6717
|
-
const
|
|
6958
|
+
const hostedSchedulerProcess = hostedDeployment && !daemonDispatchReady;
|
|
6959
|
+
const deploymentNeedsQstash = hostedSchedulerProcess && !env.qstashTokenPresent && env.kynverSchedulerProvider !== "qstash";
|
|
6960
|
+
const deploymentOpenclaw = hostedSchedulerProcess && env.kynverSchedulerProvider === "openclaw-cron";
|
|
6961
|
+
if (daemonDispatchReady && runnerOpenclaw) {
|
|
6962
|
+
return check({
|
|
6963
|
+
id: "hotspot_openclaw_scheduler",
|
|
6964
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6965
|
+
status: "warn",
|
|
6966
|
+
summary: "KYNVER_SCHEDULER_PROVIDER=openclaw-cron on this runner \u2014 dispatch is owned by kynver daemon; unset the OpenClaw override",
|
|
6967
|
+
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.",
|
|
6968
|
+
details: {
|
|
6969
|
+
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
6970
|
+
dispatchPath: "kynver-daemon-pipeline-tick",
|
|
6971
|
+
hostedDeployment
|
|
6972
|
+
}
|
|
6973
|
+
});
|
|
6974
|
+
}
|
|
6975
|
+
if (daemonDispatchReady) {
|
|
6976
|
+
return check({
|
|
6977
|
+
id: "hotspot_openclaw_scheduler",
|
|
6978
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6979
|
+
status: "pass",
|
|
6980
|
+
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",
|
|
6981
|
+
details: {
|
|
6982
|
+
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
6983
|
+
dispatchPath: "kynver-daemon-pipeline-tick",
|
|
6984
|
+
hostedDeployment
|
|
6985
|
+
}
|
|
6986
|
+
});
|
|
6987
|
+
}
|
|
6718
6988
|
if (runnerOpenclaw) {
|
|
6719
6989
|
return check({
|
|
6720
6990
|
id: "hotspot_openclaw_scheduler",
|
|
@@ -6730,7 +7000,7 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
6730
7000
|
id: "hotspot_openclaw_scheduler",
|
|
6731
7001
|
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6732
7002
|
status: "warn",
|
|
6733
|
-
summary: deploymentOpenclaw ? "Hosted deployment has KYNVER_SCHEDULER_PROVIDER=openclaw-cron \u2014 AgentOS scheduled ticks should use QStash" :
|
|
7003
|
+
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",
|
|
6734
7004
|
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.",
|
|
6735
7005
|
details: {
|
|
6736
7006
|
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
@@ -6739,15 +7009,15 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
6739
7009
|
}
|
|
6740
7010
|
});
|
|
6741
7011
|
}
|
|
6742
|
-
if (
|
|
7012
|
+
if (hostedDeployment && env.qstashTokenPresent && !env.kynverSchedulerProvider) {
|
|
6743
7013
|
return check({
|
|
6744
7014
|
id: "hotspot_openclaw_scheduler",
|
|
6745
7015
|
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6746
7016
|
status: "pass",
|
|
6747
|
-
summary:
|
|
7017
|
+
summary: "QSTASH_TOKEN present; hosted scheduler auto-selects qstash (explicit KYNVER_SCHEDULER_PROVIDER=qstash optional)",
|
|
6748
7018
|
details: {
|
|
6749
|
-
schedulerProvider:
|
|
6750
|
-
|
|
7019
|
+
schedulerProvider: null,
|
|
7020
|
+
qstashTokenPresent: true,
|
|
6751
7021
|
hostedDeployment
|
|
6752
7022
|
}
|
|
6753
7023
|
});
|
|
@@ -6841,8 +7111,14 @@ function assessUserConfig(probes) {
|
|
|
6841
7111
|
if (exists) {
|
|
6842
7112
|
const apiBaseUrl = config.apiBaseUrl?.trim();
|
|
6843
7113
|
const agentOsId = config.agentOsId?.trim();
|
|
6844
|
-
const
|
|
6845
|
-
const
|
|
7114
|
+
const resolvedDefaultRepo = resolveDefaultRepo({ config });
|
|
7115
|
+
const formatted = resolvedDefaultRepo ? formatResolvedDefaultRepo(resolvedDefaultRepo) : null;
|
|
7116
|
+
let defaultRepoRemediation;
|
|
7117
|
+
if (!resolvedDefaultRepo) {
|
|
7118
|
+
defaultRepoRemediation = "Run `kynver setup` from a Kynver checkout (auto-discovers repo) or `kynver setup --repo /path/to/Kynver`.";
|
|
7119
|
+
} else if (!resolvedDefaultRepo.persistedInConfig) {
|
|
7120
|
+
defaultRepoRemediation = "Run `kynver setup` (no --repo) to persist discovered defaultRepo in ~/.kynver/config.json.";
|
|
7121
|
+
}
|
|
6846
7122
|
checks.push(
|
|
6847
7123
|
check2({
|
|
6848
7124
|
id: "config_api_base_url",
|
|
@@ -6863,11 +7139,13 @@ function assessUserConfig(probes) {
|
|
|
6863
7139
|
check2({
|
|
6864
7140
|
id: "config_default_repo",
|
|
6865
7141
|
label: "Default repo path",
|
|
6866
|
-
status:
|
|
6867
|
-
summary:
|
|
6868
|
-
remediation:
|
|
7142
|
+
status: resolvedDefaultRepo ? "pass" : "warn",
|
|
7143
|
+
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)",
|
|
7144
|
+
remediation: defaultRepoRemediation,
|
|
6869
7145
|
details: {
|
|
6870
|
-
defaultRepo:
|
|
7146
|
+
defaultRepo: formatted?.defaultRepo ?? null,
|
|
7147
|
+
source: formatted?.source ?? null,
|
|
7148
|
+
persistedInConfig: formatted?.persistedInConfig ?? false,
|
|
6871
7149
|
harnessRoot: config.harnessRoot ? redactHomePath(config.harnessRoot) : null
|
|
6872
7150
|
}
|
|
6873
7151
|
})
|
|
@@ -6949,8 +7227,8 @@ function assessVercelCli(probes) {
|
|
|
6949
7227
|
}
|
|
6950
7228
|
function assessHarnessDirs(probes) {
|
|
6951
7229
|
const harnessRoot = probes.harnessRoot();
|
|
6952
|
-
const runsDir =
|
|
6953
|
-
const worktreesDir =
|
|
7230
|
+
const runsDir = path39.join(harnessRoot, "runs");
|
|
7231
|
+
const worktreesDir = path39.join(harnessRoot, "worktrees");
|
|
6954
7232
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
6955
7233
|
const displayRunsDir = redactHomePath(runsDir);
|
|
6956
7234
|
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
@@ -7153,9 +7431,9 @@ function usage(code = 0) {
|
|
|
7153
7431
|
"Usage:",
|
|
7154
7432
|
" kynver login --api-key KEY",
|
|
7155
7433
|
" kynver runner credential [--agent-os-id ID] [--base-url URL]",
|
|
7156
|
-
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--max-workers N] [--provider claude|cursor]",
|
|
7434
|
+
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--discover-repo] [--max-workers N] [--provider claude|cursor]",
|
|
7157
7435
|
" kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS]",
|
|
7158
|
-
" kynver run create --repo /path/repo [--name name] [--base origin/main]",
|
|
7436
|
+
" kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
|
|
7159
7437
|
" kynver run list",
|
|
7160
7438
|
" kynver run status --run RUN_ID",
|
|
7161
7439
|
" 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 /]",
|
|
@@ -7173,7 +7451,7 @@ function usage(code = 0) {
|
|
|
7173
7451
|
" kynver plan persist --operation create|add_version|update_metadata --title TITLE (--body-file PATH | --body TEXT) [--slug SLUG] [--plan PLAN_ID] [--summary TEXT] [--failure-kind approval_guard|auth|network|server|tool_interruption]",
|
|
7174
7452
|
" kynver plan outbox list",
|
|
7175
7453
|
" kynver plan outbox drain [--max N] [--id OUTBOX_ID]",
|
|
7176
|
-
" kynver cleanup [--execute] [--node-modules-age-ms MS] [--worktrees-age-ms MS] [--harness-root PATH] [--include-orphans]",
|
|
7454
|
+
" kynver cleanup [--execute] [--node-modules-age-ms MS] [--worktrees-age-ms MS] [--harness-root PATH] [--include-orphans] [--skip-finalize]",
|
|
7177
7455
|
" kynver monitor start --run RUN_ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS]",
|
|
7178
7456
|
" kynver monitor status [--run RUN_ID] [--name worker] [--tick]",
|
|
7179
7457
|
" kynver monitor stop --run RUN_ID [--name worker]",
|
|
@@ -7248,7 +7526,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
7248
7526
|
if (scope === "monitor" && action === "run-loop") return void await monitorRunLoopCli(args);
|
|
7249
7527
|
unknownCommand(scope, action);
|
|
7250
7528
|
}
|
|
7251
|
-
var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(
|
|
7529
|
+
var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(fileURLToPath5(import.meta.url));
|
|
7252
7530
|
if (isCliEntry) {
|
|
7253
7531
|
void main().catch((error) => {
|
|
7254
7532
|
console.error(error);
|
|
@@ -7571,6 +7849,8 @@ export {
|
|
|
7571
7849
|
computeWorkerStatus,
|
|
7572
7850
|
createRun,
|
|
7573
7851
|
deriveRunStatus,
|
|
7852
|
+
discoverDefaultRepo,
|
|
7853
|
+
discoverDefaultRepoCandidates,
|
|
7574
7854
|
dispatchRun,
|
|
7575
7855
|
drainPlanOutbox,
|
|
7576
7856
|
ensurePrReadyHandoff,
|
|
@@ -7578,13 +7858,16 @@ export {
|
|
|
7578
7858
|
extractPlanOutboxFromTask,
|
|
7579
7859
|
extractPrUrlFromText,
|
|
7580
7860
|
formatPlanOutboxHandoffBlock,
|
|
7861
|
+
formatResolvedDefaultRepo,
|
|
7581
7862
|
getHarnessPaths,
|
|
7582
7863
|
getMonitorStatus,
|
|
7864
|
+
gitRepoRoot,
|
|
7583
7865
|
hashPlanBody,
|
|
7584
7866
|
isDashboardVercelUrl,
|
|
7585
7867
|
isEngagementRequiredSkip,
|
|
7586
7868
|
isFinishedWorkerStatus,
|
|
7587
7869
|
isForbiddenWorkerEnvKey,
|
|
7870
|
+
isKynverMonorepoRoot,
|
|
7588
7871
|
isLandingBlockedWorkerStatus,
|
|
7589
7872
|
isSystemdRunAvailable,
|
|
7590
7873
|
isTerminalHeartbeatPhase,
|
|
@@ -7603,6 +7886,7 @@ export {
|
|
|
7603
7886
|
parseClaudeStream,
|
|
7604
7887
|
parseHarnessStream,
|
|
7605
7888
|
parseHeartbeat,
|
|
7889
|
+
persistDefaultRepo,
|
|
7606
7890
|
persistPlan,
|
|
7607
7891
|
pickVercelStatusContext,
|
|
7608
7892
|
postJson,
|
|
@@ -7611,9 +7895,11 @@ export {
|
|
|
7611
7895
|
reconcileRunsCli,
|
|
7612
7896
|
reconcileStaleWorkers,
|
|
7613
7897
|
redactHarness,
|
|
7898
|
+
remediateDefaultRepo,
|
|
7614
7899
|
resolveBaseUrl,
|
|
7615
7900
|
resolveCallbackSecret,
|
|
7616
7901
|
resolveCallbackSecretWithMint,
|
|
7902
|
+
resolveDefaultRepo,
|
|
7617
7903
|
resolveHarnessRoot,
|
|
7618
7904
|
resolveVercelInspectTarget,
|
|
7619
7905
|
runBoundedBuildCheck,
|