@kynver-app/runtime 0.1.49 → 0.1.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -35,43 +35,25 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
35
35
  }
36
36
 
37
37
  // src/dispatch.ts
38
- import path17 from "node:path";
38
+ import path18 from "node:path";
39
39
 
40
40
  // src/config.ts
41
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
41
+ import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
42
+ import { homedir as homedir3 } from "node:os";
43
+ import path4 from "node:path";
44
+
45
+ // src/default-repo-discovery.ts
46
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
42
47
  import { homedir as homedir2 } from "node:os";
43
48
  import path3 from "node:path";
49
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
44
50
 
45
- // src/path-values.ts
46
- import { homedir } from "node:os";
47
- import path from "node:path";
48
- function expandHomePath(value) {
49
- if (value === "~") return homedir();
50
- if (value.startsWith("~/") || value.startsWith("~\\")) {
51
- return path.join(homedir(), value.slice(2));
52
- }
53
- return value;
54
- }
55
- function resolveUserPath(value) {
56
- return path.resolve(expandHomePath(value));
57
- }
58
- function redactHomePath(value) {
59
- const expanded = expandHomePath(value);
60
- const resolved = path.resolve(expanded);
61
- const home = path.resolve(homedir());
62
- if (resolved === home) return "~";
63
- if (resolved.startsWith(`${home}${path.sep}`)) {
64
- return `~/${path.relative(home, resolved).split(path.sep).join("/")}`;
65
- }
66
- return resolved.replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^\/Users\/[^/]+(?=\/|$)/, "~");
67
- }
68
- function displayUserPath(value) {
69
- return redactHomePath(value);
70
- }
51
+ // src/git.ts
52
+ import { spawnSync } from "node:child_process";
71
53
 
72
54
  // src/util.ts
73
55
  import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, readdirSync, statSync, writeFileSync } from "node:fs";
74
- import path2 from "node:path";
56
+ import path from "node:path";
75
57
  function fail(message) {
76
58
  console.error(message);
77
59
  process.exit(1);
@@ -100,7 +82,7 @@ function readJson(file, fallback) {
100
82
  }
101
83
  }
102
84
  function writeJson(file, value) {
103
- mkdirSync(path2.dirname(file), { recursive: true });
85
+ mkdirSync(path.dirname(file), { recursive: true });
104
86
  writeFileSync(file, `${JSON.stringify(value, null, 2)}
105
87
  `);
106
88
  }
@@ -136,7 +118,7 @@ function tailFile(file, lines) {
136
118
  return data.split("\n").slice(-lines).join("\n");
137
119
  }
138
120
  function readMaybeFile(file) {
139
- return file ? readFileSync2(path2.resolve(file), "utf8") : "";
121
+ return file ? readFileSync2(path.resolve(file), "utf8") : "";
140
122
  }
