@adhdev/daemon-core 0.9.82-rc.262 → 0.9.82-rc.264

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.mjs CHANGED
@@ -251,6 +251,29 @@ var init_git_executor = __esm({
251
251
  }
252
252
  });
253
253
 
254
+ // src/build-info.ts
255
+ function readInjected(value) {
256
+ if (typeof value !== "string") return void 0;
257
+ const trimmed = value.trim();
258
+ if (!trimmed || trimmed === "unknown") return void 0;
259
+ return trimmed;
260
+ }
261
+ function getDaemonBuildInfo() {
262
+ if (cached) return cached;
263
+ const commit = readInjected(true ? "751232919b0935654ee3d75d7cee4607594d6836" : void 0) ?? "unknown";
264
+ const commitShort = readInjected(true ? "7512329" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
265
+ const version = readInjected(true ? "0.9.82-rc.264" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
266
+ const builtAt = readInjected(true ? "2026-06-14T15:24:06.476Z" : void 0);
267
+ cached = builtAt ? { commit, commitShort, version, builtAt } : { commit, commitShort, version };
268
+ return cached;
269
+ }
270
+ var cached;
271
+ var init_build_info = __esm({
272
+ "src/build-info.ts"() {
273
+ "use strict";
274
+ }
275
+ });
276
+
254
277
  // src/git/git-status.ts
255
278
  async function getGitRepoStatus(workspace, options = {}) {
256
279
  const lastCheckedAt = Date.now();
@@ -273,6 +296,7 @@ async function getGitRepoStatus(workspace, options = {}) {
273
296
  }
274
297
  const submoduleDirty = (submodules || []).some((submodule) => submodule.dirty || submodule.outOfSync || !!submodule.error);
275
298
  const dirty = parsed.staged + parsed.modified + parsed.untracked + parsed.deleted + parsed.renamed > 0 || parsed.conflictFiles.length > 0 || stashCount > 0 || submoduleDirty;
299
+ const daemonBuildBehind = await detectDaemonBuildBehind(repo, submodules, options);
276
300
  return {
277
301
  workspace: repo.workspace,
278
302
  repoRoot: repo.repoRoot,
@@ -296,7 +320,8 @@ async function getGitRepoStatus(workspace, options = {}) {
296
320
  conflictFiles: parsed.conflictFiles,
297
321
  stashCount,
298
322
  lastCheckedAt,
299
- submodules
323
+ submodules,
324
+ ...daemonBuildBehind ? { daemonBuildBehind } : {}
300
325
  };
301
326
  } catch (error) {
302
327
  if (error instanceof GitCommandError) {
@@ -309,6 +334,68 @@ async function getGitRepoStatus(workspace, options = {}) {
309
334
  );
310
335
  }
311
336
  }
337
+ async function classifyDaemonBuildChange(repoPath, buildCommit, options) {
338
+ try {
339
+ const diff = await runGit(repoPath, ["diff", "--name-only", `${buildCommit}..HEAD`], options);
340
+ const files = diff.stdout.split("\n").map((line) => line.trim()).filter(Boolean);
341
+ if (files.length === 0) {
342
+ return { isDaemonAffecting: true, affectedPackages: [] };
343
+ }
344
+ const pkgs = /* @__PURE__ */ new Set();
345
+ let sawNonPackageOrUnknown = false;
346
+ for (const file of files) {
347
+ const match = file.match(/(?:^|\/)packages\/([^/]+)\//);
348
+ if (!match) {
349
+ sawNonPackageOrUnknown = true;
350
+ continue;
351
+ }
352
+ pkgs.add(match[1]);
353
+ }
354
+ const affectedPackages = [...pkgs].sort();
355
+ const allWebOnly = !sawNonPackageOrUnknown && affectedPackages.length > 0 && affectedPackages.every((p) => WEB_ONLY_PACKAGES.has(p) && !DAEMON_RUNTIME_PACKAGES.has(p));
356
+ return { isDaemonAffecting: !allWebOnly, affectedPackages };
357
+ } catch {
358
+ return { isDaemonAffecting: true, affectedPackages: [] };
359
+ }
360
+ }
361
+ async function detectDaemonBuildBehind(repo, submodules, options) {
362
+ const build = options.daemonBuildInfo ?? getDaemonBuildInfo();
363
+ if (!build.commit || build.commit === "unknown") return void 0;
364
+ const scopes = [
365
+ { scope: "root", repoPath: repo.repoRoot || repo.workspace }
366
+ ];
367
+ for (const sub of submodules || []) {
368
+ if (sub.repoPath && !sub.error) scopes.push({ scope: sub.path, repoPath: sub.repoPath });
369
+ }
370
+ for (const { scope, repoPath } of scopes) {
371
+ try {
372
+ await runGit(repoPath, ["cat-file", "-e", `${build.commit}^{commit}`], options);
373
+ const headResult = await runGit(repoPath, ["rev-parse", "HEAD"], options);
374
+ const head = headResult.stdout.trim();
375
+ if (!head || head === build.commit) continue;
376
+ await runGit(repoPath, ["merge-base", "--is-ancestor", build.commit, "HEAD"], options);
377
+ const { isDaemonAffecting, affectedPackages } = await classifyDaemonBuildChange(
378
+ repoPath,
379
+ build.commit,
380
+ options
381
+ );
382
+ const scopeLabel = scope === "root" ? "workspace" : scope;
383
+ const warning = isDaemonAffecting ? `Live daemon was built from ${build.commitShort} which is behind ${scopeLabel} HEAD ${head.slice(0, 7)}. Merged code is NOT live until the daemon is rebuilt/redeployed and restarted \u2014 a local dist rebuild alone does not update a cloud daemon.` : `Live daemon was built from ${build.commitShort} which is behind ${scopeLabel} HEAD ${head.slice(0, 7)}, but only web packages changed (${(affectedPackages || []).join(", ") || "web"}). Daemon restart NOT required \u2014 redeploy the web app to reflect the change.`;
384
+ return {
385
+ buildCommit: build.commit,
386
+ buildCommitShort: build.commitShort,
387
+ head,
388
+ scope,
389
+ isDaemonAffecting,
390
+ ...affectedPackages && affectedPackages.length > 0 ? { affectedPackages } : {},
391
+ warning
392
+ };
393
+ } catch {
394
+ continue;
395
+ }
396
+ }
397
+ return void 0;
398
+ }
312
399
  async function readPorcelainStatus(repo, options) {
313
400
  const statusOutput = await runGit(repo, ["status", "--porcelain=v2", "--branch"], options);
314
401
  return parsePorcelainV2Status(statusOutput.stdout);
@@ -521,10 +608,29 @@ function parseSubmoduleStatusOutput(output, repoRoot, ignorePaths) {
521
608
  }
522
609
  return submodules;
523
610
  }
611
+ var DAEMON_RUNTIME_PACKAGES, WEB_ONLY_PACKAGES;
524
612
  var init_git_status = __esm({
525
613
  "src/git/git-status.ts"() {
526
614
  "use strict";
527
615
  init_git_executor();
616
+ init_build_info();
617
+ DAEMON_RUNTIME_PACKAGES = /* @__PURE__ */ new Set([
618
+ "daemon-core",
619
+ "daemon-standalone",
620
+ "session-host-core",
621
+ "session-host-daemon",
622
+ "terminal-mux-core",
623
+ "terminal-mux-control",
624
+ "terminal-mux-cli",
625
+ "ghostty-vt-node",
626
+ "mcp-server"
627
+ ]);
628
+ WEB_ONLY_PACKAGES = /* @__PURE__ */ new Set([
629
+ "web-core",
630
+ "web-standalone",
631
+ "web-devconsole",
632
+ "terminal-render-web"
633
+ ]);
528
634
  }
529
635
  });
530
636
 
@@ -2385,8 +2491,8 @@ function readLedgerFromStore(meshId) {
2385
2491
  }
2386
2492
  function getCachedRawEntries(meshId) {
2387
2493
  const now = Date.now();
2388
- const cached = ledgerReadCache.get(meshId);
2389
- if (cached && now - cached.cachedAt < LEDGER_CACHE_TTL_MS) return cached.entries;
2494
+ const cached2 = ledgerReadCache.get(meshId);
2495
+ if (cached2 && now - cached2.cachedAt < LEDGER_CACHE_TTL_MS) return cached2.entries;
2390
2496
  let entries;
2391
2497
  try {
2392
2498
  entries = readLedgerFromStore(meshId);
@@ -2645,6 +2751,7 @@ __export(mesh_work_queue_exports, {
2645
2751
  cancelTask: () => cancelTask,
2646
2752
  claimNextTask: () => claimNextTask,
2647
2753
  cleanupTerminalDirectDispatches: () => cleanupTerminalDirectDispatches,
2754
+ deleteDirectDispatchesByTaskId: () => deleteDirectDispatchesByTaskId,
2648
2755
  describeTaskDependencyState: () => describeTaskDependencyState,
2649
2756
  enqueueTask: () => enqueueTask,
2650
2757
  getActiveDirectDispatches: () => getActiveDirectDispatches,
@@ -3054,6 +3161,13 @@ function markStaleDirectDispatches(meshId, olderThanMs = 60 * 6e4) {
3054
3161
  } catch {
3055
3162
  }
3056
3163
  }
3164
+ function deleteDirectDispatchesByTaskId(meshId, taskIds) {
3165
+ try {
3166
+ return MeshRuntimeStore.getInstance().deleteDirectDispatchesByTaskId(meshId, taskIds);
3167
+ } catch {
3168
+ return 0;
3169
+ }
3170
+ }
3057
3171
  function recordMeshToolCall(opts) {
3058
3172
  try {
3059
3173
  return MeshRuntimeStore.getInstance().recordMeshToolCall(opts);
@@ -3685,6 +3799,24 @@ var init_mesh_runtime_store = __esm({
3685
3799
  deleteDirectDispatches(meshId) {
3686
3800
  this.db.prepare(`DELETE FROM mesh_direct_dispatches WHERE mesh_id = ?`).run(meshId);
3687
3801
  }
3802
+ /**
3803
+ * Delete specific direct dispatch rows by taskId for a mesh. Used by the staleDirect prune
3804
+ * path to remove orphaned/terminal dispatch records whose node/session is no longer in the
3805
+ * live mesh. Returns the number of rows actually deleted. No-op for an empty taskId list.
3806
+ */
3807
+ deleteDirectDispatchesByTaskId(meshId, taskIds) {
3808
+ const ids = (taskIds || []).map((id) => typeof id === "string" ? id.trim() : "").filter(Boolean);
3809
+ if (!ids.length) return 0;
3810
+ const stmt = this.db.prepare(`DELETE FROM mesh_direct_dispatches WHERE mesh_id = ? AND task_id = ?`);
3811
+ let deleted = 0;
3812
+ const run = this.db.transaction((rows) => {
3813
+ for (const taskId of rows) {
3814
+ deleted += stmt.run(meshId, taskId).changes;
3815
+ }
3816
+ });
3817
+ run(ids);
3818
+ return deleted;
3819
+ }
3688
3820
  markStaleDirectDispatches(meshId, olderThanMs) {
3689
3821
  const cutoff = new Date(Date.now() - olderThanMs).toISOString();
3690
3822
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -5353,11 +5485,14 @@ async function fastForwardMeshNode(args) {
5353
5485
  const trigger = normalizeOptionalString(args.trigger) || "manual";
5354
5486
  const updateSubmodules = args.updateSubmodules === true;
5355
5487
  const dryRun = args.dryRun === true || args.execute !== true;
5356
- const plannedSteps = buildPlannedSteps(updateSubmodules);
5488
+ const mode = args.mode === "push" ? "push" : "merge";
5489
+ const pushSubmodules = mode === "push" && args.pushSubmodules === true;
5490
+ const plannedSteps = buildPlannedSteps(mode, updateSubmodules, pushSubmodules);
5357
5491
  const base = {
5358
5492
  ...nodeId ? { nodeId } : {},
5359
5493
  ...meshId ? { meshId } : {},
5360
5494
  workspace,
5495
+ mode,
5361
5496
  dryRun,
5362
5497
  updateSubmodules,
5363
5498
  plannedSteps,
@@ -5371,13 +5506,24 @@ async function fastForwardMeshNode(args) {
5371
5506
  submoduleIgnorePaths: args.submoduleIgnorePaths,
5372
5507
  timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
5373
5508
  });
5509
+ if (mode === "push") {
5510
+ return pushMeshNode(base, args, current, {
5511
+ pushSubmodules,
5512
+ allowAutoPublishSubmoduleMainCommits: args.allowAutoPublishSubmoduleMainCommits === true
5513
+ });
5514
+ }
5374
5515
  const earlyBlockers = collectPreflightBlockers(current, requestedBranch);
5375
5516
  if (earlyBlockers.length > 0) {
5517
+ const blockCode = chooseBlockCode(current, earlyBlockers);
5376
5518
  const result2 = {
5377
- ...block(base, chooseBlockCode(current, earlyBlockers), earlyBlockers),
5519
+ ...block(base, blockCode, earlyBlockers),
5378
5520
  current,
5379
- finalBranchConvergenceState: buildConvergenceState(current, codeToConvergenceStatus(chooseBlockCode(current, earlyBlockers)))
5521
+ finalBranchConvergenceState: buildConvergenceState(current, codeToConvergenceStatus(blockCode))
5380
5522
  };
5523
+ if (blockCode === "branch_ahead" && current.ahead > 0 && current.behind === 0 && otherBlockersAreOnlyAhead(earlyBlockers)) {
5524
+ result2.code = "ahead_needs_push";
5525
+ result2.nextStep = 'Local branch is ahead of origin with nothing to merge. Re-run mesh_fast_forward_node with mode="push" (execute=true) to ff-only push the local commits to origin.';
5526
+ }
5381
5527
  await appendFastForwardLedger(result2, "blocked");
5382
5528
  return result2;
5383
5529
  }
@@ -5488,7 +5634,246 @@ async function fastForwardMeshNode(args) {
5488
5634
  await appendFastForwardLedger(result, success ? "executed" : "failed");
5489
5635
  return result;
5490
5636
  }
5491
- function buildPlannedSteps(updateSubmodules) {
5637
+ async function pushMeshNode(base, args, current, options) {
5638
+ const workspace = base.workspace;
5639
+ const requestedBranch = normalizeOptionalString(args.branch);
5640
+ const dryRun = base.dryRun;
5641
+ const blockers = collectPushPreflightBlockers(current, requestedBranch);
5642
+ if (blockers.length > 0) {
5643
+ const code2 = choosePushBlockCode(current, blockers);
5644
+ const result2 = {
5645
+ ...block(base, code2, blockers),
5646
+ current,
5647
+ preStatus: current,
5648
+ finalBranchConvergenceState: buildConvergenceState(current, codeToConvergenceStatus(code2))
5649
+ };
5650
+ await appendFastForwardLedger(result2, "blocked");
5651
+ return result2;
5652
+ }
5653
+ const target = parseUpstreamTarget(current.upstream || "");
5654
+ if (!target) {
5655
+ const result2 = {
5656
+ ...block(base, "upstream_unparseable", ["upstream_unparseable"]),
5657
+ current,
5658
+ preStatus: current,
5659
+ finalBranchConvergenceState: buildConvergenceState(current, "blocked")
5660
+ };
5661
+ await appendFastForwardLedger(result2, "blocked");
5662
+ return result2;
5663
+ }
5664
+ const refspec = `HEAD:refs/heads/${target.remoteBranch}`;
5665
+ const pushTarget = { remote: target.remote, remoteBranch: target.remoteBranch, refspec };
5666
+ if (current.ahead <= 0) {
5667
+ const result2 = {
5668
+ ...base,
5669
+ success: true,
5670
+ code: "nothing_to_push",
5671
+ allowed: true,
5672
+ willRun: false,
5673
+ executed: false,
5674
+ blockingReasons: [],
5675
+ current,
5676
+ preStatus: current,
5677
+ postStatus: current,
5678
+ pushTarget,
5679
+ finalBranchConvergenceState: buildConvergenceState(current, "up_to_date")
5680
+ };
5681
+ await appendFastForwardLedger(result2, "noop");
5682
+ return result2;
5683
+ }
5684
+ const descendant = await verifyUpstreamIsAncestorOfHead(workspace, current.upstream || "", args.timeoutMs);
5685
+ if (!descendant.ok) {
5686
+ const result2 = {
5687
+ ...block(base, "non_fast_forward_push", ["head_is_not_descendant_of_upstream"]),
5688
+ current,
5689
+ preStatus: current,
5690
+ pushTarget,
5691
+ operationError: descendant.error,
5692
+ nextStep: "origin/<branch> has commits not in local HEAD; a ff-only push would lose them. Converge by rebasing onto origin first, then re-run. This operation never force-pushes.",
5693
+ finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
5694
+ };
5695
+ await appendFastForwardLedger(result2, "blocked");
5696
+ return result2;
5697
+ }
5698
+ if (dryRun) {
5699
+ const result2 = {
5700
+ ...base,
5701
+ success: true,
5702
+ code: "push_available",
5703
+ allowed: true,
5704
+ willRun: false,
5705
+ executed: false,
5706
+ blockingReasons: [],
5707
+ current,
5708
+ preStatus: current,
5709
+ pushTarget,
5710
+ ...options.pushSubmodules ? { submodulePushes: await planSubmodulePushes(current, options, args.timeoutMs) } : {},
5711
+ finalBranchConvergenceState: buildConvergenceState(current, "push_available")
5712
+ };
5713
+ await appendFastForwardLedger(result2, "dry_run");
5714
+ return result2;
5715
+ }
5716
+ try {
5717
+ await runGit(workspace, ["push", target.remote, refspec], { timeoutMs: args.timeoutMs ?? 3e4 });
5718
+ } catch (error) {
5719
+ const result2 = {
5720
+ ...block(base, "push_ff_only_failed", ["push_ff_only_failed"]),
5721
+ current,
5722
+ preStatus: current,
5723
+ pushTarget,
5724
+ operationError: formatGitError2(error),
5725
+ finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
5726
+ };
5727
+ await appendFastForwardLedger(result2, "failed");
5728
+ return result2;
5729
+ }
5730
+ let submodulePushes;
5731
+ if (options.pushSubmodules) {
5732
+ submodulePushes = await executeSubmodulePushes(current, options, args.timeoutMs);
5733
+ }
5734
+ const postStatus = await getGitRepoStatus(workspace, {
5735
+ ...STATUS_OPTIONS,
5736
+ submoduleIgnorePaths: args.submoduleIgnorePaths,
5737
+ timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
5738
+ });
5739
+ const submodulePushFailed = (submodulePushes || []).some((entry) => !entry.pushed && !entry.skipped);
5740
+ const blockingReasons = [];
5741
+ if (postStatus.ahead !== 0) blockingReasons.push("post_branch_ahead");
5742
+ if (submodulePushFailed) blockingReasons.push("submodule_push_failed");
5743
+ const success = blockingReasons.length === 0;
5744
+ const code = success ? "push_applied" : submodulePushFailed && postStatus.ahead === 0 ? "push_applied_submodule_push_failed" : "post_push_verify_failed";
5745
+ const result = {
5746
+ ...base,
5747
+ success,
5748
+ code,
5749
+ allowed: true,
5750
+ willRun: true,
5751
+ executed: true,
5752
+ blockingReasons,
5753
+ current,
5754
+ preStatus: current,
5755
+ postStatus,
5756
+ pushTarget,
5757
+ ...submodulePushes ? { submodulePushes } : {},
5758
+ finalBranchConvergenceState: buildConvergenceState(postStatus, success ? "pushed" : "post_verify_failed")
5759
+ };
5760
+ await appendFastForwardLedger(result, success ? "executed" : "failed");
5761
+ return result;
5762
+ }
5763
+ function collectPushPreflightBlockers(status, requestedBranch) {
5764
+ const blockers = [];
5765
+ if (!status.isGitRepo) blockers.push("not_git_repo");
5766
+ if (!status.branch) blockers.push("detached_head_or_unknown_branch");
5767
+ if (requestedBranch && status.branch !== requestedBranch) blockers.push("branch_mismatch");
5768
+ if (!status.upstream) blockers.push("upstream_missing");
5769
+ if (status.upstreamStatus !== "fresh") blockers.push("upstream_not_fresh");
5770
+ if (status.hasConflicts) blockers.push("conflicts_present");
5771
+ if (status.staged > 0) blockers.push("staged_changes_present");
5772
+ if (status.modified > 0) blockers.push("modified_changes_present");
5773
+ if (status.untracked > 0) blockers.push("untracked_changes_present");
5774
+ if (status.deleted > 0) blockers.push("deleted_changes_present");
5775
+ if (status.renamed > 0) blockers.push("renamed_changes_present");
5776
+ if (status.stashCount > 0) blockers.push("stash_entries_present");
5777
+ if (status.ahead > 0 && status.behind > 0) blockers.push("branch_diverged_from_upstream");
5778
+ else if (status.behind > 0) blockers.push("branch_behind_upstream");
5779
+ return blockers;
5780
+ }
5781
+ function choosePushBlockCode(status, blockers) {
5782
+ if (blockers.includes("not_git_repo")) return "not_git_repo";
5783
+ if (blockers.includes("branch_mismatch")) return "branch_mismatch";
5784
+ if (blockers.includes("upstream_missing")) return "upstream_missing";
5785
+ if (blockers.includes("upstream_not_fresh")) return "upstream_not_fresh";
5786
+ if (blockers.includes("branch_diverged_from_upstream")) return "branch_diverged";
5787
+ if (blockers.includes("branch_behind_upstream")) return "non_fast_forward_push";
5788
+ if (blockers.some((reason) => reason.includes("changes") || reason.includes("conflicts") || reason.includes("stash"))) return "dirty_worktree";
5789
+ return "preflight_blocked";
5790
+ }
5791
+ function parseUpstreamTarget(upstream) {
5792
+ const trimmed = upstream.trim();
5793
+ const slash = trimmed.indexOf("/");
5794
+ if (slash <= 0 || slash >= trimmed.length - 1) return null;
5795
+ return { remote: trimmed.slice(0, slash), remoteBranch: trimmed.slice(slash + 1) };
5796
+ }
5797
+ async function verifyUpstreamIsAncestorOfHead(workspace, upstream, timeoutMs) {
5798
+ if (!upstream) return { ok: false, error: "missing upstream" };
5799
+ try {
5800
+ await runGit(workspace, ["merge-base", "--is-ancestor", upstream, "HEAD"], { timeoutMs: timeoutMs ?? 15e3 });
5801
+ return { ok: true };
5802
+ } catch (error) {
5803
+ return { ok: false, error: formatGitError2(error) };
5804
+ }
5805
+ }
5806
+ async function planSubmodulePushes(status, options, timeoutMs) {
5807
+ return resolveSubmodulePushes(status, options, false, timeoutMs);
5808
+ }
5809
+ async function executeSubmodulePushes(status, options, timeoutMs) {
5810
+ return resolveSubmodulePushes(status, options, true, timeoutMs);
5811
+ }
5812
+ async function resolveSubmodulePushes(status, options, execute, timeoutMs) {
5813
+ const submodules = Array.isArray(status.submodules) ? status.submodules : [];
5814
+ const results = [];
5815
+ for (const submodule of submodules) {
5816
+ const base = {
5817
+ path: submodule.path,
5818
+ commit: submodule.commit,
5819
+ remote: "origin",
5820
+ remoteBranch: "main",
5821
+ pushed: false,
5822
+ skipped: true,
5823
+ code: "submodule_push_skipped"
5824
+ };
5825
+ if (!options.allowAutoPublishSubmoduleMainCommits) {
5826
+ results.push({ ...base, code: "submodule_push_policy_disabled", error: "allowAutoPublishSubmoduleMainCommits is not enabled" });
5827
+ continue;
5828
+ }
5829
+ if (submodule.error || submodule.dirty) {
5830
+ results.push({ ...base, code: "submodule_not_clean", error: submodule.error || "submodule worktree is dirty" });
5831
+ continue;
5832
+ }
5833
+ const repoPath = submodule.repoPath;
5834
+ if (!repoPath || !submodule.commit) {
5835
+ results.push({ ...base, code: "submodule_status_incomplete" });
5836
+ continue;
5837
+ }
5838
+ try {
5839
+ await runGit(repoPath, ["-c", "protocol.file.allow=always", "fetch", "origin", "refs/heads/main:refs/remotes/origin/main"], { timeoutMs: timeoutMs ?? 3e4 });
5840
+ } catch (error) {
5841
+ results.push({ ...base, code: "submodule_fetch_failed", error: formatGitError2(error) });
5842
+ continue;
5843
+ }
5844
+ let alreadyReachable = false;
5845
+ try {
5846
+ await runGit(repoPath, ["merge-base", "--is-ancestor", submodule.commit, "refs/remotes/origin/main"], { timeoutMs: timeoutMs ?? 15e3 });
5847
+ alreadyReachable = true;
5848
+ } catch {
5849
+ }
5850
+ if (alreadyReachable) {
5851
+ results.push({ ...base, pushed: false, skipped: true, code: "submodule_already_reachable" });
5852
+ continue;
5853
+ }
5854
+ try {
5855
+ await runGit(repoPath, ["merge-base", "--is-ancestor", "refs/remotes/origin/main", submodule.commit], { timeoutMs: timeoutMs ?? 15e3 });
5856
+ } catch (error) {
5857
+ results.push({ ...base, pushed: false, skipped: false, code: "submodule_non_fast_forward", error: formatGitError2(error) });
5858
+ continue;
5859
+ }
5860
+ const refspec = `${submodule.commit}:refs/heads/main`;
5861
+ if (!execute) {
5862
+ results.push({ ...base, pushed: false, skipped: false, code: "submodule_push_available", refspec });
5863
+ continue;
5864
+ }
5865
+ try {
5866
+ await runGit(repoPath, ["push", "origin", refspec], { timeoutMs: timeoutMs ?? 3e4 });
5867
+ await runGit(repoPath, ["-c", "protocol.file.allow=always", "fetch", "origin", "refs/heads/main:refs/remotes/origin/main"], { timeoutMs: timeoutMs ?? 3e4 });
5868
+ await runGit(repoPath, ["merge-base", "--is-ancestor", submodule.commit, "refs/remotes/origin/main"], { timeoutMs: timeoutMs ?? 15e3 });
5869
+ results.push({ ...base, pushed: true, skipped: false, code: "submodule_pushed", refspec });
5870
+ } catch (error) {
5871
+ results.push({ ...base, pushed: false, skipped: false, code: "submodule_push_failed", refspec, error: formatGitError2(error) });
5872
+ }
5873
+ }
5874
+ return results;
5875
+ }
5876
+ function buildPlannedSteps(mode, updateSubmodules, pushSubmodules) {
5492
5877
  const steps = [
5493
5878
  {
5494
5879
  operation: "refresh_upstream",
@@ -5501,20 +5886,49 @@ function buildPlannedSteps(updateSubmodules) {
5501
5886
  description: "Require clean staged/modified/untracked/deleted/renamed/conflict/stash/submodule state.",
5502
5887
  safe: true,
5503
5888
  willMutateWorktree: false
5504
- },
5505
- {
5506
- operation: "verify_fast_forward",
5507
- description: "Require ahead=0, behind>0, and HEAD to be an ancestor of the upstream ref.",
5889
+ }
5890
+ ];
5891
+ if (mode === "push") {
5892
+ steps.push({
5893
+ operation: "verify_push_descendant",
5894
+ description: "Require HEAD to be a descendant of origin/<branch> (origin/<branch> is an ancestor of HEAD); refuse any non-fast-forward push.",
5508
5895
  safe: true,
5509
5896
  willMutateWorktree: false
5510
- },
5511
- {
5512
- operation: "merge_ff_only",
5513
- description: "Apply git merge --ff-only against the tracked upstream; no force, reset, rebase, push, or deploy.",
5897
+ });
5898
+ steps.push({
5899
+ operation: "push_ff_only",
5900
+ description: "Run git push origin HEAD:<branch> as a strict ff-only push; never --force, --force-with-lease, reset, or rebase. Does not mutate the worktree.",
5514
5901
  safe: true,
5515
- willMutateWorktree: true
5902
+ willMutateWorktree: false
5903
+ });
5904
+ if (pushSubmodules) {
5905
+ steps.push({
5906
+ operation: "push_submodules_ff_only",
5907
+ description: "For each submodule, if allowAutoPublishSubmoduleMainCommits is enabled and the submodule HEAD is a descendant of its origin main, ff-only push it to submodule origin main; otherwise skip.",
5908
+ safe: true,
5909
+ willMutateWorktree: false
5910
+ });
5516
5911
  }
5517
- ];
5912
+ steps.push({
5913
+ operation: "verify_post_status",
5914
+ description: "Re-read daemon-owned git status and report final branch convergence state.",
5915
+ safe: true,
5916
+ willMutateWorktree: false
5917
+ });
5918
+ return steps;
5919
+ }
5920
+ steps.push({
5921
+ operation: "verify_fast_forward",
5922
+ description: "Require ahead=0, behind>0, and HEAD to be an ancestor of the upstream ref.",
5923
+ safe: true,
5924
+ willMutateWorktree: false
5925
+ });
5926
+ steps.push({
5927
+ operation: "merge_ff_only",
5928
+ description: "Apply git merge --ff-only against the tracked upstream; no force, reset, rebase, push, or deploy.",
5929
+ safe: true,
5930
+ willMutateWorktree: true
5931
+ });
5518
5932
  if (updateSubmodules) {
5519
5933
  steps.push({
5520
5934
  operation: "submodule_update",
@@ -5531,6 +5945,10 @@ function buildPlannedSteps(updateSubmodules) {
5531
5945
  });
5532
5946
  return steps;
5533
5947
  }
5948
+ function otherBlockersAreOnlyAhead(blockers) {
5949
+ const aheadOnly = /* @__PURE__ */ new Set(["branch_has_local_commits"]);
5950
+ return blockers.every((reason) => aheadOnly.has(reason));
5951
+ }
5534
5952
  function collectPreflightBlockers(status, requestedBranch) {
5535
5953
  const blockers = [];
5536
5954
  if (!status.isGitRepo) blockers.push("not_git_repo");
@@ -5587,7 +6005,7 @@ function chooseBlockCode(status, blockers) {
5587
6005
  return "preflight_blocked";
5588
6006
  }
5589
6007
  function codeToConvergenceStatus(code) {
5590
- if (code === "branch_diverged" || code === "branch_ahead" || code === "non_fast_forward") return "not_mergeable";
6008
+ if (code === "branch_diverged" || code === "branch_ahead" || code === "non_fast_forward" || code === "non_fast_forward_push" || code === "upstream_unparseable") return "not_mergeable";
5591
6009
  if (code === "dirty_worktree" || code === "submodule_not_clean") return "blocked_review";
5592
6010
  return "blocked";
5593
6011
  }
@@ -5670,6 +6088,7 @@ async function appendFastForwardLedger(result, outcome) {
5670
6088
  ...result.nodeId ? { nodeId: result.nodeId } : {},
5671
6089
  payload: {
5672
6090
  operation: "mesh_fast_forward_node",
6091
+ mode: result.mode,
5673
6092
  trigger: result.trigger || "manual",
5674
6093
  outcome,
5675
6094
  code: result.code,
@@ -5680,6 +6099,7 @@ async function appendFastForwardLedger(result, outcome) {
5680
6099
  executed: result.executed,
5681
6100
  branch: result.postStatus?.branch ?? result.current?.branch,
5682
6101
  upstream: result.postStatus?.upstream ?? result.current?.upstream,
6102
+ ...result.pushTarget ? { pushTarget: result.pushTarget } : {},
5683
6103
  before: result.current ? {
5684
6104
  headCommit: result.current.headCommit,
5685
6105
  ahead: result.current.ahead,
@@ -5690,6 +6110,16 @@ async function appendFastForwardLedger(result, outcome) {
5690
6110
  ahead: result.postStatus.ahead,
5691
6111
  behind: result.postStatus.behind
5692
6112
  } : void 0,
6113
+ ...result.submodulePushes ? {
6114
+ submodulePushes: result.submodulePushes.map((entry) => ({
6115
+ path: entry.path,
6116
+ commit: entry.commit,
6117
+ pushed: entry.pushed,
6118
+ skipped: entry.skipped,
6119
+ code: entry.code,
6120
+ ...entry.refspec ? { refspec: entry.refspec } : {}
6121
+ }))
6122
+ } : {},
5693
6123
  blockingReasons: result.blockingReasons
5694
6124
  }
5695
6125
  });
@@ -6860,8 +7290,8 @@ var init_mesh_routing = __esm({
6860
7290
  import { existsSync as existsSync14 } from "fs";
6861
7291
  function getCachedMeshByWorkspace(workspace) {
6862
7292
  const now = Date.now();
6863
- const cached = meshByWorkspaceCache.get(workspace);
6864
- if (cached && now - cached.cachedAt < MESH_WORKSPACE_CACHE_TTL_MS) return cached.mesh;
7293
+ const cached2 = meshByWorkspaceCache.get(workspace);
7294
+ if (cached2 && now - cached2.cachedAt < MESH_WORKSPACE_CACHE_TTL_MS) return cached2.mesh;
6865
7295
  const mesh = getMeshByRepo(workspace);
6866
7296
  meshByWorkspaceCache.set(workspace, { mesh, cachedAt: now });
6867
7297
  return mesh;
@@ -11754,10 +12184,10 @@ ${lastSnapshot}`;
11754
12184
  return this.accumulatedRawBuffer.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, "").replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, "").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
11755
12185
  }
11756
12186
  getFreshParsedStatusCache() {
11757
- const cached = this.parsedStatusCache;
12187
+ const cached2 = this.parsedStatusCache;
11758
12188
  const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
11759
- if (cached && cached.responseBuffer === this.responseBuffer && cached.currentTurnScope === this.engine.currentTurnScope && cached.recentOutputBuffer === this.recentOutputBuffer && cached.accumulatedBuffer === this.accumulatedBuffer && cached.accumulatedRawBufferKey === accumulatedRawBufferKey && cached.screenText === this.lastScreenText && cached.currentStatus === this.engine.currentStatus && cached.activeModal === this.engine.activeModal && cached.cliName === this.cliName) {
11760
- return cached.result;
12189
+ if (cached2 && cached2.responseBuffer === this.responseBuffer && cached2.currentTurnScope === this.engine.currentTurnScope && cached2.recentOutputBuffer === this.recentOutputBuffer && cached2.accumulatedBuffer === this.accumulatedBuffer && cached2.accumulatedRawBufferKey === accumulatedRawBufferKey && cached2.screenText === this.lastScreenText && cached2.currentStatus === this.engine.currentStatus && cached2.activeModal === this.engine.activeModal && cached2.cliName === this.cliName) {
12190
+ return cached2.result;
11761
12191
  }
11762
12192
  return null;
11763
12193
  }
@@ -12217,10 +12647,10 @@ ${lastSnapshot}`;
12217
12647
  getScriptParsedStatus() {
12218
12648
  const screenText = this.readTerminalScreenText();
12219
12649
  const parseScreenText = this.getParseScreenText(screenText);
12220
- const cached = this.parsedStatusCache;
12650
+ const cached2 = this.parsedStatusCache;
12221
12651
  const accumulatedRawBufferKey = this.getAccumulatedRawBufferCacheKey();
12222
- if (!this.providerOwnsTranscript() && cached && cached.responseBuffer === this.responseBuffer && cached.currentTurnScope === this.engine.currentTurnScope && cached.recentOutputBuffer === this.recentOutputBuffer && cached.accumulatedBuffer === this.accumulatedBuffer && cached.accumulatedRawBufferKey === accumulatedRawBufferKey && cached.screenText === parseScreenText && cached.currentStatus === this.engine.currentStatus && cached.activeModal === this.engine.activeModal && cached.cliName === this.cliName) {
12223
- return cached.result;
12652
+ if (!this.providerOwnsTranscript() && cached2 && cached2.responseBuffer === this.responseBuffer && cached2.currentTurnScope === this.engine.currentTurnScope && cached2.recentOutputBuffer === this.recentOutputBuffer && cached2.accumulatedBuffer === this.accumulatedBuffer && cached2.accumulatedRawBufferKey === accumulatedRawBufferKey && cached2.screenText === parseScreenText && cached2.currentStatus === this.engine.currentStatus && cached2.activeModal === this.engine.activeModal && cached2.cliName === this.cliName) {
12653
+ return cached2.result;
12224
12654
  }
12225
12655
  const parsed = this.runParseSession();
12226
12656
  if (!parsed || !Array.isArray(parsed.messages)) {
@@ -16701,6 +17131,17 @@ function buildMeshActiveWork(opts) {
16701
17131
  }
16702
17132
  return { activeWork: records, staleDirectWork, staleDirectWorkNote, terminalDirectWork, summary };
16703
17133
  }
17134
+ var PRUNABLE_ORPHAN_STALE_REASONS = /* @__PURE__ */ new Set([
17135
+ "direct task node is no longer in the live mesh",
17136
+ "direct task session is not present in live session records",
17137
+ "direct task has no node id"
17138
+ ]);
17139
+ function classifyStaleDirectForPrune(record, opts = {}) {
17140
+ if (record.staleDispatchUnacknowledged === true) return "preserve_unacknowledged";
17141
+ if (record.terminal === true) return opts.includeTerminal ? "prunable_terminal" : "preserve_active";
17142
+ if (record.staleReason && PRUNABLE_ORPHAN_STALE_REASONS.has(record.staleReason)) return "prunable_orphan";
17143
+ return "preserve_active";
17144
+ }
16704
17145
  function buildCompactStaleDirectWorkSummary(staleDirectWork, opts = {}) {
16705
17146
  const sampleLimit = Math.max(0, Math.min(10, Math.floor(opts.sampleLimit ?? 3)));
16706
17147
  const reasonCounts = {};
@@ -19924,9 +20365,9 @@ function computeSavedHistorySessionSummaries(agentType, dir, files, fileSignatur
19924
20365
  for (const file of files.slice().sort()) {
19925
20366
  const filePath = path12.join(dir, file);
19926
20367
  const signature = fileSignatures.get(file) || `${file}:missing`;
19927
- const cached = savedHistoryFileSummaryCache.get(filePath);
20368
+ const cached2 = savedHistoryFileSummaryCache.get(filePath);
19928
20369
  const persisted = persistedEntries.get(file);
19929
- const reusableEntry = cached?.signature === signature ? cached : persisted?.signature === signature ? persisted : null;
20370
+ const reusableEntry = cached2?.signature === signature ? cached2 : persisted?.signature === signature ? persisted : null;
19930
20371
  const fileSummary = reusableEntry?.summary || computeSavedHistoryFileSummary(dir, file);
19931
20372
  const nextEntry = reusableEntry || {
19932
20373
  signature,
@@ -20378,23 +20819,23 @@ function listSavedHistorySessions(agentType, options = {}, historyBehavior) {
20378
20819
  savedHistorySessionCache.delete(sanitized);
20379
20820
  return { sessions: [], hasMore: false };
20380
20821
  }
20381
- const cached = savedHistorySessionCache.get(sanitized);
20822
+ const cached2 = savedHistorySessionCache.get(sanitized);
20382
20823
  const offset = Math.max(0, options.offset || 0);
20383
20824
  const limit = Math.max(1, options.limit || 30);
20384
20825
  const indexSignature = buildSavedHistoryIndexFileSignature(dir);
20385
20826
  let cacheWasInvalidated = false;
20386
- if (cached) {
20387
- const cacheLooksPersisted = cached.signature.startsWith("index:");
20388
- const cacheStillValid = cacheLooksPersisted ? cached.signature === indexSignature : (() => {
20827
+ if (cached2) {
20828
+ const cacheLooksPersisted = cached2.signature.startsWith("index:");
20829
+ const cacheStillValid = cacheLooksPersisted ? cached2.signature === indexSignature : (() => {
20389
20830
  const files2 = listHistoryFiles(dir);
20390
20831
  const fileSignatures2 = buildSavedHistoryFileSignatureMap(dir, files2);
20391
- return cached.signature === buildSavedHistoryCacheSignature(files2, fileSignatures2);
20832
+ return cached2.signature === buildSavedHistoryCacheSignature(files2, fileSignatures2);
20392
20833
  })();
20393
20834
  if (cacheStillValid) {
20394
- const sliced2 = cached.summaries.slice(offset, offset + limit);
20835
+ const sliced2 = cached2.summaries.slice(offset, offset + limit);
20395
20836
  return {
20396
20837
  sessions: sliced2,
20397
- hasMore: cached.summaries.length > offset + limit
20838
+ hasMore: cached2.summaries.length > offset + limit
20398
20839
  };
20399
20840
  }
20400
20841
  cacheWasInvalidated = true;
@@ -36988,8 +37429,8 @@ var ProviderLoader = class _ProviderLoader {
36988
37429
  return null;
36989
37430
  }
36990
37431
  registerProviderScriptRootSafely(path30.dirname(path30.dirname(providerDir)));
36991
- const cached = this.scriptsCache.get(dir);
36992
- if (cached) return cached;
37432
+ const cached2 = this.scriptsCache.get(dir);
37433
+ if (cached2) return cached2;
36993
37434
  const scriptsJs = path30.join(dir, "scripts.js");
36994
37435
  if (fs20.existsSync(scriptsJs)) {
36995
37436
  try {
@@ -38987,6 +39428,9 @@ function buildStatusSnapshot(options) {
38987
39428
  };
38988
39429
  }
38989
39430
 
39431
+ // src/commands/router.ts
39432
+ init_build_info();
39433
+
38990
39434
  // src/commands/upgrade-helper.ts
38991
39435
  import { execFileSync as execFileSync4 } from "child_process";
38992
39436
  import { spawn as spawn3 } from "child_process";
@@ -39772,13 +40216,13 @@ function sanitizeInlineMesh(inlineMesh) {
39772
40216
  nodes
39773
40217
  };
39774
40218
  }
39775
- function reconcileInlineMeshCache(cached, incoming) {
39776
- if (!cached || typeof cached !== "object" || Array.isArray(cached)) return incoming;
39777
- if (!incoming || typeof incoming !== "object" || Array.isArray(incoming)) return cached;
39778
- const cachedNodes = Array.isArray(cached.nodes) ? cached.nodes : [];
40219
+ function reconcileInlineMeshCache(cached2, incoming) {
40220
+ if (!cached2 || typeof cached2 !== "object" || Array.isArray(cached2)) return incoming;
40221
+ if (!incoming || typeof incoming !== "object" || Array.isArray(incoming)) return cached2;
40222
+ const cachedNodes = Array.isArray(cached2.nodes) ? cached2.nodes : [];
39779
40223
  const incomingNodes = Array.isArray(incoming.nodes) ? incoming.nodes : [];
39780
- if (!cachedNodes.length || !incomingNodes.length) return { ...cached, ...incoming };
39781
- const cachedUpdatedAt = Date.parse(readStringValue(cached.updatedAt, cached.updated_at) || "");
40224
+ if (!cachedNodes.length || !incomingNodes.length) return { ...cached2, ...incoming };
40225
+ const cachedUpdatedAt = Date.parse(readStringValue(cached2.updatedAt, cached2.updated_at) || "");
39782
40226
  const incomingUpdatedAt = Date.parse(readStringValue(incoming.updatedAt, incoming.updated_at) || "");
39783
40227
  const preserveCachedMembership = Number.isFinite(cachedUpdatedAt) && (!Number.isFinite(incomingUpdatedAt) || cachedUpdatedAt > incomingUpdatedAt);
39784
40228
  const cachedById = /* @__PURE__ */ new Map();
@@ -39807,7 +40251,7 @@ function reconcileInlineMeshCache(cached, incoming) {
39807
40251
  }
39808
40252
  }
39809
40253
  return {
39810
- ...cached,
40254
+ ...cached2,
39811
40255
  ...incoming,
39812
40256
  nodes
39813
40257
  };
@@ -41491,13 +41935,13 @@ var DaemonCommandRouter = class {
41491
41935
  };
41492
41936
  }
41493
41937
  getCachedAggregateMeshStatus(meshId, mesh, options) {
41494
- const cached = this.aggregateMeshStatusCache.get(meshId);
41495
- if (!cached?.snapshot || cached.snapshot.success !== true || !Array.isArray(cached.snapshot.nodes)) return null;
41496
- if (cached.queueRevision !== getMeshQueueRevision(meshId)) return null;
41497
- let snapshot = this.cloneJsonValue(cached.snapshot);
41938
+ const cached2 = this.aggregateMeshStatusCache.get(meshId);
41939
+ if (!cached2?.snapshot || cached2.snapshot.success !== true || !Array.isArray(cached2.snapshot.nodes)) return null;
41940
+ if (cached2.queueRevision !== getMeshQueueRevision(meshId)) return null;
41941
+ let snapshot = this.cloneJsonValue(cached2.snapshot);
41498
41942
  snapshot = this.hydrateCachedAggregateMeshStatusFromInline(snapshot, mesh, options);
41499
41943
  if (shouldRefreshStalePendingAggregate(snapshot, options)) return null;
41500
- const ageMs = Math.max(0, Date.now() - cached.builtAt);
41944
+ const ageMs = Math.max(0, Date.now() - cached2.builtAt);
41501
41945
  const sourceOfTruth = snapshot.sourceOfTruth && typeof snapshot.sourceOfTruth === "object" ? snapshot.sourceOfTruth : {};
41502
41946
  snapshot.sourceOfTruth = {
41503
41947
  ...sourceOfTruth,
@@ -41508,7 +41952,7 @@ var DaemonCommandRouter = class {
41508
41952
  source: "memory",
41509
41953
  refreshReason: "memory_cache_hit",
41510
41954
  ageMs,
41511
- cachedAt: new Date(cached.builtAt).toISOString(),
41955
+ cachedAt: new Date(cached2.builtAt).toISOString(),
41512
41956
  returnedAt: (/* @__PURE__ */ new Date()).toISOString()
41513
41957
  }
41514
41958
  };
@@ -41552,9 +41996,9 @@ var DaemonCommandRouter = class {
41552
41996
  warmInlineMeshCache(meshId, inlineMesh) {
41553
41997
  if (!inlineMesh || typeof inlineMesh !== "object") return void 0;
41554
41998
  const sanitizedInlineMesh = sanitizeInlineMesh(inlineMesh);
41555
- const cached = this.inlineMeshCache.get(meshId);
41556
- if (cached) {
41557
- const merged = reconcileInlineMeshCache(cached, sanitizedInlineMesh);
41999
+ const cached2 = this.inlineMeshCache.get(meshId);
42000
+ if (cached2) {
42001
+ const merged = reconcileInlineMeshCache(cached2, sanitizedInlineMesh);
41558
42002
  this.inlineMeshCache.set(meshId, merged);
41559
42003
  return merged;
41560
42004
  }
@@ -41564,14 +42008,14 @@ var DaemonCommandRouter = class {
41564
42008
  async getMeshForCommand(meshId, inlineMesh, options) {
41565
42009
  const preferInline = options?.preferInline === true;
41566
42010
  if (preferInline) {
41567
- const cached2 = this.getCachedInlineMesh(meshId);
41568
- if (cached2) {
42011
+ const cached3 = this.getCachedInlineMesh(meshId);
42012
+ if (cached3) {
41569
42013
  if (inlineMeshCarriesTransientNodeTruth(inlineMesh)) {
41570
- const merged = reconcileInlineMeshCache(cached2, inlineMesh);
42014
+ const merged = reconcileInlineMeshCache(cached3, inlineMesh);
41571
42015
  this.inlineMeshCache.set(meshId, sanitizeInlineMesh(merged));
41572
42016
  return { mesh: merged, inline: true, source: "inline_cache" };
41573
42017
  }
41574
- return { mesh: cached2, inline: true, source: "inline_cache" };
42018
+ return { mesh: cached3, inline: true, source: "inline_cache" };
41575
42019
  }
41576
42020
  if (inlineMeshCarriesTransientNodeTruth(inlineMesh)) {
41577
42021
  this.warmInlineMeshCache(meshId, inlineMesh);
@@ -41584,8 +42028,8 @@ var DaemonCommandRouter = class {
41584
42028
  if (mesh) return { mesh, inline: false, source: "local_config" };
41585
42029
  } catch {
41586
42030
  }
41587
- const cached = this.getCachedInlineMesh(meshId);
41588
- if (cached) return { mesh: cached, inline: true, source: "inline_cache" };
42031
+ const cached2 = this.getCachedInlineMesh(meshId);
42032
+ if (cached2) return { mesh: cached2, inline: true, source: "inline_cache" };
41589
42033
  const warmedInline = this.warmInlineMeshCache(meshId, inlineMesh);
41590
42034
  return warmedInline ? { mesh: warmedInline, inline: true, source: "inline_bootstrap" } : null;
41591
42035
  }
@@ -41892,6 +42336,8 @@ var DaemonCommandRouter = class {
41892
42336
  const skippedSessionIds = [];
41893
42337
  const skippedLiveSessionIds = [];
41894
42338
  const skippedCoordinatorSessionIds = [];
42339
+ const skippedLiveSessionReasons = [];
42340
+ const actedLiveDelegateSessionIds = [];
41895
42341
  const deleteUnsupportedSessionIds = [];
41896
42342
  const recordsRemainSessionIds = [];
41897
42343
  const errors = [];
@@ -41925,16 +42371,31 @@ var DaemonCommandRouter = class {
41925
42371
  const surfaceKind = getSessionHostSurfaceKind(record);
41926
42372
  const liveRuntime = surfaceKind === "live_runtime";
41927
42373
  const coordinatorSession = readStringValue(record?.meta?.meshCoordinatorFor) === args.meshId;
42374
+ const recordNodeId = readStringValue(record?.meta?.meshNodeId);
42375
+ const recordMeshNodeFor = readStringValue(record?.meta?.meshNodeFor);
42376
+ const delegateBoundToThisNode = !!recordNodeId && recordNodeId === args.nodeId && (!recordMeshNodeFor || recordMeshNodeFor === args.meshId);
41928
42377
  if (!hasExplicitSessionIds && coordinatorSession) {
41929
42378
  skippedSessionIds.push(sessionId);
41930
42379
  skippedCoordinatorSessionIds.push(sessionId);
41931
42380
  continue;
41932
42381
  }
41933
- if (!hasExplicitSessionIds && liveRuntime) {
42382
+ if (!hasExplicitSessionIds && liveRuntime && !delegateBoundToThisNode) {
41934
42383
  skippedSessionIds.push(sessionId);
41935
42384
  skippedLiveSessionIds.push(sessionId);
42385
+ const matchedByWorkspaceOnly = !recordNodeId;
42386
+ const reason = recordNodeId && recordNodeId !== args.nodeId ? `live_delegate_bound_to_other_node:${recordNodeId}` : matchedByWorkspaceOnly ? "live_session_matched_by_workspace_only_no_node_binding" : "live_session_not_bound_to_this_node";
42387
+ skippedLiveSessionReasons.push({ sessionId, reason });
41936
42388
  continue;
41937
42389
  }
42390
+ if (!hasExplicitSessionIds && liveRuntime && delegateBoundToThisNode && args.mode === "delete_stopped") {
42391
+ skippedSessionIds.push(sessionId);
42392
+ skippedLiveSessionIds.push(sessionId);
42393
+ skippedLiveSessionReasons.push({ sessionId, reason: "live_delegate_preserved_by_delete_stopped_mode_use_stop_or_stop_and_delete" });
42394
+ continue;
42395
+ }
42396
+ if (!hasExplicitSessionIds && liveRuntime && delegateBoundToThisNode) {
42397
+ actedLiveDelegateSessionIds.push(sessionId);
42398
+ }
41938
42399
  try {
41939
42400
  if (args.mode === "stop") {
41940
42401
  if (!completed) {
@@ -41996,6 +42457,8 @@ var DaemonCommandRouter = class {
41996
42457
  skippedSessionIds,
41997
42458
  skippedLiveSessionIds,
41998
42459
  skippedCoordinatorSessionIds,
42460
+ ...actedLiveDelegateSessionIds.length ? { actedLiveDelegateSessionIds } : {},
42461
+ ...skippedLiveSessionReasons.length ? { skippedLiveSessionReasons } : {},
41999
42462
  ...deleteUnsupported ? {
42000
42463
  deleteUnsupported: true,
42001
42464
  effectiveCleanup: args.mode === "stop_and_delete" ? "stopped_only_records_remain" : "delete_unsupported_records_remain",
@@ -42762,10 +43225,31 @@ ${hintLines.join("\n")}` : "",
42762
43225
  };
42763
43226
  }
42764
43227
  const cleanupStarted = Date.now();
43228
+ const refineSessionCleanupMode = this.normalizeMeshSessionCleanupMode(
43229
+ mesh?.policy?.sessionCleanupOnNodeRemove
43230
+ );
43231
+ let refineSessionIds;
43232
+ if (refineSessionCleanupMode !== "preserve" && this.deps.sessionHostControl) {
43233
+ try {
43234
+ const liveSessions = await this.deps.sessionHostControl.listSessions();
43235
+ const workspace = typeof node.workspace === "string" ? node.workspace : "";
43236
+ refineSessionIds = liveSessions.filter((record) => {
43237
+ const sid = typeof record?.sessionId === "string" ? record.sessionId : "";
43238
+ if (!sid) return false;
43239
+ if (readStringValue(record?.meta?.meshCoordinatorFor) === meshId) return false;
43240
+ const boundToNode = readStringValue(record?.meta?.meshNodeId) === nodeId;
43241
+ const matchedByWorkspace = !!workspace && record?.workspace === workspace;
43242
+ return boundToNode || matchedByWorkspace;
43243
+ }).map((record) => String(record.sessionId));
43244
+ } catch {
43245
+ refineSessionIds = void 0;
43246
+ }
43247
+ }
42765
43248
  const removeResult = await this.execute("remove_mesh_node", {
42766
43249
  meshId,
42767
43250
  nodeId,
42768
- sessionCleanupMode: "preserve",
43251
+ sessionCleanupMode: refineSessionCleanupMode,
43252
+ ...refineSessionIds && refineSessionIds.length > 0 ? { sessionIds: refineSessionIds } : {},
42769
43253
  inlineMesh: args?.inlineMesh
42770
43254
  });
42771
43255
  recordMeshRefineStage(refineStages, "cleanup", removeResult?.success === false ? "failed" : "passed", cleanupStarted, {
@@ -42923,8 +43407,8 @@ ${hintLines.join("\n")}` : "",
42923
43407
  const repoRootBaseRef = /* @__PURE__ */ new Map();
42924
43408
  const submodulePathsByRepoRoot = /* @__PURE__ */ new Map();
42925
43409
  const resolveBaseRef = async (repoRoot) => {
42926
- const cached = repoRootBaseRef.get(repoRoot);
42927
- if (cached) return cached;
43410
+ const cached2 = repoRootBaseRef.get(repoRoot);
43411
+ if (cached2) return cached2;
42928
43412
  let baseBranch = "main";
42929
43413
  try {
42930
43414
  const { stdout } = await execFileAsync4("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
@@ -43822,7 +44306,7 @@ ${hintLines.join("\n")}` : "",
43822
44306
  version: this.deps.statusVersion || "unknown",
43823
44307
  profile: "metadata"
43824
44308
  });
43825
- return { success: true, status: snapshot };
44309
+ return { success: true, status: snapshot, daemonBuild: getDaemonBuildInfo() };
43826
44310
  }
43827
44311
  case "get_machine_runtime_stats": {
43828
44312
  return {
@@ -44792,6 +45276,7 @@ ${hintLines.join("\n")}` : "",
44792
45276
  let workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
44793
45277
  let submoduleIgnorePaths = Array.isArray(args?.submoduleIgnorePaths) ? args.submoduleIgnorePaths.filter((value) => typeof value === "string") : void 0;
44794
45278
  let nodeDaemonId;
45279
+ let allowAutoPublishSubmoduleMainCommits = false;
44795
45280
  if (meshId && nodeId) {
44796
45281
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
44797
45282
  const mesh = meshRecord?.mesh;
@@ -44802,6 +45287,7 @@ ${hintLines.join("\n")}` : "",
44802
45287
  if (!submoduleIgnorePaths && Array.isArray(node?.policy?.submoduleIgnorePaths)) {
44803
45288
  submoduleIgnorePaths = node.policy.submoduleIgnorePaths.filter((value) => typeof value === "string");
44804
45289
  }
45290
+ allowAutoPublishSubmoduleMainCommits = mesh?.policy?.allowAutoPublishSubmoduleMainCommits === true;
44805
45291
  nodeDaemonId = typeof node?.daemonId === "string" ? node.daemonId.trim() : void 0;
44806
45292
  }
44807
45293
  const selfDaemonId = this.deps.statusInstanceId;
@@ -44822,7 +45308,10 @@ ${hintLines.join("\n")}` : "",
44822
45308
  execute: args?.execute === true,
44823
45309
  dryRun: args?.dryRun === true,
44824
45310
  updateSubmodules: args?.updateSubmodules === true,
44825
- submoduleIgnorePaths
45311
+ submoduleIgnorePaths,
45312
+ mode: args?.mode === "push" ? "push" : "merge",
45313
+ pushSubmodules: args?.pushSubmodules === true,
45314
+ allowAutoPublishSubmoduleMainCommits
44826
45315
  });
44827
45316
  return result;
44828
45317
  }
@@ -44830,6 +45319,23 @@ ${hintLines.join("\n")}` : "",
44830
45319
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
44831
45320
  const nodeId = typeof args?.nodeId === "string" ? args.nodeId.trim() : "";
44832
45321
  if (!meshId || !nodeId) return { success: false, error: "meshId and nodeId required" };
45322
+ const isDryRun = args?.dryRun !== false && args?.execute !== true;
45323
+ if (isDryRun) {
45324
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
45325
+ const mesh = meshRecord?.mesh;
45326
+ const node = mesh?.nodes?.find((n) => n.id === nodeId || n.nodeId === nodeId);
45327
+ if (!node?.workspace) return { success: false, error: `Node '${nodeId}' workspace not found` };
45328
+ return {
45329
+ success: true,
45330
+ dryRun: true,
45331
+ nodeId,
45332
+ workspace: node.workspace,
45333
+ validationPlan: buildMeshRefineValidationPlan(mesh, node.workspace),
45334
+ mergeWillRun: false,
45335
+ cleanupWillRun: false,
45336
+ hint: "Dry-run only \u2014 no merge/push/cleanup performed. Re-invoke with execute:true to converge this node."
45337
+ };
45338
+ }
44833
45339
  return this.startMeshRefineJob(meshId, nodeId, args);
44834
45340
  }
44835
45341
  case "batch_refine_mesh_nodes": {
@@ -44851,9 +45357,17 @@ ${hintLines.join("\n")}` : "",
44851
45357
  const sessionCleanupMode = this.normalizeMeshSessionCleanupMode(
44852
45358
  args?.sessionCleanupMode ?? args?.session_cleanup_mode ?? mesh?.policy?.sessionCleanupOnNodeRemove
44853
45359
  );
45360
+ const explicitSessionIds = Array.isArray(args?.sessionIds) ? args.sessionIds.filter((v) => typeof v === "string" && v.trim().length > 0).map((v) => v.trim()) : void 0;
44854
45361
  let sessionCleanup;
44855
45362
  if (node && sessionCleanupMode !== "preserve") {
44856
- sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode, source: "mesh_remove_node" });
45363
+ sessionCleanup = await this.cleanupMeshSessions({
45364
+ meshId,
45365
+ nodeId,
45366
+ node,
45367
+ mode: sessionCleanupMode,
45368
+ ...explicitSessionIds && explicitSessionIds.length > 0 ? { sessionIds: explicitSessionIds } : {},
45369
+ source: "mesh_remove_node"
45370
+ });
44857
45371
  if (sessionCleanup.success === false) return { success: false, removed: false, sessionCleanup };
44858
45372
  }
44859
45373
  let worktreeCleanup;
@@ -46522,6 +47036,7 @@ var DaemonStatusReporter = class {
46522
47036
  };
46523
47037
 
46524
47038
  // src/index.ts
47039
+ init_build_info();
46525
47040
  init_logger();
46526
47041
  init_debug_config();
46527
47042
 
@@ -54799,6 +55314,7 @@ export {
54799
55314
  MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
54800
55315
  NodePtyTransportFactory,
54801
55316
  P2pRelayFailureError,
55317
+ PRUNABLE_ORPHAN_STALE_REASONS,
54802
55318
  ProviderCliAdapter,
54803
55319
  ProviderInstanceManager,
54804
55320
  ProviderLoader,
@@ -54852,6 +55368,7 @@ export {
54852
55368
  classifyChatMessageVisibility,
54853
55369
  classifyHotChatSessionsForSubscriptionFlush,
54854
55370
  classifyP2pRelayFailure,
55371
+ classifyStaleDirectForPrune,
54855
55372
  cleanupTerminalDirectDispatches,
54856
55373
  clearDebugTrace,
54857
55374
  clearPendingMeshCoordinatorEvents,
@@ -54871,6 +55388,7 @@ export {
54871
55388
  createNativeHistoryDispatcher,
54872
55389
  createSessionDelivery,
54873
55390
  createWorktree,
55391
+ deleteDirectDispatchesByTaskId,
54874
55392
  deleteMesh,
54875
55393
  deriveMeshReviewInboxItems,
54876
55394
  describeTaskDependencyState,
@@ -54899,6 +55417,7 @@ export {
54899
55417
  getAvailableIdeIds,
54900
55418
  getCoordinatorForSession,
54901
55419
  getCurrentDaemonLogPath,
55420
+ getDaemonBuildInfo,
54902
55421
  getDaemonDataDir,
54903
55422
  getDaemonLogDir,
54904
55423
  getDebugRuntimeConfig,