@kynver-app/runtime 0.1.120 → 0.1.122

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
@@ -523,23 +523,23 @@ function isWslHost() {
523
523
  function observeWslHostDisk(options = {}) {
524
524
  const wsl = options.forceWsl === void 0 ? isWslHost() : options.forceWsl;
525
525
  if (!wsl) return null;
526
- const path77 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
526
+ const path79 = options.wslHostMount?.trim() || process.env.KYNVER_WSL_HOST_MOUNT?.trim() || DEFAULT_WSL_HOST_MOUNT;
527
527
  const warnBelowBytes = options.wslHostFreeWarnBytes ?? DEFAULT_WSL_HOST_WARN_FREE_BYTES;
528
528
  const criticalBelowBytes = options.wslHostFreeCriticalBytes ?? DEFAULT_WSL_HOST_CRITICAL_FREE_BYTES;
529
529
  const statfs = options.statfs ?? statfsSync;
530
530
  let stats;
531
531
  try {
532
- stats = statfs(path77);
532
+ stats = statfs(path79);
533
533
  } catch (error) {
534
534
  return {
535
535
  ok: false,
536
- path: path77,
536
+ path: path79,
537
537
  freeBytes: 0,
538
538
  totalBytes: 0,
539
539
  usedPercent: 100,
540
540
  warnBelowBytes,
541
541
  criticalBelowBytes,
542
- reason: `Windows host disk probe failed at ${path77}: ${error.message}`,
542
+ reason: `Windows host disk probe failed at ${path79}: ${error.message}`,
543
543
  probeError: error.message
544
544
  };
545
545
  }
@@ -553,11 +553,11 @@ function observeWslHostDisk(options = {}) {
553
553
  let reason = null;
554
554
  if (!ok) {
555
555
  const tag = criticalFree ? "critical" : "warning";
556
- reason = `Windows host disk ${path77} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
556
+ reason = `Windows host disk ${path79} at ${tag}: ${freeGiB} GiB free (<${(criticalFree ? criticalBelowBytes : warnBelowBytes) / 1024 / 1024 / 1024} GiB); WSL VHDX cannot grow safely. ${summarizeWslRecoverySteps()}`;
557
557
  }
558
558
  return {
559
559
  ok,
560
- path: path77,
560
+ path: path79,
561
561
  freeBytes,
562
562
  totalBytes,
563
563
  usedPercent,
@@ -583,12 +583,12 @@ var init_wsl_host = __esm({
583
583
  // src/disk-gate.ts
584
584
  import { statfsSync as statfsSync2 } from "node:fs";
585
585
  function observeRunnerDiskGate(input = {}) {
586
- const path77 = input.diskPath?.trim() || "/";
586
+ const path79 = input.diskPath?.trim() || "/";
587
587
  const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
588
588
  const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
589
589
  const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
590
590
  const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
591
- const stats = statfsSync2(path77);
591
+ const stats = statfsSync2(path79);
592
592
  const freeBytes = Number(stats.bavail) * Number(stats.bsize);
593
593
  const totalBytes = Number(stats.blocks) * Number(stats.bsize);
594
594
  const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
@@ -611,7 +611,7 @@ function observeRunnerDiskGate(input = {}) {
611
611
  }
612
612
  return {
613
613
  ok,
614
- path: path77,
614
+ path: path79,
615
615
  freeBytes,
616
616
  totalBytes,
617
617
  usedPercent,
@@ -2616,23 +2616,29 @@ function resolveConfiguredCallbackSecret(argsSecret, agentOsId) {
2616
2616
  }
2617
2617
  return void 0;
2618
2618
  }
2619
- async function resolveCallbackSecretWithMint(argsSecret, agentOsId, opts) {
2619
+ async function tryResolveCallbackSecretWithMint(argsSecret, agentOsId, opts) {
2620
2620
  const configured = resolveConfiguredCallbackSecret(argsSecret, agentOsId);
2621
- if (configured) return configured;
2621
+ if (configured) return { ok: true, secret: configured };
2622
2622
  const apiKey = loadApiKey();
2623
2623
  const baseUrl = resolveConfiguredBaseUrl(opts?.baseUrl);
2624
2624
  if (apiKey && agentOsId && baseUrl) {
2625
2625
  try {
2626
2626
  const token = await fetchRunnerCredential(agentOsId, { baseUrl, apiKey });
2627
2627
  saveRunnerToken(agentOsId, token);
2628
- return token;
2628
+ return { ok: true, secret: token };
2629
2629
  } catch (error) {
2630
- failConfig(`runner credential mint failed: ${error.message}`);
2630
+ return { ok: false, reason: `runner credential mint failed: ${error.message}` };
2631
2631
  }
2632
2632
  }
2633
- failConfig(
2634
- "requires --secret, KYNVER_RUNNER_TOKEN, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, KYNVER_API_KEY with an API base URL to mint one, or (legacy) KYNVER_RUNTIME_SECRET / KYNVER_CRON_SECRET / OPENCLAW_CRON_SECRET"
2635
- );
2633
+ return {
2634
+ ok: false,
2635
+ reason: "no runner credential: requires --secret, KYNVER_RUNNER_TOKEN, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, or KYNVER_API_KEY + API base URL to mint one"
2636
+ };
2637
+ }
2638
+ async function resolveCallbackSecretWithMint(argsSecret, agentOsId, opts) {
2639
+ const result = await tryResolveCallbackSecretWithMint(argsSecret, agentOsId, opts);
2640
+ if (result.ok) return result.secret;
2641
+ failConfig(result.reason);
2636
2642
  }
2637
2643
  async function refreshRunnerToken(agentOsId, opts) {
2638
2644
  const apiKey = loadApiKey();
@@ -2752,7 +2758,10 @@ async function runSetup(args) {
2752
2758
  ...existing,
2753
2759
  ...inferSetupFields(existing, args),
2754
2760
  ...workerConfig,
2755
- workerProvider: typeof args.provider === "string" ? args.provider : existing.workerProvider || "cursor"
2761
+ workerProvider: typeof args.provider === "string" ? args.provider : existing.workerProvider || "cursor",
2762
+ // M5.4 chat-turn delegation: `--chat-oauth` persists consent to use the
2763
+ // Claude Code CLI's local OAuth token for delegated chat turns.
2764
+ ...args.chatOauth === true ? { chatUseClaudeOauth: true } : {}
2756
2765
  });
2757
2766
  saveUserConfig(config);
2758
2767
  const boxIdentity = resolveBoxIdentity(process.env, config);
@@ -2820,7 +2829,7 @@ var init_config = __esm({
2820
2829
 
2821
2830
  // src/cli.ts
2822
2831
  init_config();
2823
- import { mkdirSync as mkdirSync13, realpathSync } from "node:fs";
2832
+ import { mkdirSync as mkdirSync15, realpathSync } from "node:fs";
2824
2833
  import { fileURLToPath as fileURLToPath5 } from "node:url";
2825
2834
 
2826
2835
  // src/bootstrap.ts
@@ -3508,7 +3517,7 @@ async function runKynverCronTick(opts = {}) {
3508
3517
  init_util();
3509
3518
 
3510
3519
  // src/pipeline-tick.ts
3511
- import path55 from "node:path";
3520
+ import path57 from "node:path";
3512
3521
 
3513
3522
  // src/active-harness-workers.ts
3514
3523
  init_run_store();
@@ -6176,8 +6185,8 @@ function dirtyPathsCoveredByDisposableRemoval(changedFiles, removed) {
6176
6185
  if (removed.length === 0) return false;
6177
6186
  const removedSet = new Set(removed.map((p) => normalizeRelativePath(p)));
6178
6187
  return material.every((line) => {
6179
- const path77 = normalizeRelativePath(pathFromGitStatusLine(line));
6180
- return removedSet.has(path77);
6188
+ const path79 = normalizeRelativePath(pathFromGitStatusLine(line));
6189
+ return removedSet.has(path79);
6181
6190
  });
6182
6191
  }
6183
6192
 
@@ -7013,6 +7022,9 @@ function buildBoard(run, workers, compact) {
7013
7022
  }
7014
7023
  return board;
7015
7024
  }
7025
+ function isTerminalDoneBoardWorker(worker) {
7026
+ return worker.status === "done" && worker.attention === "done";
7027
+ }
7016
7028
  function buildRunBoard(runId) {
7017
7029
  const run = loadRun(runId);
7018
7030
  const names = Object.keys(run.workers || {});
@@ -7033,7 +7045,7 @@ function buildRunBoard(runId) {
7033
7045
  function buildCompactRunBoard(runId) {
7034
7046
  const run = loadRun(runId);
7035
7047
  const names = Object.keys(run.workers || {});
7036
- const workers = names.map((name) => {
7048
+ const allWorkers = names.map((name) => {
7037
7049
  const worker = readJson(
7038
7050
  path23.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
7039
7051
  void 0
@@ -7044,7 +7056,17 @@ function buildCompactRunBoard(runId) {
7044
7056
  if (isMetadataTerminalDone(worker)) return buildCompactDoneEntry(name, worker);
7045
7057
  return buildWorkerBoardEntry({ run, workerName: name, worker });
7046
7058
  });
7059
+ const workers = allWorkers.filter((worker) => !isTerminalDoneBoardWorker(worker));
7047
7060
  const board = buildBoard(run, workers, true);
7061
+ board.status = deriveRunStatus(run.status, allWorkers);
7062
+ board.summary = {
7063
+ ...board.summary ?? {},
7064
+ totalWorkerCount: allWorkers.length,
7065
+ shownWorkerCount: workers.length,
7066
+ omittedTerminalDoneWorkerCount: allWorkers.length - workers.length,
7067
+ allStatusCounts: countBy(allWorkers, "status"),
7068
+ allAttentionCounts: countBy(allWorkers, "attention")
7069
+ };
7048
7070
  writeJson(path23.join(runDirectory(run.id), "last-board-compact.json"), board);
7049
7071
  return board;
7050
7072
  }
@@ -9251,7 +9273,7 @@ init_util();
9251
9273
  // src/worker-metadata-reconcile.ts
9252
9274
  init_heartbeat();
9253
9275
  init_stream();
9254
- import { existsSync as existsSync25, lstatSync, readdirSync as readdirSync7, readlinkSync, renameSync as renameSync3, rmSync } from "node:fs";
9276
+ import { existsSync as existsSync25, lstatSync, readdirSync as readdirSync7, readlinkSync, renameSync as renameSync4, rmSync } from "node:fs";
9255
9277
  import path33 from "node:path";
9256
9278
 
9257
9279
  // src/worker-metadata-paths.ts
@@ -9300,7 +9322,7 @@ function resolveWorkerJsonPath(input) {
9300
9322
  }
9301
9323
 
9302
9324
  // src/run-metadata-retention.ts
9303
- import { existsSync as existsSync24, readdirSync as readdirSync6, statSync as statSync5 } from "node:fs";
9325
+ import { existsSync as existsSync24, mkdirSync as mkdirSync7, readdirSync as readdirSync6, renameSync as renameSync3, statSync as statSync5 } from "node:fs";
9304
9326
  import path32 from "node:path";
9305
9327
 
9306
9328
  // src/default-repo.ts
@@ -9362,6 +9384,7 @@ init_paths();
9362
9384
  init_run_store();
9363
9385
  init_util();
9364
9386
  var RUN_METADATA_ACTIVE_SIGNAL_MS = 15 * 60 * 1e3;
9387
+ var TERMINAL_WORKER_ARCHIVE_AGE_MS = 60 * 60 * 1e3;
9365
9388
  function isHarnessRunMetadataPath(targetPath, harnessRoot) {
9366
9389
  const resolved = path32.resolve(targetPath);
9367
9390
  const runsDir = path32.resolve(harnessRunsDir(harnessRoot));
@@ -9385,6 +9408,14 @@ function listWorkerNamesOnDisk(runDir2) {
9385
9408
  return [];
9386
9409
  }
9387
9410
  }
9411
+ function workerArchiveDir(runDir2) {
9412
+ return path32.join(runDir2, "archived-workers");
9413
+ }
9414
+ function uniqueArchivePath(runDir2, workerName) {
9415
+ const base = path32.join(workerArchiveDir(runDir2), safeSlug(workerName));
9416
+ if (!existsSync24(base)) return base;
9417
+ return `${base}-${Date.now()}`;
9418
+ }
9388
9419
  function pathRecentlyTouched(target, now, windowMs) {
9389
9420
  if (!existsSync24(target)) return false;
9390
9421
  try {
@@ -9411,6 +9442,41 @@ function workerDirHasActiveRetentionSignals(workerDir, now = Date.now(), windowM
9411
9442
  if (pathRecentlyTouched(artifacts.heartbeatPath, now, windowMs)) return true;
9412
9443
  return false;
9413
9444
  }
9445
+ function workerTerminalArchiveAgeMs(workerDir, worker, now) {
9446
+ const completionReportedAt = typeof worker.completionReportedAt === "string" ? Date.parse(worker.completionReportedAt) : NaN;
9447
+ if (Number.isFinite(completionReportedAt)) return now - completionReportedAt;
9448
+ const completionSnapshotAt = (() => {
9449
+ const raw = worker.completionResponse;
9450
+ if (!raw || typeof raw !== "object") return NaN;
9451
+ const task = raw.task;
9452
+ if (!task || typeof task !== "object") return NaN;
9453
+ const updatedAt = task.updatedAt;
9454
+ return typeof updatedAt === "string" ? Date.parse(updatedAt) : NaN;
9455
+ })();
9456
+ if (Number.isFinite(completionSnapshotAt)) return now - completionSnapshotAt;
9457
+ try {
9458
+ return now - statSync5(path32.join(workerDir, "worker.json")).mtimeMs;
9459
+ } catch {
9460
+ return 0;
9461
+ }
9462
+ }
9463
+ function isArchiveEligibleTerminalWorker(workerDir, worker, now, archiveAgeMs) {
9464
+ if (workerDirHasActiveRetentionSignals(workerDir, now)) {
9465
+ return { eligible: false, reason: "worker has active/recent retention signals" };
9466
+ }
9467
+ const status = typeof worker.status === "string" ? worker.status : "";
9468
+ const completionBlocker = typeof worker.completionBlocker === "string" && worker.completionBlocker.trim().length > 0;
9469
+ if (completionBlocker) return { eligible: false, reason: "worker has completion blocker" };
9470
+ const completionAcknowledged = typeof worker.completionReportedAt === "string" || worker.completionOutcome === "acknowledged";
9471
+ if (status !== "done" && !completionAcknowledged) {
9472
+ return { eligible: false, reason: `worker status is ${status || "unknown"}` };
9473
+ }
9474
+ const age = workerTerminalArchiveAgeMs(workerDir, worker, now);
9475
+ if (!Number.isFinite(age) || age < archiveAgeMs) {
9476
+ return { eligible: false, reason: "terminal worker is still within archive grace window" };
9477
+ }
9478
+ return { eligible: true, reason: "terminal worker metadata archived" };
9479
+ }
9414
9480
  function runDirHasActiveRetentionSignals(runDir2, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
9415
9481
  for (const name of listWorkerNamesOnDisk(runDir2)) {
9416
9482
  if (workerDirHasActiveRetentionSignals(path32.join(runDir2, "workers", name), now, windowMs)) {
@@ -9488,6 +9554,59 @@ function repairMissingRunMetadata(harnessRootInput) {
9488
9554
  }
9489
9555
  return { runs: outcomes };
9490
9556
  }
9557
+ function archiveTerminalWorkerMetadata(harnessRootInput, options = {}) {
9558
+ const harnessRoot = normalizeHarnessRoot(harnessRootInput ?? resolveHarnessRoot());
9559
+ const runsDir = harnessRunsDir(harnessRoot);
9560
+ const now = options.now ?? Date.now();
9561
+ const archiveAgeMs = options.archiveAgeMs ?? TERMINAL_WORKER_ARCHIVE_AGE_MS;
9562
+ const outcomes = [];
9563
+ repairMissingRunMetadata(harnessRoot);
9564
+ for (const runId of listRunDirIds(runsDir)) {
9565
+ const runDir2 = path32.join(runsDir, runId);
9566
+ const runJsonPath = path32.join(runDir2, "run.json");
9567
+ const run = readJson(runJsonPath, void 0);
9568
+ if (!run?.id) continue;
9569
+ const names = /* @__PURE__ */ new Set([...Object.keys(run.workers || {}), ...listWorkerNamesOnDisk(runDir2)]);
9570
+ let changed = false;
9571
+ for (const name of names) {
9572
+ const workerDir = path32.join(runDir2, "workers", safeSlug(name));
9573
+ const workerJsonPath = path32.join(workerDir, "worker.json");
9574
+ const worker = readJson(workerJsonPath, void 0);
9575
+ if (!worker) {
9576
+ const archivedWorkerDir = path32.join(workerArchiveDir(runDir2), safeSlug(name));
9577
+ if (existsSync24(archivedWorkerDir) && (run.workers[safeSlug(name)] || run.workers[name])) {
9578
+ delete run.workers[safeSlug(name)];
9579
+ delete run.workers[name];
9580
+ changed = true;
9581
+ outcomes.push({
9582
+ runId: run.id,
9583
+ worker: name,
9584
+ action: "archived",
9585
+ reason: "removed stale live index entry for archived worker metadata",
9586
+ archivePath: archivedWorkerDir
9587
+ });
9588
+ continue;
9589
+ }
9590
+ outcomes.push({ runId: run.id, worker: name, action: "skipped", reason: "worker.json missing" });
9591
+ continue;
9592
+ }
9593
+ const eligibility = isArchiveEligibleTerminalWorker(workerDir, worker, now, archiveAgeMs);
9594
+ if (!eligibility.eligible) {
9595
+ outcomes.push({ runId: run.id, worker: name, action: "skipped", reason: eligibility.reason });
9596
+ continue;
9597
+ }
9598
+ const archivePath = uniqueArchivePath(runDir2, name);
9599
+ mkdirSync7(path32.dirname(archivePath), { recursive: true });
9600
+ renameSync3(workerDir, archivePath);
9601
+ delete run.workers[safeSlug(name)];
9602
+ delete run.workers[name];
9603
+ changed = true;
9604
+ outcomes.push({ runId: run.id, worker: name, action: "archived", reason: eligibility.reason, archivePath });
9605
+ }
9606
+ if (changed) saveRun(run);
9607
+ }
9608
+ return { workers: outcomes };
9609
+ }
9491
9610
  function collectFilesystemLiveRunKeys(harnessRoot, now = Date.now()) {
9492
9611
  const keys = /* @__PURE__ */ new Set();
9493
9612
  const runsDir = harnessRunsDir(harnessRoot);
@@ -9517,9 +9636,9 @@ function materializeSymlinkedRunDir(harnessRoot, runId) {
9517
9636
  return null;
9518
9637
  }
9519
9638
  const staging = `${canonical}.materialize-${Date.now()}`;
9520
- renameSync3(linkedTarget, staging);
9639
+ renameSync4(linkedTarget, staging);
9521
9640
  rmSync(canonical);
9522
- renameSync3(staging, canonical);
9641
+ renameSync4(staging, canonical);
9523
9642
  return linkedTarget;
9524
9643
  }
9525
9644
  function relocateArtifacts(input) {
@@ -9528,7 +9647,7 @@ function relocateArtifacts(input) {
9528
9647
  const from = path33.join(input.fromDir, fileName);
9529
9648
  const to = path33.join(input.toDir, fileName);
9530
9649
  if (!existsSync25(from) || existsSync25(to)) continue;
9531
- renameSync3(from, to);
9650
+ renameSync4(from, to);
9532
9651
  moved.push(fileName);
9533
9652
  }
9534
9653
  return moved;
@@ -9986,7 +10105,14 @@ function reconcileStaleWorkers() {
9986
10105
  const metadataReconcile = reconcileWorkerMetadata();
9987
10106
  if (staleReconcileDisabled()) {
9988
10107
  const localPrAttentionReconcile2 = reconcileLocalOnlyMergedPrAttention();
9989
- return { workers: [], finalizedRuns: finalizeStaleRuns(), metadataReconcile, localPrAttentionReconcile: localPrAttentionReconcile2 };
10108
+ const terminalWorkerArchive2 = archiveTerminalWorkerMetadata();
10109
+ return {
10110
+ workers: [],
10111
+ finalizedRuns: finalizeStaleRuns(),
10112
+ metadataReconcile,
10113
+ localPrAttentionReconcile: localPrAttentionReconcile2,
10114
+ terminalWorkerArchive: terminalWorkerArchive2
10115
+ };
9990
10116
  }
9991
10117
  const outcomes = [];
9992
10118
  const now = Date.now();
@@ -10065,7 +10191,8 @@ function reconcileStaleWorkers() {
10065
10191
  }
10066
10192
  }
10067
10193
  const localPrAttentionReconcile = reconcileLocalOnlyMergedPrAttention();
10068
- return { workers: outcomes, finalizedRuns: finalizeStaleRuns(), metadataReconcile, localPrAttentionReconcile };
10194
+ const terminalWorkerArchive = archiveTerminalWorkerMetadata();
10195
+ return { workers: outcomes, finalizedRuns: finalizeStaleRuns(), metadataReconcile, localPrAttentionReconcile, terminalWorkerArchive };
10069
10196
  }
10070
10197
  function reconcileRunsCli() {
10071
10198
  const result = reconcileStaleWorkers();
@@ -10084,6 +10211,10 @@ function reconcileRunsCli() {
10084
10211
  acc[row.action] = (acc[row.action] ?? 0) + 1;
10085
10212
  return acc;
10086
10213
  }, {});
10214
+ const archiveTotals = result.terminalWorkerArchive.workers.reduce((acc, row) => {
10215
+ acc[row.action] = (acc[row.action] ?? 0) + 1;
10216
+ return acc;
10217
+ }, {});
10087
10218
  console.log(
10088
10219
  JSON.stringify(
10089
10220
  {
@@ -10101,11 +10232,16 @@ function reconcileRunsCli() {
10101
10232
  totals: localPrAttentionTotals,
10102
10233
  total: result.localPrAttentionReconcile.workers.length
10103
10234
  },
10235
+ terminalWorkerArchive: {
10236
+ totals: archiveTotals,
10237
+ total: result.terminalWorkerArchive.workers.length
10238
+ },
10104
10239
  finalizedRuns: result.finalizedRuns.length,
10105
10240
  details: {
10106
10241
  workers: result.workers,
10107
10242
  metadataReconcile: result.metadataReconcile.workers,
10108
10243
  localPrAttentionReconcile: result.localPrAttentionReconcile.workers,
10244
+ terminalWorkerArchive: result.terminalWorkerArchive.workers,
10109
10245
  runMetadataRetention: result.metadataReconcile.runMetadataRetention.runs,
10110
10246
  finalizedRuns: result.finalizedRuns
10111
10247
  }
@@ -10312,11 +10448,11 @@ init_box_identity();
10312
10448
 
10313
10449
  // src/cleanup.ts
10314
10450
  init_paths();
10315
- import path51 from "node:path";
10451
+ import path53 from "node:path";
10316
10452
 
10317
10453
  // src/cleanup-guards.ts
10318
10454
  init_landing_gate();
10319
- import path38 from "node:path";
10455
+ import path39 from "node:path";
10320
10456
 
10321
10457
  // src/cleanup-index-status.ts
10322
10458
  init_git();
@@ -10459,6 +10595,44 @@ init_status();
10459
10595
  // src/cleanup-run-liveness.ts
10460
10596
  init_status();
10461
10597
 
10598
+ // src/cleanup-worker-harness-live.ts
10599
+ init_heartbeat();
10600
+ init_util();
10601
+ function heartbeatContentIsFresh(worker, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
10602
+ const heartbeat = parseHeartbeat(worker.heartbeatPath);
10603
+ if (!heartbeat.lastHeartbeatAt) return false;
10604
+ const age = now - Date.parse(heartbeat.lastHeartbeatAt);
10605
+ return Number.isFinite(age) && age >= 0 && age < windowMs;
10606
+ }
10607
+ function isHarnessWorkerHarnessLive(worker, now = Date.now(), windowMs = RUN_METADATA_ACTIVE_SIGNAL_MS) {
10608
+ if (isPidAlive(worker.pid)) return true;
10609
+ return heartbeatContentIsFresh(worker, now, windowMs);
10610
+ }
10611
+
10612
+ // src/cleanup-run-liveness.ts
10613
+ function deriveRunTerminal(indexed, ctx) {
10614
+ if (ctx) return ctx.runTerminalCache.derive(indexed.run);
10615
+ return deriveTerminalRunStatus(indexed.run);
10616
+ }
10617
+ function isWorkerProcessLive(indexed, now = Date.now()) {
10618
+ return isHarnessWorkerHarnessLive(indexed.worker, now);
10619
+ }
10620
+ function isRunStaleActive(indexed, ctx) {
10621
+ if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
10622
+ return deriveRunTerminal(indexed, ctx) !== null;
10623
+ }
10624
+ function runBlocksWorktreeRemoval(indexed, ctx, now = Date.now()) {
10625
+ if (isWorkerProcessLive(indexed, now)) return true;
10626
+ if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
10627
+ if (isRunStaleActive(indexed, ctx)) return false;
10628
+ const status = indexedWorktreeStatus(indexed);
10629
+ if (!isFinishedWorkerStatus(status)) {
10630
+ if (!isWorkerProcessLive(indexed, now)) return false;
10631
+ return true;
10632
+ }
10633
+ return false;
10634
+ }
10635
+
10462
10636
  // src/cleanup-completion-blocker.ts
10463
10637
  init_landing_gate();
10464
10638
  init_status();
@@ -10480,51 +10654,80 @@ function completionBlockerBlocksWorktreeRemoval(indexed, status) {
10480
10654
  return false;
10481
10655
  }
10482
10656
 
10483
- // src/cleanup-run-liveness.ts
10657
+ // src/cleanup-salvage-evidence.ts
10658
+ init_git();
10484
10659
  init_util();
10485
- var TERMINAL_WORKER_JSON_STATUSES = /* @__PURE__ */ new Set([
10486
- "done",
10487
- "exited",
10488
- "blocked",
10489
- "failed",
10490
- "abandoned"
10491
- ]);
10492
- function deriveRunTerminal(indexed, ctx) {
10493
- if (ctx) return ctx.runTerminalCache.derive(indexed.run);
10494
- return deriveTerminalRunStatus(indexed.run);
10495
- }
10496
- function isWorkerProcessLive(indexed) {
10497
- if (isPidAlive(indexed.worker.pid)) return true;
10498
- if (typeof indexed.worker.completionReportedAt === "string" && indexed.worker.completionReportedAt.trim().length > 0) {
10499
- return false;
10500
- }
10501
- const workerStatus2 = indexed.worker.status;
10502
- if (workerStatus2 && TERMINAL_WORKER_JSON_STATUSES.has(workerStatus2)) return false;
10503
- if (!indexed.worker.pid) {
10504
- if (workerStatus2 !== "running") return false;
10505
- return indexedWorktreeStatus(indexed).alive;
10506
- }
10507
- return false;
10660
+ import { existsSync as existsSync26, mkdirSync as mkdirSync8, writeFileSync as writeFileSync6 } from "node:fs";
10661
+ import path38 from "node:path";
10662
+ function salvageEvidenceDir(harnessRoot, runId, workerName) {
10663
+ return path38.join(harnessRoot, "salvage", safeSlug(runId), safeSlug(workerName));
10508
10664
  }
10509
- function isRunStaleActive(indexed, ctx) {
10510
- if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
10511
- return deriveRunTerminal(indexed, ctx) !== null;
10665
+ function hasSalvageEvidence(input) {
10666
+ return existsSync26(path38.join(salvageEvidenceDir(input.harnessRoot, input.runId, input.workerName), "evidence.json"));
10512
10667
  }
10513
- function runBlocksWorktreeRemoval(indexed, ctx) {
10514
- if (isWorkerProcessLive(indexed)) return true;
10515
- const workerStatus2 = indexed.worker.status;
10516
- if (workerStatus2 && TERMINAL_WORKER_JSON_STATUSES.has(workerStatus2) && !indexed.worker.completionBlocker) {
10517
- return false;
10518
- }
10519
- const status = indexedWorktreeStatus(indexed);
10520
- if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return true;
10521
- if (isFinishedWorkerStatus(status)) return false;
10522
- if (TERMINAL_RUN_STATUSES.has(indexed.run.status)) return false;
10523
- if (isRunStaleActive(indexed, ctx)) return false;
10524
- return deriveRunTerminal(indexed, ctx) === null;
10668
+ function writeWorktreeSalvageEvidence(input) {
10669
+ const dir = salvageEvidenceDir(input.harnessRoot, input.indexed.runId, input.indexed.workerName);
10670
+ mkdirSync8(dir, { recursive: true });
10671
+ const patchPath = path38.join(dir, "salvage.patch");
10672
+ let wrotePatch = false;
10673
+ if (existsSync26(input.indexed.worktreePath)) {
10674
+ const diff = gitCapture(input.indexed.worktreePath, ["diff", "HEAD"]);
10675
+ const staged = gitCapture(input.indexed.worktreePath, ["diff", "--cached"]);
10676
+ const patch = [diff.stdout, staged.stdout].filter((chunk) => chunk.trim()).join("\n");
10677
+ if (patch.trim()) {
10678
+ writeFileSync6(patchPath, patch.endsWith("\n") ? patch : `${patch}
10679
+ `);
10680
+ wrotePatch = true;
10681
+ }
10682
+ }
10683
+ const record = {
10684
+ capturedAt: new Date(input.now ?? Date.now()).toISOString(),
10685
+ skipReason: input.skipReason,
10686
+ runId: input.indexed.runId,
10687
+ workerName: input.indexed.workerName,
10688
+ worktreePath: input.indexed.worktreePath,
10689
+ taskId: input.indexed.worker.taskId,
10690
+ agentOsId: input.indexed.worker.agentOsId,
10691
+ branch: input.indexed.worker.branch,
10692
+ completionBlocker: input.indexed.worker.completionBlocker,
10693
+ finalResult: input.status.finalResult,
10694
+ changedFiles: input.status.changedFiles,
10695
+ gitAncestry: input.status.gitAncestry,
10696
+ prUrl: input.status.prUrl ?? null,
10697
+ ...wrotePatch ? { patchPath } : {}
10698
+ };
10699
+ writeFileSync6(path38.join(dir, "evidence.json"), `${JSON.stringify(record, null, 2)}
10700
+ `);
10701
+ return record;
10525
10702
  }
10526
10703
 
10527
10704
  // src/cleanup-guards.ts
10705
+ var SALVAGE_ELIGIBLE_SKIP_REASONS = /* @__PURE__ */ new Set([
10706
+ "completion_blocked",
10707
+ "dirty_worktree",
10708
+ "pr_or_unmerged_commits",
10709
+ "landing_blocked"
10710
+ ]);
10711
+ function maybeSalvageBlockedWorktree(input) {
10712
+ if (!SALVAGE_ELIGIBLE_SKIP_REASONS.has(input.skipReason)) return false;
10713
+ if (isWorkerProcessLive(input.indexed, input.now)) return false;
10714
+ if (hasSalvageEvidence({
10715
+ harnessRoot: input.harnessRoot,
10716
+ runId: input.indexed.runId,
10717
+ workerName: input.indexed.workerName
10718
+ })) {
10719
+ return true;
10720
+ }
10721
+ if (!input.writeSalvageEvidence) return false;
10722
+ writeWorktreeSalvageEvidence({
10723
+ harnessRoot: input.harnessRoot,
10724
+ indexed: input.indexed,
10725
+ skipReason: input.skipReason,
10726
+ status: input.status,
10727
+ now: input.now
10728
+ });
10729
+ return true;
10730
+ }
10528
10731
  function effectiveWorktreeAgeMs(input) {
10529
10732
  const { indexed, includeOrphans, worktreesAgeMs, terminalWorktreesAgeMs } = input;
10530
10733
  if (!indexed) return includeOrphans ? terminalWorktreesAgeMs : worktreesAgeMs;
@@ -10539,8 +10742,18 @@ function effectiveWorktreeAgeMs(input) {
10539
10742
  }
10540
10743
  return worktreesAgeMs;
10541
10744
  }
10745
+ function resolveHarnessRoot2(input, indexed) {
10746
+ if (input.harnessRoot?.trim()) return path39.resolve(input.harnessRoot);
10747
+ const workerDir = indexed.worker.workerDir;
10748
+ if (!workerDir) return null;
10749
+ const runsMarker = `${path39.sep}runs${path39.sep}`;
10750
+ const idx = workerDir.indexOf(runsMarker);
10751
+ if (idx < 0) return null;
10752
+ return workerDir.slice(0, idx + runsMarker.length - 1);
10753
+ }
10542
10754
  function skipWorktreeRemoval(input) {
10543
10755
  const { indexed, includeOrphans, worktreesAgeMs, ageMs, orphanSafety, worktreeRemovalGuard } = input;
10756
+ const now = input.now ?? Date.now();
10544
10757
  if (!indexed) {
10545
10758
  if (!includeOrphans) return "orphan_without_flag";
10546
10759
  return orphanSafety ?? null;
@@ -10548,25 +10761,55 @@ function skipWorktreeRemoval(input) {
10548
10761
  const ageThresholdMs = effectiveWorktreeAgeMs(input);
10549
10762
  if (worktreesAgeMs <= 0 && !includeOrphans && ageThresholdMs <= 0) return "worktrees_disabled";
10550
10763
  if (ageThresholdMs > 0 && ageMs < ageThresholdMs) return "below_age_threshold";
10551
- if (isWorkerProcessLive(indexed)) return "active_worker";
10764
+ if (isWorkerProcessLive(indexed, now)) return "active_worker";
10765
+ const status = resolveWorktreeGuardStatus(indexed, input.liveness);
10766
+ const salvageHarnessRoot = resolveHarnessRoot2(input, indexed);
10767
+ const salvageOrBlock = (skipReason) => {
10768
+ if (salvageHarnessRoot && maybeSalvageBlockedWorktree({
10769
+ indexed,
10770
+ harnessRoot: salvageHarnessRoot,
10771
+ skipReason,
10772
+ status,
10773
+ now,
10774
+ writeSalvageEvidence: input.writeSalvageEvidence === true
10775
+ })) {
10776
+ return null;
10777
+ }
10778
+ return skipReason;
10779
+ };
10780
+ if (completionBlockerBlocksWorktreeRemoval(indexed, status)) {
10781
+ const blocked = salvageOrBlock("completion_blocked");
10782
+ if (blocked) return blocked;
10783
+ }
10552
10784
  if (indexedWorktreeHasMaterialChanges(indexed, input.liveness?.gitStatusCache)) {
10553
- return "dirty_worktree";
10785
+ const blocked = salvageOrBlock("dirty_worktree");
10786
+ if (blocked) return blocked;
10554
10787
  }
10555
10788
  const ahead = input.liveness?.gitRevCache?.countAheadOfMain(input.worktreePath);
10556
- if (ahead !== null && ahead !== void 0 && ahead > 0) return "pr_or_unmerged_commits";
10557
- const status = resolveWorktreeGuardStatus(indexed, input.liveness);
10558
- if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return "completion_blocked";
10559
- if (runBlocksWorktreeRemoval(indexed, input.liveness)) return "run_still_active";
10789
+ if (ahead !== null && ahead !== void 0 && ahead > 0) {
10790
+ const blocked = salvageOrBlock("pr_or_unmerged_commits");
10791
+ if (blocked) return blocked;
10792
+ }
10793
+ if (runBlocksWorktreeRemoval(indexed, input.liveness, now)) return "run_still_active";
10560
10794
  if (!isFinishedWorkerStatus(status)) return "run_still_active";
10561
- if (isPrOrUnmergedWork(status)) return "pr_or_unmerged_commits";
10562
- if (materialWorktreeChanges2(status.changedFiles).length > 0) return "dirty_worktree";
10795
+ if (isPrOrUnmergedWork(status)) {
10796
+ const blocked = salvageOrBlock("pr_or_unmerged_commits");
10797
+ if (blocked) return blocked;
10798
+ }
10799
+ if (materialWorktreeChanges2(status.changedFiles).length > 0) {
10800
+ const blocked = salvageOrBlock("dirty_worktree");
10801
+ if (blocked) return blocked;
10802
+ }
10563
10803
  const landing = assessWorkerLanding({
10564
10804
  finalResult: status.finalResult,
10565
10805
  changedFiles: status.changedFiles,
10566
10806
  gitAncestry: status.gitAncestry,
10567
10807
  prUrl: prUrlFromFinalResult(status.finalResult)
10568
10808
  });
10569
- if (landing.blocked) return "landing_blocked";
10809
+ if (landing.blocked) {
10810
+ const blocked = salvageOrBlock("landing_blocked");
10811
+ if (blocked) return blocked;
10812
+ }
10570
10813
  if (worktreeRemovalGuard && input.worktreePath) {
10571
10814
  const overlay = worktreeRemovalGuard({
10572
10815
  worktreePath: input.worktreePath,
@@ -10583,7 +10826,7 @@ function skipWorktreeRemoval(input) {
10583
10826
  function skipDependencyCacheRemoval(input) {
10584
10827
  const { indexed, nodeModulesAgeMs, ageMs, worktreePath, activeWorktreePaths, diskPressure } = input;
10585
10828
  if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
10586
- if (activeWorktreePaths.has(path38.resolve(worktreePath))) return "active_worker";
10829
+ if (activeWorktreePaths.has(path39.resolve(worktreePath))) return "active_worker";
10587
10830
  if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
10588
10831
  if (indexed && indexedWorktreeHasMaterialChanges(indexed, input.gitStatusCache)) {
10589
10832
  return "dirty_worktree";
@@ -10612,11 +10855,11 @@ var LIVE_SKIP_REASONS = /* @__PURE__ */ new Set([
10612
10855
  function collectPreservedLivePaths(actions, skips) {
10613
10856
  const out = [];
10614
10857
  const seen = /* @__PURE__ */ new Set();
10615
- const push = (path77, reason, detail) => {
10616
- const key = `${path77}\0${reason}`;
10858
+ const push = (path79, reason, detail) => {
10859
+ const key = `${path79}\0${reason}`;
10617
10860
  if (seen.has(key) || out.length >= MAX_PRESERVED_LIVE_PATH_SAMPLES) return;
10618
10861
  seen.add(key);
10619
- out.push({ path: path77, reason, ...detail ? { detail } : {} });
10862
+ out.push({ path: path79, reason, ...detail ? { detail } : {} });
10620
10863
  };
10621
10864
  for (const skip2 of skips) {
10622
10865
  if (!LIVE_SKIP_REASONS.has(skip2.reason)) continue;
@@ -10631,31 +10874,16 @@ function collectPreservedLivePaths(actions, skips) {
10631
10874
  }
10632
10875
 
10633
10876
  // src/cleanup-run-directory.ts
10634
- import { existsSync as existsSync27, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
10635
- import path40 from "node:path";
10877
+ import { existsSync as existsSync27, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
10878
+ import path41 from "node:path";
10636
10879
 
10637
10880
  // src/cleanup-active-worktrees.ts
10638
10881
  init_run_store();
10639
10882
  init_paths();
10640
- import { existsSync as existsSync26, readdirSync as readdirSync8, statSync as statSync6 } from "node:fs";
10641
- import path39 from "node:path";
10883
+ import path40 from "node:path";
10642
10884
  init_util();
10643
- function workerHasRecentHarnessActivity(worker, now) {
10644
- const paths = [worker.heartbeatPath, worker.stdoutPath, worker.stderrPath];
10645
- for (const target of paths) {
10646
- if (!existsSync26(target)) continue;
10647
- try {
10648
- const age = now - statSync6(target).mtimeMs;
10649
- if (Number.isFinite(age) && age >= 0 && age < RUN_METADATA_ACTIVE_SIGNAL_MS) return true;
10650
- } catch {
10651
- }
10652
- }
10653
- return false;
10654
- }
10655
10885
  function isActiveHarnessWorker2(worker, now) {
10656
- if (isPidAlive(worker.pid)) return true;
10657
- if (worker.status === "running" && workerHasRecentHarnessActivity(worker, now)) return true;
10658
- return false;
10886
+ return isHarnessWorkerHarnessLive(worker, now);
10659
10887
  }
10660
10888
  function collectActiveWorktreeGuards(harnessRoots, now = Date.now()) {
10661
10889
  const activeWorktreePaths = /* @__PURE__ */ new Set();
@@ -10665,11 +10893,11 @@ function collectActiveWorktreeGuards(harnessRoots, now = Date.now()) {
10665
10893
  let runHasLive = false;
10666
10894
  for (const name of Object.keys(run.workers || {})) {
10667
10895
  const worker = readJson(
10668
- path39.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
10896
+ path40.join(runDirectoryAt(harnessRoot, run.id), "workers", safeSlug(name), "worker.json"),
10669
10897
  void 0
10670
10898
  );
10671
10899
  if (!worker?.worktreePath) continue;
10672
- const worktreePath = path39.resolve(worker.worktreePath);
10900
+ const worktreePath = path40.resolve(worker.worktreePath);
10673
10901
  if (!isActiveHarnessWorker2(worker, now)) continue;
10674
10902
  runHasLive = true;
10675
10903
  activeWorktreePaths.add(worktreePath);
@@ -10691,20 +10919,20 @@ function isWorktreeOnLiveRun(worktreePath, harnessRoot, runId, liveRunKeys) {
10691
10919
  init_util();
10692
10920
  function pathAgeMs(target, now) {
10693
10921
  try {
10694
- const mtime = statSync7(target).mtimeMs;
10922
+ const mtime = statSync6(target).mtimeMs;
10695
10923
  return Math.max(0, now - mtime);
10696
10924
  } catch {
10697
10925
  return 0;
10698
10926
  }
10699
10927
  }
10700
10928
  function loadRunStatus(harnessRoot, runId) {
10701
- const runPath = path40.join(harnessRoot, "runs", runId, "run.json");
10929
+ const runPath = path41.join(harnessRoot, "runs", runId, "run.json");
10702
10930
  if (!existsSync27(runPath)) return null;
10703
10931
  return readJson(runPath, null);
10704
10932
  }
10705
10933
  function runDirectoryIsEmpty(runPath) {
10706
10934
  try {
10707
- const entries = readdirSync9(runPath);
10935
+ const entries = readdirSync8(runPath);
10708
10936
  return entries.length === 0;
10709
10937
  } catch {
10710
10938
  return false;
@@ -10728,7 +10956,7 @@ function scanStaleRunDirectoryCandidates(opts) {
10728
10956
  const candidates = [];
10729
10957
  let entries;
10730
10958
  try {
10731
- entries = readdirSync9(opts.worktreesDir, { withFileTypes: true });
10959
+ entries = readdirSync8(opts.worktreesDir, { withFileTypes: true });
10732
10960
  } catch {
10733
10961
  return [];
10734
10962
  }
@@ -10736,7 +10964,7 @@ function scanStaleRunDirectoryCandidates(opts) {
10736
10964
  if (!runEntry.isDirectory()) continue;
10737
10965
  const runId = runEntry.name;
10738
10966
  if (opts.runIdFilter && runId !== opts.runIdFilter) continue;
10739
- const runPath = path40.join(opts.worktreesDir, runId);
10967
+ const runPath = path41.join(opts.worktreesDir, runId);
10740
10968
  if (!runDirectoryIsEmpty(runPath)) continue;
10741
10969
  candidates.push({
10742
10970
  kind: "remove_run_directory",
@@ -10756,8 +10984,8 @@ import { existsSync as existsSync30, rmSync as rmSync3 } from "node:fs";
10756
10984
 
10757
10985
  // src/cleanup-dir-size.ts
10758
10986
  import { execFileSync as execFileSync2 } from "node:child_process";
10759
- import { existsSync as existsSync28, readdirSync as readdirSync10, statSync as statSync8 } from "node:fs";
10760
- import path41 from "node:path";
10987
+ import { existsSync as existsSync28, readdirSync as readdirSync9, statSync as statSync7 } from "node:fs";
10988
+ import path42 from "node:path";
10761
10989
  var DEFAULT_DU_TIMEOUT_MS = 2500;
10762
10990
  function directorySizeBytesDu(root, timeoutMs = DEFAULT_DU_TIMEOUT_MS) {
10763
10991
  if (!existsSync28(root)) return 0;
@@ -10785,16 +11013,16 @@ function directorySizeBytes(root, maxEntries = 5e4) {
10785
11013
  const current = stack.pop();
10786
11014
  let entries;
10787
11015
  try {
10788
- entries = readdirSync10(current);
11016
+ entries = readdirSync9(current);
10789
11017
  } catch {
10790
11018
  continue;
10791
11019
  }
10792
11020
  for (const name of entries) {
10793
11021
  if (seen++ > maxEntries) return null;
10794
- const full = path41.join(current, name);
11022
+ const full = path42.join(current, name);
10795
11023
  let st;
10796
11024
  try {
10797
- st = statSync8(full);
11025
+ st = statSync7(full);
10798
11026
  } catch {
10799
11027
  continue;
10800
11028
  }
@@ -10810,7 +11038,7 @@ import { existsSync as existsSync29, rmSync as rmSync2 } from "node:fs";
10810
11038
  init_paths();
10811
11039
 
10812
11040
  // src/cleanup-path-ownership.ts
10813
- import { lstatSync as lstatSync2, readdirSync as readdirSync11 } from "node:fs";
11041
+ import { lstatSync as lstatSync2, readdirSync as readdirSync10 } from "node:fs";
10814
11042
  function readPathOwnership(targetPath) {
10815
11043
  try {
10816
11044
  const st = lstatSync2(targetPath);
@@ -10827,7 +11055,7 @@ function pathHasForeignOwnedEntry(targetPath, maxEntries = 32) {
10827
11055
  if (!root) return false;
10828
11056
  if (root.foreign) return true;
10829
11057
  try {
10830
- const names = readdirSync11(targetPath);
11058
+ const names = readdirSync10(targetPath);
10831
11059
  let checked = 0;
10832
11060
  for (const name of names) {
10833
11061
  if (checked >= maxEntries) break;
@@ -10843,20 +11071,20 @@ function pathHasForeignOwnedEntry(targetPath, maxEntries = 32) {
10843
11071
 
10844
11072
  // src/cleanup-privileged-remove.ts
10845
11073
  import { spawnSync as spawnSync6 } from "node:child_process";
10846
- import path43 from "node:path";
11074
+ import path44 from "node:path";
10847
11075
 
10848
11076
  // src/cleanup-harness-path-validate.ts
10849
- import path42 from "node:path";
11077
+ import path43 from "node:path";
10850
11078
  function isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, cacheDirName) {
10851
- const resolved = path42.resolve(targetPath);
10852
- const suffix = `${path42.sep}${cacheDirName}`;
11079
+ const resolved = path43.resolve(targetPath);
11080
+ const suffix = `${path43.sep}${cacheDirName}`;
10853
11081
  const cachePath = resolved.endsWith(suffix) ? resolved : null;
10854
11082
  if (!cachePath) return "path_outside_harness";
10855
- const rel = path42.relative(worktreesDir, cachePath);
10856
- if (rel.startsWith("..") || path42.isAbsolute(rel)) return "path_outside_harness";
10857
- const parts = rel.split(path42.sep);
11083
+ const rel = path43.relative(worktreesDir, cachePath);
11084
+ if (rel.startsWith("..") || path43.isAbsolute(rel)) return "path_outside_harness";
11085
+ const parts = rel.split(path43.sep);
10858
11086
  if (parts.length < 3 || parts[parts.length - 1] !== cacheDirName) return "path_outside_harness";
10859
- if (!resolved.startsWith(path42.resolve(harnessRoot))) return "path_outside_harness";
11087
+ if (!resolved.startsWith(path43.resolve(harnessRoot))) return "path_outside_harness";
10860
11088
  return null;
10861
11089
  }
10862
11090
  function isHarnessNodeModulesPath(targetPath, harnessRoot, worktreesDir) {
@@ -10866,16 +11094,16 @@ function isHarnessNextCachePath(targetPath, harnessRoot, worktreesDir) {
10866
11094
  return isHarnessDependencyCachePath(targetPath, harnessRoot, worktreesDir, ".next");
10867
11095
  }
10868
11096
  function isHarnessBuildCachePath(targetPath, harnessRoot, worktreesDir) {
10869
- const resolved = path42.resolve(targetPath);
10870
- const relToWt = path42.relative(worktreesDir, resolved);
10871
- if (relToWt.startsWith("..") || path42.isAbsolute(relToWt)) return "path_outside_harness";
10872
- const parts = relToWt.split(path42.sep);
11097
+ const resolved = path43.resolve(targetPath);
11098
+ const relToWt = path43.relative(worktreesDir, resolved);
11099
+ if (relToWt.startsWith("..") || path43.isAbsolute(relToWt)) return "path_outside_harness";
11100
+ const parts = relToWt.split(path43.sep);
10873
11101
  if (parts.length < 3) return "path_outside_harness";
10874
- if (!resolved.startsWith(path42.resolve(harnessRoot))) return "path_outside_harness";
11102
+ if (!resolved.startsWith(path43.resolve(harnessRoot))) return "path_outside_harness";
10875
11103
  return null;
10876
11104
  }
10877
11105
  function isHarnessGeneratedCachePath(targetPath, harnessRoot, worktreesDir) {
10878
- const resolved = path42.resolve(targetPath);
11106
+ const resolved = path43.resolve(targetPath);
10879
11107
  return isHarnessNodeModulesPath(resolved, harnessRoot, worktreesDir) === null || isHarnessNextCachePath(resolved, harnessRoot, worktreesDir) === null || isHarnessBuildCachePath(resolved, harnessRoot, worktreesDir) === null;
10880
11108
  }
10881
11109
 
@@ -10909,12 +11137,12 @@ function tryPrivilegedReclaimHarnessCache(targetPath, harnessRoot, worktreesDir)
10909
11137
  "chown",
10910
11138
  "-R",
10911
11139
  `${effectiveUid}:${effectiveGid}`,
10912
- path43.resolve(targetPath)
11140
+ path44.resolve(targetPath)
10913
11141
  ]);
10914
11142
  if (chown.ok) {
10915
11143
  return { ok: true, method: "chown_then_rm" };
10916
11144
  }
10917
- const rm = runSudoNonInteractive(["rm", "-rf", path43.resolve(targetPath)]);
11145
+ const rm = runSudoNonInteractive(["rm", "-rf", path44.resolve(targetPath)]);
10918
11146
  if (rm.ok) {
10919
11147
  return { ok: true, method: "sudo_rm" };
10920
11148
  }
@@ -11115,27 +11343,27 @@ function removeWorktree(candidate, execute) {
11115
11343
  }
11116
11344
 
11117
11345
  // src/cleanup-scan.ts
11118
- import { existsSync as existsSync31, readdirSync as readdirSync12, statSync as statSync9 } from "node:fs";
11119
- import path44 from "node:path";
11346
+ import { existsSync as existsSync31, readdirSync as readdirSync11, statSync as statSync8 } from "node:fs";
11347
+ import path45 from "node:path";
11120
11348
  function pathAgeMs2(target, now) {
11121
11349
  try {
11122
- const mtime = statSync9(target).mtimeMs;
11350
+ const mtime = statSync8(target).mtimeMs;
11123
11351
  return Math.max(0, now - mtime);
11124
11352
  } catch {
11125
11353
  return 0;
11126
11354
  }
11127
11355
  }
11128
11356
  function isPathInside(child, parent) {
11129
- const rel = path44.relative(parent, child);
11130
- return rel === "" || !rel.startsWith("..") && !path44.isAbsolute(rel);
11357
+ const rel = path45.relative(parent, child);
11358
+ return rel === "" || !rel.startsWith("..") && !path45.isAbsolute(rel);
11131
11359
  }
11132
11360
  function collectBuildCacheForWorktree(worktreePath, opts, seen, meta) {
11133
11361
  const out = [];
11134
11362
  for (const rel of HARNESS_BUILD_CACHE_RELATIVE_PATHS) {
11135
11363
  if (rel === ".next") continue;
11136
- const target = path44.join(worktreePath, rel);
11364
+ const target = path45.join(worktreePath, rel);
11137
11365
  if (!existsSync31(target)) continue;
11138
- const resolved = path44.resolve(target);
11366
+ const resolved = path45.resolve(target);
11139
11367
  if (seen.has(resolved)) continue;
11140
11368
  if (!isPathInside(resolved, opts.harnessRoot)) continue;
11141
11369
  seen.add(resolved);
@@ -11165,12 +11393,12 @@ function scanBuildCacheCandidates(opts) {
11165
11393
  );
11166
11394
  }
11167
11395
  if (!opts.includeOrphans || !existsSync31(opts.worktreesDir)) return candidates;
11168
- for (const runEntry of readdirSync12(opts.worktreesDir, { withFileTypes: true })) {
11396
+ for (const runEntry of readdirSync11(opts.worktreesDir, { withFileTypes: true })) {
11169
11397
  if (!runEntry.isDirectory()) continue;
11170
- const runPath = path44.join(opts.worktreesDir, runEntry.name);
11171
- for (const workerEntry of readdirSync12(runPath, { withFileTypes: true })) {
11398
+ const runPath = path45.join(opts.worktreesDir, runEntry.name);
11399
+ for (const workerEntry of readdirSync11(runPath, { withFileTypes: true })) {
11172
11400
  if (!workerEntry.isDirectory()) continue;
11173
- const worktreePath = path44.join(runPath, workerEntry.name);
11401
+ const worktreePath = path45.join(runPath, workerEntry.name);
11174
11402
  candidates.push(
11175
11403
  ...collectBuildCacheForWorktree(worktreePath, opts, seen, {
11176
11404
  runId: runEntry.name,
@@ -11208,21 +11436,21 @@ function scanWorktreeCandidates(opts) {
11208
11436
  if (!orphanEnabled || !existsSync31(opts.worktreesDir)) return candidates;
11209
11437
  const indexedPaths = /* @__PURE__ */ new Set();
11210
11438
  for (const entry of opts.index.values()) {
11211
- indexedPaths.add(path44.resolve(entry.worktreePath));
11439
+ indexedPaths.add(path45.resolve(entry.worktreePath));
11212
11440
  }
11213
- for (const runEntry of readdirSync12(opts.worktreesDir, { withFileTypes: true })) {
11441
+ for (const runEntry of readdirSync11(opts.worktreesDir, { withFileTypes: true })) {
11214
11442
  if (!runEntry.isDirectory()) continue;
11215
11443
  if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
11216
- const runPath = path44.join(opts.worktreesDir, runEntry.name);
11444
+ const runPath = path45.join(opts.worktreesDir, runEntry.name);
11217
11445
  let workerEntries;
11218
11446
  try {
11219
- workerEntries = readdirSync12(runPath, { withFileTypes: true });
11447
+ workerEntries = readdirSync11(runPath, { withFileTypes: true });
11220
11448
  } catch {
11221
11449
  continue;
11222
11450
  }
11223
11451
  for (const workerEntry of workerEntries) {
11224
11452
  if (!workerEntry.isDirectory()) continue;
11225
- const worktreePath = path44.resolve(path44.join(runPath, workerEntry.name));
11453
+ const worktreePath = path45.resolve(path45.join(runPath, workerEntry.name));
11226
11454
  if (seen.has(worktreePath)) continue;
11227
11455
  if (indexedPaths.has(worktreePath)) continue;
11228
11456
  if (!isPathInside(worktreePath, opts.harnessRoot)) continue;
@@ -11241,27 +11469,27 @@ function scanWorktreeCandidates(opts) {
11241
11469
  }
11242
11470
 
11243
11471
  // src/cleanup-dependency-scan.ts
11244
- import { existsSync as existsSync32, readdirSync as readdirSync13, statSync as statSync10 } from "node:fs";
11245
- import path45 from "node:path";
11472
+ import { existsSync as existsSync32, readdirSync as readdirSync12, statSync as statSync9 } from "node:fs";
11473
+ import path46 from "node:path";
11246
11474
  var DEPENDENCY_CACHE_DIRS = [
11247
11475
  { dirName: "node_modules", kind: "remove_node_modules" },
11248
11476
  { dirName: ".next", kind: "remove_next_cache" }
11249
11477
  ];
11250
11478
  function pathAgeMs3(target, now) {
11251
11479
  try {
11252
- const mtime = statSync10(target).mtimeMs;
11480
+ const mtime = statSync9(target).mtimeMs;
11253
11481
  return Math.max(0, now - mtime);
11254
11482
  } catch {
11255
11483
  return 0;
11256
11484
  }
11257
11485
  }
11258
11486
  function isPathInside2(child, parent) {
11259
- const rel = path45.relative(parent, child);
11260
- return rel === "" || !rel.startsWith("..") && !path45.isAbsolute(rel);
11487
+ const rel = path46.relative(parent, child);
11488
+ return rel === "" || !rel.startsWith("..") && !path46.isAbsolute(rel);
11261
11489
  }
11262
11490
  function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
11263
11491
  if (!existsSync32(targetPath)) return;
11264
- const resolved = path45.resolve(targetPath);
11492
+ const resolved = path46.resolve(targetPath);
11265
11493
  if (seen.has(resolved)) return;
11266
11494
  if (!isPathInside2(resolved, opts.harnessRoot)) return;
11267
11495
  seen.add(resolved);
@@ -11278,7 +11506,7 @@ function pushCandidate2(candidates, seen, opts, targetPath, kind, meta) {
11278
11506
  }
11279
11507
  function scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, meta) {
11280
11508
  for (const entry of DEPENDENCY_CACHE_DIRS) {
11281
- pushCandidate2(candidates, seen, opts, path45.join(worktreePath, entry.dirName), entry.kind, meta);
11509
+ pushCandidate2(candidates, seen, opts, path46.join(worktreePath, entry.dirName), entry.kind, meta);
11282
11510
  }
11283
11511
  }
11284
11512
  function scanDependencyCacheCandidates(opts) {
@@ -11293,19 +11521,19 @@ function scanDependencyCacheCandidates(opts) {
11293
11521
  });
11294
11522
  }
11295
11523
  if (!opts.includeOrphans || !existsSync32(opts.worktreesDir)) return candidates;
11296
- for (const runEntry of readdirSync13(opts.worktreesDir, { withFileTypes: true })) {
11524
+ for (const runEntry of readdirSync12(opts.worktreesDir, { withFileTypes: true })) {
11297
11525
  if (!runEntry.isDirectory()) continue;
11298
11526
  if (opts.runIdFilter && runEntry.name !== opts.runIdFilter) continue;
11299
- const runPath = path45.join(opts.worktreesDir, runEntry.name);
11527
+ const runPath = path46.join(opts.worktreesDir, runEntry.name);
11300
11528
  let workerEntries;
11301
11529
  try {
11302
- workerEntries = readdirSync13(runPath, { withFileTypes: true });
11530
+ workerEntries = readdirSync12(runPath, { withFileTypes: true });
11303
11531
  } catch {
11304
11532
  continue;
11305
11533
  }
11306
11534
  for (const workerEntry of workerEntries) {
11307
11535
  if (!workerEntry.isDirectory()) continue;
11308
- const worktreePath = path45.join(runPath, workerEntry.name);
11536
+ const worktreePath = path46.join(runPath, workerEntry.name);
11309
11537
  scanWorktreeDependencyCaches(candidates, seen, opts, worktreePath, {
11310
11538
  runId: runEntry.name,
11311
11539
  worker: workerEntry.name
@@ -11317,11 +11545,11 @@ function scanDependencyCacheCandidates(opts) {
11317
11545
 
11318
11546
  // src/cleanup-duplicate-worktrees.ts
11319
11547
  init_git();
11320
- import { existsSync as existsSync33, statSync as statSync11 } from "node:fs";
11321
- import path46 from "node:path";
11548
+ import { existsSync as existsSync33, statSync as statSync10 } from "node:fs";
11549
+ import path47 from "node:path";
11322
11550
  function pathAgeMs4(target, now) {
11323
11551
  try {
11324
- const mtime = statSync11(target).mtimeMs;
11552
+ const mtime = statSync10(target).mtimeMs;
11325
11553
  return Math.max(0, now - mtime);
11326
11554
  } catch {
11327
11555
  return 0;
@@ -11348,32 +11576,24 @@ function parseWorktreePorcelain(output) {
11348
11576
  return records;
11349
11577
  }
11350
11578
  function isUnderWorktreesDir(worktreePath, worktreesDir) {
11351
- const rel = path46.relative(path46.resolve(worktreesDir), path46.resolve(worktreePath));
11352
- return rel !== "" && !rel.startsWith("..") && !path46.isAbsolute(rel);
11353
- }
11354
- function isCleanWorktree(worktreePath, repoRoot) {
11355
- try {
11356
- const porcelain = git(repoRoot, ["-C", worktreePath, "status", "--porcelain"], {
11357
- allowFailure: true
11358
- });
11359
- return !String(porcelain || "").trim();
11360
- } catch {
11361
- return false;
11362
- }
11579
+ const rel = path47.relative(path47.resolve(worktreesDir), path47.resolve(worktreePath));
11580
+ return rel !== "" && !rel.startsWith("..") && !path47.isAbsolute(rel);
11363
11581
  }
11582
+ var MAX_DUPLICATE_WORKTREE_CANDIDATES_PER_REPO = 200;
11364
11583
  function scanDuplicateWorktreeCandidates(opts) {
11365
11584
  if (!opts.includeOrphans || !existsSync33(opts.worktreesDir)) return [];
11366
11585
  const repos = /* @__PURE__ */ new Set();
11367
11586
  for (const entry of opts.index.values()) {
11368
- if (entry.run.repo) repos.add(path46.resolve(entry.run.repo));
11587
+ if (entry.run.repo) repos.add(path47.resolve(entry.run.repo));
11369
11588
  }
11370
11589
  const indexedPaths = /* @__PURE__ */ new Set();
11371
11590
  for (const entry of opts.index.values()) {
11372
- indexedPaths.add(path46.resolve(entry.worktreePath));
11591
+ indexedPaths.add(path47.resolve(entry.worktreePath));
11373
11592
  }
11374
11593
  const candidates = [];
11375
11594
  const seen = /* @__PURE__ */ new Set();
11376
11595
  for (const repoRoot of repos) {
11596
+ let repoCandidateCount = 0;
11377
11597
  let porcelain;
11378
11598
  try {
11379
11599
  porcelain = git(repoRoot, ["worktree", "list", "--porcelain"], { allowFailure: true });
@@ -11382,18 +11602,19 @@ function scanDuplicateWorktreeCandidates(opts) {
11382
11602
  }
11383
11603
  const worktrees = parseWorktreePorcelain(porcelain);
11384
11604
  for (const wt of worktrees) {
11385
- const resolved = path46.resolve(wt.path);
11386
- if (resolved === path46.resolve(repoRoot)) continue;
11605
+ if (repoCandidateCount >= MAX_DUPLICATE_WORKTREE_CANDIDATES_PER_REPO) break;
11606
+ const resolved = path47.resolve(wt.path);
11607
+ if (resolved === path47.resolve(repoRoot)) continue;
11387
11608
  if (!isUnderWorktreesDir(resolved, opts.worktreesDir)) continue;
11388
11609
  if (indexedPaths.has(resolved)) continue;
11389
11610
  if (seen.has(resolved)) continue;
11390
11611
  if (!existsSync33(resolved)) continue;
11391
- if (!isCleanWorktree(resolved, repoRoot)) continue;
11392
- const rel = path46.relative(opts.worktreesDir, resolved);
11393
- const parts = rel.split(path46.sep);
11612
+ const rel = path47.relative(opts.worktreesDir, resolved);
11613
+ const parts = rel.split(path47.sep);
11394
11614
  const runId = parts[0];
11395
11615
  const worker = parts[1] ?? "unknown";
11396
11616
  seen.add(resolved);
11617
+ repoCandidateCount += 1;
11397
11618
  candidates.push({
11398
11619
  kind: "remove_worktree",
11399
11620
  path: resolved,
@@ -11411,12 +11632,22 @@ function scanDuplicateWorktreeCandidates(opts) {
11411
11632
  // src/cleanup-worktree-index.ts
11412
11633
  init_run_store();
11413
11634
  init_util();
11414
- import path47 from "node:path";
11635
+ import path48 from "node:path";
11636
+ function filterWorktreeIndexForRoot(index, harnessRoot) {
11637
+ const resolvedRoot = path48.resolve(harnessRoot);
11638
+ const scoped = /* @__PURE__ */ new Map();
11639
+ for (const [key, entry] of index) {
11640
+ const entryRoot = entry.harnessRoot ? path48.resolve(entry.harnessRoot) : null;
11641
+ if (entryRoot && entryRoot !== resolvedRoot) continue;
11642
+ scoped.set(key, entry);
11643
+ }
11644
+ return scoped;
11645
+ }
11415
11646
  function buildWorktreeIndexAt(harnessRoot) {
11416
11647
  const index = /* @__PURE__ */ new Map();
11417
11648
  for (const run of listRunRecordsForHarnessRoot(harnessRoot)) {
11418
11649
  for (const name of Object.keys(run.workers || {})) {
11419
- const workerPath = path47.join(
11650
+ const workerPath = path48.join(
11420
11651
  runDirectoryAt(harnessRoot, run.id),
11421
11652
  "workers",
11422
11653
  safeSlug(name),
@@ -11424,9 +11655,9 @@ function buildWorktreeIndexAt(harnessRoot) {
11424
11655
  );
11425
11656
  const worker = readJson(workerPath, void 0);
11426
11657
  if (!worker?.worktreePath) continue;
11427
- index.set(path47.resolve(worker.worktreePath), {
11658
+ index.set(path48.resolve(worker.worktreePath), {
11428
11659
  harnessRoot,
11429
- worktreePath: path47.resolve(worker.worktreePath),
11660
+ worktreePath: path48.resolve(worker.worktreePath),
11430
11661
  runId: run.id,
11431
11662
  workerName: name,
11432
11663
  run,
@@ -11452,6 +11683,7 @@ function resolveHarnessRetention(options = {}) {
11452
11683
  const execute = options.execute === true || options.execute !== false && envFlag3("KYNVER_CLEANUP_EXECUTE");
11453
11684
  const finalizeStaleRuns2 = options.finalizeStaleRuns !== false && !envFlag3("KYNVER_CLEANUP_SKIP_FINALIZE");
11454
11685
  const nodeModulesAgeMs = options.nodeModulesAgeMs ?? envMs("KYNVER_CLEANUP_NODE_MODULES_AGE_MS", DEFAULT_NODE_MODULES_AGE_MS);
11686
+ const scanDependencyCaches = options.scanDependencyCaches ?? (options.nodeModulesAgeMs !== void 0 || process.env.KYNVER_CLEANUP_NODE_MODULES_AGE_MS != null);
11455
11687
  const worktreesAgeMs = options.worktreesAgeMs ?? envMs("KYNVER_CLEANUP_WORKTREES_AGE_MS", 0);
11456
11688
  const terminalWorktreesAgeMs = options.terminalWorktreesAgeMs ?? envMs("KYNVER_CLEANUP_TERMINAL_WORKTREES_AGE_MS", DEFAULT_TERMINAL_WORKTREES_AGE_MS);
11457
11689
  const runDirectoriesAgeMs = options.runDirectoriesAgeMs ?? envMs("KYNVER_CLEANUP_RUN_DIRECTORIES_AGE_MS", DEFAULT_RUN_DIRECTORIES_AGE_MS);
@@ -11467,6 +11699,7 @@ function resolveHarnessRetention(options = {}) {
11467
11699
  return {
11468
11700
  execute,
11469
11701
  finalizeStaleRuns: finalizeStaleRuns2,
11702
+ scanDependencyCaches,
11470
11703
  nodeModulesAgeMs,
11471
11704
  worktreesAgeMs: worktreesAgeMs > 0 ? worktreesAgeMs : 0,
11472
11705
  terminalWorktreesAgeMs: terminalWorktreesAgeMs >= 0 ? terminalWorktreesAgeMs : 0,
@@ -11490,35 +11723,38 @@ function resolvePipelineHarnessRetention(runId) {
11490
11723
  runDirectoriesAgeMs: scopeAll ? DEFAULT_RUN_DIRECTORIES_AGE_MS : void 0,
11491
11724
  includeOrphans: scopeAll ? true : void 0,
11492
11725
  finalizeStaleRuns: true,
11493
- accountBytes: true
11726
+ accountBytes: true,
11727
+ scanDependencyCaches: true
11494
11728
  });
11495
11729
  }
11496
11730
 
11497
11731
  // src/cleanup-orphan-safety.ts
11498
11732
  init_git();
11499
- import { existsSync as existsSync34, statSync as statSync12 } from "node:fs";
11500
- import path48 from "node:path";
11733
+ import { existsSync as existsSync34 } from "node:fs";
11734
+ import path49 from "node:path";
11735
+ init_util();
11501
11736
  var DEFAULT_HEARTBEAT_FRESH_MS = 30 * 60 * 1e3;
11502
11737
  function assessOrphanWorktreeSafety(input) {
11503
11738
  const now = input.now ?? Date.now();
11504
11739
  const heartbeatFreshMs = input.heartbeatFreshMs ?? DEFAULT_HEARTBEAT_FRESH_MS;
11505
11740
  if (!existsSync34(input.worktreePath)) return null;
11506
11741
  if (input.runId && input.workerName) {
11507
- const heartbeatPath = path48.join(
11742
+ const workerDir = path49.join(
11508
11743
  input.harnessRoot,
11509
11744
  "runs",
11510
11745
  input.runId,
11511
11746
  "workers",
11512
- input.workerName,
11513
- "heartbeat.jsonl"
11747
+ input.workerName
11514
11748
  );
11515
- try {
11516
- const mtime = statSync12(heartbeatPath).mtimeMs;
11517
- if (now - mtime < heartbeatFreshMs) return "active_worker";
11518
- } catch {
11749
+ const worker = readJson(
11750
+ path49.join(workerDir, "worker.json"),
11751
+ void 0
11752
+ );
11753
+ if (worker && heartbeatContentIsFresh(worker, now, heartbeatFreshMs)) {
11754
+ return "active_worker";
11519
11755
  }
11520
11756
  }
11521
- const gitDir = path48.join(input.worktreePath, ".git");
11757
+ const gitDir = path49.join(input.worktreePath, ".git");
11522
11758
  if (!existsSync34(gitDir)) return null;
11523
11759
  const porcelain = gitCapture(input.worktreePath, ["status", "--porcelain"]);
11524
11760
  if (porcelain.status !== 0) return "pr_or_unmerged_commits";
@@ -11549,8 +11785,8 @@ function assessOrphanWorktreeSafety(input) {
11549
11785
 
11550
11786
  // src/harness-storage-snapshot.ts
11551
11787
  init_paths();
11552
- import { existsSync as existsSync35, readdirSync as readdirSync14, statSync as statSync13 } from "node:fs";
11553
- import path49 from "node:path";
11788
+ import { existsSync as existsSync35, readdirSync as readdirSync13, statSync as statSync11 } from "node:fs";
11789
+ import path50 from "node:path";
11554
11790
  function harnessStorageSnapshot(opts = {}) {
11555
11791
  const harnessRoot = normalizeHarnessRoot(opts.harnessRoot ?? resolveHarnessRoot());
11556
11792
  const worktreesDir = harnessWorktreesDir(harnessRoot);
@@ -11573,7 +11809,7 @@ function harnessStorageSnapshot(opts = {}) {
11573
11809
  let oldestMs = null;
11574
11810
  let entries;
11575
11811
  try {
11576
- entries = readdirSync14(worktreesDir, { withFileTypes: true });
11812
+ entries = readdirSync13(worktreesDir, { withFileTypes: true });
11577
11813
  } catch {
11578
11814
  return {
11579
11815
  harnessRoot,
@@ -11588,14 +11824,14 @@ function harnessStorageSnapshot(opts = {}) {
11588
11824
  for (const runEntry of entries) {
11589
11825
  if (!runEntry.isDirectory()) continue;
11590
11826
  runCount += 1;
11591
- const runPath = path49.join(worktreesDir, runEntry.name);
11827
+ const runPath = path50.join(worktreesDir, runEntry.name);
11592
11828
  try {
11593
- const st = statSync13(runPath);
11829
+ const st = statSync11(runPath);
11594
11830
  oldestMs = oldestMs === null ? st.mtimeMs : Math.min(oldestMs, st.mtimeMs);
11595
11831
  } catch {
11596
11832
  }
11597
11833
  try {
11598
- for (const workerEntry of readdirSync14(runPath, { withFileTypes: true })) {
11834
+ for (const workerEntry of readdirSync13(runPath, { withFileTypes: true })) {
11599
11835
  if (workerEntry.isDirectory()) workerCount += 1;
11600
11836
  }
11601
11837
  } catch {
@@ -11626,10 +11862,10 @@ function harnessStorageSnapshot(opts = {}) {
11626
11862
  init_paths();
11627
11863
  import { existsSync as existsSync36 } from "node:fs";
11628
11864
  import { homedir as homedir13 } from "node:os";
11629
- import path50 from "node:path";
11865
+ import path51 from "node:path";
11630
11866
  var WELL_KNOWN_HARNESS_SCAN_ROOTS = [
11631
11867
  "/var/tmp/kynver-harness",
11632
- path50.join(homedir13(), ".openclaw", "harness")
11868
+ path51.join(homedir13(), ".openclaw", "harness")
11633
11869
  ];
11634
11870
  function addRoot(seen, roots, candidate) {
11635
11871
  if (!candidate?.trim()) return;
@@ -11651,7 +11887,7 @@ function resolveHarnessScanRoots(options = {}) {
11651
11887
  for (const candidate of extra ?? []) addRoot(seen, roots, candidate);
11652
11888
  if (shouldScanWellKnownRoots(options)) {
11653
11889
  for (const candidate of WELL_KNOWN_HARNESS_SCAN_ROOTS) {
11654
- const resolved = path50.resolve(candidate);
11890
+ const resolved = path51.resolve(candidate);
11655
11891
  if (!seen.has(resolved) && existsSync36(resolved)) addRoot(seen, roots, resolved);
11656
11892
  }
11657
11893
  }
@@ -11730,15 +11966,52 @@ var CleanupGitRevCache = class {
11730
11966
  }
11731
11967
  };
11732
11968
 
11969
+ // src/cleanup-git-probe.ts
11970
+ import { spawnSync as spawnSync7 } from "node:child_process";
11971
+ import { existsSync as existsSync37 } from "node:fs";
11972
+ import path52 from "node:path";
11973
+ var CLEANUP_GIT_PROBE_TIMEOUT_MS = 5e3;
11974
+ function cleanupGitCapture(cwd, args) {
11975
+ if (!existsSync37(path52.join(cwd, ".git"))) {
11976
+ return { status: null, stdout: "", stderr: "", error: "not_a_git_repo" };
11977
+ }
11978
+ try {
11979
+ const res = spawnSync7("git", args, {
11980
+ cwd,
11981
+ encoding: "utf8",
11982
+ timeout: CLEANUP_GIT_PROBE_TIMEOUT_MS
11983
+ });
11984
+ const timedOut = res.error?.message?.includes("ETIMEDOUT") === true;
11985
+ return {
11986
+ status: timedOut ? null : res.status,
11987
+ stdout: res.stdout || "",
11988
+ stderr: res.stderr || "",
11989
+ error: timedOut ? "git_timeout" : res.error ? res.error.message : null
11990
+ };
11991
+ } catch (error) {
11992
+ return {
11993
+ status: null,
11994
+ stdout: "",
11995
+ stderr: "",
11996
+ error: error.message
11997
+ };
11998
+ }
11999
+ }
12000
+ function gitStatusShortForCleanup(worktreePath) {
12001
+ const captured = cleanupGitCapture(worktreePath, ["status", "--short"]);
12002
+ if (captured.error === "not_a_git_repo") return [];
12003
+ if (captured.error === "git_timeout" || captured.status !== 0) return null;
12004
+ return captured.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
12005
+ }
12006
+
11733
12007
  // src/cleanup-git-status-cache.ts
11734
- init_git();
11735
12008
  var CleanupGitStatusCache = class {
11736
12009
  cache = /* @__PURE__ */ new Map();
11737
12010
  porcelain(worktreePath) {
11738
12011
  const resolved = worktreePath;
11739
12012
  const cached = this.cache.get(resolved);
11740
12013
  if (cached !== void 0) return cached;
11741
- const lines = gitStatusShort(resolved);
12014
+ const lines = gitStatusShortForCleanup(resolved) ?? [];
11742
12015
  this.cache.set(resolved, lines);
11743
12016
  return lines;
11744
12017
  }
@@ -11843,9 +12116,9 @@ function mergeWorktreeIndexes(scanRoots) {
11843
12116
  }
11844
12117
  function worktreePathForCandidate(candidate, worktreesDir) {
11845
12118
  if (candidate.runId && candidate.worker) {
11846
- return path51.join(worktreesDir, candidate.runId, candidate.worker);
12119
+ return path53.join(worktreesDir, candidate.runId, candidate.worker);
11847
12120
  }
11848
- return path51.resolve(candidate.path, "..");
12121
+ return path53.resolve(candidate.path, "..");
11849
12122
  }
11850
12123
  function runHarnessCleanup(options = {}) {
11851
12124
  let retention = resolveHarnessRetention(options);
@@ -11874,7 +12147,8 @@ function runHarnessCleanup(options = {}) {
11874
12147
  for (const harnessRoot of paths.scanRoots) {
11875
12148
  if (atSweepCap()) break;
11876
12149
  emitCleanupProgress("root", harnessRoot);
11877
- const worktreesDir = path51.join(harnessRoot, "worktrees");
12150
+ const worktreesDir = path53.join(harnessRoot, "worktrees");
12151
+ const rootIndex = filterWorktreeIndexForRoot(index, harnessRoot);
11878
12152
  const scanOpts = {
11879
12153
  harnessRoot,
11880
12154
  worktreesDir,
@@ -11882,11 +12156,14 @@ function runHarnessCleanup(options = {}) {
11882
12156
  worktreesAgeMs: retention.worktreesAgeMs,
11883
12157
  includeOrphans: retention.includeOrphans,
11884
12158
  runIdFilter: retention.runIdFilter,
11885
- index,
12159
+ index: rootIndex,
11886
12160
  now: paths.now
11887
12161
  };
11888
- const dependencyCandidates = scanDependencyCacheCandidates(scanOpts);
11889
- emitCleanupProgress("dependency", `${dependencyCandidates.length} cache candidate(s) at ${harnessRoot}`);
12162
+ const dependencyCandidates = retention.scanDependencyCaches ? scanDependencyCacheCandidates(scanOpts) : [];
12163
+ emitCleanupProgress(
12164
+ "dependency",
12165
+ retention.scanDependencyCaches ? `${dependencyCandidates.length} cache candidate(s) at ${harnessRoot}` : "skipped (worktree-only sweep)"
12166
+ );
11890
12167
  let dependencyProcessed = 0;
11891
12168
  for (const raw of dependencyCandidates) {
11892
12169
  if (atSweepCap()) break;
@@ -11894,7 +12171,7 @@ function runHarnessCleanup(options = {}) {
11894
12171
  if (dependencyProcessed % 50 === 0) {
11895
12172
  emitCleanupProgress("dependency", `${dependencyProcessed}/${dependencyCandidates.length} evaluated`);
11896
12173
  }
11897
- const resolved = path51.resolve(raw.path);
12174
+ const resolved = path53.resolve(raw.path);
11898
12175
  if (processedPaths.has(resolved)) continue;
11899
12176
  processedPaths.add(resolved);
11900
12177
  const candidate = { ...raw, path: resolved };
@@ -11905,7 +12182,7 @@ function runHarnessCleanup(options = {}) {
11905
12182
  continue;
11906
12183
  }
11907
12184
  const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
11908
- const indexed = index.get(path51.resolve(worktreePath)) ?? null;
12185
+ const indexed = rootIndex.get(path53.resolve(worktreePath)) ?? null;
11909
12186
  const guardReason = skipDependencyCacheRemoval({
11910
12187
  indexed,
11911
12188
  includeOrphans: true,
@@ -11928,9 +12205,9 @@ function runHarnessCleanup(options = {}) {
11928
12205
  )
11929
12206
  );
11930
12207
  }
11931
- for (const raw of scanBuildCacheCandidates(scanOpts)) {
12208
+ if (retention.scanDependencyCaches) for (const raw of scanBuildCacheCandidates(scanOpts)) {
11932
12209
  if (atSweepCap()) break;
11933
- const resolved = path51.resolve(raw.path);
12210
+ const resolved = path53.resolve(raw.path);
11934
12211
  if (processedPaths.has(resolved)) continue;
11935
12212
  processedPaths.add(resolved);
11936
12213
  const candidate = { ...raw, path: resolved };
@@ -11941,7 +12218,7 @@ function runHarnessCleanup(options = {}) {
11941
12218
  continue;
11942
12219
  }
11943
12220
  const worktreePath = worktreePathForCandidate(candidate, worktreesDir);
11944
- const indexed = index.get(path51.resolve(worktreePath)) ?? null;
12221
+ const indexed = rootIndex.get(path53.resolve(worktreePath)) ?? null;
11945
12222
  const guardReason = skipBuildCacheRemoval({
11946
12223
  indexed,
11947
12224
  includeOrphans: true,
@@ -11977,11 +12254,11 @@ function runHarnessCleanup(options = {}) {
11977
12254
  if (worktreeProcessed % 50 === 0) {
11978
12255
  emitCleanupProgress("worktrees", `${worktreeProcessed}/${worktreeCandidates.length} evaluated`);
11979
12256
  }
11980
- const resolved = path51.resolve(raw.path);
12257
+ const resolved = path53.resolve(raw.path);
11981
12258
  if (worktreeSeen.has(resolved)) continue;
11982
12259
  worktreeSeen.add(resolved);
11983
12260
  const candidate = { ...raw, path: resolved };
11984
- const indexed = index.get(path51.resolve(candidate.path)) ?? null;
12261
+ const indexed = rootIndex.get(path53.resolve(candidate.path)) ?? null;
11985
12262
  const orphanSafety = indexed ? null : assessOrphanWorktreeSafety({
11986
12263
  worktreePath: candidate.path,
11987
12264
  harnessRoot,
@@ -11991,14 +12268,17 @@ function runHarnessCleanup(options = {}) {
11991
12268
  });
11992
12269
  const guardSkip = skipWorktreeRemoval({
11993
12270
  indexed,
11994
- worktreePath: path51.resolve(candidate.path),
12271
+ worktreePath: path53.resolve(candidate.path),
11995
12272
  includeOrphans: retention.includeOrphans,
11996
12273
  worktreesAgeMs: retention.worktreesAgeMs,
11997
12274
  terminalWorktreesAgeMs: retention.terminalWorktreesAgeMs,
11998
12275
  ageMs: candidate.ageMs,
11999
12276
  orphanSafety,
12000
12277
  worktreeRemovalGuard: options.worktreeRemovalGuard,
12001
- liveness
12278
+ liveness,
12279
+ now: paths.now,
12280
+ harnessRoot,
12281
+ writeSalvageEvidence: retention.execute
12002
12282
  });
12003
12283
  if (guardSkip) {
12004
12284
  const { reason: guardReason, detail: guardDetail } = normalizeGuardSkip(guardSkip);
@@ -12023,11 +12303,11 @@ function runHarnessCleanup(options = {}) {
12023
12303
  now: paths.now
12024
12304
  })) {
12025
12305
  if (atSweepCap()) break;
12026
- const resolved = path51.resolve(raw.path);
12306
+ const resolved = path53.resolve(raw.path);
12027
12307
  if (processedPaths.has(resolved)) continue;
12028
12308
  processedPaths.add(resolved);
12029
12309
  const candidate = { ...raw, path: resolved };
12030
- const runId = candidate.runId ?? path51.basename(resolved);
12310
+ const runId = candidate.runId ?? path53.basename(resolved);
12031
12311
  const dirSkip = skipRunDirectoryRemoval({
12032
12312
  harnessRoot,
12033
12313
  runId,
@@ -12163,12 +12443,12 @@ function isPipelineCleanupEnabled() {
12163
12443
  // src/installed-package-versions.ts
12164
12444
  import { readFile } from "node:fs/promises";
12165
12445
  import { homedir as homedir14 } from "node:os";
12166
- import path53 from "node:path";
12446
+ import path55 from "node:path";
12167
12447
 
12168
12448
  // src/memory-cost-package-version-guard.ts
12169
12449
  init_default_repo_discovery();
12170
- import { existsSync as existsSync37, readFileSync as readFileSync13 } from "node:fs";
12171
- import path52 from "node:path";
12450
+ import { existsSync as existsSync38, readFileSync as readFileSync13 } from "node:fs";
12451
+ import path54 from "node:path";
12172
12452
  var MEMORY_COST_PACKAGE_MIN_VERSIONS = {
12173
12453
  "@kynver-app/runtime": "0.1.83",
12174
12454
  "@kynver-app/openclaw-agent-os": "0.1.43",
@@ -12230,8 +12510,8 @@ function resolveRepoRoot(cwd, explicitRepoRoot) {
12230
12510
  (value) => Boolean(value?.trim())
12231
12511
  );
12232
12512
  for (const candidate of candidates) {
12233
- const resolved = path52.resolve(candidate);
12234
- if (existsSync37(path52.join(resolved, "packages/kynver-runtime/package.json")) && existsSync37(path52.join(resolved, "package.json"))) {
12513
+ const resolved = path54.resolve(candidate);
12514
+ if (existsSync38(path54.join(resolved, "packages/kynver-runtime/package.json")) && existsSync38(path54.join(resolved, "package.json"))) {
12235
12515
  return resolved;
12236
12516
  }
12237
12517
  }
@@ -12244,7 +12524,7 @@ function probeRepoPackageVersions(input = {}) {
12244
12524
  if (!repoRoot) return {};
12245
12525
  const out = {};
12246
12526
  for (const packageName of MEMORY_COST_MANAGED_PACKAGES) {
12247
- const packageJsonPath = path52.join(repoRoot, REPO_PACKAGE_JSON_RELATIVE[packageName]);
12527
+ const packageJsonPath = path54.join(repoRoot, REPO_PACKAGE_JSON_RELATIVE[packageName]);
12248
12528
  const version = readPackageJsonVersion(packageJsonPath);
12249
12529
  if (!version) continue;
12250
12530
  out[packageName] = { version, source: "repo", path: packageJsonPath };
@@ -12364,12 +12644,12 @@ function unique(values) {
12364
12644
  }
12365
12645
  function moduleRoots() {
12366
12646
  const home = homedir14();
12367
- const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path53.join(home, ".openclaw", "npm");
12368
- const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path53.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path53.join(home, ".npm-global", "lib", "node_modules"));
12647
+ const openClawPrefix = trim(process.env.KYNVER_OPENCLAW_NPM_ROOT) ?? trim(process.env.OPENCLAW_NPM_ROOT) ?? path55.join(home, ".openclaw", "npm");
12648
+ const npmGlobalRoot = trim(process.env.KYNVER_NPM_GLOBAL_ROOT) ?? trim(process.env.KYNVER_NPM_GLOBAL_MODULES_ROOT) ?? (trim(process.env.NPM_CONFIG_PREFIX) ? path55.join(trim(process.env.NPM_CONFIG_PREFIX), "lib", "node_modules") : path55.join(home, ".npm-global", "lib", "node_modules"));
12369
12649
  return unique([
12370
- path53.join(openClawPrefix, "lib", "node_modules"),
12371
- path53.join(openClawPrefix, "node_modules"),
12372
- npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path53.join(npmGlobalRoot, "lib", "node_modules")
12650
+ path55.join(openClawPrefix, "lib", "node_modules"),
12651
+ path55.join(openClawPrefix, "node_modules"),
12652
+ npmGlobalRoot.endsWith("node_modules") ? npmGlobalRoot : path55.join(npmGlobalRoot, "lib", "node_modules")
12373
12653
  ]);
12374
12654
  }
12375
12655
  async function readVersion(packageJsonPath) {
@@ -12385,7 +12665,7 @@ function installedPackageJsonCandidates(packageName) {
12385
12665
  const seen = /* @__PURE__ */ new Set();
12386
12666
  const out = [];
12387
12667
  for (const root of roots) {
12388
- const candidate = path53.join(root, packageName, "package.json");
12668
+ const candidate = path55.join(root, packageName, "package.json");
12389
12669
  if (seen.has(candidate)) continue;
12390
12670
  seen.add(candidate);
12391
12671
  out.push(candidate);
@@ -12411,12 +12691,12 @@ async function collectInstalledPackageVersions(observedAt = (/* @__PURE__ */ new
12411
12691
  }
12412
12692
 
12413
12693
  // src/provider-evidence/exec.ts
12414
- import { spawnSync as spawnSync7 } from "node:child_process";
12694
+ import { spawnSync as spawnSync8 } from "node:child_process";
12415
12695
  var DEFAULT_CLI_TIMEOUT_MS = 1e4;
12416
12696
  var MAX_CLI_BUFFER_BYTES = 4 * 1024 * 1024;
12417
12697
  var defaultCliRunner = (cmd, args, opts) => {
12418
12698
  try {
12419
- const result = spawnSync7(cmd, args, {
12699
+ const result = spawnSync8(cmd, args, {
12420
12700
  encoding: "utf8",
12421
12701
  stdio: ["ignore", "pipe", "pipe"],
12422
12702
  timeout: opts?.timeoutMs ?? DEFAULT_CLI_TIMEOUT_MS,
@@ -12716,10 +12996,10 @@ function collectProviderEvidence(wanted, opts = {}) {
12716
12996
  // src/provider-evidence/wanted-store.ts
12717
12997
  init_run_store();
12718
12998
  init_util();
12719
- import path54 from "node:path";
12999
+ import path56 from "node:path";
12720
13000
  var WANTED_FILE = "provider-evidence-wanted.json";
12721
13001
  function wantedFilePath(runId) {
12722
- return path54.join(runDirectory(runId), WANTED_FILE);
13002
+ return path56.join(runDirectory(runId), WANTED_FILE);
12723
13003
  }
12724
13004
  function parseWantedItems(value) {
12725
13005
  if (!Array.isArray(value)) return [];
@@ -12758,7 +13038,7 @@ async function completeFinishedWorkers(runId, args) {
12758
13038
  const outcomes = [];
12759
13039
  for (const name of Object.keys(run.workers || {})) {
12760
13040
  const worker = readJson(
12761
- path55.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
13041
+ path57.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
12762
13042
  void 0
12763
13043
  );
12764
13044
  if (!worker?.taskId || worker.localOnly) continue;
@@ -12933,7 +13213,7 @@ import os11 from "node:os";
12933
13213
  // src/chat/anthropic-credentials.ts
12934
13214
  import { readFileSync as readFileSync14 } from "node:fs";
12935
13215
  import { homedir as homedir15, platform } from "node:os";
12936
- import path56 from "node:path";
13216
+ import path58 from "node:path";
12937
13217
  import { execFileSync as execFileSync3 } from "node:child_process";
12938
13218
  function parseClaudeCredentials(raw, now = Date.now()) {
12939
13219
  try {
@@ -12961,17 +13241,18 @@ function readClaudeCliToken() {
12961
13241
  }
12962
13242
  }
12963
13243
  try {
12964
- const raw = readFileSync14(path56.join(homedir15(), ".claude", ".credentials.json"), "utf8");
13244
+ const raw = readFileSync14(path58.join(homedir15(), ".claude", ".credentials.json"), "utf8");
12965
13245
  return parseClaudeCredentials(raw);
12966
13246
  } catch {
12967
13247
  return null;
12968
13248
  }
12969
13249
  }
12970
- function resolveLocalAnthropicCredentials(env = process.env) {
13250
+ function resolveLocalAnthropicCredentials(env = process.env, opts = {}) {
12971
13251
  const apiKey = env.ANTHROPIC_API_KEY?.trim();
12972
13252
  if (apiKey) return { kind: "api_key", key: apiKey };
12973
- const optIn = env.KYNVER_CHAT_USE_CLAUDE_OAUTH;
12974
- if (optIn === "1" || optIn === "true" || optIn === "yes") {
13253
+ const envOptIn = env.KYNVER_CHAT_USE_CLAUDE_OAUTH;
13254
+ const optedIn = opts.oauthOptIn === true || envOptIn === "1" || envOptIn === "true" || envOptIn === "yes";
13255
+ if (optedIn) {
12975
13256
  const token = readClaudeCliToken();
12976
13257
  if (token) return { kind: "oauth", token };
12977
13258
  }
@@ -13157,20 +13438,43 @@ var CLAIM_WAIT_MS = 25e3;
13157
13438
  var CLAIM_FETCH_TIMEOUT_MS = 32e3;
13158
13439
  var ERROR_BACKOFF_MS = 5e3;
13159
13440
  var AUTH_BACKOFF_MS = 6e4;
13441
+ var BRIDGE_REDISCOVER_MS = 5 * 6e4;
13442
+ var DISCOVERY_TIMEOUT_MS = 5e3;
13160
13443
  var DEFAULT_CHAT_MODEL = "claude-sonnet-4-6";
13161
- function resolveChatBridgeTarget(env = process.env) {
13162
- const bridgeUrl = env.KYNVER_RUNTIME_CHAT_BRIDGE_URL?.trim();
13163
- if (!bridgeUrl) return null;
13444
+ async function discoverBridgeUrl(apiBaseUrl, apiKey, env) {
13445
+ const override = env.KYNVER_RUNTIME_CHAT_BRIDGE_URL?.trim();
13446
+ if (override) return override;
13447
+ if (!apiBaseUrl) return null;
13448
+ const controller = new AbortController();
13449
+ const timer = setTimeout(() => controller.abort(), DISCOVERY_TIMEOUT_MS);
13450
+ try {
13451
+ const res = await fetch(`${apiBaseUrl.replace(/\/$/, "")}/api/runtime/chat-bridge`, {
13452
+ headers: { Authorization: `Bearer ${apiKey}` },
13453
+ signal: controller.signal
13454
+ });
13455
+ if (!res.ok) return null;
13456
+ const body = await res.json().catch(() => null);
13457
+ return typeof body?.bridgeUrl === "string" && body.bridgeUrl.trim() ? body.bridgeUrl.trim() : null;
13458
+ } catch {
13459
+ return null;
13460
+ } finally {
13461
+ clearTimeout(timer);
13462
+ }
13463
+ }
13464
+ async function resolveChatBridgeTarget(env = process.env) {
13164
13465
  const config = loadUserConfig();
13165
13466
  const agentOsId = config.agentOsId?.trim();
13166
13467
  const apiKey = loadApiKey();
13167
13468
  if (!agentOsId || !apiKey) return null;
13469
+ const bridgeUrl = await discoverBridgeUrl(config.apiBaseUrl, apiKey, env);
13470
+ if (!bridgeUrl) return null;
13168
13471
  return {
13169
13472
  bridgeUrl: bridgeUrl.replace(/\/$/, ""),
13170
13473
  agentOsId,
13171
13474
  apiKey,
13172
13475
  boxId: os11.hostname(),
13173
- model: env.KYNVER_CHAT_MODEL?.trim() || config.defaultModel?.trim() || DEFAULT_CHAT_MODEL
13476
+ model: env.KYNVER_CHAT_MODEL?.trim() || config.defaultModel?.trim() || DEFAULT_CHAT_MODEL,
13477
+ useClaudeOauth: config.chatUseClaudeOauth === true
13174
13478
  };
13175
13479
  }
13176
13480
  async function claimOnce(target, shouldStop) {
@@ -13231,7 +13535,7 @@ async function executeChatTurn(target, turn) {
13231
13535
  });
13232
13536
  };
13233
13537
  const batcher = new DeltaBatcher(sendBatch);
13234
- const creds = resolveLocalAnthropicCredentials();
13538
+ const creds = resolveLocalAnthropicCredentials(process.env, { oauthOptIn: target.useClaudeOauth });
13235
13539
  if (!creds) {
13236
13540
  await pushEvents(target, turn.turnId, [
13237
13541
  { seq: 0, kind: "error", error: "no_local_credentials" }
@@ -13276,15 +13580,22 @@ async function sleep2(ms, shouldStop) {
13276
13580
  }
13277
13581
  }
13278
13582
  async function runChatClaimLoop(opts) {
13279
- const target = resolveChatBridgeTarget();
13280
- if (!target) return;
13583
+ let target = await resolveChatBridgeTarget();
13584
+ while (!target && !opts.shouldStop()) {
13585
+ if (!loadApiKey() || !loadUserConfig().agentOsId?.trim()) return;
13586
+ await sleep2(BRIDGE_REDISCOVER_MS, opts.shouldStop);
13587
+ if (opts.shouldStop()) return;
13588
+ target = await resolveChatBridgeTarget();
13589
+ }
13590
+ if (!target || opts.shouldStop()) return;
13281
13591
  console.error(
13282
13592
  JSON.stringify({
13283
13593
  event: "chat_claim_loop_start",
13284
13594
  bridgeUrl: target.bridgeUrl,
13285
13595
  agentOsId: target.agentOsId,
13286
13596
  boxId: target.boxId,
13287
- model: target.model
13597
+ model: target.model,
13598
+ useClaudeOauth: target.useClaudeOauth
13288
13599
  })
13289
13600
  );
13290
13601
  while (!opts.shouldStop()) {
@@ -13369,16 +13680,40 @@ async function runDaemon(args) {
13369
13680
  })
13370
13681
  );
13371
13682
  });
13683
+ let credentialMissingLogged = false;
13372
13684
  while (!stopping) {
13373
13685
  try {
13374
13686
  writeDaemonHeartbeat({ agentOsId, runId });
13375
- if (cronEnv.tickEnabled) {
13376
- const cronTick = await runKynverCronTick({
13377
- env: cronEnv,
13378
- agentOsIdFilter: agentOsId
13379
- });
13380
- if (cronTick.enabled && (cronTick.fired > 0 || cronTick.errors > 0)) {
13381
- console.error(JSON.stringify({ event: "daemon_cron_tick", ...cronTick }));
13687
+ const credential = await tryResolveCallbackSecretWithMint(
13688
+ args.secret ? String(args.secret) : void 0,
13689
+ agentOsId
13690
+ );
13691
+ if (!credential.ok) {
13692
+ if (!credentialMissingLogged) {
13693
+ credentialMissingLogged = true;
13694
+ console.error(
13695
+ JSON.stringify({
13696
+ event: "daemon_runner_credential_missing",
13697
+ agentOsId,
13698
+ reason: credential.reason,
13699
+ remedy: `run \`kynver runner credential --agent-os-id ${agentOsId}\` (or \`kynver bootstrap\`); ticks resume automatically once the credential exists`
13700
+ })
13701
+ );
13702
+ }
13703
+ await awaitDaemonBackoff(intervalMs, () => stopping);
13704
+ continue;
13705
+ }
13706
+ if (credentialMissingLogged) {
13707
+ credentialMissingLogged = false;
13708
+ console.error(JSON.stringify({ event: "daemon_runner_credential_recovered", agentOsId }));
13709
+ }
13710
+ if (cronEnv.tickEnabled) {
13711
+ const cronTick = await runKynverCronTick({
13712
+ env: cronEnv,
13713
+ agentOsIdFilter: agentOsId
13714
+ });
13715
+ if (cronTick.enabled && (cronTick.fired > 0 || cronTick.errors > 0)) {
13716
+ console.error(JSON.stringify({ event: "daemon_cron_tick", ...cronTick }));
13382
13717
  }
13383
13718
  }
