@kynver-app/runtime 0.1.49 → 0.1.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cleanup-guards.d.ts +3 -0
- package/dist/cleanup-retention-config.d.ts +14 -0
- package/dist/cleanup-run-liveness.d.ts +13 -0
- package/dist/cleanup-types.d.ts +12 -0
- package/dist/cleanup.d.ts +1 -1
- package/dist/cli.js +795 -546
- package/dist/cli.js.map +4 -4
- package/dist/default-repo-discovery.d.ts +15 -0
- package/dist/default-repo.d.ts +30 -0
- package/dist/finalize.d.ts +9 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +827 -541
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/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;
|
|
@@ -1016,224 +1255,59 @@ function classifyExitFailure(errorText) {
|
|
|
1016
1255
|
|
|
1017
1256
|
// src/exited-salvage.ts
|
|
1018
1257
|
function trimOrNull(value) {
|
|
1019
|
-
if (typeof value !== "string") return null;
|
|
1020
|
-
const trimmed = value.trim();
|
|
1021
|
-
return trimmed.length ? trimmed : null;
|
|
1022
|
-
}
|
|
1023
|
-
function hasFinalResult(value) {
|
|
1024
|
-
if (value === void 0 || value === null) return false;
|
|
1025
|
-
if (typeof value === "string") return value.trim().length > 0;
|
|
1026
|
-
if (typeof value === "boolean") return value;
|
|
1027
|
-
if (Array.isArray(value)) return value.length > 0;
|
|
1028
|
-
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
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
function gitIsAncestor(cwd, ancestor, descendant) {
|
|
1158
|
-
const res = gitCapture(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]);
|
|
1159
|
-
if (res.status === 0) return { isAncestor: true, error: null };
|
|
1160
|
-
if (res.status === 1) return { isAncestor: false, error: null };
|
|
1161
|
-
return { isAncestor: null, error: res.error || res.stderr || res.stdout || `git exited ${res.status}` };
|
|
1162
|
-
}
|
|
1163
|
-
function computeGitAncestry(worktreePath, baseOrOptions = "origin/main") {
|
|
1164
|
-
const options = typeof baseOrOptions === "string" ? { base: baseOrOptions } : baseOrOptions;
|
|
1165
|
-
const baseLabel = options.baseCommit?.trim() || options.base?.trim() || "origin/main";
|
|
1166
|
-
const pinnedBaseCommit = options.baseCommit?.trim() || null;
|
|
1167
|
-
if (!worktreePath) {
|
|
1168
|
-
return unknownAncestry(baseLabel, "missing worktree path");
|
|
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();
|
|
1258
|
+
if (typeof value !== "string") return null;
|
|
1259
|
+
const trimmed = value.trim();
|
|
1260
|
+
return trimmed.length ? trimmed : null;
|
|
1261
|
+
}
|
|
1262
|
+
function hasFinalResult(value) {
|
|
1263
|
+
if (value === void 0 || value === null) return false;
|
|
1264
|
+
if (typeof value === "string") return value.trim().length > 0;
|
|
1265
|
+
if (typeof value === "boolean") return value;
|
|
1266
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
1267
|
+
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
1268
|
+
return true;
|
|
1269
|
+
}
|
|
1270
|
+
function committedHeadFromAncestry(ancestry) {
|
|
1271
|
+
if (!ancestry?.checked) return null;
|
|
1272
|
+
if (ancestry.headIsAncestorOfBase !== false) return null;
|
|
1273
|
+
return trimOrNull(ancestry.head);
|
|
1274
|
+
}
|
|
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;
|
|
@@ -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) {
|
|
@@ -2788,7 +2862,7 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
|
|
|
2788
2862
|
}
|
|
2789
2863
|
|
|
2790
2864
|
// src/worker-lifecycle.ts
|
|
2791
|
-
import
|
|
2865
|
+
import path11 from "node:path";
|
|
2792
2866
|
var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
|
|
2793
2867
|
"awaiting_review",
|
|
2794
2868
|
"blocked",
|
|
@@ -2844,7 +2918,7 @@ function syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick) {
|
|
|
2844
2918
|
const synced = [];
|
|
2845
2919
|
for (const name of Object.keys(run.workers || {})) {
|
|
2846
2920
|
const worker = readJson(
|
|
2847
|
-
|
|
2921
|
+
path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
2848
2922
|
void 0
|
|
2849
2923
|
);
|
|
2850
2924
|
if (!worker?.taskId || isCompletionAcknowledged(worker)) continue;
|
|
@@ -3102,7 +3176,7 @@ function workerStatus(args) {
|
|
|
3102
3176
|
const worker = loadWorker(String(args.run), String(args.name));
|
|
3103
3177
|
const run = loadRun(worker.runId);
|
|
3104
3178
|
const status = computeWorkerStatus(worker, workerStatusOptions(run));
|
|
3105
|
-
writeJson(
|
|
3179
|
+
writeJson(path12.join(worker.workerDir, "last-status.json"), status);
|
|
3106
3180
|
console.log(JSON.stringify(status, null, 2));
|
|
3107
3181
|
}
|
|
3108
3182
|
function buildRunBoard(runId) {
|
|
@@ -3110,7 +3184,7 @@ function buildRunBoard(runId) {
|
|
|
3110
3184
|
const names = Object.keys(run.workers || {});
|
|
3111
3185
|
const workers = names.map((name) => {
|
|
3112
3186
|
const worker = readJson(
|
|
3113
|
-
|
|
3187
|
+
path12.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
3114
3188
|
void 0
|
|
3115
3189
|
);
|
|
3116
3190
|
if (!worker) {
|
|
@@ -3221,7 +3295,7 @@ function buildRunBoard(runId) {
|
|
|
3221
3295
|
needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
|
|
3222
3296
|
workers
|
|
3223
3297
|
};
|
|
3224
|
-
writeJson(
|
|
3298
|
+
writeJson(path12.join(runDirectory(run.id), "last-board.json"), board);
|
|
3225
3299
|
return board;
|
|
3226
3300
|
}
|
|
3227
3301
|
async function publishHarnessBoardSnapshot(args, source) {
|
|
@@ -3410,12 +3484,12 @@ async function autoCompleteWorkerCli(raw) {
|
|
|
3410
3484
|
}
|
|
3411
3485
|
}
|
|
3412
3486
|
function resolveDefaultCliPath() {
|
|
3413
|
-
return
|
|
3487
|
+
return path13.join(fileURLToPath2(new URL(".", import.meta.url)), "cli.js");
|
|
3414
3488
|
}
|
|
3415
3489
|
function spawnCompletionSidecar(opts) {
|
|
3416
3490
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath();
|
|
3417
|
-
if (!
|
|
3418
|
-
const logPath =
|
|
3491
|
+
if (!existsSync10(cliPath)) return void 0;
|
|
3492
|
+
const logPath = path13.join(opts.workerDir, "auto-complete.log");
|
|
3419
3493
|
let logFd;
|
|
3420
3494
|
try {
|
|
3421
3495
|
logFd = openSync3(logPath, "a");
|
|
@@ -3497,16 +3571,16 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3497
3571
|
launchModel = preflight.model;
|
|
3498
3572
|
}
|
|
3499
3573
|
const { worktreesDir } = getPaths();
|
|
3500
|
-
const workerDir =
|
|
3574
|
+
const workerDir = path14.join(runDirectory(run.id), "workers", name);
|
|
3501
3575
|
mkdirSync3(workerDir, { recursive: true });
|
|
3502
|
-
const worktreePath =
|
|
3576
|
+
const worktreePath = path14.join(worktreesDir, run.id, name);
|
|
3503
3577
|
const branch = opts.branch || `agent/${run.id}/${name}`;
|
|
3504
|
-
if (
|
|
3578
|
+
if (existsSync11(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
|
|
3505
3579
|
git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
|
|
3506
3580
|
git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
|
|
3507
|
-
const stdoutPath =
|
|
3508
|
-
const stderrPath =
|
|
3509
|
-
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");
|
|
3510
3584
|
const prompt = buildPrompt({
|
|
3511
3585
|
task: opts.task,
|
|
3512
3586
|
ownedPaths: opts.ownedPaths || [],
|
|
@@ -3571,7 +3645,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
3571
3645
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3572
3646
|
};
|
|
3573
3647
|
saveWorker(run.id, worker);
|
|
3574
|
-
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath:
|
|
3648
|
+
run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path14.join(workerDir, "worker.json") } };
|
|
3575
3649
|
run.status = "running";
|
|
3576
3650
|
saveRun(run);
|
|
3577
3651
|
if (worker.agentOsId && worker.taskId) {
|
|
@@ -3863,18 +3937,18 @@ function buildPlanPersistIdempotencyKey(input) {
|
|
|
3863
3937
|
|
|
3864
3938
|
// src/plan-persist/paths.ts
|
|
3865
3939
|
import { mkdirSync as mkdirSync4 } from "node:fs";
|
|
3866
|
-
import { homedir as
|
|
3867
|
-
import
|
|
3940
|
+
import { homedir as homedir5 } from "node:os";
|
|
3941
|
+
import path15 from "node:path";
|
|
3868
3942
|
function resolveKynverStateRoot() {
|
|
3869
3943
|
const env = process.env.KYNVER_STATE_ROOT;
|
|
3870
|
-
if (env) return
|
|
3871
|
-
return
|
|
3944
|
+
if (env) return path15.resolve(env);
|
|
3945
|
+
return path15.join(homedir5(), ".kynver", "state");
|
|
3872
3946
|
}
|
|
3873
3947
|
function planOutboxDir() {
|
|
3874
|
-
return
|
|
3948
|
+
return path15.join(resolveKynverStateRoot(), "plan-outbox");
|
|
3875
3949
|
}
|
|
3876
3950
|
function planOutboxArchiveDir() {
|
|
3877
|
-
return
|
|
3951
|
+
return path15.join(resolveKynverStateRoot(), "plan-outbox-archive");
|
|
3878
3952
|
}
|
|
3879
3953
|
function ensurePlanOutboxDirs() {
|
|
3880
3954
|
const outboxDir = planOutboxDir();
|
|
@@ -3885,20 +3959,20 @@ function ensurePlanOutboxDirs() {
|
|
|
3885
3959
|
}
|
|
3886
3960
|
function isTmpOnlyPath(filePath) {
|
|
3887
3961
|
if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
|
|
3888
|
-
const resolved =
|
|
3889
|
-
return resolved.startsWith("/tmp/") || resolved.startsWith(
|
|
3962
|
+
const resolved = path15.resolve(filePath);
|
|
3963
|
+
return resolved.startsWith("/tmp/") || resolved.startsWith(path15.join("/var", "folders"));
|
|
3890
3964
|
}
|
|
3891
3965
|
|
|
3892
3966
|
// src/plan-persist/outbox-store.ts
|
|
3893
3967
|
import {
|
|
3894
|
-
existsSync as
|
|
3895
|
-
readFileSync as
|
|
3968
|
+
existsSync as existsSync13,
|
|
3969
|
+
readFileSync as readFileSync7,
|
|
3896
3970
|
renameSync,
|
|
3897
3971
|
readdirSync as readdirSync4,
|
|
3898
3972
|
writeFileSync as writeFileSync3,
|
|
3899
3973
|
unlinkSync
|
|
3900
3974
|
} from "node:fs";
|
|
3901
|
-
import
|
|
3975
|
+
import path16 from "node:path";
|
|
3902
3976
|
import { randomUUID } from "node:crypto";
|
|
3903
3977
|
var DEFAULT_MAX_RETRIES = 12;
|
|
3904
3978
|
function listOutboxItems() {
|
|
@@ -3906,7 +3980,7 @@ function listOutboxItems() {
|
|
|
3906
3980
|
const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
|
|
3907
3981
|
const items = [];
|
|
3908
3982
|
for (const file of files) {
|
|
3909
|
-
const item = readOutboxItem(
|
|
3983
|
+
const item = readOutboxItem(path16.join(outboxDir, file));
|
|
3910
3984
|
if (item && item.queueStatus === "queued") items.push(item);
|
|
3911
3985
|
}
|
|
3912
3986
|
return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
@@ -3918,25 +3992,25 @@ function findOutboxByIdempotencyKey(key) {
|
|
|
3918
3992
|
return null;
|
|
3919
3993
|
}
|
|
3920
3994
|
function readOutboxItem(jsonPath) {
|
|
3921
|
-
if (!
|
|
3995
|
+
if (!existsSync13(jsonPath)) return null;
|
|
3922
3996
|
try {
|
|
3923
|
-
return JSON.parse(
|
|
3997
|
+
return JSON.parse(readFileSync7(jsonPath, "utf8"));
|
|
3924
3998
|
} catch {
|
|
3925
3999
|
return null;
|
|
3926
4000
|
}
|
|
3927
4001
|
}
|
|
3928
4002
|
function readOutboxBody(item) {
|
|
3929
4003
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3930
|
-
const bodyFile =
|
|
3931
|
-
return
|
|
4004
|
+
const bodyFile = path16.join(outboxDir, item.bodyPath);
|
|
4005
|
+
return readFileSync7(bodyFile, "utf8");
|
|
3932
4006
|
}
|
|
3933
4007
|
function writeOutboxItem(input, opts) {
|
|
3934
4008
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3935
4009
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3936
4010
|
const id = opts.existing?.id ?? randomUUID();
|
|
3937
4011
|
const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
|
|
3938
|
-
const jsonPath =
|
|
3939
|
-
const bodyFile =
|
|
4012
|
+
const jsonPath = path16.join(outboxDir, `${id}.json`);
|
|
4013
|
+
const bodyFile = path16.join(outboxDir, bodyPath);
|
|
3940
4014
|
if (!opts.existing) {
|
|
3941
4015
|
writeFileSync3(bodyFile, input.body, "utf8");
|
|
3942
4016
|
}
|
|
@@ -3972,24 +4046,24 @@ function writeOutboxItem(input, opts) {
|
|
|
3972
4046
|
}
|
|
3973
4047
|
function saveOutboxItem(item) {
|
|
3974
4048
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3975
|
-
const jsonPath =
|
|
4049
|
+
const jsonPath = path16.join(outboxDir, `${item.id}.json`);
|
|
3976
4050
|
writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
|
|
3977
4051
|
`, { mode: 384 });
|
|
3978
4052
|
}
|
|
3979
4053
|
function archiveOutboxItem(item) {
|
|
3980
4054
|
const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
|
|
3981
|
-
const jsonSrc =
|
|
3982
|
-
const bodySrc =
|
|
3983
|
-
const jsonDst =
|
|
3984
|
-
const bodyDst =
|
|
3985
|
-
if (
|
|
3986
|
-
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);
|
|
3987
4061
|
}
|
|
3988
4062
|
function outboxItemPaths(item) {
|
|
3989
4063
|
const { outboxDir } = ensurePlanOutboxDirs();
|
|
3990
4064
|
return {
|
|
3991
|
-
jsonPath:
|
|
3992
|
-
bodyPath:
|
|
4065
|
+
jsonPath: path16.join(outboxDir, `${item.id}.json`),
|
|
4066
|
+
bodyPath: path16.join(outboxDir, item.bodyPath)
|
|
3993
4067
|
};
|
|
3994
4068
|
}
|
|
3995
4069
|
function outboxInputFromItem(item, body) {
|
|
@@ -4175,7 +4249,7 @@ function markOutboxFailed(item, message) {
|
|
|
4175
4249
|
}
|
|
4176
4250
|
|
|
4177
4251
|
// src/plan-persist/drain.ts
|
|
4178
|
-
import
|
|
4252
|
+
import path17 from "node:path";
|
|
4179
4253
|
async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
4180
4254
|
const items = listOutboxItems().filter(
|
|
4181
4255
|
(item) => opts.outboxId ? item.id === opts.outboxId : true
|
|
@@ -4209,7 +4283,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
|
|
|
4209
4283
|
return result;
|
|
4210
4284
|
}
|
|
4211
4285
|
function loadOutboxById(outboxId) {
|
|
4212
|
-
const jsonPath =
|
|
4286
|
+
const jsonPath = path17.join(planOutboxDir(), `${outboxId}.json`);
|
|
4213
4287
|
return readOutboxItem(jsonPath);
|
|
4214
4288
|
}
|
|
4215
4289
|
|
|
@@ -4309,7 +4383,7 @@ async function dispatchRun(args) {
|
|
|
4309
4383
|
const activeHarnessWorkers = [];
|
|
4310
4384
|
for (const name of Object.keys(run.workers || {})) {
|
|
4311
4385
|
const worker = readJson(
|
|
4312
|
-
|
|
4386
|
+
path18.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4313
4387
|
void 0
|
|
4314
4388
|
);
|
|
4315
4389
|
if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
|
|
@@ -4518,7 +4592,7 @@ async function dispatchRun(args) {
|
|
|
4518
4592
|
}
|
|
4519
4593
|
|
|
4520
4594
|
// src/sweep.ts
|
|
4521
|
-
import
|
|
4595
|
+
import path19 from "node:path";
|
|
4522
4596
|
async function sweepRun(args) {
|
|
4523
4597
|
const pipeline = args.pipeline === true || args.pipeline === "true";
|
|
4524
4598
|
try {
|
|
@@ -4531,7 +4605,7 @@ async function sweepRun(args) {
|
|
|
4531
4605
|
const releasedLocalOrphans = [];
|
|
4532
4606
|
for (const name of Object.keys(run.workers || {})) {
|
|
4533
4607
|
const worker = readJson(
|
|
4534
|
-
|
|
4608
|
+
path19.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4535
4609
|
void 0
|
|
4536
4610
|
);
|
|
4537
4611
|
if (!worker || !worker.dispatched || !worker.taskId) continue;
|
|
@@ -4574,11 +4648,53 @@ async function sweepRun(args) {
|
|
|
4574
4648
|
}
|
|
4575
4649
|
|
|
4576
4650
|
// src/worktree.ts
|
|
4577
|
-
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
|
|
4578
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
|
+
}
|
|
4579
4695
|
|
|
4580
4696
|
// src/validate.ts
|
|
4581
|
-
import
|
|
4697
|
+
import path21 from "node:path";
|
|
4582
4698
|
var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
|
|
4583
4699
|
function validateRunId(runId) {
|
|
4584
4700
|
const trimmed = runId.trim();
|
|
@@ -4586,18 +4702,26 @@ function validateRunId(runId) {
|
|
|
4586
4702
|
return trimmed;
|
|
4587
4703
|
}
|
|
4588
4704
|
function validateRepo(repo) {
|
|
4589
|
-
const resolved =
|
|
4705
|
+
const resolved = path21.resolve(repo);
|
|
4590
4706
|
if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
|
|
4591
4707
|
return resolved;
|
|
4592
4708
|
}
|
|
4593
4709
|
|
|
4594
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
|
+
}
|
|
4595
4719
|
function createRun(args) {
|
|
4596
|
-
const repo = validateRepo(
|
|
4720
|
+
const repo = validateRepo(resolveCreateRunRepo(args));
|
|
4597
4721
|
ensureGitRepo(repo);
|
|
4598
4722
|
const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
|
|
4599
4723
|
const dir = runDirectory(id);
|
|
4600
|
-
if (
|
|
4724
|
+
if (existsSync14(dir)) failExists(`run already exists: ${id}`);
|
|
4601
4725
|
mkdirSync5(dir, { recursive: true });
|
|
4602
4726
|
const base = String(args.base || "origin/main");
|
|
4603
4727
|
const baseCommit = git(repo, ["rev-parse", base]).trim();
|
|
@@ -4611,12 +4735,12 @@ function createRun(args) {
|
|
|
4611
4735
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4612
4736
|
workers: {}
|
|
4613
4737
|
};
|
|
4614
|
-
writeJson(
|
|
4738
|
+
writeJson(path22.join(dir, "run.json"), run);
|
|
4615
4739
|
console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
|
|
4616
4740
|
}
|
|
4617
4741
|
function listRuns() {
|
|
4618
4742
|
const { runsDir } = getPaths();
|
|
4619
|
-
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) => ({
|
|
4620
4744
|
id: run.id,
|
|
4621
4745
|
name: run.name,
|
|
4622
4746
|
status: run.status,
|
|
@@ -4631,7 +4755,7 @@ function failExists(message) {
|
|
|
4631
4755
|
}
|
|
4632
4756
|
|
|
4633
4757
|
// src/pipeline-tick.ts
|
|
4634
|
-
import
|
|
4758
|
+
import path32 from "node:path";
|
|
4635
4759
|
|
|
4636
4760
|
// src/pipeline-dispatch.ts
|
|
4637
4761
|
var RESERVED_REVIEW_STARTS = 1;
|
|
@@ -4685,12 +4809,19 @@ async function runPipelineDispatch(args, slots) {
|
|
|
4685
4809
|
}
|
|
4686
4810
|
|
|
4687
4811
|
// src/stale-reconcile.ts
|
|
4688
|
-
import
|
|
4812
|
+
import path24 from "node:path";
|
|
4689
4813
|
|
|
4690
4814
|
// src/finalize.ts
|
|
4691
|
-
import
|
|
4692
|
-
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set([
|
|
4693
|
-
|
|
4815
|
+
import path23 from "node:path";
|
|
4816
|
+
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set([
|
|
4817
|
+
"running",
|
|
4818
|
+
"dispatching",
|
|
4819
|
+
"pending",
|
|
4820
|
+
"queued",
|
|
4821
|
+
"needs_attention"
|
|
4822
|
+
]);
|
|
4823
|
+
var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "done"]);
|
|
4824
|
+
function deriveTerminalRunStatus(run) {
|
|
4694
4825
|
const names = Object.keys(run.workers || {});
|
|
4695
4826
|
if (names.length === 0) return "failed";
|
|
4696
4827
|
let anyAlive = false;
|
|
@@ -4699,7 +4830,7 @@ function terminalStatusFor(run) {
|
|
|
4699
4830
|
let anyLandingBlocked = false;
|
|
4700
4831
|
for (const name of names) {
|
|
4701
4832
|
const worker = readJson(
|
|
4702
|
-
|
|
4833
|
+
path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4703
4834
|
void 0
|
|
4704
4835
|
);
|
|
4705
4836
|
if (!worker) continue;
|
|
@@ -4728,7 +4859,7 @@ function finalizeStaleRuns() {
|
|
|
4728
4859
|
const finalized = [];
|
|
4729
4860
|
for (const run of listRunRecords()) {
|
|
4730
4861
|
if (!ACTIVE_RUN_STATUSES.has(run.status)) continue;
|
|
4731
|
-
const next =
|
|
4862
|
+
const next = deriveTerminalRunStatus(run);
|
|
4732
4863
|
if (!next || next === run.status) continue;
|
|
4733
4864
|
const from = run.status;
|
|
4734
4865
|
run.status = next;
|
|
@@ -4751,7 +4882,7 @@ function reconcileStaleWorkers() {
|
|
|
4751
4882
|
const now = Date.now();
|
|
4752
4883
|
for (const run of listRunRecords()) {
|
|
4753
4884
|
for (const name of Object.keys(run.workers || {})) {
|
|
4754
|
-
const workerPath =
|
|
4885
|
+
const workerPath = path24.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
4755
4886
|
const worker = readJson(workerPath, void 0);
|
|
4756
4887
|
if (!worker || worker.status !== "running") {
|
|
4757
4888
|
outcomes.push({
|
|
@@ -4845,7 +4976,7 @@ function reconcileRunsCli() {
|
|
|
4845
4976
|
}
|
|
4846
4977
|
|
|
4847
4978
|
// src/plan-progress-daemon-sync.ts
|
|
4848
|
-
import
|
|
4979
|
+
import path25 from "node:path";
|
|
4849
4980
|
|
|
4850
4981
|
// src/plan-progress-sync.ts
|
|
4851
4982
|
async function syncPlanProgress(args) {
|
|
@@ -4869,7 +5000,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
4869
5000
|
const outcomes = [];
|
|
4870
5001
|
for (const name of Object.keys(run.workers || {})) {
|
|
4871
5002
|
const worker = readJson(
|
|
4872
|
-
|
|
5003
|
+
path25.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4873
5004
|
void 0
|
|
4874
5005
|
);
|
|
4875
5006
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -4918,15 +5049,28 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
4918
5049
|
}
|
|
4919
5050
|
|
|
4920
5051
|
// src/cleanup.ts
|
|
4921
|
-
import
|
|
5052
|
+
import path30 from "node:path";
|
|
4922
5053
|
|
|
4923
|
-
// src/cleanup-
|
|
4924
|
-
|
|
4925
|
-
|
|
5054
|
+
// src/cleanup-run-liveness.ts
|
|
5055
|
+
function isWorkerProcessLive(indexed) {
|
|
5056
|
+
if (indexed.status.alive) return true;
|
|
5057
|
+
if (indexed.worker.status === "running") return true;
|
|
5058
|
+
return false;
|
|
5059
|
+
}
|
|
5060
|
+
function isRunStaleActive(indexed) {
|
|
5061
|
+
if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
|
|
5062
|
+
return deriveTerminalRunStatus(indexed.run) !== null;
|
|
5063
|
+
}
|
|
5064
|
+
function runBlocksWorktreeRemoval(indexed) {
|
|
5065
|
+
if (isWorkerProcessLive(indexed)) return true;
|
|
5066
|
+
if (indexed.worker.completionBlocker) return true;
|
|
5067
|
+
if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
|
|
5068
|
+
if (isRunStaleActive(indexed)) return false;
|
|
5069
|
+
if (!isFinishedWorkerStatus(indexed.status)) return true;
|
|
5070
|
+
return deriveTerminalRunStatus(indexed.run) === null;
|
|
5071
|
+
}
|
|
4926
5072
|
|
|
4927
5073
|
// src/cleanup-guards.ts
|
|
4928
|
-
var ACTIVE_RUN_STATUSES2 = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued", "needs_attention"]);
|
|
4929
|
-
var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
4930
5074
|
function prUrlFromFinalResult(finalResult) {
|
|
4931
5075
|
if (typeof finalResult === "string") {
|
|
4932
5076
|
const match = finalResult.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/i);
|
|
@@ -4948,18 +5092,29 @@ function isPrOrUnmergedWork(status) {
|
|
|
4948
5092
|
if (status.changedFiles.length > 0 && status.finalResult) return true;
|
|
4949
5093
|
return false;
|
|
4950
5094
|
}
|
|
5095
|
+
function materialWorktreeChanges(changedFiles) {
|
|
5096
|
+
return changedFiles.filter((line) => {
|
|
5097
|
+
const trimmed = line.trim();
|
|
5098
|
+
const pathPart = trimmed.startsWith("??") ? trimmed.slice(2).trim() : trimmed.length > 3 ? trimmed.slice(3).trim() : trimmed;
|
|
5099
|
+
return pathPart !== "node_modules" && !pathPart.startsWith("node_modules/");
|
|
5100
|
+
});
|
|
5101
|
+
}
|
|
5102
|
+
function hasUnrestorableWorktreeChanges(status) {
|
|
5103
|
+
if (materialWorktreeChanges(status.changedFiles).length > 0) return true;
|
|
5104
|
+
if (status.gitAncestry?.relation === "diverged") return true;
|
|
5105
|
+
return false;
|
|
5106
|
+
}
|
|
4951
5107
|
function skipWorktreeRemoval(input) {
|
|
4952
5108
|
const { indexed, includeOrphans, worktreesAgeMs, ageMs } = input;
|
|
4953
5109
|
if (worktreesAgeMs <= 0) return "worktrees_disabled";
|
|
4954
5110
|
if (ageMs < worktreesAgeMs) return "below_age_threshold";
|
|
4955
5111
|
if (!indexed) return includeOrphans ? null : "orphan_without_flag";
|
|
4956
|
-
if (
|
|
4957
|
-
if (!TERMINAL_RUN_STATUSES.has(indexed.run.status)) return "run_still_active";
|
|
4958
|
-
if (indexed.status.alive) return "active_worker";
|
|
4959
|
-
if (indexed.worker.status === "running") return "active_worker";
|
|
5112
|
+
if (isWorkerProcessLive(indexed)) return "active_worker";
|
|
4960
5113
|
if (indexed.worker.completionBlocker) return "completion_blocked";
|
|
5114
|
+
if (runBlocksWorktreeRemoval(indexed)) return "run_still_active";
|
|
5115
|
+
if (!isFinishedWorkerStatus(indexed.status)) return "run_still_active";
|
|
4961
5116
|
if (isPrOrUnmergedWork(indexed.status)) return "pr_or_unmerged_commits";
|
|
4962
|
-
if (indexed.status.changedFiles.length > 0) return "dirty_worktree";
|
|
5117
|
+
if (materialWorktreeChanges(indexed.status.changedFiles).length > 0) return "dirty_worktree";
|
|
4963
5118
|
const landing = assessWorkerLanding({
|
|
4964
5119
|
finalResult: indexed.status.finalResult,
|
|
4965
5120
|
changedFiles: indexed.status.changedFiles,
|
|
@@ -4973,30 +5128,31 @@ function skipNodeModulesRemoval(input) {
|
|
|
4973
5128
|
const { indexed, includeOrphans, nodeModulesAgeMs, ageMs } = input;
|
|
4974
5129
|
if (ageMs < nodeModulesAgeMs) return "below_age_threshold";
|
|
4975
5130
|
if (!indexed) return includeOrphans ? null : "orphan_without_flag";
|
|
4976
|
-
if (indexed
|
|
4977
|
-
if (indexed.worker.status === "running") return "active_worker";
|
|
5131
|
+
if (isWorkerProcessLive(indexed)) return "active_worker";
|
|
4978
5132
|
if (indexed.worker.completionBlocker) return "completion_blocked";
|
|
4979
|
-
if (
|
|
4980
|
-
if (indexed.status
|
|
5133
|
+
if (!isFinishedWorkerStatus(indexed.status)) return "run_still_active";
|
|
5134
|
+
if (hasUnrestorableWorktreeChanges(indexed.status)) return "dirty_worktree";
|
|
4981
5135
|
const landing = assessWorkerLanding({
|
|
4982
5136
|
finalResult: indexed.status.finalResult,
|
|
4983
5137
|
changedFiles: indexed.status.changedFiles,
|
|
4984
5138
|
gitAncestry: indexed.status.gitAncestry,
|
|
4985
5139
|
prUrl: prUrlFromFinalResult(indexed.status.finalResult)
|
|
4986
5140
|
});
|
|
4987
|
-
if (landing.blocked)
|
|
5141
|
+
if (landing.blocked && materialWorktreeChanges(indexed.status.changedFiles).length > 0) {
|
|
5142
|
+
return "landing_blocked";
|
|
5143
|
+
}
|
|
4988
5144
|
return null;
|
|
4989
5145
|
}
|
|
4990
5146
|
|
|
4991
5147
|
// src/cleanup-execute.ts
|
|
4992
|
-
import { existsSync as
|
|
4993
|
-
import
|
|
5148
|
+
import { existsSync as existsSync16, rmSync } from "node:fs";
|
|
5149
|
+
import path27 from "node:path";
|
|
4994
5150
|
|
|
4995
5151
|
// src/cleanup-dir-size.ts
|
|
4996
|
-
import { existsSync as
|
|
4997
|
-
import
|
|
5152
|
+
import { existsSync as existsSync15, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
|
|
5153
|
+
import path26 from "node:path";
|
|
4998
5154
|
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
4999
|
-
if (!
|
|
5155
|
+
if (!existsSync15(root)) return 0;
|
|
5000
5156
|
let total = 0;
|
|
5001
5157
|
let seen = 0;
|
|
5002
5158
|
const stack = [root];
|
|
@@ -5010,7 +5166,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
5010
5166
|
}
|
|
5011
5167
|
for (const name of entries) {
|
|
5012
5168
|
if (seen++ > maxEntries) return null;
|
|
5013
|
-
const full =
|
|
5169
|
+
const full = path26.join(current, name);
|
|
5014
5170
|
let st;
|
|
5015
5171
|
try {
|
|
5016
5172
|
st = statSync2(full);
|
|
@@ -5026,7 +5182,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
5026
5182
|
|
|
5027
5183
|
// src/cleanup-execute.ts
|
|
5028
5184
|
function removeNodeModules(candidate, execute) {
|
|
5029
|
-
if (!
|
|
5185
|
+
if (!existsSync16(candidate.path)) {
|
|
5030
5186
|
return {
|
|
5031
5187
|
...candidate,
|
|
5032
5188
|
executed: false,
|
|
@@ -5057,7 +5213,7 @@ function removeNodeModules(candidate, execute) {
|
|
|
5057
5213
|
}
|
|
5058
5214
|
}
|
|
5059
5215
|
function removeWorktree(candidate, execute) {
|
|
5060
|
-
if (!
|
|
5216
|
+
if (!existsSync16(candidate.path)) {
|
|
5061
5217
|
return {
|
|
5062
5218
|
...candidate,
|
|
5063
5219
|
executed: false,
|
|
@@ -5074,7 +5230,7 @@ function removeWorktree(candidate, execute) {
|
|
|
5074
5230
|
if (repo) {
|
|
5075
5231
|
git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
|
|
5076
5232
|
}
|
|
5077
|
-
if (
|
|
5233
|
+
if (existsSync16(candidate.path)) {
|
|
5078
5234
|
rmSync(candidate.path, { recursive: true, force: true });
|
|
5079
5235
|
}
|
|
5080
5236
|
return {
|
|
@@ -5094,20 +5250,20 @@ function removeWorktree(candidate, execute) {
|
|
|
5094
5250
|
}
|
|
5095
5251
|
}
|
|
5096
5252
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
5097
|
-
const resolved =
|
|
5098
|
-
const nm = resolved.endsWith(`${
|
|
5253
|
+
const resolved = path27.resolve(targetPath);
|
|
5254
|
+
const nm = resolved.endsWith(`${path27.sep}node_modules`) ? resolved : null;
|
|
5099
5255
|
if (!nm) return "path_outside_harness";
|
|
5100
|
-
const rel =
|
|
5101
|
-
if (rel.startsWith("..") ||
|
|
5102
|
-
const parts = rel.split(
|
|
5256
|
+
const rel = path27.relative(worktreesDir, nm);
|
|
5257
|
+
if (rel.startsWith("..") || path27.isAbsolute(rel)) return "path_outside_harness";
|
|
5258
|
+
const parts = rel.split(path27.sep);
|
|
5103
5259
|
if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
|
|
5104
|
-
if (!resolved.startsWith(
|
|
5260
|
+
if (!resolved.startsWith(path27.resolve(harnessRoot))) return "path_outside_harness";
|
|
5105
5261
|
return null;
|
|
5106
5262
|
}
|
|
5107
5263
|
|
|
5108
5264
|
// src/cleanup-scan.ts
|
|
5109
|
-
import { existsSync as
|
|
5110
|
-
import
|
|
5265
|
+
import { existsSync as existsSync17, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
5266
|
+
import path28 from "node:path";
|
|
5111
5267
|
function pathAgeMs(target, now) {
|
|
5112
5268
|
try {
|
|
5113
5269
|
const mtime = statSync3(target).mtimeMs;
|
|
@@ -5117,17 +5273,17 @@ function pathAgeMs(target, now) {
|
|
|
5117
5273
|
}
|
|
5118
5274
|
}
|
|
5119
5275
|
function isPathInside(child, parent) {
|
|
5120
|
-
const rel =
|
|
5121
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
5276
|
+
const rel = path28.relative(parent, child);
|
|
5277
|
+
return rel === "" || !rel.startsWith("..") && !path28.isAbsolute(rel);
|
|
5122
5278
|
}
|
|
5123
5279
|
function scanNodeModulesCandidates(opts) {
|
|
5124
5280
|
const candidates = [];
|
|
5125
5281
|
const seen = /* @__PURE__ */ new Set();
|
|
5126
5282
|
for (const entry of opts.index.values()) {
|
|
5127
5283
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
5128
|
-
const nm =
|
|
5129
|
-
if (!
|
|
5130
|
-
const resolved =
|
|
5284
|
+
const nm = path28.join(entry.worktreePath, "node_modules");
|
|
5285
|
+
if (!existsSync17(nm)) continue;
|
|
5286
|
+
const resolved = path28.resolve(nm);
|
|
5131
5287
|
if (seen.has(resolved)) continue;
|
|
5132
5288
|
seen.add(resolved);
|
|
5133
5289
|
candidates.push({
|
|
@@ -5140,16 +5296,16 @@ function scanNodeModulesCandidates(opts) {
|
|
|
5140
5296
|
ageMs: pathAgeMs(resolved, opts.now)
|
|
5141
5297
|
});
|
|
5142
5298
|
}
|
|
5143
|
-
if (!opts.includeOrphans || !
|
|
5299
|
+
if (!opts.includeOrphans || !existsSync17(opts.worktreesDir)) return candidates;
|
|
5144
5300
|
for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
|
|
5145
5301
|
if (!runEntry.isDirectory()) continue;
|
|
5146
|
-
const runPath =
|
|
5302
|
+
const runPath = path28.join(opts.worktreesDir, runEntry.name);
|
|
5147
5303
|
for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
|
|
5148
5304
|
if (!workerEntry.isDirectory()) continue;
|
|
5149
|
-
const worktreePath =
|
|
5150
|
-
const nm =
|
|
5151
|
-
if (!
|
|
5152
|
-
const resolved =
|
|
5305
|
+
const worktreePath = path28.join(runPath, workerEntry.name);
|
|
5306
|
+
const nm = path28.join(worktreePath, "node_modules");
|
|
5307
|
+
if (!existsSync17(nm)) continue;
|
|
5308
|
+
const resolved = path28.resolve(nm);
|
|
5153
5309
|
if (seen.has(resolved)) continue;
|
|
5154
5310
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
5155
5311
|
seen.add(resolved);
|
|
@@ -5172,7 +5328,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
5172
5328
|
for (const entry of opts.index.values()) {
|
|
5173
5329
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
5174
5330
|
const resolved = entry.worktreePath;
|
|
5175
|
-
if (!
|
|
5331
|
+
if (!existsSync17(resolved)) continue;
|
|
5176
5332
|
if (seen.has(resolved)) continue;
|
|
5177
5333
|
seen.add(resolved);
|
|
5178
5334
|
candidates.push({
|
|
@@ -5189,17 +5345,17 @@ function scanWorktreeCandidates(opts) {
|
|
|
5189
5345
|
}
|
|
5190
5346
|
|
|
5191
5347
|
// src/cleanup-worktree-index.ts
|
|
5192
|
-
import
|
|
5348
|
+
import path29 from "node:path";
|
|
5193
5349
|
function buildWorktreeIndex() {
|
|
5194
5350
|
const index = /* @__PURE__ */ new Map();
|
|
5195
5351
|
for (const run of listRunRecords()) {
|
|
5196
5352
|
for (const name of Object.keys(run.workers || {})) {
|
|
5197
|
-
const workerPath =
|
|
5353
|
+
const workerPath = path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
5198
5354
|
const worker = readJson(workerPath, void 0);
|
|
5199
5355
|
if (!worker?.worktreePath) continue;
|
|
5200
5356
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
5201
|
-
index.set(
|
|
5202
|
-
worktreePath:
|
|
5357
|
+
index.set(path29.resolve(worker.worktreePath), {
|
|
5358
|
+
worktreePath: path29.resolve(worker.worktreePath),
|
|
5203
5359
|
runId: run.id,
|
|
5204
5360
|
workerName: name,
|
|
5205
5361
|
run,
|
|
@@ -5211,59 +5367,109 @@ function buildWorktreeIndex() {
|
|
|
5211
5367
|
return index;
|
|
5212
5368
|
}
|
|
5213
5369
|
|
|
5214
|
-
// src/cleanup.ts
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
const
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5370
|
+
// src/cleanup-types.ts
|
|
5371
|
+
var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
5372
|
+
var DEFAULT_WORKTREES_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
5373
|
+
|
|
5374
|
+
// src/cleanup-retention-config.ts
|
|
5375
|
+
function envFlag(name) {
|
|
5376
|
+
const v = process.env[name];
|
|
5377
|
+
return v === "1" || v === "true" || v === "yes";
|
|
5378
|
+
}
|
|
5379
|
+
function envMs(name, fallback) {
|
|
5380
|
+
const raw = process.env[name];
|
|
5381
|
+
if (!raw) return fallback;
|
|
5382
|
+
const n = Number(raw);
|
|
5383
|
+
return Number.isFinite(n) && n >= 0 ? n : fallback;
|
|
5384
|
+
}
|
|
5385
|
+
function resolveHarnessRetention(options = {}) {
|
|
5386
|
+
const execute = options.execute === true || options.execute !== false && envFlag("KYNVER_CLEANUP_EXECUTE");
|
|
5387
|
+
const finalizeStaleRuns2 = options.finalizeStaleRuns !== false && !envFlag("KYNVER_CLEANUP_SKIP_FINALIZE");
|
|
5388
|
+
const nodeModulesAgeMs = options.nodeModulesAgeMs ?? envMs("KYNVER_CLEANUP_NODE_MODULES_AGE_MS", DEFAULT_NODE_MODULES_AGE_MS);
|
|
5389
|
+
const worktreesAgeMs = options.worktreesAgeMs ?? envMs("KYNVER_CLEANUP_WORKTREES_AGE_MS", 0);
|
|
5390
|
+
const includeOrphans = options.includeOrphans === true || envFlag("KYNVER_CLEANUP_INCLUDE_ORPHANS");
|
|
5391
|
+
const scopeAll = envFlag("KYNVER_CLEANUP_SCOPE_ALL") || process.env.KYNVER_CLEANUP_SCOPE === "all";
|
|
5392
|
+
const runIdFilter = scopeAll ? options.runIdFilter : options.runIdFilter ?? (process.env.KYNVER_CLEANUP_RUN_ID || void 0);
|
|
5393
|
+
const accountBytes = options.accountBytes !== false && !envFlag("KYNVER_CLEANUP_SKIP_BYTE_ACCOUNTING");
|
|
5224
5394
|
return {
|
|
5225
|
-
harnessRoot,
|
|
5226
|
-
worktreesDir,
|
|
5227
5395
|
execute,
|
|
5228
|
-
|
|
5396
|
+
finalizeStaleRuns: finalizeStaleRuns2,
|
|
5229
5397
|
nodeModulesAgeMs,
|
|
5230
|
-
worktreesAgeMs,
|
|
5398
|
+
worktreesAgeMs: worktreesAgeMs > 0 ? worktreesAgeMs : 0,
|
|
5231
5399
|
includeOrphans,
|
|
5232
|
-
runIdFilter,
|
|
5233
|
-
|
|
5400
|
+
runIdFilter: runIdFilter ? String(runIdFilter) : void 0,
|
|
5401
|
+
accountBytes
|
|
5234
5402
|
};
|
|
5235
5403
|
}
|
|
5404
|
+
function resolvePipelineHarnessRetention(runId) {
|
|
5405
|
+
const scopeAll = process.env.KYNVER_CLEANUP_SCOPE === "all";
|
|
5406
|
+
const envWorktrees = Number(process.env.KYNVER_CLEANUP_WORKTREES_AGE_MS);
|
|
5407
|
+
const worktreesAgeMs = scopeAll ? Number.isFinite(envWorktrees) && envWorktrees > 0 ? envWorktrees : DEFAULT_WORKTREES_AGE_MS : Number.isFinite(envWorktrees) && envWorktrees > 0 ? envWorktrees : 0;
|
|
5408
|
+
return resolveHarnessRetention({
|
|
5409
|
+
runIdFilter: scopeAll ? void 0 : runId,
|
|
5410
|
+
worktreesAgeMs,
|
|
5411
|
+
finalizeStaleRuns: true,
|
|
5412
|
+
accountBytes: true
|
|
5413
|
+
});
|
|
5414
|
+
}
|
|
5415
|
+
|
|
5416
|
+
// src/cleanup.ts
|
|
5417
|
+
function resolvePaths(options = {}) {
|
|
5418
|
+
const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
|
|
5419
|
+
const { worktreesDir } = options.harnessRoot ? { worktreesDir: path30.join(harnessRoot, "worktrees") } : getHarnessPaths();
|
|
5420
|
+
const now = options.now ?? Date.now();
|
|
5421
|
+
return { harnessRoot, worktreesDir, now };
|
|
5422
|
+
}
|
|
5236
5423
|
function recordSkip(skips, pathValue, reason, detail) {
|
|
5237
5424
|
skips.push({ path: pathValue, reason, ...detail ? { detail } : {} });
|
|
5238
5425
|
}
|
|
5426
|
+
function attachCandidateBytes(candidate, accountBytes) {
|
|
5427
|
+
if (!accountBytes || candidate.bytes != null) return candidate;
|
|
5428
|
+
return { ...candidate, bytes: directorySizeBytes(candidate.path) };
|
|
5429
|
+
}
|
|
5430
|
+
function tallySkipReasons(actions, skips) {
|
|
5431
|
+
const counts = {};
|
|
5432
|
+
for (const skip of skips) {
|
|
5433
|
+
counts[skip.reason] = (counts[skip.reason] ?? 0) + 1;
|
|
5434
|
+
}
|
|
5435
|
+
for (const action of actions) {
|
|
5436
|
+
if (action.skipReason) {
|
|
5437
|
+
counts[action.skipReason] = (counts[action.skipReason] ?? 0) + 1;
|
|
5438
|
+
}
|
|
5439
|
+
}
|
|
5440
|
+
return counts;
|
|
5441
|
+
}
|
|
5239
5442
|
function runHarnessCleanup(options = {}) {
|
|
5240
|
-
const
|
|
5443
|
+
const retention = resolveHarnessRetention(options);
|
|
5444
|
+
const paths = resolvePaths(options);
|
|
5445
|
+
const finalizedRuns = retention.finalizeStaleRuns ? finalizeStaleRuns().map((f) => ({ runId: f.runId, from: f.from, to: f.to })) : [];
|
|
5241
5446
|
const index = buildWorktreeIndex();
|
|
5242
5447
|
const scanOpts = {
|
|
5243
|
-
harnessRoot:
|
|
5244
|
-
worktreesDir:
|
|
5245
|
-
nodeModulesAgeMs:
|
|
5246
|
-
worktreesAgeMs:
|
|
5247
|
-
includeOrphans:
|
|
5248
|
-
runIdFilter:
|
|
5448
|
+
harnessRoot: paths.harnessRoot,
|
|
5449
|
+
worktreesDir: paths.worktreesDir,
|
|
5450
|
+
nodeModulesAgeMs: retention.nodeModulesAgeMs,
|
|
5451
|
+
worktreesAgeMs: retention.worktreesAgeMs,
|
|
5452
|
+
includeOrphans: retention.includeOrphans,
|
|
5453
|
+
runIdFilter: retention.runIdFilter,
|
|
5249
5454
|
index,
|
|
5250
|
-
now:
|
|
5455
|
+
now: paths.now
|
|
5251
5456
|
};
|
|
5252
5457
|
const skips = [];
|
|
5253
5458
|
const actions = [];
|
|
5254
|
-
for (const
|
|
5255
|
-
const
|
|
5459
|
+
for (const raw of scanNodeModulesCandidates(scanOpts)) {
|
|
5460
|
+
const candidate = attachCandidateBytes(raw, retention.accountBytes);
|
|
5461
|
+
const pathSkip = isHarnessNodeModulesPath(candidate.path, paths.harnessRoot, paths.worktreesDir);
|
|
5256
5462
|
if (pathSkip) {
|
|
5257
5463
|
recordSkip(skips, candidate.path, pathSkip);
|
|
5258
5464
|
actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
|
|
5259
5465
|
continue;
|
|
5260
5466
|
}
|
|
5261
|
-
const worktreePath =
|
|
5467
|
+
const worktreePath = path30.resolve(candidate.path, "..");
|
|
5262
5468
|
const indexed = index.get(worktreePath) ?? null;
|
|
5263
5469
|
const guardReason = skipNodeModulesRemoval({
|
|
5264
5470
|
indexed,
|
|
5265
|
-
includeOrphans:
|
|
5266
|
-
nodeModulesAgeMs:
|
|
5471
|
+
includeOrphans: retention.includeOrphans,
|
|
5472
|
+
nodeModulesAgeMs: retention.nodeModulesAgeMs,
|
|
5267
5473
|
ageMs: candidate.ageMs
|
|
5268
5474
|
});
|
|
5269
5475
|
if (guardReason) {
|
|
@@ -5271,14 +5477,15 @@ function runHarnessCleanup(options = {}) {
|
|
|
5271
5477
|
actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
|
|
5272
5478
|
continue;
|
|
5273
5479
|
}
|
|
5274
|
-
actions.push(removeNodeModules(candidate,
|
|
5480
|
+
actions.push(removeNodeModules(candidate, retention.execute));
|
|
5275
5481
|
}
|
|
5276
|
-
for (const
|
|
5277
|
-
const
|
|
5482
|
+
for (const raw of scanWorktreeCandidates(scanOpts)) {
|
|
5483
|
+
const candidate = attachCandidateBytes(raw, retention.accountBytes);
|
|
5484
|
+
const indexed = index.get(path30.resolve(candidate.path)) ?? null;
|
|
5278
5485
|
const guardReason = skipWorktreeRemoval({
|
|
5279
5486
|
indexed,
|
|
5280
|
-
includeOrphans:
|
|
5281
|
-
worktreesAgeMs:
|
|
5487
|
+
includeOrphans: retention.includeOrphans,
|
|
5488
|
+
worktreesAgeMs: retention.worktreesAgeMs,
|
|
5282
5489
|
ageMs: candidate.ageMs
|
|
5283
5490
|
});
|
|
5284
5491
|
if (guardReason) {
|
|
@@ -5286,51 +5493,55 @@ function runHarnessCleanup(options = {}) {
|
|
|
5286
5493
|
actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
|
|
5287
5494
|
continue;
|
|
5288
5495
|
}
|
|
5289
|
-
actions.push(removeWorktree(candidate,
|
|
5496
|
+
actions.push(removeWorktree(candidate, retention.execute));
|
|
5290
5497
|
}
|
|
5291
5498
|
let candidateBytes = 0;
|
|
5499
|
+
let reclaimableBytes = 0;
|
|
5292
5500
|
let removedBytes = 0;
|
|
5293
5501
|
let removedPaths = 0;
|
|
5294
5502
|
let skippedPaths = 0;
|
|
5295
5503
|
for (const action of actions) {
|
|
5296
5504
|
if (action.bytes) candidateBytes += action.bytes;
|
|
5505
|
+
if (!action.skipped && !action.executed && action.bytes) reclaimableBytes += action.bytes;
|
|
5297
5506
|
if (action.executed) {
|
|
5298
5507
|
removedPaths += 1;
|
|
5299
5508
|
removedBytes += action.bytes ?? 0;
|
|
5300
5509
|
} else if (action.skipped) {
|
|
5301
5510
|
skippedPaths += 1;
|
|
5511
|
+
if (action.skipReason === "dry_run" && action.bytes) reclaimableBytes += action.bytes;
|
|
5302
5512
|
}
|
|
5303
5513
|
}
|
|
5304
5514
|
return {
|
|
5305
|
-
harnessRoot:
|
|
5306
|
-
dryRun:
|
|
5307
|
-
execute:
|
|
5308
|
-
nodeModulesAgeMs:
|
|
5309
|
-
worktreesAgeMs:
|
|
5310
|
-
includeOrphans:
|
|
5311
|
-
scannedAt: new Date(
|
|
5515
|
+
harnessRoot: paths.harnessRoot,
|
|
5516
|
+
dryRun: !retention.execute,
|
|
5517
|
+
execute: retention.execute,
|
|
5518
|
+
nodeModulesAgeMs: retention.nodeModulesAgeMs,
|
|
5519
|
+
worktreesAgeMs: retention.worktreesAgeMs,
|
|
5520
|
+
includeOrphans: retention.includeOrphans,
|
|
5521
|
+
scannedAt: new Date(paths.now).toISOString(),
|
|
5522
|
+
finalizedRuns,
|
|
5312
5523
|
actions,
|
|
5313
5524
|
skips,
|
|
5314
5525
|
totals: {
|
|
5315
5526
|
candidateBytes,
|
|
5527
|
+
reclaimableBytes,
|
|
5316
5528
|
removedBytes,
|
|
5317
5529
|
removedPaths,
|
|
5318
|
-
skippedPaths
|
|
5530
|
+
skippedPaths,
|
|
5531
|
+
skipReasons: tallySkipReasons(actions, skips)
|
|
5319
5532
|
}
|
|
5320
5533
|
};
|
|
5321
5534
|
}
|
|
5322
5535
|
function runPipelineHarnessCleanup(runId) {
|
|
5323
|
-
const
|
|
5324
|
-
const worktreesAgeMs = Number(process.env.KYNVER_CLEANUP_WORKTREES_AGE_MS) || 0;
|
|
5325
|
-
const execute = process.env.KYNVER_CLEANUP_EXECUTE === "1";
|
|
5326
|
-
const includeOrphans = process.env.KYNVER_CLEANUP_INCLUDE_ORPHANS === "1";
|
|
5327
|
-
const scopeAll = process.env.KYNVER_CLEANUP_SCOPE === "all";
|
|
5536
|
+
const retention = resolvePipelineHarnessRetention(runId);
|
|
5328
5537
|
return runHarnessCleanup({
|
|
5329
|
-
execute,
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5538
|
+
execute: retention.execute,
|
|
5539
|
+
finalizeStaleRuns: retention.finalizeStaleRuns,
|
|
5540
|
+
accountBytes: retention.accountBytes,
|
|
5541
|
+
nodeModulesAgeMs: retention.nodeModulesAgeMs,
|
|
5542
|
+
worktreesAgeMs: retention.worktreesAgeMs,
|
|
5543
|
+
includeOrphans: retention.includeOrphans,
|
|
5544
|
+
runIdFilter: retention.runIdFilter
|
|
5334
5545
|
});
|
|
5335
5546
|
}
|
|
5336
5547
|
function isPipelineCleanupEnabled() {
|
|
@@ -5339,8 +5550,8 @@ function isPipelineCleanupEnabled() {
|
|
|
5339
5550
|
|
|
5340
5551
|
// src/installed-package-versions.ts
|
|
5341
5552
|
import { readFile } from "node:fs/promises";
|
|
5342
|
-
import { homedir as
|
|
5343
|
-
import
|
|
5553
|
+
import { homedir as homedir6 } from "node:os";
|
|
5554
|
+
import path31 from "node:path";
|
|
5344
5555
|
var MANAGED_PACKAGES = [
|
|
5345
5556
|
"@kynver-app/runtime",
|
|
5346
5557
|
"@kynver-app/openclaw-agent-os",
|
|
@@ -5354,13 +5565,13 @@ function unique(values) {
|
|
|
5354
5565
|
return [...new Set(values.filter((value) => Boolean(value)))];
|
|
5355
5566
|
}
|
|
5356
5567
|
function moduleRoots() {
|
|
5357
|
-
const home =
|
|
5358
|
-
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ??
|
|
5359
|
-
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ?
|
|
5568
|
+
const home = homedir6();
|
|
5569
|
+
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path31.join(home, ".openclaw", "npm");
|
|
5570
|
+
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"));
|
|
5360
5571
|
return unique([
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot :
|
|
5572
|
+
path31.join(openClawPrefix, "lib", "node_modules"),
|
|
5573
|
+
path31.join(openClawPrefix, "node_modules"),
|
|
5574
|
+
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path31.join(npmGlobalRoot, "lib", "node_modules")
|
|
5364
5575
|
]);
|
|
5365
5576
|
}
|
|
5366
5577
|
async function readVersion(packageJsonPath) {
|
|
@@ -5376,7 +5587,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
|
|
|
5376
5587
|
const out = {};
|
|
5377
5588
|
for (const packageName of MANAGED_PACKAGES) {
|
|
5378
5589
|
for (const root of roots) {
|
|
5379
|
-
const packageJsonPath =
|
|
5590
|
+
const packageJsonPath = path31.join(root, packageName, "package.json");
|
|
5380
5591
|
const version = await readVersion(packageJsonPath);
|
|
5381
5592
|
if (!version) continue;
|
|
5382
5593
|
out[packageName] = { version, observedAt, path: packageJsonPath };
|
|
@@ -5392,7 +5603,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
5392
5603
|
const outcomes = [];
|
|
5393
5604
|
for (const name of Object.keys(run.workers || {})) {
|
|
5394
5605
|
const worker = readJson(
|
|
5395
|
-
|
|
5606
|
+
path32.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
5396
5607
|
void 0
|
|
5397
5608
|
);
|
|
5398
5609
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -5534,7 +5745,7 @@ async function runDaemon(args) {
|
|
|
5534
5745
|
}
|
|
5535
5746
|
|
|
5536
5747
|
// src/plan-progress.ts
|
|
5537
|
-
import
|
|
5748
|
+
import path33 from "node:path";
|
|
5538
5749
|
|
|
5539
5750
|
// src/bounded-build/constants.ts
|
|
5540
5751
|
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
@@ -5820,7 +6031,7 @@ async function emitPlanProgress(args) {
|
|
|
5820
6031
|
}
|
|
5821
6032
|
function verifyPlanLocal(args) {
|
|
5822
6033
|
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
5823
|
-
const cwd =
|
|
6034
|
+
const cwd = path33.resolve(worktree);
|
|
5824
6035
|
const summary = runHarnessVerifyCommands(cwd);
|
|
5825
6036
|
const emitJson = args.json === true || args.json === "true";
|
|
5826
6037
|
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
@@ -5869,9 +6080,9 @@ async function verifyPlan(args) {
|
|
|
5869
6080
|
}
|
|
5870
6081
|
|
|
5871
6082
|
// src/harness-verify-cli.ts
|
|
5872
|
-
import
|
|
6083
|
+
import path34 from "node:path";
|
|
5873
6084
|
function runHarnessVerifyCli(args) {
|
|
5874
|
-
const cwd =
|
|
6085
|
+
const cwd = path34.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
5875
6086
|
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
5876
6087
|
const commands = [];
|
|
5877
6088
|
const rawCmd = args.command;
|
|
@@ -5915,7 +6126,7 @@ function runHarnessVerifyCli(args) {
|
|
|
5915
6126
|
}
|
|
5916
6127
|
|
|
5917
6128
|
// src/plan-persist-cli.ts
|
|
5918
|
-
import { readFileSync as
|
|
6129
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
5919
6130
|
var OPERATIONS = ["create", "add_version", "update_metadata"];
|
|
5920
6131
|
var FAILURE_KINDS = [
|
|
5921
6132
|
"approval_guard",
|
|
@@ -5927,7 +6138,7 @@ var FAILURE_KINDS = [
|
|
|
5927
6138
|
function readBodyArg(args) {
|
|
5928
6139
|
const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
|
|
5929
6140
|
if (bodyFile) {
|
|
5930
|
-
return { body:
|
|
6141
|
+
return { body: readFileSync8(bodyFile, "utf8"), bodyPathHint: bodyFile };
|
|
5931
6142
|
}
|
|
5932
6143
|
const inline = args.body ? String(args.body) : void 0;
|
|
5933
6144
|
if (inline) return { body: inline };
|
|
@@ -5997,12 +6208,14 @@ async function runPlanOutboxDrain(args) {
|
|
|
5997
6208
|
// src/cleanup-cli.ts
|
|
5998
6209
|
function runCleanupCli(args) {
|
|
5999
6210
|
const execute = args.execute === true || args.execute === "true";
|
|
6211
|
+
const skipFinalize = args.skipFinalize === true || args.skipFinalize === "true";
|
|
6000
6212
|
const nodeModulesAgeMs = args.nodeModulesAgeMs ? Number(args.nodeModulesAgeMs) : DEFAULT_NODE_MODULES_AGE_MS;
|
|
6001
6213
|
const worktreesAgeMs = args.worktreesAgeMs ? Number(args.worktreesAgeMs) : 0;
|
|
6002
6214
|
const includeOrphans = args.includeOrphans === true || args.includeOrphans === "true";
|
|
6003
6215
|
const harnessRoot = args.harnessRoot ? String(args.harnessRoot) : void 0;
|
|
6004
6216
|
const summary = runHarnessCleanup({
|
|
6005
6217
|
execute,
|
|
6218
|
+
finalizeStaleRuns: !skipFinalize,
|
|
6006
6219
|
nodeModulesAgeMs: Number.isFinite(nodeModulesAgeMs) ? nodeModulesAgeMs : DEFAULT_NODE_MODULES_AGE_MS,
|
|
6007
6220
|
worktreesAgeMs: Number.isFinite(worktreesAgeMs) ? worktreesAgeMs : 0,
|
|
6008
6221
|
includeOrphans,
|
|
@@ -6015,7 +6228,7 @@ function runCleanupCli(args) {
|
|
|
6015
6228
|
}
|
|
6016
6229
|
|
|
6017
6230
|
// src/monitor/monitor.service.ts
|
|
6018
|
-
import
|
|
6231
|
+
import path36 from "node:path";
|
|
6019
6232
|
|
|
6020
6233
|
// src/monitor/monitor.classify.ts
|
|
6021
6234
|
function expectedLeaseOwner(runId) {
|
|
@@ -6071,11 +6284,11 @@ function classifyWorkerHealth(input) {
|
|
|
6071
6284
|
}
|
|
6072
6285
|
|
|
6073
6286
|
// src/monitor/monitor.store.ts
|
|
6074
|
-
import { existsSync as
|
|
6075
|
-
import
|
|
6287
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
|
|
6288
|
+
import path35 from "node:path";
|
|
6076
6289
|
function monitorsDir() {
|
|
6077
6290
|
const { harnessRoot } = getHarnessPaths();
|
|
6078
|
-
const dir =
|
|
6291
|
+
const dir = path35.join(harnessRoot, "monitors");
|
|
6079
6292
|
mkdirSync6(dir, { recursive: true });
|
|
6080
6293
|
return dir;
|
|
6081
6294
|
}
|
|
@@ -6083,7 +6296,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
6083
6296
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
6084
6297
|
}
|
|
6085
6298
|
function monitorPath(monitorId) {
|
|
6086
|
-
return
|
|
6299
|
+
return path35.join(monitorsDir(), `${monitorId}.json`);
|
|
6087
6300
|
}
|
|
6088
6301
|
function loadMonitorSession(monitorId) {
|
|
6089
6302
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -6093,18 +6306,18 @@ function saveMonitorSession(session) {
|
|
|
6093
6306
|
}
|
|
6094
6307
|
function deleteMonitorSession(monitorId) {
|
|
6095
6308
|
const file = monitorPath(monitorId);
|
|
6096
|
-
if (!
|
|
6309
|
+
if (!existsSync18(file)) return false;
|
|
6097
6310
|
unlinkSync2(file);
|
|
6098
6311
|
return true;
|
|
6099
6312
|
}
|
|
6100
6313
|
function listMonitorSessions() {
|
|
6101
6314
|
const dir = monitorsDir();
|
|
6102
|
-
if (!
|
|
6315
|
+
if (!existsSync18(dir)) return [];
|
|
6103
6316
|
const entries = [];
|
|
6104
6317
|
for (const name of readdirSync7(dir)) {
|
|
6105
6318
|
if (!name.endsWith(".json")) continue;
|
|
6106
6319
|
const session = readJson(
|
|
6107
|
-
|
|
6320
|
+
path35.join(dir, name),
|
|
6108
6321
|
void 0
|
|
6109
6322
|
);
|
|
6110
6323
|
if (!session?.monitorId) continue;
|
|
@@ -6195,7 +6408,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
6195
6408
|
// src/monitor/monitor.service.ts
|
|
6196
6409
|
function workerRecord2(runId, name) {
|
|
6197
6410
|
return readJson(
|
|
6198
|
-
|
|
6411
|
+
path36.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
6199
6412
|
void 0
|
|
6200
6413
|
);
|
|
6201
6414
|
}
|
|
@@ -6397,18 +6610,18 @@ async function runMonitorLoop(args) {
|
|
|
6397
6610
|
|
|
6398
6611
|
// src/monitor/monitor-spawn.ts
|
|
6399
6612
|
import { spawn as spawn4 } from "node:child_process";
|
|
6400
|
-
import { closeSync as closeSync4, existsSync as
|
|
6401
|
-
import
|
|
6402
|
-
import { fileURLToPath as
|
|
6613
|
+
import { closeSync as closeSync4, existsSync as existsSync19, openSync as openSync4 } from "node:fs";
|
|
6614
|
+
import path37 from "node:path";
|
|
6615
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
6403
6616
|
function resolveDefaultCliPath2() {
|
|
6404
|
-
return
|
|
6617
|
+
return path37.join(fileURLToPath3(new URL(".", import.meta.url)), "..", "cli.js");
|
|
6405
6618
|
}
|
|
6406
6619
|
function spawnMonitorSidecar(opts) {
|
|
6407
6620
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
6408
|
-
if (!
|
|
6621
|
+
if (!existsSync19(cliPath)) return void 0;
|
|
6409
6622
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
6410
6623
|
const { harnessRoot } = getHarnessPaths();
|
|
6411
|
-
const logPath =
|
|
6624
|
+
const logPath = path37.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
6412
6625
|
let logFd;
|
|
6413
6626
|
try {
|
|
6414
6627
|
logFd = openSync4(logPath, "a");
|
|
@@ -6528,22 +6741,22 @@ async function monitorTickCli(args) {
|
|
|
6528
6741
|
}
|
|
6529
6742
|
|
|
6530
6743
|
// src/package-version.ts
|
|
6531
|
-
import { existsSync as
|
|
6744
|
+
import { existsSync as existsSync20, readFileSync as readFileSync9 } from "node:fs";
|
|
6532
6745
|
import { dirname, join } from "node:path";
|
|
6533
|
-
import { fileURLToPath as
|
|
6746
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
6534
6747
|
function resolvePackageRoot(moduleUrl) {
|
|
6535
|
-
let dir = dirname(
|
|
6748
|
+
let dir = dirname(fileURLToPath4(moduleUrl));
|
|
6536
6749
|
for (let depth = 0; depth < 6; depth += 1) {
|
|
6537
|
-
if (
|
|
6750
|
+
if (existsSync20(join(dir, "package.json"))) return dir;
|
|
6538
6751
|
const parent = dirname(dir);
|
|
6539
6752
|
if (parent === dir) break;
|
|
6540
6753
|
dir = parent;
|
|
6541
6754
|
}
|
|
6542
|
-
throw new Error(`package.json not found above ${dirname(
|
|
6755
|
+
throw new Error(`package.json not found above ${dirname(fileURLToPath4(moduleUrl))}`);
|
|
6543
6756
|
}
|
|
6544
6757
|
function readOwnPackageVersion(moduleUrl = import.meta.url) {
|
|
6545
6758
|
const pkgPath = join(resolvePackageRoot(moduleUrl), "package.json");
|
|
6546
|
-
const pkg = JSON.parse(
|
|
6759
|
+
const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
|
|
6547
6760
|
if (typeof pkg.version !== "string" || !pkg.version.trim()) {
|
|
6548
6761
|
throw new Error(`Missing package.json version at ${pkgPath}`);
|
|
6549
6762
|
}
|
|
@@ -6564,12 +6777,12 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
|
|
|
6564
6777
|
}
|
|
6565
6778
|
|
|
6566
6779
|
// src/doctor/runtime-takeover.ts
|
|
6567
|
-
import
|
|
6780
|
+
import path39 from "node:path";
|
|
6568
6781
|
|
|
6569
6782
|
// src/doctor/runtime-takeover.probes.ts
|
|
6570
|
-
import { accessSync, constants, existsSync as
|
|
6571
|
-
import { homedir as
|
|
6572
|
-
import
|
|
6783
|
+
import { accessSync, constants, existsSync as existsSync21, readFileSync as readFileSync10 } from "node:fs";
|
|
6784
|
+
import { homedir as homedir7 } from "node:os";
|
|
6785
|
+
import path38 from "node:path";
|
|
6573
6786
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
6574
6787
|
function captureCommand(bin, args) {
|
|
6575
6788
|
try {
|
|
@@ -6598,7 +6811,7 @@ function tokenPrefix(token) {
|
|
|
6598
6811
|
return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
|
|
6599
6812
|
}
|
|
6600
6813
|
function isWritable(target) {
|
|
6601
|
-
if (!
|
|
6814
|
+
if (!existsSync21(target)) return false;
|
|
6602
6815
|
try {
|
|
6603
6816
|
accessSync(target, constants.W_OK);
|
|
6604
6817
|
return true;
|
|
@@ -6611,15 +6824,15 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
6611
6824
|
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
6612
6825
|
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
6613
6826
|
loadConfig: () => loadUserConfig(),
|
|
6614
|
-
configFilePath: () =>
|
|
6615
|
-
credentialsFilePath: () =>
|
|
6827
|
+
configFilePath: () => path38.join(homedir7(), ".kynver", "config.json"),
|
|
6828
|
+
credentialsFilePath: () => path38.join(homedir7(), ".kynver", "credentials"),
|
|
6616
6829
|
readCredentials: () => {
|
|
6617
|
-
const credPath =
|
|
6618
|
-
if (!
|
|
6830
|
+
const credPath = path38.join(homedir7(), ".kynver", "credentials");
|
|
6831
|
+
if (!existsSync21(credPath)) {
|
|
6619
6832
|
return { hasApiKey: false };
|
|
6620
6833
|
}
|
|
6621
6834
|
try {
|
|
6622
|
-
const parsed = JSON.parse(
|
|
6835
|
+
const parsed = JSON.parse(readFileSync10(credPath, "utf8"));
|
|
6623
6836
|
return {
|
|
6624
6837
|
hasApiKey: Boolean(parsed.apiKey?.trim()),
|
|
6625
6838
|
runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
|
|
@@ -6645,8 +6858,8 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
6645
6858
|
})()
|
|
6646
6859
|
}),
|
|
6647
6860
|
harnessRoot: () => resolveHarnessRoot(),
|
|
6648
|
-
legacyOpenclawHarnessRoot: () =>
|
|
6649
|
-
pathExists: (target) =>
|
|
6861
|
+
legacyOpenclawHarnessRoot: () => path38.join(homedir7(), ".openclaw", "harness"),
|
|
6862
|
+
pathExists: (target) => existsSync21(target),
|
|
6650
6863
|
pathWritable: (target) => isWritable(target),
|
|
6651
6864
|
vercelVersion: () => captureCommand("vercel", ["--version"]),
|
|
6652
6865
|
vercelWhoami: () => captureCommand("vercel", ["whoami"])
|
|
@@ -6661,8 +6874,36 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
6661
6874
|
const runnerQstash = env.kynverSchedulerProvider === "qstash";
|
|
6662
6875
|
const hostedDeployment = Boolean(env.qstashTokenPresent) || Boolean(env.kynverHostedDeployment);
|
|
6663
6876
|
const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
|
|
6664
|
-
const
|
|
6665
|
-
const
|
|
6877
|
+
const hostedSchedulerProcess = hostedDeployment && !daemonDispatchReady;
|
|
6878
|
+
const deploymentNeedsQstash = hostedSchedulerProcess && !env.qstashTokenPresent && env.kynverSchedulerProvider !== "qstash";
|
|
6879
|
+
const deploymentOpenclaw = hostedSchedulerProcess && env.kynverSchedulerProvider === "openclaw-cron";
|
|
6880
|
+
if (daemonDispatchReady && runnerOpenclaw) {
|
|
6881
|
+
return check({
|
|
6882
|
+
id: "hotspot_openclaw_scheduler",
|
|
6883
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6884
|
+
status: "warn",
|
|
6885
|
+
summary: "KYNVER_SCHEDULER_PROVIDER=openclaw-cron on this runner \u2014 dispatch is owned by kynver daemon; unset the OpenClaw override",
|
|
6886
|
+
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.",
|
|
6887
|
+
details: {
|
|
6888
|
+
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
6889
|
+
dispatchPath: "kynver-daemon-pipeline-tick",
|
|
6890
|
+
hostedDeployment
|
|
6891
|
+
}
|
|
6892
|
+
});
|
|
6893
|
+
}
|
|
6894
|
+
if (daemonDispatchReady) {
|
|
6895
|
+
return check({
|
|
6896
|
+
id: "hotspot_openclaw_scheduler",
|
|
6897
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6898
|
+
status: "pass",
|
|
6899
|
+
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",
|
|
6900
|
+
details: {
|
|
6901
|
+
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
6902
|
+
dispatchPath: "kynver-daemon-pipeline-tick",
|
|
6903
|
+
hostedDeployment
|
|
6904
|
+
}
|
|
6905
|
+
});
|
|
6906
|
+
}
|
|
6666
6907
|
if (runnerOpenclaw) {
|
|
6667
6908
|
return check({
|
|
6668
6909
|
id: "hotspot_openclaw_scheduler",
|
|
@@ -6678,7 +6919,7 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
6678
6919
|
id: "hotspot_openclaw_scheduler",
|
|
6679
6920
|
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6680
6921
|
status: "warn",
|
|
6681
|
-
summary: deploymentOpenclaw ? "Hosted deployment has KYNVER_SCHEDULER_PROVIDER=openclaw-cron \u2014 AgentOS scheduled ticks should use QStash" :
|
|
6922
|
+
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",
|
|
6682
6923
|
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.",
|
|
6683
6924
|
details: {
|
|
6684
6925
|
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
@@ -6687,15 +6928,15 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
6687
6928
|
}
|
|
6688
6929
|
});
|
|
6689
6930
|
}
|
|
6690
|
-
if (
|
|
6931
|
+
if (hostedDeployment && env.qstashTokenPresent && !env.kynverSchedulerProvider) {
|
|
6691
6932
|
return check({
|
|
6692
6933
|
id: "hotspot_openclaw_scheduler",
|
|
6693
6934
|
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6694
6935
|
status: "pass",
|
|
6695
|
-
summary:
|
|
6936
|
+
summary: "QSTASH_TOKEN present; hosted scheduler auto-selects qstash (explicit KYNVER_SCHEDULER_PROVIDER=qstash optional)",
|
|
6696
6937
|
details: {
|
|
6697
|
-
schedulerProvider:
|
|
6698
|
-
|
|
6938
|
+
schedulerProvider: null,
|
|
6939
|
+
qstashTokenPresent: true,
|
|
6699
6940
|
hostedDeployment
|
|
6700
6941
|
}
|
|
6701
6942
|
});
|
|
@@ -6789,8 +7030,14 @@ function assessUserConfig(probes) {
|
|
|
6789
7030
|
if (exists) {
|
|
6790
7031
|
const apiBaseUrl = config.apiBaseUrl?.trim();
|
|
6791
7032
|
const agentOsId = config.agentOsId?.trim();
|
|
6792
|
-
const
|
|
6793
|
-
const
|
|
7033
|
+
const resolvedDefaultRepo = resolveDefaultRepo({ config });
|
|
7034
|
+
const formatted = resolvedDefaultRepo ? formatResolvedDefaultRepo(resolvedDefaultRepo) : null;
|
|
7035
|
+
let defaultRepoRemediation;
|
|
7036
|
+
if (!resolvedDefaultRepo) {
|
|
7037
|
+
defaultRepoRemediation = "Run `kynver setup` from a Kynver checkout (auto-discovers repo) or `kynver setup --repo /path/to/Kynver`.";
|
|
7038
|
+
} else if (!resolvedDefaultRepo.persistedInConfig) {
|
|
7039
|
+
defaultRepoRemediation = "Run `kynver setup` (no --repo) to persist discovered defaultRepo in ~/.kynver/config.json.";
|
|
7040
|
+
}
|
|
6794
7041
|
checks.push(
|
|
6795
7042
|
check2({
|
|
6796
7043
|
id: "config_api_base_url",
|
|
@@ -6811,11 +7058,13 @@ function assessUserConfig(probes) {
|
|
|
6811
7058
|
check2({
|
|
6812
7059
|
id: "config_default_repo",
|
|
6813
7060
|
label: "Default repo path",
|
|
6814
|
-
status:
|
|
6815
|
-
summary:
|
|
6816
|
-
remediation:
|
|
7061
|
+
status: resolvedDefaultRepo ? "pass" : "warn",
|
|
7062
|
+
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)",
|
|
7063
|
+
remediation: defaultRepoRemediation,
|
|
6817
7064
|
details: {
|
|
6818
|
-
defaultRepo:
|
|
7065
|
+
defaultRepo: formatted?.defaultRepo ?? null,
|
|
7066
|
+
source: formatted?.source ?? null,
|
|
7067
|
+
persistedInConfig: formatted?.persistedInConfig ?? false,
|
|
6819
7068
|
harnessRoot: config.harnessRoot ? redactHomePath(config.harnessRoot) : null
|
|
6820
7069
|
}
|
|
6821
7070
|
})
|
|
@@ -6897,8 +7146,8 @@ function assessVercelCli(probes) {
|
|
|
6897
7146
|
}
|
|
6898
7147
|
function assessHarnessDirs(probes) {
|
|
6899
7148
|
const harnessRoot = probes.harnessRoot();
|
|
6900
|
-
const runsDir =
|
|
6901
|
-
const worktreesDir =
|
|
7149
|
+
const runsDir = path39.join(harnessRoot, "runs");
|
|
7150
|
+
const worktreesDir = path39.join(harnessRoot, "worktrees");
|
|
6902
7151
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
6903
7152
|
const displayRunsDir = redactHomePath(runsDir);
|
|
6904
7153
|
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
@@ -7101,9 +7350,9 @@ function usage(code = 0) {
|
|
|
7101
7350
|
"Usage:",
|
|
7102
7351
|
" kynver login --api-key KEY",
|
|
7103
7352
|
" kynver runner credential [--agent-os-id ID] [--base-url URL]",
|
|
7104
|
-
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--max-workers N] [--provider claude|cursor]",
|
|
7353
|
+
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--discover-repo] [--max-workers N] [--provider claude|cursor]",
|
|
7105
7354
|
" kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS]",
|
|
7106
|
-
" kynver run create --repo /path/repo [--name name] [--base origin/main]",
|
|
7355
|
+
" kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
|
|
7107
7356
|
" kynver run list",
|
|
7108
7357
|
" kynver run status --run RUN_ID",
|
|
7109
7358
|
" 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 /]",
|
|
@@ -7121,7 +7370,7 @@ function usage(code = 0) {
|
|
|
7121
7370
|
" kynver plan persist --operation create|add_version|update_metadata --title TITLE (--body-file PATH | --body TEXT) [--slug SLUG] [--plan PLAN_ID] [--summary TEXT] [--failure-kind approval_guard|auth|network|server|tool_interruption]",
|
|
7122
7371
|
" kynver plan outbox list",
|
|
7123
7372
|
" kynver plan outbox drain [--max N] [--id OUTBOX_ID]",
|
|
7124
|
-
" kynver cleanup [--execute] [--node-modules-age-ms MS] [--worktrees-age-ms MS] [--harness-root PATH] [--include-orphans]",
|
|
7373
|
+
" kynver cleanup [--execute] [--node-modules-age-ms MS] [--worktrees-age-ms MS] [--harness-root PATH] [--include-orphans] [--skip-finalize]",
|
|
7125
7374
|
" kynver monitor start --run RUN_ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS]",
|
|
7126
7375
|
" kynver monitor status [--run RUN_ID] [--name worker] [--tick]",
|
|
7127
7376
|
" kynver monitor stop --run RUN_ID [--name worker]",
|
|
@@ -7196,7 +7445,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
7196
7445
|
if (scope === "monitor" && action === "run-loop") return void await monitorRunLoopCli(args);
|
|
7197
7446
|
unknownCommand(scope, action);
|
|
7198
7447
|
}
|
|
7199
|
-
var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(
|
|
7448
|
+
var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(fileURLToPath5(import.meta.url));
|
|
7200
7449
|
if (isCliEntry) {
|
|
7201
7450
|
void main().catch((error) => {
|
|
7202
7451
|
console.error(error);
|