@kynver-app/runtime 0.1.106 → 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/index.js CHANGED
@@ -214,7 +214,11 @@ function scrubClaudeEnv(env) {
214
214
 
215
215
  // src/git.ts
216
216
  function git(cwd, args, options = {}) {
217
- const res = spawnSync("git", args, { cwd, encoding: "utf8" });
217
+ const res = spawnSync(
218
+ "git",
219
+ args,
220
+ hiddenSpawnOptions({ cwd, encoding: "utf8" })
221
+ );
218
222
  if (res.status !== 0 && !options.allowFailure) {
219
223
  const message = `git ${args.join(" ")} failed: ${res.stderr || res.stdout}`;
220
224
  if (options.throwError) throw new Error(message);
@@ -230,7 +234,11 @@ function gitStatusShort(worktreePath) {
230
234
  }
231
235
  function gitCapture(cwd, args) {
232
236
  try {
233
- const res = spawnSync("git", args, { cwd, encoding: "utf8" });
237
+ const res = spawnSync(
238
+ "git",
239
+ args,
240
+ hiddenSpawnOptions({ cwd, encoding: "utf8" })
241
+ );
234
242
  return {
235
243
  status: res.status,
236
244
  stdout: res.stdout || "",
@@ -6049,32 +6057,98 @@ function workerStatus(args) {
6049
6057
  writeJson(path21.join(worker.workerDir, "last-status.json"), status);
6050
6058
  console.log(JSON.stringify(status, null, 2));
6051
6059
  }
6060
+ function buildWorkerListDrilldownCommands(runId) {
6061
+ return {
6062
+ full: `kynver worker list --run ${runId} --full`,
6063
+ blocked: `kynver worker list --run ${runId} --blocked`,
6064
+ running: `kynver worker list --run ${runId} --running`,
6065
+ task: `kynver worker list --run ${runId} --task <task-id>`,
6066
+ worker: `kynver worker list --run ${runId} --worker <worker>`,
6067
+ runFull: `kynver run status --run ${runId} --full`,
6068
+ workerFull: `kynver worker status --run ${runId} --name <worker>`,
6069
+ workerTail: `kynver worker tail --run ${runId} --name <worker> --lines 80`
6070
+ };
6071
+ }
6072
+ function asOptionalArg(value) {
6073
+ if (typeof value !== "string") return void 0;
6074
+ const trimmed = value.trim();
6075
+ return trimmed.length ? trimmed : void 0;
6076
+ }
6077
+ function activeWorkerFilters(args) {
6078
+ const filters = {};
6079
+ if (args.blocked === true || args.blocked === "true") filters.blocked = true;
6080
+ if (args.running === true || args.running === "true") filters.running = true;
6081
+ const task = asOptionalArg(args.task ?? args.taskId);
6082
+ if (task) filters.task = task;
6083
+ const worker = asOptionalArg(args.worker ?? args.name);
6084
+ if (worker) filters.worker = worker;
6085
+ const status = asOptionalArg(args.status);
6086
+ if (status) filters.status = status;
6087
+ return filters;
6088
+ }
6089
+ function workerMatchesFilters(worker, filters) {
6090
+ if (filters.blocked) {
6091
+ const attention = typeof worker.attention === "string" ? worker.attention : "";
6092
+ const status = typeof worker.status === "string" ? worker.status : "";
6093
+ if (!(attention === "blocked" || attention === "needs_attention" || attention === "stale" || status === "blocked")) {
6094
+ return false;
6095
+ }
6096
+ }
6097
+ if (filters.running) {
6098
+ if (!(worker.status === "running" && worker.attention !== "blocked" && worker.attention !== "needs_attention")) {
6099
+ return false;
6100
+ }
6101
+ }
6102
+ if (typeof filters.task === "string" && worker.taskId !== filters.task) return false;
6103
+ if (typeof filters.worker === "string" && worker.worker !== filters.worker) return false;
6104
+ if (typeof filters.status === "string" && worker.status !== filters.status) return false;
6105
+ return true;
6106
+ }
6107
+ function applyWorkerFilters(board, args) {
6108
+ const filters = activeWorkerFilters(args);
6109
+ if (Object.keys(filters).length === 0) return board;
6110
+ const workers = board.workers.filter((worker) => workerMatchesFilters(worker, filters));
6111
+ const filtered = {
6112
+ ...board,
6113
+ workerCount: workers.length,
6114
+ needsAttention: workers.filter((w) => w.attention && w.attention !== "ok" && w.attention !== "done").map((w) => w.worker),
6115
+ workers,
6116
+ activeFilters: filters
6117
+ };
6118
+ if (board.summary) {
6119
+ filtered.summary = {
6120
+ statusCounts: countBy(workers, "status"),
6121
+ attentionCounts: countBy(workers, "attention"),
6122
+ lifecycleCounts: countBy(workers, "lifecycleStage")
6123
+ };
6124
+ }
6125
+ if (board.controller) filtered.controller = buildControllerSummary(workers);
6126
+ return filtered;
6127
+ }
6052
6128
  function workerList(args) {
6053
6129
  const runId = resolveRunTargetArg(args);
6054
- const explicitCompact = args.compact === true || args.compact === "true";
6055
6130
  const explicitFull = args.full === true || args.full === "true";
6056
- const run = loadRun(runId);
6057
- const workerCount = Object.keys(run.workers || {}).length;
6058
- const compact = explicitCompact || !explicitFull && workerCount > 100;
6059
- const board = compact ? buildCompactRunBoard(runId) : buildRunBoard(runId);
6060
- const autoCompact = compact && !explicitCompact;
6061
- const outputWorkers = autoCompact ? board.workers.filter((worker) => worker.attention && worker.attention !== "done" && worker.attention !== "ok") : board.workers;
6131
+ const board = applyWorkerFilters(explicitFull ? buildRunBoard(runId) : buildCompactRunBoard(runId), args);
6132
+ const outputWorkers = board.workers;
6062
6133
  console.log(
6063
6134
  JSON.stringify(
6064
6135
  {
6065
6136
  runId: board.runId,
6066
6137
  status: board.status,
6138
+ projection: explicitFull ? "full" : "compact",
6067
6139
  workerCount: board.workerCount,
6068
- ...autoCompact ? {
6069
- compact: true,
6070
- compactReason: "large-worker-board",
6071
- omittedWorkerCount: board.workers.length - outputWorkers.length,
6072
- fullCommand: `kynver worker list --run ${runId} --json --full`,
6073
- compactCommand: `kynver worker list --run ${runId} --json --compact`
6140
+ ...board.activeFilters ? { activeFilters: board.activeFilters } : {},
6141
+ ...!explicitFull ? {
6142
+ resultContract: {
6143
+ projection: "compact",
6144
+ rawWorkerPayloadsOmitted: true,
6145
+ deepDetailAvailableVia: ["--full", "worker status", "worker tail", "run status --full"]
6146
+ },
6147
+ drilldownCommands: buildWorkerListDrilldownCommands(runId)
6074
6148
  } : {},
6075
6149
  needsAttention: board.needsAttention,
6076
- ...compact && "summary" in board ? { summary: board.summary } : {},
6077
- ...compact && "controller" in board ? { controller: board.controller } : {},
6150
+ ..."summary" in board ? { summary: board.summary } : {},
6151
+ ..."controller" in board ? { controller: board.controller } : {},
6078
6152
  workers: outputWorkers
6079
6153
  },
6080
6154
  null,
@@ -6342,9 +6416,33 @@ async function publishHarnessBoardSnapshot(args, source) {
6342
6416
  authRefreshFailure: res.authRefreshFailure
6343
6417
  };
6344
6418
  }
6419
+ function buildRunStatusDrilldownCommands(runId) {
6420
+ return {
6421
+ full: `kynver run status --run ${runId} --full`,
6422
+ blocked: `kynver status --run ${runId} --blocked`,
6423
+ running: `kynver status --run ${runId} --running`,
6424
+ task: `kynver status --run ${runId} --task <task-id>`,
6425
+ worker: `kynver status --run ${runId} --worker <worker>`,
6426
+ workersFull: `kynver worker list --run ${runId} --full`,
6427
+ workerFull: `kynver worker status --run ${runId} --name <worker>`,
6428
+ workerTail: `kynver worker tail --run ${runId} --name <worker> --lines 80`,
6429
+ monitorTick: `kynver monitor status --run ${runId} --tick`
6430
+ };
6431
+ }
6345
6432
  function runStatus(args) {
6346
- const compact = args.compact === true || args.compact === "true";
6347
- const board = compact ? buildCompactRunBoard(resolveRunTargetArg(args)) : buildRunBoard(resolveRunTargetArg(args));
6433
+ const runId = resolveRunTargetArg(args);
6434
+ const explicitFull = args.full === true || args.full === "true";
6435
+ const board = applyWorkerFilters(explicitFull ? buildRunBoard(runId) : buildCompactRunBoard(runId), args);
6436
+ board.projection = explicitFull ? "full" : "compact";
6437
+ if (!explicitFull) {
6438
+ board.resultContract = {
6439
+ projection: "compact",
6440
+ rawWorkerPayloadsOmitted: true,
6441
+ omittedWorkerFields: ["finalResult", "changedFiles", "gitAncestry", "completionResponse", "stdout/stderr tails"],
6442
+ deepDetailAvailableVia: ["--full", "worker status", "worker tail", "monitor status --tick"]
6443
+ };
6444
+ board.drilldownCommands = buildRunStatusDrilldownCommands(runId);
6445
+ }
6348
6446
  console.log(JSON.stringify(board, null, 2));
6349
6447
  }
6350
6448
  function tailWorker(args) {
@@ -7439,6 +7537,344 @@ async function releaseDispatchClaimAfterSpawnFailure(input) {
7439
7537
  };
7440
7538
  }
7441
7539
 
7540
+ // src/landing/land-pr.ts
7541
+ import { spawnSync as spawnSync5 } from "node:child_process";
7542
+
7543
+ // src/landing/cli-auth.ts
7544
+ import { spawnSync as spawnSync4 } from "node:child_process";
7545
+ function ensureGitHubTokenFromCliAuth() {
7546
+ if (process.env.GITHUB_TOKEN?.trim() || process.env.GH_TOKEN?.trim()) {
7547
+ return {
7548
+ source: "env",
7549
+ configured: true,
7550
+ reason: "GitHub token already configured in environment"
7551
+ };
7552
+ }
7553
+ const result = spawnSync4("gh", ["auth", "token"], {
7554
+ encoding: "utf8",
7555
+ stdio: ["ignore", "pipe", "pipe"]
7556
+ });
7557
+ if (result.status !== 0) {
7558
+ const reason = typeof result.stderr === "string" && result.stderr.trim() ? result.stderr.trim() : "gh auth token failed; run `gh auth login` on this machine";
7559
+ return { source: "missing", configured: false, reason };
7560
+ }
7561
+ const token = typeof result.stdout === "string" ? result.stdout.trim() : "";
7562
+ if (!token) {
7563
+ return {
7564
+ source: "missing",
7565
+ configured: false,
7566
+ reason: "gh auth token returned an empty token; run `gh auth login` on this machine"
7567
+ };
7568
+ }
7569
+ process.env.GH_TOKEN = token;
7570
+ return {
7571
+ source: "gh-cli",
7572
+ configured: true,
7573
+ reason: "Using local GitHub CLI auth for daemon land_pr merge"
7574
+ };
7575
+ }
7576
+
7577
+ // src/landing/land-pr.ts
7578
+ var SUCCESSFUL_CHECK_CONCLUSIONS = /* @__PURE__ */ new Set(["SUCCESS", "SKIPPED", "NEUTRAL"]);
7579
+ var READY_MERGE_STATES = /* @__PURE__ */ new Set(["CLEAN", "HAS_HOOKS"]);
7580
+ function repoArgs(repo) {
7581
+ return repo?.trim() ? ["--repo", repo.trim()] : [];
7582
+ }
7583
+ function repoFromPrUrl(prUrl) {
7584
+ const m = /github\.com\/([^/]+)\/([^/]+)\/pull\/\d+/i.exec(prUrl.trim());
7585
+ return m ? `${m[1]}/${m[2]}` : null;
7586
+ }
7587
+ function ghJson(exec, cwd, args) {
7588
+ const res = exec.gh(cwd, args);
7589
+ if (res.status !== 0) {
7590
+ throw new Error(res.stderr || res.stdout || `gh ${args.join(" ")} failed`);
7591
+ }
7592
+ return JSON.parse(res.stdout || "{}");
7593
+ }
7594
+ function checkName(check3) {
7595
+ return typeof check3.name === "string" && check3.name || typeof check3.context === "string" && check3.context || typeof check3.workflowName === "string" && check3.workflowName || "unknown check";
7596
+ }
7597
+ function classifyChecks(statusCheckRollup) {
7598
+ const checks = Array.isArray(statusCheckRollup) ? statusCheckRollup : [];
7599
+ const pending = [];
7600
+ const failed = [];
7601
+ for (const raw of checks) {
7602
+ const check3 = raw && typeof raw === "object" ? raw : {};
7603
+ const conclusion = typeof check3.conclusion === "string" ? check3.conclusion.toUpperCase() : "";
7604
+ const status = typeof check3.status === "string" ? check3.status.toUpperCase() : "";
7605
+ const state = typeof check3.state === "string" ? check3.state.toUpperCase() : "";
7606
+ if (conclusion && SUCCESSFUL_CHECK_CONCLUSIONS.has(conclusion)) continue;
7607
+ if (conclusion) {
7608
+ failed.push(`${checkName(check3)}=${conclusion}`);
7609
+ continue;
7610
+ }
7611
+ if (state && SUCCESSFUL_CHECK_CONCLUSIONS.has(state)) continue;
7612
+ if (state && state !== "PENDING") {
7613
+ failed.push(`${checkName(check3)}=${state}`);
7614
+ continue;
7615
+ }
7616
+ if (state === "PENDING") {
7617
+ pending.push(`${checkName(check3)}=${state}`);
7618
+ continue;
7619
+ }
7620
+ if (status && status !== "COMPLETED") {
7621
+ pending.push(`${checkName(check3)}=${status}`);
7622
+ continue;
7623
+ }
7624
+ pending.push(`${checkName(check3)}=PENDING`);
7625
+ }
7626
+ return { pending, failed };
7627
+ }
7628
+ function vercelCheckSuccess(statusCheckRollup) {
7629
+ const checks = Array.isArray(statusCheckRollup) ? statusCheckRollup : [];
7630
+ return checks.some((raw) => {
7631
+ const check3 = raw && typeof raw === "object" ? raw : {};
7632
+ const name = typeof check3.name === "string" && check3.name || typeof check3.context === "string" && check3.context || "";
7633
+ const conclusion = typeof check3.conclusion === "string" ? check3.conclusion.toUpperCase() : "";
7634
+ const state = typeof check3.state === "string" ? check3.state.toUpperCase() : "";
7635
+ return /^vercel/i.test(name) && (conclusion === "SUCCESS" || state === "SUCCESS");
7636
+ });
7637
+ }
7638
+ var STRUCTURED_SECTION_RE = /^##\s*(test\s*plan|verification|test\s*evidence|verify)\b/im;
7639
+ 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;
7640
+ var DOCS_TITLE_RE = /^docs[(:]/i;
7641
+ var DOCS_BODY_RE = /docs[- ]only|documentation only|no[- ]code change|no code changes|low[- ]risk|plan tracker only|markdown only/i;
7642
+ function runtimeVerificationEvidenceSufficient(input) {
7643
+ const title = typeof input.title === "string" ? input.title : "";
7644
+ const body = typeof input.body === "string" ? input.body : "";
7645
+ const docsLowRisk = DOCS_TITLE_RE.test(title) || DOCS_BODY_RE.test(body);
7646
+ const structuredSection = STRUCTURED_SECTION_RE.test(body);
7647
+ const localVerification = LOCAL_VERIFICATION_RE.test(body);
7648
+ return docsLowRisk || structuredSection || localVerification || input.vercelCheckSuccess === true;
7649
+ }
7650
+ function assertVerificationEvidence(pr) {
7651
+ const sufficient = runtimeVerificationEvidenceSufficient({
7652
+ title: pr.title,
7653
+ body: pr.body,
7654
+ vercelCheckSuccess: vercelCheckSuccess(pr.statusCheckRollup)
7655
+ });
7656
+ if (sufficient) return;
7657
+ throw new Error(
7658
+ `PR #${pr.number} lacks landing verification evidence \u2014 add ## Test plan / ## Verification with local commands, Vercel preview, or docs/no-code rationale`
7659
+ );
7660
+ }
7661
+ function assertLandingReady(pr) {
7662
+ if (pr.state !== "OPEN") throw new Error(`PR #${pr.number} is ${pr.state}, not OPEN`);
7663
+ if (pr.isDraft) throw new Error(`PR #${pr.number} is still a draft`);
7664
+ if (!READY_MERGE_STATES.has(pr.mergeStateStatus)) {
7665
+ throw new Error(`PR #${pr.number} mergeStateStatus is ${pr.mergeStateStatus}`);
7666
+ }
7667
+ const checks = classifyChecks(pr.statusCheckRollup);
7668
+ if (checks.failed.length > 0) {
7669
+ throw new Error(`PR #${pr.number} has failing checks: ${checks.failed.join(", ")}`);
7670
+ }
7671
+ if (checks.pending.length > 0) {
7672
+ throw new Error(`PR #${pr.number} has pending checks: ${checks.pending.join(", ")}`);
7673
+ }
7674
+ assertVerificationEvidence(pr);
7675
+ }
7676
+ function landingReadinessError(pr) {
7677
+ try {
7678
+ assertLandingReady(pr);
7679
+ return null;
7680
+ } catch (err) {
7681
+ return err instanceof Error ? err.message : String(err);
7682
+ }
7683
+ }
7684
+ function deleteRemoteBranch(exec, cwd, repo, branch) {
7685
+ if (!branch?.trim()) return;
7686
+ const refPath = encodeURI(`heads/${branch}`);
7687
+ exec.gh(cwd, ["api", "-X", "DELETE", `repos/${repo}/git/refs/${refPath}`]);
7688
+ }
7689
+ function resolveRepo(exec, cwd, repo) {
7690
+ if (repo?.trim()) return repo.trim();
7691
+ const res = exec.gh(cwd, ["repo", "view", "--json", "nameWithOwner"]);
7692
+ if (res.status !== 0) throw new Error(res.stderr || "Could not resolve GitHub repo");
7693
+ const parsed = JSON.parse(res.stdout || "{}");
7694
+ if (!parsed.nameWithOwner) throw new Error("Could not resolve GitHub repo");
7695
+ return parsed.nameWithOwner;
7696
+ }
7697
+ function removeBranchWorktrees(cwd, branch) {
7698
+ if (!branch?.trim()) return;
7699
+ const list = spawnSync5("git", ["worktree", "list", "--porcelain"], {
7700
+ cwd,
7701
+ encoding: "utf8"
7702
+ });
7703
+ if (list.status !== 0) return;
7704
+ const lines = String(list.stdout || "").split(/\r?\n/);
7705
+ let currentPath = null;
7706
+ let currentBranch = null;
7707
+ for (const line of lines) {
7708
+ if (line.startsWith("worktree ")) {
7709
+ currentPath = line.slice("worktree ".length).trim();
7710
+ currentBranch = null;
7711
+ continue;
7712
+ }
7713
+ if (line.startsWith("branch ") && currentPath) {
7714
+ currentBranch = line.slice("branch ".length).trim();
7715
+ if (currentBranch.endsWith(`/${branch}`)) {
7716
+ spawnSync5("git", ["worktree", "remove", "--force", currentPath], {
7717
+ cwd,
7718
+ encoding: "utf8"
7719
+ });
7720
+ }
7721
+ }
7722
+ }
7723
+ }
7724
+ async function executeLandPrMerge(input) {
7725
+ const cwd = input.cwd ?? process.cwd();
7726
+ const exec = input.exec ?? defaultPrHandoffExec;
7727
+ const prTarget = input.prUrl.trim();
7728
+ if (!prTarget) throw new Error("prUrl is required");
7729
+ const auth = ensureGitHubTokenFromCliAuth();
7730
+ if (!auth.configured) {
7731
+ return {
7732
+ prUrl: prTarget,
7733
+ outcome: "blocked",
7734
+ reason: auth.reason
7735
+ };
7736
+ }
7737
+ const before = ghJson(exec, cwd, [
7738
+ "pr",
7739
+ "view",
7740
+ prTarget,
7741
+ ...repoArgs(input.repo),
7742
+ "--json",
7743
+ [
7744
+ "number",
7745
+ "url",
7746
+ "title",
7747
+ "body",
7748
+ "state",
7749
+ "isDraft",
7750
+ "mergeStateStatus",
7751
+ "statusCheckRollup",
7752
+ "headRefName",
7753
+ "mergedAt",
7754
+ "mergeCommit"
7755
+ ].join(",")
7756
+ ]);
7757
+ if (before.state === "MERGED" || before.mergedAt) {
7758
+ return {
7759
+ prUrl: before.url || prTarget,
7760
+ outcome: "skipped",
7761
+ mergeCommit: before.mergeCommit?.oid ?? null,
7762
+ reason: `PR #${before.number} is already merged \u2014 land_pr no-op (redelivery)`
7763
+ };
7764
+ }
7765
+ const notReadyReason = landingReadinessError(before);
7766
+ if (notReadyReason) {
7767
+ if (input.skipNotReady) {
7768
+ return {
7769
+ prUrl: before.url || prTarget,
7770
+ outcome: "skipped",
7771
+ reason: notReadyReason
7772
+ };
7773
+ }
7774
+ return {
7775
+ prUrl: before.url || prTarget,
7776
+ outcome: "blocked",
7777
+ reason: notReadyReason
7778
+ };
7779
+ }
7780
+ if (input.dryRun) {
7781
+ return {
7782
+ prUrl: before.url || prTarget,
7783
+ outcome: "skipped",
7784
+ reason: "dry-run: PR is ready to merge"
7785
+ };
7786
+ }
7787
+ const target = String(before.number);
7788
+ const mergeRes = exec.gh(cwd, [
7789
+ "pr",
7790
+ "merge",
7791
+ target,
7792
+ ...repoArgs(input.repo),
7793
+ "--squash"
7794
+ ]);
7795
+ if (mergeRes.status !== 0) {
7796
+ return {
7797
+ prUrl: before.url || prTarget,
7798
+ outcome: "blocked",
7799
+ reason: mergeRes.stderr || mergeRes.stdout || "gh pr merge failed"
7800
+ };
7801
+ }
7802
+ const after = ghJson(exec, cwd, [
7803
+ "pr",
7804
+ "view",
7805
+ target,
7806
+ ...repoArgs(input.repo),
7807
+ "--json",
7808
+ "number,url,mergedAt,mergeCommit,state"
7809
+ ]);
7810
+ if (after.state !== "MERGED" && !after.mergedAt) {
7811
+ return {
7812
+ prUrl: after.url || before.url || prTarget,
7813
+ outcome: "blocked",
7814
+ reason: `PR #${after.number} did not verify as merged after gh pr merge`
7815
+ };
7816
+ }
7817
+ const repo = resolveRepo(exec, cwd, input.repo);
7818
+ deleteRemoteBranch(exec, cwd, repo, before.headRefName);
7819
+ removeBranchWorktrees(cwd, before.headRefName);
7820
+ const mergeCommit = after.mergeCommit?.oid ?? null;
7821
+ return {
7822
+ prUrl: after.url || before.url || prTarget,
7823
+ outcome: "merged",
7824
+ mergeCommit,
7825
+ reason: `Daemon land_pr merged PR #${after.number}`
7826
+ };
7827
+ }
7828
+
7829
+ // src/landing/land-pr-completion-post.ts
7830
+ async function postLandPrHarnessCompletion(input) {
7831
+ const secret = await resolveCallbackSecretWithMint(input.secret, input.agentOsId, {
7832
+ baseUrl: input.baseUrl
7833
+ });
7834
+ const taskId = String(input.task.id);
7835
+ const url = `${input.baseUrl}/api/agent-os/by-id/${encodeURIComponent(input.agentOsId)}/harness/completion`;
7836
+ const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
7837
+ const body = {
7838
+ source: "kynver-harness",
7839
+ agentOsId: input.agentOsId,
7840
+ runId: input.runId,
7841
+ workerName: `land-pr-${taskId}`,
7842
+ taskId,
7843
+ leaseToken: input.task.leaseToken ?? null,
7844
+ startedAt: finishedAt,
7845
+ finishedAt,
7846
+ status: {
7847
+ finalResult: input.report,
7848
+ prUrl: input.report.prUrl,
7849
+ summary: input.report.reason,
7850
+ leaseToken: input.task.leaseToken ?? null
7851
+ },
7852
+ workerInjection: null
7853
+ };
7854
+ const result = await postJsonWithCredentialRefresh(url, secret, body, {
7855
+ agentOsId: input.agentOsId,
7856
+ baseUrl: input.baseUrl
7857
+ });
7858
+ return { ok: result.ok, status: result.status };
7859
+ }
7860
+
7861
+ // src/verify-live/verify-live-prompt.ts
7862
+ function formatVerifyLiveCompletionContract() {
7863
+ return [
7864
+ "## verify_live completion contract",
7865
+ "Finish with a JSON finalResult object:",
7866
+ "{",
7867
+ ' "prUrl": "<landed pr>",',
7868
+ ' "outcome": "passed" | "failed" | "skipped" | "no_mcp_surface",',
7869
+ ' "reason": "<human summary>",',
7870
+ ' "mergeCommit": "<sha optional>",',
7871
+ ' "mcpCalls": [{ "tool": "<name>", "ok": true, "summary": "..." }]',
7872
+ "}",
7873
+ "",
7874
+ "Drive the feature MCP tools against live production. Do not mark passed without exercising the MCP surface."
7875
+ ].join("\n");
7876
+ }
7877
+
7442
7878
  // src/dispatch.ts
7443
7879
  var DEFAULT_DISPATCH_LEASE_MS = 60 * 60 * 1e3;
7444
7880
  function readAdmissionExhaustion(result) {
@@ -7496,6 +7932,9 @@ function buildDispatchTaskText(task, agentOsId) {
7496
7932
  `Board linkage: agentOsId=${agentOsId}, taskId=${task.id}, attempt=${task.attempt}, executor=${task.executor}${task.executorRef ? `, executorRef=${task.executorRef}` : ""}.`,
7497
7933
  "This worker was dispatched from the AgentOS board. The harness reports your completion back to the board when you finish."
7498
7934
  ];
7935
+ if (task.executor === "verify_live") {
7936
+ lines.push("", formatVerifyLiveCompletionContract());
7937
+ }
7499
7938
  const outboxRef = extractPlanOutboxFromTask(task);
7500
7939
  if (outboxRef?.outboxId) {
7501
7940
  const item = loadOutboxById(outboxRef.outboxId);
@@ -7529,6 +7968,12 @@ function requestedTargetTaskIds(args) {
7529
7968
  async function dispatchRun(args) {
7530
7969
  const pipeline = args.pipeline === true || args.pipeline === "true";
7531
7970
  try {
7971
+ let isLandPrDecision2 = function(decision) {
7972
+ if (decision.landPrDispatch === true) return true;
7973
+ const task = decision.task;
7974
+ return task?.executor === "land_pr";
7975
+ };
7976
+ var isLandPrDecision = isLandPrDecision2;
7532
7977
  const run = loadRun(String(required(String(args.run || ""), "--run")));
7533
7978
  const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
7534
7979
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
@@ -7663,6 +8108,66 @@ async function dispatchRun(args) {
7663
8108
  });
7664
8109
  return false;
7665
8110
  }
8111
+ async function runLandPrClaimed(decision) {
8112
+ const task = decision.task;
8113
+ const taskId = String(task.id);
8114
+ const prUrl = task.prUrl ? String(task.prUrl) : "";
8115
+ if (!prUrl) {
8116
+ return abortClaimedSpawn(task, "land_pr task missing prUrl");
8117
+ }
8118
+ try {
8119
+ const report = await executeLandPrMerge({
8120
+ prUrl,
8121
+ repo: repoFromPrUrl(prUrl),
8122
+ cwd: run.repo
8123
+ });
8124
+ const post = await postLandPrHarnessCompletion({
8125
+ baseUrl: base,
8126
+ secret,
8127
+ agentOsId,
8128
+ runId: run.id,
8129
+ task,
8130
+ report
8131
+ });
8132
+ outcomes.push({
8133
+ taskId,
8134
+ started: true,
8135
+ landPr: true,
8136
+ outcome: report.outcome,
8137
+ completionStatus: post.status
8138
+ });
8139
+ if (!post.ok) {
8140
+ return abortClaimedSpawn(
8141
+ task,
8142
+ `land_pr completion POST failed (HTTP ${post.status})`
8143
+ );
8144
+ }
8145
+ return true;
8146
+ } catch (error) {
8147
+ return abortClaimedSpawn(task, error.message);
8148
+ }
8149
+ }
8150
+ async function releaseDuplicateLocalClaim(task) {
8151
+ const error = "duplicate_dispatch_prevented: live local worker already owns this task";
8152
+ const release = await releaseDispatchClaimAfterSpawnFailure({
8153
+ baseUrl: base,
8154
+ secret,
8155
+ agentOsId,
8156
+ taskId: String(task.id),
8157
+ leaseOwner,
8158
+ failureDetail: error
8159
+ });
8160
+ outcomes.push({
8161
+ taskId: task.id,
8162
+ started: false,
8163
+ error,
8164
+ alreadyRunning: true,
8165
+ nonFatal: true,
8166
+ released: release.released,
8167
+ releaseResponse: release.releaseResponse
8168
+ });
8169
+ return false;
8170
+ }
7666
8171
  async function spawnClaimed(decision) {
7667
8172
  const task = decision.task;
7668
8173
  const harnessContext = readHarnessWorkerContext(decision);
@@ -7684,10 +8189,7 @@ async function dispatchRun(args) {
7684
8189
  );
7685
8190
  }
7686
8191
  if (hasLiveWorkerForTask(run.id, taskId)) {
7687
- return abortClaimedSpawn(
7688
- task,
7689
- "duplicate_dispatch_prevented: live local worker already owns this task"
7690
- );
8192
+ return releaseDuplicateLocalClaim(task);
7691
8193
  }
7692
8194
  const attempt = Number(task.attempt) || 1;
7693
8195
  if (attempt > retryLimits.maxTaskAttempts) {
@@ -7780,7 +8282,8 @@ async function dispatchRun(args) {
7780
8282
  }
7781
8283
  let shouldContinueDispatch = true;
7782
8284
  for (const decision of result.started) {
7783
- shouldContinueDispatch = await spawnClaimed(decision) && shouldContinueDispatch;
8285
+ const admitted = isLandPrDecision2(decision) ? await runLandPrClaimed(decision) : await spawnClaimed(decision);
8286
+ shouldContinueDispatch = admitted && shouldContinueDispatch;
7784
8287
  }
7785
8288
  skipped.push(
7786
8289
  ...result.skipped ?? []
@@ -7818,7 +8321,8 @@ async function dispatchRun(args) {
7818
8321
  if (started.length === 0) break;
7819
8322
  for (const decision of started) {
7820
8323
  if (outcomes.length >= cappedStarts) break;
7821
- shouldContinueDispatch = await spawnClaimed(decision) && shouldContinueDispatch;
8324
+ const admitted = isLandPrDecision2(decision) ? await runLandPrClaimed(decision) : await spawnClaimed(decision);
8325
+ shouldContinueDispatch = admitted && shouldContinueDispatch;
7822
8326
  if (!shouldContinueDispatch) break;
7823
8327
  }
7824
8328
  }
@@ -7855,11 +8359,12 @@ async function dispatchRun(args) {
7855
8359
  diskGate: result.diskGate,
7856
8360
  resourceGate: result.resourceGate
7857
8361
  };
8362
+ const fatalOutcome = (outcome) => !outcome.started && outcome.nonFatal !== true;
7858
8363
  if (pipeline) {
7859
- return { ok: !outcomes.some((o) => !o.started), ...summary };
8364
+ return { ok: !outcomes.some(fatalOutcome), ...summary };
7860
8365
  }
7861
8366
  console.log(JSON.stringify(summary, null, 2));
7862
- if (outcomes.some((o) => !o.started)) process.exit(1);
8367
+ if (outcomes.some(fatalOutcome)) process.exit(1);
7863
8368
  } catch (error) {
7864
8369
  if (pipeline) return { ok: false, error: error.message };
7865
8370
  console.error(`run dispatch failed: ${error.message}`);
@@ -8248,7 +8753,7 @@ function discoverProductionDbEnvFilePaths(options = {}) {
8248
8753
  }
8249
8754
 
8250
8755
  // src/db-credential-resolver.ts
8251
- import { execFileSync, spawnSync as spawnSync4 } from "node:child_process";
8756
+ import { execFileSync, spawnSync as spawnSync6 } from "node:child_process";
8252
8757
  import * as fs2 from "node:fs";
8253
8758
  import * as path29 from "node:path";
8254
8759
 
@@ -8334,7 +8839,7 @@ function defaultNeonConnectionString(args) {
8334
8839
  if (args.pooled) argv.push("--pooled");
8335
8840
  let stdout;
8336
8841
  try {
8337
- const result = spawnSync4(neonBin, argv, {
8842
+ const result = spawnSync6(neonBin, argv, {
8338
8843
  encoding: "utf8",
8339
8844
  env: process.env,
8340
8845
  timeout: 3e4
@@ -9786,23 +10291,6 @@ function materialWorktreeChanges2(changedFiles) {
9786
10291
  });
9787
10292
  }
9788
10293
 
9789
- // src/cleanup-index-status.ts
9790
- function indexedWorktreeStatus(entry) {
9791
- if (!entry.status) {
9792
- entry.status = computeWorkerStatus(entry.worker, {
9793
- base: entry.run.base,
9794
- baseCommit: entry.run.baseCommit
9795
- });
9796
- }
9797
- return entry.status;
9798
- }
9799
- function indexedWorktreeHasMaterialChanges(entry) {
9800
- if (entry.status) {
9801
- return materialWorktreeChanges2(entry.status.changedFiles).length > 0;
9802
- }
9803
- return materialWorktreeChanges2(gitStatusShort(entry.worktreePath)).length > 0;
9804
- }
9805
-
9806
10294
  // src/cleanup-worktree-salvage.ts
9807
10295
  function prUrlFromFinalResult(finalResult) {
9808
10296
  if (typeof finalResult === "string") {
@@ -9829,6 +10317,83 @@ function isPrOrUnmergedWork(status) {
9829
10317
  return false;
9830
10318
  }
9831
10319
 
10320
+ // src/cleanup-index-status.ts
10321
+ function indexedWorktreeStatus(entry) {
10322
+ if (!entry.status) {
10323
+ entry.status = computeWorkerStatus(entry.worker, {
10324
+ base: entry.run.base,
10325
+ baseCommit: entry.run.baseCommit
10326
+ });
10327
+ }
10328
+ return entry.status;
10329
+ }
10330
+ function indexedWorktreeHasMaterialChanges(entry, gitStatusCache) {
10331
+ if (entry.status) {
10332
+ return materialWorktreeChanges2(entry.status.changedFiles).length > 0;
10333
+ }
10334
+ const porcelain = gitStatusCache ? gitStatusCache.porcelain(entry.worktreePath) : gitStatusShort(entry.worktreePath);
10335
+ return materialWorktreeChanges2(porcelain).length > 0;
10336
+ }
10337
+ function finalResultFromWorkerJson(entry) {
10338
+ const snapshot = entry.worker.completionSnapshot?.finalResult;
10339
+ if (snapshot !== void 0 && snapshot !== null) return snapshot;
10340
+ if (entry.worker.taskPrUrl) {
10341
+ return { prUrl: entry.worker.taskPrUrl };
10342
+ }
10343
+ return null;
10344
+ }
10345
+ function resolveWorktreeGuardStatus(entry, ctx) {
10346
+ if (entry.status) return entry.status;
10347
+ const worker = entry.worker;
10348
+ const completionAcknowledged = typeof worker.completionReportedAt === "string" && worker.completionReportedAt.trim().length > 0;
10349
+ const workerJsonTerminal = Boolean(worker.status && ["done", "exited", "blocked", "failed", "abandoned"].includes(worker.status)) || completionAcknowledged;
10350
+ const finalResult = finalResultFromWorkerJson(entry);
10351
+ if (workerJsonTerminal && !isPidAlive(worker.pid)) {
10352
+ const changedFiles = ctx?.gitStatusCache ? ctx.gitStatusCache.porcelain(entry.worktreePath) : gitStatusShort(entry.worktreePath);
10353
+ const baseLabel = entry.run.baseCommit?.trim() || entry.run.base?.trim() || "origin/main";
10354
+ const ahead = ctx?.gitRevCache?.countAheadOfMain(entry.worktreePath, baseLabel);
10355
+ const gitAncestry = ahead === 0 ? {
10356
+ checked: true,
10357
+ base: baseLabel,
10358
+ relation: "synced"
10359
+ } : computeGitAncestry(entry.worktreePath, {
10360
+ base: entry.run.base,
10361
+ baseCommit: entry.run.baseCommit
10362
+ });
10363
+ const status = {
10364
+ runId: entry.runId,
10365
+ worker: entry.workerName,
10366
+ pid: worker.pid,
10367
+ alive: false,
10368
+ status: worker.status ?? (completionAcknowledged ? "done" : "exited"),
10369
+ attention: { state: completionAcknowledged ? "done" : "stale" },
10370
+ branch: worker.branch,
10371
+ worktreePath: entry.worktreePath,
10372
+ ownedPaths: worker.ownedPaths,
10373
+ stdoutBytes: 0,
10374
+ stderrBytes: 0,
10375
+ heartbeatBytes: 0,
10376
+ firstEventAt: null,
10377
+ lastEventAt: null,
10378
+ lastActivityAt: worker.completionReportedAt ?? null,
10379
+ currentTool: null,
10380
+ heartbeatCount: 0,
10381
+ lastHeartbeatAt: null,
10382
+ lastHeartbeatPhase: null,
10383
+ lastHeartbeatSummary: null,
10384
+ heartbeatBlocker: null,
10385
+ changedFiles,
10386
+ gitAncestry,
10387
+ finalResult,
10388
+ completionBlocker: typeof worker.completionBlocker === "string" ? worker.completionBlocker.trim() || null : null,
10389
+ prUrl: worker.repairTargetPrUrl ?? worker.taskPrUrl ?? prUrlFromFinalResult(finalResult)
10390
+ };
10391
+ entry.status = status;
10392
+ return status;
10393
+ }
10394
+ return indexedWorktreeStatus(entry);
10395
+ }
10396
+
9832
10397
  // src/cleanup-completion-blocker.ts
9833
10398
  function completionBlockerBlocksWorktreeRemoval(indexed, status) {
9834
10399
  const blocker = typeof indexed.worker.completionBlocker === "string" ? indexed.worker.completionBlocker.trim() : "";
@@ -9849,13 +10414,28 @@ function completionBlockerBlocksWorktreeRemoval(indexed, status) {
9849
10414
  }
9850
10415
 
9851
10416
  // src/cleanup-run-liveness.ts
10417
+ var TERMINAL_WORKER_JSON_STATUSES = /* @__PURE__ */ new Set([
10418
+ "done",
10419
+ "exited",
10420
+ "blocked",
10421
+ "failed",
10422
+ "abandoned"
10423
+ ]);
9852
10424
  function deriveRunTerminal(indexed, ctx) {
9853
10425
  if (ctx) return ctx.runTerminalCache.derive(indexed.run);
9854
10426
  return deriveTerminalRunStatus(indexed.run);
9855
10427
  }
9856
10428
  function isWorkerProcessLive(indexed) {
9857
10429
  if (isPidAlive(indexed.worker.pid)) return true;
9858
- if (!indexed.worker.pid) return indexedWorktreeStatus(indexed).alive;
10430
+ if (typeof indexed.worker.completionReportedAt === "string" && indexed.worker.completionReportedAt.trim().length > 0) {
10431
+ return false;
10432
+ }
10433
+ const workerStatus2 = indexed.worker.status;
10434
+ if (workerStatus2 && TERMINAL_WORKER_JSON_STATUSES.has(workerStatus2)) return false;
10435
+ if (!indexed.worker.pid) {
10436
+ if (workerStatus2 !== "running") return false;
10437
+ return indexedWorktreeStatus(indexed).alive;
10438
+ }
9859
10439
  return false;
9860
10440
  }
9861
10441
  function isRunStaleActive(indexed, ctx) {
@@ -9864,6 +10444,10 @@ function isRunStaleActive(indexed, ctx) {
9864
10444
  }
9865
10445
  function runBlocksWorktreeRemoval(indexed, ctx) {
9866
10446
  if (isWorkerProcessLive(indexed)) return true;
10447
+ const workerStatus2 = indexed.worker.status;
10448
+ if (workerStatus2 && TERMINAL_WORKER_JSON_STATUSES.has(workerStatus2) && !indexed.worker.completionBlocker) {
10449
+ return false;
10450
+ }
9867
10451
  const status = indexedWorktreeStatus(indexed);
9868
10452
  if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return true;
9869
10453
  if (isFinishedWorkerStatus(status)) return false;
@@ -9882,7 +10466,7 @@ function effectiveWorktreeAgeMs(input) {
9882
10466
  if (input.liveness && isRunStaleActive(indexed, input.liveness)) {
9883
10467
  return terminalWorktreesAgeMs;
9884
10468
  }
9885
- if (input.liveness && isFinishedWorkerStatus(indexedWorktreeStatus(indexed)) && !isWorkerProcessLive(indexed)) {
10469
+ if (input.liveness && isFinishedWorkerStatus(resolveWorktreeGuardStatus(indexed, input.liveness)) && !isWorkerProcessLive(indexed)) {
9886
10470
  return terminalWorktreesAgeMs;
9887
10471
  }
9888
10472
  return worktreesAgeMs;
@@ -9896,8 +10480,13 @@ function skipWorktreeRemoval(input) {
9896
10480
  const ageThresholdMs = effectiveWorktreeAgeMs(input);
9897
10481
  if (worktreesAgeMs <= 0 && !includeOrphans && ageThresholdMs <= 0) return "worktrees_disabled";
9898
10482
  if (ageThresholdMs > 0 && ageMs < ageThresholdMs) return "below_age_threshold";
9899
- const status = indexedWorktreeStatus(indexed);
9900
10483
  if (isWorkerProcessLive(indexed)) return "active_worker";
10484
+ if (indexedWorktreeHasMaterialChanges(indexed, input.liveness?.gitStatusCache)) {
10485
+ return "dirty_worktree";
10486
+ }
10487
+ const ahead = input.liveness?.gitRevCache?.countAheadOfMain(input.worktreePath);
10488
+ if (ahead !== null && ahead !== void 0 && ahead > 0) return "pr_or_unmerged_commits";
10489
+ const status = resolveWorktreeGuardStatus(indexed, input.liveness);
9901
10490
  if (completionBlockerBlocksWorktreeRemoval(indexed, status)) return "completion_blocked";
9902
10491
  if (runBlocksWorktreeRemoval(indexed, input.liveness)) return "run_still_active";
9903
10492
  if (!isFinishedWorkerStatus(status)) return "run_still_active";
@@ -9928,7 +10517,9 @@ function skipDependencyCacheRemoval(input) {
9928
10517
  if (!diskPressure && ageMs < nodeModulesAgeMs) return "below_age_threshold";
9929
10518
  if (activeWorktreePaths.has(path42.resolve(worktreePath))) return "active_worker";
9930
10519
  if (indexed && isWorkerProcessLive(indexed)) return "active_worker";
9931
- if (indexed && indexedWorktreeHasMaterialChanges(indexed)) return "dirty_worktree";
10520
+ if (indexed && indexedWorktreeHasMaterialChanges(indexed, input.gitStatusCache)) {
10521
+ return "dirty_worktree";
10522
+ }
9932
10523
  return null;
9933
10524
  }
9934
10525
  function skipBuildCacheRemoval(input) {
@@ -10126,7 +10717,7 @@ function pathHasForeignOwnedEntry(targetPath, maxEntries = 32) {
10126
10717
  }
10127
10718
 
10128
10719
  // src/cleanup-privileged-remove.ts
10129
- import { spawnSync as spawnSync5 } from "node:child_process";
10720
+ import { spawnSync as spawnSync7 } from "node:child_process";
10130
10721
  import path46 from "node:path";
10131
10722
 
10132
10723
  // src/cleanup-harness-path-validate.ts
@@ -10171,7 +10762,7 @@ function resolvePrivilegedCleanupMode() {
10171
10762
  return "auto";
10172
10763
  }
10173
10764
  function runSudoNonInteractive(argv) {
10174
- const res = spawnSync5("sudo", ["-n", ...argv], {
10765
+ const res = spawnSync7("sudo", ["-n", ...argv], {
10175
10766
  encoding: "utf8",
10176
10767
  stdio: ["ignore", "pipe", "pipe"]
10177
10768
  });
@@ -10911,6 +11502,37 @@ function emitCleanupProgress(phase, detail) {
10911
11502
  console.error(`[kynver cleanup] ${phase}${suffix}`);
10912
11503
  }
10913
11504
 
11505
+ // src/cleanup-git-rev-cache.ts
11506
+ var CleanupGitRevCache = class {
11507
+ aheadOfMain = /* @__PURE__ */ new Map();
11508
+ countAheadOfMain(worktreePath, base = "origin/main") {
11509
+ const key = `${worktreePath}\0${base}`;
11510
+ if (this.aheadOfMain.has(key)) return this.aheadOfMain.get(key) ?? null;
11511
+ const result = gitCapture(worktreePath, ["rev-list", "--count", `${base}..HEAD`]);
11512
+ if (result.status !== 0) {
11513
+ this.aheadOfMain.set(key, null);
11514
+ return null;
11515
+ }
11516
+ const count = Number(result.stdout.trim());
11517
+ const parsed = Number.isFinite(count) ? count : null;
11518
+ this.aheadOfMain.set(key, parsed);
11519
+ return parsed;
11520
+ }
11521
+ };
11522
+
11523
+ // src/cleanup-git-status-cache.ts
11524
+ var CleanupGitStatusCache = class {
11525
+ cache = /* @__PURE__ */ new Map();
11526
+ porcelain(worktreePath) {
11527
+ const resolved = worktreePath;
11528
+ const cached = this.cache.get(resolved);
11529
+ if (cached !== void 0) return cached;
11530
+ const lines = gitStatusShort(resolved);
11531
+ this.cache.set(resolved, lines);
11532
+ return lines;
11533
+ }
11534
+ };
11535
+
10914
11536
  // src/cleanup-run-terminal-cache.ts
10915
11537
  var CleanupRunTerminalCache = class {
10916
11538
  cache = /* @__PURE__ */ new Map();
@@ -11028,7 +11650,11 @@ function runHarnessCleanup(options = {}) {
11028
11650
  emitCleanupProgress("index", "building worktree index");
11029
11651
  const index = mergeWorktreeIndexes(paths.scanRoots);
11030
11652
  emitCleanupProgress("index", `${index.size} indexed worktree(s)`);
11031
- const liveness = { runTerminalCache: new CleanupRunTerminalCache() };
11653
+ const liveness = {
11654
+ runTerminalCache: new CleanupRunTerminalCache(),
11655
+ gitStatusCache: new CleanupGitStatusCache(),
11656
+ gitRevCache: new CleanupGitRevCache()
11657
+ };
11032
11658
  const skips = [];
11033
11659
  const actions = [];
11034
11660
  const processedPaths = /* @__PURE__ */ new Set();
@@ -11048,8 +11674,15 @@ function runHarnessCleanup(options = {}) {
11048
11674
  index,
11049
11675
  now: paths.now
11050
11676
  };
11051
- for (const raw of scanDependencyCacheCandidates(scanOpts)) {
11677
+ const dependencyCandidates = scanDependencyCacheCandidates(scanOpts);
11678
+ emitCleanupProgress("dependency", `${dependencyCandidates.length} cache candidate(s) at ${harnessRoot}`);
11679
+ let dependencyProcessed = 0;
11680
+ for (const raw of dependencyCandidates) {
11052
11681
  if (atSweepCap()) break;
11682
+ dependencyProcessed += 1;
11683
+ if (dependencyProcessed % 50 === 0) {
11684
+ emitCleanupProgress("dependency", `${dependencyProcessed}/${dependencyCandidates.length} evaluated`);
11685
+ }
11053
11686
  const resolved = path53.resolve(raw.path);
11054
11687
  if (processedPaths.has(resolved)) continue;
11055
11688
  processedPaths.add(resolved);
@@ -11069,7 +11702,8 @@ function runHarnessCleanup(options = {}) {
11069
11702
  ageMs: candidate.ageMs,
11070
11703
  worktreePath,
11071
11704
  activeWorktreePaths: activeGuards.activeWorktreePaths,
11072
- diskPressure: retention.diskPressure
11705
+ diskPressure: retention.diskPressure,
11706
+ gitStatusCache: liveness.gitStatusCache
11073
11707
  });
11074
11708
  if (guardReason) {
11075
11709
  recordSkip(skips, candidate.path, guardReason);
@@ -11104,7 +11738,8 @@ function runHarnessCleanup(options = {}) {
11104
11738
  ageMs: candidate.ageMs,
11105
11739
  worktreePath,
11106
11740
  activeWorktreePaths: activeGuards.activeWorktreePaths,
11107
- diskPressure: retention.diskPressure
11741
+ diskPressure: retention.diskPressure,
11742
+ gitStatusCache: liveness.gitStatusCache
11108
11743
  });
11109
11744
  if (guardReason) {
11110
11745
  recordSkip(skips, candidate.path, guardReason);
@@ -11124,8 +11759,13 @@ function runHarnessCleanup(options = {}) {
11124
11759
  ];
11125
11760
  emitCleanupProgress("worktrees", `${worktreeCandidates.length} candidate(s) at ${harnessRoot}`);
11126
11761
  const worktreeSeen = /* @__PURE__ */ new Set();
11762
+ let worktreeProcessed = 0;
11127
11763
  for (const raw of worktreeCandidates) {
11128
11764
  if (atSweepCap()) break;
11765
+ worktreeProcessed += 1;
11766
+ if (worktreeProcessed % 50 === 0) {
11767
+ emitCleanupProgress("worktrees", `${worktreeProcessed}/${worktreeCandidates.length} evaluated`);
11768
+ }
11129
11769
  const resolved = path53.resolve(raw.path);
11130
11770
  if (worktreeSeen.has(resolved)) continue;
11131
11771
  worktreeSeen.add(resolved);
@@ -11406,11 +12046,29 @@ function validateDaemonInstallIdentity(config = loadUserConfig(), env = process.
11406
12046
  };
11407
12047
  }
11408
12048
 
12049
+ // src/daemon-platform-guard.ts
12050
+ function envFlag3(name) {
12051
+ const raw = process.env[name]?.trim().toLowerCase();
12052
+ return raw === "1" || raw === "true" || raw === "yes" || raw === "on";
12053
+ }
12054
+ function assertNativeDaemonAllowed() {
12055
+ if (process.platform !== "win32") return;
12056
+ if (envFlag3("KYNVER_DAEMON_ALLOW_NATIVE_WINDOWS")) return;
12057
+ console.error(
12058
+ JSON.stringify({
12059
+ event: "daemon_start_blocked",
12060
+ reason: "native_windows_console_flash",
12061
+ 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)."
12062
+ })
12063
+ );
12064
+ process.exit(1);
12065
+ }
12066
+
11409
12067
  // src/cron/cron-env.ts
11410
12068
  import { existsSync as existsSync42 } from "node:fs";
11411
12069
  import { homedir as homedir14 } from "node:os";
11412
12070
  import path55 from "node:path";
11413
- function envFlag3(name, defaultValue) {
12071
+ function envFlag4(name, defaultValue) {
11414
12072
  const raw = process.env[name]?.trim().toLowerCase();
11415
12073
  if (!raw) return defaultValue;
11416
12074
  if (raw === "0" || raw === "false" || raw === "no" || raw === "off") return false;
@@ -11446,14 +12104,14 @@ function resolveKynverCronEnv() {
11446
12104
  const secret = resolveKynverCronSecret();
11447
12105
  const credsReady = Boolean(fireBaseUrl && secret);
11448
12106
  const storeExists = existsSync42(storePath);
11449
- const defaultEnabled = credsReady && (storeExists || envFlag3("KYNVER_CRON_TICK_FORCE", false));
12107
+ const defaultEnabled = credsReady && (storeExists || envFlag4("KYNVER_CRON_TICK_FORCE", false));
11450
12108
  return {
11451
12109
  storePath,
11452
12110
  statePath,
11453
12111
  lockPath: `${statePath}.lock`,
11454
12112
  fireBaseUrl,
11455
12113
  secret,
11456
- tickEnabled: envFlag3("KYNVER_CRON_TICK_ENABLED", defaultEnabled),
12114
+ tickEnabled: envFlag4("KYNVER_CRON_TICK_ENABLED", defaultEnabled),
11457
12115
  tickIntervalMs: envInt("KYNVER_CRON_TICK_INTERVAL_MS", 6e4, 5e3),
11458
12116
  missedRunPolicy: process.env.KYNVER_CRON_MISSED_RUN_POLICY?.trim().toLowerCase() === "skip" ? "skip" : "catch_up",
11459
12117
  maxCatchUpPerTick: envInt("KYNVER_CRON_MAX_CATCH_UP_PER_TICK", 3, 0),
@@ -12245,6 +12903,7 @@ async function awaitDaemonBackoff(ms, isStopping) {
12245
12903
  }
12246
12904
  }
12247
12905
  async function runDaemon(args) {
12906
+ assertNativeDaemonAllowed();
12248
12907
  const runId = String(required(String(args.run || ""), "--run"));
12249
12908
  const agentOsId = String(required(String(args.agentOsId || loadUserConfig().agentOsId || ""), "--agent-os-id"));
12250
12909
  const execute = args.execute !== false && args.execute !== "false";
@@ -12349,7 +13008,7 @@ function formatNodeOptionsFlag(mb = resolveNodeOldSpaceSizeMb()) {
12349
13008
  }
12350
13009
 
12351
13010
  // src/bounded-build/systemd-wrap.ts
12352
- import { spawnSync as spawnSync6 } from "node:child_process";
13011
+ import { spawnSync as spawnSync8 } from "node:child_process";
12353
13012
  var systemdAvailableCache;
12354
13013
  function isSystemdRunAvailable() {
12355
13014
  if (process.env.KYNVER_BUILD_SKIP_SYSTEMD === "1" || process.env.KYNVER_BUILD_SKIP_SYSTEMD === "true") {
@@ -12360,7 +13019,7 @@ function isSystemdRunAvailable() {
12360
13019
  systemdAvailableCache = false;
12361
13020
  return false;
12362
13021
  }
12363
- const res = spawnSync6("systemd-run", ["--version"], { encoding: "utf8", stdio: ["ignore", "ignore", "pipe"] });
13022
+ const res = spawnSync8("systemd-run", ["--version"], { encoding: "utf8", stdio: ["ignore", "ignore", "pipe"] });
12364
13023
  systemdAvailableCache = res.status === 0;
12365
13024
  return systemdAvailableCache;
12366
13025
  }
@@ -12384,7 +13043,7 @@ function buildSystemdRunArgv(opts) {
12384
13043
  }
12385
13044
 
12386
13045
  // src/bounded-build/admission.ts
12387
- import { spawnSync as spawnSync7 } from "node:child_process";
13046
+ import { spawnSync as spawnSync9 } from "node:child_process";
12388
13047
  function positiveInt4(value, fallback) {
12389
13048
  const n = Number(value);
12390
13049
  if (!Number.isFinite(n) || n <= 0) return fallback;
@@ -12420,7 +13079,7 @@ function assessBuildAdmission(opts = {}) {
12420
13079
  }
12421
13080
  function sleepMs2(ms) {
12422
13081
  if (ms <= 0) return;
12423
- spawnSync7(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
13082
+ spawnSync9(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
12424
13083
  stdio: "ignore"
12425
13084
  });
12426
13085
  }
@@ -12441,7 +13100,7 @@ function waitForBuildAdmission(timeoutMs, pollMs = 2e3, opts = {}) {
12441
13100
  }
12442
13101
 
12443
13102
  // src/bounded-build/exec.ts
12444
- import { spawnSync as spawnSync9 } from "node:child_process";
13103
+ import { spawnSync as spawnSync11 } from "node:child_process";
12445
13104
 
12446
13105
  // src/heavy-verification/slot.ts
12447
13106
  import {
@@ -12647,10 +13306,10 @@ function assessHeavyVerificationGate(command, opts = {}) {
12647
13306
  }
12648
13307
 
12649
13308
  // src/heavy-verification/gate.ts
12650
- import { spawnSync as spawnSync8 } from "node:child_process";
13309
+ import { spawnSync as spawnSync10 } from "node:child_process";
12651
13310
  function sleepMs3(ms) {
12652
13311
  if (ms <= 0) return;
12653
- spawnSync8(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
13312
+ spawnSync10(process.execPath, ["-e", `const d=Date.now()+${Math.floor(ms)};while(Date.now()<d);`], {
12654
13313
  stdio: "ignore"
12655
13314
  });
12656
13315
  }
@@ -12693,7 +13352,7 @@ function envArgv(env) {
12693
13352
  return out;
12694
13353
  }
12695
13354
  function runSpawn(argv, opts) {
12696
- const res = spawnSync9(argv[0], argv.slice(1), {
13355
+ const res = spawnSync11(argv[0], argv.slice(1), {
12697
13356
  cwd: opts.cwd,
12698
13357
  env: opts.env,
12699
13358
  encoding: "utf8",
@@ -14096,10 +14755,10 @@ import path70 from "node:path";
14096
14755
  import { accessSync, constants, existsSync as existsSync47, readFileSync as readFileSync19 } from "node:fs";
14097
14756
  import { homedir as homedir16 } from "node:os";
14098
14757
  import path69 from "node:path";
14099
- import { spawnSync as spawnSync10 } from "node:child_process";
14758
+ import { spawnSync as spawnSync12 } from "node:child_process";
14100
14759
  function captureCommand(bin, args) {
14101
14760
  try {
14102
- const res = spawnSync10(bin, args, { encoding: "utf8" });
14761
+ const res = spawnSync12(bin, args, { encoding: "utf8" });
14103
14762
  const stdout = (res.stdout || "").trim();
14104
14763
  const stderr = (res.stderr || "").trim();
14105
14764
  const ok = res.status === 0;
@@ -14348,12 +15007,12 @@ function assessRuntimeTakeoverScheduler(env, ctx) {
14348
15007
  return check({
14349
15008
  id: "hotspot_openclaw_scheduler",
14350
15009
  label: "Scheduler provider (runtime daemon vs OpenClaw cron)",
14351
- status: "pass",
14352
- summary: "KYNVER_SCHEDULER_PROVIDER unset on hosted deployment; scheduler resolves to kynver-cron (Kynver-owned, not OpenClaw)",
14353
- 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.",
15010
+ status: "warn",
15011
+ 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",
15012
+ 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.",
14354
15013
  details: {
14355
15014
  schedulerProvider: null,
14356
- resolvedFallback: "kynver-cron",
15015
+ resolvedFallback: "none",
14357
15016
  qstashTokenPresent: false,
14358
15017
  hostedDeployment
14359
15018
  }
@@ -14999,7 +15658,7 @@ var LANDING_MAINTAINER_LANE_SPEC = {
14999
15658
  };
15000
15659
 
15001
15660
  // src/lane/landing-maintainer-local.ts
15002
- import { spawnSync as spawnSync11 } from "node:child_process";
15661
+ import { spawnSync as spawnSync13 } from "node:child_process";
15003
15662
  import path72 from "node:path";
15004
15663
  function runLandingWrapper(prNumber, repoRoot, execute) {
15005
15664
  const script = path72.join(repoRoot, LANDING_MAINTAINER_LANE_SPEC.landScript);
@@ -15013,7 +15672,7 @@ function runLandingWrapper(prNumber, repoRoot, execute) {
15013
15672
  stderr: ""
15014
15673
  };
15015
15674
  }
15016
- const result = spawnSync11("node", args, {
15675
+ const result = spawnSync13("node", args, {
15017
15676
  cwd: repoRoot,
15018
15677
  encoding: "utf8",
15019
15678
  timeout: 10 * 60 * 1e3
@@ -15155,14 +15814,15 @@ function usage(code = 0) {
15155
15814
  " kynver runner credential [--agent-os-id ID] [--base-url URL]",
15156
15815
  " 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]",
15157
15816
  " kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS]",
15817
+ " kynver status --run RUN_ID [--blocked] [--running] [--task TASK_ID] [--worker WORKER] [--full] # top-level compact run status",
15158
15818
  " kynver run create [--repo /path/repo] [--name name] [--base origin/main]",
15159
15819
  " kynver run list",
15160
15820
  " kynver run resolve --name RUN_NAME",
15161
- " kynver run status --run RUN_ID [--json] [--compact]",
15821
+ " kynver run status --run RUN_ID [--full] # defaults to compact shallow map with drill-down commands",
15162
15822
  " 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]",
15163
15823
  " kynver run sweep --run RUN_ID --agent-os-id AOS_ID [--base-url URL] [--secret SECRET] [--grace-ms MS]",
15164
15824
  ' 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]',
15165
- " kynver worker list --run RUN_ID [--json] [--compact] [--full]",
15825
+ " kynver worker list --run RUN_ID [--full] # defaults to compact shallow map with drill-down commands",
15166
15826
  " kynver worker status --run RUN_ID --name worker",
15167
15827
  " kynver worker tail --run RUN_ID --name worker [--lines 40] [--raw]",
15168
15828
  " kynver worker stop --run RUN_ID --name worker",
@@ -15216,6 +15876,9 @@ async function main(argv = process.argv.slice(2)) {
15216
15876
  const { runsDir, worktreesDir } = getPaths();
15217
15877
  mkdirSync10(runsDir, { recursive: true });
15218
15878
  mkdirSync10(worktreesDir, { recursive: true });
15879
+ if (scope === "daemon") {
15880
+ assertNativeDaemonAllowed();
15881
+ }
15219
15882
  if (shouldEnforceMemoryCostPackageGuardCli(scope, action)) {
15220
15883
  let repoRoot;
15221
15884
  const runId = args.run ? String(args.run).trim() : "";
@@ -15232,6 +15895,7 @@ async function main(argv = process.argv.slice(2)) {
15232
15895
  });
15233
15896
  }
15234
15897
  if (scope === "login") return void await runLogin(args);
15898
+ if (scope === "status") return runStatus(args);
15235
15899
  if (scope === "runner" && action === "credential") return void await mintRunnerCredential(args);
15236
15900
  if (scope === "setup") return void await runSetup(args);
15237
15901
  if (scope === "daemon") return void await runDaemon(args);