13384
13719
  const tick = await runPipelineTick({ run: runId, agentOsId, execute, ...args });
@@ -13399,21 +13734,152 @@ async function runDaemon(args) {
13399
13734
  console.error(JSON.stringify({ event: "daemon_stop", runId, agentOsId }));
13400
13735
  }
13401
13736
 
13737
+ // src/daemon-keeper.ts
13738
+ init_config();
13739
+ import { spawn as spawn6 } from "node:child_process";
13740
+ init_util();
13741
+ var DEFAULT_STALL_MS = 15 * 6e4;
13742
+ var STARTUP_GRACE_MS = 2 * 6e4;
13743
+ var KILL_GRACE_MS = 1e4;
13744
+ var BACKOFF_BASE_MS = 5e3;
13745
+ var BACKOFF_CAP_MS = 5 * 6e4;
13746
+ var HEALTHY_RESET_MS = 30 * 6e4;
13747
+ var POLL_MS = 5e3;
13748
+ function resolveKeeperStallMs(flag, env = process.env) {
13749
+ const fromFlag = typeof flag === "string" ? Number.parseInt(flag, 10) : NaN;
13750
+ if (Number.isFinite(fromFlag) && fromFlag > 0) return fromFlag;
13751
+ const fromEnv = Number.parseInt(env.KYNVER_DAEMON_STALL_MS ?? "", 10);
13752
+ if (Number.isFinite(fromEnv) && fromEnv > 0) return fromEnv;
13753
+ return DEFAULT_STALL_MS;
13754
+ }
13755
+ function shouldRunDaemonKeeper(args, env = process.env) {
13756
+ if (args.keeperChild === true || args.keeperChild === "true") return false;
13757
+ if (args.noSupervise === true || args.noSupervise === "true") return false;
13758
+ if (args.supervised === "false") return false;
13759
+ const envFlag5 = (env.KYNVER_DAEMON_SUPERVISED ?? "").trim().toLowerCase();
13760
+ if (envFlag5 === "0" || envFlag5 === "false" || envFlag5 === "no" || envFlag5 === "off") {
13761
+ return false;
13762
+ }
13763
+ return true;
13764
+ }
13765
+ function nextKeeperBackoffMs(consecutiveFailures, base = BACKOFF_BASE_MS, cap = BACKOFF_CAP_MS) {
13766
+ const exp = Math.min(Math.max(consecutiveFailures, 1) - 1, 10);
13767
+ return Math.min(base * 2 ** exp, cap);
13768
+ }
13769
+ function keeperRunWasHealthy(startedAtMs, endedAtMs, healthyMs = HEALTHY_RESET_MS) {
13770
+ return endedAtMs - startedAtMs >= healthyMs;
13771
+ }
13772
+ function keeperLog(event, detail = {}) {
13773
+ console.error(JSON.stringify({ event: `daemon_keeper_${event}`, ...detail }));
13774
+ }
13775
+ function buildKeeperChildArgv(argv) {
13776
+ const out = [];
13777
+ for (let i = 0; i < argv.length; i += 1) {
13778
+ const arg = argv[i];
13779
+ if (arg === "--supervised" || arg === "--no-supervise" || arg === "--keeper-child") continue;
13780
+ if (arg === "--stall-ms") {
13781
+ if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) i += 1;
13782
+ continue;
13783
+ }
13784
+ if (arg.startsWith("--stall-ms=") || arg.startsWith("--supervised=")) continue;
13785
+ out.push(arg);
13786
+ }
13787
+ out.push("--keeper-child");
13788
+ return out;
13789
+ }
13790
+ async function runDaemonKeeper(args, rawArgv = process.argv.slice(2)) {
13791
+ const agentOsId = String(
13792
+ required(String(args.agentOsId || loadUserConfig().agentOsId || ""), "--agent-os-id")
13793
+ );
13794
+ const stallMs = resolveKeeperStallMs(args.stallMs);
13795
+ const childArgv = buildKeeperChildArgv(rawArgv);
13796
+ const cliEntry = process.argv[1];
13797
+ let stopping = false;
13798
+ let child = null;
13799
+ let consecutiveFailures = 0;
13800
+ const stop = (signal) => {
13801
+ stopping = true;
13802
+ keeperLog("stop", { signal });
13803
+ if (child?.pid) child.kill(signal);
13804
+ };
13805
+ process.on("SIGINT", () => stop("SIGINT"));
13806
+ process.on("SIGTERM", () => stop("SIGTERM"));
13807
+ keeperLog("start", { agentOsId, stallMs, childArgv });
13808
+ while (!stopping) {
13809
+ const startedAt = Date.now();
13810
+ let exited = false;
13811
+ let exitCode = null;
13812
+ let exitSignal = null;
13813
+ child = spawn6(process.execPath, [cliEntry, ...childArgv], {
13814
+ stdio: "inherit",
13815
+ env: process.env
13816
+ });
13817
+ keeperLog("child_spawned", { pid: child.pid ?? null });
13818
+ child.on("exit", (code, signal) => {
13819
+ exited = true;
13820
+ exitCode = code;
13821
+ exitSignal = signal;
13822
+ });
13823
+ while (!exited && !stopping) {
13824
+ await sleepMsAsync(POLL_MS);
13825
+ if (exited || stopping) break;
13826
+ if (Date.now() - startedAt < STARTUP_GRACE_MS) continue;
13827
+ const beat = readDaemonHeartbeat(agentOsId);
13828
+ const ownBeat = beat && beat.pid === child.pid ? beat : null;
13829
+ if (ownBeat && isDaemonHeartbeatStale(ownBeat, stallMs)) {
13830
+ keeperLog("stall_detected", {
13831
+ pid: child.pid ?? null,
13832
+ lastBeatAt: ownBeat.observedAt,
13833
+ stallMs
13834
+ });
13835
+ child.kill("SIGTERM");
13836
+ await sleepMsAsync(KILL_GRACE_MS);
13837
+ if (!exited) child.kill("SIGKILL");
13838
+ break;
13839
+ }
13840
+ if (!ownBeat && Date.now() - startedAt > stallMs + STARTUP_GRACE_MS) {
13841
+ keeperLog("no_heartbeat_detected", { pid: child.pid ?? null, stallMs });
13842
+ child.kill("SIGTERM");
13843
+ await sleepMsAsync(KILL_GRACE_MS);
13844
+ if (!exited) child.kill("SIGKILL");
13845
+ break;
13846
+ }
13847
+ }
13848
+ while (!exited && !stopping) {
13849
+ await sleepMsAsync(POLL_MS);
13850
+ }
13851
+ if (stopping) break;
13852
+ const endedAt = Date.now();
13853
+ if (keeperRunWasHealthy(startedAt, endedAt)) consecutiveFailures = 0;
13854
+ consecutiveFailures += 1;
13855
+ const backoff = nextKeeperBackoffMs(consecutiveFailures);
13856
+ keeperLog("child_exited", {
13857
+ code: exitCode,
13858
+ signal: exitSignal,
13859
+ uptimeMs: endedAt - startedAt,
13860
+ consecutiveFailures,
13861
+ respawnInMs: backoff
13862
+ });
13863
+ await sleepMsAsync(backoff);
13864
+ }
13865
+ keeperLog("stopped", { agentOsId });
13866
+ }
13867
+
13402
13868
  // src/worktree.ts
