@kynver-app/runtime 0.1.108 → 0.1.112

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
@@ -175,7 +175,11 @@ function scrubWorkerEnv(env) {
175
175
 
176
176
  // src/git.ts
177
177
  function git(cwd, args, options = {}) {
178
- const res = spawnSync("git", args, { cwd, encoding: "utf8" });
178
+ const res = spawnSync(
179
+ "git",
180
+ args,
181
+ hiddenSpawnOptions({ cwd, encoding: "utf8" })
182
+ );
179
183
  if (res.status !== 0 && !options.allowFailure) {
180
184
  const message = `git ${args.join(" ")} failed: ${res.stderr || res.stdout}`;
181
185
  if (options.throwError) throw new Error(message);
@@ -191,7 +195,11 @@ function gitStatusShort(worktreePath) {
191
195
  }
192
196
  function gitCapture(cwd, args) {
193
197
  try {
194
- const res = spawnSync("git", args, { cwd, encoding: "utf8" });
198
+ const res = spawnSync(
199
+ "git",
200
+ args,
201
+ hiddenSpawnOptions({ cwd, encoding: "utf8" })
202
+ );
195
203
  return {
196
204
  status: res.status,
197
205
  stdout: res.stdout || "",
@@ -5648,32 +5656,98 @@ function workerStatus(args) {
5648
5656
  writeJson(path19.join(worker.workerDir, "last-status.json"), status);
5649
5657
  console.log(JSON.stringify(status, null, 2));
5650
5658
  }
5659
+ function buildWorkerListDrilldownCommands(runId) {
5660
+ return {
5661
+ full: `kynver worker list --run ${runId} --full`,
5662
+ blocked: `kynver worker list --run ${runId} --blocked`,
5663
+ running: `kynver worker list --run ${runId} --running`,
5664
+ task: `kynver worker list --run ${runId} --task <task-id>`,
5665
+ worker: `kynver worker list --run ${runId} --worker <worker>`,
5666
+ runFull: `kynver run status --run ${runId} --full`,
5667
+ workerFull: `kynver worker status --run ${runId} --name <worker>`,
5668
+ workerTail: `kynver worker tail --run ${runId} --name <worker> --lines 80`
5669
+ };
5670
+ }
5671
+ function asOptionalArg(value) {
5672
+ if (typeof value !== "string") return void 0;
5673
+ const trimmed = value.trim();
5674
+ return trimmed.length ? trimmed : void 0;
5675
+ }
5676
+ function activeWorkerFilters(args) {
5677
+ const filters = {};
5678
+ if (args.blocked === true || args.blocked === "true") filters.blocked = true;
5679
+ if (args.running === true || args.running === "true") filters.running = true;
5680
+ const task = asOptionalArg(args.task ?? args.taskId);
5681
+ if (task) filters.task = task;
5682
+ const worker = asOptionalArg(args.worker ?? args.name);
5683
+ if (worker) filters.worker = worker;
5684
+ const status = asOptionalArg(args.status);
5685
+ if (status) filters.status = status;
5686
+ return filters;
5687
+ }
5688
+ function workerMatchesFilters(worker, filters) {
5689
+ if (filters.blocked) {
5690
+ const attention = typeof worker.attention === "string" ? worker.attention : "";
5691
+ const status = typeof worker.status === "string" ? worker.status : "";
5692
+ if (!(attention === "blocked" || attention === "needs_attention" || attention === "stale" || status === "blocked")) {
5693
+ return false;
5694
+ }
5695
+ }
5696
+ if (filters.running) {
5697
+ if (!(worker.status === "running" && worker.attention !== "blocked" && worker.attention !== "needs_attention")) {
5698
+ return false;
5699
+ }
5700
+ }
5701
+ if (typeof filters.task === "string" && worker.taskId !== filters.task) return false;
5702
+ if (typeof filters.worker === "string" && worker.worker !== filters.worker) return false;
5703
+ if (typeof filters.status === "string" && worker.status !== filters.status) return false;
5704
+ return true;
5705
+ }
5706
+ function applyWorkerFilters(board, args) {
5707
+ const filters = activeWorkerFilters(args);
5708
+ if (Object.keys(filters).length === 0) return board;
5709
+ const workers = board.workers.filter((worker) => workerMatchesFilters(worker, filters));
5710
+ const filtered = {
5711
+ ...board,
5712
+ workerCount: workers.length,
5713
+ needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
5714
+ workers,
5715
+ activeFilters: filters
5716
+ };
5717
+ if (board.summary) {
5718
+ filtered.summary = {
5719
+ statusCounts: countBy(workers, "status"),
5720
+ attentionCounts: countBy(workers, "attention"),
5721
+ lifecycleCounts: countBy(workers, "lifecycleStage")
5722
+ };
5723
+ }
5724
+ if (board.controller) filtered.controller = buildControllerSummary(workers);
5725
+ return filtered;
5726
+ }
5651
5727
  function workerList(args) {
5652
5728
  const runId = resolveRunTargetArg(args);
5653
- const explicitCompact = args.compact === true || args.compact === "true";
5654
5729
  const explicitFull = args.full === true || args.full === "true";
5655
- const run = loadRun(runId);
5656
- const workerCount = Object.keys(run.workers || {}).length;
5657
- const compact = explicitCompact || !explicitFull && workerCount > 100;
5658
- const board = compact ? buildCompactRunBoard(runId) : buildRunBoard(runId);
5659
- const autoCompact = compact && !explicitCompact;
5660
- const outputWorkers = autoCompact ? board.workers.filter((worker) => worker.attention && worker.attention !== "done" && worker.attention !== "ok") : board.workers;
5730
+ const board = applyWorkerFilters(explicitFull ? buildRunBoard(runId) : buildCompactRunBoard(runId), args);
5731
+ const outputWorkers = board.workers;
5661
5732
  console.log(
5662
5733
  JSON.stringify(
5663
5734
  {
5664
5735
  runId: board.runId,
5665
5736
  status: board.status,
5737
+ projection: explicitFull ? "full" : "compact",
5666
5738
  workerCount: board.workerCount,
5667
- ...autoCompact ? {
5668
- compact: true,
5669
- compactReason: "large-worker-board",
5670
- omittedWorkerCount: board.workers.length - outputWorkers.length,
5671
- fullCommand: `kynver worker list --run ${runId} --json --full`,
5672
- compactCommand: `kynver worker list --run ${runId} --json --compact`
5739
+ ...board.activeFilters ? { activeFilters: board.activeFilters } : {},
5740
+ ...!explicitFull ? {
5741
+ resultContract: {
5742
+ projection: "compact",
5743
+ rawWorkerPayloadsOmitted: true,
5744
+ deepDetailAvailableVia: ["--full", "worker status", "worker tail", "run status --full"]
5745
+ },
5746
+ drilldownCommands: buildWorkerListDrilldownCommands(runId)
5673
5747
  } : {},
5674
5748
  needsAttention: board.needsAttention,
5675
- ...compact && "summary" in board ? { summary: board.summary } : {},
5676
- ...compact && "controller" in board ? { controller: board.controller } : {},
5749
+ ..."summary" in board ? { summary: board.summary } : {},
5750
+ ..."controller" in board ? { controller: board.controller } : {},
5677
5751
  workers: outputWorkers
5678
5752
  },
5679
5753
  null,
@@ -5941,9 +6015,33 @@ async function publishHarnessBoardSnapshot(args, source) {
5941
6015
  authRefreshFailure: res.authRefreshFailure
5942
6016
  };
5943
6017
  }
6018
+ function buildRunStatusDrilldownCommands(runId) {
6019
+ return {
6020
+ full: `kynver run status --run ${runId} --full`,
6021
+ blocked: `kynver status --run ${runId} --blocked`,
6022
+ running: `kynver status --run ${runId} --running`,
6023
+ task: `kynver status --run ${runId} --task <task-id>`,
6024
+ worker: `kynver status --run ${runId} --worker <worker>`,
6025
+ workersFull: `kynver worker list --run ${runId} --full`,
6026
+ workerFull: `kynver worker status --run ${runId} --name <worker>`,
6027
+ workerTail: `kynver worker tail --run ${runId} --name <worker> --lines 80`,
6028
+ monitorTick: `kynver monitor status --run ${runId} --tick`
6029
+ };
6030
+ }
5944
6031
  function runStatus(args) {
5945
- const compact = args.compact === true || args.compact === "true";
5946
- const board = compact ? buildCompactRunBoard(resolveRunTargetArg(args)) : buildRunBoard(resolveRunTargetArg(args));
6032
+ const runId = resolveRunTargetArg(args);
6033
+ const explicitFull = args.full === true || args.full === "true";
6034
+ const board = applyWorkerFilters(explicitFull ? buildRunBoard(runId) : buildCompactRunBoard(runId), args);
6035
+ board.projection = explicitFull ? "full" : "compact";
6036
+ if (!explicitFull) {
6037
+ board.resultContract = {
6038
+ projection: "compact",
6039
+ rawWorkerPayloadsOmitted: true,
6040
+ omittedWorkerFields: ["finalResult", "changedFiles", "gitAncestry", "completionResponse", "stdout/stderr tails"],
6041
+ deepDetailAvailableVia: ["--full", "worker status", "worker tail", "monitor status --tick"]
6042
+ };
6043
+ board.drilldownCommands = buildRunStatusDrilldownCommands(runId);
6044
+ }
5947
6045
  console.log(JSON.stringify(board, null, 2));
5948
6046
  }
5949
6047
  function tailWorker(args) {
@@ -7081,6 +7179,10 @@ var READY_MERGE_STATES = /* @__PURE__ */ new Set(["CLEAN", "HAS_HOOKS"]);
7081
7179
  function repoArgs(repo) {
7082
7180
  return repo?.trim() ? ["--repo", repo.trim()] : [];
7083
7181
  }
7182
+ function repoFromPrUrl(prUrl) {
7183
+ const m = /github\.com\/([^/]+)\/([^/]+)\/pull\/\d+/i.exec(prUrl.trim());
7184
+ return m ? `${m[1]}/${m[2]}` : null;
7185
+ }
7084
7186
  function ghJson(exec, cwd, args) {
7085
7187
  const res = exec.gh(cwd, args);
7086
7188
  if (res.status !== 0) {
@@ -7136,14 +7238,21 @@ var STRUCTURED_SECTION_RE = /^##\s*(test\s*plan|verification|test\s*evidence|ver
7136
7238
  var LOCAL_VERIFICATION_RE = /typecheck|npm run (test|build|typecheck)|vitest|tsc\b|verify-pr-local|local verify|local-verify|tests? (pass|green)|build green|node --test/i;
7137
7239
  var DOCS_TITLE_RE = /^docs[(:]/i;
7138
7240
  var DOCS_BODY_RE = /docs[- ]only|documentation only|no[- ]code change|no code changes|low[- ]risk|plan tracker only|markdown only/i;
7139
- function assertVerificationEvidence(pr) {
7140
- const title = typeof pr.title === "string" ? pr.title : "";
7141
- const body = typeof pr.body === "string" ? pr.body : "";
7241
+ function runtimeVerificationEvidenceSufficient(input) {
7242
+ const title = typeof input.title === "string" ? input.title : "";
7243
+ const body = typeof input.body === "string" ? input.body : "";
7142
7244
  const docsLowRisk = DOCS_TITLE_RE.test(title) || DOCS_BODY_RE.test(body);
7143
7245
  const structuredSection = STRUCTURED_SECTION_RE.test(body);
7144
7246
  const localVerification = LOCAL_VERIFICATION_RE.test(body);
7145
- const vercelOk = vercelCheckSuccess(pr.statusCheckRollup);
7146
- if (docsLowRisk || structuredSection || localVerification || vercelOk) return;
7247
+ return docsLowRisk || structuredSection || localVerification || input.vercelCheckSuccess === true;
7248
+ }
7249
+ function assertVerificationEvidence(pr) {
7250
+ const sufficient = runtimeVerificationEvidenceSufficient({
7251
+ title: pr.title,
7252
+ body: pr.body,
7253
+ vercelCheckSuccess: vercelCheckSuccess(pr.statusCheckRollup)
7254
+ });
7255
+ if (sufficient) return;
7147
7256
  throw new Error(
7148
7257
  `PR #${pr.number} lacks landing verification evidence \u2014 add ## Test plan / ## Verification with local commands, Vercel preview, or docs/no-code rationale`
7149
7258
  );
@@ -7244,6 +7353,14 @@ async function executeLandPrMerge(input) {
7244
7353
  "mergeCommit"
7245
7354
  ].join(",")
7246
7355
  ]);
7356
+ if (before.state === "MERGED" || before.mergedAt) {
7357
+ return {
7358
+ prUrl: before.url || prTarget,
7359
+ outcome: "skipped",
7360
+ mergeCommit: before.mergeCommit?.oid ?? null,
7361
+ reason: `PR #${before.number} is already merged \u2014 land_pr no-op (redelivery)`
7362
+ };
7363
+ }
7247
7364
  const notReadyReason = landingReadinessError(before);
7248
7365
  if (notReadyReason) {
7249
7366
  if (input.skipNotReady) {
@@ -7598,7 +7715,11 @@ async function dispatchRun(args) {
7598
7715
  return abortClaimedSpawn(task, "land_pr task missing prUrl");
7599
7716
  }
7600
7717
  try {
7601
- const report = await executeLandPrMerge({ prUrl, cwd: process.cwd() });
7718
+ const report = await executeLandPrMerge({
7719
+ prUrl,
7720
+ repo: repoFromPrUrl(prUrl),
7721
+ cwd: run.repo
7722
+ });
7602
7723
  const post = await postLandPrHarnessCompletion({
7603
7724
  baseUrl: base,
7604
7725
  secret,
@@ -7625,6 +7746,27 @@ async function dispatchRun(args) {
7625
7746
  return abortClaimedSpawn(task, error.message);
7626
7747
  }
7627
7748
  }
7749
+ async function releaseDuplicateLocalClaim(task) {
7750
+ const error = "duplicate_dispatch_prevented: live local worker already owns this task";
7751
+ const release = await releaseDispatchClaimAfterSpawnFailure({
7752
+ baseUrl: base,
7753
+ secret,
7754
+ agentOsId,
7755
+ taskId: String(task.id),
7756
+ leaseOwner,
7757
+ failureDetail: error
7758
+ });
7759
+ outcomes.push({
7760
+ taskId: task.id,
7761
+ started: false,
7762
+ error,
7763
+ alreadyRunning: true,
7764
+ nonFatal: true,
7765
+ released: release.released,
7766
+ releaseResponse: release.releaseResponse
7767
+ });
7768
+ return false;
7769
+ }
7628
7770
  async function spawnClaimed(decision) {
7629
7771
  const task = decision.task;
7630
7772
  const harnessContext = readHarnessWorkerContext(decision);
@@ -7646,10 +7788,7 @@ async function dispatchRun(args) {
7646
7788
  );
7647
7789
  }
7648
7790
  if (hasLiveWorkerForTask(run.id, taskId)) {
7649
- return abortClaimedSpawn(
7650
- task,
7651
- "duplicate_dispatch_prevented: live local worker already owns this task"
7652
- );
7791
+ return releaseDuplicateLocalClaim(task);
7653
7792
  }
7654
7793
  const attempt = Number(task.attempt) || 1;
7655
7794
  if (attempt > retryLimits.maxTaskAttempts) {
@@ -7819,11 +7958,12 @@ async function dispatchRun(args) {
7819
7958
  diskGate: result.diskGate,
7820
7959
  resourceGate: result.resourceGate
7821
7960
  };
7961
+ const fatalOutcome = (outcome) => !outcome.started && outcome.nonFatal !== true;
7822
7962
  if (pipeline) {
7823
- return { ok: !outcomes.some((o) => !o.started), ...summary };
7963
+ return { ok: !outcomes.some(fatalOutcome), ...summary };
7824
7964
  }
7825
7965
  console.log(JSON.stringify(summary, null, 2));
7826
- if (outcomes.some((o) => !o.started)) process.exit(1);
7966
+ if (outcomes.some(fatalOutcome)) process.exit(1);
7827
7967
  } catch (error) {
7828
7968
  if (pipeline) return { ok: false, error: error.message };
7829
7969
  console.error(`run dispatch failed: ${error.message}`);
@@ -9057,11 +9197,29 @@ function validateDaemonInstallIdentity(config = loadUserConfig(), env = process.
9057
9197
  };
9058
9198
  }
9059
9199
 
9200
+ // src/daemon-platform-guard.ts
9201
+ function envFlag(name) {
9202
+ const raw = process.env[name]?.trim().toLowerCase();
9203
+ return raw === "1" || raw === "true" || raw === "yes" || raw === "on";
9204
+ }
9205
+ function assertNativeDaemonAllowed() {
9206
+ if (process.platform !== "win32") return;
9207
+ if (envFlag("KYNVER_DAEMON_ALLOW_NATIVE_WINDOWS")) return;
9208
+ console.error(
9209
+ JSON.stringify({
9210
+ event: "daemon_start_blocked",
9211
+ reason: "native_windows_console_flash",
9212
+ remedy: "Run the daemon inside WSL: .\\scripts\\start-tier2-wsl.ps1 \u2014 or set KYNVER_DAEMON_ALLOW_NATIVE_WINDOWS=1 to override (flashes visible consoles)."
9213
+ })
9214
+ );
9215
+ process.exit(1);
9216
+ }
9217
+
9060
9218
  // src/cron/cron-env.ts
9061
9219
  import { existsSync as existsSync27 } from "node:fs";
9062
9220
  import { homedir as homedir11 } from "node:os";
9063
9221
  import path37 from "node:path";
9064
- function envFlag(name, defaultValue) {
9222
+ function envFlag2(name, defaultValue) {
9065
9223
  const raw = process.env[name]?.trim().toLowerCase();
9066
9224
  if (!raw) return defaultValue;
9067
9225
  if (raw === "0" || raw === "false" || raw === "no" || raw === "off") return false;
@@ -9097,14 +9255,14 @@ function resolveKynverCronEnv() {
9097
9255
  const secret = resolveKynverCronSecret();
9098
9256
  const credsReady = Boolean(fireBaseUrl && secret);
9099
9257
  const storeExists = existsSync27(storePath);
9100
- const defaultEnabled = credsReady && (storeExists || envFlag("KYNVER_CRON_TICK_FORCE", false));
9258
+ const defaultEnabled = credsReady && (storeExists || envFlag2("KYNVER_CRON_TICK_FORCE", false));
9101
9259
  return {
9102
9260
  storePath,
9103
9261
  statePath,
9104
9262
  lockPath: `${statePath}.lock`,
9105
9263
  fireBaseUrl,
9106
9264
  secret,
9107
- tickEnabled: envFlag("KYNVER_CRON_TICK_ENABLED", defaultEnabled),
9265
+ tickEnabled: envFlag2("KYNVER_CRON_TICK_ENABLED", defaultEnabled),
9108
9266
  tickIntervalMs: envInt("KYNVER_CRON_TICK_INTERVAL_MS", 6e4, 5e3),
9109
9267
  missedRunPolicy: process.env.KYNVER_CRON_MISSED_RUN_POLICY?.trim().toLowerCase() === "skip" ? "skip" : "catch_up",
9110
9268
  maxCatchUpPerTick: envInt("KYNVER_CRON_MAX_CATCH_UP_PER_TICK", 3, 0),
@@ -9791,23 +9949,6 @@ function materialWorktreeChanges2(changedFiles) {
9791
9949
  });
9792
9950
  }
9793
9951
 
9794
- // src/cleanup-index-status.ts
9795
- function indexedWorktreeStatus(entry) {
9796
- if (!entry.status) {
9797
- entry.status = computeWorkerStatus(entry.worker, {
9798
- base: entry.run.base,
9799
- baseCommit: entry.run.baseCommit
9800
- });
9801
- }
9802
- return entry.status;
9803
- }
9804
- function indexedWorktreeHasMaterialChanges(entry) {
9805
- if (entry.status) {
9806
- return materialWorktreeChanges2(entry.status.changedFiles).length > 0;
9807
- }
9808
- return materialWorktreeChanges2(gitStatusShort(entry.worktreePath)).length > 0;
9809
- }
9810
-
9811
9952
  // src/cleanup-worktree-salvage.ts
9812
9953
  function prUrlFromFinalResult(finalResult) {
9813
9954
  if (typeof finalResult === "string") {
@@ -9834,6 +9975,83 @@ function isPrOrUnmergedWork(status) {
9834
9975
  return false;
9835
9976
  }
9836
9977
 
9978
+ // src/cleanup-index-status.ts
9979
+ function indexedWorktreeStatus(entry) {
9980
+ if (!entry.status) {
9981
+ entry.status = computeWorkerStatus(entry.worker, {
9982
+ base: entry.run.base,
9983
+ baseCommit: entry.run.baseCommit
9984
+ });
9985
+ }
9986
+ return entry.status;
9987
+ }
9988
+ function indexedWorktreeHasMaterialChanges(entry, gitStatusCache) {
9989
+ if (entry.status) {
9990
+ return materialWorktreeChanges2(entry.status.changedFiles).length > 0;
9991
+ }
9992
+ const porcelain = gitStatusCache ? gitStatusCache.porcelain(entry.worktreePath) : gitStatusShort(entry.worktreePath);
9993
+ return materialWorktreeChanges2(porcelain).length > 0;
9994
+ }
9995
+ function finalResultFromWorkerJson(entry) {
9996
+ const snapshot = entry.worker.completionSnapshot?.finalResult;
9997
+ if (snapshot !== void 0 && snapshot !== null) return snapshot;
9998
+ if (entry.worker.taskPrUrl) {
9999
+ return { prUrl: entry.worker.taskPrUrl };
10000
+ }
10001
+ return null;
10002
+ }
10003
+ function resolveWorktreeGuardStatus(entry, ctx) {
10004
+ if (entry.status) return entry.status;
10005
+ const worker = entry.worker;
10006
+ const completionAcknowledged = typeof worker.completionReportedAt === "string" && worker.completionReportedAt.trim().length > 0;
10007
+ const workerJsonTerminal = Boolean(worker.status && ["done", "exited", "blocked", "failed", "abandoned"].includes(worker.status)) || completionAcknowledged;
10008
+ const finalResult = finalResultFromWorkerJson(entry);
10009
+ if (workerJsonTerminal && !isPidAlive(worker.pid)) {
10010
+ const changedFiles = ctx?.gitStatusCache ? ctx.gitStatusCache.porcelain(entry.worktreePath) : gitStatusShort(entry.worktreePath);
10011
+ const baseLabel = entry.run.baseCommit?.trim() || entry.run.base?.trim() || "origin/main";
10012
+ const ahead = ctx?.gitRevCache?.countAheadOfMain(entry.worktreePath, baseLabel);
10013
+ const gitAncestry = ahead === 0 ? {
10014
+ checked: true,
10015
+ base: baseLabel,
10016
+ relation: "synced"
10017
+ } : computeGitAncestry(entry.worktreePath, {
10018
+ base: entry.run.base,
10019
+ baseCommit: entry.run.baseCommit
10020
+ });
10021
+ const status = {
10022
+ runId: entry.runId,
10023
+ worker: entry.workerName,
10024
+ pid: worker.pid,
10025
+ alive: false,
10026
+ status: worker.status ?? (completionAcknowledged ? "done" : "exited"),
10027
+ attention: { state: completionAcknowledged ? "done" : "stale" },
10028
+ branch: worker.branch,
10029
+ worktreePath: entry.worktreePath,
10030
+ ownedPaths: worker.ownedPaths,
10031
+ stdoutBytes: 0,
10032
+ stderrBytes: 0,
10033
+ heartbeatBytes: 0,
10034
+ firstEventAt: null,
10035
+ lastEventAt: null,
10036
+ lastActivityAt: worker.completionReportedAt ?? null,
10037
+ currentTool: null,
10038
+ heartbeatCount: 0,
10039
+ lastHeartbeatAt: null,
10040
+ lastHeartbeatPhase: null,
10041
+ lastHeartbeatSummary: null,
10042
+ heartbeatBlocker: null,
10043
+ changedFiles,
10044
+ gitAncestry,
10045
+ finalResult,
10046
+ completionBlocker: typeof worker.completionBlocker === "string" ? worker.completionBlocker.trim() || null : null,
10047
+ prUrl: worker.repairTargetPrUrl ?? worker.taskPrUrl ?? prUrlFromFinalResult(finalResult)
10048
+ };
10049
+ entry.status = status;
10050
+ return status;
10051
+ }
10052
+ return indexedWorktreeStatus(entry);
10053
+ }
10054
+
9837
10055
  // src/cleanup-completion-blocker.ts
9838
10056
  function completionBlockerBlocksWorktreeRemoval(indexed, status) {
9839
10057
  const blocker = typeof indexed.worker.completionBlocker === "string" ? indexed.worker.completionBlocker.trim() : "";
@@ -9854,13 +10072,28 @@ function completionBlockerBlocksWorktreeRemoval(indexed, status) {
9854
10072
  }
9855
10073
 
9856
10074
  // src/cleanup-run-liveness.ts
10075
+ var TERMINAL_WORKER_JSON_STATUSES = /* @__PURE__ */ new Set([
10076
+ "done",
10077
+ "exited",
10078
+ "blocked",
10079
+ "failed",
10080
+ "abandoned"
10081
+ ]);
9857
10082
  function deriveRunTerminal(indexed, ctx) {
9858
10083
  if (ctx) return ctx.runTerminalCache.derive(indexed.run);
9859
10084
  return deriveTerminalRunStatus(indexed.run);
9860
10085
  }
9861
10086
  function isWorkerProcessLive(indexed) {
9862
10087
  if (isPidAlive(indexed.worker.pid)) return true;
9863
- if (!indexed.worker.pid) return indexedWorktreeStatus(indexed).alive;
10088
+ if (typeof indexed.worker.completionReportedAt === "string" && indexed.worker.completionReportedAt.trim().length > 0) {
10089
+ return false;
10090
+ }
10091
+ const workerStatus2 = indexed.worker.status;
10092
+ if (workerStatus2 && TERMINAL_WORKER_JSON_STATUSES.has(workerStatus2)) return false;
10093
+ if (!indexed.worker.pid) {
10094
+ if (workerStatus2 !== "running") return false;
10095
+ return indexedWorktreeStatus(indexed).alive;
10096
+ }
9864
10097
  return false;
9865
10098
  }
9866
10099
  function isRunStaleActive(indexed, ctx) {
@@ -9869,6 +10102,10 @@ function isRunStaleActive(indexed, ctx) {
9869
10102
  }
9870
10103
  function runBlocksWorktreeRemoval(indexed, ctx) {
9871
10104
  if (isWorkerProcessLive(indexed)) return true;
10105
+ const workerStatus2 = indexed.worker.status;
10106
+ if (workerStatus2 && TERMINAL_WORKER_JSON_STATUSES.has(workerStatus2) && !indexed.worker.completionBlocker) {
10107
+ return false;
10108
+ }
9872
10109
  const status = indexedWorktreeStatus(indexed);
9873
10110
  if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return true;
9874
10111
  if (isFinishedWorkerStatus(status)) return false;
@@ -9887,7 +10124,7 @@ function effectiveWorktreeAgeMs(input) {
9887
10124
  if (input.liveness && isRunStaleActive(indexed, input.liveness)) {
9888
10125
  return terminalWorktreesAgeMs;
9889
10126
  }
9890
- if (input.liveness && isFinishedWorkerStatus(indexedWorktreeStatus(indexed)) && !isWorkerProcessLive(indexed)) {
10127
+ if (input.liveness && isFinishedWorkerStatus(resolveWorktreeGuardStatus(indexed, input.liveness)) && !isWorkerProcessLive(indexed)) {
9891
10128
  return terminalWorktreesAgeMs;
9892
10129
  }
9893
10130
  return worktreesAgeMs;
@@ -9901,8 +10138,13 @@ function skipWorktreeRemoval(input) {
9901
10138
  const ageThresholdMs = effectiveWorktreeAgeMs(input);
9902
10139
  if (worktreesAgeMs <= 0 && !includeOrphans && ageThresholdMs <= 0) return "worktrees_disabled";
9903
10140
  if (ageThresholdMs > 0 && ageMs < ageThresholdMs) return "below_age_threshold";
9904
- const status = indexedWorktreeStatus(indexed);
9905
10141
  if (isWorkerProcessLive(indexed)) return "active_worker";
10142
+ if (indexedWorktreeHasMaterialChanges(indexed, input.liveness?.gitStatusCache)) {
10143
+ return "dirty_worktree";
10144
+ }
10145
+ const ahead = input.liveness?.gitRevCache?.countAheadOfMain(input.worktreePath);
10146
+ if (ahead !== null && ahead !== void 0 && ahead > 0) return "pr_or_unmerged_commits";
10147
+ const status = resolveWorktreeGuardStatus(indexed, input.liveness);
9906
10148
  if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return "completion_blocked";
9907
10149
  if (runBlocksWorktreeRemoval(indexed, input.liveness)) return "run_still_active";
9908
10150
  if (!isFinishedWorkerStatus(status)) return "run_still_active";
@@ -9933,7 +10175,9 @@ function skipDependencyCacheRemoval(input) {
9933
10175
  if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
9934
10176
  if (activeWorktreePaths.has(path40.resolve(worktreePath))) return "active_worker";
9935
10177
  if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
9936
- if (indexed && indexedWorktreeHasMaterialChanges(indexed)) return "dirty_worktree";
10178
+ if (indexed && indexedWorktreeHasMaterialChanges(indexed, input.gitStatusCache)) {
10179
+ return "dirty_worktree";
10180
+ }
9937
10181
  return null;
9938
10182
  }
9939
10183
  function skipBuildCacheRemoval(input) {
@@ -10775,7 +11019,7 @@ function buildWorktreeIndexAt(harnessRoot) {
10775
11019
  }
10776
11020
 
10777
11021
  // src/cleanup-retention-config.ts
10778
- function envFlag2(name) {
11022
+ function envFlag3(name) {
10779
11023
  const v = process.env[name];
10780
11024
  return v === "1" || v === "true" || v === "yes";
10781
11025
  }
@@ -10786,17 +11030,17 @@ function envMs(name, fallback) {
10786
11030
  return Number.isFinite(n) && n >= 0 ? n : fallback;
10787
11031
  }
10788
11032
  function resolveHarnessRetention(options = {}) {
10789
- const execute = options.execute === true || options.execute !== false && envFlag2("KYNVER_CLEANUP_EXECUTE");
10790
- const finalizeStaleRuns2 = options.finalizeStaleRuns !== false && !envFlag2("KYNVER_CLEANUP_SKIP_FINALIZE");
11033
+ const execute = options.execute === true || options.execute !== false && envFlag3("KYNVER_CLEANUP_EXECUTE");
11034
+ const finalizeStaleRuns2 = options.finalizeStaleRuns !== false && !envFlag3("KYNVER_CLEANUP_SKIP_FINALIZE");
10791
11035
  const nodeModulesAgeMs = options.nodeModulesAgeMs ?? envMs("KYNVER_CLEANUP_NODE_MODULES_AGE_MS", DEFAULT_NODE_MODULES_AGE_MS);
10792
11036
  const worktreesAgeMs = options.worktreesAgeMs ?? envMs("KYNVER_CLEANUP_WORKTREES_AGE_MS", 0);
10793
11037
  const terminalWorktreesAgeMs = options.terminalWorktreesAgeMs ?? envMs("KYNVER_CLEANUP_TERMINAL_WORKTREES_AGE_MS", DEFAULT_TERMINAL_WORKTREES_AGE_MS);
10794
11038
  const runDirectoriesAgeMs = options.runDirectoriesAgeMs ?? envMs("KYNVER_CLEANUP_RUN_DIRECTORIES_AGE_MS", DEFAULT_RUN_DIRECTORIES_AGE_MS);
10795
11039
  const maxActionsPerSweep = options.maxActionsPerSweep ?? envMs("KYNVER_CLEANUP_MAX_ACTIONS_PER_SWEEP", DEFAULT_MAX_ACTIONS_PER_SWEEP);
10796
- const includeOrphans = options.includeOrphans === true || envFlag2("KYNVER_CLEANUP_INCLUDE_ORPHANS");
10797
- const scopeAll = envFlag2("KYNVER_CLEANUP_SCOPE_ALL") || process.env.KYNVER_CLEANUP_SCOPE === "all";
11040
+ const includeOrphans = options.includeOrphans === true || envFlag3("KYNVER_CLEANUP_INCLUDE_ORPHANS");
11041
+ const scopeAll = envFlag3("KYNVER_CLEANUP_SCOPE_ALL") || process.env.KYNVER_CLEANUP_SCOPE === "all";
10798
11042
  const runIdFilter = scopeAll ? options.runIdFilter : options.runIdFilter ?? (process.env.KYNVER_CLEANUP_RUN_ID || void 0);
10799
- const accountBytes = options.accountBytes !== false && !envFlag2("KYNVER_CLEANUP_SKIP_BYTE_ACCOUNTING");
11043
+ const accountBytes = options.accountBytes !== false && !envFlag3("KYNVER_CLEANUP_SKIP_BYTE_ACCOUNTING");
10800
11044
  const storageCapEnv = envMs("KYNVER_CLEANUP_STORAGE_ENTRY_CAP", 2e3);
10801
11045
  const storagePerRunEntryCap = options.storagePerRunEntryCap !== void 0 ? options.storagePerRunEntryCap : accountBytes ? storageCapEnv > 0 ? storageCapEnv : null : null;
10802
11046
  const byteCapEnv = envMs("KYNVER_CLEANUP_BYTE_ENTRY_CAP", 2e3);
@@ -10993,7 +11237,7 @@ function resolveHarnessScanRoots(options = {}) {
10993
11237
  }
10994
11238
 
10995
11239
  // src/cleanup-disk-pressure.ts
10996
- function envFlag3(name) {
11240
+ function envFlag4(name) {
10997
11241
  const v = process.env[name];
10998
11242
  return v === "1" || v === "true" || v === "yes";
10999
11243
  }
@@ -11016,7 +11260,7 @@ function observeCleanupDiskPressure(input = {}) {
11016
11260
  }
11017
11261
  function applyDiskPressureToRetention(retention, pressure) {
11018
11262
  if (!pressure.pressured) return retention;
11019
- const executeOnPressure = retention.execute || !envFlag3("KYNVER_CLEANUP_DRY_RUN_ON_PRESSURE");
11263
+ const executeOnPressure = retention.execute || !envFlag4("KYNVER_CLEANUP_DRY_RUN_ON_PRESSURE");
11020
11264
  return {
11021
11265
  ...retention,
11022
11266
  execute: executeOnPressure,
@@ -11041,6 +11285,37 @@ function emitCleanupProgress(phase, detail) {
11041
11285
  console.error(`[kynver cleanup] ${phase}${suffix}`);
11042
11286
  }
11043
11287
 
11288
+ // src/cleanup-git-rev-cache.ts
11289
+ var CleanupGitRevCache = class {
11290
+ aheadOfMain = /* @__PURE__ */ new Map();
11291
+ countAheadOfMain(worktreePath, base = "origin/main") {
11292
+ const key = `${worktreePath}\0${base}`;
11293
+ if (this.aheadOfMain.has(key)) return this.aheadOfMain.get(key) ?? null;
11294
+ const result = gitCapture(worktreePath, ["rev-list", "--count", `${base}..HEAD`]);
11295
+ if (result.status !== 0) {
11296
+ this.aheadOfMain.set(key, null);
11297
+ return null;
11298
+ }
11299
+ const count = Number(result.stdout.trim());
11300
+ const parsed = Number.isFinite(count) ? count : null;
11301
+ this.aheadOfMain.set(key, parsed);
11302
+ return parsed;
11303
+ }
11304
+ };
11305
+
11306
+ // src/cleanup-git-status-cache.ts
11307
+ var CleanupGitStatusCache = class {
11308
+ cache = /* @__PURE__ */ new Map();
11309
+ porcelain(worktreePath) {
11310
+ const resolved = worktreePath;
11311
+ const cached = this.cache.get(resolved);
11312
+ if (cached !== void 0) return cached;
11313
+ const lines = gitStatusShort(resolved);
11314
+ this.cache.set(resolved, lines);
11315
+ return lines;
11316
+ }
11317
+ };
11318
+
11044
11319
  // src/cleanup-run-terminal-cache.ts
11045
11320
  var CleanupRunTerminalCache = class {
11046
11321
  cache = /* @__PURE__ */ new Map();
@@ -11158,7 +11433,11 @@ function runHarnessCleanup(options = {}) {
11158
11433
  emitCleanupProgress("index", "building worktree index");
11159
11434
  const index = mergeWorktreeIndexes(paths.scanRoots);
11160
11435
  emitCleanupProgress("index", `${index.size} indexed worktree(s)`);
11161
- const liveness = { runTerminalCache: new CleanupRunTerminalCache() };
11436
+ const liveness = {
11437
+ runTerminalCache: new CleanupRunTerminalCache(),
11438
+ gitStatusCache: new CleanupGitStatusCache(),
11439
+ gitRevCache: new CleanupGitRevCache()
11440
+ };
11162
11441
  const skips = [];
11163
11442
  const actions = [];
11164
11443
  const processedPaths = /* @__PURE__ */ new Set();
@@ -11178,8 +11457,15 @@ function runHarnessCleanup(options = {}) {
11178
11457
  index,
11179
11458
  now: paths.now
11180
11459
  };
11181
- for (const raw of scanDependencyCacheCandidates(scanOpts)) {
11460
+ const dependencyCandidates = scanDependencyCacheCandidates(scanOpts);
11461
+ emitCleanupProgress("dependency", `${dependencyCandidates.length} cache candidate(s) at ${harnessRoot}`);
11462
+ let dependencyProcessed = 0;
11463
+ for (const raw of dependencyCandidates) {
11182
11464
  if (atSweepCap()) break;
11465
+ dependencyProcessed += 1;
11466
+ if (dependencyProcessed % 50 === 0) {
11467
+ emitCleanupProgress("dependency", `${dependencyProcessed}/${dependencyCandidates.length} evaluated`);
11468
+ }
11183
11469
  const resolved = path53.resolve(raw.path);
11184
11470
  if (processedPaths.has(resolved)) continue;
11185
11471
  processedPaths.add(resolved);
@@ -11199,7 +11485,8 @@ function runHarnessCleanup(options = {}) {
11199
11485
  ageMs: candidate.ageMs,
11200
11486
  worktreePath,
11201
11487
  activeWorktreePaths: activeGuards.activeWorktreePaths,
11202
- diskPressure: retention.diskPressure
11488
+ diskPressure: retention.diskPressure,
11489
+ gitStatusCache: liveness.gitStatusCache
11203
11490
  });
11204
11491
  if (guardReason) {
11205
11492
  recordSkip(skips, candidate.path, guardReason);
@@ -11234,7 +11521,8 @@ function runHarnessCleanup(options = {}) {
11234
11521
  ageMs: candidate.ageMs,
11235
11522
  worktreePath,
11236
11523
  activeWorktreePaths: activeGuards.activeWorktreePaths,
11237
- diskPressure: retention.diskPressure
11524
+ diskPressure: retention.diskPressure,
11525
+ gitStatusCache: liveness.gitStatusCache
11238
11526
  });
11239
11527
  if (guardReason) {
11240
11528
  recordSkip(skips, candidate.path, guardReason);
@@ -11254,8 +11542,13 @@ function runHarnessCleanup(options = {}) {
11254
11542
  ];
11255
11543
  emitCleanupProgress("worktrees", `${worktreeCandidates.length} candidate(s) at ${harnessRoot}`);
11256
11544
  const worktreeSeen = /* @__PURE__ */ new Set();
11545
+ let worktreeProcessed = 0;
11257
11546
  for (const raw of worktreeCandidates) {
11258
11547
  if (atSweepCap()) break;
11548
+ worktreeProcessed += 1;
11549
+ if (worktreeProcessed % 50 === 0) {
11550
+ emitCleanupProgress("worktrees", `${worktreeProcessed}/${worktreeCandidates.length} evaluated`);
11551
+ }
11259
11552
  const resolved = path53.resolve(raw.path);
11260
11553
  if (worktreeSeen.has(resolved)) continue;
11261
11554
  worktreeSeen.add(resolved);
@@ -11851,6 +12144,7 @@ async function awaitDaemonBackoff(ms, isStopping) {
11851
12144
  }
11852
12145
  }
11853
12146
  async function runDaemon(args) {
12147
+ assertNativeDaemonAllowed();
11854
12148
  const runId = String(required(String(args.run || ""), "--run"));
11855
12149
  const agentOsId = String(required(String(args.agentOsId || loadUserConfig().agentOsId || ""), "--agent-os-id"));
11856
12150
  const execute = args.execute !== false && args.execute !== "false";
@@ -13831,12 +14125,12 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
13831
14125
  return check({
13832
14126
  id: "hotspot_openclaw_scheduler",
13833
14127
  label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
13834
- status: "pass",
13835
- summary: "KYNVER_SCHEDULER_PROVIDER unset on hosted deployment; scheduler resolves to kynver-cron (Kynver-owned, not OpenClaw)",
13836
- remediation: "For production hosted AgentOS schedules, set QSTASH_TOKEN and KYNVER_SCHEDULER_PROVIDER=qstash on the Kynver server. Dev/harness hosts may rely on kynver-cron or `kynver daemon` without OpenClaw.",
14128
+ status: "warn",
14129
+ summary: "Hosted deployment with no QSTASH_TOKEN and no KYNVER_SCHEDULER_PROVIDER \u2014 kynver-cron is an in-process stub here and will NOT fire; no firing scheduler is configured",
14130
+ remediation: "Set QSTASH_TOKEN + KYNVER_SCHEDULER_PROVIDER=qstash on the Kynver server for hosted AgentOS schedules. Only use kynver-cron when a `kynver daemon` (or local cron store) actually owns firing on this box.",
13837
14131
  details: {
13838
14132
  schedulerProvider: null,
13839
- resolvedFallback: "kynver-cron",
14133
+ resolvedFallback: "none",
13840
14134
  qstashTokenPresent: false,
13841
14135
  hostedDeployment
13842
14136
  }
@@ -14638,14 +14932,15 @@ function usage(code = 0) {
14638
14932
  " kynver runner credential [--agent-os-id ID] [--base-url URL]",
14639
14933
  " 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]",
14640
14934
  " kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS]",
14935
+ " kynver status --run RUN_ID [--blocked] [--running] [--task TASK_ID] [--worker WORKER] [--full] # top-level compact run status",
14641
14936
  " kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
14642
14937
  " kynver run list",
14643
14938
  " kynver run resolve --name RUN_NAME",
14644
- " kynver run status --run RUN_ID [--json] [--compact]",
14939
+ " kynver run status --run RUN_ID [--full] # defaults to compact shallow map with drill-down commands",
14645
14940
  " kynver run dispatch --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--execute] [--lane any|implementation|review|landing] [--target-task-id TASK_ID] [--executor harness] [--max-starts 1] [--lease-ms MS] [--owned path[,path]] [--model claude-opus-4-8] [--disk-path /] [--reconcile-stale-blockers]",
14646
14941
  " kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
14647
14942
  ' kynver worker start --run RUN_ID --name worker --task "..." [--owned path[,path]] [--model MODEL] [--provider claude|cursor] [--agent-os-id AOS_ID] [--task-id TASK_ID] [--wait]',
14648
- " kynver worker list --run RUN_ID [--json] [--compact] [--full]",
14943
+ " kynver worker list --run RUN_ID [--full] # defaults to compact shallow map with drill-down commands",
14649
14944
  " kynver worker status --run RUN_ID --name worker",
14650
14945
  " kynver worker tail --run RUN_ID --name worker [--lines 40] [--raw]",
14651
14946
  " kynver worker stop --run RUN_ID --name worker",
@@ -14699,6 +14994,9 @@ async function main(argv = process.argv.slice(2)) {
14699
14994
  const { runsDir, worktreesDir } = getPaths();
14700
14995
  mkdirSync10(runsDir, { recursive: true });
14701
14996
  mkdirSync10(worktreesDir, { recursive: true });
14997
+ if (scope === "daemon") {
14998
+ assertNativeDaemonAllowed();
14999
+ }
14702
15000
  if (shouldEnforceMemoryCostPackageGuardCli(scope, action)) {
14703
15001
  let repoRoot;
14704
15002
  const runId = args.run ? String(args.run).trim() : "";
@@ -14715,6 +15013,7 @@ async function main(argv = process.argv.slice(2)) {
14715
15013
  });
14716
15014
  }
14717
15015
  if (scope === "login") return void await runLogin(args);
15016
+ if (scope === "status") return runStatus(args);
14718
15017
  if (scope === "runner" && action === "credential") return void await mintRunnerCredential(args);
14719
15018
  if (scope === "setup") return void await runSetup(args);
14720
15019
  if (scope === "daemon") return void await runDaemon(args);