@adhdev/daemon-core 0.9.82-rc.261 → 0.9.82-rc.263

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 ? "2cf602f99ce68b3d3f1af02b429f6fb7a7c8e686" : void 0) ?? "unknown";
264
+ const commitShort = readInjected(true ? "2cf602f" : void 0) ?? (commit !== "unknown" ? commit.slice(0, 7) : "unknown");
265
+ const version = readInjected(true ? "0.9.82-rc.263" : void 0) ?? readInjected(typeof process !== "undefined" ? process.env?.ADHDEV_PKG_VERSION : void 0) ?? "unknown";
266
+ const builtAt = readInjected(true ? "2026-06-14T13:48:10.699Z" : 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,35 @@ async function getGitRepoStatus(workspace, options = {}) {
309
334
  );
310
335
  }
311
336
  }
337
+ async function detectDaemonBuildBehind(repo, submodules, options) {
338
+ const build = options.daemonBuildInfo ?? getDaemonBuildInfo();
339
+ if (!build.commit || build.commit === "unknown") return void 0;
340
+ const scopes = [
341
+ { scope: "root", repoPath: repo.repoRoot || repo.workspace }
342
+ ];
343
+ for (const sub of submodules || []) {
344
+ if (sub.repoPath && !sub.error) scopes.push({ scope: sub.path, repoPath: sub.repoPath });
345
+ }
346
+ for (const { scope, repoPath } of scopes) {
347
+ try {
348
+ await runGit(repoPath, ["cat-file", "-e", `${build.commit}^{commit}`], options);
349
+ const headResult = await runGit(repoPath, ["rev-parse", "HEAD"], options);
350
+ const head = headResult.stdout.trim();
351
+ if (!head || head === build.commit) continue;
352
+ await runGit(repoPath, ["merge-base", "--is-ancestor", build.commit, "HEAD"], options);
353
+ return {
354
+ buildCommit: build.commit,
355
+ buildCommitShort: build.commitShort,
356
+ head,
357
+ scope,
358
+ warning: `Live daemon was built from ${build.commitShort} which is behind ${scope === "root" ? "workspace" : scope} 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.`
359
+ };
360
+ } catch {
361
+ continue;
362
+ }
363
+ }
364
+ return void 0;
365
+ }
312
366
  async function readPorcelainStatus(repo, options) {
313
367
  const statusOutput = await runGit(repo, ["status", "--porcelain=v2", "--branch"], options);
314
368
  return parsePorcelainV2Status(statusOutput.stdout);
@@ -525,6 +579,7 @@ var init_git_status = __esm({
525
579
  "src/git/git-status.ts"() {
526
580
  "use strict";
527
581
  init_git_executor();
582
+ init_build_info();
528
583
  }
529
584
  });
530
585
 
@@ -2385,8 +2440,8 @@ function readLedgerFromStore(meshId) {
2385
2440
  }
2386
2441
  function getCachedRawEntries(meshId) {
2387
2442
  const now = Date.now();
2388
- const cached = ledgerReadCache.get(meshId);
2389
- if (cached && now - cached.cachedAt < LEDGER_CACHE_TTL_MS) return cached.entries;
2443
+ const cached2 = ledgerReadCache.get(meshId);
2444
+ if (cached2 && now - cached2.cachedAt < LEDGER_CACHE_TTL_MS) return cached2.entries;
2390
2445
  let entries;
2391
2446
  try {
2392
2447
  entries = readLedgerFromStore(meshId);
@@ -2645,6 +2700,7 @@ __export(mesh_work_queue_exports, {
2645
2700
  cancelTask: () => cancelTask,
2646
2701
  claimNextTask: () => claimNextTask,
2647
2702
  cleanupTerminalDirectDispatches: () => cleanupTerminalDirectDispatches,
2703
+ deleteDirectDispatchesByTaskId: () => deleteDirectDispatchesByTaskId,
2648
2704
  describeTaskDependencyState: () => describeTaskDependencyState,
2649
2705
  enqueueTask: () => enqueueTask,
2650
2706
  getActiveDirectDispatches: () => getActiveDirectDispatches,
@@ -3054,6 +3110,13 @@ function markStaleDirectDispatches(meshId, olderThanMs = 60 * 6e4) {
3054
3110
  } catch {
3055
3111
  }
3056
3112
  }
3113
+ function deleteDirectDispatchesByTaskId(meshId, taskIds) {
3114
+ try {
3115
+ return MeshRuntimeStore.getInstance().deleteDirectDispatchesByTaskId(meshId, taskIds);
3116
+ } catch {
3117
+ return 0;
3118
+ }
3119
+ }
3057
3120
  function recordMeshToolCall(opts) {
3058
3121
  try {
3059
3122
  return MeshRuntimeStore.getInstance().recordMeshToolCall(opts);
@@ -3685,6 +3748,24 @@ var init_mesh_runtime_store = __esm({
3685
3748
  deleteDirectDispatches(meshId) {
3686
3749
  this.db.prepare(`DELETE FROM mesh_direct_dispatches WHERE mesh_id = ?`).run(meshId);
3687
3750
  }
3751
+ /**
3752
+ * Delete specific direct dispatch rows by taskId for a mesh. Used by the staleDirect prune
3753
+ * path to remove orphaned/terminal dispatch records whose node/session is no longer in the
3754
+ * live mesh. Returns the number of rows actually deleted. No-op for an empty taskId list.
3755
+ */
3756
+ deleteDirectDispatchesByTaskId(meshId, taskIds) {
3757
+ const ids = (taskIds || []).map((id) => typeof id === "string" ? id.trim() : "").filter(Boolean);
3758
+ if (!ids.length) return 0;
3759
+ const stmt = this.db.prepare(`DELETE FROM mesh_direct_dispatches WHERE mesh_id = ? AND task_id = ?`);
3760
+ let deleted = 0;
3761
+ const run = this.db.transaction((rows) => {
3762
+ for (const taskId of rows) {
3763
+ deleted += stmt.run(meshId, taskId).changes;
3764
+ }
3765
+ });
3766
+ run(ids);
3767
+ return deleted;
3768
+ }
3688
3769
  markStaleDirectDispatches(meshId, olderThanMs) {
3689
3770
  const cutoff = new Date(Date.now() - olderThanMs).toISOString();
3690
3771
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -5353,11 +5434,14 @@ async function fastForwardMeshNode(args) {
5353
5434
  const trigger = normalizeOptionalString(args.trigger) || "manual";
5354
5435
  const updateSubmodules = args.updateSubmodules === true;
5355
5436
  const dryRun = args.dryRun === true || args.execute !== true;
5356
- const plannedSteps = buildPlannedSteps(updateSubmodules);
5437
+ const mode = args.mode === "push" ? "push" : "merge";
5438
+ const pushSubmodules = mode === "push" && args.pushSubmodules === true;
5439
+ const plannedSteps = buildPlannedSteps(mode, updateSubmodules, pushSubmodules);
5357
5440
  const base = {
5358
5441
  ...nodeId ? { nodeId } : {},
5359
5442
  ...meshId ? { meshId } : {},
5360
5443
  workspace,
5444
+ mode,
5361
5445
  dryRun,
5362
5446
  updateSubmodules,
5363
5447
  plannedSteps,
@@ -5371,13 +5455,24 @@ async function fastForwardMeshNode(args) {
5371
5455
  submoduleIgnorePaths: args.submoduleIgnorePaths,
5372
5456
  timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
5373
5457
  });
5458
+ if (mode === "push") {
5459
+ return pushMeshNode(base, args, current, {
5460
+ pushSubmodules,
5461
+ allowAutoPublishSubmoduleMainCommits: args.allowAutoPublishSubmoduleMainCommits === true
5462
+ });
5463
+ }
5374
5464
  const earlyBlockers = collectPreflightBlockers(current, requestedBranch);
5375
5465
  if (earlyBlockers.length > 0) {
5466
+ const blockCode = chooseBlockCode(current, earlyBlockers);
5376
5467
  const result2 = {
5377
- ...block(base, chooseBlockCode(current, earlyBlockers), earlyBlockers),
5468
+ ...block(base, blockCode, earlyBlockers),
5378
5469
  current,
5379
- finalBranchConvergenceState: buildConvergenceState(current, codeToConvergenceStatus(chooseBlockCode(current, earlyBlockers)))
5470
+ finalBranchConvergenceState: buildConvergenceState(current, codeToConvergenceStatus(blockCode))
5380
5471
  };
5472
+ if (blockCode === "branch_ahead" && current.ahead > 0 && current.behind === 0 && otherBlockersAreOnlyAhead(earlyBlockers)) {
5473
+ result2.code = "ahead_needs_push";
5474
+ 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.';
5475
+ }
5381
5476
  await appendFastForwardLedger(result2, "blocked");
5382
5477
  return result2;
5383
5478
  }
@@ -5488,7 +5583,246 @@ async function fastForwardMeshNode(args) {
5488
5583
  await appendFastForwardLedger(result, success ? "executed" : "failed");
5489
5584
  return result;
5490
5585
  }
5491
- function buildPlannedSteps(updateSubmodules) {
5586
+ async function pushMeshNode(base, args, current, options) {
5587
+ const workspace = base.workspace;
5588
+ const requestedBranch = normalizeOptionalString(args.branch);
5589
+ const dryRun = base.dryRun;
5590
+ const blockers = collectPushPreflightBlockers(current, requestedBranch);
5591
+ if (blockers.length > 0) {
5592
+ const code2 = choosePushBlockCode(current, blockers);
5593
+ const result2 = {
5594
+ ...block(base, code2, blockers),
5595
+ current,
5596
+ preStatus: current,
5597
+ finalBranchConvergenceState: buildConvergenceState(current, codeToConvergenceStatus(code2))
5598
+ };
5599
+ await appendFastForwardLedger(result2, "blocked");
5600
+ return result2;
5601
+ }
5602
+ const target = parseUpstreamTarget(current.upstream || "");
5603
+ if (!target) {
5604
+ const result2 = {
5605
+ ...block(base, "upstream_unparseable", ["upstream_unparseable"]),
5606
+ current,
5607
+ preStatus: current,
5608
+ finalBranchConvergenceState: buildConvergenceState(current, "blocked")
5609
+ };
5610
+ await appendFastForwardLedger(result2, "blocked");
5611
+ return result2;
5612
+ }
5613
+ const refspec = `HEAD:refs/heads/${target.remoteBranch}`;
5614
+ const pushTarget = { remote: target.remote, remoteBranch: target.remoteBranch, refspec };
5615
+ if (current.ahead <= 0) {
5616
+ const result2 = {
5617
+ ...base,
5618
+ success: true,
5619
+ code: "nothing_to_push",
5620
+ allowed: true,
5621
+ willRun: false,
5622
+ executed: false,
5623
+ blockingReasons: [],
5624
+ current,
5625
+ preStatus: current,
5626
+ postStatus: current,
5627
+ pushTarget,
5628
+ finalBranchConvergenceState: buildConvergenceState(current, "up_to_date")
5629
+ };
5630
+ await appendFastForwardLedger(result2, "noop");
5631
+ return result2;
5632
+ }
5633
+ const descendant = await verifyUpstreamIsAncestorOfHead(workspace, current.upstream || "", args.timeoutMs);
5634
+ if (!descendant.ok) {
5635
+ const result2 = {
5636
+ ...block(base, "non_fast_forward_push", ["head_is_not_descendant_of_upstream"]),
5637
+ current,
5638
+ preStatus: current,
5639
+ pushTarget,
5640
+ operationError: descendant.error,
5641
+ 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.",
5642
+ finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
5643
+ };
5644
+ await appendFastForwardLedger(result2, "blocked");
5645
+ return result2;
5646
+ }
5647
+ if (dryRun) {
5648
+ const result2 = {
5649
+ ...base,
5650
+ success: true,
5651
+ code: "push_available",
5652
+ allowed: true,
5653
+ willRun: false,
5654
+ executed: false,
5655
+ blockingReasons: [],
5656
+ current,
5657
+ preStatus: current,
5658
+ pushTarget,
5659
+ ...options.pushSubmodules ? { submodulePushes: await planSubmodulePushes(current, options, args.timeoutMs) } : {},
5660
+ finalBranchConvergenceState: buildConvergenceState(current, "push_available")
5661
+ };
5662
+ await appendFastForwardLedger(result2, "dry_run");
5663
+ return result2;
5664
+ }
5665
+ try {
5666
+ await runGit(workspace, ["push", target.remote, refspec], { timeoutMs: args.timeoutMs ?? 3e4 });
5667
+ } catch (error) {
5668
+ const result2 = {
5669
+ ...block(base, "push_ff_only_failed", ["push_ff_only_failed"]),
5670
+ current,
5671
+ preStatus: current,
5672
+ pushTarget,
5673
+ operationError: formatGitError2(error),
5674
+ finalBranchConvergenceState: buildConvergenceState(current, "not_mergeable")
5675
+ };
5676
+ await appendFastForwardLedger(result2, "failed");
5677
+ return result2;
5678
+ }
5679
+ let submodulePushes;
5680
+ if (options.pushSubmodules) {
5681
+ submodulePushes = await executeSubmodulePushes(current, options, args.timeoutMs);
5682
+ }
5683
+ const postStatus = await getGitRepoStatus(workspace, {
5684
+ ...STATUS_OPTIONS,
5685
+ submoduleIgnorePaths: args.submoduleIgnorePaths,
5686
+ timeoutMs: args.timeoutMs ?? STATUS_OPTIONS.timeoutMs
5687
+ });
5688
+ const submodulePushFailed = (submodulePushes || []).some((entry) => !entry.pushed && !entry.skipped);
5689
+ const blockingReasons = [];
5690
+ if (postStatus.ahead !== 0) blockingReasons.push("post_branch_ahead");
5691
+ if (submodulePushFailed) blockingReasons.push("submodule_push_failed");
5692
+ const success = blockingReasons.length === 0;
5693
+ const code = success ? "push_applied" : submodulePushFailed && postStatus.ahead === 0 ? "push_applied_submodule_push_failed" : "post_push_verify_failed";
5694
+ const result = {
5695
+ ...base,
5696
+ success,
5697
+ code,
5698
+ allowed: true,
5699
+ willRun: true,
5700
+ executed: true,
5701
+ blockingReasons,
5702
+ current,
5703
+ preStatus: current,
5704
+ postStatus,
5705
+ pushTarget,
5706
+ ...submodulePushes ? { submodulePushes } : {},
5707
+ finalBranchConvergenceState: buildConvergenceState(postStatus, success ? "pushed" : "post_verify_failed")
5708
+ };
5709
+ await appendFastForwardLedger(result, success ? "executed" : "failed");
5710
+ return result;
5711
+ }
5712
+ function collectPushPreflightBlockers(status, requestedBranch) {
5713
+ const blockers = [];
5714
+ if (!status.isGitRepo) blockers.push("not_git_repo");
5715
+ if (!status.branch) blockers.push("detached_head_or_unknown_branch");
5716
+ if (requestedBranch && status.branch !== requestedBranch) blockers.push("branch_mismatch");
5717
+ if (!status.upstream) blockers.push("upstream_missing");
5718
+ if (status.upstreamStatus !== "fresh") blockers.push("upstream_not_fresh");
5719
+ if (status.hasConflicts) blockers.push("conflicts_present");
5720
+ if (status.staged > 0) blockers.push("staged_changes_present");
5721
+ if (status.modified > 0) blockers.push("modified_changes_present");
5722
+ if (status.untracked > 0) blockers.push("untracked_changes_present");
5723
+ if (status.deleted > 0) blockers.push("deleted_changes_present");
5724
+ if (status.renamed > 0) blockers.push("renamed_changes_present");
5725
+ if (status.stashCount > 0) blockers.push("stash_entries_present");
5726
+ if (status.ahead > 0 && status.behind > 0) blockers.push("branch_diverged_from_upstream");
5727
+ else if (status.behind > 0) blockers.push("branch_behind_upstream");
5728
+ return blockers;
5729
+ }
5730
+ function choosePushBlockCode(status, blockers) {
5731
+ if (blockers.includes("not_git_repo")) return "not_git_repo";
5732
+ if (blockers.includes("branch_mismatch")) return "branch_mismatch";
5733
+ if (blockers.includes("upstream_missing")) return "upstream_missing";
5734
+ if (blockers.includes("upstream_not_fresh")) return "upstream_not_fresh";
5735
+ if (blockers.includes("branch_diverged_from_upstream")) return "branch_diverged";
5736
+ if (blockers.includes("branch_behind_upstream")) return "non_fast_forward_push";
5737
+ if (blockers.some((reason) => reason.includes("changes") || reason.includes("conflicts") || reason.includes("stash"))) return "dirty_worktree";
5738
+ return "preflight_blocked";
5739
+ }
5740
+ function parseUpstreamTarget(upstream) {
5741
+ const trimmed = upstream.trim();
5742
+ const slash = trimmed.indexOf("/");
5743
+ if (slash <= 0 || slash >= trimmed.length - 1) return null;
5744
+ return { remote: trimmed.slice(0, slash), remoteBranch: trimmed.slice(slash + 1) };
5745
+ }
5746
+ async function verifyUpstreamIsAncestorOfHead(workspace, upstream, timeoutMs) {
5747
+ if (!upstream) return { ok: false, error: "missing upstream" };
5748
+ try {
5749
+ await runGit(workspace, ["merge-base", "--is-ancestor", upstream, "HEAD"], { timeoutMs: timeoutMs ?? 15e3 });
5750
+ return { ok: true };
5751
+ } catch (error) {
5752
+ return { ok: false, error: formatGitError2(error) };
5753
+ }
5754
+ }
5755
+ async function planSubmodulePushes(status, options, timeoutMs) {
5756
+ return resolveSubmodulePushes(status, options, false, timeoutMs);
5757
+ }
5758
+ async function executeSubmodulePushes(status, options, timeoutMs) {
5759
+ return resolveSubmodulePushes(status, options, true, timeoutMs);
5760
+ }
5761
+ async function resolveSubmodulePushes(status, options, execute, timeoutMs) {
5762
+ const submodules = Array.isArray(status.submodules) ? status.submodules : [];
5763
+ const results = [];
5764
+ for (const submodule of submodules) {
5765
+ const base = {
5766
+ path: submodule.path,
5767
+ commit: submodule.commit,
5768
+ remote: "origin",
5769
+ remoteBranch: "main",
5770
+ pushed: false,
5771
+ skipped: true,
5772
+ code: "submodule_push_skipped"
5773
+ };
5774
+ if (!options.allowAutoPublishSubmoduleMainCommits) {
5775
+ results.push({ ...base, code: "submodule_push_policy_disabled", error: "allowAutoPublishSubmoduleMainCommits is not enabled" });
5776
+ continue;
5777
+ }
5778
+ if (submodule.error || submodule.dirty) {
5779
+ results.push({ ...base, code: "submodule_not_clean", error: submodule.error || "submodule worktree is dirty" });
5780
+ continue;
5781
+ }
5782
+ const repoPath = submodule.repoPath;
5783
+ if (!repoPath || !submodule.commit) {
5784
+ results.push({ ...base, code: "submodule_status_incomplete" });
5785
+ continue;
5786
+ }
5787
+ try {
5788
+ await runGit(repoPath, ["-c", "protocol.file.allow=always", "fetch", "origin", "refs/heads/main:refs/remotes/origin/main"], { timeoutMs: timeoutMs ?? 3e4 });
5789
+ } catch (error) {
5790
+ results.push({ ...base, code: "submodule_fetch_failed", error: formatGitError2(error) });
5791
+ continue;
5792
+ }
5793
+ let alreadyReachable = false;
5794
+ try {
5795
+ await runGit(repoPath, ["merge-base", "--is-ancestor", submodule.commit, "refs/remotes/origin/main"], { timeoutMs: timeoutMs ?? 15e3 });
5796
+ alreadyReachable = true;
5797
+ } catch {
5798
+ }
5799
+ if (alreadyReachable) {
5800
+ results.push({ ...base, pushed: false, skipped: true, code: "submodule_already_reachable" });
5801
+ continue;
5802
+ }
5803
+ try {
5804
+ await runGit(repoPath, ["merge-base", "--is-ancestor", "refs/remotes/origin/main", submodule.commit], { timeoutMs: timeoutMs ?? 15e3 });
5805
+ } catch (error) {
5806
+ results.push({ ...base, pushed: false, skipped: false, code: "submodule_non_fast_forward", error: formatGitError2(error) });
5807
+ continue;
5808
+ }
5809
+ const refspec = `${submodule.commit}:refs/heads/main`;
5810
+ if (!execute) {
5811
+ results.push({ ...base, pushed: false, skipped: false, code: "submodule_push_available", refspec });
5812
+ continue;
5813
+ }
5814
+ try {
5815
+ await runGit(repoPath, ["push", "origin", refspec], { timeoutMs: timeoutMs ?? 3e4 });
5816
+ await runGit(repoPath, ["-c", "protocol.file.allow=always", "fetch", "origin", "refs/heads/main:refs/remotes/origin/main"], { timeoutMs: timeoutMs ?? 3e4 });
5817
+ await runGit(repoPath, ["merge-base", "--is-ancestor", submodule.commit, "refs/remotes/origin/main"], { timeoutMs: timeoutMs ?? 15e3 });
5818
+ results.push({ ...base, pushed: true, skipped: false, code: "submodule_pushed", refspec });
5819
+ } catch (error) {
5820
+ results.push({ ...base, pushed: false, skipped: false, code: "submodule_push_failed", refspec, error: formatGitError2(error) });
5821
+ }
5822
+ }
5823
+ return results;
5824
+ }
5825
+ function buildPlannedSteps(mode, updateSubmodules, pushSubmodules) {
5492
5826
  const steps = [
5493
5827
  {
5494
5828
  operation: "refresh_upstream",
@@ -5501,20 +5835,49 @@ function buildPlannedSteps(updateSubmodules) {
5501
5835
  description: "Require clean staged/modified/untracked/deleted/renamed/conflict/stash/submodule state.",
5502
5836
  safe: true,
5503
5837
  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.",
5838
+ }
5839
+ ];
5840
+ if (mode === "push") {
5841
+ steps.push({
5842
+ operation: "verify_push_descendant",
5843
+ description: "Require HEAD to be a descendant of origin/<branch> (origin/<branch> is an ancestor of HEAD); refuse any non-fast-forward push.",
5508
5844
  safe: true,
5509
5845
  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.",
5846
+ });
5847
+ steps.push({
5848
+ operation: "push_ff_only",
5849
+ 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
5850
  safe: true,
5515
- willMutateWorktree: true
5851
+ willMutateWorktree: false
5852
+ });
5853
+ if (pushSubmodules) {
5854
+ steps.push({
5855
+ operation: "push_submodules_ff_only",
5856
+ 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.",
5857
+ safe: true,
5858
+ willMutateWorktree: false
5859
+ });
5516
5860
  }
5517
- ];
5861
+ steps.push({
5862
+ operation: "verify_post_status",
5863
+ description: "Re-read daemon-owned git status and report final branch convergence state.",
5864
+ safe: true,
5865
+ willMutateWorktree: false
5866
+ });
5867
+ return steps;
5868
+ }
5869
+ steps.push({
5870
+ operation: "verify_fast_forward",
5871
+ description: "Require ahead=0, behind>0, and HEAD to be an ancestor of the upstream ref.",
5872
+ safe: true,
5873
+ willMutateWorktree: false
5874
+ });
5875
+ steps.push({
5876
+ operation: "merge_ff_only",
5877
+ description: "Apply git merge --ff-only against the tracked upstream; no force, reset, rebase, push, or deploy.",
5878
+ safe: true,
5879
+ willMutateWorktree: true
5880
+ });
5518
5881
  if (updateSubmodules) {
5519
5882
  steps.push({
5520
5883
  operation: "submodule_update",
@@ -5531,6 +5894,10 @@ function buildPlannedSteps(updateSubmodules) {
5531
5894
  });
5532
5895
  return steps;
5533
5896
  }
5897
+ function otherBlockersAreOnlyAhead(blockers) {
5898
+ const aheadOnly = /* @__PURE__ */ new Set(["branch_has_local_commits"]);
5899
+ return blockers.every((reason) => aheadOnly.has(reason));
5900
+ }
5534
5901
  function collectPreflightBlockers(status, requestedBranch) {
5535
5902
  const blockers = [];
5536
5903
  if (!status.isGitRepo) blockers.push("not_git_repo");
@@ -5587,7 +5954,7 @@ function chooseBlockCode(status, blockers) {
5587
5954
  return "preflight_blocked";
5588
5955
  }
5589
5956
  function codeToConvergenceStatus(code) {
5590
- if (code === "branch_diverged" || code === "branch_ahead" || code === "non_fast_forward") return "not_mergeable";
5957
+ if (code === "branch_diverged" || code === "branch_ahead" || code === "non_fast_forward" || code === "non_fast_forward_push" || code === "upstream_unparseable") return "not_mergeable";
5591
5958
  if (code === "dirty_worktree" || code === "submodule_not_clean") return "blocked_review";
5592
5959
  return "blocked";
5593
5960
  }
@@ -5670,6 +6037,7 @@ async function appendFastForwardLedger(result, outcome) {
5670
6037
  ...result.nodeId ? { nodeId: result.nodeId } : {},
5671
6038
  payload: {
5672
6039
  operation: "mesh_fast_forward_node",
6040
+ mode: result.mode,
5673
6041
  trigger: result.trigger || "manual",
5674
6042
  outcome,
5675
6043
  code: result.code,
@@ -5680,6 +6048,7 @@ async function appendFastForwardLedger(result, outcome) {
5680
6048
  executed: result.executed,
5681
6049
  branch: result.postStatus?.branch ?? result.current?.branch,
5682
6050
  upstream: result.postStatus?.upstream ?? result.current?.upstream,
6051
+ ...result.pushTarget ? { pushTarget: result.pushTarget } : {},
5683
6052
  before: result.current ? {
5684
6053
  headCommit: result.current.headCommit,
5685
6054
  ahead: result.current.ahead,
@@ -5690,6 +6059,16 @@ async function appendFastForwardLedger(result, outcome) {
5690
6059
  ahead: result.postStatus.ahead,
5691
6060
  behind: result.postStatus.behind
5692
6061
  } : void 0,
6062
+ ...result.submodulePushes ? {
6063
+ submodulePushes: result.submodulePushes.map((entry) => ({
6064
+ path: entry.path,
6065
+ commit: entry.commit,
6066
+ pushed: entry.pushed,
6067
+ skipped: entry.skipped,
6068
+ code: entry.code,
6069
+ ...entry.refspec ? { refspec: entry.refspec } : {}
6070
+ }))
6071
+ } : {},
5693
6072
  blockingReasons: result.blockingReasons
5694
6073
  }
5695
6074
  });
@@ -6860,8 +7239,8 @@ var init_mesh_routing = __esm({
6860
7239
  import { existsSync as existsSync14 } from "fs";
6861
7240
  function getCachedMeshByWorkspace(workspace) {
6862
7241
  const now = Date.now();
6863
- const cached = meshByWorkspaceCache.get(workspace);
6864
- if (cached && now - cached.cachedAt < MESH_WORKSPACE_CACHE_TTL_MS) return cached.mesh;
7242
+ const cached2 = meshByWorkspaceCache.get(workspace);
7243
+ if (cached2 && now - cached2.cachedAt < MESH_WORKSPACE_CACHE_TTL_MS) return cached2.mesh;
6865
7244
  const mesh = getMeshByRepo(workspace);
6866
7245
  meshByWorkspaceCache.set(workspace, { mesh, cachedAt: now });
6867
7246
  return mesh;
@@ -11754,10 +12133,10 @@ ${lastSnapshot}`;
11754
12133
  return this.accumulatedRawBuffer.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, "").replace(/\x1B[P^_X][\s\S]*?(?:\x07|\x1B\\)/g, "").replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, "");
11755
12134
  }
11756
12135
  getFreshParsedStatusCache() {
11757
- const cached = this.parsedStatusCache;
12136
+ const cached2 = this.parsedStatusCache;
11758
12137
  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;
12138
+ 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) {
12139
+ return cached2.result;
11761
12140
  }
11762
12141
  return null;
11763
12142
  }
@@ -12217,10 +12596,10 @@ ${lastSnapshot}`;
12217
12596
  getScriptParsedStatus() {
12218
12597
  const screenText = this.readTerminalScreenText();
12219
12598
  const parseScreenText = this.getParseScreenText(screenText);
12220
- const cached = this.parsedStatusCache;
12599
+ const cached2 = this.parsedStatusCache;
12221
12600
  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;
12601
+ 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) {
12602
+ return cached2.result;
12224
12603
  }
12225
12604
  const parsed = this.runParseSession();
12226
12605
  if (!parsed || !Array.isArray(parsed.messages)) {
@@ -16701,6 +17080,17 @@ function buildMeshActiveWork(opts) {
16701
17080
  }
16702
17081
  return { activeWork: records, staleDirectWork, staleDirectWorkNote, terminalDirectWork, summary };
16703
17082
  }
17083
+ var PRUNABLE_ORPHAN_STALE_REASONS = /* @__PURE__ */ new Set([
17084
+ "direct task node is no longer in the live mesh",
17085
+ "direct task session is not present in live session records",
17086
+ "direct task has no node id"
17087
+ ]);
17088
+ function classifyStaleDirectForPrune(record, opts = {}) {
17089
+ if (record.staleDispatchUnacknowledged === true) return "preserve_unacknowledged";
17090
+ if (record.terminal === true) return opts.includeTerminal ? "prunable_terminal" : "preserve_active";
17091
+ if (record.staleReason && PRUNABLE_ORPHAN_STALE_REASONS.has(record.staleReason)) return "prunable_orphan";
17092
+ return "preserve_active";
17093
+ }
16704
17094
  function buildCompactStaleDirectWorkSummary(staleDirectWork, opts = {}) {
16705
17095
  const sampleLimit = Math.max(0, Math.min(10, Math.floor(opts.sampleLimit ?? 3)));
16706
17096
  const reasonCounts = {};
@@ -19924,9 +20314,9 @@ function computeSavedHistorySessionSummaries(agentType, dir, files, fileSignatur
19924
20314
  for (const file of files.slice().sort()) {
19925
20315
  const filePath = path12.join(dir, file);
19926
20316
  const signature = fileSignatures.get(file) || `${file}:missing`;
19927
- const cached = savedHistoryFileSummaryCache.get(filePath);
20317
+ const cached2 = savedHistoryFileSummaryCache.get(filePath);
19928
20318
  const persisted = persistedEntries.get(file);
19929
- const reusableEntry = cached?.signature === signature ? cached : persisted?.signature === signature ? persisted : null;
20319
+ const reusableEntry = cached2?.signature === signature ? cached2 : persisted?.signature === signature ? persisted : null;
19930
20320
  const fileSummary = reusableEntry?.summary || computeSavedHistoryFileSummary(dir, file);
19931
20321
  const nextEntry = reusableEntry || {
19932
20322
  signature,
@@ -20378,23 +20768,23 @@ function listSavedHistorySessions(agentType, options = {}, historyBehavior) {
20378
20768
  savedHistorySessionCache.delete(sanitized);
20379
20769
  return { sessions: [], hasMore: false };
20380
20770
  }
20381
- const cached = savedHistorySessionCache.get(sanitized);
20771
+ const cached2 = savedHistorySessionCache.get(sanitized);
20382
20772
  const offset = Math.max(0, options.offset || 0);
20383
20773
  const limit = Math.max(1, options.limit || 30);
20384
20774
  const indexSignature = buildSavedHistoryIndexFileSignature(dir);
20385
20775
  let cacheWasInvalidated = false;
20386
- if (cached) {
20387
- const cacheLooksPersisted = cached.signature.startsWith("index:");
20388
- const cacheStillValid = cacheLooksPersisted ? cached.signature === indexSignature : (() => {
20776
+ if (cached2) {
20777
+ const cacheLooksPersisted = cached2.signature.startsWith("index:");
20778
+ const cacheStillValid = cacheLooksPersisted ? cached2.signature === indexSignature : (() => {
20389
20779
  const files2 = listHistoryFiles(dir);
20390
20780
  const fileSignatures2 = buildSavedHistoryFileSignatureMap(dir, files2);
20391
- return cached.signature === buildSavedHistoryCacheSignature(files2, fileSignatures2);
20781
+ return cached2.signature === buildSavedHistoryCacheSignature(files2, fileSignatures2);
20392
20782
  })();
20393
20783
  if (cacheStillValid) {
20394
- const sliced2 = cached.summaries.slice(offset, offset + limit);
20784
+ const sliced2 = cached2.summaries.slice(offset, offset + limit);
20395
20785
  return {
20396
20786
  sessions: sliced2,
20397
- hasMore: cached.summaries.length > offset + limit
20787
+ hasMore: cached2.summaries.length > offset + limit
20398
20788
  };
20399
20789
  }
20400
20790
  cacheWasInvalidated = true;
@@ -36988,8 +37378,8 @@ var ProviderLoader = class _ProviderLoader {
36988
37378
  return null;
36989
37379
  }
36990
37380
  registerProviderScriptRootSafely(path30.dirname(path30.dirname(providerDir)));
36991
- const cached = this.scriptsCache.get(dir);
36992
- if (cached) return cached;
37381
+ const cached2 = this.scriptsCache.get(dir);
37382
+ if (cached2) return cached2;
36993
37383
  const scriptsJs = path30.join(dir, "scripts.js");
36994
37384
  if (fs20.existsSync(scriptsJs)) {
36995
37385
  try {
@@ -38987,6 +39377,9 @@ function buildStatusSnapshot(options) {
38987
39377
  };
38988
39378
  }
38989
39379
 
39380
+ // src/commands/router.ts
39381
+ init_build_info();
39382
+
38990
39383
  // src/commands/upgrade-helper.ts
38991
39384
  import { execFileSync as execFileSync4 } from "child_process";
38992
39385
  import { spawn as spawn3 } from "child_process";
@@ -39772,13 +40165,13 @@ function sanitizeInlineMesh(inlineMesh) {
39772
40165
  nodes
39773
40166
  };
39774
40167
  }
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 : [];
40168
+ function reconcileInlineMeshCache(cached2, incoming) {
40169
+ if (!cached2 || typeof cached2 !== "object" || Array.isArray(cached2)) return incoming;
40170
+ if (!incoming || typeof incoming !== "object" || Array.isArray(incoming)) return cached2;
40171
+ const cachedNodes = Array.isArray(cached2.nodes) ? cached2.nodes : [];
39779
40172
  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) || "");
40173
+ if (!cachedNodes.length || !incomingNodes.length) return { ...cached2, ...incoming };
40174
+ const cachedUpdatedAt = Date.parse(readStringValue(cached2.updatedAt, cached2.updated_at) || "");
39782
40175
  const incomingUpdatedAt = Date.parse(readStringValue(incoming.updatedAt, incoming.updated_at) || "");
39783
40176
  const preserveCachedMembership = Number.isFinite(cachedUpdatedAt) && (!Number.isFinite(incomingUpdatedAt) || cachedUpdatedAt > incomingUpdatedAt);
39784
40177
  const cachedById = /* @__PURE__ */ new Map();
@@ -39807,7 +40200,7 @@ function reconcileInlineMeshCache(cached, incoming) {
39807
40200
  }
39808
40201
  }
39809
40202
  return {
39810
- ...cached,
40203
+ ...cached2,
39811
40204
  ...incoming,
39812
40205
  nodes
39813
40206
  };
@@ -40418,8 +40811,37 @@ async function runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead)
40418
40811
  maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
40419
40812
  });
40420
40813
  const mergeBase = git(["merge-base", baseHead, branchHead]).trim();
40421
- const mergeTreeStdout = git(["merge-tree", "--write-tree", baseHead, branchHead]);
40422
- const mergedTree = mergeTreeStdout.trim().split(/\s+/)[0] || "";
40814
+ let mergedTree = "";
40815
+ let mergeTreeStdout = "";
40816
+ let gitlinkTrivialFastForward;
40817
+ try {
40818
+ mergeTreeStdout = git(["merge-tree", "--write-tree", baseHead, branchHead]);
40819
+ mergedTree = mergeTreeStdout.trim().split(/\s+/)[0] || "";
40820
+ } catch (mergeTreeErr) {
40821
+ const output = `${mergeTreeErr?.message || ""}
40822
+ ${mergeTreeErr?.stdout || ""}
40823
+ ${mergeTreeErr?.stderr || ""}`;
40824
+ const isSubmoduleConflict = /(submodule|160000)/i.test(output) || /Recursive merging with submodules/i.test(output);
40825
+ if (!isSubmoduleConflict) throw mergeTreeErr;
40826
+ const evaluation = evaluateGitlinkTrivialFastForward(repoRoot, baseHead, branchHead);
40827
+ if (!evaluation.trivial) {
40828
+ return {
40829
+ status: "failed",
40830
+ equivalent: false,
40831
+ baseHead,
40832
+ branchHead,
40833
+ mergeBase: mergeBase || void 0,
40834
+ durationMs: Date.now() - startedAt,
40835
+ error: mergeTreeErr?.message || String(mergeTreeErr),
40836
+ stdout: truncateValidationOutput(mergeTreeErr?.stdout),
40837
+ stderr: truncateValidationOutput(mergeTreeErr?.stderr),
40838
+ gitlinkTrivialFastForward: { resolved: false, gitlinks: evaluation.gitlinks, reason: evaluation.reason },
40839
+ actionableHint: buildPatchEquivalenceSubmoduleConflictHint(repoRoot, baseHead, branchHead, output)
40840
+ };
40841
+ }
40842
+ mergedTree = synthesizeTrivialFastForwardMergeTree(repoRoot, baseHead, branchHead, evaluation.gitlinks) || "";
40843
+ gitlinkTrivialFastForward = { resolved: true, gitlinks: evaluation.gitlinks };
40844
+ }
40423
40845
  if (!mergeBase || !mergedTree) {
40424
40846
  return {
40425
40847
  status: "failed",
@@ -40430,7 +40852,8 @@ async function runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead)
40430
40852
  mergedTree: mergedTree || void 0,
40431
40853
  durationMs: Date.now() - startedAt,
40432
40854
  error: "patch equivalence preflight could not resolve merge-base or synthetic merge tree",
40433
- stdout: truncateValidationOutput(mergeTreeStdout)
40855
+ stdout: truncateValidationOutput(mergeTreeStdout),
40856
+ gitlinkTrivialFastForward
40434
40857
  };
40435
40858
  }
40436
40859
  const expectedPatchId = await computeGitPatchId(repoRoot, mergeBase, branchHead);
@@ -40445,7 +40868,8 @@ async function runMeshRefinePatchEquivalenceGate(repoRoot, baseHead, branchHead)
40445
40868
  mergedTree,
40446
40869
  expectedPatchId,
40447
40870
  actualPatchId,
40448
- durationMs: Date.now() - startedAt
40871
+ durationMs: Date.now() - startedAt,
40872
+ gitlinkTrivialFastForward
40449
40873
  };
40450
40874
  } catch (e) {
40451
40875
  return {
@@ -40468,6 +40892,65 @@ ${e?.stderr || ""}`
40468
40892
  };
40469
40893
  }
40470
40894
  }
40895
+ async function runMeshRefineEffectiveDiffGate(repoRoot, baseHead, branchHead) {
40896
+ const startedAt = Date.now();
40897
+ try {
40898
+ const { execFileSync: execFileSync6 } = await import("child_process");
40899
+ const git = (args, opts) => execFileSync6("git", args, {
40900
+ cwd: opts?.cwd || repoRoot,
40901
+ encoding: "utf8",
40902
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
40903
+ });
40904
+ const rawDiff = git(["diff", "--raw", baseHead, branchHead]).trim();
40905
+ if (rawDiff) {
40906
+ const changedPaths = rawDiff.split("\n").map((line) => line.split(" ").slice(1).join(" ").trim()).filter(Boolean).slice(0, 50);
40907
+ return {
40908
+ status: "passed",
40909
+ hasEffectiveDiff: true,
40910
+ baseHead,
40911
+ branchHead,
40912
+ changedPaths,
40913
+ durationMs: Date.now() - startedAt
40914
+ };
40915
+ }
40916
+ const submoduleHints = [];
40917
+ try {
40918
+ const status = git(["submodule", "status"]);
40919
+ for (const line of status.split("\n")) {
40920
+ const trimmed = line.trimEnd();
40921
+ if (!trimmed) continue;
40922
+ if (trimmed.startsWith("+")) {
40923
+ const parts = trimmed.slice(1).trim().split(/\s+/);
40924
+ const path39 = parts[1] || parts[0] || "(unknown)";
40925
+ submoduleHints.push({
40926
+ path: path39,
40927
+ reason: "submodule checked-out commit differs from the committed gitlink (pointer bump not committed on the root branch)"
40928
+ });
40929
+ }
40930
+ }
40931
+ } catch {
40932
+ }
40933
+ return {
40934
+ status: "failed",
40935
+ hasEffectiveDiff: false,
40936
+ baseHead,
40937
+ branchHead,
40938
+ ...submoduleHints.length ? { submoduleHints } : {},
40939
+ durationMs: Date.now() - startedAt
40940
+ };
40941
+ } catch (e) {
40942
+ return {
40943
+ status: "skipped",
40944
+ hasEffectiveDiff: true,
40945
+ baseHead,
40946
+ branchHead,
40947
+ durationMs: Date.now() - startedAt,
40948
+ error: e?.message || String(e),
40949
+ stdout: truncateValidationOutput(e?.stdout),
40950
+ stderr: truncateValidationOutput(e?.stderr)
40951
+ };
40952
+ }
40953
+ }
40471
40954
  function buildPatchEquivalenceSubmoduleConflictHint(repoRoot, baseHead, branchHead, output) {
40472
40955
  if (!/(submodule|160000)/i.test(output) || !/(conflict|failed to merge)/i.test(output)) return void 0;
40473
40956
  const conflicts = readChangedGitlinkPaths(repoRoot, baseHead, branchHead).map((path39) => ({
@@ -40524,6 +41007,135 @@ function readTreeObject(repoRoot, ref, path39) {
40524
41007
  return void 0;
40525
41008
  }
40526
41009
  }
41010
+ function resolveGitDir(repoRoot) {
41011
+ const out = execFileSync5("git", ["rev-parse", "--absolute-git-dir"], {
41012
+ cwd: repoRoot,
41013
+ encoding: "utf8",
41014
+ maxBuffer: 1024 * 1024
41015
+ }).trim();
41016
+ return out;
41017
+ }
41018
+ function isSubmoduleFastForward(submoduleRepoPath, baseCommit, branchCommit) {
41019
+ if (!baseCommit || !branchCommit) return false;
41020
+ if (baseCommit === branchCommit) return true;
41021
+ try {
41022
+ if (!fs23.existsSync(submoduleRepoPath)) return false;
41023
+ execFileSync5("git", ["cat-file", "-e", `${baseCommit}^{commit}`], { cwd: submoduleRepoPath, stdio: "ignore" });
41024
+ execFileSync5("git", ["cat-file", "-e", `${branchCommit}^{commit}`], { cwd: submoduleRepoPath, stdio: "ignore" });
41025
+ execFileSync5("git", ["merge-base", "--is-ancestor", baseCommit, branchCommit], { cwd: submoduleRepoPath, stdio: "ignore" });
41026
+ return true;
41027
+ } catch {
41028
+ return false;
41029
+ }
41030
+ }
41031
+ function readChangedPathKinds(repoRoot, fromRef, toRef) {
41032
+ try {
41033
+ const output = execFileSync5("git", ["diff", "--raw", "--no-abbrev", fromRef, toRef], {
41034
+ cwd: repoRoot,
41035
+ encoding: "utf8",
41036
+ maxBuffer: REFINE_PATCH_EQUIVALENCE_OUTPUT_LIMIT_BYTES
41037
+ });
41038
+ const result = [];
41039
+ const seen = /* @__PURE__ */ new Set();
41040
+ for (const line of output.split("\n")) {
41041
+ if (!line.trim()) continue;
41042
+ const metaAndPath = line.split(" ");
41043
+ const meta = metaAndPath[0] || "";
41044
+ const path39 = metaAndPath[metaAndPath.length - 1]?.trim();
41045
+ if (!path39 || seen.has(path39)) continue;
41046
+ seen.add(path39);
41047
+ const parts = meta.split(/\s+/);
41048
+ const isGitlink = !!(parts[0]?.includes("160000") || parts[1]?.includes("160000"));
41049
+ result.push({ path: path39, isGitlink });
41050
+ }
41051
+ return result;
41052
+ } catch {
41053
+ return [];
41054
+ }
41055
+ }
41056
+ function evaluateGitlinkTrivialFastForward(repoRoot, baseHead, branchHead) {
41057
+ const changedGitlinks = readChangedGitlinkPaths(repoRoot, baseHead, branchHead).map((path39) => {
41058
+ const baseCommit = readTreeObject(repoRoot, baseHead, path39);
41059
+ const branchCommit = readTreeObject(repoRoot, branchHead, path39);
41060
+ const submoduleRepoPath = pathResolve2(repoRoot, path39);
41061
+ const fastForward = !!baseCommit && !!branchCommit && isSubmoduleFastForward(submoduleRepoPath, baseCommit, branchCommit);
41062
+ return { path: path39, baseCommit, branchCommit, fastForward };
41063
+ });
41064
+ if (changedGitlinks.length === 0) {
41065
+ return { trivial: false, reason: "no_changed_gitlinks", gitlinks: changedGitlinks };
41066
+ }
41067
+ const nonFastForward = changedGitlinks.filter((entry) => !entry.fastForward);
41068
+ if (nonFastForward.length > 0) {
41069
+ return {
41070
+ trivial: false,
41071
+ reason: `diverged_gitlinks:${nonFastForward.map((entry) => entry.path).join(",")}`,
41072
+ gitlinks: changedGitlinks
41073
+ };
41074
+ }
41075
+ let mergeBase = "";
41076
+ try {
41077
+ mergeBase = execFileSync5("git", ["merge-base", baseHead, branchHead], {
41078
+ cwd: repoRoot,
41079
+ encoding: "utf8",
41080
+ maxBuffer: 1024 * 1024
41081
+ }).trim();
41082
+ } catch {
41083
+ return { trivial: false, reason: "merge_base_unresolved", gitlinks: changedGitlinks };
41084
+ }
41085
+ if (!mergeBase) {
41086
+ return { trivial: false, reason: "merge_base_unresolved", gitlinks: changedGitlinks };
41087
+ }
41088
+ const baseSideChanges = readChangedPathKinds(repoRoot, mergeBase, baseHead);
41089
+ const branchSideChanges = readChangedPathKinds(repoRoot, mergeBase, branchHead);
41090
+ const baseChangedPaths = new Map(baseSideChanges.map((entry) => [entry.path, entry]));
41091
+ const overlapping = branchSideChanges.filter((entry) => baseChangedPaths.has(entry.path));
41092
+ const nonGitlinkOverlap = overlapping.filter((entry) => {
41093
+ const baseEntry = baseChangedPaths.get(entry.path);
41094
+ return !(entry.isGitlink && baseEntry?.isGitlink);
41095
+ });
41096
+ if (nonGitlinkOverlap.length > 0) {
41097
+ return {
41098
+ trivial: false,
41099
+ reason: `non_gitlink_overlap:${nonGitlinkOverlap.map((entry) => entry.path).join(",")}`,
41100
+ gitlinks: changedGitlinks
41101
+ };
41102
+ }
41103
+ return { trivial: true, gitlinks: changedGitlinks };
41104
+ }
41105
+ function synthesizeTrivialFastForwardMergeTree(repoRoot, baseHead, branchHead, gitlinks) {
41106
+ try {
41107
+ const baseTree = execFileSync5("git", ["rev-parse", `${baseHead}^{tree}`], {
41108
+ cwd: repoRoot,
41109
+ encoding: "utf8",
41110
+ maxBuffer: 1024 * 1024
41111
+ }).trim();
41112
+ if (!baseTree) return void 0;
41113
+ const updates = gitlinks.filter((entry) => entry.branchCommit).map((entry) => `160000 commit ${entry.branchCommit} ${entry.path}`).join("\n");
41114
+ if (!updates) return baseTree;
41115
+ const tmpIndex = pathJoin(resolveGitDir(repoRoot), `adhdev-refine-ff-${baseHead.slice(0, 12)}-${branchHead.slice(0, 12)}.index`);
41116
+ const env = { ...process.env, GIT_INDEX_FILE: tmpIndex };
41117
+ try {
41118
+ execFileSync5("git", ["read-tree", baseTree], { cwd: repoRoot, env, stdio: "ignore" });
41119
+ execFileSync5("git", ["update-index", "--index-info"], {
41120
+ cwd: repoRoot,
41121
+ env,
41122
+ input: `${updates}
41123
+ `,
41124
+ encoding: "utf8",
41125
+ stdio: ["pipe", "ignore", "ignore"]
41126
+ });
41127
+ const newTree = execFileSync5("git", ["write-tree"], { cwd: repoRoot, env, encoding: "utf8" }).trim();
41128
+ return newTree || void 0;
41129
+ } finally {
41130
+ try {
41131
+ fs23.rmSync(tmpIndex, { force: true });
41132
+ } catch {
41133
+ }
41134
+ }
41135
+ } catch {
41136
+ return void 0;
41137
+ }
41138
+ }
40527
41139
  async function alignRefinerySubmodulesAfterMerge(repoRoot, previousBaseHead, currentHead, options = {}) {
40528
41140
  const startedAt = Date.now();
40529
41141
  const changedGitlinkPaths = readChangedGitlinkPaths(repoRoot, previousBaseHead, currentHead).filter((path39) => !(options.submoduleIgnorePaths || []).includes(path39));
@@ -41191,6 +41803,10 @@ var DaemonCommandRouter = class {
41191
41803
  runningRefineJobs = /* @__PURE__ */ new Map();
41192
41804
  /** Terminal async Refinery jobs preserve a clear answer after the worktree node has been removed. */
41193
41805
  terminalRefineJobs = /* @__PURE__ */ new Map();
41806
+ /** In-memory async batch Refinery jobs keyed by meshId (one batch convergence per mesh at a time). */
41807
+ runningRefineBatchJobs = /* @__PURE__ */ new Map();
41808
+ /** Terminal async batch Refinery jobs preserve the last batch outcome for late readers. */
41809
+ terminalRefineBatchJobs = /* @__PURE__ */ new Map();
41194
41810
  constructor(deps) {
41195
41811
  this.deps = deps;
41196
41812
  }
@@ -41268,13 +41884,13 @@ var DaemonCommandRouter = class {
41268
41884
  };
41269
41885
  }
41270
41886
  getCachedAggregateMeshStatus(meshId, mesh, options) {
41271
- const cached = this.aggregateMeshStatusCache.get(meshId);
41272
- if (!cached?.snapshot || cached.snapshot.success !== true || !Array.isArray(cached.snapshot.nodes)) return null;
41273
- if (cached.queueRevision !== getMeshQueueRevision(meshId)) return null;
41274
- let snapshot = this.cloneJsonValue(cached.snapshot);
41887
+ const cached2 = this.aggregateMeshStatusCache.get(meshId);
41888
+ if (!cached2?.snapshot || cached2.snapshot.success !== true || !Array.isArray(cached2.snapshot.nodes)) return null;
41889
+ if (cached2.queueRevision !== getMeshQueueRevision(meshId)) return null;
41890
+ let snapshot = this.cloneJsonValue(cached2.snapshot);
41275
41891
  snapshot = this.hydrateCachedAggregateMeshStatusFromInline(snapshot, mesh, options);
41276
41892
  if (shouldRefreshStalePendingAggregate(snapshot, options)) return null;
41277
- const ageMs = Math.max(0, Date.now() - cached.builtAt);
41893
+ const ageMs = Math.max(0, Date.now() - cached2.builtAt);
41278
41894
  const sourceOfTruth = snapshot.sourceOfTruth && typeof snapshot.sourceOfTruth === "object" ? snapshot.sourceOfTruth : {};
41279
41895
  snapshot.sourceOfTruth = {
41280
41896
  ...sourceOfTruth,
@@ -41285,7 +41901,7 @@ var DaemonCommandRouter = class {
41285
41901
  source: "memory",
41286
41902
  refreshReason: "memory_cache_hit",
41287
41903
  ageMs,
41288
- cachedAt: new Date(cached.builtAt).toISOString(),
41904
+ cachedAt: new Date(cached2.builtAt).toISOString(),
41289
41905
  returnedAt: (/* @__PURE__ */ new Date()).toISOString()
41290
41906
  }
41291
41907
  };
@@ -41329,9 +41945,9 @@ var DaemonCommandRouter = class {
41329
41945
  warmInlineMeshCache(meshId, inlineMesh) {
41330
41946
  if (!inlineMesh || typeof inlineMesh !== "object") return void 0;
41331
41947
  const sanitizedInlineMesh = sanitizeInlineMesh(inlineMesh);
41332
- const cached = this.inlineMeshCache.get(meshId);
41333
- if (cached) {
41334
- const merged = reconcileInlineMeshCache(cached, sanitizedInlineMesh);
41948
+ const cached2 = this.inlineMeshCache.get(meshId);
41949
+ if (cached2) {
41950
+ const merged = reconcileInlineMeshCache(cached2, sanitizedInlineMesh);
41335
41951
  this.inlineMeshCache.set(meshId, merged);
41336
41952
  return merged;
41337
41953
  }
@@ -41341,14 +41957,14 @@ var DaemonCommandRouter = class {
41341
41957
  async getMeshForCommand(meshId, inlineMesh, options) {
41342
41958
  const preferInline = options?.preferInline === true;
41343
41959
  if (preferInline) {
41344
- const cached2 = this.getCachedInlineMesh(meshId);
41345
- if (cached2) {
41960
+ const cached3 = this.getCachedInlineMesh(meshId);
41961
+ if (cached3) {
41346
41962
  if (inlineMeshCarriesTransientNodeTruth(inlineMesh)) {
41347
- const merged = reconcileInlineMeshCache(cached2, inlineMesh);
41963
+ const merged = reconcileInlineMeshCache(cached3, inlineMesh);
41348
41964
  this.inlineMeshCache.set(meshId, sanitizeInlineMesh(merged));
41349
41965
  return { mesh: merged, inline: true, source: "inline_cache" };
41350
41966
  }
41351
- return { mesh: cached2, inline: true, source: "inline_cache" };
41967
+ return { mesh: cached3, inline: true, source: "inline_cache" };
41352
41968
  }
41353
41969
  if (inlineMeshCarriesTransientNodeTruth(inlineMesh)) {
41354
41970
  this.warmInlineMeshCache(meshId, inlineMesh);
@@ -41361,8 +41977,8 @@ var DaemonCommandRouter = class {
41361
41977
  if (mesh) return { mesh, inline: false, source: "local_config" };
41362
41978
  } catch {
41363
41979
  }
41364
- const cached = this.getCachedInlineMesh(meshId);
41365
- if (cached) return { mesh: cached, inline: true, source: "inline_cache" };
41980
+ const cached2 = this.getCachedInlineMesh(meshId);
41981
+ if (cached2) return { mesh: cached2, inline: true, source: "inline_cache" };
41366
41982
  const warmedInline = this.warmInlineMeshCache(meshId, inlineMesh);
41367
41983
  return warmedInline ? { mesh: warmedInline, inline: true, source: "inline_bootstrap" } : null;
41368
41984
  }
@@ -41669,6 +42285,8 @@ var DaemonCommandRouter = class {
41669
42285
  const skippedSessionIds = [];
41670
42286
  const skippedLiveSessionIds = [];
41671
42287
  const skippedCoordinatorSessionIds = [];
42288
+ const skippedLiveSessionReasons = [];
42289
+ const actedLiveDelegateSessionIds = [];
41672
42290
  const deleteUnsupportedSessionIds = [];
41673
42291
  const recordsRemainSessionIds = [];
41674
42292
  const errors = [];
@@ -41702,16 +42320,31 @@ var DaemonCommandRouter = class {
41702
42320
  const surfaceKind = getSessionHostSurfaceKind(record);
41703
42321
  const liveRuntime = surfaceKind === "live_runtime";
41704
42322
  const coordinatorSession = readStringValue(record?.meta?.meshCoordinatorFor) === args.meshId;
42323
+ const recordNodeId = readStringValue(record?.meta?.meshNodeId);
42324
+ const recordMeshNodeFor = readStringValue(record?.meta?.meshNodeFor);
42325
+ const delegateBoundToThisNode = !!recordNodeId && recordNodeId === args.nodeId && (!recordMeshNodeFor || recordMeshNodeFor === args.meshId);
41705
42326
  if (!hasExplicitSessionIds && coordinatorSession) {
41706
42327
  skippedSessionIds.push(sessionId);
41707
42328
  skippedCoordinatorSessionIds.push(sessionId);
41708
42329
  continue;
41709
42330
  }
41710
- if (!hasExplicitSessionIds && liveRuntime) {
42331
+ if (!hasExplicitSessionIds && liveRuntime && !delegateBoundToThisNode) {
42332
+ skippedSessionIds.push(sessionId);
42333
+ skippedLiveSessionIds.push(sessionId);
42334
+ const matchedByWorkspaceOnly = !recordNodeId;
42335
+ 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";
42336
+ skippedLiveSessionReasons.push({ sessionId, reason });
42337
+ continue;
42338
+ }
42339
+ if (!hasExplicitSessionIds && liveRuntime && delegateBoundToThisNode && args.mode === "delete_stopped") {
41711
42340
  skippedSessionIds.push(sessionId);
41712
42341
  skippedLiveSessionIds.push(sessionId);
42342
+ skippedLiveSessionReasons.push({ sessionId, reason: "live_delegate_preserved_by_delete_stopped_mode_use_stop_or_stop_and_delete" });
41713
42343
  continue;
41714
42344
  }
42345
+ if (!hasExplicitSessionIds && liveRuntime && delegateBoundToThisNode) {
42346
+ actedLiveDelegateSessionIds.push(sessionId);
42347
+ }
41715
42348
  try {
41716
42349
  if (args.mode === "stop") {
41717
42350
  if (!completed) {
@@ -41773,6 +42406,8 @@ var DaemonCommandRouter = class {
41773
42406
  skippedSessionIds,
41774
42407
  skippedLiveSessionIds,
41775
42408
  skippedCoordinatorSessionIds,
42409
+ ...actedLiveDelegateSessionIds.length ? { actedLiveDelegateSessionIds } : {},
42410
+ ...skippedLiveSessionReasons.length ? { skippedLiveSessionReasons } : {},
41776
42411
  ...deleteUnsupported ? {
41777
42412
  deleteUnsupported: true,
41778
42413
  effectiveCleanup: args.mode === "stop_and_delete" ? "stopped_only_records_remain" : "delete_unsupported_records_remain",
@@ -42420,6 +43055,48 @@ ${tail}` : ""
42420
43055
  }
42421
43056
  };
42422
43057
  }
43058
+ const effectiveDiffStarted = Date.now();
43059
+ const effectiveDiff = await runMeshRefineEffectiveDiffGate(repoRoot, baseHead, branchHead);
43060
+ recordMeshRefineStage(refineStages, "effective_diff", effectiveDiff.status, effectiveDiffStarted, {
43061
+ hasEffectiveDiff: effectiveDiff.hasEffectiveDiff,
43062
+ changedPaths: effectiveDiff.changedPaths,
43063
+ submoduleHints: effectiveDiff.submoduleHints,
43064
+ ...effectiveDiff.error ? { error: effectiveDiff.error } : {}
43065
+ });
43066
+ if (effectiveDiff.status === "failed" && !effectiveDiff.hasEffectiveDiff) {
43067
+ const hintLines = (effectiveDiff.submoduleHints || []).map((h) => ` - ${h.path}: ${h.reason}`);
43068
+ const message = [
43069
+ `Refinery no-op guard: branch '${branch}' has no effective root-tree diff against '${baseBranch}' (${baseHead.slice(0, 12)}); nothing would merge.`,
43070
+ "This usually means a submodule (e.g. oss) has commits but the root branch never committed the gitlink (pointer) bump, so the merge would be a silent no-op while the real change never reaches main.",
43071
+ hintLines.length ? `Submodules with uncommitted pointer bumps:
43072
+ ${hintLines.join("\n")}` : "",
43073
+ `Fix: commit the submodule pointer bump on '${branch}' (git add <submodule-path> && git commit), then re-run refine.`
43074
+ ].filter(Boolean).join("\n");
43075
+ return {
43076
+ success: false,
43077
+ code: "no_effective_diff",
43078
+ convergenceStatus: "blocked_review",
43079
+ error: message,
43080
+ branch,
43081
+ into: baseBranch,
43082
+ validationSummary,
43083
+ patchEquivalence,
43084
+ effectiveDiff,
43085
+ refineStages,
43086
+ finalBranchConvergenceState: {
43087
+ branch,
43088
+ baseBranch,
43089
+ merged: false,
43090
+ removed: false,
43091
+ validation: "passed",
43092
+ patchEquivalence: "passed",
43093
+ effectiveDiff: "no_effective_diff",
43094
+ status: "blocked_review",
43095
+ reason: "no_effective_diff",
43096
+ ...effectiveDiff.submoduleHints?.length ? { submoduleHints: effectiveDiff.submoduleHints } : {}
43097
+ }
43098
+ };
43099
+ }
42423
43100
  let mergeResult;
42424
43101
  const mergeStarted = Date.now();
42425
43102
  try {
@@ -42658,8 +43335,8 @@ ${tail}` : ""
42658
43335
  const repoRootBaseRef = /* @__PURE__ */ new Map();
42659
43336
  const submodulePathsByRepoRoot = /* @__PURE__ */ new Map();
42660
43337
  const resolveBaseRef = async (repoRoot) => {
42661
- const cached = repoRootBaseRef.get(repoRoot);
42662
- if (cached) return cached;
43338
+ const cached2 = repoRootBaseRef.get(repoRoot);
43339
+ if (cached2) return cached2;
42663
43340
  let baseBranch = "main";
42664
43341
  try {
42665
43342
  const { stdout } = await execFileAsync4("git", ["branch", "--show-current"], { cwd: repoRoot, encoding: "utf8" });
@@ -42761,6 +43438,17 @@ ${tail}` : ""
42761
43438
  note: "Dry-run: no validation, rebase, or merge was executed. Re-run with execute=true to converge nodes in this order."
42762
43439
  };
42763
43440
  }
43441
+ return this.runMeshRefineBatchConvergence(meshId, orderedNodes, ordering, args);
43442
+ }
43443
+ /**
43444
+ * Convergence core shared by the synchronous batch entry and the async batch job.
43445
+ * Refines each node in order: the per-node refine pipeline fetches origin/<base>
43446
+ * fresh, so each merged sibling advances the base before the next node's auto-rebase
43447
+ * + patch-equivalence re-check. A blocked/failed node is isolated; the batch
43448
+ * continues with the remaining nodes. Does NOT touch the per-node merge logic — it
43449
+ * only sequences calls to executeMeshRefineNodeSynchronously and aggregates outcomes.
43450
+ */
43451
+ async runMeshRefineBatchConvergence(meshId, orderedNodes, ordering, args) {
42764
43452
  const results = [];
42765
43453
  for (const node of orderedNodes) {
42766
43454
  let result;
@@ -42815,6 +43503,204 @@ ${tail}` : ""
42815
43503
  }
42816
43504
  };
42817
43505
  }
43506
+ buildRefineBatchJobKey(meshId) {
43507
+ return `${meshId}::batch`;
43508
+ }
43509
+ buildRefineBatchJobHandle(args) {
43510
+ return {
43511
+ success: true,
43512
+ async: true,
43513
+ batch: true,
43514
+ status: args.status || "accepted",
43515
+ jobId: args.jobId || `refine_batch_${createInteractionId()}`,
43516
+ interactionId: args.interactionId || createInteractionId(),
43517
+ meshId: args.meshId,
43518
+ batchLabel: `batch:${args.nodeIds.length} node${args.nodeIds.length === 1 ? "" : "s"}`,
43519
+ nodeIds: args.nodeIds,
43520
+ nodeCount: args.nodeIds.length,
43521
+ order: args.order,
43522
+ startedAt: args.startedAt || (/* @__PURE__ */ new Date()).toISOString(),
43523
+ ...args.completedAt ? { completedAt: args.completedAt } : {},
43524
+ ...args.coordinatorDaemonId ? { targetCoordinatorDaemonId: args.coordinatorDaemonId } : {},
43525
+ eventDelivery: { pendingEvents: true, ledger: true },
43526
+ evidence: {
43527
+ pendingEventsCommand: "get_pending_mesh_events",
43528
+ ledgerCommand: "get_mesh_ledger_slice",
43529
+ taskHistoryKind: args.status === "completed" ? "task_completed" : args.status === "failed" ? "task_failed" : "task_dispatched"
43530
+ }
43531
+ };
43532
+ }
43533
+ /**
43534
+ * Emit a batch Refinery terminal/accepted event through the SAME pending-event +
43535
+ * forward mechanism single-node refine uses (queueRefineJobEvent), so the
43536
+ * coordinator's existing refine:accepted/completed/failed handling and message
43537
+ * renderer apply unchanged. The aggregate per-node results ride along in `result`.
43538
+ */
43539
+ queueRefineBatchJobEvent(event, handle, result) {
43540
+ const metadataEvent = {
43541
+ source: "refine_mesh_node_async_job",
43542
+ batch: true,
43543
+ jobId: handle.jobId,
43544
+ interactionId: handle.interactionId,
43545
+ meshId: handle.meshId,
43546
+ nodeId: handle.batchLabel,
43547
+ nodeIds: handle.nodeIds,
43548
+ workspace: void 0,
43549
+ status: handle.status,
43550
+ startedAt: handle.startedAt,
43551
+ completedAt: handle.completedAt,
43552
+ order: handle.order,
43553
+ ...result ? { result } : {}
43554
+ };
43555
+ const eventPayload = {
43556
+ event,
43557
+ meshId: handle.meshId,
43558
+ nodeLabel: handle.batchLabel,
43559
+ nodeId: handle.batchLabel,
43560
+ metadataEvent,
43561
+ queuedAt: Date.now(),
43562
+ ...handle.targetCoordinatorDaemonId ? { targetCoordinatorDaemonId: handle.targetCoordinatorDaemonId } : {}
43563
+ };
43564
+ if (typeof this.deps.instanceManager?.getByCategory === "function") {
43565
+ const forwarded = handleMeshForwardEvent(
43566
+ { instanceManager: this.deps.instanceManager },
43567
+ {
43568
+ event,
43569
+ meshId: handle.meshId,
43570
+ nodeId: handle.batchLabel,
43571
+ jobId: handle.jobId,
43572
+ interactionId: handle.interactionId,
43573
+ status: handle.status,
43574
+ startedAt: handle.startedAt,
43575
+ completedAt: handle.completedAt,
43576
+ ...result ? { result } : {}
43577
+ }
43578
+ );
43579
+ if (forwarded?.success === true) return;
43580
+ LOG.warn("Mesh", `[Refinery] Failed to forward async refine batch event ${event}: ${forwarded?.error || "unknown error"}`);
43581
+ }
43582
+ queuePendingMeshCoordinatorEvent(eventPayload);
43583
+ }
43584
+ async appendRefineBatchJobLedger(kind, handle, result) {
43585
+ try {
43586
+ const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
43587
+ appendLedgerEntry2(handle.meshId, {
43588
+ kind,
43589
+ nodeId: handle.batchLabel,
43590
+ payload: {
43591
+ source: "refine_mesh_node_async_job",
43592
+ refineJob: {
43593
+ batch: true,
43594
+ jobId: handle.jobId,
43595
+ interactionId: handle.interactionId,
43596
+ status: handle.status,
43597
+ meshId: handle.meshId,
43598
+ nodeIds: handle.nodeIds,
43599
+ order: handle.order,
43600
+ targetCoordinatorDaemonId: handle.targetCoordinatorDaemonId,
43601
+ startedAt: handle.startedAt,
43602
+ completedAt: handle.completedAt
43603
+ },
43604
+ async: true,
43605
+ batch: true,
43606
+ ...result ? {
43607
+ success: result.success === true,
43608
+ result
43609
+ } : {}
43610
+ }
43611
+ });
43612
+ } catch (e) {
43613
+ LOG.warn("Mesh", `[Refinery] Failed to append async refine batch ledger entry: ${e?.message || e}`);
43614
+ }
43615
+ }
43616
+ async finishMeshRefineBatchJob(handle, orderedNodes, ordering, args) {
43617
+ const key = this.buildRefineBatchJobKey(handle.meshId);
43618
+ let result;
43619
+ try {
43620
+ result = await this.runMeshRefineBatchConvergence(handle.meshId, orderedNodes, ordering, args);
43621
+ } catch (e) {
43622
+ result = { success: false, error: e?.message || String(e), batch: true };
43623
+ }
43624
+ const completedAt = (/* @__PURE__ */ new Date()).toISOString();
43625
+ const summary = result.summary && typeof result.summary === "object" ? result.summary : void 0;
43626
+ const allConverged = result.allConverged === true;
43627
+ const isTerminalSuccess = result.success === true && allConverged;
43628
+ const nextStep = typeof result.nextStep === "string" && result.nextStep ? result.nextStep : isTerminalSuccess ? "All batched nodes converged onto base. Continue from the updated mesh state." : "Resolve blocked_review / not_mergeable nodes (see per-node code/stage/error in result.results), then re-run mesh_refine_batch for the remaining nodes.";
43629
+ const normalizedResult = {
43630
+ ...result,
43631
+ batch: true,
43632
+ nextStep,
43633
+ ...summary ? {
43634
+ convergenceStatus: allConverged ? "all_converged" : "partial"
43635
+ } : {}
43636
+ };
43637
+ const terminalHandle = this.buildRefineBatchJobHandle({
43638
+ meshId: handle.meshId,
43639
+ nodeIds: handle.nodeIds,
43640
+ order: handle.order,
43641
+ status: isTerminalSuccess ? "completed" : "failed",
43642
+ startedAt: handle.startedAt,
43643
+ completedAt,
43644
+ jobId: handle.jobId,
43645
+ interactionId: handle.interactionId,
43646
+ coordinatorDaemonId: handle.targetCoordinatorDaemonId
43647
+ });
43648
+ const terminal = { ...terminalHandle, result: normalizedResult };
43649
+ this.terminalRefineBatchJobs.set(key, terminal);
43650
+ this.runningRefineBatchJobs.delete(key);
43651
+ this.invalidateAggregateMeshStatus(handle.meshId);
43652
+ await this.appendRefineBatchJobLedger(isTerminalSuccess ? "task_completed" : "task_failed", terminalHandle, normalizedResult);
43653
+ this.queueRefineBatchJobEvent(isTerminalSuccess ? "refine:completed" : "refine:failed", terminalHandle, normalizedResult);
43654
+ }
43655
+ /**
43656
+ * Async entry for the batch Refinery execute path. Mirrors startMeshRefineJob:
43657
+ * resolves the plan synchronously (so target/ordering errors and the dry-run shape
43658
+ * stay synchronous), then for execute=true registers an in-flight batch job, returns
43659
+ * {async:true, status:'accepted', batch:true, ...plan} immediately, and runs the
43660
+ * convergence loop in the background — emitting the same terminal refine event.
43661
+ * Idempotent: a batch already in flight for this mesh returns the running handle
43662
+ * with duplicate:true rather than spawning a second background job.
43663
+ */
43664
+ async startMeshRefineBatchJob(meshId, requestedNodeIds, args) {
43665
+ const plan = await this.batchRefineMeshNodes(meshId, requestedNodeIds, { ...args, dryRun: true, execute: false });
43666
+ const planRecord = plan;
43667
+ if (planRecord.success !== true) return plan;
43668
+ if (args?.dryRun === true && args?.execute !== true) return plan;
43669
+ const order = Array.isArray(planRecord.order) ? planRecord.order.filter((v) => typeof v === "string") : [];
43670
+ const nodeIds = order.slice();
43671
+ if (nodeIds.length === 0) {
43672
+ return { ...planRecord, success: true, batch: true, dryRun: false, async: false };
43673
+ }
43674
+ const key = this.buildRefineBatchJobKey(meshId);
43675
+ const running = this.runningRefineBatchJobs.get(key);
43676
+ if (running) return { ...running, duplicate: true };
43677
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
43678
+ const mesh = meshRecord?.mesh;
43679
+ const allNodes = Array.isArray(mesh?.nodes) ? mesh.nodes : [];
43680
+ const orderedNodes = nodeIds.map((id) => allNodes.find((n) => n.id === id || n.nodeId === id)).filter((n) => !!n);
43681
+ if (orderedNodes.length === 0) {
43682
+ return { success: false, error: "Batch nodes no longer resolvable in mesh", batch: true };
43683
+ }
43684
+ const ordering = {
43685
+ order,
43686
+ rationale: planRecord.orderingRationale
43687
+ };
43688
+ const coordinatorDaemonId = typeof args?.coordinatorDaemonId === "string" && args.coordinatorDaemonId.trim() ? args.coordinatorDaemonId.trim() : this.deps.statusInstanceId || void 0;
43689
+ const handle = this.buildRefineBatchJobHandle({ meshId, nodeIds, order, coordinatorDaemonId });
43690
+ this.runningRefineBatchJobs.set(key, handle);
43691
+ await this.appendRefineBatchJobLedger("task_dispatched", handle);
43692
+ this.queueRefineBatchJobEvent("refine:accepted", handle);
43693
+ setImmediate(() => {
43694
+ void this.finishMeshRefineBatchJob(handle, orderedNodes, ordering, args);
43695
+ });
43696
+ return {
43697
+ ...handle,
43698
+ order,
43699
+ orderingRationale: planRecord.orderingRationale,
43700
+ plan: planRecord.plan,
43701
+ note: "Batch convergence accepted and running in the background. Completion/failure (with per-node results) will be delivered as a terminal refine event; do not poll repeatedly."
43702
+ };
43703
+ }
42818
43704
  async finishMeshRefineJob(handle, args) {
42819
43705
  const key = this.buildRefineJobKey(handle.meshId, handle.targetNodeId);
42820
43706
  let result;
@@ -43348,7 +44234,7 @@ ${tail}` : ""
43348
44234
  version: this.deps.statusVersion || "unknown",
43349
44235
  profile: "metadata"
43350
44236
  });
43351
- return { success: true, status: snapshot };
44237
+ return { success: true, status: snapshot, daemonBuild: getDaemonBuildInfo() };
43352
44238
  }
43353
44239
  case "get_machine_runtime_stats": {
43354
44240
  return {
@@ -44318,6 +45204,7 @@ ${tail}` : ""
44318
45204
  let workspace = typeof args?.workspace === "string" ? args.workspace.trim() : "";
44319
45205
  let submoduleIgnorePaths = Array.isArray(args?.submoduleIgnorePaths) ? args.submoduleIgnorePaths.filter((value) => typeof value === "string") : void 0;
44320
45206
  let nodeDaemonId;
45207
+ let allowAutoPublishSubmoduleMainCommits = false;
44321
45208
  if (meshId && nodeId) {
44322
45209
  const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
44323
45210
  const mesh = meshRecord?.mesh;
@@ -44328,6 +45215,7 @@ ${tail}` : ""
44328
45215
  if (!submoduleIgnorePaths && Array.isArray(node?.policy?.submoduleIgnorePaths)) {
44329
45216
  submoduleIgnorePaths = node.policy.submoduleIgnorePaths.filter((value) => typeof value === "string");
44330
45217
  }
45218
+ allowAutoPublishSubmoduleMainCommits = mesh?.policy?.allowAutoPublishSubmoduleMainCommits === true;
44331
45219
  nodeDaemonId = typeof node?.daemonId === "string" ? node.daemonId.trim() : void 0;
44332
45220
  }
44333
45221
  const selfDaemonId = this.deps.statusInstanceId;
@@ -44348,7 +45236,10 @@ ${tail}` : ""
44348
45236
  execute: args?.execute === true,
44349
45237
  dryRun: args?.dryRun === true,
44350
45238
  updateSubmodules: args?.updateSubmodules === true,
44351
- submoduleIgnorePaths
45239
+ submoduleIgnorePaths,
45240
+ mode: args?.mode === "push" ? "push" : "merge",
45241
+ pushSubmodules: args?.pushSubmodules === true,
45242
+ allowAutoPublishSubmoduleMainCommits
44352
45243
  });
44353
45244
  return result;
44354
45245
  }
@@ -44362,7 +45253,9 @@ ${tail}` : ""
44362
45253
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
44363
45254
  if (!meshId) return { success: false, error: "meshId required" };
44364
45255
  const requestedNodeIds = Array.isArray(args?.nodeIds) ? args.nodeIds.filter((v) => typeof v === "string" && v.trim().length > 0).map((v) => v.trim()) : void 0;
44365
- return this.batchRefineMeshNodes(meshId, requestedNodeIds, args);
45256
+ const isDryRun = args?.dryRun !== false && args?.execute !== true;
45257
+ if (isDryRun) return this.batchRefineMeshNodes(meshId, requestedNodeIds, args);
45258
+ return this.startMeshRefineBatchJob(meshId, requestedNodeIds, args);
44366
45259
  }
44367
45260
  case "remove_mesh_node": {
44368
45261
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
@@ -46046,6 +46939,7 @@ var DaemonStatusReporter = class {
46046
46939
  };
46047
46940
 
46048
46941
  // src/index.ts
46942
+ init_build_info();
46049
46943
  init_logger();
46050
46944
  init_debug_config();
46051
46945
 
@@ -54323,6 +55217,7 @@ export {
54323
55217
  MIN_SESSION_HOST_DIAGNOSTICS_SUBSCRIPTION_INTERVAL_MS,
54324
55218
  NodePtyTransportFactory,
54325
55219
  P2pRelayFailureError,
55220
+ PRUNABLE_ORPHAN_STALE_REASONS,
54326
55221
  ProviderCliAdapter,
54327
55222
  ProviderInstanceManager,
54328
55223
  ProviderLoader,
@@ -54376,6 +55271,7 @@ export {
54376
55271
  classifyChatMessageVisibility,
54377
55272
  classifyHotChatSessionsForSubscriptionFlush,
54378
55273
  classifyP2pRelayFailure,
55274
+ classifyStaleDirectForPrune,
54379
55275
  cleanupTerminalDirectDispatches,
54380
55276
  clearDebugTrace,
54381
55277
  clearPendingMeshCoordinatorEvents,
@@ -54395,6 +55291,7 @@ export {
54395
55291
  createNativeHistoryDispatcher,
54396
55292
  createSessionDelivery,
54397
55293
  createWorktree,
55294
+ deleteDirectDispatchesByTaskId,
54398
55295
  deleteMesh,
54399
55296
  deriveMeshReviewInboxItems,
54400
55297
  describeTaskDependencyState,
@@ -54423,6 +55320,7 @@ export {
54423
55320
  getAvailableIdeIds,
54424
55321
  getCoordinatorForSession,
54425
55322
  getCurrentDaemonLogPath,
55323
+ getDaemonBuildInfo,
54426
55324
  getDaemonDataDir,
54427
55325
  getDaemonLogDir,
54428
55326
  getDebugRuntimeConfig,