@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/cli.js CHANGED
@@ -2,43 +2,25 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { mkdirSync as mkdirSync7, realpathSync } from "node:fs";
5
- import { fileURLToPath as fileURLToPath4 } from "node:url";
5
+ import { fileURLToPath as fileURLToPath5 } from "node:url";
6
6
 
7
7
  // src/config.ts
8
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
8
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
9
+ import { homedir as homedir3 } from "node:os";
10
+ import path4 from "node:path";
11
+
12
+ // src/default-repo-discovery.ts
13
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
9
14
  import { homedir as homedir2 } from "node:os";
10
15
  import path3 from "node:path";
16
+ import { fileURLToPath } from "node:url";
11
17
 
12
- // src/path-values.ts
13
- import { homedir } from "node:os";
14
- import path from "node:path";
15
- function expandHomePath(value) {
16
- if (value === "~") return homedir();
17
- if (value.startsWith("~/") || value.startsWith("~\\")) {
18
- return path.join(homedir(), value.slice(2));
19
- }
20
- return value;
21
- }
22
- function resolveUserPath(value) {
23
- return path.resolve(expandHomePath(value));
24
- }
25
- function redactHomePath(value) {
26
- const expanded = expandHomePath(value);
27
- const resolved = path.resolve(expanded);
28
- const home = path.resolve(homedir());
29
- if (resolved === home) return "~";
30
- if (resolved.startsWith(`${home}${path.sep}`)) {
31
- return `~/${path.relative(home, resolved).split(path.sep).join("/")}`;
32
- }
33
- return resolved.replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^\/Users\/[^/]+(?=\/|$)/, "~");
34
- }
35
- function displayUserPath(value) {
36
- return redactHomePath(value);
37
- }
18
+ // src/git.ts
19
+ import { spawnSync } from "node:child_process";
38
20
 
39
21
  // src/util.ts
40
22
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
41
- import path2 from "node:path";
23
+ import path from "node:path";
42
24
  function fail(message) {
43
25
  console.error(message);
44
26
  process.exit(1);
@@ -67,7 +49,7 @@ function readJson(file, fallback) {
67
49
  }
68
50
  }
69
51
  function writeJson(file, value) {
70
- mkdirSync(path2.dirname(file), { recursive: true });
52
+ mkdirSync(path.dirname(file), { recursive: true });
71
53
  writeFileSync(file, `${JSON.stringify(value, null, 2)}
72
54
  `);
73
55
  }
@@ -103,7 +85,7 @@ function tailFile(file, lines) {
103
85
  return data.split("\n").slice(-lines).join("\n");
104
86
  }
105
87
  function readMaybeFile(file) {
106
- return file ? readFileSync(path2.resolve(file), "utf8") : "";
88
+ return file ? readFileSync(path.resolve(file), "utf8") : "";
107
89
  }
