@kynver-app/runtime 0.1.48 → 0.1.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.js +778 -492
- package/dist/cli.js.map +4 -4
- package/dist/default-repo-discovery.d.ts +15 -0
- package/dist/default-repo.d.ts +30 -0
- package/dist/doctor/runtime-takeover-scheduler.d.ts +20 -0
- package/dist/doctor/runtime-takeover.probes.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +839 -516
- package/dist/index.js.map +4 -4
- package/dist/pr-handoff/pr-handoff-assess.d.ts +10 -1
- package/dist/pr-handoff/pr-handoff.types.d.ts +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,43 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { mkdirSync as mkdirSync7, realpathSync } from "node:fs";
|
|
5
|
-
import { fileURLToPath as
|
|
5
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
6
6
|
|
|
7
7
|
// src/config.ts
|
|
8
|
-
import { existsSync as
|
|
8
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9
|
+
import { homedir as homedir3 } from "node:os";
|
|
10
|
+
import path4 from "node:path";
|
|
11
|
+
|
|
12
|
+
// src/default-repo-discovery.ts
|
|
13
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
9
14
|
import { homedir as homedir2 } from "node:os";
|
|
10
15
|
import path3 from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
11
17
|
|
|
12
|
-
// src/
|
|
13
|
-
import {
|
|
14
|
-
import path from "node:path";
|
|
15
|
-
function expandHomePath(value) {
|
|
16
|
-
if (value === "~") return homedir();
|
|
17
|
-
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
18
|
-
return path.join(homedir(), value.slice(2));
|
|
19
|
-
}
|
|
20
|
-
return value;
|
|
21
|
-
}
|
|
22
|
-
function resolveUserPath(value) {
|
|
23
|
-
return path.resolve(expandHomePath(value));
|
|
24
|
-
}
|
|
25
|
-
function redactHomePath(value) {
|
|
26
|
-
const expanded = expandHomePath(value);
|
|
27
|
-
const resolved = path.resolve(expanded);
|
|
28
|
-
const home = path.resolve(homedir());
|
|
29
|
-
if (resolved === home) return "~";
|
|
30
|
-
if (resolved.startsWith(`${home}${path.sep}`)) {
|
|
31
|
-
return `~/${path.relative(home, resolved).split(path.sep).join("/")}`;
|
|
32
|
-
}
|
|
33
|
-
return resolved.replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^\/Users\/[^/]+(?=\/|$)/, "~");
|
|
34
|
-
}
|
|
35
|
-
function displayUserPath(value) {
|
|
36
|
-
return redactHomePath(value);
|
|
37
|
-
}
|
|
18
|
+
// src/git.ts
|
|
19
|
+
import { spawnSync } from "node:child_process";
|
|
38
20
|
|
|
39
21
|
// src/util.ts
|
|
40
22
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
41
|
-
import
|
|
23
|
+
import path from "node:path";
|
|
42
24
|
function fail(message) {
|
|
43
25
|
console.error(message);
|
|
44
26
|
process.exit(1);
|
|
@@ -67,7 +49,7 @@ function readJson(file, fallback) {
|
|
|
67
49
|
}
|
|
68
50
|
}
|
|
69
51
|
function writeJson(file, value) {
|
|
70
|
-
mkdirSync(
|
|
52
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
71
53
|
writeFileSync(file, `${JSON.stringify(value, null, 2)}
|
|
72
54
|
`);
|
|
73
55
|
}
|
|
@@ -103,7 +85,7 @@ function tailFile(file, lines) {
|
|
|
103
85
|
return data.split("\n").slice(-lines).join("\n");
|
|
104
86
|
}
|
|
105
87
|
function readMaybeFile(file) {
|
|
106
|
-
return file ? readFileSync(
|
|
88
|
+
return file ? readFileSync(path.resolve(file), "utf8") : "";
|
|
107
89
|
}
|
|
108
90
|
function listRunIds(runsDir) {
|
|
109
91
|
if (!existsSync(runsDir)) return [];
|
|
@@ -145,14 +127,270 @@ function secsAgo(ms) {
|
|
|
145
127
|
return Math.max(0, Math.round((Date.now() - ms) / 1e3));
|
|
146
128
|
}
|
|
147
129
|
|
|
130
|
+
// src/worker-env.ts
|
|
131
|
+
var FORBIDDEN_WORKER_ENV_KEYS = [
|
|
132
|
+
"ANTHROPIC_API_KEY",
|
|
133
|
+
"ANALYST_API_KEY",
|
|
134
|
+
"RECRUITER_API_KEY",
|
|
135
|
+
"AUTH_SECRET",
|
|
136
|
+
"NEXTAUTH_SECRET",
|
|
137
|
+
"DATABASE_URL",
|
|
138
|
+
"PRODUCTION_DATABASE_URL",
|
|
139
|
+
"REDIS_URL",
|
|
140
|
+
"GOOGLE_CLIENT_SECRET",
|
|
141
|
+
"GITHUB_CLIENT_SECRET",
|
|
142
|
+
"KYNVER_API_KEY",
|
|
143
|
+
"KYNVER_SERVICE_SECRET",
|
|
144
|
+
"KYNVER_RUNTIME_SECRET",
|
|
145
|
+
"OPENCLAW_CRON_SECRET",
|
|
146
|
+
"QSTASH_TOKEN",
|
|
147
|
+
"QSTASH_CURRENT_SIGNING_KEY",
|
|
148
|
+
"QSTASH_NEXT_SIGNING_KEY",
|
|
149
|
+
"TOOL_SECRETS_KEK",
|
|
150
|
+
"TOOL_EXECUTOR_DISPATCH_SECRET",
|
|
151
|
+
"CLOUDFLARE_API_TOKEN",
|
|
152
|
+
"STRIPE_SECRET_KEY",
|
|
153
|
+
"STRIPE_WEBHOOK_SECRET",
|
|
154
|
+
"STRIPE_IDENTITY_WEBHOOK_SECRET",
|
|
155
|
+
"VOYAGE_API_KEY",
|
|
156
|
+
"PERPLEXITY_API_KEY",
|
|
157
|
+
"FRED_API_KEY",
|
|
158
|
+
"FMP_API_KEY",
|
|
159
|
+
"CURSOR_API_KEY"
|
|
160
|
+
];
|
|
161
|
+
var FORBIDDEN_KEY_SET = new Set(FORBIDDEN_WORKER_ENV_KEYS);
|
|
162
|
+
var FORBIDDEN_SUFFIXES = ["_SECRET", "_API_KEY"];
|
|
163
|
+
function isForbiddenWorkerEnvKey(key) {
|
|
164
|
+
if (FORBIDDEN_KEY_SET.has(key)) return true;
|
|
165
|
+
return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));
|
|
166
|
+
}
|
|
167
|
+
function scrubWorkerEnv(env) {
|
|
168
|
+
const next = { ...env };
|
|
169
|
+
for (const key of Object.keys(next)) {
|
|
170
|
+
if (isForbiddenWorkerEnvKey(key)) delete next[key];
|
|
171
|
+
}
|
|
172
|
+
return next;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/git.ts
|
|
176
|
+
function git(cwd, args, options = {}) {
|
|
177
|
+
const res = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
178
|
+
if (res.status !== 0 && !options.allowFailure) {
|
|
179
|
+
const message = `git ${args.join(" ")} failed: ${res.stderr || res.stdout}`;
|
|
180
|
+
if (options.throwError) throw new Error(message);
|
|
181
|
+
fail(message);
|
|
182
|
+
}
|
|
183
|
+
return res.stdout || "";
|
|
184
|
+
}
|
|
185
|
+
function ensureGitRepo(repo) {
|
|
186
|
+
git(repo, ["rev-parse", "--show-toplevel"]);
|
|
187
|
+
}
|
|
188
|
+
function gitStatusShort(worktreePath) {
|
|
189
|
+
return git(worktreePath, ["status", "--short"], { allowFailure: true }).split("\n").map((line) => line.trim()).filter(Boolean);
|
|
190
|
+
}
|
|
191
|
+
function gitCapture(cwd, args) {
|
|
192
|
+
try {
|
|
193
|
+
const res = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
194
|
+
return {
|
|
195
|
+
status: res.status,
|
|
196
|
+
stdout: res.stdout || "",
|
|
197
|
+
stderr: res.stderr || "",
|
|
198
|
+
error: res.error ? res.error.message : null
|
|
199
|
+
};
|
|
200
|
+
} catch (error) {
|
|
201
|
+
return {
|
|
202
|
+
status: null,
|
|
203
|
+
stdout: "",
|
|
204
|
+
stderr: "",
|
|
205
|
+
error: error.message
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function gitIsAncestor(cwd, ancestor, descendant) {
|
|
210
|
+
const res = gitCapture(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]);
|
|
211
|
+
if (res.status === 0) return { isAncestor: true, error: null };
|
|
212
|
+
if (res.status === 1) return { isAncestor: false, error: null };
|
|
213
|
+
return { isAncestor: null, error: res.error || res.stderr || res.stdout || `git exited ${res.status}` };
|
|
214
|
+
}
|
|
215
|
+
function computeGitAncestry(worktreePath, baseOrOptions = "origin/main") {
|
|
216
|
+
const options = typeof baseOrOptions === "string" ? { base: baseOrOptions } : baseOrOptions;
|
|
217
|
+
const baseLabel = options.baseCommit?.trim() || options.base?.trim() || "origin/main";
|
|
218
|
+
const pinnedBaseCommit = options.baseCommit?.trim() || null;
|
|
219
|
+
if (!worktreePath) {
|
|
220
|
+
return unknownAncestry(baseLabel, "missing worktree path");
|
|
221
|
+
}
|
|
222
|
+
const head = gitCapture(worktreePath, ["rev-parse", "HEAD"]);
|
|
223
|
+
if (head.status !== 0) {
|
|
224
|
+
return unknownAncestry(baseLabel, head.error || head.stderr || head.stdout || "failed to resolve HEAD");
|
|
225
|
+
}
|
|
226
|
+
let baseSha;
|
|
227
|
+
if (pinnedBaseCommit) {
|
|
228
|
+
baseSha = pinnedBaseCommit;
|
|
229
|
+
} else {
|
|
230
|
+
const baseHead = gitCapture(worktreePath, ["rev-parse", baseLabel]);
|
|
231
|
+
if (baseHead.status !== 0) {
|
|
232
|
+
return unknownAncestry(
|
|
233
|
+
baseLabel,
|
|
234
|
+
baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${baseLabel}`,
|
|
235
|
+
head.stdout.trim()
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
baseSha = baseHead.stdout.trim();
|
|
239
|
+
}
|
|
240
|
+
const headSha = head.stdout.trim();
|
|
241
|
+
if (headSha === baseSha) {
|
|
242
|
+
return {
|
|
243
|
+
checked: true,
|
|
244
|
+
base: baseLabel,
|
|
245
|
+
head: headSha,
|
|
246
|
+
baseHead: baseSha,
|
|
247
|
+
baseIsAncestorOfHead: true,
|
|
248
|
+
headIsAncestorOfBase: true,
|
|
249
|
+
relation: "synced"
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const baseIsAncestorOfHead = gitIsAncestor(worktreePath, baseSha, headSha);
|
|
253
|
+
const headIsAncestorOfBase = gitIsAncestor(worktreePath, headSha, baseSha);
|
|
254
|
+
const error = baseIsAncestorOfHead.error || headIsAncestorOfBase.error || void 0;
|
|
255
|
+
if (baseIsAncestorOfHead.isAncestor == null || headIsAncestorOfBase.isAncestor == null) {
|
|
256
|
+
return {
|
|
257
|
+
checked: false,
|
|
258
|
+
base: baseLabel,
|
|
259
|
+
head: headSha,
|
|
260
|
+
baseHead: baseSha,
|
|
261
|
+
baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
|
|
262
|
+
headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
|
|
263
|
+
relation: "unknown",
|
|
264
|
+
...error ? { error } : {}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const relation = baseIsAncestorOfHead.isAncestor ? "ahead" : headIsAncestorOfBase.isAncestor ? "merged" : "diverged";
|
|
268
|
+
return {
|
|
269
|
+
checked: true,
|
|
270
|
+
base: baseLabel,
|
|
271
|
+
head: headSha,
|
|
272
|
+
baseHead: baseSha,
|
|
273
|
+
baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
|
|
274
|
+
headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
|
|
275
|
+
relation,
|
|
276
|
+
...error ? { error } : {}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function unknownAncestry(base, error, head = null) {
|
|
280
|
+
return {
|
|
281
|
+
checked: false,
|
|
282
|
+
base,
|
|
283
|
+
head,
|
|
284
|
+
baseHead: null,
|
|
285
|
+
baseIsAncestorOfHead: null,
|
|
286
|
+
headIsAncestorOfBase: null,
|
|
287
|
+
relation: "unknown",
|
|
288
|
+
error
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/path-values.ts
|
|
293
|
+
import { homedir } from "node:os";
|
|
294
|
+
import path2 from "node:path";
|
|
295
|
+
function expandHomePath(value) {
|
|
296
|
+
if (value === "~") return homedir();
|
|
297
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
298
|
+
return path2.join(homedir(), value.slice(2));
|
|
299
|
+
}
|
|
300
|
+
return value;
|
|
301
|
+
}
|
|
302
|
+
function resolveUserPath(value) {
|
|
303
|
+
return path2.resolve(expandHomePath(value));
|
|
304
|
+
}
|
|
305
|
+
function redactHomePath(value) {
|
|
306
|
+
const expanded = expandHomePath(value);
|
|
307
|
+
const resolved = path2.resolve(expanded);
|
|
308
|
+
const home = path2.resolve(homedir());
|
|
309
|
+
if (resolved === home) return "~";
|
|
310
|
+
if (resolved.startsWith(`${home}${path2.sep}`)) {
|
|
311
|
+
return `~/${path2.relative(home, resolved).split(path2.sep).join("/")}`;
|
|
312
|
+
}
|
|
313
|
+
return resolved.replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^\/Users\/[^/]+(?=\/|$)/, "~");
|
|
314
|
+
}
|
|
315
|
+
function displayUserPath(value) {
|
|
316
|
+
return redactHomePath(value);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// src/default-repo-discovery.ts
|
|
320
|
+
var WELL_KNOWN_REPO_DIRS = ["Kynver", "repos/Kynver", "code/Kynver", "projects/Kynver"];
|
|
321
|
+
function readPackageName(repoRoot) {
|
|
322
|
+
const pkgPath = path3.join(repoRoot, "package.json");
|
|
323
|
+
if (!existsSync2(pkgPath)) return null;
|
|
324
|
+
try {
|
|
325
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
326
|
+
return typeof pkg.name === "string" ? pkg.name.trim() : null;
|
|
327
|
+
} catch {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
function isKynverMonorepoRoot(repoRoot) {
|
|
332
|
+
return readPackageName(repoRoot) === "kynver";
|
|
333
|
+
}
|
|
334
|
+
function gitRepoRoot(startDir) {
|
|
335
|
+
const resolvedStart = path3.resolve(startDir);
|
|
336
|
+
if (!existsSync2(resolvedStart)) return null;
|
|
337
|
+
const probe = gitCapture(resolvedStart, ["rev-parse", "--show-toplevel"]);
|
|
338
|
+
if (probe.status !== 0) return null;
|
|
339
|
+
const root = probe.stdout.trim();
|
|
340
|
+
return root.length ? path3.resolve(root) : null;
|
|
341
|
+
}
|
|
342
|
+
function resolveRuntimePackageRoot(moduleUrl = import.meta.url) {
|
|
343
|
+
let dir = path3.dirname(fileURLToPath(moduleUrl));
|
|
344
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
345
|
+
const pkgPath = path3.join(dir, "package.json");
|
|
346
|
+
if (existsSync2(pkgPath)) {
|
|
347
|
+
try {
|
|
348
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
349
|
+
if (pkg.name === "@kynver-app/runtime") return dir;
|
|
350
|
+
} catch {
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const parent = path3.dirname(dir);
|
|
354
|
+
if (parent === dir) break;
|
|
355
|
+
dir = parent;
|
|
356
|
+
}
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
function pushCandidate(seen, out, repo, source) {
|
|
360
|
+
if (!repo) return;
|
|
361
|
+
const resolved = path3.resolve(repo);
|
|
362
|
+
if (seen.has(resolved)) return;
|
|
363
|
+
if (!isKynverMonorepoRoot(resolved)) return;
|
|
364
|
+
seen.add(resolved);
|
|
365
|
+
out.push({ repo: resolved, source });
|
|
366
|
+
}
|
|
367
|
+
function discoverDefaultRepoCandidates(opts) {
|
|
368
|
+
const cwd = opts?.cwd ?? process.cwd();
|
|
369
|
+
const seen = /* @__PURE__ */ new Set();
|
|
370
|
+
const candidates = [];
|
|
371
|
+
pushCandidate(seen, candidates, gitRepoRoot(cwd), "cwd_git");
|
|
372
|
+
const runtimePkgRoot = resolveRuntimePackageRoot(opts?.runtimeModuleUrl ?? import.meta.url);
|
|
373
|
+
if (runtimePkgRoot) {
|
|
374
|
+
pushCandidate(seen, candidates, gitRepoRoot(runtimePkgRoot), "runtime_checkout");
|
|
375
|
+
}
|
|
376
|
+
const home = homedir2();
|
|
377
|
+
for (const rel of WELL_KNOWN_REPO_DIRS) {
|
|
378
|
+
pushCandidate(seen, candidates, resolveUserPath(path3.join(home, rel)), "well_known_path");
|
|
379
|
+
}
|
|
380
|
+
return candidates;
|
|
381
|
+
}
|
|
382
|
+
function discoverDefaultRepo(opts) {
|
|
383
|
+
return discoverDefaultRepoCandidates(opts)[0] ?? null;
|
|
384
|
+
}
|
|
385
|
+
|
|
148
386
|
// src/config.ts
|
|
149
|
-
var CONFIG_DIR =
|
|
150
|
-
var CONFIG_FILE =
|
|
151
|
-
var CREDENTIALS_FILE =
|
|
387
|
+
var CONFIG_DIR = path4.join(homedir3(), ".kynver");
|
|
388
|
+
var CONFIG_FILE = path4.join(CONFIG_DIR, "config.json");
|
|
389
|
+
var CREDENTIALS_FILE = path4.join(CONFIG_DIR, "credentials");
|
|
152
390
|
function loadUserConfig() {
|
|
153
|
-
if (!
|
|
391
|
+
if (!existsSync3(CONFIG_FILE)) return {};
|
|
154
392
|
try {
|
|
155
|
-
return JSON.parse(
|
|
393
|
+
return JSON.parse(readFileSync3(CONFIG_FILE, "utf8"));
|
|
156
394
|
} catch {
|
|
157
395
|
return {};
|
|
158
396
|
}
|
|
@@ -176,7 +414,8 @@ function inferSetupFields(existing, args) {
|
|
|
176
414
|
const creds = loadCredentialsFile();
|
|
177
415
|
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();
|
|
178
416
|
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);
|
|
179
|
-
const
|
|
417
|
+
const explicitRepo = typeof args.repo === "string" ? args.repo : args.discoverRepo === true || args.discoverRepo === "true" ? discoverDefaultRepo()?.repo : void 0;
|
|
418
|
+
const defaultRepo = explicitRepo || existing.defaultRepo?.trim() || process.env.KYNVER_DEFAULT_REPO?.trim() || process.env.KYNVER_HARNESS_REPO?.trim() || discoverDefaultRepo()?.repo;
|
|
180
419
|
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();
|
|
181
420
|
return {
|
|
182
421
|
...apiBaseUrl ? { apiBaseUrl: trimTrailingSlash(apiBaseUrl) } : {},
|
|
@@ -187,9 +426,9 @@ function inferSetupFields(existing, args) {
|
|
|
187
426
|
};
|
|
188
427
|
}
|
|
189
428
|
function loadCredentialsFile() {
|
|
190
|
-
if (!
|
|
429
|
+
if (!existsSync3(CREDENTIALS_FILE)) return {};
|
|
191
430
|
try {
|
|
192
|
-
return JSON.parse(
|
|
431
|
+
return JSON.parse(readFileSync3(CREDENTIALS_FILE, "utf8"));
|
|
193
432
|
} catch {
|
|
194
433
|
return {};
|
|
195
434
|
}
|
|
@@ -409,7 +648,7 @@ async function runLogin(args) {
|
|
|
409
648
|
}
|
|
410
649
|
|
|
411
650
|
// src/dispatch.ts
|
|
412
|
-
import
|
|
651
|
+
import path18 from "node:path";
|
|
413
652
|
|
|
414
653
|
// src/callback-headers.ts
|
|
415
654
|
function buildHarnessCallbackHeaders(secret) {
|
|
@@ -471,12 +710,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
|
471
710
|
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
472
711
|
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
473
712
|
function observeRunnerDiskGate(input = {}) {
|
|
474
|
-
const
|
|
713
|
+
const path40 = input.diskPath?.trim() || "/";
|
|
475
714
|
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
476
715
|
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
477
716
|
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
478
717
|
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
479
|
-
const stats = statfsSync(
|
|
718
|
+
const stats = statfsSync(path40);
|
|
480
719
|
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
481
720
|
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
482
721
|
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
@@ -496,7 +735,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
496
735
|
}
|
|
497
736
|
return {
|
|
498
737
|
ok,
|
|
499
|
-
path:
|
|
738
|
+
path: path40,
|
|
500
739
|
freeBytes,
|
|
501
740
|
totalBytes,
|
|
502
741
|
usedPercent,
|
|
@@ -512,7 +751,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
512
751
|
import os2 from "node:os";
|
|
513
752
|
|
|
514
753
|
// src/bounded-build/meminfo.ts
|
|
515
|
-
import { readFileSync as
|
|
754
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
516
755
|
import os from "node:os";
|
|
517
756
|
function readMemAvailableBytes(meminfoText) {
|
|
518
757
|
if (meminfoText !== void 0) {
|
|
@@ -522,7 +761,7 @@ function readMemAvailableBytes(meminfoText) {
|
|
|
522
761
|
}
|
|
523
762
|
if (process.platform === "linux") {
|
|
524
763
|
try {
|
|
525
|
-
const meminfo =
|
|
764
|
+
const meminfo = readFileSync4("/proc/meminfo", "utf8");
|
|
526
765
|
const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m);
|
|
527
766
|
if (match) return Number(match[1]) * 1024;
|
|
528
767
|
} catch {
|
|
@@ -532,37 +771,37 @@ function readMemAvailableBytes(meminfoText) {
|
|
|
532
771
|
}
|
|
533
772
|
|
|
534
773
|
// src/resource-gate.ts
|
|
535
|
-
import
|
|
774
|
+
import path7 from "node:path";
|
|
536
775
|
|
|
537
776
|
// src/run-store.ts
|
|
538
|
-
import { existsSync as
|
|
539
|
-
import
|
|
777
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2 } from "node:fs";
|
|
778
|
+
import path6 from "node:path";
|
|
540
779
|
|
|
541
780
|
// src/paths.ts
|
|
542
|
-
import { existsSync as
|
|
543
|
-
import { homedir as
|
|
544
|
-
import
|
|
545
|
-
var LEGACY_ROOT =
|
|
781
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
782
|
+
import { homedir as homedir4 } from "node:os";
|
|
783
|
+
import path5 from "node:path";
|
|
784
|
+
var LEGACY_ROOT = path5.join(homedir4(), ".openclaw", "harness");
|
|
546
785
|
function resolveHarnessRoot() {
|
|
547
786
|
const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
|
|
548
787
|
if (env) return resolveUserPath(env);
|
|
549
788
|
const configured = loadUserConfig().harnessRoot?.trim();
|
|
550
789
|
if (configured) return resolveUserPath(configured);
|
|
551
|
-
const kynverRoot =
|
|
552
|
-
if (
|
|
553
|
-
if (
|
|
790
|
+
const kynverRoot = path5.join(homedir4(), ".kynver", "harness");
|
|
791
|
+
if (existsSync4(kynverRoot)) return kynverRoot;
|
|
792
|
+
if (existsSync4(LEGACY_ROOT)) return LEGACY_ROOT;
|
|
554
793
|
return kynverRoot;
|
|
555
794
|
}
|
|
556
795
|
function getHarnessPaths() {
|
|
557
796
|
const harnessRoot = resolveHarnessRoot();
|
|
558
797
|
return {
|
|
559
798
|
harnessRoot,
|
|
560
|
-
runsDir:
|
|
561
|
-
worktreesDir:
|
|
799
|
+
runsDir: path5.join(harnessRoot, "runs"),
|
|
800
|
+
worktreesDir: path5.join(harnessRoot, "worktrees")
|
|
562
801
|
};
|
|
563
802
|
}
|
|
564
803
|
function runDir(runsDir, id) {
|
|
565
|
-
return
|
|
804
|
+
return path5.join(runsDir, safeSlug(id));
|
|
566
805
|
}
|
|
567
806
|
|
|
568
807
|
// src/run-store.ts
|
|
@@ -571,16 +810,16 @@ function getPaths() {
|
|
|
571
810
|
}
|
|
572
811
|
function loadRun(id) {
|
|
573
812
|
const { runsDir } = getPaths();
|
|
574
|
-
return readJson(
|
|
813
|
+
return readJson(path6.join(runDir(runsDir, safeSlug(id)), "run.json"));
|
|
575
814
|
}
|
|
576
815
|
function listRunRecords() {
|
|
577
816
|
const { runsDir } = getPaths();
|
|
578
|
-
if (!
|
|
817
|
+
if (!existsSync5(runsDir)) return [];
|
|
579
818
|
const runs = [];
|
|
580
819
|
for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
|
|
581
820
|
if (!entry.isDirectory()) continue;
|
|
582
821
|
const run = readJson(
|
|
583
|
-
|
|
822
|
+
path6.join(runsDir, entry.name, "run.json"),
|
|
584
823
|
void 0
|
|
585
824
|
);
|
|
586
825
|
if (run?.id) runs.push(run);
|
|
@@ -590,16 +829,16 @@ function listRunRecords() {
|
|
|
590
829
|
function loadWorker(runId, name) {
|
|
591
830
|
const { runsDir } = getPaths();
|
|
592
831
|
return readJson(
|
|
593
|
-
|
|
832
|
+
path6.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
|
|
594
833
|
);
|
|
595
834
|
}
|
|
596
835
|
function saveRun(run) {
|
|
597
836
|
const { runsDir } = getPaths();
|
|
598
|
-
writeJson(
|
|
837
|
+
writeJson(path6.join(runDir(runsDir, run.id), "run.json"), run);
|
|
599
838
|
}
|
|
600
839
|
function saveWorker(runId, worker) {
|
|
601
840
|
const { runsDir } = getPaths();
|
|
602
|
-
writeJson(
|
|
841
|
+
writeJson(path6.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
|
|
603
842
|
}
|
|
604
843
|
function runDirectory(id) {
|
|
605
844
|
const { runsDir } = getPaths();
|
|
@@ -607,7 +846,7 @@ function runDirectory(id) {
|
|
|
607
846
|
}
|
|
608
847
|
|
|
609
848
|
// src/heartbeat.ts
|
|
610
|
-
import { existsSync as
|
|
849
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
|
|
611
850
|
var HEARTBEAT_FUTURE_SKEW_MS = 6e4;
|
|
612
851
|
function isTerminalHeartbeatPhase(phase) {
|
|
613
852
|
return phase === "complete";
|
|
@@ -626,10 +865,10 @@ function parseHeartbeat(file) {
|
|
|
626
865
|
heartbeatBlocker: null,
|
|
627
866
|
timestampAnomalies: []
|
|
628
867
|
};
|
|
629
|
-
if (!
|
|
868
|
+
if (!existsSync6(file)) return result;
|
|
630
869
|
const maxFutureMs = Date.now() + HEARTBEAT_FUTURE_SKEW_MS;
|
|
631
870
|
const clampedTo = new Date(maxFutureMs).toISOString();
|
|
632
|
-
const lines =
|
|
871
|
+
const lines = readFileSync5(file, "utf8").split("\n").filter(Boolean);
|
|
633
872
|
for (const line of lines) {
|
|
634
873
|
const entry = safeJson(line);
|
|
635
874
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
@@ -656,7 +895,7 @@ function parseHeartbeat(file) {
|
|
|
656
895
|
}
|
|
657
896
|
|
|
658
897
|
// src/stream.ts
|
|
659
|
-
import { existsSync as
|
|
898
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
|
|
660
899
|
|
|
661
900
|
// src/shell-command-outcome.ts
|
|
662
901
|
var NPM_AUDIT_RE = /\bnpm\s+audit\b/i;
|
|
@@ -860,8 +1099,8 @@ function parseHarnessStream(file) {
|
|
|
860
1099
|
error: null,
|
|
861
1100
|
lastShellOutcome: null
|
|
862
1101
|
};
|
|
863
|
-
if (!
|
|
864
|
-
const lines =
|
|
1102
|
+
if (!existsSync7(file)) return result;
|
|
1103
|
+
const lines = readFileSync6(file, "utf8").split("\n").filter(Boolean);
|
|
865
1104
|
for (const line of lines) {
|
|
866
1105
|
const event = safeJson(line);
|
|
867
1106
|
if (!event) continue;
|
|
@@ -1026,214 +1265,49 @@ function hasFinalResult(value) {
|
|
|
1026
1265
|
if (typeof value === "boolean") return value;
|
|
1027
1266
|
if (Array.isArray(value)) return value.length > 0;
|
|
1028
1267
|
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
1029
|
-
return true;
|
|
1030
|
-
}
|
|
1031
|
-
function committedHeadFromAncestry(ancestry) {
|
|
1032
|
-
if (!ancestry?.checked) return null;
|
|
1033
|
-
if (ancestry.headIsAncestorOfBase !== false) return null;
|
|
1034
|
-
return trimOrNull(ancestry.head);
|
|
1035
|
-
}
|
|
1036
|
-
function buildAttentionReason(kind, uncommittedCount, headCommit) {
|
|
1037
|
-
const parts = ["exited_with_changes_salvage"];
|
|
1038
|
-
if (kind === "uncommitted" || kind === "both") {
|
|
1039
|
-
parts.push(
|
|
1040
|
-
`${uncommittedCount} uncommitted change${uncommittedCount === 1 ? "" : "s"} with no final result`
|
|
1041
|
-
);
|
|
1042
|
-
}
|
|
1043
|
-
if ((kind === "committed_ahead" || kind === "both") && headCommit) {
|
|
1044
|
-
const sha = headCommit.length > 12 ? headCommit.slice(0, 12) : headCommit;
|
|
1045
|
-
parts.push(`commit ${sha} ahead of base with no final result`);
|
|
1046
|
-
}
|
|
1047
|
-
parts.push("review worktree \u2014 commit, open a PR, or run a salvage worker before discarding");
|
|
1048
|
-
return parts.join(": ");
|
|
1049
|
-
}
|
|
1050
|
-
function assessExitedWorkerSalvage(input) {
|
|
1051
|
-
if (input.alive || hasFinalResult(input.finalResult)) return null;
|
|
1052
|
-
const uncommittedCount = (input.changedFiles ?? []).filter((line) => line.trim()).length;
|
|
1053
|
-
const headCommit = trimOrNull(input.headCommit) ?? committedHeadFromAncestry(input.gitAncestry);
|
|
1054
|
-
const hasUncommitted = uncommittedCount > 0;
|
|
1055
|
-
const hasCommittedAhead = Boolean(headCommit);
|
|
1056
|
-
if (!hasUncommitted && !hasCommittedAhead) {
|
|
1057
|
-
return {
|
|
1058
|
-
kind: "none",
|
|
1059
|
-
salvageable: false,
|
|
1060
|
-
uncommittedCount: 0,
|
|
1061
|
-
headCommit: null,
|
|
1062
|
-
attentionReason: "process exited without a final result"
|
|
1063
|
-
};
|
|
1064
|
-
}
|
|
1065
|
-
const kind = hasUncommitted && hasCommittedAhead ? "both" : hasUncommitted ? "uncommitted" : "committed_ahead";
|
|
1066
|
-
return {
|
|
1067
|
-
kind,
|
|
1068
|
-
salvageable: true,
|
|
1069
|
-
uncommittedCount,
|
|
1070
|
-
headCommit,
|
|
1071
|
-
attentionReason: buildAttentionReason(kind, uncommittedCount, headCommit)
|
|
1072
|
-
};
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
// src/git.ts
|
|
1076
|
-
import { spawnSync } from "node:child_process";
|
|
1077
|
-
|
|
1078
|
-
// src/worker-env.ts
|
|
1079
|
-
var FORBIDDEN_WORKER_ENV_KEYS = [
|
|
1080
|
-
"ANTHROPIC_API_KEY",
|
|
1081
|
-
"ANALYST_API_KEY",
|
|
1082
|
-
"RECRUITER_API_KEY",
|
|
1083
|
-
"AUTH_SECRET",
|
|
1084
|
-
"NEXTAUTH_SECRET",
|
|
1085
|
-
"DATABASE_URL",
|
|
1086
|
-
"PRODUCTION_DATABASE_URL",
|
|
1087
|
-
"REDIS_URL",
|
|
1088
|
-
"GOOGLE_CLIENT_SECRET",
|
|
1089
|
-
"GITHUB_CLIENT_SECRET",
|
|
1090
|
-
"KYNVER_API_KEY",
|
|
1091
|
-
"KYNVER_SERVICE_SECRET",
|
|
1092
|
-
"KYNVER_RUNTIME_SECRET",
|
|
1093
|
-
"OPENCLAW_CRON_SECRET",
|
|
1094
|
-
"QSTASH_TOKEN",
|
|
1095
|
-
"QSTASH_CURRENT_SIGNING_KEY",
|
|
1096
|
-
"QSTASH_NEXT_SIGNING_KEY",
|
|
1097
|
-
"TOOL_SECRETS_KEK",
|
|
1098
|
-
"TOOL_EXECUTOR_DISPATCH_SECRET",
|
|
1099
|
-
"CLOUDFLARE_API_TOKEN",
|
|
1100
|
-
"STRIPE_SECRET_KEY",
|
|
1101
|
-
"STRIPE_WEBHOOK_SECRET",
|
|
1102
|
-
"STRIPE_IDENTITY_WEBHOOK_SECRET",
|
|
1103
|
-
"VOYAGE_API_KEY",
|
|
1104
|
-
"PERPLEXITY_API_KEY",
|
|
1105
|
-
"FRED_API_KEY",
|
|
1106
|
-
"FMP_API_KEY",
|
|
1107
|
-
"CURSOR_API_KEY"
|
|
1108
|
-
];
|
|
1109
|
-
var FORBIDDEN_KEY_SET = new Set(FORBIDDEN_WORKER_ENV_KEYS);
|
|
1110
|
-
var FORBIDDEN_SUFFIXES = ["_SECRET", "_API_KEY"];
|
|
1111
|
-
function isForbiddenWorkerEnvKey(key) {
|
|
1112
|
-
if (FORBIDDEN_KEY_SET.has(key)) return true;
|
|
1113
|
-
return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));
|
|
1114
|
-
}
|
|
1115
|
-
function scrubWorkerEnv(env) {
|
|
1116
|
-
const next = { ...env };
|
|
1117
|
-
for (const key of Object.keys(next)) {
|
|
1118
|
-
if (isForbiddenWorkerEnvKey(key)) delete next[key];
|
|
1119
|
-
}
|
|
1120
|
-
return next;
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
// src/git.ts
|
|
1124
|
-
function git(cwd, args, options = {}) {
|
|
1125
|
-
const res = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
1126
|
-
if (res.status !== 0 && !options.allowFailure) {
|
|
1127
|
-
const message = `git ${args.join(" ")} failed: ${res.stderr || res.stdout}`;
|
|
1128
|
-
if (options.throwError) throw new Error(message);
|
|
1129
|
-
fail(message);
|
|
1130
|
-
}
|
|
1131
|
-
return res.stdout || "";
|
|
1132
|
-
}
|
|
1133
|
-
function ensureGitRepo(repo) {
|
|
1134
|
-
git(repo, ["rev-parse", "--show-toplevel"]);
|
|
1135
|
-
}
|
|
1136
|
-
function gitStatusShort(worktreePath) {
|
|
1137
|
-
return git(worktreePath, ["status", "--short"], { allowFailure: true }).split("\n").map((line) => line.trim()).filter(Boolean);
|
|
1138
|
-
}
|
|
1139
|
-
function gitCapture(cwd, args) {
|
|
1140
|
-
try {
|
|
1141
|
-
const res = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
1142
|
-
return {
|
|
1143
|
-
status: res.status,
|
|
1144
|
-
stdout: res.stdout || "",
|
|
1145
|
-
stderr: res.stderr || "",
|
|
1146
|
-
error: res.error ? res.error.message : null
|
|
1147
|
-
};
|
|
1148
|
-
} catch (error) {
|
|
1149
|
-
return {
|
|
1150
|
-
status: null,
|
|
1151
|
-
stdout: "",
|
|
1152
|
-
stderr: "",
|
|
1153
|
-
error: error.message
|
|
1154
|
-
};
|
|
1155
|
-
}
|
|
1268
|
+
return true;
|
|
1156
1269
|
}
|
|
1157
|
-
function
|
|
1158
|
-
|
|
1159
|
-
if (
|
|
1160
|
-
|
|
1161
|
-
return { isAncestor: null, error: res.error || res.stderr || res.stdout || `git exited ${res.status}` };
|
|
1270
|
+
function committedHeadFromAncestry(ancestry) {
|
|
1271
|
+
if (!ancestry?.checked) return null;
|
|
1272
|
+
if (ancestry.headIsAncestorOfBase !== false) return null;
|
|
1273
|
+
return trimOrNull(ancestry.head);
|
|
1162
1274
|
}
|
|
1163
|
-
function
|
|
1164
|
-
const
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
}
|
|
1170
|
-
const head = gitCapture(worktreePath, ["rev-parse", "HEAD"]);
|
|
1171
|
-
if (head.status !== 0) {
|
|
1172
|
-
return unknownAncestry(baseLabel, head.error || head.stderr || head.stdout || "failed to resolve HEAD");
|
|
1173
|
-
}
|
|
1174
|
-
let baseSha;
|
|
1175
|
-
if (pinnedBaseCommit) {
|
|
1176
|
-
baseSha = pinnedBaseCommit;
|
|
1177
|
-
} else {
|
|
1178
|
-
const baseHead = gitCapture(worktreePath, ["rev-parse", baseLabel]);
|
|
1179
|
-
if (baseHead.status !== 0) {
|
|
1180
|
-
return unknownAncestry(
|
|
1181
|
-
baseLabel,
|
|
1182
|
-
baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${baseLabel}`,
|
|
1183
|
-
head.stdout.trim()
|
|
1184
|
-
);
|
|
1185
|
-
}
|
|
1186
|
-
baseSha = baseHead.stdout.trim();
|
|
1275
|
+
function buildAttentionReason(kind, uncommittedCount, headCommit) {
|
|
1276
|
+
const parts = ["exited_with_changes_salvage"];
|
|
1277
|
+
if (kind === "uncommitted" || kind === "both") {
|
|
1278
|
+
parts.push(
|
|
1279
|
+
`${uncommittedCount} uncommitted change${uncommittedCount === 1 ? "" : "s"} with no final result`
|
|
1280
|
+
);
|
|
1187
1281
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
checked: true,
|
|
1192
|
-
base: baseLabel,
|
|
1193
|
-
head: headSha,
|
|
1194
|
-
baseHead: baseSha,
|
|
1195
|
-
baseIsAncestorOfHead: true,
|
|
1196
|
-
headIsAncestorOfBase: true,
|
|
1197
|
-
relation: "synced"
|
|
1198
|
-
};
|
|
1282
|
+
if ((kind === "committed_ahead" || kind === "both") && headCommit) {
|
|
1283
|
+
const sha = headCommit.length > 12 ? headCommit.slice(0, 12) : headCommit;
|
|
1284
|
+
parts.push(`commit ${sha} ahead of base with no final result`);
|
|
1199
1285
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1286
|
+
parts.push("review worktree \u2014 commit, open a PR, or run a salvage worker before discarding");
|
|
1287
|
+
return parts.join(": ");
|
|
1288
|
+
}
|
|
1289
|
+
function assessExitedWorkerSalvage(input) {
|
|
1290
|
+
if (input.alive || hasFinalResult(input.finalResult)) return null;
|
|
1291
|
+
const uncommittedCount = (input.changedFiles ?? []).filter((line) => line.trim()).length;
|
|
1292
|
+
const headCommit = trimOrNull(input.headCommit) ?? committedHeadFromAncestry(input.gitAncestry);
|
|
1293
|
+
const hasUncommitted = uncommittedCount > 0;
|
|
1294
|
+
const hasCommittedAhead = Boolean(headCommit);
|
|
1295
|
+
if (!hasUncommitted && !hasCommittedAhead) {
|
|
1204
1296
|
return {
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
|
|
1211
|
-
relation: "unknown",
|
|
1212
|
-
...error ? { error } : {}
|
|
1297
|
+
kind: "none",
|
|
1298
|
+
salvageable: false,
|
|
1299
|
+
uncommittedCount: 0,
|
|
1300
|
+
headCommit: null,
|
|
1301
|
+
attentionReason: "process exited without a final result"
|
|
1213
1302
|
};
|
|
1214
1303
|
}
|
|
1215
|
-
const
|
|
1216
|
-
return {
|
|
1217
|
-
checked: true,
|
|
1218
|
-
base: baseLabel,
|
|
1219
|
-
head: headSha,
|
|
1220
|
-
baseHead: baseSha,
|
|
1221
|
-
baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
|
|
1222
|
-
headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
|
|
1223
|
-
relation,
|
|
1224
|
-
...error ? { error } : {}
|
|
1225
|
-
};
|
|
1226
|
-
}
|
|
1227
|
-
function unknownAncestry(base, error, head = null) {
|
|
1304
|
+
const kind = hasUncommitted && hasCommittedAhead ? "both" : hasUncommitted ? "uncommitted" : "committed_ahead";
|
|
1228
1305
|
return {
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
headIsAncestorOfBase: null,
|
|
1235
|
-
relation: "unknown",
|
|
1236
|
-
error
|
|
1306
|
+
kind,
|
|
1307
|
+
salvageable: true,
|
|
1308
|
+
uncommittedCount,
|
|
1309
|
+
headCommit,
|
|
1310
|
+
attentionReason: buildAttentionReason(kind, uncommittedCount, headCommit)
|
|
1237
1311
|
};
|
|
1238
1312
|
}
|
|
1239
1313
|
|
|
@@ -1595,7 +1669,7 @@ function countActiveWorkersForRun(run) {
|
|
|
1595
1669
|
let active = 0;
|
|
1596
1670
|
for (const name of Object.keys(run.workers || {})) {
|
|
1597
1671
|
const worker = readJson(
|
|
1598
|
-
|
|
1672
|
+
path7.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1599
1673
|
void 0
|
|
1600
1674
|
);
|
|
1601
1675
|
if (!worker || !isActiveHarnessWorker(worker)) continue;
|
|
@@ -1906,7 +1980,7 @@ function inferModelRoutingFromTask(task) {
|
|
|
1906
1980
|
rule: "lane:landing"
|
|
1907
1981
|
};
|
|
1908
1982
|
}
|
|
1909
|
-
if (ref.includes("review") || title
|
|
1983
|
+
if (ref.includes("review") || /^review[\s:]/.test(title) || roleLane.includes("review")) {
|
|
1910
1984
|
if (isOpusLane(ref, title) || roleLane === "deep_reviewer") {
|
|
1911
1985
|
return { model: "claude-opus-4-7", provider: "claude", rule: "lane:deep_review" };
|
|
1912
1986
|
}
|
|
@@ -1980,10 +2054,10 @@ function readHarnessRetryLimits() {
|
|
|
1980
2054
|
}
|
|
1981
2055
|
|
|
1982
2056
|
// src/lease-renewal.ts
|
|
1983
|
-
import
|
|
2057
|
+
import path8 from "node:path";
|
|
1984
2058
|
function workerRecord(runId, name) {
|
|
1985
2059
|
return readJson(
|
|
1986
|
-
|
|
2060
|
+
path8.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
1987
2061
|
void 0
|
|
1988
2062
|
);
|
|
1989
2063
|
}
|
|
@@ -2049,8 +2123,8 @@ function hasLiveWorkerForTask(runId, taskId) {
|
|
|
2049
2123
|
}
|
|
2050
2124
|
|
|
2051
2125
|
// src/supervisor.ts
|
|
2052
|
-
import { existsSync as
|
|
2053
|
-
import
|
|
2126
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync3 } from "node:fs";
|
|
2127
|
+
import path14 from "node:path";
|
|
2054
2128
|
|
|
2055
2129
|
// src/prompt.ts
|
|
2056
2130
|
function buildPrompt(input) {
|
|
@@ -2119,13 +2193,13 @@ function buildPrompt(input) {
|
|
|
2119
2193
|
}
|
|
2120
2194
|
|
|
2121
2195
|
// src/providers/cursor.ts
|
|
2122
|
-
import { closeSync as closeSync2, existsSync as
|
|
2196
|
+
import { closeSync as closeSync2, existsSync as existsSync9, openSync as openSync2 } from "node:fs";
|
|
2123
2197
|
import { spawn as spawn2 } from "node:child_process";
|
|
2124
|
-
import
|
|
2198
|
+
import path10 from "node:path";
|
|
2125
2199
|
|
|
2126
2200
|
// src/providers/cursor-windows.ts
|
|
2127
|
-
import { existsSync as
|
|
2128
|
-
import
|
|
2201
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3 } from "node:fs";
|
|
2202
|
+
import path9 from "node:path";
|
|
2129
2203
|
var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
|
|
2130
2204
|
function parseCursorVersionSortKey(versionName) {
|
|
2131
2205
|
const datePart = versionName.split("-")[0];
|
|
@@ -2136,8 +2210,8 @@ function parseCursorVersionSortKey(versionName) {
|
|
|
2136
2210
|
return Number(`${year}${month.padStart(2, "0")}${day.padStart(2, "0")}`);
|
|
2137
2211
|
}
|
|
2138
2212
|
function pickLatestCursorVersionDir(agentRoot) {
|
|
2139
|
-
const versionsRoot =
|
|
2140
|
-
if (!
|
|
2213
|
+
const versionsRoot = path9.join(agentRoot, "versions");
|
|
2214
|
+
if (!existsSync8(versionsRoot)) return null;
|
|
2141
2215
|
let bestDir = null;
|
|
2142
2216
|
let bestKey = -1;
|
|
2143
2217
|
for (const entry of readdirSync3(versionsRoot, { withFileTypes: true })) {
|
|
@@ -2145,22 +2219,22 @@ function pickLatestCursorVersionDir(agentRoot) {
|
|
|
2145
2219
|
const key = parseCursorVersionSortKey(entry.name);
|
|
2146
2220
|
if (key == null || key <= bestKey) continue;
|
|
2147
2221
|
bestKey = key;
|
|
2148
|
-
bestDir =
|
|
2222
|
+
bestDir = path9.join(versionsRoot, entry.name);
|
|
2149
2223
|
}
|
|
2150
2224
|
return bestDir;
|
|
2151
2225
|
}
|
|
2152
2226
|
function resolveWindowsCursorBundled(agentRoot) {
|
|
2153
|
-
const root = agentRoot?.trim() ||
|
|
2154
|
-
const directNode =
|
|
2155
|
-
const directIndex =
|
|
2156
|
-
if (
|
|
2227
|
+
const root = agentRoot?.trim() || path9.join(process.env.LOCALAPPDATA || "", "cursor-agent");
|
|
2228
|
+
const directNode = path9.join(root, "node.exe");
|
|
2229
|
+
const directIndex = path9.join(root, "index.js");
|
|
2230
|
+
if (existsSync8(directNode) && existsSync8(directIndex)) {
|
|
2157
2231
|
return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
|
|
2158
2232
|
}
|
|
2159
2233
|
const versionDir = pickLatestCursorVersionDir(root);
|
|
2160
2234
|
if (!versionDir) return null;
|
|
2161
|
-
const nodeExe =
|
|
2162
|
-
const indexJs =
|
|
2163
|
-
if (!
|
|
2235
|
+
const nodeExe = path9.join(versionDir, "node.exe");
|
|
2236
|
+
const indexJs = path9.join(versionDir, "index.js");
|
|
2237
|
+
if (!existsSync8(nodeExe) || !existsSync8(indexJs)) return null;
|
|
2164
2238
|
return { nodeExe, indexJs, versionDir };
|
|
2165
2239
|
}
|
|
2166
2240
|
|
|
@@ -2178,13 +2252,13 @@ function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
|
|
|
2178
2252
|
function resolveCursorSpawn(agentBin) {
|
|
2179
2253
|
if (process.platform === "win32") {
|
|
2180
2254
|
const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
|
|
2181
|
-
const isBundledNode = /node\.exe$/i.test(agentBin) &&
|
|
2255
|
+
const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync9(path10.join(path10.dirname(agentBin), "index.js"));
|
|
2182
2256
|
const isDefaultShim = agentBin === "agent";
|
|
2183
2257
|
if (isCursorWrapper || isBundledNode || isDefaultShim) {
|
|
2184
|
-
const bundled = isCursorWrapper ? resolveWindowsCursorBundled(
|
|
2258
|
+
const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path10.dirname(agentBin)) : isBundledNode ? {
|
|
2185
2259
|
nodeExe: agentBin,
|
|
2186
|
-
indexJs:
|
|
2187
|
-
versionDir:
|
|
2260
|
+
indexJs: path10.join(path10.dirname(agentBin), "index.js"),
|
|
2261
|
+
versionDir: path10.dirname(agentBin)
|
|
2188
2262
|
} : resolveWindowsCursorBundled();
|
|
2189
2263
|
if (bundled) {
|
|
2190
2264
|
return bundledSpawnTarget(bundled.nodeExe, bundled.indexJs, bundled.versionDir);
|
|
@@ -2204,8 +2278,8 @@ function resolveAgentBin() {
|
|
|
2204
2278
|
process.env.KYNVER_CURSOR_AGENT_ROOT?.trim() || void 0
|
|
2205
2279
|
);
|
|
2206
2280
|
if (bundled) return bundled.nodeExe;
|
|
2207
|
-
const localAgent =
|
|
2208
|
-
if (
|
|
2281
|
+
const localAgent = path10.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
|
|
2282
|
+
if (existsSync9(localAgent)) return localAgent;
|
|
2209
2283
|
}
|
|
2210
2284
|
return "agent";
|
|
2211
2285
|
}
|
|
@@ -2214,7 +2288,7 @@ function cursorWorkerEnv(agentBin, spawnTarget) {
|
|
|
2214
2288
|
...process.env,
|
|
2215
2289
|
CI: "1",
|
|
2216
2290
|
NO_COLOR: "1",
|
|
2217
|
-
...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS:
|
|
2291
|
+
...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path10.basename(agentBin) || "agent.cmd" } : {}
|
|
2218
2292
|
});
|
|
2219
2293
|
}
|
|
2220
2294
|
var cursorProvider = {
|
|
@@ -2288,9 +2362,9 @@ function resolveWorkerProvider(name) {
|
|
|
2288
2362
|
|
|
2289
2363
|
// src/auto-complete.ts
|
|
2290
2364
|
import { spawn as spawn3 } from "node:child_process";
|
|
2291
|
-
import { existsSync as
|
|
2292
|
-
import
|
|
2293
|
-
import { fileURLToPath } from "node:url";
|
|
2365
|
+
import { existsSync as existsSync10, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
|
|
2366
|
+
import path13 from "node:path";
|
|
2367
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
2294
2368
|
|
|
2295
2369
|
// src/completion-ack.ts
|
|
2296
2370
|
function hasCompletionAck(worker) {
|
|
@@ -2306,7 +2380,7 @@ function persistCompletionAck(worker, runId, fields) {
|
|
|
2306
2380
|
}
|
|
2307
2381
|
|
|
2308
2382
|
// src/worker-ops.ts
|
|
2309
|
-
import
|
|
2383
|
+
import path12 from "node:path";
|
|
2310
2384
|
|
|
2311
2385
|
// src/completion-response.ts
|
|
2312
2386
|
function asRecord(value) {
|
|
@@ -2369,6 +2443,11 @@ function isHarnessExpertReviewWorker(worker) {
|
|
|
2369
2443
|
|
|
2370
2444
|
// src/pr-handoff/pr-handoff-assess.ts
|
|
2371
2445
|
var REVIEW_LANE_RULE = /^(lane:)?(review|deep_review|planning|landing)(:|$)/i;
|
|
2446
|
+
var REVIEW_PERSONA_SLUGS = /* @__PURE__ */ new Set(["lorentz", "sentinel"]);
|
|
2447
|
+
var NO_PR_COMMITS_BETWEEN_RE = /no commits between/i;
|
|
2448
|
+
function isGhNoCommitsBetweenError(detail) {
|
|
2449
|
+
return Boolean(detail && NO_PR_COMMITS_BETWEEN_RE.test(detail));
|
|
2450
|
+
}
|
|
2372
2451
|
function trimOrNull4(value) {
|
|
2373
2452
|
if (typeof value !== "string") return null;
|
|
2374
2453
|
const trimmed = value.trim();
|
|
@@ -2387,8 +2466,30 @@ function extractPrUrlFromText(value) {
|
|
|
2387
2466
|
);
|
|
2388
2467
|
return m ? trimOrNull4(m[0]) : null;
|
|
2389
2468
|
}
|
|
2390
|
-
function
|
|
2469
|
+
function countCommitsAheadOfBase(worktreePath, baseRef, exec) {
|
|
2470
|
+
const base = baseRef.trim();
|
|
2471
|
+
if (!base) return null;
|
|
2472
|
+
const count = exec.git(worktreePath, ["rev-list", "--count", `${base}..HEAD`]);
|
|
2473
|
+
if (count.status !== 0) return null;
|
|
2474
|
+
const n = Number.parseInt(count.stdout.trim(), 10);
|
|
2475
|
+
return Number.isFinite(n) ? n : null;
|
|
2476
|
+
}
|
|
2477
|
+
function isReviewArtifactWorker(worker, snapshot) {
|
|
2478
|
+
if (snapshot.changedFiles.length > 0) return false;
|
|
2479
|
+
const persona = trimOrNull4(worker.personaSlug)?.toLowerCase();
|
|
2480
|
+
if (persona && REVIEW_PERSONA_SLUGS.has(persona)) return true;
|
|
2481
|
+
const rule = trimOrNull4(worker.routingRule) ?? "";
|
|
2482
|
+
if (rule && REVIEW_LANE_RULE.test(rule)) return true;
|
|
2483
|
+
return false;
|
|
2484
|
+
}
|
|
2485
|
+
function hasWorkProduct(snapshot, options) {
|
|
2391
2486
|
if (snapshot.changedFiles.length > 0) return true;
|
|
2487
|
+
const baseRef = trimOrNull4(options?.baseRef);
|
|
2488
|
+
if (baseRef && options?.exec && options.worktreePath) {
|
|
2489
|
+
const ahead = countCommitsAheadOfBase(options.worktreePath, baseRef, options.exec);
|
|
2490
|
+
if (ahead === 0) return false;
|
|
2491
|
+
if (ahead !== null && ahead > 0) return true;
|
|
2492
|
+
}
|
|
2392
2493
|
if (trimOrNull4(snapshot.headCommit)) return true;
|
|
2393
2494
|
if (committedHead(snapshot.gitAncestry)) return true;
|
|
2394
2495
|
return false;
|
|
@@ -2409,6 +2510,13 @@ function assessPrHandoffRequirement(input) {
|
|
|
2409
2510
|
if (rule && REVIEW_LANE_RULE.test(rule)) {
|
|
2410
2511
|
return { required: false, reason: "review_lane" };
|
|
2411
2512
|
}
|
|
2513
|
+
const workerCtx = input.worker ?? {
|
|
2514
|
+
personaSlug: input.personaSlug,
|
|
2515
|
+
routingRule: input.routingRule
|
|
2516
|
+
};
|
|
2517
|
+
if (isReviewArtifactWorker(workerCtx, input.snapshot)) {
|
|
2518
|
+
return { required: false, reason: "review_artifact" };
|
|
2519
|
+
}
|
|
2412
2520
|
if (trimOrNull4(input.patchPath) || trimOrNull4(input.artifactBundlePath)) {
|
|
2413
2521
|
return { required: false, reason: "patch_or_bundle" };
|
|
2414
2522
|
}
|
|
@@ -2416,7 +2524,12 @@ function assessPrHandoffRequirement(input) {
|
|
|
2416
2524
|
if (prUrl) {
|
|
2417
2525
|
return { required: false, reason: "already_has_pr" };
|
|
2418
2526
|
}
|
|
2419
|
-
|
|
2527
|
+
const workProductOpts = input.exec && input.baseRef ? {
|
|
2528
|
+
baseRef: input.baseRef,
|
|
2529
|
+
exec: input.exec,
|
|
2530
|
+
worktreePath: input.snapshot.worktreePath
|
|
2531
|
+
} : void 0;
|
|
2532
|
+
if (!hasWorkProduct(input.snapshot, workProductOpts)) {
|
|
2420
2533
|
return { required: false, reason: "no_work_product" };
|
|
2421
2534
|
}
|
|
2422
2535
|
return { required: true, snapshot: input.snapshot };
|
|
@@ -2625,15 +2738,19 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
|
|
|
2625
2738
|
prUrl: prUrlHint,
|
|
2626
2739
|
headCommit: null
|
|
2627
2740
|
});
|
|
2741
|
+
const baseRef = input.run.baseCommit?.trim() || input.run.base?.trim() || "origin/main";
|
|
2628
2742
|
const requirement = assessPrHandoffRequirement({
|
|
2629
2743
|
dispatched: input.worker.dispatched,
|
|
2630
2744
|
routingRule: input.worker.routingRule,
|
|
2745
|
+
personaSlug: input.worker.personaSlug,
|
|
2631
2746
|
prUrl: prUrlHint,
|
|
2632
2747
|
taskTitle: input.worker.taskTitle,
|
|
2633
2748
|
executorRef: input.worker.executorRef,
|
|
2634
2749
|
parentTaskId: input.worker.parentTaskId,
|
|
2635
|
-
personaSlug: input.worker.personaSlug,
|
|
2636
2750
|
taskPrUrl: input.worker.taskPrUrl,
|
|
2751
|
+
baseRef,
|
|
2752
|
+
exec,
|
|
2753
|
+
worker: input.worker,
|
|
2637
2754
|
snapshot
|
|
2638
2755
|
});
|
|
2639
2756
|
if (!requirement.required) {
|
|
@@ -2718,6 +2835,14 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
|
|
|
2718
2835
|
exec
|
|
2719
2836
|
});
|
|
2720
2837
|
if (!pr.ok || !pr.prUrl) {
|
|
2838
|
+
if (isGhNoCommitsBetweenError(pr.detail)) {
|
|
2839
|
+
return {
|
|
2840
|
+
ok: true,
|
|
2841
|
+
headCommit: headCommit ?? void 0,
|
|
2842
|
+
committed,
|
|
2843
|
+
pushed
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2721
2846
|
const dirty = snapshot.changedFiles.length;
|
|
2722
2847
|
const detail = dirty ? `${dirty} uncommitted change(s) and no PR URL after handoff attempt` : "no PR URL after handoff attempt";
|
|
2723
2848
|
return {
|
|
@@ -2737,7 +2862,7 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
|
|
|
2737
2862
|
}
|
|
2738
2863
|
|
|
2739
2864
|
// src/worker-lifecycle.ts
|
|
2740
|
-
import
|
|
2865
|
+
import path11 from "node:path";
|
|
2741
2866
|
var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
|
|
2742
2867
|
"awaiting_review",
|
|
2743
2868
|
"blocked",
|
|
@@ -2793,7 +2918,7 @@ function syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick) {
|
|
|
2793
2918
|
const synced = [];
|
|
2794
2919
|
for (const name of Object.keys(run.workers || {})) {
|
|
2795
2920
|
const worker = readJson(
|
|
2796
|
-
|
|
2921
|
+
path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2797
2922
|
void 0
|
|
2798
2923
|
);
|
|
2799
2924
|
if (!worker?.taskId || isCompletionAcknowledged(worker)) continue;
|
|
@@ -3051,7 +3176,7 @@ function workerStatus(args) {
|
|
|
3051
3176
|
const worker = loadWorker(String(args.run), String(args.name));
|
|
3052
3177
|
const run = loadRun(worker.runId);
|
|
3053
3178
|
const status = computeWorkerStatus(worker, workerStatusOptions(run));
|
|
3054
|
-
writeJson(
|
|
3179
|
+
writeJson(path12.join(worker.workerDir, "last-status.json"), status);
|
|
3055
3180
|
console.log(JSON.stringify(status, null, 2));
|
|
3056
3181
|
}
|
|
3057
3182
|
function buildRunBoard(runId) {
|
|
@@ -3059,7 +3184,7 @@ function buildRunBoard(runId) {
|
|
|
3059
3184
|
const names = Object.keys(run.workers || {});
|
|
3060
3185
|
const workers = names.map((name) => {
|
|
3061
3186
|
const worker = readJson(
|
|
3062
|
-
|
|
3187
|
+
path12.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
3063
3188
|
void 0
|
|
3064
3189
|
);
|
|
3065
3190
|
if (!worker) {
|
|
@@ -3170,7 +3295,7 @@ function buildRunBoard(runId) {
|
|
|
3170
3295
|
needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
|
|
3171
3296
|
workers
|
|
3172
3297
|
};
|
|
3173
|
-
writeJson(
|
|
3298
|
+
writeJson(path12.join(runDirectory(run.id), "last-board.json"), board);
|
|
3174
3299
|
return board;
|
|
3175
3300
|
}
|
|
3176
3301
|
async function publishHarnessBoardSnapshot(args, source) {
|
|
@@ -3359,12 +3484,12 @@ async function autoCompleteWorkerCli(raw) {
|
|
|
3359
3484
|
}
|
|
3360
3485
|
}
|
|
3361
3486
|
function resolveDefaultCliPath() {
|
|
3362
|
-
return
|
|
3487
|
+
return path13.join(fileURLToPath2(new URL(".", import.meta.url)), "cli.js");
|
|
3363
3488
|
}
|
|
3364
3489
|
function spawnCompletionSidecar(opts) {
|
|
3365
3490
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath();
|
|
3366
|
-
if (!
|
|
3367
|
-
const logPath =
|
|
3491
|
+
if (!existsSync10(cliPath)) return void 0;
|
|
3492
|
+
const logPath = path13.join(opts.workerDir, "auto-complete.log");
|
|
3368
3493
|
let logFd;
|
|
3369
3494
|
try {
|
|
3370
3495
|
logFd = openSync3(logPath, "a");
|
|
@@ -3446,16 +3571,16 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3446
3571
|
launchModel = preflight.model;
|
|
3447
3572
|
}
|
|
3448
3573
|
const { worktreesDir } = getPaths();
|
|
3449
|
-
const workerDir =
|
|
3574
|
+
const workerDir = path14.join(runDirectory(run.id), "workers", name);
|
|
3450
3575
|
mkdirSync3(workerDir, { recursive: true });
|
|
3451
|
-
const worktreePath =
|
|
3576
|
+
const worktreePath = path14.join(worktreesDir, run.id, name);
|
|
3452
3577
|
const branch = opts.branch || `agent/${run.id}/${name}`;
|
|
3453
|
-
if (
|
|
3578
|
+
if (existsSync11(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
|
|
3454
3579
|
git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
|
|
3455
3580
|
git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
|
|
3456
|
-
const stdoutPath =
|
|
3457
|
-
const stderrPath =
|
|
3458
|
-
const heartbeatPath =
|
|
3581
|
+
const stdoutPath = path14.join(workerDir, "stdout.jsonl");
|
|
3582
|
+
const stderrPath = path14.join(workerDir, "stderr.log");
|
|
3583
|
+
const heartbeatPath = path14.join(workerDir, "heartbeat.jsonl");
|
|
3459
3584
|
const prompt = buildPrompt({
|
|
3460
3585
|
task: opts.task,
|
|
3461
3586
|
ownedPaths: opts.ownedPaths || [],
|
|
@@ -3520,7 +3645,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3520
3645
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3521
3646
|
};
|
|
3522
3647
|
saveWorker(run.id, worker);
|
|
3523
|
-
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath:
|
|
3648
|
+
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path14.join(workerDir, "worker.json") } };
|
|
3524
3649
|
run.status = "running";
|
|
3525
3650
|
saveRun(run);
|
|
3526
3651
|
if (worker.agentOsId && worker.taskId) {
|
|
@@ -3812,18 +3937,18 @@ function buildPlanPersistIdempotencyKey(input) {
|
|
|
3812
3937
|
|
|
3813
3938
|
// src/plan-persist/paths.ts
|
|
3814
3939
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
3815
|
-
import { homedir as
|
|
3816
|
-
import
|
|
3940
|
+
import { homedir as homedir5 } from "node:os";
|
|
3941
|
+
import path15 from "node:path";
|
|
3817
3942
|
function resolveKynverStateRoot() {
|
|
3818
3943
|
const env = process.env.KYNVER_STATE_ROOT;
|
|
3819
|
-
if (env) return
|
|
3820
|
-
return
|
|
3944
|
+
if (env) return path15.resolve(env);
|
|
3945
|
+
return path15.join(homedir5(), ".kynver", "state");
|
|
3821
3946
|
}
|
|
3822
3947
|
function planOutboxDir() {
|
|
3823
|
-
return
|
|
3948
|
+
return path15.join(resolveKynverStateRoot(), "plan-outbox");
|
|
3824
3949
|
}
|
|
3825
3950
|
function planOutboxArchiveDir() {
|
|
3826
|
-
return
|
|
3951
|
+
return path15.join(resolveKynverStateRoot(), "plan-outbox-archive");
|
|
3827
3952
|
}
|
|
3828
3953
|
function ensurePlanOutboxDirs() {
|
|
3829
3954
|
const outboxDir = planOutboxDir();
|
|
@@ -3834,20 +3959,20 @@ function ensurePlanOutboxDirs() {
|
|
|
3834
3959
|
}
|
|
3835
3960
|
function isTmpOnlyPath(filePath) {
|
|
3836
3961
|
if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
|
|
3837
|
-
const resolved =
|
|
3838
|
-
return resolved.startsWith("/tmp/") || resolved.startsWith(
|
|
3962
|
+
const resolved = path15.resolve(filePath);
|
|
3963
|
+
return resolved.startsWith("/tmp/") || resolved.startsWith(path15.join("/var", "folders"));
|
|
3839
3964
|
}
|
|
3840
3965
|
|
|
3841
3966
|
// src/plan-persist/outbox-store.ts
|
|
3842
3967
|
import {
|
|
3843
|
-
existsSync as
|
|
3844
|
-
readFileSync as
|
|
3968
|
+
existsSync as existsSync13,
|
|
3969
|
+
readFileSync as readFileSync7,
|
|
3845
3970
|
renameSync,
|
|
3846
3971
|
readdirSync as readdirSync4,
|
|
3847
3972
|
writeFileSync as writeFileSync3,
|
|
3848
3973
|
unlinkSync
|
|
3849
3974
|
} from "node:fs";
|
|
3850
|
-
import
|
|
3975
|
+
import path16 from "node:path";
|
|
3851
3976
|
import { randomUUID } from "node:crypto";
|
|
3852
3977
|
var DEFAULT_MAX_RETRIES = 12;
|
|
3853
3978
|
function listOutboxItems() {
|
|
@@ -3855,7 +3980,7 @@ function listOutboxItems() {
|
|
|
3855
3980
|
const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
|
|
3856
3981
|
const items = [];
|
|
3857
3982
|
for (const file of files) {
|
|
3858
|
-
const item = readOutboxItem(
|
|
3983
|
+
const item = readOutboxItem(path16.join(outboxDir, file));
|
|
3859
3984
|
if (item && item.queueStatus === "queued") items.push(item);
|
|
3860
3985
|
}
|
|
3861
3986
|
return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
@@ -3867,25 +3992,25 @@ function findOutboxByIdempotencyKey(key) {
|
|
|
3867
3992
|
return null;
|
|
3868
3993
|
}
|
|
3869
3994
|
function readOutboxItem(jsonPath) {
|
|
3870
|
-
if (!
|
|
3995
|
+
if (!existsSync13(jsonPath)) return null;
|
|
3871
3996
|
try {
|
|
3872
|
-
return JSON.parse(
|
|
3997
|
+
return JSON.parse(readFileSync7(jsonPath, "utf8"));
|
|
3873
3998
|
} catch {
|
|
3874
3999
|
return null;
|
|
3875
4000
|
}
|
|
3876
4001
|
}
|
|
3877
4002
|
function readOutboxBody(item) {
|
|
3878
4003
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3879
|
-
const bodyFile =
|
|
3880
|
-
return
|
|
4004
|
+
const bodyFile = path16.join(outboxDir, item.bodyPath);
|
|
4005
|
+
return readFileSync7(bodyFile, "utf8");
|
|
3881
4006
|
}
|
|
3882
4007
|
function writeOutboxItem(input, opts) {
|
|
3883
4008
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3884
4009
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3885
4010
|
const id = opts.existing?.id ?? randomUUID();
|
|
3886
4011
|
const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
|
|
3887
|
-
const jsonPath =
|
|
3888
|
-
const bodyFile =
|
|
4012
|
+
const jsonPath = path16.join(outboxDir, `${id}.json`);
|
|
4013
|
+
const bodyFile = path16.join(outboxDir, bodyPath);
|
|
3889
4014
|
if (!opts.existing) {
|
|
3890
4015
|
writeFileSync3(bodyFile, input.body, "utf8");
|
|
3891
4016
|
}
|
|
@@ -3921,24 +4046,24 @@ function writeOutboxItem(input, opts) {
|
|
|
3921
4046
|
}
|
|
3922
4047
|
function saveOutboxItem(item) {
|
|
3923
4048
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3924
|
-
const jsonPath =
|
|
4049
|
+
const jsonPath = path16.join(outboxDir, `${item.id}.json`);
|
|
3925
4050
|
writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
|
|
3926
4051
|
`, { mode: 384 });
|
|
3927
4052
|
}
|
|
3928
4053
|
function archiveOutboxItem(item) {
|
|
3929
4054
|
const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
|
|
3930
|
-
const jsonSrc =
|
|
3931
|
-
const bodySrc =
|
|
3932
|
-
const jsonDst =
|
|
3933
|
-
const bodyDst =
|
|
3934
|
-
if (
|
|
3935
|
-
if (
|
|
4055
|
+
const jsonSrc = path16.join(outboxDir, `${item.id}.json`);
|
|
4056
|
+
const bodySrc = path16.join(outboxDir, item.bodyPath);
|
|
4057
|
+
const jsonDst = path16.join(archiveDir, `${item.id}.json`);
|
|
4058
|
+
const bodyDst = path16.join(archiveDir, item.bodyPath);
|
|
4059
|
+
if (existsSync13(jsonSrc)) renameSync(jsonSrc, jsonDst);
|
|
4060
|
+
if (existsSync13(bodySrc)) renameSync(bodySrc, bodyDst);
|
|
3936
4061
|
}
|
|
3937
4062
|
function outboxItemPaths(item) {
|
|
3938
4063
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3939
4064
|
return {
|
|
3940
|
-
jsonPath:
|
|
3941
|
-
bodyPath:
|
|
4065
|
+
jsonPath: path16.join(outboxDir, `${item.id}.json`),
|
|
4066
|
+
bodyPath: path16.join(outboxDir, item.bodyPath)
|
|
3942
4067
|
};
|
|
3943
4068
|
}
|
|
3944
4069
|
function outboxInputFromItem(item, body) {
|
|
@@ -4124,7 +4249,7 @@ function markOutboxFailed(item, message) {
|
|
|
4124
4249
|
}
|
|
4125
4250
|
|
|
4126
4251
|
// src/plan-persist/drain.ts
|
|
4127
|
-
import
|
|
4252
|
+
import path17 from "node:path";
|
|
4128
4253
|
async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
4129
4254
|
const items = listOutboxItems().filter(
|
|
4130
4255
|
(item) => opts.outboxId ? item.id === opts.outboxId : true
|
|
@@ -4158,7 +4283,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
|
4158
4283
|
return result;
|
|
4159
4284
|
}
|
|
4160
4285
|
function loadOutboxById(outboxId) {
|
|
4161
|
-
const jsonPath =
|
|
4286
|
+
const jsonPath = path17.join(planOutboxDir(), `${outboxId}.json`);
|
|
4162
4287
|
return readOutboxItem(jsonPath);
|
|
4163
4288
|
}
|
|
4164
4289
|
|
|
@@ -4258,7 +4383,7 @@ async function dispatchRun(args) {
|
|
|
4258
4383
|
const activeHarnessWorkers = [];
|
|
4259
4384
|
for (const name of Object.keys(run.workers || {})) {
|
|
4260
4385
|
const worker = readJson(
|
|
4261
|
-
|
|
4386
|
+
path18.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4262
4387
|
void 0
|
|
4263
4388
|
);
|
|
4264
4389
|
if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
|
|
@@ -4467,7 +4592,7 @@ async function dispatchRun(args) {
|
|
|
4467
4592
|
}
|
|
4468
4593
|
|
|
4469
4594
|
// src/sweep.ts
|
|
4470
|
-
import
|
|
4595
|
+
import path19 from "node:path";
|
|
4471
4596
|
async function sweepRun(args) {
|
|
4472
4597
|
const pipeline = args.pipeline === true || args.pipeline === "true";
|
|
4473
4598
|
try {
|
|
@@ -4480,7 +4605,7 @@ async function sweepRun(args) {
|
|
|
4480
4605
|
const releasedLocalOrphans = [];
|
|
4481
4606
|
for (const name of Object.keys(run.workers || {})) {
|
|
4482
4607
|
const worker = readJson(
|
|
4483
|
-
|
|
4608
|
+
path19.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4484
4609
|
void 0
|
|
4485
4610
|
);
|
|
4486
4611
|
if (!worker || !worker.dispatched || !worker.taskId) continue;
|
|
@@ -4523,11 +4648,53 @@ async function sweepRun(args) {
|
|
|
4523
4648
|
}
|
|
4524
4649
|
|
|
4525
4650
|
// src/worktree.ts
|
|
4526
|
-
import { existsSync as
|
|
4651
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5 } from "node:fs";
|
|
4652
|
+
import path22 from "node:path";
|
|
4653
|
+
|
|
4654
|
+
// src/default-repo.ts
|
|
4527
4655
|
import path20 from "node:path";
|
|
4656
|
+
function expandConfiguredRepo(value) {
|
|
4657
|
+
return path20.resolve(resolveUserPath(value.trim()));
|
|
4658
|
+
}
|
|
4659
|
+
function fromConfigured(value, source, persistedInConfig) {
|
|
4660
|
+
const trimmed = value?.trim();
|
|
4661
|
+
if (!trimmed) return null;
|
|
4662
|
+
return {
|
|
4663
|
+
repo: expandConfiguredRepo(trimmed),
|
|
4664
|
+
source,
|
|
4665
|
+
persistedInConfig
|
|
4666
|
+
};
|
|
4667
|
+
}
|
|
4668
|
+
function resolveDefaultRepo(opts = {}) {
|
|
4669
|
+
const env = opts.env ?? process.env;
|
|
4670
|
+
const config = opts.config ?? loadUserConfig();
|
|
4671
|
+
const fromConfig = fromConfigured(config.defaultRepo, "config", true);
|
|
4672
|
+
if (fromConfig) return fromConfig;
|
|
4673
|
+
const fromDefaultEnv = fromConfigured(env.KYNVER_DEFAULT_REPO, "env_default_repo", false);
|
|
4674
|
+
if (fromDefaultEnv) return fromDefaultEnv;
|
|
4675
|
+
const fromHarnessEnv = fromConfigured(env.KYNVER_HARNESS_REPO, "env_harness_repo", false);
|
|
4676
|
+
if (fromHarnessEnv) return fromHarnessEnv;
|
|
4677
|
+
const discovered = discoverDefaultRepo({
|
|
4678
|
+
cwd: opts.cwd,
|
|
4679
|
+
runtimeModuleUrl: opts.runtimeModuleUrl
|
|
4680
|
+
});
|
|
4681
|
+
if (!discovered) return null;
|
|
4682
|
+
return {
|
|
4683
|
+
repo: discovered.repo,
|
|
4684
|
+
source: discovered.source,
|
|
4685
|
+
persistedInConfig: false
|
|
4686
|
+
};
|
|
4687
|
+
}
|
|
4688
|
+
function formatResolvedDefaultRepo(resolved) {
|
|
4689
|
+
return {
|
|
4690
|
+
defaultRepo: displayUserPath(resolved.repo),
|
|
4691
|
+
source: resolved.source,
|
|
4692
|
+
persistedInConfig: resolved.persistedInConfig
|
|
4693
|
+
};
|
|
4694
|
+
}
|
|
4528
4695
|
|
|
4529
4696
|
// src/validate.ts
|
|
4530
|
-
import
|
|
4697
|
+
import path21 from "node:path";
|
|
4531
4698
|
var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
4532
4699
|
function validateRunId(runId) {
|
|
4533
4700
|
const trimmed = runId.trim();
|
|
@@ -4535,18 +4702,26 @@ function validateRunId(runId) {
|
|
|
4535
4702
|
return trimmed;
|
|
4536
4703
|
}
|
|
4537
4704
|
function validateRepo(repo) {
|
|
4538
|
-
const resolved =
|
|
4705
|
+
const resolved = path21.resolve(repo);
|
|
4539
4706
|
if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
|
|
4540
4707
|
return resolved;
|
|
4541
4708
|
}
|
|
4542
4709
|
|
|
4543
4710
|
// src/worktree.ts
|
|
4711
|
+
function resolveCreateRunRepo(args) {
|
|
4712
|
+
const explicit = typeof args.repo === "string" ? args.repo.trim() : "";
|
|
4713
|
+
if (explicit) return explicit;
|
|
4714
|
+
const resolved = resolveDefaultRepo();
|
|
4715
|
+
if (resolved) return resolved.repo;
|
|
4716
|
+
required("", "--repo (or set defaultRepo via `kynver setup` / KYNVER_DEFAULT_REPO)");
|
|
4717
|
+
return "";
|
|
4718
|
+
}
|
|
4544
4719
|
function createRun(args) {
|
|
4545
|
-
const repo = validateRepo(
|
|
4720
|
+
const repo = validateRepo(resolveCreateRunRepo(args));
|
|
4546
4721
|
ensureGitRepo(repo);
|
|
4547
4722
|
const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
|
|
4548
4723
|
const dir = runDirectory(id);
|
|
4549
|
-
if (
|
|
4724
|
+
if (existsSync14(dir)) failExists(`run already exists: ${id}`);
|
|
4550
4725
|
mkdirSync5(dir, { recursive: true });
|
|
4551
4726
|
const base = String(args.base || "origin/main");
|
|
4552
4727
|
const baseCommit = git(repo, ["rev-parse", base]).trim();
|
|
@@ -4560,12 +4735,12 @@ function createRun(args) {
|
|
|
4560
4735
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4561
4736
|
workers: {}
|
|
4562
4737
|
};
|
|
4563
|
-
writeJson(
|
|
4738
|
+
writeJson(path22.join(dir, "run.json"), run);
|
|
4564
4739
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
4565
4740
|
}
|
|
4566
4741
|
function listRuns() {
|
|
4567
4742
|
const { runsDir } = getPaths();
|
|
4568
|
-
const rows = listRunIds(runsDir).map((id) => readJson(
|
|
4743
|
+
const rows = listRunIds(runsDir).map((id) => readJson(path22.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
|
|
4569
4744
|
id: run.id,
|
|
4570
4745
|
name: run.name,
|
|
4571
4746
|
status: run.status,
|
|
@@ -4580,7 +4755,7 @@ function failExists(message) {
|
|
|
4580
4755
|
}
|
|
4581
4756
|
|
|
4582
4757
|
// src/pipeline-tick.ts
|
|
4583
|
-
import
|
|
4758
|
+
import path32 from "node:path";
|
|
4584
4759
|
|
|
4585
4760
|
// src/pipeline-dispatch.ts
|
|
4586
4761
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -4634,10 +4809,10 @@ async function runPipelineDispatch(args, slots) {
|
|
|
4634
4809
|
}
|
|
4635
4810
|
|
|
4636
4811
|
// src/stale-reconcile.ts
|
|
4637
|
-
import
|
|
4812
|
+
import path24 from "node:path";
|
|
4638
4813
|
|
|
4639
4814
|
// src/finalize.ts
|
|
4640
|
-
import
|
|
4815
|
+
import path23 from "node:path";
|
|
4641
4816
|
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
|
|
4642
4817
|
function terminalStatusFor(run) {
|
|
4643
4818
|
const names = Object.keys(run.workers || {});
|
|
@@ -4648,7 +4823,7 @@ function terminalStatusFor(run) {
|
|
|
4648
4823
|
let anyLandingBlocked = false;
|
|
4649
4824
|
for (const name of names) {
|
|
4650
4825
|
const worker = readJson(
|
|
4651
|
-
|
|
4826
|
+
path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4652
4827
|
void 0
|
|
4653
4828
|
);
|
|
4654
4829
|
if (!worker) continue;
|
|
@@ -4700,7 +4875,7 @@ function reconcileStaleWorkers() {
|
|
|
4700
4875
|
const now = Date.now();
|
|
4701
4876
|
for (const run of listRunRecords()) {
|
|
4702
4877
|
for (const name of Object.keys(run.workers || {})) {
|
|
4703
|
-
const workerPath =
|
|
4878
|
+
const workerPath = path24.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
4704
4879
|
const worker = readJson(workerPath, void 0);
|
|
4705
4880
|
if (!worker || worker.status !== "running") {
|
|
4706
4881
|
outcomes.push({
|
|
@@ -4794,7 +4969,7 @@ function reconcileRunsCli() {
|
|
|
4794
4969
|
}
|
|
4795
4970
|
|
|
4796
4971
|
// src/plan-progress-daemon-sync.ts
|
|
4797
|
-
import
|
|
4972
|
+
import path25 from "node:path";
|
|
4798
4973
|
|
|
4799
4974
|
// src/plan-progress-sync.ts
|
|
4800
4975
|
async function syncPlanProgress(args) {
|
|
@@ -4818,7 +4993,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
4818
4993
|
const outcomes = [];
|
|
4819
4994
|
for (const name of Object.keys(run.workers || {})) {
|
|
4820
4995
|
const worker = readJson(
|
|
4821
|
-
|
|
4996
|
+
path25.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4822
4997
|
void 0
|
|
4823
4998
|
);
|
|
4824
4999
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -4867,7 +5042,7 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
4867
5042
|
}
|
|
4868
5043
|
|
|
4869
5044
|
// src/cleanup.ts
|
|
4870
|
-
import
|
|
5045
|
+
import path30 from "node:path";
|
|
4871
5046
|
|
|
4872
5047
|
// src/cleanup-types.ts
|
|
4873
5048
|
var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
@@ -4938,14 +5113,14 @@ function skipNodeModulesRemoval(input) {
|
|
|
4938
5113
|
}
|
|
4939
5114
|
|
|
4940
5115
|
// src/cleanup-execute.ts
|
|
4941
|
-
import { existsSync as
|
|
4942
|
-
import
|
|
5116
|
+
import { existsSync as existsSync16, rmSync } from "node:fs";
|
|
5117
|
+
import path27 from "node:path";
|
|
4943
5118
|
|
|
4944
5119
|
// src/cleanup-dir-size.ts
|
|
4945
|
-
import { existsSync as
|
|
4946
|
-
import
|
|
5120
|
+
import { existsSync as existsSync15, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
|
|
5121
|
+
import path26 from "node:path";
|
|
4947
5122
|
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
4948
|
-
if (!
|
|
5123
|
+
if (!existsSync15(root)) return 0;
|
|
4949
5124
|
let total = 0;
|
|
4950
5125
|
let seen = 0;
|
|
4951
5126
|
const stack = [root];
|
|
@@ -4959,7 +5134,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
4959
5134
|
}
|
|
4960
5135
|
for (const name of entries) {
|
|
4961
5136
|
if (seen++ > maxEntries) return null;
|
|
4962
|
-
const full =
|
|
5137
|
+
const full = path26.join(current, name);
|
|
4963
5138
|
let st;
|
|
4964
5139
|
try {
|
|
4965
5140
|
st = statSync2(full);
|
|
@@ -4975,7 +5150,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
4975
5150
|
|
|
4976
5151
|
// src/cleanup-execute.ts
|
|
4977
5152
|
function removeNodeModules(candidate, execute) {
|
|
4978
|
-
if (!
|
|
5153
|
+
if (!existsSync16(candidate.path)) {
|
|
4979
5154
|
return {
|
|
4980
5155
|
...candidate,
|
|
4981
5156
|
executed: false,
|
|
@@ -5006,7 +5181,7 @@ function removeNodeModules(candidate, execute) {
|
|
|
5006
5181
|
}
|
|
5007
5182
|
}
|
|
5008
5183
|
function removeWorktree(candidate, execute) {
|
|
5009
|
-
if (!
|
|
5184
|
+
if (!existsSync16(candidate.path)) {
|
|
5010
5185
|
return {
|
|
5011
5186
|
...candidate,
|
|
5012
5187
|
executed: false,
|
|
@@ -5023,7 +5198,7 @@ function removeWorktree(candidate, execute) {
|
|
|
5023
5198
|
if (repo) {
|
|
5024
5199
|
git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
|
|
5025
5200
|
}
|
|
5026
|
-
if (
|
|
5201
|
+
if (existsSync16(candidate.path)) {
|
|
5027
5202
|
rmSync(candidate.path, { recursive: true, force: true });
|
|
5028
5203
|
}
|
|
5029
5204
|
return {
|
|
@@ -5043,20 +5218,20 @@ function removeWorktree(candidate, execute) {
|
|
|
5043
5218
|
}
|
|
5044
5219
|
}
|
|
5045
5220
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
5046
|
-
const resolved =
|
|
5047
|
-
const nm = resolved.endsWith(`${
|
|
5221
|
+
const resolved = path27.resolve(targetPath);
|
|
5222
|
+
const nm = resolved.endsWith(`${path27.sep}node_modules`) ? resolved : null;
|
|
5048
5223
|
if (!nm) return "path_outside_harness";
|
|
5049
|
-
const rel =
|
|
5050
|
-
if (rel.startsWith("..") ||
|
|
5051
|
-
const parts = rel.split(
|
|
5224
|
+
const rel = path27.relative(worktreesDir, nm);
|
|
5225
|
+
if (rel.startsWith("..") || path27.isAbsolute(rel)) return "path_outside_harness";
|
|
5226
|
+
const parts = rel.split(path27.sep);
|
|
5052
5227
|
if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
|
|
5053
|
-
if (!resolved.startsWith(
|
|
5228
|
+
if (!resolved.startsWith(path27.resolve(harnessRoot))) return "path_outside_harness";
|
|
5054
5229
|
return null;
|
|
5055
5230
|
}
|
|
5056
5231
|
|
|
5057
5232
|
// src/cleanup-scan.ts
|
|
5058
|
-
import { existsSync as
|
|
5059
|
-
import
|
|
5233
|
+
import { existsSync as existsSync17, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
5234
|
+
import path28 from "node:path";
|
|
5060
5235
|
function pathAgeMs(target, now) {
|
|
5061
5236
|
try {
|
|
5062
5237
|
const mtime = statSync3(target).mtimeMs;
|
|
@@ -5066,17 +5241,17 @@ function pathAgeMs(target, now) {
|
|
|
5066
5241
|
}
|
|
5067
5242
|
}
|
|
5068
5243
|
function isPathInside(child, parent) {
|
|
5069
|
-
const rel =
|
|
5070
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
5244
|
+
const rel = path28.relative(parent, child);
|
|
5245
|
+
return rel === "" || !rel.startsWith("..") && !path28.isAbsolute(rel);
|
|
5071
5246
|
}
|
|
5072
5247
|
function scanNodeModulesCandidates(opts) {
|
|
5073
5248
|
const candidates = [];
|
|
5074
5249
|
const seen = /* @__PURE__ */ new Set();
|
|
5075
5250
|
for (const entry of opts.index.values()) {
|
|
5076
5251
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
5077
|
-
const nm =
|
|
5078
|
-
if (!
|
|
5079
|
-
const resolved =
|
|
5252
|
+
const nm = path28.join(entry.worktreePath, "node_modules");
|
|
5253
|
+
if (!existsSync17(nm)) continue;
|
|
5254
|
+
const resolved = path28.resolve(nm);
|
|
5080
5255
|
if (seen.has(resolved)) continue;
|
|
5081
5256
|
seen.add(resolved);
|
|
5082
5257
|
candidates.push({
|
|
@@ -5089,16 +5264,16 @@ function scanNodeModulesCandidates(opts) {
|
|
|
5089
5264
|
ageMs: pathAgeMs(resolved, opts.now)
|
|
5090
5265
|
});
|
|
5091
5266
|
}
|
|
5092
|
-
if (!opts.includeOrphans || !
|
|
5267
|
+
if (!opts.includeOrphans || !existsSync17(opts.worktreesDir)) return candidates;
|
|
5093
5268
|
for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
|
|
5094
5269
|
if (!runEntry.isDirectory()) continue;
|
|
5095
|
-
const runPath =
|
|
5270
|
+
const runPath = path28.join(opts.worktreesDir, runEntry.name);
|
|
5096
5271
|
for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
|
|
5097
5272
|
if (!workerEntry.isDirectory()) continue;
|
|
5098
|
-
const worktreePath =
|
|
5099
|
-
const nm =
|
|
5100
|
-
if (!
|
|
5101
|
-
const resolved =
|
|
5273
|
+
const worktreePath = path28.join(runPath, workerEntry.name);
|
|
5274
|
+
const nm = path28.join(worktreePath, "node_modules");
|
|
5275
|
+
if (!existsSync17(nm)) continue;
|
|
5276
|
+
const resolved = path28.resolve(nm);
|
|
5102
5277
|
if (seen.has(resolved)) continue;
|
|
5103
5278
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
5104
5279
|
seen.add(resolved);
|
|
@@ -5121,7 +5296,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
5121
5296
|
for (const entry of opts.index.values()) {
|
|
5122
5297
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
5123
5298
|
const resolved = entry.worktreePath;
|
|
5124
|
-
if (!
|
|
5299
|
+
if (!existsSync17(resolved)) continue;
|
|
5125
5300
|
if (seen.has(resolved)) continue;
|
|
5126
5301
|
seen.add(resolved);
|
|
5127
5302
|
candidates.push({
|
|
@@ -5138,17 +5313,17 @@ function scanWorktreeCandidates(opts) {
|
|
|
5138
5313
|
}
|
|
5139
5314
|
|
|
5140
5315
|
// src/cleanup-worktree-index.ts
|
|
5141
|
-
import
|
|
5316
|
+
import path29 from "node:path";
|
|
5142
5317
|
function buildWorktreeIndex() {
|
|
5143
5318
|
const index = /* @__PURE__ */ new Map();
|
|
5144
5319
|
for (const run of listRunRecords()) {
|
|
5145
5320
|
for (const name of Object.keys(run.workers || {})) {
|
|
5146
|
-
const workerPath =
|
|
5321
|
+
const workerPath = path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
5147
5322
|
const worker = readJson(workerPath, void 0);
|
|
5148
5323
|
if (!worker?.worktreePath) continue;
|
|
5149
5324
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
5150
|
-
index.set(
|
|
5151
|
-
worktreePath:
|
|
5325
|
+
index.set(path29.resolve(worker.worktreePath), {
|
|
5326
|
+
worktreePath: path29.resolve(worker.worktreePath),
|
|
5152
5327
|
runId: run.id,
|
|
5153
5328
|
workerName: name,
|
|
5154
5329
|
run,
|
|
@@ -5163,7 +5338,7 @@ function buildWorktreeIndex() {
|
|
|
5163
5338
|
// src/cleanup.ts
|
|
5164
5339
|
function resolveOptions(options = {}) {
|
|
5165
5340
|
const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
|
|
5166
|
-
const { worktreesDir } = options.harnessRoot ? { worktreesDir:
|
|
5341
|
+
const { worktreesDir } = options.harnessRoot ? { worktreesDir: path30.join(harnessRoot, "worktrees") } : getHarnessPaths();
|
|
5167
5342
|
const execute = options.execute === true;
|
|
5168
5343
|
const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
|
|
5169
5344
|
const worktreesAgeMs = options.worktreesAgeMs ?? 0;
|
|
@@ -5207,7 +5382,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
5207
5382
|
actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
|
|
5208
5383
|
continue;
|
|
5209
5384
|
}
|
|
5210
|
-
const worktreePath =
|
|
5385
|
+
const worktreePath = path30.resolve(candidate.path, "..");
|
|
5211
5386
|
const indexed = index.get(worktreePath) ?? null;
|
|
5212
5387
|
const guardReason = skipNodeModulesRemoval({
|
|
5213
5388
|
indexed,
|
|
@@ -5223,7 +5398,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
5223
5398
|
actions.push(removeNodeModules(candidate, resolved.execute));
|
|
5224
5399
|
}
|
|
5225
5400
|
for (const candidate of scanWorktreeCandidates(scanOpts)) {
|
|
5226
|
-
const indexed = index.get(
|
|
5401
|
+
const indexed = index.get(path30.resolve(candidate.path)) ?? null;
|
|
5227
5402
|
const guardReason = skipWorktreeRemoval({
|
|
5228
5403
|
indexed,
|
|
5229
5404
|
includeOrphans: resolved.includeOrphans,
|
|
@@ -5288,8 +5463,8 @@ function isPipelineCleanupEnabled() {
|
|
|
5288
5463
|
|
|
5289
5464
|
// src/installed-package-versions.ts
|
|
5290
5465
|
import { readFile } from "node:fs/promises";
|
|
5291
|
-
import { homedir as
|
|
5292
|
-
import
|
|
5466
|
+
import { homedir as homedir6 } from "node:os";
|
|
5467
|
+
import path31 from "node:path";
|
|
5293
5468
|
var MANAGED_PACKAGES = [
|
|
5294
5469
|
"@kynver-app/runtime",
|
|
5295
5470
|
"@kynver-app/openclaw-agent-os",
|
|
@@ -5303,13 +5478,13 @@ function unique(values) {
|
|
|
5303
5478
|
return [...new Set(values.filter((value) => Boolean(value)))];
|
|
5304
5479
|
}
|
|
5305
5480
|
function moduleRoots() {
|
|
5306
|
-
const home =
|
|
5307
|
-
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ??
|
|
5308
|
-
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ?
|
|
5481
|
+
const home = homedir6();
|
|
5482
|
+
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path31.join(home, ".openclaw", "npm");
|
|
5483
|
+
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"));
|
|
5309
5484
|
return unique([
|
|
5310
|
-
|
|
5311
|
-
|
|
5312
|
-
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot :
|
|
5485
|
+
path31.join(openClawPrefix, "lib", "node_modules"),
|
|
5486
|
+
path31.join(openClawPrefix, "node_modules"),
|
|
5487
|
+
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path31.join(npmGlobalRoot, "lib", "node_modules")
|
|
5313
5488
|
]);
|
|
5314
5489
|
}
|
|
5315
5490
|
async function readVersion(packageJsonPath) {
|
|
@@ -5325,7 +5500,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
|
|
|
5325
5500
|
const out = {};
|
|
5326
5501
|
for (const packageName of MANAGED_PACKAGES) {
|
|
5327
5502
|
for (const root of roots) {
|
|
5328
|
-
const packageJsonPath =
|
|
5503
|
+
const packageJsonPath = path31.join(root, packageName, "package.json");
|
|
5329
5504
|
const version = await readVersion(packageJsonPath);
|
|
5330
5505
|
if (!version) continue;
|
|
5331
5506
|
out[packageName] = { version, observedAt, path: packageJsonPath };
|
|
@@ -5341,7 +5516,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
5341
5516
|
const outcomes = [];
|
|
5342
5517
|
for (const name of Object.keys(run.workers || {})) {
|
|
5343
5518
|
const worker = readJson(
|
|
5344
|
-
|
|
5519
|
+
path32.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
5345
5520
|
void 0
|
|
5346
5521
|
);
|
|
5347
5522
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -5483,7 +5658,7 @@ async function runDaemon(args) {
|
|
|
5483
5658
|
}
|
|
5484
5659
|
|
|
5485
5660
|
// src/plan-progress.ts
|
|
5486
|
-
import
|
|
5661
|
+
import path33 from "node:path";
|
|
5487
5662
|
|
|
5488
5663
|
// src/bounded-build/constants.ts
|
|
5489
5664
|
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
@@ -5769,7 +5944,7 @@ async function emitPlanProgress(args) {
|
|
|
5769
5944
|
}
|
|
5770
5945
|
function verifyPlanLocal(args) {
|
|
5771
5946
|
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
5772
|
-
const cwd =
|
|
5947
|
+
const cwd = path33.resolve(worktree);
|
|
5773
5948
|
const summary = runHarnessVerifyCommands(cwd);
|
|
5774
5949
|
const emitJson = args.json === true || args.json === "true";
|
|
5775
5950
|
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
@@ -5818,9 +5993,9 @@ async function verifyPlan(args) {
|
|
|
5818
5993
|
}
|
|
5819
5994
|
|
|
5820
5995
|
// src/harness-verify-cli.ts
|
|
5821
|
-
import
|
|
5996
|
+
import path34 from "node:path";
|
|
5822
5997
|
function runHarnessVerifyCli(args) {
|
|
5823
|
-
const cwd =
|
|
5998
|
+
const cwd = path34.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
5824
5999
|
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
5825
6000
|
const commands = [];
|
|
5826
6001
|
const rawCmd = args.command;
|
|
@@ -5864,7 +6039,7 @@ function runHarnessVerifyCli(args) {
|
|
|
5864
6039
|
}
|
|
5865
6040
|
|
|
5866
6041
|
// src/plan-persist-cli.ts
|
|
5867
|
-
import { readFileSync as
|
|
6042
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
5868
6043
|
var OPERATIONS = ["create", "add_version", "update_metadata"];
|
|
5869
6044
|
var FAILURE_KINDS = [
|
|
5870
6045
|
"approval_guard",
|
|
@@ -5876,7 +6051,7 @@ var FAILURE_KINDS = [
|
|
|
5876
6051
|
function readBodyArg(args) {
|
|
5877
6052
|
const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
|
|
5878
6053
|
if (bodyFile) {
|
|
5879
|
-
return { body:
|
|
6054
|
+
return { body: readFileSync8(bodyFile, "utf8"), bodyPathHint: bodyFile };
|
|
5880
6055
|
}
|
|
5881
6056
|
const inline = args.body ? String(args.body) : void 0;
|
|
5882
6057
|
if (inline) return { body: inline };
|
|
@@ -5964,7 +6139,7 @@ function runCleanupCli(args) {
|
|
|
5964
6139
|
}
|
|
5965
6140
|
|
|
5966
6141
|
// src/monitor/monitor.service.ts
|
|
5967
|
-
import
|
|
6142
|
+
import path36 from "node:path";
|
|
5968
6143
|
|
|
5969
6144
|
// src/monitor/monitor.classify.ts
|
|
5970
6145
|
function expectedLeaseOwner(runId) {
|
|
@@ -6020,11 +6195,11 @@ function classifyWorkerHealth(input) {
|
|
|
6020
6195
|
}
|
|
6021
6196
|
|
|
6022
6197
|
// src/monitor/monitor.store.ts
|
|
6023
|
-
import { existsSync as
|
|
6024
|
-
import
|
|
6198
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
|
|
6199
|
+
import path35 from "node:path";
|
|
6025
6200
|
function monitorsDir() {
|
|
6026
6201
|
const { harnessRoot } = getHarnessPaths();
|
|
6027
|
-
const dir =
|
|
6202
|
+
const dir = path35.join(harnessRoot, "monitors");
|
|
6028
6203
|
mkdirSync6(dir, { recursive: true });
|
|
6029
6204
|
return dir;
|
|
6030
6205
|
}
|
|
@@ -6032,7 +6207,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
6032
6207
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
6033
6208
|
}
|
|
6034
6209
|
function monitorPath(monitorId) {
|
|
6035
|
-
return
|
|
6210
|
+
return path35.join(monitorsDir(), `${monitorId}.json`);
|
|
6036
6211
|
}
|
|
6037
6212
|
function loadMonitorSession(monitorId) {
|
|
6038
6213
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -6042,18 +6217,18 @@ function saveMonitorSession(session) {
|
|
|
6042
6217
|
}
|
|
6043
6218
|
function deleteMonitorSession(monitorId) {
|
|
6044
6219
|
const file = monitorPath(monitorId);
|
|
6045
|
-
if (!
|
|
6220
|
+
if (!existsSync18(file)) return false;
|
|
6046
6221
|
unlinkSync2(file);
|
|
6047
6222
|
return true;
|
|
6048
6223
|
}
|
|
6049
6224
|
function listMonitorSessions() {
|
|
6050
6225
|
const dir = monitorsDir();
|
|
6051
|
-
if (!
|
|
6226
|
+
if (!existsSync18(dir)) return [];
|
|
6052
6227
|
const entries = [];
|
|
6053
6228
|
for (const name of readdirSync7(dir)) {
|
|
6054
6229
|
if (!name.endsWith(".json")) continue;
|
|
6055
6230
|
const session = readJson(
|
|
6056
|
-
|
|
6231
|
+
path35.join(dir, name),
|
|
6057
6232
|
void 0
|
|
6058
6233
|
);
|
|
6059
6234
|
if (!session?.monitorId) continue;
|
|
@@ -6144,7 +6319,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
6144
6319
|
// src/monitor/monitor.service.ts
|
|
6145
6320
|
function workerRecord2(runId, name) {
|
|
6146
6321
|
return readJson(
|
|
6147
|
-
|
|
6322
|
+
path36.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
6148
6323
|
void 0
|
|
6149
6324
|
);
|
|
6150
6325
|
}
|
|
@@ -6346,18 +6521,18 @@ async function runMonitorLoop(args) {
|
|
|
6346
6521
|
|
|
6347
6522
|
// src/monitor/monitor-spawn.ts
|
|
6348
6523
|
import { spawn as spawn4 } from "node:child_process";
|
|
6349
|
-
import { closeSync as closeSync4, existsSync as
|
|
6350
|
-
import
|
|
6351
|
-
import { fileURLToPath as
|
|
6524
|
+
import { closeSync as closeSync4, existsSync as existsSync19, openSync as openSync4 } from "node:fs";
|
|
6525
|
+
import path37 from "node:path";
|
|
6526
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
6352
6527
|
function resolveDefaultCliPath2() {
|
|
6353
|
-
return
|
|
6528
|
+
return path37.join(fileURLToPath3(new URL(".", import.meta.url)), "..", "cli.js");
|
|
6354
6529
|
}
|
|
6355
6530
|
function spawnMonitorSidecar(opts) {
|
|
6356
6531
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
6357
|
-
if (!
|
|
6532
|
+
if (!existsSync19(cliPath)) return void 0;
|
|
6358
6533
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
6359
6534
|
const { harnessRoot } = getHarnessPaths();
|
|
6360
|
-
const logPath =
|
|
6535
|
+
const logPath = path37.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
6361
6536
|
let logFd;
|
|
6362
6537
|
try {
|
|
6363
6538
|
logFd = openSync4(logPath, "a");
|
|
@@ -6477,22 +6652,22 @@ async function monitorTickCli(args) {
|
|
|
6477
6652
|
}
|
|
6478
6653
|
|
|
6479
6654
|
// src/package-version.ts
|
|
6480
|
-
import { existsSync as
|
|
6655
|
+
import { existsSync as existsSync20, readFileSync as readFileSync9 } from "node:fs";
|
|
6481
6656
|
import { dirname, join } from "node:path";
|
|
6482
|
-
import { fileURLToPath as
|
|
6657
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
6483
6658
|
function resolvePackageRoot(moduleUrl) {
|
|
6484
|
-
let dir = dirname(
|
|
6659
|
+
let dir = dirname(fileURLToPath4(moduleUrl));
|
|
6485
6660
|
for (let depth = 0; depth < 6; depth += 1) {
|
|
6486
|
-
if (
|
|
6661
|
+
if (existsSync20(join(dir, "package.json"))) return dir;
|
|
6487
6662
|
const parent = dirname(dir);
|
|
6488
6663
|
if (parent === dir) break;
|
|
6489
6664
|
dir = parent;
|
|
6490
6665
|
}
|
|
6491
|
-
throw new Error(`package.json not found above ${dirname(
|
|
6666
|
+
throw new Error(`package.json not found above ${dirname(fileURLToPath4(moduleUrl))}`);
|
|
6492
6667
|
}
|
|
6493
6668
|
function readOwnPackageVersion(moduleUrl = import.meta.url) {
|
|
6494
6669
|
const pkgPath = join(resolvePackageRoot(moduleUrl), "package.json");
|
|
6495
|
-
const pkg = JSON.parse(
|
|
6670
|
+
const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
|
|
6496
6671
|
if (typeof pkg.version !== "string" || !pkg.version.trim()) {
|
|
6497
6672
|
throw new Error(`Missing package.json version at ${pkgPath}`);
|
|
6498
6673
|
}
|
|
@@ -6513,12 +6688,12 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
|
|
|
6513
6688
|
}
|
|
6514
6689
|
|
|
6515
6690
|
// src/doctor/runtime-takeover.ts
|
|
6516
|
-
import
|
|
6691
|
+
import path39 from "node:path";
|
|
6517
6692
|
|
|
6518
6693
|
// src/doctor/runtime-takeover.probes.ts
|
|
6519
|
-
import { accessSync, constants, existsSync as
|
|
6520
|
-
import { homedir as
|
|
6521
|
-
import
|
|
6694
|
+
import { accessSync, constants, existsSync as existsSync21, readFileSync as readFileSync10 } from "node:fs";
|
|
6695
|
+
import { homedir as homedir7 } from "node:os";
|
|
6696
|
+
import path38 from "node:path";
|
|
6522
6697
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
6523
6698
|
function captureCommand(bin, args) {
|
|
6524
6699
|
try {
|
|
@@ -6547,7 +6722,7 @@ function tokenPrefix(token) {
|
|
|
6547
6722
|
return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
|
|
6548
6723
|
}
|
|
6549
6724
|
function isWritable(target) {
|
|
6550
|
-
if (!
|
|
6725
|
+
if (!existsSync21(target)) return false;
|
|
6551
6726
|
try {
|
|
6552
6727
|
accessSync(target, constants.W_OK);
|
|
6553
6728
|
return true;
|
|
@@ -6560,15 +6735,15 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
6560
6735
|
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
6561
6736
|
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
6562
6737
|
loadConfig: () => loadUserConfig(),
|
|
6563
|
-
configFilePath: () =>
|
|
6564
|
-
credentialsFilePath: () =>
|
|
6738
|
+
configFilePath: () => path38.join(homedir7(), ".kynver", "config.json"),
|
|
6739
|
+
credentialsFilePath: () => path38.join(homedir7(), ".kynver", "credentials"),
|
|
6565
6740
|
readCredentials: () => {
|
|
6566
|
-
const credPath =
|
|
6567
|
-
if (!
|
|
6741
|
+
const credPath = path38.join(homedir7(), ".kynver", "credentials");
|
|
6742
|
+
if (!existsSync21(credPath)) {
|
|
6568
6743
|
return { hasApiKey: false };
|
|
6569
6744
|
}
|
|
6570
6745
|
try {
|
|
6571
|
-
const parsed = JSON.parse(
|
|
6746
|
+
const parsed = JSON.parse(readFileSync10(credPath, "utf8"));
|
|
6572
6747
|
return {
|
|
6573
6748
|
hasApiKey: Boolean(parsed.apiKey?.trim()),
|
|
6574
6749
|
runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
|
|
@@ -6586,20 +6761,119 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
6586
6761
|
openclawCronSecret: Boolean(process.env.OPENCLAW_CRON_SECRET?.trim()),
|
|
6587
6762
|
kynverHarnessRoot: process.env.KYNVER_HARNESS_ROOT?.trim() || void 0,
|
|
6588
6763
|
opusHarnessRoot: process.env.OPUS_HARNESS_ROOT?.trim() || void 0,
|
|
6589
|
-
kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0
|
|
6764
|
+
kynverSchedulerProvider: process.env.KYNVER_SCHEDULER_PROVIDER?.trim() || void 0,
|
|
6765
|
+
qstashTokenPresent: Boolean(process.env.QSTASH_TOKEN?.trim()),
|
|
6766
|
+
kynverHostedDeployment: (() => {
|
|
6767
|
+
const v = process.env.KYNVER_HOSTED_DEPLOYMENT?.trim().toLowerCase();
|
|
6768
|
+
return v === "1" || v === "true" || v === "yes";
|
|
6769
|
+
})()
|
|
6590
6770
|
}),
|
|
6591
6771
|
harnessRoot: () => resolveHarnessRoot(),
|
|
6592
|
-
legacyOpenclawHarnessRoot: () =>
|
|
6593
|
-
pathExists: (target) =>
|
|
6772
|
+
legacyOpenclawHarnessRoot: () => path38.join(homedir7(), ".openclaw", "harness"),
|
|
6773
|
+
pathExists: (target) => existsSync21(target),
|
|
6594
6774
|
pathWritable: (target) => isWritable(target),
|
|
6595
6775
|
vercelVersion: () => captureCommand("vercel", ["--version"]),
|
|
6596
6776
|
vercelWhoami: () => captureCommand("vercel", ["whoami"])
|
|
6597
6777
|
};
|
|
6598
6778
|
|
|
6599
|
-
// src/doctor/runtime-takeover.ts
|
|
6779
|
+
// src/doctor/runtime-takeover-scheduler.ts
|
|
6600
6780
|
function check(partial) {
|
|
6601
6781
|
return partial;
|
|
6602
6782
|
}
|
|
6783
|
+
function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
6784
|
+
const runnerOpenclaw = env.kynverSchedulerProvider === "openclaw-cron";
|
|
6785
|
+
const runnerQstash = env.kynverSchedulerProvider === "qstash";
|
|
6786
|
+
const hostedDeployment = Boolean(env.qstashTokenPresent) || Boolean(env.kynverHostedDeployment);
|
|
6787
|
+
const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
|
|
6788
|
+
const hostedSchedulerProcess = hostedDeployment && !daemonDispatchReady;
|
|
6789
|
+
const deploymentNeedsQstash = hostedSchedulerProcess && !env.qstashTokenPresent && env.kynverSchedulerProvider !== "qstash";
|
|
6790
|
+
const deploymentOpenclaw = hostedSchedulerProcess && env.kynverSchedulerProvider === "openclaw-cron";
|
|
6791
|
+
if (daemonDispatchReady && runnerOpenclaw) {
|
|
6792
|
+
return check({
|
|
6793
|
+
id: "hotspot_openclaw_scheduler",
|
|
6794
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6795
|
+
status: "warn",
|
|
6796
|
+
summary: "KYNVER_SCHEDULER_PROVIDER=openclaw-cron on this runner \u2014 dispatch is owned by kynver daemon; unset the OpenClaw override",
|
|
6797
|
+
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.",
|
|
6798
|
+
details: {
|
|
6799
|
+
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
6800
|
+
dispatchPath: "kynver-daemon-pipeline-tick",
|
|
6801
|
+
hostedDeployment
|
|
6802
|
+
}
|
|
6803
|
+
});
|
|
6804
|
+
}
|
|
6805
|
+
if (daemonDispatchReady) {
|
|
6806
|
+
return check({
|
|
6807
|
+
id: "hotspot_openclaw_scheduler",
|
|
6808
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6809
|
+
status: "pass",
|
|
6810
|
+
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",
|
|
6811
|
+
details: {
|
|
6812
|
+
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
6813
|
+
dispatchPath: "kynver-daemon-pipeline-tick",
|
|
6814
|
+
hostedDeployment
|
|
6815
|
+
}
|
|
6816
|
+
});
|
|
6817
|
+
}
|
|
6818
|
+
if (runnerOpenclaw) {
|
|
6819
|
+
return check({
|
|
6820
|
+
id: "hotspot_openclaw_scheduler",
|
|
6821
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6822
|
+
status: "warn",
|
|
6823
|
+
summary: "KYNVER_SCHEDULER_PROVIDER=openclaw-cron on this runner \u2014 hosted dispatch still depends on the OpenClaw local-cron adapter",
|
|
6824
|
+
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.",
|
|
6825
|
+
details: { schedulerProvider: env.kynverSchedulerProvider ?? null, hostedDeployment }
|
|
6826
|
+
});
|
|
6827
|
+
}
|
|
6828
|
+
if (deploymentOpenclaw || deploymentNeedsQstash) {
|
|
6829
|
+
return check({
|
|
6830
|
+
id: "hotspot_openclaw_scheduler",
|
|
6831
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6832
|
+
status: "warn",
|
|
6833
|
+
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",
|
|
6834
|
+
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.",
|
|
6835
|
+
details: {
|
|
6836
|
+
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
6837
|
+
qstashTokenPresent: env.qstashTokenPresent ?? false,
|
|
6838
|
+
hostedDeployment
|
|
6839
|
+
}
|
|
6840
|
+
});
|
|
6841
|
+
}
|
|
6842
|
+
if (hostedDeployment && env.qstashTokenPresent && !env.kynverSchedulerProvider) {
|
|
6843
|
+
return check({
|
|
6844
|
+
id: "hotspot_openclaw_scheduler",
|
|
6845
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6846
|
+
status: "pass",
|
|
6847
|
+
summary: "QSTASH_TOKEN present; hosted scheduler auto-selects qstash (explicit KYNVER_SCHEDULER_PROVIDER=qstash optional)",
|
|
6848
|
+
details: {
|
|
6849
|
+
schedulerProvider: null,
|
|
6850
|
+
qstashTokenPresent: true,
|
|
6851
|
+
hostedDeployment
|
|
6852
|
+
}
|
|
6853
|
+
});
|
|
6854
|
+
}
|
|
6855
|
+
if (!env.kynverSchedulerProvider) {
|
|
6856
|
+
return check({
|
|
6857
|
+
id: "hotspot_openclaw_scheduler",
|
|
6858
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6859
|
+
status: "pass",
|
|
6860
|
+
summary: "No KYNVER_SCHEDULER_PROVIDER on runner (expected) \u2014 finish runner setup so daemon pipeline-tick owns dispatch",
|
|
6861
|
+
details: { schedulerProvider: null, dispatchPath: "kynver-daemon-pipeline-tick-pending" }
|
|
6862
|
+
});
|
|
6863
|
+
}
|
|
6864
|
+
return check({
|
|
6865
|
+
id: "hotspot_openclaw_scheduler",
|
|
6866
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6867
|
+
status: "pass",
|
|
6868
|
+
summary: `KYNVER_SCHEDULER_PROVIDER=${env.kynverSchedulerProvider}`,
|
|
6869
|
+
details: { schedulerProvider: env.kynverSchedulerProvider ?? null }
|
|
6870
|
+
});
|
|
6871
|
+
}
|
|
6872
|
+
|
|
6873
|
+
// src/doctor/runtime-takeover.ts
|
|
6874
|
+
function check2(partial) {
|
|
6875
|
+
return partial;
|
|
6876
|
+
}
|
|
6603
6877
|
function summarizeCounts(sections) {
|
|
6604
6878
|
const counts = { pass: 0, warn: 0, fail: 0 };
|
|
6605
6879
|
for (const section of sections) {
|
|
@@ -6616,14 +6890,14 @@ function assessCliPackage(probes) {
|
|
|
6616
6890
|
const firstPath = onPath ? which.stdout.split(/\r?\n/)[0]?.trim() : void 0;
|
|
6617
6891
|
const displayCliPath = firstPath ? displayUserPath(firstPath) : void 0;
|
|
6618
6892
|
const checks = [
|
|
6619
|
-
|
|
6893
|
+
check2({
|
|
6620
6894
|
id: "cli_running_version",
|
|
6621
6895
|
label: "Running @kynver-app/runtime version",
|
|
6622
6896
|
status: "pass",
|
|
6623
6897
|
summary: `@kynver-app/runtime ${runningVersion}`,
|
|
6624
6898
|
details: { version: runningVersion }
|
|
6625
6899
|
}),
|
|
6626
|
-
|
|
6900
|
+
check2({
|
|
6627
6901
|
id: "cli_on_path",
|
|
6628
6902
|
label: "kynver executable on PATH",
|
|
6629
6903
|
status: onPath ? "pass" : "warn",
|
|
@@ -6637,7 +6911,7 @@ function assessCliPackage(probes) {
|
|
|
6637
6911
|
const installedVersion = versionProbe.stdout.replace(/^kynver\s+/i, "").trim() || void 0;
|
|
6638
6912
|
const versionMatch = versionProbe.ok && (!installedVersion || installedVersion === runningVersion);
|
|
6639
6913
|
checks.push(
|
|
6640
|
-
|
|
6914
|
+
check2({
|
|
6641
6915
|
id: "cli_installed_version",
|
|
6642
6916
|
label: "Installed kynver CLI version matches running package",
|
|
6643
6917
|
status: versionMatch ? "pass" : "warn",
|
|
@@ -6655,7 +6929,7 @@ function assessUserConfig(probes) {
|
|
|
6655
6929
|
const exists = probes.pathExists(configPath);
|
|
6656
6930
|
const config = probes.loadConfig();
|
|
6657
6931
|
const checks = [
|
|
6658
|
-
|
|
6932
|
+
check2({
|
|
6659
6933
|
id: "config_file",
|
|
6660
6934
|
label: "~/.kynver/config.json present",
|
|
6661
6935
|
status: exists ? "pass" : "fail",
|
|
@@ -6667,10 +6941,16 @@ function assessUserConfig(probes) {
|
|
|
6667
6941
|
if (exists) {
|
|
6668
6942
|
const apiBaseUrl = config.apiBaseUrl?.trim();
|
|
6669
6943
|
const agentOsId = config.agentOsId?.trim();
|
|
6670
|
-
const
|
|
6671
|
-
const
|
|
6944
|
+
const resolvedDefaultRepo = resolveDefaultRepo({ config });
|
|
6945
|
+
const formatted = resolvedDefaultRepo ? formatResolvedDefaultRepo(resolvedDefaultRepo) : null;
|
|
6946
|
+
let defaultRepoRemediation;
|
|
6947
|
+
if (!resolvedDefaultRepo) {
|
|
6948
|
+
defaultRepoRemediation = "Run `kynver setup` from a Kynver checkout (auto-discovers repo) or `kynver setup --repo /path/to/Kynver`.";
|
|
6949
|
+
} else if (!resolvedDefaultRepo.persistedInConfig) {
|
|
6950
|
+
defaultRepoRemediation = "Run `kynver setup` (no --repo) to persist discovered defaultRepo in ~/.kynver/config.json.";
|
|
6951
|
+
}
|
|
6672
6952
|
checks.push(
|
|
6673
|
-
|
|
6953
|
+
check2({
|
|
6674
6954
|
id: "config_api_base_url",
|
|
6675
6955
|
label: "Default API base URL",
|
|
6676
6956
|
status: apiBaseUrl ? "pass" : "warn",
|
|
@@ -6678,7 +6958,7 @@ function assessUserConfig(probes) {
|
|
|
6678
6958
|
remediation: apiBaseUrl ? void 0 : "Set `apiBaseUrl` via `kynver setup --api-base-url https://\u2026`.",
|
|
6679
6959
|
details: { apiBaseUrl: apiBaseUrl ?? null }
|
|
6680
6960
|
}),
|
|
6681
|
-
|
|
6961
|
+
check2({
|
|
6682
6962
|
id: "config_agent_os_id",
|
|
6683
6963
|
label: "Default AgentOS id",
|
|
6684
6964
|
status: agentOsId ? "pass" : "warn",
|
|
@@ -6686,14 +6966,16 @@ function assessUserConfig(probes) {
|
|
|
6686
6966
|
remediation: agentOsId ? void 0 : "Set `agentOsId` via `kynver setup --agent-os-id <uuid>`.",
|
|
6687
6967
|
details: { agentOsId: agentOsId ?? null, agentOsSlug: config.agentOsSlug ?? null }
|
|
6688
6968
|
}),
|
|
6689
|
-
|
|
6969
|
+
check2({
|
|
6690
6970
|
id: "config_default_repo",
|
|
6691
6971
|
label: "Default repo path",
|
|
6692
|
-
status:
|
|
6693
|
-
summary:
|
|
6694
|
-
remediation:
|
|
6972
|
+
status: resolvedDefaultRepo ? "pass" : "warn",
|
|
6973
|
+
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)",
|
|
6974
|
+
remediation: defaultRepoRemediation,
|
|
6695
6975
|
details: {
|
|
6696
|
-
defaultRepo:
|
|
6976
|
+
defaultRepo: formatted?.defaultRepo ?? null,
|
|
6977
|
+
source: formatted?.source ?? null,
|
|
6978
|
+
persistedInConfig: formatted?.persistedInConfig ?? false,
|
|
6697
6979
|
harnessRoot: config.harnessRoot ? redactHomePath(config.harnessRoot) : null
|
|
6698
6980
|
}
|
|
6699
6981
|
})
|
|
@@ -6714,7 +6996,7 @@ function assessRunnerToken(probes) {
|
|
|
6714
6996
|
const scopedSaved = Boolean(savedToken) && (!targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId);
|
|
6715
6997
|
const hasScoped = Boolean(envToken?.startsWith("krc1.")) || scopedSaved && savedToken?.startsWith("krc1.");
|
|
6716
6998
|
const checks = [
|
|
6717
|
-
|
|
6999
|
+
check2({
|
|
6718
7000
|
id: "runner_token_scoped",
|
|
6719
7001
|
label: "Scoped runner token (krc1.*) ready",
|
|
6720
7002
|
status: hasScoped ? "pass" : "fail",
|
|
@@ -6727,7 +7009,7 @@ function assessRunnerToken(probes) {
|
|
|
6727
7009
|
credentialsPath: displayCredPath
|
|
6728
7010
|
}
|
|
6729
7011
|
}),
|
|
6730
|
-
|
|
7012
|
+
check2({
|
|
6731
7013
|
id: "runner_token_agent_os_match",
|
|
6732
7014
|
label: "Saved runner token matches configured agentOsId",
|
|
6733
7015
|
status: !savedToken || !targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId ? "pass" : "warn",
|
|
@@ -6735,7 +7017,7 @@ function assessRunnerToken(probes) {
|
|
|
6735
7017
|
remediation: savedToken && targetAgentOsId && savedAgentOsId && savedAgentOsId !== targetAgentOsId ? "`kynver runner credential --agent-os-id <configured-id>` to mint a workspace-bound token." : void 0,
|
|
6736
7018
|
details: { configuredAgentOsId: targetAgentOsId ?? null, savedAgentOsId: savedAgentOsId ?? null }
|
|
6737
7019
|
}),
|
|
6738
|
-
|
|
7020
|
+
check2({
|
|
6739
7021
|
id: "runner_api_key_for_refresh",
|
|
6740
7022
|
label: "API key available for token refresh",
|
|
6741
7023
|
status: creds.hasApiKey || Boolean(process.env.KYNVER_API_KEY?.trim()) ? "pass" : "warn",
|
|
@@ -6754,7 +7036,7 @@ function assessVercelCli(probes) {
|
|
|
6754
7036
|
id: "vercel_cli",
|
|
6755
7037
|
label: "Vercel CLI",
|
|
6756
7038
|
checks: [
|
|
6757
|
-
|
|
7039
|
+
check2({
|
|
6758
7040
|
id: "vercel_installed",
|
|
6759
7041
|
label: "Vercel CLI installed",
|
|
6760
7042
|
status: installed ? "pass" : "warn",
|
|
@@ -6762,7 +7044,7 @@ function assessVercelCli(probes) {
|
|
|
6762
7044
|
remediation: installed ? void 0 : "Install Vercel CLI (`npm i -g vercel`) for deploy evidence and env pulls.",
|
|
6763
7045
|
details: { stderr: version.stderr || null }
|
|
6764
7046
|
}),
|
|
6765
|
-
|
|
7047
|
+
check2({
|
|
6766
7048
|
id: "vercel_authenticated",
|
|
6767
7049
|
label: "Vercel CLI authenticated",
|
|
6768
7050
|
status: !installed ? "warn" : whoami.ok ? "pass" : "warn",
|
|
@@ -6775,8 +7057,8 @@ function assessVercelCli(probes) {
|
|
|
6775
7057
|
}
|
|
6776
7058
|
function assessHarnessDirs(probes) {
|
|
6777
7059
|
const harnessRoot = probes.harnessRoot();
|
|
6778
|
-
const runsDir =
|
|
6779
|
-
const worktreesDir =
|
|
7060
|
+
const runsDir = path39.join(harnessRoot, "runs");
|
|
7061
|
+
const worktreesDir = path39.join(harnessRoot, "worktrees");
|
|
6780
7062
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
6781
7063
|
const displayRunsDir = redactHomePath(runsDir);
|
|
6782
7064
|
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
@@ -6786,14 +7068,14 @@ function assessHarnessDirs(probes) {
|
|
|
6786
7068
|
id: "harness_dirs",
|
|
6787
7069
|
label: "Harness / daemon directories",
|
|
6788
7070
|
checks: [
|
|
6789
|
-
|
|
7071
|
+
check2({
|
|
6790
7072
|
id: "harness_root",
|
|
6791
7073
|
label: "Harness root resolved",
|
|
6792
7074
|
status: "pass",
|
|
6793
7075
|
summary: displayHarnessRoot,
|
|
6794
7076
|
details: { harnessRoot: displayHarnessRoot }
|
|
6795
7077
|
}),
|
|
6796
|
-
|
|
7078
|
+
check2({
|
|
6797
7079
|
id: "harness_runs_dir",
|
|
6798
7080
|
label: "Runs directory ready",
|
|
6799
7081
|
status: runsExists && probes.pathWritable(runsDir) ? "pass" : runsExists ? "warn" : "warn",
|
|
@@ -6801,7 +7083,7 @@ function assessHarnessDirs(probes) {
|
|
|
6801
7083
|
remediation: runsExists && !probes.pathWritable(runsDir) ? `Fix permissions on ${displayRunsDir} or set KYNVER_HARNESS_ROOT to a writable path.` : void 0,
|
|
6802
7084
|
details: { runsDir: displayRunsDir, exists: runsExists, writable: probes.pathWritable(runsDir) }
|
|
6803
7085
|
}),
|
|
6804
|
-
|
|
7086
|
+
check2({
|
|
6805
7087
|
id: "harness_worktrees_dir",
|
|
6806
7088
|
label: "Worktrees directory ready",
|
|
6807
7089
|
status: worktreesExists && probes.pathWritable(worktreesDir) ? "pass" : "warn",
|
|
@@ -6822,7 +7104,7 @@ function assessCallbackAuth(probes) {
|
|
|
6822
7104
|
const creds = probes.readCredentials();
|
|
6823
7105
|
const savedScoped = creds.runnerTokenPrefix?.startsWith("krc1.");
|
|
6824
7106
|
const checks = [
|
|
6825
|
-
|
|
7107
|
+
check2({
|
|
6826
7108
|
id: "callback_base_url",
|
|
6827
7109
|
label: "Callback base URL configured",
|
|
6828
7110
|
status: baseUrl ? usingLegacyBase ? "warn" : "pass" : "fail",
|
|
@@ -6833,7 +7115,7 @@ function assessCallbackAuth(probes) {
|
|
|
6833
7115
|
baseUrl: baseUrl ?? null
|
|
6834
7116
|
}
|
|
6835
7117
|
}),
|
|
6836
|
-
|
|
7118
|
+
check2({
|
|
6837
7119
|
id: "callback_auth_mode",
|
|
6838
7120
|
label: "Callback auth uses scoped runner token",
|
|
6839
7121
|
status: envScoped || savedScoped ? "pass" : legacySecret ? "warn" : "fail",
|
|
@@ -6849,15 +7131,22 @@ function assessCallbackAuth(probes) {
|
|
|
6849
7131
|
}
|
|
6850
7132
|
function assessOpenclawHotspots(probes) {
|
|
6851
7133
|
const env = probes.envSnapshot();
|
|
7134
|
+
const config = probes.loadConfig();
|
|
7135
|
+
const creds = probes.readCredentials();
|
|
6852
7136
|
const harnessRoot = probes.harnessRoot();
|
|
6853
7137
|
const legacyRoot = probes.legacyOpenclawHarnessRoot();
|
|
6854
7138
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
6855
7139
|
const displayLegacyRoot = redactHomePath(legacyRoot);
|
|
6856
7140
|
const displayOpusHarnessRoot = env.opusHarnessRoot ? redactHomePath(env.opusHarnessRoot) : null;
|
|
6857
7141
|
const legacyHarnessActive = harnessRoot === legacyRoot && probes.pathExists(legacyRoot);
|
|
6858
|
-
const
|
|
7142
|
+
const targetAgentOsId = config.agentOsId?.trim();
|
|
7143
|
+
const envToken = env.kynverRunnerTokenPrefix;
|
|
7144
|
+
const savedToken = creds.runnerTokenPrefix;
|
|
7145
|
+
const savedAgentOsId = creds.runnerTokenAgentOsId;
|
|
7146
|
+
const scopedSaved = Boolean(savedToken) && (!targetAgentOsId || !savedAgentOsId || savedAgentOsId === targetAgentOsId);
|
|
7147
|
+
const hasScopedRunnerToken = Boolean(envToken?.startsWith("krc1.")) || scopedSaved && Boolean(savedToken?.startsWith("krc1."));
|
|
6859
7148
|
const checks = [
|
|
6860
|
-
|
|
7149
|
+
check2({
|
|
6861
7150
|
id: "hotspot_legacy_harness_root",
|
|
6862
7151
|
label: "Legacy ~/.openclaw/harness still active",
|
|
6863
7152
|
status: legacyHarnessActive ? "warn" : "pass",
|
|
@@ -6865,7 +7154,7 @@ function assessOpenclawHotspots(probes) {
|
|
|
6865
7154
|
remediation: legacyHarnessActive ? "Set KYNVER_HARNESS_ROOT=~/.kynver/harness (or run setup), migrate artifacts, retire OPUS_HARNESS_ROOT." : env.opusHarnessRoot ? "Prefer KYNVER_HARNESS_ROOT over OPUS_HARNESS_ROOT." : void 0,
|
|
6866
7155
|
details: { harnessRoot: displayHarnessRoot, legacyRoot: displayLegacyRoot, opusHarnessRoot: displayOpusHarnessRoot }
|
|
6867
7156
|
}),
|
|
6868
|
-
|
|
7157
|
+
check2({
|
|
6869
7158
|
id: "hotspot_openclaw_env_secrets",
|
|
6870
7159
|
label: "OpenClaw deployment secrets in runner env",
|
|
6871
7160
|
status: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "warn" : "pass",
|
|
@@ -6875,15 +7164,12 @@ function assessOpenclawHotspots(probes) {
|
|
|
6875
7164
|
].filter(Boolean).join("; ") : "No OpenClaw cron env overrides on this runner",
|
|
6876
7165
|
remediation: env.openclawCronSecret || env.openclawCronFireBaseUrl ? "Move to KYNVER_API_URL + scoped runner tokens; unset OpenClaw cron env on user-hosted runners." : void 0
|
|
6877
7166
|
}),
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
summary: schedulerOpenclaw ? env.kynverSchedulerProvider === "openclaw-cron" ? "KYNVER_SCHEDULER_PROVIDER=openclaw-cron \u2014 AgentOS ticks still routed via OpenClaw local cron adapter" : "KYNVER_SCHEDULER_PROVIDER unset \u2014 server may fall back to openclaw-cron when QStash is absent" : `KYNVER_SCHEDULER_PROVIDER=${env.kynverSchedulerProvider}`,
|
|
6883
|
-
remediation: schedulerOpenclaw ? "On Kynver-hosted scheduler: set KYNVER_SCHEDULER_PROVIDER=qstash where QStash is configured; retire openclaw-cron stub when runtime daemon owns dispatch." : void 0,
|
|
6884
|
-
details: { schedulerProvider: env.kynverSchedulerProvider ?? null }
|
|
7167
|
+
assessRuntimeTakeoverScheduler(env, {
|
|
7168
|
+
agentOsId: targetAgentOsId ?? null,
|
|
7169
|
+
apiBaseUrl: config.apiBaseUrl?.trim() ?? env.kynverApiUrl ?? null,
|
|
7170
|
+
hasScopedRunnerToken
|
|
6885
7171
|
}),
|
|
6886
|
-
|
|
7172
|
+
check2({
|
|
6887
7173
|
id: "hotspot_lease_source_names",
|
|
6888
7174
|
label: "Harness lease/completion source names",
|
|
6889
7175
|
status: "pass",
|
|
@@ -6975,9 +7261,9 @@ function usage(code = 0) {
|
|
|
6975
7261
|
"Usage:",
|
|
6976
7262
|
" kynver login --api-key KEY",
|
|
6977
7263
|
" kynver runner credential [--agent-os-id ID] [--base-url URL]",
|
|
6978
|
-
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--max-workers N] [--provider claude|cursor]",
|
|
7264
|
+
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--discover-repo] [--max-workers N] [--provider claude|cursor]",
|
|
6979
7265
|
" kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS]",
|
|
6980
|
-
" kynver run create --repo /path/repo [--name name] [--base origin/main]",
|
|
7266
|
+
" kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
|
|
6981
7267
|
" kynver run list",
|
|
6982
7268
|
" kynver run status --run RUN_ID",
|
|
6983
7269
|
" 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 /]",
|
|
@@ -7070,7 +7356,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
7070
7356
|
if (scope === "monitor" && action === "run-loop") return void await monitorRunLoopCli(args);
|
|
7071
7357
|
unknownCommand(scope, action);
|
|
7072
7358
|
}
|
|
7073
|
-
var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(
|
|
7359
|
+
var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(fileURLToPath5(import.meta.url));
|
|
7074
7360
|
if (isCliEntry) {
|
|
7075
7361
|
void main().catch((error) => {
|
|
7076
7362
|
console.error(error);
|