141
123
  function listRunIds(runsDir) {
142
124
  if (!existsSync2(runsDir)) return [];
@@ -178,14 +160,280 @@ function secsAgo(ms) {
178
160
  return Math.max(0, Math.round((Date.now() - ms) / 1e3));
179
161
  }
180
162
 
163
+ // src/worker-env.ts
164
+ var FORBIDDEN_WORKER_ENV_KEYS = [
165
+ "ANTHROPIC_API_KEY",
166
+ "ANALYST_API_KEY",
167
+ "RECRUITER_API_KEY",
168
+ "AUTH_SECRET",
169
+ "NEXTAUTH_SECRET",
170
+ "DATABASE_URL",
171
+ "PRODUCTION_DATABASE_URL",
172
+ "REDIS_URL",
173
+ "GOOGLE_CLIENT_SECRET",
174
+ "GITHUB_CLIENT_SECRET",
175
+ "KYNVER_API_KEY",
176
+ "KYNVER_SERVICE_SECRET",
177
+ "KYNVER_RUNTIME_SECRET",
178
+ "OPENCLAW_CRON_SECRET",
179
+ "QSTASH_TOKEN",
180
+ "QSTASH_CURRENT_SIGNING_KEY",
181
+ "QSTASH_NEXT_SIGNING_KEY",
182
+ "TOOL_SECRETS_KEK",
183
+ "TOOL_EXECUTOR_DISPATCH_SECRET",
184
+ "CLOUDFLARE_API_TOKEN",
185
+ "STRIPE_SECRET_KEY",
186
+ "STRIPE_WEBHOOK_SECRET",
187
+ "STRIPE_IDENTITY_WEBHOOK_SECRET",
188
+ "VOYAGE_API_KEY",
189
+ "PERPLEXITY_API_KEY",
190
+ "FRED_API_KEY",
191
+ "FMP_API_KEY",
192
+ "CURSOR_API_KEY"
193
+ ];
194
+ var FORBIDDEN_KEY_SET = new Set(FORBIDDEN_WORKER_ENV_KEYS);
195
+ var FORBIDDEN_SUFFIXES = ["_SECRET", "_API_KEY"];
196
+ function isForbiddenWorkerEnvKey(key) {
197
+ if (FORBIDDEN_KEY_SET.has(key)) return true;
198
+ return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));
199
+ }
200
+ function listForbiddenWorkerEnvKeys(env) {
201
+ return Object.keys(env).filter(isForbiddenWorkerEnvKey).sort();
202
+ }
203
+ function scrubWorkerEnv(env) {
204
+ const next = { ...env };
205
+ for (const key of Object.keys(next)) {
206
+ if (isForbiddenWorkerEnvKey(key)) delete next[key];
207
+ }
208
+ return next;
209
+ }
210
+ function auditWorkerEnv(env) {
211
+ const forbiddenPresent = listForbiddenWorkerEnvKeys(env);
212
+ return { forbiddenPresent, safe: forbiddenPresent.length === 0 };
213
+ }
214
+ function scrubClaudeEnv(env) {
215
+ return scrubWorkerEnv(env);
216
+ }
217
+
218
+ // src/git.ts
219
+ function git(cwd, args, options = {}) {
220
+ const res = spawnSync("git", args, { cwd, encoding: "utf8" });
221
+ if (res.status !== 0 && !options.allowFailure) {
222
+ const message = `git ${args.join(" ")} failed: ${res.stderr || res.stdout}`;
223
+ if (options.throwError) throw new Error(message);
224
+ fail(message);
225
+ }
226
+ return res.stdout || "";
227
+ }
228
+ function ensureGitRepo(repo) {
229
+ git(repo, ["rev-parse", "--show-toplevel"]);
230
+ }
231
+ function gitStatusShort(worktreePath) {
232
+ return git(worktreePath, ["status", "--short"], { allowFailure: true }).split("\n").map((line) => line.trim()).filter(Boolean);
233
+ }
234
+ function gitCapture(cwd, args) {
235
+ try {
236
+ const res = spawnSync("git", args, { cwd, encoding: "utf8" });
237
+ return {
238
+ status: res.status,
239
+ stdout: res.stdout || "",
240
+ stderr: res.stderr || "",
241
+ error: res.error ? res.error.message : null
242
+ };
243
+ } catch (error) {
244
+ return {
245
+ status: null,
246
+ stdout: "",
247
+ stderr: "",
248
+ error: error.message
249
+ };
250
+ }
251
+ }
252
+ function gitIsAncestor(cwd, ancestor, descendant) {
253
+ const res = gitCapture(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]);
254
+ if (res.status === 0) return { isAncestor: true, error: null };
255
+ if (res.status === 1) return { isAncestor: false, error: null };
256
+ return { isAncestor: null, error: res.error || res.stderr || res.stdout || `git exited ${res.status}` };
257
+ }
258
+ function computeGitAncestry(worktreePath, baseOrOptions = "origin/main") {
259
+ const options = typeof baseOrOptions === "string" ? { base: baseOrOptions } : baseOrOptions;
260
+ const baseLabel = options.baseCommit?.trim() || options.base?.trim() || "origin/main";
261
+ const pinnedBaseCommit = options.baseCommit?.trim() || null;
262
+ if (!worktreePath) {
263
+ return unknownAncestry(baseLabel, "missing worktree path");
264
+ }
265
+ const head = gitCapture(worktreePath, ["rev-parse", "HEAD"]);
266
+ if (head.status !== 0) {
267
+ return unknownAncestry(baseLabel, head.error || head.stderr || head.stdout || "failed to resolve HEAD");
268
+ }
269
+ let baseSha;
270
+ if (pinnedBaseCommit) {
271
+ baseSha = pinnedBaseCommit;
272
+ } else {
273
+ const baseHead = gitCapture(worktreePath, ["rev-parse", baseLabel]);
274
+ if (baseHead.status !== 0) {
275
+ return unknownAncestry(
276
+ baseLabel,
277
+ baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${baseLabel}`,
278
+ head.stdout.trim()
279
+ );
280
+ }
281
+ baseSha = baseHead.stdout.trim();
282
+ }
283
+ const headSha = head.stdout.trim();
284
+ if (headSha === baseSha) {
285
+ return {
286
+ checked: true,
287
+ base: baseLabel,
288
+ head: headSha,
289
+ baseHead: baseSha,
290
+ baseIsAncestorOfHead: true,
291
+ headIsAncestorOfBase: true,
292
+ relation: "synced"
293
+ };
294
+ }
295
+ const baseIsAncestorOfHead = gitIsAncestor(worktreePath, baseSha, headSha);
296
+ const headIsAncestorOfBase = gitIsAncestor(worktreePath, headSha, baseSha);
297
+ const error = baseIsAncestorOfHead.error || headIsAncestorOfBase.error || void 0;
298
+ if (baseIsAncestorOfHead.isAncestor == null || headIsAncestorOfBase.isAncestor == null) {
299
+ return {
300
+ checked: false,
301
+ base: baseLabel,
302
+ head: headSha,
303
+ baseHead: baseSha,
304
+ baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
305
+ headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
306
+ relation: "unknown",
307
+ ...error ? { error } : {}
308
+ };
309
+ }
310
+ const relation = baseIsAncestorOfHead.isAncestor ? "ahead" : headIsAncestorOfBase.isAncestor ? "merged" : "diverged";
311
+ return {
312
+ checked: true,
313
+ base: baseLabel,
314
+ head: headSha,
315
+ baseHead: baseSha,
316
+ baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
317
+ headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
318
+ relation,
319
+ ...error ? { error } : {}
320
+ };
321
+ }
322
+ function unknownAncestry(base, error, head = null) {
323
+ return {
324
+ checked: false,
325
+ base,
326
+ head,
327
+ baseHead: null,
328
+ baseIsAncestorOfHead: null,
329
+ headIsAncestorOfBase: null,
330
+ relation: "unknown",
331
+ error
332
+ };
333
+ }
334
+
335
+ // src/path-values.ts
336
+ import { homedir } from "node:os";
337
+ import path2 from "node:path";
338
+ function expandHomePath(value) {
339
+ if (value === "~") return homedir();
340
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
341
+ return path2.join(homedir(), value.slice(2));
342
+ }
343
+ return value;
344
+ }
345
+ function resolveUserPath(value) {
346
+ return path2.resolve(expandHomePath(value));
347
+ }
348
+ function redactHomePath(value) {
349
+ const expanded = expandHomePath(value);
350
+ const resolved = path2.resolve(expanded);
351
+ const home = path2.resolve(homedir());
352
+ if (resolved === home) return "~";
353
+ if (resolved.startsWith(`${home}${path2.sep}`)) {
354
+ return `~/${path2.relative(home, resolved).split(path2.sep).join("/")}`;
355
+ }
356
+ return resolved.replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^\/Users\/[^/]+(?=\/|$)/, "~");
357
+ }
358
+ function displayUserPath(value) {
359
+ return redactHomePath(value);
360
+ }
361
+
362
+ // src/default-repo-discovery.ts
363
+ var WELL_KNOWN_REPO_DIRS = ["Kynver", "repos/Kynver", "code/Kynver", "projects/Kynver"];
364
+ function readPackageName(repoRoot) {
365
+ const pkgPath = path3.join(repoRoot, "package.json");
366
+ if (!existsSync3(pkgPath)) return null;
367
+ try {
368
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
369
+ return typeof pkg.name === "string" ? pkg.name.trim() : null;
370
+ } catch {
371
+ return null;
372
+ }
373
+ }
374
+ function isKynverMonorepoRoot(repoRoot) {
375
+ return readPackageName(repoRoot) === "kynver";
376
+ }
377
+ function gitRepoRoot(startDir) {
378
+ const resolvedStart = path3.resolve(startDir);
379
+ if (!existsSync3(resolvedStart)) return null;
380
+ const probe = gitCapture(resolvedStart, ["rev-parse", "--show-toplevel"]);
381
+ if (probe.status !== 0) return null;
382
+ const root = probe.stdout.trim();
383
+ return root.length ? path3.resolve(root) : null;
384
+ }
385
+ function resolveRuntimePackageRoot(moduleUrl = import.meta.url) {
386
+ let dir = path3.dirname(fileURLToPath2(moduleUrl));
387
+ for (let depth = 0; depth < 8; depth += 1) {
388
+ const pkgPath = path3.join(dir, "package.json");
389
+ if (existsSync3(pkgPath)) {
390
+ try {
391
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
392
+ if (pkg.name === "@kynver-app/runtime") return dir;
393
+ } catch {
394
+ }
395
+ }
396
+ const parent = path3.dirname(dir);
397
+ if (parent === dir) break;
398
+ dir = parent;
399
+ }
400
+ return null;
401
+ }
402
+ function pushCandidate(seen, out, repo, source) {
403
+ if (!repo) return;
404
+ const resolved = path3.resolve(repo);
405
+ if (seen.has(resolved)) return;
406
+ if (!isKynverMonorepoRoot(resolved)) return;
407
+ seen.add(resolved);
408
+ out.push({ repo: resolved, source });
409
+ }
410
+ function discoverDefaultRepoCandidates(opts) {
411
+ const cwd = opts?.cwd ?? process.cwd();
412
+ const seen = /* @__PURE__ */ new Set();
413
+ const candidates = [];
414
+ pushCandidate(seen, candidates, gitRepoRoot(cwd), "cwd_git");
415
+ const runtimePkgRoot = resolveRuntimePackageRoot(opts?.runtimeModuleUrl ?? import.meta.url);
416
+ if (runtimePkgRoot) {
417
+ pushCandidate(seen, candidates, gitRepoRoot(runtimePkgRoot), "runtime_checkout");
418
+ }
419
+ const home = homedir2();
420
+ for (const rel of WELL_KNOWN_REPO_DIRS) {
421
+ pushCandidate(seen, candidates, resolveUserPath(path3.join(home, rel)), "well_known_path");
422
+ }
423
+ return candidates;
424
+ }
425
+ function discoverDefaultRepo(opts) {
426
+ return discoverDefaultRepoCandidates(opts)[0] ?? null;
427
+ }
428
+
181
429
  // src/config.ts
182
- var CONFIG_DIR = path3.join(homedir2(), ".kynver");
183
- var CONFIG_FILE = path3.join(CONFIG_DIR, "config.json");
184
- var CREDENTIALS_FILE = path3.join(CONFIG_DIR, "credentials");
430
+ var CONFIG_DIR = path4.join(homedir3(), ".kynver");
431
+ var CONFIG_FILE = path4.join(CONFIG_DIR, "config.json");
432
+ var CREDENTIALS_FILE = path4.join(CONFIG_DIR, "credentials");
185
433
  function loadUserConfig() {
186
- if (!existsSync3(CONFIG_FILE)) return {};
434
+ if (!existsSync4(CONFIG_FILE)) return {};
187
435
  try {
188
- return JSON.parse(readFileSync3(CONFIG_FILE, "utf8"));
436
+ return JSON.parse(readFileSync4(CONFIG_FILE, "utf8"));
189
437
  } catch {
190
438
  return {};
191
439
  }
@@ -209,7 +457,8 @@ function inferSetupFields(existing, args) {
209
457
  const creds = loadCredentialsFile();
210
458
  const apiBaseUrl = (typeof args.apiBaseUrl === "string" ? args.apiBaseUrl : void 0) || existing.apiBaseUrl?.trim() || process.env.KYNVER_API_URL?.trim() || process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim();
211
459
  const agentOsId = (typeof args.agentOsId === "string" ? args.agentOsId : void 0) || existing.agentOsId?.trim() || process.env.KYNVER_AGENT_OS_ID?.trim() || (creds.runnerToken?.trim().startsWith("krc1.") ? creds.runnerTokenAgentOsId?.trim() : void 0);
212
- const defaultRepo = (typeof args.repo === "string" ? args.repo : void 0) || existing.defaultRepo?.trim() || process.env.KYNVER_DEFAULT_REPO?.trim() || process.env.KYNVER_HARNESS_REPO?.trim();
460
+ const explicitRepo = typeof args.repo === "string" ? args.repo : args.discoverRepo === true || args.discoverRepo === "true" ? discoverDefaultRepo()?.repo : void 0;
461
+ const defaultRepo = explicitRepo || existing.defaultRepo?.trim() || process.env.KYNVER_DEFAULT_REPO?.trim() || process.env.KYNVER_HARNESS_REPO?.trim() || discoverDefaultRepo()?.repo;
213
462
  const harnessRoot = (typeof args.harnessRoot === "string" ? args.harnessRoot : void 0) || existing.harnessRoot?.trim() || process.env.KYNVER_HARNESS_ROOT?.trim() || process.env.OPUS_HARNESS_ROOT?.trim();
214
463
  return {
215
464
  ...apiBaseUrl ? { apiBaseUrl: trimTrailingSlash(apiBaseUrl) } : {},
@@ -220,9 +469,9 @@ function inferSetupFields(existing, args) {
220
469
  };
221
470
  }
222
471
  function loadCredentialsFile() {
223
- if (!existsSync3(CREDENTIALS_FILE)) return {};
472
+ if (!existsSync4(CREDENTIALS_FILE)) return {};
224
473
  try {
225
- return JSON.parse(readFileSync3(CREDENTIALS_FILE, "utf8"));
474
+ return JSON.parse(readFileSync4(CREDENTIALS_FILE, "utf8"));
226
475
  } catch {
227
476
  return {};
228
477
  }
@@ -508,12 +757,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
508
757
  var DEFAULT_MAX_USED_PERCENT = 80;
509
758
  var DEFAULT_HARD_MAX_USED_PERCENT = 90;
510
759
  function observeRunnerDiskGate(input = {}) {
511
- const path38 = input.diskPath?.trim() || "/";
760
+ const path40 = input.diskPath?.trim() || "/";
512
761
  const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
513
762
  const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
514
763
  const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
515
764
  const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
516
- const stats = statfsSync(path38);
765
+ const stats = statfsSync(path40);
517
766
  const freeBytes = Number(stats.bavail) * Number(stats.bsize);
518
767
  const totalBytes = Number(stats.blocks) * Number(stats.bsize);
519
768
  const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
@@ -533,7 +782,7 @@ function observeRunnerDiskGate(input = {}) {
533
782
  }
534
783
  return {
535
784
  ok,
536
- path: path38,
785
+ path: path40,
537
786
  freeBytes,
538
787
  totalBytes,
539
788
  usedPercent,
@@ -549,7 +798,7 @@ function observeRunnerDiskGate(input = {}) {
549
798
  import os2 from "node:os";
550
799
 
551
800
  // src/bounded-build/meminfo.ts
552
- import { readFileSync as readFileSync4 } from "node:fs";
801
+ import { readFileSync as readFileSync5 } from "node:fs";
553
802
  import os from "node:os";
554
803
  function readMemAvailableBytes(meminfoText) {
555
804
  if (meminfoText !== void 0) {
@@ -559,7 +808,7 @@ function readMemAvailableBytes(meminfoText) {
559
808
  }
560
809
  if (process.platform === "linux") {
561
810
  try {
562
- const meminfo = readFileSync4("/proc/meminfo", "utf8");
811
+ const meminfo = readFileSync5("/proc/meminfo", "utf8");
563
812
  const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m);
564
813
  if (match) return Number(match[1]) * 1024;
565
814
  } catch {
@@ -569,37 +818,37 @@ function readMemAvailableBytes(meminfoText) {
569
818
  }
570
819
 
571
820
  // src/resource-gate.ts
572
- import path6 from "node:path";
821
+ import path7 from "node:path";
573
822
 
574
823
  // src/run-store.ts
575
- import { existsSync as existsSync5, readdirSync as readdirSync2 } from "node:fs";
576
- import path5 from "node:path";
824
+ import { existsSync as existsSync6, readdirSync as readdirSync2 } from "node:fs";
825
+ import path6 from "node:path";
577
826
 
578
827
  // src/paths.ts
579
- import { existsSync as existsSync4 } from "node:fs";
580
- import { homedir as homedir3 } from "node:os";
581
- import path4 from "node:path";
582
- var LEGACY_ROOT = path4.join(homedir3(), ".openclaw", "harness");
828
+ import { existsSync as existsSync5 } from "node:fs";
829
+ import { homedir as homedir4 } from "node:os";
830
+ import path5 from "node:path";
831
+ var LEGACY_ROOT = path5.join(homedir4(), ".openclaw", "harness");
583
832
  function resolveHarnessRoot() {
584
833
  const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
585
834
  if (env) return resolveUserPath(env);
586
835
  const configured = loadUserConfig().harnessRoot?.trim();
587
836
  if (configured) return resolveUserPath(configured);
588
- const kynverRoot = path4.join(homedir3(), ".kynver", "harness");
589
- if (existsSync4(kynverRoot)) return kynverRoot;
590
- if (existsSync4(LEGACY_ROOT)) return LEGACY_ROOT;
837
+ const kynverRoot = path5.join(homedir4(), ".kynver", "harness");
838
+ if (existsSync5(kynverRoot)) return kynverRoot;
839
+ if (existsSync5(LEGACY_ROOT)) return LEGACY_ROOT;
591
840
  return kynverRoot;
592
841
  }
593
842
  function getHarnessPaths() {
594
843
  const harnessRoot = resolveHarnessRoot();
595
844
  return {
596
845
  harnessRoot,
597
- runsDir: path4.join(harnessRoot, "runs"),
598
- worktreesDir: path4.join(harnessRoot, "worktrees")
846
+ runsDir: path5.join(harnessRoot, "runs"),
847
+ worktreesDir: path5.join(harnessRoot, "worktrees")
599
848
  };
600
849
  }
601
850
  function runDir(runsDir, id) {
602
- return path4.join(runsDir, safeSlug(id));
851
+ return path5.join(runsDir, safeSlug(id));
603
852
  }
604
853
 
605
854
  // src/run-store.ts
@@ -608,16 +857,16 @@ function getPaths() {
608
857
  }
609
858
  function loadRun(id) {
610
859
  const { runsDir } = getPaths();
611
- return readJson(path5.join(runDir(runsDir, safeSlug(id)), "run.json"));
860
+ return readJson(path6.join(runDir(runsDir, safeSlug(id)), "run.json"));
612
861
  }
613
862
  function listRunRecords() {
614
863
  const { runsDir } = getPaths();
615
- if (!existsSync5(runsDir)) return [];
864
+ if (!existsSync6(runsDir)) return [];
616
865
  const runs = [];
617
866
  for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
618
867
  if (!entry.isDirectory()) continue;
619
868
  const run = readJson(
620
- path5.join(runsDir, entry.name, "run.json"),
869
+ path6.join(runsDir, entry.name, "run.json"),
621
870
  void 0
622
871
  );
623
872
  if (run?.id) runs.push(run);
@@ -627,16 +876,16 @@ function listRunRecords() {
627
876
  function loadWorker(runId, name) {
628
877
  const { runsDir } = getPaths();
629
878
  return readJson(
630
- path5.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
879
+ path6.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
631
880
  );
632
881
  }
633
882
  function saveRun(run) {
634
883
  const { runsDir } = getPaths();
635
- writeJson(path5.join(runDir(runsDir, run.id), "run.json"), run);
884
+ writeJson(path6.join(runDir(runsDir, run.id), "run.json"), run);
636
885
  }
637
886
  function saveWorker(runId, worker) {
638
887
  const { runsDir } = getPaths();
639
- writeJson(path5.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
888
+ writeJson(path6.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
640
889
  }
641
890
  function runDirectory(id) {
642
891
  const { runsDir } = getPaths();
@@ -644,7 +893,7 @@ function runDirectory(id) {
644
893
  }
645
894
 
646
895
  // src/heartbeat.ts
647
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
896
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
648
897
  var HEARTBEAT_FUTURE_SKEW_MS = 6e4;
649
898
  function isTerminalHeartbeatPhase(phase) {
650
899
  return phase === "complete";
@@ -663,10 +912,10 @@ function parseHeartbeat(file) {
663
912
  heartbeatBlocker: null,
664
913
  timestampAnomalies: []
665
914
  };
666
- if (!existsSync6(file)) return result;
915
+ if (!existsSync7(file)) return result;
667
916
  const maxFutureMs = Date.now() + HEARTBEAT_FUTURE_SKEW_MS;
668
917
  const clampedTo = new Date(maxFutureMs).toISOString();
669
- const lines = readFileSync5(file, "utf8").split("\n").filter(Boolean);
918
+ const lines = readFileSync6(file, "utf8").split("\n").filter(Boolean);
670
919
  for (const line of lines) {
671
920
  const entry = safeJson(line);
672
921
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
@@ -693,7 +942,7 @@ function parseHeartbeat(file) {
693
942
  }
694
943
 
695
944
  // src/stream.ts
696
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
945
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "node:fs";
697
946
 
698
947
  // src/shell-command-outcome.ts
699
948
  var NPM_AUDIT_RE = /\bnpm\s+audit\b/i;
@@ -897,8 +1146,8 @@ function parseHarnessStream(file) {
897
1146
  error: null,
898
1147
  lastShellOutcome: null
899
1148
  };
900
- if (!existsSync7(file)) return result;
901
- const lines = readFileSync6(file, "utf8").split("\n").filter(Boolean);
1149
+ if (!existsSync8(file)) return result;
1150
+ const lines = readFileSync7(file, "utf8").split("\n").filter(Boolean);
902
1151
  for (const line of lines) {
903
1152
  const event = safeJson(line);
904
1153
  if (!event) continue;
@@ -1069,221 +1318,46 @@ function hasFinalResult(value) {
1069
1318
  return true;
1070
1319
  }
1071
1320
  function committedHeadFromAncestry(ancestry) {
1072
- if (!ancestry?.checked) return null;
1073
- if (ancestry.headIsAncestorOfBase !== false) return null;
1074
- return trimOrNull(ancestry.head);
1075
- }
1076
- function buildAttentionReason(kind, uncommittedCount, headCommit) {
1077
- const parts = ["exited_with_changes_salvage"];
1078
- if (kind === "uncommitted" || kind === "both") {
1079
- parts.push(
1080
- `${uncommittedCount} uncommitted change${uncommittedCount === 1 ? "" : "s"} with no final result`
1081
- );
1082
- }
1083
- if ((kind === "committed_ahead" || kind === "both") && headCommit) {
1084
- const sha = headCommit.length > 12 ? headCommit.slice(0, 12) : headCommit;
1085
- parts.push(`commit ${sha} ahead of base with no final result`);
1086
- }
1087
- parts.push("review worktree \u2014 commit, open a PR, or run a salvage worker before discarding");
1088
- return parts.join(": ");
1089
- }
1090
- function assessExitedWorkerSalvage(input) {
1091
- if (input.alive || hasFinalResult(input.finalResult)) return null;
1092
- const uncommittedCount = (input.changedFiles ?? []).filter((line) => line.trim()).length;
1093
- const headCommit = trimOrNull(input.headCommit) ?? committedHeadFromAncestry(input.gitAncestry);
1094
- const hasUncommitted = uncommittedCount > 0;
1095
- const hasCommittedAhead = Boolean(headCommit);
1096
- if (!hasUncommitted && !hasCommittedAhead) {
1097
- return {
1098
- kind: "none",
1099
- salvageable: false,
1100
- uncommittedCount: 0,
1101
- headCommit: null,
1102
- attentionReason: "process exited without a final result"
1103
- };
1104
- }
1105
- const kind = hasUncommitted && hasCommittedAhead ? "both" : hasUncommitted ? "uncommitted" : "committed_ahead";
1106
- return {
1107
- kind,
1108
- salvageable: true,
1109
- uncommittedCount,
1110
- headCommit,
1111
- attentionReason: buildAttentionReason(kind, uncommittedCount, headCommit)
1112
- };
1113
- }
1114
-
1115
- // src/git.ts
1116
- import { spawnSync } from "node:child_process";
1117
-
1118
- // src/worker-env.ts
1119
- var FORBIDDEN_WORKER_ENV_KEYS = [
1120
- "ANTHROPIC_API_KEY",
1121
- "ANALYST_API_KEY",
1122
- "RECRUITER_API_KEY",
1123
- "AUTH_SECRET",
1124
- "NEXTAUTH_SECRET",
1125
- "DATABASE_URL",
1126
- "PRODUCTION_DATABASE_URL",
1127
- "REDIS_URL",
1128
- "GOOGLE_CLIENT_SECRET",
1129
- "GITHUB_CLIENT_SECRET",
1130
- "KYNVER_API_KEY",
1131
- "KYNVER_SERVICE_SECRET",
1132
- "KYNVER_RUNTIME_SECRET",
1133
- "OPENCLAW_CRON_SECRET",
1134
- "QSTASH_TOKEN",
1135
- "QSTASH_CURRENT_SIGNING_KEY",
1136
- "QSTASH_NEXT_SIGNING_KEY",
1137
- "TOOL_SECRETS_KEK",
1138
- "TOOL_EXECUTOR_DISPATCH_SECRET",
1139
- "CLOUDFLARE_API_TOKEN",
1140
- "STRIPE_SECRET_KEY",
1141
- "STRIPE_WEBHOOK_SECRET",
1142
- "STRIPE_IDENTITY_WEBHOOK_SECRET",
1143
- "VOYAGE_API_KEY",
1144
- "PERPLEXITY_API_KEY",
1145
- "FRED_API_KEY",
1146
- "FMP_API_KEY",
1147
- "CURSOR_API_KEY"
1148
- ];
1149
- var FORBIDDEN_KEY_SET = new Set(FORBIDDEN_WORKER_ENV_KEYS);
1150
- var FORBIDDEN_SUFFIXES = ["_SECRET", "_API_KEY"];
1151
- function isForbiddenWorkerEnvKey(key) {
1152
- if (FORBIDDEN_KEY_SET.has(key)) return true;
1153
- return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));
1154
- }
1155
- function listForbiddenWorkerEnvKeys(env) {
1156
- return Object.keys(env).filter(isForbiddenWorkerEnvKey).sort();
1157
- }
1158
- function scrubWorkerEnv(env) {
1159
- const next = { ...env };
1160
- for (const key of Object.keys(next)) {
1161
- if (isForbiddenWorkerEnvKey(key)) delete next[key];
1162
- }
1163
- return next;
1164
- }
1165
- function auditWorkerEnv(env) {
1166
- const forbiddenPresent = listForbiddenWorkerEnvKeys(env);
1167
- return { forbiddenPresent, safe: forbiddenPresent.length === 0 };
1168
- }
1169
- function scrubClaudeEnv(env) {
1170
- return scrubWorkerEnv(env);
1171
- }
1172
-
1173
- // src/git.ts
1174
- function git(cwd, args, options = {}) {
1175
- const res = spawnSync("git", args, { cwd, encoding: "utf8" });
1176
- if (res.status !== 0 && !options.allowFailure) {
1177
- const message = `git ${args.join(" ")} failed: ${res.stderr || res.stdout}`;
1178
- if (options.throwError) throw new Error(message);
1179
- fail(message);
1180
- }
1181
- return res.stdout || "";
1182
- }
1183
- function ensureGitRepo(repo) {
1184
- git(repo, ["rev-parse", "--show-toplevel"]);
1185
- }
1186
- function gitStatusShort(worktreePath) {
1187
- return git(worktreePath, ["status", "--short"], { allowFailure: true }).split("\n").map((line) => line.trim()).filter(Boolean);
1188
- }
1189
- function gitCapture(cwd, args) {
1190
- try {
1191
- const res = spawnSync("git", args, { cwd, encoding: "utf8" });
1192
- return {
1193
- status: res.status,
1194
- stdout: res.stdout || "",
1195
- stderr: res.stderr || "",
1196
- error: res.error ? res.error.message : null
1197
- };
1198
- } catch (error) {
1199
- return {
1200
- status: null,
1201
- stdout: "",
1202
- stderr: "",
1203
- error: error.message
1204
- };
1205
- }
1206
- }
1207
- function gitIsAncestor(cwd, ancestor, descendant) {
1208
- const res = gitCapture(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]);
1209
- if (res.status === 0) return { isAncestor: true, error: null };
1210
- if (res.status === 1) return { isAncestor: false, error: null };
1211
- return { isAncestor: null, error: res.error || res.stderr || res.stdout || `git exited ${res.status}` };
1212
- }
1213
- function computeGitAncestry(worktreePath, baseOrOptions = "origin/main") {
1214
- const options = typeof baseOrOptions === "string" ? { base: baseOrOptions } : baseOrOptions;
1215
- const baseLabel = options.baseCommit?.trim() || options.base?.trim() || "origin/main";
1216
- const pinnedBaseCommit = options.baseCommit?.trim() || null;
1217
- if (!worktreePath) {
1218
- return unknownAncestry(baseLabel, "missing worktree path");
1219
- }
1220
- const head = gitCapture(worktreePath, ["rev-parse", "HEAD"]);
1221
- if (head.status !== 0) {
1222
- return unknownAncestry(baseLabel, head.error || head.stderr || head.stdout || "failed to resolve HEAD");
1223
- }
1224
- let baseSha;
1225
- if (pinnedBaseCommit) {
1226
- baseSha = pinnedBaseCommit;
1227
- } else {
1228
- const baseHead = gitCapture(worktreePath, ["rev-parse", baseLabel]);
1229
- if (baseHead.status !== 0) {
1230
- return unknownAncestry(
1231
- baseLabel,
1232
- baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${baseLabel}`,
1233
- head.stdout.trim()
1234
- );
1235
- }
1236
- baseSha = baseHead.stdout.trim();
1321
+ if (!ancestry?.checked) return null;
1322
+ if (ancestry.headIsAncestorOfBase !== false) return null;
1323
+ return trimOrNull(ancestry.head);
1324
+ }
1325
+ function buildAttentionReason(kind, uncommittedCount, headCommit) {
1326
+ const parts = ["exited_with_changes_salvage"];
1327
+ if (kind === "uncommitted" || kind === "both") {
1328
+ parts.push(
1329
+ `${uncommittedCount} uncommitted change${uncommittedCount === 1 ? "" : "s"} with no final result`
1330
+ );
1237
1331
  }
1238
- const headSha = head.stdout.trim();
1239
- if (headSha === baseSha) {
1240
- return {
1241
- checked: true,
1242
- base: baseLabel,
1243
- head: headSha,
1244
- baseHead: baseSha,
1245
- baseIsAncestorOfHead: true,
1246
- headIsAncestorOfBase: true,
1247
- relation: "synced"
1248
- };
1332
+ if ((kind === "committed_ahead" || kind === "both") && headCommit) {
1333
+ const sha = headCommit.length > 12 ? headCommit.slice(0, 12) : headCommit;
1334
+ parts.push(`commit ${sha} ahead of base with no final result`);
1249
1335
  }
1250
- const baseIsAncestorOfHead = gitIsAncestor(worktreePath, baseSha, headSha);
1251
- const headIsAncestorOfBase = gitIsAncestor(worktreePath, headSha, baseSha);
1252
- const error = baseIsAncestorOfHead.error || headIsAncestorOfBase.error || void 0;
1253
- if (baseIsAncestorOfHead.isAncestor == null || headIsAncestorOfBase.isAncestor == null) {
1336
+ parts.push("review worktree \u2014 commit, open a PR, or run a salvage worker before discarding");
1337
+ return parts.join(": ");
1338
+ }
1339
+ function assessExitedWorkerSalvage(input) {
1340
+ if (input.alive || hasFinalResult(input.finalResult)) return null;
1341
+ const uncommittedCount = (input.changedFiles ?? []).filter((line) => line.trim()).length;
1342
+ const headCommit = trimOrNull(input.headCommit) ?? committedHeadFromAncestry(input.gitAncestry);
1343
+ const hasUncommitted = uncommittedCount > 0;
1344
+ const hasCommittedAhead = Boolean(headCommit);
1345
+ if (!hasUncommitted && !hasCommittedAhead) {
1254
1346
  return {
1255
- checked: false,
1256
- base: baseLabel,
1257
- head: headSha,
1258
- baseHead: baseSha,
1259
- baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
1260
- headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
1261
- relation: "unknown",
1262
- ...error ? { error } : {}
1347
+ kind: "none",
1348
+ salvageable: false,
1349
+ uncommittedCount: 0,
1350
+ headCommit: null,
1351
+ attentionReason: "process exited without a final result"
1263
1352
  };
1264
1353
  }
1265
- const relation = baseIsAncestorOfHead.isAncestor ? "ahead" : headIsAncestorOfBase.isAncestor ? "merged" : "diverged";
1266
- return {
1267
- checked: true,
1268
- base: baseLabel,
1269
- head: headSha,
1270
- baseHead: baseSha,
1271
- baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
1272
- headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
1273
- relation,
1274
- ...error ? { error } : {}
1275
- };
1276
- }
1277
- function unknownAncestry(base, error, head = null) {
1354
+ const kind = hasUncommitted && hasCommittedAhead ? "both" : hasUncommitted ? "uncommitted" : "committed_ahead";
1278
1355
  return {
1279
- checked: false,
1280
- base,
1281
- head,
1282
- baseHead: null,
1283
- baseIsAncestorOfHead: null,
1284
- headIsAncestorOfBase: null,
1285
- relation: "unknown",
1286
- error
1356
+ kind,
1357
+ salvageable: true,
1358
+ uncommittedCount,
1359
+ headCommit,
1360
+ attentionReason: buildAttentionReason(kind, uncommittedCount, headCommit)
1287
1361
  };
1288
1362
  }
1289
1363
 
@@ -1645,7 +1719,7 @@ function countActiveWorkersForRun(run) {
1645
1719
  let active = 0;
1646
1720
  for (const name of Object.keys(run.workers || {})) {
1647
1721
  const worker = readJson(
1648
- path6.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1722
+ path7.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1649
1723
  void 0
1650
1724
  );
1651
1725
  if (!worker || !isActiveHarnessWorker(worker)) continue;
@@ -2030,10 +2104,10 @@ function readHarnessRetryLimits() {
2030
2104
  }
2031
2105
 
2032
2106
  // src/lease-renewal.ts
2033
- import path7 from "node:path";
2107
+ import path8 from "node:path";
2034
2108
  function workerRecord(runId, name) {
2035
2109
  return readJson(
2036
- path7.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
2110
+ path8.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
2037
2111
  void 0
2038
2112
  );
2039
2113
  }
@@ -2099,8 +2173,8 @@ function hasLiveWorkerForTask(runId, taskId) {
2099
2173
  }
2100
2174
 
2101
2175
  // src/supervisor.ts
2102
- import { existsSync as existsSync11, mkdirSync as mkdirSync3 } from "node:fs";
2103
- import path13 from "node:path";
2176
+ import { existsSync as existsSync12, mkdirSync as mkdirSync3 } from "node:fs";
2177
+ import path14 from "node:path";
2104
2178
 
2105
2179
  // src/prompt.ts
2106
2180
  function buildPrompt(input) {
@@ -2169,13 +2243,13 @@ function buildPrompt(input) {
2169
2243
  }
2170
2244
 
2171
2245
  // src/providers/cursor.ts
2172
- import { closeSync as closeSync2, existsSync as existsSync9, openSync as openSync2 } from "node:fs";
2246
+ import { closeSync as closeSync2, existsSync as existsSync10, openSync as openSync2 } from "node:fs";
2173
2247
  import { spawn as spawn2 } from "node:child_process";
2174
- import path9 from "node:path";
2248
+ import path10 from "node:path";
2175
2249
 
2176
2250
  // src/providers/cursor-windows.ts
2177
- import { existsSync as existsSync8, readdirSync as readdirSync3 } from "node:fs";
2178
- import path8 from "node:path";
2251
+ import { existsSync as existsSync9, readdirSync as readdirSync3 } from "node:fs";
2252
+ import path9 from "node:path";
2179
2253
  var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
2180
2254
  function parseCursorVersionSortKey(versionName) {
2181
2255
  const datePart = versionName.split("-")[0];
@@ -2186,8 +2260,8 @@ function parseCursorVersionSortKey(versionName) {
2186
2260
  return Number(`${year}${month.padStart(2, "0")}${day.padStart(2, "0")}`);
2187
2261
  }
2188
2262
  function pickLatestCursorVersionDir(agentRoot) {
2189
- const versionsRoot = path8.join(agentRoot, "versions");
2190
- if (!existsSync8(versionsRoot)) return null;
2263
+ const versionsRoot = path9.join(agentRoot, "versions");
2264
+ if (!existsSync9(versionsRoot)) return null;
2191
2265
  let bestDir = null;
2192
2266
  let bestKey = -1;
2193
2267
  for (const entry of readdirSync3(versionsRoot, { withFileTypes: true })) {
@@ -2195,22 +2269,22 @@ function pickLatestCursorVersionDir(agentRoot) {
2195
2269
  const key = parseCursorVersionSortKey(entry.name);
2196
2270
  if (key == null || key <= bestKey) continue;
2197
2271
  bestKey = key;
2198
- bestDir = path8.join(versionsRoot, entry.name);
2272
+ bestDir = path9.join(versionsRoot, entry.name);
2199
2273
  }
2200
2274
  return bestDir;
2201
2275
  }
2202
2276
  function resolveWindowsCursorBundled(agentRoot) {
2203
- const root = agentRoot?.trim() || path8.join(process.env.LOCALAPPDATA || "", "cursor-agent");
2204
- const directNode = path8.join(root, "node.exe");
2205
- const directIndex = path8.join(root, "index.js");
2206
- if (existsSync8(directNode) && existsSync8(directIndex)) {
2277
+ const root = agentRoot?.trim() || path9.join(process.env.LOCALAPPDATA || "", "cursor-agent");
2278
+ const directNode = path9.join(root, "node.exe");
2279
+ const directIndex = path9.join(root, "index.js");
2280
+ if (existsSync9(directNode) && existsSync9(directIndex)) {
2207
2281
  return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
2208
2282
  }
2209
2283
  const versionDir = pickLatestCursorVersionDir(root);
2210
2284
  if (!versionDir) return null;
2211
- const nodeExe = path8.join(versionDir, "node.exe");
2212
- const indexJs = path8.join(versionDir, "index.js");
2213
- if (!existsSync8(nodeExe) || !existsSync8(indexJs)) return null;
2285
+ const nodeExe = path9.join(versionDir, "node.exe");
2286
+ const indexJs = path9.join(versionDir, "index.js");
2287
+ if (!existsSync9(nodeExe) || !existsSync9(indexJs)) return null;
2214
2288
  return { nodeExe, indexJs, versionDir };
2215
2289
  }
2216
2290
 
@@ -2228,13 +2302,13 @@ function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
2228
2302
  function resolveCursorSpawn(agentBin) {
2229
2303
  if (process.platform === "win32") {
2230
2304
  const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
2231
- const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync9(path9.join(path9.dirname(agentBin), "index.js"));
2305
+ const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync10(path10.join(path10.dirname(agentBin), "index.js"));
2232
2306
  const isDefaultShim = agentBin === "agent";
2233
2307
  if (isCursorWrapper || isBundledNode || isDefaultShim) {
2234
- const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path9.dirname(agentBin)) : isBundledNode ? {
2308
+ const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path10.dirname(agentBin)) : isBundledNode ? {
2235
2309
  nodeExe: agentBin,
2236
- indexJs: path9.join(path9.dirname(agentBin), "index.js"),
2237
- versionDir: path9.dirname(agentBin)
2310
+ indexJs: path10.join(path10.dirname(agentBin), "index.js"),
2311
+ versionDir: path10.dirname(agentBin)
2238
2312
  } : resolveWindowsCursorBundled();
2239
2313
  if (bundled) {
2240
2314
  return bundledSpawnTarget(bundled.nodeExe, bundled.indexJs, bundled.versionDir);
@@ -2254,8 +2328,8 @@ function resolveAgentBin() {
2254
2328
  process.env.KYNVER_CURSOR_AGENT_ROOT?.trim() || void 0
2255
2329
  );
2256
2330
  if (bundled) return bundled.nodeExe;
2257
- const localAgent = path9.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
2258
- if (existsSync9(localAgent)) return localAgent;
2331
+ const localAgent = path10.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
2332
+ if (existsSync10(localAgent)) return localAgent;
2259
2333
  }
2260
2334
  return "agent";
2261
2335
  }
@@ -2264,7 +2338,7 @@ function cursorWorkerEnv(agentBin, spawnTarget) {
2264
2338
  ...process.env,
2265
2339
  CI: "1",
2266
2340
  NO_COLOR: "1",
2267
- ...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path9.basename(agentBin) || "agent.cmd" } : {}
2341
+ ...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path10.basename(agentBin) || "agent.cmd" } : {}
2268
2342
  });
2269
2343
  }
2270
2344
  var cursorProvider = {
@@ -2338,9 +2412,9 @@ function resolveWorkerProvider(name) {
2338
2412
 
2339
2413
  // src/auto-complete.ts
2340
2414
  import { spawn as spawn3 } from "node:child_process";
2341
- import { existsSync as existsSync10, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
2342
- import path12 from "node:path";
2343
- import { fileURLToPath as fileURLToPath2 } from "node:url";
2415
+ import { existsSync as existsSync11, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
2416
+ import path13 from "node:path";
2417
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
2344
2418
 
2345
2419
  // src/completion-ack.ts
2346
2420
  function hasCompletionAck(worker) {
@@ -2356,7 +2430,7 @@ function persistCompletionAck(worker, runId, fields) {
2356
2430
  }
2357
2431
 
2358
2432
  // src/worker-ops.ts
2359
- import path11 from "node:path";
2433
+ import path12 from "node:path";
2360
2434
 
2361
2435
  // src/completion-response.ts
2362
2436
  function asRecord(value) {
@@ -2838,7 +2912,7 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
2838
2912
  }
2839
2913
 
2840
2914
  // src/worker-lifecycle.ts
2841
- import path10 from "node:path";
2915
+ import path11 from "node:path";
2842
2916
  var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
2843
2917
  "awaiting_review",
2844
2918
  "blocked",
@@ -2894,7 +2968,7 @@ function syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick) {
2894
2968
  const synced = [];
2895
2969
  for (const name of Object.keys(run.workers || {})) {
2896
2970
  const worker = readJson(
2897
- path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2971
+ path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2898
2972
  void 0
2899
2973
  );
2900
2974
  if (!worker?.taskId || isCompletionAcknowledged(worker)) continue;
@@ -3152,7 +3226,7 @@ function workerStatus(args) {
3152
3226
  const worker = loadWorker(String(args.run), String(args.name));
3153
3227
  const run = loadRun(worker.runId);
3154
3228
  const status = computeWorkerStatus(worker, workerStatusOptions(run));
3155
- writeJson(path11.join(worker.workerDir, "last-status.json"), status);
3229
+ writeJson(path12.join(worker.workerDir, "last-status.json"), status);
3156
3230
  console.log(JSON.stringify(status, null, 2));
3157
3231
  }
3158
3232
  function buildRunBoard(runId) {
@@ -3160,7 +3234,7 @@ function buildRunBoard(runId) {
3160
3234
  const names = Object.keys(run.workers || {});
3161
3235
  const workers = names.map((name) => {
3162
3236
  const worker = readJson(
3163
- path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
3237
+ path12.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
3164
3238
  void 0
3165
3239
  );
3166
3240
  if (!worker) {
@@ -3271,7 +3345,7 @@ function buildRunBoard(runId) {
3271
3345
  needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
3272
3346
  workers
3273
3347
  };
3274
- writeJson(path11.join(runDirectory(run.id), "last-board.json"), board);
3348
+ writeJson(path12.join(runDirectory(run.id), "last-board.json"), board);
3275
3349
  return board;
3276
3350
  }
3277
3351
  async function publishHarnessBoardSnapshot(args, source) {
@@ -3460,12 +3534,12 @@ async function autoCompleteWorkerCli(raw) {
3460
3534
  }
3461
3535
  }
3462
3536
  function resolveDefaultCliPath() {
3463
- return path12.join(fileURLToPath2(new URL(".", import.meta.url)), "cli.js");
3537
+ return path13.join(fileURLToPath3(new URL(".", import.meta.url)), "cli.js");
3464
3538
  }
3465
3539
  function spawnCompletionSidecar(opts) {
3466
3540
  const cliPath = opts.cliPath ?? resolveDefaultCliPath();
3467
- if (!existsSync10(cliPath)) return void 0;
3468
- const logPath = path12.join(opts.workerDir, "auto-complete.log");
3541
+ if (!existsSync11(cliPath)) return void 0;
3542
+ const logPath = path13.join(opts.workerDir, "auto-complete.log");
3469
3543
  let logFd;
3470
3544
  try {
3471
3545
  logFd = openSync3(logPath, "a");
@@ -3547,16 +3621,16 @@ function spawnWorkerProcess(run, opts) {
3547
3621
  launchModel = preflight.model;
3548
3622
  }
3549
3623
  const { worktreesDir } = getPaths();
3550
- const workerDir = path13.join(runDirectory(run.id), "workers", name);
3624
+ const workerDir = path14.join(runDirectory(run.id), "workers", name);
3551
3625
  mkdirSync3(workerDir, { recursive: true });
3552
- const worktreePath = path13.join(worktreesDir, run.id, name);
3626
+ const worktreePath = path14.join(worktreesDir, run.id, name);
3553
3627
  const branch = opts.branch || `agent/${run.id}/${name}`;
3554
- if (existsSync11(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
3628
+ if (existsSync12(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
3555
3629
  git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
3556
3630
  git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
3557
- const stdoutPath = path13.join(workerDir, "stdout.jsonl");
3558
- const stderrPath = path13.join(workerDir, "stderr.log");
3559
- const heartbeatPath = path13.join(workerDir, "heartbeat.jsonl");
3631
+ const stdoutPath = path14.join(workerDir, "stdout.jsonl");
3632
+ const stderrPath = path14.join(workerDir, "stderr.log");
3633
+ const heartbeatPath = path14.join(workerDir, "heartbeat.jsonl");
3560
3634
  const prompt = buildPrompt({
3561
3635
  task: opts.task,
3562
3636
  ownedPaths: opts.ownedPaths || [],
@@ -3621,7 +3695,7 @@ function spawnWorkerProcess(run, opts) {
3621
3695
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
3622
3696
  };
3623
3697
  saveWorker(run.id, worker);
3624
- run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path13.join(workerDir, "worker.json") } };
3698
+ run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path14.join(workerDir, "worker.json") } };
3625
3699
  run.status = "running";
3626
3700
  saveRun(run);
3627
3701
  if (worker.agentOsId && worker.taskId) {
@@ -3913,18 +3987,18 @@ function buildPlanPersistIdempotencyKey(input) {
3913
3987
 
3914
3988
  // src/plan-persist/paths.ts
3915
3989
  import { mkdirSync as mkdirSync4 } from "node:fs";
3916
- import { homedir as homedir4 } from "node:os";
3917
- import path14 from "node:path";
3990
+ import { homedir as homedir5 } from "node:os";
3991
+ import path15 from "node:path";
3918
3992
  function resolveKynverStateRoot() {
3919
3993
  const env = process.env.KYNVER_STATE_ROOT;
3920
- if (env) return path14.resolve(env);
3921
- return path14.join(homedir4(), ".kynver", "state");
3994
+ if (env) return path15.resolve(env);
3995
+ return path15.join(homedir5(), ".kynver", "state");
3922
3996
  }
3923
3997
  function planOutboxDir() {
3924
- return path14.join(resolveKynverStateRoot(), "plan-outbox");
3998
+ return path15.join(resolveKynverStateRoot(), "plan-outbox");
3925
3999
  }
3926
4000
  function planOutboxArchiveDir() {
3927
- return path14.join(resolveKynverStateRoot(), "plan-outbox-archive");
4001
+ return path15.join(resolveKynverStateRoot(), "plan-outbox-archive");
3928
4002
  }
3929
4003
  function ensurePlanOutboxDirs() {
3930
4004
  const outboxDir = planOutboxDir();
@@ -3935,20 +4009,20 @@ function ensurePlanOutboxDirs() {
3935
4009
  }
3936
4010
  function isTmpOnlyPath(filePath) {
3937
4011
  if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
3938
- const resolved = path14.resolve(filePath);
3939
- return resolved.startsWith("/tmp/") || resolved.startsWith(path14.join("/var", "folders"));
4012
+ const resolved = path15.resolve(filePath);
4013
+ return resolved.startsWith("/tmp/") || resolved.startsWith(path15.join("/var", "folders"));
3940
4014
  }
3941
4015
 
3942
4016
  // src/plan-persist/outbox-store.ts
3943
4017
  import {
3944
- existsSync as existsSync13,
3945
- readFileSync as readFileSync7,
4018
+ existsSync as existsSync14,
4019
+ readFileSync as readFileSync8,
3946
4020
  renameSync,
3947
4021
  readdirSync as readdirSync4,
3948
4022
  writeFileSync as writeFileSync3,
3949
4023
  unlinkSync
3950
4024
  } from "node:fs";
3951
- import path15 from "node:path";
4025
+ import path16 from "node:path";
3952
4026
  import { randomUUID } from "node:crypto";
3953
4027
  var DEFAULT_MAX_RETRIES = 12;
3954
4028
  function listOutboxItems() {
@@ -3956,7 +4030,7 @@ function listOutboxItems() {
3956
4030
  const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
3957
4031
  const items = [];
3958
4032
  for (const file of files) {
3959
- const item = readOutboxItem(path15.join(outboxDir, file));
4033
+ const item = readOutboxItem(path16.join(outboxDir, file));
3960
4034
  if (item && item.queueStatus === "queued") items.push(item);
3961
4035
  }
3962
4036
  return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
@@ -3968,25 +4042,25 @@ function findOutboxByIdempotencyKey(key) {
3968
4042
  return null;
3969
4043
  }
3970
4044
  function readOutboxItem(jsonPath) {
3971
- if (!existsSync13(jsonPath)) return null;
4045
+ if (!existsSync14(jsonPath)) return null;
3972
4046
  try {
3973
- return JSON.parse(readFileSync7(jsonPath, "utf8"));
4047
+ return JSON.parse(readFileSync8(jsonPath, "utf8"));
3974
4048
  } catch {
3975
4049
  return null;
3976
4050
  }
3977
4051
  }
3978
4052
  function readOutboxBody(item) {
3979
4053
  const { outboxDir } = ensurePlanOutboxDirs();
3980
- const bodyFile = path15.join(outboxDir, item.bodyPath);
3981
- return readFileSync7(bodyFile, "utf8");
4054
+ const bodyFile = path16.join(outboxDir, item.bodyPath);
4055
+ return readFileSync8(bodyFile, "utf8");
3982
4056
  }
3983
4057
  function writeOutboxItem(input, opts) {
3984
4058
  const { outboxDir } = ensurePlanOutboxDirs();
3985
4059
  const now = (/* @__PURE__ */ new Date()).toISOString();
3986
4060
  const id = opts.existing?.id ?? randomUUID();
3987
4061
  const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
3988
- const jsonPath = path15.join(outboxDir, `${id}.json`);
3989
- const bodyFile = path15.join(outboxDir, bodyPath);
4062
+ const jsonPath = path16.join(outboxDir, `${id}.json`);
4063
+ const bodyFile = path16.join(outboxDir, bodyPath);
3990
4064
  if (!opts.existing) {
3991
4065
  writeFileSync3(bodyFile, input.body, "utf8");
3992
4066
  }
@@ -4022,24 +4096,24 @@ function writeOutboxItem(input, opts) {
4022
4096
  }
4023
4097
  function saveOutboxItem(item) {
4024
4098
  const { outboxDir } = ensurePlanOutboxDirs();
4025
- const jsonPath = path15.join(outboxDir, `${item.id}.json`);
4099
+ const jsonPath = path16.join(outboxDir, `${item.id}.json`);
4026
4100
  writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
4027
4101
  `, { mode: 384 });
4028
4102
  }
4029
4103
  function archiveOutboxItem(item) {
4030
4104
  const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
4031
- const jsonSrc = path15.join(outboxDir, `${item.id}.json`);
4032
- const bodySrc = path15.join(outboxDir, item.bodyPath);
4033
- const jsonDst = path15.join(archiveDir, `${item.id}.json`);
4034
- const bodyDst = path15.join(archiveDir, item.bodyPath);
4035
- if (existsSync13(jsonSrc)) renameSync(jsonSrc, jsonDst);
4036
- if (existsSync13(bodySrc)) renameSync(bodySrc, bodyDst);
4105
+ const jsonSrc = path16.join(outboxDir, `${item.id}.json`);
4106
+ const bodySrc = path16.join(outboxDir, item.bodyPath);
4107
+ const jsonDst = path16.join(archiveDir, `${item.id}.json`);
4108
+ const bodyDst = path16.join(archiveDir, item.bodyPath);
4109
+ if (existsSync14(jsonSrc)) renameSync(jsonSrc, jsonDst);
4110
+ if (existsSync14(bodySrc)) renameSync(bodySrc, bodyDst);
4037
4111
  }
4038
4112
  function outboxItemPaths(item) {
4039
4113
  const { outboxDir } = ensurePlanOutboxDirs();
4040
4114
  return {
4041
- jsonPath: path15.join(outboxDir, `${item.id}.json`),
4042
- bodyPath: path15.join(outboxDir, item.bodyPath)
4115
+ jsonPath: path16.join(outboxDir, `${item.id}.json`),
4116
+ bodyPath: path16.join(outboxDir, item.bodyPath)
4043
4117
  };
4044
4118
  }
4045
4119
  function outboxInputFromItem(item, body) {
@@ -4225,7 +4299,7 @@ function markOutboxFailed(item, message) {
4225
4299
  }
4226
4300
 
4227
4301
  // src/plan-persist/drain.ts
4228
- import path16 from "node:path";
4302
+ import path17 from "node:path";
4229
4303
  async function drainPlanOutbox(opts = {}, deps = {}) {
4230
4304
  const items = listOutboxItems().filter(
4231
4305
  (item) => opts.outboxId ? item.id === opts.outboxId : true
@@ -4259,7 +4333,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
4259
4333
  return result;
4260
4334
  }
4261
4335
  function loadOutboxById(outboxId) {
4262
- const jsonPath = path16.join(planOutboxDir(), `${outboxId}.json`);
4336
+ const jsonPath = path17.join(planOutboxDir(), `${outboxId}.json`);
4263
4337
  return readOutboxItem(jsonPath);
4264
4338
  }
4265
4339
 
@@ -4359,7 +4433,7 @@ async function dispatchRun(args) {
4359
4433
  const activeHarnessWorkers = [];
4360
4434
  for (const name of Object.keys(run.workers || {})) {
4361
4435
  const worker = readJson(
4362
- path17.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4436
+ path18.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4363
4437
  void 0
4364
4438
  );
4365
4439
  if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
@@ -4584,7 +4658,7 @@ function redactHarness(text, secret) {
4584
4658
  }
4585
4659
 
4586
4660
  // src/validate.ts
4587
- import path18 from "node:path";
4661
+ import path19 from "node:path";
4588
4662
  var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
4589
4663
  var WORKER_NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
4590
4664
  function validateRunId(runId) {
@@ -4598,15 +4672,15 @@ function validateWorkerName(name) {
4598
4672
  return trimmed;
4599
4673
  }
4600
4674
  function validateRepo(repo) {
4601
- const resolved = path18.resolve(repo);
4675
+ const resolved = path19.resolve(repo);
4602
4676
  if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
4603
4677
  return resolved;
4604
4678
  }
4605
4679
  function validateOwnedPaths(repoRoot, ownedPaths) {
4606
4680
  return ownedPaths.map((owned) => {
4607
- const resolved = path18.resolve(repoRoot, owned);
4608
- const rel = path18.relative(repoRoot, resolved);
4609
- if (rel.startsWith("..") || path18.isAbsolute(rel)) {
4681
+ const resolved = path19.resolve(repoRoot, owned);
4682
+ const rel = path19.relative(repoRoot, resolved);
4683
+ if (rel.startsWith("..") || path19.isAbsolute(rel)) {
4610
4684
  throw new Error(`owned path escapes repo: ${owned}`);
4611
4685
  }
4612
4686
  return resolved;
@@ -4618,14 +4692,93 @@ function validateTailLines(lines) {
4618
4692
  }
4619
4693
 
4620
4694
  // src/worktree.ts
4621
- import { existsSync as existsSync14, mkdirSync as mkdirSync5 } from "node:fs";
4622
- import path19 from "node:path";
4695
+ import { existsSync as existsSync15, mkdirSync as mkdirSync5 } from "node:fs";
4696
+ import path21 from "node:path";
4697
+
4698
+ // src/default-repo.ts
4699
+ import path20 from "node:path";
4700
+ function expandConfiguredRepo(value) {
4701
+ return path20.resolve(resolveUserPath(value.trim()));
4702
+ }
4703
+ function fromConfigured(value, source, persistedInConfig) {
4704
+ const trimmed = value?.trim();
4705
+ if (!trimmed) return null;
4706
+ return {
4707
+ repo: expandConfiguredRepo(trimmed),
4708
+ source,
4709
+ persistedInConfig
4710
+ };
4711
+ }
4712
+ function resolveDefaultRepo(opts = {}) {
4713
+ const env = opts.env ?? process.env;
4714
+ const config = opts.config ?? loadUserConfig();
4715
+ const fromConfig = fromConfigured(config.defaultRepo, "config", true);
4716
+ if (fromConfig) return fromConfig;
4717
+ const fromDefaultEnv = fromConfigured(env.KYNVER_DEFAULT_REPO, "env_default_repo", false);
4718
+ if (fromDefaultEnv) return fromDefaultEnv;
4719
+ const fromHarnessEnv = fromConfigured(env.KYNVER_HARNESS_REPO, "env_harness_repo", false);
4720
+ if (fromHarnessEnv) return fromHarnessEnv;
4721
+ const discovered = discoverDefaultRepo({
4722
+ cwd: opts.cwd,
4723
+ runtimeModuleUrl: opts.runtimeModuleUrl
4724
+ });
4725
+ if (!discovered) return null;
4726
+ return {
4727
+ repo: discovered.repo,
4728
+ source: discovered.source,
4729
+ persistedInConfig: false
4730
+ };
4731
+ }
4732
+ function persistDefaultRepo(repo, existing) {
4733
+ const config = {
4734
+ ...existing ?? loadUserConfig(),
4735
+ defaultRepo: redactHomePath(path20.resolve(repo))
4736
+ };
4737
+ saveUserConfig(config);
4738
+ return config;
4739
+ }
4740
+ function remediateDefaultRepo(opts) {
4741
+ const existing = opts?.config ?? loadUserConfig();
4742
+ const resolved = resolveDefaultRepo({ ...opts, config: existing });
4743
+ if (!resolved) {
4744
+ return {
4745
+ ok: false,
4746
+ reason: "No Kynver git checkout found. Clone the repo, cd into it, then run `kynver setup --repo /path/to/Kynver` (or export KYNVER_DEFAULT_REPO)."
4747
+ };
4748
+ }
4749
+ if (resolved.persistedInConfig) {
4750
+ return { ok: true, resolved, config: existing };
4751
+ }
4752
+ const config = persistDefaultRepo(resolved.repo, existing);
4753
+ return {
4754
+ ok: true,
4755
+ resolved: { ...resolved, persistedInConfig: true, source: "config" },
4756
+ config
4757
+ };
4758
+ }
4759
+ function formatResolvedDefaultRepo(resolved) {
4760
+ return {
4761
+ defaultRepo: displayUserPath(resolved.repo),
4762
+ source: resolved.source,
4763
+ persistedInConfig: resolved.persistedInConfig
4764
+ };
4765
+ }
4766
+
4767
+ // src/worktree.ts
4768
+ function resolveCreateRunRepo(args) {
4769
+ const explicit = typeof args.repo === "string" ? args.repo.trim() : "";
4770
+ if (explicit) return explicit;
4771
+ const resolved = resolveDefaultRepo();
4772
+ if (resolved) return resolved.repo;
4773
+ required("", "--repo (or set defaultRepo via `kynver setup` / KYNVER_DEFAULT_REPO)");
4774
+ return "";
4775
+ }
4623
4776
  function createRun(args) {
4624
- const repo = validateRepo(required(String(args.repo || ""), "--repo"));
4777
+ const repo = validateRepo(resolveCreateRunRepo(args));
4625
4778
  ensureGitRepo(repo);
4626
4779
  const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
4627
4780
  const dir = runDirectory(id);
4628
- if (existsSync14(dir)) failExists(`run already exists: ${id}`);
4781
+ if (existsSync15(dir)) failExists(`run already exists: ${id}`);
4629
4782
  mkdirSync5(dir, { recursive: true });
4630
4783
  const base = String(args.base || "origin/main");
4631
4784
  const baseCommit = git(repo, ["rev-parse", base]).trim();
@@ -4639,12 +4792,12 @@ function createRun(args) {
4639
4792
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4640
4793
  workers: {}
4641
4794
  };
4642
- writeJson(path19.join(dir, "run.json"), run);
4795
+ writeJson(path21.join(dir, "run.json"), run);
4643
4796
  console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
4644
4797
  }
4645
4798
  function listRuns() {
4646
4799
  const { runsDir } = getPaths();
4647
- const rows = listRunIds(runsDir).map((id) => readJson(path19.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
4800
+ const rows = listRunIds(runsDir).map((id) => readJson(path21.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
4648
4801
  id: run.id,
4649
4802
  name: run.name,
4650
4803
  status: run.status,
@@ -4659,7 +4812,7 @@ function failExists(message) {
4659
4812
  }
4660
4813
 
4661
4814
  // src/sweep.ts
4662
- import path20 from "node:path";
4815
+ import path22 from "node:path";
4663
4816
  async function sweepRun(args) {
4664
4817
  const pipeline = args.pipeline === true || args.pipeline === "true";
4665
4818
  try {
@@ -4672,7 +4825,7 @@ async function sweepRun(args) {
4672
4825
  const releasedLocalOrphans = [];
4673
4826
  for (const name of Object.keys(run.workers || {})) {
4674
4827
  const worker = readJson(
4675
- path20.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4828
+ path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4676
4829
  void 0
4677
4830
  );
4678
4831
  if (!worker || !worker.dispatched || !worker.taskId) continue;
@@ -4716,10 +4869,10 @@ async function sweepRun(args) {
4716
4869
 
4717
4870
  // src/cli.ts
4718
4871
  import { mkdirSync as mkdirSync7, realpathSync } from "node:fs";
4719
- import { fileURLToPath as fileURLToPath4 } from "node:url";
4872
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
4720
4873
 
4721
4874
  // src/pipeline-tick.ts
4722
- import path30 from "node:path";
4875
+ import path32 from "node:path";
4723
4876
 
4724
4877
  // src/pipeline-dispatch.ts
4725
4878
  var RESERVED_REVIEW_STARTS = 1;
@@ -4773,12 +4926,19 @@ async function runPipelineDispatch(args, slots) {
4773
4926
  }
4774
4927
 
4775
4928
  // src/stale-reconcile.ts
4776
- import path22 from "node:path";
4929
+ import path24 from "node:path";
4777
4930
 
4778
4931
  // src/finalize.ts
4779
- import path21 from "node:path";
4780
- var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
4781
- function terminalStatusFor(run) {
4932
+ import path23 from "node:path";
4933
+ var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set([
4934
+ "running",
4935
+ "dispatching",
4936
+ "pending",
4937
+ "queued",
4938
+ "needs_attention"
4939
+ ]);
4940
+ var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "done"]);
4941
+ function deriveTerminalRunStatus(run) {
4782
4942
  const names = Object.keys(run.workers || {});
4783
4943
  if (names.length === 0) return "failed";
4784
4944
  let anyAlive = false;
@@ -4787,7 +4947,7 @@ function terminalStatusFor(run) {
4787
4947
  let anyLandingBlocked = false;
4788
4948
  for (const name of names) {
4789
4949
  const worker = readJson(
4790
- path21.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4950
+ path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4791
4951
  void 0
4792
4952
  );
4793
4953
  if (!worker) continue;
@@ -4816,7 +4976,7 @@ function finalizeStaleRuns() {
4816
4976
  const finalized = [];
4817
4977
  for (const run of listRunRecords()) {
4818
4978
  if (!ACTIVE_RUN_STATUSES.has(run.status)) continue;
4819
- const next = terminalStatusFor(run);
4979
+ const next = deriveTerminalRunStatus(run);
4820
4980
  if (!next || next === run.status) continue;
4821
4981
  const from = run.status;
4822
4982
  run.status = next;
@@ -4839,7 +4999,7 @@ function reconcileStaleWorkers() {
4839
4999
  const now = Date.now();
4840
5000
  for (const run of listRunRecords()) {
4841
5001
  for (const name of Object.keys(run.workers || {})) {
4842
- const workerPath = path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
5002
+ const workerPath = path24.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
4843
5003
  const worker = readJson(workerPath, void 0);
4844
5004
  if (!worker || worker.status !== "running") {
4845
5005
  outcomes.push({
@@ -4933,7 +5093,7 @@ function reconcileRunsCli() {
4933
5093
  }
4934
5094
 
4935
5095
  // src/plan-progress-daemon-sync.ts
4936
- import path23 from "node:path";
5096
+ import path25 from "node:path";
4937
5097
 
4938
5098
  // src/plan-progress-sync.ts
4939
5099
  async function syncPlanProgress(args) {
@@ -4957,7 +5117,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
4957
5117
  const outcomes = [];
4958
5118
  for (const name of Object.keys(run.workers || {})) {
4959
5119
  const worker = readJson(
4960
- path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
5120
+ path25.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4961
5121
  void 0
4962
5122
  );
4963
5123
  if (!worker?.dispatched || !worker.taskId) continue;
@@ -5006,15 +5166,28 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
5006
5166
  }
5007
5167
 
5008
5168
  // src/cleanup.ts
5009
- import path28 from "node:path";
5169
+ import path30 from "node:path";
5010
5170
 
5011
- // src/cleanup-types.ts
5012
- var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
5013
- var DEFAULT_WORKTREES_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
5171
+ // src/cleanup-run-liveness.ts
5172
+ function isWorkerProcessLive(indexed) {
5173
+ if (indexed.status.alive) return true;
5174
+ if (indexed.worker.status === "running") return true;
5175
+ return false;
5176
+ }
5177
+ function isRunStaleActive(indexed) {
5178
+ if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
5179
+ return deriveTerminalRunStatus(indexed.run) !== null;
5180
+ }
5181
+ function runBlocksWorktreeRemoval(indexed) {
5182
+ if (isWorkerProcessLive(indexed)) return true;
5183
+ if (indexed.worker.completionBlocker) return true;
5184
+ if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
5185
+ if (isRunStaleActive(indexed)) return false;
5186
+ if (!isFinishedWorkerStatus(indexed.status)) return true;
5187
+ return deriveTerminalRunStatus(indexed.run) === null;
5188
+ }
5014
5189
 
5015
5190
  // src/cleanup-guards.ts
5016
- var ACTIVE_RUN_STATUSES2 = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued", "needs_attention"]);
5017
- var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
5018
5191
  function prUrlFromFinalResult(finalResult) {
5019
5192
  if (typeof finalResult === "string") {
5020
5193
  const match = finalResult.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/i);
@@ -5036,18 +5209,29 @@ function isPrOrUnmergedWork(status) {
5036
5209
  if (status.changedFiles.length > 0 && status.finalResult) return true;
5037
5210
  return false;
5038
5211
  }
5212
+ function materialWorktreeChanges(changedFiles) {
5213
+ return changedFiles.filter((line) => {
5214
+ const trimmed = line.trim();
5215
+ const pathPart = trimmed.startsWith("??") ? trimmed.slice(2).trim() : trimmed.length > 3 ? trimmed.slice(3).trim() : trimmed;
5216
+ return pathPart !== "node_modules" && !pathPart.startsWith("node_modules/");
5217
+ });
5218
+ }
5219
+ function hasUnrestorableWorktreeChanges(status) {
5220
+ if (materialWorktreeChanges(status.changedFiles).length > 0) return true;
5221
+ if (status.gitAncestry?.relation === "diverged") return true;
5222
+ return false;
5223
+ }
5039
5224
  function skipWorktreeRemoval(input) {
5040
5225
  const { indexed, includeOrphans, worktreesAgeMs, ageMs } = input;
5041
5226
  if (worktreesAgeMs <= 0) return "worktrees_disabled";
5042
5227
  if (ageMs < worktreesAgeMs) return "below_age_threshold";
5043
5228
  if (!indexed) return includeOrphans ? null : "orphan_without_flag";
5044
- if (ACTIVE_RUN_STATUSES2.has(indexed.run.status)) return "run_still_active";
5045
- if (!TERMINAL_RUN_STATUSES.has(indexed.run.status)) return "run_still_active";
5046
- if (indexed.status.alive) return "active_worker";
5047
- if (indexed.worker.status === "running") return "active_worker";
5229
+ if (isWorkerProcessLive(indexed)) return "active_worker";
5048
5230
  if (indexed.worker.completionBlocker) return "completion_blocked";
5231
+ if (runBlocksWorktreeRemoval(indexed)) return "run_still_active";
5232
+ if (!isFinishedWorkerStatus(indexed.status)) return "run_still_active";
5049
5233
  if (isPrOrUnmergedWork(indexed.status)) return "pr_or_unmerged_commits";
5050
- if (indexed.status.changedFiles.length > 0) return "dirty_worktree";
5234
+ if (materialWorktreeChanges(indexed.status.changedFiles).length > 0) return "dirty_worktree";
5051
5235
  const landing = assessWorkerLanding({
5052
5236
  finalResult: indexed.status.finalResult,
5053
5237
  changedFiles: indexed.status.changedFiles,
@@ -5061,30 +5245,31 @@ function skipNodeModulesRemoval(input) {
5061
5245
  const { indexed, includeOrphans, nodeModulesAgeMs, ageMs } = input;
5062
5246
  if (ageMs < nodeModulesAgeMs) return "below_age_threshold";
5063
5247
  if (!indexed) return includeOrphans ? null : "orphan_without_flag";
5064
- if (indexed.status.alive) return "active_worker";
5065
- if (indexed.worker.status === "running") return "active_worker";
5248
+ if (isWorkerProcessLive(indexed)) return "active_worker";
5066
5249
  if (indexed.worker.completionBlocker) return "completion_blocked";
5067
- if (isPrOrUnmergedWork(indexed.status)) return "pr_or_unmerged_commits";
5068
- if (indexed.status.changedFiles.length > 0) return "dirty_worktree";
5250
+ if (!isFinishedWorkerStatus(indexed.status)) return "run_still_active";
5251
+ if (hasUnrestorableWorktreeChanges(indexed.status)) return "dirty_worktree";
5069
5252
  const landing = assessWorkerLanding({
5070
5253
  finalResult: indexed.status.finalResult,
5071
5254
  changedFiles: indexed.status.changedFiles,
5072
5255
  gitAncestry: indexed.status.gitAncestry,
5073
5256
  prUrl: prUrlFromFinalResult(indexed.status.finalResult)
5074
5257
  });
5075
- if (landing.blocked) return "landing_blocked";
5258
+ if (landing.blocked && materialWorktreeChanges(indexed.status.changedFiles).length > 0) {
5259
+ return "landing_blocked";
5260
+ }
5076
5261
  return null;
5077
5262
  }
5078
5263
 
5079
5264
  // src/cleanup-execute.ts
5080
- import { existsSync as existsSync16, rmSync } from "node:fs";
5081
- import path25 from "node:path";
5265
+ import { existsSync as existsSync17, rmSync } from "node:fs";
5266
+ import path27 from "node:path";
5082
5267
 
5083
5268
  // src/cleanup-dir-size.ts
5084
- import { existsSync as existsSync15, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
5085
- import path24 from "node:path";
5269
+ import { existsSync as existsSync16, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
5270
+ import path26 from "node:path";
5086
5271
  function directorySizeBytes(root, maxEntries = 5e4) {
5087
- if (!existsSync15(root)) return 0;
5272
+ if (!existsSync16(root)) return 0;
5088
5273
  let total = 0;
5089
5274
  let seen = 0;
5090
5275
  const stack = [root];
@@ -5098,7 +5283,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
5098
5283
  }
5099
5284
  for (const name of entries) {
5100
5285
  if (seen++ > maxEntries) return null;
5101
- const full = path24.join(current, name);
5286
+ const full = path26.join(current, name);
5102
5287
  let st;
5103
5288
  try {
5104
5289
  st = statSync2(full);
@@ -5114,7 +5299,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
5114
5299
 
5115
5300
  // src/cleanup-execute.ts
5116
5301
  function removeNodeModules(candidate, execute) {
5117
- if (!existsSync16(candidate.path)) {
5302
+ if (!existsSync17(candidate.path)) {
5118
5303
  return {
5119
5304
  ...candidate,
5120
5305
  executed: false,
@@ -5145,7 +5330,7 @@ function removeNodeModules(candidate, execute) {
5145
5330
  }
5146
5331
  }
5147
5332
  function removeWorktree(candidate, execute) {
5148
- if (!existsSync16(candidate.path)) {
5333
+ if (!existsSync17(candidate.path)) {
5149
5334
  return {
5150
5335
  ...candidate,
5151
5336
  executed: false,
@@ -5162,7 +5347,7 @@ function removeWorktree(candidate, execute) {
5162
5347
  if (repo) {
5163
5348
  git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
5164
5349
  }
5165
- if (existsSync16(candidate.path)) {
5350
+ if (existsSync17(candidate.path)) {
5166
5351
  rmSync(candidate.path, { recursive: true, force: true });
5167
5352
  }
5168
5353
  return {
@@ -5182,20 +5367,20 @@ function removeWorktree(candidate, execute) {
5182
5367
  }
5183
5368
  }
5184
5369
  function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
5185
- const resolved = path25.resolve(targetPath);
5186
- const nm = resolved.endsWith(`${path25.sep}node_modules`) ? resolved : null;
5370
+ const resolved = path27.resolve(targetPath);
5371
+ const nm = resolved.endsWith(`${path27.sep}node_modules`) ? resolved : null;
5187
5372
  if (!nm) return "path_outside_harness";
5188
- const rel = path25.relative(worktreesDir, nm);
5189
- if (rel.startsWith("..") || path25.isAbsolute(rel)) return "path_outside_harness";
5190
- const parts = rel.split(path25.sep);
5373
+ const rel = path27.relative(worktreesDir, nm);
5374
+ if (rel.startsWith("..") || path27.isAbsolute(rel)) return "path_outside_harness";
5375
+ const parts = rel.split(path27.sep);
5191
5376
  if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
5192
- if (!resolved.startsWith(path25.resolve(harnessRoot))) return "path_outside_harness";
5377
+ if (!resolved.startsWith(path27.resolve(harnessRoot))) return "path_outside_harness";
5193
5378
  return null;
5194
5379
  }
5195
5380
 
5196
5381
  // src/cleanup-scan.ts
5197
- import { existsSync as existsSync17, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
5198
- import path26 from "node:path";
5382
+ import { existsSync as existsSync18, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
5383
+ import path28 from "node:path";
5199
5384
  function pathAgeMs(target, now) {
5200
5385
  try {
5201
5386
  const mtime = statSync3(target).mtimeMs;
@@ -5205,17 +5390,17 @@ function pathAgeMs(target, now) {
5205
5390
  }
5206
5391
  }
5207
5392
  function isPathInside(child, parent) {
5208
- const rel = path26.relative(parent, child);
5209
- return rel === "" || !rel.startsWith("..") && !path26.isAbsolute(rel);
5393
+ const rel = path28.relative(parent, child);
5394
+ return rel === "" || !rel.startsWith("..") && !path28.isAbsolute(rel);
5210
5395
  }
5211
5396
  function scanNodeModulesCandidates(opts) {
5212
5397
  const candidates = [];
5213
5398
  const seen = /* @__PURE__ */ new Set();
5214
5399
  for (const entry of opts.index.values()) {
5215
5400
  if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
5216
- const nm = path26.join(entry.worktreePath, "node_modules");
5217
- if (!existsSync17(nm)) continue;
5218
- const resolved = path26.resolve(nm);
5401
+ const nm = path28.join(entry.worktreePath, "node_modules");
5402
+ if (!existsSync18(nm)) continue;
5403
+ const resolved = path28.resolve(nm);
5219
5404
  if (seen.has(resolved)) continue;
5220
5405
  seen.add(resolved);
5221
5406
  candidates.push({
@@ -5228,16 +5413,16 @@ function scanNodeModulesCandidates(opts) {
5228
5413
  ageMs: pathAgeMs(resolved, opts.now)
5229
5414
  });
5230
5415
  }
5231
- if (!opts.includeOrphans || !existsSync17(opts.worktreesDir)) return candidates;
5416
+ if (!opts.includeOrphans || !existsSync18(opts.worktreesDir)) return candidates;
5232
5417
  for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
5233
5418
  if (!runEntry.isDirectory()) continue;
5234
- const runPath = path26.join(opts.worktreesDir, runEntry.name);
5419
+ const runPath = path28.join(opts.worktreesDir, runEntry.name);
5235
5420
  for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
5236
5421
  if (!workerEntry.isDirectory()) continue;
5237
- const worktreePath = path26.join(runPath, workerEntry.name);
5238
- const nm = path26.join(worktreePath, "node_modules");
5239
- if (!existsSync17(nm)) continue;
5240
- const resolved = path26.resolve(nm);
5422
+ const worktreePath = path28.join(runPath, workerEntry.name);
5423
+ const nm = path28.join(worktreePath, "node_modules");
5424
+ if (!existsSync18(nm)) continue;
5425
+ const resolved = path28.resolve(nm);
5241
5426
  if (seen.has(resolved)) continue;
5242
5427
  if (!isPathInside(resolved, opts.harnessRoot)) continue;
5243
5428
  seen.add(resolved);
@@ -5260,7 +5445,7 @@ function scanWorktreeCandidates(opts) {
5260
5445
  for (const entry of opts.index.values()) {
5261
5446
  if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
5262
5447
  const resolved = entry.worktreePath;
5263
- if (!existsSync17(resolved)) continue;
5448
+ if (!existsSync18(resolved)) continue;
5264
5449
  if (seen.has(resolved)) continue;
5265
5450
  seen.add(resolved);
5266
5451
  candidates.push({
@@ -5277,17 +5462,17 @@ function scanWorktreeCandidates(opts) {
5277
5462
  }
5278
5463
 
5279
5464
  // src/cleanup-worktree-index.ts
5280
- import path27 from "node:path";
5465
+ import path29 from "node:path";
5281
5466
  function buildWorktreeIndex() {
5282
5467
  const index = /* @__PURE__ */ new Map();
5283
5468
  for (const run of listRunRecords()) {
5284
5469
  for (const name of Object.keys(run.workers || {})) {
5285
- const workerPath = path27.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
5470
+ const workerPath = path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
5286
5471
  const worker = readJson(workerPath, void 0);
5287
5472
  if (!worker?.worktreePath) continue;
5288
5473
  const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
5289
- index.set(path27.resolve(worker.worktreePath), {
5290
- worktreePath: path27.resolve(worker.worktreePath),
5474
+ index.set(path29.resolve(worker.worktreePath), {
5475
+ worktreePath: path29.resolve(worker.worktreePath),
5291
5476
  runId: run.id,
5292
5477
  workerName: name,
5293
5478
  run,
@@ -5299,59 +5484,109 @@ function buildWorktreeIndex() {
5299
5484
  return index;
5300
5485
  }
5301
5486
 
5302
- // src/cleanup.ts
5303
- function resolveOptions(options = {}) {
5304
- const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
5305
- const { worktreesDir } = options.harnessRoot ? { worktreesDir: path28.join(harnessRoot, "worktrees") } : getHarnessPaths();
5306
- const execute = options.execute === true;
5307
- const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
5308
- const worktreesAgeMs = options.worktreesAgeMs ?? 0;
5309
- const includeOrphans = options.includeOrphans === true;
5310
- const runIdFilter = options.runIdFilter ? String(options.runIdFilter) : void 0;
5311
- const now = options.now ?? Date.now();
5487
+ // src/cleanup-types.ts
5488
+ var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
5489
+ var DEFAULT_WORKTREES_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
5490
+
5491
+ // src/cleanup-retention-config.ts
5492
+ function envFlag(name) {
5493
+ const v = process.env[name];
5494
+ return v === "1" || v === "true" || v === "yes";
5495
+ }
5496
+ function envMs(name, fallback) {
5497
+ const raw = process.env[name];
5498
+ if (!raw) return fallback;
5499
+ const n = Number(raw);
5500
+ return Number.isFinite(n) && n >= 0 ? n : fallback;
5501
+ }
5502
+ function resolveHarnessRetention(options = {}) {
5503
+ const execute = options.execute === true || options.execute !== false && envFlag("KYNVER_CLEANUP_EXECUTE");
5504
+ const finalizeStaleRuns2 = options.finalizeStaleRuns !== false && !envFlag("KYNVER_CLEANUP_SKIP_FINALIZE");
5505
+ const nodeModulesAgeMs = options.nodeModulesAgeMs ?? envMs("KYNVER_CLEANUP_NODE_MODULES_AGE_MS", DEFAULT_NODE_MODULES_AGE_MS);
5506
+ const worktreesAgeMs = options.worktreesAgeMs ?? envMs("KYNVER_CLEANUP_WORKTREES_AGE_MS", 0);
5507
+ const includeOrphans = options.includeOrphans === true || envFlag("KYNVER_CLEANUP_INCLUDE_ORPHANS");
5508
+ const scopeAll = envFlag("KYNVER_CLEANUP_SCOPE_ALL") || process.env.KYNVER_CLEANUP_SCOPE === "all";
5509
+ const runIdFilter = scopeAll ? options.runIdFilter : options.runIdFilter ?? (process.env.KYNVER_CLEANUP_RUN_ID || void 0);
5510
+ const accountBytes = options.accountBytes !== false && !envFlag("KYNVER_CLEANUP_SKIP_BYTE_ACCOUNTING");
5312
5511
  return {
5313
- harnessRoot,
5314
- worktreesDir,
5315
5512
  execute,
5316
- dryRun: !execute,
5513
+ finalizeStaleRuns: finalizeStaleRuns2,
5317
5514
  nodeModulesAgeMs,
5318
- worktreesAgeMs,
5515
+ worktreesAgeMs: worktreesAgeMs > 0 ? worktreesAgeMs : 0,
5319
5516
  includeOrphans,
5320
- runIdFilter,
5321
- now
5517
+ runIdFilter: runIdFilter ? String(runIdFilter) : void 0,
5518
+ accountBytes
5322
5519
  };
5323
5520
  }
5521
+ function resolvePipelineHarnessRetention(runId) {
5522
+ const scopeAll = process.env.KYNVER_CLEANUP_SCOPE === "all";
5523
+ const envWorktrees = Number(process.env.KYNVER_CLEANUP_WORKTREES_AGE_MS);
5524
+ const worktreesAgeMs = scopeAll ? Number.isFinite(envWorktrees) && envWorktrees > 0 ? envWorktrees : DEFAULT_WORKTREES_AGE_MS : Number.isFinite(envWorktrees) && envWorktrees > 0 ? envWorktrees : 0;
5525
+ return resolveHarnessRetention({
5526
+ runIdFilter: scopeAll ? void 0 : runId,
5527
+ worktreesAgeMs,
5528
+ finalizeStaleRuns: true,
5529
+ accountBytes: true
5530
+ });
5531
+ }
5532
+
5533
+ // src/cleanup.ts
5534
+ function resolvePaths(options = {}) {
5535
+ const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
5536
+ const { worktreesDir } = options.harnessRoot ? { worktreesDir: path30.join(harnessRoot, "worktrees") } : getHarnessPaths();
5537
+ const now = options.now ?? Date.now();
5538
+ return { harnessRoot, worktreesDir, now };
5539
+ }
5324
5540
  function recordSkip(skips, pathValue, reason, detail) {
5325
5541
  skips.push({ path: pathValue, reason, ...detail ? { detail } : {} });
5326
5542
  }
5543
+ function attachCandidateBytes(candidate, accountBytes) {
5544
+ if (!accountBytes || candidate.bytes != null) return candidate;
5545
+ return { ...candidate, bytes: directorySizeBytes(candidate.path) };
5546
+ }
5547
+ function tallySkipReasons(actions, skips) {
5548
+ const counts = {};
5549
+ for (const skip of skips) {
5550
+ counts[skip.reason] = (counts[skip.reason] ?? 0) + 1;
5551
+ }
5552
+ for (const action of actions) {
5553
+ if (action.skipReason) {
5554
+ counts[action.skipReason] = (counts[action.skipReason] ?? 0) + 1;
5555
+ }
5556
+ }
5557
+ return counts;
5558
+ }
5327
5559
  function runHarnessCleanup(options = {}) {
5328
- const resolved = resolveOptions(options);
5560
+ const retention = resolveHarnessRetention(options);
5561
+ const paths = resolvePaths(options);
5562
+ const finalizedRuns = retention.finalizeStaleRuns ? finalizeStaleRuns().map((f) => ({ runId: f.runId, from: f.from, to: f.to })) : [];
5329
5563
  const index = buildWorktreeIndex();
5330
5564
  const scanOpts = {
5331
- harnessRoot: resolved.harnessRoot,
5332
- worktreesDir: resolved.worktreesDir,
5333
- nodeModulesAgeMs: resolved.nodeModulesAgeMs,
5334
- worktreesAgeMs: resolved.worktreesAgeMs,
5335
- includeOrphans: resolved.includeOrphans,
5336
- runIdFilter: resolved.runIdFilter,
5565
+ harnessRoot: paths.harnessRoot,
5566
+ worktreesDir: paths.worktreesDir,
5567
+ nodeModulesAgeMs: retention.nodeModulesAgeMs,
5568
+ worktreesAgeMs: retention.worktreesAgeMs,
5569
+ includeOrphans: retention.includeOrphans,
5570
+ runIdFilter: retention.runIdFilter,
5337
5571
  index,
5338
- now: resolved.now
5572
+ now: paths.now
5339
5573
  };
5340
5574
  const skips = [];
5341
5575
  const actions = [];
5342
- for (const candidate of scanNodeModulesCandidates(scanOpts)) {
5343
- const pathSkip = isHarnessNodeModulesPath(candidate.path, resolved.harnessRoot, resolved.worktreesDir);
5576
+ for (const raw of scanNodeModulesCandidates(scanOpts)) {
5577
+ const candidate = attachCandidateBytes(raw, retention.accountBytes);
5578
+ const pathSkip = isHarnessNodeModulesPath(candidate.path, paths.harnessRoot, paths.worktreesDir);
5344
5579
  if (pathSkip) {
5345
5580
  recordSkip(skips, candidate.path, pathSkip);
5346
5581
  actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
5347
5582
  continue;
5348
5583
  }
5349
- const worktreePath = path28.resolve(candidate.path, "..");
5584
+ const worktreePath = path30.resolve(candidate.path, "..");
5350
5585
  const indexed = index.get(worktreePath) ?? null;
5351
5586
  const guardReason = skipNodeModulesRemoval({
5352
5587
  indexed,
5353
- includeOrphans: resolved.includeOrphans,
5354
- nodeModulesAgeMs: resolved.nodeModulesAgeMs,
5588
+ includeOrphans: retention.includeOrphans,
5589
+ nodeModulesAgeMs: retention.nodeModulesAgeMs,
5355
5590
  ageMs: candidate.ageMs
5356
5591
  });
5357
5592
  if (guardReason) {
@@ -5359,14 +5594,15 @@ function runHarnessCleanup(options = {}) {
5359
5594
  actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
5360
5595
  continue;
5361
5596
  }
5362
- actions.push(removeNodeModules(candidate, resolved.execute));
5597
+ actions.push(removeNodeModules(candidate, retention.execute));
5363
5598
  }
5364
- for (const candidate of scanWorktreeCandidates(scanOpts)) {
5365
- const indexed = index.get(path28.resolve(candidate.path)) ?? null;
5599
+ for (const raw of scanWorktreeCandidates(scanOpts)) {
5600
+ const candidate = attachCandidateBytes(raw, retention.accountBytes);
5601
+ const indexed = index.get(path30.resolve(candidate.path)) ?? null;
5366
5602
  const guardReason = skipWorktreeRemoval({
5367
5603
  indexed,
5368
- includeOrphans: resolved.includeOrphans,
5369
- worktreesAgeMs: resolved.worktreesAgeMs,
5604
+ includeOrphans: retention.includeOrphans,
5605
+ worktreesAgeMs: retention.worktreesAgeMs,
5370
5606
  ageMs: candidate.ageMs
5371
5607
  });
5372
5608
  if (guardReason) {
@@ -5374,51 +5610,55 @@ function runHarnessCleanup(options = {}) {
5374
5610
  actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
5375
5611
  continue;
5376
5612
  }
5377
- actions.push(removeWorktree(candidate, resolved.execute));
5613
+ actions.push(removeWorktree(candidate, retention.execute));
5378
5614
  }
5379
5615
  let candidateBytes = 0;
5616
+ let reclaimableBytes = 0;
5380
5617
  let removedBytes = 0;
5381
5618
  let removedPaths = 0;
5382
5619
  let skippedPaths = 0;
5383
5620
  for (const action of actions) {
5384
5621
  if (action.bytes) candidateBytes += action.bytes;
5622
+ if (!action.skipped && !action.executed && action.bytes) reclaimableBytes += action.bytes;
5385
5623
  if (action.executed) {
5386
5624
  removedPaths += 1;
5387
5625
  removedBytes += action.bytes ?? 0;
5388
5626
  } else if (action.skipped) {
5389
5627
  skippedPaths += 1;
5628
+ if (action.skipReason === "dry_run" && action.bytes) reclaimableBytes += action.bytes;
5390
5629
  }
5391
5630
  }
5392
5631
  return {
5393
- harnessRoot: resolved.harnessRoot,
5394
- dryRun: resolved.dryRun,
5395
- execute: resolved.execute,
5396
- nodeModulesAgeMs: resolved.nodeModulesAgeMs,
5397
- worktreesAgeMs: resolved.worktreesAgeMs,
5398
- includeOrphans: resolved.includeOrphans,
5399
- scannedAt: new Date(resolved.now).toISOString(),
5632
+ harnessRoot: paths.harnessRoot,
5633
+ dryRun: !retention.execute,
5634
+ execute: retention.execute,
5635
+ nodeModulesAgeMs: retention.nodeModulesAgeMs,
5636
+ worktreesAgeMs: retention.worktreesAgeMs,
5637
+ includeOrphans: retention.includeOrphans,
5638
+ scannedAt: new Date(paths.now).toISOString(),
5639
+ finalizedRuns,
5400
5640
  actions,
5401
5641
  skips,
5402
5642
  totals: {
5403
5643
  candidateBytes,
5644
+ reclaimableBytes,
5404
5645
  removedBytes,
5405
5646
  removedPaths,
5406
- skippedPaths
5647
+ skippedPaths,
5648
+ skipReasons: tallySkipReasons(actions, skips)
5407
5649
  }
5408
5650
  };
5409
5651
  }
5410
5652
  function runPipelineHarnessCleanup(runId) {
5411
- const nodeModulesAgeMs = Number(process.env.KYNVER_CLEANUP_NODE_MODULES_AGE_MS) || DEFAULT_NODE_MODULES_AGE_MS;
5412
- const worktreesAgeMs = Number(process.env.KYNVER_CLEANUP_WORKTREES_AGE_MS) || 0;
5413
- const execute = process.env.KYNVER_CLEANUP_EXECUTE === "1";
5414
- const includeOrphans = process.env.KYNVER_CLEANUP_INCLUDE_ORPHANS === "1";
5415
- const scopeAll = process.env.KYNVER_CLEANUP_SCOPE === "all";
5653
+ const retention = resolvePipelineHarnessRetention(runId);
5416
5654
  return runHarnessCleanup({
5417
- execute,
5418
- nodeModulesAgeMs,
5419
- worktreesAgeMs,
5420
- includeOrphans,
5421
- runIdFilter: scopeAll ? void 0 : runId
5655
+ execute: retention.execute,
5656
+ finalizeStaleRuns: retention.finalizeStaleRuns,
5657
+ accountBytes: retention.accountBytes,
5658
+ nodeModulesAgeMs: retention.nodeModulesAgeMs,
5659
+ worktreesAgeMs: retention.worktreesAgeMs,
5660
+ includeOrphans: retention.includeOrphans,
5661
+ runIdFilter: retention.runIdFilter
5422
5662
  });
5423
5663
  }
5424
5664
  function isPipelineCleanupEnabled() {
@@ -5427,8 +5667,8 @@ function isPipelineCleanupEnabled() {
5427
5667
 
5428
5668
  // src/installed-package-versions.ts
5429
5669
  import { readFile } from "node:fs/promises";
5430
- import { homedir as homedir5 } from "node:os";
5431
- import path29 from "node:path";
5670
+ import { homedir as homedir6 } from "node:os";
5671
+ import path31 from "node:path";
5432
5672
  var MANAGED_PACKAGES = [
5433
5673
  "@kynver-app/runtime",
5434
5674
  "@kynver-app/openclaw-agent-os",
@@ -5442,13 +5682,13 @@ function unique(values) {
5442
5682
  return [...new Set(values.filter((value) => Boolean(value)))];
5443
5683
  }
5444
5684
  function moduleRoots() {
5445
- const home = homedir5();
5446
- const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path29.join(home, ".openclaw", "npm");
5447
- const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path29.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path29.join(home, ".npm-global", "lib", "node_modules"));
5685
+ const home = homedir6();
5686
+ const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path31.join(home, ".openclaw", "npm");
5687
+ const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path31.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path31.join(home, ".npm-global", "lib", "node_modules"));
5448
5688
  return unique([
5449
- path29.join(openClawPrefix, "lib", "node_modules"),
5450
- path29.join(openClawPrefix, "node_modules"),
5451
- npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path29.join(npmGlobalRoot, "lib", "node_modules")
5689
+ path31.join(openClawPrefix, "lib", "node_modules"),
5690
+ path31.join(openClawPrefix, "node_modules"),
5691
+ npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path31.join(npmGlobalRoot, "lib", "node_modules")
5452
5692
  ]);
5453
5693
  }
5454
5694
  async function readVersion(packageJsonPath) {
@@ -5464,7 +5704,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
5464
5704
  const out = {};
5465
5705
  for (const packageName of MANAGED_PACKAGES) {
5466
5706
  for (const root of roots) {
5467
- const packageJsonPath = path29.join(root, packageName, "package.json");
5707
+ const packageJsonPath = path31.join(root, packageName, "package.json");
5468
5708
  const version = await readVersion(packageJsonPath);
5469
5709
  if (!version) continue;
5470
5710
  out[packageName] = { version, observedAt, path: packageJsonPath };
@@ -5480,7 +5720,7 @@ async function completeFinishedWorkers(runId, args) {
5480
5720
  const outcomes = [];
5481
5721
  for (const name of Object.keys(run.workers || {})) {
5482
5722
  const worker = readJson(
5483
- path30.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
5723
+ path32.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
5484
5724
  void 0
5485
5725
  );
5486
5726
  if (!worker?.taskId || worker.localOnly) continue;
@@ -5622,7 +5862,7 @@ async function runDaemon(args) {
5622
5862
  }
5623
5863
 
5624
5864
  // src/plan-progress.ts
5625
- import path31 from "node:path";
5865
+ import path33 from "node:path";
5626
5866
 
5627
5867
  // src/bounded-build/constants.ts
5628
5868
  var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
@@ -5908,7 +6148,7 @@ async function emitPlanProgress(args) {
5908
6148
  }
5909
6149
  function verifyPlanLocal(args) {
5910
6150
  const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
5911
- const cwd = path31.resolve(worktree);
6151
+ const cwd = path33.resolve(worktree);
5912
6152
  const summary = runHarnessVerifyCommands(cwd);
5913
6153
  const emitJson = args.json === true || args.json === "true";
5914
6154
  const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
@@ -5957,9 +6197,9 @@ async function verifyPlan(args) {
5957
6197
  }
5958
6198
 
5959
6199
  // src/harness-verify-cli.ts
5960
- import path32 from "node:path";
6200
+ import path34 from "node:path";
5961
6201
  function runHarnessVerifyCli(args) {
5962
- const cwd = path32.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
6202
+ const cwd = path34.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
5963
6203
  const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
5964
6204
  const commands = [];
5965
6205
  const rawCmd = args.command;
@@ -6003,7 +6243,7 @@ function runHarnessVerifyCli(args) {
6003
6243
  }
6004
6244
 
6005
6245
  // src/plan-persist-cli.ts
6006
- import { readFileSync as readFileSync8 } from "node:fs";
6246
+ import { readFileSync as readFileSync9 } from "node:fs";
6007
6247
  var OPERATIONS = ["create", "add_version", "update_metadata"];
6008
6248
  var FAILURE_KINDS = [
6009
6249
  "approval_guard",
@@ -6015,7 +6255,7 @@ var FAILURE_KINDS = [
6015
6255
  function readBodyArg(args) {
6016
6256
  const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
6017
6257
  if (bodyFile) {
6018
- return { body: readFileSync8(bodyFile, "utf8"), bodyPathHint: bodyFile };
6258
+ return { body: readFileSync9(bodyFile, "utf8"), bodyPathHint: bodyFile };
6019
6259
  }
6020
6260
  const inline = args.body ? String(args.body) : void 0;
6021
6261
  if (inline) return { body: inline };
@@ -6085,12 +6325,14 @@ async function runPlanOutboxDrain(args) {
6085
6325
  // src/cleanup-cli.ts
6086
6326
  function runCleanupCli(args) {
6087
6327
  const execute = args.execute === true || args.execute === "true";
6328
+ const skipFinalize = args.skipFinalize === true || args.skipFinalize === "true";
6088
6329
  const nodeModulesAgeMs = args.nodeModulesAgeMs ? Number(args.nodeModulesAgeMs) : DEFAULT_NODE_MODULES_AGE_MS;
6089
6330
  const worktreesAgeMs = args.worktreesAgeMs ? Number(args.worktreesAgeMs) : 0;
6090
6331
  const includeOrphans = args.includeOrphans === true || args.includeOrphans === "true";
6091
6332
  const harnessRoot = args.harnessRoot ? String(args.harnessRoot) : void 0;
6092
6333
  const summary = runHarnessCleanup({
6093
6334
  execute,
6335
+ finalizeStaleRuns: !skipFinalize,
6094
6336
  nodeModulesAgeMs: Number.isFinite(nodeModulesAgeMs) ? nodeModulesAgeMs : DEFAULT_NODE_MODULES_AGE_MS,
6095
6337
  worktreesAgeMs: Number.isFinite(worktreesAgeMs) ? worktreesAgeMs : 0,
6096
6338
  includeOrphans,
@@ -6103,7 +6345,7 @@ function runCleanupCli(args) {
6103
6345
  }
6104
6346
 
6105
6347
  // src/monitor/monitor.service.ts
6106
- import path34 from "node:path";
6348
+ import path36 from "node:path";
6107
6349
 
6108
6350
  // src/monitor/monitor.classify.ts
6109
6351
  function expectedLeaseOwner(runId) {
@@ -6159,11 +6401,11 @@ function classifyWorkerHealth(input) {
6159
6401
  }
6160
6402
 
6161
6403
  // src/monitor/monitor.store.ts
6162
- import { existsSync as existsSync18, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
6163
- import path33 from "node:path";
6404
+ import { existsSync as existsSync19, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
6405
+ import path35 from "node:path";
6164
6406
  function monitorsDir() {
6165
6407
  const { harnessRoot } = getHarnessPaths();
6166
- const dir = path33.join(harnessRoot, "monitors");
6408
+ const dir = path35.join(harnessRoot, "monitors");
6167
6409
  mkdirSync6(dir, { recursive: true });
6168
6410
  return dir;
6169
6411
  }
@@ -6171,7 +6413,7 @@ function monitorIdFor(runId, workerName) {
6171
6413
  return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
6172
6414
  }
6173
6415
  function monitorPath(monitorId) {
6174
- return path33.join(monitorsDir(), `${monitorId}.json`);
6416
+ return path35.join(monitorsDir(), `${monitorId}.json`);
6175
6417
  }
6176
6418
  function loadMonitorSession(monitorId) {
6177
6419
  return readJson(monitorPath(monitorId), void 0);
@@ -6181,18 +6423,18 @@ function saveMonitorSession(session) {
6181
6423
  }
6182
6424
  function deleteMonitorSession(monitorId) {
6183
6425
  const file = monitorPath(monitorId);
6184
- if (!existsSync18(file)) return false;
6426
+ if (!existsSync19(file)) return false;
6185
6427
  unlinkSync2(file);
6186
6428
  return true;
6187
6429
  }
6188
6430
  function listMonitorSessions() {
6189
6431
  const dir = monitorsDir();
6190
- if (!existsSync18(dir)) return [];
6432
+ if (!existsSync19(dir)) return [];
6191
6433
  const entries = [];
6192
6434
  for (const name of readdirSync7(dir)) {
6193
6435
  if (!name.endsWith(".json")) continue;
6194
6436
  const session = readJson(
6195
- path33.join(dir, name),
6437
+ path35.join(dir, name),
6196
6438
  void 0
6197
6439
  );
6198
6440
  if (!session?.monitorId) continue;
@@ -6283,7 +6525,7 @@ async function fetchTaskLeasesForWorkers(input) {
6283
6525
  // src/monitor/monitor.service.ts
6284
6526
  function workerRecord2(runId, name) {
6285
6527
  return readJson(
6286
- path34.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
6528
+ path36.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
6287
6529
  void 0
6288
6530
  );
6289
6531
  }
@@ -6485,18 +6727,18 @@ async function runMonitorLoop(args) {
6485
6727
 
6486
6728
  // src/monitor/monitor-spawn.ts
6487
6729
  import { spawn as spawn4 } from "node:child_process";
6488
- import { closeSync as closeSync4, existsSync as existsSync19, openSync as openSync4 } from "node:fs";
6489
- import path35 from "node:path";
6490
- import { fileURLToPath as fileURLToPath3 } from "node:url";
6730
+ import { closeSync as closeSync4, existsSync as existsSync20, openSync as openSync4 } from "node:fs";
6731
+ import path37 from "node:path";
6732
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
6491
6733
  function resolveDefaultCliPath2() {
6492
- return path35.join(fileURLToPath3(new URL(".", import.meta.url)), "..", "cli.js");
6734
+ return path37.join(fileURLToPath4(new URL(".", import.meta.url)), "..", "cli.js");
6493
6735
  }
6494
6736
  function spawnMonitorSidecar(opts) {
6495
6737
  const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
6496
- if (!existsSync19(cliPath)) return void 0;
6738
+ if (!existsSync20(cliPath)) return void 0;
6497
6739
  const monitorId = monitorIdFor(opts.runId, opts.workerName);
6498
6740
  const { harnessRoot } = getHarnessPaths();
6499
- const logPath = path35.join(harnessRoot, "monitors", `${monitorId}.log`);
6741
+ const logPath = path37.join(harnessRoot, "monitors", `${monitorId}.log`);
6500
6742
  let logFd;
6501
6743
  try {
6502
6744
  logFd = openSync4(logPath, "a");
@@ -6616,12 +6858,12 @@ async function monitorTickCli(args) {
6616
6858
  }
6617
6859
 
6618
6860
  // src/doctor/runtime-takeover.ts
6619
- import path37 from "node:path";
6861
+ import path39 from "node:path";
6620
6862
 
6621
6863
  // src/doctor/runtime-takeover.probes.ts
6622
- import { accessSync, constants, existsSync as existsSync20, readFileSync as readFileSync9 } from "node:fs";
6623
- import { homedir as homedir6 } from "node:os";
6624
- import path36 from "node:path";
6864
+ import { accessSync, constants, existsSync as existsSync21, readFileSync as readFileSync10 } from "node:fs";
6865
+ import { homedir as homedir7 } from "node:os";
6866
+ import path38 from "node:path";
6625
6867
  import { spawnSync as spawnSync6 } from "node:child_process";
6626
6868
  function captureCommand(bin, args) {
6627
6869
  try {
@@ -6650,7 +6892,7 @@ function tokenPrefix(token) {
6650
6892
  return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
6651
6893
  }
6652
6894
  function isWritable(target) {
6653
- if (!existsSync20(target)) return false;
6895
+ if (!existsSync21(target)) return false;
6654
6896
  try {
6655
6897
  accessSync(target, constants.W_OK);
6656
6898
  return true;
@@ -6663,15 +6905,15 @@ var defaultRuntimeTakeoverProbes = {
6663
6905
  commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
6664
6906
  kynverVersion: (bin) => captureCommand(bin, ["--version"]),
6665
6907
  loadConfig: () => loadUserConfig(),
6666
- configFilePath: () => path36.join(homedir6(), ".kynver", "config.json"),
6667
- credentialsFilePath: () => path36.join(homedir6(), ".kynver", "credentials"),
6908
+ configFilePath: () => path38.join(homedir7(), ".kynver", "config.json"),
6909
+ credentialsFilePath: () => path38.join(homedir7(), ".kynver", "credentials"),
6668
6910
  readCredentials: () => {
6669
- const credPath = path36.join(homedir6(), ".kynver", "credentials");
6670
- if (!existsSync20(credPath)) {
6911
+ const credPath = path38.join(homedir7(), ".kynver", "credentials");
6912
+ if (!existsSync21(credPath)) {
6671
6913
  return { hasApiKey: false };
6672
6914
  }
6673
6915
  try {
6674
- const parsed = JSON.parse(readFileSync9(credPath, "utf8"));
6916
+ const parsed = JSON.parse(readFileSync10(credPath, "utf8"));
6675
6917
  return {
6676
6918
  hasApiKey: Boolean(parsed.apiKey?.trim()),
6677
6919
  runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
@@ -6697,8 +6939,8 @@ var defaultRuntimeTakeoverProbes = {
6697
6939
  })()
6698
6940
  }),
6699
6941
  harnessRoot: () => resolveHarnessRoot(),
6700
- legacyOpenclawHarnessRoot: () => path36.join(homedir6(), ".openclaw", "harness"),
6701
- pathExists: (target) => existsSync20(target),
6942
+ legacyOpenclawHarnessRoot: () => path38.join(homedir7(), ".openclaw", "harness"),
6943
+ pathExists: (target) => existsSync21(target),
6702
6944
  pathWritable: (target) => isWritable(target),
6703
6945
  vercelVersion: () => captureCommand("vercel", ["--version"]),
6704
6946
  vercelWhoami: () => captureCommand("vercel", ["whoami"])
@@ -6713,8 +6955,36 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
6713
6955
  const runnerQstash = env.kynverSchedulerProvider === "qstash";
6714
6956
  const hostedDeployment = Boolean(env.qstashTokenPresent) || Boolean(env.kynverHostedDeployment);
6715
6957
  const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
6716
- const deploymentNeedsQstash = hostedDeployment && !env.qstashTokenPresent && env.kynverSchedulerProvider !== "qstash";
6717
- const deploymentOpenclaw = hostedDeployment && env.kynverSchedulerProvider === "openclaw-cron";
6958
+ const hostedSchedulerProcess = hostedDeployment && !daemonDispatchReady;
6959
+ const deploymentNeedsQstash = hostedSchedulerProcess && !env.qstashTokenPresent && env.kynverSchedulerProvider !== "qstash";
6960
+ const deploymentOpenclaw = hostedSchedulerProcess && env.kynverSchedulerProvider === "openclaw-cron";
6961
+ if (daemonDispatchReady && runnerOpenclaw) {
6962
+ return check({
6963
+ id: "hotspot_openclaw_scheduler",
6964
+ label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
6965
+ status: "warn",
6966
+ summary: "KYNVER_SCHEDULER_PROVIDER=openclaw-cron on this runner \u2014 dispatch is owned by kynver daemon; unset the OpenClaw override",
6967
+ remediation: "Unset KYNVER_SCHEDULER_PROVIDER on user runners. Use `kynver daemon` (pipeline-tick \u2192 operator/tick). On the Kynver server set KYNVER_SCHEDULER_PROVIDER=qstash when QStash is configured.",
6968
+ details: {
6969
+ schedulerProvider: env.kynverSchedulerProvider ?? null,
6970
+ dispatchPath: "kynver-daemon-pipeline-tick",
6971
+ hostedDeployment
6972
+ }
6973
+ });
6974
+ }
6975
+ if (daemonDispatchReady) {
6976
+ return check({
6977
+ id: "hotspot_openclaw_scheduler",
6978
+ label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
6979
+ status: "pass",
6980
+ summary: runnerQstash ? "Runner override qstash present; hosted dispatch still owned by kynver daemon pipeline-tick" : "Hosted dispatch owned by kynver daemon (pipeline-tick \u2192 operator/tick); no OpenClaw cron on runner",
6981
+ details: {
6982
+ schedulerProvider: env.kynverSchedulerProvider ?? null,
6983
+ dispatchPath: "kynver-daemon-pipeline-tick",
6984
+ hostedDeployment
6985
+ }
6986
+ });
6987
+ }
6718
6988
  if (runnerOpenclaw) {
6719
6989
  return check({
6720
6990
  id: "hotspot_openclaw_scheduler",
@@ -6730,7 +7000,7 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
6730
7000
  id: "hotspot_openclaw_scheduler",
6731
7001
  label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
6732
7002
  status: "warn",
6733
- summary: deploymentOpenclaw ? "Hosted deployment has KYNVER_SCHEDULER_PROVIDER=openclaw-cron \u2014 AgentOS scheduled ticks should use QStash" : "Hosted deployment without QSTASH_TOKEN \u2014 scheduler may fall back to openclaw-cron",
7003
+ summary: deploymentOpenclaw ? "Hosted deployment has KYNVER_SCHEDULER_PROVIDER=openclaw-cron \u2014 AgentOS scheduled ticks should use QStash" : env.kynverSchedulerProvider ? `Hosted deployment without QSTASH_TOKEN (KYNVER_SCHEDULER_PROVIDER=${env.kynverSchedulerProvider}) \u2014 scheduler may fall back to openclaw-cron` : "KYNVER_SCHEDULER_PROVIDER unset on hosted deployment; without QSTASH_TOKEN the server may fall back to openclaw-cron",
6734
7004
  remediation: "Set QSTASH_TOKEN and KYNVER_SCHEDULER_PROVIDER=qstash on the Kynver server. User runners use `kynver daemon` for dispatch and should not set a scheduler provider.",
6735
7005
  details: {
6736
7006
  schedulerProvider: env.kynverSchedulerProvider ?? null,
@@ -6739,15 +7009,15 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
6739
7009
  }
6740
7010
  });
6741
7011
  }
6742
- if (daemonDispatchReady) {
7012
+ if (hostedDeployment && env.qstashTokenPresent && !env.kynverSchedulerProvider) {
6743
7013
  return check({
6744
7014
  id: "hotspot_openclaw_scheduler",
6745
7015
  label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
6746
7016
  status: "pass",
6747
- summary: 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",
7017
+ summary: "QSTASH_TOKEN present; hosted scheduler auto-selects qstash (explicit KYNVER_SCHEDULER_PROVIDER=qstash optional)",
6748
7018
  details: {
6749
- schedulerProvider: env.kynverSchedulerProvider ?? null,
6750
- dispatchPath: "kynver-daemon-pipeline-tick",
7019
+ schedulerProvider: null,
7020
+ qstashTokenPresent: true,
6751
7021
  hostedDeployment
6752
7022
  }
6753
7023
  });
@@ -6841,8 +7111,14 @@ function assessUserConfig(probes) {
6841
7111
  if (exists) {
6842
7112
  const apiBaseUrl = config.apiBaseUrl?.trim();
6843
7113
  const agentOsId = config.agentOsId?.trim();
6844
- const defaultRepo = config.defaultRepo?.trim();
6845
- const displayDefaultRepo = defaultRepo ? displayUserPath(defaultRepo) : null;
7114
+ const resolvedDefaultRepo = resolveDefaultRepo({ config });
7115
+ const formatted = resolvedDefaultRepo ? formatResolvedDefaultRepo(resolvedDefaultRepo) : null;
7116
+ let defaultRepoRemediation;
7117
+ if (!resolvedDefaultRepo) {
7118
+ defaultRepoRemediation = "Run `kynver setup` from a Kynver checkout (auto-discovers repo) or `kynver setup --repo /path/to/Kynver`.";
7119
+ } else if (!resolvedDefaultRepo.persistedInConfig) {
7120
+ defaultRepoRemediation = "Run `kynver setup` (no --repo) to persist discovered defaultRepo in ~/.kynver/config.json.";
7121
+ }
6846
7122
  checks.push(
6847
7123
  check2({
6848
7124
  id: "config_api_base_url",
@@ -6863,11 +7139,13 @@ function assessUserConfig(probes) {
6863
7139
  check2({
6864
7140
  id: "config_default_repo",
6865
7141
  label: "Default repo path",
6866
- status: defaultRepo ? "pass" : "warn",
6867
- summary: displayDefaultRepo ?? "Not set (pass --repo on `kynver run create`)",
6868
- remediation: defaultRepo ? void 0 : "Set `defaultRepo` via `kynver setup --repo /path/to/repo`.",
7142
+ status: resolvedDefaultRepo ? "pass" : "warn",
7143
+ summary: resolvedDefaultRepo ? `${formatted.defaultRepo} (${resolvedDefaultRepo.source}${resolvedDefaultRepo.persistedInConfig ? "" : ", not persisted"})` : "Not set (pass --repo on `kynver run create` or run `kynver setup` to auto-discover)",
7144
+ remediation: defaultRepoRemediation,
6869
7145
  details: {
6870
- defaultRepo: displayDefaultRepo,
7146
+ defaultRepo: formatted?.defaultRepo ?? null,
7147
+ source: formatted?.source ?? null,
7148
+ persistedInConfig: formatted?.persistedInConfig ?? false,
6871
7149
  harnessRoot: config.harnessRoot ? redactHomePath(config.harnessRoot) : null
6872
7150
  }
6873
7151
  })
@@ -6949,8 +7227,8 @@ function assessVercelCli(probes) {
6949
7227
  }
6950
7228
  function assessHarnessDirs(probes) {
6951
7229
  const harnessRoot = probes.harnessRoot();
6952
- const runsDir = path37.join(harnessRoot, "runs");
6953
- const worktreesDir = path37.join(harnessRoot, "worktrees");
7230
+ const runsDir = path39.join(harnessRoot, "runs");
7231
+ const worktreesDir = path39.join(harnessRoot, "worktrees");
6954
7232
  const displayHarnessRoot = redactHomePath(harnessRoot);
6955
7233
  const displayRunsDir = redactHomePath(runsDir);
6956
7234
  const displayWorktreesDir = redactHomePath(worktreesDir);
@@ -7153,9 +7431,9 @@ function usage(code = 0) {
7153
7431
  "Usage:",
7154
7432
  " kynver login --api-key KEY",
7155
7433
  " kynver runner credential [--agent-os-id ID] [--base-url URL]",
7156
- " kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--max-workers N] [--provider claude|cursor]",
7434
+ " kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--discover-repo] [--max-workers N] [--provider claude|cursor]",
7157
7435
  " kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS]",
7158
- " kynver run create --repo /path/repo [--name name] [--base origin/main]",
7436
+ " kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
7159
7437
  " kynver run list",
7160
7438
  " kynver run status --run RUN_ID",
7161
7439
  " kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--target-task-id TASK_ID] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-8] [--disk-path /]",
@@ -7173,7 +7451,7 @@ function usage(code = 0) {
7173
7451
  " kynver plan persist --operation create|add_version|update_metadata --title TITLE (--body-file PATH | --body TEXT) [--slug SLUG] [--plan PLAN_ID] [--summary TEXT] [--failure-kind approval_guard|auth|network|server|tool_interruption]",
7174
7452
  " kynver plan outbox list",
7175
7453
  " kynver plan outbox drain [--max N] [--id OUTBOX_ID]",
7176
- " kynver cleanup [--execute] [--node-modules-age-ms MS] [--worktrees-age-ms MS] [--harness-root PATH] [--include-orphans]",
7454
+ " kynver cleanup [--execute] [--node-modules-age-ms MS] [--worktrees-age-ms MS] [--harness-root PATH] [--include-orphans] [--skip-finalize]",
7177
7455
  " kynver monitor start --run RUN_ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS]",
7178
7456
  " kynver monitor status [--run RUN_ID] [--name worker] [--tick]",
7179
7457
  " kynver monitor stop --run RUN_ID [--name worker]",
@@ -7248,7 +7526,7 @@ async function main(argv = process.argv.slice(2)) {
7248
7526
  if (scope === "monitor" && action === "run-loop") return void await monitorRunLoopCli(args);
7249
7527
  unknownCommand(scope, action);
7250
7528
  }
7251
- var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(fileURLToPath4(import.meta.url));
7529
+ var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(fileURLToPath5(import.meta.url));
7252
7530
  if (isCliEntry) {
7253
7531
  void main().catch((error) => {
7254
7532
  console.error(error);
@@ -7571,6 +7849,8 @@ export {
7571
7849
  computeWorkerStatus,
7572
7850
  createRun,
7573
7851
  deriveRunStatus,
7852
+ discoverDefaultRepo,
7853
+ discoverDefaultRepoCandidates,
7574
7854
  dispatchRun,
7575
7855
  drainPlanOutbox,
7576
7856
  ensurePrReadyHandoff,
@@ -7578,13 +7858,16 @@ export {
7578
7858
  extractPlanOutboxFromTask,
7579
7859
  extractPrUrlFromText,
7580
7860
  formatPlanOutboxHandoffBlock,
7861
+ formatResolvedDefaultRepo,
7581
7862
  getHarnessPaths,
7582
7863
  getMonitorStatus,
7864
+ gitRepoRoot,
7583
7865
  hashPlanBody,
7584
7866
  isDashboardVercelUrl,
7585
7867
  isEngagementRequiredSkip,
7586
7868
  isFinishedWorkerStatus,
7587
7869
  isForbiddenWorkerEnvKey,
7870
+ isKynverMonorepoRoot,
7588
7871
  isLandingBlockedWorkerStatus,
7589
7872
  isSystemdRunAvailable,
7590
7873
  isTerminalHeartbeatPhase,
@@ -7603,6 +7886,7 @@ export {
7603
7886
  parseClaudeStream,
7604
7887
  parseHarnessStream,
7605
7888
  parseHeartbeat,
7889
+ persistDefaultRepo,
7606
7890
  persistPlan,
7607
7891
  pickVercelStatusContext,
7608
7892
  postJson,
@@ -7611,9 +7895,11 @@ export {
7611
7895
  reconcileRunsCli,
7612
7896
  reconcileStaleWorkers,
7613
7897
  redactHarness,
7898
+ remediateDefaultRepo,
7614
7899
  resolveBaseUrl,
7615
7900
  resolveCallbackSecret,
7616
7901
  resolveCallbackSecretWithMint,
7902
+ resolveDefaultRepo,
7617
7903
  resolveHarnessRoot,
7618
7904
  resolveVercelInspectTarget,
7619
7905
  runBoundedBuildCheck,