108
90
  function listRunIds(runsDir) {
109
91
  if (!existsSync(runsDir)) return [];
@@ -145,14 +127,270 @@ function secsAgo(ms) {
145
127
  return Math.max(0, Math.round((Date.now() - ms) / 1e3));
146
128
  }
147
129
 
130
+ // src/worker-env.ts
131
+ var FORBIDDEN_WORKER_ENV_KEYS = [
132
+ "ANTHROPIC_API_KEY",
133
+ "ANALYST_API_KEY",
134
+ "RECRUITER_API_KEY",
135
+ "AUTH_SECRET",
136
+ "NEXTAUTH_SECRET",
137
+ "DATABASE_URL",
138
+ "PRODUCTION_DATABASE_URL",
139
+ "REDIS_URL",
140
+ "GOOGLE_CLIENT_SECRET",
141
+ "GITHUB_CLIENT_SECRET",
142
+ "KYNVER_API_KEY",
143
+ "KYNVER_SERVICE_SECRET",
144
+ "KYNVER_RUNTIME_SECRET",
145
+ "OPENCLAW_CRON_SECRET",
146
+ "QSTASH_TOKEN",
147
+ "QSTASH_CURRENT_SIGNING_KEY",
148
+ "QSTASH_NEXT_SIGNING_KEY",
149
+ "TOOL_SECRETS_KEK",
150
+ "TOOL_EXECUTOR_DISPATCH_SECRET",
151
+ "CLOUDFLARE_API_TOKEN",
152
+ "STRIPE_SECRET_KEY",
153
+ "STRIPE_WEBHOOK_SECRET",
154
+ "STRIPE_IDENTITY_WEBHOOK_SECRET",
155
+ "VOYAGE_API_KEY",
156
+ "PERPLEXITY_API_KEY",
157
+ "FRED_API_KEY",
158
+ "FMP_API_KEY",
159
+ "CURSOR_API_KEY"
160
+ ];
161
+ var FORBIDDEN_KEY_SET = new Set(FORBIDDEN_WORKER_ENV_KEYS);
162
+ var FORBIDDEN_SUFFIXES = ["_SECRET", "_API_KEY"];
163
+ function isForbiddenWorkerEnvKey(key) {
164
+ if (FORBIDDEN_KEY_SET.has(key)) return true;
165
+ return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));
166
+ }
167
+ function scrubWorkerEnv(env) {
168
+ const next = { ...env };
169
+ for (const key of Object.keys(next)) {
170
+ if (isForbiddenWorkerEnvKey(key)) delete next[key];
171
+ }
172
+ return next;
173
+ }
174
+
175
+ // src/git.ts
176
+ function git(cwd, args, options = {}) {
177
+ const res = spawnSync("git", args, { cwd, encoding: "utf8" });
178
+ if (res.status !== 0 && !options.allowFailure) {
179
+ const message = `git ${args.join(" ")} failed: ${res.stderr || res.stdout}`;
180
+ if (options.throwError) throw new Error(message);
181
+ fail(message);
182
+ }
183
+ return res.stdout || "";
184
+ }
185
+ function ensureGitRepo(repo) {
186
+ git(repo, ["rev-parse", "--show-toplevel"]);
187
+ }
188
+ function gitStatusShort(worktreePath) {
189
+ return git(worktreePath, ["status", "--short"], { allowFailure: true }).split("\n").map((line) => line.trim()).filter(Boolean);
190
+ }
191
+ function gitCapture(cwd, args) {
192
+ try {
193
+ const res = spawnSync("git", args, { cwd, encoding: "utf8" });
194
+ return {
195
+ status: res.status,
196
+ stdout: res.stdout || "",
197
+ stderr: res.stderr || "",
198
+ error: res.error ? res.error.message : null
199
+ };
200
+ } catch (error) {
201
+ return {
202
+ status: null,
203
+ stdout: "",
204
+ stderr: "",
205
+ error: error.message
206
+ };
207
+ }
208
+ }
209
+ function gitIsAncestor(cwd, ancestor, descendant) {
210
+ const res = gitCapture(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]);
211
+ if (res.status === 0) return { isAncestor: true, error: null };
212
+ if (res.status === 1) return { isAncestor: false, error: null };
213
+ return { isAncestor: null, error: res.error || res.stderr || res.stdout || `git exited ${res.status}` };
214
+ }
215
+ function computeGitAncestry(worktreePath, baseOrOptions = "origin/main") {
216
+ const options = typeof baseOrOptions === "string" ? { base: baseOrOptions } : baseOrOptions;
217
+ const baseLabel = options.baseCommit?.trim() || options.base?.trim() || "origin/main";
218
+ const pinnedBaseCommit = options.baseCommit?.trim() || null;
219
+ if (!worktreePath) {
220
+ return unknownAncestry(baseLabel, "missing worktree path");
221
+ }
222
+ const head = gitCapture(worktreePath, ["rev-parse", "HEAD"]);
223
+ if (head.status !== 0) {
224
+ return unknownAncestry(baseLabel, head.error || head.stderr || head.stdout || "failed to resolve HEAD");
225
+ }
226
+ let baseSha;
227
+ if (pinnedBaseCommit) {
228
+ baseSha = pinnedBaseCommit;
229
+ } else {
230
+ const baseHead = gitCapture(worktreePath, ["rev-parse", baseLabel]);
231
+ if (baseHead.status !== 0) {
232
+ return unknownAncestry(
233
+ baseLabel,
234
+ baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${baseLabel}`,
235
+ head.stdout.trim()
236
+ );
237
+ }
238
+ baseSha = baseHead.stdout.trim();
239
+ }
240
+ const headSha = head.stdout.trim();
241
+ if (headSha === baseSha) {
242
+ return {
243
+ checked: true,
244
+ base: baseLabel,
245
+ head: headSha,
246
+ baseHead: baseSha,
247
+ baseIsAncestorOfHead: true,
248
+ headIsAncestorOfBase: true,
249
+ relation: "synced"
250
+ };
251
+ }
252
+ const baseIsAncestorOfHead = gitIsAncestor(worktreePath, baseSha, headSha);
253
+ const headIsAncestorOfBase = gitIsAncestor(worktreePath, headSha, baseSha);
254
+ const error = baseIsAncestorOfHead.error || headIsAncestorOfBase.error || void 0;
255
+ if (baseIsAncestorOfHead.isAncestor == null || headIsAncestorOfBase.isAncestor == null) {
256
+ return {
257
+ checked: false,
258
+ base: baseLabel,
259
+ head: headSha,
260
+ baseHead: baseSha,
261
+ baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
262
+ headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
263
+ relation: "unknown",
264
+ ...error ? { error } : {}
265
+ };
266
+ }
267
+ const relation = baseIsAncestorOfHead.isAncestor ? "ahead" : headIsAncestorOfBase.isAncestor ? "merged" : "diverged";
268
+ return {
269
+ checked: true,
270
+ base: baseLabel,
271
+ head: headSha,
272
+ baseHead: baseSha,
273
+ baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
274
+ headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
275
+ relation,
276
+ ...error ? { error } : {}
277
+ };
278
+ }
279
+ function unknownAncestry(base, error, head = null) {
280
+ return {
281
+ checked: false,
282
+ base,
283
+ head,
284
+ baseHead: null,
285
+ baseIsAncestorOfHead: null,
286
+ headIsAncestorOfBase: null,
287
+ relation: "unknown",
288
+ error
289
+ };
290
+ }
291
+
292
+ // src/path-values.ts
293
+ import { homedir } from "node:os";
294
+ import path2 from "node:path";
295
+ function expandHomePath(value) {
296
+ if (value === "~") return homedir();
297
+ if (value.startsWith("~/") || value.startsWith("~\\")) {
298
+ return path2.join(homedir(), value.slice(2));
299
+ }
300
+ return value;
301
+ }
302
+ function resolveUserPath(value) {
303
+ return path2.resolve(expandHomePath(value));
304
+ }
305
+ function redactHomePath(value) {
306
+ const expanded = expandHomePath(value);
307
+ const resolved = path2.resolve(expanded);
308
+ const home = path2.resolve(homedir());
309
+ if (resolved === home) return "~";
310
+ if (resolved.startsWith(`${home}${path2.sep}`)) {
311
+ return `~/${path2.relative(home, resolved).split(path2.sep).join("/")}`;
312
+ }
313
+ return resolved.replace(/^\/home\/[^/]+(?=\/|$)/, "~").replace(/^\/Users\/[^/]+(?=\/|$)/, "~");
314
+ }
315
+ function displayUserPath(value) {
316
+ return redactHomePath(value);
317
+ }
318
+
319
+ // src/default-repo-discovery.ts
320
+ var WELL_KNOWN_REPO_DIRS = ["Kynver", "repos/Kynver", "code/Kynver", "projects/Kynver"];
321
+ function readPackageName(repoRoot) {
322
+ const pkgPath = path3.join(repoRoot, "package.json");
323
+ if (!existsSync2(pkgPath)) return null;
324
+ try {
325
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
326
+ return typeof pkg.name === "string" ? pkg.name.trim() : null;
327
+ } catch {
328
+ return null;
329
+ }
330
+ }
331
+ function isKynverMonorepoRoot(repoRoot) {
332
+ return readPackageName(repoRoot) === "kynver";
333
+ }
334
+ function gitRepoRoot(startDir) {
335
+ const resolvedStart = path3.resolve(startDir);
336
+ if (!existsSync2(resolvedStart)) return null;
337
+ const probe = gitCapture(resolvedStart, ["rev-parse", "--show-toplevel"]);
338
+ if (probe.status !== 0) return null;
339
+ const root = probe.stdout.trim();
340
+ return root.length ? path3.resolve(root) : null;
341
+ }
342
+ function resolveRuntimePackageRoot(moduleUrl = import.meta.url) {
343
+ let dir = path3.dirname(fileURLToPath(moduleUrl));
344
+ for (let depth = 0; depth < 8; depth += 1) {
345
+ const pkgPath = path3.join(dir, "package.json");
346
+ if (existsSync2(pkgPath)) {
347
+ try {
348
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
349
+ if (pkg.name === "@kynver-app/runtime") return dir;
350
+ } catch {
351
+ }
352
+ }
353
+ const parent = path3.dirname(dir);
354
+ if (parent === dir) break;
355
+ dir = parent;
356
+ }
357
+ return null;
358
+ }
359
+ function pushCandidate(seen, out, repo, source) {
360
+ if (!repo) return;
361
+ const resolved = path3.resolve(repo);
362
+ if (seen.has(resolved)) return;
363
+ if (!isKynverMonorepoRoot(resolved)) return;
364
+ seen.add(resolved);
365
+ out.push({ repo: resolved, source });
366
+ }
367
+ function discoverDefaultRepoCandidates(opts) {
368
+ const cwd = opts?.cwd ?? process.cwd();
369
+ const seen = /* @__PURE__ */ new Set();
370
+ const candidates = [];
371
+ pushCandidate(seen, candidates, gitRepoRoot(cwd), "cwd_git");
372
+ const runtimePkgRoot = resolveRuntimePackageRoot(opts?.runtimeModuleUrl ?? import.meta.url);
373
+ if (runtimePkgRoot) {
374
+ pushCandidate(seen, candidates, gitRepoRoot(runtimePkgRoot), "runtime_checkout");
375
+ }
376
+ const home = homedir2();
377
+ for (const rel of WELL_KNOWN_REPO_DIRS) {
378
+ pushCandidate(seen, candidates, resolveUserPath(path3.join(home, rel)), "well_known_path");
379
+ }
380
+ return candidates;
381
+ }
382
+ function discoverDefaultRepo(opts) {
383
+ return discoverDefaultRepoCandidates(opts)[0] ?? null;
384
+ }
385
+
148
386
  // src/config.ts
149
- var CONFIG_DIR = path3.join(homedir2(), ".kynver");
150
- var CONFIG_FILE = path3.join(CONFIG_DIR, "config.json");
151
- var CREDENTIALS_FILE = path3.join(CONFIG_DIR, "credentials");
387
+ var CONFIG_DIR = path4.join(homedir3(), ".kynver");
388
+ var CONFIG_FILE = path4.join(CONFIG_DIR, "config.json");
389
+ var CREDENTIALS_FILE = path4.join(CONFIG_DIR, "credentials");
152
390
  function loadUserConfig() {
153
- if (!existsSync2(CONFIG_FILE)) return {};
391
+ if (!existsSync3(CONFIG_FILE)) return {};
154
392
  try {
155
- return JSON.parse(readFileSync2(CONFIG_FILE, "utf8"));
393
+ return JSON.parse(readFileSync3(CONFIG_FILE, "utf8"));
156
394
  } catch {
157
395
  return {};
158
396
  }
@@ -176,7 +414,8 @@ function inferSetupFields(existing, args) {
176
414
  const creds = loadCredentialsFile();
177
415
  const apiBaseUrl = (typeof args.apiBaseUrl === "string" ? args.apiBaseUrl : void 0) || existing.apiBaseUrl?.trim() || process.env.KYNVER_API_URL?.trim() || process.env.OPENCLAW_CRON_FIRE_BASE_URL?.trim();
178
416
  const agentOsId = (typeof args.agentOsId === "string" ? args.agentOsId : void 0) || existing.agentOsId?.trim() || process.env.KYNVER_AGENT_OS_ID?.trim() || (creds.runnerToken?.trim().startsWith("krc1.") ? creds.runnerTokenAgentOsId?.trim() : void 0);
179
- const defaultRepo = (typeof args.repo === "string" ? args.repo : void 0) || existing.defaultRepo?.trim() || process.env.KYNVER_DEFAULT_REPO?.trim() || process.env.KYNVER_HARNESS_REPO?.trim();
417
+ const explicitRepo = typeof args.repo === "string" ? args.repo : args.discoverRepo === true || args.discoverRepo === "true" ? discoverDefaultRepo()?.repo : void 0;
418
+ const defaultRepo = explicitRepo || existing.defaultRepo?.trim() || process.env.KYNVER_DEFAULT_REPO?.trim() || process.env.KYNVER_HARNESS_REPO?.trim() || discoverDefaultRepo()?.repo;
180
419
  const harnessRoot = (typeof args.harnessRoot === "string" ? args.harnessRoot : void 0) || existing.harnessRoot?.trim() || process.env.KYNVER_HARNESS_ROOT?.trim() || process.env.OPUS_HARNESS_ROOT?.trim();
181
420
  return {
182
421
  ...apiBaseUrl ? { apiBaseUrl: trimTrailingSlash(apiBaseUrl) } : {},
@@ -187,9 +426,9 @@ function inferSetupFields(existing, args) {
187
426
  };
188
427
  }
189
428
  function loadCredentialsFile() {
190
- if (!existsSync2(CREDENTIALS_FILE)) return {};
429
+ if (!existsSync3(CREDENTIALS_FILE)) return {};
191
430
  try {
192
- return JSON.parse(readFileSync2(CREDENTIALS_FILE, "utf8"));
431
+ return JSON.parse(readFileSync3(CREDENTIALS_FILE, "utf8"));
193
432
  } catch {
194
433
  return {};
195
434
  }
@@ -409,7 +648,7 @@ async function runLogin(args) {
409
648
  }
410
649
 
411
650
  // src/dispatch.ts
412
- import path17 from "node:path";
651
+ import path18 from "node:path";
413
652
 
414
653
  // src/callback-headers.ts
415
654
  function buildHarnessCallbackHeaders(secret) {
@@ -471,12 +710,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
471
710
  var DEFAULT_MAX_USED_PERCENT = 80;
472
711
  var DEFAULT_HARD_MAX_USED_PERCENT = 90;
473
712
  function observeRunnerDiskGate(input = {}) {
474
- const path38 = input.diskPath?.trim() || "/";
713
+ const path40 = input.diskPath?.trim() || "/";
475
714
  const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
476
715
  const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
477
716
  const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
478
717
  const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
479
- const stats = statfsSync(path38);
718
+ const stats = statfsSync(path40);
480
719
  const freeBytes = Number(stats.bavail) * Number(stats.bsize);
481
720
  const totalBytes = Number(stats.blocks) * Number(stats.bsize);
482
721
  const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
@@ -496,7 +735,7 @@ function observeRunnerDiskGate(input = {}) {
496
735
  }
497
736
  return {
498
737
  ok,
499
- path: path38,
738
+ path: path40,
500
739
  freeBytes,
501
740
  totalBytes,
502
741
  usedPercent,
@@ -512,7 +751,7 @@ function observeRunnerDiskGate(input = {}) {
512
751
  import os2 from "node:os";
513
752
 
514
753
  // src/bounded-build/meminfo.ts
515
- import { readFileSync as readFileSync3 } from "node:fs";
754
+ import { readFileSync as readFileSync4 } from "node:fs";
516
755
  import os from "node:os";
517
756
  function readMemAvailableBytes(meminfoText) {
518
757
  if (meminfoText !== void 0) {
@@ -522,7 +761,7 @@ function readMemAvailableBytes(meminfoText) {
522
761
  }
523
762
  if (process.platform === "linux") {
524
763
  try {
525
- const meminfo = readFileSync3("/proc/meminfo", "utf8");
764
+ const meminfo = readFileSync4("/proc/meminfo", "utf8");
526
765
  const match = meminfo.match(/^MemAvailable:\s+(\d+)\s*kB/m);
527
766
  if (match) return Number(match[1]) * 1024;
528
767
  } catch {
@@ -532,37 +771,37 @@ function readMemAvailableBytes(meminfoText) {
532
771
  }
533
772
 
534
773
  // src/resource-gate.ts
535
- import path6 from "node:path";
774
+ import path7 from "node:path";
536
775
 
537
776
  // src/run-store.ts
538
- import { existsSync as existsSync4, readdirSync as readdirSync2 } from "node:fs";
539
- import path5 from "node:path";
777
+ import { existsSync as existsSync5, readdirSync as readdirSync2 } from "node:fs";
778
+ import path6 from "node:path";
540
779
 
541
780
  // src/paths.ts
542
- import { existsSync as existsSync3 } from "node:fs";
543
- import { homedir as homedir3 } from "node:os";
544
- import path4 from "node:path";
545
- var LEGACY_ROOT = path4.join(homedir3(), ".openclaw", "harness");
781
+ import { existsSync as existsSync4 } from "node:fs";
782
+ import { homedir as homedir4 } from "node:os";
783
+ import path5 from "node:path";
784
+ var LEGACY_ROOT = path5.join(homedir4(), ".openclaw", "harness");
546
785
  function resolveHarnessRoot() {
547
786
  const env = process.env.KYNVER_HARNESS_ROOT || process.env.OPUS_HARNESS_ROOT;
548
787
  if (env) return resolveUserPath(env);
549
788
  const configured = loadUserConfig().harnessRoot?.trim();
550
789
  if (configured) return resolveUserPath(configured);
551
- const kynverRoot = path4.join(homedir3(), ".kynver", "harness");
552
- if (existsSync3(kynverRoot)) return kynverRoot;
553
- if (existsSync3(LEGACY_ROOT)) return LEGACY_ROOT;
790
+ const kynverRoot = path5.join(homedir4(), ".kynver", "harness");
791
+ if (existsSync4(kynverRoot)) return kynverRoot;
792
+ if (existsSync4(LEGACY_ROOT)) return LEGACY_ROOT;
554
793
  return kynverRoot;
555
794
  }
556
795
  function getHarnessPaths() {
557
796
  const harnessRoot = resolveHarnessRoot();
558
797
  return {
559
798
  harnessRoot,
560
- runsDir: path4.join(harnessRoot, "runs"),
561
- worktreesDir: path4.join(harnessRoot, "worktrees")
799
+ runsDir: path5.join(harnessRoot, "runs"),
800
+ worktreesDir: path5.join(harnessRoot, "worktrees")
562
801
  };
563
802
  }
564
803
  function runDir(runsDir, id) {
565
- return path4.join(runsDir, safeSlug(id));
804
+ return path5.join(runsDir, safeSlug(id));
566
805
  }
567
806
 
568
807
  // src/run-store.ts
@@ -571,16 +810,16 @@ function getPaths() {
571
810
  }
572
811
  function loadRun(id) {
573
812
  const { runsDir } = getPaths();
574
- return readJson(path5.join(runDir(runsDir, safeSlug(id)), "run.json"));
813
+ return readJson(path6.join(runDir(runsDir, safeSlug(id)), "run.json"));
575
814
  }
576
815
  function listRunRecords() {
577
816
  const { runsDir } = getPaths();
578
- if (!existsSync4(runsDir)) return [];
817
+ if (!existsSync5(runsDir)) return [];
579
818
  const runs = [];
580
819
  for (const entry of readdirSync2(runsDir, { withFileTypes: true })) {
581
820
  if (!entry.isDirectory()) continue;
582
821
  const run = readJson(
583
- path5.join(runsDir, entry.name, "run.json"),
822
+ path6.join(runsDir, entry.name, "run.json"),
584
823
  void 0
585
824
  );
586
825
  if (run?.id) runs.push(run);
@@ -590,16 +829,16 @@ function listRunRecords() {
590
829
  function loadWorker(runId, name) {
591
830
  const { runsDir } = getPaths();
592
831
  return readJson(
593
- path5.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
832
+ path6.join(runDir(runsDir, safeSlug(runId)), "workers", safeSlug(name), "worker.json")
594
833
  );
595
834
  }
596
835
  function saveRun(run) {
597
836
  const { runsDir } = getPaths();
598
- writeJson(path5.join(runDir(runsDir, run.id), "run.json"), run);
837
+ writeJson(path6.join(runDir(runsDir, run.id), "run.json"), run);
599
838
  }
600
839
  function saveWorker(runId, worker) {
601
840
  const { runsDir } = getPaths();
602
- writeJson(path5.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
841
+ writeJson(path6.join(runDir(runsDir, runId), "workers", worker.name, "worker.json"), worker);
603
842
  }
604
843
  function runDirectory(id) {
605
844
  const { runsDir } = getPaths();
@@ -607,7 +846,7 @@ function runDirectory(id) {
607
846
  }
608
847
 
609
848
  // src/heartbeat.ts
610
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
849
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
611
850
  var HEARTBEAT_FUTURE_SKEW_MS = 6e4;
612
851
  function isTerminalHeartbeatPhase(phase) {
613
852
  return phase === "complete";
@@ -626,10 +865,10 @@ function parseHeartbeat(file) {
626
865
  heartbeatBlocker: null,
627
866
  timestampAnomalies: []
628
867
  };
629
- if (!existsSync5(file)) return result;
868
+ if (!existsSync6(file)) return result;
630
869
  const maxFutureMs = Date.now() + HEARTBEAT_FUTURE_SKEW_MS;
631
870
  const clampedTo = new Date(maxFutureMs).toISOString();
632
- const lines = readFileSync4(file, "utf8").split("\n").filter(Boolean);
871
+ const lines = readFileSync5(file, "utf8").split("\n").filter(Boolean);
633
872
  for (const line of lines) {
634
873
  const entry = safeJson(line);
635
874
  if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
@@ -656,7 +895,7 @@ function parseHeartbeat(file) {
656
895
  }
657
896
 
658
897
  // src/stream.ts
659
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
898
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
660
899
 
661
900
  // src/shell-command-outcome.ts
662
901
  var NPM_AUDIT_RE = /\bnpm\s+audit\b/i;
@@ -860,8 +1099,8 @@ function parseHarnessStream(file) {
860
1099
  error: null,
861
1100
  lastShellOutcome: null
862
1101
  };
863
- if (!existsSync6(file)) return result;
864
- const lines = readFileSync5(file, "utf8").split("\n").filter(Boolean);
1102
+ if (!existsSync7(file)) return result;
1103
+ const lines = readFileSync6(file, "utf8").split("\n").filter(Boolean);
865
1104
  for (const line of lines) {
866
1105
  const event = safeJson(line);
867
1106
  if (!event) continue;
@@ -1016,224 +1255,59 @@ function classifyExitFailure(errorText) {
1016
1255
 
1017
1256
  // src/exited-salvage.ts
1018
1257
  function trimOrNull(value) {
1019
- if (typeof value !== "string") return null;
1020
- const trimmed = value.trim();
1021
- return trimmed.length ? trimmed : null;
1022
- }
1023
- function hasFinalResult(value) {
1024
- if (value === void 0 || value === null) return false;
1025
- if (typeof value === "string") return value.trim().length > 0;
1026
- if (typeof value === "boolean") return value;
1027
- if (Array.isArray(value)) return value.length > 0;
1028
- if (typeof value === "object") return Object.keys(value).length > 0;
1029
- return true;
1030
- }
1031
- function committedHeadFromAncestry(ancestry) {
1032
- if (!ancestry?.checked) return null;
1033
- if (ancestry.headIsAncestorOfBase !== false) return null;
1034
- return trimOrNull(ancestry.head);
1035
- }
1036
- function buildAttentionReason(kind, uncommittedCount, headCommit) {
1037
- const parts = ["exited_with_changes_salvage"];
1038
- if (kind === "uncommitted" || kind === "both") {
1039
- parts.push(
1040
- `${uncommittedCount} uncommitted change${uncommittedCount === 1 ? "" : "s"} with no final result`
1041
- );
1042
- }
1043
- if ((kind === "committed_ahead" || kind === "both") && headCommit) {
1044
- const sha = headCommit.length > 12 ? headCommit.slice(0, 12) : headCommit;
1045
- parts.push(`commit ${sha} ahead of base with no final result`);
1046
- }
1047
- parts.push("review worktree \u2014 commit, open a PR, or run a salvage worker before discarding");
1048
- return parts.join(": ");
1049
- }
1050
- function assessExitedWorkerSalvage(input) {
1051
- if (input.alive || hasFinalResult(input.finalResult)) return null;
1052
- const uncommittedCount = (input.changedFiles ?? []).filter((line) => line.trim()).length;
1053
- const headCommit = trimOrNull(input.headCommit) ?? committedHeadFromAncestry(input.gitAncestry);
1054
- const hasUncommitted = uncommittedCount > 0;
1055
- const hasCommittedAhead = Boolean(headCommit);
1056
- if (!hasUncommitted && !hasCommittedAhead) {
1057
- return {
1058
- kind: "none",
1059
- salvageable: false,
1060
- uncommittedCount: 0,
1061
- headCommit: null,
1062
- attentionReason: "process exited without a final result"
1063
- };
1064
- }
1065
- const kind = hasUncommitted && hasCommittedAhead ? "both" : hasUncommitted ? "uncommitted" : "committed_ahead";
1066
- return {
1067
- kind,
1068
- salvageable: true,
1069
- uncommittedCount,
1070
- headCommit,
1071
- attentionReason: buildAttentionReason(kind, uncommittedCount, headCommit)
1072
- };
1073
- }
1074
-
1075
- // src/git.ts
1076
- import { spawnSync } from "node:child_process";
1077
-
1078
- // src/worker-env.ts
1079
- var FORBIDDEN_WORKER_ENV_KEYS = [
1080
- "ANTHROPIC_API_KEY",
1081
- "ANALYST_API_KEY",
1082
- "RECRUITER_API_KEY",
1083
- "AUTH_SECRET",
1084
- "NEXTAUTH_SECRET",
1085
- "DATABASE_URL",
1086
- "PRODUCTION_DATABASE_URL",
1087
- "REDIS_URL",
1088
- "GOOGLE_CLIENT_SECRET",
1089
- "GITHUB_CLIENT_SECRET",
1090
- "KYNVER_API_KEY",
1091
- "KYNVER_SERVICE_SECRET",
1092
- "KYNVER_RUNTIME_SECRET",
1093
- "OPENCLAW_CRON_SECRET",
1094
- "QSTASH_TOKEN",
1095
- "QSTASH_CURRENT_SIGNING_KEY",
1096
- "QSTASH_NEXT_SIGNING_KEY",
1097
- "TOOL_SECRETS_KEK",
1098
- "TOOL_EXECUTOR_DISPATCH_SECRET",
1099
- "CLOUDFLARE_API_TOKEN",
1100
- "STRIPE_SECRET_KEY",
1101
- "STRIPE_WEBHOOK_SECRET",
1102
- "STRIPE_IDENTITY_WEBHOOK_SECRET",
1103
- "VOYAGE_API_KEY",
1104
- "PERPLEXITY_API_KEY",
1105
- "FRED_API_KEY",
1106
- "FMP_API_KEY",
1107
- "CURSOR_API_KEY"
1108
- ];
1109
- var FORBIDDEN_KEY_SET = new Set(FORBIDDEN_WORKER_ENV_KEYS);
1110
- var FORBIDDEN_SUFFIXES = ["_SECRET", "_API_KEY"];
1111
- function isForbiddenWorkerEnvKey(key) {
1112
- if (FORBIDDEN_KEY_SET.has(key)) return true;
1113
- return FORBIDDEN_SUFFIXES.some((suffix) => key.endsWith(suffix));
1114
- }
1115
- function scrubWorkerEnv(env) {
1116
- const next = { ...env };
1117
- for (const key of Object.keys(next)) {
1118
- if (isForbiddenWorkerEnvKey(key)) delete next[key];
1119
- }
1120
- return next;
1121
- }
1122
-
1123
- // src/git.ts
1124
- function git(cwd, args, options = {}) {
1125
- const res = spawnSync("git", args, { cwd, encoding: "utf8" });
1126
- if (res.status !== 0 && !options.allowFailure) {
1127
- const message = `git ${args.join(" ")} failed: ${res.stderr || res.stdout}`;
1128
- if (options.throwError) throw new Error(message);
1129
- fail(message);
1130
- }
1131
- return res.stdout || "";
1132
- }
1133
- function ensureGitRepo(repo) {
1134
- git(repo, ["rev-parse", "--show-toplevel"]);
1135
- }
1136
- function gitStatusShort(worktreePath) {
1137
- return git(worktreePath, ["status", "--short"], { allowFailure: true }).split("\n").map((line) => line.trim()).filter(Boolean);
1138
- }
1139
- function gitCapture(cwd, args) {
1140
- try {
1141
- const res = spawnSync("git", args, { cwd, encoding: "utf8" });
1142
- return {
1143
- status: res.status,
1144
- stdout: res.stdout || "",
1145
- stderr: res.stderr || "",
1146
- error: res.error ? res.error.message : null
1147
- };
1148
- } catch (error) {
1149
- return {
1150
- status: null,
1151
- stdout: "",
1152
- stderr: "",
1153
- error: error.message
1154
- };
1155
- }
1156
- }
1157
- function gitIsAncestor(cwd, ancestor, descendant) {
1158
- const res = gitCapture(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]);
1159
- if (res.status === 0) return { isAncestor: true, error: null };
1160
- if (res.status === 1) return { isAncestor: false, error: null };
1161
- return { isAncestor: null, error: res.error || res.stderr || res.stdout || `git exited ${res.status}` };
1162
- }
1163
- function computeGitAncestry(worktreePath, baseOrOptions = "origin/main") {
1164
- const options = typeof baseOrOptions === "string" ? { base: baseOrOptions } : baseOrOptions;
1165
- const baseLabel = options.baseCommit?.trim() || options.base?.trim() || "origin/main";
1166
- const pinnedBaseCommit = options.baseCommit?.trim() || null;
1167
- if (!worktreePath) {
1168
- return unknownAncestry(baseLabel, "missing worktree path");
1169
- }
1170
- const head = gitCapture(worktreePath, ["rev-parse", "HEAD"]);
1171
- if (head.status !== 0) {
1172
- return unknownAncestry(baseLabel, head.error || head.stderr || head.stdout || "failed to resolve HEAD");
1173
- }
1174
- let baseSha;
1175
- if (pinnedBaseCommit) {
1176
- baseSha = pinnedBaseCommit;
1177
- } else {
1178
- const baseHead = gitCapture(worktreePath, ["rev-parse", baseLabel]);
1179
- if (baseHead.status !== 0) {
1180
- return unknownAncestry(
1181
- baseLabel,
1182
- baseHead.error || baseHead.stderr || baseHead.stdout || `failed to resolve ${baseLabel}`,
1183
- head.stdout.trim()
1184
- );
1185
- }
1186
- baseSha = baseHead.stdout.trim();
1258
+ if (typeof value !== "string") return null;
1259
+ const trimmed = value.trim();
1260
+ return trimmed.length ? trimmed : null;
1261
+ }
1262
+ function hasFinalResult(value) {
1263
+ if (value === void 0 || value === null) return false;
1264
+ if (typeof value === "string") return value.trim().length > 0;
1265
+ if (typeof value === "boolean") return value;
1266
+ if (Array.isArray(value)) return value.length > 0;
1267
+ if (typeof value === "object") return Object.keys(value).length > 0;
1268
+ return true;
1269
+ }
1270
+ function committedHeadFromAncestry(ancestry) {
1271
+ if (!ancestry?.checked) return null;
1272
+ if (ancestry.headIsAncestorOfBase !== false) return null;
1273
+ return trimOrNull(ancestry.head);
1274
+ }
1275
+ function buildAttentionReason(kind, uncommittedCount, headCommit) {
1276
+ const parts = ["exited_with_changes_salvage"];
1277
+ if (kind === "uncommitted" || kind === "both") {
1278
+ parts.push(
1279
+ `${uncommittedCount} uncommitted change${uncommittedCount === 1 ? "" : "s"} with no final result`
1280
+ );
1187
1281
  }
1188
- const headSha = head.stdout.trim();
1189
- if (headSha === baseSha) {
1190
- return {
1191
- checked: true,
1192
- base: baseLabel,
1193
- head: headSha,
1194
- baseHead: baseSha,
1195
- baseIsAncestorOfHead: true,
1196
- headIsAncestorOfBase: true,
1197
- relation: "synced"
1198
- };
1282
+ if ((kind === "committed_ahead" || kind === "both") && headCommit) {
1283
+ const sha = headCommit.length > 12 ? headCommit.slice(0, 12) : headCommit;
1284
+ parts.push(`commit ${sha} ahead of base with no final result`);
1199
1285
  }
1200
- const baseIsAncestorOfHead = gitIsAncestor(worktreePath, baseSha, headSha);
1201
- const headIsAncestorOfBase = gitIsAncestor(worktreePath, headSha, baseSha);
1202
- const error = baseIsAncestorOfHead.error || headIsAncestorOfBase.error || void 0;
1203
- if (baseIsAncestorOfHead.isAncestor == null || headIsAncestorOfBase.isAncestor == null) {
1286
+ parts.push("review worktree \u2014 commit, open a PR, or run a salvage worker before discarding");
1287
+ return parts.join(": ");
1288
+ }
1289
+ function assessExitedWorkerSalvage(input) {
1290
+ if (input.alive || hasFinalResult(input.finalResult)) return null;
1291
+ const uncommittedCount = (input.changedFiles ?? []).filter((line) => line.trim()).length;
1292
+ const headCommit = trimOrNull(input.headCommit) ?? committedHeadFromAncestry(input.gitAncestry);
1293
+ const hasUncommitted = uncommittedCount > 0;
1294
+ const hasCommittedAhead = Boolean(headCommit);
1295
+ if (!hasUncommitted && !hasCommittedAhead) {
1204
1296
  return {
1205
- checked: false,
1206
- base: baseLabel,
1207
- head: headSha,
1208
- baseHead: baseSha,
1209
- baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
1210
- headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
1211
- relation: "unknown",
1212
- ...error ? { error } : {}
1297
+ kind: "none",
1298
+ salvageable: false,
1299
+ uncommittedCount: 0,
1300
+ headCommit: null,
1301
+ attentionReason: "process exited without a final result"
1213
1302
  };
1214
1303
  }
1215
- const relation = baseIsAncestorOfHead.isAncestor ? "ahead" : headIsAncestorOfBase.isAncestor ? "merged" : "diverged";
1216
- return {
1217
- checked: true,
1218
- base: baseLabel,
1219
- head: headSha,
1220
- baseHead: baseSha,
1221
- baseIsAncestorOfHead: baseIsAncestorOfHead.isAncestor,
1222
- headIsAncestorOfBase: headIsAncestorOfBase.isAncestor,
1223
- relation,
1224
- ...error ? { error } : {}
1225
- };
1226
- }
1227
- function unknownAncestry(base, error, head = null) {
1304
+ const kind = hasUncommitted && hasCommittedAhead ? "both" : hasUncommitted ? "uncommitted" : "committed_ahead";
1228
1305
  return {
1229
- checked: false,
1230
- base,
1231
- head,
1232
- baseHead: null,
1233
- baseIsAncestorOfHead: null,
1234
- headIsAncestorOfBase: null,
1235
- relation: "unknown",
1236
- error
1306
+ kind,
1307
+ salvageable: true,
1308
+ uncommittedCount,
1309
+ headCommit,
1310
+ attentionReason: buildAttentionReason(kind, uncommittedCount, headCommit)
1237
1311
  };
1238
1312
  }
1239
1313
 
@@ -1595,7 +1669,7 @@ function countActiveWorkersForRun(run) {
1595
1669
  let active = 0;
1596
1670
  for (const name of Object.keys(run.workers || {})) {
1597
1671
  const worker = readJson(
1598
- path6.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1672
+ path7.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1599
1673
  void 0
1600
1674
  );
1601
1675
  if (!worker || !isActiveHarnessWorker(worker)) continue;
@@ -1980,10 +2054,10 @@ function readHarnessRetryLimits() {
1980
2054
  }
1981
2055
 
1982
2056
  // src/lease-renewal.ts
1983
- import path7 from "node:path";
2057
+ import path8 from "node:path";
1984
2058
  function workerRecord(runId, name) {
1985
2059
  return readJson(
1986
- path7.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
2060
+ path8.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
1987
2061
  void 0
1988
2062
  );
1989
2063
  }
@@ -2049,8 +2123,8 @@ function hasLiveWorkerForTask(runId, taskId) {
2049
2123
  }
2050
2124
 
2051
2125
  // src/supervisor.ts
2052
- import { existsSync as existsSync10, mkdirSync as mkdirSync3 } from "node:fs";
2053
- import path13 from "node:path";
2126
+ import { existsSync as existsSync11, mkdirSync as mkdirSync3 } from "node:fs";
2127
+ import path14 from "node:path";
2054
2128
 
2055
2129
  // src/prompt.ts
2056
2130
  function buildPrompt(input) {
@@ -2119,13 +2193,13 @@ function buildPrompt(input) {
2119
2193
  }
2120
2194
 
2121
2195
  // src/providers/cursor.ts
2122
- import { closeSync as closeSync2, existsSync as existsSync8, openSync as openSync2 } from "node:fs";
2196
+ import { closeSync as closeSync2, existsSync as existsSync9, openSync as openSync2 } from "node:fs";
2123
2197
  import { spawn as spawn2 } from "node:child_process";
2124
- import path9 from "node:path";
2198
+ import path10 from "node:path";
2125
2199
 
2126
2200
  // src/providers/cursor-windows.ts
2127
- import { existsSync as existsSync7, readdirSync as readdirSync3 } from "node:fs";
2128
- import path8 from "node:path";
2201
+ import { existsSync as existsSync8, readdirSync as readdirSync3 } from "node:fs";
2202
+ import path9 from "node:path";
2129
2203
  var CURSOR_VERSION_DIR = /^\d{4}\.\d{1,2}\.\d{1,2}-[a-f0-9]+$/i;
2130
2204
  function parseCursorVersionSortKey(versionName) {
2131
2205
  const datePart = versionName.split("-")[0];
@@ -2136,8 +2210,8 @@ function parseCursorVersionSortKey(versionName) {
2136
2210
  return Number(`${year}${month.padStart(2, "0")}${day.padStart(2, "0")}`);
2137
2211
  }
2138
2212
  function pickLatestCursorVersionDir(agentRoot) {
2139
- const versionsRoot = path8.join(agentRoot, "versions");
2140
- if (!existsSync7(versionsRoot)) return null;
2213
+ const versionsRoot = path9.join(agentRoot, "versions");
2214
+ if (!existsSync8(versionsRoot)) return null;
2141
2215
  let bestDir = null;
2142
2216
  let bestKey = -1;
2143
2217
  for (const entry of readdirSync3(versionsRoot, { withFileTypes: true })) {
@@ -2145,22 +2219,22 @@ function pickLatestCursorVersionDir(agentRoot) {
2145
2219
  const key = parseCursorVersionSortKey(entry.name);
2146
2220
  if (key == null || key <= bestKey) continue;
2147
2221
  bestKey = key;
2148
- bestDir = path8.join(versionsRoot, entry.name);
2222
+ bestDir = path9.join(versionsRoot, entry.name);
2149
2223
  }
2150
2224
  return bestDir;
2151
2225
  }
2152
2226
  function resolveWindowsCursorBundled(agentRoot) {
2153
- const root = agentRoot?.trim() || path8.join(process.env.LOCALAPPDATA || "", "cursor-agent");
2154
- const directNode = path8.join(root, "node.exe");
2155
- const directIndex = path8.join(root, "index.js");
2156
- if (existsSync7(directNode) && existsSync7(directIndex)) {
2227
+ const root = agentRoot?.trim() || path9.join(process.env.LOCALAPPDATA || "", "cursor-agent");
2228
+ const directNode = path9.join(root, "node.exe");
2229
+ const directIndex = path9.join(root, "index.js");
2230
+ if (existsSync8(directNode) && existsSync8(directIndex)) {
2157
2231
  return { nodeExe: directNode, indexJs: directIndex, versionDir: root };
2158
2232
  }
2159
2233
  const versionDir = pickLatestCursorVersionDir(root);
2160
2234
  if (!versionDir) return null;
2161
- const nodeExe = path8.join(versionDir, "node.exe");
2162
- const indexJs = path8.join(versionDir, "index.js");
2163
- if (!existsSync7(nodeExe) || !existsSync7(indexJs)) return null;
2235
+ const nodeExe = path9.join(versionDir, "node.exe");
2236
+ const indexJs = path9.join(versionDir, "index.js");
2237
+ if (!existsSync8(nodeExe) || !existsSync8(indexJs)) return null;
2164
2238
  return { nodeExe, indexJs, versionDir };
2165
2239
  }
2166
2240
 
@@ -2178,13 +2252,13 @@ function bundledSpawnTarget(nodeExe, indexJs, versionDir) {
2178
2252
  function resolveCursorSpawn(agentBin) {
2179
2253
  if (process.platform === "win32") {
2180
2254
  const isCursorWrapper = /\.(cmd|bat)$/i.test(agentBin);
2181
- const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync8(path9.join(path9.dirname(agentBin), "index.js"));
2255
+ const isBundledNode = /node\.exe$/i.test(agentBin) && existsSync9(path10.join(path10.dirname(agentBin), "index.js"));
2182
2256
  const isDefaultShim = agentBin === "agent";
2183
2257
  if (isCursorWrapper || isBundledNode || isDefaultShim) {
2184
- const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path9.dirname(agentBin)) : isBundledNode ? {
2258
+ const bundled = isCursorWrapper ? resolveWindowsCursorBundled(path10.dirname(agentBin)) : isBundledNode ? {
2185
2259
  nodeExe: agentBin,
2186
- indexJs: path9.join(path9.dirname(agentBin), "index.js"),
2187
- versionDir: path9.dirname(agentBin)
2260
+ indexJs: path10.join(path10.dirname(agentBin), "index.js"),
2261
+ versionDir: path10.dirname(agentBin)
2188
2262
  } : resolveWindowsCursorBundled();
2189
2263
  if (bundled) {
2190
2264
  return bundledSpawnTarget(bundled.nodeExe, bundled.indexJs, bundled.versionDir);
@@ -2204,8 +2278,8 @@ function resolveAgentBin() {
2204
2278
  process.env.KYNVER_CURSOR_AGENT_ROOT?.trim() || void 0
2205
2279
  );
2206
2280
  if (bundled) return bundled.nodeExe;
2207
- const localAgent = path9.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
2208
- if (existsSync8(localAgent)) return localAgent;
2281
+ const localAgent = path10.join(process.env.LOCALAPPDATA || "", "cursor-agent", "agent.cmd");
2282
+ if (existsSync9(localAgent)) return localAgent;
2209
2283
  }
2210
2284
  return "agent";
2211
2285
  }
@@ -2214,7 +2288,7 @@ function cursorWorkerEnv(agentBin, spawnTarget) {
2214
2288
  ...process.env,
2215
2289
  CI: "1",
2216
2290
  NO_COLOR: "1",
2217
- ...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path9.basename(agentBin) || "agent.cmd" } : {}
2291
+ ...spawnTarget.bundledVersionDir ? { CURSOR_INVOKED_AS: path10.basename(agentBin) || "agent.cmd" } : {}
2218
2292
  });
2219
2293
  }
2220
2294
  var cursorProvider = {
@@ -2288,9 +2362,9 @@ function resolveWorkerProvider(name) {
2288
2362
 
2289
2363
  // src/auto-complete.ts
2290
2364
  import { spawn as spawn3 } from "node:child_process";
2291
- import { existsSync as existsSync9, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
2292
- import path12 from "node:path";
2293
- import { fileURLToPath } from "node:url";
2365
+ import { existsSync as existsSync10, openSync as openSync3, closeSync as closeSync3 } from "node:fs";
2366
+ import path13 from "node:path";
2367
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
2294
2368
 
2295
2369
  // src/completion-ack.ts
2296
2370
  function hasCompletionAck(worker) {
@@ -2306,7 +2380,7 @@ function persistCompletionAck(worker, runId, fields) {
2306
2380
  }
2307
2381
 
2308
2382
  // src/worker-ops.ts
2309
- import path11 from "node:path";
2383
+ import path12 from "node:path";
2310
2384
 
2311
2385
  // src/completion-response.ts
2312
2386
  function asRecord(value) {
@@ -2788,7 +2862,7 @@ function ensurePrReadyHandoff(input, exec = defaultPrHandoffExec) {
2788
2862
  }
2789
2863
 
2790
2864
  // src/worker-lifecycle.ts
2791
- import path10 from "node:path";
2865
+ import path11 from "node:path";
2792
2866
  var TASK_LEFT_RUNNING = /* @__PURE__ */ new Set([
2793
2867
  "awaiting_review",
2794
2868
  "blocked",
@@ -2844,7 +2918,7 @@ function syncCompletionAcknowledgedFromOperatorTick(runId, operatorTick) {
2844
2918
  const synced = [];
2845
2919
  for (const name of Object.keys(run.workers || {})) {
2846
2920
  const worker = readJson(
2847
- path10.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2921
+ path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
2848
2922
  void 0
2849
2923
  );
2850
2924
  if (!worker?.taskId || isCompletionAcknowledged(worker)) continue;
@@ -3102,7 +3176,7 @@ function workerStatus(args) {
3102
3176
  const worker = loadWorker(String(args.run), String(args.name));
3103
3177
  const run = loadRun(worker.runId);
3104
3178
  const status = computeWorkerStatus(worker, workerStatusOptions(run));
3105
- writeJson(path11.join(worker.workerDir, "last-status.json"), status);
3179
+ writeJson(path12.join(worker.workerDir, "last-status.json"), status);
3106
3180
  console.log(JSON.stringify(status, null, 2));
3107
3181
  }
3108
3182
  function buildRunBoard(runId) {
@@ -3110,7 +3184,7 @@ function buildRunBoard(runId) {
3110
3184
  const names = Object.keys(run.workers || {});
3111
3185
  const workers = names.map((name) => {
3112
3186
  const worker = readJson(
3113
- path11.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
3187
+ path12.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
3114
3188
  void 0
3115
3189
  );
3116
3190
  if (!worker) {
@@ -3221,7 +3295,7 @@ function buildRunBoard(runId) {
3221
3295
  needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
3222
3296
  workers
3223
3297
  };
3224
- writeJson(path11.join(runDirectory(run.id), "last-board.json"), board);
3298
+ writeJson(path12.join(runDirectory(run.id), "last-board.json"), board);
3225
3299
  return board;
3226
3300
  }
3227
3301
  async function publishHarnessBoardSnapshot(args, source) {
@@ -3410,12 +3484,12 @@ async function autoCompleteWorkerCli(raw) {
3410
3484
  }
3411
3485
  }
3412
3486
  function resolveDefaultCliPath() {
3413
- return path12.join(fileURLToPath(new URL(".", import.meta.url)), "cli.js");
3487
+ return path13.join(fileURLToPath2(new URL(".", import.meta.url)), "cli.js");
3414
3488
  }
3415
3489
  function spawnCompletionSidecar(opts) {
3416
3490
  const cliPath = opts.cliPath ?? resolveDefaultCliPath();
3417
- if (!existsSync9(cliPath)) return void 0;
3418
- const logPath = path12.join(opts.workerDir, "auto-complete.log");
3491
+ if (!existsSync10(cliPath)) return void 0;
3492
+ const logPath = path13.join(opts.workerDir, "auto-complete.log");
3419
3493
  let logFd;
3420
3494
  try {
3421
3495
  logFd = openSync3(logPath, "a");
@@ -3497,16 +3571,16 @@ function spawnWorkerProcess(run, opts) {
3497
3571
  launchModel = preflight.model;
3498
3572
  }
3499
3573
  const { worktreesDir } = getPaths();
3500
- const workerDir = path13.join(runDirectory(run.id), "workers", name);
3574
+ const workerDir = path14.join(runDirectory(run.id), "workers", name);
3501
3575
  mkdirSync3(workerDir, { recursive: true });
3502
- const worktreePath = path13.join(worktreesDir, run.id, name);
3576
+ const worktreePath = path14.join(worktreesDir, run.id, name);
3503
3577
  const branch = opts.branch || `agent/${run.id}/${name}`;
3504
- if (existsSync10(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
3578
+ if (existsSync11(worktreePath)) throw new Error(`worktree path already exists: ${worktreePath}`);
3505
3579
  git(run.repo, ["fetch", "origin", "--prune"], { allowFailure: true });
3506
3580
  git(run.repo, ["worktree", "add", "-b", branch, worktreePath, run.baseCommit], { throwError: true });
3507
- const stdoutPath = path13.join(workerDir, "stdout.jsonl");
3508
- const stderrPath = path13.join(workerDir, "stderr.log");
3509
- const heartbeatPath = path13.join(workerDir, "heartbeat.jsonl");
3581
+ const stdoutPath = path14.join(workerDir, "stdout.jsonl");
3582
+ const stderrPath = path14.join(workerDir, "stderr.log");
3583
+ const heartbeatPath = path14.join(workerDir, "heartbeat.jsonl");
3510
3584
  const prompt = buildPrompt({
3511
3585
  task: opts.task,
3512
3586
  ownedPaths: opts.ownedPaths || [],
@@ -3571,7 +3645,7 @@ function spawnWorkerProcess(run, opts) {
3571
3645
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
3572
3646
  };
3573
3647
  saveWorker(run.id, worker);
3574
- run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path13.join(workerDir, "worker.json") } };
3648
+ run.workers = { ...run.workers || {}, [name]: { workerDir, statusPath: path14.join(workerDir, "worker.json") } };
3575
3649
  run.status = "running";
3576
3650
  saveRun(run);
3577
3651
  if (worker.agentOsId && worker.taskId) {
@@ -3863,18 +3937,18 @@ function buildPlanPersistIdempotencyKey(input) {
3863
3937
 
3864
3938
  // src/plan-persist/paths.ts
3865
3939
  import { mkdirSync as mkdirSync4 } from "node:fs";
3866
- import { homedir as homedir4 } from "node:os";
3867
- import path14 from "node:path";
3940
+ import { homedir as homedir5 } from "node:os";
3941
+ import path15 from "node:path";
3868
3942
  function resolveKynverStateRoot() {
3869
3943
  const env = process.env.KYNVER_STATE_ROOT;
3870
- if (env) return path14.resolve(env);
3871
- return path14.join(homedir4(), ".kynver", "state");
3944
+ if (env) return path15.resolve(env);
3945
+ return path15.join(homedir5(), ".kynver", "state");
3872
3946
  }
3873
3947
  function planOutboxDir() {
3874
- return path14.join(resolveKynverStateRoot(), "plan-outbox");
3948
+ return path15.join(resolveKynverStateRoot(), "plan-outbox");
3875
3949
  }
3876
3950
  function planOutboxArchiveDir() {
3877
- return path14.join(resolveKynverStateRoot(), "plan-outbox-archive");
3951
+ return path15.join(resolveKynverStateRoot(), "plan-outbox-archive");
3878
3952
  }
3879
3953
  function ensurePlanOutboxDirs() {
3880
3954
  const outboxDir = planOutboxDir();
@@ -3885,20 +3959,20 @@ function ensurePlanOutboxDirs() {
3885
3959
  }
3886
3960
  function isTmpOnlyPath(filePath) {
3887
3961
  if (filePath.startsWith("/tmp/") || filePath.startsWith("/var/folders/")) return true;
3888
- const resolved = path14.resolve(filePath);
3889
- return resolved.startsWith("/tmp/") || resolved.startsWith(path14.join("/var", "folders"));
3962
+ const resolved = path15.resolve(filePath);
3963
+ return resolved.startsWith("/tmp/") || resolved.startsWith(path15.join("/var", "folders"));
3890
3964
  }
3891
3965
 
3892
3966
  // src/plan-persist/outbox-store.ts
3893
3967
  import {
3894
- existsSync as existsSync12,
3895
- readFileSync as readFileSync6,
3968
+ existsSync as existsSync13,
3969
+ readFileSync as readFileSync7,
3896
3970
  renameSync,
3897
3971
  readdirSync as readdirSync4,
3898
3972
  writeFileSync as writeFileSync3,
3899
3973
  unlinkSync
3900
3974
  } from "node:fs";
3901
- import path15 from "node:path";
3975
+ import path16 from "node:path";
3902
3976
  import { randomUUID } from "node:crypto";
3903
3977
  var DEFAULT_MAX_RETRIES = 12;
3904
3978
  function listOutboxItems() {
@@ -3906,7 +3980,7 @@ function listOutboxItems() {
3906
3980
  const files = readdirSync4(outboxDir).filter((f) => f.endsWith(".json"));
3907
3981
  const items = [];
3908
3982
  for (const file of files) {
3909
- const item = readOutboxItem(path15.join(outboxDir, file));
3983
+ const item = readOutboxItem(path16.join(outboxDir, file));
3910
3984
  if (item && item.queueStatus === "queued") items.push(item);
3911
3985
  }
3912
3986
  return items.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
@@ -3918,25 +3992,25 @@ function findOutboxByIdempotencyKey(key) {
3918
3992
  return null;
3919
3993
  }
3920
3994
  function readOutboxItem(jsonPath) {
3921
- if (!existsSync12(jsonPath)) return null;
3995
+ if (!existsSync13(jsonPath)) return null;
3922
3996
  try {
3923
- return JSON.parse(readFileSync6(jsonPath, "utf8"));
3997
+ return JSON.parse(readFileSync7(jsonPath, "utf8"));
3924
3998
  } catch {
3925
3999
  return null;
3926
4000
  }
3927
4001
  }
3928
4002
  function readOutboxBody(item) {
3929
4003
  const { outboxDir } = ensurePlanOutboxDirs();
3930
- const bodyFile = path15.join(outboxDir, item.bodyPath);
3931
- return readFileSync6(bodyFile, "utf8");
4004
+ const bodyFile = path16.join(outboxDir, item.bodyPath);
4005
+ return readFileSync7(bodyFile, "utf8");
3932
4006
  }
3933
4007
  function writeOutboxItem(input, opts) {
3934
4008
  const { outboxDir } = ensurePlanOutboxDirs();
3935
4009
  const now = (/* @__PURE__ */ new Date()).toISOString();
3936
4010
  const id = opts.existing?.id ?? randomUUID();
3937
4011
  const bodyPath = opts.existing?.bodyPath ?? `${id}.body.md`;
3938
- const jsonPath = path15.join(outboxDir, `${id}.json`);
3939
- const bodyFile = path15.join(outboxDir, bodyPath);
4012
+ const jsonPath = path16.join(outboxDir, `${id}.json`);
4013
+ const bodyFile = path16.join(outboxDir, bodyPath);
3940
4014
  if (!opts.existing) {
3941
4015
  writeFileSync3(bodyFile, input.body, "utf8");
3942
4016
  }
@@ -3972,24 +4046,24 @@ function writeOutboxItem(input, opts) {
3972
4046
  }
3973
4047
  function saveOutboxItem(item) {
3974
4048
  const { outboxDir } = ensurePlanOutboxDirs();
3975
- const jsonPath = path15.join(outboxDir, `${item.id}.json`);
4049
+ const jsonPath = path16.join(outboxDir, `${item.id}.json`);
3976
4050
  writeFileSync3(jsonPath, `${JSON.stringify(item, null, 2)}
3977
4051
  `, { mode: 384 });
3978
4052
  }
3979
4053
  function archiveOutboxItem(item) {
3980
4054
  const { outboxDir, archiveDir } = ensurePlanOutboxDirs();
3981
- const jsonSrc = path15.join(outboxDir, `${item.id}.json`);
3982
- const bodySrc = path15.join(outboxDir, item.bodyPath);
3983
- const jsonDst = path15.join(archiveDir, `${item.id}.json`);
3984
- const bodyDst = path15.join(archiveDir, item.bodyPath);
3985
- if (existsSync12(jsonSrc)) renameSync(jsonSrc, jsonDst);
3986
- if (existsSync12(bodySrc)) renameSync(bodySrc, bodyDst);
4055
+ const jsonSrc = path16.join(outboxDir, `${item.id}.json`);
4056
+ const bodySrc = path16.join(outboxDir, item.bodyPath);
4057
+ const jsonDst = path16.join(archiveDir, `${item.id}.json`);
4058
+ const bodyDst = path16.join(archiveDir, item.bodyPath);
4059
+ if (existsSync13(jsonSrc)) renameSync(jsonSrc, jsonDst);
4060
+ if (existsSync13(bodySrc)) renameSync(bodySrc, bodyDst);
3987
4061
  }
3988
4062
  function outboxItemPaths(item) {
3989
4063
  const { outboxDir } = ensurePlanOutboxDirs();
3990
4064
  return {
3991
- jsonPath: path15.join(outboxDir, `${item.id}.json`),
3992
- bodyPath: path15.join(outboxDir, item.bodyPath)
4065
+ jsonPath: path16.join(outboxDir, `${item.id}.json`),
4066
+ bodyPath: path16.join(outboxDir, item.bodyPath)
3993
4067
  };
3994
4068
  }
3995
4069
  function outboxInputFromItem(item, body) {
@@ -4175,7 +4249,7 @@ function markOutboxFailed(item, message) {
4175
4249
  }
4176
4250
 
4177
4251
  // src/plan-persist/drain.ts
4178
- import path16 from "node:path";
4252
+ import path17 from "node:path";
4179
4253
  async function drainPlanOutbox(opts = {}, deps = {}) {
4180
4254
  const items = listOutboxItems().filter(
4181
4255
  (item) => opts.outboxId ? item.id === opts.outboxId : true
@@ -4209,7 +4283,7 @@ async function drainPlanOutbox(opts = {}, deps = {}) {
4209
4283
  return result;
4210
4284
  }
4211
4285
  function loadOutboxById(outboxId) {
4212
- const jsonPath = path16.join(planOutboxDir(), `${outboxId}.json`);
4286
+ const jsonPath = path17.join(planOutboxDir(), `${outboxId}.json`);
4213
4287
  return readOutboxItem(jsonPath);
4214
4288
  }
4215
4289
 
@@ -4309,7 +4383,7 @@ async function dispatchRun(args) {
4309
4383
  const activeHarnessWorkers = [];
4310
4384
  for (const name of Object.keys(run.workers || {})) {
4311
4385
  const worker = readJson(
4312
- path17.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4386
+ path18.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4313
4387
  void 0
4314
4388
  );
4315
4389
  if (!worker?.taskId || !isPidAlive(worker.pid)) continue;
@@ -4518,7 +4592,7 @@ async function dispatchRun(args) {
4518
4592
  }
4519
4593
 
4520
4594
  // src/sweep.ts
4521
- import path18 from "node:path";
4595
+ import path19 from "node:path";
4522
4596
  async function sweepRun(args) {
4523
4597
  const pipeline = args.pipeline === true || args.pipeline === "true";
4524
4598
  try {
@@ -4531,7 +4605,7 @@ async function sweepRun(args) {
4531
4605
  const releasedLocalOrphans = [];
4532
4606
  for (const name of Object.keys(run.workers || {})) {
4533
4607
  const worker = readJson(
4534
- path18.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4608
+ path19.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4535
4609
  void 0
4536
4610
  );
4537
4611
  if (!worker || !worker.dispatched || !worker.taskId) continue;
@@ -4574,11 +4648,53 @@ async function sweepRun(args) {
4574
4648
  }
4575
4649
 
4576
4650
  // src/worktree.ts
4577
- import { existsSync as existsSync13, mkdirSync as mkdirSync5 } from "node:fs";
4651
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5 } from "node:fs";
4652
+ import path22 from "node:path";
4653
+
4654
+ // src/default-repo.ts
4578
4655
  import path20 from "node:path";
4656
+ function expandConfiguredRepo(value) {
4657
+ return path20.resolve(resolveUserPath(value.trim()));
4658
+ }
4659
+ function fromConfigured(value, source, persistedInConfig) {
4660
+ const trimmed = value?.trim();
4661
+ if (!trimmed) return null;
4662
+ return {
4663
+ repo: expandConfiguredRepo(trimmed),
4664
+ source,
4665
+ persistedInConfig
4666
+ };
4667
+ }
4668
+ function resolveDefaultRepo(opts = {}) {
4669
+ const env = opts.env ?? process.env;
4670
+ const config = opts.config ?? loadUserConfig();
4671
+ const fromConfig = fromConfigured(config.defaultRepo, "config", true);
4672
+ if (fromConfig) return fromConfig;
4673
+ const fromDefaultEnv = fromConfigured(env.KYNVER_DEFAULT_REPO, "env_default_repo", false);
4674
+ if (fromDefaultEnv) return fromDefaultEnv;
4675
+ const fromHarnessEnv = fromConfigured(env.KYNVER_HARNESS_REPO, "env_harness_repo", false);
4676
+ if (fromHarnessEnv) return fromHarnessEnv;
4677
+ const discovered = discoverDefaultRepo({
4678
+ cwd: opts.cwd,
4679
+ runtimeModuleUrl: opts.runtimeModuleUrl
4680
+ });
4681
+ if (!discovered) return null;
4682
+ return {
4683
+ repo: discovered.repo,
4684
+ source: discovered.source,
4685
+ persistedInConfig: false
4686
+ };
4687
+ }
4688
+ function formatResolvedDefaultRepo(resolved) {
4689
+ return {
4690
+ defaultRepo: displayUserPath(resolved.repo),
4691
+ source: resolved.source,
4692
+ persistedInConfig: resolved.persistedInConfig
4693
+ };
4694
+ }
4579
4695
 
4580
4696
  // src/validate.ts
4581
- import path19 from "node:path";
4697
+ import path21 from "node:path";
4582
4698
  var RUN_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;
4583
4699
  function validateRunId(runId) {
4584
4700
  const trimmed = runId.trim();
@@ -4586,18 +4702,26 @@ function validateRunId(runId) {
4586
4702
  return trimmed;
4587
4703
  }
4588
4704
  function validateRepo(repo) {
4589
- const resolved = path19.resolve(repo);
4705
+ const resolved = path21.resolve(repo);
4590
4706
  if (resolved.includes("..")) throw new Error("repo path must not contain .. segments");
4591
4707
  return resolved;
4592
4708
  }
4593
4709
 
4594
4710
  // src/worktree.ts
4711
+ function resolveCreateRunRepo(args) {
4712
+ const explicit = typeof args.repo === "string" ? args.repo.trim() : "";
4713
+ if (explicit) return explicit;
4714
+ const resolved = resolveDefaultRepo();
4715
+ if (resolved) return resolved.repo;
4716
+ required("", "--repo (or set defaultRepo via `kynver setup` / KYNVER_DEFAULT_REPO)");
4717
+ return "";
4718
+ }
4595
4719
  function createRun(args) {
4596
- const repo = validateRepo(required(String(args.repo || ""), "--repo"));
4720
+ const repo = validateRepo(resolveCreateRunRepo(args));
4597
4721
  ensureGitRepo(repo);
4598
4722
  const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
4599
4723
  const dir = runDirectory(id);
4600
- if (existsSync13(dir)) failExists(`run already exists: ${id}`);
4724
+ if (existsSync14(dir)) failExists(`run already exists: ${id}`);
4601
4725
  mkdirSync5(dir, { recursive: true });
4602
4726
  const base = String(args.base || "origin/main");
4603
4727
  const baseCommit = git(repo, ["rev-parse", base]).trim();
@@ -4611,12 +4735,12 @@ function createRun(args) {
4611
4735
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
4612
4736
  workers: {}
4613
4737
  };
4614
- writeJson(path20.join(dir, "run.json"), run);
4738
+ writeJson(path22.join(dir, "run.json"), run);
4615
4739
  console.log(JSON.stringify({ runId: id, runDir: dir, repo, base, baseCommit }, null, 2));
4616
4740
  }
4617
4741
  function listRuns() {
4618
4742
  const { runsDir } = getPaths();
4619
- const rows = listRunIds(runsDir).map((id) => readJson(path20.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
4743
+ const rows = listRunIds(runsDir).map((id) => readJson(path22.join(runDirectory(id), "run.json"), void 0)).filter(Boolean).map((run) => ({
4620
4744
  id: run.id,
4621
4745
  name: run.name,
4622
4746
  status: run.status,
@@ -4631,7 +4755,7 @@ function failExists(message) {
4631
4755
  }
4632
4756
 
4633
4757
  // src/pipeline-tick.ts
4634
- import path30 from "node:path";
4758
+ import path32 from "node:path";
4635
4759
 
4636
4760
  // src/pipeline-dispatch.ts
4637
4761
  var RESERVED_REVIEW_STARTS = 1;
@@ -4685,12 +4809,19 @@ async function runPipelineDispatch(args, slots) {
4685
4809
  }
4686
4810
 
4687
4811
  // src/stale-reconcile.ts
4688
- import path22 from "node:path";
4812
+ import path24 from "node:path";
4689
4813
 
4690
4814
  // src/finalize.ts
4691
- import path21 from "node:path";
4692
- var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued"]);
4693
- function terminalStatusFor(run) {
4815
+ import path23 from "node:path";
4816
+ var ACTIVE_RUN_STATUSES = /* @__PURE__ */ new Set([
4817
+ "running",
4818
+ "dispatching",
4819
+ "pending",
4820
+ "queued",
4821
+ "needs_attention"
4822
+ ]);
4823
+ var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled", "done"]);
4824
+ function deriveTerminalRunStatus(run) {
4694
4825
  const names = Object.keys(run.workers || {});
4695
4826
  if (names.length === 0) return "failed";
4696
4827
  let anyAlive = false;
@@ -4699,7 +4830,7 @@ function terminalStatusFor(run) {
4699
4830
  let anyLandingBlocked = false;
4700
4831
  for (const name of names) {
4701
4832
  const worker = readJson(
4702
- path21.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4833
+ path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4703
4834
  void 0
4704
4835
  );
4705
4836
  if (!worker) continue;
@@ -4728,7 +4859,7 @@ function finalizeStaleRuns() {
4728
4859
  const finalized = [];
4729
4860
  for (const run of listRunRecords()) {
4730
4861
  if (!ACTIVE_RUN_STATUSES.has(run.status)) continue;
4731
- const next = terminalStatusFor(run);
4862
+ const next = deriveTerminalRunStatus(run);
4732
4863
  if (!next || next === run.status) continue;
4733
4864
  const from = run.status;
4734
4865
  run.status = next;
@@ -4751,7 +4882,7 @@ function reconcileStaleWorkers() {
4751
4882
  const now = Date.now();
4752
4883
  for (const run of listRunRecords()) {
4753
4884
  for (const name of Object.keys(run.workers || {})) {
4754
- const workerPath = path22.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
4885
+ const workerPath = path24.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
4755
4886
  const worker = readJson(workerPath, void 0);
4756
4887
  if (!worker || worker.status !== "running") {
4757
4888
  outcomes.push({
@@ -4845,7 +4976,7 @@ function reconcileRunsCli() {
4845
4976
  }
4846
4977
 
4847
4978
  // src/plan-progress-daemon-sync.ts
4848
- import path23 from "node:path";
4979
+ import path25 from "node:path";
4849
4980
 
4850
4981
  // src/plan-progress-sync.ts
4851
4982
  async function syncPlanProgress(args) {
@@ -4869,7 +5000,7 @@ async function syncActiveWorkerPlanProgress(runId, args) {
4869
5000
  const outcomes = [];
4870
5001
  for (const name of Object.keys(run.workers || {})) {
4871
5002
  const worker = readJson(
4872
- path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
5003
+ path25.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
4873
5004
  void 0
4874
5005
  );
4875
5006
  if (!worker?.dispatched || !worker.taskId) continue;
@@ -4918,15 +5049,28 @@ async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
4918
5049
  }
4919
5050
 
4920
5051
  // src/cleanup.ts
4921
- import path28 from "node:path";
5052
+ import path30 from "node:path";
4922
5053
 
4923
- // src/cleanup-types.ts
4924
- var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
4925
- var DEFAULT_WORKTREES_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
5054
+ // src/cleanup-run-liveness.ts
5055
+ function isWorkerProcessLive(indexed) {
5056
+ if (indexed.status.alive) return true;
5057
+ if (indexed.worker.status === "running") return true;
5058
+ return false;
5059
+ }
5060
+ function isRunStaleActive(indexed) {
5061
+ if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
5062
+ return deriveTerminalRunStatus(indexed.run) !== null;
5063
+ }
5064
+ function runBlocksWorktreeRemoval(indexed) {
5065
+ if (isWorkerProcessLive(indexed)) return true;
5066
+ if (indexed.worker.completionBlocker) return true;
5067
+ if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
5068
+ if (isRunStaleActive(indexed)) return false;
5069
+ if (!isFinishedWorkerStatus(indexed.status)) return true;
5070
+ return deriveTerminalRunStatus(indexed.run) === null;
5071
+ }
4926
5072
 
4927
5073
  // src/cleanup-guards.ts
4928
- var ACTIVE_RUN_STATUSES2 = /* @__PURE__ */ new Set(["running", "dispatching", "pending", "queued", "needs_attention"]);
4929
- var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
4930
5074
  function prUrlFromFinalResult(finalResult) {
4931
5075
  if (typeof finalResult === "string") {
4932
5076
  const match = finalResult.match(/https:\/\/github\.com\/[^\s]+\/pull\/\d+/i);
@@ -4948,18 +5092,29 @@ function isPrOrUnmergedWork(status) {
4948
5092
  if (status.changedFiles.length > 0 && status.finalResult) return true;
4949
5093
  return false;
4950
5094
  }
5095
+ function materialWorktreeChanges(changedFiles) {
5096
+ return changedFiles.filter((line) => {
5097
+ const trimmed = line.trim();
5098
+ const pathPart = trimmed.startsWith("??") ? trimmed.slice(2).trim() : trimmed.length > 3 ? trimmed.slice(3).trim() : trimmed;
5099
+ return pathPart !== "node_modules" && !pathPart.startsWith("node_modules/");
5100
+ });
5101
+ }
5102
+ function hasUnrestorableWorktreeChanges(status) {
5103
+ if (materialWorktreeChanges(status.changedFiles).length > 0) return true;
5104
+ if (status.gitAncestry?.relation === "diverged") return true;
5105
+ return false;
5106
+ }
4951
5107
  function skipWorktreeRemoval(input) {
4952
5108
  const { indexed, includeOrphans, worktreesAgeMs, ageMs } = input;
4953
5109
  if (worktreesAgeMs <= 0) return "worktrees_disabled";
4954
5110
  if (ageMs < worktreesAgeMs) return "below_age_threshold";
4955
5111
  if (!indexed) return includeOrphans ? null : "orphan_without_flag";
4956
- if (ACTIVE_RUN_STATUSES2.has(indexed.run.status)) return "run_still_active";
4957
- if (!TERMINAL_RUN_STATUSES.has(indexed.run.status)) return "run_still_active";
4958
- if (indexed.status.alive) return "active_worker";
4959
- if (indexed.worker.status === "running") return "active_worker";
5112
+ if (isWorkerProcessLive(indexed)) return "active_worker";
4960
5113
  if (indexed.worker.completionBlocker) return "completion_blocked";
5114
+ if (runBlocksWorktreeRemoval(indexed)) return "run_still_active";
5115
+ if (!isFinishedWorkerStatus(indexed.status)) return "run_still_active";
4961
5116
  if (isPrOrUnmergedWork(indexed.status)) return "pr_or_unmerged_commits";
4962
- if (indexed.status.changedFiles.length > 0) return "dirty_worktree";
5117
+ if (materialWorktreeChanges(indexed.status.changedFiles).length > 0) return "dirty_worktree";
4963
5118
  const landing = assessWorkerLanding({
4964
5119
  finalResult: indexed.status.finalResult,
4965
5120
  changedFiles: indexed.status.changedFiles,
@@ -4973,30 +5128,31 @@ function skipNodeModulesRemoval(input) {
4973
5128
  const { indexed, includeOrphans, nodeModulesAgeMs, ageMs } = input;
4974
5129
  if (ageMs < nodeModulesAgeMs) return "below_age_threshold";
4975
5130
  if (!indexed) return includeOrphans ? null : "orphan_without_flag";
4976
- if (indexed.status.alive) return "active_worker";
4977
- if (indexed.worker.status === "running") return "active_worker";
5131
+ if (isWorkerProcessLive(indexed)) return "active_worker";
4978
5132
  if (indexed.worker.completionBlocker) return "completion_blocked";
4979
- if (isPrOrUnmergedWork(indexed.status)) return "pr_or_unmerged_commits";
4980
- if (indexed.status.changedFiles.length > 0) return "dirty_worktree";
5133
+ if (!isFinishedWorkerStatus(indexed.status)) return "run_still_active";
5134
+ if (hasUnrestorableWorktreeChanges(indexed.status)) return "dirty_worktree";
4981
5135
  const landing = assessWorkerLanding({
4982
5136
  finalResult: indexed.status.finalResult,
4983
5137
  changedFiles: indexed.status.changedFiles,
4984
5138
  gitAncestry: indexed.status.gitAncestry,
4985
5139
  prUrl: prUrlFromFinalResult(indexed.status.finalResult)
4986
5140
  });
4987
- if (landing.blocked) return "landing_blocked";
5141
+ if (landing.blocked && materialWorktreeChanges(indexed.status.changedFiles).length > 0) {
5142
+ return "landing_blocked";
5143
+ }
4988
5144
  return null;
4989
5145
  }
4990
5146
 
4991
5147
  // src/cleanup-execute.ts
4992
- import { existsSync as existsSync15, rmSync } from "node:fs";
4993
- import path25 from "node:path";
5148
+ import { existsSync as existsSync16, rmSync } from "node:fs";
5149
+ import path27 from "node:path";
4994
5150
 
4995
5151
  // src/cleanup-dir-size.ts
4996
- import { existsSync as existsSync14, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
4997
- import path24 from "node:path";
5152
+ import { existsSync as existsSync15, readdirSync as readdirSync5, statSync as statSync2 } from "node:fs";
5153
+ import path26 from "node:path";
4998
5154
  function directorySizeBytes(root, maxEntries = 5e4) {
4999
- if (!existsSync14(root)) return 0;
5155
+ if (!existsSync15(root)) return 0;
5000
5156
  let total = 0;
5001
5157
  let seen = 0;
5002
5158
  const stack = [root];
@@ -5010,7 +5166,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
5010
5166
  }
5011
5167
  for (const name of entries) {
5012
5168
  if (seen++ > maxEntries) return null;
5013
- const full = path24.join(current, name);
5169
+ const full = path26.join(current, name);
5014
5170
  let st;
5015
5171
  try {
5016
5172
  st = statSync2(full);
@@ -5026,7 +5182,7 @@ function directorySizeBytes(root, maxEntries = 5e4) {
5026
5182
 
5027
5183
  // src/cleanup-execute.ts
5028
5184
  function removeNodeModules(candidate, execute) {
5029
- if (!existsSync15(candidate.path)) {
5185
+ if (!existsSync16(candidate.path)) {
5030
5186
  return {
5031
5187
  ...candidate,
5032
5188
  executed: false,
@@ -5057,7 +5213,7 @@ function removeNodeModules(candidate, execute) {
5057
5213
  }
5058
5214
  }
5059
5215
  function removeWorktree(candidate, execute) {
5060
- if (!existsSync15(candidate.path)) {
5216
+ if (!existsSync16(candidate.path)) {
5061
5217
  return {
5062
5218
  ...candidate,
5063
5219
  executed: false,
@@ -5074,7 +5230,7 @@ function removeWorktree(candidate, execute) {
5074
5230
  if (repo) {
5075
5231
  git(repo, ["worktree", "remove", "--force", candidate.path], { allowFailure: true });
5076
5232
  }
5077
- if (existsSync15(candidate.path)) {
5233
+ if (existsSync16(candidate.path)) {
5078
5234
  rmSync(candidate.path, { recursive: true, force: true });
5079
5235
  }
5080
5236
  return {
@@ -5094,20 +5250,20 @@ function removeWorktree(candidate, execute) {
5094
5250
  }
5095
5251
  }
5096
5252
  function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
5097
- const resolved = path25.resolve(targetPath);
5098
- const nm = resolved.endsWith(`${path25.sep}node_modules`) ? resolved : null;
5253
+ const resolved = path27.resolve(targetPath);
5254
+ const nm = resolved.endsWith(`${path27.sep}node_modules`) ? resolved : null;
5099
5255
  if (!nm) return "path_outside_harness";
5100
- const rel = path25.relative(worktreesDir, nm);
5101
- if (rel.startsWith("..") || path25.isAbsolute(rel)) return "path_outside_harness";
5102
- const parts = rel.split(path25.sep);
5256
+ const rel = path27.relative(worktreesDir, nm);
5257
+ if (rel.startsWith("..") || path27.isAbsolute(rel)) return "path_outside_harness";
5258
+ const parts = rel.split(path27.sep);
5103
5259
  if (parts.length < 3 || parts[parts.length - 1] !== "node_modules") return "path_outside_harness";
5104
- if (!resolved.startsWith(path25.resolve(harnessRoot))) return "path_outside_harness";
5260
+ if (!resolved.startsWith(path27.resolve(harnessRoot))) return "path_outside_harness";
5105
5261
  return null;
5106
5262
  }
5107
5263
 
5108
5264
  // src/cleanup-scan.ts
5109
- import { existsSync as existsSync16, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
5110
- import path26 from "node:path";
5265
+ import { existsSync as existsSync17, readdirSync as readdirSync6, statSync as statSync3 } from "node:fs";
5266
+ import path28 from "node:path";
5111
5267
  function pathAgeMs(target, now) {
5112
5268
  try {
5113
5269
  const mtime = statSync3(target).mtimeMs;
@@ -5117,17 +5273,17 @@ function pathAgeMs(target, now) {
5117
5273
  }
5118
5274
  }
5119
5275
  function isPathInside(child, parent) {
5120
- const rel = path26.relative(parent, child);
5121
- return rel === "" || !rel.startsWith("..") && !path26.isAbsolute(rel);
5276
+ const rel = path28.relative(parent, child);
5277
+ return rel === "" || !rel.startsWith("..") && !path28.isAbsolute(rel);
5122
5278
  }
5123
5279
  function scanNodeModulesCandidates(opts) {
5124
5280
  const candidates = [];
5125
5281
  const seen = /* @__PURE__ */ new Set();
5126
5282
  for (const entry of opts.index.values()) {
5127
5283
  if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
5128
- const nm = path26.join(entry.worktreePath, "node_modules");
5129
- if (!existsSync16(nm)) continue;
5130
- const resolved = path26.resolve(nm);
5284
+ const nm = path28.join(entry.worktreePath, "node_modules");
5285
+ if (!existsSync17(nm)) continue;
5286
+ const resolved = path28.resolve(nm);
5131
5287
  if (seen.has(resolved)) continue;
5132
5288
  seen.add(resolved);
5133
5289
  candidates.push({
@@ -5140,16 +5296,16 @@ function scanNodeModulesCandidates(opts) {
5140
5296
  ageMs: pathAgeMs(resolved, opts.now)
5141
5297
  });
5142
5298
  }
5143
- if (!opts.includeOrphans || !existsSync16(opts.worktreesDir)) return candidates;
5299
+ if (!opts.includeOrphans || !existsSync17(opts.worktreesDir)) return candidates;
5144
5300
  for (const runEntry of readdirSync6(opts.worktreesDir, { withFileTypes: true })) {
5145
5301
  if (!runEntry.isDirectory()) continue;
5146
- const runPath = path26.join(opts.worktreesDir, runEntry.name);
5302
+ const runPath = path28.join(opts.worktreesDir, runEntry.name);
5147
5303
  for (const workerEntry of readdirSync6(runPath, { withFileTypes: true })) {
5148
5304
  if (!workerEntry.isDirectory()) continue;
5149
- const worktreePath = path26.join(runPath, workerEntry.name);
5150
- const nm = path26.join(worktreePath, "node_modules");
5151
- if (!existsSync16(nm)) continue;
5152
- const resolved = path26.resolve(nm);
5305
+ const worktreePath = path28.join(runPath, workerEntry.name);
5306
+ const nm = path28.join(worktreePath, "node_modules");
5307
+ if (!existsSync17(nm)) continue;
5308
+ const resolved = path28.resolve(nm);
5153
5309
  if (seen.has(resolved)) continue;
5154
5310
  if (!isPathInside(resolved, opts.harnessRoot)) continue;
5155
5311
  seen.add(resolved);
@@ -5172,7 +5328,7 @@ function scanWorktreeCandidates(opts) {
5172
5328
  for (const entry of opts.index.values()) {
5173
5329
  if (opts.runIdFilter && entry.runId !== opts.runIdFilter) continue;
5174
5330
  const resolved = entry.worktreePath;
5175
- if (!existsSync16(resolved)) continue;
5331
+ if (!existsSync17(resolved)) continue;
5176
5332
  if (seen.has(resolved)) continue;
5177
5333
  seen.add(resolved);
5178
5334
  candidates.push({
@@ -5189,17 +5345,17 @@ function scanWorktreeCandidates(opts) {
5189
5345
  }
5190
5346
 
5191
5347
  // src/cleanup-worktree-index.ts
5192
- import path27 from "node:path";
5348
+ import path29 from "node:path";
5193
5349
  function buildWorktreeIndex() {
5194
5350
  const index = /* @__PURE__ */ new Map();
5195
5351
  for (const run of listRunRecords()) {
5196
5352
  for (const name of Object.keys(run.workers || {})) {
5197
- const workerPath = path27.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
5353
+ const workerPath = path29.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
5198
5354
  const worker = readJson(workerPath, void 0);
5199
5355
  if (!worker?.worktreePath) continue;
5200
5356
  const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
5201
- index.set(path27.resolve(worker.worktreePath), {
5202
- worktreePath: path27.resolve(worker.worktreePath),
5357
+ index.set(path29.resolve(worker.worktreePath), {
5358
+ worktreePath: path29.resolve(worker.worktreePath),
5203
5359
  runId: run.id,
5204
5360
  workerName: name,
5205
5361
  run,
@@ -5211,59 +5367,109 @@ function buildWorktreeIndex() {
5211
5367
  return index;
5212
5368
  }
5213
5369
 
5214
- // src/cleanup.ts
5215
- function resolveOptions(options = {}) {
5216
- const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
5217
- const { worktreesDir } = options.harnessRoot ? { worktreesDir: path28.join(harnessRoot, "worktrees") } : getHarnessPaths();
5218
- const execute = options.execute === true;
5219
- const nodeModulesAgeMs = options.nodeModulesAgeMs ?? DEFAULT_NODE_MODULES_AGE_MS;
5220
- const worktreesAgeMs = options.worktreesAgeMs ?? 0;
5221
- const includeOrphans = options.includeOrphans === true;
5222
- const runIdFilter = options.runIdFilter ? String(options.runIdFilter) : void 0;
5223
- const now = options.now ?? Date.now();
5370
+ // src/cleanup-types.ts
5371
+ var DEFAULT_NODE_MODULES_AGE_MS = 6 * 60 * 60 * 1e3;
5372
+ var DEFAULT_WORKTREES_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
5373
+
5374
+ // src/cleanup-retention-config.ts
5375
+ function envFlag(name) {
5376
+ const v = process.env[name];
5377
+ return v === "1" || v === "true" || v === "yes";
5378
+ }
5379
+ function envMs(name, fallback) {
5380
+ const raw = process.env[name];
5381
+ if (!raw) return fallback;
5382
+ const n = Number(raw);
5383
+ return Number.isFinite(n) && n >= 0 ? n : fallback;
5384
+ }
5385
+ function resolveHarnessRetention(options = {}) {
5386
+ const execute = options.execute === true || options.execute !== false && envFlag("KYNVER_CLEANUP_EXECUTE");
5387
+ const finalizeStaleRuns2 = options.finalizeStaleRuns !== false && !envFlag("KYNVER_CLEANUP_SKIP_FINALIZE");
5388
+ const nodeModulesAgeMs = options.nodeModulesAgeMs ?? envMs("KYNVER_CLEANUP_NODE_MODULES_AGE_MS", DEFAULT_NODE_MODULES_AGE_MS);
5389
+ const worktreesAgeMs = options.worktreesAgeMs ?? envMs("KYNVER_CLEANUP_WORKTREES_AGE_MS", 0);
5390
+ const includeOrphans = options.includeOrphans === true || envFlag("KYNVER_CLEANUP_INCLUDE_ORPHANS");
5391
+ const scopeAll = envFlag("KYNVER_CLEANUP_SCOPE_ALL") || process.env.KYNVER_CLEANUP_SCOPE === "all";
5392
+ const runIdFilter = scopeAll ? options.runIdFilter : options.runIdFilter ?? (process.env.KYNVER_CLEANUP_RUN_ID || void 0);
5393
+ const accountBytes = options.accountBytes !== false && !envFlag("KYNVER_CLEANUP_SKIP_BYTE_ACCOUNTING");
5224
5394
  return {
5225
- harnessRoot,
5226
- worktreesDir,
5227
5395
  execute,
5228
- dryRun: !execute,
5396
+ finalizeStaleRuns: finalizeStaleRuns2,
5229
5397
  nodeModulesAgeMs,
5230
- worktreesAgeMs,
5398
+ worktreesAgeMs: worktreesAgeMs > 0 ? worktreesAgeMs : 0,
5231
5399
  includeOrphans,
5232
- runIdFilter,
5233
- now
5400
+ runIdFilter: runIdFilter ? String(runIdFilter) : void 0,
5401
+ accountBytes
5234
5402
  };
5235
5403
  }
5404
+ function resolvePipelineHarnessRetention(runId) {
5405
+ const scopeAll = process.env.KYNVER_CLEANUP_SCOPE === "all";
5406
+ const envWorktrees = Number(process.env.KYNVER_CLEANUP_WORKTREES_AGE_MS);
5407
+ const worktreesAgeMs = scopeAll ? Number.isFinite(envWorktrees) && envWorktrees > 0 ? envWorktrees : DEFAULT_WORKTREES_AGE_MS : Number.isFinite(envWorktrees) && envWorktrees > 0 ? envWorktrees : 0;
5408
+ return resolveHarnessRetention({
5409
+ runIdFilter: scopeAll ? void 0 : runId,
5410
+ worktreesAgeMs,
5411
+ finalizeStaleRuns: true,
5412
+ accountBytes: true
5413
+ });
5414
+ }
5415
+
5416
+ // src/cleanup.ts
5417
+ function resolvePaths(options = {}) {
5418
+ const harnessRoot = options.harnessRoot ? resolveUserPath(options.harnessRoot) : resolveHarnessRoot();
5419
+ const { worktreesDir } = options.harnessRoot ? { worktreesDir: path30.join(harnessRoot, "worktrees") } : getHarnessPaths();
5420
+ const now = options.now ?? Date.now();
5421
+ return { harnessRoot, worktreesDir, now };
5422
+ }
5236
5423
  function recordSkip(skips, pathValue, reason, detail) {
5237
5424
  skips.push({ path: pathValue, reason, ...detail ? { detail } : {} });
5238
5425
  }
5426
+ function attachCandidateBytes(candidate, accountBytes) {
5427
+ if (!accountBytes || candidate.bytes != null) return candidate;
5428
+ return { ...candidate, bytes: directorySizeBytes(candidate.path) };
5429
+ }
5430
+ function tallySkipReasons(actions, skips) {
5431
+ const counts = {};
5432
+ for (const skip of skips) {
5433
+ counts[skip.reason] = (counts[skip.reason] ?? 0) + 1;
5434
+ }
5435
+ for (const action of actions) {
5436
+ if (action.skipReason) {
5437
+ counts[action.skipReason] = (counts[action.skipReason] ?? 0) + 1;
5438
+ }
5439
+ }
5440
+ return counts;
5441
+ }
5239
5442
  function runHarnessCleanup(options = {}) {
5240
- const resolved = resolveOptions(options);
5443
+ const retention = resolveHarnessRetention(options);
5444
+ const paths = resolvePaths(options);
5445
+ const finalizedRuns = retention.finalizeStaleRuns ? finalizeStaleRuns().map((f) => ({ runId: f.runId, from: f.from, to: f.to })) : [];
5241
5446
  const index = buildWorktreeIndex();
5242
5447
  const scanOpts = {
5243
- harnessRoot: resolved.harnessRoot,
5244
- worktreesDir: resolved.worktreesDir,
5245
- nodeModulesAgeMs: resolved.nodeModulesAgeMs,
5246
- worktreesAgeMs: resolved.worktreesAgeMs,
5247
- includeOrphans: resolved.includeOrphans,
5248
- runIdFilter: resolved.runIdFilter,
5448
+ harnessRoot: paths.harnessRoot,
5449
+ worktreesDir: paths.worktreesDir,
5450
+ nodeModulesAgeMs: retention.nodeModulesAgeMs,
5451
+ worktreesAgeMs: retention.worktreesAgeMs,
5452
+ includeOrphans: retention.includeOrphans,
5453
+ runIdFilter: retention.runIdFilter,
5249
5454
  index,
5250
- now: resolved.now
5455
+ now: paths.now
5251
5456
  };
5252
5457
  const skips = [];
5253
5458
  const actions = [];
5254
- for (const candidate of scanNodeModulesCandidates(scanOpts)) {
5255
- const pathSkip = isHarnessNodeModulesPath(candidate.path, resolved.harnessRoot, resolved.worktreesDir);
5459
+ for (const raw of scanNodeModulesCandidates(scanOpts)) {
5460
+ const candidate = attachCandidateBytes(raw, retention.accountBytes);
5461
+ const pathSkip = isHarnessNodeModulesPath(candidate.path, paths.harnessRoot, paths.worktreesDir);
5256
5462
  if (pathSkip) {
5257
5463
  recordSkip(skips, candidate.path, pathSkip);
5258
5464
  actions.push({ ...candidate, executed: false, skipped: true, skipReason: pathSkip });
5259
5465
  continue;
5260
5466
  }
5261
- const worktreePath = path28.resolve(candidate.path, "..");
5467
+ const worktreePath = path30.resolve(candidate.path, "..");
5262
5468
  const indexed = index.get(worktreePath) ?? null;
5263
5469
  const guardReason = skipNodeModulesRemoval({
5264
5470
  indexed,
5265
- includeOrphans: resolved.includeOrphans,
5266
- nodeModulesAgeMs: resolved.nodeModulesAgeMs,
5471
+ includeOrphans: retention.includeOrphans,
5472
+ nodeModulesAgeMs: retention.nodeModulesAgeMs,
5267
5473
  ageMs: candidate.ageMs
5268
5474
  });
5269
5475
  if (guardReason) {
@@ -5271,14 +5477,15 @@ function runHarnessCleanup(options = {}) {
5271
5477
  actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
5272
5478
  continue;
5273
5479
  }
5274
- actions.push(removeNodeModules(candidate, resolved.execute));
5480
+ actions.push(removeNodeModules(candidate, retention.execute));
5275
5481
  }
5276
- for (const candidate of scanWorktreeCandidates(scanOpts)) {
5277
- const indexed = index.get(path28.resolve(candidate.path)) ?? null;
5482
+ for (const raw of scanWorktreeCandidates(scanOpts)) {
5483
+ const candidate = attachCandidateBytes(raw, retention.accountBytes);
5484
+ const indexed = index.get(path30.resolve(candidate.path)) ?? null;
5278
5485
  const guardReason = skipWorktreeRemoval({
5279
5486
  indexed,
5280
- includeOrphans: resolved.includeOrphans,
5281
- worktreesAgeMs: resolved.worktreesAgeMs,
5487
+ includeOrphans: retention.includeOrphans,
5488
+ worktreesAgeMs: retention.worktreesAgeMs,
5282
5489
  ageMs: candidate.ageMs
5283
5490
  });
5284
5491
  if (guardReason) {
@@ -5286,51 +5493,55 @@ function runHarnessCleanup(options = {}) {
5286
5493
  actions.push({ ...candidate, executed: false, skipped: true, skipReason: guardReason });
5287
5494
  continue;
5288
5495
  }
5289
- actions.push(removeWorktree(candidate, resolved.execute));
5496
+ actions.push(removeWorktree(candidate, retention.execute));
5290
5497
  }
5291
5498
  let candidateBytes = 0;
5499
+ let reclaimableBytes = 0;
5292
5500
  let removedBytes = 0;
5293
5501
  let removedPaths = 0;
5294
5502
  let skippedPaths = 0;
5295
5503
  for (const action of actions) {
5296
5504
  if (action.bytes) candidateBytes += action.bytes;
5505
+ if (!action.skipped && !action.executed && action.bytes) reclaimableBytes += action.bytes;
5297
5506
  if (action.executed) {
5298
5507
  removedPaths += 1;
5299
5508
  removedBytes += action.bytes ?? 0;
5300
5509
  } else if (action.skipped) {
5301
5510
  skippedPaths += 1;
5511
+ if (action.skipReason === "dry_run" && action.bytes) reclaimableBytes += action.bytes;
5302
5512
  }
5303
5513
  }
5304
5514
  return {
5305
- harnessRoot: resolved.harnessRoot,
5306
- dryRun: resolved.dryRun,
5307
- execute: resolved.execute,
5308
- nodeModulesAgeMs: resolved.nodeModulesAgeMs,
5309
- worktreesAgeMs: resolved.worktreesAgeMs,
5310
- includeOrphans: resolved.includeOrphans,
5311
- scannedAt: new Date(resolved.now).toISOString(),
5515
+ harnessRoot: paths.harnessRoot,
5516
+ dryRun: !retention.execute,
5517
+ execute: retention.execute,
5518
+ nodeModulesAgeMs: retention.nodeModulesAgeMs,
5519
+ worktreesAgeMs: retention.worktreesAgeMs,
5520
+ includeOrphans: retention.includeOrphans,
5521
+ scannedAt: new Date(paths.now).toISOString(),
5522
+ finalizedRuns,
5312
5523
  actions,
5313
5524
  skips,
5314
5525
  totals: {
5315
5526
  candidateBytes,
5527
+ reclaimableBytes,
5316
5528
  removedBytes,
5317
5529
  removedPaths,
5318
- skippedPaths
5530
+ skippedPaths,
5531
+ skipReasons: tallySkipReasons(actions, skips)
5319
5532
  }
5320
5533
  };
5321
5534
  }
5322
5535
  function runPipelineHarnessCleanup(runId) {
5323
- const nodeModulesAgeMs = Number(process.env.KYNVER_CLEANUP_NODE_MODULES_AGE_MS) || DEFAULT_NODE_MODULES_AGE_MS;
5324
- const worktreesAgeMs = Number(process.env.KYNVER_CLEANUP_WORKTREES_AGE_MS) || 0;
5325
- const execute = process.env.KYNVER_CLEANUP_EXECUTE === "1";
5326
- const includeOrphans = process.env.KYNVER_CLEANUP_INCLUDE_ORPHANS === "1";
5327
- const scopeAll = process.env.KYNVER_CLEANUP_SCOPE === "all";
5536
+ const retention = resolvePipelineHarnessRetention(runId);
5328
5537
  return runHarnessCleanup({
5329
- execute,
5330
- nodeModulesAgeMs,
5331
- worktreesAgeMs,
5332
- includeOrphans,
5333
- runIdFilter: scopeAll ? void 0 : runId
5538
+ execute: retention.execute,
5539
+ finalizeStaleRuns: retention.finalizeStaleRuns,
5540
+ accountBytes: retention.accountBytes,
5541
+ nodeModulesAgeMs: retention.nodeModulesAgeMs,
5542
+ worktreesAgeMs: retention.worktreesAgeMs,
5543
+ includeOrphans: retention.includeOrphans,
5544
+ runIdFilter: retention.runIdFilter
5334
5545
  });
5335
5546
  }
5336
5547
  function isPipelineCleanupEnabled() {
@@ -5339,8 +5550,8 @@ function isPipelineCleanupEnabled() {
5339
5550
 
5340
5551
  // src/installed-package-versions.ts
5341
5552
  import { readFile } from "node:fs/promises";
5342
- import { homedir as homedir5 } from "node:os";
5343
- import path29 from "node:path";
5553
+ import { homedir as homedir6 } from "node:os";
5554
+ import path31 from "node:path";
5344
5555
  var MANAGED_PACKAGES = [
5345
5556
  "@kynver-app/runtime",
5346
5557
  "@kynver-app/openclaw-agent-os",
@@ -5354,13 +5565,13 @@ function unique(values) {
5354
5565
  return [...new Set(values.filter((value) => Boolean(value)))];
5355
5566
  }
5356
5567
  function moduleRoots() {
5357
- const home = homedir5();
5358
- const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path29.join(home, ".openclaw", "npm");
5359
- 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"));
5568
+ const home = homedir6();
5569
+ const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path31.join(home, ".openclaw", "npm");
5570
+ const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path31.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path31.join(home, ".npm-global", "lib", "node_modules"));
5360
5571
  return unique([
5361
- path29.join(openClawPrefix, "lib", "node_modules"),
5362
- path29.join(openClawPrefix, "node_modules"),
5363
- npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path29.join(npmGlobalRoot, "lib", "node_modules")
5572
+ path31.join(openClawPrefix, "lib", "node_modules"),
5573
+ path31.join(openClawPrefix, "node_modules"),
5574
+ npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path31.join(npmGlobalRoot, "lib", "node_modules")
5364
5575
  ]);
5365
5576
  }
5366
5577
  async function readVersion(packageJsonPath) {
@@ -5376,7 +5587,7 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
5376
5587
  const out = {};
5377
5588
  for (const packageName of MANAGED_PACKAGES) {
5378
5589
  for (const root of roots) {
5379
- const packageJsonPath = path29.join(root, packageName, "package.json");
5590
+ const packageJsonPath = path31.join(root, packageName, "package.json");
5380
5591
  const version = await readVersion(packageJsonPath);
5381
5592
  if (!version) continue;
5382
5593
  out[packageName] = { version, observedAt, path: packageJsonPath };
@@ -5392,7 +5603,7 @@ async function completeFinishedWorkers(runId, args) {
5392
5603
  const outcomes = [];
5393
5604
  for (const name of Object.keys(run.workers || {})) {
5394
5605
  const worker = readJson(
5395
- path30.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
5606
+ path32.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
5396
5607
  void 0
5397
5608
  );
5398
5609
  if (!worker?.taskId || worker.localOnly) continue;
@@ -5534,7 +5745,7 @@ async function runDaemon(args) {
5534
5745
  }
5535
5746
 
5536
5747
  // src/plan-progress.ts
5537
- import path31 from "node:path";
5748
+ import path33 from "node:path";
5538
5749
 
5539
5750
  // src/bounded-build/constants.ts
5540
5751
  var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
@@ -5820,7 +6031,7 @@ async function emitPlanProgress(args) {
5820
6031
  }
5821
6032
  function verifyPlanLocal(args) {
5822
6033
  const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
5823
- const cwd = path31.resolve(worktree);
6034
+ const cwd = path33.resolve(worktree);
5824
6035
  const summary = runHarnessVerifyCommands(cwd);
5825
6036
  const emitJson = args.json === true || args.json === "true";
5826
6037
  const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
@@ -5869,9 +6080,9 @@ async function verifyPlan(args) {
5869
6080
  }
5870
6081
 
5871
6082
  // src/harness-verify-cli.ts
5872
- import path32 from "node:path";
6083
+ import path34 from "node:path";
5873
6084
  function runHarnessVerifyCli(args) {
5874
- const cwd = path32.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
6085
+ const cwd = path34.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
5875
6086
  const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
5876
6087
  const commands = [];
5877
6088
  const rawCmd = args.command;
@@ -5915,7 +6126,7 @@ function runHarnessVerifyCli(args) {
5915
6126
  }
5916
6127
 
5917
6128
  // src/plan-persist-cli.ts
5918
- import { readFileSync as readFileSync7 } from "node:fs";
6129
+ import { readFileSync as readFileSync8 } from "node:fs";
5919
6130
  var OPERATIONS = ["create", "add_version", "update_metadata"];
5920
6131
  var FAILURE_KINDS = [
5921
6132
  "approval_guard",
@@ -5927,7 +6138,7 @@ var FAILURE_KINDS = [
5927
6138
  function readBodyArg(args) {
5928
6139
  const bodyFile = args.bodyFile ? String(args.bodyFile) : void 0;
5929
6140
  if (bodyFile) {
5930
- return { body: readFileSync7(bodyFile, "utf8"), bodyPathHint: bodyFile };
6141
+ return { body: readFileSync8(bodyFile, "utf8"), bodyPathHint: bodyFile };
5931
6142
  }
5932
6143
  const inline = args.body ? String(args.body) : void 0;
5933
6144
  if (inline) return { body: inline };
@@ -5997,12 +6208,14 @@ async function runPlanOutboxDrain(args) {
5997
6208
  // src/cleanup-cli.ts
5998
6209
  function runCleanupCli(args) {
5999
6210
  const execute = args.execute === true || args.execute === "true";
6211
+ const skipFinalize = args.skipFinalize === true || args.skipFinalize === "true";
6000
6212
  const nodeModulesAgeMs = args.nodeModulesAgeMs ? Number(args.nodeModulesAgeMs) : DEFAULT_NODE_MODULES_AGE_MS;
6001
6213
  const worktreesAgeMs = args.worktreesAgeMs ? Number(args.worktreesAgeMs) : 0;
6002
6214
  const includeOrphans = args.includeOrphans === true || args.includeOrphans === "true";
6003
6215
  const harnessRoot = args.harnessRoot ? String(args.harnessRoot) : void 0;
6004
6216
  const summary = runHarnessCleanup({
6005
6217
  execute,
6218
+ finalizeStaleRuns: !skipFinalize,
6006
6219
  nodeModulesAgeMs: Number.isFinite(nodeModulesAgeMs) ? nodeModulesAgeMs : DEFAULT_NODE_MODULES_AGE_MS,
6007
6220
  worktreesAgeMs: Number.isFinite(worktreesAgeMs) ? worktreesAgeMs : 0,
6008
6221
  includeOrphans,
@@ -6015,7 +6228,7 @@ function runCleanupCli(args) {
6015
6228
  }
6016
6229
 
6017
6230
  // src/monitor/monitor.service.ts
6018
- import path34 from "node:path";
6231
+ import path36 from "node:path";
6019
6232
 
6020
6233
  // src/monitor/monitor.classify.ts
6021
6234
  function expectedLeaseOwner(runId) {
@@ -6071,11 +6284,11 @@ function classifyWorkerHealth(input) {
6071
6284
  }
6072
6285
 
6073
6286
  // src/monitor/monitor.store.ts
6074
- import { existsSync as existsSync17, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
6075
- import path33 from "node:path";
6287
+ import { existsSync as existsSync18, mkdirSync as mkdirSync6, readdirSync as readdirSync7, unlinkSync as unlinkSync2 } from "node:fs";
6288
+ import path35 from "node:path";
6076
6289
  function monitorsDir() {
6077
6290
  const { harnessRoot } = getHarnessPaths();
6078
- const dir = path33.join(harnessRoot, "monitors");
6291
+ const dir = path35.join(harnessRoot, "monitors");
6079
6292
  mkdirSync6(dir, { recursive: true });
6080
6293
  return dir;
6081
6294
  }
@@ -6083,7 +6296,7 @@ function monitorIdFor(runId, workerName) {
6083
6296
  return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
6084
6297
  }
6085
6298
  function monitorPath(monitorId) {
6086
- return path33.join(monitorsDir(), `${monitorId}.json`);
6299
+ return path35.join(monitorsDir(), `${monitorId}.json`);
6087
6300
  }
6088
6301
  function loadMonitorSession(monitorId) {
6089
6302
  return readJson(monitorPath(monitorId), void 0);
@@ -6093,18 +6306,18 @@ function saveMonitorSession(session) {
6093
6306
  }
6094
6307
  function deleteMonitorSession(monitorId) {
6095
6308
  const file = monitorPath(monitorId);
6096
- if (!existsSync17(file)) return false;
6309
+ if (!existsSync18(file)) return false;
6097
6310
  unlinkSync2(file);
6098
6311
  return true;
6099
6312
  }
6100
6313
  function listMonitorSessions() {
6101
6314
  const dir = monitorsDir();
6102
- if (!existsSync17(dir)) return [];
6315
+ if (!existsSync18(dir)) return [];
6103
6316
  const entries = [];
6104
6317
  for (const name of readdirSync7(dir)) {
6105
6318
  if (!name.endsWith(".json")) continue;
6106
6319
  const session = readJson(
6107
- path33.join(dir, name),
6320
+ path35.join(dir, name),
6108
6321
  void 0
6109
6322
  );
6110
6323
  if (!session?.monitorId) continue;
@@ -6195,7 +6408,7 @@ async function fetchTaskLeasesForWorkers(input) {
6195
6408
  // src/monitor/monitor.service.ts
6196
6409
  function workerRecord2(runId, name) {
6197
6410
  return readJson(
6198
- path34.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
6411
+ path36.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
6199
6412
  void 0
6200
6413
  );
6201
6414
  }
@@ -6397,18 +6610,18 @@ async function runMonitorLoop(args) {
6397
6610
 
6398
6611
  // src/monitor/monitor-spawn.ts
6399
6612
  import { spawn as spawn4 } from "node:child_process";
6400
- import { closeSync as closeSync4, existsSync as existsSync18, openSync as openSync4 } from "node:fs";
6401
- import path35 from "node:path";
6402
- import { fileURLToPath as fileURLToPath2 } from "node:url";
6613
+ import { closeSync as closeSync4, existsSync as existsSync19, openSync as openSync4 } from "node:fs";
6614
+ import path37 from "node:path";
6615
+ import { fileURLToPath as fileURLToPath3 } from "node:url";
6403
6616
  function resolveDefaultCliPath2() {
6404
- return path35.join(fileURLToPath2(new URL(".", import.meta.url)), "..", "cli.js");
6617
+ return path37.join(fileURLToPath3(new URL(".", import.meta.url)), "..", "cli.js");
6405
6618
  }
6406
6619
  function spawnMonitorSidecar(opts) {
6407
6620
  const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
6408
- if (!existsSync18(cliPath)) return void 0;
6621
+ if (!existsSync19(cliPath)) return void 0;
6409
6622
  const monitorId = monitorIdFor(opts.runId, opts.workerName);
6410
6623
  const { harnessRoot } = getHarnessPaths();
6411
- const logPath = path35.join(harnessRoot, "monitors", `${monitorId}.log`);
6624
+ const logPath = path37.join(harnessRoot, "monitors", `${monitorId}.log`);
6412
6625
  let logFd;
6413
6626
  try {
6414
6627
  logFd = openSync4(logPath, "a");
@@ -6528,22 +6741,22 @@ async function monitorTickCli(args) {
6528
6741
  }
6529
6742
 
6530
6743
  // src/package-version.ts
6531
- import { existsSync as existsSync19, readFileSync as readFileSync8 } from "node:fs";
6744
+ import { existsSync as existsSync20, readFileSync as readFileSync9 } from "node:fs";
6532
6745
  import { dirname, join } from "node:path";
6533
- import { fileURLToPath as fileURLToPath3 } from "node:url";
6746
+ import { fileURLToPath as fileURLToPath4 } from "node:url";
6534
6747
  function resolvePackageRoot(moduleUrl) {
6535
- let dir = dirname(fileURLToPath3(moduleUrl));
6748
+ let dir = dirname(fileURLToPath4(moduleUrl));
6536
6749
  for (let depth = 0; depth < 6; depth += 1) {
6537
- if (existsSync19(join(dir, "package.json"))) return dir;
6750
+ if (existsSync20(join(dir, "package.json"))) return dir;
6538
6751
  const parent = dirname(dir);
6539
6752
  if (parent === dir) break;
6540
6753
  dir = parent;
6541
6754
  }
6542
- throw new Error(`package.json not found above ${dirname(fileURLToPath3(moduleUrl))}`);
6755
+ throw new Error(`package.json not found above ${dirname(fileURLToPath4(moduleUrl))}`);
6543
6756
  }
6544
6757
  function readOwnPackageVersion(moduleUrl = import.meta.url) {
6545
6758
  const pkgPath = join(resolvePackageRoot(moduleUrl), "package.json");
6546
- const pkg = JSON.parse(readFileSync8(pkgPath, "utf8"));
6759
+ const pkg = JSON.parse(readFileSync9(pkgPath, "utf8"));
6547
6760
  if (typeof pkg.version !== "string" || !pkg.version.trim()) {
6548
6761
  throw new Error(`Missing package.json version at ${pkgPath}`);
6549
6762
  }
@@ -6564,12 +6777,12 @@ function handleCliVersionFlag(argv, moduleUrl = import.meta.url, binName) {
6564
6777
  }
6565
6778
 
6566
6779
  // src/doctor/runtime-takeover.ts
6567
- import path37 from "node:path";
6780
+ import path39 from "node:path";
6568
6781
 
6569
6782
  // src/doctor/runtime-takeover.probes.ts
6570
- import { accessSync, constants, existsSync as existsSync20, readFileSync as readFileSync9 } from "node:fs";
6571
- import { homedir as homedir6 } from "node:os";
6572
- import path36 from "node:path";
6783
+ import { accessSync, constants, existsSync as existsSync21, readFileSync as readFileSync10 } from "node:fs";
6784
+ import { homedir as homedir7 } from "node:os";
6785
+ import path38 from "node:path";
6573
6786
  import { spawnSync as spawnSync6 } from "node:child_process";
6574
6787
  function captureCommand(bin, args) {
6575
6788
  try {
@@ -6598,7 +6811,7 @@ function tokenPrefix(token) {
6598
6811
  return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
6599
6812
  }
6600
6813
  function isWritable(target) {
6601
- if (!existsSync20(target)) return false;
6814
+ if (!existsSync21(target)) return false;
6602
6815
  try {
6603
6816
  accessSync(target, constants.W_OK);
6604
6817
  return true;
@@ -6611,15 +6824,15 @@ var defaultRuntimeTakeoverProbes = {
6611
6824
  commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
6612
6825
  kynverVersion: (bin) => captureCommand(bin, ["--version"]),
6613
6826
  loadConfig: () => loadUserConfig(),
6614
- configFilePath: () => path36.join(homedir6(), ".kynver", "config.json"),
6615
- credentialsFilePath: () => path36.join(homedir6(), ".kynver", "credentials"),
6827
+ configFilePath: () => path38.join(homedir7(), ".kynver", "config.json"),
6828
+ credentialsFilePath: () => path38.join(homedir7(), ".kynver", "credentials"),
6616
6829
  readCredentials: () => {
6617
- const credPath = path36.join(homedir6(), ".kynver", "credentials");
6618
- if (!existsSync20(credPath)) {
6830
+ const credPath = path38.join(homedir7(), ".kynver", "credentials");
6831
+ if (!existsSync21(credPath)) {
6619
6832
  return { hasApiKey: false };
6620
6833
  }
6621
6834
  try {
6622
- const parsed = JSON.parse(readFileSync9(credPath, "utf8"));
6835
+ const parsed = JSON.parse(readFileSync10(credPath, "utf8"));
6623
6836
  return {
6624
6837
  hasApiKey: Boolean(parsed.apiKey?.trim()),
6625
6838
  runnerTokenPrefix: tokenPrefix(parsed.runnerToken),
@@ -6645,8 +6858,8 @@ var defaultRuntimeTakeoverProbes = {
6645
6858
  })()
6646
6859
  }),
6647
6860
  harnessRoot: () => resolveHarnessRoot(),
6648
- legacyOpenclawHarnessRoot: () => path36.join(homedir6(), ".openclaw", "harness"),
6649
- pathExists: (target) => existsSync20(target),
6861
+ legacyOpenclawHarnessRoot: () => path38.join(homedir7(), ".openclaw", "harness"),
6862
+ pathExists: (target) => existsSync21(target),
6650
6863
  pathWritable: (target) => isWritable(target),
6651
6864
  vercelVersion: () => captureCommand("vercel", ["--version"]),
6652
6865
  vercelWhoami: () => captureCommand("vercel", ["whoami"])
@@ -6661,8 +6874,36 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
6661
6874
  const runnerQstash = env.kynverSchedulerProvider === "qstash";
6662
6875
  const hostedDeployment = Boolean(env.qstashTokenPresent) || Boolean(env.kynverHostedDeployment);
6663
6876
  const daemonDispatchReady = Boolean(ctx.agentOsId?.trim()) && Boolean(ctx.apiBaseUrl?.trim()) && ctx.hasScopedRunnerToken;
6664
- const deploymentNeedsQstash = hostedDeployment && !env.qstashTokenPresent && env.kynverSchedulerProvider !== "qstash";
6665
- const deploymentOpenclaw = hostedDeployment && env.kynverSchedulerProvider === "openclaw-cron";
6877
+ const hostedSchedulerProcess = hostedDeployment && !daemonDispatchReady;
6878
+ const deploymentNeedsQstash = hostedSchedulerProcess && !env.qstashTokenPresent && env.kynverSchedulerProvider !== "qstash";
6879
+ const deploymentOpenclaw = hostedSchedulerProcess && env.kynverSchedulerProvider === "openclaw-cron";
6880
+ if (daemonDispatchReady && runnerOpenclaw) {
6881
+ return check({
6882
+ id: "hotspot_openclaw_scheduler",
6883
+ label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
6884
+ status: "warn",
6885
+ summary: "KYNVER_SCHEDULER_PROVIDER=openclaw-cron on this runner \u2014 dispatch is owned by kynver daemon; unset the OpenClaw override",
6886
+ remediation: "Unset KYNVER_SCHEDULER_PROVIDER on user runners. Use `kynver daemon` (pipeline-tick \u2192 operator/tick). On the Kynver server set KYNVER_SCHEDULER_PROVIDER=qstash when QStash is configured.",
6887
+ details: {
6888
+ schedulerProvider: env.kynverSchedulerProvider ?? null,
6889
+ dispatchPath: "kynver-daemon-pipeline-tick",
6890
+ hostedDeployment
6891
+ }
6892
+ });
6893
+ }
6894
+ if (daemonDispatchReady) {
6895
+ return check({
6896
+ id: "hotspot_openclaw_scheduler",
6897
+ label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
6898
+ status: "pass",
6899
+ summary: runnerQstash ? "Runner override qstash present; hosted dispatch still owned by kynver daemon pipeline-tick" : "Hosted dispatch owned by kynver daemon (pipeline-tick \u2192 operator/tick); no OpenClaw cron on runner",
6900
+ details: {
6901
+ schedulerProvider: env.kynverSchedulerProvider ?? null,
6902
+ dispatchPath: "kynver-daemon-pipeline-tick",
6903
+ hostedDeployment
6904
+ }
6905
+ });
6906
+ }
6666
6907
  if (runnerOpenclaw) {
6667
6908
  return check({
6668
6909
  id: "hotspot_openclaw_scheduler",
@@ -6678,7 +6919,7 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
6678
6919
  id: "hotspot_openclaw_scheduler",
6679
6920
  label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
6680
6921
  status: "warn",
6681
- summary: deploymentOpenclaw ? "Hosted deployment has KYNVER_SCHEDULER_PROVIDER=openclaw-cron \u2014 AgentOS scheduled ticks should use QStash" : "Hosted deployment without QSTASH_TOKEN \u2014 scheduler may fall back to openclaw-cron",
6922
+ summary: deploymentOpenclaw ? "Hosted deployment has KYNVER_SCHEDULER_PROVIDER=openclaw-cron \u2014 AgentOS scheduled ticks should use QStash" : env.kynverSchedulerProvider ? `Hosted deployment without QSTASH_TOKEN (KYNVER_SCHEDULER_PROVIDER=${env.kynverSchedulerProvider}) \u2014 scheduler may fall back to openclaw-cron` : "KYNVER_SCHEDULER_PROVIDER unset on hosted deployment; without QSTASH_TOKEN the server may fall back to openclaw-cron",
6682
6923
  remediation: "Set QSTASH_TOKEN and KYNVER_SCHEDULER_PROVIDER=qstash on the Kynver server. User runners use `kynver daemon` for dispatch and should not set a scheduler provider.",
6683
6924
  details: {
6684
6925
  schedulerProvider: env.kynverSchedulerProvider ?? null,
@@ -6687,15 +6928,15 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
6687
6928
  }
6688
6929
  });
6689
6930
  }
6690
- if (daemonDispatchReady) {
6931
+ if (hostedDeployment && env.qstashTokenPresent && !env.kynverSchedulerProvider) {
6691
6932
  return check({
6692
6933
  id: "hotspot_openclaw_scheduler",
6693
6934
  label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
6694
6935
  status: "pass",
6695
- summary: 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",
6936
+ summary: "QSTASH_TOKEN present; hosted scheduler auto-selects qstash (explicit KYNVER_SCHEDULER_PROVIDER=qstash optional)",
6696
6937
  details: {
6697
- schedulerProvider: env.kynverSchedulerProvider ?? null,
6698
- dispatchPath: "kynver-daemon-pipeline-tick",
6938
+ schedulerProvider: null,
6939
+ qstashTokenPresent: true,
6699
6940
  hostedDeployment
6700
6941
  }
6701
6942
  });
@@ -6789,8 +7030,14 @@ function assessUserConfig(probes) {
6789
7030
  if (exists) {
6790
7031
  const apiBaseUrl = config.apiBaseUrl?.trim();
6791
7032
  const agentOsId = config.agentOsId?.trim();
6792
- const defaultRepo = config.defaultRepo?.trim();
6793
- const displayDefaultRepo = defaultRepo ? displayUserPath(defaultRepo) : null;
7033
+ const resolvedDefaultRepo = resolveDefaultRepo({ config });
7034
+ const formatted = resolvedDefaultRepo ? formatResolvedDefaultRepo(resolvedDefaultRepo) : null;
7035
+ let defaultRepoRemediation;
7036
+ if (!resolvedDefaultRepo) {
7037
+ defaultRepoRemediation = "Run `kynver setup` from a Kynver checkout (auto-discovers repo) or `kynver setup --repo /path/to/Kynver`.";
7038
+ } else if (!resolvedDefaultRepo.persistedInConfig) {
7039
+ defaultRepoRemediation = "Run `kynver setup` (no --repo) to persist discovered defaultRepo in ~/.kynver/config.json.";
7040
+ }
6794
7041
  checks.push(
6795
7042
  check2({
6796
7043
  id: "config_api_base_url",
@@ -6811,11 +7058,13 @@ function assessUserConfig(probes) {
6811
7058
  check2({
6812
7059
  id: "config_default_repo",
6813
7060
  label: "Default repo path",
6814
- status: defaultRepo ? "pass" : "warn",
6815
- summary: displayDefaultRepo ?? "Not set (pass --repo on `kynver run create`)",
6816
- remediation: defaultRepo ? void 0 : "Set `defaultRepo` via `kynver setup --repo /path/to/repo`.",
7061
+ status: resolvedDefaultRepo ? "pass" : "warn",
7062
+ summary: resolvedDefaultRepo ? `${formatted.defaultRepo} (${resolvedDefaultRepo.source}${resolvedDefaultRepo.persistedInConfig ? "" : ", not persisted"})` : "Not set (pass --repo on `kynver run create` or run `kynver setup` to auto-discover)",
7063
+ remediation: defaultRepoRemediation,
6817
7064
  details: {
6818
- defaultRepo: displayDefaultRepo,
7065
+ defaultRepo: formatted?.defaultRepo ?? null,
7066
+ source: formatted?.source ?? null,
7067
+ persistedInConfig: formatted?.persistedInConfig ?? false,
6819
7068
  harnessRoot: config.harnessRoot ? redactHomePath(config.harnessRoot) : null
6820
7069
  }
6821
7070
  })
@@ -6897,8 +7146,8 @@ function assessVercelCli(probes) {
6897
7146
  }
6898
7147
  function assessHarnessDirs(probes) {
6899
7148
  const harnessRoot = probes.harnessRoot();
6900
- const runsDir = path37.join(harnessRoot, "runs");
6901
- const worktreesDir = path37.join(harnessRoot, "worktrees");
7149
+ const runsDir = path39.join(harnessRoot, "runs");
7150
+ const worktreesDir = path39.join(harnessRoot, "worktrees");
6902
7151
  const displayHarnessRoot = redactHomePath(harnessRoot);
6903
7152
  const displayRunsDir = redactHomePath(runsDir);
6904
7153
  const displayWorktreesDir = redactHomePath(worktreesDir);
@@ -7101,9 +7350,9 @@ function usage(code = 0) {
7101
7350
  "Usage:",
7102
7351
  " kynver login --api-key KEY",
7103
7352
  " kynver runner credential [--agent-os-id ID] [--base-url URL]",
7104
- " kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--max-workers N] [--provider claude|cursor]",
7353
+ " kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--discover-repo] [--max-workers N] [--provider claude|cursor]",
7105
7354
  " kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS]",
7106
- " kynver run create --repo /path/repo [--name name] [--base origin/main]",
7355
+ " kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
7107
7356
  " kynver run list",
7108
7357
  " kynver run status --run RUN_ID",
7109
7358
  " kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--target-task-id TASK_ID] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-8] [--disk-path /]",
@@ -7121,7 +7370,7 @@ function usage(code = 0) {
7121
7370
  " kynver plan persist --operation create|add_version|update_metadata --title TITLE (--body-file PATH | --body TEXT) [--slug SLUG] [--plan PLAN_ID] [--summary TEXT] [--failure-kind approval_guard|auth|network|server|tool_interruption]",
7122
7371
  " kynver plan outbox list",
7123
7372
  " kynver plan outbox drain [--max N] [--id OUTBOX_ID]",
7124
- " kynver cleanup [--execute] [--node-modules-age-ms MS] [--worktrees-age-ms MS] [--harness-root PATH] [--include-orphans]",
7373
+ " kynver cleanup [--execute] [--node-modules-age-ms MS] [--worktrees-age-ms MS] [--harness-root PATH] [--include-orphans] [--skip-finalize]",
7125
7374
  " kynver monitor start --run RUN_ID [--name worker] [--agent-os-id AOS_ID] [--poll-ms MS]",
7126
7375
  " kynver monitor status [--run RUN_ID] [--name worker] [--tick]",
7127
7376
  " kynver monitor stop --run RUN_ID [--name worker]",
@@ -7196,7 +7445,7 @@ async function main(argv = process.argv.slice(2)) {
7196
7445
  if (scope === "monitor" && action === "run-loop") return void await monitorRunLoopCli(args);
7197
7446
  unknownCommand(scope, action);
7198
7447
  }
7199
- var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(fileURLToPath4(import.meta.url));
7448
+ var isCliEntry = process.argv[1] && realpathSync.native(process.argv[1]) === realpathSync.native(fileURLToPath5(import.meta.url));
7200
7449
  if (isCliEntry) {
7201
7450
  void main().catch((error) => {
7202
7451
  console.error(error);