@kynver-app/runtime 0.1.49 → 0.1.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/cli.js +653 -493
- package/dist/cli.js.map +4 -4
- package/dist/default-repo-discovery.d.ts +15 -0
- package/dist/default-repo.d.ts +30 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +685 -488
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/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;
|
|
@@ -998,242 +1237,77 @@ var FAILURE_PATTERNS = [
|
|
|
998
1237
|
test: /\b(?:not logged in|unauthorized|authentication (?:failed|required)|invalid api key|missing api key|401)\b/i,
|
|
999
1238
|
label: "provider authentication failed"
|
|
1000
1239
|
}
|
|
1001
|
-
];
|
|
1002
|
-
function tidy2(errorText, max = 240) {
|
|
1003
|
-
const oneLine2 = errorText.replace(/\s+/g, " ").trim();
|
|
1004
|
-
return oneLine2.length > max ? `${oneLine2.slice(0, max - 1)}\u2026` : oneLine2;
|
|
1005
|
-
}
|
|
1006
|
-
function classifyExitFailure(errorText) {
|
|
1007
|
-
const text = (errorText ?? "").trim();
|
|
1008
|
-
if (!text) return null;
|
|
1009
|
-
for (const pattern of FAILURE_PATTERNS) {
|
|
1010
|
-
if (pattern.test.test(text)) {
|
|
1011
|
-
return { blocked: true, reason: `${pattern.label}: ${tidy2(text)}` };
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
return null;
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
// src/exited-salvage.ts
|
|
1018
|
-
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}` };
|
|
1240
|
+
];
|
|
1241
|
+
function tidy2(errorText, max = 240) {
|
|
1242
|
+
const oneLine2 = errorText.replace(/\s+/g, " ").trim();
|
|
1243
|
+
return oneLine2.length > max ? `${oneLine2.slice(0, max - 1)}\u2026` : oneLine2;
|
|
1162
1244
|
}
|
|
1163
|
-
function
|
|
1164
|
-
const
|
|
1165
|
-
|
|
1166
|
-
const
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
}
|
|
1170
|
-
const head = gitCapture(worktreePath, ["rev-parse", "HEAD"]);
|
|
1171
|
-
if (head.status !== 0) {
|
|
1172
|
-
return unknownAncestry(baseLabel, head.error || head.stderr || head.stdout || "failed to resolve HEAD");
|
|
1173
|
-
}
|
|
1174
|
-
let baseSha;
|
|
1175
|
-
if (pinnedBaseCommit) {
|
|
1176
|
-
baseSha = pinnedBaseCommit;
|
|
1177
|
-
} else {
|
|
1178
|
-
const baseHead = gitCapture(worktreePath, ["rev-parse", baseLabel]);
|
|
1179
|
-
if (baseHead.status !== 0) {
|
|
1180
|
-
return unknownAncestry(
|
|
1181
|
-
baseLabel,
|
|
1182
|
-
baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${baseLabel}`,
|
|
1183
|
-
head.stdout.trim()
|
|
1184
|
-
);
|
|
1245
|
+
function classifyExitFailure(errorText) {
|
|
1246
|
+
const text = (errorText ?? "").trim();
|
|
1247
|
+
if (!text) return null;
|
|
1248
|
+
for (const pattern of FAILURE_PATTERNS) {
|
|
1249
|
+
if (pattern.test.test(text)) {
|
|
1250
|
+
return { blocked: true, reason: `${pattern.label}: ${tidy2(text)}` };
|
|
1185
1251
|
}
|
|
1186
|
-
baseSha = baseHead.stdout.trim();
|
|
1187
1252
|
}
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1253
|
+
return null;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// src/exited-salvage.ts
|
|
1257
|
+
function trimOrNull(value) {
|
|
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
|
+
);
|
|
1199
1281
|
}
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
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`);
|
|
1285
|
+
}
|
|
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,10 +4809,10 @@ 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
|
|
4815
|
+
import path23 from "node:path";
|
|
4692
4816
|
var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
|
|
4693
4817
|
function terminalStatusFor(run) {
|
|
4694
4818
|
const names = Object.keys(run.workers || {});
|
|
@@ -4699,7 +4823,7 @@ function terminalStatusFor(run) {
|
|
|
4699
4823
|
let anyLandingBlocked = false;
|
|
4700
4824
|
for (const name of names) {
|
|
4701
4825
|
const worker = readJson(
|
|
4702
|
-
|
|
4826
|
+
path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4703
4827
|
void 0
|
|
4704
4828
|
);
|
|
4705
4829
|
if (!worker) continue;
|
|
@@ -4751,7 +4875,7 @@ function reconcileStaleWorkers() {
|
|
|
4751
4875
|
const now = Date.now();
|
|
4752
4876
|
for (const run of listRunRecords()) {
|
|
4753
4877
|
for (const name of Object.keys(run.workers || {})) {
|
|
4754
|
-
const workerPath =
|
|
4878
|
+
const workerPath = path24.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
4755
4879
|
const worker = readJson(workerPath, void 0);
|
|
4756
4880
|
if (!worker || worker.status !== "running") {
|
|
4757
4881
|
outcomes.push({
|
|
@@ -4845,7 +4969,7 @@ function reconcileRunsCli() {
|
|
|
4845
4969
|
}
|
|
4846
4970
|
|
|
4847
4971
|
// src/plan-progress-daemon-sync.ts
|
|
4848
|
-
import
|
|
4972
|
+
import path25 from "node:path";
|
|
4849
4973
|
|
|
4850
4974
|
// src/plan-progress-sync.ts
|
|
4851
4975
|
async function syncPlanProgress(args) {
|
|
@@ -4869,7 +4993,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
|
|
|
4869
4993
|
const outcomes = [];
|
|
4870
4994
|
for (const name of Object.keys(run.workers || {})) {
|
|
4871
4995
|
const worker = readJson(
|
|
4872
|
-
|
|
4996
|
+
path25.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
4873
4997
|
void 0
|
|
4874
4998
|
);
|
|
4875
4999
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -4918,7 +5042,7 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
|
4918
5042
|
}
|
|
4919
5043
|
|
|
4920
5044
|
// src/cleanup.ts
|
|
4921
|
-
import
|
|
5045
|
+
import path30 from "node:path";
|
|
4922
5046
|
|
|
4923
5047
|
// src/cleanup-types.ts
|
|
4924
5048
|
var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
|
|
@@ -4989,14 +5113,14 @@ function skipNodeModulesRemoval(input) {
|
|
|
4989
5113
|
}
|
|
4990
5114
|
|
|
4991
5115
|
// src/cleanup-execute.ts
|
|
4992
|
-
import { existsSync as
|
|
4993
|
-
import
|
|
5116
|
+
import { existsSync as existsSync16, rmSync } from "node:fs";
|
|
5117
|
+
import path27 from "node:path";
|
|
4994
5118
|
|
|
4995
5119
|
// src/cleanup-dir-size.ts
|
|
4996
|
-
import { existsSync as
|
|
4997
|
-
import
|
|
5120
|
+
import { existsSync as existsSync15, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
|
|
5121
|
+
import path26 from "node:path";
|
|
4998
5122
|
function directorySizeBytes(root, maxEntries = 5e4) {
|
|
4999
|
-
if (!
|
|
5123
|
+
if (!existsSync15(root)) return 0;
|
|
5000
5124
|
let total = 0;
|
|
5001
5125
|
let seen = 0;
|
|
5002
5126
|
const stack = [root];
|
|
@@ -5010,7 +5134,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
5010
5134
|
}
|
|
5011
5135
|
for (const name of entries) {
|
|
5012
5136
|
if (seen++ > maxEntries) return null;
|
|
5013
|
-
const full =
|
|
5137
|
+
const full = path26.join(current, name);
|
|
5014
5138
|
let st;
|
|
5015
5139
|
try {
|
|
5016
5140
|
st = statSync2(full);
|
|
@@ -5026,7 +5150,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
|
|
|
5026
5150
|
|
|
5027
5151
|
// src/cleanup-execute.ts
|
|
5028
5152
|
function removeNodeModules(candidate, execute) {
|
|
5029
|
-
if (!
|
|
5153
|
+
if (!existsSync16(candidate.path)) {
|
|
5030
5154
|
return {
|
|
5031
5155
|
...candidate,
|
|
5032
5156
|
executed: false,
|
|
@@ -5057,7 +5181,7 @@ function removeNodeModules(candidate, execute) {
|
|
|
5057
5181
|
}
|
|
5058
5182
|
}
|
|
5059
5183
|
function removeWorktree(candidate, execute) {
|
|
5060
|
-
if (!
|
|
5184
|
+
if (!existsSync16(candidate.path)) {
|
|
5061
5185
|
return {
|
|
5062
5186
|
...candidate,
|
|
5063
5187
|
executed: false,
|
|
@@ -5074,7 +5198,7 @@ function removeWorktree(candidate, execute) {
|
|
|
5074
5198
|
if (repo) {
|
|
5075
5199
|
git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
|
|
5076
5200
|
}
|
|
5077
|
-
if (
|
|
5201
|
+
if (existsSync16(candidate.path)) {
|
|
5078
5202
|
rmSync(candidate.path, { recursive: true, force: true });
|
|
5079
5203
|
}
|
|
5080
5204
|
return {
|
|
@@ -5094,20 +5218,20 @@ function removeWorktree(candidate, execute) {
|
|
|
5094
5218
|
}
|
|
5095
5219
|
}
|
|
5096
5220
|
function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
|
|
5097
|
-
const resolved =
|
|
5098
|
-
const nm = resolved.endsWith(`${
|
|
5221
|
+
const resolved = path27.resolve(targetPath);
|
|
5222
|
+
const nm = resolved.endsWith(`${path27.sep}node_modules`) ? resolved : null;
|
|
5099
5223
|
if (!nm) return "path_outside_harness";
|
|
5100
|
-
const rel =
|
|
5101
|
-
if (rel.startsWith("..") ||
|
|
5102
|
-
const parts = rel.split(
|
|
5224
|
+
const rel = path27.relative(worktreesDir, nm);
|
|
5225
|
+
if (rel.startsWith("..") || path27.isAbsolute(rel)) return "path_outside_harness";
|
|
5226
|
+
const parts = rel.split(path27.sep);
|
|
5103
5227
|
if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
|
|
5104
|
-
if (!resolved.startsWith(
|
|
5228
|
+
if (!resolved.startsWith(path27.resolve(harnessRoot))) return "path_outside_harness";
|
|
5105
5229
|
return null;
|
|
5106
5230
|
}
|
|
5107
5231
|
|
|
5108
5232
|
// src/cleanup-scan.ts
|
|
5109
|
-
import { existsSync as
|
|
5110
|
-
import
|
|
5233
|
+
import { existsSync as existsSync17, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
|
|
5234
|
+
import path28 from "node:path";
|
|
5111
5235
|
function pathAgeMs(target, now) {
|
|
5112
5236
|
try {
|
|
5113
5237
|
const mtime = statSync3(target).mtimeMs;
|
|
@@ -5117,17 +5241,17 @@ function pathAgeMs(target, now) {
|
|
|
5117
5241
|
}
|
|
5118
5242
|
}
|
|
5119
5243
|
function isPathInside(child, parent) {
|
|
5120
|
-
const rel =
|
|
5121
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
5244
|
+
const rel = path28.relative(parent, child);
|
|
5245
|
+
return rel === "" || !rel.startsWith("..") && !path28.isAbsolute(rel);
|
|
5122
5246
|
}
|
|
5123
5247
|
function scanNodeModulesCandidates(opts) {
|
|
5124
5248
|
const candidates = [];
|
|
5125
5249
|
const seen = /* @__PURE__ */ new Set();
|
|
5126
5250
|
for (const entry of opts.index.values()) {
|
|
5127
5251
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
5128
|
-
const nm =
|
|
5129
|
-
if (!
|
|
5130
|
-
const resolved =
|
|
5252
|
+
const nm = path28.join(entry.worktreePath, "node_modules");
|
|
5253
|
+
if (!existsSync17(nm)) continue;
|
|
5254
|
+
const resolved = path28.resolve(nm);
|
|
5131
5255
|
if (seen.has(resolved)) continue;
|
|
5132
5256
|
seen.add(resolved);
|
|
5133
5257
|
candidates.push({
|
|
@@ -5140,16 +5264,16 @@ function scanNodeModulesCandidates(opts) {
|
|
|
5140
5264
|
ageMs: pathAgeMs(resolved, opts.now)
|
|
5141
5265
|
});
|
|
5142
5266
|
}
|
|
5143
|
-
if (!opts.includeOrphans || !
|
|
5267
|
+
if (!opts.includeOrphans || !existsSync17(opts.worktreesDir)) return candidates;
|
|
5144
5268
|
for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
|
|
5145
5269
|
if (!runEntry.isDirectory()) continue;
|
|
5146
|
-
const runPath =
|
|
5270
|
+
const runPath = path28.join(opts.worktreesDir, runEntry.name);
|
|
5147
5271
|
for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
|
|
5148
5272
|
if (!workerEntry.isDirectory()) continue;
|
|
5149
|
-
const worktreePath =
|
|
5150
|
-
const nm =
|
|
5151
|
-
if (!
|
|
5152
|
-
const resolved =
|
|
5273
|
+
const worktreePath = path28.join(runPath, workerEntry.name);
|
|
5274
|
+
const nm = path28.join(worktreePath, "node_modules");
|
|
5275
|
+
if (!existsSync17(nm)) continue;
|
|
5276
|
+
const resolved = path28.resolve(nm);
|
|
5153
5277
|
if (seen.has(resolved)) continue;
|
|
5154
5278
|
if (!isPathInside(resolved, opts.harnessRoot)) continue;
|
|
5155
5279
|
seen.add(resolved);
|
|
@@ -5172,7 +5296,7 @@ function scanWorktreeCandidates(opts) {
|
|
|
5172
5296
|
for (const entry of opts.index.values()) {
|
|
5173
5297
|
if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
|
|
5174
5298
|
const resolved = entry.worktreePath;
|
|
5175
|
-
if (!
|
|
5299
|
+
if (!existsSync17(resolved)) continue;
|
|
5176
5300
|
if (seen.has(resolved)) continue;
|
|
5177
5301
|
seen.add(resolved);
|
|
5178
5302
|
candidates.push({
|
|
@@ -5189,17 +5313,17 @@ function scanWorktreeCandidates(opts) {
|
|
|
5189
5313
|
}
|
|
5190
5314
|
|
|
5191
5315
|
// src/cleanup-worktree-index.ts
|
|
5192
|
-
import
|
|
5316
|
+
import path29 from "node:path";
|
|
5193
5317
|
function buildWorktreeIndex() {
|
|
5194
5318
|
const index = /* @__PURE__ */ new Map();
|
|
5195
5319
|
for (const run of listRunRecords()) {
|
|
5196
5320
|
for (const name of Object.keys(run.workers || {})) {
|
|
5197
|
-
const workerPath =
|
|
5321
|
+
const workerPath = path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
|
|
5198
5322
|
const worker = readJson(workerPath, void 0);
|
|
5199
5323
|
if (!worker?.worktreePath) continue;
|
|
5200
5324
|
const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
|
|
5201
|
-
index.set(
|
|
5202
|
-
worktreePath:
|
|
5325
|
+
index.set(path29.resolve(worker.worktreePath), {
|
|
5326
|
+
worktreePath: path29.resolve(worker.worktreePath),
|
|
5203
5327
|
runId: run.id,
|
|
5204
5328
|
workerName: name,
|
|
5205
5329
|
run,
|
|
@@ -5214,7 +5338,7 @@ function buildWorktreeIndex() {
|
|
|
5214
5338
|
// src/cleanup.ts
|
|
5215
5339
|
function resolveOptions(options = {}) {
|
|
5216
5340
|
const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
|
|
5217
|
-
const { worktreesDir } = options.harnessRoot ? { worktreesDir:
|
|
5341
|
+
const { worktreesDir } = options.harnessRoot ? { worktreesDir: path30.join(harnessRoot, "worktrees") } : getHarnessPaths();
|
|
5218
5342
|
const execute = options.execute === true;
|
|
5219
5343
|
const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
|
|
5220
5344
|
const worktreesAgeMs = options.worktreesAgeMs ?? 0;
|
|
@@ -5258,7 +5382,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
5258
5382
|
actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
|
|
5259
5383
|
continue;
|
|
5260
5384
|
}
|
|
5261
|
-
const worktreePath =
|
|
5385
|
+
const worktreePath = path30.resolve(candidate.path, "..");
|
|
5262
5386
|
const indexed = index.get(worktreePath) ?? null;
|
|
5263
5387
|
const guardReason = skipNodeModulesRemoval({
|
|
5264
5388
|
indexed,
|
|
@@ -5274,7 +5398,7 @@ function runHarnessCleanup(options = {}) {
|
|
|
5274
5398
|
actions.push(removeNodeModules(candidate, resolved.execute));
|
|
5275
5399
|
}
|
|
5276
5400
|
for (const candidate of scanWorktreeCandidates(scanOpts)) {
|
|
5277
|
-
const indexed = index.get(
|
|
5401
|
+
const indexed = index.get(path30.resolve(candidate.path)) ?? null;
|
|
5278
5402
|
const guardReason = skipWorktreeRemoval({
|
|
5279
5403
|
indexed,
|
|
5280
5404
|
includeOrphans: resolved.includeOrphans,
|
|
@@ -5339,8 +5463,8 @@ function isPipelineCleanupEnabled() {
|
|
|
5339
5463
|
|
|
5340
5464
|
// src/installed-package-versions.ts
|
|
5341
5465
|
import { readFile } from "node:fs/promises";
|
|
5342
|
-
import { homedir as
|
|
5343
|
-
import
|
|
5466
|
+
import { homedir as homedir6 } from "node:os";
|
|
5467
|
+
import path31 from "node:path";
|
|
5344
5468
|
var MANAGED_PACKAGES = [
|
|
5345
5469
|
"@kynver-app/runtime",
|
|
5346
5470
|
"@kynver-app/openclaw-agent-os",
|
|
@@ -5354,13 +5478,13 @@ function unique(values) {
|
|
|
5354
5478
|
return [...new Set(values.filter((value) => Boolean(value)))];
|
|
5355
5479
|
}
|
|
5356
5480
|
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) ?
|
|
5481
|
+
const home = homedir6();
|
|
5482
|
+
const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path31.join(home, ".openclaw", "npm");
|
|
5483
|
+
const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path31.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path31.join(home, ".npm-global", "lib", "node_modules"));
|
|
5360
5484
|
return unique([
|
|
5361
|
-
|
|
5362
|
-
|
|
5363
|
-
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot :
|
|
5485
|
+
path31.join(openClawPrefix, "lib", "node_modules"),
|
|
5486
|
+
path31.join(openClawPrefix, "node_modules"),
|
|
5487
|
+
npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path31.join(npmGlobalRoot, "lib", "node_modules")
|
|
5364
5488
|
]);
|
|
5365
5489
|
}
|
|
5366
5490
|
async function readVersion(packageJsonPath) {
|
|
@@ -5376,7 +5500,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
|
|
|
5376
5500
|
const out = {};
|
|
5377
5501
|
for (const packageName of MANAGED_PACKAGES) {
|
|
5378
5502
|
for (const root of roots) {
|
|
5379
|
-
const packageJsonPath =
|
|
5503
|
+
const packageJsonPath = path31.join(root, packageName, "package.json");
|
|
5380
5504
|
const version = await readVersion(packageJsonPath);
|
|
5381
5505
|
if (!version) continue;
|
|
5382
5506
|
out[packageName] = { version, observedAt, path: packageJsonPath };
|
|
@@ -5392,7 +5516,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
5392
5516
|
const outcomes = [];
|
|
5393
5517
|
for (const name of Object.keys(run.workers || {})) {
|
|
5394
5518
|
const worker = readJson(
|
|
5395
|
-
|
|
5519
|
+
path32.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
5396
5520
|
void 0
|
|
5397
5521
|
);
|
|
5398
5522
|
if (!worker?.taskId || worker.localOnly) continue;
|
|
@@ -5534,7 +5658,7 @@ async function runDaemon(args) {
|
|
|
5534
5658
|
}
|
|
5535
5659
|
|
|
5536
5660
|
// src/plan-progress.ts
|
|
5537
|
-
import
|
|
5661
|
+
import path33 from "node:path";
|
|
5538
5662
|
|
|
5539
5663
|
// src/bounded-build/constants.ts
|
|
5540
5664
|
var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
|
|
@@ -5820,7 +5944,7 @@ async function emitPlanProgress(args) {
|
|
|
5820
5944
|
}
|
|
5821
5945
|
function verifyPlanLocal(args) {
|
|
5822
5946
|
const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
|
|
5823
|
-
const cwd =
|
|
5947
|
+
const cwd = path33.resolve(worktree);
|
|
5824
5948
|
const summary = runHarnessVerifyCommands(cwd);
|
|
5825
5949
|
const emitJson = args.json === true || args.json === "true";
|
|
5826
5950
|
const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
|
|
@@ -5869,9 +5993,9 @@ async function verifyPlan(args) {
|
|
|
5869
5993
|
}
|
|
5870
5994
|
|
|
5871
5995
|
// src/harness-verify-cli.ts
|
|
5872
|
-
import
|
|
5996
|
+
import path34 from "node:path";
|
|
5873
5997
|
function runHarnessVerifyCli(args) {
|
|
5874
|
-
const cwd =
|
|
5998
|
+
const cwd = path34.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
|
|
5875
5999
|
const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
|
|
5876
6000
|
const commands = [];
|
|
5877
6001
|
const rawCmd = args.command;
|
|
@@ -5915,7 +6039,7 @@ function runHarnessVerifyCli(args) {
|
|
|
5915
6039
|
}
|
|
5916
6040
|
|
|
5917
6041
|
// src/plan-persist-cli.ts
|
|
5918
|
-
import { readFileSync as
|
|
6042
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
5919
6043
|
var OPERATIONS = ["create", "add_version", "update_metadata"];
|
|
5920
6044
|
var FAILURE_KINDS = [
|
|
5921
6045
|
"approval_guard",
|
|
@@ -5927,7 +6051,7 @@ var FAILURE_KINDS = [
|
|
|
5927
6051
|
function readBodyArg(args) {
|
|
5928
6052
|
const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
|
|
5929
6053
|
if (bodyFile) {
|
|
5930
|
-
return { body:
|
|
6054
|
+
return { body: readFileSync8(bodyFile, "utf8"), bodyPathHint: bodyFile };
|
|
5931
6055
|
}
|
|
5932
6056
|
const inline = args.body ? String(args.body) : void 0;
|
|
5933
6057
|
if (inline) return { body: inline };
|
|
@@ -6015,7 +6139,7 @@ function runCleanupCli(args) {
|
|
|
6015
6139
|
}
|
|
6016
6140
|
|
|
6017
6141
|
// src/monitor/monitor.service.ts
|
|
6018
|
-
import
|
|
6142
|
+
import path36 from "node:path";
|
|
6019
6143
|
|
|
6020
6144
|
// src/monitor/monitor.classify.ts
|
|
6021
6145
|
function expectedLeaseOwner(runId) {
|
|
@@ -6071,11 +6195,11 @@ function classifyWorkerHealth(input) {
|
|
|
6071
6195
|
}
|
|
6072
6196
|
|
|
6073
6197
|
// src/monitor/monitor.store.ts
|
|
6074
|
-
import { existsSync as
|
|
6075
|
-
import
|
|
6198
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
|
|
6199
|
+
import path35 from "node:path";
|
|
6076
6200
|
function monitorsDir() {
|
|
6077
6201
|
const { harnessRoot } = getHarnessPaths();
|
|
6078
|
-
const dir =
|
|
6202
|
+
const dir = path35.join(harnessRoot, "monitors");
|
|
6079
6203
|
mkdirSync6(dir, { recursive: true });
|
|
6080
6204
|
return dir;
|
|
6081
6205
|
}
|
|
@@ -6083,7 +6207,7 @@ function monitorIdFor(runId, workerName) {
|
|
|
6083
6207
|
return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
|
|
6084
6208
|
}
|
|
6085
6209
|
function monitorPath(monitorId) {
|
|
6086
|
-
return
|
|
6210
|
+
return path35.join(monitorsDir(), `${monitorId}.json`);
|
|
6087
6211
|
}
|
|
6088
6212
|
function loadMonitorSession(monitorId) {
|
|
6089
6213
|
return readJson(monitorPath(monitorId), void 0);
|
|
@@ -6093,18 +6217,18 @@ function saveMonitorSession(session) {
|
|
|
6093
6217
|
}
|
|
6094
6218
|
function deleteMonitorSession(monitorId) {
|
|
6095
6219
|
const file = monitorPath(monitorId);
|
|
6096
|
-
if (!
|
|
6220
|
+
if (!existsSync18(file)) return false;
|
|
6097
6221
|
unlinkSync2(file);
|
|
6098
6222
|
return true;
|
|
6099
6223
|
}
|
|
6100
6224
|
function listMonitorSessions() {
|
|
6101
6225
|
const dir = monitorsDir();
|
|
6102
|
-
if (!
|
|
6226
|
+
if (!existsSync18(dir)) return [];
|
|
6103
6227
|
const entries = [];
|
|
6104
6228
|
for (const name of readdirSync7(dir)) {
|
|
6105
6229
|
if (!name.endsWith(".json")) continue;
|
|
6106
6230
|
const session = readJson(
|
|
6107
|
-
|
|
6231
|
+
path35.join(dir, name),
|
|
6108
6232
|
void 0
|
|
6109
6233
|
);
|
|
6110
6234
|
if (!session?.monitorId) continue;
|
|
@@ -6195,7 +6319,7 @@ async function fetchTaskLeasesForWorkers(input) {
|
|
|
6195
6319
|
// src/monitor/monitor.service.ts
|
|
6196
6320
|
function workerRecord2(runId, name) {
|
|
6197
6321
|
return readJson(
|
|
6198
|
-
|
|
6322
|
+
path36.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
|
|
6199
6323
|
void 0
|
|
6200
6324
|
);
|
|
6201
6325
|
}
|
|
@@ -6397,18 +6521,18 @@ async function runMonitorLoop(args) {
|
|
|
6397
6521
|
|
|
6398
6522
|
// src/monitor/monitor-spawn.ts
|
|
6399
6523
|
import { spawn as spawn4 } from "node:child_process";
|
|
6400
|
-
import { closeSync as closeSync4, existsSync as
|
|
6401
|
-
import
|
|
6402
|
-
import { fileURLToPath as
|
|
6524
|
+
import { closeSync as closeSync4, existsSync as existsSync19, openSync as openSync4 } from "node:fs";
|
|
6525
|
+
import path37 from "node:path";
|
|
6526
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
6403
6527
|
function resolveDefaultCliPath2() {
|
|
6404
|
-
return
|
|
6528
|
+
return path37.join(fileURLToPath3(new URL(".", import.meta.url)), "..", "cli.js");
|
|
6405
6529
|
}
|
|
6406
6530
|
function spawnMonitorSidecar(opts) {
|
|
6407
6531
|
const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
|
|
6408
|
-
if (!
|
|
6532
|
+
if (!existsSync19(cliPath)) return void 0;
|
|
6409
6533
|
const monitorId = monitorIdFor(opts.runId, opts.workerName);
|
|
6410
6534
|
const { harnessRoot } = getHarnessPaths();
|
|
6411
|
-
const logPath =
|
|
6535
|
+
const logPath = path37.join(harnessRoot, "monitors", `${monitorId}.log`);
|
|
6412
6536
|
let logFd;
|
|
6413
6537
|
try {
|
|
6414
6538
|
logFd = openSync4(logPath, "a");
|
|
@@ -6528,22 +6652,22 @@ async function monitorTickCli(args) {
|
|
|
6528
6652
|
}
|
|
6529
6653
|
|
|
6530
6654
|
// src/package-version.ts
|
|
6531
|
-
import { existsSync as
|
|
6655
|
+
import { existsSync as existsSync20, readFileSync as readFileSync9 } from "node:fs";
|
|
6532
6656
|
import { dirname, join } from "node:path";
|
|
6533
|
-
import { fileURLToPath as
|
|
6657
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
6534
6658
|
function resolvePackageRoot(moduleUrl) {
|
|
6535
|
-
let dir = dirname(
|
|
6659
|
+
let dir = dirname(fileURLToPath4(moduleUrl));
|
|
6536
6660
|
for (let depth = 0; depth < 6; depth += 1) {
|
|
6537
|
-
if (
|
|
6661
|
+
if (existsSync20(join(dir, "package.json"))) return dir;
|
|
6538
6662
|
const parent = dirname(dir);
|
|
6539
6663
|
if (parent === dir) break;
|
|
6540
6664
|
dir = parent;
|
|
6541
6665
|
}
|
|
6542
|
-
throw new Error(`package.json not found above ${dirname(
|
|
6666
|
+
throw new Error(`package.json not found above ${dirname(fileURLToPath4(moduleUrl))}`);
|
|
6543
6667
|
}
|
|
6544
6668
|
function readOwnPackageVersion(moduleUrl = import.meta.url) {
|
|
6545
6669
|
const pkgPath = join(resolvePackageRoot(moduleUrl), "package.json");
|
|
6546
|
-
const pkg = JSON.parse(
|
|
6670
|
+
const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
|
|
6547
6671
|
if (typeof pkg.version !== "string" || !pkg.version.trim()) {
|
|
6548
6672
|
throw new Error(`Missing package.json version at ${pkgPath}`);
|
|
6549
6673
|
}
|
|
@@ -6564,12 +6688,12 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
|
|
|
6564
6688
|
}
|
|
6565
6689
|
|
|
6566
6690
|
// src/doctor/runtime-takeover.ts
|
|
6567
|
-
import
|
|
6691
|
+
import path39 from "node:path";
|
|
6568
6692
|
|
|
6569
6693
|
// src/doctor/runtime-takeover.probes.ts
|
|
6570
|
-
import { accessSync, constants, existsSync as
|
|
6571
|
-
import { homedir as
|
|
6572
|
-
import
|
|
6694
|
+
import { accessSync, constants, existsSync as existsSync21, readFileSync as readFileSync10 } from "node:fs";
|
|
6695
|
+
import { homedir as homedir7 } from "node:os";
|
|
6696
|
+
import path38 from "node:path";
|
|
6573
6697
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
6574
6698
|
function captureCommand(bin, args) {
|
|
6575
6699
|
try {
|
|
@@ -6598,7 +6722,7 @@ function tokenPrefix(token) {
|
|
|
6598
6722
|
return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
|
|
6599
6723
|
}
|
|
6600
6724
|
function isWritable(target) {
|
|
6601
|
-
if (!
|
|
6725
|
+
if (!existsSync21(target)) return false;
|
|
6602
6726
|
try {
|
|
6603
6727
|
accessSync(target, constants.W_OK);
|
|
6604
6728
|
return true;
|
|
@@ -6611,15 +6735,15 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
6611
6735
|
commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
|
|
6612
6736
|
kynverVersion: (bin) => captureCommand(bin, ["--version"]),
|
|
6613
6737
|
loadConfig: () => loadUserConfig(),
|
|
6614
|
-
configFilePath: () =>
|
|
6615
|
-
credentialsFilePath: () =>
|
|
6738
|
+
configFilePath: () => path38.join(homedir7(), ".kynver", "config.json"),
|
|
6739
|
+
credentialsFilePath: () => path38.join(homedir7(), ".kynver", "credentials"),
|
|
6616
6740
|
readCredentials: () => {
|
|
6617
|
-
const credPath =
|
|
6618
|
-
if (!
|
|
6741
|
+
const credPath = path38.join(homedir7(), ".kynver", "credentials");
|
|
6742
|
+
if (!existsSync21(credPath)) {
|
|
6619
6743
|
return { hasApiKey: false };
|
|
6620
6744
|
}
|
|
6621
6745
|
try {
|
|
6622
|
-
const parsed = JSON.parse(
|
|
6746
|
+
const parsed = JSON.parse(readFileSync10(credPath, "utf8"));
|
|
6623
6747
|
return {
|
|
6624
6748
|
hasApiKey: Boolean(parsed.apiKey?.trim()),
|
|
6625
6749
|
runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
|
|
@@ -6645,8 +6769,8 @@ var defaultRuntimeTakeoverProbes = {
|
|
|
6645
6769
|
})()
|
|
6646
6770
|
}),
|
|
6647
6771
|
harnessRoot: () => resolveHarnessRoot(),
|
|
6648
|
-
legacyOpenclawHarnessRoot: () =>
|
|
6649
|
-
pathExists: (target) =>
|
|
6772
|
+
legacyOpenclawHarnessRoot: () => path38.join(homedir7(), ".openclaw", "harness"),
|
|
6773
|
+
pathExists: (target) => existsSync21(target),
|
|
6650
6774
|
pathWritable: (target) => isWritable(target),
|
|
6651
6775
|
vercelVersion: () => captureCommand("vercel", ["--version"]),
|
|
6652
6776
|
vercelWhoami: () => captureCommand("vercel", ["whoami"])
|
|
@@ -6661,8 +6785,36 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
6661
6785
|
const runnerQstash = env.kynverSchedulerProvider === "qstash";
|
|
6662
6786
|
const hostedDeployment = Boolean(env.qstashTokenPresent) || Boolean(env.kynverHostedDeployment);
|
|
6663
6787
|
const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
|
|
6664
|
-
const
|
|
6665
|
-
const
|
|
6788
|
+
const hostedSchedulerProcess = hostedDeployment && !daemonDispatchReady;
|
|
6789
|
+
const deploymentNeedsQstash = hostedSchedulerProcess && !env.qstashTokenPresent && env.kynverSchedulerProvider !== "qstash";
|
|
6790
|
+
const deploymentOpenclaw = hostedSchedulerProcess && env.kynverSchedulerProvider === "openclaw-cron";
|
|
6791
|
+
if (daemonDispatchReady && runnerOpenclaw) {
|
|
6792
|
+
return check({
|
|
6793
|
+
id: "hotspot_openclaw_scheduler",
|
|
6794
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6795
|
+
status: "warn",
|
|
6796
|
+
summary: "KYNVER_SCHEDULER_PROVIDER=openclaw-cron on this runner \u2014 dispatch is owned by kynver daemon; unset the OpenClaw override",
|
|
6797
|
+
remediation: "Unset KYNVER_SCHEDULER_PROVIDER on user runners. Use `kynver daemon` (pipeline-tick \u2192 operator/tick). On the Kynver server set KYNVER_SCHEDULER_PROVIDER=qstash when QStash is configured.",
|
|
6798
|
+
details: {
|
|
6799
|
+
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
6800
|
+
dispatchPath: "kynver-daemon-pipeline-tick",
|
|
6801
|
+
hostedDeployment
|
|
6802
|
+
}
|
|
6803
|
+
});
|
|
6804
|
+
}
|
|
6805
|
+
if (daemonDispatchReady) {
|
|
6806
|
+
return check({
|
|
6807
|
+
id: "hotspot_openclaw_scheduler",
|
|
6808
|
+
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6809
|
+
status: "pass",
|
|
6810
|
+
summary: runnerQstash ? "Runner override qstash present; hosted dispatch still owned by kynver daemon pipeline-tick" : "Hosted dispatch owned by kynver daemon (pipeline-tick \u2192 operator/tick); no OpenClaw cron on runner",
|
|
6811
|
+
details: {
|
|
6812
|
+
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
6813
|
+
dispatchPath: "kynver-daemon-pipeline-tick",
|
|
6814
|
+
hostedDeployment
|
|
6815
|
+
}
|
|
6816
|
+
});
|
|
6817
|
+
}
|
|
6666
6818
|
if (runnerOpenclaw) {
|
|
6667
6819
|
return check({
|
|
6668
6820
|
id: "hotspot_openclaw_scheduler",
|
|
@@ -6678,7 +6830,7 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
6678
6830
|
id: "hotspot_openclaw_scheduler",
|
|
6679
6831
|
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6680
6832
|
status: "warn",
|
|
6681
|
-
summary: deploymentOpenclaw ? "Hosted deployment has KYNVER_SCHEDULER_PROVIDER=openclaw-cron \u2014 AgentOS scheduled ticks should use QStash" :
|
|
6833
|
+
summary: deploymentOpenclaw ? "Hosted deployment has KYNVER_SCHEDULER_PROVIDER=openclaw-cron \u2014 AgentOS scheduled ticks should use QStash" : env.kynverSchedulerProvider ? `Hosted deployment without QSTASH_TOKEN (KYNVER_SCHEDULER_PROVIDER=${env.kynverSchedulerProvider}) \u2014 scheduler may fall back to openclaw-cron` : "KYNVER_SCHEDULER_PROVIDER unset on hosted deployment; without QSTASH_TOKEN the server may fall back to openclaw-cron",
|
|
6682
6834
|
remediation: "Set QSTASH_TOKEN and KYNVER_SCHEDULER_PROVIDER=qstash on the Kynver server. User runners use `kynver daemon` for dispatch and should not set a scheduler provider.",
|
|
6683
6835
|
details: {
|
|
6684
6836
|
schedulerProvider: env.kynverSchedulerProvider ?? null,
|
|
@@ -6687,15 +6839,15 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
|
|
|
6687
6839
|
}
|
|
6688
6840
|
});
|
|
6689
6841
|
}
|
|
6690
|
-
if (
|
|
6842
|
+
if (hostedDeployment && env.qstashTokenPresent && !env.kynverSchedulerProvider) {
|
|
6691
6843
|
return check({
|
|
6692
6844
|
id: "hotspot_openclaw_scheduler",
|
|
6693
6845
|
label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
|
|
6694
6846
|
status: "pass",
|
|
6695
|
-
summary:
|
|
6847
|
+
summary: "QSTASH_TOKEN present; hosted scheduler auto-selects qstash (explicit KYNVER_SCHEDULER_PROVIDER=qstash optional)",
|
|
6696
6848
|
details: {
|
|
6697
|
-
schedulerProvider:
|
|
6698
|
-
|
|
6849
|
+
schedulerProvider: null,
|
|
6850
|
+
qstashTokenPresent: true,
|
|
6699
6851
|
hostedDeployment
|
|
6700
6852
|
}
|
|
6701
6853
|
});
|
|
@@ -6789,8 +6941,14 @@ function assessUserConfig(probes) {
|
|
|
6789
6941
|
if (exists) {
|
|
6790
6942
|
const apiBaseUrl = config.apiBaseUrl?.trim();
|
|
6791
6943
|
const agentOsId = config.agentOsId?.trim();
|
|
6792
|
-
const
|
|
6793
|
-
const
|
|
6944
|
+
const resolvedDefaultRepo = resolveDefaultRepo({ config });
|
|
6945
|
+
const formatted = resolvedDefaultRepo ? formatResolvedDefaultRepo(resolvedDefaultRepo) : null;
|
|
6946
|
+
let defaultRepoRemediation;
|
|
6947
|
+
if (!resolvedDefaultRepo) {
|
|
6948
|
+
defaultRepoRemediation = "Run `kynver setup` from a Kynver checkout (auto-discovers repo) or `kynver setup --repo /path/to/Kynver`.";
|
|
6949
|
+
} else if (!resolvedDefaultRepo.persistedInConfig) {
|
|
6950
|
+
defaultRepoRemediation = "Run `kynver setup` (no --repo) to persist discovered defaultRepo in ~/.kynver/config.json.";
|
|
6951
|
+
}
|
|
6794
6952
|
checks.push(
|
|
6795
6953
|
check2({
|
|
6796
6954
|
id: "config_api_base_url",
|
|
@@ -6811,11 +6969,13 @@ function assessUserConfig(probes) {
|
|
|
6811
6969
|
check2({
|
|
6812
6970
|
id: "config_default_repo",
|
|
6813
6971
|
label: "Default repo path",
|
|
6814
|
-
status:
|
|
6815
|
-
summary:
|
|
6816
|
-
remediation:
|
|
6972
|
+
status: resolvedDefaultRepo ? "pass" : "warn",
|
|
6973
|
+
summary: resolvedDefaultRepo ? `${formatted.defaultRepo} (${resolvedDefaultRepo.source}${resolvedDefaultRepo.persistedInConfig ? "" : ", not persisted"})` : "Not set (pass --repo on `kynver run create` or run `kynver setup` to auto-discover)",
|
|
6974
|
+
remediation: defaultRepoRemediation,
|
|
6817
6975
|
details: {
|
|
6818
|
-
defaultRepo:
|
|
6976
|
+
defaultRepo: formatted?.defaultRepo ?? null,
|
|
6977
|
+
source: formatted?.source ?? null,
|
|
6978
|
+
persistedInConfig: formatted?.persistedInConfig ?? false,
|
|
6819
6979
|
harnessRoot: config.harnessRoot ? redactHomePath(config.harnessRoot) : null
|
|
6820
6980
|
}
|
|
6821
6981
|
})
|
|
@@ -6897,8 +7057,8 @@ function assessVercelCli(probes) {
|
|
|
6897
7057
|
}
|
|
6898
7058
|
function assessHarnessDirs(probes) {
|
|
6899
7059
|
const harnessRoot = probes.harnessRoot();
|
|
6900
|
-
const runsDir =
|
|
6901
|
-
const worktreesDir =
|
|
7060
|
+
const runsDir = path39.join(harnessRoot, "runs");
|
|
7061
|
+
const worktreesDir = path39.join(harnessRoot, "worktrees");
|
|
6902
7062
|
const displayHarnessRoot = redactHomePath(harnessRoot);
|
|
6903
7063
|
const displayRunsDir = redactHomePath(runsDir);
|
|
6904
7064
|
const displayWorktreesDir = redactHomePath(worktreesDir);
|
|
@@ -7101,9 +7261,9 @@ function usage(code = 0) {
|
|
|
7101
7261
|
"Usage:",
|
|
7102
7262
|
" kynver login --api-key KEY",
|
|
7103
7263
|
" 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]",
|
|
7264
|
+
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--discover-repo] [--max-workers N] [--provider claude|cursor]",
|
|
7105
7265
|
" 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]",
|
|
7266
|
+
" kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
|
|
7107
7267
|
" kynver run list",
|
|
7108
7268
|
" kynver run status --run RUN_ID",
|
|
7109
7269
|
" kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--target-task-id TASK_ID] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-8] [--disk-path /]",
|
|
@@ -7196,7 +7356,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
7196
7356
|
if (scope === "monitor" && action === "run-loop") return void await monitorRunLoopCli(args);
|
|
7197
7357
|
unknownCommand(scope, action);
|
|
7198
7358
|
}
|
|
7199
|
-
var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(
|
|
7359
|
+
var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(fileURLToPath5(import.meta.url));
|
|
7200
7360
|
if (isCliEntry) {
|
|
7201
7361
|
void main().catch((error) => {
|
|
7202
7362
|
console.error(error);
|