@kynver-app/runtime 0.1.49 → 0.1.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.js +653 -493
- 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/index.d.ts +2 -0
- package/dist/index.js +685 -488
- 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;
|
|
@@ -1051,239 +1300,64 @@ function classifyExitFailure(errorText) {
|
|
|
1051
1300
|
return { blocked: true, reason: `${pattern.label}: ${tidy2(text)}` };
|
|
1052
1301
|
}
|
|
1053
1302
|
}
|
|
1054
|
-
return null;
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
// src/exited-salvage.ts
|
|
1058
|
-
function trimOrNull(value) {
|
|
1059
|
-
if (typeof value !== "string") return null;
|
|
1060
|
-
const trimmed = value.trim();
|
|
1061
|
-
return trimmed.length ? trimmed : null;
|
|
1062
|
-
}
|
|
1063
|
-
function hasFinalResult(value) {
|
|
1064
|
-
if (value === void 0 || value === null) return false;
|
|
1065
|
-
if (typeof value === "string") return value.trim().length > 0;
|
|
1066
|
-
if (typeof value === "boolean") return value;
|
|
1067
|
-
if (Array.isArray(value)) return value.length > 0;
|
|
1068
|
-
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
1069
|
-
return true;
|
|
1070
|
-
}
|
|
1071
|
-
function committedHeadFromAncestry(ancestry) {
|
|
1072
|
-
if (!ancestry?.checked) return null;
|
|
1073
|
-
if (ancestry.headIsAncestorOfBase !== false) return null;
|
|
1074
|
-
return trimOrNull(ancestry.head);
|
|
1075
|
-
}
|
|
1076
|
-
function buildAttentionReason(kind, uncommittedCount, headCommit) {
|
|
1077
|
-
const parts = ["exited_with_changes_salvage"];
|
|
1078
|
-
if (kind === "uncommitted" || kind === "both") {
|
|
1079
|
-
parts.push(
|
|
1080
|
-
`${uncommittedCount} uncommitted change${uncommittedCount === 1 ? "" : "s"} with no final result`
|
|
1081
|
-
);
|
|
1082
|
-
}
|
|
1083
|
-
if ((kind === "committed_ahead" || kind === "both") && headCommit) {
|
|
1084
|
-
const sha = headCommit.length > 12 ? headCommit.slice(0, 12) : headCommit;
|
|
1085
|
-
parts.push(`commit ${sha} ahead of base with no final result`);
|
|
1086
|
-
}
|
|
1087
|
-
parts.push("review worktree \u2014 commit, open a PR, or run a salvage worker before discarding");
|
|
1088
|
-
return parts.join(": ");
|
|
1089
|
-
}
|
|
1090
|
-
function assessExitedWorkerSalvage(input) {
|
|
1091
|
-
if (input.alive || hasFinalResult(input.finalResult)) return null;
|
|
1092
|
-
const uncommittedCount = (input.changedFiles ?? []).filter((line) => line.trim()).length;
|
|
1093
|
-
const headCommit = trimOrNull(input.headCommit) ?? committedHeadFromAncestry(input.gitAncestry);
|
|
1094
|
-
const hasUncommitted = uncommittedCount > 0;
|
|
1095
|
-
const hasCommittedAhead = Boolean(headCommit);
|
|
1096
|
-
if (!hasUncommitted && !hasCommittedAhead) {
|
|
1097
|
-
return {
|
|
1098
|
-
kind: "none",
|
|
1099
|
-
salvageable: false,
|
|
1100
|
-
uncommittedCount: 0,
|
|
1101
|
-
headCommit: null,
|
|
1102
|
-
attentionReason: "process exited without a final result"
|
|
1103
|
-
};
|
|
1104
|
-
}
|
|
1105
|
-
const kind = hasUncommitted && hasCommittedAhead ? "both" : hasUncommitted ? "uncommitted" : "committed_ahead";
|
|
1106
|
-
return {
|
|
1107
|
-
kind,
|
|
1108
|
-
salvageable: true,
|
|
1109
|
-
uncommittedCount,
|
|
1110
|
-
headCommit,
|
|
1111
|
-
attentionReason: buildAttentionReason(kind, uncommittedCount, headCommit)
|
|
1112
|
-
};
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
// src/git.ts
|
|
1116
|
-
import { spawnSync } from "node:child_process";
|
|
1117
|
-
|
|
1118
|
-
// src/worker-env.ts
|
|
1119
|
-
var FORBIDDEN_WORKER_ENV_KEYS = [
|
|
1120
|
-
"ANTHROPIC_API_KEY",
|
|
1121
|
-
"ANALYST_API_KEY",
|
|
1122
|
-
"RECRUITER_API_KEY",
|
|
1123
|
-
"AUTH_SECRET",
|
|
1124
|
-
"NEXTAUTH_SECRET",
|
|
1125
|
-
"DATABASE_URL",
|
|
1126
|
-
"PRODUCTION_DATABASE_URL",
|
|
1127
|
-
"REDIS_URL",
|
|
1128
|
-
"GOOGLE_CLIENT_SECRET",
|
|
1129
|
-
"GITHUB_CLIENT_SECRET",
|
|
1130
|
-
"KYNVER_API_KEY",
|
|
1131
|
-
"KYNVER_SERVICE_SECRET",
|
|
1132
|
-
"KYNVER_RUNTIME_SECRET",
|
|
1133
|
-
"OPENCLAW_CRON_SECRET",
|
|
1134
|
-
"QSTASH_TOKEN",
|
|
1135
|
-
"QSTASH_CURRENT_SIGNING_KEY",
|
|
1136
|
-
"QSTASH_NEXT_SIGNING_KEY",
|
|
1137
|
-
"TOOL_SECRETS_KEK",
|
|
1138
|
-
"TOOL_EXECUTOR_DISPATCH_SECRET",
|
|
1139
|
-
"CLOUDFLARE_API_TOKEN",
|
|
1140
|
-
"STRIPE_SECRET_KEY",
|
|
1141
|
-
"STRIPE_WEBHOOK_SECRET",
|
|
1142
|
-
"STRIPE_IDENTITY_WEBHOOK_SECRET",
|
|
1143
|
-
"VOYAGE_API_KEY",
|
|
1144
|
-
"PERPLEXITY_API_KEY",
|
|
1145
|
-
"FRED_API_KEY",
|
|
1146
|
-
"FMP_API_KEY",
|
|
1147
|
-
"CURSOR_API_KEY"
|
|
1148
|
-
];
|
|
1149
|
-
var FORBIDDEN_KEY_SET = new Set(FORBIDDEN_WORKER_ENV_KEYS);
|
|
1150
|
-
var FORBIDDEN_SUFFIXES = ["_SECRET", "_API_KEY"];
|
|
1151
|
-
function isForbiddenWorkerEnvKey(key) {
|
|
1152
|
-
if (FORBIDDEN_KEY_SET.has(key)) return true;
|
|
1153
|
-
return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));
|
|
1154
|
-
}
|
|
1155
|
-
function listForbiddenWorkerEnvKeys(env) {
|
|
1156
|
-
return Object.keys(env).filter(isForbiddenWorkerEnvKey).sort();
|
|
1157
|
-
}
|
|
1158
|
-
function scrubWorkerEnv(env) {
|
|
1159
|
-
const next = { ...env };
|
|
1160
|
-
for (const key of Object.keys(next)) {
|
|
1161
|
-
if (isForbiddenWorkerEnvKey(key)) delete next[key];
|
|
1162
|
-
}
|
|
1163
|
-
return next;
|
|
1164
|
-
}
|
|
1165
|
-
function auditWorkerEnv(env) {
|
|
1166
|
-
const forbiddenPresent = listForbiddenWorkerEnvKeys(env);
|
|
1167
|
-
return { forbiddenPresent, safe: forbiddenPresent.length === 0 };
|
|
1168
|
-
}
|
|
1169
|
-
function scrubClaudeEnv(env) {
|
|
1170
|
-
return scrubWorkerEnv(env);
|
|
1171
|
-
}
|
|
1172
|
-
|
|
1173
|
-
// src/git.ts
|
|
1174
|
-
function git(cwd, args, options = {}) {
|
|
1175
|
-
const res = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
1176
|
-
if (res.status !== 0 && !options.allowFailure) {
|
|
1177
|
-
const message = `git ${args.join(" ")} failed: ${res.stderr || res.stdout}`;
|
|
1178
|
-
if (options.throwError) throw new Error(message);
|
|
1179
|
-
fail(message);
|
|
1180
|
-
}
|
|
1181
|
-
return res.stdout || "";
|
|
1182
|
-
}
|
|
1183
|
-
function ensureGitRepo(repo) {
|
|
1184
|
-
git(repo, ["rev-parse", "--show-toplevel"]);
|
|
1185
|
-
}
|
|
1186
|
-
function gitStatusShort(worktreePath) {
|
|
1187
|
-
return git(worktreePath, ["status", "--short"], { allowFailure: true }).split("\n").map((line) => line.trim()).filter(Boolean);
|
|
1188
|
-
}
|
|
1189
|
-
function gitCapture(cwd, args) {
|
|
1190
|
-
try {
|
|
1191
|
-
const res = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
1192
|
-
return {
|
|
1193
|
-
status: res.status,
|
|
1194
|
-
stdout: res.stdout || "",
|
|
1195
|
-
stderr: res.stderr || "",
|
|
1196
|
-
error: res.error ? res.error.message : null
|
|
1197
|
-
};
|
|
1198
|
-
} catch (error) {
|
|
1199
|
-
return {
|
|
1200
|
-
status: null,
|
|
1201
|
-
stdout: "",
|
|
1202
|
-
stderr: "",
|
|
1203
|
-
error: error.message
|
|
1204
|
-
};
|
|
1205
|
-
}
|
|
1303
|
+
return null;
|
|
1206
1304
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
if (
|
|
1211
|
-
|
|
1305
|
+
|
|
1306
|
+
// src/exited-salvage.ts
|
|
1307
|
+
function trimOrNull(value) {
|
|
1308
|
+
if (typeof value !== "string") return null;
|
|
1309
|
+
const trimmed = value.trim();
|
|
1310
|
+
return trimmed.length ? trimmed : null;
|
|
1212
1311
|
}
|
|
1213
|
-
function
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
if (
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${baseLabel}`,
|
|
1233
|
-
head.stdout.trim()
|
|
1234
|
-
);
|
|
1235
|
-
}
|
|
1236
|
-
baseSha = baseHead.stdout.trim();
|
|
1312
|
+
function hasFinalResult(value) {
|
|
1313
|
+
if (value === void 0 || value === null) return false;
|
|
1314
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
1315
|
+
if (typeof value === "boolean") return value;
|
|
1316
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
1317
|
+
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
1318
|
+
return true;
|
|
1319
|
+
}
|
|
1320
|
+
function committedHeadFromAncestry(ancestry) {
|
|
1321
|
+
if (!ancestry?.checked) return null;
|
|
1322
|
+
if (ancestry.headIsAncestorOfBase !== false) return null;
|
|
1323
|
+
return trimOrNull(ancestry.head);
|
|
1324
|
+
}
|
|
1325
|
+
function buildAttentionReason(kind, uncommittedCount, headCommit) {
|
|
1326
|
+
const parts = ["exited_with_changes_salvage"];
|
|
1327
|
+
if (kind === "uncommitted" || kind === "both") {
|
|
1328
|
+
parts.push(
|
|
1329
|
+
`${uncommittedCount} uncommitted change${uncommittedCount === 1 ? "" : "s"} with no final result`
|
|
1330
|
+
);
|
|
1237
1331
|
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
checked: true,
|
|
1242
|
-
base: baseLabel,
|
|
1243
|
-
head: headSha,
|
|
1244
|
-
baseHead: baseSha,
|
|
1245
|
-
baseIsAncestorOfHead: true,
|
|
1246
|
-
headIsAncestorOfBase: true,
|
|
1247
|
-
relation: "synced"
|
|
1248
|
-
};
|
|
1332
|
+
if ((kind === "committed_ahead" || kind === "both") && headCommit) {
|
|
1333
|
+
const sha = headCommit.length > 12 ? headCommit.slice(0, 12) : headCommit;
|
|
1334
|
+
parts.push(`commit ${sha} ahead of base with no final result`);
|
|
1249
1335
|
}
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1336
|
+
parts.push("review worktree \u2014 commit, open a PR, or run a salvage worker before discarding");
|
|
1337
|
+
return parts.join(": ");
|
|
1338
|
+
}
|
|
1339
|
+
function assessExitedWorkerSalvage(input) {
|
|
1340
|
+
if (input.alive || hasFinalResult(input.finalResult)) return null;
|
|
1341
|
+
const uncommittedCount = (input.changedFiles ?? []).filter((line) => line.trim()).length;
|
|
1342
|
+
const headCommit = trimOrNull(input.headCommit) ?? committedHeadFromAncestry(input.gitAncestry);
|
|
1343
|
+
const hasUncommitted = uncommittedCount > 0;
|
|
1344
|
+
const hasCommittedAhead = Boolean(headCommit);
|
|
1345
|
+
if (!hasUncommitted && !hasCommittedAhead) {
|
|
1254
1346
|
return {
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
|
|
1261
|
-
relation: "unknown",
|
|
1262
|
-
...error ? { error } : {}
|
|
1347
|
+
kind: "none",
|
|
1348
|
+
salvageable: false,
|
|
1349
|
+
uncommittedCount: 0,
|
|
1350
|
+
headCommit: null,
|
|
1351
|
+
attentionReason: "process exited without a final result"
|
|
1263
1352
|
};
|
|
1264
1353
|
}
|
|
1265
|
-
const
|
|
1266
|
-
return {
|
|
1267
|
-
checked: true,
|
|
1268
|
-
base: baseLabel,
|
|
1269
|
-
head: headSha,
|
|
1270
|
-
baseHead: baseSha,
|
|
1271
|
-
baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
|
|
1272
|
-
headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
|
|
1273
|
-
relation,
|
|
1274
|
-
...error ? { error } : {}
|
|
1275
|
-
};
|
|
1276
|
-
}
|
|
1277
|
-
function unknownAncestry(base, error, head = null) {
|
|
1354
|
+
const kind = hasUncommitted && hasCommittedAhead ? "both" : hasUncommitted ? "uncommitted" : "committed_ahead";
|
|
1278
1355
|
return {
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
headIsAncestorOfBase: null,
|
|
1285
|
-
relation: "unknown",
|
|
1286
|
-
error
|
|
1356
|
+
kind,
|
|
1357
|
+
salvageable: true,
|
|
1358
|
+
uncommittedCount,
|
|
1359
|
+
headCommit,
|
|
1360
|
+
attentionReason: buildAttentionReason(kind, uncommittedCount, headCommit)
|
|
1287
1361
|
};
|
|
1288
1362
|
}
|
|
1289
1363
|
|
|
@@ -1645,7 +1719,7 @@ function countActiveWorkersForRun(run) {
|
|
|
1645
1719
|
let active = 0;
|
|
1646
1720
|
for (const name of Object.keys(run.workers || {})) {
|
|
1647
1721
|
const worker = readJson(
|
|
1648
|
-
|
|
1722
|
+
path7.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1649
1723
|
void 0
|
|
1650
1724
|
);
|
|
1651
1725
|
if (!worker || !isActiveHarnessWorker(worker)) continue;
|
|
@@ -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,10 +4926,10 @@ 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
|
|
4932
|
+
import path23 from "node:path";
|
|
4780
4933
|
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
|
|
4781
4934
|
function terminalStatusFor(run) {
|
|
4782
4935
|
const names = Object.keys(run.workers || {});
|
|
@@ -4787,7 +4940,7 @@ function terminalStatusFor(run) {
|
|
|
4787
4940
|
let anyLandingBlocked = false;
|
|
4788
4941
|
for (const name of names) {
|
|
4789
4942
|
const worker = readJson(
|
|
4790
|
-
|
|
4943
|
+
path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4791
4944
|
void 0
|
|
4792
4945
|
);
|
|
4793
4946
|
if (!worker) continue;
|
|
@@ -4839,7 +4992,7 @@ function reconcileStaleWorkers() {
|
|
|
4839
4992
|
const now = Date.now();
|
|
4840
4993
|
for (const run of listRunRecords()) {
|
|
4841
4994
|
for (const name of Object.keys(run.workers || {})) {
|
|
4842
|
-
const workerPath =
|
|
4995
|
+
const workerPath = path24.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
4843
4996
|
const worker = readJson(workerPath, void 0);
|
|
4844
4997
|
if (!worker || worker.status !== "running") {
|
|
4845
4998
|
outcomes.push({
|
|
@@ -4933,7 +5086,7 @@ function reconcileRunsCli() {
|
|
|
4933
5086
|
}
|
|
4934
5087
|
|
|
4935
5088
|
// src/plan-progress-daemon-sync.ts
|
|
4936
|
-
import
|
|
5089
|
+
import path25 from "node:path";
|
|
4937
5090
|
|
|
4938
5091
|
// src/plan-progress-sync.ts
|
|
4939
5092
|
async function syncPlanProgress(args) {
|
|
@@ -4957,7 +5110,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
4957
5110
|
const outcomes = [];
|
|
4958
5111
|
for (const name of Object.keys(run.workers || {})) {
|
|
4959
5112
|
const worker = readJson(
|
|
4960
|
-
|
|
5113
|
+
path25.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4961
5114
|
void 0
|
|
4962
5115
|
);
|
|
4963
5116
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -5006,7 +5159,7 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
5006
5159
|
}
|
|
5007
5160
|
|
|
5008
5161
|
// src/cleanup.ts
|
|
5009
|
-
import
|
|
5162
|
+
import path30 from "node:path";
|
|
5010
5163
|
|
|
5011
5164
|
// src/cleanup-types.ts
|
|
5012
5165
|
var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
@@ -5077,14 +5230,14 @@ function skipNodeModulesRemoval(input) {
|
|
|
5077
5230
|
}
|
|
5078
5231
|
|
|
5079
5232
|
// src/cleanup-execute.ts
|
|
5080
|
-
import { existsSync as
|
|
5081
|
-
import
|
|
5233
|
+
import { existsSync as existsSync17, rmSync } from "node:fs";
|
|
5234
|
+
import path27 from "node:path";
|
|
5082
5235
|
|
|
5083
5236
|
// src/cleanup-dir-size.ts
|
|
5084
|
-
import { existsSync as
|
|
5085
|
-
import
|
|
5237
|
+
import { existsSync as existsSync16, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
|
|
5238
|
+
import path26 from "node:path";
|
|
5086
5239
|
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
5087
|
-
if (!
|
|
5240
|
+
if (!existsSync16(root)) return 0;
|
|
5088
5241
|
let total = 0;
|
|
5089
5242
|
let seen = 0;
|
|
5090
5243
|
const stack = [root];
|
|
@@ -5098,7 +5251,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
5098
5251
|
}
|
|
5099
5252
|
for (const name of entries) {
|
|
5100
5253
|
if (seen++ > maxEntries) return null;
|
|
5101
|
-
const full =
|
|
5254
|
+
const full = path26.join(current, name);
|
|
5102
5255
|
let st;
|
|
5103
5256
|
try {
|
|
5104
5257
|
st = statSync2(full);
|
|
@@ -5114,7 +5267,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
5114
5267
|
|
|
5115
5268
|
// src/cleanup-execute.ts
|
|
5116
5269
|
function removeNodeModules(candidate, execute) {
|
|
5117
|
-
if (!
|
|
5270
|
+
if (!existsSync17(candidate.path)) {
|
|
5118
5271
|
return {
|
|
5119
5272
|
...candidate,
|
|
5120
5273
|
executed: false,
|
|
@@ -5145,7 +5298,7 @@ function removeNodeModules(candidate, execute) {
|
|
|
5145
5298
|
}
|
|
5146
5299
|
}
|
|
5147
5300
|
function removeWorktree(candidate, execute) {
|
|
5148
|
-
if (!
|
|
5301
|
+
if (!existsSync17(candidate.path)) {
|
|
5149
5302
|
return {
|
|
5150
5303
|
...candidate,
|
|
5151
5304
|
executed: false,
|
|
@@ -5162,7 +5315,7 @@ function removeWorktree(candidate, execute) {
|
|
|
5162
5315
|
if (repo) {
|
|
5163
5316
|
git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
|
|
5164
5317
|
}
|
|
5165
|
-
if (
|
|
5318
|
+
if (existsSync17(candidate.path)) {
|
|
5166
5319
|
rmSync(candidate.path, { recursive: true, force: true });
|
|
5167
5320
|
}
|
|
5168
5321
|
return {
|
|
@@ -5182,20 +5335,20 @@ function removeWorktree(candidate, execute) {
|
|
|
5182
5335
|
}
|
|
5183
5336
|
}
|
|
5184
5337
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
5185
|
-
const resolved =
|
|
5186
|
-
const nm = resolved.endsWith(`${
|
|
5338
|
+
const resolved = path27.resolve(targetPath);
|
|
5339
|
+
const nm = resolved.endsWith(`${path27.sep}node_modules`) ? resolved : null;
|
|
5187
5340
|
if (!nm) return "path_outside_harness";
|
|
5188
|
-
const rel =
|
|
5189
|
-
if (rel.startsWith("..") ||
|
|
5190
|
-
const parts = rel.split(
|
|
5341
|
+
const rel = path27.relative(worktreesDir, nm);
|
|
5342
|
+
if (rel.startsWith("..") || path27.isAbsolute(rel)) return "path_outside_harness";
|
|
5343
|
+
const parts = rel.split(path27.sep);
|
|
5191
5344
|
if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
|
|
5192
|
-
if (!resolved.startsWith(
|
|
5345
|
+
if (!resolved.startsWith(path27.resolve(harnessRoot))) return "path_outside_harness";
|
|
5193
5346
|
return null;
|
|
5194
5347
|
}
|
|
5195
5348
|
|
|
5196
5349
|
// src/cleanup-scan.ts
|
|
5197
|
-
import { existsSync as
|
|
5198
|
-
import
|
|
5350
|
+
import { existsSync as existsSync18, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
5351
|
+
import path28 from "node:path";
|
|
5199
5352
|
function pathAgeMs(target, now) {
|
|
5200
5353
|
try {
|
|
5201
5354
|
const mtime = statSync3(target).mtimeMs;
|
|
@@ -5205,17 +5358,17 @@ function pathAgeMs(target, now) {
|
|
|
5205
5358
|
}
|
|
5206
5359
|
}
|
|
5207
5360
|
function isPathInside(child, parent) {
|
|
5208
|
-
const rel =
|
|
5209
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
5361
|
+
const rel = path28.relative(parent, child);
|
|
5362
|
+
return rel === "" || !rel.startsWith("..") && !path28.isAbsolute(rel);
|
|
5210
5363
|
}
|
|
5211
5364
|
function scanNodeModulesCandidates(opts) {
|
|
5212
5365
|
const candidates = [];
|
|
5213
5366
|
const seen = /* @__PURE__ */ new Set();
|
|
5214
5367
|
for (const entry of opts.index.values()) {
|
|
5215
5368
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
5216
|
-
const nm =
|
|
5217
|
-
if (!
|
|
5218
|
-
const resolved =
|
|
5369
|
+
const nm = path28.join(entry.worktreePath, "node_modules");
|
|
5370
|
+
if (!existsSync18(nm)) continue;
|
|
5371
|
+
const resolved = path28.resolve(nm);
|
|
5219
5372
|
if (seen.has(resolved)) continue;
|
|
5220
5373
|
seen.add(resolved);
|
|
5221
5374
|
candidates.push({
|
|
@@ -5228,16 +5381,16 @@ function scanNodeModulesCandidates(opts) {
|
|
|
5228
5381
|
ageMs: pathAgeMs(resolved, opts.now)
|
|
5229
5382
|
});
|
|
5230
5383
|
}
|
|
5231
|
-
if (!opts.includeOrphans || !
|
|
5384
|
+
if (!opts.includeOrphans || !existsSync18(opts.worktreesDir)) return candidates;
|
|
5232
5385
|
for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
|
|
5233
5386
|
if (!runEntry.isDirectory()) continue;
|
|
5234
|
-
const runPath =
|
|
5387
|
+
const runPath = path28.join(opts.worktreesDir, runEntry.name);
|
|
5235
5388
|
for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
|
|
5236
5389
|
if (!workerEntry.isDirectory()) continue;
|
|
5237
|
-
const worktreePath =
|
|
5238
|
-
const nm =
|
|
5239
|
-
if (!
|
|
5240
|
-
const resolved =
|
|
5390
|
+
const worktreePath = path28.join(runPath, workerEntry.name);
|
|
5391
|
+
const nm = path28.join(worktreePath, "node_modules");
|
|
5392
|
+
if (!existsSync18(nm)) continue;
|
|
5393
|
+
const resolved = path28.resolve(nm);
|
|
5241
5394
|
if (seen.has(resolved)) continue;
|
|
5242
5395
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
5243
5396
|
seen.add(resolved);
|
|
@@ -5260,7 +5413,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
5260
5413
|
for (const entry of opts.index.values()) {
|
|
5261
5414
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
5262
5415
|
const resolved = entry.worktreePath;
|
|
5263
|
-
if (!
|
|
5416
|
+
if (!existsSync18(resolved)) continue;
|
|
5264
5417
|
if (seen.has(resolved)) continue;
|
|
5265
5418
|
seen.add(resolved);
|
|
5266
5419
|
candidates.push({
|
|
@@ -5277,17 +5430,17 @@ function scanWorktreeCandidates(opts) {
|
|
|
5277
5430
|
}
|
|
5278
5431
|
|
|
5279
5432
|
// src/cleanup-worktree-index.ts
|
|
5280
|
-
import
|
|
5433
|
+
import path29 from "node:path";
|
|
5281
5434
|
function buildWorktreeIndex() {
|
|
5282
5435
|
const index = /* @__PURE__ */ new Map();
|
|
5283
5436
|
for (const run of listRunRecords()) {
|
|
5284
5437
|
for (const name of Object.keys(run.workers || {})) {
|
|
5285
|
-
const workerPath =
|
|
5438
|
+
const workerPath = path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
5286
5439
|
const worker = readJson(workerPath, void 0);
|
|
5287
5440
|
if (!worker?.worktreePath) continue;
|
|
5288
5441
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
5289
|
-
index.set(
|
|
5290
|
-
worktreePath:
|
|
5442
|
+
index.set(path29.resolve(worker.worktreePath), {
|
|
5443
|
+
worktreePath: path29.resolve(worker.worktreePath),
|
|
5291
5444
|
runId: run.id,
|
|
5292
5445
|
workerName: name,
|
|
5293
5446
|
run,
|
|
@@ -5302,7 +5455,7 @@ function buildWorktreeIndex() {
|
|
|
5302
5455
|
// src/cleanup.ts
|
|
5303
5456
|
function resolveOptions(options = {}) {
|
|
5304
5457
|
const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
|
|
5305
|
-
const { worktreesDir } = options.harnessRoot ? { worktreesDir:
|
|
5458
|
+
const { worktreesDir } = options.harnessRoot ? { worktreesDir: path30.join(harnessRoot, "worktrees") } : getHarnessPaths();
|
|
5306
5459
|
const execute = options.execute === true;
|
|
5307
5460
|
const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
|
|
5308
5461
|
const worktreesAgeMs = options.worktreesAgeMs ?? 0;
|
|
@@ -5346,7 +5499,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
5346
5499
|
actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
|
|
5347
5500
|
continue;
|
|
5348
5501
|
}
|
|
5349
|
-
const worktreePath =
|
|
5502
|
+
const worktreePath = path30.resolve(candidate.path, "..");
|
|
5350
5503
|
const indexed = index.get(worktreePath) ?? null;
|
|
5351
5504
|
const guardReason = skipNodeModulesRemoval({
|
|
5352
5505
|
indexed,
|
|
@@ -5362,7 +5515,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
5362
5515
|
actions.push(removeNodeModules(candidate, resolved.execute));
|
|
5363
5516
|
}
|
|
5364
5517
|
for (const candidate of scanWorktreeCandidates(scanOpts)) {
|
|
5365
|
-
const indexed = index.get(
|
|
5518
|
+
const indexed = index.get(path30.resolve(candidate.path)) ?? null;
|
|
5366
5519
|
const guardReason = skipWorktreeRemoval({
|
|
5367
5520
|
indexed,
|
|
5368
5521
|
includeOrphans: resolved.includeOrphans,
|
|
@@ -5427,8 +5580,8 @@ function isPipelineCleanupEnabled() {
|
|
|
5427
5580
|
|
|
5428
5581
|
// src/installed-package-versions.ts
|
|
5429
5582
|
import { readFile } from "node:fs/promises";
|
|
5430
|
-
import { homedir as
|
|
5431
|
-
import
|
|
5583
|
+
import { homedir as homedir6 } from "node:os";
|
|
5584
|
+
import path31 from "node:path";
|
|
5432
5585
|
var MANAGED_PACKAGES = [
|
|
5433
5586
|
"@kynver-app/runtime",
|
|
5434
5587
|
"@kynver-app/openclaw-agent-os",
|
|
@@ -5442,13 +5595,13 @@ function unique(values) {
|
|
|
5442
5595
|
return [...new Set(values.filter((value) => Boolean(value)))];
|
|
5443
5596
|
}
|
|
5444
5597
|
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) ?
|
|
5598
|
+
const home = homedir6();
|
|
5599
|
+
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path31.join(home, ".openclaw", "npm");
|
|
5600
|
+
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path31.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path31.join(home, ".npm-global", "lib", "node_modules"));
|
|
5448
5601
|
return unique([
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot :
|
|
5602
|
+
path31.join(openClawPrefix, "lib", "node_modules"),
|
|
5603
|
+
path31.join(openClawPrefix, "node_modules"),
|
|
5604
|
+
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path31.join(npmGlobalRoot, "lib", "node_modules")
|
|
5452
5605
|
]);
|
|
5453
5606
|
}
|
|
5454
5607
|
async function readVersion(packageJsonPath) {
|
|
@@ -5464,7 +5617,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
|
|
|
5464
5617
|
const out = {};
|
|
5465
5618
|
for (const packageName of MANAGED_PACKAGES) {
|
|
5466
5619
|
for (const root of roots) {
|
|
5467
|
-
const packageJsonPath =
|
|
5620
|
+
const packageJsonPath = path31.join(root, packageName, "package.json");
|
|
5468
5621
|
const version = await readVersion(packageJsonPath);
|
|
5469
5622
|
if (!version) continue;
|
|
5470
5623
|
out[packageName] = { version, observedAt, path: packageJsonPath };
|
|
@@ -5480,7 +5633,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
5480
5633
|
const outcomes = [];
|
|
5481
5634
|
for (const name of Object.keys(run.workers || {})) {
|
|
5482
5635
|
const worker = readJson(
|
|
5483
|
-
|
|
5636
|
+
path32.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
5484
5637
|
void 0
|
|
5485
5638
|
);
|
|
5486
5639
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -5622,7 +5775,7 @@ async function runDaemon(args) {
|
|
|
5622
5775
|
}
|
|
5623
5776
|
|
|
5624
5777
|
// src/plan-progress.ts
|
|
5625
|
-
import
|
|
5778
|
+
import path33 from "node:path";
|
|
5626
5779
|
|
|
5627
5780
|
// src/bounded-build/constants.ts
|
|
5628
5781
|
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
@@ -5908,7 +6061,7 @@ async function emitPlanProgress(args) {
|
|
|
5908
6061
|
}
|
|
5909
6062
|
function verifyPlanLocal(args) {
|
|
5910
6063
|
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
5911
|
-
const cwd =
|
|
6064
|
+
const cwd = path33.resolve(worktree);
|
|
5912
6065
|
const summary = runHarnessVerifyCommands(cwd);
|
|
5913
6066
|
const emitJson = args.json === true || args.json === "true";
|
|
5914
6067
|
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
@@ -5957,9 +6110,9 @@ async function verifyPlan(args) {
|
|
|
5957
6110
|
}
|
|
5958
6111
|
|
|
5959
6112
|
// src/harness-verify-cli.ts
|
|
5960
|
-
import
|
|
6113
|
+
import path34 from "node:path";
|
|
5961
6114
|
function runHarnessVerifyCli(args) {
|
|
5962
|
-
const cwd =
|
|
6115
|
+
const cwd = path34.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
5963
6116
|
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
5964
6117
|
const commands = [];
|
|
5965
6118
|
const rawCmd = args.command;
|
|
@@ -6003,7 +6156,7 @@ function runHarnessVerifyCli(args) {
|
|
|
6003
6156
|
}
|
|
6004
6157
|
|
|
6005
6158
|
// src/plan-persist-cli.ts
|
|
6006
|
-
import { readFileSync as
|
|
6159
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
6007
6160
|
var OPERATIONS = ["create", "add_version", "update_metadata"];
|
|
6008
6161
|
var FAILURE_KINDS = [
|
|
6009
6162
|
"approval_guard",
|
|
@@ -6015,7 +6168,7 @@ var FAILURE_KINDS = [
|
|
|
6015
6168
|
function readBodyArg(args) {
|
|
6016
6169
|
const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
|
|
6017
6170
|
if (bodyFile) {
|
|
6018
|
-
return { body:
|
|
6171
|
+
return { body: readFileSync9(bodyFile, "utf8"), bodyPathHint: bodyFile };
|
|
6019
6172
|
}
|
|
6020
6173
|
const inline = args.body ? String(args.body) : void 0;
|
|
6021
6174
|
if (inline) return { body: inline };
|
|
@@ -6103,7 +6256,7 @@ function runCleanupCli(args) {
|
|
|
6103
6256
|
}
|
|
6104
6257
|
|
|
6105
6258
|
// src/monitor/monitor.service.ts
|
|
6106
|
-
import
|
|
6259
|
+
import path36 from "node:path";
|
|
6107
6260
|
|
|
6108
6261
|
// src/monitor/monitor.classify.ts
|
|
6109
6262
|
function expectedLeaseOwner(runId) {
|
|
@@ -6159,11 +6312,11 @@ function classifyWorkerHealth(input) {
|
|
|
6159
6312
|
}
|
|
6160
6313
|
|
|
6161
6314
|
// src/monitor/monitor.store.ts
|
|
6162
|
-
import { existsSync as
|
|
6163
|
-
import
|
|
6315
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
|
|
6316
|
+
import path35 from "node:path";
|
|
6164
6317
|
function monitorsDir() {
|
|
6165
6318
|
const { harnessRoot } = getHarnessPaths();
|
|
6166
|
-
const dir =
|
|
6319
|
+
const dir = path35.join(harnessRoot, "monitors");
|
|
6167
6320
|
mkdirSync6(dir, { recursive: true });
|
|
6168
6321
|
return dir;
|
|
6169
6322
|
}
|
|
@@ -6171,7 +6324,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
6171
6324
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
6172
6325
|
}
|
|
6173
6326
|
function monitorPath(monitorId) {
|
|
6174
|
-
return
|
|
6327
|
+
return path35.join(monitorsDir(), `${monitorId}.json`);
|
|
6175
6328
|
}
|
|
6176
6329
|
function loadMonitorSession(monitorId) {
|
|
6177
6330
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -6181,18 +6334,18 @@ function saveMonitorSession(session) {
|
|
|
6181
6334
|
}
|
|
6182
6335
|
function deleteMonitorSession(monitorId) {
|
|
6183
6336
|
const file = monitorPath(monitorId);
|
|
6184
|
-
if (!
|
|
6337
|
+
if (!existsSync19(file)) return false;
|
|
6185
6338
|
unlinkSync2(file);
|
|
6186
6339
|
return true;
|
|
6187
6340
|
}
|
|
6188
6341
|
function listMonitorSessions() {
|
|
6189
6342
|
const dir = monitorsDir();
|
|
6190
|
-
if (!
|
|
6343
|
+
if (!existsSync19(dir)) return [];
|
|
6191
6344
|
const entries = [];
|
|
6192
6345
|
for (const name of readdirSync7(dir)) {
|
|
6193
6346
|
if (!name.endsWith(".json")) continue;
|
|
6194
6347
|
const session = readJson(
|
|
6195
|
-
|
|
6348
|
+
path35.join(dir, name),
|
|
6196
6349
|
void 0
|
|
6197
6350
|
);
|
|
6198
6351
|
if (!session?.monitorId) continue;
|
|
@@ -6283,7 +6436,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
6283
6436
|
// src/monitor/monitor.service.ts
|
|
6284
6437
|
function workerRecord2(runId, name) {
|
|
6285
6438
|
return readJson(
|
|
6286
|
-
|
|
6439
|
+
path36.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
6287
6440
|
void 0
|
|
6288
6441
|
);
|
|
6289
6442
|
}
|
|
@@ -6485,18 +6638,18 @@ async function runMonitorLoop(args) {
|
|
|
6485
6638
|
|
|
6486
6639
|
// src/monitor/monitor-spawn.ts
|
|
6487
6640
|
import { spawn as spawn4 } from "node:child_process";
|
|
6488
|
-
import { closeSync as closeSync4, existsSync as
|
|
6489
|
-
import
|
|
6490
|
-
import { fileURLToPath as
|
|
6641
|
+
import { closeSync as closeSync4, existsSync as existsSync20, openSync as openSync4 } from "node:fs";
|
|
6642
|
+
import path37 from "node:path";
|
|
6643
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
6491
6644
|
function resolveDefaultCliPath2() {
|
|
6492
|
-
return
|
|
6645
|
+
return path37.join(fileURLToPath4(new URL(".", import.meta.url)), "..", "cli.js");
|
|
6493
6646
|
}
|
|
6494
6647
|
function spawnMonitorSidecar(opts) {
|
|
6495
6648
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
6496
|
-
if (!
|
|
6649
|
+
if (!existsSync20(cliPath)) return void 0;
|
|
6497
6650
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
6498
6651
|
const { harnessRoot } = getHarnessPaths();
|
|
6499
|
-
const logPath =
|
|
6652
|
+
const logPath = path37.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
6500
6653
|
let logFd;
|
|
6501
6654
|
try {
|
|
6502
6655
|
logFd = openSync4(logPath, "a");
|
|
@@ -6616,12 +6769,12 @@ async function monitorTickCli(args) {
|
|
|
6616
6769
|
}
|
|
6617
6770
|
|
|
6618
6771
|
// src/doctor/runtime-takeover.ts
|
|
6619
|
-
import
|
|
6772
|
+
import path39 from "node:path";
|
|
6620
6773
|
|
|
6621
6774
|
// src/doctor/runtime-takeover.probes.ts
|
|
6622
|
-
import { accessSync, constants, existsSync as
|
|
6623
|
-
import { homedir as
|
|
6624
|
-
import
|
|
6775
|
+
import { accessSync, constants, existsSync as existsSync21, readFileSync as readFileSync10 } from "node:fs";
|
|
6776
|
+
import { homedir as homedir7 } from "node:os";
|
|
6777
|
+
import path38 from "node:path";
|
|
6625
6778
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
6626
6779
|
function captureCommand(bin, args) {
|
|
6627
6780
|
try {
|
|
@@ -6650,7 +6803,7 @@ function tokenPrefix(token) {
|
|
|
6650
6803
|
return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
|
|
6651
6804
|
}
|
|
6652
6805
|
function isWritable(target) {
|
|
6653
|
-
if (!
|
|
6806
|
+
if (!existsSync21(target)) return false;
|
|
6654
6807
|
try {
|
|
6655
6808
|
accessSync(target, constants.W_OK);
|
|
6656
6809
|
return true;
|
|
@@ -6663,15 +6816,15 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
6663
6816
|
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
6664
6817
|
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
6665
6818
|
loadConfig: () => loadUserConfig(),
|
|
6666
|
-
configFilePath: () =>
|
|
6667
|
-
credentialsFilePath: () =>
|
|
6819
|
+
configFilePath: () => path38.join(homedir7(), ".kynver", "config.json"),
|
|
6820
|
+
credentialsFilePath: () => path38.join(homedir7(), ".kynver", "credentials"),
|
|
6668
6821
|
readCredentials: () => {
|
|
6669
|
-
const credPath =
|
|
6670
|
-
if (!
|
|
6822
|
+
const credPath = path38.join(homedir7(), ".kynver", "credentials");
|
|
6823
|
+
if (!existsSync21(credPath)) {
|
|
6671
6824
|
return { hasApiKey: false };
|
|
6672
6825
|
}
|
|
6673
6826
|
try {
|
|
6674
|
-
const parsed = JSON.parse(
|
|
6827
|
+
const parsed = JSON.parse(readFileSync10(credPath, "utf8"));
|
|
6675
6828
|
return {
|
|
6676
6829
|
hasApiKey: Boolean(parsed.apiKey?.trim()),
|
|
6677
6830
|
runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
|
|
@@ -6697,8 +6850,8 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
6697
6850
|
})()
|
|
6698
6851
|
}),
|
|
6699
6852
|
harnessRoot: () => resolveHarnessRoot(),
|
|
6700
|
-
legacyOpenclawHarnessRoot: () =>
|
|
6701
|
-
pathExists: (target) =>
|
|
6853
|
+
legacyOpenclawHarnessRoot: () => path38.join(homedir7(), ".openclaw", "harness"),
|
|
6854
|
+
pathExists: (target) => existsSync21(target),
|
|
6702
6855
|
pathWritable: (target) => isWritable(target),
|
|
6703
6856
|
vercelVersion: () => captureCommand("vercel", ["--version"]),
|
|
6704
6857
|
vercelWhoami: () => captureCommand("vercel", ["whoami"])
|
|
@@ -6713,8 +6866,36 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
6713
6866
|
const runnerQstash = env.kynverSchedulerProvider === "qstash";
|
|
6714
6867
|
const hostedDeployment = Boolean(env.qstashTokenPresent) || Boolean(env.kynverHostedDeployment);
|
|
6715
6868
|
const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
|
|
6716
|
-
const
|
|
6717
|
-
const
|
|
6869
|
+
const hostedSchedulerProcess = hostedDeployment && !daemonDispatchReady;
|
|
6870
|
+
const deploymentNeedsQstash = hostedSchedulerProcess && !env.qstashTokenPresent && env.kynverSchedulerProvider !== "qstash";
|
|
6871
|
+
const deploymentOpenclaw = hostedSchedulerProcess && env.kynverSchedulerProvider === "openclaw-cron";
|
|
6872
|
+
if (daemonDispatchReady && runnerOpenclaw) {
|
|
6873
|
+
return check({
|
|
6874
|
+
id: "hotspot_openclaw_scheduler",
|
|
6875
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6876
|
+
status: "warn",
|
|
6877
|
+
summary: "KYNVER_SCHEDULER_PROVIDER=openclaw-cron on this runner \u2014 dispatch is owned by kynver daemon; unset the OpenClaw override",
|
|
6878
|
+
remediation: "Unset KYNVER_SCHEDULER_PROVIDER on user runners. Use `kynver daemon` (pipeline-tick \u2192 operator/tick). On the Kynver server set KYNVER_SCHEDULER_PROVIDER=qstash when QStash is configured.",
|
|
6879
|
+
details: {
|
|
6880
|
+
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
6881
|
+
dispatchPath: "kynver-daemon-pipeline-tick",
|
|
6882
|
+
hostedDeployment
|
|
6883
|
+
}
|
|
6884
|
+
});
|
|
6885
|
+
}
|
|
6886
|
+
if (daemonDispatchReady) {
|
|
6887
|
+
return check({
|
|
6888
|
+
id: "hotspot_openclaw_scheduler",
|
|
6889
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6890
|
+
status: "pass",
|
|
6891
|
+
summary: runnerQstash ? "Runner override qstash present; hosted dispatch still owned by kynver daemon pipeline-tick" : "Hosted dispatch owned by kynver daemon (pipeline-tick \u2192 operator/tick); no OpenClaw cron on runner",
|
|
6892
|
+
details: {
|
|
6893
|
+
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
6894
|
+
dispatchPath: "kynver-daemon-pipeline-tick",
|
|
6895
|
+
hostedDeployment
|
|
6896
|
+
}
|
|
6897
|
+
});
|
|
6898
|
+
}
|
|
6718
6899
|
if (runnerOpenclaw) {
|
|
6719
6900
|
return check({
|
|
6720
6901
|
id: "hotspot_openclaw_scheduler",
|
|
@@ -6730,7 +6911,7 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
6730
6911
|
id: "hotspot_openclaw_scheduler",
|
|
6731
6912
|
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6732
6913
|
status: "warn",
|
|
6733
|
-
summary: deploymentOpenclaw ? "Hosted deployment has KYNVER_SCHEDULER_PROVIDER=openclaw-cron \u2014 AgentOS scheduled ticks should use QStash" :
|
|
6914
|
+
summary: deploymentOpenclaw ? "Hosted deployment has KYNVER_SCHEDULER_PROVIDER=openclaw-cron \u2014 AgentOS scheduled ticks should use QStash" : env.kynverSchedulerProvider ? `Hosted deployment without QSTASH_TOKEN (KYNVER_SCHEDULER_PROVIDER=${env.kynverSchedulerProvider}) \u2014 scheduler may fall back to openclaw-cron` : "KYNVER_SCHEDULER_PROVIDER unset on hosted deployment; without QSTASH_TOKEN the server may fall back to openclaw-cron",
|
|
6734
6915
|
remediation: "Set QSTASH_TOKEN and KYNVER_SCHEDULER_PROVIDER=qstash on the Kynver server. User runners use `kynver daemon` for dispatch and should not set a scheduler provider.",
|
|
6735
6916
|
details: {
|
|
6736
6917
|
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
@@ -6739,15 +6920,15 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
6739
6920
|
}
|
|
6740
6921
|
});
|
|
6741
6922
|
}
|
|
6742
|
-
if (
|
|
6923
|
+
if (hostedDeployment && env.qstashTokenPresent && !env.kynverSchedulerProvider) {
|
|
6743
6924
|
return check({
|
|
6744
6925
|
id: "hotspot_openclaw_scheduler",
|
|
6745
6926
|
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6746
6927
|
status: "pass",
|
|
6747
|
-
summary:
|
|
6928
|
+
summary: "QSTASH_TOKEN present; hosted scheduler auto-selects qstash (explicit KYNVER_SCHEDULER_PROVIDER=qstash optional)",
|
|
6748
6929
|
details: {
|
|
6749
|
-
schedulerProvider:
|
|
6750
|
-
|
|
6930
|
+
schedulerProvider: null,
|
|
6931
|
+
qstashTokenPresent: true,
|
|
6751
6932
|
hostedDeployment
|
|
6752
6933
|
}
|
|
6753
6934
|
});
|
|
@@ -6841,8 +7022,14 @@ function assessUserConfig(probes) {
|
|
|
6841
7022
|
if (exists) {
|
|
6842
7023
|
const apiBaseUrl = config.apiBaseUrl?.trim();
|
|
6843
7024
|
const agentOsId = config.agentOsId?.trim();
|
|
6844
|
-
const
|
|
6845
|
-
const
|
|
7025
|
+
const resolvedDefaultRepo = resolveDefaultRepo({ config });
|
|
7026
|
+
const formatted = resolvedDefaultRepo ? formatResolvedDefaultRepo(resolvedDefaultRepo) : null;
|
|
7027
|
+
let defaultRepoRemediation;
|
|
7028
|
+
if (!resolvedDefaultRepo) {
|
|
7029
|
+
defaultRepoRemediation = "Run `kynver setup` from a Kynver checkout (auto-discovers repo) or `kynver setup --repo /path/to/Kynver`.";
|
|
7030
|
+
} else if (!resolvedDefaultRepo.persistedInConfig) {
|
|
7031
|
+
defaultRepoRemediation = "Run `kynver setup` (no --repo) to persist discovered defaultRepo in ~/.kynver/config.json.";
|
|
7032
|
+
}
|
|
6846
7033
|
checks.push(
|
|
6847
7034
|
check2({
|
|
6848
7035
|
id: "config_api_base_url",
|
|
@@ -6863,11 +7050,13 @@ function assessUserConfig(probes) {
|
|
|
6863
7050
|
check2({
|
|
6864
7051
|
id: "config_default_repo",
|
|
6865
7052
|
label: "Default repo path",
|
|
6866
|
-
status:
|
|
6867
|
-
summary:
|
|
6868
|
-
remediation:
|
|
7053
|
+
status: resolvedDefaultRepo ? "pass" : "warn",
|
|
7054
|
+
summary: resolvedDefaultRepo ? `${formatted.defaultRepo} (${resolvedDefaultRepo.source}${resolvedDefaultRepo.persistedInConfig ? "" : ", not persisted"})` : "Not set (pass --repo on `kynver run create` or run `kynver setup` to auto-discover)",
|
|
7055
|
+
remediation: defaultRepoRemediation,
|
|
6869
7056
|
details: {
|
|
6870
|
-
defaultRepo:
|
|
7057
|
+
defaultRepo: formatted?.defaultRepo ?? null,
|
|
7058
|
+
source: formatted?.source ?? null,
|
|
7059
|
+
persistedInConfig: formatted?.persistedInConfig ?? false,
|
|
6871
7060
|
harnessRoot: config.harnessRoot ? redactHomePath(config.harnessRoot) : null
|
|
6872
7061
|
}
|
|
6873
7062
|
})
|
|
@@ -6949,8 +7138,8 @@ function assessVercelCli(probes) {
|
|
|
6949
7138
|
}
|
|
6950
7139
|
function assessHarnessDirs(probes) {
|
|
6951
7140
|
const harnessRoot = probes.harnessRoot();
|
|
6952
|
-
const runsDir =
|
|
6953
|
-
const worktreesDir =
|
|
7141
|
+
const runsDir = path39.join(harnessRoot, "runs");
|
|
7142
|
+
const worktreesDir = path39.join(harnessRoot, "worktrees");
|
|
6954
7143
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
6955
7144
|
const displayRunsDir = redactHomePath(runsDir);
|
|
6956
7145
|
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
@@ -7153,9 +7342,9 @@ function usage(code = 0) {
|
|
|
7153
7342
|
"Usage:",
|
|
7154
7343
|
" kynver login --api-key KEY",
|
|
7155
7344
|
" 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]",
|
|
7345
|
+
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--discover-repo] [--max-workers N] [--provider claude|cursor]",
|
|
7157
7346
|
" 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]",
|
|
7347
|
+
" kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
|
|
7159
7348
|
" kynver run list",
|
|
7160
7349
|
" kynver run status --run RUN_ID",
|
|
7161
7350
|
" kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--target-task-id TASK_ID] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-8] [--disk-path /]",
|
|
@@ -7248,7 +7437,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
7248
7437
|
if (scope === "monitor" && action === "run-loop") return void await monitorRunLoopCli(args);
|
|
7249
7438
|
unknownCommand(scope, action);
|
|
7250
7439
|
}
|
|
7251
|
-
var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(
|
|
7440
|
+
var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(fileURLToPath5(import.meta.url));
|
|
7252
7441
|
if (isCliEntry) {
|
|
7253
7442
|
void main().catch((error) => {
|
|
7254
7443
|
console.error(error);
|
|
@@ -7571,6 +7760,8 @@ export {
|
|
|
7571
7760
|
computeWorkerStatus,
|
|
7572
7761
|
createRun,
|
|
7573
7762
|
deriveRunStatus,
|
|
7763
|
+
discoverDefaultRepo,
|
|
7764
|
+
discoverDefaultRepoCandidates,
|
|
7574
7765
|
dispatchRun,
|
|
7575
7766
|
drainPlanOutbox,
|
|
7576
7767
|
ensurePrReadyHandoff,
|
|
@@ -7578,13 +7769,16 @@ export {
|
|
|
7578
7769
|
extractPlanOutboxFromTask,
|
|
7579
7770
|
extractPrUrlFromText,
|
|
7580
7771
|
formatPlanOutboxHandoffBlock,
|
|
7772
|
+
formatResolvedDefaultRepo,
|
|
7581
7773
|
getHarnessPaths,
|
|
7582
7774
|
getMonitorStatus,
|
|
7775
|
+
gitRepoRoot,
|
|
7583
7776
|
hashPlanBody,
|
|
7584
7777
|
isDashboardVercelUrl,
|
|
7585
7778
|
isEngagementRequiredSkip,
|
|
7586
7779
|
isFinishedWorkerStatus,
|
|
7587
7780
|
isForbiddenWorkerEnvKey,
|
|
7781
|
+
isKynverMonorepoRoot,
|
|
7588
7782
|
isLandingBlockedWorkerStatus,
|
|
7589
7783
|
isSystemdRunAvailable,
|
|
7590
7784
|
isTerminalHeartbeatPhase,
|
|
@@ -7603,6 +7797,7 @@ export {
|
|
|
7603
7797
|
parseClaudeStream,
|
|
7604
7798
|
parseHarnessStream,
|
|
7605
7799
|
parseHeartbeat,
|
|
7800
|
+
persistDefaultRepo,
|
|
7606
7801
|
persistPlan,
|
|
7607
7802
|
pickVercelStatusContext,
|
|
7608
7803
|
postJson,
|
|
@@ -7611,9 +7806,11 @@ export {
|
|
|
7611
7806
|
reconcileRunsCli,
|
|
7612
7807
|
reconcileStaleWorkers,
|
|
7613
7808
|
redactHarness,
|
|
7809
|
+
remediateDefaultRepo,
|
|
7614
7810
|
resolveBaseUrl,
|
|
7615
7811
|
resolveCallbackSecret,
|
|
7616
7812
|
resolveCallbackSecretWithMint,
|
|
7813
|
+
resolveDefaultRepo,
|
|
7617
7814
|
resolveHarnessRoot,
|
|
7618
7815
|
resolveVercelInspectTarget,
|
|
7619
7816
|
runBoundedBuildCheck,
|