13403
13869
  init_git();
13404
13870
  init_run_store();
13405
- import { existsSync as existsSync39, mkdirSync as mkdirSync7 } from "node:fs";
13406
- import path58 from "node:path";
13871
+ import { existsSync as existsSync40, mkdirSync as mkdirSync9 } from "node:fs";
13872
+ import path60 from "node:path";
13407
13873
 
13408
13874
  // src/run-list.ts
13409
13875
  init_run_store();
13410
13876
  init_run_worker_index();
13411
- import { existsSync as existsSync38, readFileSync as readFileSync15 } from "node:fs";
13412
- import path57 from "node:path";
13877
+ import { existsSync as existsSync39, readFileSync as readFileSync15 } from "node:fs";
13878
+ import path59 from "node:path";
13413
13879
  init_status();
13414
13880
  init_util();
13415
13881
  function heartbeatByteLength(heartbeatPath) {
13416
- if (!heartbeatPath || !existsSync38(heartbeatPath)) return 0;
13882
+ if (!heartbeatPath || !existsSync39(heartbeatPath)) return 0;
13417
13883
  try {
13418
13884
  return readFileSync15(heartbeatPath, "utf8").trim().length;
13419
13885
  } catch {
@@ -13421,7 +13887,7 @@ function heartbeatByteLength(heartbeatPath) {
13421
13887
  }
13422
13888
  }
13423
13889
  function workerEvidence(run, workerName) {
13424
- const workerPath = path57.join(runDirectory(run.id), "workers", safeSlug(workerName), "worker.json");
13890
+ const workerPath = path59.join(runDirectory(run.id), "workers", safeSlug(workerName), "worker.json");
13425
13891
  const worker = readJson(workerPath, void 0);
13426
13892
  if (!worker) {
13427
13893
  return {
@@ -13478,7 +13944,7 @@ function aggregateRunAttention(workers) {
13478
13944
  function countOpenWorkers(run) {
13479
13945
  let open = 0;
13480
13946
  for (const name of listRunWorkerNames(run)) {
13481
- const workerPath = path57.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
13947
+ const workerPath = path59.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
13482
13948
  const worker = readJson(workerPath, void 0);
13483
13949
  if (!worker) continue;
13484
13950
  const status = computeWorkerStatus(worker, { base: run.base, baseCommit: run.baseCommit });
@@ -13541,8 +14007,8 @@ function createRun(args) {
13541
14007
  ensureGitRepo(repo);
13542
14008
  const id = args.id ? validateRunId(String(args.id)) : timestampSlug(String(args.name || "run"));
13543
14009
  const dir = runDirectory(id);
13544
- if (existsSync39(dir)) failExists(`run already exists: ${id}`);
13545
- mkdirSync7(dir, { recursive: true });
14010
+ if (existsSync40(dir)) failExists(`run already exists: ${id}`);
14011
+ mkdirSync9(dir, { recursive: true });
13546
14012
  const base = String(args.base || "origin/main");
13547
14013
  const baseCommit = git(repo, ["rev-parse", base]).trim();
13548
14014
  const run = {
@@ -13555,7 +14021,7 @@ function createRun(args) {
13555
14021
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
13556
14022
  workers: {}
13557
14023
  };
13558
- writeJson(path58.join(dir, "run.json"), run);
14024
+ writeJson(path60.join(dir, "run.json"), run);
13559
14025
  const info = { runId: id, runDir: dir, repo, base, baseCommit };
13560
14026
  console.log(JSON.stringify(info, null, 2));
13561
14027
  return info;
@@ -13587,6 +14053,11 @@ async function runStart(args) {
13587
14053
  console.error("No AgentOS workspace configured \u2014 run `kynver bootstrap` (or pass --agent-os-id).");
13588
14054
  process.exit(1);
13589
14055
  }
14056
+ if (args.chatOauth === true && config.chatUseClaudeOauth !== true) {
14057
+ saveUserConfig({ ...config, chatUseClaudeOauth: true });
14058
+ config = loadUserConfig();
14059
+ console.log(" Chat: Claude Code OAuth opt-in saved (delegated turns may use your local subscription).");
14060
+ }
13590
14061
  const repo = (typeof args.repo === "string" ? args.repo.trim() : "") || config.defaultRepo?.trim() || resolveDefaultRepo()?.repo || "";
13591
14062
  if (!repo) {
13592
14063
  console.error("No repo configured \u2014 pass --repo /path/to/repo or run `kynver setup --discover-repo`.");
@@ -13608,7 +14079,25 @@ async function runStart(args) {
13608
14079
  console.log(` run: ${runId}`);
13609
14080
  console.log(" Ctrl-C stops the agent. (Advanced control: `kynver daemon --help`.)");
13610
14081
  console.log("");
13611
- await runDaemon({ ...args, run: runId, agentOsId });
14082
+ const daemonArgs = { ...args, run: runId, agentOsId };
14083
+ if (shouldRunDaemonKeeper(daemonArgs)) {
14084
+ await runDaemonKeeper(daemonArgs, buildStartDaemonArgv(runId, agentOsId, args));
14085
+ return;
14086
+ }
14087
+ await runDaemon(daemonArgs);
14088
+ }
14089
+ function buildStartDaemonArgv(runId, agentOsId, args) {
14090
+ const argv = ["daemon", "--run", runId, "--agent-os-id", agentOsId];
14091
+ if (typeof args.intervalMs === "string" && args.intervalMs.trim()) {
14092
+ argv.push("--interval-ms", args.intervalMs.trim());
14093
+ }
14094
+ if (args.execute === false || args.execute === "false") {
14095
+ argv.push("--execute", "false");
14096
+ }
14097
+ if (typeof args.stallMs === "string" && args.stallMs.trim()) {
14098
+ argv.push("--stall-ms", args.stallMs.trim());
14099
+ }
14100
+ return argv;
13612
14101
  }
13613
14102
 
13614
14103
  // src/cli.ts
@@ -13617,8 +14106,8 @@ init_run_store();
13617
14106
  // src/discard-disposable.ts
13618
14107
  init_run_store();
13619
14108
  init_status();
13620
- import { existsSync as existsSync40, rmSync as rmSync4 } from "node:fs";
13621
- import path59 from "node:path";
14109
+ import { existsSync as existsSync41, rmSync as rmSync4 } from "node:fs";
14110
+ import path61 from "node:path";
13622
14111
  function normalizeRelativePath2(value) {
13623
14112
  const normalized = value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
13624
14113
  if (!normalized || normalized.startsWith("/") || normalized.includes("..")) {
@@ -13640,15 +14129,15 @@ function discardDisposableArtifacts(args) {
13640
14129
  if (paths.length === 0) {
13641
14130
  return { ok: false, removed: [], reason: "requires at least one --path" };
13642
14131
  }
13643
- const worktreeRoot = path59.resolve(worker.worktreePath);
14132
+ const worktreeRoot = path61.resolve(worker.worktreePath);
13644
14133
  const removed = [];
13645
14134
  for (const raw of paths) {
13646
14135
  const rel = normalizeRelativePath2(raw);
13647
- const abs = path59.resolve(worktreeRoot, rel);
13648
- if (!abs.startsWith(worktreeRoot + path59.sep) && abs !== worktreeRoot) {
14136
+ const abs = path61.resolve(worktreeRoot, rel);
14137
+ if (!abs.startsWith(worktreeRoot + path61.sep) && abs !== worktreeRoot) {
13649
14138
  return { ok: false, removed, reason: `path escapes worktree: ${raw}` };
13650
14139
  }
13651
- if (!existsSync40(abs)) {
14140
+ if (!existsSync41(abs)) {
13652
14141
  return { ok: false, removed, reason: `path not found: ${raw}` };
13653
14142
  }
13654
14143
  rmSync4(abs, { recursive: true, force: true });
@@ -13670,140 +14159,9 @@ function discardDisposableCli(args) {
13670
14159
  if (!result.ok) process.exit(1);
13671
14160
  }
13672
14161
 
13673
- // src/daemon-keeper.ts
13674
- init_config();
13675
- import { spawn as spawn6 } from "node:child_process";
13676
- init_util();
13677
- var DEFAULT_STALL_MS = 15 * 6e4;
13678
- var STARTUP_GRACE_MS = 2 * 6e4;
13679
- var KILL_GRACE_MS = 1e4;
13680
- var BACKOFF_BASE_MS = 5e3;
13681
- var BACKOFF_CAP_MS = 5 * 6e4;
13682
- var HEALTHY_RESET_MS = 30 * 6e4;
13683
- var POLL_MS = 5e3;
13684
- function resolveKeeperStallMs(flag, env = process.env) {
13685
- const fromFlag = typeof flag === "string" ? Number.parseInt(flag, 10) : NaN;
13686
- if (Number.isFinite(fromFlag) && fromFlag > 0) return fromFlag;
13687
- const fromEnv = Number.parseInt(env.KYNVER_DAEMON_STALL_MS ?? "", 10);
13688
- if (Number.isFinite(fromEnv) && fromEnv > 0) return fromEnv;
13689
- return DEFAULT_STALL_MS;
13690
- }
13691
- function shouldRunDaemonKeeper(args, env = process.env) {
13692
- if (args.keeperChild === true || args.keeperChild === "true") return false;
13693
- if (args.noSupervise === true || args.noSupervise === "true") return false;
13694
- if (args.supervised === "false") return false;
13695
- const envFlag5 = (env.KYNVER_DAEMON_SUPERVISED ?? "").trim().toLowerCase();
13696
- if (envFlag5 === "0" || envFlag5 === "false" || envFlag5 === "no" || envFlag5 === "off") {
13697
- return false;
13698
- }
13699
- return true;
13700
- }
13701
- function nextKeeperBackoffMs(consecutiveFailures, base = BACKOFF_BASE_MS, cap = BACKOFF_CAP_MS) {
13702
- const exp = Math.min(Math.max(consecutiveFailures, 1) - 1, 10);
13703
- return Math.min(base * 2 ** exp, cap);
13704
- }
13705
- function keeperRunWasHealthy(startedAtMs, endedAtMs, healthyMs = HEALTHY_RESET_MS) {
13706
- return endedAtMs - startedAtMs >= healthyMs;
13707
- }
13708
- function keeperLog(event, detail = {}) {
13709
- console.error(JSON.stringify({ event: `daemon_keeper_${event}`, ...detail }));
13710
- }
13711
- function buildKeeperChildArgv(argv) {
13712
- const out = [];
13713
- for (let i = 0; i < argv.length; i += 1) {
13714
- const arg = argv[i];
13715
- if (arg === "--supervised" || arg === "--no-supervise" || arg === "--keeper-child") continue;
13716
- if (arg === "--stall-ms") {
13717
- if (i + 1 < argv.length && !argv[i + 1].startsWith("--")) i += 1;
13718
- continue;
13719
- }
13720
- if (arg.startsWith("--stall-ms=") || arg.startsWith("--supervised=")) continue;
13721
- out.push(arg);
13722
- }
13723
- out.push("--keeper-child");
13724
- return out;
13725
- }
13726
- async function runDaemonKeeper(args, rawArgv = process.argv.slice(2)) {
13727
- const agentOsId = String(
13728
- required(String(args.agentOsId || loadUserConfig().agentOsId || ""), "--agent-os-id")
13729
- );
13730
- const stallMs = resolveKeeperStallMs(args.stallMs);
13731
- const childArgv = buildKeeperChildArgv(rawArgv);
13732
- const cliEntry = process.argv[1];
13733
- let stopping = false;
13734
- let child = null;
13735
- let consecutiveFailures = 0;
13736
- const stop = (signal) => {
13737
- stopping = true;
13738
- keeperLog("stop", { signal });
13739
- if (child?.pid) child.kill(signal);
13740
- };
13741
- process.on("SIGINT", () => stop("SIGINT"));
13742
- process.on("SIGTERM", () => stop("SIGTERM"));
13743
- keeperLog("start", { agentOsId, stallMs, childArgv });
13744
- while (!stopping) {
13745
- const startedAt = Date.now();
13746
- let exited = false;
13747
- let exitCode = null;
13748
- let exitSignal = null;
13749
- child = spawn6(process.execPath, [cliEntry, ...childArgv], {
13750
- stdio: "inherit",
13751
- env: process.env
13752
- });
13753
- keeperLog("child_spawned", { pid: child.pid ?? null });
13754
- child.on("exit", (code, signal) => {
13755
- exited = true;
13756
- exitCode = code;
13757
- exitSignal = signal;
13758
- });
13759
- while (!exited && !stopping) {
13760
- await sleepMsAsync(POLL_MS);
13761
- if (exited || stopping) break;
13762
- if (Date.now() - startedAt < STARTUP_GRACE_MS) continue;
13763
- const beat = readDaemonHeartbeat(agentOsId);
13764
- const ownBeat = beat && beat.pid === child.pid ? beat : null;
13765
- if (ownBeat && isDaemonHeartbeatStale(ownBeat, stallMs)) {
13766
- keeperLog("stall_detected", {
13767
- pid: child.pid ?? null,
13768
- lastBeatAt: ownBeat.observedAt,
13769
- stallMs
13770
- });
13771
- child.kill("SIGTERM");
13772
- await sleepMsAsync(KILL_GRACE_MS);
13773
- if (!exited) child.kill("SIGKILL");
13774
- break;
13775
- }
13776
- if (!ownBeat && Date.now() - startedAt > stallMs + STARTUP_GRACE_MS) {
13777
- keeperLog("no_heartbeat_detected", { pid: child.pid ?? null, stallMs });
13778
- child.kill("SIGTERM");
13779
- await sleepMsAsync(KILL_GRACE_MS);
13780
- if (!exited) child.kill("SIGKILL");
13781
- break;
13782
- }
13783
- }
13784
- while (!exited && !stopping) {
13785
- await sleepMsAsync(POLL_MS);
13786
- }
13787
- if (stopping) break;
13788
- const endedAt = Date.now();
13789
- if (keeperRunWasHealthy(startedAt, endedAt)) consecutiveFailures = 0;
13790
- consecutiveFailures += 1;
13791
- const backoff = nextKeeperBackoffMs(consecutiveFailures);
13792
- keeperLog("child_exited", {
13793
- code: exitCode,
13794
- signal: exitSignal,
13795
- uptimeMs: endedAt - startedAt,
13796
- consecutiveFailures,
13797
- respawnInMs: backoff
13798
- });
13799
- await sleepMsAsync(backoff);
13800
- }
13801
- keeperLog("stopped", { agentOsId });
13802
- }
13803
-
13804
14162
  // src/plan-progress.ts
13805
14163
  init_config();
13806
- import path63 from "node:path";
14164
+ import path65 from "node:path";
13807
14165
 
13808
14166
  // src/bounded-build/constants.ts
13809
14167
  var DEFAULT_BUILD_MEM_BUDGET_BYTES = 1536 * 1024 * 1024;
@@ -13844,7 +14202,7 @@ function formatNodeOptionsFlag(mb = resolveNodeOldSpaceSizeMb()) {
13844
14202
  }
13845
14203
 
13846
14204
  // src/bounded-build/systemd-wrap.ts
13847
- import { spawnSync as spawnSync8 } from "node:child_process";
14205
+ import { spawnSync as spawnSync9 } from "node:child_process";
13848
14206
  var systemdAvailableCache;
13849
14207
  function isSystemdRunAvailable() {
13850
14208
  if (process.env.KYNVER_BUILD_SKIP_SYSTEMD === "1" || process.env.KYNVER_BUILD_SKIP_SYSTEMD === "true") {
@@ -13855,7 +14213,7 @@ function isSystemdRunAvailable() {
13855
14213
  systemdAvailableCache = false;
13856
14214
  return false;
13857
14215
  }
13858
- const res = spawnSync8("systemd-run", ["--version"], { encoding: "utf8", stdio: ["ignore", "ignore", "pipe"] });
14216
+ const res = spawnSync9("systemd-run", ["--version"], { encoding: "utf8", stdio: ["ignore", "ignore", "pipe"] });
13859
14217
  systemdAvailableCache = res.status === 0;
13860
14218
  return systemdAvailableCache;
13861
14219
  }
@@ -13880,7 +14238,7 @@ function buildSystemdRunArgv(opts) {
13880
14238
 
13881
14239
  // src/bounded-build/admission.ts
13882
14240
  init_config();
13883
- import { spawnSync as spawnSync9 } from "node:child_process";
14241
+ import { spawnSync as spawnSync10 } from "node:child_process";
13884
14242
  init_meminfo();
13885
14243
  function positiveInt4(value, fallback) {
13886
14244
  const n = Number(value);
@@ -13917,7 +14275,7 @@ function assessBuildAdmission(opts = {}) {
13917
14275
  }
13918
14276
  function sleepMs2(ms) {
13919
14277
  if (ms <= 0) return;
13920
- spawnSync9(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
14278
+ spawnSync10(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
13921
14279
  stdio: "ignore"
13922
14280
  });
13923
14281
  }
@@ -13938,34 +14296,34 @@ function waitForBuildAdmission(timeoutMs, pollMs = 2e3, opts = {}) {
13938
14296
  }
13939
14297
 
13940
14298
  // src/bounded-build/exec.ts
13941
- import { spawnSync as spawnSync11 } from "node:child_process";
14299
+ import { spawnSync as spawnSync12 } from "node:child_process";
13942
14300
 
13943
14301
  // src/heavy-verification/slot.ts
13944
14302
  init_util();
13945
14303
  import {
13946
14304
  closeSync as closeSync7,
13947
- existsSync as existsSync41,
13948
- mkdirSync as mkdirSync9,
14305
+ existsSync as existsSync42,
14306
+ mkdirSync as mkdirSync11,
13949
14307
  openSync as openSync7,
13950
- readdirSync as readdirSync15,
14308
+ readdirSync as readdirSync14,
13951
14309
  readFileSync as readFileSync16,
13952
14310
  unlinkSync as unlinkSync4,
13953
- writeFileSync as writeFileSync6
14311
+ writeFileSync as writeFileSync7
13954
14312
  } from "node:fs";
13955
- import path61 from "node:path";
14313
+ import path63 from "node:path";
13956
14314
 
13957
14315
  // src/heavy-verification/paths.ts
13958
- import { mkdirSync as mkdirSync8 } from "node:fs";
13959
- import path60 from "node:path";
14316
+ import { mkdirSync as mkdirSync10 } from "node:fs";
14317
+ import path62 from "node:path";
13960
14318
  function resolveHeavyVerificationRoot() {
13961
- return path60.join(resolveKynverStateRoot(), "heavy-verification");
14319
+ return path62.join(resolveKynverStateRoot(), "heavy-verification");
13962
14320
  }
13963
14321
  function heavyVerificationSlotsDir() {
13964
- return path60.join(resolveHeavyVerificationRoot(), "slots");
14322
+ return path62.join(resolveHeavyVerificationRoot(), "slots");
13965
14323
  }
13966
14324
  function ensureHeavyVerificationDirs() {
13967
14325
  const dir = heavyVerificationSlotsDir();
13968
- mkdirSync8(dir, { recursive: true });
14326
+ mkdirSync10(dir, { recursive: true });
13969
14327
  return dir;
13970
14328
  }
13971
14329
 
@@ -13990,10 +14348,10 @@ function indexedSlotId(index) {
13990
14348
  return `slot-${index}`;
13991
14349
  }
13992
14350
  function slotFilePath(slotId, slotsDir = heavyVerificationSlotsDir()) {
13993
- return path61.join(slotsDir, `${slotId}.json`);
14351
+ return path63.join(slotsDir, `${slotId}.json`);
13994
14352
  }
13995
14353
  function readSlotRecord(filePath) {
13996
- if (!existsSync41(filePath)) return null;
14354
+ if (!existsSync42(filePath)) return null;
13997
14355
  try {
13998
14356
  const parsed = JSON.parse(readFileSync16(filePath, "utf8"));
13999
14357
  if (typeof parsed.slotId === "string" && typeof parsed.pid === "number" && typeof parsed.acquiredAt === "string" && typeof parsed.command === "string") {
@@ -14020,19 +14378,19 @@ function reclaimStaleSlot(filePath, staleMs) {
14020
14378
  }
14021
14379
  }
14022
14380
  function ensureSlotsDir(slotsDir) {
14023
- mkdirSync9(slotsDir, { recursive: true });
14381
+ mkdirSync11(slotsDir, { recursive: true });
14024
14382
  return slotsDir;
14025
14383
  }
14026
14384
  function reclaimStaleHeavyVerificationSlots(opts = {}) {
14027
14385
  const slotsDir = ensureSlotsDir(opts.slotsDir ?? ensureHeavyVerificationDirs());
14028
14386
  const staleMs = opts.staleMs ?? DEFAULT_HEAVY_VERIFICATION_STALE_MS;
14029
14387
  let reclaimed = 0;
14030
- for (const name of readdirSync15(slotsDir)) {
14388
+ for (const name of readdirSync14(slotsDir)) {
14031
14389
  if (!name.endsWith(".json")) continue;
14032
- const filePath = path61.join(slotsDir, name);
14033
- const before = existsSync41(filePath);
14390
+ const filePath = path63.join(slotsDir, name);
14391
+ const before = existsSync42(filePath);
14034
14392
  reclaimStaleSlot(filePath, staleMs);
14035
- if (before && !existsSync41(filePath)) reclaimed += 1;
14393
+ if (before && !existsSync42(filePath)) reclaimed += 1;
14036
14394
  }
14037
14395
  return reclaimed;
14038
14396
  }
@@ -14041,9 +14399,9 @@ function listActiveHeavyVerificationSlots(opts = {}) {
14041
14399
  const staleMs = opts.staleMs ?? DEFAULT_HEAVY_VERIFICATION_STALE_MS;
14042
14400
  reclaimStaleHeavyVerificationSlots({ slotsDir, staleMs });
14043
14401
  const active = [];
14044
- for (const name of readdirSync15(slotsDir)) {
14402
+ for (const name of readdirSync14(slotsDir)) {
14045
14403
  if (!name.endsWith(".json")) continue;
14046
- const record = readSlotRecord(path61.join(slotsDir, name));
14404
+ const record = readSlotRecord(path63.join(slotsDir, name));
14047
14405
  if (record && !slotIsStale(record, staleMs)) active.push(record);
14048
14406
  }
14049
14407
  return active;
@@ -14085,7 +14443,7 @@ function tryAcquireHeavyVerificationSlot(command, opts = {}) {
14085
14443
  };
14086
14444
  try {
14087
14445
  const fd = openSync7(filePath, "wx");
14088
- writeFileSync6(fd, JSON.stringify(record, null, 2), "utf8");
14446
+ writeFileSync7(fd, JSON.stringify(record, null, 2), "utf8");
14089
14447
  closeSync7(fd);
14090
14448
  const activeSlots2 = countActiveHeavyVerificationSlots({ slotsDir, staleMs });
14091
14449
  return {
@@ -14145,10 +14503,10 @@ function assessHeavyVerificationGate(command, opts = {}) {
14145
14503
  }
14146
14504
 
14147
14505
  // src/heavy-verification/gate.ts
14148
- import { spawnSync as spawnSync10 } from "node:child_process";
14506
+ import { spawnSync as spawnSync11 } from "node:child_process";
14149
14507
  function sleepMs3(ms) {
14150
14508
  if (ms <= 0) return;
14151
- spawnSync10(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
14509
+ spawnSync11(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
14152
14510
  stdio: "ignore"
14153
14511
  });
14154
14512
  }
@@ -14164,11 +14522,11 @@ function waitForHeavyVerificationSlot(command, timeoutMs, pollMs = 2e3, opts = {
14164
14522
 
14165
14523
  // src/harness-worktree-build-guard.ts
14166
14524
  init_paths();
14167
- import path62 from "node:path";
14525
+ import path64 from "node:path";
14168
14526
  function isPathUnderHarnessWorktree(cwd) {
14169
14527
  const worktreesDir = harnessWorktreesDir(resolveHarnessRoot());
14170
- const rel = path62.relative(worktreesDir, path62.resolve(cwd));
14171
- return rel.length > 0 && !rel.startsWith("..") && !path62.isAbsolute(rel);
14528
+ const rel = path64.relative(worktreesDir, path64.resolve(cwd));
14529
+ return rel.length > 0 && !rel.startsWith("..") && !path64.isAbsolute(rel);
14172
14530
  }
14173
14531
  function assessHarnessWorktreeBuildGuard(cwd) {
14174
14532
  if (!isPathUnderHarnessWorktree(cwd)) return { ok: true };
@@ -14192,7 +14550,7 @@ function envArgv(env) {
14192
14550
  return out;
14193
14551
  }
14194
14552
  function runSpawn(argv, opts) {
14195
- const res = spawnSync11(argv[0], argv.slice(1), {
14553
+ const res = spawnSync12(argv[0], argv.slice(1), {
14196
14554
  cwd: opts.cwd,
14197
14555
  env: opts.env,
14198
14556
  encoding: "utf8",
@@ -14381,7 +14739,7 @@ async function emitPlanProgress(args) {
14381
14739
  }
14382
14740
  function verifyPlanLocal(args) {
14383
14741
  const worktree = required(args.worktree ? String(args.worktree) : void 0, "worktree");
14384
- const cwd = path63.resolve(worktree);
14742
+ const cwd = path65.resolve(worktree);
14385
14743
  const summary = runHarnessVerifyCommands(cwd);
14386
14744
  const emitJson = args.json === true || args.json === "true";
14387
14745
  const payload = { passed: summary.passed, worktree: cwd, steps: summary.steps };
@@ -14430,10 +14788,10 @@ async function verifyPlan(args) {
14430
14788
  }
14431
14789
 
14432
14790
  // src/harness-verify-cli.ts
14433
- import path64 from "node:path";
14791
+ import path66 from "node:path";
14434
14792
  init_util();
14435
14793
  function runHarnessVerifyCli(args) {
14436
- const cwd = path64.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
14794
+ const cwd = path66.resolve(required(args.worktree ? String(args.worktree) : void 0, "worktree"));
14437
14795
  const emitJson = args.json === true || args.json === "true" || args.emitJson === true || args.emitJson === "true";
14438
14796
  const commands = [];
14439
14797
  const rawCmd = args.command;
@@ -14565,7 +14923,9 @@ function runCleanupCli(args) {
14565
14923
  const skipFinalize = args.skipFinalize === true || args.skipFinalize === "true";
14566
14924
  const accountBytes = args.accountBytes === true || args.accountBytes === "true";
14567
14925
  const compact = args.compact === true || args.compact === "true";
14568
- const nodeModulesAgeMs = args.nodeModulesAgeMs ? Number(args.nodeModulesAgeMs) : DEFAULT_NODE_MODULES_AGE_MS;
14926
+ const nodeModulesAgeMsRaw = args.nodeModulesAgeMs;
14927
+ const scanDependencyCaches = nodeModulesAgeMsRaw != null && nodeModulesAgeMsRaw !== "";
14928
+ const nodeModulesAgeMs = scanDependencyCaches ? Number(nodeModulesAgeMsRaw) : void 0;
14569
14929
  const worktreesAgeMs = args.worktreesAgeMs ? Number(args.worktreesAgeMs) : 0;
14570
14930
  const includeOrphans = args.includeOrphans === true || args.includeOrphans === "true";
14571
14931
  const harnessRoot = args.harnessRoot ? String(args.harnessRoot) : void 0;
@@ -14573,7 +14933,8 @@ function runCleanupCli(args) {
14573
14933
  execute,
14574
14934
  finalizeStaleRuns: !skipFinalize,
14575
14935
  accountBytes,
14576
- nodeModulesAgeMs: Number.isFinite(nodeModulesAgeMs) ? nodeModulesAgeMs : DEFAULT_NODE_MODULES_AGE_MS,
14936
+ scanDependencyCaches,
14937
+ nodeModulesAgeMs: nodeModulesAgeMs !== void 0 && Number.isFinite(nodeModulesAgeMs) ? nodeModulesAgeMs : scanDependencyCaches ? DEFAULT_NODE_MODULES_AGE_MS : void 0,
14577
14938
  worktreesAgeMs: Number.isFinite(worktreesAgeMs) ? worktreesAgeMs : 0,
14578
14939
  includeOrphans,
14579
14940
  harnessRoot
@@ -14643,7 +15004,7 @@ function formatMonitorTickNotice(tick) {
14643
15004
  }
14644
15005
 
14645
15006
  // src/monitor/monitor.service.ts
14646
- import path66 from "node:path";
15007
+ import path68 from "node:path";
14647
15008
  init_run_store();
14648
15009
  init_status();
14649
15010
  init_util();
@@ -14702,19 +15063,19 @@ function classifyWorkerHealth(input) {
14702
15063
  // src/monitor/monitor.store.ts
14703
15064
  init_paths();
14704
15065
  init_util();
14705
- import { existsSync as existsSync42, mkdirSync as mkdirSync10, readdirSync as readdirSync16, unlinkSync as unlinkSync5 } from "node:fs";
14706
- import path65 from "node:path";
15066
+ import { existsSync as existsSync43, mkdirSync as mkdirSync12, readdirSync as readdirSync15, unlinkSync as unlinkSync5 } from "node:fs";
15067
+ import path67 from "node:path";
14707
15068
  function monitorsDir() {
14708
15069
  const { harnessRoot } = getHarnessPaths();
14709
- const dir = path65.join(harnessRoot, "monitors");
14710
- mkdirSync10(dir, { recursive: true });
15070
+ const dir = path67.join(harnessRoot, "monitors");
15071
+ mkdirSync12(dir, { recursive: true });
14711
15072
  return dir;
14712
15073
  }
14713
15074
  function monitorIdFor(runId, workerName) {
14714
15075
  return workerName ? `${safeSlug(runId)}--${safeSlug(workerName)}` : safeSlug(runId);
14715
15076
  }
14716
15077
  function monitorPath(monitorId) {
14717
- return path65.join(monitorsDir(), `${monitorId}.json`);
15078
+ return path67.join(monitorsDir(), `${monitorId}.json`);
14718
15079
  }
14719
15080
  function loadMonitorSession(monitorId) {
14720
15081
  return readJson(monitorPath(monitorId), void 0);
@@ -14724,18 +15085,18 @@ function saveMonitorSession(session) {
14724
15085
  }
14725
15086
  function deleteMonitorSession(monitorId) {
14726
15087
  const file = monitorPath(monitorId);
14727
- if (!existsSync42(file)) return false;
15088
+ if (!existsSync43(file)) return false;
14728
15089
  unlinkSync5(file);
14729
15090
  return true;
14730
15091
  }
14731
15092
  function listMonitorSessions() {
14732
15093
  const dir = monitorsDir();
14733
- if (!existsSync42(dir)) return [];
15094
+ if (!existsSync43(dir)) return [];
14734
15095
  const entries = [];
14735
- for (const name of readdirSync16(dir)) {
15096
+ for (const name of readdirSync15(dir)) {
14736
15097
  if (!name.endsWith(".json")) continue;
14737
15098
  const session = readJson(
14738
- path65.join(dir, name),
15099
+ path67.join(dir, name),
14739
15100
  void 0
14740
15101
  );
14741
15102
  if (!session?.monitorId) continue;
@@ -14828,7 +15189,7 @@ async function fetchTaskLeasesForWorkers(input) {
14828
15189
  // src/monitor/monitor.service.ts
14829
15190
  function workerRecord2(runId, name) {
14830
15191
  return readJson(
14831
- path66.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
15192
+ path68.join(runDirectory(runId), "workers", safeSlug(name), "worker.json"),
14832
15193
  void 0
14833
15194
  );
14834
15195
  }
@@ -15037,18 +15398,18 @@ async function runMonitorLoop(args) {
15037
15398
  init_util();
15038
15399
  init_paths();
15039
15400
  import { spawn as spawn7 } from "node:child_process";
15040
- import { closeSync as closeSync8, existsSync as existsSync43, openSync as openSync8 } from "node:fs";
15041
- import path67 from "node:path";
15401
+ import { closeSync as closeSync8, existsSync as existsSync44, openSync as openSync8 } from "node:fs";
15402
+ import path69 from "node:path";
15042
15403
  import { fileURLToPath as fileURLToPath3 } from "node:url";
15043
15404
  function resolveDefaultCliPath2() {
15044
- return path67.join(fileURLToPath3(new URL(".", import.meta.url)), "cli.js");
15405
+ return path69.join(fileURLToPath3(new URL(".", import.meta.url)), "cli.js");
15045
15406
  }
15046
15407
  function spawnMonitorSidecar(opts) {
15047
15408
  const cliPath = opts.cliPath ?? resolveDefaultCliPath2();
15048
- if (!existsSync43(cliPath)) return void 0;
15409
+ if (!existsSync44(cliPath)) return void 0;
15049
15410
  const monitorId = monitorIdFor(opts.runId, opts.workerName);
15050
15411
  const { harnessRoot } = getHarnessPaths();
15051
- const logPath = path67.join(harnessRoot, "monitors", `${monitorId}.log`);
15412
+ const logPath = path69.join(harnessRoot, "monitors", `${monitorId}.log`);
15052
15413
  let logFd;
15053
15414
  try {
15054
15415
  logFd = openSync8(logPath, "a");
@@ -15169,13 +15530,13 @@ async function monitorTickCli(args) {
15169
15530
  }
15170
15531
 
15171
15532
  // src/package-version.ts
15172
- import { existsSync as existsSync44, readFileSync as readFileSync18 } from "node:fs";
15533
+ import { existsSync as existsSync45, readFileSync as readFileSync18 } from "node:fs";
15173
15534
  import { dirname, join } from "node:path";
15174
15535
  import { fileURLToPath as fileURLToPath4 } from "node:url";
15175
15536
  function resolvePackageRoot(moduleUrl) {
15176
15537
  let dir = dirname(fileURLToPath4(moduleUrl));
15177
15538
  for (let depth = 0; depth < 6; depth += 1) {
15178
- if (existsSync44(join(dir, "package.json"))) return dir;
15539
+ if (existsSync45(join(dir, "package.json"))) return dir;
15179
15540
  const parent = dirname(dir);
15180
15541
  if (parent === dir) break;
15181
15542
  dir = parent;
@@ -15281,7 +15642,7 @@ init_run_store();
15281
15642
  init_status();
15282
15643
  init_util();
15283
15644
  init_config();
15284
- import path68 from "node:path";
15645
+ import path70 from "node:path";
15285
15646
  function skip(runId, worker, taskId, agentOsId, leaseOwner, reason) {
15286
15647
  return { runId, worker, taskId, agentOsId, leaseOwner, action: "skipped", reason };
15287
15648
  }
@@ -15294,7 +15655,7 @@ async function postRestartUnblock(args) {
15294
15655
  const errors = [];
15295
15656
  for (const run of listRunRecords()) {
15296
15657
  for (const name of Object.keys(run.workers ?? {})) {
15297
- const workerPath = path68.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
15658
+ const workerPath = path70.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json");
15298
15659
  const worker = readJson(workerPath, void 0);
15299
15660
  if (!worker) {
15300
15661
  skipped.push(skip(run.id, name, "", "", "", "worker.json missing"));
@@ -15408,9 +15769,9 @@ async function postRestartUnblockCli(args) {
15408
15769
  // src/default-repo-cli.ts
15409
15770
  init_path_values();
15410
15771
  init_config();
15411
- import path69 from "node:path";
15772
+ import path71 from "node:path";
15412
15773
  import { homedir as homedir16 } from "node:os";
15413
- var CONFIG_FILE2 = path69.join(homedir16(), ".kynver", "config.json");
15774
+ var CONFIG_FILE2 = path71.join(homedir16(), ".kynver", "config.json");
15414
15775
  function ensureDefaultRepo(opts) {
15415
15776
  const existing = loadUserConfig();
15416
15777
  const resolved = resolveDefaultRepo({ ...opts, config: existing });
@@ -15491,19 +15852,19 @@ function summarizeResolvedDefaultRepo(resolved) {
15491
15852
  }
15492
15853
 
15493
15854
  // src/doctor/runtime-takeover.ts
15494
- import path71 from "node:path";
15855
+ import path73 from "node:path";
15495
15856
  init_path_values();
15496
15857
 
15497
15858
  // src/doctor/runtime-takeover.probes.ts
15498
15859
  init_config();
15499
- import { accessSync, constants, existsSync as existsSync45, readFileSync as readFileSync19 } from "node:fs";
15860
+ import { accessSync, constants, existsSync as existsSync46, readFileSync as readFileSync19 } from "node:fs";
15500
15861
  import { homedir as homedir17 } from "node:os";
15501
- import path70 from "node:path";
15502
- import { spawnSync as spawnSync12 } from "node:child_process";
15862
+ import path72 from "node:path";
15863
+ import { spawnSync as spawnSync13 } from "node:child_process";
15503
15864
  init_paths();
15504
15865
  function captureCommand(bin, args) {
15505
15866
  try {
15506
- const res = spawnSync12(bin, args, { encoding: "utf8" });
15867
+ const res = spawnSync13(bin, args, { encoding: "utf8" });
15507
15868
  const stdout = (res.stdout || "").trim();
15508
15869
  const stderr = (res.stderr || "").trim();
15509
15870
  const ok = res.status === 0;
@@ -15528,7 +15889,7 @@ function tokenPrefix(token) {
15528
15889
  return trimmed.length <= 12 ? `${trimmed}\u2026` : `${trimmed.slice(0, 12)}\u2026`;
15529
15890
  }
15530
15891
  function isWritable(target) {
15531
- if (!existsSync45(target)) return false;
15892
+ if (!existsSync46(target)) return false;
15532
15893
  try {
15533
15894
  accessSync(target, constants.W_OK);
15534
15895
  return true;
@@ -15541,11 +15902,11 @@ var defaultRuntimeTakeoverProbes = {
15541
15902
  commandOnPath: (bin) => captureCommand(process.platform === "win32" ? "where" : "which", [bin]),
15542
15903
  kynverVersion: (bin) => captureCommand(bin, ["--version"]),
15543
15904
  loadConfig: () => loadUserConfig(),
15544
- configFilePath: () => path70.join(homedir17(), ".kynver", "config.json"),
15545
- credentialsFilePath: () => path70.join(homedir17(), ".kynver", "credentials"),
15905
+ configFilePath: () => path72.join(homedir17(), ".kynver", "config.json"),
15906
+ credentialsFilePath: () => path72.join(homedir17(), ".kynver", "credentials"),
15546
15907
  readCredentials: () => {
15547
- const credPath = path70.join(homedir17(), ".kynver", "credentials");
15548
- if (!existsSync45(credPath)) {
15908
+ const credPath = path72.join(homedir17(), ".kynver", "credentials");
15909
+ if (!existsSync46(credPath)) {
15549
15910
  return { hasApiKey: false };
15550
15911
  }
15551
15912
  try {
@@ -15579,8 +15940,8 @@ var defaultRuntimeTakeoverProbes = {
15579
15940
  })()
15580
15941
  }),
15581
15942
  harnessRoot: () => resolveHarnessRoot(),
15582
- legacyOpenclawHarnessRoot: () => path70.join(homedir17(), ".openclaw", "harness"),
15583
- pathExists: (target) => existsSync45(target),
15943
+ legacyOpenclawHarnessRoot: () => path72.join(homedir17(), ".openclaw", "harness"),
15944
+ pathExists: (target) => existsSync46(target),
15584
15945
  pathWritable: (target) => isWritable(target)
15585
15946
  };
15586
15947
 
@@ -15986,8 +16347,8 @@ function assessVercelDeployEvidence(probes) {
15986
16347
  }
15987
16348
  function assessHarnessDirs(probes) {
15988
16349
  const harnessRoot = probes.harnessRoot();
15989
- const runsDir = path71.join(harnessRoot, "runs");
15990
- const worktreesDir = path71.join(harnessRoot, "worktrees");
16350
+ const runsDir = path73.join(harnessRoot, "runs");
16351
+ const worktreesDir = path73.join(harnessRoot, "worktrees");
15991
16352
  const displayHarnessRoot = redactHomePath(harnessRoot);
15992
16353
  const displayRunsDir = redactHomePath(runsDir);
15993
16354
  const displayWorktreesDir = redactHomePath(worktreesDir);
@@ -16256,9 +16617,9 @@ function applySchedulerCutoverAttestation(config) {
16256
16617
 
16257
16618
  // src/scheduler-cutover-cli.ts
16258
16619
  init_config();
16259
- import path72 from "node:path";
16620
+ import path74 from "node:path";
16260
16621
  import { homedir as homedir18 } from "node:os";
16261
- var CONFIG_FILE3 = path72.join(homedir18(), ".kynver", "config.json");
16622
+ var CONFIG_FILE3 = path74.join(homedir18(), ".kynver", "config.json");
16262
16623
  function runSchedulerCutoverCheckCli(json = false) {
16263
16624
  const config = loadUserConfig();
16264
16625
  const report = assessSchedulerCutover(config);
@@ -16402,7 +16763,7 @@ init_config();
16402
16763
  init_config();
16403
16764
  init_path_values();
16404
16765
  init_util();
16405
- import { existsSync as existsSync49 } from "node:fs";
16766
+ import { existsSync as existsSync50 } from "node:fs";
16406
16767
 
16407
16768
  // src/cron/cron-id.ts
16408
16769
  import { createHash as createHash4 } from "node:crypto";
@@ -16421,13 +16782,13 @@ function deterministicCronProviderId(spec) {
16421
16782
 
16422
16783
  // src/cron/cron-install-plan.ts
16423
16784
  import { homedir as homedir20 } from "node:os";
16424
- import path74 from "node:path";
16785
+ import path76 from "node:path";
16425
16786
 
16426
16787
  // src/cron/cron-env-file.ts
16427
- import { existsSync as existsSync46, mkdirSync as mkdirSync11, readFileSync as readFileSync20, writeFileSync as writeFileSync7 } from "node:fs";
16788
+ import { existsSync as existsSync47, mkdirSync as mkdirSync13, readFileSync as readFileSync20, writeFileSync as writeFileSync8 } from "node:fs";
16428
16789
  import { homedir as homedir19 } from "node:os";
16429
- import path73 from "node:path";
16430
- var DEFAULT_KYNVER_ENV_FILE = path73.join(homedir19(), ".kynver", ".env");
16790
+ import path75 from "node:path";
16791
+ var DEFAULT_KYNVER_ENV_FILE = path75.join(homedir19(), ".kynver", ".env");
16431
16792
  function parseEnvFile(content) {
16432
16793
  const map = /* @__PURE__ */ new Map();
16433
16794
  for (const line of content.split(/\r?\n/)) {
@@ -16454,12 +16815,12 @@ function serializeEnvFile(values, header = "# Managed by kynver cron install \u2
16454
16815
  return lines.join("\n");
16455
16816
  }
16456
16817
  function readEnvFile(filePath = DEFAULT_KYNVER_ENV_FILE) {
16457
- if (!existsSync46(filePath)) return /* @__PURE__ */ new Map();
16818
+ if (!existsSync47(filePath)) return /* @__PURE__ */ new Map();
16458
16819
  return parseEnvFile(readFileSync20(filePath, "utf8"));
16459
16820
  }
16460
16821
  function mergeEnvFile(updates, options = {}) {
16461
16822
  const filePath = options.filePath ?? DEFAULT_KYNVER_ENV_FILE;
16462
- const existing = existsSync46(filePath) ? readFileSync20(filePath, "utf8") : "";
16823
+ const existing = existsSync47(filePath) ? readFileSync20(filePath, "utf8") : "";
16463
16824
  const map = parseEnvFile(existing);
16464
16825
  const keysWritten = [];
16465
16826
  const keysRemoved = [];
@@ -16484,8 +16845,8 @@ function mergeEnvFile(updates, options = {}) {
16484
16845
  }
16485
16846
  const nextContent = serializeEnvFile(map);
16486
16847
  if (changed) {
16487
- mkdirSync11(path73.dirname(filePath), { recursive: true });
16488
- writeFileSync7(filePath, nextContent, { mode: 384 });
16848
+ mkdirSync13(path75.dirname(filePath), { recursive: true });
16849
+ writeFileSync8(filePath, nextContent, { mode: 384 });
16489
16850
  }
16490
16851
  return { path: filePath, changed, keysWritten, keysRemoved };
16491
16852
  }
@@ -16502,7 +16863,7 @@ var VERCEL_KYNVER_CRON_CUTOVER_STEPS = [
16502
16863
  function buildCronInstallPlan(input) {
16503
16864
  const storePath = input.storePath?.trim() || defaultKynverCronStorePath();
16504
16865
  const envFilePath = input.envFilePath?.trim() || DEFAULT_KYNVER_ENV_FILE;
16505
- const configPath = path74.join(homedir20(), ".kynver", "config.json");
16866
+ const configPath = path76.join(homedir20(), ".kynver", "config.json");
16506
16867
  const callbackPath = `/api/agent-os/by-id/${input.agentOsId}/scheduler/fire`;
16507
16868
  const prerequisites = [];
16508
16869
  if (!input.apiBaseUrl?.trim()) prerequisites.push("apiBaseUrl \u2014 run `kynver setup --api-base-url \u2026`");
@@ -16654,13 +17015,13 @@ function resolveCronSecretForInstall(envFilePath = DEFAULT_KYNVER_ENV_FILE) {
16654
17015
  }
16655
17016
 
16656
17017
  // src/cron/cron-install-systemd.ts
16657
- import { existsSync as existsSync47, mkdirSync as mkdirSync12, writeFileSync as writeFileSync8 } from "node:fs";
17018
+ import { existsSync as existsSync48, mkdirSync as mkdirSync14, writeFileSync as writeFileSync9 } from "node:fs";
16658
17019
  import { homedir as homedir21 } from "node:os";
16659
- import path75 from "node:path";
16660
- import { spawnSync as spawnSync13 } from "node:child_process";
17020
+ import path77 from "node:path";
17021
+ import { spawnSync as spawnSync14 } from "node:child_process";
16661
17022
  var KYNVER_CRON_DAEMON_UNIT = "kynver-cron-daemon.service";
16662
17023
  function defaultSystemdUserUnitDir() {
16663
- return path75.join(homedir21(), ".config", "systemd", "user");
17024
+ return path77.join(homedir21(), ".config", "systemd", "user");
16664
17025
  }
16665
17026
  function renderKynverCronDaemonService(input) {
16666
17027
  const kynverBin = input.kynverBin?.trim() || "kynver";
@@ -16693,7 +17054,7 @@ function installSystemdUserDaemon(input, execute) {
16693
17054
  };
16694
17055
  }
16695
17056
  const unitDir = defaultSystemdUserUnitDir();
16696
- const unitPath = path75.join(unitDir, KYNVER_CRON_DAEMON_UNIT);
17057
+ const unitPath = path77.join(unitDir, KYNVER_CRON_DAEMON_UNIT);
16697
17058
  const content = renderKynverCronDaemonService(input);
16698
17059
  if (!execute) {
16699
17060
  return {
@@ -16705,10 +17066,10 @@ function installSystemdUserDaemon(input, execute) {
16705
17066
  note: "Dry-run \u2014 pass --execute to write and enable the user unit."
16706
17067
  };
16707
17068
  }
16708
- mkdirSync12(unitDir, { recursive: true });
16709
- const existed = existsSync47(unitPath);
16710
- writeFileSync8(unitPath, content, "utf8");
16711
- const reload = spawnSync13("systemctl", ["--user", "daemon-reload"], { encoding: "utf8" });
17069
+ mkdirSync14(unitDir, { recursive: true });
17070
+ const existed = existsSync48(unitPath);
17071
+ writeFileSync9(unitPath, content, "utf8");
17072
+ const reload = spawnSync14("systemctl", ["--user", "daemon-reload"], { encoding: "utf8" });
16712
17073
  if (reload.status !== 0) {
16713
17074
  return {
16714
17075
  supported: true,
@@ -16719,7 +17080,7 @@ function installSystemdUserDaemon(input, execute) {
16719
17080
  note: `Wrote ${unitPath} but systemctl --user daemon-reload failed: ${reload.stderr || reload.stdout}`
16720
17081
  };
16721
17082
  }
16722
- const enable = spawnSync13("systemctl", ["--user", "enable", "--now", KYNVER_CRON_DAEMON_UNIT], {
17083
+ const enable = spawnSync14("systemctl", ["--user", "enable", "--now", KYNVER_CRON_DAEMON_UNIT], {
16723
17084
  encoding: "utf8"
16724
17085
  });
16725
17086
  return {
@@ -16733,7 +17094,7 @@ function installSystemdUserDaemon(input, execute) {
16733
17094
  }
16734
17095
 
16735
17096
  // src/cron/cron-install-verify.ts
16736
- import { existsSync as existsSync48 } from "node:fs";
17097
+ import { existsSync as existsSync49 } from "node:fs";
16737
17098
  async function verifyCronInstall(input) {
16738
17099
  const envFilePath = input.envFilePath ?? DEFAULT_KYNVER_ENV_FILE;
16739
17100
  const checks = [];
@@ -16766,14 +17127,14 @@ async function verifyCronInstall(input) {
16766
17127
  });
16767
17128
  checks.push({
16768
17129
  id: "env_file",
16769
- ok: existsSync48(envFilePath) && Boolean(fileEnv.get("KYNVER_CRON_SECRET")),
16770
- summary: existsSync48(envFilePath) ? `~/.kynver/.env present (${fileEnv.size} keys)` : "~/.kynver/.env missing",
17130
+ ok: existsSync49(envFilePath) && Boolean(fileEnv.get("KYNVER_CRON_SECRET")),
17131
+ summary: existsSync49(envFilePath) ? `~/.kynver/.env present (${fileEnv.size} keys)` : "~/.kynver/.env missing",
16771
17132
  remediation: "Run `kynver cron install` to write ~/.kynver/.env."
16772
17133
  });
16773
17134
  checks.push({
16774
17135
  id: "cron_store",
16775
- ok: existsSync48(env.storePath),
16776
- summary: existsSync48(env.storePath) ? `cron store present (${env.storePath})` : `cron store missing (${env.storePath})`,
17136
+ ok: existsSync49(env.storePath),
17137
+ summary: existsSync49(env.storePath) ? `cron store present (${env.storePath})` : `cron store missing (${env.storePath})`,
16777
17138
  remediation: "Run `kynver cron install` to initialize the local store."
16778
17139
  });
16779
17140
  const jobs = await loadCronJobs(env.storePath).catch(() => []);
@@ -16807,7 +17168,7 @@ function resolveDaemonRunId(config, explicit) {
16807
17168
  if (explicit?.trim()) return explicit.trim();
16808
17169
  if (config.defaultDaemonRunId?.trim()) return config.defaultDaemonRunId.trim();
16809
17170
  const runsDir = getPaths().runsDir;
16810
- if (!existsSync49(runsDir)) return null;
17171
+ if (!existsSync50(runsDir)) return null;
16811
17172
  const runs = listRunRecords().sort(
16812
17173
  (a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt)
16813
17174
  );
@@ -16859,7 +17220,7 @@ async function runCronInstall(opts = {}) {
16859
17220
  process.env.KYNVER_CRON_STORE_PATH = plan.storePath;
16860
17221
  process.env.KYNVER_CRON_TICK_ENABLED = "1";
16861
17222
  const storeInit = await ensureCronStoreInitialized(plan.storePath);
16862
- result.storeInitialized = storeInit.created || existsSync49(plan.storePath);
17223
+ result.storeInitialized = storeInit.created || existsSync50(plan.storePath);
16863
17224
  const apiKey = loadApiKey();
16864
17225
  if (apiKey) {
16865
17226
  try {
@@ -17078,10 +17439,10 @@ var LANDING_MAINTAINER_LANE_SPEC = {
17078
17439
  };
17079
17440
 
17080
17441
  // src/lane/landing-maintainer-local.ts
17081
- import { spawnSync as spawnSync14 } from "node:child_process";
17082
- import path76 from "node:path";
17442
+ import { spawnSync as spawnSync15 } from "node:child_process";
17443
+ import path78 from "node:path";
17083
17444
  function runLandingWrapper(prNumber, repoRoot, execute) {
17084
- const script = path76.join(repoRoot, LANDING_MAINTAINER_LANE_SPEC.landScript);
17445
+ const script = path78.join(repoRoot, LANDING_MAINTAINER_LANE_SPEC.landScript);
17085
17446
  const args = [script, String(prNumber), ...LANDING_MAINTAINER_LANE_SPEC.landScriptArgs];
17086
17447
  if (!execute) {
17087
17448
  return {
@@ -17092,7 +17453,7 @@ function runLandingWrapper(prNumber, repoRoot, execute) {
17092
17453
  stderr: ""
17093
17454
  };
17094
17455
  }
17095
- const result = spawnSync14("node", args, {
17456
+ const result = spawnSync15("node", args, {
17096
17457
  cwd: repoRoot,
17097
17458
  encoding: "utf8",
17098
17459
  timeout: 10 * 60 * 1e3
@@ -17107,7 +17468,7 @@ function runLandingWrapper(prNumber, repoRoot, execute) {
17107
17468
  }
17108
17469
  function resolveLandingMaintainerRepoRoot(args) {
17109
17470
  const explicit = args.repoPath ? String(args.repoPath).trim() : "";
17110
- if (explicit) return path76.resolve(explicit);
17471
+ if (explicit) return path78.resolve(explicit);
17111
17472
  const resolved = resolveDefaultRepo();
17112
17473
  return resolved?.repo ?? process.cwd();
17113
17474
  }
@@ -17232,9 +17593,9 @@ function usage(code = 0) {
17232
17593
  "Usage:",
17233
17594
  " kynver login [--api-key KEY] [--api-base-url URL] (omit --api-key to authorize in the browser)",
17234
17595
  " kynver bootstrap [--api-base-url URL] [--api-key KEY] [--repo PATH] (login + setup + runner credential in one shot)",
17235
- " kynver start [--repo PATH] [--api-base-url URL] [--run RUN_ID] [--interval-ms MS] (bring your agent online: bootstrap if needed + run + daemon)",
17596
+ " kynver start [--repo PATH] [--api-base-url URL] [--run RUN_ID] [--interval-ms MS] [--chat-oauth] (bring your agent online: bootstrap if needed + run + daemon; --chat-oauth lets delegated chat turns use your local Claude subscription)",
17236
17597
  " kynver runner credential [--agent-os-id ID] [--base-url URL]",
17237
- " kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--box-kind forge|ghost] [--repo PATH] [--discover-repo] [--max-workers N] [--provider claude|cursor]",
17598
+ " kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--box-kind forge|ghost] [--repo PATH] [--discover-repo] [--max-workers N] [--provider claude|cursor] [--chat-oauth]",
17238
17599
  " kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS] [--stall-ms MS] [--no-supervise]",
17239
17600
  " kynver status --run RUN_ID [--blocked] [--running] [--task TASK_ID] [--worker WORKER] [--full] # top-level compact run status",
17240
17601
  " kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
@@ -17298,8 +17659,8 @@ async function main(argv = process.argv.slice(2)) {
17298
17659
  if (action && isHelpFlag(action) || rest.some(isHelpFlag)) return usage(0);
17299
17660
  const args = parseArgs(rest);
17300
17661
  const { runsDir, worktreesDir } = getPaths();
17301
- mkdirSync13(runsDir, { recursive: true });
17302
- mkdirSync13(worktreesDir, { recursive: true });
17662
+ mkdirSync15(runsDir, { recursive: true });
17663
+ mkdirSync15(worktreesDir, { recursive: true });
17303
17664
  if (scope === "daemon") {
17304
17665
  assertNativeDaemonAllowed();
17305
17666
  }